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 INT wheel_remain; /* Left over scroll amount */
94 BOOL caret_on; /* Is caret on? */
95 BOOL captured; /* Is mouse captured? */
97 HFONT font; /* Current font */
98 LCID locale; /* Current locale for string comparisons */
99 LPHEADCOMBO lphc; /* ComboLBox */
103 #define IS_OWNERDRAW(descr) \
104 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
106 #define HAS_STRINGS(descr) \
107 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
110 #define IS_MULTISELECT(descr) \
111 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
112 !((descr)->style & LBS_NOSEL))
114 #define SEND_NOTIFICATION(descr,code) \
115 (SendMessageW( (descr)->owner, WM_COMMAND, \
116 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
118 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
120 /* Current timer status */
130 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
132 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
134 /*********************************************************************
135 * listbox class descriptor
137 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
138 const struct builtin_class_descr LISTBOX_builtin_class =
141 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
142 WINPROC_LISTBOX, /* proc */
143 sizeof(LB_DESCR *), /* extra */
144 IDC_ARROW, /* cursor */
149 /*********************************************************************
150 * combolbox class descriptor
152 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
153 const struct builtin_class_descr COMBOLBOX_builtin_class =
155 combolboxW, /* name */
156 CS_DBLCLKS | CS_SAVEBITS, /* style */
157 WINPROC_LISTBOX, /* proc */
158 sizeof(LB_DESCR *), /* extra */
159 IDC_ARROW, /* cursor */
164 /***********************************************************************
165 * LISTBOX_GetCurrentPageSize
167 * Return the current page size
169 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
172 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
173 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
175 if ((height += descr->items[i].height) > descr->height) break;
177 if (i == descr->top_item) return 1;
178 else return i - descr->top_item;
182 /***********************************************************************
183 * LISTBOX_GetMaxTopIndex
185 * Return the maximum possible index for the top of the listbox.
187 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
191 if (descr->style & LBS_OWNERDRAWVARIABLE)
193 page = descr->height;
194 for (max = descr->nb_items - 1; max >= 0; max--)
195 if ((page -= descr->items[max].height) < 0) break;
196 if (max < descr->nb_items - 1) max++;
198 else if (descr->style & LBS_MULTICOLUMN)
200 if ((page = descr->width / descr->column_width) < 1) page = 1;
201 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
202 max = (max - page) * descr->page_size;
206 max = descr->nb_items - descr->page_size;
208 if (max < 0) max = 0;
213 /***********************************************************************
214 * LISTBOX_UpdateScroll
216 * Update the scrollbars. Should be called whenever the content
217 * of the listbox changes.
219 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
223 /* Check the listbox scroll bar flags individually before we call
224 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
225 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
226 scroll bar when we do not need one.
227 if (!(descr->style & WS_VSCROLL)) return;
230 /* It is important that we check descr->style, and not wnd->dwStyle,
231 for WS_VSCROLL, as the former is exactly the one passed in
232 argument to CreateWindow.
233 In Windows (and from now on in Wine :) a listbox created
234 with such a style (no WS_SCROLL) does not update
235 the scrollbar with listbox-related data, thus letting
236 the programmer use it for his/her own purposes. */
238 if (descr->style & LBS_NOREDRAW) return;
239 info.cbSize = sizeof(info);
241 if (descr->style & LBS_MULTICOLUMN)
244 info.nMax = (descr->nb_items - 1) / descr->page_size;
245 info.nPos = descr->top_item / descr->page_size;
246 info.nPage = descr->width / descr->column_width;
247 if (info.nPage < 1) info.nPage = 1;
248 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
249 if (descr->style & LBS_DISABLENOSCROLL)
250 info.fMask |= SIF_DISABLENOSCROLL;
251 if (descr->style & WS_HSCROLL)
252 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
254 info.fMask = SIF_RANGE;
255 if (descr->style & WS_VSCROLL)
256 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
261 info.nMax = descr->nb_items - 1;
262 info.nPos = descr->top_item;
263 info.nPage = LISTBOX_GetCurrentPageSize( descr );
264 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
265 if (descr->style & LBS_DISABLENOSCROLL)
266 info.fMask |= SIF_DISABLENOSCROLL;
267 if (descr->style & WS_VSCROLL)
268 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
270 if (descr->horz_extent)
273 info.nMax = descr->horz_extent - 1;
274 info.nPos = descr->horz_pos;
275 info.nPage = descr->width;
276 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
277 if (descr->style & LBS_DISABLENOSCROLL)
278 info.fMask |= SIF_DISABLENOSCROLL;
279 if (descr->style & WS_HSCROLL)
280 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
286 /***********************************************************************
289 * Set the top item of the listbox, scrolling up or down if necessary.
291 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
293 INT max = LISTBOX_GetMaxTopIndex( descr );
295 TRACE("setting top item %d, scroll %d\n", index, scroll);
297 if (index > max) index = max;
298 if (index < 0) index = 0;
299 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
300 if (descr->top_item == index) return LB_OKAY;
304 if (descr->style & LBS_MULTICOLUMN)
305 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
306 else if (descr->style & LBS_OWNERDRAWVARIABLE)
310 if (index > descr->top_item)
312 for (i = index - 1; i >= descr->top_item; i--)
313 diff -= descr->items[i].height;
317 for (i = index; i < descr->top_item; i++)
318 diff += descr->items[i].height;
322 diff = (descr->top_item - index) * descr->item_height;
324 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
325 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
327 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
328 descr->top_item = index;
329 LISTBOX_UpdateScroll( descr );
334 /***********************************************************************
337 * Update the page size. Should be called when the size of
338 * the client area or the item height changes.
340 static void LISTBOX_UpdatePage( LB_DESCR *descr )
344 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
346 if (page_size == descr->page_size) return;
347 descr->page_size = page_size;
348 if (descr->style & LBS_MULTICOLUMN)
349 InvalidateRect( descr->self, NULL, TRUE );
350 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
354 /***********************************************************************
357 * Update the size of the listbox. Should be called when the size of
358 * the client area changes.
360 static void LISTBOX_UpdateSize( LB_DESCR *descr )
364 GetClientRect( descr->self, &rect );
365 descr->width = rect.right - rect.left;
366 descr->height = rect.bottom - rect.top;
367 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
372 GetWindowRect( descr->self, &rect );
373 if(descr->item_height != 0)
374 remaining = descr->height % descr->item_height;
377 if ((descr->height > descr->item_height) && remaining)
379 TRACE("[%p]: changing height %d -> %d\n",
380 descr->self, descr->height, descr->height - remaining );
381 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
382 rect.bottom - rect.top - remaining,
383 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
387 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
388 LISTBOX_UpdatePage( descr );
389 LISTBOX_UpdateScroll( descr );
391 /* Invalidate the focused item so it will be repainted correctly */
392 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
394 InvalidateRect( descr->self, &rect, FALSE );
399 /***********************************************************************
400 * LISTBOX_GetItemRect
402 * Get the rectangle enclosing an item, in listbox client coordinates.
403 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
405 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
407 /* Index <= 0 is legal even on empty listboxes */
408 if (index && (index >= descr->nb_items))
410 memset(rect, 0, sizeof(*rect));
411 SetLastError(ERROR_INVALID_INDEX);
414 SetRect( rect, 0, 0, descr->width, descr->height );
415 if (descr->style & LBS_MULTICOLUMN)
417 INT col = (index / descr->page_size) -
418 (descr->top_item / descr->page_size);
419 rect->left += col * descr->column_width;
420 rect->right = rect->left + descr->column_width;
421 rect->top += (index % descr->page_size) * descr->item_height;
422 rect->bottom = rect->top + descr->item_height;
424 else if (descr->style & LBS_OWNERDRAWVARIABLE)
427 rect->right += descr->horz_pos;
428 if ((index >= 0) && (index < descr->nb_items))
430 if (index < descr->top_item)
432 for (i = descr->top_item-1; i >= index; i--)
433 rect->top -= descr->items[i].height;
437 for (i = descr->top_item; i < index; i++)
438 rect->top += descr->items[i].height;
440 rect->bottom = rect->top + descr->items[index].height;
446 rect->top += (index - descr->top_item) * descr->item_height;
447 rect->bottom = rect->top + descr->item_height;
448 rect->right += descr->horz_pos;
451 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
453 return ((rect->left < descr->width) && (rect->right > 0) &&
454 (rect->top < descr->height) && (rect->bottom > 0));
458 /***********************************************************************
459 * LISTBOX_GetItemFromPoint
461 * Return the item nearest from point (x,y) (in client coordinates).
463 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
465 INT index = descr->top_item;
467 if (!descr->nb_items) return -1; /* No items */
468 if (descr->style & LBS_OWNERDRAWVARIABLE)
473 while (index < descr->nb_items)
475 if ((pos += descr->items[index].height) > y) break;
484 if ((pos -= descr->items[index].height) <= y) break;
488 else if (descr->style & LBS_MULTICOLUMN)
490 if (y >= descr->item_height * descr->page_size) return -1;
491 if (y >= 0) index += y / descr->item_height;
492 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
493 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
497 index += (y / descr->item_height);
499 if (index < 0) return 0;
500 if (index >= descr->nb_items) return -1;
505 /***********************************************************************
510 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
511 INT index, UINT action, BOOL ignoreFocus )
513 LB_ITEMDATA *item = NULL;
514 if (index < descr->nb_items) item = &descr->items[index];
516 if (IS_OWNERDRAW(descr))
524 if (action == ODA_FOCUS)
525 DrawFocusRect( hdc, rect );
527 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
531 /* some programs mess with the clipping region when
532 drawing the item, *and* restore the previous region
533 after they are done, so a region has better to exist
534 else everything ends clipped */
535 GetClientRect(descr->self, &r);
536 hrgn = set_control_clipping( hdc, &r );
538 dis.CtlType = ODT_LISTBOX;
539 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
540 dis.hwndItem = descr->self;
541 dis.itemAction = action;
545 if (item->selected) dis.itemState |= ODS_SELECTED;
546 if (!ignoreFocus && (descr->focus_item == index) &&
548 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
549 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
550 dis.itemData = item->data;
552 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
553 descr->self, index, debugstr_w(item->str), action,
554 dis.itemState, wine_dbgstr_rect(rect) );
555 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
556 SelectClipRgn( hdc, hrgn );
557 if (hrgn) DeleteObject( hrgn );
561 COLORREF oldText = 0, oldBk = 0;
563 if (action == ODA_FOCUS)
565 DrawFocusRect( hdc, rect );
568 if (item && item->selected)
570 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
571 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
574 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
575 descr->self, index, item ? debugstr_w(item->str) : "", action,
576 wine_dbgstr_rect(rect) );
578 ExtTextOutW( hdc, rect->left + 1, rect->top,
579 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
580 else if (!(descr->style & LBS_USETABSTOPS))
581 ExtTextOutW( hdc, rect->left + 1, rect->top,
582 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
583 strlenW(item->str), NULL );
586 /* Output empty string to paint background in the full width. */
587 ExtTextOutW( hdc, rect->left + 1, rect->top,
588 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
589 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
590 item->str, strlenW(item->str),
591 descr->nb_tabs, descr->tabs, 0);
593 if (item && item->selected)
595 SetBkColor( hdc, oldBk );
596 SetTextColor( hdc, oldText );
598 if (!ignoreFocus && (descr->focus_item == index) &&
600 (descr->in_focus)) DrawFocusRect( hdc, rect );
605 /***********************************************************************
608 * Change the redraw flag.
610 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
614 if (!(descr->style & LBS_NOREDRAW)) return;
615 descr->style &= ~LBS_NOREDRAW;
616 if (descr->style & LBS_DISPLAYCHANGED)
617 { /* page was changed while setredraw false, refresh automatically */
618 InvalidateRect(descr->self, NULL, TRUE);
619 if ((descr->top_item + descr->page_size) > descr->nb_items)
620 { /* reset top of page if less than number of items/page */
621 descr->top_item = descr->nb_items - descr->page_size;
622 if (descr->top_item < 0) descr->top_item = 0;
624 descr->style &= ~LBS_DISPLAYCHANGED;
626 LISTBOX_UpdateScroll( descr );
628 else descr->style |= LBS_NOREDRAW;
632 /***********************************************************************
633 * LISTBOX_RepaintItem
635 * Repaint a single item synchronously.
637 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
642 HBRUSH hbrush, oldBrush = 0;
644 /* Do not repaint the item if the item is not visible */
645 if (!IsWindowVisible(descr->self)) return;
646 if (descr->style & LBS_NOREDRAW)
648 descr->style |= LBS_DISPLAYCHANGED;
651 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
652 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
653 if (descr->font) oldFont = SelectObject( hdc, descr->font );
654 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
655 (WPARAM)hdc, (LPARAM)descr->self );
656 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
657 if (!IsWindowEnabled(descr->self))
658 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
659 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
660 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
661 if (oldFont) SelectObject( hdc, oldFont );
662 if (oldBrush) SelectObject( hdc, oldBrush );
663 ReleaseDC( descr->self, hdc );
667 /***********************************************************************
668 * LISTBOX_DrawFocusRect
670 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
676 /* Do not repaint the item if the item is not visible */
677 if (!IsWindowVisible(descr->self)) return;
679 if (descr->focus_item == -1) return;
680 if (!descr->caret_on || !descr->in_focus) return;
682 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
683 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
684 if (descr->font) oldFont = SelectObject( hdc, descr->font );
685 if (!IsWindowEnabled(descr->self))
686 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
687 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
688 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
689 if (oldFont) SelectObject( hdc, oldFont );
690 ReleaseDC( descr->self, hdc );
694 /***********************************************************************
695 * LISTBOX_InitStorage
697 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
701 nb_items += LB_ARRAY_GRANULARITY - 1;
702 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
704 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
705 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
706 nb_items * sizeof(LB_ITEMDATA));
709 item = HeapAlloc( GetProcessHeap(), 0,
710 nb_items * sizeof(LB_ITEMDATA));
715 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
723 /***********************************************************************
724 * LISTBOX_SetTabStops
726 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
730 if (!(descr->style & LBS_USETABSTOPS))
732 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
736 HeapFree( GetProcessHeap(), 0, descr->tabs );
737 if (!(descr->nb_tabs = count))
742 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
743 descr->nb_tabs * sizeof(INT) )))
745 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
747 /* convert into "dialog units"*/
748 for (i = 0; i < descr->nb_tabs; i++)
749 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
755 /***********************************************************************
758 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
762 if ((index < 0) || (index >= descr->nb_items))
764 SetLastError(ERROR_INVALID_INDEX);
767 if (HAS_STRINGS(descr))
771 len = strlenW(descr->items[index].str);
774 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
775 NULL, 0, NULL, NULL );
778 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
780 __TRY /* hide a Delphi bug that passes a read-only buffer */
784 strcpyW( buffer, descr->items[index].str );
785 len = strlenW(buffer);
789 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
790 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
795 WARN( "got an invalid buffer (Delphi bug?)\n" );
796 SetLastError( ERROR_INVALID_PARAMETER );
802 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
808 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
810 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
811 if (ret == CSTR_LESS_THAN)
813 if (ret == CSTR_EQUAL)
815 if (ret == CSTR_GREATER_THAN)
820 /***********************************************************************
821 * LISTBOX_FindStringPos
823 * Find the nearest string located before a given string in sort order.
824 * If 'exact' is TRUE, return an error if we don't get an exact match.
826 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
828 INT index, min, max, res = -1;
830 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
832 max = descr->nb_items;
835 index = (min + max) / 2;
836 if (HAS_STRINGS(descr))
837 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
840 COMPAREITEMSTRUCT cis;
841 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
843 cis.CtlType = ODT_LISTBOX;
845 cis.hwndItem = descr->self;
846 /* note that some application (MetaStock) expects the second item
847 * to be in the listbox */
849 cis.itemData1 = (ULONG_PTR)str;
851 cis.itemData2 = descr->items[index].data;
852 cis.dwLocaleId = descr->locale;
853 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
855 if (!res) return index;
856 if (res < 0) max = index;
857 else min = index + 1;
859 return exact ? -1 : max;
863 /***********************************************************************
864 * LISTBOX_FindFileStrPos
866 * Find the nearest string located before a given string in directory
867 * sort order (i.e. first files, then directories, then drives).
869 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
871 INT min, max, res = -1;
873 if (!HAS_STRINGS(descr))
874 return LISTBOX_FindStringPos( descr, str, FALSE );
876 max = descr->nb_items;
879 INT index = (min + max) / 2;
880 LPCWSTR p = descr->items[index].str;
881 if (*p == '[') /* drive or directory */
883 if (*str != '[') res = -1;
884 else if (p[1] == '-') /* drive */
886 if (str[1] == '-') res = str[2] - p[2];
891 if (str[1] == '-') res = 1;
892 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
897 if (*str == '[') res = 1;
898 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
900 if (!res) return index;
901 if (res < 0) max = index;
902 else min = index + 1;
908 /***********************************************************************
911 * Find the item beginning with a given string.
913 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
918 if (start >= descr->nb_items) start = -1;
919 item = descr->items + start + 1;
920 if (HAS_STRINGS(descr))
922 if (!str || ! str[0] ) return LB_ERR;
925 for (i = start + 1; i < descr->nb_items; i++, item++)
926 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
927 for (i = 0, item = descr->items; i <= start; i++, item++)
928 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
932 /* Special case for drives and directories: ignore prefix */
933 #define CHECK_DRIVE(item) \
934 if ((item)->str[0] == '[') \
936 if (!strncmpiW( str, (item)->str+1, len )) return i; \
937 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
941 INT len = strlenW(str);
942 for (i = start + 1; i < descr->nb_items; i++, item++)
944 if (!strncmpiW( str, item->str, len )) return i;
947 for (i = 0, item = descr->items; i <= start; i++, item++)
949 if (!strncmpiW( str, item->str, len )) return i;
957 if (exact && (descr->style & LBS_SORT))
958 /* If sorted, use a WM_COMPAREITEM binary search */
959 return LISTBOX_FindStringPos( descr, str, TRUE );
961 /* Otherwise use a linear search */
962 for (i = start + 1; i < descr->nb_items; i++, item++)
963 if (item->data == (ULONG_PTR)str) return i;
964 for (i = 0, item = descr->items; i <= start; i++, item++)
965 if (item->data == (ULONG_PTR)str) return i;
971 /***********************************************************************
972 * LISTBOX_GetSelCount
974 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
977 const LB_ITEMDATA *item = descr->items;
979 if (!(descr->style & LBS_MULTIPLESEL) ||
980 (descr->style & LBS_NOSEL))
982 for (i = count = 0; i < descr->nb_items; i++, item++)
983 if (item->selected) count++;
988 /***********************************************************************
989 * LISTBOX_GetSelItems
991 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
994 const LB_ITEMDATA *item = descr->items;
996 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
997 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
998 if (item->selected) array[count++] = i;
1003 /***********************************************************************
1006 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1008 INT i, col_pos = descr->page_size - 1;
1010 RECT focusRect = {-1, -1, -1, -1};
1012 HBRUSH hbrush, oldBrush = 0;
1014 if (descr->style & LBS_NOREDRAW) return 0;
1016 SetRect( &rect, 0, 0, descr->width, descr->height );
1017 if (descr->style & LBS_MULTICOLUMN)
1018 rect.right = rect.left + descr->column_width;
1019 else if (descr->horz_pos)
1021 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1022 rect.right += descr->horz_pos;
1025 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1026 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1027 (WPARAM)hdc, (LPARAM)descr->self );
1028 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1029 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1031 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1034 /* Special case for empty listbox: paint focus rect */
1035 rect.bottom = rect.top + descr->item_height;
1036 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1037 &rect, NULL, 0, NULL );
1038 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1039 rect.top = rect.bottom;
1042 /* Paint all the item, regarding the selection
1043 Focus state will be painted after */
1045 for (i = descr->top_item; i < descr->nb_items; i++)
1047 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1048 rect.bottom = rect.top + descr->item_height;
1050 rect.bottom = rect.top + descr->items[i].height;
1052 if (i == descr->focus_item)
1054 /* keep the focus rect, to paint the focus item after */
1055 focusRect.left = rect.left;
1056 focusRect.right = rect.right;
1057 focusRect.top = rect.top;
1058 focusRect.bottom = rect.bottom;
1060 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1061 rect.top = rect.bottom;
1063 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1065 if (!IS_OWNERDRAW(descr))
1067 /* Clear the bottom of the column */
1068 if (rect.top < descr->height)
1070 rect.bottom = descr->height;
1071 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1072 &rect, NULL, 0, NULL );
1076 /* Go to the next column */
1077 rect.left += descr->column_width;
1078 rect.right += descr->column_width;
1080 col_pos = descr->page_size - 1;
1085 if (rect.top >= descr->height) break;
1089 /* Paint the focus item now */
1090 if (focusRect.top != focusRect.bottom &&
1091 descr->caret_on && descr->in_focus)
1092 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1094 if (!IS_OWNERDRAW(descr))
1096 /* Clear the remainder of the client area */
1097 if (rect.top < descr->height)
1099 rect.bottom = descr->height;
1100 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1101 &rect, NULL, 0, NULL );
1103 if (rect.right < descr->width)
1105 rect.left = rect.right;
1106 rect.right = descr->width;
1108 rect.bottom = descr->height;
1109 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1110 &rect, NULL, 0, NULL );
1113 if (oldFont) SelectObject( hdc, oldFont );
1114 if (oldBrush) SelectObject( hdc, oldBrush );
1119 /***********************************************************************
1120 * LISTBOX_InvalidateItems
1122 * Invalidate all items from a given item. If the specified item is not
1123 * visible, nothing happens.
1125 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1129 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1131 if (descr->style & LBS_NOREDRAW)
1133 descr->style |= LBS_DISPLAYCHANGED;
1136 rect.bottom = descr->height;
1137 InvalidateRect( descr->self, &rect, TRUE );
1138 if (descr->style & LBS_MULTICOLUMN)
1140 /* Repaint the other columns */
1141 rect.left = rect.right;
1142 rect.right = descr->width;
1144 InvalidateRect( descr->self, &rect, TRUE );
1149 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1153 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1154 InvalidateRect( descr->self, &rect, TRUE );
1157 /***********************************************************************
1158 * LISTBOX_GetItemHeight
1160 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1162 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1164 if ((index < 0) || (index >= descr->nb_items))
1166 SetLastError(ERROR_INVALID_INDEX);
1169 return descr->items[index].height;
1171 else return descr->item_height;
1175 /***********************************************************************
1176 * LISTBOX_SetItemHeight
1178 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1180 if (height > MAXBYTE)
1183 if (!height) height = 1;
1185 if (descr->style & LBS_OWNERDRAWVARIABLE)
1187 if ((index < 0) || (index >= descr->nb_items))
1189 SetLastError(ERROR_INVALID_INDEX);
1192 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1193 descr->items[index].height = height;
1194 LISTBOX_UpdateScroll( descr );
1196 LISTBOX_InvalidateItems( descr, index );
1198 else if (height != descr->item_height)
1200 TRACE("[%p]: new height = %d\n", descr->self, height );
1201 descr->item_height = height;
1202 LISTBOX_UpdatePage( descr );
1203 LISTBOX_UpdateScroll( descr );
1205 InvalidateRect( descr->self, 0, TRUE );
1211 /***********************************************************************
1212 * LISTBOX_SetHorizontalPos
1214 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1218 if (pos > descr->horz_extent - descr->width)
1219 pos = descr->horz_extent - descr->width;
1220 if (pos < 0) pos = 0;
1221 if (!(diff = descr->horz_pos - pos)) return;
1222 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1223 descr->horz_pos = pos;
1224 LISTBOX_UpdateScroll( descr );
1225 if (abs(diff) < descr->width)
1228 /* Invalidate the focused item so it will be repainted correctly */
1229 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1230 InvalidateRect( descr->self, &rect, TRUE );
1231 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1232 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1235 InvalidateRect( descr->self, NULL, TRUE );
1239 /***********************************************************************
1240 * LISTBOX_SetHorizontalExtent
1242 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1244 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1246 if (extent <= 0) extent = 1;
1247 if (extent == descr->horz_extent) return LB_OKAY;
1248 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1249 descr->horz_extent = extent;
1250 if (descr->horz_pos > extent - descr->width)
1251 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1253 LISTBOX_UpdateScroll( descr );
1258 /***********************************************************************
1259 * LISTBOX_SetColumnWidth
1261 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1263 if (width == descr->column_width) return LB_OKAY;
1264 TRACE("[%p]: new column width = %d\n", descr->self, width );
1265 descr->column_width = width;
1266 LISTBOX_UpdatePage( descr );
1271 /***********************************************************************
1274 * Returns the item height.
1276 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1280 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1285 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1287 ERR("unable to get DC.\n" );
1290 if (font) oldFont = SelectObject( hdc, font );
1291 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1292 if (oldFont) SelectObject( hdc, oldFont );
1293 ReleaseDC( descr->self, hdc );
1295 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1296 if (!IS_OWNERDRAW(descr))
1297 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1302 /***********************************************************************
1303 * LISTBOX_MakeItemVisible
1305 * Make sure that a given item is partially or fully visible.
1307 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1311 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1313 if (index <= descr->top_item) top = index;
1314 else if (descr->style & LBS_MULTICOLUMN)
1316 INT cols = descr->width;
1317 if (!fully) cols += descr->column_width - 1;
1318 if (cols >= descr->column_width) cols /= descr->column_width;
1320 if (index < descr->top_item + (descr->page_size * cols)) return;
1321 top = index - descr->page_size * (cols - 1);
1323 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1325 INT height = fully ? descr->items[index].height : 1;
1326 for (top = index; top > descr->top_item; top--)
1327 if ((height += descr->items[top-1].height) > descr->height) break;
1331 if (index < descr->top_item + descr->page_size) return;
1332 if (!fully && (index == descr->top_item + descr->page_size) &&
1333 (descr->height > (descr->page_size * descr->item_height))) return;
1334 top = index - descr->page_size + 1;
1336 LISTBOX_SetTopItem( descr, top, TRUE );
1339 /***********************************************************************
1340 * LISTBOX_SetCaretIndex
1343 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1346 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1348 INT oldfocus = descr->focus_item;
1350 TRACE("old focus %d, index %d\n", oldfocus, index);
1352 if (descr->style & LBS_NOSEL) return LB_ERR;
1353 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1354 if (index == oldfocus) return LB_OKAY;
1356 LISTBOX_DrawFocusRect( descr, FALSE );
1357 descr->focus_item = index;
1359 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1360 LISTBOX_DrawFocusRect( descr, TRUE );
1366 /***********************************************************************
1367 * LISTBOX_SelectItemRange
1369 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1371 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1376 /* A few sanity checks */
1378 if (descr->style & LBS_NOSEL) return LB_ERR;
1379 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1381 if (!descr->nb_items) return LB_OKAY;
1383 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1384 if (first < 0) first = 0;
1385 if (last < first) return LB_OKAY;
1387 if (on) /* Turn selection on */
1389 for (i = first; i <= last; i++)
1391 if (descr->items[i].selected) continue;
1392 descr->items[i].selected = TRUE;
1393 LISTBOX_InvalidateItemRect(descr, i);
1396 else /* Turn selection off */
1398 for (i = first; i <= last; i++)
1400 if (!descr->items[i].selected) continue;
1401 descr->items[i].selected = FALSE;
1402 LISTBOX_InvalidateItemRect(descr, i);
1408 /***********************************************************************
1409 * LISTBOX_SetSelection
1411 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1412 BOOL on, BOOL send_notify )
1414 TRACE( "cur_sel=%d index=%d notify=%s\n",
1415 descr->selected_item, index, send_notify ? "YES" : "NO" );
1417 if (descr->style & LBS_NOSEL)
1419 descr->selected_item = index;
1422 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1423 if (descr->style & LBS_MULTIPLESEL)
1425 if (index == -1) /* Select all items */
1426 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1427 else /* Only one item */
1428 return LISTBOX_SelectItemRange( descr, index, index, on );
1432 INT oldsel = descr->selected_item;
1433 if (index == oldsel) return LB_OKAY;
1434 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1435 if (index != -1) descr->items[index].selected = TRUE;
1436 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1437 descr->selected_item = index;
1438 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1439 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1440 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1442 if( descr->lphc ) /* set selection change flag for parent combo */
1443 descr->lphc->wState |= CBF_SELCHANGE;
1449 /***********************************************************************
1452 * Change the caret position and extend the selection to the new caret.
1454 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1456 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1458 if ((index < 0) || (index >= descr->nb_items))
1461 /* Important, repaint needs to be done in this order if
1462 you want to mimic Windows behavior:
1463 1. Remove the focus and paint the item
1464 2. Remove the selection and paint the item(s)
1465 3. Set the selection and repaint the item(s)
1466 4. Set the focus to 'index' and repaint the item */
1468 /* 1. remove the focus and repaint the item */
1469 LISTBOX_DrawFocusRect( descr, FALSE );
1471 /* 2. then turn off the previous selection */
1472 /* 3. repaint the new selected item */
1473 if (descr->style & LBS_EXTENDEDSEL)
1475 if (descr->anchor_item != -1)
1477 INT first = min( index, descr->anchor_item );
1478 INT last = max( index, descr->anchor_item );
1480 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1481 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1482 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1485 else if (!(descr->style & LBS_MULTIPLESEL))
1487 /* Set selection to new caret item */
1488 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1491 /* 4. repaint the new item with the focus */
1492 descr->focus_item = index;
1493 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1494 LISTBOX_DrawFocusRect( descr, TRUE );
1498 /***********************************************************************
1499 * LISTBOX_InsertItem
1501 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1502 LPWSTR str, ULONG_PTR data )
1506 INT oldfocus = descr->focus_item;
1508 if (index == -1) index = descr->nb_items;
1509 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1510 if (!descr->items) max_items = 0;
1511 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1512 if (descr->nb_items == max_items)
1514 /* We need to grow the array */
1515 max_items += LB_ARRAY_GRANULARITY;
1517 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1518 max_items * sizeof(LB_ITEMDATA) );
1520 item = HeapAlloc( GetProcessHeap(), 0,
1521 max_items * sizeof(LB_ITEMDATA) );
1524 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1527 descr->items = item;
1530 /* Insert the item structure */
1532 item = &descr->items[index];
1533 if (index < descr->nb_items)
1534 RtlMoveMemory( item + 1, item,
1535 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1539 item->selected = FALSE;
1542 /* Get item height */
1544 if (descr->style & LBS_OWNERDRAWVARIABLE)
1546 MEASUREITEMSTRUCT mis;
1547 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1549 mis.CtlType = ODT_LISTBOX;
1552 mis.itemData = descr->items[index].data;
1553 mis.itemHeight = descr->item_height;
1554 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1555 item->height = mis.itemHeight ? mis.itemHeight : 1;
1556 TRACE("[%p]: measure item %d (%s) = %d\n",
1557 descr->self, index, str ? debugstr_w(str) : "", item->height );
1560 /* Repaint the items */
1562 LISTBOX_UpdateScroll( descr );
1563 LISTBOX_InvalidateItems( descr, index );
1565 /* Move selection and focused item */
1566 /* If listbox was empty, set focus to the first item */
1567 if (descr->nb_items == 1)
1568 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1569 /* single select don't change selection index in win31 */
1570 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1572 descr->selected_item++;
1573 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1577 if (index <= descr->selected_item)
1579 descr->selected_item++;
1580 descr->focus_item = oldfocus; /* focus not changed */
1587 /***********************************************************************
1588 * LISTBOX_InsertString
1590 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1592 LPWSTR new_str = NULL;
1596 if (HAS_STRINGS(descr))
1598 static const WCHAR empty_stringW[] = { 0 };
1599 if (!str) str = empty_stringW;
1600 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1602 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1605 strcpyW(new_str, str);
1607 else data = (ULONG_PTR)str;
1609 if (index == -1) index = descr->nb_items;
1610 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1612 HeapFree( GetProcessHeap(), 0, new_str );
1616 TRACE("[%p]: added item %d %s\n",
1617 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1622 /***********************************************************************
1623 * LISTBOX_DeleteItem
1625 * Delete the content of an item. 'index' must be a valid index.
1627 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1629 /* save the item data before it gets freed by LB_RESETCONTENT */
1630 ULONG_PTR item_data = descr->items[index].data;
1631 LPWSTR item_str = descr->items[index].str;
1633 if (!descr->nb_items)
1634 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1636 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1637 * while Win95 sends it for all items with user data.
1638 * It's probably better to send it too often than not
1639 * often enough, so this is what we do here.
1641 if (IS_OWNERDRAW(descr) || item_data)
1643 DELETEITEMSTRUCT dis;
1644 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1646 dis.CtlType = ODT_LISTBOX;
1649 dis.hwndItem = descr->self;
1650 dis.itemData = item_data;
1651 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1653 if (HAS_STRINGS(descr))
1654 HeapFree( GetProcessHeap(), 0, item_str );
1658 /***********************************************************************
1659 * LISTBOX_RemoveItem
1661 * Remove an item from the listbox and delete its content.
1663 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1668 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1670 /* We need to invalidate the original rect instead of the updated one. */
1671 LISTBOX_InvalidateItems( descr, index );
1674 LISTBOX_DeleteItem( descr, index );
1676 if (!descr->nb_items) return LB_OKAY;
1678 /* Remove the item */
1680 item = &descr->items[index];
1681 if (index < descr->nb_items)
1682 RtlMoveMemory( item, item + 1,
1683 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1684 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1686 /* Shrink the item array if possible */
1688 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1689 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1691 max_items -= LB_ARRAY_GRANULARITY;
1692 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1693 max_items * sizeof(LB_ITEMDATA) );
1694 if (item) descr->items = item;
1696 /* Repaint the items */
1698 LISTBOX_UpdateScroll( descr );
1699 /* if we removed the scrollbar, reset the top of the list
1700 (correct for owner-drawn ???) */
1701 if (descr->nb_items == descr->page_size)
1702 LISTBOX_SetTopItem( descr, 0, TRUE );
1704 /* Move selection and focused item */
1705 if (!IS_MULTISELECT(descr))
1707 if (index == descr->selected_item)
1708 descr->selected_item = -1;
1709 else if (index < descr->selected_item)
1711 descr->selected_item--;
1712 if (ISWIN31) /* win 31 do not change the selected item number */
1713 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1717 if (descr->focus_item >= descr->nb_items)
1719 descr->focus_item = descr->nb_items - 1;
1720 if (descr->focus_item < 0) descr->focus_item = 0;
1726 /***********************************************************************
1727 * LISTBOX_ResetContent
1729 static void LISTBOX_ResetContent( LB_DESCR *descr )
1733 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1734 HeapFree( GetProcessHeap(), 0, descr->items );
1735 descr->nb_items = 0;
1736 descr->top_item = 0;
1737 descr->selected_item = -1;
1738 descr->focus_item = 0;
1739 descr->anchor_item = -1;
1740 descr->items = NULL;
1744 /***********************************************************************
1747 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1751 if (HAS_STRINGS(descr))
1753 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1757 /* FIXME: this is far from optimal... */
1758 if (count > descr->nb_items)
1760 while (count > descr->nb_items)
1761 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1764 else if (count < descr->nb_items)
1766 while (count < descr->nb_items)
1767 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1774 /***********************************************************************
1777 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1778 LPCWSTR filespec, BOOL long_names )
1781 LRESULT ret = LB_OKAY;
1782 WIN32_FIND_DATAW entry;
1784 LRESULT maxinsert = LB_ERR;
1786 /* don't scan directory if we just want drives exclusively */
1787 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1788 /* scan directory */
1789 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1791 int le = GetLastError();
1792 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1799 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1801 static const WCHAR bracketW[] = { ']',0 };
1802 static const WCHAR dotW[] = { '.',0 };
1803 if (!(attrib & DDL_DIRECTORY) ||
1804 !strcmpW( entry.cFileName, dotW )) continue;
1806 if (!long_names && entry.cAlternateFileName[0])
1807 strcpyW( buffer + 1, entry.cAlternateFileName );
1809 strcpyW( buffer + 1, entry.cFileName );
1810 strcatW(buffer, bracketW);
1812 else /* not a directory */
1814 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1815 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1817 if ((attrib & DDL_EXCLUSIVE) &&
1818 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1821 if (!long_names && entry.cAlternateFileName[0])
1822 strcpyW( buffer, entry.cAlternateFileName );
1824 strcpyW( buffer, entry.cFileName );
1826 if (!long_names) CharLowerW( buffer );
1827 pos = LISTBOX_FindFileStrPos( descr, buffer );
1828 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1830 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1831 } while (FindNextFileW( handle, &entry ));
1832 FindClose( handle );
1840 if (attrib & DDL_DRIVES)
1842 WCHAR buffer[] = {'[','-','a','-',']',0};
1843 WCHAR root[] = {'A',':','\\',0};
1845 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1847 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1848 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1857 /***********************************************************************
1858 * LISTBOX_HandleVScroll
1860 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1864 if (descr->style & LBS_MULTICOLUMN) return 0;
1868 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1871 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1874 LISTBOX_SetTopItem( descr, descr->top_item -
1875 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1878 LISTBOX_SetTopItem( descr, descr->top_item +
1879 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1881 case SB_THUMBPOSITION:
1882 LISTBOX_SetTopItem( descr, pos, TRUE );
1885 info.cbSize = sizeof(info);
1886 info.fMask = SIF_TRACKPOS;
1887 GetScrollInfo( descr->self, SB_VERT, &info );
1888 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1891 LISTBOX_SetTopItem( descr, 0, TRUE );
1894 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1901 /***********************************************************************
1902 * LISTBOX_HandleHScroll
1904 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1909 if (descr->style & LBS_MULTICOLUMN)
1914 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1918 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1922 page = descr->width / descr->column_width;
1923 if (page < 1) page = 1;
1924 LISTBOX_SetTopItem( descr,
1925 descr->top_item - page * descr->page_size, TRUE );
1928 page = descr->width / descr->column_width;
1929 if (page < 1) page = 1;
1930 LISTBOX_SetTopItem( descr,
1931 descr->top_item + page * descr->page_size, TRUE );
1933 case SB_THUMBPOSITION:
1934 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1937 info.cbSize = sizeof(info);
1938 info.fMask = SIF_TRACKPOS;
1939 GetScrollInfo( descr->self, SB_VERT, &info );
1940 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1944 LISTBOX_SetTopItem( descr, 0, TRUE );
1947 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1951 else if (descr->horz_extent)
1956 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1959 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1962 LISTBOX_SetHorizontalPos( descr,
1963 descr->horz_pos - descr->width );
1966 LISTBOX_SetHorizontalPos( descr,
1967 descr->horz_pos + descr->width );
1969 case SB_THUMBPOSITION:
1970 LISTBOX_SetHorizontalPos( descr, pos );
1973 info.cbSize = sizeof(info);
1974 info.fMask = SIF_TRACKPOS;
1975 GetScrollInfo( descr->self, SB_HORZ, &info );
1976 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1979 LISTBOX_SetHorizontalPos( descr, 0 );
1982 LISTBOX_SetHorizontalPos( descr,
1983 descr->horz_extent - descr->width );
1990 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1992 UINT pulScrollLines = 3;
1994 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1996 /* if scrolling changes direction, ignore left overs */
1997 if ((delta < 0 && descr->wheel_remain < 0) ||
1998 (delta > 0 && descr->wheel_remain > 0))
1999 descr->wheel_remain += delta;
2001 descr->wheel_remain = delta;
2003 if (descr->wheel_remain && pulScrollLines)
2006 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2007 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2008 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2009 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2014 /***********************************************************************
2015 * LISTBOX_HandleLButtonDown
2017 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2019 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2021 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2022 descr->self, x, y, index, descr->focus_item);
2024 if (!descr->caret_on && (descr->in_focus)) return 0;
2026 if (!descr->in_focus)
2028 if( !descr->lphc ) SetFocus( descr->self );
2029 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2032 if (index == -1) return 0;
2036 if (descr->style & LBS_NOTIFY )
2037 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2038 MAKELPARAM( x, y ) );
2041 descr->captured = TRUE;
2042 SetCapture( descr->self );
2044 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2046 /* we should perhaps make sure that all items are deselected
2047 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2048 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2049 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2052 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2053 if (keys & MK_CONTROL)
2055 LISTBOX_SetCaretIndex( descr, index, FALSE );
2056 LISTBOX_SetSelection( descr, index,
2057 !descr->items[index].selected,
2058 (descr->style & LBS_NOTIFY) != 0);
2062 LISTBOX_MoveCaret( descr, index, FALSE );
2064 if (descr->style & LBS_EXTENDEDSEL)
2066 LISTBOX_SetSelection( descr, index,
2067 descr->items[index].selected,
2068 (descr->style & LBS_NOTIFY) != 0 );
2072 LISTBOX_SetSelection( descr, index,
2073 !descr->items[index].selected,
2074 (descr->style & LBS_NOTIFY) != 0 );
2080 descr->anchor_item = index;
2081 LISTBOX_MoveCaret( descr, index, FALSE );
2082 LISTBOX_SetSelection( descr, index,
2083 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2088 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2095 if (DragDetect( descr->self, pt ))
2096 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2103 /*************************************************************************
2104 * LISTBOX_HandleLButtonDownCombo [Internal]
2106 * Process LButtonDown message for the ComboListBox
2109 * pWnd [I] The windows internal structure
2110 * pDescr [I] The ListBox internal structure
2111 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2112 * x [I] X Mouse Coordinate
2113 * y [I] Y Mouse Coordinate
2116 * 0 since we are processing the WM_LBUTTONDOWN Message
2119 * This function is only to be used when a ListBox is a ComboListBox
2122 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2124 RECT clientRect, screenRect;
2130 GetClientRect(descr->self, &clientRect);
2132 if(PtInRect(&clientRect, mousePos))
2134 /* MousePos is in client, resume normal processing */
2135 if (msg == WM_LBUTTONDOWN)
2137 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2138 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2140 else if (descr->style & LBS_NOTIFY)
2141 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2145 POINT screenMousePos;
2146 HWND hWndOldCapture;
2148 /* Check the Non-Client Area */
2149 screenMousePos = mousePos;
2150 hWndOldCapture = GetCapture();
2152 GetWindowRect(descr->self, &screenRect);
2153 ClientToScreen(descr->self, &screenMousePos);
2155 if(!PtInRect(&screenRect, screenMousePos))
2157 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2158 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2159 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2163 /* Check to see the NC is a scrollbar */
2165 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2166 /* Check Vertical scroll bar */
2167 if (style & WS_VSCROLL)
2169 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2170 if (PtInRect( &clientRect, mousePos ))
2171 nHitTestType = HTVSCROLL;
2173 /* Check horizontal scroll bar */
2174 if (style & WS_HSCROLL)
2176 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2177 if (PtInRect( &clientRect, mousePos ))
2178 nHitTestType = HTHSCROLL;
2180 /* Windows sends this message when a scrollbar is clicked
2183 if(nHitTestType != 0)
2185 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2186 MAKELONG(screenMousePos.x, screenMousePos.y));
2188 /* Resume the Capture after scrolling is complete
2190 if(hWndOldCapture != 0)
2191 SetCapture(hWndOldCapture);
2197 /***********************************************************************
2198 * LISTBOX_HandleLButtonUp
2200 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2202 if (LISTBOX_Timer != LB_TIMER_NONE)
2203 KillSystemTimer( descr->self, LB_TIMER_ID );
2204 LISTBOX_Timer = LB_TIMER_NONE;
2205 if (descr->captured)
2207 descr->captured = FALSE;
2208 if (GetCapture() == descr->self) ReleaseCapture();
2209 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2210 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2216 /***********************************************************************
2217 * LISTBOX_HandleTimer
2219 * Handle scrolling upon a timer event.
2220 * Return TRUE if scrolling should continue.
2222 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2227 if (descr->top_item) index = descr->top_item - 1;
2231 if (descr->top_item) index -= descr->page_size;
2234 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2235 if (index == descr->focus_item) index++;
2236 if (index >= descr->nb_items) index = descr->nb_items - 1;
2238 case LB_TIMER_RIGHT:
2239 if (index + descr->page_size < descr->nb_items)
2240 index += descr->page_size;
2245 if (index == descr->focus_item) return FALSE;
2246 LISTBOX_MoveCaret( descr, index, FALSE );
2251 /***********************************************************************
2252 * LISTBOX_HandleSystemTimer
2254 * WM_SYSTIMER handler.
2256 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2258 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2260 KillSystemTimer( descr->self, LB_TIMER_ID );
2261 LISTBOX_Timer = LB_TIMER_NONE;
2267 /***********************************************************************
2268 * LISTBOX_HandleMouseMove
2270 * WM_MOUSEMOVE handler.
2272 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2276 TIMER_DIRECTION dir = LB_TIMER_NONE;
2278 if (!descr->captured) return;
2280 if (descr->style & LBS_MULTICOLUMN)
2283 else if (y >= descr->item_height * descr->page_size)
2284 y = descr->item_height * descr->page_size - 1;
2288 dir = LB_TIMER_LEFT;
2291 else if (x >= descr->width)
2293 dir = LB_TIMER_RIGHT;
2294 x = descr->width - 1;
2299 if (y < 0) dir = LB_TIMER_UP; /* above */
2300 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2303 index = LISTBOX_GetItemFromPoint( descr, x, y );
2304 if (index == -1) index = descr->focus_item;
2305 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2307 /* Start/stop the system timer */
2309 if (dir != LB_TIMER_NONE)
2310 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2311 else if (LISTBOX_Timer != LB_TIMER_NONE)
2312 KillSystemTimer( descr->self, LB_TIMER_ID );
2313 LISTBOX_Timer = dir;
2317 /***********************************************************************
2318 * LISTBOX_HandleKeyDown
2320 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2323 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2324 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2325 bForceSelection = FALSE; /* only for single select list */
2327 if (descr->style & LBS_WANTKEYBOARDINPUT)
2329 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2330 MAKEWPARAM(LOWORD(key), descr->focus_item),
2331 (LPARAM)descr->self );
2332 if (caret == -2) return 0;
2334 if (caret == -1) switch(key)
2337 if (descr->style & LBS_MULTICOLUMN)
2339 bForceSelection = FALSE;
2340 if (descr->focus_item >= descr->page_size)
2341 caret = descr->focus_item - descr->page_size;
2346 caret = descr->focus_item - 1;
2347 if (caret < 0) caret = 0;
2350 if (descr->style & LBS_MULTICOLUMN)
2352 bForceSelection = FALSE;
2353 if (descr->focus_item + descr->page_size < descr->nb_items)
2354 caret = descr->focus_item + descr->page_size;
2359 caret = descr->focus_item + 1;
2360 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2364 if (descr->style & LBS_MULTICOLUMN)
2366 INT page = descr->width / descr->column_width;
2367 if (page < 1) page = 1;
2368 caret = descr->focus_item - (page * descr->page_size) + 1;
2370 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2371 if (caret < 0) caret = 0;
2374 if (descr->style & LBS_MULTICOLUMN)
2376 INT page = descr->width / descr->column_width;
2377 if (page < 1) page = 1;
2378 caret = descr->focus_item + (page * descr->page_size) - 1;
2380 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2381 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2387 caret = descr->nb_items - 1;
2390 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2391 else if (descr->style & LBS_MULTIPLESEL)
2393 LISTBOX_SetSelection( descr, descr->focus_item,
2394 !descr->items[descr->focus_item].selected,
2395 (descr->style & LBS_NOTIFY) != 0 );
2399 bForceSelection = FALSE;
2401 if (bForceSelection) /* focused item is used instead of key */
2402 caret = descr->focus_item;
2405 if (((descr->style & LBS_EXTENDEDSEL) &&
2406 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2407 !IS_MULTISELECT(descr))
2408 descr->anchor_item = caret;
2409 LISTBOX_MoveCaret( descr, caret, TRUE );
2411 if (descr->style & LBS_MULTIPLESEL)
2412 descr->selected_item = caret;
2414 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2415 if (descr->style & LBS_NOTIFY)
2417 if (descr->lphc && IsWindowVisible( descr->self ))
2419 /* make sure that combo parent doesn't hide us */
2420 descr->lphc->wState |= CBF_NOROLLUP;
2422 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2429 /***********************************************************************
2430 * LISTBOX_HandleChar
2432 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2440 if (descr->style & LBS_WANTKEYBOARDINPUT)
2442 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2443 MAKEWPARAM(charW, descr->focus_item),
2444 (LPARAM)descr->self );
2445 if (caret == -2) return 0;
2448 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2451 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2452 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2453 LISTBOX_MoveCaret( descr, caret, TRUE );
2454 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2455 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2461 /***********************************************************************
2464 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2467 MEASUREITEMSTRUCT mis;
2470 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2473 GetClientRect( hwnd, &rect );
2475 descr->owner = GetParent( descr->self );
2476 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2477 descr->width = rect.right - rect.left;
2478 descr->height = rect.bottom - rect.top;
2479 descr->items = NULL;
2480 descr->nb_items = 0;
2481 descr->top_item = 0;
2482 descr->selected_item = -1;
2483 descr->focus_item = 0;
2484 descr->anchor_item = -1;
2485 descr->item_height = 1;
2486 descr->page_size = 1;
2487 descr->column_width = 150;
2488 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2489 descr->horz_pos = 0;
2492 descr->wheel_remain = 0;
2493 descr->caret_on = !lphc;
2494 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2495 descr->in_focus = FALSE;
2496 descr->captured = FALSE;
2498 descr->locale = GetUserDefaultLCID();
2503 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2504 descr->owner = lphc->self;
2507 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2509 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2511 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2512 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2513 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2514 descr->item_height = LISTBOX_SetFont( descr, 0 );
2516 if (descr->style & LBS_OWNERDRAWFIXED)
2518 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2520 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2521 descr->item_height = lphc->fixedOwnerDrawHeight;
2525 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2526 mis.CtlType = ODT_LISTBOX;
2531 mis.itemHeight = descr->item_height;
2532 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2533 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2537 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2542 /***********************************************************************
2545 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2547 LISTBOX_ResetContent( descr );
2548 SetWindowLongPtrW( descr->self, 0, 0 );
2549 HeapFree( GetProcessHeap(), 0, descr );
2554 /***********************************************************************
2555 * ListBoxWndProc_common
2557 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2559 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2560 LPHEADCOMBO lphc = 0;
2565 if (!IsWindow(hwnd)) return 0;
2567 if (msg == WM_CREATE)
2569 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2570 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2571 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2572 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2575 /* Ignore all other messages before we get a WM_CREATE */
2576 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2577 DefWindowProcA( hwnd, msg, wParam, lParam );
2579 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2581 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2582 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2586 case LB_RESETCONTENT:
2587 LISTBOX_ResetContent( descr );
2588 LISTBOX_UpdateScroll( descr );
2589 InvalidateRect( descr->self, NULL, TRUE );
2596 if(unicode || !HAS_STRINGS(descr))
2597 textW = (LPWSTR)lParam;
2600 LPSTR textA = (LPSTR)lParam;
2601 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2602 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2603 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2607 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2608 ret = LISTBOX_InsertString( descr, wParam, textW );
2609 if (!unicode && HAS_STRINGS(descr))
2610 HeapFree(GetProcessHeap(), 0, textW);
2614 case LB_INSERTSTRING:
2618 if(unicode || !HAS_STRINGS(descr))
2619 textW = (LPWSTR)lParam;
2622 LPSTR textA = (LPSTR)lParam;
2623 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2624 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2625 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2629 ret = LISTBOX_InsertString( descr, wParam, textW );
2630 if(!unicode && HAS_STRINGS(descr))
2631 HeapFree(GetProcessHeap(), 0, textW);
2639 if(unicode || !HAS_STRINGS(descr))
2640 textW = (LPWSTR)lParam;
2643 LPSTR textA = (LPSTR)lParam;
2644 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2645 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2646 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2650 wParam = LISTBOX_FindFileStrPos( descr, textW );
2651 ret = LISTBOX_InsertString( descr, wParam, textW );
2652 if(!unicode && HAS_STRINGS(descr))
2653 HeapFree(GetProcessHeap(), 0, textW);
2657 case LB_DELETESTRING:
2658 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2659 return descr->nb_items;
2662 SetLastError(ERROR_INVALID_INDEX);
2666 case LB_GETITEMDATA:
2667 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2669 SetLastError(ERROR_INVALID_INDEX);
2672 return descr->items[wParam].data;
2674 case LB_SETITEMDATA:
2675 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2677 SetLastError(ERROR_INVALID_INDEX);
2680 descr->items[wParam].data = lParam;
2681 /* undocumented: returns TRUE, not LB_OKAY (0) */
2685 return descr->nb_items;
2688 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2691 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2693 SetLastError(ERROR_INVALID_INDEX);
2696 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2697 if (unicode) return strlenW( descr->items[wParam].str );
2698 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2699 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2702 if (descr->nb_items == 0)
2704 if (!IS_MULTISELECT(descr))
2705 return descr->selected_item;
2706 if (descr->selected_item != -1)
2707 return descr->selected_item;
2708 return descr->focus_item;
2709 /* otherwise, if the user tries to move the selection with the */
2710 /* arrow keys, we will give the application something to choke on */
2711 case LB_GETTOPINDEX:
2712 return descr->top_item;
2714 case LB_GETITEMHEIGHT:
2715 return LISTBOX_GetItemHeight( descr, wParam );
2717 case LB_SETITEMHEIGHT:
2718 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2720 case LB_ITEMFROMPOINT:
2727 /* The hiword of the return value is not a client area
2728 hittest as suggested by MSDN, but rather a hittest on
2729 the returned listbox item. */
2731 if(descr->nb_items == 0)
2732 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2734 pt.x = (short)LOWORD(lParam);
2735 pt.y = (short)HIWORD(lParam);
2737 SetRect(&rect, 0, 0, descr->width, descr->height);
2739 if(!PtInRect(&rect, pt))
2741 pt.x = min(pt.x, rect.right - 1);
2742 pt.x = max(pt.x, 0);
2743 pt.y = min(pt.y, rect.bottom - 1);
2744 pt.y = max(pt.y, 0);
2748 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2752 index = descr->nb_items - 1;
2755 return MAKELONG(index, hit ? 0 : 1);
2758 case LB_SETCARETINDEX:
2759 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2760 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2767 case LB_GETCARETINDEX:
2768 return descr->focus_item;
2770 case LB_SETTOPINDEX:
2771 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2773 case LB_SETCOLUMNWIDTH:
2774 return LISTBOX_SetColumnWidth( descr, wParam );
2776 case LB_GETITEMRECT:
2777 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2783 if(unicode || !HAS_STRINGS(descr))
2784 textW = (LPWSTR)lParam;
2787 LPSTR textA = (LPSTR)lParam;
2788 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2789 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2790 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2792 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2793 if(!unicode && HAS_STRINGS(descr))
2794 HeapFree(GetProcessHeap(), 0, textW);
2798 case LB_FINDSTRINGEXACT:
2802 if(unicode || !HAS_STRINGS(descr))
2803 textW = (LPWSTR)lParam;
2806 LPSTR textA = (LPSTR)lParam;
2807 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2808 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2809 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2811 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2812 if(!unicode && HAS_STRINGS(descr))
2813 HeapFree(GetProcessHeap(), 0, textW);
2817 case LB_SELECTSTRING:
2822 if(HAS_STRINGS(descr))
2823 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2824 debugstr_a((LPSTR)lParam));
2825 if(unicode || !HAS_STRINGS(descr))
2826 textW = (LPWSTR)lParam;
2829 LPSTR textA = (LPSTR)lParam;
2830 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2831 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2832 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2834 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2835 if(!unicode && HAS_STRINGS(descr))
2836 HeapFree(GetProcessHeap(), 0, textW);
2837 if (index != LB_ERR)
2839 LISTBOX_MoveCaret( descr, index, TRUE );
2840 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2846 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2848 return descr->items[wParam].selected;
2851 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2854 if (IS_MULTISELECT(descr)) return LB_ERR;
2855 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2856 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2857 if (ret != LB_ERR) ret = descr->selected_item;
2860 case LB_GETSELCOUNT:
2861 return LISTBOX_GetSelCount( descr );
2863 case LB_GETSELITEMS:
2864 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2866 case LB_SELITEMRANGE:
2867 if (LOWORD(lParam) <= HIWORD(lParam))
2868 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2869 HIWORD(lParam), wParam );
2871 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2872 LOWORD(lParam), wParam );
2874 case LB_SELITEMRANGEEX:
2875 if ((INT)lParam >= (INT)wParam)
2876 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2878 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2880 case LB_GETHORIZONTALEXTENT:
2881 return descr->horz_extent;
2883 case LB_SETHORIZONTALEXTENT:
2884 return LISTBOX_SetHorizontalExtent( descr, wParam );
2886 case LB_GETANCHORINDEX:
2887 return descr->anchor_item;
2889 case LB_SETANCHORINDEX:
2890 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2892 SetLastError(ERROR_INVALID_INDEX);
2895 descr->anchor_item = (INT)wParam;
2903 textW = (LPWSTR)lParam;
2906 LPSTR textA = (LPSTR)lParam;
2907 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2908 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2909 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2911 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2913 HeapFree(GetProcessHeap(), 0, textW);
2918 return descr->locale;
2923 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2925 ret = descr->locale;
2926 descr->locale = (LCID)wParam;
2930 case LB_INITSTORAGE:
2931 return LISTBOX_InitStorage( descr, wParam );
2934 return LISTBOX_SetCount( descr, (INT)wParam );
2936 case LB_SETTABSTOPS:
2937 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2940 if (descr->caret_on)
2942 descr->caret_on = TRUE;
2943 if ((descr->focus_item != -1) && (descr->in_focus))
2944 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2948 if (!descr->caret_on)
2950 descr->caret_on = FALSE;
2951 if ((descr->focus_item != -1) && (descr->in_focus))
2952 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2955 case LB_GETLISTBOXINFO:
2956 FIXME("LB_GETLISTBOXINFO: stub!\n");
2960 return LISTBOX_Destroy( descr );
2963 InvalidateRect( descr->self, NULL, TRUE );
2967 LISTBOX_SetRedraw( descr, wParam != 0 );
2971 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2973 case WM_PRINTCLIENT:
2977 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2978 ret = LISTBOX_Paint( descr, hdc );
2979 if( !wParam ) EndPaint( descr->self, &ps );
2983 LISTBOX_UpdateSize( descr );
2986 return (LRESULT)descr->font;
2988 LISTBOX_SetFont( descr, (HFONT)wParam );
2989 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2992 descr->in_focus = TRUE;
2993 descr->caret_on = TRUE;
2994 if (descr->focus_item != -1)
2995 LISTBOX_DrawFocusRect( descr, TRUE );
2996 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2999 descr->in_focus = FALSE;
3000 descr->wheel_remain = 0;
3001 if ((descr->focus_item != -1) && descr->caret_on)
3002 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3003 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3006 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3008 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3010 if (wParam & (MK_SHIFT | MK_CONTROL))
3011 return DefWindowProcW( descr->self, msg, wParam, lParam );
3012 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3013 case WM_LBUTTONDOWN:
3015 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3016 (INT16)LOWORD(lParam),
3017 (INT16)HIWORD(lParam) );
3018 return LISTBOX_HandleLButtonDown( descr, wParam,
3019 (INT16)LOWORD(lParam),
3020 (INT16)HIWORD(lParam) );
3021 case WM_LBUTTONDBLCLK:
3023 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3024 (INT16)LOWORD(lParam),
3025 (INT16)HIWORD(lParam) );
3026 if (descr->style & LBS_NOTIFY)
3027 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3030 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3032 BOOL captured = descr->captured;
3036 mousePos.x = (INT16)LOWORD(lParam);
3037 mousePos.y = (INT16)HIWORD(lParam);
3040 * If we are in a dropdown combobox, we simulate that
3041 * the mouse is captured to show the tracking of the item.
3043 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3044 descr->captured = TRUE;
3046 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3048 descr->captured = captured;
3050 else if (GetCapture() == descr->self)
3052 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3053 (INT16)HIWORD(lParam) );
3063 * If the mouse button "up" is not in the listbox,
3064 * we make sure there is no selection by re-selecting the
3065 * item that was selected when the listbox was made visible.
3067 mousePos.x = (INT16)LOWORD(lParam);
3068 mousePos.y = (INT16)HIWORD(lParam);
3070 GetClientRect(descr->self, &clientRect);
3073 * When the user clicks outside the combobox and the focus
3074 * is lost, the owning combobox will send a fake buttonup with
3075 * 0xFFFFFFF as the mouse location, we must also revert the
3076 * selection to the original selection.
3078 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3079 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3081 return LISTBOX_HandleLButtonUp( descr );
3083 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3085 /* for some reason Windows makes it possible to
3086 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3088 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3089 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3090 && (wParam == VK_DOWN || wParam == VK_UP)) )
3092 COMBO_FlipListbox( lphc, FALSE, FALSE );
3096 return LISTBOX_HandleKeyDown( descr, wParam );
3101 charW = (WCHAR)wParam;
3104 CHAR charA = (CHAR)wParam;
3105 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3107 return LISTBOX_HandleChar( descr, charW );
3110 return LISTBOX_HandleSystemTimer( descr );
3112 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3115 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3116 wParam, (LPARAM)descr->self );
3117 TRACE("hbrush = %p\n", hbrush);
3119 hbrush = GetSysColorBrush(COLOR_WINDOW);
3122 GetClientRect(descr->self, &rect);
3123 FillRect((HDC)wParam, &rect, hbrush);
3128 if( lphc ) return 0;
3129 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3130 SendMessageA( descr->owner, msg, wParam, lParam );
3133 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3142 if ((msg >= WM_USER) && (msg < 0xc000))
3143 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3144 hwnd, msg, wParam, lParam );
3147 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3148 DefWindowProcA( hwnd, msg, wParam, lParam );