1 /*
2  * GraphApp - Cross-Platform Graphics Programming Library.
3  *
4  * File: events.c -- winprocs and timers are contained here.
5  * Platform: Windows  Version: 2.35  Date: 1998/04/04
6  *
7  * Version: 1.00  Changes: Original version by Lachlan Patrick.
8  * Version: 2.00  Changes: New class system implemented.
9  * Version: 2.20  Changes: Non-native buttons supported.
10  * Version: 2.22  Changes: 32-bit fix by Wim Rijnders.
11  * Version: 2.35  Changes: New delayed deletion technique.
12  */
13 
14 /* Copyright (C) 1993-1998 Lachlan Patrick
15 
16    This file is part of GraphApp, a cross-platform C graphics library.
17 
18    GraphApp is free software; you can redistribute it and/or modify it
19    under the terms of the GNU Library General Public License.
20    GraphApp is distributed in the hope that it will be useful, but
21    WITHOUT ANY WARRANTY.
22 
23    See the file COPYLIB.TXT for details.
24 */
25 
26 /* Copyright (C) 2004, 2009	The R Foundation
27    Copyright (C) 2013		The R Core Team
28 
29    Changes for R, Chris Jackson, 2004
30    Handle find-and-replace modeless dialogs
31    Menu shortcut keys re-enabled
32    Handle WM_CONTEXTMENU events for right-clicking on a (rich) edit control
33    Handle mouse wheel scrolling
34    Remove assumption that current->dest is non-NULL
35    Add waitevent() function
36  */
37 
38 #include "internal.h"
39 
40 /*
41  *  Library variables.
42  */
43 static timerfn do_timer = NULL;
44 static void *timer_data = NULL;
45 
46 static MSG    msg;
47 PROTECTED int keystate = 0; /* state of shift, ctrl and alt keys */
48 
49 /* a user-timer and a mouse-down timer function can be started */
50 
51 static	UINT	timer_id	= 0;
52 static	UINT	mouse_timer_id	= 0;
53 
54 /* buttons and xy are used to record the state of the mouse */
55 
56 static	int	buttons = 0;
57 static	point	xy;
58 
59 static	long	mouse_msec = 0;
60 
61 TIMERPROC app_timer_proc;
62 WNDPROC   app_control_proc;
63 
64 static object frontwindow = NULL; /* the window receiving events */
65 
66 static UINT uFindReplaceMsg; // message identifier for FINDMSGSTRING
67 
68 
69 /* Surrogate Pairs MACRO */
70 #define SURROGATE_PAIRS_HI_MIN  ((uint16_t)0xd800)
71 #define SURROGATE_PAIRS_HI_MAX  ((uint16_t)0xdbff)
72 #define SURROGATE_PAIRS_LO_MIN  ((uint16_t)0xdc00)
73 #define SURROGATE_PAIRS_LO_MAX  ((uint16_t)0xdfff)
74 #define SURROGATE_PAIRS_BIT_SZ  ((uint32_t)10)
75 #define SURROGATE_PAIRS_MASK    (((uint16_t)1 << SURROGATE_PAIRS_BIT_SZ)-1)
76 #define IsSurrogatePairsHi(_h)  (SURROGATE_PAIRS_HI_MIN == \
77 		      ((uint16_t)(_h) &~ (uint16_t)SURROGATE_PAIRS_MASK ))
78 #define IsSurrogatePairsLo(_l)  (SURROGATE_PAIRS_LO_MIN == \
79 		      ((uint16_t)(_l) &~ (uint16_t)SURROGATE_PAIRS_MASK ))
80 
81 /*
82  *  Call the relevent mouse handler function.
83  */
handle_mouse(object obj,HWND hwnd,UINT message,int param,int x,int y)84 static void handle_mouse(object obj, HWND hwnd, UINT message,
85 			int param, int x, int y)
86 {
87     menu m;
88     POINT wp;
89     HWND hw;
90     int dble = 1;
91 
92     xy.x = x;
93     xy.y = y;
94     buttons = 0;
95     if (!obj) return;
96     if (param & MK_LBUTTON)
97 	buttons |= LeftButton;
98     if (param & MK_MBUTTON)
99 	buttons |= MiddleButton;
100     if (param & MK_RBUTTON)
101 	buttons |= RightButton;
102 
103     /* dispatch the mouse event to the relevent handler */
104     if (obj && obj->drawstate && obj->drawstate->crsr)
105 	SetCursor((HCURSOR)obj->drawstate->crsr->handle);
106 
107     switch (message)
108     {
109     case WM_MOUSEMOVE:
110 	if (obj->call) {
111 	    if (buttons) {
112 		if (obj->call->mousedrag)
113 		    obj->call->mousedrag(obj, buttons, xy);
114 	    }
115 	    else if (obj->call->mousemove)
116 		obj->call->mousemove(obj, buttons, xy);
117 	}
118 	break;
119     case WM_LBUTTONDOWN:
120     case WM_RBUTTONDOWN:
121     case WM_MBUTTONDOWN:
122 	dble = 0;
123 	setmousetimer(mouse_msec); /* restart timer */
124 	/* fall through to next case */
125     case WM_LBUTTONDBLCLK:
126     case WM_RBUTTONDBLCLK:
127     case WM_MBUTTONDBLCLK:
128 	if (dble) buttons |= DblClick;
129 	if ((obj->flags & ChildWindow) &&  (obj->kind != LabelObject))
130 	    SetFocus(hwnd);
131 	if (obj->flags & TrackMouse)
132 	    SetCapture(hwnd);
133 	if (buttons == RightButton) {
134 	    m = obj->popup; hw = hwnd;
135 	    if (!m) {
136 		m = obj->parent->popup;
137 		hw = (HWND) obj->parent->handle;
138 	    }
139 	    if (m) {
140 		wp.x = x; wp.y = y;
141 		ClientToScreen(hw, (LPPOINT) &wp);
142 		if (m->action) m->action(m);
143 		TrackPopupMenu(m->handle,
144 			       TPM_LEFTALIGN|
145 			       TPM_LEFTBUTTON|TPM_RIGHTBUTTON,
146 			       wp.x,wp.y,
147 			       0,
148 			       obj->handle,
149 			       NULL);
150 		break;
151 	    }
152 	}
153 	if (obj->call && obj->call->mousedown)
154 	    obj->call->mousedown(obj, buttons, xy);
155 	break;
156     case WM_LBUTTONUP:
157     case WM_RBUTTONUP:
158     case WM_MBUTTONUP:
159 	if ((obj->flags & TrackMouse) && (buttons == 0))
160 	    ReleaseCapture();
161 	if (obj->call && obj->call->mouseup)
162 	    obj->call->mouseup(obj, buttons, xy);
163 	break;
164     }
165 }
166 
167 /*
168  *  Some WM_KEYDOWN VK_* messages call the keyaction associated
169  *  with a window.
170  */
handle_virtual_keydown(object obj,int param)171 static void handle_virtual_keydown(object obj, int param)
172 {
173     if ((! obj->call) || (! obj->call->keyaction))
174 	return;
175 
176     /* translate arrow key combinations into Unicode arrow symbols */
177     if ((param >= VK_LEFT) && (param <= VK_DOWN)) {
178 	param += (LEFT - VK_LEFT);
179     }
180 
181     /* translate functions keys into Unicode circled numbers */
182     else if ((param >= VK_F1) && (param <= VK_F10)) {
183 	param += (F1 - VK_F1);
184     }
185 
186     /* translate other keyboard keys into Unicode 'equivalents' */
187     else switch (param) {
188 	case VK_PRIOR:	param = PGUP; break;
189 	case VK_NEXT:	param = PGDN; break;
190 	case VK_END:	param = END;  break;
191 	case VK_HOME:	param = HOME; break;
192 	case VK_INSERT:	param = INS;  break;
193 	case VK_DELETE:	param = DEL;  break;
194 	default:	return; /* do nothing */
195 	}
196 
197     drawto(obj);
198     obj->call->keyaction(obj, param);
199 }
200 
handle_keydown(int param)201 static void handle_keydown(int param)
202 {
203     if (param == VK_SHIFT)
204 	keystate |= ShiftKey;
205     else if (param == VK_CONTROL)
206 	keystate |= CtrlKey;
207     else if (param == VK_MENU)
208 	keystate |= AltKey;
209 }
210 
handle_keyup(int param)211 static void handle_keyup(int param)
212 {
213     if (param == VK_SHIFT)
214 	keystate &= ~ShiftKey;
215     else if (param == VK_CONTROL)
216 	keystate &= ~CtrlKey;
217     else if (param == VK_MENU)
218 	keystate &= ~AltKey;
219 }
220 
221 /*
222  *  Handle char messages.
223  */
handle_char(object obj,int ch)224 static void handle_char(object obj, int ch)
225 {
226     if (obj->call && obj->call->keydown) {
227 	if (ch == '\r') /* carriage return becomes newline */
228 	    ch = '\n';
229 	drawto(obj);
230 	obj->call->keydown(obj, ch);
231     }
232 }
233 
234 static int showMDIToolbar = 1;
toolbar_show(void)235 void toolbar_show(void)
236 {
237     showMDIToolbar = 1;
238     SendMessage(hwndFrame,WM_PAINT, (WPARAM) 0, (LPARAM) 0);
239 }
toolbar_hide(void)240 void toolbar_hide(void)
241 {
242     showMDIToolbar = 0;
243     SendMessage(hwndFrame,WM_PAINT, (WPARAM) 0, (LPARAM) 0);
244 }
245 
handle_mdiframesize(void)246 static void handle_mdiframesize(void)
247 {
248     HWND tool=NULL ,status=NULL;
249     RECT rFrame,rToolbar;
250     int  fw, fh, th=0, sh=0;
251     GetClientRect(hwndFrame,&rFrame);
252     fw = rFrame.right-rFrame.left;
253     fh = rFrame.bottom-rFrame.top;
254     if (showMDIToolbar && MDIToolbar) {
255 	tool = (HWND)MDIToolbar->handle;
256 	GetWindowRect(tool,&rToolbar);
257 	th = rToolbar.bottom-rToolbar.top;
258     }
259     if (MDIStatus) {
260 	status = (HWND)MDIStatus;
261 	GetWindowRect(status,&rToolbar);
262 	sh = rToolbar.bottom-rToolbar.top;
263     }
264     MoveWindow(hwndClient,0,th+1,fw,fh-sh-th-1,TRUE);
265     if (tool) {
266 	MoveWindow(tool,1,0,fw-2,th,TRUE);
267 	show(MDIToolbar);
268     }
269     if (status) {
270 	MoveWindow(status,1,fh-sh,fw-2,sh,TRUE);
271     }
272     SetFocus((HWND)SendMessage(hwndClient,
273 			       WM_MDIGETACTIVE,(WPARAM)0,(LPARAM) 0));
274 }
275 
276 /*
277  *  The window is being resized for some reason.
278  */
handle_resize(object obj)279 static void handle_resize(object obj)
280 {
281     if (obj->call && obj->call->resize) {
282 	drawto(obj);
283 	obj->call->resize(obj,
284 			  rect(0,0,obj->rect.width,obj->rect.height));
285     }
286     deletion_traversal();  /* We may be called again before
287 			      returning to doevent */
288 }
289 
290 /*
291  *  The window is being redrawn for some reason.
292  */
handle_redraw(object obj,HWND hwnd)293 static void handle_redraw(object obj, HWND hwnd)
294 {
295     PAINTSTRUCT ps;
296     if (obj==MDIFrame)
297 	handle_mdiframesize();
298     del_context(obj);
299     add_context(obj, BeginPaint(hwnd, &ps), NULL);
300     if (ps.fErase)
301 	clear(obj);
302     draw(obj);
303     EndPaint(hwnd, &ps);
304     remove_context(obj);
305     dc = 0;
306 }
307 
308 /*
309  *  Hide an application window, or call close() function if possible.
310  */
handle_close(object obj)311 static void handle_close(object obj)
312 {
313     if (obj->call && obj->call->close) {
314 	drawto(obj);
315 	obj->call->close(obj);
316     } else {
317 	hide(obj);
318     }
319 }
320 
handle_destroy(object obj)321 static void handle_destroy(object obj)
322 {
323     /*
324       drawto(obj);
325       del_object(obj);
326     */
327 }
328 
handle_focus(object obj,int gained_focus)329 static void handle_focus(object obj, int gained_focus)
330 {
331     if (gained_focus) {
332 	obj->state |= GA_Focus;
333 	if (obj->caretwidth < 0) {
334 	    setcaret(obj, 0,0, -obj->caretwidth, obj->caretheight);
335 	    showcaret(obj, 1);
336 	}
337     } else {
338 	obj->state &= ~GA_Focus;
339 	if (obj->caretwidth > 0) {
340 	    setcaret(obj, 0,0, -obj->caretwidth, obj->caretheight);
341 	    showcaret(obj, 0);
342 	}
343     }
344     if ((! USE_NATIVE_BUTTONS) && (obj->kind == ButtonObject))
345 	InvalidateRect(obj->handle, NULL, 0);
346     if (obj->call && obj->call->focus)
347 	obj->call->focus(obj);
348 }
349 
350 /*
351  *  Handle scrollbars. Designed to also work with normal window
352  *  scrollbars.
353  */
handle_scroll(object obj,HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)354 static void handle_scroll(object obj, HWND hwnd, UINT message,
355 				WPARAM wParam, LPARAM lParam)
356 {
357     int size_shown = 10;
358     int max_value = 100;
359     int where = 0;
360     int prev = 0;
361     int which = 0;
362     /* we need to look at the recorded values */
363     max_value = obj->max;
364     size_shown = obj->size;
365     if (obj->kind != WindowObject) which = SB_CTL;
366     else if (message == WM_VSCROLL) which = SB_VERT;
367     else if (message == WM_HSCROLL) {
368         which = SB_HORZ;
369 	max_value = obj->xmax;
370 	size_shown = obj->xsize;
371     }
372     prev = where = GetScrollPos(hwnd, which);
373 
374     /* next we look at wParam to see what happened */
375     switch(LOWORD(wParam))
376     {
377     case SB_PAGEDOWN:	where += (size_shown-1);
378 	/* fall through to next case */
379     case SB_LINEDOWN:	where = min(max_value, where+1);
380 	break;
381     case SB_PAGEUP:		where -= (size_shown-1);
382 	/* fall through to next case */
383     case SB_LINEUP:		where = max(0, where-1);
384 	break;
385     case SB_TOP:		where = 0;
386 	break;
387     case SB_BOTTOM:		where = max_value;
388 	break;
389     case SB_THUMBPOSITION:
390     case SB_THUMBTRACK:
391 #ifdef WIN32
392 	{
393 	    /* The message only contains a 16 bit position.  We need to query to get 32 bits. */
394 	    SCROLLINFO si;
395 	    si.cbSize = sizeof(SCROLLINFO);
396 	    si.fMask = SIF_TRACKPOS;
397 	    if (GetScrollInfo(hwnd, which, &si))
398 		where = si.nTrackPos;
399 	    else
400 		where = HIWORD(wParam); /* Just in case this mask is not supported. Not sure when it arrived... */
401 	}
402 #else
403 	where = LOWORD(lParam);
404 #endif
405 	break;
406     default:		break;
407     }
408     /* check if something happened */
409     if (prev == where)
410 	return;
411     /* now we reset the scrollbar's values */
412     SetScrollPos(hwnd, which, where, 1);
413     if (message == WM_HSCROLL) {
414 	where = -(where+1);
415     }
416     setvalue(obj, where);
417     activatecontrol(obj);
418 }
419 
420 /*
421  *  Perform some brush manipulation to handle background colours
422  *  in native checkboxes and radio buttons.
423  */
424 #if USE_NATIVE_TOGGLES
425   #ifdef WM_CTLCOLOR
handle_colour(HDC dc,object obj)426 static void handle_colour(HDC dc, object obj)
427 {
428     rgb fg, bg;
429     COLORREF wincolour;
430 
431     if (obj->drawstate)
432 	fg = obj->drawstate->hue;
433     else
434 	fg = obj->fg;
435     wincolour = RGB((fg&Red)>>16,(fg&Green)>>8,(fg&Blue));
436     SetTextColor(dc, wincolour);
437 
438     bg = obj->bg;
439     wincolour = RGB((bg&Red)>>16,(bg&Green)>>8,(bg&Blue));
440     SetBkColor(dc, wincolour);
441 
442     fix_brush(dc, obj, obj->bgbrush);
443 }
444    #endif
445 #endif
446 
447 static char dfilename[MAX_PATH + 1];
handle_drop(object obj,HANDLE dropstruct)448 static void handle_drop(object obj, HANDLE dropstruct)
449 {
450     if (obj->call && obj->call->drop) {
451 	int len = DragQueryFile(dropstruct, 0, NULL, 0);
452 	if (len > MAX_PATH) {
453 	    DragFinish(dropstruct);
454 	    return;
455 	}
456 	DragQueryFile(dropstruct, 0, dfilename, MAX_PATH);
457 	DragFinish(dropstruct);
458 	obj->call->drop(obj, dfilename);
459     }
460 }
461 
462 /* Handle a right-click context menu in non-window objects such as text areas */
463 
handle_context_menu(object obj,HWND hwnd,int x,int y)464 static void handle_context_menu(object obj, HWND hwnd, int x, int y)
465 {
466     menu m = obj->popup;
467     HWND hw = hwnd;
468     POINT wp;
469     if (!m) {
470 	m = obj->parent->popup;
471 	hw = (HWND) obj->parent->handle;
472     }
473     if (m) {
474 	wp.x = x; wp.y = y;
475 	if (m->action) m->action(m);
476 	TrackPopupMenu(m->handle,
477 		       TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON,
478 		       wp.x, wp.y, 0, hw, NULL);
479     }
480 }
481 
482 /*
483  *  Shared window procedure code. The pass variable is initially zero.
484  *  It can be set to non-zero in this procedure if we wish to pass
485  *  the event to the default Windows winprocs.
486  */
handle_message(HWND hwnd,UINT message,WPARAM wParam,LONG lParam,int * pass)487 static long handle_message(HWND hwnd, UINT message,
488 			WPARAM wParam, LONG lParam, int *pass)
489 {
490     object obj;
491     WPARAM upDown;
492     static unsigned short altnpad = 0;
493 
494     /* Find the library object associated with the hwnd. */
495     obj = find_by_handle(hwnd);
496 
497     if (! obj) { /* Not a library object ... */
498 	*pass = 1; /* ... so pass the event. */
499 	return 0;
500     }
501 
502     frontwindow = obj; /* Needed for auto-mousedowns. */
503 
504     /* Handle mouse messages. */
505     if ((message >= WM_MOUSEMOVE) && (message <= WM_MBUTTONDBLCLK))
506     {
507 	handle_mouse(obj, hwnd, message, LOWORD(wParam),
508 		     LOWORD(lParam), HIWORD(lParam));
509 	return 0;
510     }
511 
512     /* Handle other messages. */
513     switch (message)
514     {
515     case WM_MOUSEWHEEL:  /* convert MOUSEWHEEL messages to VSCROLL. Scroll by pairs of lines   */
516 	upDown = (short)HIWORD(wParam) > 0 ? SB_LINEUP : SB_LINEDOWN;
517 	PostMessage(hwnd, WM_VSCROLL, upDown, 0);
518 	PostMessage(hwnd, WM_VSCROLL, upDown, 0);
519 	break;
520 
521     case WM_SYSKEYDOWN:
522         if(obj->flags & UseUnicode)
523 	    if(VK_NUMPAD0 <= LOWORD(wParam) && LOWORD(wParam) <= VK_NUMPAD9) {
524 	        altnpad *= 10;
525 		altnpad += LOWORD(wParam) & 0xf;
526 	    }
527         break;
528 
529     case WM_KEYDOWN: /* record state of shift and control keys */
530 	handle_keydown(LOWORD(wParam));
531 	handle_virtual_keydown(obj, LOWORD(wParam));
532 
533 	if(obj->flags & UseUnicode) {
534 	    BYTE           sta[256];
535 	    wchar_t        wcs[3];
536 	    HKL            dwhkl;
537 	    static wchar_t deadkey = L'\0';
538 
539 	    dwhkl = GetKeyboardLayout((DWORD) 0);
540 	    GetKeyboardState(sta);
541 	    if(ToUnicodeEx(wParam, lParam, sta,
542 			   wcs, /* 3 */ sizeof(wcs)/sizeof(wchar_t),
543 			   0, dwhkl) == 1) {
544 		if(deadkey != L'\0') {
545 		    wchar_t wcs_in[3];
546 		    wchar_t wcs_out[3];
547 		    wcs_in[0] = wcs[0];
548 		    wcs_in[1] = deadkey;
549 		    wcs_in[2] = L'\0';
550 		    /* from accent char to unicode */
551 		    if (FoldStringW(MAP_PRECOMPOSED, wcs_in, 3, wcs_out, 3))
552 			handle_char(obj, wcs_out[0]);
553 		    /* deadchar convert failure to skip. */
554 		} else
555 		    handle_char(obj, wcs[0]);
556 		deadkey = L'\0';
557 	    } else {
558 		switch(wcs[0]) {
559 		case 0x5e:          /* circumflex */
560 		    deadkey = 0x302;  break;
561 		case 0x60:          /* grave accent */
562 		    deadkey = 0x300;  break;
563 		case 0xa8:          /* diaeresis */
564 		    deadkey = 0x308;  break;
565 		case 0xb4:          /* acute accent */
566 		    deadkey = 0x301;  break;
567 		case 0xb8:          /* cedilla */
568 		    deadkey = 0x327;  break;
569 		default:
570 		    deadkey = wcs[0];
571 		    break;
572 		}
573 	    }
574 	}
575 	break;
576 
577     case WM_KEYUP: /* record state of shift and control keys */
578         if(obj->flags & UseUnicode)
579 	    if(LOWORD(wParam) == VK_MENU && altnpad) {
580 	        handle_char(obj, altnpad);
581 		altnpad = 0;
582 	    }
583 	handle_keyup(LOWORD(wParam));
584 	break;
585 
586     case WM_CHAR: /* SBCS Only */
587 	if(obj->flags & UseUnicode) return 0;
588 	else {
589 	    handle_char(obj, LOWORD(wParam));
590 	    return 0;
591 	}
592 
593     case WM_IME_COMPOSITION: /* DBCS Only */
594 	if (lParam & GCS_RESULTSTR) { /* is fixed multiGAbyte string */
595 	    HIMC            himc = ImmGetContext(hwnd);
596 	    wchar_t         buf[80];
597 	    wchar_t         *p;
598 	    int             i;
599 	    int             len;
600 
601 	    if(obj->flags & UseUnicode) {
602 		/* len is GAbyte */
603 		len = ImmGetCompositionStringW(himc, GCS_RESULTSTR, NULL,0);
604 		if(NULL == (p=( len > sizeof(buf)-1) ? calloc(len,sizeof(char)) : buf)) {
605 		    len = sizeof(buf);
606 		    p = buf;
607 		}
608 		ImmGetCompositionStringW(himc,GCS_RESULTSTR, p, len);
609 		ImmReleaseContext(hwnd,himc);
610 		/* Surrogate Pairs Block */
611 		for(i = 0; i < (len/sizeof(wchar_t)); i++)
612 		    if(IsSurrogatePairsHi(p[i]) &&
613 		       i+1 < (len/sizeof(wchar_t)) &&
614 		       IsSurrogatePairsLo(p[i+1]) ) {
615 			handle_char(obj, L'?');
616 			handle_char(obj, L'?');
617 			i++;
618 		    } else handle_char(obj, p[i]);
619 		if(p != buf) free(p);
620 		return 0;
621 	    }
622 	}
623 	break;
624 
625     case WM_SETFOCUS:
626 	handle_focus(obj, 1);
627 	break;
628 
629     case WM_KILLFOCUS:
630 	handle_focus(obj, 0);
631 	break;
632 
633     case WM_PAINT:
634 	handle_redraw(obj, hwnd);
635 	return 0;
636 
637     case WM_INITMENUPOPUP:
638 	if (HIWORD(lParam)) /* true if system menu */
639 	    return 0; /* else fall through */
640     case WM_INITMENU:
641 	adjust_menu(wParam);
642 	break;
643 
644     case WM_MOVE:
645 	obj->rect.x = LOWORD(lParam);
646 	obj->rect.y = HIWORD(lParam);
647 	break;
648 
649     case WM_SIZE:
650 	obj->rect.width = LOWORD(lParam);
651 	obj->rect.height = HIWORD(lParam);
652 	handle_resize(obj);
653 	break;
654 
655     case WM_ACTIVATE:
656 	/* Keep track of which window is in front. */
657 	if (LOWORD(wParam) != WA_INACTIVE)
658 	    move_to_front(obj);
659 	break;
660 
661     case WM_QUERYENDSESSION:
662 	handle_close(obj);
663 	return 1L; /* ensure Windows can terminate */
664 
665     case WM_CLOSE:
666 	handle_close(obj);
667 	return 0;
668 
669     case WM_DESTROY:
670 	handle_destroy(obj);
671 	break;
672 
673 	/*case WM_SYSCOMMAND:*/
674     case WM_COMMAND:
675 	if (LOWORD(wParam) >= MinDocID)
676 	    break; /* MDI Client window will handle it */
677 	else if (LOWORD(wParam) >= MinChildID) {
678 #ifdef WIN32
679 	    handle_control((HWND) (intptr_t) lParam, HIWORD(wParam));
680 #else
681 	    handle_control((HWND) LOWORD(lParam), HIWORD(lParam));
682 #endif /* WIN32 */
683 	}
684 	else if ((LOWORD(wParam) >= MinMenuID) && menus_active)
685 	    handle_menu_id(LOWORD(wParam));
686 	break;
687 
688     case WM_VSCROLL:
689     case WM_HSCROLL:
690 #ifdef WIN32
691 	if (lParam != 0) { /* scrollbar object */
692 	    hwnd = (HWND) (intptr_t) lParam;
693 #else
694 	if (HIWORD(lParam) != 0) { /* scrollbar object */
695 	    hwnd = (HWND) HIWORD(lParam);
696 #endif /* WIN32 */
697 	    obj = find_by_handle(hwnd);
698 	    if (! obj)
699 		return 0;
700 	}
701 	handle_scroll(obj, hwnd, message, wParam, lParam);
702 	return 0;
703 
704 #if USE_NATIVE_TOGGLES
705 #ifdef WM_CTLCOLOR
706     case WM_CTLCOLOR:
707 #ifdef WIN32
708 	hwnd = (HWND) lParam;
709 #else
710 	hwnd = (HWND) LOWORD(lParam);
711 #endif  /* WIN32 */
712 
713 	obj = find_by_handle(hwnd);
714 	if (! obj)
715 	    break;
716 	if ((obj->kind != CheckboxObject) && (obj->kind != RadioObject))
717 	    break;
718 	handle_colour((HDC) wParam, obj);
719 	return (LRESULT) obj->bgbrush;
720 #endif
721 #endif
722     case WM_IME_STARTCOMPOSITION:
723 	if(obj->call && obj->call->im) {
724 	    HIMC himc ;
725 	    LOGFONT lf;
726 	    font f;
727 	    COMPOSITIONFORM cf;
728 
729 	    himc = ImmGetContext(hwnd);
730 	    obj->call->im(obj, &f, (void *) &cf.ptCurrentPos);
731 	    GetObject(f->handle, sizeof(LOGFONT), &lf);
732 	    ImmSetCompositionFont(himc, &lf);
733 		cf.dwStyle = CFS_POINT;
734 		ImmSetCompositionWindow(himc, &cf);
735 		ImmReleaseContext(hwnd, himc);
736 	    }
737 	break;
738     case WM_DROPFILES:
739 	handle_drop(obj, (HANDLE) wParam);
740     }
741 
742     /* If we got this far the event must be passed along
743      * to the default Windows event handling procedures. */
744     *pass = 1;
745     return 0;
746 }
747 
748 /*
749  *  Window procedures call a generic window handling routine.
750  *  We need three window procedures since the different calls
751  *  tell us which default window procedure to pass the messages
752  *  to if we don't wish to handle a message.
753  *  If we were to use only one window procedure, we would have to
754  *  have a way of determining which is the default window proc
755  *  for a window from just knowing the hwnd (which may or may not
756  *  belong to us).
757  */
758 LRESULT WINAPI
759 app_win_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
760 {
761     long result;
762     int pass = 0;
763 
764     result = handle_message(hwnd, message, wParam, lParam, &pass);
765     if (pass)
766 	result = DefWindowProc(hwnd, message, wParam, lParam);
767     return result;
768 }
769 
770 LRESULT WINAPI
771 app_doc_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
772 {
773     long result;
774     int pass = 0;
775     object obj;
776     if ((message==WM_MDIACTIVATE) && ((HWND)lParam==hwnd)) {
777 	if (MDIToolbar) hide(MDIToolbar);
778 	obj = find_by_handle(hwnd);
779 	MDIToolbar = (obj) ? obj->toolbar : NULL;
780 	handle_mdiframesize();
781 	if (obj && obj->menubar) {
782 	    menu mdi = (obj->menubar)->menubar;
783 	    SendMessage(hwndClient, WM_MDISETMENU,
784 			(WPARAM)obj->menubar->handle,
785 			(LPARAM)(mdi?(mdi->handle):0));
786 	    DrawMenuBar(hwndFrame);
787 	}
788 	if (obj) updatestatus(obj->status);
789 	RedrawWindow(hwndFrame,NULL,NULL,
790 		     RDW_UPDATENOW|RDW_ALLCHILDREN);
791 	SetFocus(hwnd);
792 	return 1;
793     }
794     result = handle_message(hwnd, message, wParam, lParam, &pass);
795     if (pass)
796 	result = DefMDIChildProc(hwnd, message, wParam, lParam);
797     return result;
798 }
799 
800 LRESULT WINAPI
801 app_work_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
802 {
803     long result;
804     int pass = 0;
805     result = handle_message(hwnd, message, wParam, lParam, &pass);
806     if (pass)
807 	result = DefFrameProc(hwnd, hwndClient, message, wParam, lParam);
808     return result;
809 }
810 
811 /*
812  *  To handle controls correctly, we replace each control's event
813  *  handling procedure with our own when we create it. We handle
814  *  certain events ourselves, and pass the rest to the default
815  *  routines.
816  *  Things we do here include: allowing the TAB key to change
817  *  input focus to the next control; for one-line text fields
818  *  pressing return causes the event to be sent to the parent window.
819  */
820 
821 /* Send a char to an object, or its parent if it has no handler. */
822 static void send_char(object obj, int ch)
823 {
824     while (obj) {
825 	if ((obj->call) && (obj->call->keydown))
826 	    break;
827 	obj = obj->parent;
828     }
829     if (! obj)
830 	return;
831     if (ch == '\r')
832 	ch = '\n';
833     drawto(obj);
834     obj->call->keydown(obj, ch);
835     keystate = 0;
836 }
837 
838 long WINAPI
839 app_control_procedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
840 {
841     int prevent_activation = 0;
842     int key;
843     long result;
844     object obj, next;
845 
846     /* Find the library object associated with the hwnd. */
847     obj = find_by_handle(hwnd);
848     key = LOWORD(wParam);
849 
850     if (! obj) /* Not a library object ... */
851 	return 0; /* ... so do nothing. */
852     if (! obj->winproc)
853 	return 0; /* Nowhere to send events! */
854 
855     next = find_valid_sibling(obj->next);
856 
857     if (message == WM_KEYDOWN)
858 	handle_keydown(key);
859     else if (message == WM_KEYUP)
860 	handle_keyup(key);
861 
862     switch (message)
863     {
864     case WM_KEYDOWN:
865 	if (obj->kind == TextboxObject) {
866 	    handle_virtual_keydown(obj, key); /* call user's virtual key handler */
867 	    if ((key == VK_TAB) && (keystate & CtrlKey)) {
868 		SetFocus(next->handle);
869 		return 0;
870 	    }
871 	    break;
872 	}
873 	if (key == VK_TAB) {
874 	    SetFocus(next->handle);
875 	    return 0;
876 	}
877 	else if ((key == VK_RETURN) || (key == VK_ESCAPE)) {
878 	    send_char(obj, key);
879 	    return 0;
880 	}
881 	break;
882 
883     case WM_CHAR:
884 	switch (obj->kind) {
885 	case TextboxObject:
886 	    send_char(obj, key); /* call user's key handler */
887 	    break;
888 	case LabelObject:
889 	case ButtonObject:   case CheckboxObject:
890 	case RadioObject:    case ScrollbarObject:
891 	case ListboxObject:  case MultilistObject:
892 	case DroplistObject: case DropfieldObject:
893 	    if (key != ' ') {
894 		send_char(obj, key);
895 		return 0;
896 	    }
897 	case FieldObject:
898 	    if (key == '\t')
899 		return 0;
900 	    if ((key == '\n') || (key == ESC))
901 		return 0;
902 	    break;
903 	}
904 	break;
905 
906     case WM_SETFOCUS:
907 	if (obj->kind == RadioObject) {
908 	    /* Temporarily disable the control manually.
909 	     * We do this to work around the way Windows
910 	     * sends WM_COMMAND messages to radio buttons
911 	     * when we use TAB to set focus to them. */
912 #if USE_NATIVE_TOGGLES
913 	    if (isenabled(obj)) {
914 		obj->state &= ~GA_Enabled;
915 		prevent_activation = 1;
916 	    }
917 #endif
918 	}
919 	else if (obj->kind == FieldObject) {
920 #ifdef WIN32
921 	    sendmessage(hwnd, EM_SETSEL, 32767, 32767);
922 #else
923 	    sendmessage(hwnd, EM_SETSEL, 0, MAKELONG(32767,32767));
924 #endif /* WIN32 */
925 	}
926 	break;
927 
928 
929     case WM_CONTEXTMENU:
930 	handle_context_menu(obj, hwnd, LOWORD(lParam), HIWORD(lParam)); /* Handles right-click menus in, for example, edit controls */
931 	break;
932     }
933 
934     if (message == uFindReplaceMsg) {
935 	handle_findreplace(hwnd, (LPFINDREPLACE) lParam);
936 	return 0;
937     }
938 
939     result = CallWindowProc((obj->winproc), hwnd, message, wParam, lParam);
940 
941     /* Re-activate the control if necessary. */
942     if (prevent_activation)
943 	obj->state |= GA_Enabled;
944     return result;
945 }
946 
947 /*
948  *  Timer functions use a timer procedure not associated with a window.
949  *  We use this one procedure to handle both timer events and mouse-down
950  *  timer events. The mouse-down timer happens when the user has held
951  *  a mouse button down for longer than mouse_msec milliseconds, and
952  *  it causes the last mouse event to repeat.
953  */
954 UINT WINAPI
955 app_timer_procedure(HWND hwnd, UINT message, UINT tid, DWORD time)
956 {
957     object obj;
958     UINT id = LOWORD(tid);
959 
960     if ((id == 0) || (message != WM_TIMER))
961 	return 0;
962 
963     if (id == mouse_timer_id) {
964 	obj = frontwindow;
965 	if ((buttons == 0) || (! obj) || (! obj->call)
966 	    || (! obj->call->mouserepeat))
967 	    setmousetimer(0);
968 	else
969 	    obj->call->mouserepeat(obj, buttons, xy);
970     }
971     else if (id == timer_id) {
972 	if (do_timer)
973 	    do_timer(timer_data);
974     }
975 
976     return 0;
977 }
978 
979 /*
980  *  Set the timer function.
981  */
982 void settimerfn(timerfn timeout, void *data)
983 {
984     do_timer = timeout;
985     timer_data = data;
986 }
987 
988 /*
989  * Start the timer with a period of msec milliseconds.
990  */
991 int settimer(unsigned msec)
992 {
993     if (timer_id != 0) {
994 	KillTimer(0, timer_id);
995 	timer_id = 0;
996     }
997     if (msec == 0)
998 	timer_id = 0;
999     else {
1000 	timer_id = SetTimer(0, 0, (UINT) msec, app_timer_proc);
1001 	if (timer_id == 0)
1002 	    return 0;
1003     }
1004     return 1;
1005 }
1006 
1007 /*
1008  * Start the mouse-down timer with a period of msec milliseconds.
1009  *
1010  * Notes: setmousetimer() starts the mouse-down auto-repeat timer.
1011  *  The timer will not do anything unless the user holds down a
1012  *  mouse button for longer than msec milliseconds, in which case
1013  *  it will call the mouserepeat function associated with which ever
1014  *  window is currently active.
1015  *  Also, an interval of zero should stop the timer without
1016  *  destroying the previous interval recorded in mouse_msec.
1017  */
1018 int setmousetimer(unsigned msec)
1019 {
1020     if (mouse_timer_id != 0) {
1021 	KillTimer(0, mouse_timer_id);
1022 	mouse_timer_id = 0;
1023     }
1024     if (msec == 0) {
1025 	mouse_timer_id = 0;
1026 	return 1;
1027     }
1028     else {
1029 	mouse_timer_id = SetTimer(0, 0, (UINT) msec, app_timer_proc);
1030 	if (mouse_timer_id == 0)
1031 	    return 0;
1032     }
1033     mouse_msec = msec;
1034     return 1;
1035 }
1036 
1037 /*
1038  *  Delay execution for a given number of milliseconds.
1039  *  This is a blocking function which should be used sparingly.
1040  */
1041 void delay(unsigned msec)
1042 {
1043     unsigned long now;
1044     unsigned long stop;
1045 
1046     stop = msec;
1047     now = GetTickCount();
1048     stop += now;
1049     while(now < stop)
1050 	now = GetTickCount();
1051 }
1052 
1053 /*
1054  *  Report current time in milliseconds since initialisation of
1055  *  the graphics interface. Not reliable for timing events.
1056  */
1057 long currenttime(void)
1058 {
1059     return GetTickCount();
1060 }
1061 
1062 /*
1063  *  Intercept menu keys, since we don't always have accelerator tables.
1064  *  Return 1 if doing something which should not go to the winproc, else 0.
1065  */
1066 static int TranslateMenuKeys(MSG *msg)
1067 {
1068     int key = LOWORD(msg->wParam);
1069 
1070     /* Translate F10 from syskey to normal keydown message. */
1071     if ((key == VK_F10) && (msg->message == WM_SYSKEYDOWN))
1072 	msg->message = WM_KEYDOWN;
1073 
1074     /* Check for menu control keys. */
1075     /* disabled for R 0.9.1 to 1.9.1.
1076        Added proper AltGr fix for 2.5.0 */
1077 
1078     if ((GetKeyState(VK_CONTROL) & 0x8000)
1079 	&& (msg->message == WM_KEYDOWN)
1080 	&& !(GetKeyState(VK_RMENU) & 0x8000))
1081     {
1082 	/* ctrl-letter or ctrl-number is a menu key */
1083 	if (((key >= 'A') && (key <= 'Z')) ||
1084 	    ((key >= '0') && (key <= '9')))
1085 	{
1086 	    if (menus_active && handle_menu_key(key))
1087 		return 1;
1088 	}
1089     }
1090     return 0; /* 0 = pass to TranslateMessage and DispatchMessage */
1091 }
1092 
1093 /*
1094  *  Return zero if there are no messages, non-zero otherwise.
1095  */
1096 int peekevent(void)
1097 {
1098     return PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE);
1099 }
1100 
1101 /*
1102  *  Wait for the next message
1103  */
1104 void waitevent(void)
1105 {
1106     if (!peekevent()) WaitMessage();
1107 }
1108 
1109 
1110 /*
1111  *  Handle one event.
1112  */
1113 int doevent(void)
1114 {
1115     int result = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
1116     HWND modeless = get_modeless();
1117 
1118     if (result)
1119     {
1120 	/*		del_all_contexts();*/
1121 	if (TranslateMenuKeys(&msg))
1122 	    return result;
1123 	if ((hwndClient) &&
1124 	    TranslateMDISysAccel(hwndClient, &msg))
1125 	    return result;
1126 	if ((hwndFrame) && (hAccel) &&
1127 	    TranslateAccelerator(hwndFrame, hAccel, &msg))
1128 	    return result;
1129 	if ((modeless) && IsDialogMessage(modeless, &msg))
1130 	    return result;
1131 	TranslateMessage(&msg);
1132 	DispatchMessage(&msg);
1133     }
1134     deletion_traversal();
1135     if ((active_windows <= 0) || (msg.message == WM_QUIT))
1136 	return 0;
1137     else
1138 	return 1;
1139 }
1140 
1141 /*
1142  *  Handle events until the program has finished receiving events,
1143  *  or until there are no windows open to receive events.
1144  */
1145 void gamainloop(void)
1146 {
1147     while (doevent())
1148 	continue;
1149 }
1150 
1151 /*
1152  *  Finish all pending graphics requests.
1153  */
1154 void drawall(void)
1155 {
1156     /* Do nothing here. */
1157 }
1158 
1159 /*
1160  *  Initialise the timer and make some instance 'thunks' for
1161  *  the event callbacks.
1162  */
1163 PROTECTED
1164 void init_events(void)
1165 {
1166     uFindReplaceMsg = RegisterWindowMessage(FINDMSGSTRING);
1167     app_timer_proc = (TIMERPROC) MakeProcInstance((FARPROC) app_timer_procedure,
1168 						  this_instance);
1169     setmousetimer(100); /* start 1/10 second mouse-down auto-repeat */
1170 
1171     app_control_proc = (WNDPROC) MakeProcInstance((FARPROC) app_control_procedure,
1172 						  this_instance);
1173 }
1174 
1175 /*
1176  *  Stop all timers and release the memory requirements of
1177  *  the proc instance 'thunks'.
1178  */
1179 PROTECTED
1180 void finish_events(void)
1181 {
1182     settimer(0);
1183     setmousetimer(0);
1184 }
1185