From: Gabriel Ivăncescu Subject: [PATCH 2/4] shell32/autocomplete: Implement the listbox resizing grip and make it resize-able Message-Id: <46243efb943645eb4bbb5117ce2f903736a41ece.1554465958.git.gabrielopcode@gmail.com> Date: Fri, 5 Apr 2019 15:14:25 +0300 In-Reply-To: <61b38176f8a7619309066752ebdaa687468dec0a.1554465958.git.gabrielopcode@gmail.com> References: <61b38176f8a7619309066752ebdaa687468dec0a.1554465958.git.gabrielopcode@gmail.com> Signed-off-by: Gabriel Ivăncescu --- The scrollbar and grip are implemented as non-client area (just like the listbox's normal vertical scrollbar) which simplifies the code and avoids having to meddle with yet another window (which would also add more flickering when resizing as well). This means that the grip has to be drawn manually, but that needs to happen anyway for the last patch in the series, which flips the grip vertically if required, so it's needed anyway. Some changes (e.g. in WM_NCCALCSIZE) are done to keep the last patch also smaller. dlls/shell32/autocomplete.c | 150 +++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 10 deletions(-) diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c index 38ca8ca..136d9e8 100644 --- a/dlls/shell32/autocomplete.c +++ b/dlls/shell32/autocomplete.c @@ -62,6 +62,8 @@ typedef struct UINT enum_strs_num; WCHAR **enum_strs; WCHAR **listbox_strs; + UINT listbox_width; + UINT listbox_height; HWND hwndEdit; HWND hwndListBox; HWND hwndListBoxOwner; @@ -288,8 +290,14 @@ static void free_enum_strs(IAutoCompleteImpl *ac) static void update_listbox_size(IAutoCompleteImpl *ac, UINT width, UINT height) { - UINT item_height, listbox_width, grip_sz, grip_height; + UINT item_height, listbox_width, grip_sz, grip_height, grip_top = 0; SCROLLINFO info; + BOOL had_scroll; + RECT r; + + /* Scrollbar was visible if the grip's client height is nonzero */ + GetClientRect(ac->hwndListBoxGrip, &r); + had_scroll = r.bottom != r.top; info.nMax = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0) - 1; item_height = SendMessageW(ac->hwndListBox, LB_GETITEMHEIGHT, 0, 0); @@ -300,7 +308,7 @@ static void update_listbox_size(IAutoCompleteImpl *ac, UINT width, UINT height) /* Set the scrollbar info if it's visible */ listbox_width = width; - if (info.nMax >= info.nPage) + if (info.nMax >= info.nPage && height > grip_sz) { grip_height = height; info.cbSize = sizeof(info); @@ -309,12 +317,30 @@ static void update_listbox_size(IAutoCompleteImpl *ac, UINT width, UINT height) info.nPos = SendMessageW(ac->hwndListBox, LB_GETTOPINDEX, 0, 0); SetScrollInfo(ac->hwndListBoxGrip, SB_VERT, &info, FALSE); + if (!had_scroll) SetWindowRgn(ac->hwndListBoxGrip, NULL, FALSE); listbox_width -= grip_sz; } else - grip_height = 0; + { + /* Scrollbar is not visible so adjust the position and size */ + grip_top += height - grip_sz; + grip_height = grip_sz; + + if (had_scroll) + { + /* The grip is a triangle when the scrollbar is not visible */ + HRGN hrgn; + POINT pt[3]; + pt[0].x = pt[1].y = pt[2].x = pt[2].y = grip_sz; + pt[0].y = pt[1].x = 0; + + hrgn = CreatePolygonRgn(pt, ARRAY_SIZE(pt), WINDING); + if (hrgn && !SetWindowRgn(ac->hwndListBoxGrip, hrgn, FALSE)) + DeleteObject(hrgn); + } + } - SetWindowPos(ac->hwndListBoxGrip, NULL, width - grip_sz, 0, grip_sz, grip_height, + SetWindowPos(ac->hwndListBoxGrip, NULL, width - grip_sz, grip_top, grip_sz, grip_height, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_DEFERERASE); SetWindowPos(ac->hwndListBox, NULL, 0, 0, listbox_width, height, @@ -335,10 +361,19 @@ static void show_listbox(IAutoCompleteImpl *ac) GetWindowRect(ac->hwndEdit, &r); - /* Windows XP displays 7 lines at most, then it uses a scroll bar */ - cnt = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0); - height = SendMessageW(ac->hwndListBox, LB_GETITEMHEIGHT, 0, 0) * min(cnt + 1, 7); - width = r.right - r.left; + /* See if the listbox has been resized by the user */ + if (ac->listbox_width) + { + width = ac->listbox_width; + height = ac->listbox_height; + } + else + { + /* Windows XP displays 7 lines at most, then it uses a scroll bar */ + cnt = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0); + height = SendMessageW(ac->hwndListBox, LB_GETITEMHEIGHT, 0, 0) * min(cnt + 1, 7); + width = r.right - r.left; + } SetWindowPos(ac->hwndListBoxOwner, HWND_TOP, r.left, r.bottom + 1, width, height, SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOSENDCHANGING | SWP_DEFERERASE); @@ -403,6 +438,54 @@ static BOOL draw_listbox_item(IAutoCompleteImpl *ac, DRAWITEMSTRUCT *info, UINT return TRUE; } +static void draw_listbox_grip(HWND hwnd, HRGN clip) +{ + LONG orig_top; + int type; + HRGN hrgn; + HDC hdc; + RECT r; + + /* Get the grip's square (without the scrollbar) */ + GetWindowRect(hwnd, &r); + orig_top = r.top; + r.top = r.bottom - GetSystemMetrics(SM_CXVSCROLL); + + /* Draw the scrollbar, if visible */ + if (r.top > orig_top) DefWindowProcW(hwnd, WM_NCPAINT, (WPARAM)clip, 0); + + /* Create a region for the grip */ + if (!(hrgn = CreateRectRgnIndirect(&r))) return; + + /* Exclude the clipping region outside of the grip */ + if ((clip > (HRGN)1 && + ((type = CombineRgn(hrgn, clip, hrgn, RGN_AND)) == ERROR || type == NULLREGION) + ) + || !(hdc = GetDCEx(hwnd, hrgn, DCX_CACHE | DCX_WINDOW | DCX_INTERSECTRGN))) + { + DeleteObject(hrgn); + return; + } + + /* Adjust for DrawFrameControl */ + OffsetRect(&r, -r.left, -orig_top); + + DrawFrameControl(hdc, &r, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); + ReleaseDC(hwnd, hdc); +} + +static BOOL hit_test_listbox_grip(IAutoCompleteImpl *ac, LPARAM lParam) +{ + /* The grip's width is always the same as its height */ + POINT pt = { (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam) }; + RECT r; + + GetWindowRect(ac->hwndListBoxGrip, &r); + r.top = r.bottom - GetSystemMetrics(SM_CXVSCROLL); + + return PtInRect(&r, pt); +} + static size_t format_quick_complete(WCHAR *dst, const WCHAR *qc, const WCHAR *str, size_t str_len) { /* Replace the first %s directly without using snprintf, to avoid @@ -935,6 +1018,10 @@ static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, SetScrollInfo(This->hwndListBoxGrip, SB_VERT, &info, TRUE); return ret; } + case WM_NCHITTEST: + if (hit_test_listbox_grip(This, lParam)) + return HTTRANSPARENT; + break; } return CallWindowProcW(This->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam); } @@ -951,6 +1038,19 @@ static LRESULT APIENTRY ACLBoxOwnerSubclassProc(HWND hwnd, UINT uMsg, WPARAM wPa if (draw_listbox_item(This, (DRAWITEMSTRUCT*)lParam, wParam)) return TRUE; break; + case WM_GETMINMAXINFO: + { + /* Prevent shrinking the dropdown below the grip's size */ + UINT grip_sz = GetSystemMetrics(SM_CXVSCROLL); + ((MINMAXINFO*)lParam)->ptMinTrackSize.x = grip_sz + GetSystemMetrics(SM_CXBORDER) * 2; + ((MINMAXINFO*)lParam)->ptMinTrackSize.y = grip_sz + GetSystemMetrics(SM_CYBORDER) * 2; + return 0; + } + case WM_WINDOWPOSCHANGING: + /* Fix a bug when resizing against the taskbar */ + ((WINDOWPOS*)lParam)->flags |= SWP_NOACTIVATE | SWP_NOREPOSITION | SWP_DEFERERASE; + ((WINDOWPOS*)lParam)->hwndInsertAfter = HWND_TOP; + break; case WM_WINDOWPOSCHANGED: { const WINDOWPOS *wp = (const WINDOWPOS*)lParam; @@ -960,10 +1060,27 @@ static LRESULT APIENTRY ACLBoxOwnerSubclassProc(HWND hwnd, UINT uMsg, WPARAM wPa /* Only proceed with the update if the user resized it */ if (wp->flags & SWP_NOSENDCHANGING) break; + This->listbox_width = wp->cx; + This->listbox_height = wp->cy; update_listbox_size(This, wp->cx, wp->cy); } break; } + case WM_NCHITTEST: + if (hit_test_listbox_grip(This, lParam)) + return HTBOTTOMRIGHT; + break; + case WM_NCLBUTTONDBLCLK: + if (hit_test_listbox_grip(This, lParam)) + { + /* For convenience, double-click on the grip reverts to auto sizing */ + This->listbox_width = 0; + show_listbox(This); + return 0; + } + /* fall through */ + case WM_NCLBUTTONDOWN: + return DefWindowProcW(hwnd, uMsg, wParam, lParam); } return CallWindowProcW(This->wpOrigLBoxOwnerProc, hwnd, uMsg, wParam, lParam); } @@ -978,17 +1095,30 @@ static LRESULT APIENTRY ACLBoxGripSubclassProc(HWND hwnd, UINT uMsg, WPARAM wPar return MA_NOACTIVATE; case WM_VSCROLL: return SendMessageW(This->hwndListBox, uMsg, wParam, lParam); + case WM_WINDOWPOSCHANGING: + /* This shouldn't happen, don't let the user move or resize the grip */ + ((WINDOWPOS*)lParam)->flags |= SWP_NOACTIVATE | SWP_NOREPOSITION | SWP_NOMOVE | + SWP_NOSIZE | SWP_NOZORDER | SWP_DEFERERASE; + return 0; + case WM_NCPAINT: + draw_listbox_grip(hwnd, (HRGN)wParam); + return 0; case WM_NCCALCSIZE: { + UINT grip_sz; NCCALCSIZE_PARAMS *p = (NCCALCSIZE_PARAMS*)lParam; if (wParam == FALSE) return 0; + /* The grip is made up of non-client area only, so client width is zero, + and the client height is the height of the scrollbar (if visible) */ + grip_sz = GetSystemMetrics(SM_CXVSCROLL); p->rgrc[0].right = p->rgrc[0].left; + p->rgrc[0].bottom = max(p->rgrc[0].bottom - grip_sz, p->rgrc[0].top); return 0; } case WM_NCHITTEST: - return HTVSCROLL; + return hit_test_listbox_grip(This, lParam) ? HTTRANSPARENT : HTVSCROLL; case WM_NCLBUTTONDOWN: case WM_NCLBUTTONDBLCLK: return DefWindowProcW(hwnd, uMsg, wParam, lParam); @@ -1009,7 +1139,7 @@ static void create_listbox(IAutoCompleteImpl *This) return; } - /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */ + /* Create a size grip that will also host a scrollbar using non-client area */ This->hwndListBoxGrip = CreateWindowExW(WS_EX_NOACTIVATE, WC_STATICW, NULL, WS_CHILD | WS_VISIBLE, 0, 0, grip_sz, grip_sz, This->hwndListBoxOwner, NULL, shell32_hInstance, NULL); -- 2.20.1