{- The Eden Trace Viewer (or simply EdenTV) is a tool that can generate diagrams to visualize the behaviour of Eden programs. Copyright (C) 2005-2010 Phillips Universitaet Marburg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -} module Main (main) where import Graphics.UI.Gtk hiding (deleteEvent, destroyEvent, eventButton) import Graphics.UI.Gtk.Gdk.Events import Graphics.UI.Gtk.Windows.MessageDialog import Graphics.UI.Gtk.Glade import Graphics.UI.Gtk.General.StockItems import Graphics.Rendering.Cairo hiding (version) import Control.Concurrent import Control.Monad.Fix import Control.Monad import EdenTvType import EdenTvBasic import EdenTvInteract import EdenTvViewer import System.Environment import System.Exit import System.Directory import System.FilePath.Posix import System.Directory import System.CPUTime import System.IO import Data.List import RTSEventsParser import Debug.Trace import Paths_edentv( -- version, getDataFileName ) -- this file is generated by cabal import qualified Paths_edentv import Data.Version(versionBranch) -- to extract version from there -- unsafe CAF to detect the file name once and return it as a -- constant immediately for every call later: -- cafGladeFile = unsafePerformIO $ do -- let filename = "edentv.glade" -- change only here if desired -- gladefile <- getDataFileName filename -- inDataDir <- doesFileExist gladefile -- if inDataDir -- then return gladefile -- else return filename -- default: local -- Datadir, usually "share/edentv-" holds the glade file. -- Load the glade file from install path or local, specify root and domain -- (XML is allegedly cached internally, but check is always repeated) edentvGladeFile :: Maybe String -> Maybe String -> IO (Maybe GladeXML) edentvGladeFile root domain = let filename = "edentv.glade" -- change only here if desired in do gladefile <- getDataFileName filename inDataDir <- doesFileExist gladefile inLocalDir <- doesFileExist filename case (inDataDir, inLocalDir) of (True, _) -> xmlNewWithRootAndDomain gladefile root domain (False, True) -> xmlNewWithRootAndDomain filename root domain (False, False) -> return Nothing version,subversion :: String -- version = "4.0" version = show (head ( versionBranch Paths_edentv.version )) subversion = "0" regpath = "/apps/unimar/edentv/lastPath" -- some general GTK-Events: deleteEvent :: Event -> IO Bool deleteEvent _ = do return False destroyEvent, quitEvent, closeEvent :: IO () destroyEvent = do mainQuit quitEvent = do mainQuit closeEvent = do return () data WindowsState = WS { windowsMenus :: [Menu] } type WidgetCollection = (Menu, MenuItem, MenuItem, MenuItem, MVar WindowsState) -- Wins, menuOpen, menuOpenWithoutMsgs, menuExit, state -- openEvent: open a new Tracefile openEvent :: Window -> WidgetCollection -> MVar EdenTvState -> Bool -> IO () openEvent parent ws edentvState ignoreMessages = do --Select Tracefile to load fileSelect <- fileChooserDialogNew (Just "Select Tracefile") (Just parent) FileChooserActionOpen [("OK",ResponseOk),("Cancel",ResponseCancel)] globalState <- readMVar edentvState let oldPath = lastPath globalState if null oldPath then return True else fileChooserSetCurrentFolder fileSelect oldPath widgetShow fileSelect response <- dialogRun fileSelect case response of -- which Button was pressed? ResponseOk -> do Just fileName <- fileChooserGetFilename fileSelect path <- fileChooserGetCurrentFolder fileSelect case path of Just filePath -> swapMVar edentvState (globalState { lastPath = filePath }) Nothing -> return globalState widgetDestroy fileSelect -- display dialog with progress indicator tryToOpen fileName ws ignoreMessages False edentvState _ -> do putMVar edentvState (globalState { lastPath = oldPath }) widgetDestroy fileSelect -- any other response tryToOpen :: String -> WidgetCollection -> Bool -> Bool -> MVar EdenTvState -> IO () tryToOpen filename ws ignoreMessages showMessages edentvState = do dlg <- dialogNew windowSetTitle dlg "parsing file..." button <- dialogAddButton dlg stockCancel ResponseCancel upper <- dialogGetUpper dlg pg <- progressBarNew boxPackStartDefaults upper pg widgetShowAll dlg -- animate the progressbar threadProgress <- forkIO (showProgress pg) hID <- idleAdd (yield >> return True) priorityDefaultIdle -- start concurrent process to read tracefile threadTrace <- forkIO (openTracefile filename ws dlg ignoreMessages showMessages edentvState) -- Cancel-event! onClicked button (opCancel threadTrace dlg) dialogRun dlg -- wating for end of parsing or user interrupt killThread threadProgress idleRemove hID widgetDestroy dlg where showProgress :: ProgressBar -> IO () showProgress pb = do progressBarPulse pb threadDelay 200000 showProgress pb opCancel :: ThreadId -> Dialog -> IO () opCancel thread dialog = do killThread thread dialogResponse dialog ResponseOk hLines :: Show a => Handle -> [a] -> IO () hLines h (x:xs) = do hPutStrLn h (show x) hLines h xs hLines _ _ = return () -- open a new Trace-Window and display the Trace openTracefile :: String -> WidgetCollection -> Dialog -> Bool -> Bool -> MVar EdenTvState -> IO () openTracefile filename widgets@(winList,mainOpen, mainOpenWithoutMsgs, exitApp, winMenus) dialog ignoreMessages showMessages edentvState = do putStrLn (filename ++ ":") time <- getCPUTime traces' <- (traceRTSFile filename ignoreMessages) case traces' of Failed msg -> do dialogResponse dialog ResponseOk errorMessage ("Tracefile seems not to be valid:\n" ++ msg) Ok traces -> do -- build window and render traces: let ((m,p,t),mt,(maxStartupTimeInSeconds, _), (msgs,amsgs,hmsgs,pTree,rcvtimes),(minT,maxT,maxST,_,maxD),(nm,allP,nt)) = traces np = length p strRunT = formatFloat (maxT - minT) allMsgs = sum (map (\ (_,_,_,d) -> d) hmsgs) + length msgs time' <- getCPUTime let timeString = ("\n Traces computed in " ++ show (fromIntegral (time' - time) / 1000000000000) ++ " seconds.") putStrLn timeString -- load XML-description from gladefile maybeGladeXML <- edentvGladeFile (Just "EdTVTrace") Nothing let gladeXML = case maybeGladeXML of (Just glade) -> glade Nothing -> error "missing file: \"edentv.glade\"!" -- get access to the widgets from gladefile window <- xmlGetWidget gladeXML castToWindow "EdTVTrace" pic <- xmlGetWidget gladeXML castToDrawingArea "drawingarea" win <- widgetGetDrawWindow pic viewport <- xmlGetWidget gladeXML castToViewport "viewport" scrldWnd <- xmlGetWidget gladeXML castToScrolledWindow "scrolledwindow" buttonLocalNull <- xmlGetWidget gladeXML castToToggleToolButton "localNull" buttonMessages <- xmlGetWidget gladeXML castToToggleToolButton "messages" buttonInfo <- xmlGetWidget gladeXML castToToolButton "infoButton" buttonConfMsgs <- xmlGetWidget gladeXML castToToolButton "msgs-relations" traceZoomInH <- xmlGetWidget gladeXML castToToolButton "zoomInH" traceZoomInV <- xmlGetWidget gladeXML castToToolButton "zoomInV" traceZoomIn <- xmlGetWidget gladeXML castToMenuItem "zoom_in" traceZoomOutH <- xmlGetWidget gladeXML castToToolButton "zoomOutH" traceZoomOutV <- xmlGetWidget gladeXML castToToolButton "zoomOutV" traceZoomOut <- xmlGetWidget gladeXML castToMenuItem "zoom_out" moveUp <- xmlGetWidget gladeXML castToToolButton "go_up" moveDown <- xmlGetWidget gladeXML castToToolButton "go_down" viewselect <- xmlGetWidget gladeXML castToComboBox "viewselect" buttonToPng <- xmlGetWidget gladeXML castToToolButton "saveAsPng" winm <- xmlGetWidget gladeXML castToMenu "windows_menu" menuClose <- xmlGetWidget gladeXML castToMenuItem "close" menuQuit <- xmlGetWidget gladeXML castToMenuItem "tquit" menuOpen <- xmlGetWidget gladeXML castToMenuItem "topen" menuOpenWithoutMsgs <- xmlGetWidget gladeXML castToMenuItem "topenWithoutMsgs" menuSave <- xmlGetWidget gladeXML castToMenuItem "save" menuAllM <- xmlGetWidget gladeXML castToRadioMenuItem "showAllMachines" menuAllP <- xmlGetWidget gladeXML castToRadioMenuItem "showAllProcesses" menuAllGP <- xmlGetWidget gladeXML castToRadioMenuItem "showAllGProcesses" menuAllT <- xmlGetWidget gladeXML castToRadioMenuItem "showAllThreads" menuSort <- xmlGetWidget gladeXML castToMenuItem "sort" menuShowRange <- xmlGetWidget gladeXML castToMenuItem "edit_range" menuEditTicks <- xmlGetWidget gladeXML castToMenuItem "edit_ticks" menuTotal <- xmlGetWidget gladeXML castToMenuItem "totalView" menuMarker <- xmlGetWidget gladeXML castToCheckMenuItem "show_marker" menuStartupMarker <- xmlGetWidget gladeXML castToCheckMenuItem "show_startup" menuHideStartupPhase <- xmlGetWidget gladeXML castToCheckMenuItem "hide_startup_phase" menuBlockMsgs <- xmlGetWidget gladeXML castToCheckMenuItem "show_blockmsgs" menuDataMsgs <- xmlGetWidget gladeXML castToCheckMenuItem "show_data_messages" menuSystemMsgs <- xmlGetWidget gladeXML castToCheckMenuItem "show_system_messages" menuRefresh <- xmlGetWidget gladeXML castToMenuItem "refresh" menuAbout <- xmlGetWidget gladeXML castToMenuItem "tabout" -- add labels for tool-items so that a label is displayed in the toolbar overflow menu addMenuLabelToToolItem gladeXML "toolitemViewSelect" "Select View" addMenuLabelToToolItem gladeXML "toolitemSort" "Sort" addMenuLabelToToolItem gladeXML "toolitemZoomH" "Zoom Horizontal" addMenuLabelToToolItem gladeXML "toolitemZoomV" "Zoom Vertical" -- the statusbar: statusbar <- xmlGetWidget gladeXML castToStatusbar "traceStatusBar" let statusString = " Runtime: " ++ strRunT ++ "s, " ++ show nm ++ " Machines, " ++ show allP ++ " Processes, " ++ show nt ++ " Threads, " ++ show (length msgs + (length hmsgs)) ++ " Conversations, " ++ show allMsgs ++ " Messages" botID <- statusbarGetContextId statusbar statusString statusbarPush statusbar botID statusString -- initial settings comboBoxSetActive viewselect 0 -- default to view machines let listOfMachineIds = map (\(mId,_,_,_,_) -> mId) m listOfProcessIds = map (\(pId,_,_,_,_) -> pId) p st <- newMVar (initMatrix nm (length p) nt listOfMachineIds listOfProcessIds filename ignoreMessages) -- filename in titlebar let winTitle = "Trace " ++ (takeFileName filename) windowSetTitle window winTitle -- create entry for windows-menu in main window (and now all other open windows too) ws <- takeMVar winMenus -- update all menus which are found in ws to display new tracefile item sequence_ $ map (\winList -> do newListItem <- menuItemNewWithLabel winTitle submenu <- menuNew showItem <- menuItemNewWithLabel "Show" refreshItem <- menuItemNewWithLabel "Refresh" menuShellAppend winList newListItem menuShellAppend submenu showItem menuShellAppend submenu refreshItem menuItemSetSubmenu newListItem submenu widgetShow newListItem onActivateLeaf showItem (windowPresent window) onActivateLeaf refreshItem (widgetQueueDraw pic) widgetShowAll newListItem onDestroy window (widgetDestroy newListItem) onDestroy window (do oldWS <- takeMVar winMenus let oldM = windowsMenus oldWS --putStrLn "removing old menu from update list" --putStrLn (show $ length oldM) let removeSelf (x:xs) | x == winm = xs | otherwise = x : (removeSelf xs) removeSelf [] = [] newM = removeSelf oldM --putStrLn (show $ length newM) putMVar winMenus (oldWS{windowsMenus=newM}) )) (windowsMenus ws) -- get list of windows and build a windows menu from ground up -- for this window activeWins <- windowListToplevels sequence_ $ map (\window -> do title <- (windowGetTitle window) if isPrefixOf "Trace" title -- only show if window is a tracefile window then do newListItem <- menuItemNewWithLabel title submenu <- menuNew showItem <- menuItemNewWithLabel "Show" refreshItem <- menuItemNewWithLabel "Refresh" menuShellAppend winm newListItem menuShellAppend submenu showItem menuShellAppend submenu refreshItem menuItemSetSubmenu newListItem submenu widgetShow newListItem onActivateLeaf showItem (windowPresent window) onActivateLeaf refreshItem (widgetQueueDraw pic) widgetShowAll newListItem onDestroy window (widgetDestroy newListItem) return () else return ()) activeWins -- register this windows menu for future updates putMVar winMenus (ws{windowsMenus=(winm:(windowsMenus ws))}) -- option buttons: -- zoom in/out, horizontal/vertical -- when zooming out also refresh the drawing area to get rid of the old drawings onToolButtonClicked traceZoomInH (zoom pic (*2) (id) viewport) onToolButtonClicked traceZoomInV (zoom pic (id) (*2) viewport) onToolButtonClicked traceZoomOutH (do zoom pic (`div` 2) (id) viewport; widgetQueueDraw pic) onToolButtonClicked traceZoomOutV (do zoom pic (id) (`div` 2) viewport; widgetQueueDraw pic) onActivateLeaf traceZoomIn (zoom pic (*2) (*2) viewport) onActivateLeaf traceZoomOut (do zoom pic (`div` 2) (`div` 2) viewport; widgetQueueDraw pic) -- mouse zoom event onScroll pic (\ event -> do let scrollDirection = eventDirection event when (scrollDirection == ScrollUp || scrollDirection == ScrollDown) $ do -- mouse position on the drawing area let mousePosX = eventX event let mousePosY = eventY event let zoomFactor | scrollDirection == ScrollUp = (*2) | otherwise = (`div` 2) zoomAt mousePosX mousePosY pic zoomFactor viewport widgetQueueDraw pic return (True)) -- toggle local/global zero => refresh picture onToolButtonToggled buttonLocalNull (widgetQueueDraw pic) -- close the "parsing file"-dialog dialogResponse dialog ResponseOk -- events from the menu -- close window, not program onActivateLeaf menuClose (widgetDestroy window) -- close window AND program onActivateLeaf menuQuit (do widgetActivate exitApp return ()) -- open new tracefile, use controls of main window onActivateLeaf menuOpen (do widgetActivate mainOpen return ()) -- open new tracefile without msgs, use controls of main window onActivateLeaf menuOpenWithoutMsgs (do widgetActivate mainOpenWithoutMsgs return ()) -- redraw onActivateLeaf menuBlockMsgs (widgetQueueDraw pic) onActivateLeaf menuDataMsgs (widgetQueueDraw pic) onActivateLeaf menuSystemMsgs (widgetQueueDraw pic) onActivateLeaf menuHideStartupPhase (widgetQueueDraw pic) -- save image to file (menu entry) onActivateLeaf menuSave (do windowPresent window v <- comboBoxGetActive viewselect drawStartupMarker <- checkMenuItemGetActive menuStartupMarker hideStartupPhase <- checkMenuItemGetActive menuHideStartupPhase drawBlockMsgs <- checkMenuItemGetActive menuBlockMsgs drawDataMsgs <- checkMenuItemGetActive menuDataMsgs drawSystemMsgs <- checkMenuItemGetActive menuSystemMsgs saveToFile drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs traces (nt,np,nm) v st edentvState pic) -- sort current view onActivateLeaf menuSort ( do state <- takeMVar st -- which view has to be sorted? v <- comboBoxGetActive viewselect case v of -- set the right list to default: 3 -> putMVar st (state { matrixGP = [1..(fromIntegral nm)] }) 2 -> putMVar st (state { matrixT = [1..(fromIntegral nt)] }) 1 -> putMVar st (state { matrixP = [1..(fromIntegral np)] }) 0 -> putMVar st (state { matrixM = [1..(fromIntegral nm)] }) widgetQueueDraw pic) -- refresh picture onActivateLeaf menuShowRange ( do -- show specific range (Just gladeXML) <- edentvGladeFile (Just "range_win") Nothing rangeDlg <- xmlGetWidget gladeXML castToWindow "range_win" spinBegin <- xmlGetWidget gladeXML castToSpinButton "range_begin" spinEnd <- xmlGetWidget gladeXML castToSpinButton "range_end" buttonShow <- xmlGetWidget gladeXML castToButton "show_intervall" viewRange <- viewportGetHAdjustment viewport viewLower <- adjustmentGetLower viewRange viewUpper <- adjustmentGetUpper viewRange viewInit <- adjustmentGetValue viewRange viewStep <- adjustmentGetStepIncrement viewRange --putStrLn ("posX init: " ++ (show viewInit)) state <- readMVar st let useDiff = locTime state ((vx,vy,vw,vh),(ulx,uly,lrx,lry)) <- getCorners pic hideStartupPhase <- checkMenuItemGetActive menuHideStartupPhase let minT' | hideStartupPhase = maxStartupTimeInSeconds | otherwise = minT let getTimeFromPosition x | useDiff = posToTime minT' (maxT+maxST) ulx lrx x | otherwise = posToTime minT' maxT ulx lrx x let timeBeginInit = getTimeFromPosition (viewInit+ulx) timeBeginLower | hideStartupPhase = 0 | otherwise = minT' timeBeginUpper | hideStartupPhase = maxT - minT' | otherwise = maxT timeEndInit = getTimeFromPosition (viewInit+(fromIntegral vw)) timeEndLower = timeBeginLower timeEndUpper = timeBeginUpper timeStep = getTimeFromPosition viewStep beginAdjustment <- adjustmentNew timeBeginInit timeBeginLower timeBeginUpper timeStep 0 0 endAdjustment <- adjustmentNew timeEndInit timeEndLower timeEndUpper timeStep 0 0 spinButtonConfigure spinBegin beginAdjustment timeStep 5 spinButtonConfigure spinEnd endAdjustment timeStep 5 onClicked buttonShow (do --putStrLn "Ok?" timeX <- spinButtonGetValue spinBegin timeX2 <- spinButtonGetValue spinEnd --let scale_total = (maxT-minT) let scale = (((fromIntegral vw) - ulx) / (timeX2 - timeX)) * (maxT - minT') (w,h) <- widgetGetSize pic widgetSetSizeRequest pic (minimum [round scale, 32000]) (h) let newlrx = (realToFrac (round scale)) - 20 let getPositionFromTime x | useDiff = timeToPos minT' (maxT+maxST) ulx newlrx x | otherwise = timeToPos minT' maxT ulx newlrx x let posX = (getPositionFromTime timeX) - ulx -- At the moment the drawing area might no have changed -- its size yet. So if we try to set the adjustment value now, -- the adjustment might not be changed because the new value is not -- between lowerValue and upperValue. -- So instead we set an event, which is called when the drawing area -- has changed its size, so that we can safely scroll to the right -- position. viewAdj <- viewportGetHAdjustment viewport mfix $ \ handlerId -> afterAdjChanged viewAdj (do adjustmentSetValue viewAdj posX -- remove the event again signalDisconnect handlerId) return () ) windowSetTitle rangeDlg "Show Range" windowSetTransientFor rangeDlg window widgetShowAll rangeDlg ) onActivateLeaf menuEditTicks ( do -- show ticks setup (Just gladeXML) <- edentvGladeFile (Just "ticks_win") Nothing ticksDlg <- xmlGetWidget gladeXML castToWindow "ticks_win" ticksSkip <- xmlGetWidget gladeXML castToSpinButton "ticks_skip" ticksMark <- xmlGetWidget gladeXML castToSpinButton "ticks_mark" buttonSet <- xmlGetWidget gladeXML castToButton "set_ticks" buttonAuto <- xmlGetWidget gladeXML castToCheckButton "auto_ticks" state <- readMVar st let useDiff = locTime state auto = autoTicks state tSkip = tickSkip state tMark = tickMark state maxSkip | useDiff = maxT + maxST | otherwise = maxT skipAdjustment <- adjustmentNew tSkip 0.00001 maxSkip 0.1 0 0 markAdjustment <- adjustmentNew (fromIntegral tMark) 1 32000 1 0 0 toggleButtonSetActive buttonAuto auto spinButtonConfigure ticksSkip skipAdjustment 0.1 5 spinButtonConfigure ticksMark markAdjustment 1 0 onClicked buttonSet (do --putStrLn "Ok?" valSkip <- spinButtonGetValue ticksSkip valMark <- spinButtonGetValue ticksMark valAuto <- toggleButtonGetActive buttonAuto state <- takeMVar st putMVar st (state { autoTicks=valAuto, tickSkip=valSkip, tickMark=floor valMark }) widgetQueueDraw pic ) windowSetTitle ticksDlg "Edit Ticks" windowSetTransientFor ticksDlg window widgetShowAll ticksDlg ) onActivateLeaf menuTotal (do -- zoom picture to the size of this window visibleRegion <- drawableGetVisibleRegion win Rectangle _ _ w h <- regionGetClipbox visibleRegion -- get dimensions widgetSetSizeRequest pic (minimum [w, 32000]) (minimum [h, 32000])) onActivateLeaf menuRefresh (widgetQueueDraw pic) -- refresh picture (remove artefacts) onActivateLeaf menuAbout showAboutDlg -- show program information onActivateLeaf menuStartupMarker (widgetQueueDraw pic) -- Actions/Events from Toolbar onExpose pic (\_ -> do v <- comboBoxGetActive viewselect drawStartupMarker <- checkMenuItemGetActive menuStartupMarker hideStartupPhase <- checkMenuItemGetActive menuHideStartupPhase drawBlockMsgs <- checkMenuItemGetActive menuBlockMsgs drawDataMsgs <- checkMenuItemGetActive menuDataMsgs drawSystemMsgs <- checkMenuItemGetActive menuSystemMsgs case v of 3 -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs 0 0 (renderWithDrawable win) (drawGroupProcesses) pic traces st edentvState nm 2 -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs 0 0 (renderWithDrawable win) (drawThreads) pic traces st edentvState nt 1 -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs 0 0 (renderWithDrawable win) (drawProcesses) pic traces st edentvState np _ -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs 0 0 (renderWithDrawable win) (drawMachines) pic traces st edentvState nm return False) -- when the viewport slides over the image, the axes have to be redrawn: hAdj <- viewportGetHAdjustment viewport onValueChanged hAdj ( do ((vx,vy,vw,vh),(ulx,uly,lrx,lry)) <- getCorners pic drawWindowInvalidateRect win (Rectangle vx vy 100 vh) True) vAdj <- viewportGetVAdjustment viewport onValueChanged vAdj ( do ((vx,vy,vw,vh),(ulx,uly,lrx,lry)) <- getCorners pic let bot = 100 drawWindowInvalidateRect win (Rectangle vx (vy+vh-bot) vw bot) True) onToolButtonClicked buttonToPng (do -- save image to file (toolbutton) widgetActivate menuSave return ()) onActivateLeaf menuAllM ( do -- switch to machine view v <- comboBoxGetActive viewselect setTo <- checkMenuItemGetActive menuAllM if setTo && v /= 0 then comboBoxSetActive viewselect 0 else return () ) onActivateLeaf menuAllP ( do -- switch to process view v <- comboBoxGetActive viewselect setTo <- checkMenuItemGetActive menuAllP if setTo && v /= 1 then comboBoxSetActive viewselect 1 else return () ) onActivateLeaf menuAllGP ( do -- switch to process view v <- comboBoxGetActive viewselect setTo <- checkMenuItemGetActive menuAllGP if setTo && v /= 3 then comboBoxSetActive viewselect 3 else return () ) onActivateLeaf menuAllT ( do -- switch to thread view v <- comboBoxGetActive viewselect setTo <- checkMenuItemGetActive menuAllT if setTo && v /= 2 then comboBoxSetActive viewselect 2 else return () ) on viewselect changed (do state <- takeMVar st v <- comboBoxGetActive viewselect -- get the view to switch to -- update the menu items: case v of 3 -> checkMenuItemSetActive menuAllGP True 2 -> checkMenuItemSetActive menuAllT True 1 -> checkMenuItemSetActive menuAllP True 0 -> checkMenuItemSetActive menuAllM True -- save the view settings and refresh image: putMVar st (state { selRow = [], selView = v }) widgetQueueDraw pic) onMotionNotify pic False (\e -> do -- the mouse has moved above the image: active <- checkMenuItemGetActive menuMarker let mx = eventX e my = eventY e if active -- then marker has to be redrawn then do hideStartupPhase <- checkMenuItemGetActive menuHideStartupPhase handleMouseMove st edentvState hideStartupPhase pic traces mx my else return () state <- readMVar st if (clicked state) then handleDragnDrop st pic traces mx my else return () return False) onButtonPress pic (\e -> do -- a mousebutton was pressed state <- takeMVar st win <- widgetGetDrawWindow pic ((vx,vy,vw,vh),(ulx,uly,lrx,lry)) <- getCorners pic let mx = eventX e my = eventY e activeClick = (mx > ulx && my > uly && mx < lrx && my < lry) pixbuf <- pixbufGetFromDrawable win (Rectangle 0 0 50 vh) putMVar st (state { clicked = activeClick, oldView = pixbuf }) v <- comboBoxGetActive viewselect handleButtonPress pic v st e traces) onButtonRelease pic (\e -> do -- a mousebutton was released state <- takeMVar st ((vx,vy,vw,vh),(ulx,uly,lrx,lry)) <- getCorners pic let mx = eventX e my = eventY e didClick = clicked state putMVar st (state { clicked = False }) v <- comboBoxGetActive viewselect if (mx > ulx && my > uly && mx < lrx && my < lry) && (didClick) then performDragnDrop v st pic traces mx my else return () widgetQueueDraw pic return False ) onToolButtonClicked buttonInfo ( do -- show traceinformation (Just gladeXML) <- edentvGladeFile (Just "info_win") Nothing infoDlg <- xmlGetWidget gladeXML castToWindow "info_win" windowSetTitle infoDlg ("Info - " ++ filename) windowSetTransientFor infoDlg window showTraceInfo traces filename (statusString {- ++ "\n(" ++ show allMsgs ++ " Messages overall; " ++ show allP ++ " Processes overall)"-}) gladeXML widgetShowAll infoDlg) onToolButtonClicked buttonConfMsgs ( do -- show configure Messages Window (Just gladeXML) <- edentvGladeFile (Just "confmsgs_win") Nothing confDlg <- xmlGetWidget gladeXML castToWindow "confmsgs_win" v <- comboBoxGetActive viewselect buildConfMsg st gladeXML pic v windowSetTitle confDlg "Configure Messages" windowSetTransientFor confDlg window widgetShowAll confDlg ) onToolButtonClicked buttonLocalNull (do -- toggle local/global zero state <- takeMVar st localNull <- toggleToolButtonGetActive buttonLocalNull putMVar st (state { selRow = [], locTime = localNull }) widgetQueueDraw pic) onToolButtonClicked buttonMessages (do -- show/hide Messages messagesOn <- toggleToolButtonGetActive buttonMessages state <- takeMVar st if (EdenTvType.ignoreMessages state) then do -- the trace file was read without parsing messages, -- so the file has to be read again putMVar st state if messagesOn then do -- ask the user if the file should be re-parsed reparseDialog <- messageDialogNew (Just window) [DialogModal] MessageQuestion ButtonsOkCancel ("The current trace file has been opened without parsing the messages. " ++ "Should the file be read again and should the messages be displayed?") response <- dialogRun reparseDialog widgetDestroy reparseDialog case response of ResponseOk -> -- load file and also show messages tryToOpen (EdenTvType.filename state) widgets False True edentvState _ -> return() else return () -- also disable the button again, because no messages are displayed -- in this window toggleToolButtonSetActive buttonMessages False else do -- messages were parsed while reading the trace file, -- so just show them putMVar st (state { selRow = [], showMsg = messagesOn }) widgetQueueDraw pic) -- change theposition of selected line onToolButtonClicked moveUp ( do v <- comboBoxGetActive viewselect handleMoveUp pic v st) onToolButtonClicked moveDown (do v <- comboBoxGetActive viewselect handleMoveDown pic v st) -- Show Trace-Window widgetShowAll window widgetQueueDraw pic -- traces drawn => all information consumed => file can be closed --hClose fileHdl -- show messages on first display? if showMessages then toggleToolButtonSetActive buttonMessages True else return() where -- Zoom in/out horizontally by keeping the current position -- zoomAt :: Double -> Double -> DrawingArea -> (Int -> Int) -> Viewport -> IO () zoomAt mousePosX mousePosYFlipped pic zoomFactor viewport = do (w,h) <- widgetGetSize pic adjustmentH <- viewportGetHAdjustment viewport adjustmentV <- viewportGetVAdjustment viewport -- point (0, 0) on the drawing area is in the lower left, but the -- coordinate system of the mouse position starts in upper left heightViewPort <- adjustmentGetPageSize adjustmentV let mousePosY = heightViewPort - mousePosYFlipped -- get the current position (relativePosXViewPort, relativePosXTotal) <- getRelativePositonAt mousePosX adjustmentH (relativePosYViewPort, relativePosYTotal) <- getRelativePositonAt mousePosY adjustmentV -- restore the position once the new size is set restorePosition (setPositionAt adjustmentH relativePosXViewPort relativePosXTotal) (setPositionAt adjustmentV relativePosYViewPort relativePosYTotal) adjustmentH adjustmentV widgetSetSizeRequest pic (minimum [zoomFactor w, 32000]) h where -- chart borders in pixels pageBorderLeft = 55 pageBorderRight = 20 pageBorder = pageBorderLeft + pageBorderRight -- Returns the relative position of the mouse pointer -- - relative to the viewable extent -- - relative to whole size (including the scrollable part) -- getRelativePositonAt :: Double -> Adjustment -> IO (Double, Double) getRelativePositonAt position adj = do min <- adjustmentGetLower adj max <- adjustmentGetUpper adj val <- adjustmentGetValue adj viewableSize <- adjustmentGetPageSize adj let totalSize = max - min return ((position - val - pageBorderLeft) / (viewableSize - pageBorder), (position - min - pageBorderLeft) / (totalSize - pageBorder)) setPositionAt :: Adjustment -> Double -> Double -> IO () setPositionAt adj relativePosViewPort relativePosTotal = do min <- adjustmentGetLower adj max <- adjustmentGetUpper adj viewableSize <- adjustmentGetPageSize adj let totalSize = max - min let zoomPoint = (totalSize - pageBorder) * relativePosTotal let position = zoomPoint - ((viewableSize - pageBorder) * relativePosViewPort) let newValue | position < min = min | position + viewableSize > max = max - viewableSize | otherwise = position adjustmentSetValue adj newValue zoom :: DrawingArea -> (Int -> Int) -> (Int -> Int) -> Viewport -> IO () zoom pic fX fY viewport = do (w,h) <- widgetGetSize pic -- get the current position adjustmentH <- viewportGetHAdjustment viewport adjustmentV <- viewportGetVAdjustment viewport relativePosX <- getRelativePositon adjustmentH relativePosY <- getRelativePositon adjustmentV -- restore the position once the new size is set restorePosition (setPosition adjustmentH relativePosX) (setPosition adjustmentV relativePosY) adjustmentH adjustmentV -- the resolution is restricted to 32K*32K, -- this is because DrawingArea immediately would allocate a backing store -- of 32k * 32k * (3 Bytes/pixel) memory and because x values are -- limited to short integer (-32768/+32767) widgetSetSizeRequest pic (minimum [fX w, 32000]) (minimum [fY h, 32000]) where -- Returns the relative position of the center point. -- getRelativePositon :: Adjustment -> IO (Double) getRelativePositon adj = do min <- adjustmentGetLower adj max <- adjustmentGetUpper adj val <- adjustmentGetValue adj pageSize <- adjustmentGetPageSize adj let center = val + (pageSize / 2) return ((center - min) / (max - min)) setPosition :: Adjustment -> Double -> IO () setPosition adj relativePos = do min <- adjustmentGetLower adj max <- adjustmentGetUpper adj pageSize <- adjustmentGetPageSize adj let center = min + (max - min) * relativePos let newValue | (center + (pageSize / 2)) >= max = max - pageSize | otherwise = (center - (pageSize / 2)) adjustmentSetValue adj newValue restorePosition :: (IO ()) -> (IO ()) -> Adjustment -> Adjustment -> IO () restorePosition setPositionH setPositionV adjustmentH adjustmentV = do mfix $ \ handlerId -> afterAdjChanged adjustmentH (do setPositionH signalDisconnect handlerId) mfix $ \ handlerId -> afterAdjChanged adjustmentV (do setPositionV signalDisconnect handlerId) return () -- export to Picture: -- Attention: parts of the window, that are off-screen, are not being exported! --saveToFile :: DrawColor -> Events -> (Int,Int,Int) -> Int -> ViewerState -> DrawingArea -> IO () saveToFile drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs traces (nt,np,nm) v st edentvState pic = do -- redraw window (refresh the image) widgetQueueDraw pic -- extract Image from Window: win <- widgetGetDrawWindow pic visibleRegion <- drawableGetVisibleRegion win visible <- regionGetClipbox visibleRegion let Rectangle visX visY visW visH = visible mPixbuf <- pixbufGetFromDrawable win visible -- ask for filename: selectFilename <- fileChooserDialogNew (Just "Save Picture") Nothing FileChooserActionSave [("_Save",ResponseOk),("Cancel",ResponseCancel)] dialogSetDefaultResponse selectFilename ResponseOk -- add filters for special filetypes: filterAll <- fileFilterNew filterAll `fileFilterAddPattern` "*.*" filterAll `fileFilterSetName` "all Files" filterImg <- fileFilterNew -- to select the filetype fileFilterAddPixbufFormats filterImg -- add imagetypes filterImg `fileFilterAddPattern` "*.pdf" filterImg `fileFilterSetName` "all known images" selectFilename `fileChooserAddFilter` filterAll -- add filters to dialog selectFilename `fileChooserAddFilter` filterImg filterPDF <- fileFilterNew filterPDF `fileFilterAddPattern` ("*.pdf") filterPDF `fileFilterSetName` "Portable Document Format (*.pdf)" selectFilename `fileChooserAddFilter` filterPDF addSpecialFilters selectFilename pixbufGetFormats -- show and run the dialog: widgetShow selectFilename response <- dialogRun selectFilename case response of ResponseCancel -> widgetDestroy selectFilename -- don't save the image ResponseOk -> do Just filename <- fileChooserGetFilename selectFilename let filetype = reverse (takeWhile (/= '.') (reverse filename)) -- extract the choosen filetype if (elem filetype ["bmp","jpg","jpeg","png", "pdf"]) -- filetype recognized? then do widgetDestroy selectFilename pathExists <- doesDirectoryExist filename fileExists <- doesFileExist filename doWrite <- if pathExists then do errorMessage (filename ++ " is a Directory.") return False else if fileExists then yesNoMessage (filename ++ " exists, overwrite?") else return True if doWrite then case mPixbuf of Nothing -> error "saveToFile: Window not visible" Just p -> do if (elem filetype ["bmp","jpg","jpeg","png"]) then pixbufSave p filename (if filetype == "jpg" then "jpeg" else filetype) [] else do case v of 3 -> withPDFSurface filename (fromIntegral visW) (fromIntegral visH) (\s -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs (-(fromIntegral visX)) (-(fromIntegral visY)) (renderWith s) (drawGroupProcesses) pic traces st edentvState nt ) 2 -> withPDFSurface filename (fromIntegral visW) (fromIntegral visH) (\s -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs (-(fromIntegral visX)) (-(fromIntegral visY)) (renderWith s) (drawThreads) pic traces st edentvState nt ) 1 -> withPDFSurface filename (fromIntegral visW) (fromIntegral visH) (\s -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs (-(fromIntegral visX)) (-(fromIntegral visY)) (renderWith s) (drawProcesses) pic traces st edentvState nt ) _ -> withPDFSurface filename (fromIntegral visW) (fromIntegral visH) (\s -> drawAnyPic drawBlockMsgs drawStartupMarker hideStartupPhase drawDataMsgs drawSystemMsgs (-(fromIntegral visX)) (-(fromIntegral visY)) (renderWith s) (drawMachines) pic traces st edentvState nt ) else return () else do -- unknown filetype widgetDestroy selectFilename maybeGladeXML <- edentvGladeFile (Just "dlg_err_save_filetype") Nothing let gladeXML = case maybeGladeXML of (Just glade) -> glade Nothing -> error "missing file: \"edentv.glade\"!" dlg <- xmlGetWidget gladeXML castToDialog "dlg_err_save_filetype" dlg `afterResponse` (\_ -> widgetDestroy dlg) widgetShowAll dlg -- allowed filetypes: png bmp wbmp gif ico ani jpeg pnm ras tiff xpm xbm tga -- supported: png, bmp, jpeg _ -> widgetDestroy selectFilename return () where addSpecialFilters _ [] = return () addSpecialFilters dlg (f:fs) = do filter <- fileFilterNew filter `fileFilterAddPattern` ("*." ++ f) case f of "pdf" -> do filter `fileFilterSetName` "Portable Document Format(*.pdf)" dlg `fileChooserAddFilter` filter "png" -> do filter `fileFilterSetName` "Portable Networks Graphics (*.png)" dlg `fileChooserAddFilter` filter "bmp" -> do filter `fileFilterSetName` "Windows Bitmap (*.bmp)" dlg `fileChooserAddFilter` filter "jpeg" -> do filter `fileFilterAddPattern` ("*.jpg") filter `fileFilterSetName` "Joint Photographic Experts Group (*.jpeg)" dlg `fileChooserAddFilter` filter _ -> return () addSpecialFilters dlg fs -- start GUI main :: IO () main = do putStrLn ("This is EdenTV v" ++ version ++ ". Happy tracing!") initGUI -- for GTK+ -- load Glade-description maybeGladeXML <- edentvGladeFile (Just "EdTVMain") Nothing -- TODO: Search in current directory and in directory of EdenTV-executable let gladeXML = case maybeGladeXML of (Just glade) -> glade Nothing -> error "missing file: \"edentv.glade\"!" -- load Widgets from Glade-description -- main window windowMain <- xmlGetWidget gladeXML castToWindow "EdTVMain" -- colorbuttons iCB <- xmlGetWidget gladeXML castToColorButton "idle_colorbutton" rCB <- xmlGetWidget gladeXML castToColorButton "running_colorbutton" sCB <- xmlGetWidget gladeXML castToColorButton "suspended_colorbutton" bCB <- xmlGetWidget gladeXML castToColorButton "blocked_colorbutton" -- other toolbar entries toolbarOpen <- xmlGetWidget gladeXML castToToolButton "toolbar_Open" -- menu entries menuFileOpen <- xmlGetWidget gladeXML castToMenuItem "menuFile_Open" menuFileOpenWithoutMsgs <- xmlGetWidget gladeXML castToMenuItem "menuFile_OpenWithoutMsgs" menuFileExit <- xmlGetWidget gladeXML castToMenuItem "menuFile_Quit" cCol <- xmlGetWidget gladeXML castToRadioMenuItem "default_colors" cBW <- xmlGetWidget gladeXML castToRadioMenuItem "default_colBW" menuOptsSetColors <- xmlGetWidget gladeXML castToMenuItem "menuOptions_SetColors" menuWindows <- xmlGetWidget gladeXML castToMenu "windows_menu" refreshWins <- xmlGetWidget gladeXML castToMenuItem "refresh_all" menuHelpAbout <- xmlGetWidget gladeXML castToMenuItem "menuHelp_About" -- menuSetTitle menuWindows "active Documents" -- initial state with default colors edentvState <- newMVar (ES { lastPath = "", colors = getDefaultColors }) -- Eventhandling: onDelete windowMain deleteEvent onDestroy windowMain destroyEvent onActivateLeaf menuFileExit quitEvent onToolButtonClicked toolbarOpen (do {widgetActivate menuFileOpen; return ()}) -- open tracefile winMenus <- newMVar WS {windowsMenus=[menuWindows]} -- open tracefile onActivateLeaf menuFileOpen (openEvent windowMain (menuWindows,menuFileOpen,menuFileOpenWithoutMsgs,menuFileExit, winMenus) edentvState False) -- open tracefile without parsing messages onActivateLeaf menuFileOpenWithoutMsgs (openEvent windowMain (menuWindows,menuFileOpen,menuFileOpenWithoutMsgs,menuFileExit, winMenus) edentvState True) -- handling colors let updateMainColorButtons colorMap = do updateColorButton iCB (statusIdle colorMap) updateColorButton rCB (statusRunning colorMap) updateColorButton sCB (statusSuspended colorMap) updateColorButton bCB (statusBlocked colorMap) let setTemplateMenuInconsistent = do checkMenuItemSetInconsistent cCol True checkMenuItemSetInconsistent cBW True onActivateLeaf menuOptsSetColors (showSetColorDialog edentvState menuWindows updateMainColorButtons setTemplateMenuInconsistent) onActivateLeaf cCol ( do setDefaultColors cCol edentvState menuWindows updateMainColorButtons checkMenuItemSetInconsistent cBW False) onActivateLeaf cBW ( do setDefaultBW cBW edentvState menuWindows updateMainColorButtons checkMenuItemSetInconsistent cCol False) onColorSet iCB (colorChanged iCB edentvState (\ color colors -> colors {statusIdle = color}) menuWindows setTemplateMenuInconsistent) onColorSet rCB (colorChanged rCB edentvState (\ color colors -> colors {statusRunning = color}) menuWindows setTemplateMenuInconsistent) onColorSet sCB (colorChanged sCB edentvState (\ color colors -> colors {statusSuspended = color}) menuWindows setTemplateMenuInconsistent) onColorSet bCB (colorChanged bCB edentvState (\ color colors -> colors {statusBlocked = color}) menuWindows setTemplateMenuInconsistent) -- a list of all opened windows onActivateLeaf refreshWins (refreshAll menuWindows) onActivateLeaf menuHelpAbout showAboutDlg -- not supported on CentOS!!! -- set default colors on start-up setDefaultColors cCol edentvState menuWindows updateMainColorButtons checkMenuItemSetActive cCol True -- load tracefile if filename is in commandline args <- getArgs if (null args) then return () else case (head args) of "--version" -> exitWith ExitSuccess "--help" -> do putStrLn " EdenTV just open main dialog of EdenTV" putStrLn " EdenTV run EdenTV and open " putStrLn " EdenTV --version print version and exit" putStrLn " EdenTV --help print this help and exit" exitWith ExitSuccess hArgs -> do cPath <- canonicalizePath hArgs let path = (reverse (tail (dropWhile (\c -> notElem c ['\\','/']) (reverse cPath)))) swapMVar edentvState (ES { lastPath = path, colors = getDefaultColors }) tryToOpen hArgs (menuWindows, menuFileOpen, menuFileOpenWithoutMsgs, menuFileExit, winMenus) False False edentvState -- for GTK+ widgetShowAll windowMain mainGUI where -- Called when one of the 4 colors in the main windows was changed -- colorChanged colorButton edentvState setter openTraceWindows setTemplateMenuInconsistent = do globalState <- readMVar edentvState let colorMap = colors globalState newColor <- getColorFromColorButton colorButton -- store new color in the state let colorMap' = setter newColor colorMap swapMVar edentvState (globalState {colors = colorMap' }) setTemplateMenuInconsistent refreshAll openTraceWindows setDefaultColors, setDefaultBW :: RadioMenuItem -> MVar EdenTvState -> Menu -> (Colors -> IO ()) -> IO () setDefaultColors cCol edentvState openTraceWindows updateMainColorButtons = do isActive <- get cCol checkMenuItemActive when isActive $ do setNewColors edentvState (getDefaultColors) openTraceWindows updateMainColorButtons checkMenuItemSetInconsistent cCol False setDefaultBW cBW edentvState openTraceWindows updateMainColorButtons = do isActive <- get cBW checkMenuItemActive when isActive $ do setNewColors edentvState (getDefaultColorsBW) openTraceWindows updateMainColorButtons checkMenuItemSetInconsistent cBW False -- This function takes care of displaying the dialog to customize all -- colors (not just the four shown in the main window). If the dialog is -- closed with `Ok`, the new colors are stored in the state and all open -- trace windows are refreshed. -- showSetColorDialog :: MVar EdenTvState -> Menu -> (Colors -> IO ()) -> (IO ()) -> IO () showSetColorDialog edentvState openTraceWindows updateMainColorButtons setTemplateMenuInconsistent = do (Just gladeXML) <- edentvGladeFile (Just "dialogColors") Nothing colorsDialog <- xmlGetWidget gladeXML castToDialog "dialogColors" globalState <- readMVar edentvState updateDialogFromState gladeXML (colors globalState) dialogResponse <- dialogRun colorsDialog case dialogResponse of ResponseAccept -> do colors' <- updateStateFromDialog gladeXML (colors globalState) setNewColors edentvState colors' openTraceWindows updateMainColorButtons setTemplateMenuInconsistent _ -> return () widgetDestroy colorsDialog return () where updateDialogFromState :: GladeXML -> Colors -> IO () updateDialogFromState gladeXML colors = do mapM_ (updateColorButtonById gladeXML colors) colorMapping -- Sets the color of a color button using the current value in -- the state. -- updateColorButtonById :: GladeXML -> Colors -> ColorMapping -> IO () updateColorButtonById gladeXML colors (id, getter, _) = do let color = getter colors colorButton <- xmlGetWidget gladeXML castToColorButton id updateColorButton colorButton color -- Retrieves the changed colors from the dialog and stores them in -- the state. -- updateStateFromDialog :: GladeXML -> Colors -> IO (Colors) updateStateFromDialog gladeXML colors = do newColors <- mapM getColor colorMapping let colors' = foldr applyColor colors $ zip colorMapping newColors return (colors') where applyColor :: (ColorMapping, ColorRGBA) -> Colors -> Colors applyColor ((_, _, setter), newColor) colors = setter newColor colors getColor :: ColorMapping -> IO (ColorRGBA) getColor (id, _, _) = getColorFromColorButtonById gladeXML id getColorFromColorButtonById :: GladeXML -> String -> IO (ColorRGBA) getColorFromColorButtonById gladeXML id = do colorButton <- xmlGetWidget gladeXML castToColorButton id getColorFromColorButton colorButton -- Stores new colors in the state, updates the main windows color buttons and -- refreshes all open trace windows. -- setNewColors :: MVar EdenTvState -> Colors -> Menu -> (Colors -> IO ()) -> IO () setNewColors edentvState newColors openTraceWindows updateMainColorButtons = do globalState <- takeMVar edentvState putMVar edentvState (globalState {colors = newColors}) updateMainColorButtons newColors refreshAll openTraceWindows updateColorButton :: ColorButton -> ColorRGBA -> IO () updateColorButton colorButton color = do colorButtonSetColor colorButton (rgbColor color) colorButtonSetAlpha colorButton (alpha color) getColorFromColorButton :: ColorButton -> IO (ColorRGBA) getColorFromColorButton colorButton = do rgbColor <- colorButtonGetColor colorButton alpha <- colorButtonGetAlpha colorButton return (RGBA {rgbColor = rgbColor, alpha = alpha}) addMenuLabelToToolItem :: GladeXML -> String -> String -> IO () addMenuLabelToToolItem gladeXML toolItemId labelCaption = do toolItem <- xmlGetWidget gladeXML castToToolItem toolItemId label <- imageMenuItemNewWithLabel labelCaption let labelId = toolItemId ++ "Label" toolItemSetProxyMenuItem toolItem labelId label refreshAll :: Menu -> IO () refreshAll menu = do (_:winList) <- containerGetChildren menu refresh winList where refresh [] = return () refresh (l:ls) = do Just submenu <- menuItemGetSubmenu (castToMenuItem l) (_:r:_) <- containerGetChildren (castToMenu submenu) widgetActivate r refresh ls showAboutDlg = do dlg <- aboutDialogNew dlg `aboutDialogSetName` "EdenTV" dlg `aboutDialogSetVersion` version dlg `aboutDialogSetComments` "Trace Viewer for Eden Trace Files" dlg `aboutDialogSetLicense` Nothing dlg `aboutDialogSetWebsite` "http://www.mathematik.uni-marburg.de/~eden" dlg `aboutDialogSetWebsiteLabel` "Take me to Eden" dlg `aboutDialogSetAuthors` ["Bjoern Struckmeier","Bernhard Pickenbrock","","Betreuung:","Prof. Dr. Rita Loogen"] dlg `afterResponse` (\_ -> widgetDestroy dlg) widgetShowAll dlg