File: GraphicsWND.hs

package info (click to toggle)
hugs 1.4.199801-1
  • links: PTS
  • area: non-free
  • in suites: slink
  • size: 7,220 kB
  • ctags: 5,609
  • sloc: ansic: 32,083; haskell: 12,143; yacc: 949; perl: 823; sh: 602; makefile: 236
file content (297 lines) | stat: -rw-r--r-- 8,267 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
module GraphicsWND
	( WND, mkWND, openWND, closeWND, redrawWND
	, handleEvents, closeAllHWNDs
	, wndRect
	, getHWND
	) where

import GraphicsEvent( Event(..) )
import GraphicsEvents( Events, sendEvent, sendTick )
import GraphicsPicture( DrawFun )
import GraphicsTypes
import GraphicsUtilities(safeTry)
import IOExts
import IO(try)
import Monad(liftM2)
import Maybe(isJust)
import Bits

import Win32 hiding ( readFile, writeFile )

----------------------------------------------------------------
-- Once a window has been closed, we want to detect any further
-- operations on the window - so all access is via a mutable Maybe
----------------------------------------------------------------

newtype WND = MkWND (IORef (Maybe HWND))

closeWND :: WND -> IO ()
closeWND wnd@(MkWND hwndref) = do
  mb_hwnd <- readIORef hwndref
  writeIORef hwndref Nothing    -- mark it as closed
  case mb_hwnd of
  Just hwnd -> destroyWindow hwnd
  Nothing   -> return ()

getHWND :: WND -> IO HWND
getHWND (MkWND hwndref) = do
  mb_hwnd <- readIORef hwndref
  case mb_hwnd of 
  Just hwnd -> return hwnd
  Nothing   -> fail (userError "Attempted to act on closed window")

redrawWND :: WND -> IO ()
redrawWND wnd = do
  hwnd <- getHWND wnd
  invalidateRect (Just hwnd) Nothing False

wndRect :: WND -> IO (Point, Point)
wndRect wnd = do
  hwnd <- getHWND wnd
  (l,t,r,b) <- getClientRect hwnd
  return (toPoint (l,t), toPoint (r,b))

mkWND :: HWND -> IO WND
mkWND hwnd = map MkWND (newIORef (Just hwnd))

openWND :: String -> Maybe POINT -> Maybe POINT 
           -> Events      -- where to send the events
           -> DrawFun     -- how to redraw the picture
           -> Maybe MilliSeconds  -- time between timer ticks
           -> IO WND
openWND name pos size events draw tickRate = do
  clAss <- newClass
  hwnd <- createWND name wndProc pos size wS_OVERLAPPEDWINDOW Nothing
  show hwnd False
  updateWindow hwnd
  maybe (return ())
	(\ rate -> setWinTimer hwnd 1 rate >> return ())
	tickRate	
  map MkWND (newIORef (Just hwnd))
 where
  wndProc hwnd msg wParam lParam = do
    -- print msg
    rs <- safeTry $ windowProc 
		      (sendEvent events) 
		      draw
		      (\ wParam -> sendTick events)
		      hwnd msg wParam lParam
    case rs of
    Right a -> return a                   -- ToDo: really ought to force it!
    Left  e -> uncaughtError e >> return 0  -- Let's hope this works ok

  show hwnd iconified = 
    if iconified 
    then do  
      showWindow hwnd sW_SHOWNORMAL -- open "iconified"
      return ()
    else do 
      showWindow hwnd sW_RESTORE    -- open "restored" (ie normal size)
      bringWindowToTop hwnd

-- Note that this code uses a single (static) MSG throughout the whole
-- system - let's hope this isn't a problem
handleEvents :: IO Bool -> IO ()
handleEvents userQuit = do
	      safeTry $
		while (map not (liftM2 (||) userQuit systemQuit)) $ do
		  lpmsg <- getMessage Nothing
		  --getMessage lpmsg Nothing
		  translateMessage lpmsg
		  dispatchMessage lpmsg
	      return ()
 where
  while p s = do { c <- p; if c then s >> while p s else return () }

----------------------------------------------------------------
-- The grotty details - opening WNDs, creating classes, etc
----------------------------------------------------------------

className = mkClassName "GraphicsWND"

newClass :: IO ATOM
newClass = do
  icon         <- loadIcon   Nothing iDI_APPLICATION
  cursor       <- loadCursor Nothing iDC_ARROW
  blackBrush   <- getStockBrush bLACK_BRUSH
  mainInstance <- getModuleHandle Nothing
  atom <- registerClass (
	(cS_HREDRAW .|. cS_VREDRAW), -- redraw if window size Changes
	mainInstance,
	(Just icon),
	(Just cursor),
	(Just blackBrush),
	Nothing,
	className)
  --return atom
  return (maybe undefined id atom)

createWND :: String -> WindowClosure -> Maybe POINT -> Maybe POINT 
		    -> WindowStyle -> Maybe HMENU -> IO HWND
createWND name wndProc posn size style menu = do
  mainInstance <- getModuleHandle Nothing
  mbSize <- calcSize size
  hwnd <- createWindowEx 
	    0 -- Win32.wS_EX_TOPMOST 
	    className
	    name
	    style
	    (map (toInt.fst) posn)   (map (toInt.snd) posn)   -- x y
	    (map (toInt.fst) mbSize) (map (toInt.snd) mbSize) -- w h
	    Nothing                           -- parent
	    menu
	    mainInstance
	    wndProc
  addHWND hwnd
  return hwnd
 where
  calcSize :: Maybe POINT -> IO (Maybe POINT)
  calcSize = 
    maybe (return Nothing)
          (\ (width, height) -> do
             (l,t,r,b) <- adjustWindowRect (0,0,width,height) style (isJust menu)
             return $ Just (r-l, b-t))

windowProc :: (Event -> IO ()) ->   	-- Event Handler
              DrawFun ->		-- Picture redraw
	      (WPARAM -> IO ()) ->	-- tick
	      (HWND -> WindowMessage -> WPARAM -> LPARAM -> IO LRESULT)
windowProc send redraw tick hwnd msg wParam lParam
  | msg == wM_PAINT
  = paint
  | msg == wM_MOUSEMOVE
  = mouseMove lParam
  | msg == wM_LBUTTONDOWN || msg == wM_LBUTTONDBLCLK
  = button lParam True True
  | msg == wM_LBUTTONUP
  = button lParam True False
  | msg == wM_RBUTTONDOWN || msg == wM_RBUTTONDBLCLK
  = button lParam False True
  | msg == wM_RBUTTONUP
  = button lParam False False
  | msg == wM_KEYDOWN
  = key wParam True
  | msg == wM_KEYUP
  = key wParam False
  | msg == wM_TIMER
  = timer wParam
  | msg == wM_SIZE
  = resize
{-
  | msg == wM_MOUSEACTIVATE
  = do
      hwnd' <- setFocus hwnd
      if hwnd `eqHWND` hwnd' 
        then
          return mA_NOACTIVATE  -- already had input focus
        else
          return mA_ACTIVATEANDEAT
-}
  | msg == wM_DESTROY
  = destroy
  | otherwise
  = defWindowProc (Just hwnd) msg wParam lParam

 where
  paint :: IO LRESULT
  paint = paintWith hwnd (\hdc lpps -> do
	redraw hwnd hdc
	return 0
	)

  button :: LPARAM -> Bool -> Bool -> IO LRESULT
  button lParam isLeft isDown = do
	let (y,x) = lParam `divMod` 65536
	send (Button {pt = toPoint (x,y), isLeft, isDown})
	return 0

  key :: WPARAM -> Bool -> IO LRESULT
  key wParam isDown = do
     	send (Key { char = toEnum (toInt wParam), isDown })
--     	send (Key { char = toEnum wParam, isDown })
	return 0

  mouseMove :: LPARAM -> IO LRESULT
  mouseMove lParam = do
	let (y,x) = lParam `divMod` 65536
	send (MouseMove { pt = toPoint (x,y) })
	return 0

  timer :: WPARAM -> IO LRESULT
  timer wParam = do
	tick wParam
	return 0

  resize :: IO LRESULT
  resize = do
	-- don't send new size, it may be out of date by the time we
	-- get round to reading the event
	send Resize
	return 0

  destroy :: IO LRESULT
  destroy = do
        removeHWND hwnd
        send Closed
	return 0

paintWith :: HWND -> (HDC -> LPPAINTSTRUCT -> IO a) -> IO a
paintWith hwnd p = do
  lpps <- malloc sizeofPAINTSTRUCT
  hdc  <- beginPaint hwnd lpps
  a    <- p hdc lpps
  endPaint hwnd lpps
  free lpps 
  return a

----------------------------------------------------------------
-- The open window list
----------------------------------------------------------------

-- It's very important that we close any windows - even if the 
-- Haskell application fails to do so (or aborts for some reason).
-- Therefore we keep a list of open windows and close them all at the
-- end.

-- persistent list of open windows
windows :: IORef [HWND]
windows = unsafePerformIO (newIORef [])

noMoreWindows :: IO Bool
noMoreWindows = map null (readIORef windows)

-- It's also important that we abort cleanly if an uncaught IOError
-- occurs - this flag keeps track of such things

hadUncaughtError :: IORef Bool
hadUncaughtError = unsafePerformIO (newIORef False)

-- We call this if an uncaught error has occured
uncaughtError :: IOError -> IO ()
uncaughtError e = do
  putStr "Uncaught Error: "
  print e  
  writeIORef hadUncaughtError True

systemQuit :: IO Bool
systemQuit = liftM2 (||) (readIORef hadUncaughtError) noMoreWindows

closeAllHWNDs :: IO ()
closeAllHWNDs = do
  hwnds <- readIORef windows
  mapM_ destroyWindow hwnds
  writeIORef windows []
  writeIORef hadUncaughtError False -- clear the system


addHWND :: HWND -> IO ()
addHWND hwnd = do
  hwnds <- readIORef windows
  writeIORef windows (hwnd:hwnds)

-- remove a HWND from windows list
removeHWND :: HWND -> IO ()
removeHWND hwnd = do
  hwnds <- readIORef windows
  writeIORef windows (filter (/= hwnd) hwnds)