winejoystick: Fix a crash on accessing a CFArray past its end due to an off-by-one...
[wine/multimedia.git] / dlls / user32 / listbox.c
blob4853c3f0b7940692cfd7cedd5b575f644ce74f13
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * NOTES
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.
29 * TODO:
30 * - LBS_NODATA
33 #include <string.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include "windef.h"
38 #include "winbase.h"
39 #include "wingdi.h"
40 #include "wine/unicode.h"
41 #include "user_private.h"
42 #include "controls.h"
43 #include "wine/exception.h"
44 #include "wine/debug.h"
46 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
48 /* Items array granularity */
49 #define LB_ARRAY_GRANULARITY 16
51 /* Scrolling timeout in ms */
52 #define LB_SCROLL_TIMEOUT 50
54 /* Listbox system timer id */
55 #define LB_TIMER_ID 2
57 /* flag listbox changed while setredraw false - internal style */
58 #define LBS_DISPLAYCHANGED 0x80000000
60 /* Item structure */
61 typedef struct
63 LPWSTR str; /* Item text */
64 BOOL selected; /* Is item selected? */
65 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
66 ULONG_PTR data; /* User data */
67 } LB_ITEMDATA;
69 /* Listbox structure */
70 typedef struct
72 HWND self; /* Our own window handle */
73 HWND owner; /* Owner window to send notifications to */
74 UINT style; /* Window style */
75 INT width; /* Window width */
76 INT height; /* Window height */
77 LB_ITEMDATA *items; /* Array of items */
78 INT nb_items; /* Number of items */
79 INT top_item; /* Top visible item */
80 INT selected_item; /* Selected item */
81 INT focus_item; /* Item that has the focus */
82 INT anchor_item; /* Anchor item for extended selection */
83 INT item_height; /* Default item height */
84 INT page_size; /* Items per listbox page */
85 INT column_width; /* Column width for multi-column listboxes */
86 INT horz_extent; /* Horizontal extent */
87 INT horz_pos; /* Horizontal position */
88 INT nb_tabs; /* Number of tabs in array */
89 INT *tabs; /* Array of tabs */
90 INT avg_char_width; /* Average width of characters */
91 INT wheel_remain; /* Left over scroll amount */
92 BOOL caret_on; /* Is caret on? */
93 BOOL captured; /* Is mouse captured? */
94 BOOL in_focus;
95 HFONT font; /* Current font */
96 LCID locale; /* Current locale for string comparisons */
97 LPHEADCOMBO lphc; /* ComboLBox */
98 } LB_DESCR;
101 #define IS_OWNERDRAW(descr) \
102 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
104 #define HAS_STRINGS(descr) \
105 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108 #define IS_MULTISELECT(descr) \
109 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
110 !((descr)->style & LBS_NOSEL))
112 #define SEND_NOTIFICATION(descr,code) \
113 (SendMessageW( (descr)->owner, WM_COMMAND, \
114 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
116 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
118 /* Current timer status */
119 typedef enum
121 LB_TIMER_NONE,
122 LB_TIMER_UP,
123 LB_TIMER_LEFT,
124 LB_TIMER_DOWN,
125 LB_TIMER_RIGHT
126 } TIMER_DIRECTION;
128 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
130 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
132 /*********************************************************************
133 * listbox class descriptor
135 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
136 const struct builtin_class_descr LISTBOX_builtin_class =
138 listboxW, /* name */
139 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
140 WINPROC_LISTBOX, /* proc */
141 sizeof(LB_DESCR *), /* extra */
142 IDC_ARROW, /* cursor */
143 0 /* brush */
147 /*********************************************************************
148 * combolbox class descriptor
150 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
151 const struct builtin_class_descr COMBOLBOX_builtin_class =
153 combolboxW, /* name */
154 CS_DBLCLKS | CS_SAVEBITS, /* style */
155 WINPROC_LISTBOX, /* proc */
156 sizeof(LB_DESCR *), /* extra */
157 IDC_ARROW, /* cursor */
158 0 /* brush */
162 /***********************************************************************
163 * LISTBOX_GetCurrentPageSize
165 * Return the current page size
167 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
169 INT i, height;
170 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
171 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
173 if ((height += descr->items[i].height) > descr->height) break;
175 if (i == descr->top_item) return 1;
176 else return i - descr->top_item;
180 /***********************************************************************
181 * LISTBOX_GetMaxTopIndex
183 * Return the maximum possible index for the top of the listbox.
185 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
187 INT max, page;
189 if (descr->style & LBS_OWNERDRAWVARIABLE)
191 page = descr->height;
192 for (max = descr->nb_items - 1; max >= 0; max--)
193 if ((page -= descr->items[max].height) < 0) break;
194 if (max < descr->nb_items - 1) max++;
196 else if (descr->style & LBS_MULTICOLUMN)
198 if ((page = descr->width / descr->column_width) < 1) page = 1;
199 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
200 max = (max - page) * descr->page_size;
202 else
204 max = descr->nb_items - descr->page_size;
206 if (max < 0) max = 0;
207 return max;
211 /***********************************************************************
212 * LISTBOX_UpdateScroll
214 * Update the scrollbars. Should be called whenever the content
215 * of the listbox changes.
217 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
219 SCROLLINFO info;
221 /* Check the listbox scroll bar flags individually before we call
222 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
223 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
224 scroll bar when we do not need one.
225 if (!(descr->style & WS_VSCROLL)) return;
228 /* It is important that we check descr->style, and not wnd->dwStyle,
229 for WS_VSCROLL, as the former is exactly the one passed in
230 argument to CreateWindow.
231 In Windows (and from now on in Wine :) a listbox created
232 with such a style (no WS_SCROLL) does not update
233 the scrollbar with listbox-related data, thus letting
234 the programmer use it for his/her own purposes. */
236 if (descr->style & LBS_NOREDRAW) return;
237 info.cbSize = sizeof(info);
239 if (descr->style & LBS_MULTICOLUMN)
241 info.nMin = 0;
242 info.nMax = (descr->nb_items - 1) / descr->page_size;
243 info.nPos = descr->top_item / descr->page_size;
244 info.nPage = descr->width / descr->column_width;
245 if (info.nPage < 1) info.nPage = 1;
246 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
247 if (descr->style & LBS_DISABLENOSCROLL)
248 info.fMask |= SIF_DISABLENOSCROLL;
249 if (descr->style & WS_HSCROLL)
250 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
251 info.nMax = 0;
252 info.fMask = SIF_RANGE;
253 if (descr->style & WS_VSCROLL)
254 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
256 else
258 info.nMin = 0;
259 info.nMax = descr->nb_items - 1;
260 info.nPos = descr->top_item;
261 info.nPage = LISTBOX_GetCurrentPageSize( descr );
262 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
263 if (descr->style & LBS_DISABLENOSCROLL)
264 info.fMask |= SIF_DISABLENOSCROLL;
265 if (descr->style & WS_VSCROLL)
266 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
268 if (descr->style & WS_HSCROLL)
270 info.nPos = descr->horz_pos;
271 info.nPage = descr->width;
272 info.fMask = SIF_POS | SIF_PAGE;
273 if (descr->style & LBS_DISABLENOSCROLL)
274 info.fMask |= SIF_DISABLENOSCROLL;
275 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
281 /***********************************************************************
282 * LISTBOX_SetTopItem
284 * Set the top item of the listbox, scrolling up or down if necessary.
286 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
288 INT max = LISTBOX_GetMaxTopIndex( descr );
290 TRACE("setting top item %d, scroll %d\n", index, scroll);
292 if (index > max) index = max;
293 if (index < 0) index = 0;
294 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
295 if (descr->top_item == index) return LB_OKAY;
296 if (scroll)
298 INT diff;
299 if (descr->style & LBS_MULTICOLUMN)
300 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
301 else if (descr->style & LBS_OWNERDRAWVARIABLE)
303 INT i;
304 diff = 0;
305 if (index > descr->top_item)
307 for (i = index - 1; i >= descr->top_item; i--)
308 diff -= descr->items[i].height;
310 else
312 for (i = index; i < descr->top_item; i++)
313 diff += descr->items[i].height;
316 else
317 diff = (descr->top_item - index) * descr->item_height;
319 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
320 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
322 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
323 descr->top_item = index;
324 LISTBOX_UpdateScroll( descr );
325 return LB_OKAY;
329 /***********************************************************************
330 * LISTBOX_UpdatePage
332 * Update the page size. Should be called when the size of
333 * the client area or the item height changes.
335 static void LISTBOX_UpdatePage( LB_DESCR *descr )
337 INT page_size;
339 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
340 page_size = 1;
341 if (page_size == descr->page_size) return;
342 descr->page_size = page_size;
343 if (descr->style & LBS_MULTICOLUMN)
344 InvalidateRect( descr->self, NULL, TRUE );
345 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
349 /***********************************************************************
350 * LISTBOX_UpdateSize
352 * Update the size of the listbox. Should be called when the size of
353 * the client area changes.
355 static void LISTBOX_UpdateSize( LB_DESCR *descr )
357 RECT rect;
359 GetClientRect( descr->self, &rect );
360 descr->width = rect.right - rect.left;
361 descr->height = rect.bottom - rect.top;
362 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
364 INT remaining;
365 RECT rect;
367 GetWindowRect( descr->self, &rect );
368 if(descr->item_height != 0)
369 remaining = descr->height % descr->item_height;
370 else
371 remaining = 0;
372 if ((descr->height > descr->item_height) && remaining)
374 TRACE("[%p]: changing height %d -> %d\n",
375 descr->self, descr->height, descr->height - remaining );
376 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
377 rect.bottom - rect.top - remaining,
378 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
379 return;
382 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
383 LISTBOX_UpdatePage( descr );
384 LISTBOX_UpdateScroll( descr );
386 /* Invalidate the focused item so it will be repainted correctly */
387 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
389 InvalidateRect( descr->self, &rect, FALSE );
394 /***********************************************************************
395 * LISTBOX_GetItemRect
397 * Get the rectangle enclosing an item, in listbox client coordinates.
398 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
400 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
402 /* Index <= 0 is legal even on empty listboxes */
403 if (index && (index >= descr->nb_items))
405 memset(rect, 0, sizeof(*rect));
406 SetLastError(ERROR_INVALID_INDEX);
407 return LB_ERR;
409 SetRect( rect, 0, 0, descr->width, descr->height );
410 if (descr->style & LBS_MULTICOLUMN)
412 INT col = (index / descr->page_size) -
413 (descr->top_item / descr->page_size);
414 rect->left += col * descr->column_width;
415 rect->right = rect->left + descr->column_width;
416 rect->top += (index % descr->page_size) * descr->item_height;
417 rect->bottom = rect->top + descr->item_height;
419 else if (descr->style & LBS_OWNERDRAWVARIABLE)
421 INT i;
422 rect->right += descr->horz_pos;
423 if ((index >= 0) && (index < descr->nb_items))
425 if (index < descr->top_item)
427 for (i = descr->top_item-1; i >= index; i--)
428 rect->top -= descr->items[i].height;
430 else
432 for (i = descr->top_item; i < index; i++)
433 rect->top += descr->items[i].height;
435 rect->bottom = rect->top + descr->items[index].height;
439 else
441 rect->top += (index - descr->top_item) * descr->item_height;
442 rect->bottom = rect->top + descr->item_height;
443 rect->right += descr->horz_pos;
446 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
448 return ((rect->left < descr->width) && (rect->right > 0) &&
449 (rect->top < descr->height) && (rect->bottom > 0));
453 /***********************************************************************
454 * LISTBOX_GetItemFromPoint
456 * Return the item nearest from point (x,y) (in client coordinates).
458 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
460 INT index = descr->top_item;
462 if (!descr->nb_items) return -1; /* No items */
463 if (descr->style & LBS_OWNERDRAWVARIABLE)
465 INT pos = 0;
466 if (y >= 0)
468 while (index < descr->nb_items)
470 if ((pos += descr->items[index].height) > y) break;
471 index++;
474 else
476 while (index > 0)
478 index--;
479 if ((pos -= descr->items[index].height) <= y) break;
483 else if (descr->style & LBS_MULTICOLUMN)
485 if (y >= descr->item_height * descr->page_size) return -1;
486 if (y >= 0) index += y / descr->item_height;
487 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
488 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
490 else
492 index += (y / descr->item_height);
494 if (index < 0) return 0;
495 if (index >= descr->nb_items) return -1;
496 return index;
500 /***********************************************************************
501 * LISTBOX_PaintItem
503 * Paint an item.
505 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
506 INT index, UINT action, BOOL ignoreFocus )
508 LB_ITEMDATA *item = NULL;
509 if (index < descr->nb_items) item = &descr->items[index];
511 if (IS_OWNERDRAW(descr))
513 DRAWITEMSTRUCT dis;
514 RECT r;
515 HRGN hrgn;
517 if (!item)
519 if (action == ODA_FOCUS)
520 DrawFocusRect( hdc, rect );
521 else
522 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
523 return;
526 /* some programs mess with the clipping region when
527 drawing the item, *and* restore the previous region
528 after they are done, so a region has better to exist
529 else everything ends clipped */
530 GetClientRect(descr->self, &r);
531 hrgn = set_control_clipping( hdc, &r );
533 dis.CtlType = ODT_LISTBOX;
534 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
535 dis.hwndItem = descr->self;
536 dis.itemAction = action;
537 dis.hDC = hdc;
538 dis.itemID = index;
539 dis.itemState = 0;
540 if (item->selected) dis.itemState |= ODS_SELECTED;
541 if (!ignoreFocus && (descr->focus_item == index) &&
542 (descr->caret_on) &&
543 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
544 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
545 dis.itemData = item->data;
546 dis.rcItem = *rect;
547 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
548 descr->self, index, debugstr_w(item->str), action,
549 dis.itemState, wine_dbgstr_rect(rect) );
550 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
551 SelectClipRgn( hdc, hrgn );
552 if (hrgn) DeleteObject( hrgn );
554 else
556 COLORREF oldText = 0, oldBk = 0;
558 if (action == ODA_FOCUS)
560 DrawFocusRect( hdc, rect );
561 return;
563 if (item && item->selected)
565 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
566 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
569 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
570 descr->self, index, item ? debugstr_w(item->str) : "", action,
571 wine_dbgstr_rect(rect) );
572 if (!item)
573 ExtTextOutW( hdc, rect->left + 1, rect->top,
574 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
575 else if (!(descr->style & LBS_USETABSTOPS))
576 ExtTextOutW( hdc, rect->left + 1, rect->top,
577 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
578 strlenW(item->str), NULL );
579 else
581 /* Output empty string to paint background in the full width. */
582 ExtTextOutW( hdc, rect->left + 1, rect->top,
583 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
584 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
585 item->str, strlenW(item->str),
586 descr->nb_tabs, descr->tabs, 0);
588 if (item && item->selected)
590 SetBkColor( hdc, oldBk );
591 SetTextColor( hdc, oldText );
593 if (!ignoreFocus && (descr->focus_item == index) &&
594 (descr->caret_on) &&
595 (descr->in_focus)) DrawFocusRect( hdc, rect );
600 /***********************************************************************
601 * LISTBOX_SetRedraw
603 * Change the redraw flag.
605 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
607 if (on)
609 if (!(descr->style & LBS_NOREDRAW)) return;
610 descr->style &= ~LBS_NOREDRAW;
611 if (descr->style & LBS_DISPLAYCHANGED)
612 { /* page was changed while setredraw false, refresh automatically */
613 InvalidateRect(descr->self, NULL, TRUE);
614 if ((descr->top_item + descr->page_size) > descr->nb_items)
615 { /* reset top of page if less than number of items/page */
616 descr->top_item = descr->nb_items - descr->page_size;
617 if (descr->top_item < 0) descr->top_item = 0;
619 descr->style &= ~LBS_DISPLAYCHANGED;
621 LISTBOX_UpdateScroll( descr );
623 else descr->style |= LBS_NOREDRAW;
627 /***********************************************************************
628 * LISTBOX_RepaintItem
630 * Repaint a single item synchronously.
632 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
634 HDC hdc;
635 RECT rect;
636 HFONT oldFont = 0;
637 HBRUSH hbrush, oldBrush = 0;
639 /* Do not repaint the item if the item is not visible */
640 if (!IsWindowVisible(descr->self)) return;
641 if (descr->style & LBS_NOREDRAW)
643 descr->style |= LBS_DISPLAYCHANGED;
644 return;
646 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
647 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
648 if (descr->font) oldFont = SelectObject( hdc, descr->font );
649 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
650 (WPARAM)hdc, (LPARAM)descr->self );
651 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
652 if (!IsWindowEnabled(descr->self))
653 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
654 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
655 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
656 if (oldFont) SelectObject( hdc, oldFont );
657 if (oldBrush) SelectObject( hdc, oldBrush );
658 ReleaseDC( descr->self, hdc );
662 /***********************************************************************
663 * LISTBOX_DrawFocusRect
665 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
667 HDC hdc;
668 RECT rect;
669 HFONT oldFont = 0;
671 /* Do not repaint the item if the item is not visible */
672 if (!IsWindowVisible(descr->self)) return;
674 if (descr->focus_item == -1) return;
675 if (!descr->caret_on || !descr->in_focus) return;
677 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
678 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
679 if (descr->font) oldFont = SelectObject( hdc, descr->font );
680 if (!IsWindowEnabled(descr->self))
681 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
682 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
683 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
684 if (oldFont) SelectObject( hdc, oldFont );
685 ReleaseDC( descr->self, hdc );
689 /***********************************************************************
690 * LISTBOX_InitStorage
692 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
694 LB_ITEMDATA *item;
696 nb_items += LB_ARRAY_GRANULARITY - 1;
697 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
698 if (descr->items) {
699 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
700 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
701 nb_items * sizeof(LB_ITEMDATA));
703 else {
704 item = HeapAlloc( GetProcessHeap(), 0,
705 nb_items * sizeof(LB_ITEMDATA));
708 if (!item)
710 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
711 return LB_ERRSPACE;
713 descr->items = item;
714 return LB_OKAY;
718 /***********************************************************************
719 * LISTBOX_SetTabStops
721 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
723 INT i;
725 if (!(descr->style & LBS_USETABSTOPS))
727 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
728 return FALSE;
731 HeapFree( GetProcessHeap(), 0, descr->tabs );
732 if (!(descr->nb_tabs = count))
734 descr->tabs = NULL;
735 return TRUE;
737 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
738 descr->nb_tabs * sizeof(INT) )))
739 return FALSE;
740 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
742 /* convert into "dialog units"*/
743 for (i = 0; i < descr->nb_tabs; i++)
744 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
746 return TRUE;
750 /***********************************************************************
751 * LISTBOX_GetText
753 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
755 DWORD len;
757 if ((index < 0) || (index >= descr->nb_items))
759 SetLastError(ERROR_INVALID_INDEX);
760 return LB_ERR;
762 if (HAS_STRINGS(descr))
764 if (!buffer)
766 len = strlenW(descr->items[index].str);
767 if( unicode )
768 return len;
769 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
770 NULL, 0, NULL, NULL );
773 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
775 __TRY /* hide a Delphi bug that passes a read-only buffer */
777 if(unicode)
779 strcpyW( buffer, descr->items[index].str );
780 len = strlenW(buffer);
782 else
784 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
785 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
788 __EXCEPT_PAGE_FAULT
790 WARN( "got an invalid buffer (Delphi bug?)\n" );
791 SetLastError( ERROR_INVALID_PARAMETER );
792 return LB_ERR;
794 __ENDTRY
795 } else {
796 if (buffer)
797 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
798 len = sizeof(DWORD);
800 return len;
803 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
805 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
806 if (ret == CSTR_LESS_THAN)
807 return -1;
808 if (ret == CSTR_EQUAL)
809 return 0;
810 if (ret == CSTR_GREATER_THAN)
811 return 1;
812 return -1;
815 /***********************************************************************
816 * LISTBOX_FindStringPos
818 * Find the nearest string located before a given string in sort order.
819 * If 'exact' is TRUE, return an error if we don't get an exact match.
821 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
823 INT index, min, max, res = -1;
825 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
826 min = 0;
827 max = descr->nb_items;
828 while (min != max)
830 index = (min + max) / 2;
831 if (HAS_STRINGS(descr))
832 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
833 else
835 COMPAREITEMSTRUCT cis;
836 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
838 cis.CtlType = ODT_LISTBOX;
839 cis.CtlID = id;
840 cis.hwndItem = descr->self;
841 /* note that some application (MetaStock) expects the second item
842 * to be in the listbox */
843 cis.itemID1 = -1;
844 cis.itemData1 = (ULONG_PTR)str;
845 cis.itemID2 = index;
846 cis.itemData2 = descr->items[index].data;
847 cis.dwLocaleId = descr->locale;
848 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
850 if (!res) return index;
851 if (res < 0) max = index;
852 else min = index + 1;
854 return exact ? -1 : max;
858 /***********************************************************************
859 * LISTBOX_FindFileStrPos
861 * Find the nearest string located before a given string in directory
862 * sort order (i.e. first files, then directories, then drives).
864 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
866 INT min, max, res = -1;
868 if (!HAS_STRINGS(descr))
869 return LISTBOX_FindStringPos( descr, str, FALSE );
870 min = 0;
871 max = descr->nb_items;
872 while (min != max)
874 INT index = (min + max) / 2;
875 LPCWSTR p = descr->items[index].str;
876 if (*p == '[') /* drive or directory */
878 if (*str != '[') res = -1;
879 else if (p[1] == '-') /* drive */
881 if (str[1] == '-') res = str[2] - p[2];
882 else res = -1;
884 else /* directory */
886 if (str[1] == '-') res = 1;
887 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
890 else /* filename */
892 if (*str == '[') res = 1;
893 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
895 if (!res) return index;
896 if (res < 0) max = index;
897 else min = index + 1;
899 return max;
903 /***********************************************************************
904 * LISTBOX_FindString
906 * Find the item beginning with a given string.
908 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
910 INT i;
911 LB_ITEMDATA *item;
913 if (start >= descr->nb_items) start = -1;
914 item = descr->items + start + 1;
915 if (HAS_STRINGS(descr))
917 if (!str || ! str[0] ) return LB_ERR;
918 if (exact)
920 for (i = start + 1; i < descr->nb_items; i++, item++)
921 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
922 for (i = 0, item = descr->items; i <= start; i++, item++)
923 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
925 else
927 /* Special case for drives and directories: ignore prefix */
928 #define CHECK_DRIVE(item) \
929 if ((item)->str[0] == '[') \
931 if (!strncmpiW( str, (item)->str+1, len )) return i; \
932 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
933 return i; \
936 INT len = strlenW(str);
937 for (i = start + 1; i < descr->nb_items; i++, item++)
939 if (!strncmpiW( str, item->str, len )) return i;
940 CHECK_DRIVE(item);
942 for (i = 0, item = descr->items; i <= start; i++, item++)
944 if (!strncmpiW( str, item->str, len )) return i;
945 CHECK_DRIVE(item);
947 #undef CHECK_DRIVE
950 else
952 if (exact && (descr->style & LBS_SORT))
953 /* If sorted, use a WM_COMPAREITEM binary search */
954 return LISTBOX_FindStringPos( descr, str, TRUE );
956 /* Otherwise use a linear search */
957 for (i = start + 1; i < descr->nb_items; i++, item++)
958 if (item->data == (ULONG_PTR)str) return i;
959 for (i = 0, item = descr->items; i <= start; i++, item++)
960 if (item->data == (ULONG_PTR)str) return i;
962 return LB_ERR;
966 /***********************************************************************
967 * LISTBOX_GetSelCount
969 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
971 INT i, count;
972 const LB_ITEMDATA *item = descr->items;
974 if (!(descr->style & LBS_MULTIPLESEL) ||
975 (descr->style & LBS_NOSEL))
976 return LB_ERR;
977 for (i = count = 0; i < descr->nb_items; i++, item++)
978 if (item->selected) count++;
979 return count;
983 /***********************************************************************
984 * LISTBOX_GetSelItems
986 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
988 INT i, count;
989 const LB_ITEMDATA *item = descr->items;
991 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
992 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
993 if (item->selected) array[count++] = i;
994 return count;
998 /***********************************************************************
999 * LISTBOX_Paint
1001 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1003 INT i, col_pos = descr->page_size - 1;
1004 RECT rect;
1005 RECT focusRect = {-1, -1, -1, -1};
1006 HFONT oldFont = 0;
1007 HBRUSH hbrush, oldBrush = 0;
1009 if (descr->style & LBS_NOREDRAW) return 0;
1011 SetRect( &rect, 0, 0, descr->width, descr->height );
1012 if (descr->style & LBS_MULTICOLUMN)
1013 rect.right = rect.left + descr->column_width;
1014 else if (descr->horz_pos)
1016 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1017 rect.right += descr->horz_pos;
1020 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1021 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1022 (WPARAM)hdc, (LPARAM)descr->self );
1023 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1024 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1026 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1027 (descr->in_focus))
1029 /* Special case for empty listbox: paint focus rect */
1030 rect.bottom = rect.top + descr->item_height;
1031 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1032 &rect, NULL, 0, NULL );
1033 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1034 rect.top = rect.bottom;
1037 /* Paint all the item, regarding the selection
1038 Focus state will be painted after */
1040 for (i = descr->top_item; i < descr->nb_items; i++)
1042 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1043 rect.bottom = rect.top + descr->item_height;
1044 else
1045 rect.bottom = rect.top + descr->items[i].height;
1047 if (i == descr->focus_item)
1049 /* keep the focus rect, to paint the focus item after */
1050 focusRect.left = rect.left;
1051 focusRect.right = rect.right;
1052 focusRect.top = rect.top;
1053 focusRect.bottom = rect.bottom;
1055 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1056 rect.top = rect.bottom;
1058 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1060 if (!IS_OWNERDRAW(descr))
1062 /* Clear the bottom of the column */
1063 if (rect.top < descr->height)
1065 rect.bottom = descr->height;
1066 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1067 &rect, NULL, 0, NULL );
1071 /* Go to the next column */
1072 rect.left += descr->column_width;
1073 rect.right += descr->column_width;
1074 rect.top = 0;
1075 col_pos = descr->page_size - 1;
1077 else
1079 col_pos--;
1080 if (rect.top >= descr->height) break;
1084 /* Paint the focus item now */
1085 if (focusRect.top != focusRect.bottom &&
1086 descr->caret_on && descr->in_focus)
1087 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1089 if (!IS_OWNERDRAW(descr))
1091 /* Clear the remainder of the client area */
1092 if (rect.top < descr->height)
1094 rect.bottom = descr->height;
1095 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1096 &rect, NULL, 0, NULL );
1098 if (rect.right < descr->width)
1100 rect.left = rect.right;
1101 rect.right = descr->width;
1102 rect.top = 0;
1103 rect.bottom = descr->height;
1104 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1105 &rect, NULL, 0, NULL );
1108 if (oldFont) SelectObject( hdc, oldFont );
1109 if (oldBrush) SelectObject( hdc, oldBrush );
1110 return 0;
1114 /***********************************************************************
1115 * LISTBOX_InvalidateItems
1117 * Invalidate all items from a given item. If the specified item is not
1118 * visible, nothing happens.
1120 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1122 RECT rect;
1124 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1126 if (descr->style & LBS_NOREDRAW)
1128 descr->style |= LBS_DISPLAYCHANGED;
1129 return;
1131 rect.bottom = descr->height;
1132 InvalidateRect( descr->self, &rect, TRUE );
1133 if (descr->style & LBS_MULTICOLUMN)
1135 /* Repaint the other columns */
1136 rect.left = rect.right;
1137 rect.right = descr->width;
1138 rect.top = 0;
1139 InvalidateRect( descr->self, &rect, TRUE );
1144 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1146 RECT rect;
1148 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1149 InvalidateRect( descr->self, &rect, TRUE );
1152 /***********************************************************************
1153 * LISTBOX_GetItemHeight
1155 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1157 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1159 if ((index < 0) || (index >= descr->nb_items))
1161 SetLastError(ERROR_INVALID_INDEX);
1162 return LB_ERR;
1164 return descr->items[index].height;
1166 else return descr->item_height;
1170 /***********************************************************************
1171 * LISTBOX_SetItemHeight
1173 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1175 if (height > MAXBYTE)
1176 return -1;
1178 if (!height) height = 1;
1180 if (descr->style & LBS_OWNERDRAWVARIABLE)
1182 if ((index < 0) || (index >= descr->nb_items))
1184 SetLastError(ERROR_INVALID_INDEX);
1185 return LB_ERR;
1187 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1188 descr->items[index].height = height;
1189 LISTBOX_UpdateScroll( descr );
1190 if (repaint)
1191 LISTBOX_InvalidateItems( descr, index );
1193 else if (height != descr->item_height)
1195 TRACE("[%p]: new height = %d\n", descr->self, height );
1196 descr->item_height = height;
1197 LISTBOX_UpdatePage( descr );
1198 LISTBOX_UpdateScroll( descr );
1199 if (repaint)
1200 InvalidateRect( descr->self, 0, TRUE );
1202 return LB_OKAY;
1206 /***********************************************************************
1207 * LISTBOX_SetHorizontalPos
1209 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1211 INT diff;
1213 if (pos > descr->horz_extent - descr->width)
1214 pos = descr->horz_extent - descr->width;
1215 if (pos < 0) pos = 0;
1216 if (!(diff = descr->horz_pos - pos)) return;
1217 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1218 descr->horz_pos = pos;
1219 LISTBOX_UpdateScroll( descr );
1220 if (abs(diff) < descr->width)
1222 RECT rect;
1223 /* Invalidate the focused item so it will be repainted correctly */
1224 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1225 InvalidateRect( descr->self, &rect, TRUE );
1226 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1227 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1229 else
1230 InvalidateRect( descr->self, NULL, TRUE );
1234 /***********************************************************************
1235 * LISTBOX_SetHorizontalExtent
1237 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1239 if (descr->style & LBS_MULTICOLUMN)
1240 return LB_OKAY;
1241 if (extent == descr->horz_extent) return LB_OKAY;
1242 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1243 descr->horz_extent = extent;
1244 if (descr->style & WS_HSCROLL) {
1245 SCROLLINFO info;
1246 info.cbSize = sizeof(info);
1247 info.nMin = 0;
1248 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1249 info.fMask = SIF_RANGE;
1250 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1252 if (descr->horz_pos > extent - descr->width)
1253 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1254 return LB_OKAY;
1258 /***********************************************************************
1259 * LISTBOX_SetColumnWidth
1261 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1263 if (width == descr->column_width) return LB_OKAY;
1264 TRACE("[%p]: new column width = %d\n", descr->self, width );
1265 descr->column_width = width;
1266 LISTBOX_UpdatePage( descr );
1267 return LB_OKAY;
1271 /***********************************************************************
1272 * LISTBOX_SetFont
1274 * Returns the item height.
1276 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1278 HDC hdc;
1279 HFONT oldFont = 0;
1280 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1281 SIZE sz;
1283 descr->font = font;
1285 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1287 ERR("unable to get DC.\n" );
1288 return 16;
1290 if (font) oldFont = SelectObject( hdc, font );
1291 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1292 if (oldFont) SelectObject( hdc, oldFont );
1293 ReleaseDC( descr->self, hdc );
1295 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1296 if (!IS_OWNERDRAW(descr))
1297 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1298 return sz.cy;
1302 /***********************************************************************
1303 * LISTBOX_MakeItemVisible
1305 * Make sure that a given item is partially or fully visible.
1307 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1309 INT top;
1311 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1313 if (index <= descr->top_item) top = index;
1314 else if (descr->style & LBS_MULTICOLUMN)
1316 INT cols = descr->width;
1317 if (!fully) cols += descr->column_width - 1;
1318 if (cols >= descr->column_width) cols /= descr->column_width;
1319 else cols = 1;
1320 if (index < descr->top_item + (descr->page_size * cols)) return;
1321 top = index - descr->page_size * (cols - 1);
1323 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1325 INT height = fully ? descr->items[index].height : 1;
1326 for (top = index; top > descr->top_item; top--)
1327 if ((height += descr->items[top-1].height) > descr->height) break;
1329 else
1331 if (index < descr->top_item + descr->page_size) return;
1332 if (!fully && (index == descr->top_item + descr->page_size) &&
1333 (descr->height > (descr->page_size * descr->item_height))) return;
1334 top = index - descr->page_size + 1;
1336 LISTBOX_SetTopItem( descr, top, TRUE );
1339 /***********************************************************************
1340 * LISTBOX_SetCaretIndex
1342 * NOTES
1343 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1346 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1348 INT oldfocus = descr->focus_item;
1350 TRACE("old focus %d, index %d\n", oldfocus, index);
1352 if (descr->style & LBS_NOSEL) return LB_ERR;
1353 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1354 if (index == oldfocus) return LB_OKAY;
1356 LISTBOX_DrawFocusRect( descr, FALSE );
1357 descr->focus_item = index;
1359 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1360 LISTBOX_DrawFocusRect( descr, TRUE );
1362 return LB_OKAY;
1366 /***********************************************************************
1367 * LISTBOX_SelectItemRange
1369 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1371 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1372 INT last, BOOL on )
1374 INT i;
1376 /* A few sanity checks */
1378 if (descr->style & LBS_NOSEL) return LB_ERR;
1379 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1381 if (!descr->nb_items) return LB_OKAY;
1383 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1384 if (first < 0) first = 0;
1385 if (last < first) return LB_OKAY;
1387 if (on) /* Turn selection on */
1389 for (i = first; i <= last; i++)
1391 if (descr->items[i].selected) continue;
1392 descr->items[i].selected = TRUE;
1393 LISTBOX_InvalidateItemRect(descr, i);
1396 else /* Turn selection off */
1398 for (i = first; i <= last; i++)
1400 if (!descr->items[i].selected) continue;
1401 descr->items[i].selected = FALSE;
1402 LISTBOX_InvalidateItemRect(descr, i);
1405 return LB_OKAY;
1408 /***********************************************************************
1409 * LISTBOX_SetSelection
1411 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1412 BOOL on, BOOL send_notify )
1414 TRACE( "cur_sel=%d index=%d notify=%s\n",
1415 descr->selected_item, index, send_notify ? "YES" : "NO" );
1417 if (descr->style & LBS_NOSEL)
1419 descr->selected_item = index;
1420 return LB_ERR;
1422 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1423 if (descr->style & LBS_MULTIPLESEL)
1425 if (index == -1) /* Select all items */
1426 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1427 else /* Only one item */
1428 return LISTBOX_SelectItemRange( descr, index, index, on );
1430 else
1432 INT oldsel = descr->selected_item;
1433 if (index == oldsel) return LB_OKAY;
1434 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1435 if (index != -1) descr->items[index].selected = TRUE;
1436 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1437 descr->selected_item = index;
1438 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1439 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1440 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1441 else
1442 if( descr->lphc ) /* set selection change flag for parent combo */
1443 descr->lphc->wState |= CBF_SELCHANGE;
1445 return LB_OKAY;
1449 /***********************************************************************
1450 * LISTBOX_MoveCaret
1452 * Change the caret position and extend the selection to the new caret.
1454 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1456 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1458 if ((index < 0) || (index >= descr->nb_items))
1459 return;
1461 /* Important, repaint needs to be done in this order if
1462 you want to mimic Windows behavior:
1463 1. Remove the focus and paint the item
1464 2. Remove the selection and paint the item(s)
1465 3. Set the selection and repaint the item(s)
1466 4. Set the focus to 'index' and repaint the item */
1468 /* 1. remove the focus and repaint the item */
1469 LISTBOX_DrawFocusRect( descr, FALSE );
1471 /* 2. then turn off the previous selection */
1472 /* 3. repaint the new selected item */
1473 if (descr->style & LBS_EXTENDEDSEL)
1475 if (descr->anchor_item != -1)
1477 INT first = min( index, descr->anchor_item );
1478 INT last = max( index, descr->anchor_item );
1479 if (first > 0)
1480 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1481 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1482 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1485 else if (!(descr->style & LBS_MULTIPLESEL))
1487 /* Set selection to new caret item */
1488 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1491 /* 4. repaint the new item with the focus */
1492 descr->focus_item = index;
1493 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1494 LISTBOX_DrawFocusRect( descr, TRUE );
1498 /***********************************************************************
1499 * LISTBOX_InsertItem
1501 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1502 LPWSTR str, ULONG_PTR data )
1504 LB_ITEMDATA *item;
1505 INT max_items;
1506 INT oldfocus = descr->focus_item;
1508 if (index == -1) index = descr->nb_items;
1509 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1510 if (!descr->items) max_items = 0;
1511 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1512 if (descr->nb_items == max_items)
1514 /* We need to grow the array */
1515 max_items += LB_ARRAY_GRANULARITY;
1516 if (descr->items)
1517 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1518 max_items * sizeof(LB_ITEMDATA) );
1519 else
1520 item = HeapAlloc( GetProcessHeap(), 0,
1521 max_items * sizeof(LB_ITEMDATA) );
1522 if (!item)
1524 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1525 return LB_ERRSPACE;
1527 descr->items = item;
1530 /* Insert the item structure */
1532 item = &descr->items[index];
1533 if (index < descr->nb_items)
1534 RtlMoveMemory( item + 1, item,
1535 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1536 item->str = str;
1537 item->data = data;
1538 item->height = 0;
1539 item->selected = FALSE;
1540 descr->nb_items++;
1542 /* Get item height */
1544 if (descr->style & LBS_OWNERDRAWVARIABLE)
1546 MEASUREITEMSTRUCT mis;
1547 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1549 mis.CtlType = ODT_LISTBOX;
1550 mis.CtlID = id;
1551 mis.itemID = index;
1552 mis.itemData = descr->items[index].data;
1553 mis.itemHeight = descr->item_height;
1554 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1555 item->height = mis.itemHeight ? mis.itemHeight : 1;
1556 TRACE("[%p]: measure item %d (%s) = %d\n",
1557 descr->self, index, str ? debugstr_w(str) : "", item->height );
1560 /* Repaint the items */
1562 LISTBOX_UpdateScroll( descr );
1563 LISTBOX_InvalidateItems( descr, index );
1565 /* Move selection and focused item */
1566 /* If listbox was empty, set focus to the first item */
1567 if (descr->nb_items == 1)
1568 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1569 /* single select don't change selection index in win31 */
1570 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1572 descr->selected_item++;
1573 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1575 else
1577 if (index <= descr->selected_item)
1579 descr->selected_item++;
1580 descr->focus_item = oldfocus; /* focus not changed */
1583 return LB_OKAY;
1587 /***********************************************************************
1588 * LISTBOX_InsertString
1590 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1592 LPWSTR new_str = NULL;
1593 ULONG_PTR data = 0;
1594 LRESULT ret;
1596 if (HAS_STRINGS(descr))
1598 static const WCHAR empty_stringW[] = { 0 };
1599 if (!str) str = empty_stringW;
1600 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1602 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1603 return LB_ERRSPACE;
1605 strcpyW(new_str, str);
1607 else data = (ULONG_PTR)str;
1609 if (index == -1) index = descr->nb_items;
1610 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1612 HeapFree( GetProcessHeap(), 0, new_str );
1613 return ret;
1616 TRACE("[%p]: added item %d %s\n",
1617 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1618 return index;
1622 /***********************************************************************
1623 * LISTBOX_DeleteItem
1625 * Delete the content of an item. 'index' must be a valid index.
1627 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1629 /* save the item data before it gets freed by LB_RESETCONTENT */
1630 ULONG_PTR item_data = descr->items[index].data;
1631 LPWSTR item_str = descr->items[index].str;
1633 if (!descr->nb_items)
1634 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1636 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1637 * while Win95 sends it for all items with user data.
1638 * It's probably better to send it too often than not
1639 * often enough, so this is what we do here.
1641 if (IS_OWNERDRAW(descr) || item_data)
1643 DELETEITEMSTRUCT dis;
1644 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1646 dis.CtlType = ODT_LISTBOX;
1647 dis.CtlID = id;
1648 dis.itemID = index;
1649 dis.hwndItem = descr->self;
1650 dis.itemData = item_data;
1651 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1653 if (HAS_STRINGS(descr))
1654 HeapFree( GetProcessHeap(), 0, item_str );
1658 /***********************************************************************
1659 * LISTBOX_RemoveItem
1661 * Remove an item from the listbox and delete its content.
1663 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1665 LB_ITEMDATA *item;
1666 INT max_items;
1668 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1670 /* We need to invalidate the original rect instead of the updated one. */
1671 LISTBOX_InvalidateItems( descr, index );
1673 descr->nb_items--;
1674 LISTBOX_DeleteItem( descr, index );
1676 if (!descr->nb_items) return LB_OKAY;
1678 /* Remove the item */
1680 item = &descr->items[index];
1681 if (index < descr->nb_items)
1682 RtlMoveMemory( item, item + 1,
1683 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1684 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1686 /* Shrink the item array if possible */
1688 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1689 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1691 max_items -= LB_ARRAY_GRANULARITY;
1692 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1693 max_items * sizeof(LB_ITEMDATA) );
1694 if (item) descr->items = item;
1696 /* Repaint the items */
1698 LISTBOX_UpdateScroll( descr );
1699 /* if we removed the scrollbar, reset the top of the list
1700 (correct for owner-drawn ???) */
1701 if (descr->nb_items == descr->page_size)
1702 LISTBOX_SetTopItem( descr, 0, TRUE );
1704 /* Move selection and focused item */
1705 if (!IS_MULTISELECT(descr))
1707 if (index == descr->selected_item)
1708 descr->selected_item = -1;
1709 else if (index < descr->selected_item)
1711 descr->selected_item--;
1712 if (ISWIN31) /* win 31 do not change the selected item number */
1713 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1717 if (descr->focus_item >= descr->nb_items)
1719 descr->focus_item = descr->nb_items - 1;
1720 if (descr->focus_item < 0) descr->focus_item = 0;
1722 return LB_OKAY;
1726 /***********************************************************************
1727 * LISTBOX_ResetContent
1729 static void LISTBOX_ResetContent( LB_DESCR *descr )
1731 INT i;
1733 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1734 HeapFree( GetProcessHeap(), 0, descr->items );
1735 descr->nb_items = 0;
1736 descr->top_item = 0;
1737 descr->selected_item = -1;
1738 descr->focus_item = 0;
1739 descr->anchor_item = -1;
1740 descr->items = NULL;
1744 /***********************************************************************
1745 * LISTBOX_SetCount
1747 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1749 LRESULT ret;
1751 if (HAS_STRINGS(descr))
1753 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1754 return LB_ERR;
1757 /* FIXME: this is far from optimal... */
1758 if (count > descr->nb_items)
1760 while (count > descr->nb_items)
1761 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1762 return ret;
1764 else if (count < descr->nb_items)
1766 while (count < descr->nb_items)
1767 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1768 return ret;
1771 InvalidateRect( descr->self, NULL, TRUE );
1772 return LB_OKAY;
1776 /***********************************************************************
1777 * LISTBOX_Directory
1779 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1780 LPCWSTR filespec, BOOL long_names )
1782 HANDLE handle;
1783 LRESULT ret = LB_OKAY;
1784 WIN32_FIND_DATAW entry;
1785 int pos;
1786 LRESULT maxinsert = LB_ERR;
1788 /* don't scan directory if we just want drives exclusively */
1789 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1790 /* scan directory */
1791 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1793 int le = GetLastError();
1794 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1796 else
1800 WCHAR buffer[270];
1801 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1803 static const WCHAR bracketW[] = { ']',0 };
1804 static const WCHAR dotW[] = { '.',0 };
1805 if (!(attrib & DDL_DIRECTORY) ||
1806 !strcmpW( entry.cFileName, dotW )) continue;
1807 buffer[0] = '[';
1808 if (!long_names && entry.cAlternateFileName[0])
1809 strcpyW( buffer + 1, entry.cAlternateFileName );
1810 else
1811 strcpyW( buffer + 1, entry.cFileName );
1812 strcatW(buffer, bracketW);
1814 else /* not a directory */
1816 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1817 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1819 if ((attrib & DDL_EXCLUSIVE) &&
1820 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1821 continue;
1822 #undef ATTRIBS
1823 if (!long_names && entry.cAlternateFileName[0])
1824 strcpyW( buffer, entry.cAlternateFileName );
1825 else
1826 strcpyW( buffer, entry.cFileName );
1828 if (!long_names) CharLowerW( buffer );
1829 pos = LISTBOX_FindFileStrPos( descr, buffer );
1830 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1831 break;
1832 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1833 } while (FindNextFileW( handle, &entry ));
1834 FindClose( handle );
1837 if (ret >= 0)
1839 ret = maxinsert;
1841 /* scan drives */
1842 if (attrib & DDL_DRIVES)
1844 WCHAR buffer[] = {'[','-','a','-',']',0};
1845 WCHAR root[] = {'A',':','\\',0};
1846 int drive;
1847 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1849 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1850 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1851 break;
1855 return ret;
1859 /***********************************************************************
1860 * LISTBOX_HandleVScroll
1862 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1864 SCROLLINFO info;
1866 if (descr->style & LBS_MULTICOLUMN) return 0;
1867 switch(scrollReq)
1869 case SB_LINEUP:
1870 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1871 break;
1872 case SB_LINEDOWN:
1873 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1874 break;
1875 case SB_PAGEUP:
1876 LISTBOX_SetTopItem( descr, descr->top_item -
1877 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1878 break;
1879 case SB_PAGEDOWN:
1880 LISTBOX_SetTopItem( descr, descr->top_item +
1881 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1882 break;
1883 case SB_THUMBPOSITION:
1884 LISTBOX_SetTopItem( descr, pos, TRUE );
1885 break;
1886 case SB_THUMBTRACK:
1887 info.cbSize = sizeof(info);
1888 info.fMask = SIF_TRACKPOS;
1889 GetScrollInfo( descr->self, SB_VERT, &info );
1890 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1891 break;
1892 case SB_TOP:
1893 LISTBOX_SetTopItem( descr, 0, TRUE );
1894 break;
1895 case SB_BOTTOM:
1896 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1897 break;
1899 return 0;
1903 /***********************************************************************
1904 * LISTBOX_HandleHScroll
1906 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1908 SCROLLINFO info;
1909 INT page;
1911 if (descr->style & LBS_MULTICOLUMN)
1913 switch(scrollReq)
1915 case SB_LINELEFT:
1916 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1917 TRUE );
1918 break;
1919 case SB_LINERIGHT:
1920 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1921 TRUE );
1922 break;
1923 case SB_PAGELEFT:
1924 page = descr->width / descr->column_width;
1925 if (page < 1) page = 1;
1926 LISTBOX_SetTopItem( descr,
1927 descr->top_item - page * descr->page_size, TRUE );
1928 break;
1929 case SB_PAGERIGHT:
1930 page = descr->width / descr->column_width;
1931 if (page < 1) page = 1;
1932 LISTBOX_SetTopItem( descr,
1933 descr->top_item + page * descr->page_size, TRUE );
1934 break;
1935 case SB_THUMBPOSITION:
1936 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1937 break;
1938 case SB_THUMBTRACK:
1939 info.cbSize = sizeof(info);
1940 info.fMask = SIF_TRACKPOS;
1941 GetScrollInfo( descr->self, SB_VERT, &info );
1942 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1943 TRUE );
1944 break;
1945 case SB_LEFT:
1946 LISTBOX_SetTopItem( descr, 0, TRUE );
1947 break;
1948 case SB_RIGHT:
1949 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1950 break;
1953 else if (descr->horz_extent)
1955 switch(scrollReq)
1957 case SB_LINELEFT:
1958 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1959 break;
1960 case SB_LINERIGHT:
1961 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1962 break;
1963 case SB_PAGELEFT:
1964 LISTBOX_SetHorizontalPos( descr,
1965 descr->horz_pos - descr->width );
1966 break;
1967 case SB_PAGERIGHT:
1968 LISTBOX_SetHorizontalPos( descr,
1969 descr->horz_pos + descr->width );
1970 break;
1971 case SB_THUMBPOSITION:
1972 LISTBOX_SetHorizontalPos( descr, pos );
1973 break;
1974 case SB_THUMBTRACK:
1975 info.cbSize = sizeof(info);
1976 info.fMask = SIF_TRACKPOS;
1977 GetScrollInfo( descr->self, SB_HORZ, &info );
1978 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1979 break;
1980 case SB_LEFT:
1981 LISTBOX_SetHorizontalPos( descr, 0 );
1982 break;
1983 case SB_RIGHT:
1984 LISTBOX_SetHorizontalPos( descr,
1985 descr->horz_extent - descr->width );
1986 break;
1989 return 0;
1992 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1994 UINT pulScrollLines = 3;
1996 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1998 /* if scrolling changes direction, ignore left overs */
1999 if ((delta < 0 && descr->wheel_remain < 0) ||
2000 (delta > 0 && descr->wheel_remain > 0))
2001 descr->wheel_remain += delta;
2002 else
2003 descr->wheel_remain = delta;
2005 if (descr->wheel_remain && pulScrollLines)
2007 int cLineScroll;
2008 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2009 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2010 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2011 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2013 return 0;
2016 /***********************************************************************
2017 * LISTBOX_HandleLButtonDown
2019 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2021 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2023 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2024 descr->self, x, y, index, descr->focus_item);
2026 if (!descr->caret_on && (descr->in_focus)) return 0;
2028 if (!descr->in_focus)
2030 if( !descr->lphc ) SetFocus( descr->self );
2031 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2034 if (index == -1) return 0;
2036 if (!descr->lphc)
2038 if (descr->style & LBS_NOTIFY )
2039 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2040 MAKELPARAM( x, y ) );
2043 descr->captured = TRUE;
2044 SetCapture( descr->self );
2046 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2048 /* we should perhaps make sure that all items are deselected
2049 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2050 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2051 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2054 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2055 if (keys & MK_CONTROL)
2057 LISTBOX_SetCaretIndex( descr, index, FALSE );
2058 LISTBOX_SetSelection( descr, index,
2059 !descr->items[index].selected,
2060 (descr->style & LBS_NOTIFY) != 0);
2062 else
2064 LISTBOX_MoveCaret( descr, index, FALSE );
2066 if (descr->style & LBS_EXTENDEDSEL)
2068 LISTBOX_SetSelection( descr, index,
2069 descr->items[index].selected,
2070 (descr->style & LBS_NOTIFY) != 0 );
2072 else
2074 LISTBOX_SetSelection( descr, index,
2075 !descr->items[index].selected,
2076 (descr->style & LBS_NOTIFY) != 0 );
2080 else
2082 descr->anchor_item = index;
2083 LISTBOX_MoveCaret( descr, index, FALSE );
2084 LISTBOX_SetSelection( descr, index,
2085 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2088 if (!descr->lphc)
2090 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2092 POINT pt;
2094 pt.x = x;
2095 pt.y = y;
2097 if (DragDetect( descr->self, pt ))
2098 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2101 return 0;
2105 /*************************************************************************
2106 * LISTBOX_HandleLButtonDownCombo [Internal]
2108 * Process LButtonDown message for the ComboListBox
2110 * PARAMS
2111 * pWnd [I] The windows internal structure
2112 * pDescr [I] The ListBox internal structure
2113 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2114 * x [I] X Mouse Coordinate
2115 * y [I] Y Mouse Coordinate
2117 * RETURNS
2118 * 0 since we are processing the WM_LBUTTONDOWN Message
2120 * NOTES
2121 * This function is only to be used when a ListBox is a ComboListBox
2124 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2126 RECT clientRect, screenRect;
2127 POINT mousePos;
2129 mousePos.x = x;
2130 mousePos.y = y;
2132 GetClientRect(descr->self, &clientRect);
2134 if(PtInRect(&clientRect, mousePos))
2136 /* MousePos is in client, resume normal processing */
2137 if (msg == WM_LBUTTONDOWN)
2139 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2140 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2142 else if (descr->style & LBS_NOTIFY)
2143 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2145 else
2147 POINT screenMousePos;
2148 HWND hWndOldCapture;
2150 /* Check the Non-Client Area */
2151 screenMousePos = mousePos;
2152 hWndOldCapture = GetCapture();
2153 ReleaseCapture();
2154 GetWindowRect(descr->self, &screenRect);
2155 ClientToScreen(descr->self, &screenMousePos);
2157 if(!PtInRect(&screenRect, screenMousePos))
2159 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2160 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2161 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2163 else
2165 /* Check to see the NC is a scrollbar */
2166 INT nHitTestType=0;
2167 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2168 /* Check Vertical scroll bar */
2169 if (style & WS_VSCROLL)
2171 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2172 if (PtInRect( &clientRect, mousePos ))
2173 nHitTestType = HTVSCROLL;
2175 /* Check horizontal scroll bar */
2176 if (style & WS_HSCROLL)
2178 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2179 if (PtInRect( &clientRect, mousePos ))
2180 nHitTestType = HTHSCROLL;
2182 /* Windows sends this message when a scrollbar is clicked
2185 if(nHitTestType != 0)
2187 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2188 MAKELONG(screenMousePos.x, screenMousePos.y));
2190 /* Resume the Capture after scrolling is complete
2192 if(hWndOldCapture != 0)
2193 SetCapture(hWndOldCapture);
2196 return 0;
2199 /***********************************************************************
2200 * LISTBOX_HandleLButtonUp
2202 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2204 if (LISTBOX_Timer != LB_TIMER_NONE)
2205 KillSystemTimer( descr->self, LB_TIMER_ID );
2206 LISTBOX_Timer = LB_TIMER_NONE;
2207 if (descr->captured)
2209 descr->captured = FALSE;
2210 if (GetCapture() == descr->self) ReleaseCapture();
2211 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2212 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2214 return 0;
2218 /***********************************************************************
2219 * LISTBOX_HandleTimer
2221 * Handle scrolling upon a timer event.
2222 * Return TRUE if scrolling should continue.
2224 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2226 switch(dir)
2228 case LB_TIMER_UP:
2229 if (descr->top_item) index = descr->top_item - 1;
2230 else index = 0;
2231 break;
2232 case LB_TIMER_LEFT:
2233 if (descr->top_item) index -= descr->page_size;
2234 break;
2235 case LB_TIMER_DOWN:
2236 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2237 if (index == descr->focus_item) index++;
2238 if (index >= descr->nb_items) index = descr->nb_items - 1;
2239 break;
2240 case LB_TIMER_RIGHT:
2241 if (index + descr->page_size < descr->nb_items)
2242 index += descr->page_size;
2243 break;
2244 case LB_TIMER_NONE:
2245 break;
2247 if (index == descr->focus_item) return FALSE;
2248 LISTBOX_MoveCaret( descr, index, FALSE );
2249 return TRUE;
2253 /***********************************************************************
2254 * LISTBOX_HandleSystemTimer
2256 * WM_SYSTIMER handler.
2258 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2260 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2262 KillSystemTimer( descr->self, LB_TIMER_ID );
2263 LISTBOX_Timer = LB_TIMER_NONE;
2265 return 0;
2269 /***********************************************************************
2270 * LISTBOX_HandleMouseMove
2272 * WM_MOUSEMOVE handler.
2274 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2275 INT x, INT y )
2277 INT index;
2278 TIMER_DIRECTION dir = LB_TIMER_NONE;
2280 if (!descr->captured) return;
2282 if (descr->style & LBS_MULTICOLUMN)
2284 if (y < 0) y = 0;
2285 else if (y >= descr->item_height * descr->page_size)
2286 y = descr->item_height * descr->page_size - 1;
2288 if (x < 0)
2290 dir = LB_TIMER_LEFT;
2291 x = 0;
2293 else if (x >= descr->width)
2295 dir = LB_TIMER_RIGHT;
2296 x = descr->width - 1;
2299 else
2301 if (y < 0) dir = LB_TIMER_UP; /* above */
2302 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2305 index = LISTBOX_GetItemFromPoint( descr, x, y );
2306 if (index == -1) index = descr->focus_item;
2307 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2309 /* Start/stop the system timer */
2311 if (dir != LB_TIMER_NONE)
2312 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2313 else if (LISTBOX_Timer != LB_TIMER_NONE)
2314 KillSystemTimer( descr->self, LB_TIMER_ID );
2315 LISTBOX_Timer = dir;
2319 /***********************************************************************
2320 * LISTBOX_HandleKeyDown
2322 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2324 INT caret = -1;
2325 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2326 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2327 bForceSelection = FALSE; /* only for single select list */
2329 if (descr->style & LBS_WANTKEYBOARDINPUT)
2331 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2332 MAKEWPARAM(LOWORD(key), descr->focus_item),
2333 (LPARAM)descr->self );
2334 if (caret == -2) return 0;
2336 if (caret == -1) switch(key)
2338 case VK_LEFT:
2339 if (descr->style & LBS_MULTICOLUMN)
2341 bForceSelection = FALSE;
2342 if (descr->focus_item >= descr->page_size)
2343 caret = descr->focus_item - descr->page_size;
2344 break;
2346 /* fall through */
2347 case VK_UP:
2348 caret = descr->focus_item - 1;
2349 if (caret < 0) caret = 0;
2350 break;
2351 case VK_RIGHT:
2352 if (descr->style & LBS_MULTICOLUMN)
2354 bForceSelection = FALSE;
2355 if (descr->focus_item + descr->page_size < descr->nb_items)
2356 caret = descr->focus_item + descr->page_size;
2357 break;
2359 /* fall through */
2360 case VK_DOWN:
2361 caret = descr->focus_item + 1;
2362 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2363 break;
2365 case VK_PRIOR:
2366 if (descr->style & LBS_MULTICOLUMN)
2368 INT page = descr->width / descr->column_width;
2369 if (page < 1) page = 1;
2370 caret = descr->focus_item - (page * descr->page_size) + 1;
2372 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2373 if (caret < 0) caret = 0;
2374 break;
2375 case VK_NEXT:
2376 if (descr->style & LBS_MULTICOLUMN)
2378 INT page = descr->width / descr->column_width;
2379 if (page < 1) page = 1;
2380 caret = descr->focus_item + (page * descr->page_size) - 1;
2382 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2383 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2384 break;
2385 case VK_HOME:
2386 caret = 0;
2387 break;
2388 case VK_END:
2389 caret = descr->nb_items - 1;
2390 break;
2391 case VK_SPACE:
2392 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2393 else if (descr->style & LBS_MULTIPLESEL)
2395 LISTBOX_SetSelection( descr, descr->focus_item,
2396 !descr->items[descr->focus_item].selected,
2397 (descr->style & LBS_NOTIFY) != 0 );
2399 break;
2400 default:
2401 bForceSelection = FALSE;
2403 if (bForceSelection) /* focused item is used instead of key */
2404 caret = descr->focus_item;
2405 if (caret >= 0)
2407 if (((descr->style & LBS_EXTENDEDSEL) &&
2408 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2409 !IS_MULTISELECT(descr))
2410 descr->anchor_item = caret;
2411 LISTBOX_MoveCaret( descr, caret, TRUE );
2413 if (descr->style & LBS_MULTIPLESEL)
2414 descr->selected_item = caret;
2415 else
2416 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2417 if (descr->style & LBS_NOTIFY)
2419 if (descr->lphc && IsWindowVisible( descr->self ))
2421 /* make sure that combo parent doesn't hide us */
2422 descr->lphc->wState |= CBF_NOROLLUP;
2424 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2427 return 0;
2431 /***********************************************************************
2432 * LISTBOX_HandleChar
2434 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2436 INT caret = -1;
2437 WCHAR str[2];
2439 str[0] = charW;
2440 str[1] = '\0';
2442 if (descr->style & LBS_WANTKEYBOARDINPUT)
2444 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2445 MAKEWPARAM(charW, descr->focus_item),
2446 (LPARAM)descr->self );
2447 if (caret == -2) return 0;
2449 if (caret == -1)
2450 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2451 if (caret != -1)
2453 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2454 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2455 LISTBOX_MoveCaret( descr, caret, TRUE );
2456 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2457 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2459 return 0;
2463 /***********************************************************************
2464 * LISTBOX_Create
2466 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2468 LB_DESCR *descr;
2469 MEASUREITEMSTRUCT mis;
2470 RECT rect;
2472 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2473 return FALSE;
2475 GetClientRect( hwnd, &rect );
2476 descr->self = hwnd;
2477 descr->owner = GetParent( descr->self );
2478 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2479 descr->width = rect.right - rect.left;
2480 descr->height = rect.bottom - rect.top;
2481 descr->items = NULL;
2482 descr->nb_items = 0;
2483 descr->top_item = 0;
2484 descr->selected_item = -1;
2485 descr->focus_item = 0;
2486 descr->anchor_item = -1;
2487 descr->item_height = 1;
2488 descr->page_size = 1;
2489 descr->column_width = 150;
2490 descr->horz_extent = 0;
2491 descr->horz_pos = 0;
2492 descr->nb_tabs = 0;
2493 descr->tabs = NULL;
2494 descr->wheel_remain = 0;
2495 descr->caret_on = !lphc;
2496 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2497 descr->in_focus = FALSE;
2498 descr->captured = FALSE;
2499 descr->font = 0;
2500 descr->locale = GetUserDefaultLCID();
2501 descr->lphc = lphc;
2503 if( lphc )
2505 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2506 descr->owner = lphc->self;
2509 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2511 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2513 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2514 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2515 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2516 descr->item_height = LISTBOX_SetFont( descr, 0 );
2518 if (descr->style & LBS_OWNERDRAWFIXED)
2520 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2522 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2523 descr->item_height = lphc->fixedOwnerDrawHeight;
2525 else
2527 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2528 mis.CtlType = ODT_LISTBOX;
2529 mis.CtlID = id;
2530 mis.itemID = -1;
2531 mis.itemWidth = 0;
2532 mis.itemData = 0;
2533 mis.itemHeight = descr->item_height;
2534 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2535 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2539 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2540 return TRUE;
2544 /***********************************************************************
2545 * LISTBOX_Destroy
2547 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2549 LISTBOX_ResetContent( descr );
2550 SetWindowLongPtrW( descr->self, 0, 0 );
2551 HeapFree( GetProcessHeap(), 0, descr );
2552 return TRUE;
2556 /***********************************************************************
2557 * ListBoxWndProc_common
2559 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2561 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2562 LPHEADCOMBO lphc = 0;
2563 LRESULT ret;
2565 if (!descr)
2567 if (!IsWindow(hwnd)) return 0;
2569 if (msg == WM_CREATE)
2571 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2572 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2573 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2574 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2575 return 0;
2577 /* Ignore all other messages before we get a WM_CREATE */
2578 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2579 DefWindowProcA( hwnd, msg, wParam, lParam );
2581 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2583 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2584 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2586 switch(msg)
2588 case LB_RESETCONTENT:
2589 LISTBOX_ResetContent( descr );
2590 LISTBOX_UpdateScroll( descr );
2591 InvalidateRect( descr->self, NULL, TRUE );
2592 return 0;
2594 case LB_ADDSTRING:
2596 INT ret;
2597 LPWSTR textW;
2598 if(unicode || !HAS_STRINGS(descr))
2599 textW = (LPWSTR)lParam;
2600 else
2602 LPSTR textA = (LPSTR)lParam;
2603 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2604 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2605 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2606 else
2607 return LB_ERRSPACE;
2609 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2610 ret = LISTBOX_InsertString( descr, wParam, textW );
2611 if (!unicode && HAS_STRINGS(descr))
2612 HeapFree(GetProcessHeap(), 0, textW);
2613 return ret;
2616 case LB_INSERTSTRING:
2618 INT ret;
2619 LPWSTR textW;
2620 if(unicode || !HAS_STRINGS(descr))
2621 textW = (LPWSTR)lParam;
2622 else
2624 LPSTR textA = (LPSTR)lParam;
2625 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2626 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2627 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2628 else
2629 return LB_ERRSPACE;
2631 ret = LISTBOX_InsertString( descr, wParam, textW );
2632 if(!unicode && HAS_STRINGS(descr))
2633 HeapFree(GetProcessHeap(), 0, textW);
2634 return ret;
2637 case LB_ADDFILE:
2639 INT ret;
2640 LPWSTR textW;
2641 if(unicode || !HAS_STRINGS(descr))
2642 textW = (LPWSTR)lParam;
2643 else
2645 LPSTR textA = (LPSTR)lParam;
2646 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2647 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2648 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2649 else
2650 return LB_ERRSPACE;
2652 wParam = LISTBOX_FindFileStrPos( descr, textW );
2653 ret = LISTBOX_InsertString( descr, wParam, textW );
2654 if(!unicode && HAS_STRINGS(descr))
2655 HeapFree(GetProcessHeap(), 0, textW);
2656 return ret;
2659 case LB_DELETESTRING:
2660 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2661 return descr->nb_items;
2662 else
2664 SetLastError(ERROR_INVALID_INDEX);
2665 return LB_ERR;
2668 case LB_GETITEMDATA:
2669 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2671 SetLastError(ERROR_INVALID_INDEX);
2672 return LB_ERR;
2674 return descr->items[wParam].data;
2676 case LB_SETITEMDATA:
2677 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2679 SetLastError(ERROR_INVALID_INDEX);
2680 return LB_ERR;
2682 descr->items[wParam].data = lParam;
2683 /* undocumented: returns TRUE, not LB_OKAY (0) */
2684 return TRUE;
2686 case LB_GETCOUNT:
2687 return descr->nb_items;
2689 case LB_GETTEXT:
2690 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2692 case LB_GETTEXTLEN:
2693 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2695 SetLastError(ERROR_INVALID_INDEX);
2696 return LB_ERR;
2698 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2699 if (unicode) return strlenW( descr->items[wParam].str );
2700 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2701 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2703 case LB_GETCURSEL:
2704 if (descr->nb_items == 0)
2705 return LB_ERR;
2706 if (!IS_MULTISELECT(descr))
2707 return descr->selected_item;
2708 if (descr->selected_item != -1)
2709 return descr->selected_item;
2710 return descr->focus_item;
2711 /* otherwise, if the user tries to move the selection with the */
2712 /* arrow keys, we will give the application something to choke on */
2713 case LB_GETTOPINDEX:
2714 return descr->top_item;
2716 case LB_GETITEMHEIGHT:
2717 return LISTBOX_GetItemHeight( descr, wParam );
2719 case LB_SETITEMHEIGHT:
2720 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2722 case LB_ITEMFROMPOINT:
2724 POINT pt;
2725 RECT rect;
2726 int index;
2727 BOOL hit = TRUE;
2729 /* The hiword of the return value is not a client area
2730 hittest as suggested by MSDN, but rather a hittest on
2731 the returned listbox item. */
2733 if(descr->nb_items == 0)
2734 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2736 pt.x = (short)LOWORD(lParam);
2737 pt.y = (short)HIWORD(lParam);
2739 SetRect(&rect, 0, 0, descr->width, descr->height);
2741 if(!PtInRect(&rect, pt))
2743 pt.x = min(pt.x, rect.right - 1);
2744 pt.x = max(pt.x, 0);
2745 pt.y = min(pt.y, rect.bottom - 1);
2746 pt.y = max(pt.y, 0);
2747 hit = FALSE;
2750 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2752 if(index == -1)
2754 index = descr->nb_items - 1;
2755 hit = FALSE;
2757 return MAKELONG(index, hit ? 0 : 1);
2760 case LB_SETCARETINDEX:
2761 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2762 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2763 return LB_ERR;
2764 else if (ISWIN31)
2765 return wParam;
2766 else
2767 return LB_OKAY;
2769 case LB_GETCARETINDEX:
2770 return descr->focus_item;
2772 case LB_SETTOPINDEX:
2773 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2775 case LB_SETCOLUMNWIDTH:
2776 return LISTBOX_SetColumnWidth( descr, wParam );
2778 case LB_GETITEMRECT:
2779 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2781 case LB_FINDSTRING:
2783 INT ret;
2784 LPWSTR textW;
2785 if(unicode || !HAS_STRINGS(descr))
2786 textW = (LPWSTR)lParam;
2787 else
2789 LPSTR textA = (LPSTR)lParam;
2790 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2791 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2792 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2794 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2795 if(!unicode && HAS_STRINGS(descr))
2796 HeapFree(GetProcessHeap(), 0, textW);
2797 return ret;
2800 case LB_FINDSTRINGEXACT:
2802 INT ret;
2803 LPWSTR textW;
2804 if(unicode || !HAS_STRINGS(descr))
2805 textW = (LPWSTR)lParam;
2806 else
2808 LPSTR textA = (LPSTR)lParam;
2809 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2810 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2811 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2813 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2814 if(!unicode && HAS_STRINGS(descr))
2815 HeapFree(GetProcessHeap(), 0, textW);
2816 return ret;
2819 case LB_SELECTSTRING:
2821 INT index;
2822 LPWSTR textW;
2824 if(HAS_STRINGS(descr))
2825 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2826 debugstr_a((LPSTR)lParam));
2827 if(unicode || !HAS_STRINGS(descr))
2828 textW = (LPWSTR)lParam;
2829 else
2831 LPSTR textA = (LPSTR)lParam;
2832 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2833 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2834 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2836 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2837 if(!unicode && HAS_STRINGS(descr))
2838 HeapFree(GetProcessHeap(), 0, textW);
2839 if (index != LB_ERR)
2841 LISTBOX_MoveCaret( descr, index, TRUE );
2842 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2844 return index;
2847 case LB_GETSEL:
2848 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2849 return LB_ERR;
2850 return descr->items[wParam].selected;
2852 case LB_SETSEL:
2853 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2855 case LB_SETCURSEL:
2856 if (IS_MULTISELECT(descr)) return LB_ERR;
2857 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2858 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2859 if (ret != LB_ERR) ret = descr->selected_item;
2860 return ret;
2862 case LB_GETSELCOUNT:
2863 return LISTBOX_GetSelCount( descr );
2865 case LB_GETSELITEMS:
2866 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2868 case LB_SELITEMRANGE:
2869 if (LOWORD(lParam) <= HIWORD(lParam))
2870 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2871 HIWORD(lParam), wParam );
2872 else
2873 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2874 LOWORD(lParam), wParam );
2876 case LB_SELITEMRANGEEX:
2877 if ((INT)lParam >= (INT)wParam)
2878 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2879 else
2880 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2882 case LB_GETHORIZONTALEXTENT:
2883 return descr->horz_extent;
2885 case LB_SETHORIZONTALEXTENT:
2886 return LISTBOX_SetHorizontalExtent( descr, wParam );
2888 case LB_GETANCHORINDEX:
2889 return descr->anchor_item;
2891 case LB_SETANCHORINDEX:
2892 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2894 SetLastError(ERROR_INVALID_INDEX);
2895 return LB_ERR;
2897 descr->anchor_item = (INT)wParam;
2898 return LB_OKAY;
2900 case LB_DIR:
2902 INT ret;
2903 LPWSTR textW;
2904 if(unicode)
2905 textW = (LPWSTR)lParam;
2906 else
2908 LPSTR textA = (LPSTR)lParam;
2909 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2910 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2911 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2913 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2914 if(!unicode)
2915 HeapFree(GetProcessHeap(), 0, textW);
2916 return ret;
2919 case LB_GETLOCALE:
2920 return descr->locale;
2922 case LB_SETLOCALE:
2924 LCID ret;
2925 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2926 return LB_ERR;
2927 ret = descr->locale;
2928 descr->locale = (LCID)wParam;
2929 return ret;
2932 case LB_INITSTORAGE:
2933 return LISTBOX_InitStorage( descr, wParam );
2935 case LB_SETCOUNT:
2936 return LISTBOX_SetCount( descr, (INT)wParam );
2938 case LB_SETTABSTOPS:
2939 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2941 case LB_CARETON:
2942 if (descr->caret_on)
2943 return LB_OKAY;
2944 descr->caret_on = TRUE;
2945 if ((descr->focus_item != -1) && (descr->in_focus))
2946 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2947 return LB_OKAY;
2949 case LB_CARETOFF:
2950 if (!descr->caret_on)
2951 return LB_OKAY;
2952 descr->caret_on = FALSE;
2953 if ((descr->focus_item != -1) && (descr->in_focus))
2954 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2955 return LB_OKAY;
2957 case LB_GETLISTBOXINFO:
2958 return descr->page_size;
2960 case WM_DESTROY:
2961 return LISTBOX_Destroy( descr );
2963 case WM_ENABLE:
2964 InvalidateRect( descr->self, NULL, TRUE );
2965 return 0;
2967 case WM_SETREDRAW:
2968 LISTBOX_SetRedraw( descr, wParam != 0 );
2969 return 0;
2971 case WM_GETDLGCODE:
2972 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2974 case WM_PRINTCLIENT:
2975 case WM_PAINT:
2977 PAINTSTRUCT ps;
2978 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2979 ret = LISTBOX_Paint( descr, hdc );
2980 if( !wParam ) EndPaint( descr->self, &ps );
2982 return ret;
2983 case WM_SIZE:
2984 LISTBOX_UpdateSize( descr );
2985 return 0;
2986 case WM_GETFONT:
2987 return (LRESULT)descr->font;
2988 case WM_SETFONT:
2989 LISTBOX_SetFont( descr, (HFONT)wParam );
2990 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2991 return 0;
2992 case WM_SETFOCUS:
2993 descr->in_focus = TRUE;
2994 descr->caret_on = TRUE;
2995 if (descr->focus_item != -1)
2996 LISTBOX_DrawFocusRect( descr, TRUE );
2997 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2998 return 0;
2999 case WM_KILLFOCUS:
3000 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3001 descr->in_focus = FALSE;
3002 descr->wheel_remain = 0;
3003 if ((descr->focus_item != -1) && descr->caret_on)
3004 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3005 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3006 return 0;
3007 case WM_HSCROLL:
3008 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3009 case WM_VSCROLL:
3010 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3011 case WM_MOUSEWHEEL:
3012 if (wParam & (MK_SHIFT | MK_CONTROL))
3013 return DefWindowProcW( descr->self, msg, wParam, lParam );
3014 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3015 case WM_LBUTTONDOWN:
3016 if (lphc)
3017 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3018 (INT16)LOWORD(lParam),
3019 (INT16)HIWORD(lParam) );
3020 return LISTBOX_HandleLButtonDown( descr, wParam,
3021 (INT16)LOWORD(lParam),
3022 (INT16)HIWORD(lParam) );
3023 case WM_LBUTTONDBLCLK:
3024 if (lphc)
3025 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3026 (INT16)LOWORD(lParam),
3027 (INT16)HIWORD(lParam) );
3028 if (descr->style & LBS_NOTIFY)
3029 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3030 return 0;
3031 case WM_MOUSEMOVE:
3032 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3034 BOOL captured = descr->captured;
3035 POINT mousePos;
3036 RECT clientRect;
3038 mousePos.x = (INT16)LOWORD(lParam);
3039 mousePos.y = (INT16)HIWORD(lParam);
3042 * If we are in a dropdown combobox, we simulate that
3043 * the mouse is captured to show the tracking of the item.
3045 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3046 descr->captured = TRUE;
3048 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3050 descr->captured = captured;
3052 else if (GetCapture() == descr->self)
3054 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3055 (INT16)HIWORD(lParam) );
3057 return 0;
3058 case WM_LBUTTONUP:
3059 if (lphc)
3061 POINT mousePos;
3062 RECT clientRect;
3065 * If the mouse button "up" is not in the listbox,
3066 * we make sure there is no selection by re-selecting the
3067 * item that was selected when the listbox was made visible.
3069 mousePos.x = (INT16)LOWORD(lParam);
3070 mousePos.y = (INT16)HIWORD(lParam);
3072 GetClientRect(descr->self, &clientRect);
3075 * When the user clicks outside the combobox and the focus
3076 * is lost, the owning combobox will send a fake buttonup with
3077 * 0xFFFFFFF as the mouse location, we must also revert the
3078 * selection to the original selection.
3080 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3081 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3083 return LISTBOX_HandleLButtonUp( descr );
3084 case WM_KEYDOWN:
3085 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3087 /* for some reason Windows makes it possible to
3088 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3090 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3091 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3092 && (wParam == VK_DOWN || wParam == VK_UP)) )
3094 COMBO_FlipListbox( lphc, FALSE, FALSE );
3095 return 0;
3098 return LISTBOX_HandleKeyDown( descr, wParam );
3099 case WM_CHAR:
3101 WCHAR charW;
3102 if(unicode)
3103 charW = (WCHAR)wParam;
3104 else
3106 CHAR charA = (CHAR)wParam;
3107 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3109 return LISTBOX_HandleChar( descr, charW );
3111 case WM_SYSTIMER:
3112 return LISTBOX_HandleSystemTimer( descr );
3113 case WM_ERASEBKGND:
3114 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3116 RECT rect;
3117 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3118 wParam, (LPARAM)descr->self );
3119 TRACE("hbrush = %p\n", hbrush);
3120 if(!hbrush)
3121 hbrush = GetSysColorBrush(COLOR_WINDOW);
3122 if(hbrush)
3124 GetClientRect(descr->self, &rect);
3125 FillRect((HDC)wParam, &rect, hbrush);
3128 return 1;
3129 case WM_DROPFILES:
3130 if( lphc ) return 0;
3131 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3132 SendMessageA( descr->owner, msg, wParam, lParam );
3134 case WM_NCDESTROY:
3135 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3136 lphc->hWndLBox = 0;
3137 break;
3139 case WM_NCACTIVATE:
3140 if (lphc) return 0;
3141 break;
3143 default:
3144 if ((msg >= WM_USER) && (msg < 0xc000))
3145 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3146 hwnd, msg, wParam, lParam );
3149 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3150 DefWindowProcA( hwnd, msg, wParam, lParam );
3153 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3155 TRACE("%p\n", hwnd);
3156 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);