| /* |
| * 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 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 |
| */ |
| |
| #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; |
| |
| 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 |= Focus; |
| if (obj->caretwidth < 0) { |
| setcaret(obj, 0,0, -obj->caretwidth, obj->caretheight); |
| showcaret(obj, 1); |
| } |
| } else { |
| obj->state &= ~Focus; |
| if (obj->caretwidth > 0) { |
| setcaret(obj, 0,0, -obj->caretwidth, obj->caretheight); |
| showcaret(obj, 0); |
| } |
| } |
| 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 char dfilename[MAX_PATH + 1]; |
| static void handle_drop(object obj, HANDLE dropstruct) |
| { |
| if (obj->call && obj->call->drop) { |
| int len = DragQueryFile(dropstruct, 0, NULL, 0); |
| if (len > MAX_PATH) { |
| DragFinish(dropstruct); |
| return; |
| } |
| DragQueryFile(dropstruct, 0, dfilename, MAX_PATH); |
| DragFinish(dropstruct); |
| obj->call->drop(obj, 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) { |
| BYTE sta[256]; |
| wchar_t wcs[3]; |
| HKL dwhkl; |
| static wchar_t deadkey = L'\0'; |
| |
| dwhkl = GetKeyboardLayout((DWORD) 0); |
| GetKeyboardState(sta); |
| if(ToUnicodeEx(wParam, lParam, sta, |
| wcs, /* 3 */ sizeof(wcs)/sizeof(wchar_t), |
| 0, dwhkl) == 1) { |
| if(deadkey != L'\0') { |
| wchar_t wcs_in[3]; |
| wchar_t wcs_out[3]; |
| wcs_in[0] = wcs[0]; |
| wcs_in[1] = deadkey; |
| wcs_in[2] = L'\0'; |
| /* from accent char to unicode */ |
| if (FoldStringW(MAP_PRECOMPOSED, wcs_in, 3, wcs_out, 3)) |
| handle_char(obj, wcs_out[0]); |
| /* deadchar convert failure to skip. */ |
| } else |
| handle_char(obj, wcs[0]); |
| deadkey = L'\0'; |
| } else { |
| switch(wcs[0]) { |
| case 0x5e: /* circumflex */ |
| deadkey = 0x302; break; |
| case 0x60: /* grave accent */ |
| deadkey = 0x300; break; |
| case 0xa8: /* diaeresis */ |
| deadkey = 0x308; break; |
| case 0xb4: /* acute accent */ |
| deadkey = 0x301; break; |
| case 0xb8: /* cedilla */ |
| deadkey = 0x327; break; |
| default: |
| deadkey = wcs[0]; |
| break; |
| } |
| } |
| } |
| 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 multibyte string */ |
| HIMC himc = ImmGetContext(hwnd); |
| wchar_t buf[80]; |
| wchar_t *p; |
| int i; |
| int len; |
| |
| if(obj->flags & UseUnicode) { |
| /* len is byte */ |
| 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; |
| |
| /* 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_valid_sibling(obj->next); |
| |
| 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) && (keystate & CtrlKey)) { |
| SetFocus(next->handle); |
| return 0; |
| } |
| break; |
| } |
| if (key == VK_TAB) { |
| 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 &= ~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 |= Enabled; |
| return result; |
| } |
| |
| /* |
| * 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); |
| } |
| |
| /* |
| * Stop all timers and release the memory requirements of |
| * the proc instance 'thunks'. |
| */ |
| PROTECTED |
| void finish_events(void) |
| { |
| settimer(0); |
| setmousetimer(0); |
| } |