    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Graphics.SOE
    -- Copyright   :  (c) Alastair Reid, 1999-2003
    -- License     :  BSD-style (see the file libraries/base/LICENSE)
    --
    -- Maintainer  :  libraries@haskell.org
    -- Stability   :  stable
    -- Portability :  non-portable (requires concurrency)
    --
    -- The graphics library used in /The Haskell School of Expression/,
    -- by Paul Hudak, cf <http://www.haskell.org/soe/>.
    --
    -- /Notes:/
    --
    -- * This module is called @SOEGraphics@ in the book.  It is a cut
    --   down version of "Graphics.HGL", with the interface frozen to match
    --   the book.
    --
    -- * In chapters 13, 17 and 19 of the book, there are imports of modules
    --   @Win32Misc@ and @Word@.  These should be omitted, as 'timeGetTime'
    --   and 'word32ToInt' are provided by this module.
    -----------------------------------------------------------------------------

    module Graphics.SOE
    	(
    	-- * Getting started
    	  runGraphics		-- p41

    	-- * Windows
    	, Title			-- p40
    	, Size
    	, Window
    	, openWindow
    	, getWindowSize		-- not in SOE, but Resize is
    	, clearWindow		-- used on p127
    	, drawInWindow		-- p41
    	, drawInWindowNow	-- backward compatibility (p281)
    	, setGraphic		-- p168
    	, closeWindow		-- p41

    	-- ** General windows
    	, openWindowEx		-- p168

    	, RedrawMode		-- SOE has (Graphic -> DrawFun)
    	, drawGraphic		-- p168
    	, drawBufferedGraphic

    	-- * Drawing
    	, Graphic		-- p41
    	, emptyGraphic		-- p171
    	, overGraphic
    	, overGraphics		-- not in SOE, but an obvious extension

    	-- ** Color
    	, Color(..)		-- p43
    	, withColor

    	-- ** Drawing text
    	, text			-- p41

    	-- ** Drawing shapes
    	, Point
    	, ellipse		-- p43
    	, shearEllipse
    	, line
    	, polygon
    	, polyline
    	, polyBezier -- warning: becomes error message and polyline in X11

    	, Angle			-- not in SOE
    	, arc			-- not in SOE, but handy for pie charts

    	-- ** Regions
    	, Region		-- p117
    	, createRectangle
    	, createEllipse
    	, createPolygon
    	, andRegion
    	, orRegion
    	, xorRegion
    	, diffRegion
    	, drawRegion

    	-- * User interaction

    	-- ** Keyboard events
    	, getKey		-- p41

    	-- ** Mouse events
    	, getLBP		-- used on p127
    	, getRBP		-- not in SOE, but obvious

    	-- ** General events
    	, Event(..)		-- p214
    	, maybeGetWindowEvent	-- p248
    	, getWindowEvent	-- not in SOE, but obvious

    	-- * Time
    	-- Timers that tick at regular intervals are set up by 'openWindowEx'.
    	, Word32		-- p168
    	, getWindowTick

    	, timeGetTime		-- from Win32
    	, word32ToInt		-- obsolete function from Data.Word

    	) where

    import Graphics.HGL
    	hiding (getKey, getKeyEx, openWindowEx,
    		Event(..), getWindowEvent, maybeGetWindowEvent)
    import qualified Graphics.HGL as HGL
    import Control.Monad(liftM)
    import Data.Word(Word32)

    ----------------------------------------------------------------
    -- Interface
    ----------------------------------------------------------------

    -- | A rectangular region, with the given points as opposite corners.
    createRectangle     :: Point -> Point -> Region

    -- | A polygonal region defined by a list of 'Point's.
    createPolygon       :: [Point] -> Region

    -- | An elliptical region that fits in the rectangle with the given points
    -- as opposite corners.
    createEllipse       :: Point -> Point -> Region

    -- | The union of two regions.
    orRegion            :: Region -> Region -> Region

    -- | The intersection of two regions.
    andRegion           :: Region -> Region -> Region

    -- | The part of the first region that is not also in the second.
    diffRegion          :: Region -> Region -> Region

    -- | Draw a 'Region' in the current color.
    drawRegion          :: Region -> Graphic

    -- | Another name for 'drawInWindow', retained for backwards compatibility.
    drawInWindowNow     :: Window -> Graphic -> IO ()

    ----------------------------------------------------------------
    -- Implementation
    ----------------------------------------------------------------

    -- | an extended version of 'openWindow'.
    openWindowEx :: Title		-- ^ the title of the window
                 -> Maybe Point	-- ^ the initial position of the window
                 -> Maybe Size	-- ^ the initial size of the window
                 -> RedrawMode	-- ^ how to display a graphic on the window
                 -> Maybe Word32	-- ^ optionally attach a timer to the window,
    				-- with the specified time (in milliseconds)
    				-- between ticks.
                 -> IO Window
    openWindowEx a b (Just c) d e =
      HGL.openWindowEx a b c d (fmap fromIntegral e)
    openWindowEx a b Nothing d e =
      HGL.openWindowEx a b (300,300) d (fmap fromIntegral e)

    createRectangle = rectangleRegion
    createEllipse   = ellipseRegion
    createPolygon   = polygonRegion
    orRegion        = unionRegion
    andRegion       = intersectRegion
    diffRegion      = subtractRegion
    drawRegion      = regionToGraphic

    -- backwards compatibility:

    -- | Draw directly to the window
    -- (slightly faster than 'drawBufferedGraphic', but more prone to flicker).
    drawGraphic         :: RedrawMode
    drawGraphic          = Unbuffered

    -- | Use a /double buffer/ to reduce flicker and thus improve the look
    -- of animations.
    drawBufferedGraphic :: RedrawMode
    drawBufferedGraphic  = DoubleBuffered

    -- should have a different way to specify background color
    -- drawBufferedGraphicBC :: RGB -> RedrawMode

    drawInWindowNow = drawInWindow

    -- | The current time of day (in milliseconds).
    timeGetTime         :: IO Word32
    timeGetTime = liftM integerToWord32 getTime

    integerToWord32 :: Integer -> Word32
    #ifdef __GLASGOW_HASKELL__
    integerToWord32 = fromInteger	-- conversion to Word32 doesn't overflow
    #else
    integerToWord32 n = fromInteger (n `mod` (toInteger (maxBound::Word32) + 1))
    #endif

    -- | An obsolete special case of 'fromIntegral'.
    word32ToInt         :: Word32 -> Int
    word32ToInt = fromIntegral

    ----------------------------------------------------------------
    -- Event, getKey, and maybeGetWindowEvent compatibility
    ----------------------------------------------------------------

    {-
     The SOE sources are set in stone, so this module provides the interface
     SOE expects, even if the Graphics library moves on (cf. Event.Key).
    -}

    -- Deprecated SOE compatibility.

    -- | Wait until a key is pressed and released,
    -- and return the corresponding character.
    getKey :: Window -> IO Char
    getKey w = do { getKeyEx w True; getKeyEx w False }

    -- | Wait until a key is pressed (if the second argument is 'True')
    -- or released (otherwise), and return the corresponding character.
    -- (not in SOE)
    getKeyEx :: Window -> Bool -> IO Char
    getKeyEx w down = loop
     where
      loop = do
            e <- HGL.getWindowEvent w
            case e of
              HGL.Key { HGL.keysym = k, HGL.isDown = isDown }
                |  isDown == down && isCharKey k
                -> return (keyToChar k)
              _ -> loop

    -- | Wait for the next event in the window.
    getWindowEvent :: Window -> IO Event
    getWindowEvent w = liftM toSOEEvent (HGL.getWindowEvent w)

    -- | Return a pending eventin the window, if any.
    maybeGetWindowEvent :: Window -> IO (Maybe Event)
    maybeGetWindowEvent w = liftM (fmap toSOEEvent) (HGL.maybeGetWindowEvent w)

    -- tiresome, but necessary.
    toSOEEvent :: HGL.Event -> Event
    toSOEEvent (HGL.Char x)       = Key x True
    toSOEEvent (HGL.Key k isDown) = Key (keyToChar k) isDown
    toSOEEvent (HGL.Button pt left down) = Button pt left down
    toSOEEvent (HGL.MouseMove p)  = MouseMove p
    toSOEEvent (HGL.Resize)       = Resize
    toSOEEvent (HGL.Closed)       = Closed

    -- | User interface events
    data Event
      = Key
        { char :: Char	-- ^ character corresponding to the key
        , isDown :: Bool	-- ^ if 'True', the key was pressed;
    			-- otherwise it was released
        }			-- ^ occurs when a key was pressed or released.
      | Button
        { pt :: Point	-- ^ the position of the mouse cursor
        , isLeft :: Bool	-- ^ if 'True', it was the left button
        , isDown :: Bool	-- ^ if 'True', the button was pressed;
    			-- otherwise it was released
        }			-- ^ occurs when a mouse button is pressed or released.
      | MouseMove
        { pt :: Point	-- ^ the position of the mouse cursor
        }			-- ^ occurs when the mouse is moved inside the window.
      | Resize		-- ^ occurs when the window is resized.
    			-- The new window size can be discovered using
    			-- 'getWindowSize'.
      | Closed		-- ^ occurs when the window is closed.
     deriving Show

    ----------------------------------------------------------------
    -- End
    ----------------------------------------------------------------