packages feed

AutoForms-0.4.0: src/Examples/GhciGui/GhciGui.hs

{-# OPTIONS -fglasgow-exts #-}
{-# OPTIONS -Wall #-}

-- FIXME: Change OPTIONS pragmas to LANGUAGE pragmas

module GhciGui where

import Graphics.UI.AF.WxFormAll
import qualified Protocol as P
import ECOptions()

import System(getArgs)
import System.IO (Handle, hPutStrLn, stderr)
import Network (Socket, accept, withSocketsDo)

main :: IO()
main = do args <- getArgs
          case args of
            [ghcPath] -> startGui ghcPath `catch` (\e -> hPutStrLn stderr $ "Exception raised: " ++ show e)
            _         -> do putStrLn "ghcGui <path to ghc lib>"
                            putStrLn "And your PATH=<location of GhciGui and GhcProcess>"
    where startGui ghcPath = withSocketsDo $
           do (inSocket,  inPortNumber)  <- P.listenOnRandomPort 10
              (outSocket, outPortNumber) <- P.listenOnRandomPort 10
              (runAF $ mainWindow ghcPath inSocket inPortNumber outSocket outPortNumber)
                   

data GuiState =
    GuiState { inHandle      :: Handle
             , outHandle     :: Handle
             , isExecuting   :: Bool
             }

mainWindow :: String -> Socket -> Int -> Socket -> Int -> WxM()
mainWindow ghcPath inSocket inPortNumber outSocket outPortNumber
    = do window menus
           $ addTimer 200 updateState
           $ addButtons buttons
           $ comStateM state component
         
    where
      -- It is important to "hide" away the outputArea (the sndCom bit), so that we not risk
      -- calling pickGetVal on it, as it will ruin performance.
      -- Maybe we just need to make pickGetVal lazy in the AutoForms library :)
      component  = sndCom $ join outputArea inputArea
      inputArea  = giveFocus $ multiLineEC ""
      outputArea = noValue $ label "Output" $ startGhcProcess $ multiLineEC ""
      
      state = do (inHandle', _, _)  <- io $ accept inSocket
                 (outHandle', _, _) <- io $ accept outSocket
                 return $ constState $ GuiState inHandle' outHandle' False
      startGhcProcess = executeProcess ("./GhcProcess " ++ show (ghcPath, inPortNumber, outPortNumber))
                                             onEnd doOutput doError where
          doOutput str = do appendVal (str ++ "\n")  -- have to make a newline, otherwise WxHaskell gets really slow
                                                     -- on large data series.
          doError str  = do appendVal ("Error: " ++ str ++ "\n")
          onEnd exitCode = do errorDialog "Error" ("The GhcProcess failed with exit code: " ++ show exitCode)
                              closeWindow
      updateState
          = do s <- getState
               executing <- io $ P.isExecuting $ inHandle s
               maybe (return ()) (\x -> setState s { isExecuting = x }) executing

      menus = [ ("&File", [ MenuItem optionsAction
                          , MenuItem quitAction
                          ]
                )
              , ("&Help", [ MenuItem $ alwaysEnabled "&About" $ infoDialog "GHCi GUI" aboutText
                          ]
                )
              ]
          where
            aboutText = unlines [ "GHCi GUI made with AutoForms."
                                , ""
                                , "The program was made by Mads Lindstrom."  -- FIXME: should use real ø
                                , ""
                                , "See http://autoforms.sourceforge.net"
                                ]
            optionsAction = alwaysEnabled "&Options..." $
                        do options <- doInGhc P.getOptions
                           maybeNewOptions <- lift $ blockingSettingsDialog (mkCom options)
                           maybe (return()) (doInGhc . P.setOptions) maybeNewOptions
                           
      buttons = [killButton, exec, quitAction] where
          killButton
              = Action "&Cancel" (\_ s -> isExecuting s) $ doInGhc P.kill
          exec = alwaysEnabled "&Exec" $
                   do stmt <- getVal
                      doInGhc (P.executeStmt stmt)
      doInGhc cmd = do s <- getState
                       io $ cmd $ outHandle s 

                      

{- Design rationale: Why the seperate GhcProcess?

* It seems to be the only cross-platform way. "GHC as a library"
  writes directly to stdout. Thus we need to capture this output. It
  could be done using System.Posix, but that is Unix-only. Also see
  http://www.mail-archive.com/glasgow-haskell-users@haskell.org/msg12000.html

* We can then, in the future, have multiply GhcProcesses. We can not
  do that without a seperate process, as "GHC as a library" is a one
  session-per-process design - as far as I know.

* Good seperation between GUI and "GHC as a library".

* I had major problems with deadlocks, and stronger seperation between
  GUI and "GHC as a library" helped me pinpoint where the problem was
  located.

* WxHaskell is not multi-threaded. Thus if we want a responsive GUI
  (and we do), then we need to use processExecAsyncTimed, which starts
  another process. See http://wxhaskell.sourceforge.net/faq.html

-}