/* * Scrollbar control * * Copyright 1993 Martin Ayotte * Copyright 1994, 1996 Alexandre Julliard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #if 0 #pragma makedep unix #endif #include "ntgdi_private.h" #include "ntuser_private.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(scroll); #define SCROLLBAR_MAGIC 0x5c6011ba /* Minimum size of the rectangle between the arrows */ #define SCROLL_MIN_RECT 4 /* Minimum size of the thumb in pixels */ #define SCROLL_MIN_THUMB 8 /* Overlap between arrows and thumb */ #define SCROLL_ARROW_THUMB_OVERLAP 0 /* Delay (in ms) before first repetition when holding the button down */ #define SCROLL_FIRST_DELAY 200 /* Delay (in ms) between scroll repetitions */ #define SCROLL_REPEAT_DELAY 50 /* Scroll timer id */ #define SCROLL_TIMER 0 /* What to do after set_scroll_info() */ #define SA_SSI_HIDE 0x0001 #define SA_SSI_SHOW 0x0002 #define SA_SSI_REFRESH 0x0004 #define SA_SSI_REPAINT_ARROWS 0x0008 /* Scroll Bar tracking information */ static struct SCROLL_TRACKING_INFO g_tracking_info; /* Is the moving thumb being displayed? */ static BOOL scroll_moving_thumb = FALSE; /* data for window that has (one or two) scroll bars */ struct win_scroll_bar_info { struct scroll_info horz; struct scroll_info vert; }; #define SCROLLBAR_MAGIC 0x5c6011ba static struct scroll_info *get_scroll_info_ptr( HWND hwnd, int bar, BOOL alloc ) { struct scroll_info *info = NULL; WND *win = get_win_ptr( hwnd ); if (!win || win == WND_OTHER_PROCESS || win == WND_DESKTOP) return NULL; switch (bar) { case SB_HORZ: if (win->pScroll) info = &win->pScroll->horz; break; case SB_VERT: if (win->pScroll) info = &win->pScroll->vert; break; case SB_CTL: if (win->cbWndExtra >= sizeof(struct scroll_bar_win_data)) { struct scroll_bar_win_data *data = (struct scroll_bar_win_data *)win->wExtra; if (data->magic == SCROLLBAR_MAGIC) info = &data->info; } if (!info) WARN( "window is not a scrollbar control\n" ); break; case SB_BOTH: WARN( "with SB_BOTH\n" ); break; } if (!info && alloc) { struct win_scroll_bar_info *win_info; if (bar != SB_HORZ && bar != SB_VERT) WARN("Cannot initialize bar=%d\n",bar); else if ((win_info = malloc( sizeof(struct win_scroll_bar_info) ))) { /* Set default values */ win_info->horz.minVal = 0; win_info->horz.curVal = 0; win_info->horz.page = 0; /* From MSDN and our own tests: * max for a standard scroll bar is 100 by default. */ win_info->horz.maxVal = 100; win_info->horz.flags = ESB_ENABLE_BOTH; win_info->vert = win_info->horz; win->pScroll = win_info; info = bar == SB_HORZ ? &win_info->horz : &win_info->vert; } } if (info) user_lock(); release_win_ptr( win ); return info; } static void release_scroll_info_ptr( struct scroll_info *info ) { user_unlock(); } static BOOL show_scroll_bar( HWND hwnd, int bar, BOOL show_horz, BOOL show_vert ) { ULONG old_style, set_bits = 0, clear_bits = 0; TRACE( "hwnd=%p bar=%d horz=%d, vert=%d\n", hwnd, bar, show_horz, show_vert ); switch (bar) { case SB_CTL: NtUserShowWindow( hwnd, show_horz ? SW_SHOW : SW_HIDE ); return TRUE; case SB_BOTH: case SB_HORZ: if (show_horz) set_bits |= WS_HSCROLL; else clear_bits |= WS_HSCROLL; if (bar == SB_HORZ) break; /* fall through */ case SB_VERT: if (show_vert) set_bits |= WS_VSCROLL; else clear_bits |= WS_VSCROLL; break; default: return FALSE; /* Nothing to do! */ } old_style = set_window_style( hwnd, set_bits, clear_bits ); if ((old_style & clear_bits) != 0 || (old_style & set_bits) != set_bits) { /* frame has been changed, let the window redraw itself */ NtUserSetWindowPos( hwnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED ); return TRUE; } return FALSE; /* no frame changes */ } /*********************************************************************** * get_scroll_bar_rect * * Compute the scroll bar rectangle, in drawing coordinates (i.e. client * coords for SB_CTL, window coords for SB_VERT and SB_HORZ). * 'arrow_size' returns the width or height of an arrow (depending on * the orientation of the scrollbar), 'thumb_size' returns the size of * the thumb, and 'thumb_pos' returns the position of the thumb * relative to the left or to the top. * Return TRUE if the scrollbar is vertical, FALSE if horizontal. */ static BOOL get_scroll_bar_rect( HWND hwnd, int bar, RECT *rect, int *arrow_size, int *thumb_size, int *thumb_pos ) { int pixels, min_thumb_size; BOOL vertical; WND *win = get_win_ptr( hwnd ); if (!win || win == WND_OTHER_PROCESS || win == WND_DESKTOP) return FALSE; switch(bar) { case SB_HORZ: get_window_rects( hwnd, COORDS_WINDOW, NULL, rect, get_thread_dpi() ); rect->top = rect->bottom; rect->bottom += get_system_metrics( SM_CYHSCROLL ); if (win->dwStyle & WS_VSCROLL) rect->right++; vertical = FALSE; break; case SB_VERT: get_window_rects( hwnd, COORDS_WINDOW, NULL, rect, get_thread_dpi() ); if (win->dwExStyle & WS_EX_LEFTSCROLLBAR) { rect->right = rect->left; rect->left -= get_system_metrics( SM_CXVSCROLL ); } else { rect->left = rect->right; rect->right += get_system_metrics( SM_CXVSCROLL ); } if (win->dwStyle & WS_HSCROLL) rect->bottom++; vertical = TRUE; break; case SB_CTL: get_client_rect( hwnd, rect ); vertical = (win->dwStyle & SBS_VERT) != 0; break; default: release_win_ptr( win ); return FALSE; } if (vertical) pixels = rect->bottom - rect->top; else pixels = rect->right - rect->left; if (pixels <= 2 * get_system_metrics( SM_CXVSCROLL ) + SCROLL_MIN_RECT) { if (pixels > SCROLL_MIN_RECT) *arrow_size = (pixels - SCROLL_MIN_RECT) / 2; else *arrow_size = 0; *thumb_pos = *thumb_size = 0; } else { struct scroll_info *info = get_scroll_info_ptr( hwnd, bar, TRUE ); if (!info) { WARN( "called for missing scroll bar\n" ); release_win_ptr( win ); return FALSE; } *arrow_size = get_system_metrics( SM_CXVSCROLL ); pixels -= 2 * (get_system_metrics( SM_CXVSCROLL ) - SCROLL_ARROW_THUMB_OVERLAP); if (info->page) { *thumb_size = muldiv( pixels,info->page, info->maxVal - info->minVal + 1 ); min_thumb_size = muldiv( SCROLL_MIN_THUMB, get_dpi_for_window( hwnd ), 96 ); if (*thumb_size < min_thumb_size) *thumb_size = min_thumb_size; } else *thumb_size = get_system_metrics( SM_CXVSCROLL ); if ((pixels -= *thumb_size ) < 0 || (info->flags & ESB_DISABLE_BOTH) == ESB_DISABLE_BOTH) { /* Rectangle too small or scrollbar disabled -> no thumb */ *thumb_pos = *thumb_size = 0; } else { int max = info->maxVal - max( info->page-1, 0 ); if (info->minVal >= max) *thumb_pos = *arrow_size - SCROLL_ARROW_THUMB_OVERLAP; else *thumb_pos = *arrow_size - SCROLL_ARROW_THUMB_OVERLAP + muldiv( pixels, info->curVal - info->minVal, max - info->minVal ); } release_scroll_info_ptr( info ); } release_win_ptr( win ); return vertical; } /*********************************************************************** * draw_scroll_bar * * Redraw the whole scrollbar. */ static void draw_scroll_bar( HWND hwnd, HDC hdc, int bar, enum SCROLL_HITTEST hit_test, const struct SCROLL_TRACKING_INFO *tracking_info, BOOL draw_arrows, BOOL draw_interior ) { struct draw_scroll_bar_params params; struct scroll_info *info; RECT clip_box, intersect; DWORD style; void *ret_ptr; ULONG ret_len; if (!(hwnd = get_full_window_handle( hwnd ))) return; style = get_window_long( hwnd, GWL_STYLE ); if ((bar == SB_VERT && !(style & WS_VSCROLL)) || (bar == SB_HORZ && !(style & WS_HSCROLL))) return; if (!is_window_drawable( hwnd, FALSE )) return; if (!(info = get_scroll_info_ptr( hwnd, bar, TRUE ))) return; params.enable_flags = info->flags; release_scroll_info_ptr( info ); if (bar == SB_CTL && get_window_long( hwnd, GWL_STYLE ) & (SBS_SIZEGRIP | SBS_SIZEBOX)) { get_client_rect( hwnd, ¶ms.rect ); params.arrow_size = 0; params.thumb_pos = 0; params.thumb_size = 0; params.vertical = FALSE; } else { int pos, max_size; params.vertical = get_scroll_bar_rect( hwnd, bar, ¶ms.rect, ¶ms.arrow_size, ¶ms.thumb_size, ¶ms.thumb_pos ); if (scroll_moving_thumb && tracking_info->win == hwnd && tracking_info->bar == bar) { max_size = params.vertical ? params.rect.bottom - params.rect.top : params.rect.right - params.rect.left; max_size -= params.arrow_size - SCROLL_ARROW_THUMB_OVERLAP + params.thumb_size; pos = tracking_info->thumb_pos; if (pos < params.arrow_size - SCROLL_ARROW_THUMB_OVERLAP) pos = params.arrow_size - SCROLL_ARROW_THUMB_OVERLAP; else if (pos > max_size) pos = max_size; params.thumb_pos = pos; } } /* do not draw if the scrollbar rectangle is empty */ if (IsRectEmpty( ¶ms.rect )) return; TRACE( "hwnd %p, hdc %p, bar %d, hit_test %d, tracking_info(win %p, bar %d, thumb_pos %d, " "track_pos %d, vertical %d, hit_test %d), draw_arrows %d, draw_interior %d, rect %s, " "arrow_size %d, thumb_pos %d, thumb_val %d, vertical %d, captured window %p\n", hwnd, hdc, bar, hit_test, tracking_info->win, tracking_info->bar, tracking_info->thumb_pos, tracking_info->thumb_val, tracking_info->vertical, tracking_info->hit_test, draw_arrows, draw_interior, wine_dbgstr_rect(¶ms.rect), params.arrow_size, params.thumb_pos, params.thumb_size, params.vertical, get_capture() ); params.hwnd = hwnd; params.hdc = hdc; params.bar = bar; params.hit_test = hit_test; params.tracking_info = *tracking_info; params.arrows = draw_arrows; params.interior = draw_interior; KeUserModeCallback( NtUserDrawScrollBar, ¶ms, sizeof(params), &ret_ptr, &ret_len ); if (bar == SB_HORZ || bar == SB_VERT) { NtGdiGetAppClipBox( hdc, &clip_box ); if (intersect_rect( &intersect, ¶ms.rect, &clip_box )) set_standard_scroll_painted( hwnd, bar, TRUE ); } } void draw_nc_scrollbar( HWND hwnd, HDC hdc, BOOL draw_horizontal, BOOL draw_vertical ) { if (draw_horizontal) draw_scroll_bar( hwnd, hdc, SB_HORZ, g_tracking_info.hit_test, &g_tracking_info, TRUE, TRUE ); if (draw_vertical) draw_scroll_bar( hwnd, hdc, SB_VERT, g_tracking_info.hit_test, &g_tracking_info, TRUE, TRUE ); } static void refresh_scroll_bar( HWND hwnd, int nBar, BOOL arrows, BOOL interior ) { HDC hdc = NtUserGetDCEx( hwnd, 0, DCX_CACHE | (nBar == SB_CTL ? 0 : DCX_WINDOW) ); if (!hdc) return; draw_scroll_bar( hwnd, hdc, nBar, g_tracking_info.hit_test, &g_tracking_info, arrows, interior ); NtUserReleaseDC( hwnd, hdc ); } static BOOL point_in_scroll_rect( RECT *r, POINT pt, BOOL vertical ) { RECT rect = *r; int width; /* Pad hit rect to allow mouse to be dragged outside of scrollbar and * still be considered in the scrollbar. */ if (vertical) { width = r->right - r->left; InflateRect( &rect, width * 8, width * 2 ); } else { width = r->bottom - r->top; InflateRect( &rect, width * 2, width * 8 ); } return PtInRect( &rect, pt ); } /*********************************************************************** * scroll_hit_test * * Scroll-bar hit testing (don't confuse this with WM_NCHITTEST!). */ static enum SCROLL_HITTEST scroll_hit_test( HWND hwnd, int bar, POINT pt, BOOL dragging ) { int arrow_size, thumb_size, thumb_pos; BOOL vertical; RECT rect; vertical = get_scroll_bar_rect( hwnd, bar, &rect, &arrow_size, &thumb_size, &thumb_pos ); if ((dragging && !point_in_scroll_rect( &rect, pt, vertical )) || !PtInRect( &rect, pt )) return SCROLL_NOWHERE; if (vertical) { if (pt.y < rect.top + arrow_size) return SCROLL_TOP_ARROW; if (pt.y >= rect.bottom - arrow_size) return SCROLL_BOTTOM_ARROW; if (!thumb_pos) return SCROLL_TOP_RECT; pt.y -= rect.top; if (pt.y < thumb_pos) return SCROLL_TOP_RECT; if (pt.y >= thumb_pos + thumb_size) return SCROLL_BOTTOM_RECT; } else /* horizontal */ { if (pt.x < rect.left + arrow_size) return SCROLL_TOP_ARROW; if (pt.x >= rect.right - arrow_size) return SCROLL_BOTTOM_ARROW; if (!thumb_pos) return SCROLL_TOP_RECT; pt.x -= rect.left; if (pt.x < thumb_pos) return SCROLL_TOP_RECT; if (pt.x >= thumb_pos + thumb_size) return SCROLL_BOTTOM_RECT; } return SCROLL_THUMB; } static BOOL is_standard_scroll_painted( HWND hwnd, int bar ) { struct scroll_info *info; BOOL ret; if (bar != SB_HORZ && bar != SB_VERT) return FALSE; if (!(info = get_scroll_info_ptr( hwnd, bar, FALSE ))) return FALSE; ret = info->painted; release_scroll_info_ptr( info ); return ret; } /*********************************************************************** * get_thumb_val * * Compute the current scroll position based on the thumb position in pixels * from the top of the scroll-bar. */ static UINT get_thumb_val( HWND hwnd, int bar, RECT *rect, BOOL vertical, int pos ) { int thumb_size, min_thumb_size, pixels, range; struct scroll_info *info; UINT ret; pixels = vertical ? rect->bottom-rect->top : rect->right-rect->left; pixels -= 2 * (get_system_metrics( SM_CXVSCROLL ) - SCROLL_ARROW_THUMB_OVERLAP); if (!(info = get_scroll_info_ptr( hwnd, bar, FALSE ))) return 0; ret = info->minVal; if (pixels > 0) { if (info->page) { thumb_size = muldiv( pixels, info->page, info->maxVal - info->minVal + 1 ); min_thumb_size = muldiv( SCROLL_MIN_THUMB, get_dpi_for_window( hwnd ), 96 ); if (thumb_size < min_thumb_size) thumb_size = min_thumb_size; } else thumb_size = get_system_metrics( SM_CXVSCROLL ); if ((pixels -= thumb_size) > 0) { pos = max( 0, pos - (get_system_metrics( SM_CXVSCROLL ) - SCROLL_ARROW_THUMB_OVERLAP )); if (pos > pixels) pos = pixels; if (!info->page) range = info->maxVal - info->minVal; else range = info->maxVal - info->minVal - info->page + 1; ret = info->minVal + muldiv( pos, range, pixels ); } } release_scroll_info_ptr( info ); return ret; } static POINT clip_scroll_pos( RECT *rect, POINT pt ) { if (pt.x < rect->left) pt.x = rect->left; else if (pt.x > rect->right) pt.x = rect->right; if (pt.y < rect->top) pt.y = rect->top; else if (pt.y > rect->bottom) pt.y = rect->bottom; return pt; } /*********************************************************************** * handle_scroll_event * * Handle a mouse or timer event for the scrollbar. * 'pt' is the location of the mouse event in client (for SB_CTL) or * windows coordinates. */ void handle_scroll_event( HWND hwnd, int bar, UINT msg, POINT pt ) { /* Previous mouse position for timer events. */ static POINT prev_pt; /* Thumb position when tracking started. */ static UINT track_thumb_pos; /* Position in the scroll-bar of the last button-down event. */ static int last_click_pos; /* Position in the scroll-bar of the last mouse event. */ static int last_mouse_pos; int arrow_size, thumb_size, thumb_pos; enum SCROLL_HITTEST hittest; HWND owner_hwnd, ctl_hwnd; struct scroll_info *info; TRACKMOUSEEVENT tme; BOOL vertical; RECT rect; HDC hdc; if (!(info = get_scroll_info_ptr( hwnd, bar, FALSE ))) return; release_scroll_info_ptr( info ); if (g_tracking_info.hit_test == SCROLL_NOWHERE && (msg != WM_LBUTTONDOWN && msg != WM_MOUSEMOVE && msg != WM_MOUSELEAVE && msg != WM_NCMOUSEMOVE && msg != WM_NCMOUSELEAVE)) return; if (bar == SB_CTL && (get_window_long( hwnd, GWL_STYLE ) & (SBS_SIZEGRIP | SBS_SIZEBOX))) { switch (msg) { case WM_LBUTTONDOWN: /* Initialise mouse tracking */ NtUserHideCaret( hwnd ); /* hide caret while holding down LBUTTON */ NtUserSetCapture( hwnd ); prev_pt = pt; g_tracking_info.hit_test = hittest = SCROLL_THUMB; break; case WM_MOUSEMOVE: get_client_rect( get_parent( get_parent( hwnd )), &rect ); prev_pt = pt; break; case WM_LBUTTONUP: release_capture(); g_tracking_info.hit_test = hittest = SCROLL_NOWHERE; if (hwnd == get_focus()) NtUserShowCaret( hwnd ); break; case WM_SYSTIMER: pt = prev_pt; break; } return; } hdc = NtUserGetDCEx( hwnd, 0, DCX_CACHE | (bar == SB_CTL ? 0 : DCX_WINDOW)); vertical = get_scroll_bar_rect( hwnd, bar, &rect, &arrow_size, &thumb_size, &thumb_pos ); owner_hwnd = bar == SB_CTL ? get_parent( hwnd ) : hwnd; ctl_hwnd = bar == SB_CTL ? hwnd : 0; switch (msg) { case WM_LBUTTONDOWN: /* Initialise mouse tracking */ NtUserHideCaret( hwnd ); /* hide caret while holding down LBUTTON */ g_tracking_info.vertical = vertical; g_tracking_info.hit_test = hittest = scroll_hit_test( hwnd, bar, pt, FALSE ); last_click_pos = vertical ? pt.y - rect.top : pt.x - rect.left; last_mouse_pos = last_click_pos; track_thumb_pos = thumb_pos; prev_pt = pt; if (bar == SB_CTL && (get_window_long( hwnd, GWL_STYLE ) & WS_TABSTOP)) NtUserSetFocus( hwnd ); NtUserSetCapture( hwnd ); break; case WM_MOUSEMOVE: hittest = scroll_hit_test( hwnd, bar, pt, vertical == g_tracking_info.vertical && get_capture() == hwnd ); prev_pt = pt; if (bar != SB_CTL) break; tme.cbSize = sizeof(tme); tme.dwFlags = TME_QUERY; NtUserTrackMouseEvent( &tme ); if (!(tme.dwFlags & TME_LEAVE) || tme.hwndTrack != hwnd) { tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; NtUserTrackMouseEvent( &tme ); } break; case WM_NCMOUSEMOVE: hittest = scroll_hit_test( hwnd, bar, pt, vertical == g_tracking_info.vertical && get_capture() == hwnd ); prev_pt = pt; if (bar == SB_CTL) break; tme.cbSize = sizeof(tme); tme.dwFlags = TME_QUERY; NtUserTrackMouseEvent( &tme ); if (((tme.dwFlags & (TME_NONCLIENT | TME_LEAVE)) != (TME_NONCLIENT | TME_LEAVE)) || tme.hwndTrack != hwnd) { tme.dwFlags = TME_NONCLIENT | TME_LEAVE; tme.hwndTrack = hwnd; NtUserTrackMouseEvent( &tme ); } break; case WM_NCMOUSELEAVE: if (bar == SB_CTL) return; hittest = SCROLL_NOWHERE; break; case WM_MOUSELEAVE: if (bar != SB_CTL) return; hittest = SCROLL_NOWHERE; break; case WM_LBUTTONUP: hittest = SCROLL_NOWHERE; release_capture(); /* if scrollbar has focus, show back caret */ if (hwnd == get_focus()) NtUserShowCaret( hwnd ); break; case WM_SYSTIMER: pt = prev_pt; hittest = scroll_hit_test( hwnd, bar, pt, FALSE ); break; default: return; /* Should never happen */ } TRACE( "Event: hwnd=%p bar=%d msg=%s pt=%d,%d hit=%d\n", hwnd, bar, debugstr_msg_name( msg, hwnd ), (int)pt.x, (int)pt.y, hittest ); switch (g_tracking_info.hit_test) { case SCROLL_NOWHERE: /* No tracking in progress */ /* For standard scroll bars, hovered state gets painted only when the scroll bar was * previously painted by DefWinProc(). If an application handles WM_NCPAINT by itself, then * the scrollbar shouldn't be repainted here to avoid overwriting the application painted * content */ if (msg == WM_MOUSEMOVE || msg == WM_MOUSELEAVE || ((msg == WM_NCMOUSEMOVE || msg == WM_NCMOUSELEAVE) && is_standard_scroll_painted( hwnd, bar ))) draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, TRUE, TRUE ); break; case SCROLL_TOP_ARROW: draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, TRUE, FALSE ); if (hittest == g_tracking_info.hit_test) { if ((msg == WM_LBUTTONDOWN) || (msg == WM_SYSTIMER)) { send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, SB_LINEUP, (LPARAM)ctl_hwnd ); } NtUserSetSystemTimer( hwnd, SCROLL_TIMER, msg == WM_LBUTTONDOWN ? SCROLL_FIRST_DELAY : SCROLL_REPEAT_DELAY ); } else NtUserKillSystemTimer( hwnd, SCROLL_TIMER ); break; case SCROLL_TOP_RECT: draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, FALSE, TRUE ); if (hittest == g_tracking_info.hit_test) { if (msg == WM_LBUTTONDOWN || msg == WM_SYSTIMER) { send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, SB_PAGEUP, (LPARAM)ctl_hwnd ); } NtUserSetSystemTimer( hwnd, SCROLL_TIMER, msg == WM_LBUTTONDOWN ? SCROLL_FIRST_DELAY : SCROLL_REPEAT_DELAY ); } else NtUserKillSystemTimer( hwnd, SCROLL_TIMER ); break; case SCROLL_THUMB: if (msg == WM_LBUTTONDOWN) { g_tracking_info.win = hwnd; g_tracking_info.bar = bar; g_tracking_info.thumb_pos = track_thumb_pos + last_mouse_pos - last_click_pos; g_tracking_info.thumb_val = get_thumb_val( hwnd, bar, &rect, vertical, g_tracking_info.thumb_pos ); if (!scroll_moving_thumb) { scroll_moving_thumb = TRUE; draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, FALSE, TRUE ); } } else if (msg == WM_LBUTTONUP) { draw_scroll_bar( hwnd, hdc, bar, SCROLL_NOWHERE, &g_tracking_info, FALSE, TRUE ); } else if (msg == WM_MOUSEMOVE) { int pos; if (!point_in_scroll_rect( &rect, pt, vertical )) pos = last_click_pos; else { pt = clip_scroll_pos( &rect, pt ); pos = vertical ? pt.y - rect.top : pt.x - rect.left; } if (pos != last_mouse_pos || !scroll_moving_thumb) { last_mouse_pos = pos; g_tracking_info.thumb_pos = track_thumb_pos + pos - last_click_pos; g_tracking_info.thumb_val = get_thumb_val( hwnd, bar, &rect, vertical, g_tracking_info.thumb_pos ); send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, MAKEWPARAM( SB_THUMBTRACK, g_tracking_info.thumb_val ), (LPARAM)ctl_hwnd ); scroll_moving_thumb = TRUE; draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, FALSE, TRUE ); } } break; case SCROLL_BOTTOM_RECT: draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, FALSE, TRUE ); if (hittest == g_tracking_info.hit_test) { if (msg == WM_LBUTTONDOWN || msg == WM_SYSTIMER) { send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, SB_PAGEDOWN, (LPARAM)ctl_hwnd ); } NtUserSetSystemTimer( hwnd, SCROLL_TIMER, msg == WM_LBUTTONDOWN ? SCROLL_FIRST_DELAY : SCROLL_REPEAT_DELAY ); } else NtUserKillSystemTimer( hwnd, SCROLL_TIMER ); break; case SCROLL_BOTTOM_ARROW: draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, TRUE, FALSE ); if (hittest == g_tracking_info.hit_test) { if (msg == WM_LBUTTONDOWN || msg == WM_SYSTIMER) { send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, SB_LINEDOWN, (LPARAM)ctl_hwnd ); } NtUserSetSystemTimer( hwnd, SCROLL_TIMER, msg == WM_LBUTTONDOWN ? SCROLL_FIRST_DELAY : SCROLL_REPEAT_DELAY ); } else NtUserKillSystemTimer( hwnd, SCROLL_TIMER ); break; } if (msg == WM_LBUTTONDOWN) { if (hittest == SCROLL_THUMB) { UINT val = get_thumb_val( hwnd, bar, &rect, vertical, track_thumb_pos + last_mouse_pos - last_click_pos ); send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, MAKEWPARAM( SB_THUMBTRACK, val ), (LPARAM)ctl_hwnd ); } } if (msg == WM_LBUTTONUP) { hittest = g_tracking_info.hit_test; g_tracking_info.hit_test = SCROLL_NOWHERE; /* Terminate tracking */ if (hittest == SCROLL_THUMB) { UINT val = get_thumb_val( hwnd, bar, &rect, vertical, track_thumb_pos + last_mouse_pos - last_click_pos ); send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, MAKEWPARAM( SB_THUMBPOSITION, val ), (LPARAM)ctl_hwnd ); } /* SB_ENDSCROLL doesn't report thumb position */ send_message( owner_hwnd, vertical ? WM_VSCROLL : WM_HSCROLL, SB_ENDSCROLL, (LPARAM)ctl_hwnd ); /* Terminate tracking */ g_tracking_info.win = 0; scroll_moving_thumb = FALSE; hittest = SCROLL_NOWHERE; draw_scroll_bar( hwnd, hdc, bar, hittest, &g_tracking_info, TRUE, TRUE ); } NtUserReleaseDC( hwnd, hdc ); } /*********************************************************************** * track_scroll_bar * * Track a mouse button press on a scroll-bar. * pt is in screen-coordinates for non-client scroll bars. */ void track_scroll_bar( HWND hwnd, int scrollbar, POINT pt ) { MSG msg; RECT rect; if (scrollbar != SB_CTL) { get_window_rects( hwnd, COORDS_CLIENT, &rect, NULL, get_thread_dpi() ); screen_to_client( hwnd, &pt ); pt.x -= rect.left; pt.y -= rect.top; } else rect.left = rect.top = 0; handle_scroll_event( hwnd, scrollbar, WM_LBUTTONDOWN, pt ); do { if (!NtUserGetMessage( &msg, 0, 0, 0 )) break; if (NtUserCallMsgFilter( &msg, MSGF_SCROLLBAR )) continue; if (msg.message == WM_LBUTTONUP || msg.message == WM_MOUSEMOVE || msg.message == WM_MOUSELEAVE || msg.message == WM_NCMOUSEMOVE || msg.message == WM_NCMOUSELEAVE || (msg.message == WM_SYSTIMER && msg.wParam == SCROLL_TIMER)) { pt.x = (short)LOWORD( msg.lParam ) - rect.left; pt.y = (short)HIWORD( msg.lParam ) - rect.top; handle_scroll_event( hwnd, scrollbar, msg.message, pt ); } else { NtUserTranslateMessage( &msg, 0 ); NtUserDispatchMessage( &msg ); } if (!is_window( hwnd )) { release_capture(); break; } } while (msg.message != WM_LBUTTONUP && get_capture() == hwnd); } /*********************************************************************** * validate_scroll_info * * Determine if the supplied SCROLLINFO struct is valid. */ static inline BOOL validate_scroll_info( const SCROLLINFO *info ) { return !(info->fMask & ~(SIF_ALL | SIF_DISABLENOSCROLL | SIF_RETURNPREV) || (info->cbSize != sizeof(*info) && info->cbSize != sizeof(*info) - sizeof(info->nTrackPos))); } BOOL get_scroll_info( HWND hwnd, int bar, SCROLLINFO *info ) { struct scroll_info *scroll; /* handle invalid data structure */ if (!validate_scroll_info( info ) || !(scroll = get_scroll_info_ptr( hwnd, bar, FALSE ))) return FALSE; /* fill in the desired scroll info structure */ if (info->fMask & SIF_PAGE) info->nPage = scroll->page; if (info->fMask & SIF_POS) info->nPos = scroll->curVal; if ((info->fMask & SIF_TRACKPOS) && (info->cbSize == sizeof(*info))) info->nTrackPos = g_tracking_info.win == get_full_window_handle( hwnd ) ? g_tracking_info.thumb_val : scroll->curVal; if (info->fMask & SIF_RANGE) { info->nMin = scroll->minVal; info->nMax = scroll->maxVal; } release_scroll_info_ptr( scroll ); TRACE( "cbSize %02x fMask %04x nMin %d nMax %d nPage %u nPos %d nTrackPos %d\n", info->cbSize, info->fMask, info->nMin, info->nMax, info->nPage, info->nPos, info->nTrackPos ); return (info->fMask & SIF_ALL) != 0; } static int set_scroll_info( HWND hwnd, int bar, const SCROLLINFO *info, BOOL redraw ) { struct scroll_info *scroll; UINT new_flags; int action = 0, ret = 0; /* handle invalid data structure */ if (!validate_scroll_info( info ) || !(scroll = get_scroll_info_ptr( hwnd, bar, TRUE ))) return 0; if (TRACE_ON(scroll)) { TRACE( "hwnd=%p bar=%d", hwnd, bar ); if (info->fMask & SIF_PAGE) TRACE( " page=%d", info->nPage ); if (info->fMask & SIF_POS) TRACE( " pos=%d", info->nPos ); if (info->fMask & SIF_RANGE) TRACE( " min=%d max=%d", info->nMin, info->nMax ); TRACE( "\n" ); } /* undocumented flag, return previous position instead of modified */ if (info->fMask & SIF_RETURNPREV) ret = scroll->curVal; /* Set the page size */ if ((info->fMask & SIF_PAGE) && scroll->page != info->nPage) { scroll->page = info->nPage; action |= SA_SSI_REFRESH; } /* Set the scroll pos */ if ((info->fMask & SIF_POS) && scroll->curVal != info->nPos) { scroll->curVal = info->nPos; action |= SA_SSI_REFRESH; } /* Set the scroll range */ if (info->fMask & SIF_RANGE) { /* Invalid range -> range is set to (0,0) */ if (info->nMin > info->nMax || (UINT)(info->nMax - info->nMin) >= 0x80000000) { action |= SA_SSI_REFRESH; scroll->minVal = 0; scroll->maxVal = 0; } else { if (scroll->minVal != info->nMin || scroll->maxVal != info->nMax) { action |= SA_SSI_REFRESH; scroll->minVal = info->nMin; scroll->maxVal = info->nMax; } } } /* Make sure the page size is valid */ if (scroll->page < 0) scroll->page = 0; else if (scroll->page > scroll->maxVal - scroll->minVal + 1) scroll->page = scroll->maxVal - scroll->minVal + 1; /* Make sure the pos is inside the range */ if (scroll->curVal < scroll->minVal) scroll->curVal = scroll->minVal; else if (scroll->curVal > scroll->maxVal - max( scroll->page - 1, 0 )) scroll->curVal = scroll->maxVal - max( scroll->page - 1, 0 ); TRACE( " new values: page=%d pos=%d min=%d max=%d\n", scroll->page, scroll->curVal, scroll->minVal, scroll->maxVal ); /* don't change the scrollbar state if SetScrollInfo is just called with SIF_DISABLENOSCROLL */ if(!(info->fMask & SIF_ALL)) goto done; /* Check if the scrollbar should be hidden or disabled */ if (info->fMask & (SIF_RANGE | SIF_PAGE | SIF_DISABLENOSCROLL)) { new_flags = scroll->flags; if (scroll->minVal >= scroll->maxVal - max( scroll->page - 1, 0 )) { /* Hide or disable scroll-bar */ if (info->fMask & SIF_DISABLENOSCROLL) { new_flags = ESB_DISABLE_BOTH; action |= SA_SSI_REFRESH; } else if (bar != SB_CTL && (action & SA_SSI_REFRESH)) { action = SA_SSI_HIDE; } } else if (info->fMask != SIF_PAGE) { /* Show and enable scroll-bar only if no page only changed. */ new_flags = ESB_ENABLE_BOTH; if (bar != SB_CTL && (action & SA_SSI_REFRESH)) action |= SA_SSI_SHOW; } if (bar == SB_CTL && redraw && is_window_visible( hwnd ) && (new_flags == ESB_ENABLE_BOTH || new_flags == ESB_DISABLE_BOTH)) { release_scroll_info_ptr( scroll ); enable_window( hwnd, new_flags == ESB_ENABLE_BOTH ); if (!(scroll = get_scroll_info_ptr( hwnd, bar, FALSE ))) return 0; } if (scroll->flags != new_flags) /* check arrow flags */ { scroll->flags = new_flags; action |= SA_SSI_REPAINT_ARROWS; } } done: if (!(info->fMask & SIF_RETURNPREV)) ret = scroll->curVal; release_scroll_info_ptr( scroll ); if (action & SA_SSI_HIDE) show_scroll_bar( hwnd, bar, FALSE, FALSE ); else { if (action & SA_SSI_SHOW && show_scroll_bar( hwnd, bar, TRUE, TRUE )) return ret; /* NtUserSetWindowPos() already did the painting */ if (redraw) refresh_scroll_bar( hwnd, bar, TRUE, TRUE ); else if (action & SA_SSI_REPAINT_ARROWS) refresh_scroll_bar( hwnd, bar, TRUE, FALSE ); } return ret; /* Return current position */ } static BOOL get_scroll_bar_info( HWND hwnd, LONG id, SCROLLBARINFO *info ) { struct scroll_info *scroll; int bar, dummy; DWORD style = get_window_long( hwnd, GWL_STYLE ); BOOL pressed; RECT rect; switch (id) { case OBJID_CLIENT: bar = SB_CTL; break; case OBJID_HSCROLL: bar = SB_HORZ; break; case OBJID_VSCROLL: bar = SB_VERT; break; default: return FALSE; } /* handle invalid data structure */ if (info->cbSize != sizeof(*info)) return FALSE; get_scroll_bar_rect( hwnd, bar, &info->rcScrollBar, &dummy, &info->dxyLineButton, &info->xyThumbTop ); /* rcScrollBar needs to be in screen coordinates */ get_window_rect( hwnd, &rect, get_thread_dpi() ); OffsetRect( &info->rcScrollBar, rect.left, rect.top ); info->xyThumbBottom = info->xyThumbTop + info->dxyLineButton; if (!(scroll = get_scroll_info_ptr( hwnd, bar, TRUE ))) return FALSE; /* Scroll bar state */ info->rgstate[0] = 0; if ((bar == SB_HORZ && !(style & WS_HSCROLL)) || (bar == SB_VERT && !(style & WS_VSCROLL))) info->rgstate[0] |= STATE_SYSTEM_INVISIBLE; if (scroll->minVal >= scroll->maxVal - max( scroll->page - 1, 0 )) { if (!(info->rgstate[0] & STATE_SYSTEM_INVISIBLE)) info->rgstate[0] |= STATE_SYSTEM_UNAVAILABLE; else info->rgstate[0] |= STATE_SYSTEM_OFFSCREEN; } if (bar == SB_CTL && !is_window_enabled( hwnd )) info->rgstate[0] |= STATE_SYSTEM_UNAVAILABLE; pressed = (bar == SB_VERT) == g_tracking_info.vertical && get_capture() == hwnd; /* Top/left arrow button state. MSDN says top/right, but I don't believe it */ info->rgstate[1] = 0; if (pressed && g_tracking_info.hit_test == SCROLL_TOP_ARROW) info->rgstate[1] |= STATE_SYSTEM_PRESSED; if (scroll->flags & ESB_DISABLE_LTUP) info->rgstate[1] |= STATE_SYSTEM_UNAVAILABLE; /* Page up/left region state. MSDN says up/right, but I don't believe it */ info->rgstate[2] = 0; if (scroll->curVal == scroll->minVal) info->rgstate[2] |= STATE_SYSTEM_INVISIBLE; if (pressed && g_tracking_info.hit_test == SCROLL_TOP_RECT) info->rgstate[2] |= STATE_SYSTEM_PRESSED; /* Thumb state */ info->rgstate[3] = 0; if (pressed && g_tracking_info.hit_test == SCROLL_THUMB) info->rgstate[3] |= STATE_SYSTEM_PRESSED; /* Page down/right region state. MSDN says down/left, but I don't believe it */ info->rgstate[4] = 0; if (scroll->curVal >= scroll->maxVal - 1) info->rgstate[4] |= STATE_SYSTEM_INVISIBLE; if (pressed && g_tracking_info.hit_test == SCROLL_BOTTOM_RECT) info->rgstate[4] |= STATE_SYSTEM_PRESSED; /* Bottom/right arrow button state. MSDN says bottom/left, but I don't believe it */ info->rgstate[5] = 0; if (pressed && g_tracking_info.hit_test == SCROLL_BOTTOM_ARROW) info->rgstate[5] |= STATE_SYSTEM_PRESSED; if (scroll->flags & ESB_DISABLE_RTDN) info->rgstate[5] |= STATE_SYSTEM_UNAVAILABLE; release_scroll_info_ptr( scroll ); return TRUE; } static void create_scroll_bar( HWND hwnd, CREATESTRUCTW *create ) { struct scroll_info *info = NULL; WND *win; TRACE( "hwnd=%p create=%p\n", hwnd, create ); win = get_win_ptr( hwnd ); if (win->cbWndExtra >= sizeof(struct scroll_bar_win_data)) { struct scroll_bar_win_data *data = (struct scroll_bar_win_data *)win->wExtra; data->magic = SCROLLBAR_MAGIC; info = &data->info; } else WARN( "Not enough extra data\n" ); release_win_ptr( win ); if (!info) return; if (create->style & WS_DISABLED) { info->flags = ESB_DISABLE_BOTH; TRACE( "Created WS_DISABLED scrollbar\n" ); } if (create->style & (SBS_SIZEGRIP | SBS_SIZEBOX)) { if (create->style & SBS_SIZEBOXTOPLEFTALIGN) NtUserMoveWindow( hwnd, create->x, create->y, get_system_metrics( SM_CXVSCROLL ) + 1, get_system_metrics( SM_CYHSCROLL ) + 1, FALSE ); else if(create->style & SBS_SIZEBOXBOTTOMRIGHTALIGN) NtUserMoveWindow( hwnd, create->x + create->cx - get_system_metrics( SM_CXVSCROLL ) - 1, create->y + create->cy-get_system_metrics( SM_CYHSCROLL ) - 1, get_system_metrics( SM_CXVSCROLL ) + 1, get_system_metrics( SM_CYHSCROLL ) + 1, FALSE ); } else if (create->style & SBS_VERT) { if (create->style & SBS_LEFTALIGN) NtUserMoveWindow( hwnd, create->x, create->y, get_system_metrics( SM_CXVSCROLL ) + 1, create->cy, FALSE ); else if (create->style & SBS_RIGHTALIGN) NtUserMoveWindow( hwnd, create->x + create->cx - get_system_metrics( SM_CXVSCROLL ) - 1, create->y, get_system_metrics( SM_CXVSCROLL ) + 1, create->cy, FALSE ); } else /* SBS_HORZ */ { if (create->style & SBS_TOPALIGN) NtUserMoveWindow( hwnd, create->x, create->y, create->cx, get_system_metrics( SM_CYHSCROLL ) + 1, FALSE ); else if (create->style & SBS_BOTTOMALIGN) NtUserMoveWindow( hwnd, create->x, create->y + create->cy - get_system_metrics( SM_CYHSCROLL ) - 1, create->cx, get_system_metrics( SM_CYHSCROLL ) + 1, FALSE ); } } static void handle_kbd_event( HWND hwnd, WPARAM wparam, LPARAM lparam ) { TRACE( "hwnd=%p wparam=%ld lparam=%ld\n", hwnd, (long)wparam, lparam ); /* hide caret on first KEYDOWN to prevent flicker */ if ((lparam & PFD_DOUBLEBUFFER_DONTCARE) == 0) NtUserHideCaret( hwnd ); switch (wparam) { case VK_PRIOR: wparam = SB_PAGEUP; break; case VK_NEXT: wparam = SB_PAGEDOWN; break; case VK_HOME: wparam = SB_TOP; break; case VK_END: wparam = SB_BOTTOM; break; case VK_UP: wparam = SB_LINEUP; break; case VK_DOWN: wparam = SB_LINEDOWN; break; case VK_LEFT: wparam = SB_LINEUP; break; case VK_RIGHT: wparam = SB_LINEDOWN; break; default: return; } send_message( get_parent(hwnd), (get_window_long( hwnd, GWL_STYLE ) & SBS_VERT) ? WM_VSCROLL : WM_HSCROLL, wparam, (LPARAM)hwnd); } static int get_scroll_pos(HWND hwnd, int bar) { struct scroll_info *scroll = get_scroll_info_ptr( hwnd, bar, FALSE ); int ret; if (!scroll) return 0; ret = scroll->curVal; release_scroll_info_ptr( scroll ); return ret; } static BOOL set_scroll_range( HWND hwnd, int bar, int min_val, int max_val ) { struct scroll_info *scroll = get_scroll_info_ptr( hwnd, bar, FALSE ); TRACE( "hwnd=%p bar=%d min=%d max=%d\n", hwnd, bar, min_val, max_val ); if (scroll) { scroll->minVal = min_val; scroll->maxVal = max_val; release_scroll_info_ptr( scroll ); } return TRUE; } static BOOL get_scroll_range( HWND hwnd, int nBar, int *min, int *max ) { struct scroll_info *info; if (!(info = get_scroll_info_ptr( hwnd, nBar, FALSE ))) return FALSE; if (min) *min = info->minVal; if (max) *max = info->maxVal; release_scroll_info_ptr( info ); return TRUE; } LRESULT scroll_bar_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, BOOL ansi ) { if (!is_window( hwnd )) return 0; switch (msg) { case WM_CREATE: create_scroll_bar( hwnd, (CREATESTRUCTW *)lparam ); return 0; case WM_KEYDOWN: handle_kbd_event( hwnd, wparam, lparam ); return 0; case WM_KEYUP: NtUserShowCaret( hwnd ); return 0; case WM_LBUTTONDBLCLK: case WM_LBUTTONDOWN: if (get_window_long( hwnd, GWL_STYLE ) & SBS_SIZEGRIP) { send_message( get_parent(hwnd), WM_SYSCOMMAND, SC_SIZE + ((get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) ? WMSZ_BOTTOMLEFT : WMSZ_BOTTOMRIGHT), lparam ); } else { POINT pt; pt.x = (short)LOWORD( lparam ); pt.y = (short)HIWORD( lparam ); track_scroll_bar( hwnd, SB_CTL, pt ); } return 0; case WM_LBUTTONUP: case WM_NCMOUSEMOVE: case WM_NCMOUSELEAVE: case WM_MOUSEMOVE: case WM_MOUSELEAVE: case WM_SYSTIMER: { POINT pt; pt.x = (short)LOWORD( lparam ); pt.y = (short)HIWORD( lparam ); handle_scroll_event( hwnd, SB_CTL, msg, pt ); } return 0; case WM_ENABLE: { struct scroll_info *scroll; if ((scroll = get_scroll_info_ptr( hwnd, SB_CTL, FALSE ))) { scroll->flags = wparam ? ESB_ENABLE_BOTH : ESB_DISABLE_BOTH; release_scroll_info_ptr( scroll ); refresh_scroll_bar( hwnd, SB_CTL, TRUE, TRUE ); } } return 0; case WM_SETFOCUS: { /* Create a caret when a ScrollBar get focus */ RECT rect; int arrow_size, thumb_size, thumb_pos, vertical; vertical = get_scroll_bar_rect( hwnd, SB_CTL, &rect, &arrow_size, &thumb_size, &thumb_pos ); if (!vertical) { NtUserCreateCaret( hwnd, (HBITMAP)1, thumb_size - 2, rect.bottom - rect.top - 2 ); set_caret_pos( thumb_pos + 1, rect.top + 1 ); } else { NtUserCreateCaret( hwnd, (HBITMAP)1, rect.right - rect.left - 2, thumb_size - 2 ); set_caret_pos( rect.top + 1, thumb_pos + 1 ); } NtUserShowCaret( hwnd ); } return 0; case WM_KILLFOCUS: { int arrow_size, thumb_size, thumb_pos, vertical; RECT rect; vertical = get_scroll_bar_rect( hwnd, SB_CTL, &rect,&arrow_size, &thumb_size, &thumb_pos ); if (!vertical) { rect.left = thumb_pos + 1; rect.right = rect.left + thumb_size; } else { rect.top = thumb_pos + 1; rect.bottom = rect.top + thumb_size; } NtUserHideCaret( hwnd ); NtUserInvalidateRect( hwnd, &rect, 0 ); destroy_caret(); } return 0; case WM_ERASEBKGND: return 1; case WM_GETDLGCODE: return DLGC_WANTARROWS; /* Windows returns this value */ case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = wparam ? (HDC)wparam : NtUserBeginPaint( hwnd, &ps ); draw_scroll_bar( hwnd, hdc, SB_CTL, g_tracking_info.hit_test, &g_tracking_info, TRUE, TRUE ); if (!wparam) NtUserEndPaint( hwnd, &ps ); } return 0; case SBM_SETRANGEREDRAW: case SBM_SETRANGE: { int old_pos = get_scroll_pos( hwnd, SB_CTL ); set_scroll_range( hwnd, SB_CTL, wparam, lparam ); if (msg == SBM_SETRANGEREDRAW) refresh_scroll_bar( hwnd, SB_CTL, TRUE, TRUE ); if (old_pos != get_scroll_pos( hwnd, SB_CTL )) return old_pos; } return 0; case SBM_GETSCROLLINFO: return get_scroll_info( hwnd, SB_CTL, (SCROLLINFO *)lparam ); case SBM_GETSCROLLBARINFO: return get_scroll_bar_info( hwnd, OBJID_CLIENT, (SCROLLBARINFO *)lparam ); case SBM_SETSCROLLINFO: return set_scroll_info( hwnd, SB_CTL, (SCROLLINFO *)lparam, wparam ); case WM_SETCURSOR: if (get_window_long( hwnd, GWL_STYLE ) & SBS_SIZEGRIP) { ULONG_PTR cursor = (get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) ? IDC_SIZENESW : IDC_SIZENWSE; return (LRESULT)NtUserSetCursor( LoadImageW( 0, (const WCHAR *)cursor, IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE )); } return default_window_proc( hwnd, msg, wparam, lparam, ansi ); case SBM_SETPOS: { SCROLLINFO info; info.cbSize = sizeof(info); info.nPos = wparam; info.fMask = SIF_POS | SIF_RETURNPREV; return NtUserSetScrollInfo( hwnd, SB_CTL, &info, lparam ); } case SBM_GETPOS: return get_scroll_pos( hwnd, SB_CTL ); case SBM_GETRANGE: return get_scroll_range( hwnd, SB_CTL, (int *)wparam, (int *)lparam ); case SBM_ENABLE_ARROWS: return NtUserEnableScrollBar( hwnd, SB_CTL, wparam ); case 0x00e5: case 0x00e7: case 0x00e8: case 0x00ec: case 0x00ed: case 0x00ee: case 0x00ef: ERR( "unknown Win32 msg %04x wp=%08lx lp=%08lx\n", msg, (long)wparam, lparam ); return 0; default: if (msg >= WM_USER) WARN( "unknown msg %04x wp=%08lx lp=%08lx\n", msg, (long)wparam, lparam ); return default_window_proc( hwnd, msg, wparam, lparam, ansi ); } } void set_standard_scroll_painted( HWND hwnd, int bar, BOOL painted ) { struct scroll_info *info; if (bar != SB_HORZ && bar != SB_VERT) return; if ((info = get_scroll_info_ptr( hwnd, bar, FALSE ))) { info->painted = painted; release_scroll_info_ptr( info ); } } /************************************************************************* * NtUserShowScrollBar (win32u.@) */ BOOL WINAPI NtUserShowScrollBar( HWND hwnd, INT bar, BOOL show ) { if (!hwnd) return FALSE; show_scroll_bar( hwnd, bar, bar == SB_VERT ? 0 : show, bar == SB_HORZ ? 0 : show ); return TRUE; } /************************************************************************* * NtUserGetScrollBarInfo (win32u.@) */ BOOL WINAPI NtUserGetScrollBarInfo( HWND hwnd, LONG id, SCROLLBARINFO *info ) { TRACE( "hwnd=%p id=%d info=%p\n", hwnd, (int)id, info ); /* Refer OBJID_CLIENT requests to the window */ if (id == OBJID_CLIENT) return send_message( hwnd, SBM_GETSCROLLBARINFO, 0, (LPARAM)info ); return get_scroll_bar_info( hwnd, id, info ); } /************************************************************************* * NtUserEnableScrollBar (win32u.@) */ BOOL WINAPI NtUserEnableScrollBar( HWND hwnd, UINT bar, UINT flags ) { struct scroll_info *scroll; BOOL check_flags; flags &= ESB_DISABLE_BOTH; if (bar == SB_BOTH) { if (!(scroll = get_scroll_info_ptr( hwnd, SB_VERT, TRUE ))) return FALSE; check_flags = scroll->flags == flags; scroll->flags = flags; release_scroll_info_ptr( scroll ); if (!check_flags) refresh_scroll_bar( hwnd, SB_VERT, TRUE, TRUE ); bar = SB_HORZ; } else check_flags = bar != SB_CTL; if (!(scroll = get_scroll_info_ptr( hwnd, bar, TRUE ))) return FALSE; if (check_flags) check_flags = scroll->flags == flags; scroll->flags = flags; release_scroll_info_ptr( scroll ); if (check_flags) return FALSE; if (bar == SB_CTL && (flags == ESB_DISABLE_BOTH || flags == ESB_ENABLE_BOTH)) NtUserEnableWindow( hwnd, flags == ESB_ENABLE_BOTH ); refresh_scroll_bar( hwnd, bar, TRUE, TRUE ); return TRUE; } /************************************************************************* * NtUserSetScrollInfo (win32u.@) */ INT WINAPI NtUserSetScrollInfo( HWND hwnd, int bar, const SCROLLINFO *info, BOOL redraw ) { TRACE( "hwnd=%p bar=%d info=%p, redraw=%d\n", hwnd, bar, info, redraw ); /* Refer SB_CTL requests to the window */ if (bar == SB_CTL) return send_message( hwnd, SBM_SETSCROLLINFO, redraw, (LPARAM)info ); return set_scroll_info( hwnd, bar, info, redraw ); }