winebus.sys: Report WINEBUS\WINE_COMP_XINPUT compatible id for gamepads.
[wine.git] / dlls / user32 / listbox.c
blob912adb716d0d2e566f4891da953b427cdf2f2399
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include <string.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winnls.h"
29 #include "wingdi.h"
30 #include "user_private.h"
31 #include "controls.h"
32 #include "wine/exception.h"
33 #include "wine/debug.h"
34 #include "wine/heap.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
38 /* Items array granularity (must be power of 2) */
39 #define LB_ARRAY_GRANULARITY 16
41 /* Scrolling timeout in ms */
42 #define LB_SCROLL_TIMEOUT 50
44 /* Listbox system timer id */
45 #define LB_TIMER_ID 2
47 /* flag listbox changed while setredraw false - internal style */
48 #define LBS_DISPLAYCHANGED 0x80000000
50 /* Item structure */
51 typedef struct
53 LPWSTR str; /* Item text */
54 BOOL selected; /* Is item selected? */
55 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
56 ULONG_PTR data; /* User data */
57 } LB_ITEMDATA;
59 /* Listbox structure */
60 typedef struct
62 HWND self; /* Our own window handle */
63 HWND owner; /* Owner window to send notifications to */
64 UINT style; /* Window style */
65 INT width; /* Window width */
66 INT height; /* Window height */
67 union
69 LB_ITEMDATA *items; /* Array of items */
70 BYTE *nodata_items; /* For multi-selection LBS_NODATA */
71 } u;
72 INT nb_items; /* Number of items */
73 UINT items_size; /* Total number of allocated items in the array */
74 INT top_item; /* Top visible item */
75 INT selected_item; /* Selected item */
76 INT focus_item; /* Item that has the focus */
77 INT anchor_item; /* Anchor item for extended selection */
78 INT item_height; /* Default item height */
79 INT page_size; /* Items per listbox page */
80 INT column_width; /* Column width for multi-column listboxes */
81 INT horz_extent; /* Horizontal extent */
82 INT horz_pos; /* Horizontal position */
83 INT nb_tabs; /* Number of tabs in array */
84 INT *tabs; /* Array of tabs */
85 INT avg_char_width; /* Average width of characters */
86 INT wheel_remain; /* Left over scroll amount */
87 BOOL caret_on; /* Is caret on? */
88 BOOL captured; /* Is mouse captured? */
89 BOOL in_focus;
90 HFONT font; /* Current font */
91 LCID locale; /* Current locale for string comparisons */
92 HEADCOMBO *lphc; /* ComboLBox */
93 } LB_DESCR;
96 #define IS_OWNERDRAW(descr) \
97 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
99 #define HAS_STRINGS(descr) \
100 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
103 #define IS_MULTISELECT(descr) \
104 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
105 !((descr)->style & LBS_NOSEL))
107 #define SEND_NOTIFICATION(descr,code) \
108 (SendMessageW( (descr)->owner, WM_COMMAND, \
109 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
111 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
113 /* Current timer status */
114 typedef enum
116 LB_TIMER_NONE,
117 LB_TIMER_UP,
118 LB_TIMER_LEFT,
119 LB_TIMER_DOWN,
120 LB_TIMER_RIGHT
121 } TIMER_DIRECTION;
123 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
125 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
128 For listboxes without LBS_NODATA, an array of LB_ITEMDATA is allocated
129 to store the states of each item into descr->u.items.
131 For single-selection LBS_NODATA listboxes, no storage is allocated,
132 and thus descr->u.nodata_items will always be NULL.
134 For multi-selection LBS_NODATA listboxes, one byte per item is stored
135 for the item's selection state into descr->u.nodata_items.
137 static size_t get_sizeof_item( const LB_DESCR *descr )
139 return (descr->style & LBS_NODATA) ? sizeof(BYTE) : sizeof(LB_ITEMDATA);
142 static BOOL resize_storage(LB_DESCR *descr, UINT items_size)
144 LB_ITEMDATA *items;
146 if (items_size > descr->items_size ||
147 items_size + LB_ARRAY_GRANULARITY * 2 < descr->items_size)
149 items_size = (items_size + LB_ARRAY_GRANULARITY - 1) & ~(LB_ARRAY_GRANULARITY - 1);
150 if ((descr->style & (LBS_NODATA | LBS_MULTIPLESEL | LBS_EXTENDEDSEL)) != LBS_NODATA)
152 items = heap_realloc(descr->u.items, items_size * get_sizeof_item(descr));
153 if (!items)
155 SEND_NOTIFICATION(descr, LBN_ERRSPACE);
156 return FALSE;
158 descr->u.items = items;
160 descr->items_size = items_size;
163 if ((descr->style & LBS_NODATA) && descr->u.nodata_items && items_size > descr->nb_items)
165 memset(descr->u.nodata_items + descr->nb_items, 0,
166 (items_size - descr->nb_items) * get_sizeof_item(descr));
168 return TRUE;
171 static ULONG_PTR get_item_data( const LB_DESCR *descr, UINT index )
173 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].data;
176 static void set_item_data( LB_DESCR *descr, UINT index, ULONG_PTR data )
178 if (!(descr->style & LBS_NODATA)) descr->u.items[index].data = data;
181 static WCHAR *get_item_string( const LB_DESCR *descr, UINT index )
183 return HAS_STRINGS(descr) ? descr->u.items[index].str : NULL;
186 static void set_item_string( const LB_DESCR *descr, UINT index, WCHAR *string )
188 if (!(descr->style & LBS_NODATA)) descr->u.items[index].str = string;
191 static UINT get_item_height( const LB_DESCR *descr, UINT index )
193 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].height;
196 static void set_item_height( LB_DESCR *descr, UINT index, UINT height )
198 if (!(descr->style & LBS_NODATA)) descr->u.items[index].height = height;
201 static BOOL is_item_selected( const LB_DESCR *descr, UINT index )
203 if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
204 return index == descr->selected_item;
205 if (descr->style & LBS_NODATA)
206 return descr->u.nodata_items[index];
207 else
208 return descr->u.items[index].selected;
211 static void set_item_selected_state(LB_DESCR *descr, UINT index, BOOL state)
213 if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
215 if (descr->style & LBS_NODATA)
216 descr->u.nodata_items[index] = state;
217 else
218 descr->u.items[index].selected = state;
222 static void insert_item_data(LB_DESCR *descr, UINT index)
224 size_t size = get_sizeof_item(descr);
225 BYTE *p = descr->u.nodata_items + index * size;
227 if (!descr->u.items) return;
229 if (index < descr->nb_items)
230 memmove(p + size, p, (descr->nb_items - index) * size);
233 static void remove_item_data(LB_DESCR *descr, UINT index)
235 size_t size = get_sizeof_item(descr);
236 BYTE *p = descr->u.nodata_items + index * size;
238 if (!descr->u.items) return;
240 if (index < descr->nb_items)
241 memmove(p, p + size, (descr->nb_items - index) * size);
244 /*********************************************************************
245 * listbox class descriptor
247 const struct builtin_class_descr LISTBOX_builtin_class =
249 L"ListBox", /* name */
250 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
251 WINPROC_LISTBOX, /* proc */
252 sizeof(LB_DESCR *), /* extra */
253 IDC_ARROW, /* cursor */
254 0 /* brush */
258 /*********************************************************************
259 * combolbox class descriptor
261 const struct builtin_class_descr COMBOLBOX_builtin_class =
263 L"ComboLBox", /* name */
264 CS_DBLCLKS | CS_SAVEBITS, /* style */
265 WINPROC_LISTBOX, /* proc */
266 sizeof(LB_DESCR *), /* extra */
267 IDC_ARROW, /* cursor */
268 0 /* brush */
272 /***********************************************************************
273 * LISTBOX_GetCurrentPageSize
275 * Return the current page size
277 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
279 INT i, height;
280 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
281 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
283 if ((height += get_item_height(descr, i)) > descr->height) break;
285 if (i == descr->top_item) return 1;
286 else return i - descr->top_item;
290 /***********************************************************************
291 * LISTBOX_GetMaxTopIndex
293 * Return the maximum possible index for the top of the listbox.
295 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
297 INT max, page;
299 if (descr->style & LBS_OWNERDRAWVARIABLE)
301 page = descr->height;
302 for (max = descr->nb_items - 1; max >= 0; max--)
303 if ((page -= get_item_height(descr, max)) < 0) break;
304 if (max < descr->nb_items - 1) max++;
306 else if (descr->style & LBS_MULTICOLUMN)
308 if ((page = descr->width / descr->column_width) < 1) page = 1;
309 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
310 max = (max - page) * descr->page_size;
312 else
314 max = descr->nb_items - descr->page_size;
316 if (max < 0) max = 0;
317 return max;
321 /***********************************************************************
322 * LISTBOX_UpdateScroll
324 * Update the scrollbars. Should be called whenever the content
325 * of the listbox changes.
327 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
329 SCROLLINFO info;
331 /* Check the listbox scroll bar flags individually before we call
332 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
333 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
334 scroll bar when we do not need one.
335 if (!(descr->style & WS_VSCROLL)) return;
338 /* It is important that we check descr->style, and not wnd->dwStyle,
339 for WS_VSCROLL, as the former is exactly the one passed in
340 argument to CreateWindow.
341 In Windows (and from now on in Wine :) a listbox created
342 with such a style (no WS_SCROLL) does not update
343 the scrollbar with listbox-related data, thus letting
344 the programmer use it for his/her own purposes. */
346 if (descr->style & LBS_NOREDRAW) return;
347 info.cbSize = sizeof(info);
349 if (descr->style & LBS_MULTICOLUMN)
351 info.nMin = 0;
352 info.nMax = (descr->nb_items - 1) / descr->page_size;
353 info.nPos = descr->top_item / descr->page_size;
354 info.nPage = descr->width / descr->column_width;
355 if (info.nPage < 1) info.nPage = 1;
356 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
357 if (descr->style & LBS_DISABLENOSCROLL)
358 info.fMask |= SIF_DISABLENOSCROLL;
359 if (descr->style & WS_HSCROLL)
360 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
361 info.nMax = 0;
362 info.fMask = SIF_RANGE;
363 if (descr->style & WS_VSCROLL)
364 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
366 else
368 info.nMin = 0;
369 info.nMax = descr->nb_items - 1;
370 info.nPos = descr->top_item;
371 info.nPage = LISTBOX_GetCurrentPageSize( descr );
372 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
373 if (descr->style & LBS_DISABLENOSCROLL)
374 info.fMask |= SIF_DISABLENOSCROLL;
375 if (descr->style & WS_VSCROLL)
376 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
378 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
380 info.nPos = descr->horz_pos;
381 info.nPage = descr->width;
382 info.fMask = SIF_POS | SIF_PAGE;
383 if (descr->style & LBS_DISABLENOSCROLL)
384 info.fMask |= SIF_DISABLENOSCROLL;
385 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
387 else
389 if (descr->style & LBS_DISABLENOSCROLL)
391 info.nMin = 0;
392 info.nMax = 0;
393 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
394 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
396 else
398 ShowScrollBar( descr->self, SB_HORZ, FALSE );
405 /***********************************************************************
406 * LISTBOX_SetTopItem
408 * Set the top item of the listbox, scrolling up or down if necessary.
410 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
412 INT max = LISTBOX_GetMaxTopIndex( descr );
414 TRACE("setting top item %d, scroll %d\n", index, scroll);
416 if (index > max) index = max;
417 if (index < 0) index = 0;
418 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
419 if (descr->top_item == index) return LB_OKAY;
420 if (scroll)
422 INT dx = 0, dy = 0;
423 if (descr->style & LBS_MULTICOLUMN)
424 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
425 else if (descr->style & LBS_OWNERDRAWVARIABLE)
427 INT i;
428 if (index > descr->top_item)
430 for (i = index - 1; i >= descr->top_item; i--)
431 dy -= get_item_height(descr, i);
433 else
435 for (i = index; i < descr->top_item; i++)
436 dy += get_item_height(descr, i);
439 else
440 dy = (descr->top_item - index) * descr->item_height;
442 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
443 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
445 else
446 InvalidateRect( descr->self, NULL, TRUE );
447 descr->top_item = index;
448 LISTBOX_UpdateScroll( descr );
449 return LB_OKAY;
453 /***********************************************************************
454 * LISTBOX_UpdatePage
456 * Update the page size. Should be called when the size of
457 * the client area or the item height changes.
459 static void LISTBOX_UpdatePage( LB_DESCR *descr )
461 INT page_size;
463 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
464 page_size = 1;
465 if (page_size == descr->page_size) return;
466 descr->page_size = page_size;
467 if (descr->style & LBS_MULTICOLUMN)
468 InvalidateRect( descr->self, NULL, TRUE );
469 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
473 /***********************************************************************
474 * LISTBOX_UpdateSize
476 * Update the size of the listbox. Should be called when the size of
477 * the client area changes.
479 static void LISTBOX_UpdateSize( LB_DESCR *descr )
481 RECT rect;
483 GetClientRect( descr->self, &rect );
484 descr->width = rect.right - rect.left;
485 descr->height = rect.bottom - rect.top;
486 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
488 INT remaining;
489 RECT rect;
491 GetWindowRect( descr->self, &rect );
492 if(descr->item_height != 0)
493 remaining = descr->height % descr->item_height;
494 else
495 remaining = 0;
496 if ((descr->height > descr->item_height) && remaining)
498 TRACE("[%p]: changing height %d -> %d\n",
499 descr->self, descr->height, descr->height - remaining );
500 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
501 rect.bottom - rect.top - remaining,
502 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
503 return;
506 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
507 LISTBOX_UpdatePage( descr );
508 LISTBOX_UpdateScroll( descr );
510 /* Invalidate the focused item so it will be repainted correctly */
511 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
513 InvalidateRect( descr->self, &rect, FALSE );
518 /***********************************************************************
519 * LISTBOX_GetItemRect
521 * Get the rectangle enclosing an item, in listbox client coordinates.
522 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
524 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
526 /* Index <= 0 is legal even on empty listboxes */
527 if (index && (index >= descr->nb_items))
529 SetRectEmpty(rect);
530 SetLastError(ERROR_INVALID_INDEX);
531 return LB_ERR;
533 SetRect( rect, 0, 0, descr->width, descr->height );
534 if (descr->style & LBS_MULTICOLUMN)
536 INT col = (index / descr->page_size) -
537 (descr->top_item / descr->page_size);
538 rect->left += col * descr->column_width;
539 rect->right = rect->left + descr->column_width;
540 rect->top += (index % descr->page_size) * descr->item_height;
541 rect->bottom = rect->top + descr->item_height;
543 else if (descr->style & LBS_OWNERDRAWVARIABLE)
545 INT i;
546 rect->right += descr->horz_pos;
547 if ((index >= 0) && (index < descr->nb_items))
549 if (index < descr->top_item)
551 for (i = descr->top_item-1; i >= index; i--)
552 rect->top -= get_item_height(descr, i);
554 else
556 for (i = descr->top_item; i < index; i++)
557 rect->top += get_item_height(descr, i);
559 rect->bottom = rect->top + get_item_height(descr, index);
563 else
565 rect->top += (index - descr->top_item) * descr->item_height;
566 rect->bottom = rect->top + descr->item_height;
567 rect->right += descr->horz_pos;
570 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
572 return ((rect->left < descr->width) && (rect->right > 0) &&
573 (rect->top < descr->height) && (rect->bottom > 0));
577 /***********************************************************************
578 * LISTBOX_GetItemFromPoint
580 * Return the item nearest from point (x,y) (in client coordinates).
582 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
584 INT index = descr->top_item;
586 if (!descr->nb_items) return -1; /* No items */
587 if (descr->style & LBS_OWNERDRAWVARIABLE)
589 INT pos = 0;
590 if (y >= 0)
592 while (index < descr->nb_items)
594 if ((pos += get_item_height(descr, index)) > y) break;
595 index++;
598 else
600 while (index > 0)
602 index--;
603 if ((pos -= get_item_height(descr, index)) <= y) break;
607 else if (descr->style & LBS_MULTICOLUMN)
609 if (y >= descr->item_height * descr->page_size) return -1;
610 if (y >= 0) index += y / descr->item_height;
611 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
612 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
614 else
616 index += (y / descr->item_height);
618 if (index < 0) return 0;
619 if (index >= descr->nb_items) return -1;
620 return index;
624 /***********************************************************************
625 * LISTBOX_PaintItem
627 * Paint an item.
629 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
630 INT index, UINT action, BOOL ignoreFocus )
632 BOOL selected = FALSE, focused;
633 WCHAR *item_str = NULL;
635 if (index < descr->nb_items)
637 item_str = get_item_string(descr, index);
638 selected = is_item_selected(descr, index);
641 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
643 if (IS_OWNERDRAW(descr))
645 DRAWITEMSTRUCT dis;
646 RECT r;
647 HRGN hrgn;
649 if (index >= descr->nb_items)
651 if (action == ODA_FOCUS)
652 DrawFocusRect( hdc, rect );
653 else
654 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
655 return;
658 /* some programs mess with the clipping region when
659 drawing the item, *and* restore the previous region
660 after they are done, so a region has better to exist
661 else everything ends clipped */
662 GetClientRect(descr->self, &r);
663 hrgn = set_control_clipping( hdc, &r );
665 dis.CtlType = ODT_LISTBOX;
666 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
667 dis.hwndItem = descr->self;
668 dis.itemAction = action;
669 dis.hDC = hdc;
670 dis.itemID = index;
671 dis.itemState = 0;
672 if (selected)
673 dis.itemState |= ODS_SELECTED;
674 if (focused)
675 dis.itemState |= ODS_FOCUS;
676 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
677 dis.itemData = get_item_data(descr, index);
678 dis.rcItem = *rect;
679 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
680 descr->self, index, debugstr_w(item_str), action,
681 dis.itemState, wine_dbgstr_rect(rect) );
682 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
683 SelectClipRgn( hdc, hrgn );
684 if (hrgn) DeleteObject( hrgn );
686 else
688 COLORREF oldText = 0, oldBk = 0;
690 if (action == ODA_FOCUS)
692 DrawFocusRect( hdc, rect );
693 return;
695 if (selected)
697 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
698 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
701 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
702 descr->self, index, debugstr_w(item_str), action,
703 wine_dbgstr_rect(rect) );
704 if (!item_str)
705 ExtTextOutW( hdc, rect->left + 1, rect->top,
706 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
707 else if (!(descr->style & LBS_USETABSTOPS))
708 ExtTextOutW( hdc, rect->left + 1, rect->top,
709 ETO_OPAQUE | ETO_CLIPPED, rect, item_str,
710 lstrlenW(item_str), NULL );
711 else
713 /* Output empty string to paint background in the full width. */
714 ExtTextOutW( hdc, rect->left + 1, rect->top,
715 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
716 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
717 item_str, lstrlenW(item_str),
718 descr->nb_tabs, descr->tabs, 0);
720 if (selected)
722 SetBkColor( hdc, oldBk );
723 SetTextColor( hdc, oldText );
725 if (focused)
726 DrawFocusRect( hdc, rect );
731 /***********************************************************************
732 * LISTBOX_SetRedraw
734 * Change the redraw flag.
736 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
738 if (on)
740 if (!(descr->style & LBS_NOREDRAW)) return;
741 descr->style &= ~LBS_NOREDRAW;
742 if (descr->style & LBS_DISPLAYCHANGED)
743 { /* page was changed while setredraw false, refresh automatically */
744 InvalidateRect(descr->self, NULL, TRUE);
745 if ((descr->top_item + descr->page_size) > descr->nb_items)
746 { /* reset top of page if less than number of items/page */
747 descr->top_item = descr->nb_items - descr->page_size;
748 if (descr->top_item < 0) descr->top_item = 0;
750 descr->style &= ~LBS_DISPLAYCHANGED;
752 LISTBOX_UpdateScroll( descr );
754 else descr->style |= LBS_NOREDRAW;
758 /***********************************************************************
759 * LISTBOX_RepaintItem
761 * Repaint a single item synchronously.
763 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
765 HDC hdc;
766 RECT rect;
767 HFONT oldFont = 0;
768 HBRUSH hbrush, oldBrush = 0;
770 /* Do not repaint the item if the item is not visible */
771 if (!IsWindowVisible(descr->self)) return;
772 if (descr->style & LBS_NOREDRAW)
774 descr->style |= LBS_DISPLAYCHANGED;
775 return;
777 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
778 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
779 if (descr->font) oldFont = SelectObject( hdc, descr->font );
780 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
781 (WPARAM)hdc, (LPARAM)descr->self );
782 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
783 if (!IsWindowEnabled(descr->self))
784 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
785 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
786 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
787 if (oldFont) SelectObject( hdc, oldFont );
788 if (oldBrush) SelectObject( hdc, oldBrush );
789 ReleaseDC( descr->self, hdc );
793 /***********************************************************************
794 * LISTBOX_DrawFocusRect
796 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
798 HDC hdc;
799 RECT rect;
800 HFONT oldFont = 0;
802 /* Do not repaint the item if the item is not visible */
803 if (!IsWindowVisible(descr->self)) return;
805 if (descr->focus_item == -1) return;
806 if (!descr->caret_on || !descr->in_focus) return;
808 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
809 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
810 if (descr->font) oldFont = SelectObject( hdc, descr->font );
811 if (!IsWindowEnabled(descr->self))
812 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
813 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
814 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
815 if (oldFont) SelectObject( hdc, oldFont );
816 ReleaseDC( descr->self, hdc );
820 /***********************************************************************
821 * LISTBOX_InitStorage
823 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
825 UINT new_size = descr->nb_items + nb_items;
827 if (new_size > descr->items_size && !resize_storage(descr, new_size))
828 return LB_ERRSPACE;
829 return descr->items_size;
833 /***********************************************************************
834 * LISTBOX_SetTabStops
836 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
838 INT i;
840 if (!(descr->style & LBS_USETABSTOPS))
842 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
843 return FALSE;
846 HeapFree( GetProcessHeap(), 0, descr->tabs );
847 if (!(descr->nb_tabs = count))
849 descr->tabs = NULL;
850 return TRUE;
852 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
853 descr->nb_tabs * sizeof(INT) )))
854 return FALSE;
855 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
857 /* convert into "dialog units"*/
858 for (i = 0; i < descr->nb_tabs; i++)
859 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
861 return TRUE;
865 /***********************************************************************
866 * LISTBOX_GetText
868 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
870 DWORD len;
872 if ((index < 0) || (index >= descr->nb_items))
874 SetLastError(ERROR_INVALID_INDEX);
875 return LB_ERR;
878 if (HAS_STRINGS(descr))
880 WCHAR *str = get_item_string(descr, index);
882 if (!buffer)
884 len = lstrlenW(str);
885 if( unicode )
886 return len;
887 return WideCharToMultiByte( CP_ACP, 0, str, len, NULL, 0, NULL, NULL );
890 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(str));
892 __TRY /* hide a Delphi bug that passes a read-only buffer */
894 if(unicode)
896 lstrcpyW(buffer, str);
897 len = lstrlenW(buffer);
899 else
901 len = WideCharToMultiByte(CP_ACP, 0, str, -1, (LPSTR)buffer,
902 0x7FFFFFFF, NULL, NULL) - 1;
905 __EXCEPT_PAGE_FAULT
907 WARN( "got an invalid buffer (Delphi bug?)\n" );
908 SetLastError( ERROR_INVALID_PARAMETER );
909 return LB_ERR;
911 __ENDTRY
912 } else
914 if (buffer)
915 *((ULONG_PTR *)buffer) = get_item_data(descr, index);
916 len = sizeof(ULONG_PTR);
918 return len;
921 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
923 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
924 if (ret == CSTR_LESS_THAN)
925 return -1;
926 if (ret == CSTR_EQUAL)
927 return 0;
928 if (ret == CSTR_GREATER_THAN)
929 return 1;
930 return -1;
933 /***********************************************************************
934 * LISTBOX_FindStringPos
936 * Find the nearest string located before a given string in sort order.
937 * If 'exact' is TRUE, return an error if we don't get an exact match.
939 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
941 INT index, min, max, res;
943 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
945 min = 0;
946 max = descr->nb_items - 1;
947 while (min <= max)
949 index = (min + max) / 2;
950 if (HAS_STRINGS(descr))
951 res = LISTBOX_lstrcmpiW( descr->locale, get_item_string(descr, index), str );
952 else
954 COMPAREITEMSTRUCT cis;
955 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
957 cis.CtlType = ODT_LISTBOX;
958 cis.CtlID = id;
959 cis.hwndItem = descr->self;
960 /* note that some application (MetaStock) expects the second item
961 * to be in the listbox */
962 cis.itemID1 = index;
963 cis.itemData1 = get_item_data(descr, index);
964 cis.itemID2 = -1;
965 cis.itemData2 = (ULONG_PTR)str;
966 cis.dwLocaleId = descr->locale;
967 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
969 if (!res) return index;
970 if (res > 0) max = index - 1;
971 else min = index + 1;
973 return exact ? -1 : min;
977 /***********************************************************************
978 * LISTBOX_FindFileStrPos
980 * Find the nearest string located before a given string in directory
981 * sort order (i.e. first files, then directories, then drives).
983 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
985 INT min, max, res;
987 if (!HAS_STRINGS(descr))
988 return LISTBOX_FindStringPos( descr, str, FALSE );
989 min = 0;
990 max = descr->nb_items;
991 while (min != max)
993 INT index = (min + max) / 2;
994 LPCWSTR p = get_item_string(descr, index);
995 if (*p == '[') /* drive or directory */
997 if (*str != '[') res = -1;
998 else if (p[1] == '-') /* drive */
1000 if (str[1] == '-') res = str[2] - p[2];
1001 else res = -1;
1003 else /* directory */
1005 if (str[1] == '-') res = 1;
1006 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
1009 else /* filename */
1011 if (*str == '[') res = 1;
1012 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
1014 if (!res) return index;
1015 if (res < 0) max = index;
1016 else min = index + 1;
1018 return max;
1022 /***********************************************************************
1023 * LISTBOX_FindString
1025 * Find the item beginning with a given string.
1027 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
1029 INT i, index;
1031 if (descr->style & LBS_NODATA)
1033 SetLastError(ERROR_INVALID_PARAMETER);
1034 return LB_ERR;
1037 start++;
1038 if (start >= descr->nb_items) start = 0;
1039 if (HAS_STRINGS(descr))
1041 if (!str || ! str[0] ) return LB_ERR;
1042 if (exact)
1044 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1046 if (index == descr->nb_items) index = 0;
1047 if (!LISTBOX_lstrcmpiW(descr->locale, str, get_item_string(descr, index)))
1048 return index;
1051 else
1053 /* Special case for drives and directories: ignore prefix */
1054 INT len = lstrlenW(str);
1055 WCHAR *item_str;
1057 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1059 if (index == descr->nb_items) index = 0;
1060 item_str = get_item_string(descr, index);
1062 if (!wcsnicmp(str, item_str, len)) return index;
1063 if (item_str[0] == '[')
1065 if (!wcsnicmp(str, item_str + 1, len)) return index;
1066 if (item_str[1] == '-' && !wcsnicmp(str, item_str + 2, len)) return index;
1071 else
1073 if (exact && (descr->style & LBS_SORT))
1074 /* If sorted, use a WM_COMPAREITEM binary search */
1075 return LISTBOX_FindStringPos( descr, str, TRUE );
1077 /* Otherwise use a linear search */
1078 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1080 if (index == descr->nb_items) index = 0;
1081 if (get_item_data(descr, index) == (ULONG_PTR)str) return index;
1084 return LB_ERR;
1088 /***********************************************************************
1089 * LISTBOX_GetSelCount
1091 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1093 INT i, count;
1095 if (!(descr->style & LBS_MULTIPLESEL) ||
1096 (descr->style & LBS_NOSEL))
1097 return LB_ERR;
1098 for (i = count = 0; i < descr->nb_items; i++)
1099 if (is_item_selected(descr, i)) count++;
1100 return count;
1104 /***********************************************************************
1105 * LISTBOX_GetSelItems
1107 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1109 INT i, count;
1111 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1112 for (i = count = 0; (i < descr->nb_items) && (count < max); i++)
1113 if (is_item_selected(descr, i)) array[count++] = i;
1114 return count;
1118 /***********************************************************************
1119 * LISTBOX_Paint
1121 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1123 INT i, col_pos = descr->page_size - 1;
1124 RECT rect;
1125 RECT focusRect = {-1, -1, -1, -1};
1126 HFONT oldFont = 0;
1127 HBRUSH hbrush, oldBrush = 0;
1129 if (descr->style & LBS_NOREDRAW) return 0;
1131 SetRect( &rect, 0, 0, descr->width, descr->height );
1132 if (descr->style & LBS_MULTICOLUMN)
1133 rect.right = rect.left + descr->column_width;
1134 else if (descr->horz_pos)
1136 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1137 rect.right += descr->horz_pos;
1140 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1141 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1142 (WPARAM)hdc, (LPARAM)descr->self );
1143 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1144 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1146 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1147 (descr->in_focus))
1149 /* Special case for empty listbox: paint focus rect */
1150 rect.bottom = rect.top + descr->item_height;
1151 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1152 &rect, NULL, 0, NULL );
1153 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1154 rect.top = rect.bottom;
1157 /* Paint all the item, regarding the selection
1158 Focus state will be painted after */
1160 for (i = descr->top_item; i < descr->nb_items; i++)
1162 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1163 rect.bottom = rect.top + descr->item_height;
1164 else
1165 rect.bottom = rect.top + get_item_height(descr, i);
1167 /* keep the focus rect, to paint the focus item after */
1168 if (i == descr->focus_item)
1169 focusRect = rect;
1171 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1172 rect.top = rect.bottom;
1174 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1176 if (!IS_OWNERDRAW(descr))
1178 /* Clear the bottom of the column */
1179 if (rect.top < descr->height)
1181 rect.bottom = descr->height;
1182 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1183 &rect, NULL, 0, NULL );
1187 /* Go to the next column */
1188 rect.left += descr->column_width;
1189 rect.right += descr->column_width;
1190 rect.top = 0;
1191 col_pos = descr->page_size - 1;
1192 if (rect.left >= descr->width) break;
1194 else
1196 col_pos--;
1197 if (rect.top >= descr->height) break;
1201 /* Paint the focus item now */
1202 if (focusRect.top != focusRect.bottom &&
1203 descr->caret_on && descr->in_focus)
1204 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1206 if (!IS_OWNERDRAW(descr))
1208 /* Clear the remainder of the client area */
1209 if (rect.top < descr->height)
1211 rect.bottom = descr->height;
1212 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1213 &rect, NULL, 0, NULL );
1215 if (rect.right < descr->width)
1217 rect.left = rect.right;
1218 rect.right = descr->width;
1219 rect.top = 0;
1220 rect.bottom = descr->height;
1221 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1222 &rect, NULL, 0, NULL );
1225 if (oldFont) SelectObject( hdc, oldFont );
1226 if (oldBrush) SelectObject( hdc, oldBrush );
1227 return 0;
1231 /***********************************************************************
1232 * LISTBOX_InvalidateItems
1234 * Invalidate all items from a given item. If the specified item is not
1235 * visible, nothing happens.
1237 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1239 RECT rect;
1241 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1243 if (descr->style & LBS_NOREDRAW)
1245 descr->style |= LBS_DISPLAYCHANGED;
1246 return;
1248 rect.bottom = descr->height;
1249 InvalidateRect( descr->self, &rect, TRUE );
1250 if (descr->style & LBS_MULTICOLUMN)
1252 /* Repaint the other columns */
1253 rect.left = rect.right;
1254 rect.right = descr->width;
1255 rect.top = 0;
1256 InvalidateRect( descr->self, &rect, TRUE );
1261 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1263 RECT rect;
1265 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1266 InvalidateRect( descr->self, &rect, TRUE );
1269 /***********************************************************************
1270 * LISTBOX_GetItemHeight
1272 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1274 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1276 if ((index < 0) || (index >= descr->nb_items))
1278 SetLastError(ERROR_INVALID_INDEX);
1279 return LB_ERR;
1281 return get_item_height(descr, index);
1283 else return descr->item_height;
1287 /***********************************************************************
1288 * LISTBOX_SetItemHeight
1290 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1292 if (height > MAXBYTE)
1293 return -1;
1295 if (!height) height = 1;
1297 if (descr->style & LBS_OWNERDRAWVARIABLE)
1299 if ((index < 0) || (index >= descr->nb_items))
1301 SetLastError(ERROR_INVALID_INDEX);
1302 return LB_ERR;
1304 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1305 set_item_height(descr, index, height);
1306 LISTBOX_UpdateScroll( descr );
1307 if (repaint)
1308 LISTBOX_InvalidateItems( descr, index );
1310 else if (height != descr->item_height)
1312 TRACE("[%p]: new height = %d\n", descr->self, height );
1313 descr->item_height = height;
1314 LISTBOX_UpdatePage( descr );
1315 LISTBOX_UpdateScroll( descr );
1316 if (repaint)
1317 InvalidateRect( descr->self, 0, TRUE );
1319 return LB_OKAY;
1323 /***********************************************************************
1324 * LISTBOX_SetHorizontalPos
1326 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1328 INT diff;
1330 if (pos > descr->horz_extent - descr->width)
1331 pos = descr->horz_extent - descr->width;
1332 if (pos < 0) pos = 0;
1333 if (!(diff = descr->horz_pos - pos)) return;
1334 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1335 descr->horz_pos = pos;
1336 LISTBOX_UpdateScroll( descr );
1337 if (abs(diff) < descr->width)
1339 RECT rect;
1340 /* Invalidate the focused item so it will be repainted correctly */
1341 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1342 InvalidateRect( descr->self, &rect, TRUE );
1343 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1344 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1346 else
1347 InvalidateRect( descr->self, NULL, TRUE );
1351 /***********************************************************************
1352 * LISTBOX_SetHorizontalExtent
1354 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1356 if (descr->style & LBS_MULTICOLUMN)
1357 return LB_OKAY;
1358 if (extent == descr->horz_extent) return LB_OKAY;
1359 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1360 descr->horz_extent = extent;
1361 if (descr->style & WS_HSCROLL) {
1362 SCROLLINFO info;
1363 info.cbSize = sizeof(info);
1364 info.nMin = 0;
1365 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1366 info.fMask = SIF_RANGE;
1367 if (descr->style & LBS_DISABLENOSCROLL)
1368 info.fMask |= SIF_DISABLENOSCROLL;
1369 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1371 if (descr->horz_pos > extent - descr->width)
1372 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1373 return LB_OKAY;
1377 /***********************************************************************
1378 * LISTBOX_SetColumnWidth
1380 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1382 RECT rect;
1384 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1386 GetClientRect(descr->self, &rect);
1387 descr->width = rect.right - rect.left;
1388 descr->height = rect.bottom - rect.top;
1389 descr->column_width = column_width;
1391 LISTBOX_UpdatePage(descr);
1392 LISTBOX_UpdateScroll(descr);
1393 return LB_OKAY;
1397 /***********************************************************************
1398 * LISTBOX_SetFont
1400 * Returns the item height.
1402 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1404 HDC hdc;
1405 HFONT oldFont = 0;
1406 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1407 SIZE sz;
1409 descr->font = font;
1411 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1413 ERR("unable to get DC.\n" );
1414 return 16;
1416 if (font) oldFont = SelectObject( hdc, font );
1417 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1418 if (oldFont) SelectObject( hdc, oldFont );
1419 ReleaseDC( descr->self, hdc );
1421 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1422 if (!IS_OWNERDRAW(descr))
1423 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1424 return sz.cy;
1428 /***********************************************************************
1429 * LISTBOX_MakeItemVisible
1431 * Make sure that a given item is partially or fully visible.
1433 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1435 INT top;
1437 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1439 if (index <= descr->top_item) top = index;
1440 else if (descr->style & LBS_MULTICOLUMN)
1442 INT cols = descr->width;
1443 if (!fully) cols += descr->column_width - 1;
1444 if (cols >= descr->column_width) cols /= descr->column_width;
1445 else cols = 1;
1446 if (index < descr->top_item + (descr->page_size * cols)) return;
1447 top = index - descr->page_size * (cols - 1);
1449 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1451 INT height = fully ? get_item_height(descr, index) : 1;
1452 for (top = index; top > descr->top_item; top--)
1453 if ((height += get_item_height(descr, top - 1)) > descr->height) break;
1455 else
1457 if (index < descr->top_item + descr->page_size) return;
1458 if (!fully && (index == descr->top_item + descr->page_size) &&
1459 (descr->height > (descr->page_size * descr->item_height))) return;
1460 top = index - descr->page_size + 1;
1462 LISTBOX_SetTopItem( descr, top, TRUE );
1465 /***********************************************************************
1466 * LISTBOX_SetCaretIndex
1468 * NOTES
1469 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1472 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1474 BOOL focus_changed = descr->focus_item != index;
1476 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1478 if (descr->style & LBS_NOSEL) return LB_ERR;
1479 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1481 if (focus_changed)
1483 LISTBOX_DrawFocusRect( descr, FALSE );
1484 descr->focus_item = index;
1487 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1489 if (focus_changed)
1490 LISTBOX_DrawFocusRect( descr, TRUE );
1492 return LB_OKAY;
1496 /***********************************************************************
1497 * LISTBOX_SelectItemRange
1499 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1501 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1502 INT last, BOOL on )
1504 INT i;
1506 /* A few sanity checks */
1508 if (descr->style & LBS_NOSEL) return LB_ERR;
1509 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1511 if (!descr->nb_items) return LB_OKAY;
1513 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1514 if (first < 0) first = 0;
1515 if (last < first) return LB_OKAY;
1517 if (on) /* Turn selection on */
1519 for (i = first; i <= last; i++)
1521 if (is_item_selected(descr, i)) continue;
1522 set_item_selected_state(descr, i, TRUE);
1523 LISTBOX_InvalidateItemRect(descr, i);
1526 else /* Turn selection off */
1528 for (i = first; i <= last; i++)
1530 if (!is_item_selected(descr, i)) continue;
1531 set_item_selected_state(descr, i, FALSE);
1532 LISTBOX_InvalidateItemRect(descr, i);
1535 return LB_OKAY;
1538 /***********************************************************************
1539 * LISTBOX_SetSelection
1541 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1542 BOOL on, BOOL send_notify )
1544 TRACE( "cur_sel=%d index=%d notify=%s\n",
1545 descr->selected_item, index, send_notify ? "YES" : "NO" );
1547 if (descr->style & LBS_NOSEL)
1549 descr->selected_item = index;
1550 return LB_ERR;
1552 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1553 if (descr->style & LBS_MULTIPLESEL)
1555 if (index == -1) /* Select all items */
1556 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1557 else /* Only one item */
1558 return LISTBOX_SelectItemRange( descr, index, index, on );
1560 else
1562 INT oldsel = descr->selected_item;
1563 if (index == oldsel) return LB_OKAY;
1564 if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
1565 if (index != -1) set_item_selected_state(descr, index, TRUE);
1566 descr->selected_item = index;
1567 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1568 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1569 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1570 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1571 else
1572 if( descr->lphc ) /* set selection change flag for parent combo */
1573 descr->lphc->wState |= CBF_SELCHANGE;
1575 return LB_OKAY;
1579 /***********************************************************************
1580 * LISTBOX_MoveCaret
1582 * Change the caret position and extend the selection to the new caret.
1584 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1586 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1588 if ((index < 0) || (index >= descr->nb_items))
1589 return;
1591 /* Important, repaint needs to be done in this order if
1592 you want to mimic Windows behavior:
1593 1. Remove the focus and paint the item
1594 2. Remove the selection and paint the item(s)
1595 3. Set the selection and repaint the item(s)
1596 4. Set the focus to 'index' and repaint the item */
1598 /* 1. remove the focus and repaint the item */
1599 LISTBOX_DrawFocusRect( descr, FALSE );
1601 /* 2. then turn off the previous selection */
1602 /* 3. repaint the new selected item */
1603 if (descr->style & LBS_EXTENDEDSEL)
1605 if (descr->anchor_item != -1)
1607 INT first = min( index, descr->anchor_item );
1608 INT last = max( index, descr->anchor_item );
1609 if (first > 0)
1610 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1611 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1612 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1615 else if (!(descr->style & LBS_MULTIPLESEL))
1617 /* Set selection to new caret item */
1618 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1621 /* 4. repaint the new item with the focus */
1622 descr->focus_item = index;
1623 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1624 LISTBOX_DrawFocusRect( descr, TRUE );
1628 /***********************************************************************
1629 * LISTBOX_InsertItem
1631 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1632 LPWSTR str, ULONG_PTR data )
1634 INT oldfocus = descr->focus_item;
1636 if (index == -1) index = descr->nb_items;
1637 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1638 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1640 insert_item_data(descr, index);
1641 descr->nb_items++;
1642 set_item_string(descr, index, str);
1643 set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
1644 set_item_height(descr, index, 0);
1645 set_item_selected_state(descr, index, FALSE);
1647 /* Get item height */
1649 if (descr->style & LBS_OWNERDRAWVARIABLE)
1651 MEASUREITEMSTRUCT mis;
1652 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1654 mis.CtlType = ODT_LISTBOX;
1655 mis.CtlID = id;
1656 mis.itemID = index;
1657 mis.itemData = str ? (ULONG_PTR)str : data;
1658 mis.itemHeight = descr->item_height;
1659 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1660 set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
1661 TRACE("[%p]: measure item %d (%s) = %d\n",
1662 descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
1665 /* Repaint the items */
1667 LISTBOX_UpdateScroll( descr );
1668 LISTBOX_InvalidateItems( descr, index );
1670 /* Move selection and focused item */
1671 /* If listbox was empty, set focus to the first item */
1672 if (descr->nb_items == 1)
1673 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1674 /* single select don't change selection index in win31 */
1675 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1677 descr->selected_item++;
1678 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1680 else
1682 if (index <= descr->selected_item)
1684 descr->selected_item++;
1685 descr->focus_item = oldfocus; /* focus not changed */
1688 return LB_OKAY;
1692 /***********************************************************************
1693 * LISTBOX_InsertString
1695 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1697 LPWSTR new_str = NULL;
1698 LRESULT ret;
1700 if (HAS_STRINGS(descr))
1702 if (!str) str = L"";
1703 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str) + 1) * sizeof(WCHAR) )))
1705 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1706 return LB_ERRSPACE;
1708 lstrcpyW(new_str, str);
1711 if (index == -1) index = descr->nb_items;
1712 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1714 HeapFree( GetProcessHeap(), 0, new_str );
1715 return ret;
1718 TRACE("[%p]: added item %d %s\n",
1719 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1720 return index;
1724 /***********************************************************************
1725 * LISTBOX_DeleteItem
1727 * Delete the content of an item. 'index' must be a valid index.
1729 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1731 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1732 * while Win95 sends it for all items with user data.
1733 * It's probably better to send it too often than not
1734 * often enough, so this is what we do here.
1736 if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
1738 DELETEITEMSTRUCT dis;
1739 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1741 dis.CtlType = ODT_LISTBOX;
1742 dis.CtlID = id;
1743 dis.itemID = index;
1744 dis.hwndItem = descr->self;
1745 dis.itemData = get_item_data(descr, index);
1746 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1748 HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
1752 /***********************************************************************
1753 * LISTBOX_RemoveItem
1755 * Remove an item from the listbox and delete its content.
1757 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1759 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1761 /* We need to invalidate the original rect instead of the updated one. */
1762 LISTBOX_InvalidateItems( descr, index );
1764 if (descr->nb_items == 1)
1766 SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1767 return LB_OKAY;
1769 descr->nb_items--;
1770 LISTBOX_DeleteItem( descr, index );
1771 remove_item_data(descr, index);
1773 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1774 resize_storage(descr, descr->nb_items);
1776 /* Repaint the items */
1778 LISTBOX_UpdateScroll( descr );
1779 /* if we removed the scrollbar, reset the top of the list
1780 (correct for owner-drawn ???) */
1781 if (descr->nb_items == descr->page_size)
1782 LISTBOX_SetTopItem( descr, 0, TRUE );
1784 /* Move selection and focused item */
1785 if (!IS_MULTISELECT(descr))
1787 if (index == descr->selected_item)
1788 descr->selected_item = -1;
1789 else if (index < descr->selected_item)
1791 descr->selected_item--;
1792 if (ISWIN31) /* win 31 do not change the selected item number */
1793 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1797 if (descr->focus_item >= descr->nb_items)
1799 descr->focus_item = descr->nb_items - 1;
1800 if (descr->focus_item < 0) descr->focus_item = 0;
1802 return LB_OKAY;
1806 /***********************************************************************
1807 * LISTBOX_ResetContent
1809 static void LISTBOX_ResetContent( LB_DESCR *descr )
1811 INT i;
1813 if (!(descr->style & LBS_NODATA))
1814 for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
1815 HeapFree( GetProcessHeap(), 0, descr->u.items );
1816 descr->nb_items = 0;
1817 descr->top_item = 0;
1818 descr->selected_item = -1;
1819 descr->focus_item = 0;
1820 descr->anchor_item = -1;
1821 descr->items_size = 0;
1822 descr->u.items = NULL;
1826 /***********************************************************************
1827 * LISTBOX_SetCount
1829 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1831 UINT orig_num = descr->nb_items;
1833 if (!(descr->style & LBS_NODATA))
1835 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1836 return LB_ERR;
1839 if (!resize_storage(descr, count))
1840 return LB_ERRSPACE;
1841 descr->nb_items = count;
1842 if (descr->style & LBS_NOREDRAW)
1843 descr->style |= LBS_DISPLAYCHANGED;
1845 if (count)
1847 LISTBOX_UpdateScroll(descr);
1848 if (count < orig_num)
1850 descr->anchor_item = min(descr->anchor_item, count - 1);
1851 if (descr->selected_item >= count)
1852 descr->selected_item = -1;
1854 /* If we removed the scrollbar, reset the top of the list */
1855 if (count <= descr->page_size && orig_num > descr->page_size)
1856 LISTBOX_SetTopItem(descr, 0, TRUE);
1858 descr->focus_item = min(descr->focus_item, count - 1);
1861 /* If it was empty before growing, set focus to the first item */
1862 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1864 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1866 InvalidateRect( descr->self, NULL, TRUE );
1867 return LB_OKAY;
1871 /***********************************************************************
1872 * LISTBOX_Directory
1874 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1875 LPCWSTR filespec, BOOL long_names )
1877 HANDLE handle;
1878 LRESULT ret = LB_OKAY;
1879 WIN32_FIND_DATAW entry;
1880 int pos;
1881 LRESULT maxinsert = LB_ERR;
1883 /* don't scan directory if we just want drives exclusively */
1884 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1885 /* scan directory */
1886 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1888 int le = GetLastError();
1889 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1891 else
1895 WCHAR buffer[270];
1896 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1898 if (!(attrib & DDL_DIRECTORY) ||
1899 !wcscmp( entry.cFileName, L"." )) continue;
1900 buffer[0] = '[';
1901 if (!long_names && entry.cAlternateFileName[0])
1902 lstrcpyW( buffer + 1, entry.cAlternateFileName );
1903 else
1904 lstrcpyW( buffer + 1, entry.cFileName );
1905 lstrcatW(buffer, L"]");
1907 else /* not a directory */
1909 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1910 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1912 if ((attrib & DDL_EXCLUSIVE) &&
1913 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1914 continue;
1915 #undef ATTRIBS
1916 if (!long_names && entry.cAlternateFileName[0])
1917 lstrcpyW( buffer, entry.cAlternateFileName );
1918 else
1919 lstrcpyW( buffer, entry.cFileName );
1921 if (!long_names) CharLowerW( buffer );
1922 pos = LISTBOX_FindFileStrPos( descr, buffer );
1923 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1924 break;
1925 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1926 } while (FindNextFileW( handle, &entry ));
1927 FindClose( handle );
1930 if (ret >= 0)
1932 ret = maxinsert;
1934 /* scan drives */
1935 if (attrib & DDL_DRIVES)
1937 WCHAR buffer[] = L"[-a-]";
1938 WCHAR root[] = L"A:\\";
1939 int drive;
1940 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1942 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1943 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1944 break;
1948 return ret;
1952 /***********************************************************************
1953 * LISTBOX_HandleVScroll
1955 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1957 SCROLLINFO info;
1959 if (descr->style & LBS_MULTICOLUMN) return 0;
1960 switch(scrollReq)
1962 case SB_LINEUP:
1963 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1964 break;
1965 case SB_LINEDOWN:
1966 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1967 break;
1968 case SB_PAGEUP:
1969 LISTBOX_SetTopItem( descr, descr->top_item -
1970 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1971 break;
1972 case SB_PAGEDOWN:
1973 LISTBOX_SetTopItem( descr, descr->top_item +
1974 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1975 break;
1976 case SB_THUMBPOSITION:
1977 LISTBOX_SetTopItem( descr, pos, TRUE );
1978 break;
1979 case SB_THUMBTRACK:
1980 info.cbSize = sizeof(info);
1981 info.fMask = SIF_TRACKPOS;
1982 GetScrollInfo( descr->self, SB_VERT, &info );
1983 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1984 break;
1985 case SB_TOP:
1986 LISTBOX_SetTopItem( descr, 0, TRUE );
1987 break;
1988 case SB_BOTTOM:
1989 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1990 break;
1992 return 0;
1996 /***********************************************************************
1997 * LISTBOX_HandleHScroll
1999 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
2001 SCROLLINFO info;
2002 INT page;
2004 if (descr->style & LBS_MULTICOLUMN)
2006 switch(scrollReq)
2008 case SB_LINELEFT:
2009 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
2010 TRUE );
2011 break;
2012 case SB_LINERIGHT:
2013 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
2014 TRUE );
2015 break;
2016 case SB_PAGELEFT:
2017 page = descr->width / descr->column_width;
2018 if (page < 1) page = 1;
2019 LISTBOX_SetTopItem( descr,
2020 descr->top_item - page * descr->page_size, TRUE );
2021 break;
2022 case SB_PAGERIGHT:
2023 page = descr->width / descr->column_width;
2024 if (page < 1) page = 1;
2025 LISTBOX_SetTopItem( descr,
2026 descr->top_item + page * descr->page_size, TRUE );
2027 break;
2028 case SB_THUMBPOSITION:
2029 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
2030 break;
2031 case SB_THUMBTRACK:
2032 info.cbSize = sizeof(info);
2033 info.fMask = SIF_TRACKPOS;
2034 GetScrollInfo( descr->self, SB_VERT, &info );
2035 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
2036 TRUE );
2037 break;
2038 case SB_LEFT:
2039 LISTBOX_SetTopItem( descr, 0, TRUE );
2040 break;
2041 case SB_RIGHT:
2042 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2043 break;
2046 else if (descr->horz_extent)
2048 switch(scrollReq)
2050 case SB_LINELEFT:
2051 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2052 break;
2053 case SB_LINERIGHT:
2054 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2055 break;
2056 case SB_PAGELEFT:
2057 LISTBOX_SetHorizontalPos( descr,
2058 descr->horz_pos - descr->width );
2059 break;
2060 case SB_PAGERIGHT:
2061 LISTBOX_SetHorizontalPos( descr,
2062 descr->horz_pos + descr->width );
2063 break;
2064 case SB_THUMBPOSITION:
2065 LISTBOX_SetHorizontalPos( descr, pos );
2066 break;
2067 case SB_THUMBTRACK:
2068 info.cbSize = sizeof(info);
2069 info.fMask = SIF_TRACKPOS;
2070 GetScrollInfo( descr->self, SB_HORZ, &info );
2071 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2072 break;
2073 case SB_LEFT:
2074 LISTBOX_SetHorizontalPos( descr, 0 );
2075 break;
2076 case SB_RIGHT:
2077 LISTBOX_SetHorizontalPos( descr,
2078 descr->horz_extent - descr->width );
2079 break;
2082 return 0;
2085 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2087 INT pulScrollLines = 3;
2089 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2091 /* if scrolling changes direction, ignore left overs */
2092 if ((delta < 0 && descr->wheel_remain < 0) ||
2093 (delta > 0 && descr->wheel_remain > 0))
2094 descr->wheel_remain += delta;
2095 else
2096 descr->wheel_remain = delta;
2098 if (descr->wheel_remain && pulScrollLines)
2100 int cLineScroll;
2101 if (descr->style & LBS_MULTICOLUMN)
2103 pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
2104 pulScrollLines = max(1, pulScrollLines);
2105 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2106 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2107 cLineScroll *= descr->page_size;
2109 else
2111 pulScrollLines = min(descr->page_size, pulScrollLines);
2112 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2113 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2115 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2117 return 0;
2120 /***********************************************************************
2121 * LISTBOX_HandleLButtonDown
2123 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2125 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2127 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2128 descr->self, x, y, index, descr->focus_item);
2130 if (!descr->caret_on && (descr->in_focus)) return 0;
2132 if (!descr->in_focus)
2134 if( !descr->lphc ) SetFocus( descr->self );
2135 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2138 if (index == -1) return 0;
2140 if (!descr->lphc)
2142 if (descr->style & LBS_NOTIFY )
2143 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2144 MAKELPARAM( x, y ) );
2147 descr->captured = TRUE;
2148 SetCapture( descr->self );
2150 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2152 /* we should perhaps make sure that all items are deselected
2153 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2154 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2155 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2158 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2159 if (keys & MK_CONTROL)
2161 LISTBOX_SetCaretIndex( descr, index, FALSE );
2162 LISTBOX_SetSelection( descr, index,
2163 !is_item_selected(descr, index),
2164 (descr->style & LBS_NOTIFY) != 0);
2166 else
2168 LISTBOX_MoveCaret( descr, index, FALSE );
2170 if (descr->style & LBS_EXTENDEDSEL)
2172 LISTBOX_SetSelection( descr, index,
2173 is_item_selected(descr, index),
2174 (descr->style & LBS_NOTIFY) != 0 );
2176 else
2178 LISTBOX_SetSelection( descr, index,
2179 !is_item_selected(descr, index),
2180 (descr->style & LBS_NOTIFY) != 0 );
2184 else
2186 descr->anchor_item = index;
2187 LISTBOX_MoveCaret( descr, index, FALSE );
2188 LISTBOX_SetSelection( descr, index,
2189 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2192 if (!descr->lphc)
2194 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2196 POINT pt;
2198 pt.x = x;
2199 pt.y = y;
2201 if (DragDetect( descr->self, pt ))
2202 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2205 return 0;
2209 /*************************************************************************
2210 * LISTBOX_HandleLButtonDownCombo [Internal]
2212 * Process LButtonDown message for the ComboListBox
2214 * PARAMS
2215 * pWnd [I] The windows internal structure
2216 * pDescr [I] The ListBox internal structure
2217 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2218 * x [I] X Mouse Coordinate
2219 * y [I] Y Mouse Coordinate
2221 * RETURNS
2222 * 0 since we are processing the WM_LBUTTONDOWN Message
2224 * NOTES
2225 * This function is only to be used when a ListBox is a ComboListBox
2228 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2230 RECT clientRect, screenRect;
2231 POINT mousePos;
2233 mousePos.x = x;
2234 mousePos.y = y;
2236 GetClientRect(descr->self, &clientRect);
2238 if(PtInRect(&clientRect, mousePos))
2240 /* MousePos is in client, resume normal processing */
2241 if (msg == WM_LBUTTONDOWN)
2243 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2244 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2246 else if (descr->style & LBS_NOTIFY)
2247 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2249 else
2251 POINT screenMousePos;
2252 HWND hWndOldCapture;
2254 /* Check the Non-Client Area */
2255 screenMousePos = mousePos;
2256 hWndOldCapture = GetCapture();
2257 ReleaseCapture();
2258 GetWindowRect(descr->self, &screenRect);
2259 ClientToScreen(descr->self, &screenMousePos);
2261 if(!PtInRect(&screenRect, screenMousePos))
2263 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2264 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2265 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2267 else
2269 /* Check to see the NC is a scrollbar */
2270 INT nHitTestType=0;
2271 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2272 /* Check Vertical scroll bar */
2273 if (style & WS_VSCROLL)
2275 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2276 if (PtInRect( &clientRect, mousePos ))
2277 nHitTestType = HTVSCROLL;
2279 /* Check horizontal scroll bar */
2280 if (style & WS_HSCROLL)
2282 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2283 if (PtInRect( &clientRect, mousePos ))
2284 nHitTestType = HTHSCROLL;
2286 /* Windows sends this message when a scrollbar is clicked
2289 if(nHitTestType != 0)
2291 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2292 MAKELONG(screenMousePos.x, screenMousePos.y));
2294 /* Resume the Capture after scrolling is complete
2296 if(hWndOldCapture != 0)
2297 SetCapture(hWndOldCapture);
2300 return 0;
2303 /***********************************************************************
2304 * LISTBOX_HandleLButtonUp
2306 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2308 if (LISTBOX_Timer != LB_TIMER_NONE)
2309 KillSystemTimer( descr->self, LB_TIMER_ID );
2310 LISTBOX_Timer = LB_TIMER_NONE;
2311 if (descr->captured)
2313 descr->captured = FALSE;
2314 if (GetCapture() == descr->self) ReleaseCapture();
2315 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2316 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2318 return 0;
2322 /***********************************************************************
2323 * LISTBOX_HandleTimer
2325 * Handle scrolling upon a timer event.
2326 * Return TRUE if scrolling should continue.
2328 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2330 switch(dir)
2332 case LB_TIMER_UP:
2333 if (descr->top_item) index = descr->top_item - 1;
2334 else index = 0;
2335 break;
2336 case LB_TIMER_LEFT:
2337 if (descr->top_item) index -= descr->page_size;
2338 break;
2339 case LB_TIMER_DOWN:
2340 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2341 if (index == descr->focus_item) index++;
2342 if (index >= descr->nb_items) index = descr->nb_items - 1;
2343 break;
2344 case LB_TIMER_RIGHT:
2345 if (index + descr->page_size < descr->nb_items)
2346 index += descr->page_size;
2347 break;
2348 case LB_TIMER_NONE:
2349 break;
2351 if (index == descr->focus_item) return FALSE;
2352 LISTBOX_MoveCaret( descr, index, FALSE );
2353 return TRUE;
2357 /***********************************************************************
2358 * LISTBOX_HandleSystemTimer
2360 * WM_SYSTIMER handler.
2362 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2364 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2366 KillSystemTimer( descr->self, LB_TIMER_ID );
2367 LISTBOX_Timer = LB_TIMER_NONE;
2369 return 0;
2373 /***********************************************************************
2374 * LISTBOX_HandleMouseMove
2376 * WM_MOUSEMOVE handler.
2378 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2379 INT x, INT y )
2381 INT index;
2382 TIMER_DIRECTION dir = LB_TIMER_NONE;
2384 if (!descr->captured) return;
2386 if (descr->style & LBS_MULTICOLUMN)
2388 if (y < 0) y = 0;
2389 else if (y >= descr->item_height * descr->page_size)
2390 y = descr->item_height * descr->page_size - 1;
2392 if (x < 0)
2394 dir = LB_TIMER_LEFT;
2395 x = 0;
2397 else if (x >= descr->width)
2399 dir = LB_TIMER_RIGHT;
2400 x = descr->width - 1;
2403 else
2405 if (y < 0) dir = LB_TIMER_UP; /* above */
2406 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2409 index = LISTBOX_GetItemFromPoint( descr, x, y );
2410 if (index == -1) index = descr->focus_item;
2411 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2413 /* Start/stop the system timer */
2415 if (dir != LB_TIMER_NONE)
2416 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2417 else if (LISTBOX_Timer != LB_TIMER_NONE)
2418 KillSystemTimer( descr->self, LB_TIMER_ID );
2419 LISTBOX_Timer = dir;
2423 /***********************************************************************
2424 * LISTBOX_HandleKeyDown
2426 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2428 INT caret = -1;
2429 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2430 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2431 bForceSelection = FALSE; /* only for single select list */
2433 if (descr->style & LBS_WANTKEYBOARDINPUT)
2435 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2436 MAKEWPARAM(LOWORD(key), descr->focus_item),
2437 (LPARAM)descr->self );
2438 if (caret == -2) return 0;
2440 if (caret == -1) switch(key)
2442 case VK_LEFT:
2443 if (descr->style & LBS_MULTICOLUMN)
2445 bForceSelection = FALSE;
2446 if (descr->focus_item >= descr->page_size)
2447 caret = descr->focus_item - descr->page_size;
2448 break;
2450 /* fall through */
2451 case VK_UP:
2452 caret = descr->focus_item - 1;
2453 if (caret < 0) caret = 0;
2454 break;
2455 case VK_RIGHT:
2456 if (descr->style & LBS_MULTICOLUMN)
2458 bForceSelection = FALSE;
2459 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2460 break;
2462 /* fall through */
2463 case VK_DOWN:
2464 caret = descr->focus_item + 1;
2465 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2466 break;
2468 case VK_PRIOR:
2469 if (descr->style & LBS_MULTICOLUMN)
2471 INT page = descr->width / descr->column_width;
2472 if (page < 1) page = 1;
2473 caret = descr->focus_item - (page * descr->page_size) + 1;
2475 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2476 if (caret < 0) caret = 0;
2477 break;
2478 case VK_NEXT:
2479 if (descr->style & LBS_MULTICOLUMN)
2481 INT page = descr->width / descr->column_width;
2482 if (page < 1) page = 1;
2483 caret = descr->focus_item + (page * descr->page_size) - 1;
2485 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2486 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2487 break;
2488 case VK_HOME:
2489 caret = 0;
2490 break;
2491 case VK_END:
2492 caret = descr->nb_items - 1;
2493 break;
2494 case VK_SPACE:
2495 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2496 else if (descr->style & LBS_MULTIPLESEL)
2498 LISTBOX_SetSelection( descr, descr->focus_item,
2499 !is_item_selected(descr, descr->focus_item),
2500 (descr->style & LBS_NOTIFY) != 0 );
2502 break;
2503 default:
2504 bForceSelection = FALSE;
2506 if (bForceSelection) /* focused item is used instead of key */
2507 caret = descr->focus_item;
2508 if (caret >= 0)
2510 if (((descr->style & LBS_EXTENDEDSEL) &&
2511 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2512 !IS_MULTISELECT(descr))
2513 descr->anchor_item = caret;
2514 LISTBOX_MoveCaret( descr, caret, TRUE );
2516 if (descr->style & LBS_MULTIPLESEL)
2517 descr->selected_item = caret;
2518 else
2519 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2520 if (descr->style & LBS_NOTIFY)
2522 if (descr->lphc && IsWindowVisible( descr->self ))
2524 /* make sure that combo parent doesn't hide us */
2525 descr->lphc->wState |= CBF_NOROLLUP;
2527 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2530 return 0;
2534 /***********************************************************************
2535 * LISTBOX_HandleChar
2537 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2539 INT caret = -1;
2540 WCHAR str[2];
2542 str[0] = charW;
2543 str[1] = '\0';
2545 if (descr->style & LBS_WANTKEYBOARDINPUT)
2547 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2548 MAKEWPARAM(charW, descr->focus_item),
2549 (LPARAM)descr->self );
2550 if (caret == -2) return 0;
2552 if (caret == -1 && !(descr->style & LBS_NODATA))
2553 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2554 if (caret != -1)
2556 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2557 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2558 LISTBOX_MoveCaret( descr, caret, TRUE );
2559 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2560 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2562 return 0;
2566 /***********************************************************************
2567 * LISTBOX_Create
2569 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2571 LB_DESCR *descr;
2572 MEASUREITEMSTRUCT mis;
2573 RECT rect;
2575 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2576 return FALSE;
2578 GetClientRect( hwnd, &rect );
2579 descr->self = hwnd;
2580 descr->owner = GetParent( descr->self );
2581 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2582 descr->width = rect.right - rect.left;
2583 descr->height = rect.bottom - rect.top;
2584 descr->u.items = NULL;
2585 descr->items_size = 0;
2586 descr->nb_items = 0;
2587 descr->top_item = 0;
2588 descr->selected_item = -1;
2589 descr->focus_item = 0;
2590 descr->anchor_item = -1;
2591 descr->item_height = 1;
2592 descr->page_size = 1;
2593 descr->column_width = 150;
2594 descr->horz_extent = 0;
2595 descr->horz_pos = 0;
2596 descr->nb_tabs = 0;
2597 descr->tabs = NULL;
2598 descr->wheel_remain = 0;
2599 descr->caret_on = !lphc;
2600 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2601 descr->in_focus = FALSE;
2602 descr->captured = FALSE;
2603 descr->font = 0;
2604 descr->locale = GetUserDefaultLCID();
2605 descr->lphc = lphc;
2607 if( lphc )
2609 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2610 descr->owner = lphc->self;
2613 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2615 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2617 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2618 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2619 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2620 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2621 descr->style &= ~LBS_NODATA;
2622 descr->item_height = LISTBOX_SetFont( descr, 0 );
2624 if (descr->style & LBS_OWNERDRAWFIXED)
2626 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2628 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2630 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2631 descr->item_height = lphc->fixedOwnerDrawHeight;
2633 else
2635 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2636 mis.CtlType = ODT_LISTBOX;
2637 mis.CtlID = id;
2638 mis.itemID = -1;
2639 mis.itemWidth = 0;
2640 mis.itemData = 0;
2641 mis.itemHeight = descr->item_height;
2642 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2643 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2647 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2648 return TRUE;
2652 /***********************************************************************
2653 * LISTBOX_Destroy
2655 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2657 LISTBOX_ResetContent( descr );
2658 SetWindowLongPtrW( descr->self, 0, 0 );
2659 HeapFree( GetProcessHeap(), 0, descr );
2660 return TRUE;
2664 /***********************************************************************
2665 * ListBoxWndProc_common
2667 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2669 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2670 HEADCOMBO *lphc = NULL;
2671 LRESULT ret;
2673 if (!descr)
2675 if (!IsWindow(hwnd)) return 0;
2677 if (msg == WM_CREATE)
2679 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2680 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2681 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2682 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2683 return 0;
2685 /* Ignore all other messages before we get a WM_CREATE */
2686 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2687 DefWindowProcA( hwnd, msg, wParam, lParam );
2689 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2691 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2692 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2694 switch(msg)
2696 case LB_RESETCONTENT:
2697 LISTBOX_ResetContent( descr );
2698 LISTBOX_UpdateScroll( descr );
2699 InvalidateRect( descr->self, NULL, TRUE );
2700 return 0;
2702 case LB_ADDSTRING:
2704 INT ret;
2705 LPWSTR textW;
2706 if(unicode || !HAS_STRINGS(descr))
2707 textW = (LPWSTR)lParam;
2708 else
2710 LPSTR textA = (LPSTR)lParam;
2711 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2712 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2713 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2714 else
2715 return LB_ERRSPACE;
2717 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2718 ret = LISTBOX_InsertString( descr, wParam, textW );
2719 if (!unicode && HAS_STRINGS(descr))
2720 HeapFree(GetProcessHeap(), 0, textW);
2721 return ret;
2724 case LB_INSERTSTRING:
2726 INT ret;
2727 LPWSTR textW;
2728 if(unicode || !HAS_STRINGS(descr))
2729 textW = (LPWSTR)lParam;
2730 else
2732 LPSTR textA = (LPSTR)lParam;
2733 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2734 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2735 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2736 else
2737 return LB_ERRSPACE;
2739 ret = LISTBOX_InsertString( descr, wParam, textW );
2740 if(!unicode && HAS_STRINGS(descr))
2741 HeapFree(GetProcessHeap(), 0, textW);
2742 return ret;
2745 case LB_ADDFILE:
2747 INT ret;
2748 LPWSTR textW;
2749 if(unicode || !HAS_STRINGS(descr))
2750 textW = (LPWSTR)lParam;
2751 else
2753 LPSTR textA = (LPSTR)lParam;
2754 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2755 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2756 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2757 else
2758 return LB_ERRSPACE;
2760 wParam = LISTBOX_FindFileStrPos( descr, textW );
2761 ret = LISTBOX_InsertString( descr, wParam, textW );
2762 if(!unicode && HAS_STRINGS(descr))
2763 HeapFree(GetProcessHeap(), 0, textW);
2764 return ret;
2767 case LB_DELETESTRING:
2768 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2769 return descr->nb_items;
2770 else
2772 SetLastError(ERROR_INVALID_INDEX);
2773 return LB_ERR;
2776 case LB_GETITEMDATA:
2777 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2779 SetLastError(ERROR_INVALID_INDEX);
2780 return LB_ERR;
2782 return get_item_data(descr, wParam);
2784 case LB_SETITEMDATA:
2785 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2787 SetLastError(ERROR_INVALID_INDEX);
2788 return LB_ERR;
2790 set_item_data(descr, wParam, lParam);
2791 /* undocumented: returns TRUE, not LB_OKAY (0) */
2792 return TRUE;
2794 case LB_GETCOUNT:
2795 return descr->nb_items;
2797 case LB_GETTEXT:
2798 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2800 case LB_GETTEXTLEN:
2801 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2803 SetLastError(ERROR_INVALID_INDEX);
2804 return LB_ERR;
2806 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2807 if (unicode) return lstrlenW(get_item_string(descr, wParam));
2808 return WideCharToMultiByte( CP_ACP, 0, get_item_string(descr, wParam),
2809 lstrlenW(get_item_string(descr, wParam)), NULL, 0, NULL, NULL );
2811 case LB_GETCURSEL:
2812 if (descr->nb_items == 0)
2813 return LB_ERR;
2814 if (!IS_MULTISELECT(descr))
2815 return descr->selected_item;
2816 if (descr->selected_item != -1)
2817 return descr->selected_item;
2818 return descr->focus_item;
2819 /* otherwise, if the user tries to move the selection with the */
2820 /* arrow keys, we will give the application something to choke on */
2821 case LB_GETTOPINDEX:
2822 return descr->top_item;
2824 case LB_GETITEMHEIGHT:
2825 return LISTBOX_GetItemHeight( descr, wParam );
2827 case LB_SETITEMHEIGHT:
2828 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2830 case LB_ITEMFROMPOINT:
2832 POINT pt;
2833 RECT rect;
2834 int index;
2835 BOOL hit = TRUE;
2837 /* The hiword of the return value is not a client area
2838 hittest as suggested by MSDN, but rather a hittest on
2839 the returned listbox item. */
2841 if(descr->nb_items == 0)
2842 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2844 pt.x = (short)LOWORD(lParam);
2845 pt.y = (short)HIWORD(lParam);
2847 SetRect(&rect, 0, 0, descr->width, descr->height);
2849 if(!PtInRect(&rect, pt))
2851 pt.x = min(pt.x, rect.right - 1);
2852 pt.x = max(pt.x, 0);
2853 pt.y = min(pt.y, rect.bottom - 1);
2854 pt.y = max(pt.y, 0);
2855 hit = FALSE;
2858 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2860 if(index == -1)
2862 index = descr->nb_items - 1;
2863 hit = FALSE;
2865 return MAKELONG(index, hit ? 0 : 1);
2868 case LB_SETCARETINDEX:
2869 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2870 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2871 return LB_ERR;
2872 else if (ISWIN31)
2873 return wParam;
2874 else
2875 return LB_OKAY;
2877 case LB_GETCARETINDEX:
2878 return descr->focus_item;
2880 case LB_SETTOPINDEX:
2881 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2883 case LB_SETCOLUMNWIDTH:
2884 return LISTBOX_SetColumnWidth( descr, wParam );
2886 case LB_GETITEMRECT:
2887 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2889 case LB_FINDSTRING:
2891 INT ret;
2892 LPWSTR textW;
2893 if(unicode || !HAS_STRINGS(descr))
2894 textW = (LPWSTR)lParam;
2895 else
2897 LPSTR textA = (LPSTR)lParam;
2898 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2899 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2900 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2902 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2903 if(!unicode && HAS_STRINGS(descr))
2904 HeapFree(GetProcessHeap(), 0, textW);
2905 return ret;
2908 case LB_FINDSTRINGEXACT:
2910 INT ret;
2911 LPWSTR textW;
2912 if(unicode || !HAS_STRINGS(descr))
2913 textW = (LPWSTR)lParam;
2914 else
2916 LPSTR textA = (LPSTR)lParam;
2917 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2918 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2919 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2921 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2922 if(!unicode && HAS_STRINGS(descr))
2923 HeapFree(GetProcessHeap(), 0, textW);
2924 return ret;
2927 case LB_SELECTSTRING:
2929 INT index;
2930 LPWSTR textW;
2932 if(HAS_STRINGS(descr))
2933 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2934 debugstr_a((LPSTR)lParam));
2935 if(unicode || !HAS_STRINGS(descr))
2936 textW = (LPWSTR)lParam;
2937 else
2939 LPSTR textA = (LPSTR)lParam;
2940 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2941 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2942 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2944 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2945 if(!unicode && HAS_STRINGS(descr))
2946 HeapFree(GetProcessHeap(), 0, textW);
2947 if (index != LB_ERR)
2949 LISTBOX_MoveCaret( descr, index, TRUE );
2950 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2952 return index;
2955 case LB_GETSEL:
2956 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2957 return LB_ERR;
2958 return is_item_selected(descr, wParam);
2960 case LB_SETSEL:
2961 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2962 if (ret != LB_ERR && wParam)
2964 descr->anchor_item = lParam;
2965 if (lParam != -1)
2966 LISTBOX_SetCaretIndex( descr, lParam, TRUE );
2968 return ret;
2970 case LB_SETCURSEL:
2971 if (IS_MULTISELECT(descr)) return LB_ERR;
2972 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2973 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2974 if (ret != LB_ERR) ret = descr->selected_item;
2975 return ret;
2977 case LB_GETSELCOUNT:
2978 return LISTBOX_GetSelCount( descr );
2980 case LB_GETSELITEMS:
2981 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2983 case LB_SELITEMRANGE:
2984 if (LOWORD(lParam) <= HIWORD(lParam))
2985 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2986 HIWORD(lParam), wParam );
2987 else
2988 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2989 LOWORD(lParam), wParam );
2991 case LB_SELITEMRANGEEX:
2992 if ((INT)lParam >= (INT)wParam)
2993 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2994 else
2995 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2997 case LB_GETHORIZONTALEXTENT:
2998 return descr->horz_extent;
3000 case LB_SETHORIZONTALEXTENT:
3001 return LISTBOX_SetHorizontalExtent( descr, wParam );
3003 case LB_GETANCHORINDEX:
3004 return descr->anchor_item;
3006 case LB_SETANCHORINDEX:
3007 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
3009 SetLastError(ERROR_INVALID_INDEX);
3010 return LB_ERR;
3012 descr->anchor_item = (INT)wParam;
3013 return LB_OKAY;
3015 case LB_DIR:
3017 INT ret;
3018 LPWSTR textW;
3019 if(unicode)
3020 textW = (LPWSTR)lParam;
3021 else
3023 LPSTR textA = (LPSTR)lParam;
3024 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3025 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3026 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3028 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3029 if(!unicode)
3030 HeapFree(GetProcessHeap(), 0, textW);
3031 return ret;
3034 case LB_GETLOCALE:
3035 return descr->locale;
3037 case LB_SETLOCALE:
3039 LCID ret;
3040 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3041 return LB_ERR;
3042 ret = descr->locale;
3043 descr->locale = (LCID)wParam;
3044 return ret;
3047 case LB_INITSTORAGE:
3048 return LISTBOX_InitStorage( descr, wParam );
3050 case LB_SETCOUNT:
3051 return LISTBOX_SetCount( descr, (INT)wParam );
3053 case LB_SETTABSTOPS:
3054 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
3056 case LB_CARETON:
3057 if (descr->caret_on)
3058 return LB_OKAY;
3059 descr->caret_on = TRUE;
3060 if ((descr->focus_item != -1) && (descr->in_focus))
3061 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3062 return LB_OKAY;
3064 case LB_CARETOFF:
3065 if (!descr->caret_on)
3066 return LB_OKAY;
3067 descr->caret_on = FALSE;
3068 if ((descr->focus_item != -1) && (descr->in_focus))
3069 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3070 return LB_OKAY;
3072 case LB_GETLISTBOXINFO:
3073 return descr->page_size;
3075 case WM_DESTROY:
3076 return LISTBOX_Destroy( descr );
3078 case WM_ENABLE:
3079 InvalidateRect( descr->self, NULL, TRUE );
3080 return 0;
3082 case WM_SETREDRAW:
3083 LISTBOX_SetRedraw( descr, wParam != 0 );
3084 return 0;
3086 case WM_GETDLGCODE:
3087 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3089 case WM_PRINTCLIENT:
3090 case WM_PAINT:
3092 PAINTSTRUCT ps;
3093 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3094 ret = LISTBOX_Paint( descr, hdc );
3095 if( !wParam ) EndPaint( descr->self, &ps );
3097 return ret;
3098 case WM_SIZE:
3099 LISTBOX_UpdateSize( descr );
3100 return 0;
3101 case WM_GETFONT:
3102 return (LRESULT)descr->font;
3103 case WM_SETFONT:
3104 LISTBOX_SetFont( descr, (HFONT)wParam );
3105 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3106 return 0;
3107 case WM_SETFOCUS:
3108 descr->in_focus = TRUE;
3109 descr->caret_on = TRUE;
3110 if (descr->focus_item != -1)
3111 LISTBOX_DrawFocusRect( descr, TRUE );
3112 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3113 return 0;
3114 case WM_KILLFOCUS:
3115 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3116 descr->in_focus = FALSE;
3117 descr->wheel_remain = 0;
3118 if ((descr->focus_item != -1) && descr->caret_on)
3119 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3120 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3121 return 0;
3122 case WM_HSCROLL:
3123 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3124 case WM_VSCROLL:
3125 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3126 case WM_MOUSEWHEEL:
3127 if (wParam & (MK_SHIFT | MK_CONTROL))
3128 return DefWindowProcW( descr->self, msg, wParam, lParam );
3129 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3130 case WM_LBUTTONDOWN:
3131 if (lphc)
3132 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3133 (INT16)LOWORD(lParam),
3134 (INT16)HIWORD(lParam) );
3135 return LISTBOX_HandleLButtonDown( descr, wParam,
3136 (INT16)LOWORD(lParam),
3137 (INT16)HIWORD(lParam) );
3138 case WM_LBUTTONDBLCLK:
3139 if (lphc)
3140 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3141 (INT16)LOWORD(lParam),
3142 (INT16)HIWORD(lParam) );
3143 if (descr->style & LBS_NOTIFY)
3144 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3145 return 0;
3146 case WM_MOUSEMOVE:
3147 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3149 BOOL captured = descr->captured;
3150 POINT mousePos;
3151 RECT clientRect;
3153 mousePos.x = (INT16)LOWORD(lParam);
3154 mousePos.y = (INT16)HIWORD(lParam);
3157 * If we are in a dropdown combobox, we simulate that
3158 * the mouse is captured to show the tracking of the item.
3160 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3161 descr->captured = TRUE;
3163 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3165 descr->captured = captured;
3167 else if (GetCapture() == descr->self)
3169 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3170 (INT16)HIWORD(lParam) );
3172 return 0;
3173 case WM_LBUTTONUP:
3174 if (lphc)
3176 POINT mousePos;
3177 RECT clientRect;
3180 * If the mouse button "up" is not in the listbox,
3181 * we make sure there is no selection by re-selecting the
3182 * item that was selected when the listbox was made visible.
3184 mousePos.x = (INT16)LOWORD(lParam);
3185 mousePos.y = (INT16)HIWORD(lParam);
3187 GetClientRect(descr->self, &clientRect);
3190 * When the user clicks outside the combobox and the focus
3191 * is lost, the owning combobox will send a fake buttonup with
3192 * 0xFFFFFFF as the mouse location, we must also revert the
3193 * selection to the original selection.
3195 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3196 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3198 return LISTBOX_HandleLButtonUp( descr );
3199 case WM_KEYDOWN:
3200 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3202 /* for some reason Windows makes it possible to
3203 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3205 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3206 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3207 && (wParam == VK_DOWN || wParam == VK_UP)) )
3209 COMBO_FlipListbox( lphc, FALSE, FALSE );
3210 return 0;
3213 return LISTBOX_HandleKeyDown( descr, wParam );
3214 case WM_CHAR:
3216 WCHAR charW;
3217 if(unicode)
3218 charW = (WCHAR)wParam;
3219 else
3221 CHAR charA = (CHAR)wParam;
3222 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3224 return LISTBOX_HandleChar( descr, charW );
3226 case WM_SYSTIMER:
3227 return LISTBOX_HandleSystemTimer( descr );
3228 case WM_ERASEBKGND:
3229 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3231 RECT rect;
3232 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3233 wParam, (LPARAM)descr->self );
3234 TRACE("hbrush = %p\n", hbrush);
3235 if(!hbrush)
3236 hbrush = GetSysColorBrush(COLOR_WINDOW);
3237 if(hbrush)
3239 GetClientRect(descr->self, &rect);
3240 FillRect((HDC)wParam, &rect, hbrush);
3243 return 1;
3244 case WM_DROPFILES:
3245 if( lphc ) return 0;
3246 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3247 SendMessageA( descr->owner, msg, wParam, lParam );
3249 case WM_NCDESTROY:
3250 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3251 lphc->hWndLBox = 0;
3252 break;
3254 case WM_NCACTIVATE:
3255 if (lphc) return 0;
3256 break;
3258 default:
3259 if ((msg >= WM_USER) && (msg < 0xc000))
3260 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3261 hwnd, msg, wParam, lParam );
3264 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3265 DefWindowProcA( hwnd, msg, wParam, lParam );
3268 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3270 TRACE("%p\n", hwnd);
3271 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);