/* * GraphApp - Cross-Platform Graphics Programming Library. * * File: events.c -- winprocs and timers are contained here. * Platform: Windows Version: 2.35 Date: 1998/04/04 * * Version: 1.00 Changes: Original version by Lachlan Patrick. * Version: 2.00 Changes: New class system implemented. * Version: 2.20 Changes: Non-native buttons supported. * Version: 2.22 Changes: 32-bit fix by Wim Rijnders. * Version: 2.35 Changes: New delayed deletion technique. */ /* Copyright (C) 1993-1998 Lachlan Patrick This file is part of GraphApp, a cross-platform C graphics library. GraphApp is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License. GraphApp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the file COPYLIB.TXT for details. */ /* Copyright (C) 2004, 2009 The R Foundation Copyright (C) 2013-2023 The R Core Team Changes for R, Chris Jackson, 2004 Handle find-and-replace modeless dialogs Menu shortcut keys re-enabled Handle WM_CONTEXTMENU events for right-clicking on a (rich) edit control Handle mouse wheel scrolling Remove assumption that current->dest is non-NULL Add waitevent() function Caret handling improvements (see comments in controls.c) Allow to leave a dropfield (combo box) with TAB key Allow navigating to previous controls with Shift+TAB key Path length limitations */ #include "internal.h" /* * Library variables. */ static timerfn do_timer = NULL; static void *timer_data = NULL; static MSG msg; PROTECTED int keystate = 0; /* state of shift, ctrl and alt keys */ /* a user-timer and a mouse-down timer function can be started */ static UINT timer_id = 0; static UINT mouse_timer_id = 0; /* buttons and xy are used to record the state of the mouse */ static int buttons = 0; static point xy; static long mouse_msec = 0; TIMERPROC app_timer_proc; WNDPROC app_control_proc; WNDPROC edit_control_proc; static object frontwindow = NULL; /* the window receiving events */ static UINT uFindReplaceMsg; // message identifier for FINDMSGSTRING /* Surrogate Pairs MACRO */ #define SURROGATE_PAIRS_HI_MIN ((uint16_t)0xd800) #define SURROGATE_PAIRS_HI_MAX ((uint16_t)0xdbff) #define SURROGATE_PAIRS_LO_MIN ((uint16_t)0xdc00) #define SURROGATE_PAIRS_LO_MAX ((uint16_t)0xdfff) #define SURROGATE_PAIRS_BIT_SZ ((uint32_t)10) #define SURROGATE_PAIRS_MASK (((uint16_t)1 << SURROGATE_PAIRS_BIT_SZ)-1) #define IsSurrogatePairsHi(_h) (SURROGATE_PAIRS_HI_MIN == \ ((uint16_t)(_h) &~ (uint16_t)SURROGATE_PAIRS_MASK )) #define IsSurrogatePairsLo(_l) (SURROGATE_PAIRS_LO_MIN == \ ((uint16_t)(_l) &~ (uint16_t)SURROGATE_PAIRS_MASK )) /* * Call the relevent mouse handler function. */ static void handle_mouse(object obj, HWND hwnd, UINT message, int param, int x, int y) { menu m; POINT wp; HWND hw; int dble = 1; xy.x = x; xy.y = y; buttons = 0; if (!obj) return; if (param & MK_LBUTTON) buttons |= LeftButton; if (param & MK_MBUTTON) buttons |= MiddleButton; if (param & MK_RBUTTON) buttons |= RightButton; /* dispatch the mouse event to the relevent handler */ if (obj && obj->drawstate && obj->drawstate->crsr) SetCursor((HCURSOR)obj->drawstate->crsr->handle); switch (message) { case WM_MOUSEMOVE: if (obj->call) { if (buttons) { if (obj->call->mousedrag) obj->call->mousedrag(obj, buttons, xy); } else if (obj->call->mousemove) obj->call->mousemove(obj, buttons, xy); } break; case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: dble = 0; setmousetimer(mouse_msec); /* restart timer */ /* fall through to next case */ case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK: if (dble) buttons |= DblClick; if ((obj->flags & ChildWindow) && (obj->kind != LabelObject)) SetFocus(hwnd); if (obj->flags & TrackMouse) SetCapture(hwnd); if (buttons == RightButton) { m = obj->popup; hw = hwnd; if (!m) { m = obj->parent->popup; hw = (HWND) obj->parent->handle; } if (m) { wp.x = x; wp.y = y; ClientToScreen(hw, (LPPOINT) &wp); if (m->action) m->action(m); TrackPopupMenu(m->handle, TPM_LEFTALIGN| TPM_LEFTBUTTON|TPM_RIGHTBUTTON, wp.x,wp.y, 0, obj->handle, NULL); break; } } if (obj->call && obj->call->mousedown) obj->call->mousedown(obj, buttons, xy); break; case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: if ((obj->flags & TrackMouse) && (buttons == 0)) ReleaseCapture(); if (obj->call && obj->call->mouseup) obj->call->mouseup(obj, buttons, xy); break; } } /* * Some WM_KEYDOWN VK_* messages call the keyaction associated * with a window. */ static void handle_virtual_keydown(object obj, int param) { if ((! obj->call) || (! obj->call->keyaction)) return; /* translate arrow key combinations into Unicode arrow symbols */ if ((param >= VK_LEFT) && (param <= VK_DOWN)) { param += (LEFT - VK_LEFT); } /* translate functions keys into Unicode circled numbers */ else if ((param >= VK_F1) && (param <= VK_F10)) { param += (F1 - VK_F1); } /* translate other keyboard keys into Unicode 'equivalents' */ else switch (param) { case VK_PRIOR: param = PGUP; break; case VK_NEXT: param = PGDN; break; case VK_END: param = END; break; case VK_HOME: param = HOME; break; case VK_INSERT: param = INS; break; case VK_DELETE: param = DEL; break; default: return; /* do nothing */ } drawto(obj); obj->call->keyaction(obj, param); } static void handle_keydown(int param) { if (param == VK_SHIFT) keystate |= ShiftKey; else if (param == VK_CONTROL) keystate |= CtrlKey; else if (param == VK_MENU) keystate |= AltKey; } static void handle_keyup(int param) { if (param == VK_SHIFT) keystate &= ~ShiftKey; else if (param == VK_CONTROL) keystate &= ~CtrlKey; else if (param == VK_MENU) keystate &= ~AltKey; } /* * Handle char messages. */ static void handle_char(object obj, int ch) { if (obj->call && obj->call->keydown) { if (ch == '\r') /* carriage return becomes newline */ ch = '\n'; drawto(obj); obj->call->keydown(obj, ch); } } static int showMDIToolbar = 1; void toolbar_show(void) { showMDIToolbar = 1; SendMessage(hwndFrame,WM_PAINT, (WPARAM) 0, (LPARAM) 0); } void toolbar_hide(void) { showMDIToolbar = 0; SendMessage(hwndFrame,WM_PAINT, (WPARAM) 0, (LPARAM) 0); } static void handle_mdiframesize(void) { HWND tool=NULL ,status=NULL; RECT rFrame,rToolbar; int fw, fh, th=0, sh=0; GetClientRect(hwndFrame,&rFrame); fw = rFrame.right-rFrame.left; fh = rFrame.bottom-rFrame.top; if (showMDIToolbar && MDIToolbar) { tool = (HWND)MDIToolbar->handle; GetWindowRect(tool,&rToolbar); th = rToolbar.bottom-rToolbar.top; } if (MDIStatus) { status = (HWND)MDIStatus; GetWindowRect(status,&rToolbar); sh = rToolbar.bottom-rToolbar.top; } MoveWindow(hwndClient,0,th+1,fw,fh-sh-th-1,TRUE); if (tool) { MoveWindow(tool,1,0,fw-2,th,TRUE); show(MDIToolbar); } if (status) { MoveWindow(status,1,fh-sh,fw-2,sh,TRUE); } SetFocus((HWND)SendMessage(hwndClient, WM_MDIGETACTIVE,(WPARAM)0,(LPARAM) 0)); } /* * The window is being resized for some reason. */ static void handle_resize(object obj) { if (obj->call && obj->call->resize) { drawto(obj); obj->call->resize(obj, rect(0,0,obj->rect.width,obj->rect.height)); } deletion_traversal(); /* We may be called again before returning to doevent */ } /* * The window is being redrawn for some reason. */ static void handle_redraw(object obj, HWND hwnd) { PAINTSTRUCT ps; if (obj==MDIFrame) handle_mdiframesize(); del_context(obj); add_context(obj, BeginPaint(hwnd, &ps), NULL); if (ps.fErase) clear(obj); draw(obj); EndPaint(hwnd, &ps); remove_context(obj); dc = 0; } /* * Hide an application window, or call close() function if possible. */ static void handle_close(object obj) { if (obj->call && obj->call->close) { drawto(obj); obj->call->close(obj); } else { hide(obj); } } static void handle_destroy(object obj) { /* drawto(obj); del_object(obj); */ } static void handle_focus(object obj, int gained_focus) { if (gained_focus) { obj->state |= GA_Focus; if ((!obj->caretexists) && obj->caretwidth == 0 && (obj->flags & SetUpCaret)) { /* set up a dummy caret to help NVDA initialization, see comment in console.c, newconsole */ setcaret(obj, 0, 0, 2, 10); showcaret(obj, 1); } if (obj->caretwidth < 0) { /* creates the caret object and restores obj->caretshowing */ setcaret(obj, obj->caretx, obj->carety, -obj->caretwidth, obj->caretheight); if (obj->caretshowing) /* redraw the caret in case it has been destroyed by a recursive redraw of the screen, e.g. via disable() when using the menu; such a redraw can happen while waiting for keyboard input */ ShowCaret(obj->handle); } } else { obj->state &= ~GA_Focus; if (obj->caretwidth > 0) /* destroys the caret object and preserves obj->caretshowing */ setcaret(obj, obj->caretx, obj->carety, -obj->caretwidth, obj->caretheight); } if ((! USE_NATIVE_BUTTONS) && (obj->kind == ButtonObject)) InvalidateRect(obj->handle, NULL, 0); if (obj->call && obj->call->focus) obj->call->focus(obj); } /* * Handle scrollbars. Designed to also work with normal window * scrollbars. */ static void handle_scroll(object obj, HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int size_shown = 10; int max_value = 100; int where = 0; int prev = 0; int which = 0; /* we need to look at the recorded values */ max_value = obj->max; size_shown = obj->size; if (obj->kind != WindowObject) which = SB_CTL; else if (message == WM_VSCROLL) which = SB_VERT; else if (message == WM_HSCROLL) { which = SB_HORZ; max_value = obj->xmax; size_shown = obj->xsize; } prev = where = GetScrollPos(hwnd, which); /* next we look at wParam to see what happened */ switch(LOWORD(wParam)) { case SB_PAGEDOWN: where += (size_shown-1); /* fall through to next case */ case SB_LINEDOWN: where = min(max_value, where+1); break; case SB_PAGEUP: where -= (size_shown-1); /* fall through to next case */ case SB_LINEUP: where = max(0, where-1); break; case SB_TOP: where = 0; break; case SB_BOTTOM: where = max_value; break; case SB_THUMBPOSITION: case SB_THUMBTRACK: #ifdef WIN32 { /* The message only contains a 16 bit position. We need to query to get 32 bits. */ SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_TRACKPOS; if (GetScrollInfo(hwnd, which, &si)) where = si.nTrackPos; else where = HIWORD(wParam); /* Just in case this mask is not supported. Not sure when it arrived... */ } #else where = LOWORD(lParam); #endif break; default: break; } /* check if something happened */ if (prev == where) return; /* now we reset the scrollbar's values */ SetScrollPos(hwnd, which, where, 1); if (message == WM_HSCROLL) { where = -(where+1); } setvalue(obj, where); activatecontrol(obj); } /* * Perform some brush manipulation to handle background colours * in native checkboxes and radio buttons. */ #if USE_NATIVE_TOGGLES #ifdef WM_CTLCOLOR static void handle_colour(HDC dc, object obj) { rgb fg, bg; COLORREF wincolour; if (obj->drawstate) fg = obj->drawstate->hue; else fg = obj->fg; wincolour = RGB((fg&Red)>>16,(fg&Green)>>8,(fg&Blue)); SetTextColor(dc, wincolour); bg = obj->bg; wincolour = RGB((bg&Red)>>16,(bg&Green)>>8,(bg&Blue)); SetBkColor(dc, wincolour); fix_brush(dc, obj, obj->bgbrush); } #endif #endif static void handle_drop(object obj, HANDLE dropstruct) { if (obj->call && obj->call->drop) { int len = DragQueryFile(dropstruct, 0, NULL, 0); char *dfilename = (char*)malloc(len + 1); if (!dfilename) { DragFinish(dropstruct); return; } DragQueryFile(dropstruct, 0, dfilename, len + 1); DragFinish(dropstruct); obj->call->drop(obj, dfilename); free(dfilename); } } /* Handle a right-click context menu in non-window objects such as text areas */ static void handle_context_menu(object obj, HWND hwnd, int x, int y) { menu m = obj->popup; HWND hw = hwnd; POINT wp; if (!m) { m = obj->parent->popup; hw = (HWND) obj->parent->handle; } if (m) { wp.x = x; wp.y = y; if (m->action) m->action(m); TrackPopupMenu(m->handle, TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, wp.x, wp.y, 0, hw, NULL); } } /* * Shared window procedure code. The pass variable is initially zero. * It can be set to non-zero in this procedure if we wish to pass * the event to the default Windows winprocs. */ static long handle_message(HWND hwnd, UINT message, WPARAM wParam, LONG lParam, int *pass) { object obj; WPARAM upDown; static unsigned short altnpad = 0; /* Find the library object associated with the hwnd. */ obj = find_by_handle(hwnd); if (! obj) { /* Not a library object ... */ *pass = 1; /* ... so pass the event. */ return 0; } frontwindow = obj; /* Needed for auto-mousedowns. */ /* Handle mouse messages. */ if ((message >= WM_MOUSEMOVE) && (message <= WM_MBUTTONDBLCLK)) { handle_mouse(obj, hwnd, message, LOWORD(wParam), LOWORD(lParam), HIWORD(lParam)); return 0; } /* Handle other messages. */ switch (message) { case WM_MOUSEWHEEL: /* convert MOUSEWHEEL messages to VSCROLL. Scroll by pairs of lines */ upDown = (short)HIWORD(wParam) > 0 ? SB_LINEUP : SB_LINEDOWN; PostMessage(hwnd, WM_VSCROLL, upDown, 0); PostMessage(hwnd, WM_VSCROLL, upDown, 0); break; case WM_SYSKEYDOWN: if(obj->flags & UseUnicode) if(VK_NUMPAD0 <= LOWORD(wParam) && LOWORD(wParam) <= VK_NUMPAD9) { altnpad *= 10; altnpad += LOWORD(wParam) & 0xf; } break; case WM_KEYDOWN: /* record state of shift and control keys */ handle_keydown(LOWORD(wParam)); handle_virtual_keydown(obj, LOWORD(wParam)); if((obj->flags & UseUnicode) && LOWORD(wParam) == VK_PACKET) { /* This handling of VK_PACKET is inspired by gdkevents-win32.c. Handling the WM_CHAR messages, instead, would be more reliable, but Unicode windows were designed to not handle those, so it would require bigger changes. VK_PACKET messages are used e.g. by Dasher via SendInput/KEYEVENTF_UNICODE. */ int result; BYTE sta[256]; wchar_t wcs[3]; static wchar_t high = L'\0'; GetKeyboardState(sta); result = ToUnicodeEx(VK_PACKET, HIWORD(lParam), sta, wcs, /* 3 */ sizeof(wcs)/sizeof(wchar_t), 1, 0); if (result == 1) { if (IsSurrogatePairsHi(wcs[0])) high = wcs[0]; else if (IsSurrogatePairsLo(wcs[0]) && high != L'\0') { /* Surrogate pairs block */ handle_char(obj, L'?'); handle_char(obj, L'?'); high = L'\0'; } else { handle_char(obj, wcs[0]); high = L'\0'; } } } else if(obj->flags & UseUnicode) { BYTE sta[256]; wchar_t wcs[3]; HKL dwhkl; static wchar_t deadkey = L'\0'; int result; dwhkl = GetKeyboardLayout((DWORD) 0); GetKeyboardState(sta); result = ToUnicodeEx(wParam, lParam, sta, wcs, /* 3 */ sizeof(wcs)/sizeof(wchar_t), 0, dwhkl); if(result == 1) { if(deadkey != L'\0') { wchar_t wcs_in[3]; wchar_t wcs_out[3]; wcs_in[0] = wcs[0]; /* convert an accent to a combining version */ switch(deadkey) { case 0x5e: /* circumflex */ wcs_in[1] = 0x302; break; case 0x60: /* grave accent */ wcs_in[1] = 0x300; break; case 0xa8: /* diaeresis */ case 0x22: wcs_in[1] = 0x308; break; case 0xb4: /* acute accent */ case 0x27: wcs_in[1] = 0x301; break; case 0xb8: /* cedilla */ wcs_in[1] = 0x327; break; case 0x2c7: /* caron */ wcs_in[1] = 0x30c; break; case 0xaf: /* macron */ wcs_in[1] = 0x304; break; case 0x2d8: /* breve */ wcs_in[1] = 0x306; break; case 0x2dd: /* double accute */ wcs_in[1] = 0x30b; break; case 0x2db: /* ogonek */ wcs_in[1] = 0x328; break; case 0x7e: /* tilde */ wcs_in[1] = 0x303; break; case 0x2d9: /* dot above */ wcs_in[1] = 0x307; break; case 0xb0: /* ring above */ wcs_in[1] = 0x30a; break; default: wcs_in[1] = deadkey; break; } wcs_in[2] = L'\0'; /* from accent char to unicode */ int nchars = FoldStringW(MAP_PRECOMPOSED, wcs_in, 3, wcs_out, 3); if (nchars == 2) handle_char(obj, wcs_out[0]); else if (nchars == 3 && wcs_in[0] == L' ' && wcs_out[0] == wcs_in[0] && wcs_out[1] == wcs_in[1]) /* accent followed by space prints the accent */ handle_char(obj, deadkey); else if (nchars > 2) { /* accent followed by non-composable character, matches R 4.1 behavior with non-Unicode Window */ handle_char(obj, deadkey); handle_char(obj, wcs_out[0]); } /* deadchar convert failure to skip. */ } else handle_char(obj, wcs[0]); deadkey = L'\0'; } else if (result != 0) { if (deadkey == L'\0') deadkey = wcs[0]; else { /* cancel the effect of previous dead key and print non-combining versions of both */ handle_char(obj, deadkey); handle_char(obj, wcs[0]); deadkey = L'\0'; } } } break; case WM_KEYUP: /* record state of shift and control keys */ if(obj->flags & UseUnicode) if(LOWORD(wParam) == VK_MENU && altnpad) { handle_char(obj, altnpad); altnpad = 0; } handle_keyup(LOWORD(wParam)); break; case WM_CHAR: /* SBCS Only */ if(obj->flags & UseUnicode) return 0; else { handle_char(obj, LOWORD(wParam)); return 0; } case WM_IME_COMPOSITION: /* DBCS Only */ if (lParam & GCS_RESULTSTR) { /* is fixed multiGAbyte string */ HIMC himc = ImmGetContext(hwnd); wchar_t buf[80]; wchar_t *p; int i; int len; if(obj->flags & UseUnicode) { /* len is GAbyte */ len = ImmGetCompositionStringW(himc, GCS_RESULTSTR, NULL,0); if(NULL == (p=( len > sizeof(buf)-1) ? calloc(len,sizeof(char)) : buf)) { len = sizeof(buf); p = buf; } ImmGetCompositionStringW(himc,GCS_RESULTSTR, p, len); ImmReleaseContext(hwnd,himc); /* Surrogate Pairs Block */ for(i = 0; i < (len/sizeof(wchar_t)); i++) if(IsSurrogatePairsHi(p[i]) && i+1 < (len/sizeof(wchar_t)) && IsSurrogatePairsLo(p[i+1]) ) { handle_char(obj, L'?'); handle_char(obj, L'?'); i++; } else handle_char(obj, p[i]); if(p != buf) free(p); return 0; } } break; case WM_SETFOCUS: handle_focus(obj, 1); break; case WM_KILLFOCUS: handle_focus(obj, 0); break; case WM_PAINT: handle_redraw(obj, hwnd); return 0; case WM_INITMENUPOPUP: if (HIWORD(lParam)) /* true if system menu */ return 0; /* else fall through */ case WM_INITMENU: adjust_menu(wParam); break; case WM_MOVE: obj->rect.x = LOWORD(lParam); obj->rect.y = HIWORD(lParam); break; case WM_SIZE: obj->rect.width = LOWORD(lParam); obj->rect.height = HIWORD(lParam); handle_resize(obj); break; case WM_ACTIVATE: /* Keep track of which window is in front. */ if (LOWORD(wParam) != WA_INACTIVE) move_to_front(obj); break; case WM_QUERYENDSESSION: handle_close(obj); return 1L; /* ensure Windows can terminate */ case WM_CLOSE: handle_close(obj); return 0; case WM_DESTROY: handle_destroy(obj); break; /*case WM_SYSCOMMAND:*/ case WM_COMMAND: if (LOWORD(wParam) >= MinDocID) break; /* MDI Client window will handle it */ else if (LOWORD(wParam) >= MinChildID) { #ifdef WIN32 handle_control((HWND) (intptr_t) lParam, HIWORD(wParam)); #else handle_control((HWND) LOWORD(lParam), HIWORD(lParam)); #endif /* WIN32 */ } else if ((LOWORD(wParam) >= MinMenuID) && menus_active) handle_menu_id(LOWORD(wParam)); break; case WM_VSCROLL: case WM_HSCROLL: #ifdef WIN32 if (lParam != 0) { /* scrollbar object */ hwnd = (HWND) (intptr_t) lParam; #else if (HIWORD(lParam) != 0) { /* scrollbar object */ hwnd = (HWND) HIWORD(lParam); #endif /* WIN32 */ obj = find_by_handle(hwnd); if (! obj) return 0; } handle_scroll(obj, hwnd, message, wParam, lParam); return 0; #if USE_NATIVE_TOGGLES #ifdef WM_CTLCOLOR case WM_CTLCOLOR: #ifdef WIN32 hwnd = (HWND) lParam; #else hwnd = (HWND) LOWORD(lParam); #endif /* WIN32 */ obj = find_by_handle(hwnd); if (! obj) break; if ((obj->kind != CheckboxObject) && (obj->kind != RadioObject)) break; handle_colour((HDC) wParam, obj); return (LRESULT) obj->bgbrush; #endif #endif case WM_IME_STARTCOMPOSITION: if(obj->call && obj->call->im) { HIMC himc ; LOGFONT lf; font f; COMPOSITIONFORM cf; himc = ImmGetContext(hwnd); obj->call->im(obj, &f, (void *) &cf.ptCurrentPos); GetObject(f->handle, sizeof(LOGFONT), &lf); ImmSetCompositionFont(himc, &lf); cf.dwStyle = CFS_POINT; ImmSetCompositionWindow(himc, &cf); ImmReleaseContext(hwnd, himc); } break; case WM_DROPFILES: handle_drop(obj, (HANDLE) wParam); } /* If we got this far the event must be passed along * to the default Windows event handling procedures. */ *pass = 1; return 0; } /* * Window procedures call a generic window handling routine. * We need three window procedures since the different calls * tell us which default window procedure to pass the messages * to if we don't wish to handle a message. * If we were to use only one window procedure, we would have to * have a way of determining which is the default window proc * for a window from just knowing the hwnd (which may or may not * belong to us). */ LRESULT WINAPI app_win_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { long result; int pass = 0; result = handle_message(hwnd, message, wParam, lParam, &pass); if (pass) result = DefWindowProc(hwnd, message, wParam, lParam); return result; } LRESULT WINAPI app_doc_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { long result; int pass = 0; object obj; if ((message==WM_MDIACTIVATE) && ((HWND)lParam==hwnd)) { if (MDIToolbar) hide(MDIToolbar); obj = find_by_handle(hwnd); MDIToolbar = (obj) ? obj->toolbar : NULL; handle_mdiframesize(); if (obj && obj->menubar) { menu mdi = (obj->menubar)->menubar; SendMessage(hwndClient, WM_MDISETMENU, (WPARAM)obj->menubar->handle, (LPARAM)(mdi?(mdi->handle):0)); DrawMenuBar(hwndFrame); } if (obj) updatestatus(obj->status); RedrawWindow(hwndFrame,NULL,NULL, RDW_UPDATENOW|RDW_ALLCHILDREN); SetFocus(hwnd); return 1; } result = handle_message(hwnd, message, wParam, lParam, &pass); if (pass) result = DefMDIChildProc(hwnd, message, wParam, lParam); return result; } LRESULT WINAPI app_work_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { long result; int pass = 0; result = handle_message(hwnd, message, wParam, lParam, &pass); if (pass) result = DefFrameProc(hwnd, hwndClient, message, wParam, lParam); return result; } /* * To handle controls correctly, we replace each control's event * handling procedure with our own when we create it. We handle * certain events ourselves, and pass the rest to the default * routines. * Things we do here include: allowing the TAB key to change * input focus to the next control; for one-line text fields * pressing return causes the event to be sent to the parent window. */ /* Send a char to an object, or its parent if it has no handler. */ static void send_char(object obj, int ch) { while (obj) { if ((obj->call) && (obj->call->keydown)) break; obj = obj->parent; } if (! obj) return; if (ch == '\r') ch = '\n'; drawto(obj); obj->call->keydown(obj, ch); keystate = 0; } long WINAPI app_control_procedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int prevent_activation = 0; int key; long result; object obj, next, prev; /* Find the library object associated with the hwnd. */ obj = find_by_handle(hwnd); key = LOWORD(wParam); if (! obj) /* Not a library object ... */ return 0; /* ... so do nothing. */ if (! obj->winproc) return 0; /* Nowhere to send events! */ next = find_next_valid_sibling(obj->next); prev = find_prev_valid_sibling(obj->prev); if (message == WM_KEYDOWN) handle_keydown(key); else if (message == WM_KEYUP) handle_keyup(key); switch (message) { case WM_KEYDOWN: if (obj->kind == TextboxObject) { handle_virtual_keydown(obj, key); /* call user's virtual key handler */ if (key == VK_TAB) { if (keystate & ShiftKey) { SetFocus(prev->handle); return 0; } else if (keystate & CtrlKey) { SetFocus(next->handle); return 0; } } break; } if (key == VK_TAB) { if (keystate & ShiftKey) SetFocus(prev->handle); else SetFocus(next->handle); return 0; } else if ((key == VK_RETURN) || (key == VK_ESCAPE)) { send_char(obj, key); return 0; } break; case WM_CHAR: switch (obj->kind) { case TextboxObject: send_char(obj, key); /* call user's key handler */ break; case LabelObject: case ButtonObject: case CheckboxObject: case RadioObject: case ScrollbarObject: case ListboxObject: case MultilistObject: case DroplistObject: case DropfieldObject: if (key != ' ') { send_char(obj, key); return 0; } case FieldObject: if (key == '\t') return 0; if ((key == '\n') || (key == ESC)) return 0; break; } break; case WM_SETFOCUS: if (obj->kind == RadioObject) { /* Temporarily disable the control manually. * We do this to work around the way Windows * sends WM_COMMAND messages to radio buttons * when we use TAB to set focus to them. */ #if USE_NATIVE_TOGGLES if (isenabled(obj)) { obj->state &= ~GA_Enabled; prevent_activation = 1; } #endif } else if (obj->kind == FieldObject) { #ifdef WIN32 sendmessage(hwnd, EM_SETSEL, 32767, 32767); #else sendmessage(hwnd, EM_SETSEL, 0, MAKELONG(32767,32767)); #endif /* WIN32 */ } break; case WM_CONTEXTMENU: handle_context_menu(obj, hwnd, LOWORD(lParam), HIWORD(lParam)); /* Handles right-click menus in, for example, edit controls */ break; } if (message == uFindReplaceMsg) { handle_findreplace(hwnd, (LPFINDREPLACE) lParam); return 0; } result = CallWindowProc((obj->winproc), hwnd, message, wParam, lParam); /* Re-activate the control if necessary. */ if (prevent_activation) obj->state |= GA_Enabled; return result; } long WINAPI edit_control_procedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int key; object obj, next, prev; HANDLE hwndCombo; /* Find the library (dropfield/combo box) object associated with the hwnd. */ hwndCombo = GetParent(hwnd); if (! hwndCombo) return 0; obj = find_by_handle(hwndCombo); key = LOWORD(wParam); if (! obj) /* Not a library object ... */ return 0; /* ... so do nothing. */ if (! obj->edit_winproc) return 0; /* Nowhere to send events! */ next = find_next_valid_sibling(obj->next); prev = find_prev_valid_sibling(obj->prev); if (message == WM_KEYDOWN) handle_keydown(key); else if (message == WM_KEYUP) handle_keyup(key); switch (message) { case WM_KEYDOWN: if (key == VK_TAB) { if (keystate & ShiftKey) SetFocus(prev->handle); else SetFocus(next->handle); return 0; } break; case WM_KEYUP: case WM_CHAR: if (key == VK_TAB) return 0; break; } return CallWindowProc((obj->edit_winproc), hwnd, message, wParam, lParam); } /* * Timer functions use a timer procedure not associated with a window. * We use this one procedure to handle both timer events and mouse-down * timer events. The mouse-down timer happens when the user has held * a mouse button down for longer than mouse_msec milliseconds, and * it causes the last mouse event to repeat. */ UINT WINAPI app_timer_procedure(HWND hwnd, UINT message, UINT tid, DWORD time) { object obj; UINT id = LOWORD(tid); if ((id == 0) || (message != WM_TIMER)) return 0; if (id == mouse_timer_id) { obj = frontwindow; if ((buttons == 0) || (! obj) || (! obj->call) || (! obj->call->mouserepeat)) setmousetimer(0); else obj->call->mouserepeat(obj, buttons, xy); } else if (id == timer_id) { if (do_timer) do_timer(timer_data); } return 0; } /* * Set the timer function. */ void settimerfn(timerfn timeout, void *data) { do_timer = timeout; timer_data = data; } /* * Start the timer with a period of msec milliseconds. */ int settimer(unsigned msec) { if (timer_id != 0) { KillTimer(0, timer_id); timer_id = 0; } if (msec == 0) timer_id = 0; else { timer_id = SetTimer(0, 0, (UINT) msec, app_timer_proc); if (timer_id == 0) return 0; } return 1; } /* * Start the mouse-down timer with a period of msec milliseconds. * * Notes: setmousetimer() starts the mouse-down auto-repeat timer. * The timer will not do anything unless the user holds down a * mouse button for longer than msec milliseconds, in which case * it will call the mouserepeat function associated with which ever * window is currently active. * Also, an interval of zero should stop the timer without * destroying the previous interval recorded in mouse_msec. */ int setmousetimer(unsigned msec) { if (mouse_timer_id != 0) { KillTimer(0, mouse_timer_id); mouse_timer_id = 0; } if (msec == 0) { mouse_timer_id = 0; return 1; } else { mouse_timer_id = SetTimer(0, 0, (UINT) msec, app_timer_proc); if (mouse_timer_id == 0) return 0; } mouse_msec = msec; return 1; } /* * Delay execution for a given number of milliseconds. * This is a blocking function which should be used sparingly. */ void delay(unsigned msec) { unsigned long now; unsigned long stop; stop = msec; now = GetTickCount(); stop += now; while(now < stop) now = GetTickCount(); } /* * Report current time in milliseconds since initialisation of * the graphics interface. Not reliable for timing events. */ long currenttime(void) { return GetTickCount(); } /* * Intercept menu keys, since we don't always have accelerator tables. * Return 1 if doing something which should not go to the winproc, else 0. */ static int TranslateMenuKeys(MSG *msg) { int key = LOWORD(msg->wParam); /* Translate F10 from syskey to normal keydown message. */ if ((key == VK_F10) && (msg->message == WM_SYSKEYDOWN)) msg->message = WM_KEYDOWN; /* Check for menu control keys. */ /* disabled for R 0.9.1 to 1.9.1. Added proper AltGr fix for 2.5.0 */ if ((GetKeyState(VK_CONTROL) & 0x8000) && (msg->message == WM_KEYDOWN) && !(GetKeyState(VK_RMENU) & 0x8000)) { /* ctrl-letter or ctrl-number is a menu key */ if (((key >= 'A') && (key <= 'Z')) || ((key >= '0') && (key <= '9'))) { if (menus_active && handle_menu_key(key)) return 1; } } return 0; /* 0 = pass to TranslateMessage and DispatchMessage */ } /* * Return zero if there are no messages, non-zero otherwise. */ int peekevent(void) { return PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE); } /* * Wait for the next message */ void waitevent(void) { if (!peekevent()) WaitMessage(); } /* * Handle one event. */ int doevent(void) { int result = PeekMessage(&msg, 0, 0, 0, PM_REMOVE); HWND modeless = get_modeless(); if (result) { /* del_all_contexts();*/ if (TranslateMenuKeys(&msg)) return result; if ((hwndClient) && TranslateMDISysAccel(hwndClient, &msg)) return result; if ((hwndFrame) && (hAccel) && TranslateAccelerator(hwndFrame, hAccel, &msg)) return result; if ((modeless) && IsDialogMessage(modeless, &msg)) return result; TranslateMessage(&msg); DispatchMessage(&msg); } deletion_traversal(); if ((active_windows <= 0) || (msg.message == WM_QUIT)) return 0; else return 1; } /* * Handle events until the program has finished receiving events, * or until there are no windows open to receive events. */ void gamainloop(void) { while (doevent()) continue; } /* * Finish all pending graphics requests. */ void drawall(void) { /* Do nothing here. */ } /* * Initialise the timer and make some instance 'thunks' for * the event callbacks. */ PROTECTED void init_events(void) { uFindReplaceMsg = RegisterWindowMessage(FINDMSGSTRING); app_timer_proc = (TIMERPROC) MakeProcInstance((FARPROC) app_timer_procedure, this_instance); setmousetimer(100); /* start 1/10 second mouse-down auto-repeat */ app_control_proc = (WNDPROC) MakeProcInstance((FARPROC) app_control_procedure, this_instance); edit_control_proc = (WNDPROC) MakeProcInstance((FARPROC) edit_control_procedure, this_instance); } /* * Stop all timers and release the memory requirements of * the proc instance 'thunks'. */ PROTECTED void finish_events(void) { settimer(0); setmousetimer(0); }