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 * This code was audited for completeness against the documented features
23 * of Comctl32.dll version 6.0 on Oct. 9, 2004, by Dimitrie O. Paun.
25 * Unless otherwise noted, we believe this code to be complete, as per
26 * the specification mentioned above.
27 * If you discover missing features, or bugs, please note them below.
42 #include "wine/unicode.h"
43 #include "user_private.h"
45 #include "wine/exception.h"
46 #include "wine/debug.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
50 /* Items array granularity */
51 #define LB_ARRAY_GRANULARITY 16
53 /* Scrolling timeout in ms */
54 #define LB_SCROLL_TIMEOUT 50
56 /* Listbox system timer id */
59 /* flag listbox changed while setredraw false - internal style */
60 #define LBS_DISPLAYCHANGED 0x80000000
65 LPWSTR str; /* Item text */
66 BOOL selected; /* Is item selected? */
67 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
68 ULONG_PTR data; /* User data */
71 /* Listbox structure */
74 HWND self; /* Our own window handle */
75 HWND owner; /* Owner window to send notifications to */
76 UINT style; /* Window style */
77 INT width; /* Window width */
78 INT height; /* Window height */
79 LB_ITEMDATA *items; /* Array of items */
80 INT nb_items; /* Number of items */
81 INT top_item; /* Top visible item */
82 INT selected_item; /* Selected item */
83 INT focus_item; /* Item that has the focus */
84 INT anchor_item; /* Anchor item for extended selection */
85 INT item_height; /* Default item height */
86 INT page_size; /* Items per listbox page */
87 INT column_width; /* Column width for multi-column listboxes */
88 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
89 INT horz_pos; /* Horizontal position */
90 INT nb_tabs; /* Number of tabs in array */
91 INT *tabs; /* Array of tabs */
92 INT avg_char_width; /* Average width of characters */
93 BOOL caret_on; /* Is caret on? */
94 BOOL captured; /* Is mouse captured? */
96 HFONT font; /* Current font */
97 LCID locale; /* Current locale for string comparisons */
98 LPHEADCOMBO lphc; /* ComboLBox */
102 #define IS_OWNERDRAW(descr) \
103 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
105 #define HAS_STRINGS(descr) \
106 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
109 #define IS_MULTISELECT(descr) \
110 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
111 !((descr)->style & LBS_NOSEL))
113 #define SEND_NOTIFICATION(descr,code) \
114 (SendMessageW( (descr)->owner, WM_COMMAND, \
115 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
117 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
119 /* Current timer status */
129 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
131 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
133 /*********************************************************************
134 * listbox class descriptor
136 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
137 const struct builtin_class_descr LISTBOX_builtin_class =
140 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
141 WINPROC_LISTBOX, /* proc */
142 sizeof(LB_DESCR *), /* extra */
143 IDC_ARROW, /* cursor */
148 /*********************************************************************
149 * combolbox class descriptor
151 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
152 const struct builtin_class_descr COMBOLBOX_builtin_class =
154 combolboxW, /* name */
155 CS_DBLCLKS | CS_SAVEBITS, /* style */
156 WINPROC_LISTBOX, /* proc */
157 sizeof(LB_DESCR *), /* extra */
158 IDC_ARROW, /* cursor */
163 /***********************************************************************
164 * LISTBOX_GetCurrentPageSize
166 * Return the current page size
168 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
171 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
172 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
174 if ((height += descr->items[i].height) > descr->height) break;
176 if (i == descr->top_item) return 1;
177 else return i - descr->top_item;
181 /***********************************************************************
182 * LISTBOX_GetMaxTopIndex
184 * Return the maximum possible index for the top of the listbox.
186 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
190 if (descr->style & LBS_OWNERDRAWVARIABLE)
192 page = descr->height;
193 for (max = descr->nb_items - 1; max >= 0; max--)
194 if ((page -= descr->items[max].height) < 0) break;
195 if (max < descr->nb_items - 1) max++;
197 else if (descr->style & LBS_MULTICOLUMN)
199 if ((page = descr->width / descr->column_width) < 1) page = 1;
200 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
201 max = (max - page) * descr->page_size;
205 max = descr->nb_items - descr->page_size;
207 if (max < 0) max = 0;
212 /***********************************************************************
213 * LISTBOX_UpdateScroll
215 * Update the scrollbars. Should be called whenever the content
216 * of the listbox changes.
218 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
222 /* Check the listbox scroll bar flags individually before we call
223 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
224 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
225 scroll bar when we do not need one.
226 if (!(descr->style & WS_VSCROLL)) return;
229 /* It is important that we check descr->style, and not wnd->dwStyle,
230 for WS_VSCROLL, as the former is exactly the one passed in
231 argument to CreateWindow.
232 In Windows (and from now on in Wine :) a listbox created
233 with such a style (no WS_SCROLL) does not update
234 the scrollbar with listbox-related data, thus letting
235 the programmer use it for his/her own purposes. */
237 if (descr->style & LBS_NOREDRAW) return;
238 info.cbSize = sizeof(info);
240 if (descr->style & LBS_MULTICOLUMN)
243 info.nMax = (descr->nb_items - 1) / descr->page_size;
244 info.nPos = descr->top_item / descr->page_size;
245 info.nPage = descr->width / descr->column_width;
246 if (info.nPage < 1) info.nPage = 1;
247 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
248 if (descr->style & LBS_DISABLENOSCROLL)
249 info.fMask |= SIF_DISABLENOSCROLL;
250 if (descr->style & WS_HSCROLL)
251 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
253 info.fMask = SIF_RANGE;
254 if (descr->style & WS_VSCROLL)
255 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
260 info.nMax = descr->nb_items - 1;
261 info.nPos = descr->top_item;
262 info.nPage = LISTBOX_GetCurrentPageSize( descr );
263 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
264 if (descr->style & LBS_DISABLENOSCROLL)
265 info.fMask |= SIF_DISABLENOSCROLL;
266 if (descr->style & WS_VSCROLL)
267 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
269 if (descr->horz_extent)
272 info.nMax = descr->horz_extent - 1;
273 info.nPos = descr->horz_pos;
274 info.nPage = descr->width;
275 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
276 if (descr->style & LBS_DISABLENOSCROLL)
277 info.fMask |= SIF_DISABLENOSCROLL;
278 if (descr->style & WS_HSCROLL)
279 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
285 /***********************************************************************
288 * Set the top item of the listbox, scrolling up or down if necessary.
290 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
292 INT max = LISTBOX_GetMaxTopIndex( descr );
294 TRACE("setting top item %d, scroll %d\n", index, scroll);
296 if (index > max) index = max;
297 if (index < 0) index = 0;
298 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
299 if (descr->top_item == index) return LB_OKAY;
300 if (descr->style & LBS_MULTICOLUMN)
302 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
303 if (scroll && (abs(diff) < descr->width))
304 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
305 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
313 if (descr->style & LBS_OWNERDRAWVARIABLE)
317 if (index > descr->top_item)
319 for (i = index - 1; i >= descr->top_item; i--)
320 diff -= descr->items[i].height;
324 for (i = index; i < descr->top_item; i++)
325 diff += descr->items[i].height;
329 diff = (descr->top_item - index) * descr->item_height;
331 if (abs(diff) < descr->height)
332 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
333 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
337 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
338 descr->top_item = index;
339 LISTBOX_UpdateScroll( descr );
344 /***********************************************************************
347 * Update the page size. Should be called when the size of
348 * the client area or the item height changes.
350 static void LISTBOX_UpdatePage( LB_DESCR *descr )
354 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
356 if (page_size == descr->page_size) return;
357 descr->page_size = page_size;
358 if (descr->style & LBS_MULTICOLUMN)
359 InvalidateRect( descr->self, NULL, TRUE );
360 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
364 /***********************************************************************
367 * Update the size of the listbox. Should be called when the size of
368 * the client area changes.
370 static void LISTBOX_UpdateSize( LB_DESCR *descr )
374 GetClientRect( descr->self, &rect );
375 descr->width = rect.right - rect.left;
376 descr->height = rect.bottom - rect.top;
377 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
382 GetWindowRect( descr->self, &rect );
383 if(descr->item_height != 0)
384 remaining = descr->height % descr->item_height;
387 if ((descr->height > descr->item_height) && remaining)
389 TRACE("[%p]: changing height %d -> %d\n",
390 descr->self, descr->height, descr->height - remaining );
391 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
392 rect.bottom - rect.top - remaining,
393 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
397 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
398 LISTBOX_UpdatePage( descr );
399 LISTBOX_UpdateScroll( descr );
401 /* Invalidate the focused item so it will be repainted correctly */
402 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
404 InvalidateRect( descr->self, &rect, FALSE );
409 /***********************************************************************
410 * LISTBOX_GetItemRect
412 * Get the rectangle enclosing an item, in listbox client coordinates.
413 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
415 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
417 /* Index <= 0 is legal even on empty listboxes */
418 if (index && (index >= descr->nb_items))
420 memset(rect, 0, sizeof(*rect));
421 SetLastError(ERROR_INVALID_INDEX);
424 SetRect( rect, 0, 0, descr->width, descr->height );
425 if (descr->style & LBS_MULTICOLUMN)
427 INT col = (index / descr->page_size) -
428 (descr->top_item / descr->page_size);
429 rect->left += col * descr->column_width;
430 rect->right = rect->left + descr->column_width;
431 rect->top += (index % descr->page_size) * descr->item_height;
432 rect->bottom = rect->top + descr->item_height;
434 else if (descr->style & LBS_OWNERDRAWVARIABLE)
437 rect->right += descr->horz_pos;
438 if ((index >= 0) && (index < descr->nb_items))
440 if (index < descr->top_item)
442 for (i = descr->top_item-1; i >= index; i--)
443 rect->top -= descr->items[i].height;
447 for (i = descr->top_item; i < index; i++)
448 rect->top += descr->items[i].height;
450 rect->bottom = rect->top + descr->items[index].height;
456 rect->top += (index - descr->top_item) * descr->item_height;
457 rect->bottom = rect->top + descr->item_height;
458 rect->right += descr->horz_pos;
461 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
463 return ((rect->left < descr->width) && (rect->right > 0) &&
464 (rect->top < descr->height) && (rect->bottom > 0));
468 /***********************************************************************
469 * LISTBOX_GetItemFromPoint
471 * Return the item nearest from point (x,y) (in client coordinates).
473 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
475 INT index = descr->top_item;
477 if (!descr->nb_items) return -1; /* No items */
478 if (descr->style & LBS_OWNERDRAWVARIABLE)
483 while (index < descr->nb_items)
485 if ((pos += descr->items[index].height) > y) break;
494 if ((pos -= descr->items[index].height) <= y) break;
498 else if (descr->style & LBS_MULTICOLUMN)
500 if (y >= descr->item_height * descr->page_size) return -1;
501 if (y >= 0) index += y / descr->item_height;
502 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
503 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
507 index += (y / descr->item_height);
509 if (index < 0) return 0;
510 if (index >= descr->nb_items) return -1;
515 /***********************************************************************
520 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
521 INT index, UINT action, BOOL ignoreFocus )
523 LB_ITEMDATA *item = NULL;
524 if (index < descr->nb_items) item = &descr->items[index];
526 if (IS_OWNERDRAW(descr))
534 if (action == ODA_FOCUS)
535 DrawFocusRect( hdc, rect );
537 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
541 /* some programs mess with the clipping region when
542 drawing the item, *and* restore the previous region
543 after they are done, so a region has better to exist
544 else everything ends clipped */
545 GetClientRect(descr->self, &r);
546 hrgn = set_control_clipping( hdc, &r );
548 dis.CtlType = ODT_LISTBOX;
549 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
550 dis.hwndItem = descr->self;
551 dis.itemAction = action;
555 if (item->selected) dis.itemState |= ODS_SELECTED;
556 if (!ignoreFocus && (descr->focus_item == index) &&
558 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
559 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
560 dis.itemData = item->data;
562 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
563 descr->self, index, debugstr_w(item->str), action,
564 dis.itemState, wine_dbgstr_rect(rect) );
565 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
566 SelectClipRgn( hdc, hrgn );
567 if (hrgn) DeleteObject( hrgn );
571 COLORREF oldText = 0, oldBk = 0;
573 if (action == ODA_FOCUS)
575 DrawFocusRect( hdc, rect );
578 if (item && item->selected)
580 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
581 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
584 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
585 descr->self, index, item ? debugstr_w(item->str) : "", action,
586 wine_dbgstr_rect(rect) );
588 ExtTextOutW( hdc, rect->left + 1, rect->top,
589 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
590 else if (!(descr->style & LBS_USETABSTOPS))
591 ExtTextOutW( hdc, rect->left + 1, rect->top,
592 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
593 strlenW(item->str), NULL );
596 /* Output empty string to paint background in the full width. */
597 ExtTextOutW( hdc, rect->left + 1, rect->top,
598 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
599 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
600 item->str, strlenW(item->str),
601 descr->nb_tabs, descr->tabs, 0);
603 if (item && item->selected)
605 SetBkColor( hdc, oldBk );
606 SetTextColor( hdc, oldText );
608 if (!ignoreFocus && (descr->focus_item == index) &&
610 (descr->in_focus)) DrawFocusRect( hdc, rect );
615 /***********************************************************************
618 * Change the redraw flag.
620 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
624 if (!(descr->style & LBS_NOREDRAW)) return;
625 descr->style &= ~LBS_NOREDRAW;
626 if (descr->style & LBS_DISPLAYCHANGED)
627 { /* page was changed while setredraw false, refresh automatically */
628 InvalidateRect(descr->self, NULL, TRUE);
629 if ((descr->top_item + descr->page_size) > descr->nb_items)
630 { /* reset top of page if less than number of items/page */
631 descr->top_item = descr->nb_items - descr->page_size;
632 if (descr->top_item < 0) descr->top_item = 0;
634 descr->style &= ~LBS_DISPLAYCHANGED;
636 LISTBOX_UpdateScroll( descr );
638 else descr->style |= LBS_NOREDRAW;
642 /***********************************************************************
643 * LISTBOX_RepaintItem
645 * Repaint a single item synchronously.
647 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
652 HBRUSH hbrush, oldBrush = 0;
654 /* Do not repaint the item if the item is not visible */
655 if (!IsWindowVisible(descr->self)) return;
656 if (descr->style & LBS_NOREDRAW)
658 descr->style |= LBS_DISPLAYCHANGED;
661 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
662 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
663 if (descr->font) oldFont = SelectObject( hdc, descr->font );
664 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
665 (WPARAM)hdc, (LPARAM)descr->self );
666 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
667 if (!IsWindowEnabled(descr->self))
668 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
669 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
670 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
671 if (oldFont) SelectObject( hdc, oldFont );
672 if (oldBrush) SelectObject( hdc, oldBrush );
673 ReleaseDC( descr->self, hdc );
677 /***********************************************************************
678 * LISTBOX_DrawFocusRect
680 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
686 /* Do not repaint the item if the item is not visible */
687 if (!IsWindowVisible(descr->self)) return;
689 if (descr->focus_item == -1) return;
690 if (!descr->caret_on || !descr->in_focus) return;
692 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
693 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
694 if (descr->font) oldFont = SelectObject( hdc, descr->font );
695 if (!IsWindowEnabled(descr->self))
696 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
697 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
698 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, on ? FALSE : TRUE );
699 if (oldFont) SelectObject( hdc, oldFont );
700 ReleaseDC( descr->self, hdc );
704 /***********************************************************************
705 * LISTBOX_InitStorage
707 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
711 nb_items += LB_ARRAY_GRANULARITY - 1;
712 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
714 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
715 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
716 nb_items * sizeof(LB_ITEMDATA));
719 item = HeapAlloc( GetProcessHeap(), 0,
720 nb_items * sizeof(LB_ITEMDATA));
725 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
733 /***********************************************************************
734 * LISTBOX_SetTabStops
736 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
740 if (!(descr->style & LBS_USETABSTOPS))
742 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
746 HeapFree( GetProcessHeap(), 0, descr->tabs );
747 if (!(descr->nb_tabs = count))
752 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
753 descr->nb_tabs * sizeof(INT) )))
755 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
757 /* convert into "dialog units"*/
758 for (i = 0; i < descr->nb_tabs; i++)
759 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
765 /***********************************************************************
768 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
772 if ((index < 0) || (index >= descr->nb_items))
774 SetLastError(ERROR_INVALID_INDEX);
777 if (HAS_STRINGS(descr))
781 len = strlenW(descr->items[index].str);
784 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
785 NULL, 0, NULL, NULL );
788 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
790 __TRY /* hide a Delphi bug that passes a read-only buffer */
794 strcpyW( buffer, descr->items[index].str );
795 len = strlenW(buffer);
799 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
800 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
805 WARN( "got an invalid buffer (Delphi bug?)\n" );
806 SetLastError( ERROR_INVALID_PARAMETER );
812 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
818 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
820 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
821 if (ret == CSTR_LESS_THAN)
823 if (ret == CSTR_EQUAL)
825 if (ret == CSTR_GREATER_THAN)
830 /***********************************************************************
831 * LISTBOX_FindStringPos
833 * Find the nearest string located before a given string in sort order.
834 * If 'exact' is TRUE, return an error if we don't get an exact match.
836 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
838 INT index, min, max, res = -1;
840 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
842 max = descr->nb_items;
845 index = (min + max) / 2;
846 if (HAS_STRINGS(descr))
847 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
850 COMPAREITEMSTRUCT cis;
851 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
853 cis.CtlType = ODT_LISTBOX;
855 cis.hwndItem = descr->self;
856 /* note that some application (MetaStock) expects the second item
857 * to be in the listbox */
859 cis.itemData1 = (ULONG_PTR)str;
861 cis.itemData2 = descr->items[index].data;
862 cis.dwLocaleId = descr->locale;
863 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
865 if (!res) return index;
866 if (res < 0) max = index;
867 else min = index + 1;
869 return exact ? -1 : max;
873 /***********************************************************************
874 * LISTBOX_FindFileStrPos
876 * Find the nearest string located before a given string in directory
877 * sort order (i.e. first files, then directories, then drives).
879 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
881 INT min, max, res = -1;
883 if (!HAS_STRINGS(descr))
884 return LISTBOX_FindStringPos( descr, str, FALSE );
886 max = descr->nb_items;
889 INT index = (min + max) / 2;
890 LPCWSTR p = descr->items[index].str;
891 if (*p == '[') /* drive or directory */
893 if (*str != '[') res = -1;
894 else if (p[1] == '-') /* drive */
896 if (str[1] == '-') res = str[2] - p[2];
901 if (str[1] == '-') res = 1;
902 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
907 if (*str == '[') res = 1;
908 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
910 if (!res) return index;
911 if (res < 0) max = index;
912 else min = index + 1;
918 /***********************************************************************
921 * Find the item beginning with a given string.
923 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
928 if (start >= descr->nb_items) start = -1;
929 item = descr->items + start + 1;
930 if (HAS_STRINGS(descr))
932 if (!str || ! str[0] ) return LB_ERR;
935 for (i = start + 1; i < descr->nb_items; i++, item++)
936 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
937 for (i = 0, item = descr->items; i <= start; i++, item++)
938 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
942 /* Special case for drives and directories: ignore prefix */
943 #define CHECK_DRIVE(item) \
944 if ((item)->str[0] == '[') \
946 if (!strncmpiW( str, (item)->str+1, len )) return i; \
947 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
951 INT len = strlenW(str);
952 for (i = start + 1; i < descr->nb_items; i++, item++)
954 if (!strncmpiW( str, item->str, len )) return i;
957 for (i = 0, item = descr->items; i <= start; i++, item++)
959 if (!strncmpiW( str, item->str, len )) return i;
967 if (exact && (descr->style & LBS_SORT))
968 /* If sorted, use a WM_COMPAREITEM binary search */
969 return LISTBOX_FindStringPos( descr, str, TRUE );
971 /* Otherwise use a linear search */
972 for (i = start + 1; i < descr->nb_items; i++, item++)
973 if (item->data == (ULONG_PTR)str) return i;
974 for (i = 0, item = descr->items; i <= start; i++, item++)
975 if (item->data == (ULONG_PTR)str) return i;
981 /***********************************************************************
982 * LISTBOX_GetSelCount
984 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
987 const LB_ITEMDATA *item = descr->items;
989 if (!(descr->style & LBS_MULTIPLESEL) ||
990 (descr->style & LBS_NOSEL))
992 for (i = count = 0; i < descr->nb_items; i++, item++)
993 if (item->selected) count++;
998 /***********************************************************************
999 * LISTBOX_GetSelItems
1001 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1004 const LB_ITEMDATA *item = descr->items;
1006 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1007 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1008 if (item->selected) array[count++] = i;
1013 /***********************************************************************
1016 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1018 INT i, col_pos = descr->page_size - 1;
1020 RECT focusRect = {-1, -1, -1, -1};
1022 HBRUSH hbrush, oldBrush = 0;
1024 if (descr->style & LBS_NOREDRAW) return 0;
1026 SetRect( &rect, 0, 0, descr->width, descr->height );
1027 if (descr->style & LBS_MULTICOLUMN)
1028 rect.right = rect.left + descr->column_width;
1029 else if (descr->horz_pos)
1031 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1032 rect.right += descr->horz_pos;
1035 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1036 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1037 (WPARAM)hdc, (LPARAM)descr->self );
1038 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1039 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1041 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1044 /* Special case for empty listbox: paint focus rect */
1045 rect.bottom = rect.top + descr->item_height;
1046 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1047 &rect, NULL, 0, NULL );
1048 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1049 rect.top = rect.bottom;
1052 /* Paint all the item, regarding the selection
1053 Focus state will be painted after */
1055 for (i = descr->top_item; i < descr->nb_items; i++)
1057 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1058 rect.bottom = rect.top + descr->item_height;
1060 rect.bottom = rect.top + descr->items[i].height;
1062 if (i == descr->focus_item)
1064 /* keep the focus rect, to paint the focus item after */
1065 focusRect.left = rect.left;
1066 focusRect.right = rect.right;
1067 focusRect.top = rect.top;
1068 focusRect.bottom = rect.bottom;
1070 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1071 rect.top = rect.bottom;
1073 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1075 if (!IS_OWNERDRAW(descr))
1077 /* Clear the bottom of the column */
1078 if (rect.top < descr->height)
1080 rect.bottom = descr->height;
1081 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1082 &rect, NULL, 0, NULL );
1086 /* Go to the next column */
1087 rect.left += descr->column_width;
1088 rect.right += descr->column_width;
1090 col_pos = descr->page_size - 1;
1095 if (rect.top >= descr->height) break;
1099 /* Paint the focus item now */
1100 if (focusRect.top != focusRect.bottom &&
1101 descr->caret_on && descr->in_focus)
1102 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1104 if (!IS_OWNERDRAW(descr))
1106 /* Clear the remainder of the client area */
1107 if (rect.top < descr->height)
1109 rect.bottom = descr->height;
1110 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1111 &rect, NULL, 0, NULL );
1113 if (rect.right < descr->width)
1115 rect.left = rect.right;
1116 rect.right = descr->width;
1118 rect.bottom = descr->height;
1119 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1120 &rect, NULL, 0, NULL );
1123 if (oldFont) SelectObject( hdc, oldFont );
1124 if (oldBrush) SelectObject( hdc, oldBrush );
1129 /***********************************************************************
1130 * LISTBOX_InvalidateItems
1132 * Invalidate all items from a given item. If the specified item is not
1133 * visible, nothing happens.
1135 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1139 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1141 if (descr->style & LBS_NOREDRAW)
1143 descr->style |= LBS_DISPLAYCHANGED;
1146 rect.bottom = descr->height;
1147 InvalidateRect( descr->self, &rect, TRUE );
1148 if (descr->style & LBS_MULTICOLUMN)
1150 /* Repaint the other columns */
1151 rect.left = rect.right;
1152 rect.right = descr->width;
1154 InvalidateRect( descr->self, &rect, TRUE );
1159 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1163 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1164 InvalidateRect( descr->self, &rect, TRUE );
1167 /***********************************************************************
1168 * LISTBOX_GetItemHeight
1170 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1172 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1174 if ((index < 0) || (index >= descr->nb_items))
1176 SetLastError(ERROR_INVALID_INDEX);
1179 return descr->items[index].height;
1181 else return descr->item_height;
1185 /***********************************************************************
1186 * LISTBOX_SetItemHeight
1188 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1190 if (height > MAXBYTE)
1193 if (!height) height = 1;
1195 if (descr->style & LBS_OWNERDRAWVARIABLE)
1197 if ((index < 0) || (index >= descr->nb_items))
1199 SetLastError(ERROR_INVALID_INDEX);
1202 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1203 descr->items[index].height = height;
1204 LISTBOX_UpdateScroll( descr );
1206 LISTBOX_InvalidateItems( descr, index );
1208 else if (height != descr->item_height)
1210 TRACE("[%p]: new height = %d\n", descr->self, height );
1211 descr->item_height = height;
1212 LISTBOX_UpdatePage( descr );
1213 LISTBOX_UpdateScroll( descr );
1215 InvalidateRect( descr->self, 0, TRUE );
1221 /***********************************************************************
1222 * LISTBOX_SetHorizontalPos
1224 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1228 if (pos > descr->horz_extent - descr->width)
1229 pos = descr->horz_extent - descr->width;
1230 if (pos < 0) pos = 0;
1231 if (!(diff = descr->horz_pos - pos)) return;
1232 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1233 descr->horz_pos = pos;
1234 LISTBOX_UpdateScroll( descr );
1235 if (abs(diff) < descr->width)
1238 /* Invalidate the focused item so it will be repainted correctly */
1239 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1240 InvalidateRect( descr->self, &rect, TRUE );
1241 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1242 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1245 InvalidateRect( descr->self, NULL, TRUE );
1249 /***********************************************************************
1250 * LISTBOX_SetHorizontalExtent
1252 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1254 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1256 if (extent <= 0) extent = 1;
1257 if (extent == descr->horz_extent) return LB_OKAY;
1258 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1259 descr->horz_extent = extent;
1260 if (descr->horz_pos > extent - descr->width)
1261 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1263 LISTBOX_UpdateScroll( descr );
1268 /***********************************************************************
1269 * LISTBOX_SetColumnWidth
1271 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1273 if (width == descr->column_width) return LB_OKAY;
1274 TRACE("[%p]: new column width = %d\n", descr->self, width );
1275 descr->column_width = width;
1276 LISTBOX_UpdatePage( descr );
1281 /***********************************************************************
1284 * Returns the item height.
1286 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1290 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1295 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1297 ERR("unable to get DC.\n" );
1300 if (font) oldFont = SelectObject( hdc, font );
1301 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1302 if (oldFont) SelectObject( hdc, oldFont );
1303 ReleaseDC( descr->self, hdc );
1305 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1306 if (!IS_OWNERDRAW(descr))
1307 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1312 /***********************************************************************
1313 * LISTBOX_MakeItemVisible
1315 * Make sure that a given item is partially or fully visible.
1317 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1321 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1323 if (index <= descr->top_item) top = index;
1324 else if (descr->style & LBS_MULTICOLUMN)
1326 INT cols = descr->width;
1327 if (!fully) cols += descr->column_width - 1;
1328 if (cols >= descr->column_width) cols /= descr->column_width;
1330 if (index < descr->top_item + (descr->page_size * cols)) return;
1331 top = index - descr->page_size * (cols - 1);
1333 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1335 INT height = fully ? descr->items[index].height : 1;
1336 for (top = index; top > descr->top_item; top--)
1337 if ((height += descr->items[top-1].height) > descr->height) break;
1341 if (index < descr->top_item + descr->page_size) return;
1342 if (!fully && (index == descr->top_item + descr->page_size) &&
1343 (descr->height > (descr->page_size * descr->item_height))) return;
1344 top = index - descr->page_size + 1;
1346 LISTBOX_SetTopItem( descr, top, TRUE );
1349 /***********************************************************************
1350 * LISTBOX_SetCaretIndex
1353 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1356 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1358 INT oldfocus = descr->focus_item;
1360 TRACE("old focus %d, index %d\n", oldfocus, index);
1362 if (descr->style & LBS_NOSEL) return LB_ERR;
1363 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1364 if (index == oldfocus) return LB_OKAY;
1366 LISTBOX_DrawFocusRect( descr, FALSE );
1367 descr->focus_item = index;
1369 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1370 LISTBOX_DrawFocusRect( descr, TRUE );
1376 /***********************************************************************
1377 * LISTBOX_SelectItemRange
1379 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1381 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1386 /* A few sanity checks */
1388 if (descr->style & LBS_NOSEL) return LB_ERR;
1389 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1391 if (!descr->nb_items) return LB_OKAY;
1393 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1394 if (first < 0) first = 0;
1395 if (last < first) return LB_OKAY;
1397 if (on) /* Turn selection on */
1399 for (i = first; i <= last; i++)
1401 if (descr->items[i].selected) continue;
1402 descr->items[i].selected = TRUE;
1403 LISTBOX_InvalidateItemRect(descr, i);
1406 else /* Turn selection off */
1408 for (i = first; i <= last; i++)
1410 if (!descr->items[i].selected) continue;
1411 descr->items[i].selected = FALSE;
1412 LISTBOX_InvalidateItemRect(descr, i);
1418 /***********************************************************************
1419 * LISTBOX_SetSelection
1421 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1422 BOOL on, BOOL send_notify )
1424 TRACE( "cur_sel=%d index=%d notify=%s\n",
1425 descr->selected_item, index, send_notify ? "YES" : "NO" );
1427 if (descr->style & LBS_NOSEL)
1429 descr->selected_item = index;
1432 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1433 if (descr->style & LBS_MULTIPLESEL)
1435 if (index == -1) /* Select all items */
1436 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1437 else /* Only one item */
1438 return LISTBOX_SelectItemRange( descr, index, index, on );
1442 INT oldsel = descr->selected_item;
1443 if (index == oldsel) return LB_OKAY;
1444 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1445 if (index != -1) descr->items[index].selected = TRUE;
1446 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1447 descr->selected_item = index;
1448 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1449 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1450 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1452 if( descr->lphc ) /* set selection change flag for parent combo */
1453 descr->lphc->wState |= CBF_SELCHANGE;
1459 /***********************************************************************
1462 * Change the caret position and extend the selection to the new caret.
1464 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1466 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1468 if ((index < 0) || (index >= descr->nb_items))
1471 /* Important, repaint needs to be done in this order if
1472 you want to mimic Windows behavior:
1473 1. Remove the focus and paint the item
1474 2. Remove the selection and paint the item(s)
1475 3. Set the selection and repaint the item(s)
1476 4. Set the focus to 'index' and repaint the item */
1478 /* 1. remove the focus and repaint the item */
1479 LISTBOX_DrawFocusRect( descr, FALSE );
1481 /* 2. then turn off the previous selection */
1482 /* 3. repaint the new selected item */
1483 if (descr->style & LBS_EXTENDEDSEL)
1485 if (descr->anchor_item != -1)
1487 INT first = min( index, descr->anchor_item );
1488 INT last = max( index, descr->anchor_item );
1490 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1491 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1492 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1495 else if (!(descr->style & LBS_MULTIPLESEL))
1497 /* Set selection to new caret item */
1498 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1501 /* 4. repaint the new item with the focus */
1502 descr->focus_item = index;
1503 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1504 LISTBOX_DrawFocusRect( descr, TRUE );
1508 /***********************************************************************
1509 * LISTBOX_InsertItem
1511 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1512 LPWSTR str, ULONG_PTR data )
1516 INT oldfocus = descr->focus_item;
1518 if (index == -1) index = descr->nb_items;
1519 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1520 if (!descr->items) max_items = 0;
1521 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1522 if (descr->nb_items == max_items)
1524 /* We need to grow the array */
1525 max_items += LB_ARRAY_GRANULARITY;
1527 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1528 max_items * sizeof(LB_ITEMDATA) );
1530 item = HeapAlloc( GetProcessHeap(), 0,
1531 max_items * sizeof(LB_ITEMDATA) );
1534 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1537 descr->items = item;
1540 /* Insert the item structure */
1542 item = &descr->items[index];
1543 if (index < descr->nb_items)
1544 RtlMoveMemory( item + 1, item,
1545 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1549 item->selected = FALSE;
1552 /* Get item height */
1554 if (descr->style & LBS_OWNERDRAWVARIABLE)
1556 MEASUREITEMSTRUCT mis;
1557 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1559 mis.CtlType = ODT_LISTBOX;
1562 mis.itemData = descr->items[index].data;
1563 mis.itemHeight = descr->item_height;
1564 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1565 item->height = mis.itemHeight ? mis.itemHeight : 1;
1566 TRACE("[%p]: measure item %d (%s) = %d\n",
1567 descr->self, index, str ? debugstr_w(str) : "", item->height );
1570 /* Repaint the items */
1572 LISTBOX_UpdateScroll( descr );
1573 LISTBOX_InvalidateItems( descr, index );
1575 /* Move selection and focused item */
1576 /* If listbox was empty, set focus to the first item */
1577 if (descr->nb_items == 1)
1578 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1579 /* single select don't change selection index in win31 */
1580 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1582 descr->selected_item++;
1583 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1587 if (index <= descr->selected_item)
1589 descr->selected_item++;
1590 descr->focus_item = oldfocus; /* focus not changed */
1597 /***********************************************************************
1598 * LISTBOX_InsertString
1600 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1602 LPWSTR new_str = NULL;
1606 if (HAS_STRINGS(descr))
1608 static const WCHAR empty_stringW[] = { 0 };
1609 if (!str) str = empty_stringW;
1610 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1612 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1615 strcpyW(new_str, str);
1617 else data = (ULONG_PTR)str;
1619 if (index == -1) index = descr->nb_items;
1620 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1622 HeapFree( GetProcessHeap(), 0, new_str );
1626 TRACE("[%p]: added item %d %s\n",
1627 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1632 /***********************************************************************
1633 * LISTBOX_DeleteItem
1635 * Delete the content of an item. 'index' must be a valid index.
1637 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1639 /* save the item data before it gets freed by LB_RESETCONTENT */
1640 ULONG_PTR item_data = descr->items[index].data;
1641 LPWSTR item_str = descr->items[index].str;
1643 if (!descr->nb_items)
1644 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1646 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1647 * while Win95 sends it for all items with user data.
1648 * It's probably better to send it too often than not
1649 * often enough, so this is what we do here.
1651 if (IS_OWNERDRAW(descr) || item_data)
1653 DELETEITEMSTRUCT dis;
1654 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1656 dis.CtlType = ODT_LISTBOX;
1659 dis.hwndItem = descr->self;
1660 dis.itemData = item_data;
1661 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1663 if (HAS_STRINGS(descr))
1664 HeapFree( GetProcessHeap(), 0, item_str );
1668 /***********************************************************************
1669 * LISTBOX_RemoveItem
1671 * Remove an item from the listbox and delete its content.
1673 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1678 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1680 /* We need to invalidate the original rect instead of the updated one. */
1681 LISTBOX_InvalidateItems( descr, index );
1684 LISTBOX_DeleteItem( descr, index );
1686 if (!descr->nb_items) return LB_OKAY;
1688 /* Remove the item */
1690 item = &descr->items[index];
1691 if (index < descr->nb_items)
1692 RtlMoveMemory( item, item + 1,
1693 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1694 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1696 /* Shrink the item array if possible */
1698 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1699 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1701 max_items -= LB_ARRAY_GRANULARITY;
1702 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1703 max_items * sizeof(LB_ITEMDATA) );
1704 if (item) descr->items = item;
1706 /* Repaint the items */
1708 LISTBOX_UpdateScroll( descr );
1709 /* if we removed the scrollbar, reset the top of the list
1710 (correct for owner-drawn ???) */
1711 if (descr->nb_items == descr->page_size)
1712 LISTBOX_SetTopItem( descr, 0, TRUE );
1714 /* Move selection and focused item */
1715 if (!IS_MULTISELECT(descr))
1717 if (index == descr->selected_item)
1718 descr->selected_item = -1;
1719 else if (index < descr->selected_item)
1721 descr->selected_item--;
1722 if (ISWIN31) /* win 31 do not change the selected item number */
1723 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1727 if (descr->focus_item >= descr->nb_items)
1729 descr->focus_item = descr->nb_items - 1;
1730 if (descr->focus_item < 0) descr->focus_item = 0;
1736 /***********************************************************************
1737 * LISTBOX_ResetContent
1739 static void LISTBOX_ResetContent( LB_DESCR *descr )
1743 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1744 HeapFree( GetProcessHeap(), 0, descr->items );
1745 descr->nb_items = 0;
1746 descr->top_item = 0;
1747 descr->selected_item = -1;
1748 descr->focus_item = 0;
1749 descr->anchor_item = -1;
1750 descr->items = NULL;
1754 /***********************************************************************
1757 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1761 if (HAS_STRINGS(descr))
1763 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1767 /* FIXME: this is far from optimal... */
1768 if (count > descr->nb_items)
1770 while (count > descr->nb_items)
1771 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1774 else if (count < descr->nb_items)
1776 while (count < descr->nb_items)
1777 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1784 /***********************************************************************
1787 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1788 LPCWSTR filespec, BOOL long_names )
1791 LRESULT ret = LB_OKAY;
1792 WIN32_FIND_DATAW entry;
1794 LRESULT maxinsert = LB_ERR;
1796 /* don't scan directory if we just want drives exclusively */
1797 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1798 /* scan directory */
1799 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1801 int le = GetLastError();
1802 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1809 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1811 static const WCHAR bracketW[] = { ']',0 };
1812 static const WCHAR dotW[] = { '.',0 };
1813 if (!(attrib & DDL_DIRECTORY) ||
1814 !strcmpW( entry.cFileName, dotW )) continue;
1816 if (!long_names && entry.cAlternateFileName[0])
1817 strcpyW( buffer + 1, entry.cAlternateFileName );
1819 strcpyW( buffer + 1, entry.cFileName );
1820 strcatW(buffer, bracketW);
1822 else /* not a directory */
1824 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1825 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1827 if ((attrib & DDL_EXCLUSIVE) &&
1828 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1831 if (!long_names && entry.cAlternateFileName[0])
1832 strcpyW( buffer, entry.cAlternateFileName );
1834 strcpyW( buffer, entry.cFileName );
1836 if (!long_names) CharLowerW( buffer );
1837 pos = LISTBOX_FindFileStrPos( descr, buffer );
1838 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1840 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1841 } while (FindNextFileW( handle, &entry ));
1842 FindClose( handle );
1850 if (attrib & DDL_DRIVES)
1852 WCHAR buffer[] = {'[','-','a','-',']',0};
1853 WCHAR root[] = {'A',':','\\',0};
1855 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1857 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1858 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1867 /***********************************************************************
1868 * LISTBOX_HandleVScroll
1870 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1874 if (descr->style & LBS_MULTICOLUMN) return 0;
1878 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1881 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1884 LISTBOX_SetTopItem( descr, descr->top_item -
1885 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1888 LISTBOX_SetTopItem( descr, descr->top_item +
1889 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1891 case SB_THUMBPOSITION:
1892 LISTBOX_SetTopItem( descr, pos, TRUE );
1895 info.cbSize = sizeof(info);
1896 info.fMask = SIF_TRACKPOS;
1897 GetScrollInfo( descr->self, SB_VERT, &info );
1898 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1901 LISTBOX_SetTopItem( descr, 0, TRUE );
1904 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1911 /***********************************************************************
1912 * LISTBOX_HandleHScroll
1914 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1919 if (descr->style & LBS_MULTICOLUMN)
1924 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1928 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1932 page = descr->width / descr->column_width;
1933 if (page < 1) page = 1;
1934 LISTBOX_SetTopItem( descr,
1935 descr->top_item - page * descr->page_size, TRUE );
1938 page = descr->width / descr->column_width;
1939 if (page < 1) page = 1;
1940 LISTBOX_SetTopItem( descr,
1941 descr->top_item + page * descr->page_size, TRUE );
1943 case SB_THUMBPOSITION:
1944 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1947 info.cbSize = sizeof(info);
1948 info.fMask = SIF_TRACKPOS;
1949 GetScrollInfo( descr->self, SB_VERT, &info );
1950 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1954 LISTBOX_SetTopItem( descr, 0, TRUE );
1957 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1961 else if (descr->horz_extent)
1966 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1969 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1972 LISTBOX_SetHorizontalPos( descr,
1973 descr->horz_pos - descr->width );
1976 LISTBOX_SetHorizontalPos( descr,
1977 descr->horz_pos + descr->width );
1979 case SB_THUMBPOSITION:
1980 LISTBOX_SetHorizontalPos( descr, pos );
1983 info.cbSize = sizeof(info);
1984 info.fMask = SIF_TRACKPOS;
1985 GetScrollInfo( descr->self, SB_HORZ, &info );
1986 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1989 LISTBOX_SetHorizontalPos( descr, 0 );
1992 LISTBOX_SetHorizontalPos( descr,
1993 descr->horz_extent - descr->width );
2000 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2002 short gcWheelDelta = 0;
2003 UINT pulScrollLines = 3;
2005 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2007 gcWheelDelta -= delta;
2009 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2011 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2012 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2013 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2018 /***********************************************************************
2019 * LISTBOX_HandleLButtonDown
2021 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2023 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2025 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2026 descr->self, x, y, index, descr->focus_item);
2028 if (!descr->caret_on && (descr->in_focus)) return 0;
2030 if (!descr->in_focus)
2032 if( !descr->lphc ) SetFocus( descr->self );
2033 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2036 if (index == -1) return 0;
2040 if (descr->style & LBS_NOTIFY )
2041 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2042 MAKELPARAM( x, y ) );
2045 descr->captured = TRUE;
2046 SetCapture( descr->self );
2048 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2050 /* we should perhaps make sure that all items are deselected
2051 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2052 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2053 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2056 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2057 if (keys & MK_CONTROL)
2059 LISTBOX_SetCaretIndex( descr, index, FALSE );
2060 LISTBOX_SetSelection( descr, index,
2061 !descr->items[index].selected,
2062 (descr->style & LBS_NOTIFY) != 0);
2066 LISTBOX_MoveCaret( descr, index, FALSE );
2068 if (descr->style & LBS_EXTENDEDSEL)
2070 LISTBOX_SetSelection( descr, index,
2071 descr->items[index].selected,
2072 (descr->style & LBS_NOTIFY) != 0 );
2076 LISTBOX_SetSelection( descr, index,
2077 !descr->items[index].selected,
2078 (descr->style & LBS_NOTIFY) != 0 );
2084 descr->anchor_item = index;
2085 LISTBOX_MoveCaret( descr, index, FALSE );
2086 LISTBOX_SetSelection( descr, index,
2087 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2092 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2099 if (DragDetect( descr->self, pt ))
2100 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2107 /*************************************************************************
2108 * LISTBOX_HandleLButtonDownCombo [Internal]
2110 * Process LButtonDown message for the ComboListBox
2113 * pWnd [I] The windows internal structure
2114 * pDescr [I] The ListBox internal structure
2115 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2116 * x [I] X Mouse Coordinate
2117 * y [I] Y Mouse Coordinate
2120 * 0 since we are processing the WM_LBUTTONDOWN Message
2123 * This function is only to be used when a ListBox is a ComboListBox
2126 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2128 RECT clientRect, screenRect;
2134 GetClientRect(descr->self, &clientRect);
2136 if(PtInRect(&clientRect, mousePos))
2138 /* MousePos is in client, resume normal processing */
2139 if (msg == WM_LBUTTONDOWN)
2141 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2142 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2144 else if (descr->style & LBS_NOTIFY)
2145 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2149 POINT screenMousePos;
2150 HWND hWndOldCapture;
2152 /* Check the Non-Client Area */
2153 screenMousePos = mousePos;
2154 hWndOldCapture = GetCapture();
2156 GetWindowRect(descr->self, &screenRect);
2157 ClientToScreen(descr->self, &screenMousePos);
2159 if(!PtInRect(&screenRect, screenMousePos))
2161 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2162 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2163 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2167 /* Check to see the NC is a scrollbar */
2169 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2170 /* Check Vertical scroll bar */
2171 if (style & WS_VSCROLL)
2173 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2174 if (PtInRect( &clientRect, mousePos ))
2175 nHitTestType = HTVSCROLL;
2177 /* Check horizontal scroll bar */
2178 if (style & WS_HSCROLL)
2180 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2181 if (PtInRect( &clientRect, mousePos ))
2182 nHitTestType = HTHSCROLL;
2184 /* Windows sends this message when a scrollbar is clicked
2187 if(nHitTestType != 0)
2189 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2190 MAKELONG(screenMousePos.x, screenMousePos.y));
2192 /* Resume the Capture after scrolling is complete
2194 if(hWndOldCapture != 0)
2195 SetCapture(hWndOldCapture);
2201 /***********************************************************************
2202 * LISTBOX_HandleLButtonUp
2204 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2206 if (LISTBOX_Timer != LB_TIMER_NONE)
2207 KillSystemTimer( descr->self, LB_TIMER_ID );
2208 LISTBOX_Timer = LB_TIMER_NONE;
2209 if (descr->captured)
2211 descr->captured = FALSE;
2212 if (GetCapture() == descr->self) ReleaseCapture();
2213 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2214 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2220 /***********************************************************************
2221 * LISTBOX_HandleTimer
2223 * Handle scrolling upon a timer event.
2224 * Return TRUE if scrolling should continue.
2226 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2231 if (descr->top_item) index = descr->top_item - 1;
2235 if (descr->top_item) index -= descr->page_size;
2238 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2239 if (index == descr->focus_item) index++;
2240 if (index >= descr->nb_items) index = descr->nb_items - 1;
2242 case LB_TIMER_RIGHT:
2243 if (index + descr->page_size < descr->nb_items)
2244 index += descr->page_size;
2249 if (index == descr->focus_item) return FALSE;
2250 LISTBOX_MoveCaret( descr, index, FALSE );
2255 /***********************************************************************
2256 * LISTBOX_HandleSystemTimer
2258 * WM_SYSTIMER handler.
2260 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2262 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2264 KillSystemTimer( descr->self, LB_TIMER_ID );
2265 LISTBOX_Timer = LB_TIMER_NONE;
2271 /***********************************************************************
2272 * LISTBOX_HandleMouseMove
2274 * WM_MOUSEMOVE handler.
2276 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2280 TIMER_DIRECTION dir = LB_TIMER_NONE;
2282 if (!descr->captured) return;
2284 if (descr->style & LBS_MULTICOLUMN)
2287 else if (y >= descr->item_height * descr->page_size)
2288 y = descr->item_height * descr->page_size - 1;
2292 dir = LB_TIMER_LEFT;
2295 else if (x >= descr->width)
2297 dir = LB_TIMER_RIGHT;
2298 x = descr->width - 1;
2303 if (y < 0) dir = LB_TIMER_UP; /* above */
2304 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2307 index = LISTBOX_GetItemFromPoint( descr, x, y );
2308 if (index == -1) index = descr->focus_item;
2309 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2311 /* Start/stop the system timer */
2313 if (dir != LB_TIMER_NONE)
2314 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2315 else if (LISTBOX_Timer != LB_TIMER_NONE)
2316 KillSystemTimer( descr->self, LB_TIMER_ID );
2317 LISTBOX_Timer = dir;
2321 /***********************************************************************
2322 * LISTBOX_HandleKeyDown
2324 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2327 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2328 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2329 bForceSelection = FALSE; /* only for single select list */
2331 if (descr->style & LBS_WANTKEYBOARDINPUT)
2333 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2334 MAKEWPARAM(LOWORD(key), descr->focus_item),
2335 (LPARAM)descr->self );
2336 if (caret == -2) return 0;
2338 if (caret == -1) switch(key)
2341 if (descr->style & LBS_MULTICOLUMN)
2343 bForceSelection = FALSE;
2344 if (descr->focus_item >= descr->page_size)
2345 caret = descr->focus_item - descr->page_size;
2350 caret = descr->focus_item - 1;
2351 if (caret < 0) caret = 0;
2354 if (descr->style & LBS_MULTICOLUMN)
2356 bForceSelection = FALSE;
2357 if (descr->focus_item + descr->page_size < descr->nb_items)
2358 caret = descr->focus_item + descr->page_size;
2363 caret = descr->focus_item + 1;
2364 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2368 if (descr->style & LBS_MULTICOLUMN)
2370 INT page = descr->width / descr->column_width;
2371 if (page < 1) page = 1;
2372 caret = descr->focus_item - (page * descr->page_size) + 1;
2374 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2375 if (caret < 0) caret = 0;
2378 if (descr->style & LBS_MULTICOLUMN)
2380 INT page = descr->width / descr->column_width;
2381 if (page < 1) page = 1;
2382 caret = descr->focus_item + (page * descr->page_size) - 1;
2384 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2385 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2391 caret = descr->nb_items - 1;
2394 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2395 else if (descr->style & LBS_MULTIPLESEL)
2397 LISTBOX_SetSelection( descr, descr->focus_item,
2398 !descr->items[descr->focus_item].selected,
2399 (descr->style & LBS_NOTIFY) != 0 );
2403 bForceSelection = FALSE;
2405 if (bForceSelection) /* focused item is used instead of key */
2406 caret = descr->focus_item;
2409 if (((descr->style & LBS_EXTENDEDSEL) &&
2410 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2411 !IS_MULTISELECT(descr))
2412 descr->anchor_item = caret;
2413 LISTBOX_MoveCaret( descr, caret, TRUE );
2415 if (descr->style & LBS_MULTIPLESEL)
2416 descr->selected_item = caret;
2418 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2419 if (descr->style & LBS_NOTIFY)
2421 if (descr->lphc && IsWindowVisible( descr->self ))
2423 /* make sure that combo parent doesn't hide us */
2424 descr->lphc->wState |= CBF_NOROLLUP;
2426 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2433 /***********************************************************************
2434 * LISTBOX_HandleChar
2436 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2444 if (descr->style & LBS_WANTKEYBOARDINPUT)
2446 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2447 MAKEWPARAM(charW, descr->focus_item),
2448 (LPARAM)descr->self );
2449 if (caret == -2) return 0;
2452 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2455 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2456 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2457 LISTBOX_MoveCaret( descr, caret, TRUE );
2458 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2459 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2465 /***********************************************************************
2468 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2471 MEASUREITEMSTRUCT mis;
2474 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2477 GetClientRect( hwnd, &rect );
2479 descr->owner = GetParent( descr->self );
2480 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2481 descr->width = rect.right - rect.left;
2482 descr->height = rect.bottom - rect.top;
2483 descr->items = NULL;
2484 descr->nb_items = 0;
2485 descr->top_item = 0;
2486 descr->selected_item = -1;
2487 descr->focus_item = 0;
2488 descr->anchor_item = -1;
2489 descr->item_height = 1;
2490 descr->page_size = 1;
2491 descr->column_width = 150;
2492 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2493 descr->horz_pos = 0;
2496 descr->caret_on = lphc ? FALSE : TRUE;
2497 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2498 descr->in_focus = FALSE;
2499 descr->captured = FALSE;
2501 descr->locale = GetUserDefaultLCID();
2506 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2507 descr->owner = lphc->self;
2510 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2512 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2514 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2515 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2516 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2517 descr->item_height = LISTBOX_SetFont( descr, 0 );
2519 if (descr->style & LBS_OWNERDRAWFIXED)
2521 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2523 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2524 descr->item_height = lphc->fixedOwnerDrawHeight;
2528 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2529 mis.CtlType = ODT_LISTBOX;
2534 mis.itemHeight = descr->item_height;
2535 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2536 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2540 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2545 /***********************************************************************
2548 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2550 LISTBOX_ResetContent( descr );
2551 SetWindowLongPtrW( descr->self, 0, 0 );
2552 HeapFree( GetProcessHeap(), 0, descr );
2557 /***********************************************************************
2558 * ListBoxWndProc_common
2560 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2562 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2563 LPHEADCOMBO lphc = 0;
2568 if (!IsWindow(hwnd)) return 0;
2570 if (msg == WM_CREATE)
2572 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2573 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2574 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2575 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2578 /* Ignore all other messages before we get a WM_CREATE */
2579 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2580 DefWindowProcA( hwnd, msg, wParam, lParam );
2582 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2584 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2585 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2589 case LB_RESETCONTENT:
2590 LISTBOX_ResetContent( descr );
2591 LISTBOX_UpdateScroll( descr );
2592 InvalidateRect( descr->self, NULL, TRUE );
2599 if(unicode || !HAS_STRINGS(descr))
2600 textW = (LPWSTR)lParam;
2603 LPSTR textA = (LPSTR)lParam;
2604 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2605 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2606 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2610 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2611 ret = LISTBOX_InsertString( descr, wParam, textW );
2612 if (!unicode && HAS_STRINGS(descr))
2613 HeapFree(GetProcessHeap(), 0, textW);
2617 case LB_INSERTSTRING:
2621 if(unicode || !HAS_STRINGS(descr))
2622 textW = (LPWSTR)lParam;
2625 LPSTR textA = (LPSTR)lParam;
2626 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2627 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2628 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2632 ret = LISTBOX_InsertString( descr, wParam, textW );
2633 if(!unicode && HAS_STRINGS(descr))
2634 HeapFree(GetProcessHeap(), 0, textW);
2642 if(unicode || !HAS_STRINGS(descr))
2643 textW = (LPWSTR)lParam;
2646 LPSTR textA = (LPSTR)lParam;
2647 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2648 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2649 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2653 wParam = LISTBOX_FindFileStrPos( descr, textW );
2654 ret = LISTBOX_InsertString( descr, wParam, textW );
2655 if(!unicode && HAS_STRINGS(descr))
2656 HeapFree(GetProcessHeap(), 0, textW);
2660 case LB_DELETESTRING:
2661 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2662 return descr->nb_items;
2665 SetLastError(ERROR_INVALID_INDEX);
2669 case LB_GETITEMDATA:
2670 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2672 SetLastError(ERROR_INVALID_INDEX);
2675 return descr->items[wParam].data;
2677 case LB_SETITEMDATA:
2678 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2680 SetLastError(ERROR_INVALID_INDEX);
2683 descr->items[wParam].data = lParam;
2684 /* undocumented: returns TRUE, not LB_OKAY (0) */
2688 return descr->nb_items;
2691 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2694 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2696 SetLastError(ERROR_INVALID_INDEX);
2699 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2700 if (unicode) return strlenW( descr->items[wParam].str );
2701 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2702 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2705 if (descr->nb_items == 0)
2707 if (!IS_MULTISELECT(descr))
2708 return descr->selected_item;
2709 if (descr->selected_item != -1)
2710 return descr->selected_item;
2711 return descr->focus_item;
2712 /* otherwise, if the user tries to move the selection with the */
2713 /* arrow keys, we will give the application something to choke on */
2714 case LB_GETTOPINDEX:
2715 return descr->top_item;
2717 case LB_GETITEMHEIGHT:
2718 return LISTBOX_GetItemHeight( descr, wParam );
2720 case LB_SETITEMHEIGHT:
2721 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2723 case LB_ITEMFROMPOINT:
2730 /* The hiword of the return value is not a client area
2731 hittest as suggested by MSDN, but rather a hittest on
2732 the returned listbox item. */
2734 if(descr->nb_items == 0)
2735 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2737 pt.x = (short)LOWORD(lParam);
2738 pt.y = (short)HIWORD(lParam);
2740 SetRect(&rect, 0, 0, descr->width, descr->height);
2742 if(!PtInRect(&rect, pt))
2744 pt.x = min(pt.x, rect.right - 1);
2745 pt.x = max(pt.x, 0);
2746 pt.y = min(pt.y, rect.bottom - 1);
2747 pt.y = max(pt.y, 0);
2751 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2755 index = descr->nb_items - 1;
2758 return MAKELONG(index, hit ? 0 : 1);
2761 case LB_SETCARETINDEX:
2762 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2763 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2770 case LB_GETCARETINDEX:
2771 return descr->focus_item;
2773 case LB_SETTOPINDEX:
2774 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2776 case LB_SETCOLUMNWIDTH:
2777 return LISTBOX_SetColumnWidth( descr, wParam );
2779 case LB_GETITEMRECT:
2780 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2786 if(unicode || !HAS_STRINGS(descr))
2787 textW = (LPWSTR)lParam;
2790 LPSTR textA = (LPSTR)lParam;
2791 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2792 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2793 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2795 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2796 if(!unicode && HAS_STRINGS(descr))
2797 HeapFree(GetProcessHeap(), 0, textW);
2801 case LB_FINDSTRINGEXACT:
2805 if(unicode || !HAS_STRINGS(descr))
2806 textW = (LPWSTR)lParam;
2809 LPSTR textA = (LPSTR)lParam;
2810 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2811 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2812 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2814 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2815 if(!unicode && HAS_STRINGS(descr))
2816 HeapFree(GetProcessHeap(), 0, textW);
2820 case LB_SELECTSTRING:
2825 if(HAS_STRINGS(descr))
2826 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2827 debugstr_a((LPSTR)lParam));
2828 if(unicode || !HAS_STRINGS(descr))
2829 textW = (LPWSTR)lParam;
2832 LPSTR textA = (LPSTR)lParam;
2833 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2834 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2835 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2837 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2838 if(!unicode && HAS_STRINGS(descr))
2839 HeapFree(GetProcessHeap(), 0, textW);
2840 if (index != LB_ERR)
2842 LISTBOX_MoveCaret( descr, index, TRUE );
2843 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2849 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2851 return descr->items[wParam].selected;
2854 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2857 if (IS_MULTISELECT(descr)) return LB_ERR;
2858 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2859 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2860 if (ret != LB_ERR) ret = descr->selected_item;
2863 case LB_GETSELCOUNT:
2864 return LISTBOX_GetSelCount( descr );
2866 case LB_GETSELITEMS:
2867 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2869 case LB_SELITEMRANGE:
2870 if (LOWORD(lParam) <= HIWORD(lParam))
2871 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2872 HIWORD(lParam), wParam );
2874 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2875 LOWORD(lParam), wParam );
2877 case LB_SELITEMRANGEEX:
2878 if ((INT)lParam >= (INT)wParam)
2879 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2881 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2883 case LB_GETHORIZONTALEXTENT:
2884 return descr->horz_extent;
2886 case LB_SETHORIZONTALEXTENT:
2887 return LISTBOX_SetHorizontalExtent( descr, wParam );
2889 case LB_GETANCHORINDEX:
2890 return descr->anchor_item;
2892 case LB_SETANCHORINDEX:
2893 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2895 SetLastError(ERROR_INVALID_INDEX);
2898 descr->anchor_item = (INT)wParam;
2906 textW = (LPWSTR)lParam;
2909 LPSTR textA = (LPSTR)lParam;
2910 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2911 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2912 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2914 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2916 HeapFree(GetProcessHeap(), 0, textW);
2921 return descr->locale;
2926 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2928 ret = descr->locale;
2929 descr->locale = (LCID)wParam;
2933 case LB_INITSTORAGE:
2934 return LISTBOX_InitStorage( descr, wParam );
2937 return LISTBOX_SetCount( descr, (INT)wParam );
2939 case LB_SETTABSTOPS:
2940 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2943 if (descr->caret_on)
2945 descr->caret_on = TRUE;
2946 if ((descr->focus_item != -1) && (descr->in_focus))
2947 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2951 if (!descr->caret_on)
2953 descr->caret_on = FALSE;
2954 if ((descr->focus_item != -1) && (descr->in_focus))
2955 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2958 case LB_GETLISTBOXINFO:
2959 FIXME("LB_GETLISTBOXINFO: stub!\n");
2963 return LISTBOX_Destroy( descr );
2966 InvalidateRect( descr->self, NULL, TRUE );
2970 LISTBOX_SetRedraw( descr, wParam != 0 );
2974 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2976 case WM_PRINTCLIENT:
2980 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2981 ret = LISTBOX_Paint( descr, hdc );
2982 if( !wParam ) EndPaint( descr->self, &ps );
2986 LISTBOX_UpdateSize( descr );
2989 return (LRESULT)descr->font;
2991 LISTBOX_SetFont( descr, (HFONT)wParam );
2992 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2995 descr->in_focus = TRUE;
2996 descr->caret_on = TRUE;
2997 if (descr->focus_item != -1)
2998 LISTBOX_DrawFocusRect( descr, TRUE );
2999 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3002 descr->in_focus = FALSE;
3003 if ((descr->focus_item != -1) && descr->caret_on)
3004 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3005 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3008 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3010 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3012 if (wParam & (MK_SHIFT | MK_CONTROL))
3013 return DefWindowProcW( descr->self, msg, wParam, lParam );
3014 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3015 case WM_LBUTTONDOWN:
3017 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3018 (INT16)LOWORD(lParam),
3019 (INT16)HIWORD(lParam) );
3020 return LISTBOX_HandleLButtonDown( descr, wParam,
3021 (INT16)LOWORD(lParam),
3022 (INT16)HIWORD(lParam) );
3023 case WM_LBUTTONDBLCLK:
3025 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3026 (INT16)LOWORD(lParam),
3027 (INT16)HIWORD(lParam) );
3028 if (descr->style & LBS_NOTIFY)
3029 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3032 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3034 BOOL captured = descr->captured;
3038 mousePos.x = (INT16)LOWORD(lParam);
3039 mousePos.y = (INT16)HIWORD(lParam);
3042 * If we are in a dropdown combobox, we simulate that
3043 * the mouse is captured to show the tracking of the item.
3045 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3046 descr->captured = TRUE;
3048 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3050 descr->captured = captured;
3052 else if (GetCapture() == descr->self)
3054 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3055 (INT16)HIWORD(lParam) );
3065 * If the mouse button "up" is not in the listbox,
3066 * we make sure there is no selection by re-selecting the
3067 * item that was selected when the listbox was made visible.
3069 mousePos.x = (INT16)LOWORD(lParam);
3070 mousePos.y = (INT16)HIWORD(lParam);
3072 GetClientRect(descr->self, &clientRect);
3075 * When the user clicks outside the combobox and the focus
3076 * is lost, the owning combobox will send a fake buttonup with
3077 * 0xFFFFFFF as the mouse location, we must also revert the
3078 * selection to the original selection.
3080 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3081 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3083 return LISTBOX_HandleLButtonUp( descr );
3085 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3087 /* for some reason Windows makes it possible to
3088 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3090 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3091 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3092 && (wParam == VK_DOWN || wParam == VK_UP)) )
3094 COMBO_FlipListbox( lphc, FALSE, FALSE );
3098 return LISTBOX_HandleKeyDown( descr, wParam );
3103 charW = (WCHAR)wParam;
3106 CHAR charA = (CHAR)wParam;
3107 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3109 return LISTBOX_HandleChar( descr, charW );
3112 return LISTBOX_HandleSystemTimer( descr );
3114 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3117 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3118 wParam, (LPARAM)descr->self );
3119 TRACE("hbrush = %p\n", hbrush);
3121 hbrush = GetSysColorBrush(COLOR_WINDOW);
3124 GetClientRect(descr->self, &rect);
3125 FillRect((HDC)wParam, &rect, hbrush);
3130 if( lphc ) return 0;
3131 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3132 SendMessageA( descr->owner, msg, wParam, lParam );
3135 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3144 if ((msg >= WM_USER) && (msg < 0xc000))
3145 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3146 hwnd, msg, wParam, lParam );
3149 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3150 DefWindowProcA( hwnd, msg, wParam, lParam );