#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "commctrl.h"
#include "uxtheme.h"
#include "vssym32.h"
#include "wine/exception.h"
#include "wine/debug.h"
#include "comctl32.h"
WINE_DEFAULT_DEBUG_CHANNEL(listbox);
#define LB_ARRAY_GRANULARITY 16
#define LB_SCROLL_TIMEOUT 50
#define LB_TIMER_ID 2
#define LBS_DISPLAYCHANGED 0x80000000
typedef struct
{
LPWSTR str;
BOOL selected;
UINT height;
ULONG_PTR data;
} LB_ITEMDATA;
typedef struct
{
HWND self;
HWND owner;
UINT style;
INT width;
INT height;
union
{
LB_ITEMDATA *items;
BYTE *nodata_items;
} u;
INT nb_items;
UINT items_size;
INT top_item;
INT selected_item;
INT focus_item;
INT anchor_item;
INT item_height;
INT page_size;
INT column_width;
INT horz_extent;
INT horz_pos;
INT nb_tabs;
INT *tabs;
INT avg_char_width;
INT wheel_remain;
BOOL caret_on;
BOOL captured;
BOOL in_focus;
HFONT font;
LCID locale;
HEADCOMBO *lphc;
} LB_DESCR;
#define IS_OWNERDRAW(descr) \
((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
#define HAS_STRINGS(descr) \
(!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
#define IS_MULTISELECT(descr) \
((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
!((descr)->style & LBS_NOSEL))
#define SEND_NOTIFICATION(descr,code) \
(SendMessageW( (descr)->owner, WM_COMMAND, \
MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
#define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
typedef enum
{
LB_TIMER_NONE,
LB_TIMER_UP,
LB_TIMER_LEFT,
LB_TIMER_DOWN,
LB_TIMER_RIGHT
} TIMER_DIRECTION;
static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
static size_t get_sizeof_item( const LB_DESCR *descr )
{
return (descr->style & LBS_NODATA) ? sizeof(BYTE) : sizeof(LB_ITEMDATA);
}
static BOOL resize_storage(LB_DESCR *descr, UINT items_size)
{
LB_ITEMDATA *items;
if (items_size > descr->items_size ||
items_size + LB_ARRAY_GRANULARITY * 2 < descr->items_size)
{
items_size = (items_size + LB_ARRAY_GRANULARITY - 1) & ~(LB_ARRAY_GRANULARITY - 1);
if ((descr->style & (LBS_NODATA | LBS_MULTIPLESEL | LBS_EXTENDEDSEL)) != LBS_NODATA)
{
items = ReAlloc(descr->u.items, items_size * get_sizeof_item(descr));
if (!items)
{
SEND_NOTIFICATION(descr, LBN_ERRSPACE);
return FALSE;
}
descr->u.items = items;
}
descr->items_size = items_size;
}
if ((descr->style & LBS_NODATA) && descr->u.nodata_items && items_size > descr->nb_items)
{
memset(descr->u.nodata_items + descr->nb_items, 0,
(items_size - descr->nb_items) * get_sizeof_item(descr));
}
return TRUE;
}
static ULONG_PTR get_item_data( const LB_DESCR *descr, UINT index )
{
return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].data;
}
static void set_item_data( LB_DESCR *descr, UINT index, ULONG_PTR data )
{
if (!(descr->style & LBS_NODATA)) descr->u.items[index].data = data;
}
static WCHAR *get_item_string( const LB_DESCR *descr, UINT index )
{
return HAS_STRINGS(descr) ? descr->u.items[index].str : NULL;
}
static void set_item_string( const LB_DESCR *descr, UINT index, WCHAR *string )
{
if (!(descr->style & LBS_NODATA)) descr->u.items[index].str = string;
}
static UINT get_item_height( const LB_DESCR *descr, UINT index )
{
return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].height;
}
static void set_item_height( LB_DESCR *descr, UINT index, UINT height )
{
if (!(descr->style & LBS_NODATA)) descr->u.items[index].height = height;
}
static BOOL is_item_selected( const LB_DESCR *descr, UINT index )
{
if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
return index == descr->selected_item;
if (descr->style & LBS_NODATA)
return descr->u.nodata_items[index];
else
return descr->u.items[index].selected;
}
static void set_item_selected_state(LB_DESCR *descr, UINT index, BOOL state)
{
if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
{
if (descr->style & LBS_NODATA)
descr->u.nodata_items[index] = state;
else
descr->u.items[index].selected = state;
}
}
static void insert_item_data(LB_DESCR *descr, UINT index)
{
size_t size = get_sizeof_item(descr);
BYTE *p = descr->u.nodata_items + index * size;
if (!descr->u.items) return;
if (index < descr->nb_items)
memmove(p + size, p, (descr->nb_items - index) * size);
}
static void remove_item_data(LB_DESCR *descr, UINT index)
{
size_t size = get_sizeof_item(descr);
BYTE *p = descr->u.nodata_items + index * size;
if (!descr->u.items) return;
if (index < descr->nb_items)
memmove(p, p + size, (descr->nb_items - index) * size);
}
static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
{
INT i, height;
if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
{
if ((height += get_item_height(descr, i)) > descr->height) break;
}
if (i == descr->top_item) return 1;
else return i - descr->top_item;
}
static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
{
INT max, page;
if (descr->style & LBS_OWNERDRAWVARIABLE)
{
page = descr->height;
for (max = descr->nb_items - 1; max >= 0; max--)
if ((page -= get_item_height(descr, max)) < 0) break;
if (max < descr->nb_items - 1) max++;
}
else if (descr->style & LBS_MULTICOLUMN)
{
if ((page = descr->width / descr->column_width) < 1) page = 1;
max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
max = (max - page) * descr->page_size;
}
else
{
max = descr->nb_items - descr->page_size;
}
if (max < 0) max = 0;
return max;
}
static void LISTBOX_UpdateScroll( LB_DESCR *descr )
{
SCROLLINFO info;
if (descr->style & LBS_NOREDRAW) return;
info.cbSize = sizeof(info);
if (descr->style & LBS_MULTICOLUMN)
{
info.nMin = 0;
info.nMax = (descr->nb_items - 1) / descr->page_size;
info.nPos = descr->top_item / descr->page_size;
info.nPage = descr->width / descr->column_width;
if (info.nPage < 1) info.nPage = 1;
info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
if (descr->style & LBS_DISABLENOSCROLL)
info.fMask |= SIF_DISABLENOSCROLL;
if (descr->style & WS_HSCROLL)
SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
info.nMax = 0;
info.fMask = SIF_RANGE;
if (descr->style & WS_VSCROLL)
SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
}
else
{
info.nMin = 0;
info.nMax = descr->nb_items - 1;
info.nPos = descr->top_item;
info.nPage = LISTBOX_GetCurrentPageSize( descr );
info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
if (descr->style & LBS_DISABLENOSCROLL)
info.fMask |= SIF_DISABLENOSCROLL;
if (descr->style & WS_VSCROLL)
SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
if ((descr->style & WS_HSCROLL) && descr->horz_extent)
{
info.nPos = descr->horz_pos;
info.nPage = descr->width;
info.fMask = SIF_POS | SIF_PAGE;
if (descr->style & LBS_DISABLENOSCROLL)
info.fMask |= SIF_DISABLENOSCROLL;
SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
}
else
{
if (descr->style & LBS_DISABLENOSCROLL)
{
info.nMin = 0;
info.nMax = 0;
info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
}
else
{
ShowScrollBar( descr->self, SB_HORZ, FALSE );
}
}
}
}
static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
{
INT max = LISTBOX_GetMaxTopIndex( descr );
TRACE("setting top item %d, scroll %d\n", index, scroll);
if (index > max) index = max;
if (index < 0) index = 0;
if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
if (descr->top_item == index) return LB_OKAY;
if (scroll)
{
INT dx = 0, dy = 0;
if (descr->style & LBS_MULTICOLUMN)
dx = (descr->top_item - index) / descr->page_size * descr->column_width;
else if (descr->style & LBS_OWNERDRAWVARIABLE)
{
INT i;
if (index > descr->top_item)
{
for (i = index - 1; i >= descr->top_item; i--)
dy -= get_item_height(descr, i);
}
else
{
for (i = index; i < descr->top_item; i++)
dy += get_item_height(descr, i);
}
}
else
dy = (descr->top_item - index) * descr->item_height;
ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
}
else
InvalidateRect( descr->self, NULL, TRUE );
descr->top_item = index;
LISTBOX_UpdateScroll( descr );
return LB_OKAY;
}
static void LISTBOX_UpdatePage( LB_DESCR *descr )
{
INT page_size;
if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
page_size = 1;
if (page_size == descr->page_size) return;
descr->page_size = page_size;
if (descr->style & LBS_MULTICOLUMN)
InvalidateRect( descr->self, NULL, TRUE );
LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
}
static void LISTBOX_UpdateSize( LB_DESCR *descr )
{
RECT rect;
GetClientRect( descr->self, &rect );
descr->width = rect.right - rect.left;
descr->height = rect.bottom - rect.top;
if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
{
INT remaining;
RECT rect;
GetWindowRect( descr->self, &rect );
if(descr->item_height != 0)
remaining = descr->height % descr->item_height;
else
remaining = 0;
if ((descr->height > descr->item_height) && remaining)
{
TRACE("[%p]: changing height %d -> %d\n",
descr->self, descr->height, descr->height - remaining );
SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
rect.bottom - rect.top - remaining,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
return;
}
}
TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
LISTBOX_UpdatePage( descr );
LISTBOX_UpdateScroll( descr );
if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
{
InvalidateRect( descr->self, &rect, FALSE );
}
}
static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
{
if (index && (index >= descr->nb_items))
{
SetRectEmpty(rect);
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
SetRect( rect, 0, 0, descr->width, descr->height );
if (descr->style & LBS_MULTICOLUMN)
{
INT col = (index / descr->page_size) -
(descr->top_item / descr->page_size);
rect->left += col * descr->column_width;
rect->right = rect->left + descr->column_width;
rect->top += (index % descr->page_size) * descr->item_height;
rect->bottom = rect->top + descr->item_height;
}
else if (descr->style & LBS_OWNERDRAWVARIABLE)
{
INT i;
rect->right += descr->horz_pos;
if ((index >= 0) && (index < descr->nb_items))
{
if (index < descr->top_item)
{
for (i = descr->top_item-1; i >= index; i--)
rect->top -= get_item_height(descr, i);
}
else
{
for (i = descr->top_item; i < index; i++)
rect->top += get_item_height(descr, i);
}
rect->bottom = rect->top + get_item_height(descr, index);
}
}
else
{
rect->top += (index - descr->top_item) * descr->item_height;
rect->bottom = rect->top + descr->item_height;
rect->right += descr->horz_pos;
}
TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
return ((rect->left < descr->width) && (rect->right > 0) &&
(rect->top < descr->height) && (rect->bottom > 0));
}
static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
{
INT index = descr->top_item;
if (!descr->nb_items) return -1;
if (descr->style & LBS_OWNERDRAWVARIABLE)
{
INT pos = 0;
if (y >= 0)
{
while (index < descr->nb_items)
{
if ((pos += get_item_height(descr, index)) > y) break;
index++;
}
}
else
{
while (index > 0)
{
index--;
if ((pos -= get_item_height(descr, index)) <= y) break;
}
}
}
else if (descr->style & LBS_MULTICOLUMN)
{
if (y >= descr->item_height * descr->page_size) return -1;
if (y >= 0) index += y / descr->item_height;
if (x >= 0) index += (x / descr->column_width) * descr->page_size;
else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
}
else
{
index += (y / descr->item_height);
}
if (index < 0) return 0;
if (index >= descr->nb_items) return -1;
return index;
}
static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
INT index, UINT action, BOOL ignoreFocus )
{
BOOL selected = FALSE, focused;
WCHAR *item_str = NULL;
if (index < descr->nb_items)
{
item_str = get_item_string(descr, index);
selected = !(descr->style & LBS_NOSEL) && is_item_selected(descr, index);
}
focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
if (IS_OWNERDRAW(descr))
{
DRAWITEMSTRUCT dis;
RECT r;
HRGN hrgn;
if (index >= descr->nb_items)
{
if (action == ODA_FOCUS)
DrawFocusRect( hdc, rect );
else
ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
return;
}
GetClientRect(descr->self, &r);
hrgn = set_control_clipping( hdc, &r );
dis.CtlType = ODT_LISTBOX;
dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
dis.hwndItem = descr->self;
dis.itemAction = action;
dis.hDC = hdc;
dis.itemID = index;
dis.itemState = 0;
if (selected)
dis.itemState |= ODS_SELECTED;
if (focused)
dis.itemState |= ODS_FOCUS;
if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
dis.itemData = get_item_data(descr, index);
dis.rcItem = *rect;
TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
descr->self, index, debugstr_w(item_str), action,
dis.itemState, wine_dbgstr_rect(rect) );
SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
SelectClipRgn( hdc, hrgn );
if (hrgn) DeleteObject( hrgn );
}
else
{
COLORREF oldText = 0, oldBk = 0;
if (action == ODA_FOCUS)
{
DrawFocusRect( hdc, rect );
return;
}
if (selected)
{
oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
}
TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
descr->self, index, debugstr_w(item_str), action,
wine_dbgstr_rect(rect) );
if (!item_str)
ExtTextOutW( hdc, rect->left + 1, rect->top,
ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
else if (!(descr->style & LBS_USETABSTOPS))
ExtTextOutW( hdc, rect->left + 1, rect->top,
ETO_OPAQUE | ETO_CLIPPED, rect, item_str,
lstrlenW(item_str), NULL );
else
{
ExtTextOutW( hdc, rect->left + 1, rect->top,
ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
TabbedTextOutW( hdc, rect->left + 1 , rect->top,
item_str, lstrlenW(item_str),
descr->nb_tabs, descr->tabs, 0);
}
if (selected)
{
SetBkColor( hdc, oldBk );
SetTextColor( hdc, oldText );
}
if (focused)
DrawFocusRect( hdc, rect );
}
}
static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
{
if (on)
{
if (!(descr->style & LBS_NOREDRAW)) return;
descr->style &= ~LBS_NOREDRAW;
if (descr->style & LBS_DISPLAYCHANGED)
{
InvalidateRect(descr->self, NULL, TRUE);
if ((descr->top_item + descr->page_size) > descr->nb_items)
{
descr->top_item = descr->nb_items - descr->page_size;
if (descr->top_item < 0) descr->top_item = 0;
}
descr->style &= ~LBS_DISPLAYCHANGED;
}
LISTBOX_UpdateScroll( descr );
}
else descr->style |= LBS_NOREDRAW;
}
static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
{
HDC hdc;
RECT rect;
HFONT oldFont = 0;
HBRUSH hbrush, oldBrush = 0;
if (!IsWindowVisible(descr->self)) return;
if (descr->style & LBS_NOREDRAW)
{
descr->style |= LBS_DISPLAYCHANGED;
return;
}
if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
if (descr->font) oldFont = SelectObject( hdc, descr->font );
hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
(WPARAM)hdc, (LPARAM)descr->self );
if (hbrush) oldBrush = SelectObject( hdc, hbrush );
if (!IsWindowEnabled(descr->self))
SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
if (oldFont) SelectObject( hdc, oldFont );
if (oldBrush) SelectObject( hdc, oldBrush );
ReleaseDC( descr->self, hdc );
}
static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
{
HDC hdc;
RECT rect;
HFONT oldFont = 0;
if (!IsWindowVisible(descr->self)) return;
if (descr->focus_item == -1) return;
if (!descr->caret_on || !descr->in_focus) return;
if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
if (descr->font) oldFont = SelectObject( hdc, descr->font );
if (!IsWindowEnabled(descr->self))
SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
if (oldFont) SelectObject( hdc, oldFont );
ReleaseDC( descr->self, hdc );
}
static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
{
UINT new_size = descr->nb_items + nb_items;
if (new_size > descr->items_size && !resize_storage(descr, new_size))
return LB_ERRSPACE;
return descr->items_size;
}
static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
{
INT i;
if (!(descr->style & LBS_USETABSTOPS))
{
SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
return FALSE;
}
HeapFree( GetProcessHeap(), 0, descr->tabs );
if (!(descr->nb_tabs = count))
{
descr->tabs = NULL;
return TRUE;
}
if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
descr->nb_tabs * sizeof(INT) )))
return FALSE;
memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
for (i = 0; i < descr->nb_tabs; i++)
descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
return TRUE;
}
static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
{
DWORD len;
if ((index < 0) || (index >= descr->nb_items))
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
if (HAS_STRINGS(descr))
{
WCHAR *str = get_item_string(descr, index);
if (!buffer)
return lstrlenW(str);
TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(str));
__TRY
{
lstrcpyW(buffer, str);
len = lstrlenW(buffer);
}
__EXCEPT_PAGE_FAULT
{
WARN( "got an invalid buffer (Delphi bug?)\n" );
SetLastError( ERROR_INVALID_PARAMETER );
return LB_ERR;
}
__ENDTRY
} else
{
if (buffer)
*((ULONG_PTR *)buffer) = get_item_data(descr, index);
len = sizeof(ULONG_PTR);
}
return len;
}
static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2, int len )
{
INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, len, str2, len );
if (ret == CSTR_LESS_THAN)
return -1;
if (ret == CSTR_EQUAL)
return 0;
if (ret == CSTR_GREATER_THAN)
return 1;
return -1;
}
static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
{
INT index, min, max, res;
if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1;
min = 0;
max = descr->nb_items - 1;
while (min <= max)
{
index = (min + max) / 2;
if (HAS_STRINGS(descr))
res = LISTBOX_lstrcmpiW( descr->locale, get_item_string(descr, index), str, -1 );
else
{
COMPAREITEMSTRUCT cis;
UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
cis.CtlType = ODT_LISTBOX;
cis.CtlID = id;
cis.hwndItem = descr->self;
cis.itemID1 = index;
cis.itemData1 = get_item_data(descr, index);
cis.itemID2 = -1;
cis.itemData2 = (ULONG_PTR)str;
cis.dwLocaleId = descr->locale;
res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
}
if (!res) return index;
if (res > 0) max = index - 1;
else min = index + 1;
}
return exact ? -1 : min;
}
static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
{
INT min, max, res;
if (!HAS_STRINGS(descr))
return LISTBOX_FindStringPos( descr, str, FALSE );
min = 0;
max = descr->nb_items;
while (min != max)
{
INT index = (min + max) / 2;
LPCWSTR p = get_item_string(descr, index);
if (*p == '[')
{
if (*str != '[') res = -1;
else if (p[1] == '-')
{
if (str[1] == '-') res = str[2] - p[2];
else res = -1;
}
else
{
if (str[1] == '-') res = 1;
else res = LISTBOX_lstrcmpiW( descr->locale, str, p, -1 );
}
}
else
{
if (*str == '[') res = 1;
else res = LISTBOX_lstrcmpiW( descr->locale, str, p, -1 );
}
if (!res) return index;
if (res < 0) max = index;
else min = index + 1;
}
return max;
}
static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
{
INT i, index;
if (descr->style & LBS_NODATA) return LB_ERR;
start++;
if (start >= descr->nb_items) start = 0;
if (HAS_STRINGS(descr))
{
if (!str || ! str[0] ) return LB_ERR;
if (exact)
{
for (i = 0, index = start; i < descr->nb_items; i++, index++)
{
if (index == descr->nb_items) index = 0;
if (!LISTBOX_lstrcmpiW(descr->locale, str, get_item_string(descr, index), -1))
return index;
}
}
else
{
INT len = lstrlenW(str);
WCHAR *item_str;
for (i = 0, index = start; i < descr->nb_items; i++, index++)
{
if (index == descr->nb_items) index = 0;
item_str = get_item_string(descr, index);
if (!LISTBOX_lstrcmpiW(descr->locale, str, item_str, len)) return index;
if (item_str[0] == '[')
{
if (!LISTBOX_lstrcmpiW(descr->locale, str, item_str + 1, len)) return index;
if (item_str[1] == '-' && !LISTBOX_lstrcmpiW(descr->locale, str, item_str + 2, len)) return index;
}
}
}
}
else
{
if (exact && (descr->style & LBS_SORT))
return LISTBOX_FindStringPos( descr, str, TRUE );
for (i = 0, index = start; i < descr->nb_items; i++, index++)
{
if (index == descr->nb_items) index = 0;
if (get_item_data(descr, index) == (ULONG_PTR)str) return index;
}
}
return LB_ERR;
}
static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
{
INT i, count;
if (!(descr->style & LBS_MULTIPLESEL) ||
(descr->style & LBS_NOSEL))
return LB_ERR;
for (i = count = 0; i < descr->nb_items; i++)
if (is_item_selected(descr, i)) count++;
return count;
}
static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
{
INT i, count;
if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
for (i = count = 0; (i < descr->nb_items) && (count < max); i++)
if (is_item_selected(descr, i)) array[count++] = i;
return count;
}
static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
{
INT i, col_pos = descr->page_size - 1;
RECT rect;
RECT focusRect = {-1, -1, -1, -1};
HFONT oldFont = 0;
HBRUSH hbrush, oldBrush = 0;
if (descr->style & LBS_NOREDRAW) return 0;
SetRect( &rect, 0, 0, descr->width, descr->height );
if (descr->style & LBS_MULTICOLUMN)
rect.right = rect.left + descr->column_width;
else if (descr->horz_pos)
{
SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
rect.right += descr->horz_pos;
}
if (descr->font) oldFont = SelectObject( hdc, descr->font );
hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
(WPARAM)hdc, (LPARAM)descr->self );
if (hbrush) oldBrush = SelectObject( hdc, hbrush );
if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
(descr->in_focus))
{
rect.bottom = rect.top + descr->item_height;
ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
&rect, NULL, 0, NULL );
LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
rect.top = rect.bottom;
}
for (i = descr->top_item; i < descr->nb_items; i++)
{
if (!(descr->style & LBS_OWNERDRAWVARIABLE))
rect.bottom = rect.top + descr->item_height;
else
rect.bottom = rect.top + get_item_height(descr, i);
if (i == descr->focus_item)
focusRect = rect;
LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
rect.top = rect.bottom;
if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
{
if (!IS_OWNERDRAW(descr))
{
if (rect.top < descr->height)
{
rect.bottom = descr->height;
ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
&rect, NULL, 0, NULL );
}
}
rect.left += descr->column_width;
rect.right += descr->column_width;
rect.top = 0;
col_pos = descr->page_size - 1;
if (rect.left >= descr->width) break;
}
else
{
col_pos--;
if (rect.top >= descr->height) break;
}
}
if (focusRect.top != focusRect.bottom &&
descr->caret_on && descr->in_focus)
LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
if (!IS_OWNERDRAW(descr))
{
if (rect.top < descr->height)
{
rect.bottom = descr->height;
ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
&rect, NULL, 0, NULL );
}
if (rect.right < descr->width)
{
rect.left = rect.right;
rect.right = descr->width;
rect.top = 0;
rect.bottom = descr->height;
ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
&rect, NULL, 0, NULL );
}
}
if (oldFont) SelectObject( hdc, oldFont );
if (oldBrush) SelectObject( hdc, oldBrush );
return 0;
}
static LRESULT LISTBOX_NCPaint( LB_DESCR *descr, HRGN region )
{
DWORD exstyle = GetWindowLongW( descr->self, GWL_EXSTYLE);
HTHEME theme = GetWindowTheme( descr->self );
HRGN cliprgn = region;
int cxEdge, cyEdge;
HDC hdc;
RECT r;
if (!theme || !(exstyle & WS_EX_CLIENTEDGE))
return DefWindowProcW(descr->self, WM_NCPAINT, (WPARAM)region, 0);
cxEdge = GetSystemMetrics(SM_CXEDGE);
cyEdge = GetSystemMetrics(SM_CYEDGE);
GetWindowRect(descr->self, &r);
cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge,
r.right - cxEdge, r.bottom - cyEdge);
if (region != (HRGN)1)
CombineRgn(cliprgn, cliprgn, region, RGN_AND);
OffsetRect(&r, -r.left, -r.top);
hdc = GetDCEx(descr->self, region, DCX_WINDOW|DCX_INTERSECTRGN);
if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
DrawThemeParentBackground(descr->self, hdc, &r);
DrawThemeBackground (theme, hdc, 0, 0, &r, 0);
ReleaseDC(descr->self, hdc);
DefWindowProcW(descr->self, WM_NCPAINT, (WPARAM)cliprgn, 0);
DeleteObject(cliprgn);
return 0;
}
static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
{
RECT rect;
if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
{
if (descr->style & LBS_NOREDRAW)
{
descr->style |= LBS_DISPLAYCHANGED;
return;
}
rect.bottom = descr->height;
InvalidateRect( descr->self, &rect, TRUE );
if (descr->style & LBS_MULTICOLUMN)
{
rect.left = rect.right;
rect.right = descr->width;
rect.top = 0;
InvalidateRect( descr->self, &rect, TRUE );
}
}
}
static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
{
RECT rect;
if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
InvalidateRect( descr->self, &rect, TRUE );
}
static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
{
if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
{
if ((index < 0) || (index >= descr->nb_items))
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
return get_item_height(descr, index);
}
else return descr->item_height;
}
static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
{
if (height > MAXWORD)
return -1;
if (!height) height = 1;
if (descr->style & LBS_OWNERDRAWVARIABLE)
{
if ((index < 0) || (index >= descr->nb_items))
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
set_item_height(descr, index, height);
LISTBOX_UpdateScroll( descr );
if (repaint)
LISTBOX_InvalidateItems( descr, index );
}
else if (height != descr->item_height)
{
TRACE("[%p]: new height = %d\n", descr->self, height );
descr->item_height = height;
LISTBOX_UpdatePage( descr );
LISTBOX_UpdateScroll( descr );
if (repaint)
InvalidateRect( descr->self, 0, TRUE );
}
return LB_OKAY;
}
static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
{
INT diff;
if (pos > descr->horz_extent - descr->width)
pos = descr->horz_extent - descr->width;
if (pos < 0) pos = 0;
if (!(diff = descr->horz_pos - pos)) return;
TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
descr->horz_pos = pos;
LISTBOX_UpdateScroll( descr );
if (abs(diff) < descr->width)
{
RECT rect;
if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
InvalidateRect( descr->self, &rect, TRUE );
ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
}
else
InvalidateRect( descr->self, NULL, TRUE );
}
static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
{
if (descr->style & LBS_MULTICOLUMN)
return LB_OKAY;
if (extent == descr->horz_extent) return LB_OKAY;
TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
descr->horz_extent = extent;
if (descr->style & WS_HSCROLL) {
SCROLLINFO info;
info.cbSize = sizeof(info);
info.nMin = 0;
info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
info.fMask = SIF_RANGE;
if (descr->style & LBS_DISABLENOSCROLL)
info.fMask |= SIF_DISABLENOSCROLL;
SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
}
if (descr->horz_pos > extent - descr->width)
LISTBOX_SetHorizontalPos( descr, extent - descr->width );
return LB_OKAY;
}
static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
{
RECT rect;
TRACE("[%p]: new column width = %d\n", descr->self, column_width);
GetClientRect(descr->self, &rect);
descr->width = rect.right - rect.left;
descr->height = rect.bottom - rect.top;
descr->column_width = column_width;
LISTBOX_UpdatePage(descr);
LISTBOX_UpdateScroll(descr);
return LB_OKAY;
}
static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
{
HDC hdc;
HFONT oldFont = 0;
const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
SIZE sz;
descr->font = font;
if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
{
ERR("unable to get DC.\n" );
return 16;
}
if (font) oldFont = SelectObject( hdc, font );
GetTextExtentPointA( hdc, alphabet, 52, &sz);
if (oldFont) SelectObject( hdc, oldFont );
ReleaseDC( descr->self, hdc );
descr->avg_char_width = (sz.cx / 26 + 1) / 2;
if (!IS_OWNERDRAW(descr))
LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
return sz.cy;
}
static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
{
INT top;
TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
if (index <= descr->top_item) top = index;
else if (descr->style & LBS_MULTICOLUMN)
{
INT cols = descr->width;
if (!fully) cols += descr->column_width - 1;
if (cols >= descr->column_width) cols /= descr->column_width;
else cols = 1;
if (index < descr->top_item + (descr->page_size * cols)) return;
top = index - descr->page_size * (cols - 1);
}
else if (descr->style & LBS_OWNERDRAWVARIABLE)
{
INT height = fully ? get_item_height(descr, index) : 1;
for (top = index; top > descr->top_item; top--)
if ((height += get_item_height(descr, top - 1)) > descr->height) break;
}
else
{
if (index < descr->top_item + descr->page_size) return;
if (!fully && (index == descr->top_item + descr->page_size) &&
(descr->height > (descr->page_size * descr->item_height))) return;
top = index - descr->page_size + 1;
}
LISTBOX_SetTopItem( descr, top, TRUE );
}
static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
{
BOOL focus_changed = descr->focus_item != index;
TRACE("old focus %d, index %d\n", descr->focus_item, index);
if (descr->style & LBS_NOSEL) return LB_ERR;
if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
if (focus_changed)
{
LISTBOX_DrawFocusRect( descr, FALSE );
descr->focus_item = index;
}
LISTBOX_MakeItemVisible( descr, index, fully_visible );
if (focus_changed)
LISTBOX_DrawFocusRect( descr, TRUE );
return LB_OKAY;
}
static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
INT last, BOOL on )
{
INT i;
if (descr->style & LBS_NOSEL) return LB_ERR;
if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
if (!descr->nb_items) return LB_OKAY;
if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
if (first < 0) first = 0;
if (last < first) return LB_OKAY;
if (on)
{
for (i = first; i <= last; i++)
{
if (is_item_selected(descr, i)) continue;
set_item_selected_state(descr, i, TRUE);
LISTBOX_InvalidateItemRect(descr, i);
}
}
else
{
for (i = first; i <= last; i++)
{
if (!is_item_selected(descr, i)) continue;
set_item_selected_state(descr, i, FALSE);
LISTBOX_InvalidateItemRect(descr, i);
}
}
NotifyWinEvent( EVENT_OBJECT_SELECTIONWITHIN, descr->self, OBJID_CLIENT, 0 );
return LB_OKAY;
}
static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
BOOL on, BOOL send_notify )
{
TRACE( "cur_sel=%d index=%d notify=%s\n",
descr->selected_item, index, send_notify ? "YES" : "NO" );
if (descr->style & LBS_NOSEL)
{
descr->selected_item = index;
return LB_ERR;
}
if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
if (descr->style & LBS_MULTIPLESEL)
{
if (index == -1)
return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
else
return LISTBOX_SelectItemRange( descr, index, index, on );
}
else
{
INT oldsel = descr->selected_item;
if (index == oldsel) return LB_OKAY;
if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
if (index != -1) set_item_selected_state(descr, index, TRUE);
descr->selected_item = index;
if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
(index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
else
if( descr->lphc )
descr->lphc->wState |= CBF_SELCHANGE;
}
return LB_OKAY;
}
static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
{
TRACE("old focus %d, index %d\n", descr->focus_item, index);
if ((index < 0) || (index >= descr->nb_items))
return;
LISTBOX_DrawFocusRect( descr, FALSE );
if (descr->style & LBS_EXTENDEDSEL)
{
if (descr->anchor_item != -1)
{
INT first = min( index, descr->anchor_item );
INT last = max( index, descr->anchor_item );
if (first > 0)
LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
LISTBOX_SelectItemRange( descr, first, last, TRUE );
}
}
else if (!(descr->style & LBS_MULTIPLESEL))
{
LISTBOX_SetSelection( descr, index, TRUE, FALSE );
}
descr->focus_item = index;
LISTBOX_MakeItemVisible( descr, index, fully_visible );
LISTBOX_DrawFocusRect( descr, TRUE );
}
static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
LPWSTR str, ULONG_PTR data )
{
INT oldfocus = descr->focus_item;
if (index == -1) index = descr->nb_items;
else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
insert_item_data(descr, index);
descr->nb_items++;
set_item_string(descr, index, str);
set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
set_item_height(descr, index, 0);
set_item_selected_state(descr, index, FALSE);
if (descr->style & LBS_OWNERDRAWVARIABLE)
{
MEASUREITEMSTRUCT mis;
UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
mis.CtlType = ODT_LISTBOX;
mis.CtlID = id;
mis.itemID = index;
mis.itemData = data;
mis.itemHeight = descr->item_height;
SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
TRACE("[%p]: measure item %d (%s) = %d\n",
descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
}
LISTBOX_UpdateScroll( descr );
LISTBOX_InvalidateItems( descr, index );
if (descr->nb_items == 1)
LISTBOX_SetCaretIndex( descr, 0, FALSE );
else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
{
descr->selected_item++;
LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
}
else
{
if (index <= descr->selected_item)
{
descr->selected_item++;
descr->focus_item = oldfocus;
}
}
return LB_OKAY;
}
static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
{
LPWSTR new_str = NULL;
LRESULT ret;
if (HAS_STRINGS(descr))
{
if (!str) str = L"";
if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str) + 1) * sizeof(WCHAR) )))
{
SEND_NOTIFICATION( descr, LBN_ERRSPACE );
return LB_ERRSPACE;
}
lstrcpyW(new_str, str);
}
if (index == -1) index = descr->nb_items;
if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
{
HeapFree( GetProcessHeap(), 0, new_str );
return ret;
}
TRACE("[%p]: added item %d %s\n",
descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
NotifyWinEvent( EVENT_OBJECT_CREATE, descr->self, OBJID_CLIENT, index + 1 );
return index;
}
static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
{
if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
{
DELETEITEMSTRUCT dis;
UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
dis.CtlType = ODT_LISTBOX;
dis.CtlID = id;
dis.itemID = index;
dis.hwndItem = descr->self;
dis.itemData = get_item_data(descr, index);
SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
}
HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
}
static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
{
if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
LISTBOX_InvalidateItems( descr, index );
if (descr->nb_items == 1)
{
NotifyWinEvent( EVENT_OBJECT_DESTROY, descr->self, OBJID_CLIENT, 1 );
SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
return LB_OKAY;
}
descr->nb_items--;
LISTBOX_DeleteItem( descr, index );
remove_item_data(descr, index);
if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
resize_storage(descr, descr->nb_items);
LISTBOX_UpdateScroll( descr );
if (descr->nb_items == descr->page_size)
LISTBOX_SetTopItem( descr, 0, TRUE );
if (!IS_MULTISELECT(descr))
{
if (index == descr->selected_item)
descr->selected_item = -1;
else if (index < descr->selected_item)
{
descr->selected_item--;
if (ISWIN31)
LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
}
}
if (descr->focus_item >= descr->nb_items)
{
descr->focus_item = descr->nb_items - 1;
if (descr->focus_item < 0) descr->focus_item = 0;
}
NotifyWinEvent( EVENT_OBJECT_DESTROY, descr->self, OBJID_CLIENT, index + 1 );
return LB_OKAY;
}
static void LISTBOX_ResetContent( LB_DESCR *descr )
{
INT i;
if (!(descr->style & LBS_NODATA))
for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
HeapFree( GetProcessHeap(), 0, descr->u.items );
descr->nb_items = 0;
descr->top_item = 0;
descr->selected_item = -1;
descr->focus_item = 0;
descr->anchor_item = -1;
descr->items_size = 0;
descr->u.items = NULL;
}
static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
{
UINT orig_num = descr->nb_items;
if (!(descr->style & LBS_NODATA)) return LB_ERR;
if (!resize_storage(descr, count))
return LB_ERRSPACE;
descr->nb_items = count;
if (descr->style & LBS_NOREDRAW)
descr->style |= LBS_DISPLAYCHANGED;
if (count)
{
LISTBOX_UpdateScroll(descr);
if (count < orig_num)
{
descr->anchor_item = min(descr->anchor_item, count - 1);
if (descr->selected_item >= count)
descr->selected_item = -1;
if (count <= descr->page_size && orig_num > descr->page_size)
LISTBOX_SetTopItem(descr, 0, TRUE);
descr->focus_item = min(descr->focus_item, count - 1);
}
else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
}
else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
InvalidateRect( descr->self, NULL, TRUE );
return LB_OKAY;
}
static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
LPCWSTR filespec, BOOL long_names )
{
HANDLE handle;
LRESULT ret = LB_OKAY;
WIN32_FIND_DATAW entry;
int pos;
LRESULT maxinsert = LB_ERR;
if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
{
int le = GetLastError();
if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
}
else
{
do
{
WCHAR buffer[270];
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (!(attrib & DDL_DIRECTORY) ||
!lstrcmpW( entry.cFileName, L"." )) continue;
buffer[0] = '[';
if (!long_names && entry.cAlternateFileName[0])
lstrcpyW( buffer + 1, entry.cAlternateFileName );
else
lstrcpyW( buffer + 1, entry.cFileName );
lstrcatW(buffer, L"]");
}
else
{
#define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
if ((attrib & DDL_EXCLUSIVE) &&
((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
continue;
#undef ATTRIBS
if (!long_names && entry.cAlternateFileName[0])
lstrcpyW( buffer, entry.cAlternateFileName );
else
lstrcpyW( buffer, entry.cFileName );
}
if (!long_names) CharLowerW( buffer );
pos = LISTBOX_FindFileStrPos( descr, buffer );
if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
break;
if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
} while (FindNextFileW( handle, &entry ));
FindClose( handle );
}
}
if (ret >= 0)
{
ret = maxinsert;
if (attrib & DDL_DRIVES)
{
WCHAR buffer[] = L"[-a-]";
WCHAR root[] = L"A:\\";
int drive;
for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
{
if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
break;
}
}
}
return ret;
}
static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
{
SCROLLINFO info;
if (descr->style & LBS_MULTICOLUMN) return 0;
switch(scrollReq)
{
case SB_LINEUP:
LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
break;
case SB_LINEDOWN:
LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
break;
case SB_PAGEUP:
LISTBOX_SetTopItem( descr, descr->top_item -
LISTBOX_GetCurrentPageSize( descr ), TRUE );
break;
case SB_PAGEDOWN:
LISTBOX_SetTopItem( descr, descr->top_item +
LISTBOX_GetCurrentPageSize( descr ), TRUE );
break;
case SB_THUMBPOSITION:
LISTBOX_SetTopItem( descr, pos, TRUE );
break;
case SB_THUMBTRACK:
info.cbSize = sizeof(info);
info.fMask = SIF_TRACKPOS;
GetScrollInfo( descr->self, SB_VERT, &info );
LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
break;
case SB_TOP:
LISTBOX_SetTopItem( descr, 0, TRUE );
break;
case SB_BOTTOM:
LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
break;
}
return 0;
}
static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
{
SCROLLINFO info;
INT page;
if (descr->style & LBS_MULTICOLUMN)
{
switch(scrollReq)
{
case SB_LINELEFT:
LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
TRUE );
break;
case SB_LINERIGHT:
LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
TRUE );
break;
case SB_PAGELEFT:
page = descr->width / descr->column_width;
if (page < 1) page = 1;
LISTBOX_SetTopItem( descr,
descr->top_item - page * descr->page_size, TRUE );
break;
case SB_PAGERIGHT:
page = descr->width / descr->column_width;
if (page < 1) page = 1;
LISTBOX_SetTopItem( descr,
descr->top_item + page * descr->page_size, TRUE );
break;
case SB_THUMBPOSITION:
LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
break;
case SB_THUMBTRACK:
info.cbSize = sizeof(info);
info.fMask = SIF_TRACKPOS;
GetScrollInfo( descr->self, SB_VERT, &info );
LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
TRUE );
break;
case SB_LEFT:
LISTBOX_SetTopItem( descr, 0, TRUE );
break;
case SB_RIGHT:
LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
break;
}
}
else if (descr->horz_extent)
{
switch(scrollReq)
{
case SB_LINELEFT:
LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
break;
case SB_LINERIGHT:
LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
break;
case SB_PAGELEFT:
LISTBOX_SetHorizontalPos( descr,
descr->horz_pos - descr->width );
break;
case SB_PAGERIGHT:
LISTBOX_SetHorizontalPos( descr,
descr->horz_pos + descr->width );
break;
case SB_THUMBPOSITION:
LISTBOX_SetHorizontalPos( descr, pos );
break;
case SB_THUMBTRACK:
info.cbSize = sizeof(info);
info.fMask = SIF_TRACKPOS;
GetScrollInfo( descr->self, SB_HORZ, &info );
LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
break;
case SB_LEFT:
LISTBOX_SetHorizontalPos( descr, 0 );
break;
case SB_RIGHT:
LISTBOX_SetHorizontalPos( descr,
descr->horz_extent - descr->width );
break;
}
}
return 0;
}
static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
{
INT pulScrollLines = 3;
SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
if ((delta < 0 && descr->wheel_remain < 0) ||
(delta > 0 && descr->wheel_remain > 0))
descr->wheel_remain += delta;
else
descr->wheel_remain = delta;
if (descr->wheel_remain && pulScrollLines)
{
int cLineScroll;
if (descr->style & LBS_MULTICOLUMN)
{
pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
pulScrollLines = max(1, pulScrollLines);
cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
cLineScroll *= descr->page_size;
}
else
{
pulScrollLines = min(descr->page_size, pulScrollLines);
cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
}
LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
}
return 0;
}
static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
{
INT index = LISTBOX_GetItemFromPoint( descr, x, y );
TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
descr->self, x, y, index, descr->focus_item);
if (!descr->caret_on && (descr->in_focus)) return 0;
if (!descr->in_focus)
{
if( !descr->lphc ) SetFocus( descr->self );
else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
}
if (index == -1) return 0;
if (!descr->lphc)
{
if (descr->style & LBS_NOTIFY )
SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
MAKELPARAM( x, y ) );
}
descr->captured = TRUE;
SetCapture( descr->self );
if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
{
if (!(keys & MK_SHIFT)) descr->anchor_item = index;
if (keys & MK_CONTROL)
{
LISTBOX_SetCaretIndex( descr, index, FALSE );
LISTBOX_SetSelection( descr, index,
!is_item_selected(descr, index),
(descr->style & LBS_NOTIFY) != 0);
}
else
{
LISTBOX_MoveCaret( descr, index, FALSE );
if (descr->style & LBS_EXTENDEDSEL)
{
LISTBOX_SetSelection( descr, index,
is_item_selected(descr, index),
(descr->style & LBS_NOTIFY) != 0 );
}
else
{
LISTBOX_SetSelection( descr, index,
!is_item_selected(descr, index),
(descr->style & LBS_NOTIFY) != 0 );
}
}
}
else
{
descr->anchor_item = index;
LISTBOX_MoveCaret( descr, index, FALSE );
LISTBOX_SetSelection( descr, index,
TRUE, (descr->style & LBS_NOTIFY) != 0 );
}
if (!descr->lphc)
{
if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
{
POINT pt;
pt.x = x;
pt.y = y;
if (DragDetect( descr->self, pt ))
SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
}
}
return 0;
}
static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
{
RECT clientRect, screenRect;
POINT mousePos;
mousePos.x = x;
mousePos.y = y;
GetClientRect(descr->self, &clientRect);
if(PtInRect(&clientRect, mousePos))
{
if (msg == WM_LBUTTONDOWN)
{
descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
return LISTBOX_HandleLButtonDown( descr, keys, x, y);
}
else if (descr->style & LBS_NOTIFY)
SEND_NOTIFICATION( descr, LBN_DBLCLK );
}
else
{
POINT screenMousePos;
HWND hWndOldCapture;
screenMousePos = mousePos;
hWndOldCapture = GetCapture();
ReleaseCapture();
GetWindowRect(descr->self, &screenRect);
ClientToScreen(descr->self, &screenMousePos);
if(!PtInRect(&screenRect, screenMousePos))
{
LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
}
else
{
INT nHitTestType=0;
LONG style = GetWindowLongW( descr->self, GWL_STYLE );
if (style & WS_VSCROLL)
{
clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
if (PtInRect( &clientRect, mousePos ))
nHitTestType = HTVSCROLL;
}
if (style & WS_HSCROLL)
{
clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
if (PtInRect( &clientRect, mousePos ))
nHitTestType = HTHSCROLL;
}
if(nHitTestType != 0)
{
SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
MAKELONG(screenMousePos.x, screenMousePos.y));
}
if(hWndOldCapture != 0)
SetCapture(hWndOldCapture);
}
}
return 0;
}
static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
{
if (LISTBOX_Timer != LB_TIMER_NONE)
KillSystemTimer( descr->self, LB_TIMER_ID );
LISTBOX_Timer = LB_TIMER_NONE;
if (descr->captured)
{
descr->captured = FALSE;
if (GetCapture() == descr->self) ReleaseCapture();
if ((descr->style & LBS_NOTIFY) && descr->nb_items)
SEND_NOTIFICATION( descr, LBN_SELCHANGE );
}
return 0;
}
static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
{
switch(dir)
{
case LB_TIMER_UP:
if (descr->top_item) index = descr->top_item - 1;
else index = 0;
break;
case LB_TIMER_LEFT:
if (descr->top_item) index -= descr->page_size;
break;
case LB_TIMER_DOWN:
index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
if (index == descr->focus_item) index++;
if (index >= descr->nb_items) index = descr->nb_items - 1;
break;
case LB_TIMER_RIGHT:
if (index + descr->page_size < descr->nb_items)
index += descr->page_size;
break;
case LB_TIMER_NONE:
break;
}
if (index == descr->focus_item) return FALSE;
LISTBOX_MoveCaret( descr, index, FALSE );
return TRUE;
}
static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
{
if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
{
KillSystemTimer( descr->self, LB_TIMER_ID );
LISTBOX_Timer = LB_TIMER_NONE;
}
return 0;
}
static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
INT x, INT y )
{
INT index;
TIMER_DIRECTION dir = LB_TIMER_NONE;
if (!descr->captured) return;
if (descr->style & LBS_MULTICOLUMN)
{
if (y < 0) y = 0;
else if (y >= descr->item_height * descr->page_size)
y = descr->item_height * descr->page_size - 1;
if (x < 0)
{
dir = LB_TIMER_LEFT;
x = 0;
}
else if (x >= descr->width)
{
dir = LB_TIMER_RIGHT;
x = descr->width - 1;
}
}
else
{
if (y < 0) dir = LB_TIMER_UP;
else if (y >= descr->height) dir = LB_TIMER_DOWN;
}
index = LISTBOX_GetItemFromPoint( descr, x, y );
if (index == -1) index = descr->focus_item;
if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
if (dir != LB_TIMER_NONE)
SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, 0 );
else if (LISTBOX_Timer != LB_TIMER_NONE)
KillSystemTimer( descr->self, LB_TIMER_ID );
LISTBOX_Timer = dir;
}
static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
{
INT caret = -1;
BOOL bForceSelection = TRUE;
if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
bForceSelection = FALSE;
if (descr->style & LBS_WANTKEYBOARDINPUT)
{
caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
MAKEWPARAM(LOWORD(key), descr->focus_item),
(LPARAM)descr->self );
if (caret == -2) return 0;
}
if (caret == -1) switch(key)
{
case VK_LEFT:
if (descr->style & LBS_MULTICOLUMN)
{
bForceSelection = FALSE;
if (descr->focus_item >= descr->page_size)
caret = descr->focus_item - descr->page_size;
break;
}
case VK_UP:
caret = descr->focus_item - 1;
if (caret < 0) caret = 0;
break;
case VK_RIGHT:
if (descr->style & LBS_MULTICOLUMN)
{
bForceSelection = FALSE;
caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
break;
}
case VK_DOWN:
caret = descr->focus_item + 1;
if (caret >= descr->nb_items) caret = descr->nb_items - 1;
break;
case VK_PRIOR:
if (descr->style & LBS_MULTICOLUMN)
{
INT page = descr->width / descr->column_width;
if (page < 1) page = 1;
caret = descr->focus_item - (page * descr->page_size) + 1;
}
else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
if (caret < 0) caret = 0;
break;
case VK_NEXT:
if (descr->style & LBS_MULTICOLUMN)
{
INT page = descr->width / descr->column_width;
if (page < 1) page = 1;
caret = descr->focus_item + (page * descr->page_size) - 1;
}
else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
if (caret >= descr->nb_items) caret = descr->nb_items - 1;
break;
case VK_HOME:
caret = 0;
break;
case VK_END:
caret = descr->nb_items - 1;
break;
case VK_SPACE:
if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
else if (descr->style & LBS_MULTIPLESEL)
{
LISTBOX_SetSelection( descr, descr->focus_item,
!is_item_selected(descr, descr->focus_item),
(descr->style & LBS_NOTIFY) != 0 );
}
break;
default:
bForceSelection = FALSE;
}
if (bForceSelection)
caret = descr->focus_item;
if (caret >= 0)
{
if (((descr->style & LBS_EXTENDEDSEL) &&
!(GetKeyState( VK_SHIFT ) & 0x8000)) ||
!IS_MULTISELECT(descr))
descr->anchor_item = caret;
LISTBOX_MoveCaret( descr, caret, TRUE );
if (descr->style & LBS_MULTIPLESEL)
descr->selected_item = caret;
else
LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
if (descr->style & LBS_NOTIFY)
{
if (descr->lphc && IsWindowVisible( descr->self ))
{
descr->lphc->wState |= CBF_NOROLLUP;
}
if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
}
}
return 0;
}
static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
{
INT caret = -1;
WCHAR str[2];
str[0] = charW;
str[1] = '\0';
if (descr->style & LBS_WANTKEYBOARDINPUT)
{
caret = SendMessageW( descr->owner, WM_CHARTOITEM,
MAKEWPARAM(charW, descr->focus_item),
(LPARAM)descr->self );
if (caret == -2) return 0;
}
if (caret == -1)
caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
if (caret != -1)
{
if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
LISTBOX_MoveCaret( descr, caret, TRUE );
if ((descr->style & LBS_NOTIFY) && descr->nb_items)
SEND_NOTIFICATION( descr, LBN_SELCHANGE );
}
return 0;
}
static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
{
LB_DESCR *descr;
MEASUREITEMSTRUCT mis;
RECT rect;
if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
return FALSE;
GetClientRect( hwnd, &rect );
descr->self = hwnd;
descr->owner = GetParent( descr->self );
descr->style = GetWindowLongW( descr->self, GWL_STYLE );
descr->width = rect.right - rect.left;
descr->height = rect.bottom - rect.top;
descr->u.items = NULL;
descr->items_size = 0;
descr->nb_items = 0;
descr->top_item = 0;
descr->selected_item = -1;
descr->focus_item = 0;
descr->anchor_item = -1;
descr->item_height = 1;
descr->page_size = 1;
descr->column_width = 150;
descr->horz_extent = 0;
descr->horz_pos = 0;
descr->nb_tabs = 0;
descr->tabs = NULL;
descr->wheel_remain = 0;
descr->caret_on = !lphc;
if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
descr->in_focus = FALSE;
descr->captured = FALSE;
descr->font = 0;
descr->locale = GetUserDefaultLCID();
descr->lphc = lphc;
if( lphc )
{
TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
descr->owner = lphc->self;
}
SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
descr->style &= ~LBS_NODATA;
descr->item_height = LISTBOX_SetFont( descr, 0 );
if (descr->style & LBS_OWNERDRAWFIXED)
{
descr->style &= ~LBS_OWNERDRAWVARIABLE;
if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
{
descr->item_height = lphc->fixedOwnerDrawHeight;
}
else
{
UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
mis.CtlType = ODT_LISTBOX;
mis.CtlID = id;
mis.itemID = -1;
mis.itemWidth = 0;
mis.itemData = 0;
mis.itemHeight = descr->item_height;
SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
}
}
OpenThemeData( descr->self, WC_LISTBOXW );
TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
return TRUE;
}
static BOOL LISTBOX_Destroy( LB_DESCR *descr )
{
HTHEME theme = GetWindowTheme( descr->self );
CloseThemeData( theme );
LISTBOX_ResetContent( descr );
SetWindowLongPtrW( descr->self, 0, 0 );
HeapFree( GetProcessHeap(), 0, descr );
return TRUE;
}
static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
HEADCOMBO *lphc = NULL;
HTHEME theme;
LRESULT ret;
if (!descr)
{
if (!IsWindow(hwnd)) return 0;
if (msg == WM_CREATE)
{
CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
if (!LISTBOX_Create( hwnd, lphc )) return -1;
TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
return 0;
}
return DefWindowProcW( hwnd, msg, wParam, lParam );
}
if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
TRACE("[%p]: msg %#x, wp %Ix, lp %Ix\n", descr->self, msg, wParam, lParam );
switch(msg)
{
case LB_RESETCONTENT:
LISTBOX_ResetContent( descr );
LISTBOX_UpdateScroll( descr );
InvalidateRect( descr->self, NULL, TRUE );
return 0;
case LB_ADDSTRING:
{
const WCHAR *textW = (const WCHAR *)lParam;
INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
return LISTBOX_InsertString( descr, index, textW );
}
case LB_INSERTSTRING:
return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
case LB_ADDFILE:
{
const WCHAR *textW = (const WCHAR *)lParam;
INT index = LISTBOX_FindFileStrPos( descr, textW );
return LISTBOX_InsertString( descr, index, textW );
}
case LB_DELETESTRING:
if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
return descr->nb_items;
else
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
case LB_GETITEMDATA:
if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
return get_item_data(descr, wParam);
case LB_SETITEMDATA:
if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
set_item_data(descr, wParam, lParam);
return TRUE;
case LB_GETCOUNT:
return descr->nb_items;
case LB_GETTEXT:
return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
case LB_GETTEXTLEN:
if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
return lstrlenW(get_item_string(descr, wParam));
case LB_GETCURSEL:
if (descr->nb_items == 0)
return LB_ERR;
if (!IS_MULTISELECT(descr))
return descr->selected_item;
if (descr->selected_item != -1)
return descr->selected_item;
return descr->focus_item;
case LB_GETTOPINDEX:
return descr->top_item;
case LB_GETITEMHEIGHT:
return LISTBOX_GetItemHeight( descr, wParam );
case LB_SETITEMHEIGHT:
return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
case LB_ITEMFROMPOINT:
{
POINT pt;
RECT rect;
int index;
BOOL hit = TRUE;
if(descr->nb_items == 0)
return 0x1ffff;
pt.x = (short)LOWORD(lParam);
pt.y = (short)HIWORD(lParam);
SetRect(&rect, 0, 0, descr->width, descr->height);
if(!PtInRect(&rect, pt))
{
pt.x = min(pt.x, rect.right - 1);
pt.x = max(pt.x, 0);
pt.y = min(pt.y, rect.bottom - 1);
pt.y = max(pt.y, 0);
hit = FALSE;
}
index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
if(index == -1)
{
index = descr->nb_items - 1;
hit = FALSE;
}
return MAKELONG(index, hit ? 0 : 1);
}
case LB_SETCARETINDEX:
if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
return LB_ERR;
else if (ISWIN31)
return wParam;
else
return LB_OKAY;
case LB_GETCARETINDEX:
return descr->focus_item;
case LB_SETTOPINDEX:
return LISTBOX_SetTopItem( descr, wParam, TRUE );
case LB_SETCOLUMNWIDTH:
return LISTBOX_SetColumnWidth( descr, wParam );
case LB_GETITEMRECT:
return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
case LB_FINDSTRING:
return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
case LB_FINDSTRINGEXACT:
return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
case LB_SELECTSTRING:
{
const WCHAR *textW = (const WCHAR *)lParam;
INT index;
if (HAS_STRINGS(descr))
TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
index = LISTBOX_FindString( descr, wParam, textW, FALSE );
if (index != LB_ERR)
{
LISTBOX_MoveCaret( descr, index, TRUE );
LISTBOX_SetSelection( descr, index, TRUE, FALSE );
}
return index;
}
case LB_GETSEL:
if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
return LB_ERR;
return is_item_selected(descr, wParam);
case LB_SETSEL:
ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
if (ret != LB_ERR && wParam)
{
descr->anchor_item = lParam;
if (lParam != -1)
LISTBOX_SetCaretIndex( descr, lParam, TRUE );
}
return ret;
case LB_SETCURSEL:
if (IS_MULTISELECT(descr)) return LB_ERR;
LISTBOX_SetCaretIndex( descr, wParam, TRUE );
ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
if (ret != LB_ERR) ret = descr->selected_item;
return ret;
case LB_GETSELCOUNT:
return LISTBOX_GetSelCount( descr );
case LB_GETSELITEMS:
return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
case LB_SELITEMRANGE:
if (LOWORD(lParam) <= HIWORD(lParam))
return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
HIWORD(lParam), wParam );
else
return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
LOWORD(lParam), wParam );
case LB_SELITEMRANGEEX:
if ((INT)lParam >= (INT)wParam)
return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
else
return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
case LB_GETHORIZONTALEXTENT:
return descr->horz_extent;
case LB_SETHORIZONTALEXTENT:
return LISTBOX_SetHorizontalExtent( descr, wParam );
case LB_GETANCHORINDEX:
return descr->anchor_item;
case LB_SETANCHORINDEX:
if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
{
SetLastError(ERROR_INVALID_INDEX);
return LB_ERR;
}
descr->anchor_item = (INT)wParam;
return LB_OKAY;
case LB_DIR:
return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
case LB_GETLOCALE:
return descr->locale;
case LB_SETLOCALE:
{
LCID ret;
if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
return LB_ERR;
ret = descr->locale;
descr->locale = (LCID)wParam;
return ret;
}
case LB_INITSTORAGE:
return LISTBOX_InitStorage( descr, wParam );
case LB_SETCOUNT:
return LISTBOX_SetCount( descr, (INT)wParam );
case LB_SETTABSTOPS:
return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
case LB_CARETON:
if (descr->caret_on)
return LB_OKAY;
descr->caret_on = TRUE;
if ((descr->focus_item != -1) && (descr->in_focus))
LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
return LB_OKAY;
case LB_CARETOFF:
if (!descr->caret_on)
return LB_OKAY;
descr->caret_on = FALSE;
if ((descr->focus_item != -1) && (descr->in_focus))
LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
return LB_OKAY;
case LB_GETLISTBOXINFO:
return descr->page_size;
case WM_DESTROY:
return LISTBOX_Destroy( descr );
case WM_ENABLE:
InvalidateRect( descr->self, NULL, TRUE );
return 0;
case WM_SETREDRAW:
LISTBOX_SetRedraw( descr, wParam != 0 );
return 0;
case WM_GETDLGCODE:
return DLGC_WANTARROWS | DLGC_WANTCHARS;
case WM_PRINTCLIENT:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
ret = LISTBOX_Paint( descr, hdc );
if( !wParam ) EndPaint( descr->self, &ps );
}
return ret;
case WM_NCPAINT:
return LISTBOX_NCPaint( descr, (HRGN)wParam );
case WM_GETOBJECT:
if ((LONG)lParam == OBJID_QUERYCLASSNAMEIDX)
return 0x10000;
break;
case WM_SIZE:
LISTBOX_UpdateSize( descr );
return 0;
case WM_GETFONT:
return (LRESULT)descr->font;
case WM_SETFONT:
LISTBOX_SetFont( descr, (HFONT)wParam );
if (lParam) InvalidateRect( descr->self, 0, TRUE );
return 0;
case WM_SETFOCUS:
descr->in_focus = TRUE;
descr->caret_on = TRUE;
if (descr->focus_item != -1)
LISTBOX_DrawFocusRect( descr, TRUE );
SEND_NOTIFICATION( descr, LBN_SETFOCUS );
return 0;
case WM_KILLFOCUS:
LISTBOX_HandleLButtonUp( descr );
descr->in_focus = FALSE;
descr->wheel_remain = 0;
if ((descr->focus_item != -1) && descr->caret_on)
LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
return 0;
case WM_HSCROLL:
return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
case WM_VSCROLL:
return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
case WM_MOUSEWHEEL:
if (wParam & (MK_SHIFT | MK_CONTROL))
return DefWindowProcW( descr->self, msg, wParam, lParam );
return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
case WM_LBUTTONDOWN:
if (lphc)
return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
(INT16)LOWORD(lParam),
(INT16)HIWORD(lParam) );
return LISTBOX_HandleLButtonDown( descr, wParam,
(INT16)LOWORD(lParam),
(INT16)HIWORD(lParam) );
case WM_LBUTTONDBLCLK:
if (lphc)
return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
(INT16)LOWORD(lParam),
(INT16)HIWORD(lParam) );
if (descr->style & LBS_NOTIFY)
SEND_NOTIFICATION( descr, LBN_DBLCLK );
return 0;
case WM_MOUSEMOVE:
if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
{
BOOL captured = descr->captured;
POINT mousePos;
RECT clientRect;
mousePos.x = (INT16)LOWORD(lParam);
mousePos.y = (INT16)HIWORD(lParam);
if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
descr->captured = TRUE;
LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
descr->captured = captured;
}
else if (GetCapture() == descr->self)
{
LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
(INT16)HIWORD(lParam) );
}
return 0;
case WM_LBUTTONUP:
if (lphc)
{
POINT mousePos;
RECT clientRect;
mousePos.x = (INT16)LOWORD(lParam);
mousePos.y = (INT16)HIWORD(lParam);
GetClientRect(descr->self, &clientRect);
if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
}
return LISTBOX_HandleLButtonUp( descr );
case WM_KEYDOWN:
if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
{
if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
&& (wParam == VK_DOWN || wParam == VK_UP)) )
{
COMBO_FlipListbox( lphc, FALSE, FALSE );
return 0;
}
}
return LISTBOX_HandleKeyDown( descr, wParam );
case WM_CHAR:
return LISTBOX_HandleChar( descr, wParam );
case WM_SYSTIMER:
return LISTBOX_HandleSystemTimer( descr );
case WM_ERASEBKGND:
if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
{
RECT rect;
HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
wParam, (LPARAM)descr->self );
TRACE("hbrush = %p\n", hbrush);
if(!hbrush)
hbrush = GetSysColorBrush(COLOR_WINDOW);
if(hbrush)
{
GetClientRect(descr->self, &rect);
FillRect((HDC)wParam, &rect, hbrush);
}
}
return 1;
case WM_DROPFILES:
if( lphc ) return 0;
return SendMessageW( descr->owner, msg, wParam, lParam );
case WM_NCDESTROY:
if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
lphc->hWndLBox = 0;
break;
case WM_NCACTIVATE:
if (lphc) return 0;
break;
case WM_THEMECHANGED:
theme = GetWindowTheme( hwnd );
CloseThemeData( theme );
OpenThemeData( hwnd, WC_LISTBOXW );
InvalidateRect( hwnd, NULL, TRUE );
break;
default:
if ((msg >= WM_USER) && (msg < 0xc000))
WARN("[%p]: unknown msg %04x, wp %Ix, lp %Ix\n", hwnd, msg, wParam, lParam );
}
return DefWindowProcW( hwnd, msg, wParam, lParam );
}
void LISTBOX_Register(void)
{
WNDCLASSW wndClass;
memset(&wndClass, 0, sizeof(wndClass));
wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
wndClass.lpfnWndProc = LISTBOX_WindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = sizeof(LB_DESCR *);
wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
wndClass.hbrBackground = NULL;
wndClass.lpszClassName = WC_LISTBOXW;
RegisterClassW(&wndClass);
}
void COMBOLBOX_Register(void)
{
WNDCLASSW wndClass;
memset(&wndClass, 0, sizeof(wndClass));
wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
wndClass.lpfnWndProc = LISTBOX_WindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = sizeof(LB_DESCR *);
wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
wndClass.hbrBackground = NULL;
wndClass.lpszClassName = L"ComboLBox";
RegisterClassW(&wndClass);
}