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