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/winuser16.h"
43 #include "wine/unicode.h"
44 #include "user_private.h"
46 #include "wine/exception.h"
47 #include "wine/debug.h"
49 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
51 /* Items array granularity */
52 #define LB_ARRAY_GRANULARITY 16
54 /* Scrolling timeout in ms */
55 #define LB_SCROLL_TIMEOUT 50
57 /* Listbox system timer id */
60 /* flag listbox changed while setredraw false - internal style */
61 #define LBS_DISPLAYCHANGED 0x80000000
66 LPWSTR str; /* Item text */
67 BOOL selected; /* Is item selected? */
68 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
69 ULONG_PTR data; /* User data */
72 /* Listbox structure */
75 HWND self; /* Our own window handle */
76 HWND owner; /* Owner window to send notifications to */
77 UINT style; /* Window style */
78 INT width; /* Window width */
79 INT height; /* Window height */
80 LB_ITEMDATA *items; /* Array of items */
81 INT nb_items; /* Number of items */
82 INT top_item; /* Top visible item */
83 INT selected_item; /* Selected item */
84 INT focus_item; /* Item that has the focus */
85 INT anchor_item; /* Anchor item for extended selection */
86 INT item_height; /* Default item height */
87 INT page_size; /* Items per listbox page */
88 INT column_width; /* Column width for multi-column listboxes */
89 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
90 INT horz_pos; /* Horizontal position */
91 INT nb_tabs; /* Number of tabs in array */
92 INT *tabs; /* Array of tabs */
93 INT avg_char_width; /* Average width of characters */
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 WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
133 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
135 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
137 /*********************************************************************
138 * listbox class descriptor
140 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
141 const struct builtin_class_descr LISTBOX_builtin_class =
144 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
145 ListBoxWndProcA, /* procA */
146 ListBoxWndProcW, /* procW */
147 sizeof(LB_DESCR *), /* extra */
148 IDC_ARROW, /* cursor */
153 /*********************************************************************
154 * combolbox class descriptor
156 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
157 const struct builtin_class_descr COMBOLBOX_builtin_class =
159 combolboxW, /* name */
160 CS_DBLCLKS | CS_SAVEBITS, /* style */
161 ListBoxWndProcA, /* procA */
162 ListBoxWndProcW, /* procW */
163 sizeof(LB_DESCR *), /* extra */
164 IDC_ARROW, /* cursor */
169 /* check whether app is a Win 3.1 app */
170 static inline BOOL is_old_app( LB_DESCR *descr )
172 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
176 /***********************************************************************
177 * LISTBOX_GetCurrentPageSize
179 * Return the current page size
181 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
184 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
185 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
187 if ((height += descr->items[i].height) > descr->height) break;
189 if (i == descr->top_item) return 1;
190 else return i - descr->top_item;
194 /***********************************************************************
195 * LISTBOX_GetMaxTopIndex
197 * Return the maximum possible index for the top of the listbox.
199 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
203 if (descr->style & LBS_OWNERDRAWVARIABLE)
205 page = descr->height;
206 for (max = descr->nb_items - 1; max >= 0; max--)
207 if ((page -= descr->items[max].height) < 0) break;
208 if (max < descr->nb_items - 1) max++;
210 else if (descr->style & LBS_MULTICOLUMN)
212 if ((page = descr->width / descr->column_width) < 1) page = 1;
213 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
214 max = (max - page) * descr->page_size;
218 max = descr->nb_items - descr->page_size;
220 if (max < 0) max = 0;
225 /***********************************************************************
226 * LISTBOX_UpdateScroll
228 * Update the scrollbars. Should be called whenever the content
229 * of the listbox changes.
231 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
235 /* Check the listbox scroll bar flags individually before we call
236 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
237 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
238 scroll bar when we do not need one.
239 if (!(descr->style & WS_VSCROLL)) return;
242 /* It is important that we check descr->style, and not wnd->dwStyle,
243 for WS_VSCROLL, as the former is exactly the one passed in
244 argument to CreateWindow.
245 In Windows (and from now on in Wine :) a listbox created
246 with such a style (no WS_SCROLL) does not update
247 the scrollbar with listbox-related data, thus letting
248 the programmer use it for his/her own purposes. */
250 if (descr->style & LBS_NOREDRAW) return;
251 info.cbSize = sizeof(info);
253 if (descr->style & LBS_MULTICOLUMN)
256 info.nMax = (descr->nb_items - 1) / descr->page_size;
257 info.nPos = descr->top_item / descr->page_size;
258 info.nPage = descr->width / descr->column_width;
259 if (info.nPage < 1) info.nPage = 1;
260 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
261 if (descr->style & LBS_DISABLENOSCROLL)
262 info.fMask |= SIF_DISABLENOSCROLL;
263 if (descr->style & WS_HSCROLL)
264 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
266 info.fMask = SIF_RANGE;
267 if (descr->style & WS_VSCROLL)
268 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
273 info.nMax = descr->nb_items - 1;
274 info.nPos = descr->top_item;
275 info.nPage = LISTBOX_GetCurrentPageSize( descr );
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_VSCROLL)
280 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
282 if (descr->horz_extent)
285 info.nMax = descr->horz_extent - 1;
286 info.nPos = descr->horz_pos;
287 info.nPage = descr->width;
288 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
289 if (descr->style & LBS_DISABLENOSCROLL)
290 info.fMask |= SIF_DISABLENOSCROLL;
291 if (descr->style & WS_HSCROLL)
292 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
298 /***********************************************************************
301 * Set the top item of the listbox, scrolling up or down if necessary.
303 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
305 INT max = LISTBOX_GetMaxTopIndex( descr );
307 TRACE("setting top item %d, scroll %d\n", index, scroll);
309 if (index > max) index = max;
310 if (index < 0) index = 0;
311 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
312 if (descr->top_item == index) return LB_OKAY;
313 if (descr->style & LBS_MULTICOLUMN)
315 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
316 if (scroll && (abs(diff) < descr->width))
317 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
318 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
326 if (descr->style & LBS_OWNERDRAWVARIABLE)
330 if (index > descr->top_item)
332 for (i = index - 1; i >= descr->top_item; i--)
333 diff -= descr->items[i].height;
337 for (i = index; i < descr->top_item; i++)
338 diff += descr->items[i].height;
342 diff = (descr->top_item - index) * descr->item_height;
344 if (abs(diff) < descr->height)
345 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
346 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
350 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
351 descr->top_item = index;
352 LISTBOX_UpdateScroll( descr );
357 /***********************************************************************
360 * Update the page size. Should be called when the size of
361 * the client area or the item height changes.
363 static void LISTBOX_UpdatePage( LB_DESCR *descr )
367 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
369 if (page_size == descr->page_size) return;
370 descr->page_size = page_size;
371 if (descr->style & LBS_MULTICOLUMN)
372 InvalidateRect( descr->self, NULL, TRUE );
373 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
377 /***********************************************************************
380 * Update the size of the listbox. Should be called when the size of
381 * the client area changes.
383 static void LISTBOX_UpdateSize( LB_DESCR *descr )
387 GetClientRect( descr->self, &rect );
388 descr->width = rect.right - rect.left;
389 descr->height = rect.bottom - rect.top;
390 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
395 GetWindowRect( descr->self, &rect );
396 if(descr->item_height != 0)
397 remaining = descr->height % descr->item_height;
400 if ((descr->height > descr->item_height) && remaining)
402 if (is_old_app(descr))
403 { /* give a margin for error to 16 bits programs - if we need
404 less than the height of the nonclient area, round to the
405 *next* number of items */
406 int ncheight = rect.bottom - rect.top - descr->height;
407 if ((descr->item_height - remaining) <= ncheight)
408 remaining = remaining - descr->item_height;
410 TRACE("[%p]: changing height %d -> %d\n",
411 descr->self, descr->height, descr->height - remaining );
412 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
413 rect.bottom - rect.top - remaining,
414 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
418 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
419 LISTBOX_UpdatePage( descr );
420 LISTBOX_UpdateScroll( descr );
422 /* Invalidate the focused item so it will be repainted correctly */
423 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
425 InvalidateRect( descr->self, &rect, FALSE );
430 /***********************************************************************
431 * LISTBOX_GetItemRect
433 * Get the rectangle enclosing an item, in listbox client coordinates.
434 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
436 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
438 /* Index <= 0 is legal even on empty listboxes */
439 if (index && (index >= descr->nb_items))
441 memset(rect, 0, sizeof(*rect));
442 SetLastError(ERROR_INVALID_INDEX);
445 SetRect( rect, 0, 0, descr->width, descr->height );
446 if (descr->style & LBS_MULTICOLUMN)
448 INT col = (index / descr->page_size) -
449 (descr->top_item / descr->page_size);
450 rect->left += col * descr->column_width;
451 rect->right = rect->left + descr->column_width;
452 rect->top += (index % descr->page_size) * descr->item_height;
453 rect->bottom = rect->top + descr->item_height;
455 else if (descr->style & LBS_OWNERDRAWVARIABLE)
458 rect->right += descr->horz_pos;
459 if ((index >= 0) && (index < descr->nb_items))
461 if (index < descr->top_item)
463 for (i = descr->top_item-1; i >= index; i--)
464 rect->top -= descr->items[i].height;
468 for (i = descr->top_item; i < index; i++)
469 rect->top += descr->items[i].height;
471 rect->bottom = rect->top + descr->items[index].height;
477 rect->top += (index - descr->top_item) * descr->item_height;
478 rect->bottom = rect->top + descr->item_height;
479 rect->right += descr->horz_pos;
482 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
484 return ((rect->left < descr->width) && (rect->right > 0) &&
485 (rect->top < descr->height) && (rect->bottom > 0));
489 /***********************************************************************
490 * LISTBOX_GetItemFromPoint
492 * Return the item nearest from point (x,y) (in client coordinates).
494 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
496 INT index = descr->top_item;
498 if (!descr->nb_items) return -1; /* No items */
499 if (descr->style & LBS_OWNERDRAWVARIABLE)
504 while (index < descr->nb_items)
506 if ((pos += descr->items[index].height) > y) break;
515 if ((pos -= descr->items[index].height) <= y) break;
519 else if (descr->style & LBS_MULTICOLUMN)
521 if (y >= descr->item_height * descr->page_size) return -1;
522 if (y >= 0) index += y / descr->item_height;
523 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
524 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
528 index += (y / descr->item_height);
530 if (index < 0) return 0;
531 if (index >= descr->nb_items) return -1;
536 /***********************************************************************
541 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
542 INT index, UINT action, BOOL ignoreFocus )
544 LB_ITEMDATA *item = NULL;
545 if (index < descr->nb_items) item = &descr->items[index];
547 if (IS_OWNERDRAW(descr))
555 if (action == ODA_FOCUS)
556 DrawFocusRect( hdc, rect );
558 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
562 /* some programs mess with the clipping region when
563 drawing the item, *and* restore the previous region
564 after they are done, so a region has better to exist
565 else everything ends clipped */
566 GetClientRect(descr->self, &r);
567 hrgn = CreateRectRgnIndirect(&r);
568 SelectClipRgn( hdc, hrgn);
569 DeleteObject( hrgn );
571 dis.CtlType = ODT_LISTBOX;
572 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
573 dis.hwndItem = descr->self;
574 dis.itemAction = action;
578 if (item->selected) dis.itemState |= ODS_SELECTED;
579 if (!ignoreFocus && (descr->focus_item == index) &&
581 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
582 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
583 dis.itemData = item->data;
585 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
586 descr->self, index, item ? debugstr_w(item->str) : "", action,
587 dis.itemState, wine_dbgstr_rect(rect) );
588 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
592 COLORREF oldText = 0, oldBk = 0;
594 if (action == ODA_FOCUS)
596 DrawFocusRect( hdc, rect );
599 if (item && item->selected)
601 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
602 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
605 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
606 descr->self, index, item ? debugstr_w(item->str) : "", action,
607 wine_dbgstr_rect(rect) );
609 ExtTextOutW( hdc, rect->left + 1, rect->top,
610 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
611 else if (!(descr->style & LBS_USETABSTOPS))
612 ExtTextOutW( hdc, rect->left + 1, rect->top,
613 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
614 strlenW(item->str), NULL );
617 /* Output empty string to paint background in the full width. */
618 ExtTextOutW( hdc, rect->left + 1, rect->top,
619 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
620 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
621 item->str, strlenW(item->str),
622 descr->nb_tabs, descr->tabs, 0);
624 if (item && item->selected)
626 SetBkColor( hdc, oldBk );
627 SetTextColor( hdc, oldText );
629 if (!ignoreFocus && (descr->focus_item == index) &&
631 (descr->in_focus)) DrawFocusRect( hdc, rect );
636 /***********************************************************************
639 * Change the redraw flag.
641 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
645 if (!(descr->style & LBS_NOREDRAW)) return;
646 descr->style &= ~LBS_NOREDRAW;
647 if (descr->style & LBS_DISPLAYCHANGED)
648 { /* page was changed while setredraw false, refresh automatically */
649 InvalidateRect(descr->self, NULL, TRUE);
650 if ((descr->top_item + descr->page_size) > descr->nb_items)
651 { /* reset top of page if less than number of items/page */
652 descr->top_item = descr->nb_items - descr->page_size;
653 if (descr->top_item < 0) descr->top_item = 0;
655 descr->style &= ~LBS_DISPLAYCHANGED;
657 LISTBOX_UpdateScroll( descr );
659 else descr->style |= LBS_NOREDRAW;
663 /***********************************************************************
664 * LISTBOX_RepaintItem
666 * Repaint a single item synchronously.
668 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
673 HBRUSH hbrush, oldBrush = 0;
675 /* Do not repaint the item if the item is not visible */
676 if (!IsWindowVisible(descr->self)) return;
677 if (descr->style & LBS_NOREDRAW)
679 descr->style |= LBS_DISPLAYCHANGED;
682 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
683 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
684 if (descr->font) oldFont = SelectObject( hdc, descr->font );
685 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
686 (WPARAM)hdc, (LPARAM)descr->self );
687 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
688 if (!IsWindowEnabled(descr->self))
689 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
690 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
691 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
692 if (oldFont) SelectObject( hdc, oldFont );
693 if (oldBrush) SelectObject( hdc, oldBrush );
694 ReleaseDC( descr->self, hdc );
698 /***********************************************************************
699 * LISTBOX_DrawFocusRect
701 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
707 /* Do not repaint the item if the item is not visible */
708 if (!IsWindowVisible(descr->self)) return;
710 if (descr->focus_item == -1) return;
711 if (!descr->caret_on || !descr->in_focus) return;
713 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
714 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
715 if (descr->font) oldFont = SelectObject( hdc, descr->font );
716 if (!IsWindowEnabled(descr->self))
717 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
718 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
719 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, on ? FALSE : TRUE );
720 if (oldFont) SelectObject( hdc, oldFont );
721 ReleaseDC( descr->self, hdc );
725 /***********************************************************************
726 * LISTBOX_InitStorage
728 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
732 nb_items += LB_ARRAY_GRANULARITY - 1;
733 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
735 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
736 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
737 nb_items * sizeof(LB_ITEMDATA));
740 item = HeapAlloc( GetProcessHeap(), 0,
741 nb_items * sizeof(LB_ITEMDATA));
746 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
754 /***********************************************************************
755 * LISTBOX_SetTabStops
757 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
761 if (!(descr->style & LBS_USETABSTOPS))
763 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
767 HeapFree( GetProcessHeap(), 0, descr->tabs );
768 if (!(descr->nb_tabs = count))
773 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
774 descr->nb_tabs * sizeof(INT) )))
779 LPINT16 p = (LPINT16)tabs;
781 TRACE("[%p]: settabstops ", descr->self );
782 for (i = 0; i < descr->nb_tabs; i++) {
783 descr->tabs[i] = *p++<<1; /* FIXME */
784 TRACE("%hd ", descr->tabs[i]);
788 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
790 /* convert into "dialog units"*/
791 for (i = 0; i < descr->nb_tabs; i++)
792 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
798 /***********************************************************************
801 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
805 if ((index < 0) || (index >= descr->nb_items))
807 SetLastError(ERROR_INVALID_INDEX);
810 if (HAS_STRINGS(descr))
814 len = strlenW(descr->items[index].str);
817 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
818 NULL, 0, NULL, NULL );
821 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
823 __TRY /* hide a Delphi bug that passes a read-only buffer */
827 strcpyW( buffer, descr->items[index].str );
828 len = strlenW(buffer);
832 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
833 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
838 WARN( "got an invalid buffer (Delphi bug?)\n" );
839 SetLastError( ERROR_INVALID_PARAMETER );
845 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
851 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
853 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
854 if (ret == CSTR_LESS_THAN)
856 if (ret == CSTR_EQUAL)
858 if (ret == CSTR_GREATER_THAN)
863 /***********************************************************************
864 * LISTBOX_FindStringPos
866 * Find the nearest string located before a given string in sort order.
867 * If 'exact' is TRUE, return an error if we don't get an exact match.
869 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
871 INT index, min, max, res = -1;
873 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
875 max = descr->nb_items;
878 index = (min + max) / 2;
879 if (HAS_STRINGS(descr))
880 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
883 COMPAREITEMSTRUCT cis;
884 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
886 cis.CtlType = ODT_LISTBOX;
888 cis.hwndItem = descr->self;
889 /* note that some application (MetaStock) expects the second item
890 * to be in the listbox */
892 cis.itemData1 = (ULONG_PTR)str;
894 cis.itemData2 = descr->items[index].data;
895 cis.dwLocaleId = descr->locale;
896 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
898 if (!res) return index;
899 if (res < 0) max = index;
900 else min = index + 1;
902 return exact ? -1 : max;
906 /***********************************************************************
907 * LISTBOX_FindFileStrPos
909 * Find the nearest string located before a given string in directory
910 * sort order (i.e. first files, then directories, then drives).
912 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
914 INT min, max, res = -1;
916 if (!HAS_STRINGS(descr))
917 return LISTBOX_FindStringPos( descr, str, FALSE );
919 max = descr->nb_items;
922 INT index = (min + max) / 2;
923 LPCWSTR p = descr->items[index].str;
924 if (*p == '[') /* drive or directory */
926 if (*str != '[') res = -1;
927 else if (p[1] == '-') /* drive */
929 if (str[1] == '-') res = str[2] - p[2];
934 if (str[1] == '-') res = 1;
935 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
940 if (*str == '[') res = 1;
941 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
943 if (!res) return index;
944 if (res < 0) max = index;
945 else min = index + 1;
951 /***********************************************************************
954 * Find the item beginning with a given string.
956 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
961 if (start >= descr->nb_items) start = -1;
962 item = descr->items + start + 1;
963 if (HAS_STRINGS(descr))
965 if (!str || ! str[0] ) return LB_ERR;
968 for (i = start + 1; i < descr->nb_items; i++, item++)
969 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
970 for (i = 0, item = descr->items; i <= start; i++, item++)
971 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
975 /* Special case for drives and directories: ignore prefix */
976 #define CHECK_DRIVE(item) \
977 if ((item)->str[0] == '[') \
979 if (!strncmpiW( str, (item)->str+1, len )) return i; \
980 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
984 INT len = strlenW(str);
985 for (i = start + 1; i < descr->nb_items; i++, item++)
987 if (!strncmpiW( str, item->str, len )) return i;
990 for (i = 0, item = descr->items; i <= start; i++, item++)
992 if (!strncmpiW( str, item->str, len )) return i;
1000 if (exact && (descr->style & LBS_SORT))
1001 /* If sorted, use a WM_COMPAREITEM binary search */
1002 return LISTBOX_FindStringPos( descr, str, TRUE );
1004 /* Otherwise use a linear search */
1005 for (i = start + 1; i < descr->nb_items; i++, item++)
1006 if (item->data == (ULONG_PTR)str) return i;
1007 for (i = 0, item = descr->items; i <= start; i++, item++)
1008 if (item->data == (ULONG_PTR)str) return i;
1014 /***********************************************************************
1015 * LISTBOX_GetSelCount
1017 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1020 const LB_ITEMDATA *item = descr->items;
1022 if (!(descr->style & LBS_MULTIPLESEL) ||
1023 (descr->style & LBS_NOSEL))
1025 for (i = count = 0; i < descr->nb_items; i++, item++)
1026 if (item->selected) count++;
1031 /***********************************************************************
1032 * LISTBOX_GetSelItems16
1034 static LRESULT LISTBOX_GetSelItems16( const LB_DESCR *descr, INT16 max, LPINT16 array )
1037 const LB_ITEMDATA *item = descr->items;
1039 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1040 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1041 if (item->selected) array[count++] = (INT16)i;
1046 /***********************************************************************
1047 * LISTBOX_GetSelItems
1049 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1052 const LB_ITEMDATA *item = descr->items;
1054 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1055 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1056 if (item->selected) array[count++] = i;
1061 /***********************************************************************
1064 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1066 INT i, col_pos = descr->page_size - 1;
1068 RECT focusRect = {-1, -1, -1, -1};
1070 HBRUSH hbrush, oldBrush = 0;
1072 if (descr->style & LBS_NOREDRAW) return 0;
1074 SetRect( &rect, 0, 0, descr->width, descr->height );
1075 if (descr->style & LBS_MULTICOLUMN)
1076 rect.right = rect.left + descr->column_width;
1077 else if (descr->horz_pos)
1079 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1080 rect.right += descr->horz_pos;
1083 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1084 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1085 (WPARAM)hdc, (LPARAM)descr->self );
1086 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1087 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1089 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1092 /* Special case for empty listbox: paint focus rect */
1093 rect.bottom = rect.top + descr->item_height;
1094 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1095 &rect, NULL, 0, NULL );
1096 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1097 rect.top = rect.bottom;
1100 /* Paint all the item, regarding the selection
1101 Focus state will be painted after */
1103 for (i = descr->top_item; i < descr->nb_items; i++)
1105 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1106 rect.bottom = rect.top + descr->item_height;
1108 rect.bottom = rect.top + descr->items[i].height;
1110 if (i == descr->focus_item)
1112 /* keep the focus rect, to paint the focus item after */
1113 focusRect.left = rect.left;
1114 focusRect.right = rect.right;
1115 focusRect.top = rect.top;
1116 focusRect.bottom = rect.bottom;
1118 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1119 rect.top = rect.bottom;
1121 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1123 if (!IS_OWNERDRAW(descr))
1125 /* Clear the bottom of the column */
1126 if (rect.top < descr->height)
1128 rect.bottom = descr->height;
1129 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1130 &rect, NULL, 0, NULL );
1134 /* Go to the next column */
1135 rect.left += descr->column_width;
1136 rect.right += descr->column_width;
1138 col_pos = descr->page_size - 1;
1143 if (rect.top >= descr->height) break;
1147 /* Paint the focus item now */
1148 if (focusRect.top != focusRect.bottom &&
1149 descr->caret_on && descr->in_focus)
1150 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1152 if (!IS_OWNERDRAW(descr))
1154 /* Clear the remainder of the client area */
1155 if (rect.top < descr->height)
1157 rect.bottom = descr->height;
1158 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1159 &rect, NULL, 0, NULL );
1161 if (rect.right < descr->width)
1163 rect.left = rect.right;
1164 rect.right = descr->width;
1166 rect.bottom = descr->height;
1167 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1168 &rect, NULL, 0, NULL );
1171 if (oldFont) SelectObject( hdc, oldFont );
1172 if (oldBrush) SelectObject( hdc, oldBrush );
1177 /***********************************************************************
1178 * LISTBOX_InvalidateItems
1180 * Invalidate all items from a given item. If the specified item is not
1181 * visible, nothing happens.
1183 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1187 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1189 if (descr->style & LBS_NOREDRAW)
1191 descr->style |= LBS_DISPLAYCHANGED;
1194 rect.bottom = descr->height;
1195 InvalidateRect( descr->self, &rect, TRUE );
1196 if (descr->style & LBS_MULTICOLUMN)
1198 /* Repaint the other columns */
1199 rect.left = rect.right;
1200 rect.right = descr->width;
1202 InvalidateRect( descr->self, &rect, TRUE );
1207 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1211 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1212 InvalidateRect( descr->self, &rect, TRUE );
1215 /***********************************************************************
1216 * LISTBOX_GetItemHeight
1218 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1220 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1222 if ((index < 0) || (index >= descr->nb_items))
1224 SetLastError(ERROR_INVALID_INDEX);
1227 return descr->items[index].height;
1229 else return descr->item_height;
1233 /***********************************************************************
1234 * LISTBOX_SetItemHeight
1236 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1238 if (height > MAXBYTE)
1241 if (!height) height = 1;
1243 if (descr->style & LBS_OWNERDRAWVARIABLE)
1245 if ((index < 0) || (index >= descr->nb_items))
1247 SetLastError(ERROR_INVALID_INDEX);
1250 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1251 descr->items[index].height = height;
1252 LISTBOX_UpdateScroll( descr );
1254 LISTBOX_InvalidateItems( descr, index );
1256 else if (height != descr->item_height)
1258 TRACE("[%p]: new height = %d\n", descr->self, height );
1259 descr->item_height = height;
1260 LISTBOX_UpdatePage( descr );
1261 LISTBOX_UpdateScroll( descr );
1263 InvalidateRect( descr->self, 0, TRUE );
1269 /***********************************************************************
1270 * LISTBOX_SetHorizontalPos
1272 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1276 if (pos > descr->horz_extent - descr->width)
1277 pos = descr->horz_extent - descr->width;
1278 if (pos < 0) pos = 0;
1279 if (!(diff = descr->horz_pos - pos)) return;
1280 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1281 descr->horz_pos = pos;
1282 LISTBOX_UpdateScroll( descr );
1283 if (abs(diff) < descr->width)
1286 /* Invalidate the focused item so it will be repainted correctly */
1287 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1288 InvalidateRect( descr->self, &rect, TRUE );
1289 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1290 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1293 InvalidateRect( descr->self, NULL, TRUE );
1297 /***********************************************************************
1298 * LISTBOX_SetHorizontalExtent
1300 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1302 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1304 if (extent <= 0) extent = 1;
1305 if (extent == descr->horz_extent) return LB_OKAY;
1306 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1307 descr->horz_extent = extent;
1308 if (descr->horz_pos > extent - descr->width)
1309 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1311 LISTBOX_UpdateScroll( descr );
1316 /***********************************************************************
1317 * LISTBOX_SetColumnWidth
1319 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1321 if (width == descr->column_width) return LB_OKAY;
1322 TRACE("[%p]: new column width = %d\n", descr->self, width );
1323 descr->column_width = width;
1324 LISTBOX_UpdatePage( descr );
1329 /***********************************************************************
1332 * Returns the item height.
1334 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1338 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1343 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1345 ERR("unable to get DC.\n" );
1348 if (font) oldFont = SelectObject( hdc, font );
1349 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1350 if (oldFont) SelectObject( hdc, oldFont );
1351 ReleaseDC( descr->self, hdc );
1353 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1354 if (!IS_OWNERDRAW(descr))
1355 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1360 /***********************************************************************
1361 * LISTBOX_MakeItemVisible
1363 * Make sure that a given item is partially or fully visible.
1365 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1369 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1371 if (index <= descr->top_item) top = index;
1372 else if (descr->style & LBS_MULTICOLUMN)
1374 INT cols = descr->width;
1375 if (!fully) cols += descr->column_width - 1;
1376 if (cols >= descr->column_width) cols /= descr->column_width;
1378 if (index < descr->top_item + (descr->page_size * cols)) return;
1379 top = index - descr->page_size * (cols - 1);
1381 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1383 INT height = fully ? descr->items[index].height : 1;
1384 for (top = index; top > descr->top_item; top--)
1385 if ((height += descr->items[top-1].height) > descr->height) break;
1389 if (index < descr->top_item + descr->page_size) return;
1390 if (!fully && (index == descr->top_item + descr->page_size) &&
1391 (descr->height > (descr->page_size * descr->item_height))) return;
1392 top = index - descr->page_size + 1;
1394 LISTBOX_SetTopItem( descr, top, TRUE );
1397 /***********************************************************************
1398 * LISTBOX_SetCaretIndex
1401 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1404 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1406 INT oldfocus = descr->focus_item;
1408 TRACE("old focus %d, index %d\n", oldfocus, index);
1410 if (descr->style & LBS_NOSEL) return LB_ERR;
1411 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1412 if (index == oldfocus) return LB_OKAY;
1414 LISTBOX_DrawFocusRect( descr, FALSE );
1415 descr->focus_item = index;
1417 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1418 LISTBOX_DrawFocusRect( descr, TRUE );
1424 /***********************************************************************
1425 * LISTBOX_SelectItemRange
1427 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1429 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1434 /* A few sanity checks */
1436 if (descr->style & LBS_NOSEL) return LB_ERR;
1437 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1439 if (!descr->nb_items) return LB_OKAY;
1441 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1442 if (first < 0) first = 0;
1443 if (last < first) return LB_OKAY;
1445 if (on) /* Turn selection on */
1447 for (i = first; i <= last; i++)
1449 if (descr->items[i].selected) continue;
1450 descr->items[i].selected = TRUE;
1451 LISTBOX_InvalidateItemRect(descr, i);
1454 else /* Turn selection off */
1456 for (i = first; i <= last; i++)
1458 if (!descr->items[i].selected) continue;
1459 descr->items[i].selected = FALSE;
1460 LISTBOX_InvalidateItemRect(descr, i);
1466 /***********************************************************************
1467 * LISTBOX_SetSelection
1469 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1470 BOOL on, BOOL send_notify )
1472 TRACE( "cur_sel=%d index=%d notify=%s\n",
1473 descr->selected_item, index, send_notify ? "YES" : "NO" );
1475 if (descr->style & LBS_NOSEL)
1477 descr->selected_item = index;
1480 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1481 if (descr->style & LBS_MULTIPLESEL)
1483 if (index == -1) /* Select all items */
1484 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1485 else /* Only one item */
1486 return LISTBOX_SelectItemRange( descr, index, index, on );
1490 INT oldsel = descr->selected_item;
1491 if (index == oldsel) return LB_OKAY;
1492 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1493 if (index != -1) descr->items[index].selected = TRUE;
1494 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1495 descr->selected_item = index;
1496 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1497 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1498 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1500 if( descr->lphc ) /* set selection change flag for parent combo */
1501 descr->lphc->wState |= CBF_SELCHANGE;
1507 /***********************************************************************
1510 * Change the caret position and extend the selection to the new caret.
1512 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1514 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1516 if ((index < 0) || (index >= descr->nb_items))
1519 /* Important, repaint needs to be done in this order if
1520 you want to mimic Windows behavior:
1521 1. Remove the focus and paint the item
1522 2. Remove the selection and paint the item(s)
1523 3. Set the selection and repaint the item(s)
1524 4. Set the focus to 'index' and repaint the item */
1526 /* 1. remove the focus and repaint the item */
1527 LISTBOX_DrawFocusRect( descr, FALSE );
1529 /* 2. then turn off the previous selection */
1530 /* 3. repaint the new selected item */
1531 if (descr->style & LBS_EXTENDEDSEL)
1533 if (descr->anchor_item != -1)
1535 INT first = min( index, descr->anchor_item );
1536 INT last = max( index, descr->anchor_item );
1538 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1539 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1540 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1543 else if (!(descr->style & LBS_MULTIPLESEL))
1545 /* Set selection to new caret item */
1546 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1549 /* 4. repaint the new item with the focus */
1550 descr->focus_item = index;
1551 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1552 LISTBOX_DrawFocusRect( descr, TRUE );
1556 /***********************************************************************
1557 * LISTBOX_InsertItem
1559 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1560 LPWSTR str, ULONG_PTR data )
1564 INT oldfocus = descr->focus_item;
1566 if (index == -1) index = descr->nb_items;
1567 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1568 if (!descr->items) max_items = 0;
1569 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1570 if (descr->nb_items == max_items)
1572 /* We need to grow the array */
1573 max_items += LB_ARRAY_GRANULARITY;
1575 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1576 max_items * sizeof(LB_ITEMDATA) );
1578 item = HeapAlloc( GetProcessHeap(), 0,
1579 max_items * sizeof(LB_ITEMDATA) );
1582 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1585 descr->items = item;
1588 /* Insert the item structure */
1590 item = &descr->items[index];
1591 if (index < descr->nb_items)
1592 RtlMoveMemory( item + 1, item,
1593 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1597 item->selected = FALSE;
1600 /* Get item height */
1602 if (descr->style & LBS_OWNERDRAWVARIABLE)
1604 MEASUREITEMSTRUCT mis;
1605 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1607 mis.CtlType = ODT_LISTBOX;
1610 mis.itemData = descr->items[index].data;
1611 mis.itemHeight = descr->item_height;
1612 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1613 item->height = mis.itemHeight ? mis.itemHeight : 1;
1614 TRACE("[%p]: measure item %d (%s) = %d\n",
1615 descr->self, index, str ? debugstr_w(str) : "", item->height );
1618 /* Repaint the items */
1620 LISTBOX_UpdateScroll( descr );
1621 LISTBOX_InvalidateItems( descr, index );
1623 /* Move selection and focused item */
1624 /* If listbox was empty, set focus to the first item */
1625 if (descr->nb_items == 1)
1626 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1627 /* single select don't change selection index in win31 */
1628 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1630 descr->selected_item++;
1631 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1635 if (index <= descr->selected_item)
1637 descr->selected_item++;
1638 descr->focus_item = oldfocus; /* focus not changed */
1645 /***********************************************************************
1646 * LISTBOX_InsertString
1648 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1650 LPWSTR new_str = NULL;
1654 if (HAS_STRINGS(descr))
1656 static const WCHAR empty_stringW[] = { 0 };
1657 if (!str) str = empty_stringW;
1658 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1660 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1663 strcpyW(new_str, str);
1665 else data = (ULONG_PTR)str;
1667 if (index == -1) index = descr->nb_items;
1668 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1670 HeapFree( GetProcessHeap(), 0, new_str );
1674 TRACE("[%p]: added item %d %s\n",
1675 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1680 /***********************************************************************
1681 * LISTBOX_DeleteItem
1683 * Delete the content of an item. 'index' must be a valid index.
1685 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1687 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1688 * while Win95 sends it for all items with user data.
1689 * It's probably better to send it too often than not
1690 * often enough, so this is what we do here.
1692 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1694 DELETEITEMSTRUCT dis;
1695 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1697 dis.CtlType = ODT_LISTBOX;
1700 dis.hwndItem = descr->self;
1701 dis.itemData = descr->items[index].data;
1702 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1704 if (HAS_STRINGS(descr))
1705 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1709 /***********************************************************************
1710 * LISTBOX_RemoveItem
1712 * Remove an item from the listbox and delete its content.
1714 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1719 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1721 /* We need to invalidate the original rect instead of the updated one. */
1722 LISTBOX_InvalidateItems( descr, index );
1724 LISTBOX_DeleteItem( descr, index );
1726 /* Remove the item */
1728 item = &descr->items[index];
1729 if (index < descr->nb_items-1)
1730 RtlMoveMemory( item, item + 1,
1731 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1733 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1735 /* Shrink the item array if possible */
1737 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1738 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1740 max_items -= LB_ARRAY_GRANULARITY;
1741 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1742 max_items * sizeof(LB_ITEMDATA) );
1743 if (item) descr->items = item;
1745 /* Repaint the items */
1747 LISTBOX_UpdateScroll( descr );
1748 /* if we removed the scrollbar, reset the top of the list
1749 (correct for owner-drawn ???) */
1750 if (descr->nb_items == descr->page_size)
1751 LISTBOX_SetTopItem( descr, 0, TRUE );
1753 /* Move selection and focused item */
1754 if (!IS_MULTISELECT(descr))
1756 if (index == descr->selected_item)
1757 descr->selected_item = -1;
1758 else if (index < descr->selected_item)
1760 descr->selected_item--;
1761 if (ISWIN31) /* win 31 do not change the selected item number */
1762 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1766 if (descr->focus_item >= descr->nb_items)
1768 descr->focus_item = descr->nb_items - 1;
1769 if (descr->focus_item < 0) descr->focus_item = 0;
1775 /***********************************************************************
1776 * LISTBOX_ResetContent
1778 static void LISTBOX_ResetContent( LB_DESCR *descr )
1782 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1783 HeapFree( GetProcessHeap(), 0, descr->items );
1784 descr->nb_items = 0;
1785 descr->top_item = 0;
1786 descr->selected_item = -1;
1787 descr->focus_item = 0;
1788 descr->anchor_item = -1;
1789 descr->items = NULL;
1793 /***********************************************************************
1796 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1800 if (HAS_STRINGS(descr))
1802 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1806 /* FIXME: this is far from optimal... */
1807 if (count > descr->nb_items)
1809 while (count > descr->nb_items)
1810 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1813 else if (count < descr->nb_items)
1815 while (count < descr->nb_items)
1816 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1823 /***********************************************************************
1826 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1827 LPCWSTR filespec, BOOL long_names )
1830 LRESULT ret = LB_OKAY;
1831 WIN32_FIND_DATAW entry;
1833 LRESULT maxinsert = LB_ERR;
1835 /* don't scan directory if we just want drives exclusively */
1836 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1837 /* scan directory */
1838 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1840 int le = GetLastError();
1841 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1848 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1850 static const WCHAR bracketW[] = { ']',0 };
1851 static const WCHAR dotW[] = { '.',0 };
1852 if (!(attrib & DDL_DIRECTORY) ||
1853 !strcmpW( entry.cFileName, dotW )) continue;
1855 if (!long_names && entry.cAlternateFileName[0])
1856 strcpyW( buffer + 1, entry.cAlternateFileName );
1858 strcpyW( buffer + 1, entry.cFileName );
1859 strcatW(buffer, bracketW);
1861 else /* not a directory */
1863 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1864 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1866 if ((attrib & DDL_EXCLUSIVE) &&
1867 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1870 if (!long_names && entry.cAlternateFileName[0])
1871 strcpyW( buffer, entry.cAlternateFileName );
1873 strcpyW( buffer, entry.cFileName );
1875 if (!long_names) CharLowerW( buffer );
1876 pos = LISTBOX_FindFileStrPos( descr, buffer );
1877 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1879 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1880 } while (FindNextFileW( handle, &entry ));
1881 FindClose( handle );
1889 if (attrib & DDL_DRIVES)
1891 WCHAR buffer[] = {'[','-','a','-',']',0};
1892 WCHAR root[] = {'A',':','\\',0};
1894 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1896 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1897 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1906 /***********************************************************************
1907 * LISTBOX_HandleVScroll
1909 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1913 if (descr->style & LBS_MULTICOLUMN) return 0;
1917 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1920 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1923 LISTBOX_SetTopItem( descr, descr->top_item -
1924 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1927 LISTBOX_SetTopItem( descr, descr->top_item +
1928 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1930 case SB_THUMBPOSITION:
1931 LISTBOX_SetTopItem( descr, pos, TRUE );
1934 info.cbSize = sizeof(info);
1935 info.fMask = SIF_TRACKPOS;
1936 GetScrollInfo( descr->self, SB_VERT, &info );
1937 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1940 LISTBOX_SetTopItem( descr, 0, TRUE );
1943 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1950 /***********************************************************************
1951 * LISTBOX_HandleHScroll
1953 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1958 if (descr->style & LBS_MULTICOLUMN)
1963 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1967 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1971 page = descr->width / descr->column_width;
1972 if (page < 1) page = 1;
1973 LISTBOX_SetTopItem( descr,
1974 descr->top_item - page * descr->page_size, TRUE );
1977 page = descr->width / descr->column_width;
1978 if (page < 1) page = 1;
1979 LISTBOX_SetTopItem( descr,
1980 descr->top_item + page * descr->page_size, TRUE );
1982 case SB_THUMBPOSITION:
1983 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1986 info.cbSize = sizeof(info);
1987 info.fMask = SIF_TRACKPOS;
1988 GetScrollInfo( descr->self, SB_VERT, &info );
1989 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1993 LISTBOX_SetTopItem( descr, 0, TRUE );
1996 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2000 else if (descr->horz_extent)
2005 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2008 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2011 LISTBOX_SetHorizontalPos( descr,
2012 descr->horz_pos - descr->width );
2015 LISTBOX_SetHorizontalPos( descr,
2016 descr->horz_pos + descr->width );
2018 case SB_THUMBPOSITION:
2019 LISTBOX_SetHorizontalPos( descr, pos );
2022 info.cbSize = sizeof(info);
2023 info.fMask = SIF_TRACKPOS;
2024 GetScrollInfo( descr->self, SB_HORZ, &info );
2025 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2028 LISTBOX_SetHorizontalPos( descr, 0 );
2031 LISTBOX_SetHorizontalPos( descr,
2032 descr->horz_extent - descr->width );
2039 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2041 short gcWheelDelta = 0;
2042 UINT pulScrollLines = 3;
2044 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2046 gcWheelDelta -= delta;
2048 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2050 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2051 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2052 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2057 /***********************************************************************
2058 * LISTBOX_HandleLButtonDown
2060 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2062 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2064 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2065 descr->self, x, y, index, descr->focus_item);
2067 if (!descr->caret_on && (descr->in_focus)) return 0;
2069 if (!descr->in_focus)
2071 if( !descr->lphc ) SetFocus( descr->self );
2072 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2075 if (index == -1) return 0;
2079 if (descr->style & LBS_NOTIFY )
2080 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2081 MAKELPARAM( x, y ) );
2084 descr->captured = TRUE;
2085 SetCapture( descr->self );
2087 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2089 /* we should perhaps make sure that all items are deselected
2090 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2091 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2092 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2095 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2096 if (keys & MK_CONTROL)
2098 LISTBOX_SetCaretIndex( descr, index, FALSE );
2099 LISTBOX_SetSelection( descr, index,
2100 !descr->items[index].selected,
2101 (descr->style & LBS_NOTIFY) != 0);
2105 LISTBOX_MoveCaret( descr, index, FALSE );
2107 if (descr->style & LBS_EXTENDEDSEL)
2109 LISTBOX_SetSelection( descr, index,
2110 descr->items[index].selected,
2111 (descr->style & LBS_NOTIFY) != 0 );
2115 LISTBOX_SetSelection( descr, index,
2116 !descr->items[index].selected,
2117 (descr->style & LBS_NOTIFY) != 0 );
2123 descr->anchor_item = index;
2124 LISTBOX_MoveCaret( descr, index, FALSE );
2125 LISTBOX_SetSelection( descr, index,
2126 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2131 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2138 if (DragDetect( descr->self, pt ))
2139 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2146 /*************************************************************************
2147 * LISTBOX_HandleLButtonDownCombo [Internal]
2149 * Process LButtonDown message for the ComboListBox
2152 * pWnd [I] The windows internal structure
2153 * pDescr [I] The ListBox internal structure
2154 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2155 * x [I] X Mouse Coordinate
2156 * y [I] Y Mouse Coordinate
2159 * 0 since we are processing the WM_LBUTTONDOWN Message
2162 * This function is only to be used when a ListBox is a ComboListBox
2165 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2167 RECT clientRect, screenRect;
2173 GetClientRect(descr->self, &clientRect);
2175 if(PtInRect(&clientRect, mousePos))
2177 /* MousePos is in client, resume normal processing */
2178 if (msg == WM_LBUTTONDOWN)
2180 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2181 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2183 else if (descr->style & LBS_NOTIFY)
2184 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2188 POINT screenMousePos;
2189 HWND hWndOldCapture;
2191 /* Check the Non-Client Area */
2192 screenMousePos = mousePos;
2193 hWndOldCapture = GetCapture();
2195 GetWindowRect(descr->self, &screenRect);
2196 ClientToScreen(descr->self, &screenMousePos);
2198 if(!PtInRect(&screenRect, screenMousePos))
2200 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2201 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2202 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2206 /* Check to see the NC is a scrollbar */
2208 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2209 /* Check Vertical scroll bar */
2210 if (style & WS_VSCROLL)
2212 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2213 if (PtInRect( &clientRect, mousePos ))
2214 nHitTestType = HTVSCROLL;
2216 /* Check horizontal scroll bar */
2217 if (style & WS_HSCROLL)
2219 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2220 if (PtInRect( &clientRect, mousePos ))
2221 nHitTestType = HTHSCROLL;
2223 /* Windows sends this message when a scrollbar is clicked
2226 if(nHitTestType != 0)
2228 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2229 MAKELONG(screenMousePos.x, screenMousePos.y));
2231 /* Resume the Capture after scrolling is complete
2233 if(hWndOldCapture != 0)
2234 SetCapture(hWndOldCapture);
2240 /***********************************************************************
2241 * LISTBOX_HandleLButtonUp
2243 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2245 if (LISTBOX_Timer != LB_TIMER_NONE)
2246 KillSystemTimer( descr->self, LB_TIMER_ID );
2247 LISTBOX_Timer = LB_TIMER_NONE;
2248 if (descr->captured)
2250 descr->captured = FALSE;
2251 if (GetCapture() == descr->self) ReleaseCapture();
2252 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2253 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2259 /***********************************************************************
2260 * LISTBOX_HandleTimer
2262 * Handle scrolling upon a timer event.
2263 * Return TRUE if scrolling should continue.
2265 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2270 if (descr->top_item) index = descr->top_item - 1;
2274 if (descr->top_item) index -= descr->page_size;
2277 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2278 if (index == descr->focus_item) index++;
2279 if (index >= descr->nb_items) index = descr->nb_items - 1;
2281 case LB_TIMER_RIGHT:
2282 if (index + descr->page_size < descr->nb_items)
2283 index += descr->page_size;
2288 if (index == descr->focus_item) return FALSE;
2289 LISTBOX_MoveCaret( descr, index, FALSE );
2294 /***********************************************************************
2295 * LISTBOX_HandleSystemTimer
2297 * WM_SYSTIMER handler.
2299 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2301 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2303 KillSystemTimer( descr->self, LB_TIMER_ID );
2304 LISTBOX_Timer = LB_TIMER_NONE;
2310 /***********************************************************************
2311 * LISTBOX_HandleMouseMove
2313 * WM_MOUSEMOVE handler.
2315 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2319 TIMER_DIRECTION dir = LB_TIMER_NONE;
2321 if (!descr->captured) return;
2323 if (descr->style & LBS_MULTICOLUMN)
2326 else if (y >= descr->item_height * descr->page_size)
2327 y = descr->item_height * descr->page_size - 1;
2331 dir = LB_TIMER_LEFT;
2334 else if (x >= descr->width)
2336 dir = LB_TIMER_RIGHT;
2337 x = descr->width - 1;
2342 if (y < 0) dir = LB_TIMER_UP; /* above */
2343 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2346 index = LISTBOX_GetItemFromPoint( descr, x, y );
2347 if (index == -1) index = descr->focus_item;
2348 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2350 /* Start/stop the system timer */
2352 if (dir != LB_TIMER_NONE)
2353 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2354 else if (LISTBOX_Timer != LB_TIMER_NONE)
2355 KillSystemTimer( descr->self, LB_TIMER_ID );
2356 LISTBOX_Timer = dir;
2360 /***********************************************************************
2361 * LISTBOX_HandleKeyDown
2363 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2366 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2367 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2368 bForceSelection = FALSE; /* only for single select list */
2370 if (descr->style & LBS_WANTKEYBOARDINPUT)
2372 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2373 MAKEWPARAM(LOWORD(key), descr->focus_item),
2374 (LPARAM)descr->self );
2375 if (caret == -2) return 0;
2377 if (caret == -1) switch(key)
2380 if (descr->style & LBS_MULTICOLUMN)
2382 bForceSelection = FALSE;
2383 if (descr->focus_item >= descr->page_size)
2384 caret = descr->focus_item - descr->page_size;
2389 caret = descr->focus_item - 1;
2390 if (caret < 0) caret = 0;
2393 if (descr->style & LBS_MULTICOLUMN)
2395 bForceSelection = FALSE;
2396 if (descr->focus_item + descr->page_size < descr->nb_items)
2397 caret = descr->focus_item + descr->page_size;
2402 caret = descr->focus_item + 1;
2403 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2407 if (descr->style & LBS_MULTICOLUMN)
2409 INT page = descr->width / descr->column_width;
2410 if (page < 1) page = 1;
2411 caret = descr->focus_item - (page * descr->page_size) + 1;
2413 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2414 if (caret < 0) caret = 0;
2417 if (descr->style & LBS_MULTICOLUMN)
2419 INT page = descr->width / descr->column_width;
2420 if (page < 1) page = 1;
2421 caret = descr->focus_item + (page * descr->page_size) - 1;
2423 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2424 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2430 caret = descr->nb_items - 1;
2433 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2434 else if (descr->style & LBS_MULTIPLESEL)
2436 LISTBOX_SetSelection( descr, descr->focus_item,
2437 !descr->items[descr->focus_item].selected,
2438 (descr->style & LBS_NOTIFY) != 0 );
2442 bForceSelection = FALSE;
2444 if (bForceSelection) /* focused item is used instead of key */
2445 caret = descr->focus_item;
2448 if (((descr->style & LBS_EXTENDEDSEL) &&
2449 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2450 !IS_MULTISELECT(descr))
2451 descr->anchor_item = caret;
2452 LISTBOX_MoveCaret( descr, caret, TRUE );
2454 if (descr->style & LBS_MULTIPLESEL)
2455 descr->selected_item = caret;
2457 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2458 if (descr->style & LBS_NOTIFY)
2460 if (descr->lphc && IsWindowVisible( descr->self ))
2462 /* make sure that combo parent doesn't hide us */
2463 descr->lphc->wState |= CBF_NOROLLUP;
2465 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2472 /***********************************************************************
2473 * LISTBOX_HandleChar
2475 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2483 if (descr->style & LBS_WANTKEYBOARDINPUT)
2485 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2486 MAKEWPARAM(charW, descr->focus_item),
2487 (LPARAM)descr->self );
2488 if (caret == -2) return 0;
2491 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2494 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2495 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2496 LISTBOX_MoveCaret( descr, caret, TRUE );
2497 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2498 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2504 /***********************************************************************
2507 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2510 MEASUREITEMSTRUCT mis;
2513 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2516 GetClientRect( hwnd, &rect );
2518 descr->owner = GetParent( descr->self );
2519 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2520 descr->width = rect.right - rect.left;
2521 descr->height = rect.bottom - rect.top;
2522 descr->items = NULL;
2523 descr->nb_items = 0;
2524 descr->top_item = 0;
2525 descr->selected_item = -1;
2526 descr->focus_item = 0;
2527 descr->anchor_item = -1;
2528 descr->item_height = 1;
2529 descr->page_size = 1;
2530 descr->column_width = 150;
2531 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2532 descr->horz_pos = 0;
2535 descr->caret_on = lphc ? FALSE : TRUE;
2536 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2537 descr->in_focus = FALSE;
2538 descr->captured = FALSE;
2540 descr->locale = GetUserDefaultLCID();
2543 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2545 /* Win95 document "List Box Differences" from MSDN:
2546 If a list box in a version 3.x application has either the
2547 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2548 horizontal and vertical scroll bars.
2550 descr->style |= WS_VSCROLL | WS_HSCROLL;
2555 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2556 descr->owner = lphc->self;
2559 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2561 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2563 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2564 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2565 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2566 descr->item_height = LISTBOX_SetFont( descr, 0 );
2568 if (descr->style & LBS_OWNERDRAWFIXED)
2570 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2572 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2573 descr->item_height = lphc->fixedOwnerDrawHeight;
2577 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2578 mis.CtlType = ODT_LISTBOX;
2583 mis.itemHeight = descr->item_height;
2584 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2585 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2589 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2594 /***********************************************************************
2597 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2599 LISTBOX_ResetContent( descr );
2600 SetWindowLongPtrW( descr->self, 0, 0 );
2601 HeapFree( GetProcessHeap(), 0, descr );
2606 /***********************************************************************
2607 * ListBoxWndProc_common
2609 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2610 WPARAM wParam, LPARAM lParam, BOOL unicode )
2612 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2613 LPHEADCOMBO lphc = 0;
2618 if (!IsWindow(hwnd)) return 0;
2620 if (msg == WM_CREATE)
2622 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2623 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2624 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2625 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2628 /* Ignore all other messages before we get a WM_CREATE */
2629 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2630 DefWindowProcA( hwnd, msg, wParam, lParam );
2632 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2634 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2635 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2639 case LB_RESETCONTENT16:
2640 case LB_RESETCONTENT:
2641 LISTBOX_ResetContent( descr );
2642 LISTBOX_UpdateScroll( descr );
2643 InvalidateRect( descr->self, NULL, TRUE );
2646 case LB_ADDSTRING16:
2647 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2653 if(unicode || !HAS_STRINGS(descr))
2654 textW = (LPWSTR)lParam;
2657 LPSTR textA = (LPSTR)lParam;
2658 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2659 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2660 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2662 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2663 ret = LISTBOX_InsertString( descr, wParam, textW );
2664 if (!unicode && HAS_STRINGS(descr))
2665 HeapFree(GetProcessHeap(), 0, textW);
2669 case LB_INSERTSTRING16:
2670 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2671 wParam = (INT)(INT16)wParam;
2673 case LB_INSERTSTRING:
2677 if(unicode || !HAS_STRINGS(descr))
2678 textW = (LPWSTR)lParam;
2681 LPSTR textA = (LPSTR)lParam;
2682 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2683 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2684 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2686 ret = LISTBOX_InsertString( descr, wParam, textW );
2687 if(!unicode && HAS_STRINGS(descr))
2688 HeapFree(GetProcessHeap(), 0, textW);
2693 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2699 if(unicode || !HAS_STRINGS(descr))
2700 textW = (LPWSTR)lParam;
2703 LPSTR textA = (LPSTR)lParam;
2704 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2705 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2706 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2708 wParam = LISTBOX_FindFileStrPos( descr, textW );
2709 ret = LISTBOX_InsertString( descr, wParam, textW );
2710 if(!unicode && HAS_STRINGS(descr))
2711 HeapFree(GetProcessHeap(), 0, textW);
2715 case LB_DELETESTRING16:
2716 case LB_DELETESTRING:
2717 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2718 return descr->nb_items;
2721 SetLastError(ERROR_INVALID_INDEX);
2725 case LB_GETITEMDATA16:
2726 case LB_GETITEMDATA:
2727 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2729 SetLastError(ERROR_INVALID_INDEX);
2732 return descr->items[wParam].data;
2734 case LB_SETITEMDATA16:
2735 case LB_SETITEMDATA:
2736 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2738 SetLastError(ERROR_INVALID_INDEX);
2741 descr->items[wParam].data = lParam;
2742 /* undocumented: returns TRUE, not LB_OKAY (0) */
2747 return descr->nb_items;
2750 lParam = (LPARAM)MapSL(lParam);
2753 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2755 case LB_GETTEXTLEN16:
2758 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2760 SetLastError(ERROR_INVALID_INDEX);
2763 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2764 if (unicode) return strlenW( descr->items[wParam].str );
2765 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2766 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2768 case LB_GETCURSEL16:
2770 if (descr->nb_items == 0)
2772 if (!IS_MULTISELECT(descr))
2773 return descr->selected_item;
2774 if (descr->selected_item != -1)
2775 return descr->selected_item;
2776 return descr->focus_item;
2777 /* otherwise, if the user tries to move the selection with the */
2778 /* arrow keys, we will give the application something to choke on */
2779 case LB_GETTOPINDEX16:
2780 case LB_GETTOPINDEX:
2781 return descr->top_item;
2783 case LB_GETITEMHEIGHT16:
2784 case LB_GETITEMHEIGHT:
2785 return LISTBOX_GetItemHeight( descr, wParam );
2787 case LB_SETITEMHEIGHT16:
2788 lParam = LOWORD(lParam);
2790 case LB_SETITEMHEIGHT:
2791 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2793 case LB_ITEMFROMPOINT:
2800 /* The hiword of the return value is not a client area
2801 hittest as suggested by MSDN, but rather a hittest on
2802 the returned listbox item. */
2804 if(descr->nb_items == 0)
2805 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2807 pt.x = (short)LOWORD(lParam);
2808 pt.y = (short)HIWORD(lParam);
2810 SetRect(&rect, 0, 0, descr->width, descr->height);
2812 if(!PtInRect(&rect, pt))
2814 pt.x = min(pt.x, rect.right - 1);
2815 pt.x = max(pt.x, 0);
2816 pt.y = min(pt.y, rect.bottom - 1);
2817 pt.y = max(pt.y, 0);
2821 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2825 index = descr->nb_items - 1;
2828 return MAKELONG(index, hit ? 0 : 1);
2831 case LB_SETCARETINDEX16:
2832 case LB_SETCARETINDEX:
2833 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2834 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2841 case LB_GETCARETINDEX16:
2842 case LB_GETCARETINDEX:
2843 return descr->focus_item;
2845 case LB_SETTOPINDEX16:
2846 case LB_SETTOPINDEX:
2847 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2849 case LB_SETCOLUMNWIDTH16:
2850 case LB_SETCOLUMNWIDTH:
2851 return LISTBOX_SetColumnWidth( descr, wParam );
2853 case LB_GETITEMRECT16:
2856 RECT16 *r16 = MapSL(lParam);
2857 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2858 r16->left = rect.left;
2859 r16->top = rect.top;
2860 r16->right = rect.right;
2861 r16->bottom = rect.bottom;
2865 case LB_GETITEMRECT:
2866 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2868 case LB_FINDSTRING16:
2869 wParam = (INT)(INT16)wParam;
2870 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2876 if(unicode || !HAS_STRINGS(descr))
2877 textW = (LPWSTR)lParam;
2880 LPSTR textA = (LPSTR)lParam;
2881 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2882 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2883 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2885 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2886 if(!unicode && HAS_STRINGS(descr))
2887 HeapFree(GetProcessHeap(), 0, textW);
2891 case LB_FINDSTRINGEXACT16:
2892 wParam = (INT)(INT16)wParam;
2893 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2895 case LB_FINDSTRINGEXACT:
2899 if(unicode || !HAS_STRINGS(descr))
2900 textW = (LPWSTR)lParam;
2903 LPSTR textA = (LPSTR)lParam;
2904 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2905 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2906 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2908 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2909 if(!unicode && HAS_STRINGS(descr))
2910 HeapFree(GetProcessHeap(), 0, textW);
2914 case LB_SELECTSTRING16:
2915 wParam = (INT)(INT16)wParam;
2916 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2918 case LB_SELECTSTRING:
2923 if(HAS_STRINGS(descr))
2924 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2925 debugstr_a((LPSTR)lParam));
2926 if(unicode || !HAS_STRINGS(descr))
2927 textW = (LPWSTR)lParam;
2930 LPSTR textA = (LPSTR)lParam;
2931 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2932 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2933 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2935 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2936 if(!unicode && HAS_STRINGS(descr))
2937 HeapFree(GetProcessHeap(), 0, textW);
2938 if (index != LB_ERR)
2940 LISTBOX_MoveCaret( descr, index, TRUE );
2941 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2947 wParam = (INT)(INT16)wParam;
2950 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2952 return descr->items[wParam].selected;
2955 lParam = (INT)(INT16)lParam;
2958 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2960 case LB_SETCURSEL16:
2961 wParam = (INT)(INT16)wParam;
2964 if (IS_MULTISELECT(descr)) return LB_ERR;
2965 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2966 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2967 if (ret != LB_ERR) ret = descr->selected_item;
2970 case LB_GETSELCOUNT16:
2971 case LB_GETSELCOUNT:
2972 return LISTBOX_GetSelCount( descr );
2974 case LB_GETSELITEMS16:
2975 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2977 case LB_GETSELITEMS:
2978 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2980 case LB_SELITEMRANGE16:
2981 case LB_SELITEMRANGE:
2982 if (LOWORD(lParam) <= HIWORD(lParam))
2983 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2984 HIWORD(lParam), wParam );
2986 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2987 LOWORD(lParam), wParam );
2989 case LB_SELITEMRANGEEX16:
2990 case LB_SELITEMRANGEEX:
2991 if ((INT)lParam >= (INT)wParam)
2992 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2994 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2996 case LB_GETHORIZONTALEXTENT16:
2997 case LB_GETHORIZONTALEXTENT:
2998 return descr->horz_extent;
3000 case LB_SETHORIZONTALEXTENT16:
3001 case LB_SETHORIZONTALEXTENT:
3002 return LISTBOX_SetHorizontalExtent( descr, wParam );
3004 case LB_GETANCHORINDEX16:
3005 case LB_GETANCHORINDEX:
3006 return descr->anchor_item;
3008 case LB_SETANCHORINDEX16:
3009 wParam = (INT)(INT16)wParam;
3011 case LB_SETANCHORINDEX:
3012 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
3014 SetLastError(ERROR_INVALID_INDEX);
3017 descr->anchor_item = (INT)wParam;
3021 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
3022 * be set automatically (this is different in Win32) */
3023 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
3024 lParam = (LPARAM)MapSL(lParam);
3031 textW = (LPWSTR)lParam;
3034 LPSTR textA = (LPSTR)lParam;
3035 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3036 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3037 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3039 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3041 HeapFree(GetProcessHeap(), 0, textW);
3046 return descr->locale;
3051 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3053 ret = descr->locale;
3054 descr->locale = (LCID)wParam;
3058 case LB_INITSTORAGE:
3059 return LISTBOX_InitStorage( descr, wParam );
3062 return LISTBOX_SetCount( descr, (INT)wParam );
3064 case LB_SETTABSTOPS16:
3065 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
3067 case LB_SETTABSTOPS:
3068 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
3072 if (descr->caret_on)
3074 descr->caret_on = TRUE;
3075 if ((descr->focus_item != -1) && (descr->in_focus))
3076 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3081 if (!descr->caret_on)
3083 descr->caret_on = FALSE;
3084 if ((descr->focus_item != -1) && (descr->in_focus))
3085 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3088 case LB_GETLISTBOXINFO:
3089 FIXME("LB_GETLISTBOXINFO: stub!\n");
3093 return LISTBOX_Destroy( descr );
3096 InvalidateRect( descr->self, NULL, TRUE );
3100 LISTBOX_SetRedraw( descr, wParam != 0 );
3104 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3106 case WM_PRINTCLIENT:
3110 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3111 ret = LISTBOX_Paint( descr, hdc );
3112 if( !wParam ) EndPaint( descr->self, &ps );
3116 LISTBOX_UpdateSize( descr );
3119 return (LRESULT)descr->font;
3121 LISTBOX_SetFont( descr, (HFONT)wParam );
3122 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3125 descr->in_focus = TRUE;
3126 descr->caret_on = TRUE;
3127 if (descr->focus_item != -1)
3128 LISTBOX_DrawFocusRect( descr, TRUE );
3129 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3132 descr->in_focus = FALSE;
3133 if ((descr->focus_item != -1) && descr->caret_on)
3134 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3135 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3138 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3140 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3142 if (wParam & (MK_SHIFT | MK_CONTROL))
3143 return DefWindowProcW( descr->self, msg, wParam, lParam );
3144 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3145 case WM_LBUTTONDOWN:
3147 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3148 (INT16)LOWORD(lParam),
3149 (INT16)HIWORD(lParam) );
3150 return LISTBOX_HandleLButtonDown( descr, wParam,
3151 (INT16)LOWORD(lParam),
3152 (INT16)HIWORD(lParam) );
3153 case WM_LBUTTONDBLCLK:
3155 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3156 (INT16)LOWORD(lParam),
3157 (INT16)HIWORD(lParam) );
3158 if (descr->style & LBS_NOTIFY)
3159 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3162 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3164 BOOL captured = descr->captured;
3168 mousePos.x = (INT16)LOWORD(lParam);
3169 mousePos.y = (INT16)HIWORD(lParam);
3172 * If we are in a dropdown combobox, we simulate that
3173 * the mouse is captured to show the tracking of the item.
3175 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3176 descr->captured = TRUE;
3178 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3180 descr->captured = captured;
3182 else if (GetCapture() == descr->self)
3184 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3185 (INT16)HIWORD(lParam) );
3195 * If the mouse button "up" is not in the listbox,
3196 * we make sure there is no selection by re-selecting the
3197 * item that was selected when the listbox was made visible.
3199 mousePos.x = (INT16)LOWORD(lParam);
3200 mousePos.y = (INT16)HIWORD(lParam);
3202 GetClientRect(descr->self, &clientRect);
3205 * When the user clicks outside the combobox and the focus
3206 * is lost, the owning combobox will send a fake buttonup with
3207 * 0xFFFFFFF as the mouse location, we must also revert the
3208 * selection to the original selection.
3210 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3211 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3213 return LISTBOX_HandleLButtonUp( descr );
3215 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3217 /* for some reason Windows makes it possible to
3218 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3220 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3221 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3222 && (wParam == VK_DOWN || wParam == VK_UP)) )
3224 COMBO_FlipListbox( lphc, FALSE, FALSE );
3228 return LISTBOX_HandleKeyDown( descr, wParam );
3233 charW = (WCHAR)wParam;
3236 CHAR charA = (CHAR)wParam;
3237 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3239 return LISTBOX_HandleChar( descr, charW );
3242 return LISTBOX_HandleSystemTimer( descr );
3244 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3247 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3248 wParam, (LPARAM)descr->self );
3249 TRACE("hbrush = %p\n", hbrush);
3251 hbrush = GetSysColorBrush(COLOR_WINDOW);
3254 GetClientRect(descr->self, &rect);
3255 FillRect((HDC)wParam, &rect, hbrush);
3260 if( lphc ) return 0;
3261 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3262 SendMessageA( descr->owner, msg, wParam, lParam );
3265 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3274 if ((msg >= WM_USER) && (msg < 0xc000))
3275 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3276 hwnd, msg, wParam, lParam );
3279 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3280 DefWindowProcA( hwnd, msg, wParam, lParam );
3283 /***********************************************************************
3286 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3288 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3291 /***********************************************************************
3294 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3296 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );