mirror of
git://source.winehq.org/git/wine.git
synced 2024-08-31 23:53:37 +00:00
1521 lines
51 KiB
C
1521 lines
51 KiB
C
/*
|
|
* 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 );
|
|
}
|