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;
303 if (descr->style & LBS_MULTICOLUMN)
304 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
305 else if (descr->style & LBS_OWNERDRAWVARIABLE)
309 if (index > descr->top_item)
311 for (i = index - 1; i >= descr->top_item; i--)
312 diff -= descr->items[i].height;
316 for (i = index; i < descr->top_item; i++)
317 diff += descr->items[i].height;
321 diff = (descr->top_item - index) * descr->item_height;
323 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
324 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
326 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
327 descr->top_item = index;
328 LISTBOX_UpdateScroll( descr );
333 /***********************************************************************
336 * Update the page size. Should be called when the size of
337 * the client area or the item height changes.
339 static void LISTBOX_UpdatePage( LB_DESCR *descr )
343 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
345 if (page_size == descr->page_size) return;
346 descr->page_size = page_size;
347 if (descr->style & LBS_MULTICOLUMN)
348 InvalidateRect( descr->self, NULL, TRUE );
349 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
353 /***********************************************************************
356 * Update the size of the listbox. Should be called when the size of
357 * the client area changes.
359 static void LISTBOX_UpdateSize( LB_DESCR *descr )
363 GetClientRect( descr->self, &rect );
364 descr->width = rect.right - rect.left;
365 descr->height = rect.bottom - rect.top;
366 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
371 GetWindowRect( descr->self, &rect );
372 if(descr->item_height != 0)
373 remaining = descr->height % descr->item_height;
376 if ((descr->height > descr->item_height) && remaining)
378 TRACE("[%p]: changing height %d -> %d\n",
379 descr->self, descr->height, descr->height - remaining );
380 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
381 rect.bottom - rect.top - remaining,
382 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
386 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
387 LISTBOX_UpdatePage( descr );
388 LISTBOX_UpdateScroll( descr );
390 /* Invalidate the focused item so it will be repainted correctly */
391 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
393 InvalidateRect( descr->self, &rect, FALSE );
398 /***********************************************************************
399 * LISTBOX_GetItemRect
401 * Get the rectangle enclosing an item, in listbox client coordinates.
402 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
404 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
406 /* Index <= 0 is legal even on empty listboxes */
407 if (index && (index >= descr->nb_items))
409 memset(rect, 0, sizeof(*rect));
410 SetLastError(ERROR_INVALID_INDEX);
413 SetRect( rect, 0, 0, descr->width, descr->height );
414 if (descr->style & LBS_MULTICOLUMN)
416 INT col = (index / descr->page_size) -
417 (descr->top_item / descr->page_size);
418 rect->left += col * descr->column_width;
419 rect->right = rect->left + descr->column_width;
420 rect->top += (index % descr->page_size) * descr->item_height;
421 rect->bottom = rect->top + descr->item_height;
423 else if (descr->style & LBS_OWNERDRAWVARIABLE)
426 rect->right += descr->horz_pos;
427 if ((index >= 0) && (index < descr->nb_items))
429 if (index < descr->top_item)
431 for (i = descr->top_item-1; i >= index; i--)
432 rect->top -= descr->items[i].height;
436 for (i = descr->top_item; i < index; i++)
437 rect->top += descr->items[i].height;
439 rect->bottom = rect->top + descr->items[index].height;
445 rect->top += (index - descr->top_item) * descr->item_height;
446 rect->bottom = rect->top + descr->item_height;
447 rect->right += descr->horz_pos;
450 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
452 return ((rect->left < descr->width) && (rect->right > 0) &&
453 (rect->top < descr->height) && (rect->bottom > 0));
457 /***********************************************************************
458 * LISTBOX_GetItemFromPoint
460 * Return the item nearest from point (x,y) (in client coordinates).
462 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
464 INT index = descr->top_item;
466 if (!descr->nb_items) return -1; /* No items */
467 if (descr->style & LBS_OWNERDRAWVARIABLE)
472 while (index < descr->nb_items)
474 if ((pos += descr->items[index].height) > y) break;
483 if ((pos -= descr->items[index].height) <= y) break;
487 else if (descr->style & LBS_MULTICOLUMN)
489 if (y >= descr->item_height * descr->page_size) return -1;
490 if (y >= 0) index += y / descr->item_height;
491 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
492 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
496 index += (y / descr->item_height);
498 if (index < 0) return 0;
499 if (index >= descr->nb_items) return -1;
504 /***********************************************************************
509 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
510 INT index, UINT action, BOOL ignoreFocus )
512 LB_ITEMDATA *item = NULL;
513 if (index < descr->nb_items) item = &descr->items[index];
515 if (IS_OWNERDRAW(descr))
523 if (action == ODA_FOCUS)
524 DrawFocusRect( hdc, rect );
526 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
530 /* some programs mess with the clipping region when
531 drawing the item, *and* restore the previous region
532 after they are done, so a region has better to exist
533 else everything ends clipped */
534 GetClientRect(descr->self, &r);
535 hrgn = set_control_clipping( hdc, &r );
537 dis.CtlType = ODT_LISTBOX;
538 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
539 dis.hwndItem = descr->self;
540 dis.itemAction = action;
544 if (item->selected) dis.itemState |= ODS_SELECTED;
545 if (!ignoreFocus && (descr->focus_item == index) &&
547 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
548 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
549 dis.itemData = item->data;
551 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
552 descr->self, index, debugstr_w(item->str), action,
553 dis.itemState, wine_dbgstr_rect(rect) );
554 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
555 SelectClipRgn( hdc, hrgn );
556 if (hrgn) DeleteObject( hrgn );
560 COLORREF oldText = 0, oldBk = 0;
562 if (action == ODA_FOCUS)
564 DrawFocusRect( hdc, rect );
567 if (item && item->selected)
569 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
570 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
573 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
574 descr->self, index, item ? debugstr_w(item->str) : "", action,
575 wine_dbgstr_rect(rect) );
577 ExtTextOutW( hdc, rect->left + 1, rect->top,
578 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
579 else if (!(descr->style & LBS_USETABSTOPS))
580 ExtTextOutW( hdc, rect->left + 1, rect->top,
581 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
582 strlenW(item->str), NULL );
585 /* Output empty string to paint background in the full width. */
586 ExtTextOutW( hdc, rect->left + 1, rect->top,
587 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
588 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
589 item->str, strlenW(item->str),
590 descr->nb_tabs, descr->tabs, 0);
592 if (item && item->selected)
594 SetBkColor( hdc, oldBk );
595 SetTextColor( hdc, oldText );
597 if (!ignoreFocus && (descr->focus_item == index) &&
599 (descr->in_focus)) DrawFocusRect( hdc, rect );
604 /***********************************************************************
607 * Change the redraw flag.
609 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
613 if (!(descr->style & LBS_NOREDRAW)) return;
614 descr->style &= ~LBS_NOREDRAW;
615 if (descr->style & LBS_DISPLAYCHANGED)
616 { /* page was changed while setredraw false, refresh automatically */
617 InvalidateRect(descr->self, NULL, TRUE);
618 if ((descr->top_item + descr->page_size) > descr->nb_items)
619 { /* reset top of page if less than number of items/page */
620 descr->top_item = descr->nb_items - descr->page_size;
621 if (descr->top_item < 0) descr->top_item = 0;
623 descr->style &= ~LBS_DISPLAYCHANGED;
625 LISTBOX_UpdateScroll( descr );
627 else descr->style |= LBS_NOREDRAW;
631 /***********************************************************************
632 * LISTBOX_RepaintItem
634 * Repaint a single item synchronously.
636 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
641 HBRUSH hbrush, oldBrush = 0;
643 /* Do not repaint the item if the item is not visible */
644 if (!IsWindowVisible(descr->self)) return;
645 if (descr->style & LBS_NOREDRAW)
647 descr->style |= LBS_DISPLAYCHANGED;
650 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
651 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
652 if (descr->font) oldFont = SelectObject( hdc, descr->font );
653 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
654 (WPARAM)hdc, (LPARAM)descr->self );
655 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
656 if (!IsWindowEnabled(descr->self))
657 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
658 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
659 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
660 if (oldFont) SelectObject( hdc, oldFont );
661 if (oldBrush) SelectObject( hdc, oldBrush );
662 ReleaseDC( descr->self, hdc );
666 /***********************************************************************
667 * LISTBOX_DrawFocusRect
669 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
675 /* Do not repaint the item if the item is not visible */
676 if (!IsWindowVisible(descr->self)) return;
678 if (descr->focus_item == -1) return;
679 if (!descr->caret_on || !descr->in_focus) return;
681 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
682 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
683 if (descr->font) oldFont = SelectObject( hdc, descr->font );
684 if (!IsWindowEnabled(descr->self))
685 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
686 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
687 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
688 if (oldFont) SelectObject( hdc, oldFont );
689 ReleaseDC( descr->self, hdc );
693 /***********************************************************************
694 * LISTBOX_InitStorage
696 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
700 nb_items += LB_ARRAY_GRANULARITY - 1;
701 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
703 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
704 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
705 nb_items * sizeof(LB_ITEMDATA));
708 item = HeapAlloc( GetProcessHeap(), 0,
709 nb_items * sizeof(LB_ITEMDATA));
714 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
722 /***********************************************************************
723 * LISTBOX_SetTabStops
725 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
729 if (!(descr->style & LBS_USETABSTOPS))
731 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
735 HeapFree( GetProcessHeap(), 0, descr->tabs );
736 if (!(descr->nb_tabs = count))
741 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
742 descr->nb_tabs * sizeof(INT) )))
744 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
746 /* convert into "dialog units"*/
747 for (i = 0; i < descr->nb_tabs; i++)
748 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
754 /***********************************************************************
757 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
761 if ((index < 0) || (index >= descr->nb_items))
763 SetLastError(ERROR_INVALID_INDEX);
766 if (HAS_STRINGS(descr))
770 len = strlenW(descr->items[index].str);
773 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
774 NULL, 0, NULL, NULL );
777 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
779 __TRY /* hide a Delphi bug that passes a read-only buffer */
783 strcpyW( buffer, descr->items[index].str );
784 len = strlenW(buffer);
788 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
789 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
794 WARN( "got an invalid buffer (Delphi bug?)\n" );
795 SetLastError( ERROR_INVALID_PARAMETER );
801 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
807 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
809 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
810 if (ret == CSTR_LESS_THAN)
812 if (ret == CSTR_EQUAL)
814 if (ret == CSTR_GREATER_THAN)
819 /***********************************************************************
820 * LISTBOX_FindStringPos
822 * Find the nearest string located before a given string in sort order.
823 * If 'exact' is TRUE, return an error if we don't get an exact match.
825 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
827 INT index, min, max, res = -1;
829 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
831 max = descr->nb_items;
834 index = (min + max) / 2;
835 if (HAS_STRINGS(descr))
836 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
839 COMPAREITEMSTRUCT cis;
840 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
842 cis.CtlType = ODT_LISTBOX;
844 cis.hwndItem = descr->self;
845 /* note that some application (MetaStock) expects the second item
846 * to be in the listbox */
848 cis.itemData1 = (ULONG_PTR)str;
850 cis.itemData2 = descr->items[index].data;
851 cis.dwLocaleId = descr->locale;
852 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
854 if (!res) return index;
855 if (res < 0) max = index;
856 else min = index + 1;
858 return exact ? -1 : max;
862 /***********************************************************************
863 * LISTBOX_FindFileStrPos
865 * Find the nearest string located before a given string in directory
866 * sort order (i.e. first files, then directories, then drives).
868 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
870 INT min, max, res = -1;
872 if (!HAS_STRINGS(descr))
873 return LISTBOX_FindStringPos( descr, str, FALSE );
875 max = descr->nb_items;
878 INT index = (min + max) / 2;
879 LPCWSTR p = descr->items[index].str;
880 if (*p == '[') /* drive or directory */
882 if (*str != '[') res = -1;
883 else if (p[1] == '-') /* drive */
885 if (str[1] == '-') res = str[2] - p[2];
890 if (str[1] == '-') res = 1;
891 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
896 if (*str == '[') res = 1;
897 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
899 if (!res) return index;
900 if (res < 0) max = index;
901 else min = index + 1;
907 /***********************************************************************
910 * Find the item beginning with a given string.
912 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
917 if (start >= descr->nb_items) start = -1;
918 item = descr->items + start + 1;
919 if (HAS_STRINGS(descr))
921 if (!str || ! str[0] ) return LB_ERR;
924 for (i = start + 1; i < descr->nb_items; i++, item++)
925 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
926 for (i = 0, item = descr->items; i <= start; i++, item++)
927 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
931 /* Special case for drives and directories: ignore prefix */
932 #define CHECK_DRIVE(item) \
933 if ((item)->str[0] == '[') \
935 if (!strncmpiW( str, (item)->str+1, len )) return i; \
936 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
940 INT len = strlenW(str);
941 for (i = start + 1; i < descr->nb_items; i++, item++)
943 if (!strncmpiW( str, item->str, len )) return i;
946 for (i = 0, item = descr->items; i <= start; i++, item++)
948 if (!strncmpiW( str, item->str, len )) return i;
956 if (exact && (descr->style & LBS_SORT))
957 /* If sorted, use a WM_COMPAREITEM binary search */
958 return LISTBOX_FindStringPos( descr, str, TRUE );
960 /* Otherwise use a linear search */
961 for (i = start + 1; i < descr->nb_items; i++, item++)
962 if (item->data == (ULONG_PTR)str) return i;
963 for (i = 0, item = descr->items; i <= start; i++, item++)
964 if (item->data == (ULONG_PTR)str) return i;
970 /***********************************************************************
971 * LISTBOX_GetSelCount
973 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
976 const LB_ITEMDATA *item = descr->items;
978 if (!(descr->style & LBS_MULTIPLESEL) ||
979 (descr->style & LBS_NOSEL))
981 for (i = count = 0; i < descr->nb_items; i++, item++)
982 if (item->selected) count++;
987 /***********************************************************************
988 * LISTBOX_GetSelItems
990 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
993 const LB_ITEMDATA *item = descr->items;
995 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
996 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
997 if (item->selected) array[count++] = i;
1002 /***********************************************************************
1005 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1007 INT i, col_pos = descr->page_size - 1;
1009 RECT focusRect = {-1, -1, -1, -1};
1011 HBRUSH hbrush, oldBrush = 0;
1013 if (descr->style & LBS_NOREDRAW) return 0;
1015 SetRect( &rect, 0, 0, descr->width, descr->height );
1016 if (descr->style & LBS_MULTICOLUMN)
1017 rect.right = rect.left + descr->column_width;
1018 else if (descr->horz_pos)
1020 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1021 rect.right += descr->horz_pos;
1024 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1025 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1026 (WPARAM)hdc, (LPARAM)descr->self );
1027 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1028 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1030 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1033 /* Special case for empty listbox: paint focus rect */
1034 rect.bottom = rect.top + descr->item_height;
1035 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1036 &rect, NULL, 0, NULL );
1037 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1038 rect.top = rect.bottom;
1041 /* Paint all the item, regarding the selection
1042 Focus state will be painted after */
1044 for (i = descr->top_item; i < descr->nb_items; i++)
1046 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1047 rect.bottom = rect.top + descr->item_height;
1049 rect.bottom = rect.top + descr->items[i].height;
1051 if (i == descr->focus_item)
1053 /* keep the focus rect, to paint the focus item after */
1054 focusRect.left = rect.left;
1055 focusRect.right = rect.right;
1056 focusRect.top = rect.top;
1057 focusRect.bottom = rect.bottom;
1059 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1060 rect.top = rect.bottom;
1062 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1064 if (!IS_OWNERDRAW(descr))
1066 /* Clear the bottom of the column */
1067 if (rect.top < descr->height)
1069 rect.bottom = descr->height;
1070 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1071 &rect, NULL, 0, NULL );
1075 /* Go to the next column */
1076 rect.left += descr->column_width;
1077 rect.right += descr->column_width;
1079 col_pos = descr->page_size - 1;
1084 if (rect.top >= descr->height) break;
1088 /* Paint the focus item now */
1089 if (focusRect.top != focusRect.bottom &&
1090 descr->caret_on && descr->in_focus)
1091 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1093 if (!IS_OWNERDRAW(descr))
1095 /* Clear the remainder of the client area */
1096 if (rect.top < descr->height)
1098 rect.bottom = descr->height;
1099 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1100 &rect, NULL, 0, NULL );
1102 if (rect.right < descr->width)
1104 rect.left = rect.right;
1105 rect.right = descr->width;
1107 rect.bottom = descr->height;
1108 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1109 &rect, NULL, 0, NULL );
1112 if (oldFont) SelectObject( hdc, oldFont );
1113 if (oldBrush) SelectObject( hdc, oldBrush );
1118 /***********************************************************************
1119 * LISTBOX_InvalidateItems
1121 * Invalidate all items from a given item. If the specified item is not
1122 * visible, nothing happens.
1124 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1128 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1130 if (descr->style & LBS_NOREDRAW)
1132 descr->style |= LBS_DISPLAYCHANGED;
1135 rect.bottom = descr->height;
1136 InvalidateRect( descr->self, &rect, TRUE );
1137 if (descr->style & LBS_MULTICOLUMN)
1139 /* Repaint the other columns */
1140 rect.left = rect.right;
1141 rect.right = descr->width;
1143 InvalidateRect( descr->self, &rect, TRUE );
1148 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1152 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1153 InvalidateRect( descr->self, &rect, TRUE );
1156 /***********************************************************************
1157 * LISTBOX_GetItemHeight
1159 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1161 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1163 if ((index < 0) || (index >= descr->nb_items))
1165 SetLastError(ERROR_INVALID_INDEX);
1168 return descr->items[index].height;
1170 else return descr->item_height;
1174 /***********************************************************************
1175 * LISTBOX_SetItemHeight
1177 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1179 if (height > MAXBYTE)
1182 if (!height) height = 1;
1184 if (descr->style & LBS_OWNERDRAWVARIABLE)
1186 if ((index < 0) || (index >= descr->nb_items))
1188 SetLastError(ERROR_INVALID_INDEX);
1191 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1192 descr->items[index].height = height;
1193 LISTBOX_UpdateScroll( descr );
1195 LISTBOX_InvalidateItems( descr, index );
1197 else if (height != descr->item_height)
1199 TRACE("[%p]: new height = %d\n", descr->self, height );
1200 descr->item_height = height;
1201 LISTBOX_UpdatePage( descr );
1202 LISTBOX_UpdateScroll( descr );
1204 InvalidateRect( descr->self, 0, TRUE );
1210 /***********************************************************************
1211 * LISTBOX_SetHorizontalPos
1213 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1217 if (pos > descr->horz_extent - descr->width)
1218 pos = descr->horz_extent - descr->width;
1219 if (pos < 0) pos = 0;
1220 if (!(diff = descr->horz_pos - pos)) return;
1221 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1222 descr->horz_pos = pos;
1223 LISTBOX_UpdateScroll( descr );
1224 if (abs(diff) < descr->width)
1227 /* Invalidate the focused item so it will be repainted correctly */
1228 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1229 InvalidateRect( descr->self, &rect, TRUE );
1230 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1231 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1234 InvalidateRect( descr->self, NULL, TRUE );
1238 /***********************************************************************
1239 * LISTBOX_SetHorizontalExtent
1241 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1243 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1245 if (extent <= 0) extent = 1;
1246 if (extent == descr->horz_extent) return LB_OKAY;
1247 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1248 descr->horz_extent = extent;
1249 if (descr->horz_pos > extent - descr->width)
1250 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1252 LISTBOX_UpdateScroll( descr );
1257 /***********************************************************************
1258 * LISTBOX_SetColumnWidth
1260 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1262 if (width == descr->column_width) return LB_OKAY;
1263 TRACE("[%p]: new column width = %d\n", descr->self, width );
1264 descr->column_width = width;
1265 LISTBOX_UpdatePage( descr );
1270 /***********************************************************************
1273 * Returns the item height.
1275 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1279 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1284 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1286 ERR("unable to get DC.\n" );
1289 if (font) oldFont = SelectObject( hdc, font );
1290 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1291 if (oldFont) SelectObject( hdc, oldFont );
1292 ReleaseDC( descr->self, hdc );
1294 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1295 if (!IS_OWNERDRAW(descr))
1296 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1301 /***********************************************************************
1302 * LISTBOX_MakeItemVisible
1304 * Make sure that a given item is partially or fully visible.
1306 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1310 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1312 if (index <= descr->top_item) top = index;
1313 else if (descr->style & LBS_MULTICOLUMN)
1315 INT cols = descr->width;
1316 if (!fully) cols += descr->column_width - 1;
1317 if (cols >= descr->column_width) cols /= descr->column_width;
1319 if (index < descr->top_item + (descr->page_size * cols)) return;
1320 top = index - descr->page_size * (cols - 1);
1322 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1324 INT height = fully ? descr->items[index].height : 1;
1325 for (top = index; top > descr->top_item; top--)
1326 if ((height += descr->items[top-1].height) > descr->height) break;
1330 if (index < descr->top_item + descr->page_size) return;
1331 if (!fully && (index == descr->top_item + descr->page_size) &&
1332 (descr->height > (descr->page_size * descr->item_height))) return;
1333 top = index - descr->page_size + 1;
1335 LISTBOX_SetTopItem( descr, top, TRUE );
1338 /***********************************************************************
1339 * LISTBOX_SetCaretIndex
1342 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1345 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1347 INT oldfocus = descr->focus_item;
1349 TRACE("old focus %d, index %d\n", oldfocus, index);
1351 if (descr->style & LBS_NOSEL) return LB_ERR;
1352 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1353 if (index == oldfocus) return LB_OKAY;
1355 LISTBOX_DrawFocusRect( descr, FALSE );
1356 descr->focus_item = index;
1358 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1359 LISTBOX_DrawFocusRect( descr, TRUE );
1365 /***********************************************************************
1366 * LISTBOX_SelectItemRange
1368 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1370 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1375 /* A few sanity checks */
1377 if (descr->style & LBS_NOSEL) return LB_ERR;
1378 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1380 if (!descr->nb_items) return LB_OKAY;
1382 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1383 if (first < 0) first = 0;
1384 if (last < first) return LB_OKAY;
1386 if (on) /* Turn selection on */
1388 for (i = first; i <= last; i++)
1390 if (descr->items[i].selected) continue;
1391 descr->items[i].selected = TRUE;
1392 LISTBOX_InvalidateItemRect(descr, i);
1395 else /* Turn selection off */
1397 for (i = first; i <= last; i++)
1399 if (!descr->items[i].selected) continue;
1400 descr->items[i].selected = FALSE;
1401 LISTBOX_InvalidateItemRect(descr, i);
1407 /***********************************************************************
1408 * LISTBOX_SetSelection
1410 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1411 BOOL on, BOOL send_notify )
1413 TRACE( "cur_sel=%d index=%d notify=%s\n",
1414 descr->selected_item, index, send_notify ? "YES" : "NO" );
1416 if (descr->style & LBS_NOSEL)
1418 descr->selected_item = index;
1421 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1422 if (descr->style & LBS_MULTIPLESEL)
1424 if (index == -1) /* Select all items */
1425 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1426 else /* Only one item */
1427 return LISTBOX_SelectItemRange( descr, index, index, on );
1431 INT oldsel = descr->selected_item;
1432 if (index == oldsel) return LB_OKAY;
1433 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1434 if (index != -1) descr->items[index].selected = TRUE;
1435 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1436 descr->selected_item = index;
1437 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1438 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1439 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1441 if( descr->lphc ) /* set selection change flag for parent combo */
1442 descr->lphc->wState |= CBF_SELCHANGE;
1448 /***********************************************************************
1451 * Change the caret position and extend the selection to the new caret.
1453 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1455 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1457 if ((index < 0) || (index >= descr->nb_items))
1460 /* Important, repaint needs to be done in this order if
1461 you want to mimic Windows behavior:
1462 1. Remove the focus and paint the item
1463 2. Remove the selection and paint the item(s)
1464 3. Set the selection and repaint the item(s)
1465 4. Set the focus to 'index' and repaint the item */
1467 /* 1. remove the focus and repaint the item */
1468 LISTBOX_DrawFocusRect( descr, FALSE );
1470 /* 2. then turn off the previous selection */
1471 /* 3. repaint the new selected item */
1472 if (descr->style & LBS_EXTENDEDSEL)
1474 if (descr->anchor_item != -1)
1476 INT first = min( index, descr->anchor_item );
1477 INT last = max( index, descr->anchor_item );
1479 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1480 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1481 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1484 else if (!(descr->style & LBS_MULTIPLESEL))
1486 /* Set selection to new caret item */
1487 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1490 /* 4. repaint the new item with the focus */
1491 descr->focus_item = index;
1492 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1493 LISTBOX_DrawFocusRect( descr, TRUE );
1497 /***********************************************************************
1498 * LISTBOX_InsertItem
1500 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1501 LPWSTR str, ULONG_PTR data )
1505 INT oldfocus = descr->focus_item;
1507 if (index == -1) index = descr->nb_items;
1508 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1509 if (!descr->items) max_items = 0;
1510 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1511 if (descr->nb_items == max_items)
1513 /* We need to grow the array */
1514 max_items += LB_ARRAY_GRANULARITY;
1516 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1517 max_items * sizeof(LB_ITEMDATA) );
1519 item = HeapAlloc( GetProcessHeap(), 0,
1520 max_items * sizeof(LB_ITEMDATA) );
1523 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1526 descr->items = item;
1529 /* Insert the item structure */
1531 item = &descr->items[index];
1532 if (index < descr->nb_items)
1533 RtlMoveMemory( item + 1, item,
1534 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1538 item->selected = FALSE;
1541 /* Get item height */
1543 if (descr->style & LBS_OWNERDRAWVARIABLE)
1545 MEASUREITEMSTRUCT mis;
1546 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1548 mis.CtlType = ODT_LISTBOX;
1551 mis.itemData = descr->items[index].data;
1552 mis.itemHeight = descr->item_height;
1553 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1554 item->height = mis.itemHeight ? mis.itemHeight : 1;
1555 TRACE("[%p]: measure item %d (%s) = %d\n",
1556 descr->self, index, str ? debugstr_w(str) : "", item->height );
1559 /* Repaint the items */
1561 LISTBOX_UpdateScroll( descr );
1562 LISTBOX_InvalidateItems( descr, index );
1564 /* Move selection and focused item */
1565 /* If listbox was empty, set focus to the first item */
1566 if (descr->nb_items == 1)
1567 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1568 /* single select don't change selection index in win31 */
1569 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1571 descr->selected_item++;
1572 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1576 if (index <= descr->selected_item)
1578 descr->selected_item++;
1579 descr->focus_item = oldfocus; /* focus not changed */
1586 /***********************************************************************
1587 * LISTBOX_InsertString
1589 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1591 LPWSTR new_str = NULL;
1595 if (HAS_STRINGS(descr))
1597 static const WCHAR empty_stringW[] = { 0 };
1598 if (!str) str = empty_stringW;
1599 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1601 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1604 strcpyW(new_str, str);
1606 else data = (ULONG_PTR)str;
1608 if (index == -1) index = descr->nb_items;
1609 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1611 HeapFree( GetProcessHeap(), 0, new_str );
1615 TRACE("[%p]: added item %d %s\n",
1616 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1621 /***********************************************************************
1622 * LISTBOX_DeleteItem
1624 * Delete the content of an item. 'index' must be a valid index.
1626 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1628 /* save the item data before it gets freed by LB_RESETCONTENT */
1629 ULONG_PTR item_data = descr->items[index].data;
1630 LPWSTR item_str = descr->items[index].str;
1632 if (!descr->nb_items)
1633 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1635 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1636 * while Win95 sends it for all items with user data.
1637 * It's probably better to send it too often than not
1638 * often enough, so this is what we do here.
1640 if (IS_OWNERDRAW(descr) || item_data)
1642 DELETEITEMSTRUCT dis;
1643 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1645 dis.CtlType = ODT_LISTBOX;
1648 dis.hwndItem = descr->self;
1649 dis.itemData = item_data;
1650 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1652 if (HAS_STRINGS(descr))
1653 HeapFree( GetProcessHeap(), 0, item_str );
1657 /***********************************************************************
1658 * LISTBOX_RemoveItem
1660 * Remove an item from the listbox and delete its content.
1662 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1667 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1669 /* We need to invalidate the original rect instead of the updated one. */
1670 LISTBOX_InvalidateItems( descr, index );
1673 LISTBOX_DeleteItem( descr, index );
1675 if (!descr->nb_items) return LB_OKAY;
1677 /* Remove the item */
1679 item = &descr->items[index];
1680 if (index < descr->nb_items)
1681 RtlMoveMemory( item, item + 1,
1682 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1683 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1685 /* Shrink the item array if possible */
1687 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1688 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1690 max_items -= LB_ARRAY_GRANULARITY;
1691 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1692 max_items * sizeof(LB_ITEMDATA) );
1693 if (item) descr->items = item;
1695 /* Repaint the items */
1697 LISTBOX_UpdateScroll( descr );
1698 /* if we removed the scrollbar, reset the top of the list
1699 (correct for owner-drawn ???) */
1700 if (descr->nb_items == descr->page_size)
1701 LISTBOX_SetTopItem( descr, 0, TRUE );
1703 /* Move selection and focused item */
1704 if (!IS_MULTISELECT(descr))
1706 if (index == descr->selected_item)
1707 descr->selected_item = -1;
1708 else if (index < descr->selected_item)
1710 descr->selected_item--;
1711 if (ISWIN31) /* win 31 do not change the selected item number */
1712 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1716 if (descr->focus_item >= descr->nb_items)
1718 descr->focus_item = descr->nb_items - 1;
1719 if (descr->focus_item < 0) descr->focus_item = 0;
1725 /***********************************************************************
1726 * LISTBOX_ResetContent
1728 static void LISTBOX_ResetContent( LB_DESCR *descr )
1732 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1733 HeapFree( GetProcessHeap(), 0, descr->items );
1734 descr->nb_items = 0;
1735 descr->top_item = 0;
1736 descr->selected_item = -1;
1737 descr->focus_item = 0;
1738 descr->anchor_item = -1;
1739 descr->items = NULL;
1743 /***********************************************************************
1746 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1750 if (HAS_STRINGS(descr))
1752 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1756 /* FIXME: this is far from optimal... */
1757 if (count > descr->nb_items)
1759 while (count > descr->nb_items)
1760 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1763 else if (count < descr->nb_items)
1765 while (count < descr->nb_items)
1766 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1773 /***********************************************************************
1776 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1777 LPCWSTR filespec, BOOL long_names )
1780 LRESULT ret = LB_OKAY;
1781 WIN32_FIND_DATAW entry;
1783 LRESULT maxinsert = LB_ERR;
1785 /* don't scan directory if we just want drives exclusively */
1786 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1787 /* scan directory */
1788 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1790 int le = GetLastError();
1791 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1798 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1800 static const WCHAR bracketW[] = { ']',0 };
1801 static const WCHAR dotW[] = { '.',0 };
1802 if (!(attrib & DDL_DIRECTORY) ||
1803 !strcmpW( entry.cFileName, dotW )) continue;
1805 if (!long_names && entry.cAlternateFileName[0])
1806 strcpyW( buffer + 1, entry.cAlternateFileName );
1808 strcpyW( buffer + 1, entry.cFileName );
1809 strcatW(buffer, bracketW);
1811 else /* not a directory */
1813 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1814 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1816 if ((attrib & DDL_EXCLUSIVE) &&
1817 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1820 if (!long_names && entry.cAlternateFileName[0])
1821 strcpyW( buffer, entry.cAlternateFileName );
1823 strcpyW( buffer, entry.cFileName );
1825 if (!long_names) CharLowerW( buffer );
1826 pos = LISTBOX_FindFileStrPos( descr, buffer );
1827 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1829 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1830 } while (FindNextFileW( handle, &entry ));
1831 FindClose( handle );
1839 if (attrib & DDL_DRIVES)
1841 WCHAR buffer[] = {'[','-','a','-',']',0};
1842 WCHAR root[] = {'A',':','\\',0};
1844 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1846 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1847 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1856 /***********************************************************************
1857 * LISTBOX_HandleVScroll
1859 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1863 if (descr->style & LBS_MULTICOLUMN) return 0;
1867 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1870 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1873 LISTBOX_SetTopItem( descr, descr->top_item -
1874 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1877 LISTBOX_SetTopItem( descr, descr->top_item +
1878 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1880 case SB_THUMBPOSITION:
1881 LISTBOX_SetTopItem( descr, pos, TRUE );
1884 info.cbSize = sizeof(info);
1885 info.fMask = SIF_TRACKPOS;
1886 GetScrollInfo( descr->self, SB_VERT, &info );
1887 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1890 LISTBOX_SetTopItem( descr, 0, TRUE );
1893 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1900 /***********************************************************************
1901 * LISTBOX_HandleHScroll
1903 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1908 if (descr->style & LBS_MULTICOLUMN)
1913 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1917 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1921 page = descr->width / descr->column_width;
1922 if (page < 1) page = 1;
1923 LISTBOX_SetTopItem( descr,
1924 descr->top_item - page * descr->page_size, TRUE );
1927 page = descr->width / descr->column_width;
1928 if (page < 1) page = 1;
1929 LISTBOX_SetTopItem( descr,
1930 descr->top_item + page * descr->page_size, TRUE );
1932 case SB_THUMBPOSITION:
1933 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1936 info.cbSize = sizeof(info);
1937 info.fMask = SIF_TRACKPOS;
1938 GetScrollInfo( descr->self, SB_VERT, &info );
1939 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1943 LISTBOX_SetTopItem( descr, 0, TRUE );
1946 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1950 else if (descr->horz_extent)
1955 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1958 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1961 LISTBOX_SetHorizontalPos( descr,
1962 descr->horz_pos - descr->width );
1965 LISTBOX_SetHorizontalPos( descr,
1966 descr->horz_pos + descr->width );
1968 case SB_THUMBPOSITION:
1969 LISTBOX_SetHorizontalPos( descr, pos );
1972 info.cbSize = sizeof(info);
1973 info.fMask = SIF_TRACKPOS;
1974 GetScrollInfo( descr->self, SB_HORZ, &info );
1975 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1978 LISTBOX_SetHorizontalPos( descr, 0 );
1981 LISTBOX_SetHorizontalPos( descr,
1982 descr->horz_extent - descr->width );
1989 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1991 short gcWheelDelta = 0;
1992 UINT pulScrollLines = 3;
1994 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1996 gcWheelDelta -= delta;
1998 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2000 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2001 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2002 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2007 /***********************************************************************
2008 * LISTBOX_HandleLButtonDown
2010 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2012 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2014 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2015 descr->self, x, y, index, descr->focus_item);
2017 if (!descr->caret_on && (descr->in_focus)) return 0;
2019 if (!descr->in_focus)
2021 if( !descr->lphc ) SetFocus( descr->self );
2022 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2025 if (index == -1) return 0;
2029 if (descr->style & LBS_NOTIFY )
2030 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2031 MAKELPARAM( x, y ) );
2034 descr->captured = TRUE;
2035 SetCapture( descr->self );
2037 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2039 /* we should perhaps make sure that all items are deselected
2040 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2041 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2042 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2045 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2046 if (keys & MK_CONTROL)
2048 LISTBOX_SetCaretIndex( descr, index, FALSE );
2049 LISTBOX_SetSelection( descr, index,
2050 !descr->items[index].selected,
2051 (descr->style & LBS_NOTIFY) != 0);
2055 LISTBOX_MoveCaret( descr, index, FALSE );
2057 if (descr->style & LBS_EXTENDEDSEL)
2059 LISTBOX_SetSelection( descr, index,
2060 descr->items[index].selected,
2061 (descr->style & LBS_NOTIFY) != 0 );
2065 LISTBOX_SetSelection( descr, index,
2066 !descr->items[index].selected,
2067 (descr->style & LBS_NOTIFY) != 0 );
2073 descr->anchor_item = index;
2074 LISTBOX_MoveCaret( descr, index, FALSE );
2075 LISTBOX_SetSelection( descr, index,
2076 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2081 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2088 if (DragDetect( descr->self, pt ))
2089 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2096 /*************************************************************************
2097 * LISTBOX_HandleLButtonDownCombo [Internal]
2099 * Process LButtonDown message for the ComboListBox
2102 * pWnd [I] The windows internal structure
2103 * pDescr [I] The ListBox internal structure
2104 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2105 * x [I] X Mouse Coordinate
2106 * y [I] Y Mouse Coordinate
2109 * 0 since we are processing the WM_LBUTTONDOWN Message
2112 * This function is only to be used when a ListBox is a ComboListBox
2115 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2117 RECT clientRect, screenRect;
2123 GetClientRect(descr->self, &clientRect);
2125 if(PtInRect(&clientRect, mousePos))
2127 /* MousePos is in client, resume normal processing */
2128 if (msg == WM_LBUTTONDOWN)
2130 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2131 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2133 else if (descr->style & LBS_NOTIFY)
2134 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2138 POINT screenMousePos;
2139 HWND hWndOldCapture;
2141 /* Check the Non-Client Area */
2142 screenMousePos = mousePos;
2143 hWndOldCapture = GetCapture();
2145 GetWindowRect(descr->self, &screenRect);
2146 ClientToScreen(descr->self, &screenMousePos);
2148 if(!PtInRect(&screenRect, screenMousePos))
2150 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2151 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2152 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2156 /* Check to see the NC is a scrollbar */
2158 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2159 /* Check Vertical scroll bar */
2160 if (style & WS_VSCROLL)
2162 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2163 if (PtInRect( &clientRect, mousePos ))
2164 nHitTestType = HTVSCROLL;
2166 /* Check horizontal scroll bar */
2167 if (style & WS_HSCROLL)
2169 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2170 if (PtInRect( &clientRect, mousePos ))
2171 nHitTestType = HTHSCROLL;
2173 /* Windows sends this message when a scrollbar is clicked
2176 if(nHitTestType != 0)
2178 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2179 MAKELONG(screenMousePos.x, screenMousePos.y));
2181 /* Resume the Capture after scrolling is complete
2183 if(hWndOldCapture != 0)
2184 SetCapture(hWndOldCapture);
2190 /***********************************************************************
2191 * LISTBOX_HandleLButtonUp
2193 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2195 if (LISTBOX_Timer != LB_TIMER_NONE)
2196 KillSystemTimer( descr->self, LB_TIMER_ID );
2197 LISTBOX_Timer = LB_TIMER_NONE;
2198 if (descr->captured)
2200 descr->captured = FALSE;
2201 if (GetCapture() == descr->self) ReleaseCapture();
2202 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2203 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2209 /***********************************************************************
2210 * LISTBOX_HandleTimer
2212 * Handle scrolling upon a timer event.
2213 * Return TRUE if scrolling should continue.
2215 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2220 if (descr->top_item) index = descr->top_item - 1;
2224 if (descr->top_item) index -= descr->page_size;
2227 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2228 if (index == descr->focus_item) index++;
2229 if (index >= descr->nb_items) index = descr->nb_items - 1;
2231 case LB_TIMER_RIGHT:
2232 if (index + descr->page_size < descr->nb_items)
2233 index += descr->page_size;
2238 if (index == descr->focus_item) return FALSE;
2239 LISTBOX_MoveCaret( descr, index, FALSE );
2244 /***********************************************************************
2245 * LISTBOX_HandleSystemTimer
2247 * WM_SYSTIMER handler.
2249 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2251 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2253 KillSystemTimer( descr->self, LB_TIMER_ID );
2254 LISTBOX_Timer = LB_TIMER_NONE;
2260 /***********************************************************************
2261 * LISTBOX_HandleMouseMove
2263 * WM_MOUSEMOVE handler.
2265 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2269 TIMER_DIRECTION dir = LB_TIMER_NONE;
2271 if (!descr->captured) return;
2273 if (descr->style & LBS_MULTICOLUMN)
2276 else if (y >= descr->item_height * descr->page_size)
2277 y = descr->item_height * descr->page_size - 1;
2281 dir = LB_TIMER_LEFT;
2284 else if (x >= descr->width)
2286 dir = LB_TIMER_RIGHT;
2287 x = descr->width - 1;
2292 if (y < 0) dir = LB_TIMER_UP; /* above */
2293 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2296 index = LISTBOX_GetItemFromPoint( descr, x, y );
2297 if (index == -1) index = descr->focus_item;
2298 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2300 /* Start/stop the system timer */
2302 if (dir != LB_TIMER_NONE)
2303 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2304 else if (LISTBOX_Timer != LB_TIMER_NONE)
2305 KillSystemTimer( descr->self, LB_TIMER_ID );
2306 LISTBOX_Timer = dir;
2310 /***********************************************************************
2311 * LISTBOX_HandleKeyDown
2313 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2316 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2317 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2318 bForceSelection = FALSE; /* only for single select list */
2320 if (descr->style & LBS_WANTKEYBOARDINPUT)
2322 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2323 MAKEWPARAM(LOWORD(key), descr->focus_item),
2324 (LPARAM)descr->self );
2325 if (caret == -2) return 0;
2327 if (caret == -1) switch(key)
2330 if (descr->style & LBS_MULTICOLUMN)
2332 bForceSelection = FALSE;
2333 if (descr->focus_item >= descr->page_size)
2334 caret = descr->focus_item - descr->page_size;
2339 caret = descr->focus_item - 1;
2340 if (caret < 0) caret = 0;
2343 if (descr->style & LBS_MULTICOLUMN)
2345 bForceSelection = FALSE;
2346 if (descr->focus_item + descr->page_size < descr->nb_items)
2347 caret = descr->focus_item + descr->page_size;
2352 caret = descr->focus_item + 1;
2353 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2357 if (descr->style & LBS_MULTICOLUMN)
2359 INT page = descr->width / descr->column_width;
2360 if (page < 1) page = 1;
2361 caret = descr->focus_item - (page * descr->page_size) + 1;
2363 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2364 if (caret < 0) caret = 0;
2367 if (descr->style & LBS_MULTICOLUMN)
2369 INT page = descr->width / descr->column_width;
2370 if (page < 1) page = 1;
2371 caret = descr->focus_item + (page * descr->page_size) - 1;
2373 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2374 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2380 caret = descr->nb_items - 1;
2383 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2384 else if (descr->style & LBS_MULTIPLESEL)
2386 LISTBOX_SetSelection( descr, descr->focus_item,
2387 !descr->items[descr->focus_item].selected,
2388 (descr->style & LBS_NOTIFY) != 0 );
2392 bForceSelection = FALSE;
2394 if (bForceSelection) /* focused item is used instead of key */
2395 caret = descr->focus_item;
2398 if (((descr->style & LBS_EXTENDEDSEL) &&
2399 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2400 !IS_MULTISELECT(descr))
2401 descr->anchor_item = caret;
2402 LISTBOX_MoveCaret( descr, caret, TRUE );
2404 if (descr->style & LBS_MULTIPLESEL)
2405 descr->selected_item = caret;
2407 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2408 if (descr->style & LBS_NOTIFY)
2410 if (descr->lphc && IsWindowVisible( descr->self ))
2412 /* make sure that combo parent doesn't hide us */
2413 descr->lphc->wState |= CBF_NOROLLUP;
2415 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2422 /***********************************************************************
2423 * LISTBOX_HandleChar
2425 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2433 if (descr->style & LBS_WANTKEYBOARDINPUT)
2435 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2436 MAKEWPARAM(charW, descr->focus_item),
2437 (LPARAM)descr->self );
2438 if (caret == -2) return 0;
2441 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2444 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2445 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2446 LISTBOX_MoveCaret( descr, caret, TRUE );
2447 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2448 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2454 /***********************************************************************
2457 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2460 MEASUREITEMSTRUCT mis;
2463 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2466 GetClientRect( hwnd, &rect );
2468 descr->owner = GetParent( descr->self );
2469 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2470 descr->width = rect.right - rect.left;
2471 descr->height = rect.bottom - rect.top;
2472 descr->items = NULL;
2473 descr->nb_items = 0;
2474 descr->top_item = 0;
2475 descr->selected_item = -1;
2476 descr->focus_item = 0;
2477 descr->anchor_item = -1;
2478 descr->item_height = 1;
2479 descr->page_size = 1;
2480 descr->column_width = 150;
2481 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2482 descr->horz_pos = 0;
2485 descr->caret_on = !lphc;
2486 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2487 descr->in_focus = FALSE;
2488 descr->captured = FALSE;
2490 descr->locale = GetUserDefaultLCID();
2495 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2496 descr->owner = lphc->self;
2499 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2501 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2503 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2504 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2505 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2506 descr->item_height = LISTBOX_SetFont( descr, 0 );
2508 if (descr->style & LBS_OWNERDRAWFIXED)
2510 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2512 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2513 descr->item_height = lphc->fixedOwnerDrawHeight;
2517 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2518 mis.CtlType = ODT_LISTBOX;
2523 mis.itemHeight = descr->item_height;
2524 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2525 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2529 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2534 /***********************************************************************
2537 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2539 LISTBOX_ResetContent( descr );
2540 SetWindowLongPtrW( descr->self, 0, 0 );
2541 HeapFree( GetProcessHeap(), 0, descr );
2546 /***********************************************************************
2547 * ListBoxWndProc_common
2549 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2551 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2552 LPHEADCOMBO lphc = 0;
2557 if (!IsWindow(hwnd)) return 0;
2559 if (msg == WM_CREATE)
2561 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2562 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2563 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2564 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2567 /* Ignore all other messages before we get a WM_CREATE */
2568 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2569 DefWindowProcA( hwnd, msg, wParam, lParam );
2571 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2573 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2574 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2578 case LB_RESETCONTENT:
2579 LISTBOX_ResetContent( descr );
2580 LISTBOX_UpdateScroll( descr );
2581 InvalidateRect( descr->self, NULL, TRUE );
2588 if(unicode || !HAS_STRINGS(descr))
2589 textW = (LPWSTR)lParam;
2592 LPSTR textA = (LPSTR)lParam;
2593 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2594 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2595 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2599 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2600 ret = LISTBOX_InsertString( descr, wParam, textW );
2601 if (!unicode && HAS_STRINGS(descr))
2602 HeapFree(GetProcessHeap(), 0, textW);
2606 case LB_INSERTSTRING:
2610 if(unicode || !HAS_STRINGS(descr))
2611 textW = (LPWSTR)lParam;
2614 LPSTR textA = (LPSTR)lParam;
2615 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2616 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2617 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2621 ret = LISTBOX_InsertString( descr, wParam, textW );
2622 if(!unicode && HAS_STRINGS(descr))
2623 HeapFree(GetProcessHeap(), 0, textW);
2631 if(unicode || !HAS_STRINGS(descr))
2632 textW = (LPWSTR)lParam;
2635 LPSTR textA = (LPSTR)lParam;
2636 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2637 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2638 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2642 wParam = LISTBOX_FindFileStrPos( descr, textW );
2643 ret = LISTBOX_InsertString( descr, wParam, textW );
2644 if(!unicode && HAS_STRINGS(descr))
2645 HeapFree(GetProcessHeap(), 0, textW);
2649 case LB_DELETESTRING:
2650 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2651 return descr->nb_items;
2654 SetLastError(ERROR_INVALID_INDEX);
2658 case LB_GETITEMDATA:
2659 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2661 SetLastError(ERROR_INVALID_INDEX);
2664 return descr->items[wParam].data;
2666 case LB_SETITEMDATA:
2667 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2669 SetLastError(ERROR_INVALID_INDEX);
2672 descr->items[wParam].data = lParam;
2673 /* undocumented: returns TRUE, not LB_OKAY (0) */
2677 return descr->nb_items;
2680 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2683 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2685 SetLastError(ERROR_INVALID_INDEX);
2688 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2689 if (unicode) return strlenW( descr->items[wParam].str );
2690 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2691 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2694 if (descr->nb_items == 0)
2696 if (!IS_MULTISELECT(descr))
2697 return descr->selected_item;
2698 if (descr->selected_item != -1)
2699 return descr->selected_item;
2700 return descr->focus_item;
2701 /* otherwise, if the user tries to move the selection with the */
2702 /* arrow keys, we will give the application something to choke on */
2703 case LB_GETTOPINDEX:
2704 return descr->top_item;
2706 case LB_GETITEMHEIGHT:
2707 return LISTBOX_GetItemHeight( descr, wParam );
2709 case LB_SETITEMHEIGHT:
2710 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2712 case LB_ITEMFROMPOINT:
2719 /* The hiword of the return value is not a client area
2720 hittest as suggested by MSDN, but rather a hittest on
2721 the returned listbox item. */
2723 if(descr->nb_items == 0)
2724 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2726 pt.x = (short)LOWORD(lParam);
2727 pt.y = (short)HIWORD(lParam);
2729 SetRect(&rect, 0, 0, descr->width, descr->height);
2731 if(!PtInRect(&rect, pt))
2733 pt.x = min(pt.x, rect.right - 1);
2734 pt.x = max(pt.x, 0);
2735 pt.y = min(pt.y, rect.bottom - 1);
2736 pt.y = max(pt.y, 0);
2740 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2744 index = descr->nb_items - 1;
2747 return MAKELONG(index, hit ? 0 : 1);
2750 case LB_SETCARETINDEX:
2751 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2752 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2759 case LB_GETCARETINDEX:
2760 return descr->focus_item;
2762 case LB_SETTOPINDEX:
2763 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2765 case LB_SETCOLUMNWIDTH:
2766 return LISTBOX_SetColumnWidth( descr, wParam );
2768 case LB_GETITEMRECT:
2769 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2775 if(unicode || !HAS_STRINGS(descr))
2776 textW = (LPWSTR)lParam;
2779 LPSTR textA = (LPSTR)lParam;
2780 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2781 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2782 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2784 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2785 if(!unicode && HAS_STRINGS(descr))
2786 HeapFree(GetProcessHeap(), 0, textW);
2790 case LB_FINDSTRINGEXACT:
2794 if(unicode || !HAS_STRINGS(descr))
2795 textW = (LPWSTR)lParam;
2798 LPSTR textA = (LPSTR)lParam;
2799 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2800 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2801 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2803 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2804 if(!unicode && HAS_STRINGS(descr))
2805 HeapFree(GetProcessHeap(), 0, textW);
2809 case LB_SELECTSTRING:
2814 if(HAS_STRINGS(descr))
2815 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2816 debugstr_a((LPSTR)lParam));
2817 if(unicode || !HAS_STRINGS(descr))
2818 textW = (LPWSTR)lParam;
2821 LPSTR textA = (LPSTR)lParam;
2822 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2823 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2824 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2826 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2827 if(!unicode && HAS_STRINGS(descr))
2828 HeapFree(GetProcessHeap(), 0, textW);
2829 if (index != LB_ERR)
2831 LISTBOX_MoveCaret( descr, index, TRUE );
2832 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2838 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2840 return descr->items[wParam].selected;
2843 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2846 if (IS_MULTISELECT(descr)) return LB_ERR;
2847 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2848 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2849 if (ret != LB_ERR) ret = descr->selected_item;
2852 case LB_GETSELCOUNT:
2853 return LISTBOX_GetSelCount( descr );
2855 case LB_GETSELITEMS:
2856 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2858 case LB_SELITEMRANGE:
2859 if (LOWORD(lParam) <= HIWORD(lParam))
2860 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2861 HIWORD(lParam), wParam );
2863 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2864 LOWORD(lParam), wParam );
2866 case LB_SELITEMRANGEEX:
2867 if ((INT)lParam >= (INT)wParam)
2868 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2870 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2872 case LB_GETHORIZONTALEXTENT:
2873 return descr->horz_extent;
2875 case LB_SETHORIZONTALEXTENT:
2876 return LISTBOX_SetHorizontalExtent( descr, wParam );
2878 case LB_GETANCHORINDEX:
2879 return descr->anchor_item;
2881 case LB_SETANCHORINDEX:
2882 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2884 SetLastError(ERROR_INVALID_INDEX);
2887 descr->anchor_item = (INT)wParam;
2895 textW = (LPWSTR)lParam;
2898 LPSTR textA = (LPSTR)lParam;
2899 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2900 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2901 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2903 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2905 HeapFree(GetProcessHeap(), 0, textW);
2910 return descr->locale;
2915 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2917 ret = descr->locale;
2918 descr->locale = (LCID)wParam;
2922 case LB_INITSTORAGE:
2923 return LISTBOX_InitStorage( descr, wParam );
2926 return LISTBOX_SetCount( descr, (INT)wParam );
2928 case LB_SETTABSTOPS:
2929 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2932 if (descr->caret_on)
2934 descr->caret_on = TRUE;
2935 if ((descr->focus_item != -1) && (descr->in_focus))
2936 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2940 if (!descr->caret_on)
2942 descr->caret_on = FALSE;
2943 if ((descr->focus_item != -1) && (descr->in_focus))
2944 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2947 case LB_GETLISTBOXINFO:
2948 FIXME("LB_GETLISTBOXINFO: stub!\n");
2952 return LISTBOX_Destroy( descr );
2955 InvalidateRect( descr->self, NULL, TRUE );
2959 LISTBOX_SetRedraw( descr, wParam != 0 );
2963 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2965 case WM_PRINTCLIENT:
2969 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2970 ret = LISTBOX_Paint( descr, hdc );
2971 if( !wParam ) EndPaint( descr->self, &ps );
2975 LISTBOX_UpdateSize( descr );
2978 return (LRESULT)descr->font;
2980 LISTBOX_SetFont( descr, (HFONT)wParam );
2981 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2984 descr->in_focus = TRUE;
2985 descr->caret_on = TRUE;
2986 if (descr->focus_item != -1)
2987 LISTBOX_DrawFocusRect( descr, TRUE );
2988 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2991 descr->in_focus = FALSE;
2992 if ((descr->focus_item != -1) && descr->caret_on)
2993 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2994 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
2997 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2999 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3001 if (wParam & (MK_SHIFT | MK_CONTROL))
3002 return DefWindowProcW( descr->self, msg, wParam, lParam );
3003 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3004 case WM_LBUTTONDOWN:
3006 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3007 (INT16)LOWORD(lParam),
3008 (INT16)HIWORD(lParam) );
3009 return LISTBOX_HandleLButtonDown( descr, wParam,
3010 (INT16)LOWORD(lParam),
3011 (INT16)HIWORD(lParam) );
3012 case WM_LBUTTONDBLCLK:
3014 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3015 (INT16)LOWORD(lParam),
3016 (INT16)HIWORD(lParam) );
3017 if (descr->style & LBS_NOTIFY)
3018 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3021 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3023 BOOL captured = descr->captured;
3027 mousePos.x = (INT16)LOWORD(lParam);
3028 mousePos.y = (INT16)HIWORD(lParam);
3031 * If we are in a dropdown combobox, we simulate that
3032 * the mouse is captured to show the tracking of the item.
3034 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3035 descr->captured = TRUE;
3037 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3039 descr->captured = captured;
3041 else if (GetCapture() == descr->self)
3043 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3044 (INT16)HIWORD(lParam) );
3054 * If the mouse button "up" is not in the listbox,
3055 * we make sure there is no selection by re-selecting the
3056 * item that was selected when the listbox was made visible.
3058 mousePos.x = (INT16)LOWORD(lParam);
3059 mousePos.y = (INT16)HIWORD(lParam);
3061 GetClientRect(descr->self, &clientRect);
3064 * When the user clicks outside the combobox and the focus
3065 * is lost, the owning combobox will send a fake buttonup with
3066 * 0xFFFFFFF as the mouse location, we must also revert the
3067 * selection to the original selection.
3069 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3070 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3072 return LISTBOX_HandleLButtonUp( descr );
3074 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3076 /* for some reason Windows makes it possible to
3077 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3079 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3080 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3081 && (wParam == VK_DOWN || wParam == VK_UP)) )
3083 COMBO_FlipListbox( lphc, FALSE, FALSE );
3087 return LISTBOX_HandleKeyDown( descr, wParam );
3092 charW = (WCHAR)wParam;
3095 CHAR charA = (CHAR)wParam;
3096 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3098 return LISTBOX_HandleChar( descr, charW );
3101 return LISTBOX_HandleSystemTimer( descr );
3103 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3106 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3107 wParam, (LPARAM)descr->self );
3108 TRACE("hbrush = %p\n", hbrush);
3110 hbrush = GetSysColorBrush(COLOR_WINDOW);
3113 GetClientRect(descr->self, &rect);
3114 FillRect((HDC)wParam, &rect, hbrush);
3119 if( lphc ) return 0;
3120 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3121 SendMessageA( descr->owner, msg, wParam, lParam );
3124 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3133 if ((msg >= WM_USER) && (msg < 0xc000))
3134 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3135 hwnd, msg, wParam, lParam );
3138 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3139 DefWindowProcA( hwnd, msg, wParam, lParam );