dbghelp: Use local declarations of r_debug and link_map structs.
[wine.git] / dlls / user32 / listbox.c
blob65b052eb62bd8d202af94791e31e4bcffe0e636c
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 "wingdi.h"
29 #include "wine/unicode.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 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
248 const struct builtin_class_descr LISTBOX_builtin_class =
250 listboxW, /* name */
251 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
252 WINPROC_LISTBOX, /* proc */
253 sizeof(LB_DESCR *), /* extra */
254 IDC_ARROW, /* cursor */
255 0 /* brush */
259 /*********************************************************************
260 * combolbox class descriptor
262 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
263 const struct builtin_class_descr COMBOLBOX_builtin_class =
265 combolboxW, /* name */
266 CS_DBLCLKS | CS_SAVEBITS, /* style */
267 WINPROC_LISTBOX, /* proc */
268 sizeof(LB_DESCR *), /* extra */
269 IDC_ARROW, /* cursor */
270 0 /* brush */
274 /***********************************************************************
275 * LISTBOX_GetCurrentPageSize
277 * Return the current page size
279 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
281 INT i, height;
282 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
283 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
285 if ((height += get_item_height(descr, i)) > descr->height) break;
287 if (i == descr->top_item) return 1;
288 else return i - descr->top_item;
292 /***********************************************************************
293 * LISTBOX_GetMaxTopIndex
295 * Return the maximum possible index for the top of the listbox.
297 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
299 INT max, page;
301 if (descr->style & LBS_OWNERDRAWVARIABLE)
303 page = descr->height;
304 for (max = descr->nb_items - 1; max >= 0; max--)
305 if ((page -= get_item_height(descr, max)) < 0) break;
306 if (max < descr->nb_items - 1) max++;
308 else if (descr->style & LBS_MULTICOLUMN)
310 if ((page = descr->width / descr->column_width) < 1) page = 1;
311 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
312 max = (max - page) * descr->page_size;
314 else
316 max = descr->nb_items - descr->page_size;
318 if (max < 0) max = 0;
319 return max;
323 /***********************************************************************
324 * LISTBOX_UpdateScroll
326 * Update the scrollbars. Should be called whenever the content
327 * of the listbox changes.
329 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
331 SCROLLINFO info;
333 /* Check the listbox scroll bar flags individually before we call
334 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
335 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
336 scroll bar when we do not need one.
337 if (!(descr->style & WS_VSCROLL)) return;
340 /* It is important that we check descr->style, and not wnd->dwStyle,
341 for WS_VSCROLL, as the former is exactly the one passed in
342 argument to CreateWindow.
343 In Windows (and from now on in Wine :) a listbox created
344 with such a style (no WS_SCROLL) does not update
345 the scrollbar with listbox-related data, thus letting
346 the programmer use it for his/her own purposes. */
348 if (descr->style & LBS_NOREDRAW) return;
349 info.cbSize = sizeof(info);
351 if (descr->style & LBS_MULTICOLUMN)
353 info.nMin = 0;
354 info.nMax = (descr->nb_items - 1) / descr->page_size;
355 info.nPos = descr->top_item / descr->page_size;
356 info.nPage = descr->width / descr->column_width;
357 if (info.nPage < 1) info.nPage = 1;
358 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
359 if (descr->style & LBS_DISABLENOSCROLL)
360 info.fMask |= SIF_DISABLENOSCROLL;
361 if (descr->style & WS_HSCROLL)
362 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
363 info.nMax = 0;
364 info.fMask = SIF_RANGE;
365 if (descr->style & WS_VSCROLL)
366 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
368 else
370 info.nMin = 0;
371 info.nMax = descr->nb_items - 1;
372 info.nPos = descr->top_item;
373 info.nPage = LISTBOX_GetCurrentPageSize( descr );
374 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
375 if (descr->style & LBS_DISABLENOSCROLL)
376 info.fMask |= SIF_DISABLENOSCROLL;
377 if (descr->style & WS_VSCROLL)
378 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
380 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
382 info.nPos = descr->horz_pos;
383 info.nPage = descr->width;
384 info.fMask = SIF_POS | SIF_PAGE;
385 if (descr->style & LBS_DISABLENOSCROLL)
386 info.fMask |= SIF_DISABLENOSCROLL;
387 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
389 else
391 if (descr->style & LBS_DISABLENOSCROLL)
393 info.nMin = 0;
394 info.nMax = 0;
395 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
396 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
398 else
400 ShowScrollBar( descr->self, SB_HORZ, FALSE );
407 /***********************************************************************
408 * LISTBOX_SetTopItem
410 * Set the top item of the listbox, scrolling up or down if necessary.
412 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
414 INT max = LISTBOX_GetMaxTopIndex( descr );
416 TRACE("setting top item %d, scroll %d\n", index, scroll);
418 if (index > max) index = max;
419 if (index < 0) index = 0;
420 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
421 if (descr->top_item == index) return LB_OKAY;
422 if (scroll)
424 INT dx = 0, dy = 0;
425 if (descr->style & LBS_MULTICOLUMN)
426 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
427 else if (descr->style & LBS_OWNERDRAWVARIABLE)
429 INT i;
430 if (index > descr->top_item)
432 for (i = index - 1; i >= descr->top_item; i--)
433 dy -= get_item_height(descr, i);
435 else
437 for (i = index; i < descr->top_item; i++)
438 dy += get_item_height(descr, i);
441 else
442 dy = (descr->top_item - index) * descr->item_height;
444 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
445 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
447 else
448 InvalidateRect( descr->self, NULL, TRUE );
449 descr->top_item = index;
450 LISTBOX_UpdateScroll( descr );
451 return LB_OKAY;
455 /***********************************************************************
456 * LISTBOX_UpdatePage
458 * Update the page size. Should be called when the size of
459 * the client area or the item height changes.
461 static void LISTBOX_UpdatePage( LB_DESCR *descr )
463 INT page_size;
465 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
466 page_size = 1;
467 if (page_size == descr->page_size) return;
468 descr->page_size = page_size;
469 if (descr->style & LBS_MULTICOLUMN)
470 InvalidateRect( descr->self, NULL, TRUE );
471 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
475 /***********************************************************************
476 * LISTBOX_UpdateSize
478 * Update the size of the listbox. Should be called when the size of
479 * the client area changes.
481 static void LISTBOX_UpdateSize( LB_DESCR *descr )
483 RECT rect;
485 GetClientRect( descr->self, &rect );
486 descr->width = rect.right - rect.left;
487 descr->height = rect.bottom - rect.top;
488 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
490 INT remaining;
491 RECT rect;
493 GetWindowRect( descr->self, &rect );
494 if(descr->item_height != 0)
495 remaining = descr->height % descr->item_height;
496 else
497 remaining = 0;
498 if ((descr->height > descr->item_height) && remaining)
500 TRACE("[%p]: changing height %d -> %d\n",
501 descr->self, descr->height, descr->height - remaining );
502 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
503 rect.bottom - rect.top - remaining,
504 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
505 return;
508 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
509 LISTBOX_UpdatePage( descr );
510 LISTBOX_UpdateScroll( descr );
512 /* Invalidate the focused item so it will be repainted correctly */
513 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
515 InvalidateRect( descr->self, &rect, FALSE );
520 /***********************************************************************
521 * LISTBOX_GetItemRect
523 * Get the rectangle enclosing an item, in listbox client coordinates.
524 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
526 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
528 /* Index <= 0 is legal even on empty listboxes */
529 if (index && (index >= descr->nb_items))
531 SetRectEmpty(rect);
532 SetLastError(ERROR_INVALID_INDEX);
533 return LB_ERR;
535 SetRect( rect, 0, 0, descr->width, descr->height );
536 if (descr->style & LBS_MULTICOLUMN)
538 INT col = (index / descr->page_size) -
539 (descr->top_item / descr->page_size);
540 rect->left += col * descr->column_width;
541 rect->right = rect->left + descr->column_width;
542 rect->top += (index % descr->page_size) * descr->item_height;
543 rect->bottom = rect->top + descr->item_height;
545 else if (descr->style & LBS_OWNERDRAWVARIABLE)
547 INT i;
548 rect->right += descr->horz_pos;
549 if ((index >= 0) && (index < descr->nb_items))
551 if (index < descr->top_item)
553 for (i = descr->top_item-1; i >= index; i--)
554 rect->top -= get_item_height(descr, i);
556 else
558 for (i = descr->top_item; i < index; i++)
559 rect->top += get_item_height(descr, i);
561 rect->bottom = rect->top + get_item_height(descr, index);
565 else
567 rect->top += (index - descr->top_item) * descr->item_height;
568 rect->bottom = rect->top + descr->item_height;
569 rect->right += descr->horz_pos;
572 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
574 return ((rect->left < descr->width) && (rect->right > 0) &&
575 (rect->top < descr->height) && (rect->bottom > 0));
579 /***********************************************************************
580 * LISTBOX_GetItemFromPoint
582 * Return the item nearest from point (x,y) (in client coordinates).
584 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
586 INT index = descr->top_item;
588 if (!descr->nb_items) return -1; /* No items */
589 if (descr->style & LBS_OWNERDRAWVARIABLE)
591 INT pos = 0;
592 if (y >= 0)
594 while (index < descr->nb_items)
596 if ((pos += get_item_height(descr, index)) > y) break;
597 index++;
600 else
602 while (index > 0)
604 index--;
605 if ((pos -= get_item_height(descr, index)) <= y) break;
609 else if (descr->style & LBS_MULTICOLUMN)
611 if (y >= descr->item_height * descr->page_size) return -1;
612 if (y >= 0) index += y / descr->item_height;
613 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
614 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
616 else
618 index += (y / descr->item_height);
620 if (index < 0) return 0;
621 if (index >= descr->nb_items) return -1;
622 return index;
626 /***********************************************************************
627 * LISTBOX_PaintItem
629 * Paint an item.
631 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
632 INT index, UINT action, BOOL ignoreFocus )
634 BOOL selected = FALSE, focused;
635 WCHAR *item_str = NULL;
637 if (index < descr->nb_items)
639 item_str = get_item_string(descr, index);
640 selected = is_item_selected(descr, index);
643 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
645 if (IS_OWNERDRAW(descr))
647 DRAWITEMSTRUCT dis;
648 RECT r;
649 HRGN hrgn;
651 if (index >= descr->nb_items)
653 if (action == ODA_FOCUS)
654 DrawFocusRect( hdc, rect );
655 else
656 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
657 return;
660 /* some programs mess with the clipping region when
661 drawing the item, *and* restore the previous region
662 after they are done, so a region has better to exist
663 else everything ends clipped */
664 GetClientRect(descr->self, &r);
665 hrgn = set_control_clipping( hdc, &r );
667 dis.CtlType = ODT_LISTBOX;
668 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
669 dis.hwndItem = descr->self;
670 dis.itemAction = action;
671 dis.hDC = hdc;
672 dis.itemID = index;
673 dis.itemState = 0;
674 if (selected)
675 dis.itemState |= ODS_SELECTED;
676 if (focused)
677 dis.itemState |= ODS_FOCUS;
678 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
679 dis.itemData = get_item_data(descr, index);
680 dis.rcItem = *rect;
681 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
682 descr->self, index, debugstr_w(item_str), action,
683 dis.itemState, wine_dbgstr_rect(rect) );
684 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
685 SelectClipRgn( hdc, hrgn );
686 if (hrgn) DeleteObject( hrgn );
688 else
690 COLORREF oldText = 0, oldBk = 0;
692 if (action == ODA_FOCUS)
694 DrawFocusRect( hdc, rect );
695 return;
697 if (selected)
699 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
700 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
703 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
704 descr->self, index, debugstr_w(item_str), action,
705 wine_dbgstr_rect(rect) );
706 if (!item_str)
707 ExtTextOutW( hdc, rect->left + 1, rect->top,
708 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
709 else if (!(descr->style & LBS_USETABSTOPS))
710 ExtTextOutW( hdc, rect->left + 1, rect->top,
711 ETO_OPAQUE | ETO_CLIPPED, rect, item_str,
712 strlenW(item_str), NULL );
713 else
715 /* Output empty string to paint background in the full width. */
716 ExtTextOutW( hdc, rect->left + 1, rect->top,
717 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
718 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
719 item_str, strlenW(item_str),
720 descr->nb_tabs, descr->tabs, 0);
722 if (selected)
724 SetBkColor( hdc, oldBk );
725 SetTextColor( hdc, oldText );
727 if (focused)
728 DrawFocusRect( hdc, rect );
733 /***********************************************************************
734 * LISTBOX_SetRedraw
736 * Change the redraw flag.
738 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
740 if (on)
742 if (!(descr->style & LBS_NOREDRAW)) return;
743 descr->style &= ~LBS_NOREDRAW;
744 if (descr->style & LBS_DISPLAYCHANGED)
745 { /* page was changed while setredraw false, refresh automatically */
746 InvalidateRect(descr->self, NULL, TRUE);
747 if ((descr->top_item + descr->page_size) > descr->nb_items)
748 { /* reset top of page if less than number of items/page */
749 descr->top_item = descr->nb_items - descr->page_size;
750 if (descr->top_item < 0) descr->top_item = 0;
752 descr->style &= ~LBS_DISPLAYCHANGED;
754 LISTBOX_UpdateScroll( descr );
756 else descr->style |= LBS_NOREDRAW;
760 /***********************************************************************
761 * LISTBOX_RepaintItem
763 * Repaint a single item synchronously.
765 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
767 HDC hdc;
768 RECT rect;
769 HFONT oldFont = 0;
770 HBRUSH hbrush, oldBrush = 0;
772 /* Do not repaint the item if the item is not visible */
773 if (!IsWindowVisible(descr->self)) return;
774 if (descr->style & LBS_NOREDRAW)
776 descr->style |= LBS_DISPLAYCHANGED;
777 return;
779 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
780 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
781 if (descr->font) oldFont = SelectObject( hdc, descr->font );
782 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
783 (WPARAM)hdc, (LPARAM)descr->self );
784 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
785 if (!IsWindowEnabled(descr->self))
786 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
787 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
788 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
789 if (oldFont) SelectObject( hdc, oldFont );
790 if (oldBrush) SelectObject( hdc, oldBrush );
791 ReleaseDC( descr->self, hdc );
795 /***********************************************************************
796 * LISTBOX_DrawFocusRect
798 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
800 HDC hdc;
801 RECT rect;
802 HFONT oldFont = 0;
804 /* Do not repaint the item if the item is not visible */
805 if (!IsWindowVisible(descr->self)) return;
807 if (descr->focus_item == -1) return;
808 if (!descr->caret_on || !descr->in_focus) return;
810 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
811 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
812 if (descr->font) oldFont = SelectObject( hdc, descr->font );
813 if (!IsWindowEnabled(descr->self))
814 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
815 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
816 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
817 if (oldFont) SelectObject( hdc, oldFont );
818 ReleaseDC( descr->self, hdc );
822 /***********************************************************************
823 * LISTBOX_InitStorage
825 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
827 UINT new_size = descr->nb_items + nb_items;
829 if (new_size > descr->items_size && !resize_storage(descr, new_size))
830 return LB_ERRSPACE;
831 return descr->items_size;
835 /***********************************************************************
836 * LISTBOX_SetTabStops
838 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
840 INT i;
842 if (!(descr->style & LBS_USETABSTOPS))
844 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
845 return FALSE;
848 HeapFree( GetProcessHeap(), 0, descr->tabs );
849 if (!(descr->nb_tabs = count))
851 descr->tabs = NULL;
852 return TRUE;
854 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
855 descr->nb_tabs * sizeof(INT) )))
856 return FALSE;
857 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
859 /* convert into "dialog units"*/
860 for (i = 0; i < descr->nb_tabs; i++)
861 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
863 return TRUE;
867 /***********************************************************************
868 * LISTBOX_GetText
870 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
872 DWORD len;
874 if ((index < 0) || (index >= descr->nb_items))
876 SetLastError(ERROR_INVALID_INDEX);
877 return LB_ERR;
880 if (HAS_STRINGS(descr))
882 WCHAR *str = get_item_string(descr, index);
884 if (!buffer)
886 len = strlenW(str);
887 if( unicode )
888 return len;
889 return WideCharToMultiByte( CP_ACP, 0, str, len, NULL, 0, NULL, NULL );
892 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(str));
894 __TRY /* hide a Delphi bug that passes a read-only buffer */
896 if(unicode)
898 strcpyW(buffer, str);
899 len = strlenW(buffer);
901 else
903 len = WideCharToMultiByte(CP_ACP, 0, str, -1, (LPSTR)buffer,
904 0x7FFFFFFF, NULL, NULL) - 1;
907 __EXCEPT_PAGE_FAULT
909 WARN( "got an invalid buffer (Delphi bug?)\n" );
910 SetLastError( ERROR_INVALID_PARAMETER );
911 return LB_ERR;
913 __ENDTRY
914 } else
916 if (buffer)
917 *((ULONG_PTR *)buffer) = get_item_data(descr, index);
918 len = sizeof(ULONG_PTR);
920 return len;
923 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
925 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
926 if (ret == CSTR_LESS_THAN)
927 return -1;
928 if (ret == CSTR_EQUAL)
929 return 0;
930 if (ret == CSTR_GREATER_THAN)
931 return 1;
932 return -1;
935 /***********************************************************************
936 * LISTBOX_FindStringPos
938 * Find the nearest string located before a given string in sort order.
939 * If 'exact' is TRUE, return an error if we don't get an exact match.
941 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
943 INT index, min, max, res;
945 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
947 min = 0;
948 max = descr->nb_items - 1;
949 while (min <= max)
951 index = (min + max) / 2;
952 if (HAS_STRINGS(descr))
953 res = LISTBOX_lstrcmpiW( descr->locale, get_item_string(descr, index), str );
954 else
956 COMPAREITEMSTRUCT cis;
957 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
959 cis.CtlType = ODT_LISTBOX;
960 cis.CtlID = id;
961 cis.hwndItem = descr->self;
962 /* note that some application (MetaStock) expects the second item
963 * to be in the listbox */
964 cis.itemID1 = index;
965 cis.itemData1 = get_item_data(descr, index);
966 cis.itemID2 = -1;
967 cis.itemData2 = (ULONG_PTR)str;
968 cis.dwLocaleId = descr->locale;
969 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
971 if (!res) return index;
972 if (res > 0) max = index - 1;
973 else min = index + 1;
975 return exact ? -1 : min;
979 /***********************************************************************
980 * LISTBOX_FindFileStrPos
982 * Find the nearest string located before a given string in directory
983 * sort order (i.e. first files, then directories, then drives).
985 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
987 INT min, max, res;
989 if (!HAS_STRINGS(descr))
990 return LISTBOX_FindStringPos( descr, str, FALSE );
991 min = 0;
992 max = descr->nb_items;
993 while (min != max)
995 INT index = (min + max) / 2;
996 LPCWSTR p = get_item_string(descr, index);
997 if (*p == '[') /* drive or directory */
999 if (*str != '[') res = -1;
1000 else if (p[1] == '-') /* drive */
1002 if (str[1] == '-') res = str[2] - p[2];
1003 else res = -1;
1005 else /* directory */
1007 if (str[1] == '-') res = 1;
1008 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
1011 else /* filename */
1013 if (*str == '[') res = 1;
1014 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
1016 if (!res) return index;
1017 if (res < 0) max = index;
1018 else min = index + 1;
1020 return max;
1024 /***********************************************************************
1025 * LISTBOX_FindString
1027 * Find the item beginning with a given string.
1029 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
1031 INT i, index;
1033 if (descr->style & LBS_NODATA)
1035 SetLastError(ERROR_INVALID_PARAMETER);
1036 return LB_ERR;
1039 start++;
1040 if (start >= descr->nb_items) start = 0;
1041 if (HAS_STRINGS(descr))
1043 if (!str || ! str[0] ) return LB_ERR;
1044 if (exact)
1046 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1048 if (index == descr->nb_items) index = 0;
1049 if (!LISTBOX_lstrcmpiW(descr->locale, str, get_item_string(descr, index)))
1050 return index;
1053 else
1055 /* Special case for drives and directories: ignore prefix */
1056 INT len = strlenW(str);
1057 WCHAR *item_str;
1059 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1061 if (index == descr->nb_items) index = 0;
1062 item_str = get_item_string(descr, index);
1064 if (!strncmpiW(str, item_str, len)) return index;
1065 if (item_str[0] == '[')
1067 if (!strncmpiW(str, item_str + 1, len)) return index;
1068 if (item_str[1] == '-' && !strncmpiW(str, item_str + 2, len)) return index;
1073 else
1075 if (exact && (descr->style & LBS_SORT))
1076 /* If sorted, use a WM_COMPAREITEM binary search */
1077 return LISTBOX_FindStringPos( descr, str, TRUE );
1079 /* Otherwise use a linear search */
1080 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1082 if (index == descr->nb_items) index = 0;
1083 if (get_item_data(descr, index) == (ULONG_PTR)str) return index;
1086 return LB_ERR;
1090 /***********************************************************************
1091 * LISTBOX_GetSelCount
1093 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1095 INT i, count;
1097 if (!(descr->style & LBS_MULTIPLESEL) ||
1098 (descr->style & LBS_NOSEL))
1099 return LB_ERR;
1100 for (i = count = 0; i < descr->nb_items; i++)
1101 if (is_item_selected(descr, i)) count++;
1102 return count;
1106 /***********************************************************************
1107 * LISTBOX_GetSelItems
1109 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1111 INT i, count;
1113 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1114 for (i = count = 0; (i < descr->nb_items) && (count < max); i++)
1115 if (is_item_selected(descr, i)) array[count++] = i;
1116 return count;
1120 /***********************************************************************
1121 * LISTBOX_Paint
1123 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1125 INT i, col_pos = descr->page_size - 1;
1126 RECT rect;
1127 RECT focusRect = {-1, -1, -1, -1};
1128 HFONT oldFont = 0;
1129 HBRUSH hbrush, oldBrush = 0;
1131 if (descr->style & LBS_NOREDRAW) return 0;
1133 SetRect( &rect, 0, 0, descr->width, descr->height );
1134 if (descr->style & LBS_MULTICOLUMN)
1135 rect.right = rect.left + descr->column_width;
1136 else if (descr->horz_pos)
1138 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1139 rect.right += descr->horz_pos;
1142 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1143 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1144 (WPARAM)hdc, (LPARAM)descr->self );
1145 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1146 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1148 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1149 (descr->in_focus))
1151 /* Special case for empty listbox: paint focus rect */
1152 rect.bottom = rect.top + descr->item_height;
1153 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1154 &rect, NULL, 0, NULL );
1155 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1156 rect.top = rect.bottom;
1159 /* Paint all the item, regarding the selection
1160 Focus state will be painted after */
1162 for (i = descr->top_item; i < descr->nb_items; i++)
1164 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1165 rect.bottom = rect.top + descr->item_height;
1166 else
1167 rect.bottom = rect.top + get_item_height(descr, i);
1169 /* keep the focus rect, to paint the focus item after */
1170 if (i == descr->focus_item)
1171 focusRect = rect;
1173 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1174 rect.top = rect.bottom;
1176 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1178 if (!IS_OWNERDRAW(descr))
1180 /* Clear the bottom of the column */
1181 if (rect.top < descr->height)
1183 rect.bottom = descr->height;
1184 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1185 &rect, NULL, 0, NULL );
1189 /* Go to the next column */
1190 rect.left += descr->column_width;
1191 rect.right += descr->column_width;
1192 rect.top = 0;
1193 col_pos = descr->page_size - 1;
1194 if (rect.left >= descr->width) break;
1196 else
1198 col_pos--;
1199 if (rect.top >= descr->height) break;
1203 /* Paint the focus item now */
1204 if (focusRect.top != focusRect.bottom &&
1205 descr->caret_on && descr->in_focus)
1206 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1208 if (!IS_OWNERDRAW(descr))
1210 /* Clear the remainder of the client area */
1211 if (rect.top < descr->height)
1213 rect.bottom = descr->height;
1214 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1215 &rect, NULL, 0, NULL );
1217 if (rect.right < descr->width)
1219 rect.left = rect.right;
1220 rect.right = descr->width;
1221 rect.top = 0;
1222 rect.bottom = descr->height;
1223 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1224 &rect, NULL, 0, NULL );
1227 if (oldFont) SelectObject( hdc, oldFont );
1228 if (oldBrush) SelectObject( hdc, oldBrush );
1229 return 0;
1233 /***********************************************************************
1234 * LISTBOX_InvalidateItems
1236 * Invalidate all items from a given item. If the specified item is not
1237 * visible, nothing happens.
1239 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1241 RECT rect;
1243 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1245 if (descr->style & LBS_NOREDRAW)
1247 descr->style |= LBS_DISPLAYCHANGED;
1248 return;
1250 rect.bottom = descr->height;
1251 InvalidateRect( descr->self, &rect, TRUE );
1252 if (descr->style & LBS_MULTICOLUMN)
1254 /* Repaint the other columns */
1255 rect.left = rect.right;
1256 rect.right = descr->width;
1257 rect.top = 0;
1258 InvalidateRect( descr->self, &rect, TRUE );
1263 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1265 RECT rect;
1267 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1268 InvalidateRect( descr->self, &rect, TRUE );
1271 /***********************************************************************
1272 * LISTBOX_GetItemHeight
1274 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1276 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1278 if ((index < 0) || (index >= descr->nb_items))
1280 SetLastError(ERROR_INVALID_INDEX);
1281 return LB_ERR;
1283 return get_item_height(descr, index);
1285 else return descr->item_height;
1289 /***********************************************************************
1290 * LISTBOX_SetItemHeight
1292 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1294 if (height > MAXBYTE)
1295 return -1;
1297 if (!height) height = 1;
1299 if (descr->style & LBS_OWNERDRAWVARIABLE)
1301 if ((index < 0) || (index >= descr->nb_items))
1303 SetLastError(ERROR_INVALID_INDEX);
1304 return LB_ERR;
1306 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1307 set_item_height(descr, index, height);
1308 LISTBOX_UpdateScroll( descr );
1309 if (repaint)
1310 LISTBOX_InvalidateItems( descr, index );
1312 else if (height != descr->item_height)
1314 TRACE("[%p]: new height = %d\n", descr->self, height );
1315 descr->item_height = height;
1316 LISTBOX_UpdatePage( descr );
1317 LISTBOX_UpdateScroll( descr );
1318 if (repaint)
1319 InvalidateRect( descr->self, 0, TRUE );
1321 return LB_OKAY;
1325 /***********************************************************************
1326 * LISTBOX_SetHorizontalPos
1328 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1330 INT diff;
1332 if (pos > descr->horz_extent - descr->width)
1333 pos = descr->horz_extent - descr->width;
1334 if (pos < 0) pos = 0;
1335 if (!(diff = descr->horz_pos - pos)) return;
1336 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1337 descr->horz_pos = pos;
1338 LISTBOX_UpdateScroll( descr );
1339 if (abs(diff) < descr->width)
1341 RECT rect;
1342 /* Invalidate the focused item so it will be repainted correctly */
1343 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1344 InvalidateRect( descr->self, &rect, TRUE );
1345 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1346 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1348 else
1349 InvalidateRect( descr->self, NULL, TRUE );
1353 /***********************************************************************
1354 * LISTBOX_SetHorizontalExtent
1356 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1358 if (descr->style & LBS_MULTICOLUMN)
1359 return LB_OKAY;
1360 if (extent == descr->horz_extent) return LB_OKAY;
1361 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1362 descr->horz_extent = extent;
1363 if (descr->style & WS_HSCROLL) {
1364 SCROLLINFO info;
1365 info.cbSize = sizeof(info);
1366 info.nMin = 0;
1367 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1368 info.fMask = SIF_RANGE;
1369 if (descr->style & LBS_DISABLENOSCROLL)
1370 info.fMask |= SIF_DISABLENOSCROLL;
1371 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1373 if (descr->horz_pos > extent - descr->width)
1374 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1375 return LB_OKAY;
1379 /***********************************************************************
1380 * LISTBOX_SetColumnWidth
1382 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1384 RECT rect;
1386 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1388 GetClientRect(descr->self, &rect);
1389 descr->width = rect.right - rect.left;
1390 descr->height = rect.bottom - rect.top;
1391 descr->column_width = column_width;
1393 LISTBOX_UpdatePage(descr);
1394 LISTBOX_UpdateScroll(descr);
1395 return LB_OKAY;
1399 /***********************************************************************
1400 * LISTBOX_SetFont
1402 * Returns the item height.
1404 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1406 HDC hdc;
1407 HFONT oldFont = 0;
1408 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1409 SIZE sz;
1411 descr->font = font;
1413 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1415 ERR("unable to get DC.\n" );
1416 return 16;
1418 if (font) oldFont = SelectObject( hdc, font );
1419 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1420 if (oldFont) SelectObject( hdc, oldFont );
1421 ReleaseDC( descr->self, hdc );
1423 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1424 if (!IS_OWNERDRAW(descr))
1425 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1426 return sz.cy;
1430 /***********************************************************************
1431 * LISTBOX_MakeItemVisible
1433 * Make sure that a given item is partially or fully visible.
1435 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1437 INT top;
1439 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1441 if (index <= descr->top_item) top = index;
1442 else if (descr->style & LBS_MULTICOLUMN)
1444 INT cols = descr->width;
1445 if (!fully) cols += descr->column_width - 1;
1446 if (cols >= descr->column_width) cols /= descr->column_width;
1447 else cols = 1;
1448 if (index < descr->top_item + (descr->page_size * cols)) return;
1449 top = index - descr->page_size * (cols - 1);
1451 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1453 INT height = fully ? get_item_height(descr, index) : 1;
1454 for (top = index; top > descr->top_item; top--)
1455 if ((height += get_item_height(descr, top - 1)) > descr->height) break;
1457 else
1459 if (index < descr->top_item + descr->page_size) return;
1460 if (!fully && (index == descr->top_item + descr->page_size) &&
1461 (descr->height > (descr->page_size * descr->item_height))) return;
1462 top = index - descr->page_size + 1;
1464 LISTBOX_SetTopItem( descr, top, TRUE );
1467 /***********************************************************************
1468 * LISTBOX_SetCaretIndex
1470 * NOTES
1471 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1474 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1476 BOOL focus_changed = descr->focus_item != index;
1478 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1480 if (descr->style & LBS_NOSEL) return LB_ERR;
1481 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1483 if (focus_changed)
1485 LISTBOX_DrawFocusRect( descr, FALSE );
1486 descr->focus_item = index;
1489 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1491 if (focus_changed)
1492 LISTBOX_DrawFocusRect( descr, TRUE );
1494 return LB_OKAY;
1498 /***********************************************************************
1499 * LISTBOX_SelectItemRange
1501 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1503 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1504 INT last, BOOL on )
1506 INT i;
1508 /* A few sanity checks */
1510 if (descr->style & LBS_NOSEL) return LB_ERR;
1511 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1513 if (!descr->nb_items) return LB_OKAY;
1515 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1516 if (first < 0) first = 0;
1517 if (last < first) return LB_OKAY;
1519 if (on) /* Turn selection on */
1521 for (i = first; i <= last; i++)
1523 if (is_item_selected(descr, i)) continue;
1524 set_item_selected_state(descr, i, TRUE);
1525 LISTBOX_InvalidateItemRect(descr, i);
1528 else /* Turn selection off */
1530 for (i = first; i <= last; i++)
1532 if (!is_item_selected(descr, i)) continue;
1533 set_item_selected_state(descr, i, FALSE);
1534 LISTBOX_InvalidateItemRect(descr, i);
1537 return LB_OKAY;
1540 /***********************************************************************
1541 * LISTBOX_SetSelection
1543 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1544 BOOL on, BOOL send_notify )
1546 TRACE( "cur_sel=%d index=%d notify=%s\n",
1547 descr->selected_item, index, send_notify ? "YES" : "NO" );
1549 if (descr->style & LBS_NOSEL)
1551 descr->selected_item = index;
1552 return LB_ERR;
1554 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1555 if (descr->style & LBS_MULTIPLESEL)
1557 if (index == -1) /* Select all items */
1558 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1559 else /* Only one item */
1560 return LISTBOX_SelectItemRange( descr, index, index, on );
1562 else
1564 INT oldsel = descr->selected_item;
1565 if (index == oldsel) return LB_OKAY;
1566 if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
1567 if (index != -1) set_item_selected_state(descr, index, TRUE);
1568 descr->selected_item = index;
1569 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1570 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1571 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1572 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1573 else
1574 if( descr->lphc ) /* set selection change flag for parent combo */
1575 descr->lphc->wState |= CBF_SELCHANGE;
1577 return LB_OKAY;
1581 /***********************************************************************
1582 * LISTBOX_MoveCaret
1584 * Change the caret position and extend the selection to the new caret.
1586 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1588 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1590 if ((index < 0) || (index >= descr->nb_items))
1591 return;
1593 /* Important, repaint needs to be done in this order if
1594 you want to mimic Windows behavior:
1595 1. Remove the focus and paint the item
1596 2. Remove the selection and paint the item(s)
1597 3. Set the selection and repaint the item(s)
1598 4. Set the focus to 'index' and repaint the item */
1600 /* 1. remove the focus and repaint the item */
1601 LISTBOX_DrawFocusRect( descr, FALSE );
1603 /* 2. then turn off the previous selection */
1604 /* 3. repaint the new selected item */
1605 if (descr->style & LBS_EXTENDEDSEL)
1607 if (descr->anchor_item != -1)
1609 INT first = min( index, descr->anchor_item );
1610 INT last = max( index, descr->anchor_item );
1611 if (first > 0)
1612 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1613 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1614 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1617 else if (!(descr->style & LBS_MULTIPLESEL))
1619 /* Set selection to new caret item */
1620 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1623 /* 4. repaint the new item with the focus */
1624 descr->focus_item = index;
1625 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1626 LISTBOX_DrawFocusRect( descr, TRUE );
1630 /***********************************************************************
1631 * LISTBOX_InsertItem
1633 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1634 LPWSTR str, ULONG_PTR data )
1636 INT oldfocus = descr->focus_item;
1638 if (index == -1) index = descr->nb_items;
1639 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1640 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1642 insert_item_data(descr, index);
1643 descr->nb_items++;
1644 set_item_string(descr, index, str);
1645 set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
1646 set_item_height(descr, index, 0);
1647 set_item_selected_state(descr, index, FALSE);
1649 /* Get item height */
1651 if (descr->style & LBS_OWNERDRAWVARIABLE)
1653 MEASUREITEMSTRUCT mis;
1654 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1656 mis.CtlType = ODT_LISTBOX;
1657 mis.CtlID = id;
1658 mis.itemID = index;
1659 mis.itemData = str ? (ULONG_PTR)str : data;
1660 mis.itemHeight = descr->item_height;
1661 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1662 set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
1663 TRACE("[%p]: measure item %d (%s) = %d\n",
1664 descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
1667 /* Repaint the items */
1669 LISTBOX_UpdateScroll( descr );
1670 LISTBOX_InvalidateItems( descr, index );
1672 /* Move selection and focused item */
1673 /* If listbox was empty, set focus to the first item */
1674 if (descr->nb_items == 1)
1675 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1676 /* single select don't change selection index in win31 */
1677 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1679 descr->selected_item++;
1680 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1682 else
1684 if (index <= descr->selected_item)
1686 descr->selected_item++;
1687 descr->focus_item = oldfocus; /* focus not changed */
1690 return LB_OKAY;
1694 /***********************************************************************
1695 * LISTBOX_InsertString
1697 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1699 LPWSTR new_str = NULL;
1700 LRESULT ret;
1702 if (HAS_STRINGS(descr))
1704 static const WCHAR empty_stringW[] = { 0 };
1705 if (!str) str = empty_stringW;
1706 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1708 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1709 return LB_ERRSPACE;
1711 strcpyW(new_str, str);
1714 if (index == -1) index = descr->nb_items;
1715 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1717 HeapFree( GetProcessHeap(), 0, new_str );
1718 return ret;
1721 TRACE("[%p]: added item %d %s\n",
1722 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1723 return index;
1727 /***********************************************************************
1728 * LISTBOX_DeleteItem
1730 * Delete the content of an item. 'index' must be a valid index.
1732 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1734 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1735 * while Win95 sends it for all items with user data.
1736 * It's probably better to send it too often than not
1737 * often enough, so this is what we do here.
1739 if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
1741 DELETEITEMSTRUCT dis;
1742 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1744 dis.CtlType = ODT_LISTBOX;
1745 dis.CtlID = id;
1746 dis.itemID = index;
1747 dis.hwndItem = descr->self;
1748 dis.itemData = get_item_data(descr, index);
1749 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1751 HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
1755 /***********************************************************************
1756 * LISTBOX_RemoveItem
1758 * Remove an item from the listbox and delete its content.
1760 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1762 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1764 /* We need to invalidate the original rect instead of the updated one. */
1765 LISTBOX_InvalidateItems( descr, index );
1767 if (descr->nb_items == 1)
1769 SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1770 return LB_OKAY;
1772 descr->nb_items--;
1773 LISTBOX_DeleteItem( descr, index );
1774 remove_item_data(descr, index);
1776 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1777 resize_storage(descr, descr->nb_items);
1779 /* Repaint the items */
1781 LISTBOX_UpdateScroll( descr );
1782 /* if we removed the scrollbar, reset the top of the list
1783 (correct for owner-drawn ???) */
1784 if (descr->nb_items == descr->page_size)
1785 LISTBOX_SetTopItem( descr, 0, TRUE );
1787 /* Move selection and focused item */
1788 if (!IS_MULTISELECT(descr))
1790 if (index == descr->selected_item)
1791 descr->selected_item = -1;
1792 else if (index < descr->selected_item)
1794 descr->selected_item--;
1795 if (ISWIN31) /* win 31 do not change the selected item number */
1796 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1800 if (descr->focus_item >= descr->nb_items)
1802 descr->focus_item = descr->nb_items - 1;
1803 if (descr->focus_item < 0) descr->focus_item = 0;
1805 return LB_OKAY;
1809 /***********************************************************************
1810 * LISTBOX_ResetContent
1812 static void LISTBOX_ResetContent( LB_DESCR *descr )
1814 INT i;
1816 if (!(descr->style & LBS_NODATA))
1817 for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
1818 HeapFree( GetProcessHeap(), 0, descr->u.items );
1819 descr->nb_items = 0;
1820 descr->top_item = 0;
1821 descr->selected_item = -1;
1822 descr->focus_item = 0;
1823 descr->anchor_item = -1;
1824 descr->items_size = 0;
1825 descr->u.items = NULL;
1829 /***********************************************************************
1830 * LISTBOX_SetCount
1832 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1834 UINT orig_num = descr->nb_items;
1836 if (!(descr->style & LBS_NODATA))
1838 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1839 return LB_ERR;
1842 if (!resize_storage(descr, count))
1843 return LB_ERRSPACE;
1844 descr->nb_items = count;
1846 if (count)
1848 LISTBOX_UpdateScroll(descr);
1849 if (count < orig_num)
1851 descr->anchor_item = min(descr->anchor_item, count - 1);
1852 if (descr->selected_item >= count)
1853 descr->selected_item = -1;
1855 /* If we removed the scrollbar, reset the top of the list */
1856 if (count <= descr->page_size && orig_num > descr->page_size)
1857 LISTBOX_SetTopItem(descr, 0, TRUE);
1859 descr->focus_item = min(descr->focus_item, count - 1);
1862 /* If it was empty before growing, set focus to the first item */
1863 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1865 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1867 InvalidateRect( descr->self, NULL, TRUE );
1868 return LB_OKAY;
1872 /***********************************************************************
1873 * LISTBOX_Directory
1875 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1876 LPCWSTR filespec, BOOL long_names )
1878 HANDLE handle;
1879 LRESULT ret = LB_OKAY;
1880 WIN32_FIND_DATAW entry;
1881 int pos;
1882 LRESULT maxinsert = LB_ERR;
1884 /* don't scan directory if we just want drives exclusively */
1885 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1886 /* scan directory */
1887 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1889 int le = GetLastError();
1890 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1892 else
1896 WCHAR buffer[270];
1897 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1899 static const WCHAR bracketW[] = { ']',0 };
1900 static const WCHAR dotW[] = { '.',0 };
1901 if (!(attrib & DDL_DIRECTORY) ||
1902 !strcmpW( entry.cFileName, dotW )) continue;
1903 buffer[0] = '[';
1904 if (!long_names && entry.cAlternateFileName[0])
1905 strcpyW( buffer + 1, entry.cAlternateFileName );
1906 else
1907 strcpyW( buffer + 1, entry.cFileName );
1908 strcatW(buffer, bracketW);
1910 else /* not a directory */
1912 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1913 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1915 if ((attrib & DDL_EXCLUSIVE) &&
1916 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1917 continue;
1918 #undef ATTRIBS
1919 if (!long_names && entry.cAlternateFileName[0])
1920 strcpyW( buffer, entry.cAlternateFileName );
1921 else
1922 strcpyW( buffer, entry.cFileName );
1924 if (!long_names) CharLowerW( buffer );
1925 pos = LISTBOX_FindFileStrPos( descr, buffer );
1926 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1927 break;
1928 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1929 } while (FindNextFileW( handle, &entry ));
1930 FindClose( handle );
1933 if (ret >= 0)
1935 ret = maxinsert;
1937 /* scan drives */
1938 if (attrib & DDL_DRIVES)
1940 WCHAR buffer[] = {'[','-','a','-',']',0};
1941 WCHAR root[] = {'A',':','\\',0};
1942 int drive;
1943 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1945 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1946 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1947 break;
1951 return ret;
1955 /***********************************************************************
1956 * LISTBOX_HandleVScroll
1958 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1960 SCROLLINFO info;
1962 if (descr->style & LBS_MULTICOLUMN) return 0;
1963 switch(scrollReq)
1965 case SB_LINEUP:
1966 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1967 break;
1968 case SB_LINEDOWN:
1969 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1970 break;
1971 case SB_PAGEUP:
1972 LISTBOX_SetTopItem( descr, descr->top_item -
1973 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1974 break;
1975 case SB_PAGEDOWN:
1976 LISTBOX_SetTopItem( descr, descr->top_item +
1977 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1978 break;
1979 case SB_THUMBPOSITION:
1980 LISTBOX_SetTopItem( descr, pos, TRUE );
1981 break;
1982 case SB_THUMBTRACK:
1983 info.cbSize = sizeof(info);
1984 info.fMask = SIF_TRACKPOS;
1985 GetScrollInfo( descr->self, SB_VERT, &info );
1986 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1987 break;
1988 case SB_TOP:
1989 LISTBOX_SetTopItem( descr, 0, TRUE );
1990 break;
1991 case SB_BOTTOM:
1992 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1993 break;
1995 return 0;
1999 /***********************************************************************
2000 * LISTBOX_HandleHScroll
2002 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
2004 SCROLLINFO info;
2005 INT page;
2007 if (descr->style & LBS_MULTICOLUMN)
2009 switch(scrollReq)
2011 case SB_LINELEFT:
2012 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
2013 TRUE );
2014 break;
2015 case SB_LINERIGHT:
2016 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
2017 TRUE );
2018 break;
2019 case SB_PAGELEFT:
2020 page = descr->width / descr->column_width;
2021 if (page < 1) page = 1;
2022 LISTBOX_SetTopItem( descr,
2023 descr->top_item - page * descr->page_size, TRUE );
2024 break;
2025 case SB_PAGERIGHT:
2026 page = descr->width / descr->column_width;
2027 if (page < 1) page = 1;
2028 LISTBOX_SetTopItem( descr,
2029 descr->top_item + page * descr->page_size, TRUE );
2030 break;
2031 case SB_THUMBPOSITION:
2032 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
2033 break;
2034 case SB_THUMBTRACK:
2035 info.cbSize = sizeof(info);
2036 info.fMask = SIF_TRACKPOS;
2037 GetScrollInfo( descr->self, SB_VERT, &info );
2038 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
2039 TRUE );
2040 break;
2041 case SB_LEFT:
2042 LISTBOX_SetTopItem( descr, 0, TRUE );
2043 break;
2044 case SB_RIGHT:
2045 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2046 break;
2049 else if (descr->horz_extent)
2051 switch(scrollReq)
2053 case SB_LINELEFT:
2054 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2055 break;
2056 case SB_LINERIGHT:
2057 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2058 break;
2059 case SB_PAGELEFT:
2060 LISTBOX_SetHorizontalPos( descr,
2061 descr->horz_pos - descr->width );
2062 break;
2063 case SB_PAGERIGHT:
2064 LISTBOX_SetHorizontalPos( descr,
2065 descr->horz_pos + descr->width );
2066 break;
2067 case SB_THUMBPOSITION:
2068 LISTBOX_SetHorizontalPos( descr, pos );
2069 break;
2070 case SB_THUMBTRACK:
2071 info.cbSize = sizeof(info);
2072 info.fMask = SIF_TRACKPOS;
2073 GetScrollInfo( descr->self, SB_HORZ, &info );
2074 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2075 break;
2076 case SB_LEFT:
2077 LISTBOX_SetHorizontalPos( descr, 0 );
2078 break;
2079 case SB_RIGHT:
2080 LISTBOX_SetHorizontalPos( descr,
2081 descr->horz_extent - descr->width );
2082 break;
2085 return 0;
2088 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2090 INT pulScrollLines = 3;
2092 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2094 /* if scrolling changes direction, ignore left overs */
2095 if ((delta < 0 && descr->wheel_remain < 0) ||
2096 (delta > 0 && descr->wheel_remain > 0))
2097 descr->wheel_remain += delta;
2098 else
2099 descr->wheel_remain = delta;
2101 if (descr->wheel_remain && pulScrollLines)
2103 int cLineScroll;
2104 if (descr->style & LBS_MULTICOLUMN)
2106 pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
2107 pulScrollLines = max(1, pulScrollLines);
2108 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2109 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2110 cLineScroll *= descr->page_size;
2112 else
2114 pulScrollLines = min(descr->page_size, pulScrollLines);
2115 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2116 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2118 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2120 return 0;
2123 /***********************************************************************
2124 * LISTBOX_HandleLButtonDown
2126 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2128 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2130 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2131 descr->self, x, y, index, descr->focus_item);
2133 if (!descr->caret_on && (descr->in_focus)) return 0;
2135 if (!descr->in_focus)
2137 if( !descr->lphc ) SetFocus( descr->self );
2138 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2141 if (index == -1) return 0;
2143 if (!descr->lphc)
2145 if (descr->style & LBS_NOTIFY )
2146 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2147 MAKELPARAM( x, y ) );
2150 descr->captured = TRUE;
2151 SetCapture( descr->self );
2153 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2155 /* we should perhaps make sure that all items are deselected
2156 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2157 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2158 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2161 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2162 if (keys & MK_CONTROL)
2164 LISTBOX_SetCaretIndex( descr, index, FALSE );
2165 LISTBOX_SetSelection( descr, index,
2166 !is_item_selected(descr, index),
2167 (descr->style & LBS_NOTIFY) != 0);
2169 else
2171 LISTBOX_MoveCaret( descr, index, FALSE );
2173 if (descr->style & LBS_EXTENDEDSEL)
2175 LISTBOX_SetSelection( descr, index,
2176 is_item_selected(descr, index),
2177 (descr->style & LBS_NOTIFY) != 0 );
2179 else
2181 LISTBOX_SetSelection( descr, index,
2182 !is_item_selected(descr, index),
2183 (descr->style & LBS_NOTIFY) != 0 );
2187 else
2189 descr->anchor_item = index;
2190 LISTBOX_MoveCaret( descr, index, FALSE );
2191 LISTBOX_SetSelection( descr, index,
2192 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2195 if (!descr->lphc)
2197 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2199 POINT pt;
2201 pt.x = x;
2202 pt.y = y;
2204 if (DragDetect( descr->self, pt ))
2205 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2208 return 0;
2212 /*************************************************************************
2213 * LISTBOX_HandleLButtonDownCombo [Internal]
2215 * Process LButtonDown message for the ComboListBox
2217 * PARAMS
2218 * pWnd [I] The windows internal structure
2219 * pDescr [I] The ListBox internal structure
2220 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2221 * x [I] X Mouse Coordinate
2222 * y [I] Y Mouse Coordinate
2224 * RETURNS
2225 * 0 since we are processing the WM_LBUTTONDOWN Message
2227 * NOTES
2228 * This function is only to be used when a ListBox is a ComboListBox
2231 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2233 RECT clientRect, screenRect;
2234 POINT mousePos;
2236 mousePos.x = x;
2237 mousePos.y = y;
2239 GetClientRect(descr->self, &clientRect);
2241 if(PtInRect(&clientRect, mousePos))
2243 /* MousePos is in client, resume normal processing */
2244 if (msg == WM_LBUTTONDOWN)
2246 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2247 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2249 else if (descr->style & LBS_NOTIFY)
2250 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2252 else
2254 POINT screenMousePos;
2255 HWND hWndOldCapture;
2257 /* Check the Non-Client Area */
2258 screenMousePos = mousePos;
2259 hWndOldCapture = GetCapture();
2260 ReleaseCapture();
2261 GetWindowRect(descr->self, &screenRect);
2262 ClientToScreen(descr->self, &screenMousePos);
2264 if(!PtInRect(&screenRect, screenMousePos))
2266 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2267 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2268 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2270 else
2272 /* Check to see the NC is a scrollbar */
2273 INT nHitTestType=0;
2274 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2275 /* Check Vertical scroll bar */
2276 if (style & WS_VSCROLL)
2278 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2279 if (PtInRect( &clientRect, mousePos ))
2280 nHitTestType = HTVSCROLL;
2282 /* Check horizontal scroll bar */
2283 if (style & WS_HSCROLL)
2285 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2286 if (PtInRect( &clientRect, mousePos ))
2287 nHitTestType = HTHSCROLL;
2289 /* Windows sends this message when a scrollbar is clicked
2292 if(nHitTestType != 0)
2294 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2295 MAKELONG(screenMousePos.x, screenMousePos.y));
2297 /* Resume the Capture after scrolling is complete
2299 if(hWndOldCapture != 0)
2300 SetCapture(hWndOldCapture);
2303 return 0;
2306 /***********************************************************************
2307 * LISTBOX_HandleLButtonUp
2309 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2311 if (LISTBOX_Timer != LB_TIMER_NONE)
2312 KillSystemTimer( descr->self, LB_TIMER_ID );
2313 LISTBOX_Timer = LB_TIMER_NONE;
2314 if (descr->captured)
2316 descr->captured = FALSE;
2317 if (GetCapture() == descr->self) ReleaseCapture();
2318 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2319 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2321 return 0;
2325 /***********************************************************************
2326 * LISTBOX_HandleTimer
2328 * Handle scrolling upon a timer event.
2329 * Return TRUE if scrolling should continue.
2331 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2333 switch(dir)
2335 case LB_TIMER_UP:
2336 if (descr->top_item) index = descr->top_item - 1;
2337 else index = 0;
2338 break;
2339 case LB_TIMER_LEFT:
2340 if (descr->top_item) index -= descr->page_size;
2341 break;
2342 case LB_TIMER_DOWN:
2343 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2344 if (index == descr->focus_item) index++;
2345 if (index >= descr->nb_items) index = descr->nb_items - 1;
2346 break;
2347 case LB_TIMER_RIGHT:
2348 if (index + descr->page_size < descr->nb_items)
2349 index += descr->page_size;
2350 break;
2351 case LB_TIMER_NONE:
2352 break;
2354 if (index == descr->focus_item) return FALSE;
2355 LISTBOX_MoveCaret( descr, index, FALSE );
2356 return TRUE;
2360 /***********************************************************************
2361 * LISTBOX_HandleSystemTimer
2363 * WM_SYSTIMER handler.
2365 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2367 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2369 KillSystemTimer( descr->self, LB_TIMER_ID );
2370 LISTBOX_Timer = LB_TIMER_NONE;
2372 return 0;
2376 /***********************************************************************
2377 * LISTBOX_HandleMouseMove
2379 * WM_MOUSEMOVE handler.
2381 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2382 INT x, INT y )
2384 INT index;
2385 TIMER_DIRECTION dir = LB_TIMER_NONE;
2387 if (!descr->captured) return;
2389 if (descr->style & LBS_MULTICOLUMN)
2391 if (y < 0) y = 0;
2392 else if (y >= descr->item_height * descr->page_size)
2393 y = descr->item_height * descr->page_size - 1;
2395 if (x < 0)
2397 dir = LB_TIMER_LEFT;
2398 x = 0;
2400 else if (x >= descr->width)
2402 dir = LB_TIMER_RIGHT;
2403 x = descr->width - 1;
2406 else
2408 if (y < 0) dir = LB_TIMER_UP; /* above */
2409 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2412 index = LISTBOX_GetItemFromPoint( descr, x, y );
2413 if (index == -1) index = descr->focus_item;
2414 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2416 /* Start/stop the system timer */
2418 if (dir != LB_TIMER_NONE)
2419 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2420 else if (LISTBOX_Timer != LB_TIMER_NONE)
2421 KillSystemTimer( descr->self, LB_TIMER_ID );
2422 LISTBOX_Timer = dir;
2426 /***********************************************************************
2427 * LISTBOX_HandleKeyDown
2429 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2431 INT caret = -1;
2432 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2433 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2434 bForceSelection = FALSE; /* only for single select list */
2436 if (descr->style & LBS_WANTKEYBOARDINPUT)
2438 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2439 MAKEWPARAM(LOWORD(key), descr->focus_item),
2440 (LPARAM)descr->self );
2441 if (caret == -2) return 0;
2443 if (caret == -1) switch(key)
2445 case VK_LEFT:
2446 if (descr->style & LBS_MULTICOLUMN)
2448 bForceSelection = FALSE;
2449 if (descr->focus_item >= descr->page_size)
2450 caret = descr->focus_item - descr->page_size;
2451 break;
2453 /* fall through */
2454 case VK_UP:
2455 caret = descr->focus_item - 1;
2456 if (caret < 0) caret = 0;
2457 break;
2458 case VK_RIGHT:
2459 if (descr->style & LBS_MULTICOLUMN)
2461 bForceSelection = FALSE;
2462 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2463 break;
2465 /* fall through */
2466 case VK_DOWN:
2467 caret = descr->focus_item + 1;
2468 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2469 break;
2471 case VK_PRIOR:
2472 if (descr->style & LBS_MULTICOLUMN)
2474 INT page = descr->width / descr->column_width;
2475 if (page < 1) page = 1;
2476 caret = descr->focus_item - (page * descr->page_size) + 1;
2478 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2479 if (caret < 0) caret = 0;
2480 break;
2481 case VK_NEXT:
2482 if (descr->style & LBS_MULTICOLUMN)
2484 INT page = descr->width / descr->column_width;
2485 if (page < 1) page = 1;
2486 caret = descr->focus_item + (page * descr->page_size) - 1;
2488 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2489 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2490 break;
2491 case VK_HOME:
2492 caret = 0;
2493 break;
2494 case VK_END:
2495 caret = descr->nb_items - 1;
2496 break;
2497 case VK_SPACE:
2498 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2499 else if (descr->style & LBS_MULTIPLESEL)
2501 LISTBOX_SetSelection( descr, descr->focus_item,
2502 !is_item_selected(descr, descr->focus_item),
2503 (descr->style & LBS_NOTIFY) != 0 );
2505 break;
2506 default:
2507 bForceSelection = FALSE;
2509 if (bForceSelection) /* focused item is used instead of key */
2510 caret = descr->focus_item;
2511 if (caret >= 0)
2513 if (((descr->style & LBS_EXTENDEDSEL) &&
2514 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2515 !IS_MULTISELECT(descr))
2516 descr->anchor_item = caret;
2517 LISTBOX_MoveCaret( descr, caret, TRUE );
2519 if (descr->style & LBS_MULTIPLESEL)
2520 descr->selected_item = caret;
2521 else
2522 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2523 if (descr->style & LBS_NOTIFY)
2525 if (descr->lphc && IsWindowVisible( descr->self ))
2527 /* make sure that combo parent doesn't hide us */
2528 descr->lphc->wState |= CBF_NOROLLUP;
2530 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2533 return 0;
2537 /***********************************************************************
2538 * LISTBOX_HandleChar
2540 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2542 INT caret = -1;
2543 WCHAR str[2];
2545 str[0] = charW;
2546 str[1] = '\0';
2548 if (descr->style & LBS_WANTKEYBOARDINPUT)
2550 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2551 MAKEWPARAM(charW, descr->focus_item),
2552 (LPARAM)descr->self );
2553 if (caret == -2) return 0;
2555 if (caret == -1 && !(descr->style & LBS_NODATA))
2556 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2557 if (caret != -1)
2559 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2560 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2561 LISTBOX_MoveCaret( descr, caret, TRUE );
2562 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2563 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2565 return 0;
2569 /***********************************************************************
2570 * LISTBOX_Create
2572 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2574 LB_DESCR *descr;
2575 MEASUREITEMSTRUCT mis;
2576 RECT rect;
2578 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2579 return FALSE;
2581 GetClientRect( hwnd, &rect );
2582 descr->self = hwnd;
2583 descr->owner = GetParent( descr->self );
2584 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2585 descr->width = rect.right - rect.left;
2586 descr->height = rect.bottom - rect.top;
2587 descr->u.items = NULL;
2588 descr->items_size = 0;
2589 descr->nb_items = 0;
2590 descr->top_item = 0;
2591 descr->selected_item = -1;
2592 descr->focus_item = 0;
2593 descr->anchor_item = -1;
2594 descr->item_height = 1;
2595 descr->page_size = 1;
2596 descr->column_width = 150;
2597 descr->horz_extent = 0;
2598 descr->horz_pos = 0;
2599 descr->nb_tabs = 0;
2600 descr->tabs = NULL;
2601 descr->wheel_remain = 0;
2602 descr->caret_on = !lphc;
2603 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2604 descr->in_focus = FALSE;
2605 descr->captured = FALSE;
2606 descr->font = 0;
2607 descr->locale = GetUserDefaultLCID();
2608 descr->lphc = lphc;
2610 if( lphc )
2612 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2613 descr->owner = lphc->self;
2616 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2618 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2620 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2621 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2622 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2623 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2624 descr->style &= ~LBS_NODATA;
2625 descr->item_height = LISTBOX_SetFont( descr, 0 );
2627 if (descr->style & LBS_OWNERDRAWFIXED)
2629 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2631 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2633 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2634 descr->item_height = lphc->fixedOwnerDrawHeight;
2636 else
2638 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2639 mis.CtlType = ODT_LISTBOX;
2640 mis.CtlID = id;
2641 mis.itemID = -1;
2642 mis.itemWidth = 0;
2643 mis.itemData = 0;
2644 mis.itemHeight = descr->item_height;
2645 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2646 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2650 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2651 return TRUE;
2655 /***********************************************************************
2656 * LISTBOX_Destroy
2658 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2660 LISTBOX_ResetContent( descr );
2661 SetWindowLongPtrW( descr->self, 0, 0 );
2662 HeapFree( GetProcessHeap(), 0, descr );
2663 return TRUE;
2667 /***********************************************************************
2668 * ListBoxWndProc_common
2670 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2672 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2673 HEADCOMBO *lphc = NULL;
2674 LRESULT ret;
2676 if (!descr)
2678 if (!IsWindow(hwnd)) return 0;
2680 if (msg == WM_CREATE)
2682 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2683 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2684 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2685 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2686 return 0;
2688 /* Ignore all other messages before we get a WM_CREATE */
2689 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2690 DefWindowProcA( hwnd, msg, wParam, lParam );
2692 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2694 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2695 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2697 switch(msg)
2699 case LB_RESETCONTENT:
2700 LISTBOX_ResetContent( descr );
2701 LISTBOX_UpdateScroll( descr );
2702 InvalidateRect( descr->self, NULL, TRUE );
2703 return 0;
2705 case LB_ADDSTRING:
2707 INT ret;
2708 LPWSTR textW;
2709 if(unicode || !HAS_STRINGS(descr))
2710 textW = (LPWSTR)lParam;
2711 else
2713 LPSTR textA = (LPSTR)lParam;
2714 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2715 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2716 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2717 else
2718 return LB_ERRSPACE;
2720 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2721 ret = LISTBOX_InsertString( descr, wParam, textW );
2722 if (!unicode && HAS_STRINGS(descr))
2723 HeapFree(GetProcessHeap(), 0, textW);
2724 return ret;
2727 case LB_INSERTSTRING:
2729 INT ret;
2730 LPWSTR textW;
2731 if(unicode || !HAS_STRINGS(descr))
2732 textW = (LPWSTR)lParam;
2733 else
2735 LPSTR textA = (LPSTR)lParam;
2736 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2737 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2738 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2739 else
2740 return LB_ERRSPACE;
2742 ret = LISTBOX_InsertString( descr, wParam, textW );
2743 if(!unicode && HAS_STRINGS(descr))
2744 HeapFree(GetProcessHeap(), 0, textW);
2745 return ret;
2748 case LB_ADDFILE:
2750 INT ret;
2751 LPWSTR textW;
2752 if(unicode || !HAS_STRINGS(descr))
2753 textW = (LPWSTR)lParam;
2754 else
2756 LPSTR textA = (LPSTR)lParam;
2757 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2758 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2759 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2760 else
2761 return LB_ERRSPACE;
2763 wParam = LISTBOX_FindFileStrPos( descr, textW );
2764 ret = LISTBOX_InsertString( descr, wParam, textW );
2765 if(!unicode && HAS_STRINGS(descr))
2766 HeapFree(GetProcessHeap(), 0, textW);
2767 return ret;
2770 case LB_DELETESTRING:
2771 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2772 return descr->nb_items;
2773 else
2775 SetLastError(ERROR_INVALID_INDEX);
2776 return LB_ERR;
2779 case LB_GETITEMDATA:
2780 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2782 SetLastError(ERROR_INVALID_INDEX);
2783 return LB_ERR;
2785 return get_item_data(descr, wParam);
2787 case LB_SETITEMDATA:
2788 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2790 SetLastError(ERROR_INVALID_INDEX);
2791 return LB_ERR;
2793 set_item_data(descr, wParam, lParam);
2794 /* undocumented: returns TRUE, not LB_OKAY (0) */
2795 return TRUE;
2797 case LB_GETCOUNT:
2798 return descr->nb_items;
2800 case LB_GETTEXT:
2801 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2803 case LB_GETTEXTLEN:
2804 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2806 SetLastError(ERROR_INVALID_INDEX);
2807 return LB_ERR;
2809 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2810 if (unicode) return strlenW(get_item_string(descr, wParam));
2811 return WideCharToMultiByte( CP_ACP, 0, get_item_string(descr, wParam),
2812 strlenW(get_item_string(descr, wParam)), NULL, 0, NULL, NULL );
2814 case LB_GETCURSEL:
2815 if (descr->nb_items == 0)
2816 return LB_ERR;
2817 if (!IS_MULTISELECT(descr))
2818 return descr->selected_item;
2819 if (descr->selected_item != -1)
2820 return descr->selected_item;
2821 return descr->focus_item;
2822 /* otherwise, if the user tries to move the selection with the */
2823 /* arrow keys, we will give the application something to choke on */
2824 case LB_GETTOPINDEX:
2825 return descr->top_item;
2827 case LB_GETITEMHEIGHT:
2828 return LISTBOX_GetItemHeight( descr, wParam );
2830 case LB_SETITEMHEIGHT:
2831 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2833 case LB_ITEMFROMPOINT:
2835 POINT pt;
2836 RECT rect;
2837 int index;
2838 BOOL hit = TRUE;
2840 /* The hiword of the return value is not a client area
2841 hittest as suggested by MSDN, but rather a hittest on
2842 the returned listbox item. */
2844 if(descr->nb_items == 0)
2845 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2847 pt.x = (short)LOWORD(lParam);
2848 pt.y = (short)HIWORD(lParam);
2850 SetRect(&rect, 0, 0, descr->width, descr->height);
2852 if(!PtInRect(&rect, pt))
2854 pt.x = min(pt.x, rect.right - 1);
2855 pt.x = max(pt.x, 0);
2856 pt.y = min(pt.y, rect.bottom - 1);
2857 pt.y = max(pt.y, 0);
2858 hit = FALSE;
2861 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2863 if(index == -1)
2865 index = descr->nb_items - 1;
2866 hit = FALSE;
2868 return MAKELONG(index, hit ? 0 : 1);
2871 case LB_SETCARETINDEX:
2872 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2873 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2874 return LB_ERR;
2875 else if (ISWIN31)
2876 return wParam;
2877 else
2878 return LB_OKAY;
2880 case LB_GETCARETINDEX:
2881 return descr->focus_item;
2883 case LB_SETTOPINDEX:
2884 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2886 case LB_SETCOLUMNWIDTH:
2887 return LISTBOX_SetColumnWidth( descr, wParam );
2889 case LB_GETITEMRECT:
2890 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2892 case LB_FINDSTRING:
2894 INT ret;
2895 LPWSTR textW;
2896 if(unicode || !HAS_STRINGS(descr))
2897 textW = (LPWSTR)lParam;
2898 else
2900 LPSTR textA = (LPSTR)lParam;
2901 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2902 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2903 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2905 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2906 if(!unicode && HAS_STRINGS(descr))
2907 HeapFree(GetProcessHeap(), 0, textW);
2908 return ret;
2911 case LB_FINDSTRINGEXACT:
2913 INT ret;
2914 LPWSTR textW;
2915 if(unicode || !HAS_STRINGS(descr))
2916 textW = (LPWSTR)lParam;
2917 else
2919 LPSTR textA = (LPSTR)lParam;
2920 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2921 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2922 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2924 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2925 if(!unicode && HAS_STRINGS(descr))
2926 HeapFree(GetProcessHeap(), 0, textW);
2927 return ret;
2930 case LB_SELECTSTRING:
2932 INT index;
2933 LPWSTR textW;
2935 if(HAS_STRINGS(descr))
2936 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2937 debugstr_a((LPSTR)lParam));
2938 if(unicode || !HAS_STRINGS(descr))
2939 textW = (LPWSTR)lParam;
2940 else
2942 LPSTR textA = (LPSTR)lParam;
2943 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2944 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2945 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2947 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2948 if(!unicode && HAS_STRINGS(descr))
2949 HeapFree(GetProcessHeap(), 0, textW);
2950 if (index != LB_ERR)
2952 LISTBOX_MoveCaret( descr, index, TRUE );
2953 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2955 return index;
2958 case LB_GETSEL:
2959 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2960 return LB_ERR;
2961 return is_item_selected(descr, wParam);
2963 case LB_SETSEL:
2964 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2965 if (ret != LB_ERR && wParam)
2967 descr->anchor_item = lParam;
2968 if (lParam != -1)
2969 LISTBOX_SetCaretIndex( descr, lParam, TRUE );
2971 return ret;
2973 case LB_SETCURSEL:
2974 if (IS_MULTISELECT(descr)) return LB_ERR;
2975 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2976 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2977 if (ret != LB_ERR) ret = descr->selected_item;
2978 return ret;
2980 case LB_GETSELCOUNT:
2981 return LISTBOX_GetSelCount( descr );
2983 case LB_GETSELITEMS:
2984 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2986 case LB_SELITEMRANGE:
2987 if (LOWORD(lParam) <= HIWORD(lParam))
2988 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2989 HIWORD(lParam), wParam );
2990 else
2991 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2992 LOWORD(lParam), wParam );
2994 case LB_SELITEMRANGEEX:
2995 if ((INT)lParam >= (INT)wParam)
2996 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2997 else
2998 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
3000 case LB_GETHORIZONTALEXTENT:
3001 return descr->horz_extent;
3003 case LB_SETHORIZONTALEXTENT:
3004 return LISTBOX_SetHorizontalExtent( descr, wParam );
3006 case LB_GETANCHORINDEX:
3007 return descr->anchor_item;
3009 case LB_SETANCHORINDEX:
3010 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
3012 SetLastError(ERROR_INVALID_INDEX);
3013 return LB_ERR;
3015 descr->anchor_item = (INT)wParam;
3016 return LB_OKAY;
3018 case LB_DIR:
3020 INT ret;
3021 LPWSTR textW;
3022 if(unicode)
3023 textW = (LPWSTR)lParam;
3024 else
3026 LPSTR textA = (LPSTR)lParam;
3027 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3028 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3029 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3031 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3032 if(!unicode)
3033 HeapFree(GetProcessHeap(), 0, textW);
3034 return ret;
3037 case LB_GETLOCALE:
3038 return descr->locale;
3040 case LB_SETLOCALE:
3042 LCID ret;
3043 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3044 return LB_ERR;
3045 ret = descr->locale;
3046 descr->locale = (LCID)wParam;
3047 return ret;
3050 case LB_INITSTORAGE:
3051 return LISTBOX_InitStorage( descr, wParam );
3053 case LB_SETCOUNT:
3054 return LISTBOX_SetCount( descr, (INT)wParam );
3056 case LB_SETTABSTOPS:
3057 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
3059 case LB_CARETON:
3060 if (descr->caret_on)
3061 return LB_OKAY;
3062 descr->caret_on = TRUE;
3063 if ((descr->focus_item != -1) && (descr->in_focus))
3064 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3065 return LB_OKAY;
3067 case LB_CARETOFF:
3068 if (!descr->caret_on)
3069 return LB_OKAY;
3070 descr->caret_on = FALSE;
3071 if ((descr->focus_item != -1) && (descr->in_focus))
3072 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3073 return LB_OKAY;
3075 case LB_GETLISTBOXINFO:
3076 return descr->page_size;
3078 case WM_DESTROY:
3079 return LISTBOX_Destroy( descr );
3081 case WM_ENABLE:
3082 InvalidateRect( descr->self, NULL, TRUE );
3083 return 0;
3085 case WM_SETREDRAW:
3086 LISTBOX_SetRedraw( descr, wParam != 0 );
3087 return 0;
3089 case WM_GETDLGCODE:
3090 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3092 case WM_PRINTCLIENT:
3093 case WM_PAINT:
3095 PAINTSTRUCT ps;
3096 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3097 ret = LISTBOX_Paint( descr, hdc );
3098 if( !wParam ) EndPaint( descr->self, &ps );
3100 return ret;
3101 case WM_SIZE:
3102 LISTBOX_UpdateSize( descr );
3103 return 0;
3104 case WM_GETFONT:
3105 return (LRESULT)descr->font;
3106 case WM_SETFONT:
3107 LISTBOX_SetFont( descr, (HFONT)wParam );
3108 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3109 return 0;
3110 case WM_SETFOCUS:
3111 descr->in_focus = TRUE;
3112 descr->caret_on = TRUE;
3113 if (descr->focus_item != -1)
3114 LISTBOX_DrawFocusRect( descr, TRUE );
3115 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3116 return 0;
3117 case WM_KILLFOCUS:
3118 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3119 descr->in_focus = FALSE;
3120 descr->wheel_remain = 0;
3121 if ((descr->focus_item != -1) && descr->caret_on)
3122 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3123 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3124 return 0;
3125 case WM_HSCROLL:
3126 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3127 case WM_VSCROLL:
3128 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3129 case WM_MOUSEWHEEL:
3130 if (wParam & (MK_SHIFT | MK_CONTROL))
3131 return DefWindowProcW( descr->self, msg, wParam, lParam );
3132 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3133 case WM_LBUTTONDOWN:
3134 if (lphc)
3135 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3136 (INT16)LOWORD(lParam),
3137 (INT16)HIWORD(lParam) );
3138 return LISTBOX_HandleLButtonDown( descr, wParam,
3139 (INT16)LOWORD(lParam),
3140 (INT16)HIWORD(lParam) );
3141 case WM_LBUTTONDBLCLK:
3142 if (lphc)
3143 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3144 (INT16)LOWORD(lParam),
3145 (INT16)HIWORD(lParam) );
3146 if (descr->style & LBS_NOTIFY)
3147 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3148 return 0;
3149 case WM_MOUSEMOVE:
3150 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3152 BOOL captured = descr->captured;
3153 POINT mousePos;
3154 RECT clientRect;
3156 mousePos.x = (INT16)LOWORD(lParam);
3157 mousePos.y = (INT16)HIWORD(lParam);
3160 * If we are in a dropdown combobox, we simulate that
3161 * the mouse is captured to show the tracking of the item.
3163 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3164 descr->captured = TRUE;
3166 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3168 descr->captured = captured;
3170 else if (GetCapture() == descr->self)
3172 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3173 (INT16)HIWORD(lParam) );
3175 return 0;
3176 case WM_LBUTTONUP:
3177 if (lphc)
3179 POINT mousePos;
3180 RECT clientRect;
3183 * If the mouse button "up" is not in the listbox,
3184 * we make sure there is no selection by re-selecting the
3185 * item that was selected when the listbox was made visible.
3187 mousePos.x = (INT16)LOWORD(lParam);
3188 mousePos.y = (INT16)HIWORD(lParam);
3190 GetClientRect(descr->self, &clientRect);
3193 * When the user clicks outside the combobox and the focus
3194 * is lost, the owning combobox will send a fake buttonup with
3195 * 0xFFFFFFF as the mouse location, we must also revert the
3196 * selection to the original selection.
3198 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3199 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3201 return LISTBOX_HandleLButtonUp( descr );
3202 case WM_KEYDOWN:
3203 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3205 /* for some reason Windows makes it possible to
3206 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3208 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3209 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3210 && (wParam == VK_DOWN || wParam == VK_UP)) )
3212 COMBO_FlipListbox( lphc, FALSE, FALSE );
3213 return 0;
3216 return LISTBOX_HandleKeyDown( descr, wParam );
3217 case WM_CHAR:
3219 WCHAR charW;
3220 if(unicode)
3221 charW = (WCHAR)wParam;
3222 else
3224 CHAR charA = (CHAR)wParam;
3225 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3227 return LISTBOX_HandleChar( descr, charW );
3229 case WM_SYSTIMER:
3230 return LISTBOX_HandleSystemTimer( descr );
3231 case WM_ERASEBKGND:
3232 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3234 RECT rect;
3235 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3236 wParam, (LPARAM)descr->self );
3237 TRACE("hbrush = %p\n", hbrush);
3238 if(!hbrush)
3239 hbrush = GetSysColorBrush(COLOR_WINDOW);
3240 if(hbrush)
3242 GetClientRect(descr->self, &rect);
3243 FillRect((HDC)wParam, &rect, hbrush);
3246 return 1;
3247 case WM_DROPFILES:
3248 if( lphc ) return 0;
3249 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3250 SendMessageA( descr->owner, msg, wParam, lParam );
3252 case WM_NCDESTROY:
3253 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3254 lphc->hWndLBox = 0;
3255 break;
3257 case WM_NCACTIVATE:
3258 if (lphc) return 0;
3259 break;
3261 default:
3262 if ((msg >= WM_USER) && (msg < 0xc000))
3263 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3264 hwnd, msg, wParam, lParam );
3267 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3268 DefWindowProcA( hwnd, msg, wParam, lParam );
3271 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3273 TRACE("%p\n", hwnd);
3274 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);