X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/5f3286d17d6f65c3acddabb45545c2ef1b0d9b20..a0be434e78d628764aa143b82b3c57e9603b7357:/src/msw/window.cpp diff --git a/src/msw/window.cpp b/src/msw/window.cpp index 5b4cbc254a..a0398ffae2 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -37,6 +37,7 @@ #include "wx/menu.h" #include "wx/dc.h" #include "wx/dcclient.h" + #include "wx/dcmemory.h" #include "wx/utils.h" #include "wx/app.h" #include "wx/layout.h" @@ -101,6 +102,10 @@ #endif #endif +// ---------------------------------------------------------------------------- +// standard constants not available with all compilers/headers +// ---------------------------------------------------------------------------- + // This didn't appear in mingw until 2.95.2 #ifndef SIF_TRACKPOS #define SIF_TRACKPOS 16 @@ -116,13 +121,27 @@ #ifndef SPI_GETWHEELSCROLLLINES #define SPI_GETWHEELSCROLLLINES 104 #endif +#endif // wxUSE_MOUSEWHEEL + +#ifndef VK_OEM_1 + #define VK_OEM_1 0xBA + #define VK_OEM_PLUS 0xBB + #define VK_OEM_COMMA 0xBC + #define VK_OEM_MINUS 0xBD + #define VK_OEM_PERIOD 0xBE + #define VK_OEM_2 0xBF + #define VK_OEM_3 0xC0 + #define VK_OEM_4 0xDB + #define VK_OEM_5 0xDC + #define VK_OEM_6 0xDD + #define VK_OEM_7 0xDE #endif // --------------------------------------------------------------------------- // global variables // --------------------------------------------------------------------------- -// the last Windows message we got (MT-UNSAFE) +// the last Windows message we got (FIXME-MT) extern MSG s_currentMsg; #if wxUSE_MENUS_NATIVE @@ -131,6 +150,10 @@ wxMenu *wxCurrentPopupMenu = NULL; extern const wxChar *wxCanvasClassName; +// true if we had already created the std colour map, used by +// wxGetStdColourMap() and wxWindow::OnSysColourChanged() (FIXME-MT) +static bool gs_hasStdCmap = FALSE; + // --------------------------------------------------------------------------- // private functions // --------------------------------------------------------------------------- @@ -155,6 +178,9 @@ static void TranslateKbdEventToMouse(wxWindowMSW *win, // get the text metrics for the current font static TEXTMETRIC wxGetTextMetrics(const wxWindowMSW *win); +// find the window for the mouse event at the specified position +static wxWindowMSW *FindWindowForMouseEvent(wxWindowMSW *win, int *x, int *y); //TW:REQ:Univ + // wrapper around BringWindowToTop() API static inline void wxBringWindowToTop(HWND hwnd) { @@ -286,21 +312,19 @@ void wxWindowMSW::Init() InitBase(); // MSW specific - m_doubleClickAllowed = 0; - m_isBeingDeleted = FALSE; - m_oldWndProc = 0; + m_oldWndProc = NULL; m_useCtl3D = FALSE; m_mouseInWindow = FALSE; + m_lastKeydownProcessed = FALSE; + + m_childrenDisabled = NULL; // wxWnd m_hMenu = 0; m_hWnd = 0; - // pass WM_GETDLGCODE to DefWindowProc() - m_lDlgCode = 0; - m_xThumbSize = 0; m_yThumbSize = 0; m_backgroundTransparent = FALSE; @@ -320,18 +344,16 @@ wxWindowMSW::~wxWindowMSW() { m_isBeingDeleted = TRUE; - MSWDetachWindowMenu(); - #ifndef __WXUNIVERSAL__ // VS: make sure there's no wxFrame with last focus set to us: for ( wxWindow *win = GetParent(); win; win = win->GetParent() ) { - wxFrame *frame = wxDynamicCast(win, wxFrame); + wxTopLevelWindow *frame = wxDynamicCast(win, wxTopLevelWindow); if ( frame ) { if ( frame->GetLastFocus() == this ) { - frame->SetLastFocus((wxWindow*)NULL); + frame->SetLastFocus(NULL); } break; } @@ -358,6 +380,8 @@ wxWindowMSW::~wxWindowMSW() // remove hWnd <-> wxWindow association wxRemoveHandleAssociation(this); } + + delete m_childrenDisabled; } // real construction (Init() must have been called before!) @@ -370,69 +394,36 @@ bool wxWindowMSW::Create(wxWindow *parent, { wxCHECK_MSG( parent, FALSE, wxT("can't create wxWindow without parent") ); -#if wxUSE_STATBOX - // wxGTK doesn't allow to create controls with static box as the parent so - // this will result in a crash when the program is ported to wxGTK - warn - // about it - // - // the correct solution is to create the controls as siblings of the - // static box - wxASSERT_MSG( !wxDynamicCastThis(wxStaticBox), - _T("wxStaticBox can't be used as a window parent!") ); -#endif // wxUSE_STATBOX - if ( !CreateBase(parent, id, pos, size, style, wxDefaultValidator, name) ) return FALSE; parent->AddChild(this); - // all windows are created visible - DWORD msflags = WS_CHILD | WS_VISIBLE; + WXDWORD exstyle; + DWORD msflags = MSWGetCreateWindowFlags(&exstyle); #ifdef __WXUNIVERSAL__ - // no 3d effects, we draw them ourselves - WXDWORD exStyle = 0; -#else // !wxUniversal - if ( style & wxCLIP_CHILDREN ) - msflags |= WS_CLIPCHILDREN; - if ( style & wxCLIP_SIBLINGS ) - msflags |= WS_CLIPSIBLINGS; - - bool want3D; - WXDWORD exStyle = Determine3DEffects(WS_EX_CLIENTEDGE, &want3D); - - // Even with extended styles, need to combine with WS_BORDER - // for them to look right. - if ( want3D || - (m_windowStyle & (wxBORDER | - wxSIMPLE_BORDER | - wxRAISED_BORDER | - wxSUNKEN_BORDER | - wxDOUBLE_BORDER)) ) - { - msflags |= WS_BORDER; - } - - // calculate the value to return from WM_GETDLGCODE handler - if ( GetWindowStyleFlag() & wxWANTS_CHARS ) - { - // want everything: i.e. all keys and WM_CHAR message - m_lDlgCode = DLGC_WANTARROWS | DLGC_WANTCHARS | - DLGC_WANTTAB | DLGC_WANTMESSAGE; - } -#endif // wxUniversal/!wxUniversal - + // no borders, we draw them ourselves + exstyle &= ~(WS_EX_DLGMODALFRAME | + WS_EX_STATICEDGE | + WS_EX_CLIENTEDGE | + WS_EX_WINDOWEDGE); + msflags &= ~WS_BORDER; +#endif // wxUniversal + + // all windows are created visible by default except popup ones (which are + // like the wxTopLevelWindows in this aspect) if ( style & wxPOPUP_WINDOW ) { - // a popup window floats on top of everything - exStyle |= WS_EX_TOPMOST | WS_EX_TOOLWINDOW; - - // it is also created hidden as other top level windows msflags &= ~WS_VISIBLE; m_isShown = FALSE; } + else + { + msflags |= WS_VISIBLE; + } - return MSWCreate(wxCanvasClassName, NULL, pos, size, msflags, exStyle); + return MSWCreate(wxCanvasClassName, NULL, pos, size, msflags, exstyle); } // --------------------------------------------------------------------------- @@ -450,23 +441,32 @@ void wxWindowMSW::SetFocus() if ( !::SetFocus(hWnd) ) { +#if defined(__WXDEBUG__) && !defined(__WXMICROWIN__) // was there really an error? -#ifndef __WXMICROWIN__ DWORD dwRes = ::GetLastError(); -#else - - DWORD dwRes = 0; -#endif if ( dwRes ) { - wxLogApiError(_T("SetFocus"), dwRes); + HWND hwndFocus = ::GetFocus(); + if ( hwndFocus != hWnd ) + { + wxLogApiError(_T("SetFocus"), dwRes); + } } +#endif // Debug + } +} - // VZ: just why does this happen sometimes?? any idea? -#if 0 - HWND hwndFocus = ::GetFocus(); - wxASSERT_MSG( hwndFocus == hWnd, _T("SetFocus() didn't work?") ); -#endif // 0 +void wxWindowMSW::SetFocusFromKbd() +{ + wxWindowBase::SetFocusFromKbd(); + + // when the focus is given to the control with DLGC_HASSETSEL style from + // keyboard its contents should be entirely selected: this is what + // ::IsDialogMessage() does and so we should do it as well to provide the + // same LNF as the native programs + if ( ::SendMessage(GetHwnd(), WM_GETDLGCODE, 0, 0) & DLGC_HASSETSEL ) + { + ::SendMessage(GetHwnd(), EM_SETSEL, 0, -1); } } @@ -491,19 +491,58 @@ bool wxWindowMSW::Enable(bool enable) if ( hWnd ) ::EnableWindow(hWnd, (BOOL)enable); - // VZ: no, this is a bad idea: imagine that you have a dialog with some - // disabled controls and disable it - you really wouldn't like the - // disabled controls be reenabled too when you reenable the dialog! -#if 0 - wxWindowList::Node *node = GetChildren().GetFirst(); - while ( node ) + // the logic below doesn't apply to the top level windows -- otherwise + // showing a modal dialog would result in total greying out (and ungreying + // out later) of everything which would be really ugly + if ( IsTopLevel() ) + return TRUE; + + // when the parent is disabled, all of its children should be disabled as + // well but when it is enabled back, only those of the children which + // hadn't been already disabled in the beginning should be enabled again, + // so we have to keep the list of those children + for ( wxWindowList::Node *node = GetChildren().GetFirst(); + node; + node = node->GetNext() ) { wxWindow *child = node->GetData(); - child->Enable(enable); + if ( child->IsTopLevel() ) + { + // the logic below doesn't apply to top level children + continue; + } - node = node->GetNext(); + if ( enable ) + { + // enable the child back unless it had been disabled before us + if ( !m_childrenDisabled || !m_childrenDisabled->Find(child) ) + child->Enable(); + } + else // we're being disabled + { + if ( child->IsEnabled() ) + { + // disable it as children shouldn't stay enabled while the + // parent is not + child->Disable(); + } + else // child already disabled, remember it + { + // have we created the list of disabled children already? + if ( !m_childrenDisabled ) + m_childrenDisabled = new wxWindowList; + + m_childrenDisabled->Append(child); + } + } + } + + if ( enable && m_childrenDisabled ) + { + // we don't need this list any more, don't keep unused memory + delete m_childrenDisabled; + m_childrenDisabled = NULL; } -#endif // 0 return TRUE; } @@ -548,7 +587,7 @@ wxString wxWindowMSW::GetTitle() const return wxGetWindowText(GetHWND()); } -void wxWindowMSW::CaptureMouse() +void wxWindowMSW::DoCaptureMouse() { HWND hWnd = GetHwnd(); if ( hWnd ) @@ -557,7 +596,7 @@ void wxWindowMSW::CaptureMouse() } } -void wxWindowMSW::ReleaseMouse() +void wxWindowMSW::DoReleaseMouse() { if ( !::ReleaseCapture() ) { @@ -904,15 +943,21 @@ void wxWindowMSW::SetScrollbar(int orient, int pos, int thumbVisible, void wxWindowMSW::ScrollWindow(int dx, int dy, const wxRect *prect) { RECT rect; + RECT *pr; if ( prect ) { rect.left = prect->x; rect.top = prect->y; rect.right = prect->x + prect->width; rect.bottom = prect->y + prect->height; + pr = ▭ + } + else + { + pr = NULL; } - ::ScrollWindow(GetHwnd(), dx, dy, prect ? &rect : NULL, NULL); + ::ScrollWindow(GetHwnd(), dx, dy, pr, pr); } static bool ScrollVertically(HWND hwnd, int kind, int count) @@ -968,17 +1013,20 @@ void wxWindowMSW::SubclassWin(WXHWND hWnd) wxAssociateWinWithHandle(hwnd, this); - m_oldWndProc = (WXFARPROC)::GetWindowLong(hwnd, GWL_WNDPROC); + m_oldWndProc = (WXFARPROC)::GetWindowLong((HWND)hWnd, GWL_WNDPROC); // we don't need to subclass the window of our own class (in the Windows // sense of the word) - if ( (WXFARPROC) m_oldWndProc != (WXFARPROC) wxWndProc ) + if ( !wxCheckWindowWndProc(hWnd, (WXFARPROC)wxWndProc) ) { ::SetWindowLong(hwnd, GWL_WNDPROC, (LONG) wxWndProc); } else { - // don't bother restoring it neither + // don't bother restoring it neither: this also makes it easy to + // implement IsOfStandardClass() method which returns TRUE for the + // standard controls and FALSE for the wxWindows own windows as it can + // simply check m_oldWndProc m_oldWndProc = NULL; } } @@ -997,8 +1045,7 @@ void wxWindowMSW::UnsubclassWin() if ( m_oldWndProc ) { - FARPROC wndProc = (FARPROC)::GetWindowLong(hwnd, GWL_WNDPROC); - if ( wndProc != (FARPROC) m_oldWndProc ) + if ( !wxCheckWindowWndProc((WXHWND)hwnd, m_oldWndProc) ) { ::SetWindowLong(hwnd, GWL_WNDPROC, (LONG) m_oldWndProc); } @@ -1008,6 +1055,148 @@ void wxWindowMSW::UnsubclassWin() } } +bool wxCheckWindowWndProc(WXHWND hWnd, WXFARPROC wndProc) +{ + // Unicows note: the code below works, but only because WNDCLASS contains + // original window handler rather that the unicows fake one. This may not + // be on purpose, though; if it stops working with future versions of + // unicows.dll, we can override unicows hooks by setting + // Unicows_{Set,Get}WindowLong and Unicows_RegisterClass to our own + // versions that keep track of fake<->real wnd proc mapping. + WNDCLASS cls; + if ( !::GetClassInfo(wxGetInstance(), wxGetWindowClass(hWnd), &cls) ) + { + wxLogLastError(_T("GetClassInfo")); + + return FALSE; + } + + return wndProc == (WXFARPROC)cls.lpfnWndProc; +} + +// ---------------------------------------------------------------------------- +// Style handling +// ---------------------------------------------------------------------------- + +void wxWindowMSW::SetWindowStyleFlag(long flags) +{ + long flagsOld = GetWindowStyleFlag(); + if ( flags == flagsOld ) + return; + + // update the internal variable + wxWindowBase::SetWindowStyleFlag(flags); + + // now update the Windows style as well if needed - and if the window had + // been already created + if ( !GetHwnd() ) + return; + + WXDWORD exstyle, exstyleOld; + long style = MSWGetStyle(flags, &exstyle), + styleOld = MSWGetStyle(flagsOld, &exstyleOld); + + if ( style != styleOld ) + { + // some flags (e.g. WS_VISIBLE or WS_DISABLED) should not be changed by + // this function so instead of simply setting the style to the new + // value we clear the bits which were set in styleOld but are set in + // the new one and set the ones which were not set before + long styleReal = ::GetWindowLong(GetHwnd(), GWL_STYLE); + styleReal &= ~styleOld; + styleReal |= style; + + ::SetWindowLong(GetHwnd(), GWL_STYLE, styleReal); + } + + // and the extended style + if ( exstyle != exstyleOld ) + { + long exstyleReal = ::GetWindowLong(GetHwnd(), GWL_EXSTYLE); + exstyleReal &= ~exstyleOld; + exstyleReal |= exstyle; + + ::SetWindowLong(GetHwnd(), GWL_EXSTYLE, exstyleReal); + + // we must call SetWindowPos() to flash the cached extended style and + // also to make the change to wxSTAY_ON_TOP style take effect: just + // setting the style simply doesn't work + if ( !::SetWindowPos(GetHwnd(), + exstyleReal & WS_EX_TOPMOST ? HWND_TOPMOST + : HWND_NOTOPMOST, + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE) ) + { + wxLogLastError(_T("SetWindowPos")); + } + } +} + +WXDWORD wxWindowMSW::MSWGetStyle(long flags, WXDWORD *exstyle) const +{ + // translate the style + WXDWORD style = WS_CHILD; + + if ( flags & wxCLIP_CHILDREN ) + style |= WS_CLIPCHILDREN; + + if ( flags & wxCLIP_SIBLINGS ) + style |= WS_CLIPSIBLINGS; + + wxBorder border = (wxBorder)(flags & wxBORDER_MASK); + if ( border != wxBORDER_NONE && border != wxBORDER_DEFAULT ) + style |= WS_BORDER; + + // now deal with ext style if the caller wants it + if ( exstyle ) + { + *exstyle = 0; + + if ( flags & wxTRANSPARENT_WINDOW ) + *exstyle |= WS_EX_TRANSPARENT; + + switch ( flags & wxBORDER_MASK ) + { + default: + wxFAIL_MSG( _T("unknown border style") ); + // fall through + + case wxBORDER_NONE: + case wxBORDER_SIMPLE: + case wxBORDER_DEFAULT: + break; + + case wxBORDER_STATIC: + *exstyle |= WS_EX_STATICEDGE; + break; + + case wxBORDER_RAISED: + *exstyle |= WS_EX_WINDOWEDGE; + break; + + case wxBORDER_SUNKEN: + *exstyle |= WS_EX_CLIENTEDGE; + break; + + case wxBORDER_DOUBLE: + *exstyle |= WS_EX_DLGMODALFRAME; + break; + } + + // wxUniv doesn't use Windows dialog navigation functions at all +#ifndef __WXUNIVERSAL__ + // to make the dialog navigation work with the nested panels we must + // use this style (top level windows such as dialogs don't need it) + if ( (flags & wxTAB_TRAVERSAL) && !IsTopLevel() ) + { + *exstyle |= WS_EX_CONTROLPARENT; + } +#endif // __WXUNIVERSAL__ + } + + return style; +} + // Make a Windows extended style from the given wxWindows window style WXDWORD wxWindowMSW::MakeExtendedStyle(long style, bool eliminateBorders) { @@ -1154,6 +1343,8 @@ void wxWindowMSW::OnIdle(wxIdleEvent& WXUNUSED(event)) // Check if we need to send a LEAVE event if ( m_mouseInWindow ) { + // note that we should generate the leave event whether the window has + // or doesn't have mouse capture if ( !IsMouseInWindow() ) { // Generate a LEAVE event @@ -1313,7 +1504,7 @@ void wxWindowMSW::DoSetToolTip(wxToolTip *tooltip) wxWindowBase::DoSetToolTip(tooltip); if ( m_tooltip ) - m_tooltip->SetWindow(this); + m_tooltip->SetWindow((wxWindow *)this); } #endif // wxUSE_TOOLTIPS @@ -1418,6 +1609,12 @@ void wxWindowMSW::DoClientToScreen(int *x, int *y) const void wxWindowMSW::DoMoveWindow(int x, int y, int width, int height) { + // TODO: is this consistent with other platforms? + // Still, negative width or height shouldn't be allowed + if (width < 0) + width = 0; + if (height < 0) + height = 0; if ( !::MoveWindow(GetHwnd(), x, y, width, height, TRUE) ) { wxLogLastError(wxT("MoveWindow")); @@ -1493,40 +1690,59 @@ void wxWindowMSW::DoSetSize(int x, int y, int width, int height, int sizeFlags) void wxWindowMSW::DoSetClientSize(int width, int height) { - wxWindow *parent = GetParent(); - HWND hWnd = GetHwnd(); - HWND hParentWnd = (HWND) 0; - if ( parent ) - hParentWnd = (HWND) parent->GetHWND(); + // setting the client size is less obvious than it it could have been + // because in the result of changing the total size the window scrollbar + // may [dis]appear and/or its menubar may [un]wrap and so the client size + // will not be correct as the difference between the total and client size + // changes - so we keep changing it until we get it right + // + // normally this loop shouldn't take more than 3 iterations (usually 1 but + // if scrollbars [dis]appear as the result of the first call, then 2 and it + // may become 3 if the window had 0 size originally and so we didn't + // calculate the scrollbar correction correctly during the first iteration) + // but just to be on the safe side we check for it instead of making it an + // "infinite" loop (i.e. leaving break inside as the only way to get out) + for ( int i = 0; i < 4; i++ ) + { + RECT rectClient; + ::GetClientRect(GetHwnd(), &rectClient); - RECT rect; - ::GetClientRect(hWnd, &rect); + // if the size is already ok, stop here (rectClient.left = top = 0) + if ( (rectClient.right == width || width == -1) && + (rectClient.bottom == height || height == -1) ) + { + break; + } - RECT rect2; - GetWindowRect(hWnd, &rect2); + int widthClient = width, + heightClient = height; - // Find the difference between the entire window (title bar and all) - // and the client area; add this to the new client size to move the - // window - int actual_width = rect2.right - rect2.left - rect.right + width; - int actual_height = rect2.bottom - rect2.top - rect.bottom + height; + // Find the difference between the entire window (title bar and all) + // and the client area; add this to the new client size to move the + // window + RECT rectWin; + ::GetWindowRect(GetHwnd(), &rectWin); - // If there's a parent, must subtract the parent's top left corner - // since MoveWindow moves relative to the parent + widthClient += rectWin.right - rectWin.left - rectClient.right; + heightClient += rectWin.bottom - rectWin.top - rectClient.bottom; - POINT point; - point.x = rect2.left; - point.y = rect2.top; - if ( parent ) - { - ::ScreenToClient(hParentWnd, &point); - } + POINT point; + point.x = rectWin.left; + point.y = rectWin.top; - DoMoveWindow(point.x, point.y, actual_width, actual_height); + // MoveWindow positions the child windows relative to the parent, so + // adjust if necessary + if ( !IsTopLevel() ) + { + wxWindow *parent = GetParent(); + if ( parent ) + { + ::ScreenToClient(GetHwndOf(parent), &point); + } + } - wxSizeEvent event(wxSize(width, height), m_windowId); - event.SetEventObject(this); - GetEventHandler()->ProcessEvent(event); + DoMoveWindow(point.x, point.y, widthClient, heightClient); + } } // For implementation purposes - sometimes decorations make the client area @@ -1660,6 +1876,10 @@ static void wxYieldForCommandsOnly() { wxTheApp->DoMessage((WXMSG *)&msg); } + + // If we retrieved a WM_QUIT, insert back into the message queue. + if (msg.message == WM_QUIT) + ::PostQuitMessage(0); } bool wxWindowMSW::DoPopupMenu(wxMenu *menu, int x, int y) @@ -1718,14 +1938,7 @@ bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg) // here we try to do all the job which ::IsDialogMessage() usually does // internally #if 1 - bool bProcess = TRUE; - if ( msg->message != WM_KEYDOWN ) - bProcess = FALSE; - - if ( bProcess && (HIWORD(msg->lParam) & KF_ALTDOWN) == KF_ALTDOWN ) - bProcess = FALSE; - - if ( bProcess ) + if ( msg->message == WM_KEYDOWN ) { bool bCtrlDown = wxIsCtrlDown(); bool bShiftDown = wxIsShiftDown(); @@ -1737,11 +1950,21 @@ bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg) if ( !bCtrlDown ) { lDlgCode = ::SendMessage(msg->hwnd, WM_GETDLGCODE, 0, 0); + + // surprizingly, DLGC_WANTALLKEYS bit mask doesn't contain the + // DLGC_WANTTAB nor DLGC_WANTARROWS bits although, logically, + // it, of course, implies them + if ( lDlgCode & DLGC_WANTALLKEYS ) + { + lDlgCode |= DLGC_WANTTAB | DLGC_WANTARROWS; + } } bool bForward = TRUE, bWindowChange = FALSE; + // should we process this message specially? + bool bProcess = TRUE; switch ( msg->wParam ) { case VK_TAB: @@ -1812,12 +2035,27 @@ bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg) return TRUE; } else // no default button -#endif // wxUSE_BUTTON { - // no special function for enter and don't even - // let IsDialogMessage() have it: it seems to - // do something really strange with it - return FALSE; +#endif // wxUSE_BUTTON + // this is a quick and dirty test for a text + // control + if ( !(lDlgCode & DLGC_HASSETSEL) ) + { + // don't process Enter, the control might + // need it for itself and don't let + // ::IsDialogMessage() have it as it can + // eat the Enter events sometimes + return FALSE; + } + else if (!IsTopLevel()) + { + // if not a top level window, let parent + // handle it + return FALSE; + } + //else: treat Enter as TAB: pass to the next + // control as this is the best thing to do + // if the text doesn't handle Enter itself } } } @@ -1870,10 +2108,46 @@ bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg) } #endif // 1/0 - if ( ::IsDialogMessage(GetHwnd(), msg) ) + // we handle VK_ESCAPE ourselves in wxDialog::OnCharHook() and we + // shouldn't let IsDialogMessage() get it as it _always_ eats the + // message even when there is no cancel button and when the message is + // needed by the control itself: in particular, it prevents the tree in + // place edit control from being closed with Escape in a dialog + if ( msg->message != WM_KEYDOWN || msg->wParam != VK_ESCAPE ) { - // IsDialogMessage() did something... - return TRUE; + // ::IsDialogMessage() can enter in an infinite loop when the + // currently focused window is disabled or hidden and its parent + // has WS_EX_CONTROLPARENT style, so don't call it in this case + bool canSafelyCallIsDlgMsg = TRUE; + + HWND hwndFocus = ::GetFocus(); + while ( hwndFocus ) + { + if ( !::IsWindowEnabled(hwndFocus) || + !::IsWindowVisible(hwndFocus) ) + { + // it would enter an infinite loop if we do this! + canSafelyCallIsDlgMsg = FALSE; + + break; + } + + if ( !(::GetWindowLong(hwndFocus, GWL_STYLE) & WS_CHILD) ) + { + // it's a top level window, don't go further -- e.g. even + // if the parent of a dialog is disabled, this doesn't + // break navigation inside the dialog + break; + } + + hwndFocus = ::GetParent(hwndFocus); + } + + if ( canSafelyCallIsDlgMsg && ::IsDialogMessage(GetHwnd(), msg) ) + { + // IsDialogMessage() did something... + return TRUE; + } } } #endif // __WXUNIVERSAL__ @@ -1896,11 +2170,12 @@ bool wxWindowMSW::MSWTranslateMessage(WXMSG* pMsg) #if wxUSE_ACCEL && !defined(__WXUNIVERSAL__) return m_acceleratorTable.Translate(this, pMsg); #else + (void) pMsg; return FALSE; #endif // wxUSE_ACCEL } -bool wxWindowMSW::MSWShouldPreProcessMessage(WXMSG* pMsg) +bool wxWindowMSW::MSWShouldPreProcessMessage(WXMSG* WXUNUSED(pMsg)) { // preprocess all messages by default return TRUE; @@ -2023,8 +2298,10 @@ LRESULT WXDLLEXPORT APIENTRY _EXPORT wxWndProc(HWND hWnd, UINT message, WPARAM w { // trace all messages - useful for the debugging #ifdef __WXDEBUG__ +#if wxUSE_LOG wxLogTrace(wxTraceMessages, wxT("Processing %s(wParam=%8lx, lParam=%8lx)"), - wxGetMessageName(message), wParam, lParam); + wxGetMessageName(message), (long) wParam, lParam); +#endif // wxUSE_LOG #endif // __WXDEBUG__ wxWindowMSW *wnd = wxFindWinFromHandle((WXHWND) hWnd); @@ -2149,10 +2426,18 @@ long wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam break; case WM_CLOSE: +#ifdef __WXUNIVERSAL__ + // Universal uses its own wxFrame/wxDialog, so we don't receive + // close events unless we have this. + Close(); + processed = TRUE; + rc.result = TRUE; +#else // don't let the DefWindowProc() destroy our window - we'll do it // ourselves in ~wxWindow processed = TRUE; rc.result = TRUE; +#endif break; case WM_SHOWWINDOW: @@ -2180,68 +2465,63 @@ long wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDBLCLK: - { - processed = FALSE; + { #ifdef __WXMICROWIN__ // MicroWindows seems to ignore the fact that a window is // disabled. So catch mouse events and throw them away if // necessary. wxWindowMSW* win = this; - while (win) + for ( ;; ) { if (!win->IsEnabled()) { processed = TRUE; break; } + win = win->GetParent(); - if (win && win->IsTopLevel()) + if ( !win || win->IsTopLevel() ) break; } + + if ( processed ) + break; + #endif // __WXMICROWIN__ - if (!processed) + int x = GET_X_LPARAM(lParam), + y = GET_Y_LPARAM(lParam); + + // redirect the event to a static control if necessary by + // finding one under mouse + wxWindowMSW *win; + if ( GetCapture() == this ) { - if (message == WM_LBUTTONDOWN && AcceptsFocus()) - SetFocus(); - processed = HandleMouseEvent(message, - GET_X_LPARAM(lParam), - GET_Y_LPARAM(lParam), - wParam); + // but don't do it if the mouse is captured by this window + // because then it should really get this event itself + win = this; } - break; - } - -#ifdef __WXMICROWIN__ - case WM_NCLBUTTONDOWN: - case WM_NCLBUTTONUP: - case WM_NCLBUTTONDBLCLK: - case WM_NCRBUTTONDOWN: - case WM_NCRBUTTONUP: - case WM_NCRBUTTONDBLCLK: -#if 0 - case WM_NCMBUTTONDOWN: - case WM_NCMBUTTONUP: - case WM_NCMBUTTONDBLCLK: -#endif - { - // MicroWindows seems to ignore the fact that a window - // is disabled. So catch mouse events and throw them away if necessary. - processed = FALSE; - wxWindowMSW* win = this; - while (win) + else { - if (!win->IsEnabled()) + win = FindWindowForMouseEvent(this, &x, &y); + + // this should never happen + wxCHECK_MSG( win, 0, + _T("FindWindowForMouseEvent() returned NULL") ); + + // for the standard classes their WndProc sets the focus to + // them anyhow and doing it from here results in some weird + // problems, but for our windows we want them to acquire + // focus when clicked + if ( !win->IsOfStandardClass() ) { - processed = TRUE; - break; + if ( message == WM_LBUTTONDOWN && win->AcceptsFocus() ) + win->SetFocus(); } - win = win->GetParent(); - if (win && win->IsTopLevel()) - break; } - break; + + processed = win->HandleMouseEvent(message, x, y, wParam); } -#endif // __WXMICROWIN__ + break; #ifdef MM_JOY1MOVE case MM_JOY1MOVE: @@ -2303,9 +2583,19 @@ long wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam #endif // defined(WM_DRAWITEM) case WM_GETDLGCODE: - if ( m_lDlgCode ) + if ( !IsOfStandardClass() ) { - rc.result = m_lDlgCode; + // we always want to get the char events + rc.result = DLGC_WANTCHARS; + + if ( GetWindowStyleFlag() & wxWANTS_CHARS ) + { + // in fact, we want everything + rc.result |= DLGC_WANTARROWS | + DLGC_WANTTAB | + DLGC_WANTALLKEYS; + } + processed = TRUE; } //else: get the dlg code from the DefWindowProc() @@ -2313,62 +2603,79 @@ long wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam case WM_SYSKEYDOWN: case WM_KEYDOWN: - // If this has been processed by an event handler, - // return 0 now (we've handled it). - if ( HandleKeyDown((WORD) wParam, lParam) ) + // If this has been processed by an event handler, return 0 now + // (we've handled it). + m_lastKeydownProcessed = HandleKeyDown((WORD) wParam, lParam); + if ( m_lastKeydownProcessed ) { processed = TRUE; - - break; } - // we consider these message "not interesting" to OnChar - if ( wParam == VK_SHIFT || wParam == VK_CONTROL ) + if ( !processed ) { - processed = TRUE; - - break; - } - - switch ( wParam ) - { - // avoid duplicate messages to OnChar for these ASCII keys: they - // will be translated by TranslateMessage() and received in WM_CHAR - case VK_ESCAPE: - case VK_SPACE: - case VK_RETURN: - case VK_BACK: - case VK_TAB: - case VK_ADD: - case VK_SUBTRACT: - // but set processed to FALSE, not TRUE to still pass them to - // the control's default window proc - otherwise built-in - // keyboard handling won't work - processed = FALSE; + switch ( wParam ) + { + // we consider these message "not interesting" to OnChar, so + // just don't do anything more with them + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + processed = TRUE; + break; - break; + // avoid duplicate messages to OnChar for these ASCII keys: + // they will be translated by TranslateMessage() and received + // in WM_CHAR + case VK_ESCAPE: + case VK_SPACE: + case VK_RETURN: + case VK_BACK: + case VK_TAB: + case VK_ADD: + case VK_SUBTRACT: + case VK_MULTIPLY: + case VK_DIVIDE: + case VK_OEM_1: + case VK_OEM_2: + case VK_OEM_3: + case VK_OEM_4: + case VK_OEM_5: + case VK_OEM_6: + case VK_OEM_7: + case VK_OEM_PLUS: + case VK_OEM_COMMA: + case VK_OEM_MINUS: + case VK_OEM_PERIOD: + // but set processed to FALSE, not TRUE to still pass them + // to the control's default window proc - otherwise + // built-in keyboard handling won't work + processed = FALSE; + break; #ifdef VK_APPS - // special case of VK_APPS: treat it the same as right mouse - // click because both usually pop up a context menu - case VK_APPS: - { - WPARAM flags; - int x, y; + // special case of VK_APPS: treat it the same as right mouse + // click because both usually pop up a context menu + case VK_APPS: + { + WPARAM flags; + int x, y; - TranslateKbdEventToMouse(this, &x, &y, &flags); - processed = HandleMouseEvent(WM_RBUTTONDOWN, x, y, flags); - } - break; + TranslateKbdEventToMouse(this, &x, &y, &flags); + processed = HandleMouseEvent(WM_RBUTTONDOWN, x, y, flags); + } + break; #endif // VK_APPS - case VK_LEFT: - case VK_RIGHT: - case VK_DOWN: - case VK_UP: - default: - processed = HandleChar((WORD)wParam, lParam); + default: + // do generate a CHAR event + processed = HandleChar((WORD)wParam, lParam); + } } + if (message == WM_SYSKEYDOWN) // Let Windows still handle the SYSKEYs + processed = FALSE; break; case WM_SYSKEYUP: @@ -2392,7 +2699,18 @@ long wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam case WM_SYSCHAR: case WM_CHAR: // Always an ASCII character - processed = HandleChar((WORD)wParam, lParam, TRUE); + if ( m_lastKeydownProcessed ) + { + // The key was handled in the EVT_KEY_DOWN and handling + // a key in an EVT_KEY_DOWN handler is meant, by + // design, to prevent EVT_CHARs from happening + m_lastKeydownProcessed = FALSE; + processed = TRUE; + } + else + { + processed = HandleChar((WORD)wParam, lParam, TRUE); + } break; case WM_HSCROLL: @@ -2439,15 +2757,23 @@ long wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam break; #endif // !__WXMICROWIN__ - // the return value for this message is ignored case WM_SYSCOLORCHANGE: + // the return value for this message is ignored processed = HandleSysColorChange(); break; + case WM_DISPLAYCHANGE: + processed = HandleDisplayChange(); + break; + case WM_PALETTECHANGED: processed = HandlePaletteChanged((WXHWND) (HWND) wParam); break; + case WM_CAPTURECHANGED: + processed = HandleCaptureChanged((WXHWND) (HWND) lParam); + break; + case WM_QUERYNEWPALETTE: processed = HandleQueryNewPalette(); break; @@ -2548,29 +2874,30 @@ long wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam processed = GetEventHandler()->ProcessEvent(evtCtx); } break; -#endif // __WIN32__ - // unfortunately this doesn't really work as then window which - // doesn't accept focus doesn't get any mouse events neither which - // means it can't get any input at all -#if 0 //def __WXUNIVERSAL__ - case WM_NCHITTEST: - // we shouldn't allow the windows which don't want to get focus to - // get it - if ( !AcceptsFocus() ) + case WM_MENUCHAR: + // we're only interested in our own menus, not MF_SYSMENU + if ( HIWORD(wParam) == MF_POPUP ) { - rc.result = HTTRANSPARENT; - processed = TRUE; + // handle menu chars for ownerdrawn menu items + int i = HandleMenuChar(toupper(LOWORD(wParam)), lParam); + if ( i != wxNOT_FOUND ) + { + rc.result = MAKELRESULT(i, MNC_EXECUTE); + processed = TRUE; + } } break; -#endif // __WXUNIVERSAL__ +#endif // __WIN32__ } if ( !processed ) { #ifdef __WXDEBUG__ +#if wxUSE_LOG wxLogTrace(wxTraceMessages, wxT("Forwarding %s to DefWindowProc."), wxGetMessageName(message)); +#endif // wxUSE_LOG #endif // __WXDEBUG__ rc.result = MSWDefWindowProc(message, wParam, lParam); } @@ -2601,7 +2928,7 @@ void wxAssociateWinWithHandle(HWND hWnd, wxWindowMSW *win) if ( oldWin && (oldWin != win) ) { wxLogDebug(wxT("HWND %X already associated with another window (%s)"), - hWnd, win->GetClassInfo()->GetClassName()); + (int) hWnd, win->GetClassInfo()->GetClassName()); } else #endif // __WXDEBUG__ @@ -2626,43 +2953,14 @@ void wxWindowMSW::MSWDestroyWindow() { } -void wxWindowMSW::MSWDetachWindowMenu() -{ -#ifndef __WXUNIVERSAL__ - if ( m_hMenu ) - { - wxChar buf[1024]; - HMENU hMenu = (HMENU)m_hMenu; - - int N = ::GetMenuItemCount(hMenu); - for ( int i = 0; i < N; i++ ) - { - if ( !::GetMenuString(hMenu, i, buf, WXSIZEOF(buf), MF_BYPOSITION) ) - { - wxLogLastError(wxT("GetMenuString")); - - continue; - } - - if ( wxStrcmp(buf, _("&Window")) == 0 ) - { - if ( !::RemoveMenu(hMenu, i, MF_BYPOSITION) ) - { - wxLogLastError(wxT("RemoveMenu")); - } - - break; - } - } - } -#endif // __WXUNIVERSAL__ -} - bool wxWindowMSW::MSWGetCreateWindowCoords(const wxPoint& pos, const wxSize& size, int& x, int& y, int& w, int& h) const { + static const int DEFAULT_Y = 200; + static const int DEFAULT_H = 250; + bool nonDefault = FALSE; if ( pos.x == -1 ) @@ -2674,8 +2972,11 @@ bool wxWindowMSW::MSWGetCreateWindowCoords(const wxPoint& pos, } else { + // OTOH, if x is not set to CW_USEDEFAULT, y shouldn't be set to it + // neither because it is not handled as a special value by Windows then + // and so we have to choose some default value for it x = pos.x; - y = pos.y == -1 ? CW_USEDEFAULT : pos.y; + y = pos.y == -1 ? DEFAULT_Y : pos.y; nonDefault = TRUE; } @@ -2698,14 +2999,15 @@ bool wxWindowMSW::MSWGetCreateWindowCoords(const wxPoint& pos, */ if ( size.x == -1 ) { - // as abobe, h is not used at all in this case anyhow + // as above, h is not used at all in this case anyhow w = h = CW_USEDEFAULT; } else { + // and, again as above, we can't set the height to CW_USEDEFAULT here w = size.x; - h = size.y == -1 ? CW_USEDEFAULT : size.y; + h = size.y == -1 ? DEFAULT_H : size.y; nonDefault = TRUE; } @@ -2713,6 +3015,11 @@ bool wxWindowMSW::MSWGetCreateWindowCoords(const wxPoint& pos, return nonDefault; } +WXHWND wxWindowMSW::MSWGetParent() const +{ + return m_parent ? m_parent->GetHWND() : WXHWND(NULL); +} + bool wxWindowMSW::MSWCreate(const wxChar *wclass, const wxChar *title, const wxPoint& pos, @@ -2724,53 +3031,9 @@ bool wxWindowMSW::MSWCreate(const wxChar *wclass, int x, y, w, h; (void)MSWGetCreateWindowCoords(pos, size, x, y, w, h); - // find the correct parent HWND - wxWindow *parent = GetParent(); - bool isChild = (style & WS_CHILD) != 0; - HWND hParent; - if ( GetWindowStyleFlag() & wxPOPUP_WINDOW ) - { - // popup windows should have desktop as parent because they shouldn't - // be limited to the parents client area as child windows usually are - hParent = ::GetDesktopWindow(); - } - else // !popup - { - if ( (isChild || HasFlag(wxFRAME_TOOL_WINDOW)) && parent ) - { - // this is either a normal child window or a top level window with - // wxFRAME_TOOL_WINDOW style (see below) - hParent = GetHwndOf(parent); - } - else - { - // this is either a window for which no parent was specified (not - // much we can do then) or a frame without wxFRAME_TOOL_WINDOW - // style: we should use NULL parent HWND for it or it would be - // always on top of its parent which is not what we usually want - // (in fact, we only want it for frames with the special - // wxFRAME_TOOL_WINDOW as above) - hParent = NULL; - } - - } - // controlId is menu handle for the top level windows, so set it to 0 // unless we're creating a child window - int controlId; - if ( isChild ) - { - controlId = GetId(); - - if ( GetWindowStyleFlag() & wxCLIP_SIBLINGS ) - { - style |= WS_CLIPSIBLINGS; - } - } - else // !child - { - controlId = 0; - } + int controlId = style & WS_CHILD ? GetId() : 0; // for each class "Foo" we have we also have "FooNR" ("no repaint") class // which is the same but without CS_[HV]REDRAW class styles so using it @@ -2785,17 +3048,17 @@ bool wxWindowMSW::MSWCreate(const wxChar *wclass, wxWindowCreationHook hook(this); m_hWnd = (WXHWND)::CreateWindowEx - ( - extendedStyle, - className, - title ? title : wxT(""), - style, - x, y, w, h, - hParent, - (HMENU)controlId, - wxGetInstance(), - NULL // no extra data - ); + ( + extendedStyle, + className, + title ? title : wxT(""), + style, + x, y, w, h, + (HWND)MSWGetParent(), + (HMENU)controlId, + wxGetInstance(), + NULL // no extra data + ); if ( !m_hWnd ) { @@ -2806,7 +3069,7 @@ bool wxWindowMSW::MSWCreate(const wxChar *wclass, SubclassWin(m_hWnd); - SetFont(wxSystemSettings::GetSystemFont(wxSYS_DEFAULT_GUI_FONT)); + SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); return TRUE; } @@ -2820,7 +3083,7 @@ bool wxWindowMSW::MSWCreate(const wxChar *wclass, // --------------------------------------------------------------------------- #ifdef __WIN95__ -// FIXME: VZ: I'm not sure at all that the order of processing is correct + bool wxWindowMSW::HandleNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) { #ifndef __WXMICROWIN__ @@ -2828,12 +3091,19 @@ bool wxWindowMSW::HandleNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) HWND hWnd = hdr->hwndFrom; wxWindow *win = wxFindWinFromHandle((WXHWND)hWnd); - // is this one of our windows? + // if the control is one of our windows, let it handle the message itself if ( win ) { return win->MSWOnNotify(idCtrl, lParam, result); } + // VZ: why did we do it? normally this is unnecessary and, besides, it + // breaks the message processing for the toolbars because the tooltip + // notifications were being forwarded to the toolbar child controls + // (if it had any) before being passed to the toolbar itself, so in my + // example the tooltip for the combobox was always shown instead of the + // correct button tooltips +#if 0 // try all our children wxWindowList::Node *node = GetChildren().GetFirst(); while ( node ) @@ -2846,32 +3116,105 @@ bool wxWindowMSW::HandleNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) node = node->GetNext(); } +#endif // 0 - // finally try this window too (catches toolbar case) + // by default, handle it ourselves return MSWOnNotify(idCtrl, lParam, result); #else // __WXMICROWIN__ return FALSE; #endif } +#if wxUSE_TOOLTIPS + +bool wxWindowMSW::HandleTooltipNotify(WXUINT code, + WXLPARAM lParam, + const wxString& ttip) +{ + // I don't know why it happens, but the versions of comctl32.dll starting + // from 4.70 sometimes send TTN_NEEDTEXTW even to ANSI programs (normally, + // this message is supposed to be sent to Unicode programs only) -- hence + // we need to handle it as well, otherwise no tooltips will be shown in + // this case + + if ( !(code == (WXUINT) TTN_NEEDTEXTA || code == (WXUINT) TTN_NEEDTEXTW) || ttip.empty() ) + { + // not a tooltip message or no tooltip to show anyhow + return FALSE; + } + + LPTOOLTIPTEXT ttText = (LPTOOLTIPTEXT)lParam; + +#if !wxUSE_UNICODE + if ( code == (WXUINT) TTN_NEEDTEXTA ) + { + // we pass just the pointer as we store the string internally anyhow + ttText->lpszText = (char *)ttip.c_str(); + } + else // TTN_NEEDTEXTW +#endif // !Unicode + { +#if wxUSE_UNICODE + // in Unicode mode this is just what we need + ttText->lpszText = (wxChar *)ttip.c_str(); +#else // !Unicode + // in ANSI mode we have to convert the string and put it into the + // provided buffer: be careful not to overrun it + const size_t lenAnsi = ttip.length(); + + // some compilers (MetroWerks and Cygwin) don't like calling mbstowcs + // with NULL argument + #if defined( __MWERKS__ ) || defined( __CYGWIN__ ) + size_t lenUnicode = 2*lenAnsi; + #else + size_t lenUnicode = mbstowcs(NULL, ttip, lenAnsi); + #endif + + // using the pointer of right type avoids us doing all sorts of + // pointer arithmetics ourselves + wchar_t *dst = (wchar_t *)ttText->szText, + *pwz = new wchar_t[lenUnicode + 1]; + mbstowcs(pwz, ttip, lenAnsi + 1); + + // stay inside the buffer (-1 because it must be NUL-terminated) + if ( lenUnicode > WXSIZEOF(ttText->szText) - 1 ) + { + lenUnicode = WXSIZEOF(ttText->szText) - 1; + } + + memcpy(dst, pwz, lenUnicode*sizeof(wchar_t)); + + // put the terminating wide NUL + dst[lenUnicode] = L'\0'; + + delete [] pwz; +#endif // Unicode/!Unicode + } + + return TRUE; +} + +#endif // wxUSE_TOOLTIPS + bool wxWindowMSW::MSWOnNotify(int WXUNUSED(idCtrl), - WXLPARAM lParam, - WXLPARAM* WXUNUSED(result)) + WXLPARAM lParam, + WXLPARAM* WXUNUSED(result)) { #if wxUSE_TOOLTIPS - NMHDR* hdr = (NMHDR *)lParam; - if ( (int)hdr->code == TTN_NEEDTEXT && m_tooltip ) + if ( m_tooltip ) { - TOOLTIPTEXT *ttt = (TOOLTIPTEXT *)lParam; - ttt->lpszText = (wxChar *)m_tooltip->GetTip().c_str(); - - // processed - return TRUE; + NMHDR* hdr = (NMHDR *)lParam; + if ( HandleTooltipNotify(hdr->code, lParam, m_tooltip->GetTip())) + { + // processed + return TRUE; + } } #endif // wxUSE_TOOLTIPS return FALSE; } + #endif // __WIN95__ // --------------------------------------------------------------------------- @@ -2919,8 +3262,33 @@ bool wxWindowMSW::HandleEndSession(bool endSession, long logOff) // window creation/destruction // --------------------------------------------------------------------------- -bool wxWindowMSW::HandleCreate(WXLPCREATESTRUCT WXUNUSED(cs), bool *mayCreate) +bool wxWindowMSW::HandleCreate(WXLPCREATESTRUCT cs, bool *mayCreate) { + // if we have WS_EX_CONTROLPARENT flag we absolutely *must* set it for our + // parent as well as otherwise several Win32 functions using + // GetNextDlgTabItem() to iterate over all controls such as + // IsDialogMessage() or DefDlgProc() would enter an infinite loop: indeed, + // all of them iterate over all the controls starting from the focus and + // stop iterating when they get back to the focus but unless all parents + // have WS_EX_CONTROLPARENT bit set, they would never get back to focus + if ( ((CREATESTRUCT *)cs)->dwExStyle & WS_EX_CONTROLPARENT ) + { + // there is no need to do anything for the top level windows + const wxWindow *parent = GetParent(); + while ( parent && !parent->IsTopLevel() ) + { + LONG exStyle = ::GetWindowLong(GetHwndOf(parent), GWL_EXSTYLE); + if ( !(exStyle & WS_EX_CONTROLPARENT) ) + { + // force the parent to have this style + ::SetWindowLong(GetHwndOf(parent), GWL_EXSTYLE, + exStyle | WS_EX_CONTROLPARENT); + } + + parent = parent->GetParent(); + } + } + // TODO: should generate this event from WM_NCCREATE wxWindowCreateEvent event((wxWindow *)this); (void)GetEventHandler()->ProcessEvent(event); @@ -3019,6 +3387,13 @@ bool wxWindowMSW::HandleKillFocus(WXHWND hwnd) } #endif + // Don't send the event when in the process of being deleted. This can + // only cause problems if the event handler tries to access the object. + if ( m_isBeingDeleted ) + { + return FALSE; + } + wxFocusEvent event(wxEVT_KILL_FOCUS, m_windowId); event.SetEventObject(this); @@ -3166,6 +3541,8 @@ bool wxWindowMSW::HandleSetCursor(WXHWND WXUNUSED(hWnd), if ( hcursor ) { +// wxLogDebug("HandleSetCursor: Setting cursor %ld", (long) hcursor); + ::SetCursor(hcursor); // cursor set, stop here @@ -3257,6 +3634,18 @@ bool wxWindowMSW::HandleSysColorChange() wxSysColourChangedEvent event; event.SetEventObject(this); + (void)GetEventHandler()->ProcessEvent(event); + + // always let the system carry on the default processing to allow the + // native controls to react to the colours update + return FALSE; +} + +bool wxWindowMSW::HandleDisplayChange() +{ + wxDisplayChangedEvent event; + event.SetEventObject(this); + return GetEventHandler()->ProcessEvent(event); } @@ -3306,6 +3695,40 @@ WXHBRUSH wxWindowMSW::OnCtlColor(WXHDC WXUNUSED(hDC), bool wxWindowMSW::HandlePaletteChanged(WXHWND hWndPalChange) { +#if wxUSE_PALETTE + // same as below except we don't respond to our own messages + if ( hWndPalChange != GetHWND() ) + { + // check to see if we our our parents have a custom palette + wxWindowMSW *win = this; + while ( win && !win->HasCustomPalette() ) + { + win = win->GetParent(); + } + + if ( win && win->HasCustomPalette() ) + { + // realize the palette to see whether redrawing is needed + HDC hdc = ::GetDC((HWND) hWndPalChange); + win->m_palette.SetHPALETTE((WXHPALETTE) + ::SelectPalette(hdc, GetHpaletteOf(win->m_palette), FALSE)); + + int result = ::RealizePalette(hdc); + + // restore the palette (before releasing the DC) + win->m_palette.SetHPALETTE((WXHPALETTE) + ::SelectPalette(hdc, GetHpaletteOf(win->m_palette), FALSE)); + ::RealizePalette(hdc); + ::ReleaseDC((HWND) hWndPalChange, hdc); + + // now check for the need to redraw + if (result > 0) + InvalidateRect((HWND) hWndPalChange, NULL, TRUE); + } + + } +#endif // wxUSE_PALETTE + wxPaletteChangedEvent event(GetId()); event.SetEventObject(this); event.SetChangedWindow(wxFindWinFromHandle(hWndPalChange)); @@ -3313,8 +3736,39 @@ bool wxWindowMSW::HandlePaletteChanged(WXHWND hWndPalChange) return GetEventHandler()->ProcessEvent(event); } +bool wxWindowMSW::HandleCaptureChanged(WXHWND hWndGainedCapture) +{ + wxMouseCaptureChangedEvent event(GetId(), wxFindWinFromHandle(hWndGainedCapture)); + event.SetEventObject(this); + + return GetEventHandler()->ProcessEvent(event); +} + bool wxWindowMSW::HandleQueryNewPalette() { + +#if wxUSE_PALETTE + // check to see if we our our parents have a custom palette + wxWindowMSW *win = this; + while (!win->HasCustomPalette() && win->GetParent()) win = win->GetParent(); + if (win->HasCustomPalette()) { + /* realize the palette to see whether redrawing is needed */ + HDC hdc = GetDC((HWND) GetHWND()); + win->m_palette.SetHPALETTE( (WXHPALETTE) + ::SelectPalette(hdc, (HPALETTE) win->m_palette.GetHPALETTE(), FALSE) ); + + int result = ::RealizePalette(hdc); + /* restore the palette (before releasing the DC) */ + win->m_palette.SetHPALETTE( (WXHPALETTE) + ::SelectPalette(hdc, (HPALETTE) win->m_palette.GetHPALETTE(), TRUE) ); + ::RealizePalette(hdc); + ::ReleaseDC((HWND) GetHWND(), hdc); + /* now check for the need to redraw */ + if (result > 0) + ::InvalidateRect((HWND) GetHWND(), NULL, TRUE); + } +#endif // wxUSE_PALETTE + wxQueryNewPaletteEvent event(GetId()); event.SetEventObject(this); @@ -3322,24 +3776,114 @@ bool wxWindowMSW::HandleQueryNewPalette() } // Responds to colour changes: passes event on to children. -void wxWindowMSW::OnSysColourChanged(wxSysColourChangedEvent& event) +void wxWindowMSW::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event)) { - wxNode *node = GetChildren().First(); + // the top level window also reset the standard colour map as it might have + // changed (there is no need to do it for the non top level windows as we + // only have to do it once) + if ( IsTopLevel() ) + { + // FIXME-MT + gs_hasStdCmap = FALSE; + } + wxWindowList::Node *node = GetChildren().GetFirst(); while ( node ) { - // Only propagate to non-top-level windows - wxWindow *win = (wxWindow *)node->Data(); - if ( win->GetParent() ) + // Only propagate to non-top-level windows because Windows already + // sends this event to all top-level ones + wxWindow *win = node->GetData(); + if ( !win->IsTopLevel() ) { - wxSysColourChangedEvent event2; - event.m_eventObject = win; - win->GetEventHandler()->ProcessEvent(event2); + // we need to send the real WM_SYSCOLORCHANGE and not just trigger + // EVT_SYS_COLOUR_CHANGED call because the latter wouldn't work for + // the standard controls + ::SendMessage(GetHwndOf(win), WM_SYSCOLORCHANGE, 0, 0); } - node = node->Next(); + node = node->GetNext(); + } + + // update the colours we use if they were not set explicitly by the user: + // this must be done or OnCtlColor() would continue to use the old colours + if ( !m_hasFgCol ) + { + m_foregroundColour = wxSystemSettings:: + GetSystemColour(wxSYS_COLOUR_WINDOWTEXT); + } + + if ( !m_hasBgCol ) + { + m_backgroundColour = wxSystemSettings:: + GetSystemColour(wxSYS_COLOUR_BTNFACE); } } +extern wxCOLORMAP *wxGetStdColourMap() +{ + static COLORREF s_stdColours[wxSTD_COL_MAX]; + static wxCOLORMAP s_cmap[wxSTD_COL_MAX]; + + if ( !gs_hasStdCmap ) + { + static bool s_coloursInit = FALSE; + + if ( !s_coloursInit ) + { + // When a bitmap is loaded, the RGB values can change (apparently + // because Windows adjusts them to care for the old programs always + // using 0xc0c0c0 while the transparent colour for the new Windows + // versions is different). But we do this adjustment ourselves so + // we want to avoid Windows' "help" and for this we need to have a + // reference bitmap which can tell us what the RGB values change + // to. + wxBitmap stdColourBitmap(_T("wxBITMAP_STD_COLOURS")); + if ( stdColourBitmap.Ok() ) + { + // the pixels in the bitmap must correspond to wxSTD_COL_XXX! + wxASSERT_MSG( stdColourBitmap.GetWidth() == wxSTD_COL_MAX, + _T("forgot to update wxBITMAP_STD_COLOURS!") ); + + wxMemoryDC memDC; + memDC.SelectObject(stdColourBitmap); + + wxColour colour; + for ( size_t i = 0; i < WXSIZEOF(s_stdColours); i++ ) + { + memDC.GetPixel(i, 0, &colour); + s_stdColours[i] = wxColourToRGB(colour); + } + } + else // wxBITMAP_STD_COLOURS couldn't be loaded + { + s_stdColours[0] = RGB(000,000,000); // black + s_stdColours[1] = RGB(128,128,128); // dark grey + s_stdColours[2] = RGB(192,192,192); // light grey + s_stdColours[3] = RGB(255,255,255); // white + //s_stdColours[4] = RGB(000,000,255); // blue + //s_stdColours[5] = RGB(255,000,255); // magenta + } + + s_coloursInit = TRUE; + } + + gs_hasStdCmap = TRUE; + + // create the colour map +#define INIT_CMAP_ENTRY(col) \ + s_cmap[wxSTD_COL_##col].from = s_stdColours[wxSTD_COL_##col]; \ + s_cmap[wxSTD_COL_##col].to = ::GetSysColor(COLOR_##col) + + INIT_CMAP_ENTRY(BTNTEXT); + INIT_CMAP_ENTRY(BTNSHADOW); + INIT_CMAP_ENTRY(BTNFACE); + INIT_CMAP_ENTRY(BTNHIGHLIGHT); + +#undef INIT_CMAP_ENTRY + } + + return s_cmap; +} + // --------------------------------------------------------------------------- // painting // --------------------------------------------------------------------------- @@ -3465,9 +4009,13 @@ bool wxWindowMSW::HandleMove(int x, int y) return GetEventHandler()->ProcessEvent(event); } -bool wxWindowMSW::HandleSize(int w, int h, WXUINT WXUNUSED(flag)) +bool wxWindowMSW::HandleSize(int WXUNUSED(w), int WXUNUSED(h), + WXUINT WXUNUSED(flag)) { - wxSizeEvent event(wxSize(w, h), m_windowId); + // don't use w and h parameters as they specify the client size while + // according to the docs EVT_SIZE handler is supposed to receive the total + // size + wxSizeEvent event(GetSize(), m_windowId); event.SetEventObject(this); return GetEventHandler()->ProcessEvent(event); @@ -3479,27 +4027,32 @@ bool wxWindowMSW::HandleGetMinMaxInfo(void *mmInfo) bool rc = FALSE; - if ( m_minWidth != -1 ) + int minWidth = GetMinWidth(), + minHeight = GetMinHeight(), + maxWidth = GetMaxWidth(), + maxHeight = GetMaxHeight(); + + if ( minWidth != -1 ) { - info->ptMinTrackSize.x = m_minWidth; + info->ptMinTrackSize.x = minWidth; rc = TRUE; } - if ( m_minHeight != -1 ) + if ( minHeight != -1 ) { - info->ptMinTrackSize.y = m_minHeight; + info->ptMinTrackSize.y = minHeight; rc = TRUE; } - if ( m_maxWidth != -1 ) + if ( maxWidth != -1 ) { - info->ptMaxTrackSize.x = m_maxWidth; + info->ptMaxTrackSize.x = maxWidth; rc = TRUE; } - if ( m_maxHeight != -1 ) + if ( maxHeight != -1 ) { - info->ptMaxTrackSize.y = m_maxHeight; + info->ptMaxTrackSize.y = maxHeight; rc = TRUE; } @@ -3611,6 +4164,7 @@ void wxWindowMSW::InitMouseEvent(wxMouseEvent& event, event.SetTimestamp(s_currentMsg.time); event.m_eventObject = this; + event.SetId(GetId()); #if wxUSE_MOUSEEVENT_HACK m_lastMouseX = x; @@ -3619,6 +4173,64 @@ void wxWindowMSW::InitMouseEvent(wxMouseEvent& event, #endif // wxUSE_MOUSEEVENT_HACK } +// Windows doesn't send the mouse events to the static controls (which are +// transparent in the sense that their WM_NCHITTEST handler returns +// HTTRANSPARENT) at all but we want all controls to receive the mouse events +// and so we manually check if we don't have a child window under mouse and if +// we do, send the event to it instead of the window Windows had sent WM_XXX +// to. +// +// Notice that this is not done for the mouse move events because this could +// (would?) be too slow, but only for clicks which means that the static texts +// still don't get move, enter nor leave events. +static wxWindowMSW *FindWindowForMouseEvent(wxWindowMSW *win, int *x, int *y) //TW:REQ:Univ +{ + wxCHECK_MSG( x && y, win, _T("NULL pointer in FindWindowForMouseEvent") ); + + // first try to find a non transparent child: this allows us to send events + // to a static text which is inside a static box, for example + POINT pt = { *x, *y }; + HWND hwnd = GetHwndOf(win), + hwndUnderMouse; + +#ifdef __WIN32__ + hwndUnderMouse = ::ChildWindowFromPointEx + ( + hwnd, + pt, + CWP_SKIPINVISIBLE | + CWP_SKIPDISABLED | + CWP_SKIPTRANSPARENT + ); + + if ( !hwndUnderMouse || hwndUnderMouse == hwnd ) +#endif // __WIN32__ + { + // now try any child window at all + hwndUnderMouse = ::ChildWindowFromPoint(hwnd, pt); + } + + // check that we have a child window which is susceptible to receive mouse + // events: for this it must be shown and enabled + if ( hwndUnderMouse && + hwndUnderMouse != hwnd && + ::IsWindowVisible(hwndUnderMouse) && + ::IsWindowEnabled(hwndUnderMouse) ) + { + wxWindow *winUnderMouse = wxFindWinFromHandle((WXHWND)hwndUnderMouse); + if ( winUnderMouse ) + { + // translate the mouse coords to the other window coords + win->ClientToScreen(x, y); + winUnderMouse->ScreenToClient(x, y); + + win = winUnderMouse; + } + } + + return win; +} + bool wxWindowMSW::HandleMouseEvent(WXUINT msg, int x, int y, WXUINT flags) { // the mouse events take consecutive IDs from WM_MOUSEFIRST to @@ -3734,7 +4346,8 @@ bool wxWindowMSW::HandleMouseWheel(WXWPARAM wParam, WXLPARAM lParam) // HandleChar and HandleKeyDown/Up wxKeyEvent wxWindowMSW::CreateKeyEvent(wxEventType evType, int id, - WXLPARAM lParam) const + WXLPARAM lParam, + WXWPARAM wParam) const { wxKeyEvent event(evType); event.SetId(GetId()); @@ -3744,6 +4357,8 @@ wxKeyEvent wxWindowMSW::CreateKeyEvent(wxEventType evType, event.m_eventObject = (wxWindow *)this; // const_cast event.m_keyCode = id; + event.m_rawCode = (wxUint32) wParam; + event.m_rawFlags = (wxUint32) lParam; event.SetTimestamp(s_currentMsg.time); // translate the position to client coords @@ -3769,7 +4384,9 @@ bool wxWindowMSW::HandleChar(WXWPARAM wParam, WXLPARAM lParam, bool isASCII) int id; if ( isASCII ) { - // If 1 -> 26, translate to CTRL plus a letter. + // If 1 -> 26, translate to either special keycode or just set + // ctrlDown. IOW, Ctrl-C should result in keycode == 3 and + // ControlDown() == TRUE. id = wParam; if ( (id > 0) && (id < 27) ) { @@ -3789,30 +4406,37 @@ bool wxWindowMSW::HandleChar(WXWPARAM wParam, WXLPARAM lParam, bool isASCII) default: ctrlDown = TRUE; - id = id + 96; + break; } } } - else if ( (id = wxCharCodeMSWToWX(wParam)) == 0 ) + else // we're called from WM_KEYDOWN { - // it's ASCII and will be processed here only when called from - // WM_CHAR (i.e. when isASCII = TRUE), don't process it now - id = -1; - } - - if ( id != -1 ) - { - wxKeyEvent event(CreateKeyEvent(wxEVT_CHAR, id, lParam)); - if ( ctrlDown ) + id = wxCharCodeMSWToWX(wParam); + if ( id == 0 ) { - event.m_controlDown = TRUE; + // it's ASCII and will be processed here only when called from + // WM_CHAR (i.e. when isASCII = TRUE), don't process it now + return FALSE; } + } - if ( GetEventHandler()->ProcessEvent(event) ) - return TRUE; + wxKeyEvent event(CreateKeyEvent(wxEVT_CHAR, id, lParam, wParam)); + + // the alphanumeric keys produced by pressing AltGr+something on European + // keyboards have both Ctrl and Alt modifiers which may confuse the user + // code as, normally, keys with Ctrl and/or Alt don't result in anything + // alphanumeric, so pretend that there are no modifiers at all (the + // KEY_DOWN event would still have the correct modifiers if they're really + // needed) + if ( event.m_controlDown && event.m_altDown && + (id >= 32 && id < 256) ) + { + event.m_controlDown = + event.m_altDown = FALSE; } - return FALSE; + return GetEventHandler()->ProcessEvent(event); } bool wxWindowMSW::HandleKeyDown(WXWPARAM wParam, WXLPARAM lParam) @@ -3827,7 +4451,7 @@ bool wxWindowMSW::HandleKeyDown(WXWPARAM wParam, WXLPARAM lParam) if ( id != -1 ) // VZ: does this ever happen (FIXME)? { - wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_DOWN, id, lParam)); + wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_DOWN, id, lParam, wParam)); if ( GetEventHandler()->ProcessEvent(event) ) { return TRUE; @@ -3849,7 +4473,7 @@ bool wxWindowMSW::HandleKeyUp(WXWPARAM wParam, WXLPARAM lParam) if ( id != -1 ) // VZ: does this ever happen (FIXME)? { - wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_UP, id, lParam)); + wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_UP, id, lParam, wParam)); if ( GetEventHandler()->ProcessEvent(event) ) return TRUE; } @@ -3857,6 +4481,69 @@ bool wxWindowMSW::HandleKeyUp(WXWPARAM wParam, WXLPARAM lParam) return FALSE; } +#ifdef __WIN32__ + +int wxWindowMSW::HandleMenuChar(int chAccel, WXLPARAM lParam) +{ + const HMENU hmenu = (HMENU)lParam; + + MENUITEMINFO mii; + wxZeroMemory(mii); + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_TYPE | MIIM_DATA; + + // find if we have this letter in any owner drawn item + const int count = ::GetMenuItemCount(hmenu); + for ( int i = 0; i < count; i++ ) + { + if ( ::GetMenuItemInfo(hmenu, i, TRUE, &mii) ) + { + if ( mii.fType == MFT_OWNERDRAW ) + { + // dwItemData member of the MENUITEMINFO is a + // pointer to the associated wxMenuItem -- see the + // menu creation code + wxMenuItem *item = (wxMenuItem*)mii.dwItemData; + + const wxChar *p = wxStrchr(item->GetText(), _T('&')); + while ( p++ ) + { + if ( *p == _T('&') ) + { + // this is not the accel char, find the real one + p = wxStrchr(p + 1, _T('&')); + } + else // got the accel char + { + // FIXME-UNICODE: this comparison doesn't risk to work + // for non ASCII accelerator characters I'm afraid, but + // what can we do? + if ( wxToupper(*p) == chAccel ) + { + return i; + } + else + { + // this one doesn't match + break; + } + } + } + } + } + else // failed ot get the menu text? + { + // it's not fatal, so don't show error, but still log + // it + wxLogLastError(_T("GetMenuItemInfo")); + } + } + + return wxNOT_FOUND; +} + +#endif // __WIN32__ + // --------------------------------------------------------------------------- // joystick // --------------------------------------------------------------------------- @@ -3950,7 +4637,7 @@ bool wxWindowMSW::HandleJoystickEvent(WXUINT msg, int x, int y, WXUINT flags) // --------------------------------------------------------------------------- bool wxWindowMSW::MSWOnScroll(int orientation, WXWORD wParam, - WXWORD pos, WXHWND control) + WXWORD pos, WXHWND control) { if ( control ) { @@ -4078,6 +4765,7 @@ int wxCharCodeMSWToWX(int keySym) case VK_CONTROL: id = WXK_CONTROL; break; case VK_MENU : id = WXK_MENU; break; case VK_PAUSE: id = WXK_PAUSE; break; + case VK_CAPITAL: id = WXK_CAPITAL; break; case VK_SPACE: id = WXK_SPACE; break; case VK_ESCAPE: id = WXK_ESCAPE; break; case VK_PRIOR: id = WXK_PRIOR; break; @@ -4135,6 +4823,19 @@ int wxCharCodeMSWToWX(int keySym) case VK_F24: id = WXK_F24; break; case VK_NUMLOCK: id = WXK_NUMLOCK; break; case VK_SCROLL: id = WXK_SCROLL; break; + + case VK_OEM_1: id = ';'; break; + case VK_OEM_PLUS: id = '+'; break; + case VK_OEM_COMMA: id = ','; break; + case VK_OEM_MINUS: id = '-'; break; + case VK_OEM_PERIOD: id = '.'; break; + case VK_OEM_2: id = '/'; break; + case VK_OEM_3: id = '~'; break; + case VK_OEM_4: id = '['; break; + case VK_OEM_5: id = '\\'; break; + case VK_OEM_6: id = ']'; break; + case VK_OEM_7: id = '\''; break; + default: id = 0; } @@ -4849,9 +5550,10 @@ static TEXTMETRIC wxGetTextMetrics(const wxWindowMSW *win) // Find the wxWindow at the current mouse position, returning the mouse // position. -wxWindow* wxFindWindowAtPointer(wxPoint& WXUNUSED(pt)) +wxWindow* wxFindWindowAtPointer(wxPoint& pt) { - return wxFindWindowAtPoint(wxGetMousePosition()); + pt = wxGetMousePosition(); + return wxFindWindowAtPoint(pt); } wxWindow* wxFindWindowAtPoint(const wxPoint& pt) @@ -4878,6 +5580,7 @@ wxPoint wxGetMousePosition() { POINT pt; GetCursorPos( & pt ); + return wxPoint(pt.x, pt.y); }