user32: Remove confusing comments.
[wine.git] / dlls / user32 / listbox.c
blobf6b936ab9072abdc75a344e0556d55b43a7d07de
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 * TODO:
21 * - LBS_NODATA
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "wine/unicode.h"
32 #include "user_private.h"
33 #include "controls.h"
34 #include "wine/exception.h"
35 #include "wine/debug.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
39 /* Items array granularity */
40 #define LB_ARRAY_GRANULARITY 16
42 /* Scrolling timeout in ms */
43 #define LB_SCROLL_TIMEOUT 50
45 /* Listbox system timer id */
46 #define LB_TIMER_ID 2
48 /* flag listbox changed while setredraw false - internal style */
49 #define LBS_DISPLAYCHANGED 0x80000000
51 /* Item structure */
52 typedef struct
54 LPWSTR str; /* Item text */
55 BOOL selected; /* Is item selected? */
56 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
57 ULONG_PTR data; /* User data */
58 } LB_ITEMDATA;
60 /* Listbox structure */
61 typedef struct
63 HWND self; /* Our own window handle */
64 HWND owner; /* Owner window to send notifications to */
65 UINT style; /* Window style */
66 INT width; /* Window width */
67 INT height; /* Window height */
68 LB_ITEMDATA *items; /* Array of items */
69 INT nb_items; /* Number of items */
70 INT top_item; /* Top visible item */
71 INT selected_item; /* Selected item */
72 INT focus_item; /* Item that has the focus */
73 INT anchor_item; /* Anchor item for extended selection */
74 INT item_height; /* Default item height */
75 INT page_size; /* Items per listbox page */
76 INT column_width; /* Column width for multi-column listboxes */
77 INT horz_extent; /* Horizontal extent */
78 INT horz_pos; /* Horizontal position */
79 INT nb_tabs; /* Number of tabs in array */
80 INT *tabs; /* Array of tabs */
81 INT avg_char_width; /* Average width of characters */
82 INT wheel_remain; /* Left over scroll amount */
83 BOOL caret_on; /* Is caret on? */
84 BOOL captured; /* Is mouse captured? */
85 BOOL in_focus;
86 HFONT font; /* Current font */
87 LCID locale; /* Current locale for string comparisons */
88 LPHEADCOMBO lphc; /* ComboLBox */
89 } LB_DESCR;
92 #define IS_OWNERDRAW(descr) \
93 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
95 #define HAS_STRINGS(descr) \
96 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
99 #define IS_MULTISELECT(descr) \
100 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
101 !((descr)->style & LBS_NOSEL))
103 #define SEND_NOTIFICATION(descr,code) \
104 (SendMessageW( (descr)->owner, WM_COMMAND, \
105 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
107 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
109 /* Current timer status */
110 typedef enum
112 LB_TIMER_NONE,
113 LB_TIMER_UP,
114 LB_TIMER_LEFT,
115 LB_TIMER_DOWN,
116 LB_TIMER_RIGHT
117 } TIMER_DIRECTION;
119 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
121 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
123 /*********************************************************************
124 * listbox class descriptor
126 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
127 const struct builtin_class_descr LISTBOX_builtin_class =
129 listboxW, /* name */
130 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
131 WINPROC_LISTBOX, /* proc */
132 sizeof(LB_DESCR *), /* extra */
133 IDC_ARROW, /* cursor */
134 0 /* brush */
138 /*********************************************************************
139 * combolbox class descriptor
141 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
142 const struct builtin_class_descr COMBOLBOX_builtin_class =
144 combolboxW, /* name */
145 CS_DBLCLKS | CS_SAVEBITS, /* style */
146 WINPROC_LISTBOX, /* proc */
147 sizeof(LB_DESCR *), /* extra */
148 IDC_ARROW, /* cursor */
149 0 /* brush */
153 /***********************************************************************
154 * LISTBOX_GetCurrentPageSize
156 * Return the current page size
158 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
160 INT i, height;
161 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
162 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
164 if ((height += descr->items[i].height) > descr->height) break;
166 if (i == descr->top_item) return 1;
167 else return i - descr->top_item;
171 /***********************************************************************
172 * LISTBOX_GetMaxTopIndex
174 * Return the maximum possible index for the top of the listbox.
176 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
178 INT max, page;
180 if (descr->style & LBS_OWNERDRAWVARIABLE)
182 page = descr->height;
183 for (max = descr->nb_items - 1; max >= 0; max--)
184 if ((page -= descr->items[max].height) < 0) break;
185 if (max < descr->nb_items - 1) max++;
187 else if (descr->style & LBS_MULTICOLUMN)
189 if ((page = descr->width / descr->column_width) < 1) page = 1;
190 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
191 max = (max - page) * descr->page_size;
193 else
195 max = descr->nb_items - descr->page_size;
197 if (max < 0) max = 0;
198 return max;
202 /***********************************************************************
203 * LISTBOX_UpdateScroll
205 * Update the scrollbars. Should be called whenever the content
206 * of the listbox changes.
208 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
210 SCROLLINFO info;
212 /* Check the listbox scroll bar flags individually before we call
213 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
214 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
215 scroll bar when we do not need one.
216 if (!(descr->style & WS_VSCROLL)) return;
219 /* It is important that we check descr->style, and not wnd->dwStyle,
220 for WS_VSCROLL, as the former is exactly the one passed in
221 argument to CreateWindow.
222 In Windows (and from now on in Wine :) a listbox created
223 with such a style (no WS_SCROLL) does not update
224 the scrollbar with listbox-related data, thus letting
225 the programmer use it for his/her own purposes. */
227 if (descr->style & LBS_NOREDRAW) return;
228 info.cbSize = sizeof(info);
230 if (descr->style & LBS_MULTICOLUMN)
232 info.nMin = 0;
233 info.nMax = (descr->nb_items - 1) / descr->page_size;
234 info.nPos = descr->top_item / descr->page_size;
235 info.nPage = descr->width / descr->column_width;
236 if (info.nPage < 1) info.nPage = 1;
237 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
238 if (descr->style & LBS_DISABLENOSCROLL)
239 info.fMask |= SIF_DISABLENOSCROLL;
240 if (descr->style & WS_HSCROLL)
241 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
242 info.nMax = 0;
243 info.fMask = SIF_RANGE;
244 if (descr->style & WS_VSCROLL)
245 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
247 else
249 info.nMin = 0;
250 info.nMax = descr->nb_items - 1;
251 info.nPos = descr->top_item;
252 info.nPage = LISTBOX_GetCurrentPageSize( descr );
253 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
254 if (descr->style & LBS_DISABLENOSCROLL)
255 info.fMask |= SIF_DISABLENOSCROLL;
256 if (descr->style & WS_VSCROLL)
257 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
259 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
261 info.nPos = descr->horz_pos;
262 info.nPage = descr->width;
263 info.fMask = SIF_POS | SIF_PAGE;
264 if (descr->style & LBS_DISABLENOSCROLL)
265 info.fMask |= SIF_DISABLENOSCROLL;
266 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
268 else
270 if (descr->style & LBS_DISABLENOSCROLL)
272 info.nMin = 0;
273 info.nMax = 0;
274 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
275 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
277 else
279 ShowScrollBar( descr->self, SB_HORZ, FALSE );
286 /***********************************************************************
287 * LISTBOX_SetTopItem
289 * Set the top item of the listbox, scrolling up or down if necessary.
291 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
293 INT max = LISTBOX_GetMaxTopIndex( descr );
295 TRACE("setting top item %d, scroll %d\n", index, scroll);
297 if (index > max) index = max;
298 if (index < 0) index = 0;
299 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
300 if (descr->top_item == index) return LB_OKAY;
301 if (scroll)
303 INT diff;
304 if (descr->style & LBS_MULTICOLUMN)
305 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
306 else if (descr->style & LBS_OWNERDRAWVARIABLE)
308 INT i;
309 diff = 0;
310 if (index > descr->top_item)
312 for (i = index - 1; i >= descr->top_item; i--)
313 diff -= descr->items[i].height;
315 else
317 for (i = index; i < descr->top_item; i++)
318 diff += descr->items[i].height;
321 else
322 diff = (descr->top_item - index) * descr->item_height;
324 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
325 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
327 else
328 InvalidateRect( descr->self, NULL, TRUE );
329 descr->top_item = index;
330 LISTBOX_UpdateScroll( descr );
331 return LB_OKAY;
335 /***********************************************************************
336 * LISTBOX_UpdatePage
338 * Update the page size. Should be called when the size of
339 * the client area or the item height changes.
341 static void LISTBOX_UpdatePage( LB_DESCR *descr )
343 INT page_size;
345 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
346 page_size = 1;
347 if (page_size == descr->page_size) return;
348 descr->page_size = page_size;
349 if (descr->style & LBS_MULTICOLUMN)
350 InvalidateRect( descr->self, NULL, TRUE );
351 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
355 /***********************************************************************
356 * LISTBOX_UpdateSize
358 * Update the size of the listbox. Should be called when the size of
359 * the client area changes.
361 static void LISTBOX_UpdateSize( LB_DESCR *descr )
363 RECT rect;
365 GetClientRect( descr->self, &rect );
366 descr->width = rect.right - rect.left;
367 descr->height = rect.bottom - rect.top;
368 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
370 INT remaining;
371 RECT rect;
373 GetWindowRect( descr->self, &rect );
374 if(descr->item_height != 0)
375 remaining = descr->height % descr->item_height;
376 else
377 remaining = 0;
378 if ((descr->height > descr->item_height) && remaining)
380 TRACE("[%p]: changing height %d -> %d\n",
381 descr->self, descr->height, descr->height - remaining );
382 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
383 rect.bottom - rect.top - remaining,
384 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
385 return;
388 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
389 LISTBOX_UpdatePage( descr );
390 LISTBOX_UpdateScroll( descr );
392 /* Invalidate the focused item so it will be repainted correctly */
393 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
395 InvalidateRect( descr->self, &rect, FALSE );
400 /***********************************************************************
401 * LISTBOX_GetItemRect
403 * Get the rectangle enclosing an item, in listbox client coordinates.
404 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
406 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
408 /* Index <= 0 is legal even on empty listboxes */
409 if (index && (index >= descr->nb_items))
411 SetRectEmpty(rect);
412 SetLastError(ERROR_INVALID_INDEX);
413 return LB_ERR;
415 SetRect( rect, 0, 0, descr->width, descr->height );
416 if (descr->style & LBS_MULTICOLUMN)
418 INT col = (index / descr->page_size) -
419 (descr->top_item / descr->page_size);
420 rect->left += col * descr->column_width;
421 rect->right = rect->left + descr->column_width;
422 rect->top += (index % descr->page_size) * descr->item_height;
423 rect->bottom = rect->top + descr->item_height;
425 else if (descr->style & LBS_OWNERDRAWVARIABLE)
427 INT i;
428 rect->right += descr->horz_pos;
429 if ((index >= 0) && (index < descr->nb_items))
431 if (index < descr->top_item)
433 for (i = descr->top_item-1; i >= index; i--)
434 rect->top -= descr->items[i].height;
436 else
438 for (i = descr->top_item; i < index; i++)
439 rect->top += descr->items[i].height;
441 rect->bottom = rect->top + descr->items[index].height;
445 else
447 rect->top += (index - descr->top_item) * descr->item_height;
448 rect->bottom = rect->top + descr->item_height;
449 rect->right += descr->horz_pos;
452 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
454 return ((rect->left < descr->width) && (rect->right > 0) &&
455 (rect->top < descr->height) && (rect->bottom > 0));
459 /***********************************************************************
460 * LISTBOX_GetItemFromPoint
462 * Return the item nearest from point (x,y) (in client coordinates).
464 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
466 INT index = descr->top_item;
468 if (!descr->nb_items) return -1; /* No items */
469 if (descr->style & LBS_OWNERDRAWVARIABLE)
471 INT pos = 0;
472 if (y >= 0)
474 while (index < descr->nb_items)
476 if ((pos += descr->items[index].height) > y) break;
477 index++;
480 else
482 while (index > 0)
484 index--;
485 if ((pos -= descr->items[index].height) <= y) break;
489 else if (descr->style & LBS_MULTICOLUMN)
491 if (y >= descr->item_height * descr->page_size) return -1;
492 if (y >= 0) index += y / descr->item_height;
493 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
494 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
496 else
498 index += (y / descr->item_height);
500 if (index < 0) return 0;
501 if (index >= descr->nb_items) return -1;
502 return index;
506 /***********************************************************************
507 * LISTBOX_PaintItem
509 * Paint an item.
511 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
512 INT index, UINT action, BOOL ignoreFocus )
514 LB_ITEMDATA *item = NULL;
515 if (index < descr->nb_items) item = &descr->items[index];
517 if (IS_OWNERDRAW(descr))
519 DRAWITEMSTRUCT dis;
520 RECT r;
521 HRGN hrgn;
523 if (!item)
525 if (action == ODA_FOCUS)
526 DrawFocusRect( hdc, rect );
527 else
528 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
529 return;
532 /* some programs mess with the clipping region when
533 drawing the item, *and* restore the previous region
534 after they are done, so a region has better to exist
535 else everything ends clipped */
536 GetClientRect(descr->self, &r);
537 hrgn = set_control_clipping( hdc, &r );
539 dis.CtlType = ODT_LISTBOX;
540 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
541 dis.hwndItem = descr->self;
542 dis.itemAction = action;
543 dis.hDC = hdc;
544 dis.itemID = index;
545 dis.itemState = 0;
546 if (item->selected) dis.itemState |= ODS_SELECTED;
547 if (!ignoreFocus && (descr->focus_item == index) &&
548 (descr->caret_on) &&
549 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
550 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
551 dis.itemData = item->data;
552 dis.rcItem = *rect;
553 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
554 descr->self, index, debugstr_w(item->str), action,
555 dis.itemState, wine_dbgstr_rect(rect) );
556 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
557 SelectClipRgn( hdc, hrgn );
558 if (hrgn) DeleteObject( hrgn );
560 else
562 COLORREF oldText = 0, oldBk = 0;
564 if (action == ODA_FOCUS)
566 DrawFocusRect( hdc, rect );
567 return;
569 if (item && item->selected)
571 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
572 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
575 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
576 descr->self, index, item ? debugstr_w(item->str) : "", action,
577 wine_dbgstr_rect(rect) );
578 if (!item)
579 ExtTextOutW( hdc, rect->left + 1, rect->top,
580 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
581 else if (!(descr->style & LBS_USETABSTOPS))
582 ExtTextOutW( hdc, rect->left + 1, rect->top,
583 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
584 strlenW(item->str), NULL );
585 else
587 /* Output empty string to paint background in the full width. */
588 ExtTextOutW( hdc, rect->left + 1, rect->top,
589 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
590 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
591 item->str, strlenW(item->str),
592 descr->nb_tabs, descr->tabs, 0);
594 if (item && item->selected)
596 SetBkColor( hdc, oldBk );
597 SetTextColor( hdc, oldText );
599 if (!ignoreFocus && (descr->focus_item == index) &&
600 (descr->caret_on) &&
601 (descr->in_focus)) DrawFocusRect( hdc, rect );
606 /***********************************************************************
607 * LISTBOX_SetRedraw
609 * Change the redraw flag.
611 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
613 if (on)
615 if (!(descr->style & LBS_NOREDRAW)) return;
616 descr->style &= ~LBS_NOREDRAW;
617 if (descr->style & LBS_DISPLAYCHANGED)
618 { /* page was changed while setredraw false, refresh automatically */
619 InvalidateRect(descr->self, NULL, TRUE);
620 if ((descr->top_item + descr->page_size) > descr->nb_items)
621 { /* reset top of page if less than number of items/page */
622 descr->top_item = descr->nb_items - descr->page_size;
623 if (descr->top_item < 0) descr->top_item = 0;
625 descr->style &= ~LBS_DISPLAYCHANGED;
627 LISTBOX_UpdateScroll( descr );
629 else descr->style |= LBS_NOREDRAW;
633 /***********************************************************************
634 * LISTBOX_RepaintItem
636 * Repaint a single item synchronously.
638 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
640 HDC hdc;
641 RECT rect;
642 HFONT oldFont = 0;
643 HBRUSH hbrush, oldBrush = 0;
645 /* Do not repaint the item if the item is not visible */
646 if (!IsWindowVisible(descr->self)) return;
647 if (descr->style & LBS_NOREDRAW)
649 descr->style |= LBS_DISPLAYCHANGED;
650 return;
652 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
653 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
654 if (descr->font) oldFont = SelectObject( hdc, descr->font );
655 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
656 (WPARAM)hdc, (LPARAM)descr->self );
657 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
658 if (!IsWindowEnabled(descr->self))
659 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
660 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
661 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
662 if (oldFont) SelectObject( hdc, oldFont );
663 if (oldBrush) SelectObject( hdc, oldBrush );
664 ReleaseDC( descr->self, hdc );
668 /***********************************************************************
669 * LISTBOX_DrawFocusRect
671 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
673 HDC hdc;
674 RECT rect;
675 HFONT oldFont = 0;
677 /* Do not repaint the item if the item is not visible */
678 if (!IsWindowVisible(descr->self)) return;
680 if (descr->focus_item == -1) return;
681 if (!descr->caret_on || !descr->in_focus) return;
683 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
684 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
685 if (descr->font) oldFont = SelectObject( hdc, descr->font );
686 if (!IsWindowEnabled(descr->self))
687 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
688 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
689 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
690 if (oldFont) SelectObject( hdc, oldFont );
691 ReleaseDC( descr->self, hdc );
695 /***********************************************************************
696 * LISTBOX_InitStorage
698 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
700 LB_ITEMDATA *item;
702 nb_items += LB_ARRAY_GRANULARITY - 1;
703 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
704 if (descr->items) {
705 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
706 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
707 nb_items * sizeof(LB_ITEMDATA));
709 else {
710 item = HeapAlloc( GetProcessHeap(), 0,
711 nb_items * sizeof(LB_ITEMDATA));
714 if (!item)
716 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
717 return LB_ERRSPACE;
719 descr->items = item;
720 return LB_OKAY;
724 /***********************************************************************
725 * LISTBOX_SetTabStops
727 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
729 INT i;
731 if (!(descr->style & LBS_USETABSTOPS))
733 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
734 return FALSE;
737 HeapFree( GetProcessHeap(), 0, descr->tabs );
738 if (!(descr->nb_tabs = count))
740 descr->tabs = NULL;
741 return TRUE;
743 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
744 descr->nb_tabs * sizeof(INT) )))
745 return FALSE;
746 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
748 /* convert into "dialog units"*/
749 for (i = 0; i < descr->nb_tabs; i++)
750 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
752 return TRUE;
756 /***********************************************************************
757 * LISTBOX_GetText
759 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
761 DWORD len;
763 if ((index < 0) || (index >= descr->nb_items))
765 SetLastError(ERROR_INVALID_INDEX);
766 return LB_ERR;
768 if (HAS_STRINGS(descr))
770 if (!buffer)
772 len = strlenW(descr->items[index].str);
773 if( unicode )
774 return len;
775 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
776 NULL, 0, NULL, NULL );
779 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
781 __TRY /* hide a Delphi bug that passes a read-only buffer */
783 if(unicode)
785 strcpyW( buffer, descr->items[index].str );
786 len = strlenW(buffer);
788 else
790 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
791 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
794 __EXCEPT_PAGE_FAULT
796 WARN( "got an invalid buffer (Delphi bug?)\n" );
797 SetLastError( ERROR_INVALID_PARAMETER );
798 return LB_ERR;
800 __ENDTRY
801 } else {
802 if (buffer)
803 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
804 len = sizeof(DWORD);
806 return len;
809 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
811 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
812 if (ret == CSTR_LESS_THAN)
813 return -1;
814 if (ret == CSTR_EQUAL)
815 return 0;
816 if (ret == CSTR_GREATER_THAN)
817 return 1;
818 return -1;
821 /***********************************************************************
822 * LISTBOX_FindStringPos
824 * Find the nearest string located before a given string in sort order.
825 * If 'exact' is TRUE, return an error if we don't get an exact match.
827 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
829 INT index, min, max, res;
831 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
832 min = 0;
833 max = descr->nb_items;
834 while (min != max)
836 index = (min + max) / 2;
837 if (HAS_STRINGS(descr))
838 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
839 else
841 COMPAREITEMSTRUCT cis;
842 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
844 cis.CtlType = ODT_LISTBOX;
845 cis.CtlID = id;
846 cis.hwndItem = descr->self;
847 /* note that some application (MetaStock) expects the second item
848 * to be in the listbox */
849 cis.itemID1 = -1;
850 cis.itemData1 = (ULONG_PTR)str;
851 cis.itemID2 = index;
852 cis.itemData2 = descr->items[index].data;
853 cis.dwLocaleId = descr->locale;
854 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
856 if (!res) return index;
857 if (res < 0) max = index;
858 else min = index + 1;
860 return exact ? -1 : max;
864 /***********************************************************************
865 * LISTBOX_FindFileStrPos
867 * Find the nearest string located before a given string in directory
868 * sort order (i.e. first files, then directories, then drives).
870 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
872 INT min, max, res;
874 if (!HAS_STRINGS(descr))
875 return LISTBOX_FindStringPos( descr, str, FALSE );
876 min = 0;
877 max = descr->nb_items;
878 while (min != max)
880 INT index = (min + max) / 2;
881 LPCWSTR p = descr->items[index].str;
882 if (*p == '[') /* drive or directory */
884 if (*str != '[') res = -1;
885 else if (p[1] == '-') /* drive */
887 if (str[1] == '-') res = str[2] - p[2];
888 else res = -1;
890 else /* directory */
892 if (str[1] == '-') res = 1;
893 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
896 else /* filename */
898 if (*str == '[') res = 1;
899 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
901 if (!res) return index;
902 if (res < 0) max = index;
903 else min = index + 1;
905 return max;
909 /***********************************************************************
910 * LISTBOX_FindString
912 * Find the item beginning with a given string.
914 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
916 INT i;
917 LB_ITEMDATA *item;
919 if (start >= descr->nb_items) start = -1;
920 item = descr->items + start + 1;
921 if (HAS_STRINGS(descr))
923 if (!str || ! str[0] ) return LB_ERR;
924 if (exact)
926 for (i = start + 1; i < descr->nb_items; i++, item++)
927 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
928 for (i = 0, item = descr->items; i <= start; i++, item++)
929 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
931 else
933 /* Special case for drives and directories: ignore prefix */
934 #define CHECK_DRIVE(item) \
935 if ((item)->str[0] == '[') \
937 if (!strncmpiW( str, (item)->str+1, len )) return i; \
938 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
939 return i; \
942 INT len = strlenW(str);
943 for (i = start + 1; i < descr->nb_items; i++, item++)
945 if (!strncmpiW( str, item->str, len )) return i;
946 CHECK_DRIVE(item);
948 for (i = 0, item = descr->items; i <= start; i++, item++)
950 if (!strncmpiW( str, item->str, len )) return i;
951 CHECK_DRIVE(item);
953 #undef CHECK_DRIVE
956 else
958 if (exact && (descr->style & LBS_SORT))
959 /* If sorted, use a WM_COMPAREITEM binary search */
960 return LISTBOX_FindStringPos( descr, str, TRUE );
962 /* Otherwise use a linear search */
963 for (i = start + 1; i < descr->nb_items; i++, item++)
964 if (item->data == (ULONG_PTR)str) return i;
965 for (i = 0, item = descr->items; i <= start; i++, item++)
966 if (item->data == (ULONG_PTR)str) return i;
968 return LB_ERR;
972 /***********************************************************************
973 * LISTBOX_GetSelCount
975 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
977 INT i, count;
978 const LB_ITEMDATA *item = descr->items;
980 if (!(descr->style & LBS_MULTIPLESEL) ||
981 (descr->style & LBS_NOSEL))
982 return LB_ERR;
983 for (i = count = 0; i < descr->nb_items; i++, item++)
984 if (item->selected) count++;
985 return count;
989 /***********************************************************************
990 * LISTBOX_GetSelItems
992 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
994 INT i, count;
995 const LB_ITEMDATA *item = descr->items;
997 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
998 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
999 if (item->selected) array[count++] = i;
1000 return count;
1004 /***********************************************************************
1005 * LISTBOX_Paint
1007 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1009 INT i, col_pos = descr->page_size - 1;
1010 RECT rect;
1011 RECT focusRect = {-1, -1, -1, -1};
1012 HFONT oldFont = 0;
1013 HBRUSH hbrush, oldBrush = 0;
1015 if (descr->style & LBS_NOREDRAW) return 0;
1017 SetRect( &rect, 0, 0, descr->width, descr->height );
1018 if (descr->style & LBS_MULTICOLUMN)
1019 rect.right = rect.left + descr->column_width;
1020 else if (descr->horz_pos)
1022 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1023 rect.right += descr->horz_pos;
1026 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1027 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1028 (WPARAM)hdc, (LPARAM)descr->self );
1029 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1030 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1032 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1033 (descr->in_focus))
1035 /* Special case for empty listbox: paint focus rect */
1036 rect.bottom = rect.top + descr->item_height;
1037 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1038 &rect, NULL, 0, NULL );
1039 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1040 rect.top = rect.bottom;
1043 /* Paint all the item, regarding the selection
1044 Focus state will be painted after */
1046 for (i = descr->top_item; i < descr->nb_items; i++)
1048 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1049 rect.bottom = rect.top + descr->item_height;
1050 else
1051 rect.bottom = rect.top + descr->items[i].height;
1053 /* keep the focus rect, to paint the focus item after */
1054 if (i == descr->focus_item)
1055 focusRect = rect;
1057 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1058 rect.top = rect.bottom;
1060 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1062 if (!IS_OWNERDRAW(descr))
1064 /* Clear the bottom of the column */
1065 if (rect.top < descr->height)
1067 rect.bottom = descr->height;
1068 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1069 &rect, NULL, 0, NULL );
1073 /* Go to the next column */
1074 rect.left += descr->column_width;
1075 rect.right += descr->column_width;
1076 rect.top = 0;
1077 col_pos = descr->page_size - 1;
1079 else
1081 col_pos--;
1082 if (rect.top >= descr->height) break;
1086 /* Paint the focus item now */
1087 if (focusRect.top != focusRect.bottom &&
1088 descr->caret_on && descr->in_focus)
1089 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1091 if (!IS_OWNERDRAW(descr))
1093 /* Clear the remainder of the client area */
1094 if (rect.top < descr->height)
1096 rect.bottom = descr->height;
1097 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1098 &rect, NULL, 0, NULL );
1100 if (rect.right < descr->width)
1102 rect.left = rect.right;
1103 rect.right = descr->width;
1104 rect.top = 0;
1105 rect.bottom = descr->height;
1106 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1107 &rect, NULL, 0, NULL );
1110 if (oldFont) SelectObject( hdc, oldFont );
1111 if (oldBrush) SelectObject( hdc, oldBrush );
1112 return 0;
1116 /***********************************************************************
1117 * LISTBOX_InvalidateItems
1119 * Invalidate all items from a given item. If the specified item is not
1120 * visible, nothing happens.
1122 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1124 RECT rect;
1126 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1128 if (descr->style & LBS_NOREDRAW)
1130 descr->style |= LBS_DISPLAYCHANGED;
1131 return;
1133 rect.bottom = descr->height;
1134 InvalidateRect( descr->self, &rect, TRUE );
1135 if (descr->style & LBS_MULTICOLUMN)
1137 /* Repaint the other columns */
1138 rect.left = rect.right;
1139 rect.right = descr->width;
1140 rect.top = 0;
1141 InvalidateRect( descr->self, &rect, TRUE );
1146 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1148 RECT rect;
1150 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1151 InvalidateRect( descr->self, &rect, TRUE );
1154 /***********************************************************************
1155 * LISTBOX_GetItemHeight
1157 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1159 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1161 if ((index < 0) || (index >= descr->nb_items))
1163 SetLastError(ERROR_INVALID_INDEX);
1164 return LB_ERR;
1166 return descr->items[index].height;
1168 else return descr->item_height;
1172 /***********************************************************************
1173 * LISTBOX_SetItemHeight
1175 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1177 if (height > MAXBYTE)
1178 return -1;
1180 if (!height) height = 1;
1182 if (descr->style & LBS_OWNERDRAWVARIABLE)
1184 if ((index < 0) || (index >= descr->nb_items))
1186 SetLastError(ERROR_INVALID_INDEX);
1187 return LB_ERR;
1189 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1190 descr->items[index].height = height;
1191 LISTBOX_UpdateScroll( descr );
1192 if (repaint)
1193 LISTBOX_InvalidateItems( descr, index );
1195 else if (height != descr->item_height)
1197 TRACE("[%p]: new height = %d\n", descr->self, height );
1198 descr->item_height = height;
1199 LISTBOX_UpdatePage( descr );
1200 LISTBOX_UpdateScroll( descr );
1201 if (repaint)
1202 InvalidateRect( descr->self, 0, TRUE );
1204 return LB_OKAY;
1208 /***********************************************************************
1209 * LISTBOX_SetHorizontalPos
1211 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1213 INT diff;
1215 if (pos > descr->horz_extent - descr->width)
1216 pos = descr->horz_extent - descr->width;
1217 if (pos < 0) pos = 0;
1218 if (!(diff = descr->horz_pos - pos)) return;
1219 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1220 descr->horz_pos = pos;
1221 LISTBOX_UpdateScroll( descr );
1222 if (abs(diff) < descr->width)
1224 RECT rect;
1225 /* Invalidate the focused item so it will be repainted correctly */
1226 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1227 InvalidateRect( descr->self, &rect, TRUE );
1228 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1229 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1231 else
1232 InvalidateRect( descr->self, NULL, TRUE );
1236 /***********************************************************************
1237 * LISTBOX_SetHorizontalExtent
1239 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1241 if (descr->style & LBS_MULTICOLUMN)
1242 return LB_OKAY;
1243 if (extent == descr->horz_extent) return LB_OKAY;
1244 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1245 descr->horz_extent = extent;
1246 if (descr->style & WS_HSCROLL) {
1247 SCROLLINFO info;
1248 info.cbSize = sizeof(info);
1249 info.nMin = 0;
1250 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1251 info.fMask = SIF_RANGE;
1252 if (descr->style & LBS_DISABLENOSCROLL)
1253 info.fMask |= SIF_DISABLENOSCROLL;
1254 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1256 if (descr->horz_pos > extent - descr->width)
1257 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1258 return LB_OKAY;
1262 /***********************************************************************
1263 * LISTBOX_SetColumnWidth
1265 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1267 if (width == descr->column_width) return LB_OKAY;
1268 TRACE("[%p]: new column width = %d\n", descr->self, width );
1269 descr->column_width = width;
1270 LISTBOX_UpdatePage( descr );
1271 return LB_OKAY;
1275 /***********************************************************************
1276 * LISTBOX_SetFont
1278 * Returns the item height.
1280 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1282 HDC hdc;
1283 HFONT oldFont = 0;
1284 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1285 SIZE sz;
1287 descr->font = font;
1289 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1291 ERR("unable to get DC.\n" );
1292 return 16;
1294 if (font) oldFont = SelectObject( hdc, font );
1295 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1296 if (oldFont) SelectObject( hdc, oldFont );
1297 ReleaseDC( descr->self, hdc );
1299 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1300 if (!IS_OWNERDRAW(descr))
1301 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1302 return sz.cy;
1306 /***********************************************************************
1307 * LISTBOX_MakeItemVisible
1309 * Make sure that a given item is partially or fully visible.
1311 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1313 INT top;
1315 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1317 if (index <= descr->top_item) top = index;
1318 else if (descr->style & LBS_MULTICOLUMN)
1320 INT cols = descr->width;
1321 if (!fully) cols += descr->column_width - 1;
1322 if (cols >= descr->column_width) cols /= descr->column_width;
1323 else cols = 1;
1324 if (index < descr->top_item + (descr->page_size * cols)) return;
1325 top = index - descr->page_size * (cols - 1);
1327 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1329 INT height = fully ? descr->items[index].height : 1;
1330 for (top = index; top > descr->top_item; top--)
1331 if ((height += descr->items[top-1].height) > descr->height) break;
1333 else
1335 if (index < descr->top_item + descr->page_size) return;
1336 if (!fully && (index == descr->top_item + descr->page_size) &&
1337 (descr->height > (descr->page_size * descr->item_height))) return;
1338 top = index - descr->page_size + 1;
1340 LISTBOX_SetTopItem( descr, top, TRUE );
1343 /***********************************************************************
1344 * LISTBOX_SetCaretIndex
1346 * NOTES
1347 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1350 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1352 INT oldfocus = descr->focus_item;
1354 TRACE("old focus %d, index %d\n", oldfocus, index);
1356 if (descr->style & LBS_NOSEL) return LB_ERR;
1357 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1358 if (index == oldfocus) return LB_OKAY;
1360 LISTBOX_DrawFocusRect( descr, FALSE );
1361 descr->focus_item = index;
1363 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1364 LISTBOX_DrawFocusRect( descr, TRUE );
1366 return LB_OKAY;
1370 /***********************************************************************
1371 * LISTBOX_SelectItemRange
1373 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1375 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1376 INT last, BOOL on )
1378 INT i;
1380 /* A few sanity checks */
1382 if (descr->style & LBS_NOSEL) return LB_ERR;
1383 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1385 if (!descr->nb_items) return LB_OKAY;
1387 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1388 if (first < 0) first = 0;
1389 if (last < first) return LB_OKAY;
1391 if (on) /* Turn selection on */
1393 for (i = first; i <= last; i++)
1395 if (descr->items[i].selected) continue;
1396 descr->items[i].selected = TRUE;
1397 LISTBOX_InvalidateItemRect(descr, i);
1400 else /* Turn selection off */
1402 for (i = first; i <= last; i++)
1404 if (!descr->items[i].selected) continue;
1405 descr->items[i].selected = FALSE;
1406 LISTBOX_InvalidateItemRect(descr, i);
1409 return LB_OKAY;
1412 /***********************************************************************
1413 * LISTBOX_SetSelection
1415 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1416 BOOL on, BOOL send_notify )
1418 TRACE( "cur_sel=%d index=%d notify=%s\n",
1419 descr->selected_item, index, send_notify ? "YES" : "NO" );
1421 if (descr->style & LBS_NOSEL)
1423 descr->selected_item = index;
1424 return LB_ERR;
1426 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1427 if (descr->style & LBS_MULTIPLESEL)
1429 if (index == -1) /* Select all items */
1430 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1431 else /* Only one item */
1432 return LISTBOX_SelectItemRange( descr, index, index, on );
1434 else
1436 INT oldsel = descr->selected_item;
1437 if (index == oldsel) return LB_OKAY;
1438 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1439 if (index != -1) descr->items[index].selected = TRUE;
1440 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1441 descr->selected_item = index;
1442 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1443 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1444 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1445 else
1446 if( descr->lphc ) /* set selection change flag for parent combo */
1447 descr->lphc->wState |= CBF_SELCHANGE;
1449 return LB_OKAY;
1453 /***********************************************************************
1454 * LISTBOX_MoveCaret
1456 * Change the caret position and extend the selection to the new caret.
1458 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1460 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1462 if ((index < 0) || (index >= descr->nb_items))
1463 return;
1465 /* Important, repaint needs to be done in this order if
1466 you want to mimic Windows behavior:
1467 1. Remove the focus and paint the item
1468 2. Remove the selection and paint the item(s)
1469 3. Set the selection and repaint the item(s)
1470 4. Set the focus to 'index' and repaint the item */
1472 /* 1. remove the focus and repaint the item */
1473 LISTBOX_DrawFocusRect( descr, FALSE );
1475 /* 2. then turn off the previous selection */
1476 /* 3. repaint the new selected item */
1477 if (descr->style & LBS_EXTENDEDSEL)
1479 if (descr->anchor_item != -1)
1481 INT first = min( index, descr->anchor_item );
1482 INT last = max( index, descr->anchor_item );
1483 if (first > 0)
1484 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1485 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1486 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1489 else if (!(descr->style & LBS_MULTIPLESEL))
1491 /* Set selection to new caret item */
1492 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1495 /* 4. repaint the new item with the focus */
1496 descr->focus_item = index;
1497 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1498 LISTBOX_DrawFocusRect( descr, TRUE );
1502 /***********************************************************************
1503 * LISTBOX_InsertItem
1505 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1506 LPWSTR str, ULONG_PTR data )
1508 LB_ITEMDATA *item;
1509 INT max_items;
1510 INT oldfocus = descr->focus_item;
1512 if (index == -1) index = descr->nb_items;
1513 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1514 if (!descr->items) max_items = 0;
1515 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1516 if (descr->nb_items == max_items)
1518 /* We need to grow the array */
1519 max_items += LB_ARRAY_GRANULARITY;
1520 if (descr->items)
1521 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1522 max_items * sizeof(LB_ITEMDATA) );
1523 else
1524 item = HeapAlloc( GetProcessHeap(), 0,
1525 max_items * sizeof(LB_ITEMDATA) );
1526 if (!item)
1528 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1529 return LB_ERRSPACE;
1531 descr->items = item;
1534 /* Insert the item structure */
1536 item = &descr->items[index];
1537 if (index < descr->nb_items)
1538 RtlMoveMemory( item + 1, item,
1539 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1540 item->str = str;
1541 item->data = data;
1542 item->height = 0;
1543 item->selected = FALSE;
1544 descr->nb_items++;
1546 /* Get item height */
1548 if (descr->style & LBS_OWNERDRAWVARIABLE)
1550 MEASUREITEMSTRUCT mis;
1551 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1553 mis.CtlType = ODT_LISTBOX;
1554 mis.CtlID = id;
1555 mis.itemID = index;
1556 mis.itemData = descr->items[index].data;
1557 mis.itemHeight = descr->item_height;
1558 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1559 item->height = mis.itemHeight ? mis.itemHeight : 1;
1560 TRACE("[%p]: measure item %d (%s) = %d\n",
1561 descr->self, index, str ? debugstr_w(str) : "", item->height );
1564 /* Repaint the items */
1566 LISTBOX_UpdateScroll( descr );
1567 LISTBOX_InvalidateItems( descr, index );
1569 /* Move selection and focused item */
1570 /* If listbox was empty, set focus to the first item */
1571 if (descr->nb_items == 1)
1572 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1573 /* single select don't change selection index in win31 */
1574 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1576 descr->selected_item++;
1577 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1579 else
1581 if (index <= descr->selected_item)
1583 descr->selected_item++;
1584 descr->focus_item = oldfocus; /* focus not changed */
1587 return LB_OKAY;
1591 /***********************************************************************
1592 * LISTBOX_InsertString
1594 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1596 LPWSTR new_str = NULL;
1597 ULONG_PTR data = 0;
1598 LRESULT ret;
1600 if (HAS_STRINGS(descr))
1602 static const WCHAR empty_stringW[] = { 0 };
1603 if (!str) str = empty_stringW;
1604 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1606 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1607 return LB_ERRSPACE;
1609 strcpyW(new_str, str);
1611 else data = (ULONG_PTR)str;
1613 if (index == -1) index = descr->nb_items;
1614 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1616 HeapFree( GetProcessHeap(), 0, new_str );
1617 return ret;
1620 TRACE("[%p]: added item %d %s\n",
1621 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1622 return index;
1626 /***********************************************************************
1627 * LISTBOX_DeleteItem
1629 * Delete the content of an item. 'index' must be a valid index.
1631 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1633 /* save the item data before it gets freed by LB_RESETCONTENT */
1634 ULONG_PTR item_data = descr->items[index].data;
1635 LPWSTR item_str = descr->items[index].str;
1637 if (!descr->nb_items)
1638 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1640 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1641 * while Win95 sends it for all items with user data.
1642 * It's probably better to send it too often than not
1643 * often enough, so this is what we do here.
1645 if (IS_OWNERDRAW(descr) || item_data)
1647 DELETEITEMSTRUCT dis;
1648 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1650 dis.CtlType = ODT_LISTBOX;
1651 dis.CtlID = id;
1652 dis.itemID = index;
1653 dis.hwndItem = descr->self;
1654 dis.itemData = item_data;
1655 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1657 if (HAS_STRINGS(descr))
1658 HeapFree( GetProcessHeap(), 0, item_str );
1662 /***********************************************************************
1663 * LISTBOX_RemoveItem
1665 * Remove an item from the listbox and delete its content.
1667 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1669 LB_ITEMDATA *item;
1670 INT max_items;
1672 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1674 /* We need to invalidate the original rect instead of the updated one. */
1675 LISTBOX_InvalidateItems( descr, index );
1677 descr->nb_items--;
1678 LISTBOX_DeleteItem( descr, index );
1680 if (!descr->nb_items) return LB_OKAY;
1682 /* Remove the item */
1684 item = &descr->items[index];
1685 if (index < descr->nb_items)
1686 RtlMoveMemory( item, item + 1,
1687 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1688 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1690 /* Shrink the item array if possible */
1692 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1693 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1695 max_items -= LB_ARRAY_GRANULARITY;
1696 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1697 max_items * sizeof(LB_ITEMDATA) );
1698 if (item) descr->items = item;
1700 /* Repaint the items */
1702 LISTBOX_UpdateScroll( descr );
1703 /* if we removed the scrollbar, reset the top of the list
1704 (correct for owner-drawn ???) */
1705 if (descr->nb_items == descr->page_size)
1706 LISTBOX_SetTopItem( descr, 0, TRUE );
1708 /* Move selection and focused item */
1709 if (!IS_MULTISELECT(descr))
1711 if (index == descr->selected_item)
1712 descr->selected_item = -1;
1713 else if (index < descr->selected_item)
1715 descr->selected_item--;
1716 if (ISWIN31) /* win 31 do not change the selected item number */
1717 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1721 if (descr->focus_item >= descr->nb_items)
1723 descr->focus_item = descr->nb_items - 1;
1724 if (descr->focus_item < 0) descr->focus_item = 0;
1726 return LB_OKAY;
1730 /***********************************************************************
1731 * LISTBOX_ResetContent
1733 static void LISTBOX_ResetContent( LB_DESCR *descr )
1735 INT i;
1737 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1738 HeapFree( GetProcessHeap(), 0, descr->items );
1739 descr->nb_items = 0;
1740 descr->top_item = 0;
1741 descr->selected_item = -1;
1742 descr->focus_item = 0;
1743 descr->anchor_item = -1;
1744 descr->items = NULL;
1748 /***********************************************************************
1749 * LISTBOX_SetCount
1751 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1753 LRESULT ret;
1755 if (HAS_STRINGS(descr))
1757 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1758 return LB_ERR;
1761 /* FIXME: this is far from optimal... */
1762 if (count > descr->nb_items)
1764 while (count > descr->nb_items)
1765 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1766 return ret;
1768 else if (count < descr->nb_items)
1770 while (count < descr->nb_items)
1771 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1772 return ret;
1775 InvalidateRect( descr->self, NULL, TRUE );
1776 return LB_OKAY;
1780 /***********************************************************************
1781 * LISTBOX_Directory
1783 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1784 LPCWSTR filespec, BOOL long_names )
1786 HANDLE handle;
1787 LRESULT ret = LB_OKAY;
1788 WIN32_FIND_DATAW entry;
1789 int pos;
1790 LRESULT maxinsert = LB_ERR;
1792 /* don't scan directory if we just want drives exclusively */
1793 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1794 /* scan directory */
1795 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1797 int le = GetLastError();
1798 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1800 else
1804 WCHAR buffer[270];
1805 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1807 static const WCHAR bracketW[] = { ']',0 };
1808 static const WCHAR dotW[] = { '.',0 };
1809 if (!(attrib & DDL_DIRECTORY) ||
1810 !strcmpW( entry.cFileName, dotW )) continue;
1811 buffer[0] = '[';
1812 if (!long_names && entry.cAlternateFileName[0])
1813 strcpyW( buffer + 1, entry.cAlternateFileName );
1814 else
1815 strcpyW( buffer + 1, entry.cFileName );
1816 strcatW(buffer, bracketW);
1818 else /* not a directory */
1820 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1821 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1823 if ((attrib & DDL_EXCLUSIVE) &&
1824 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1825 continue;
1826 #undef ATTRIBS
1827 if (!long_names && entry.cAlternateFileName[0])
1828 strcpyW( buffer, entry.cAlternateFileName );
1829 else
1830 strcpyW( buffer, entry.cFileName );
1832 if (!long_names) CharLowerW( buffer );
1833 pos = LISTBOX_FindFileStrPos( descr, buffer );
1834 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1835 break;
1836 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1837 } while (FindNextFileW( handle, &entry ));
1838 FindClose( handle );
1841 if (ret >= 0)
1843 ret = maxinsert;
1845 /* scan drives */
1846 if (attrib & DDL_DRIVES)
1848 WCHAR buffer[] = {'[','-','a','-',']',0};
1849 WCHAR root[] = {'A',':','\\',0};
1850 int drive;
1851 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1853 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1854 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1855 break;
1859 return ret;
1863 /***********************************************************************
1864 * LISTBOX_HandleVScroll
1866 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1868 SCROLLINFO info;
1870 if (descr->style & LBS_MULTICOLUMN) return 0;
1871 switch(scrollReq)
1873 case SB_LINEUP:
1874 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1875 break;
1876 case SB_LINEDOWN:
1877 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1878 break;
1879 case SB_PAGEUP:
1880 LISTBOX_SetTopItem( descr, descr->top_item -
1881 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1882 break;
1883 case SB_PAGEDOWN:
1884 LISTBOX_SetTopItem( descr, descr->top_item +
1885 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1886 break;
1887 case SB_THUMBPOSITION:
1888 LISTBOX_SetTopItem( descr, pos, TRUE );
1889 break;
1890 case SB_THUMBTRACK:
1891 info.cbSize = sizeof(info);
1892 info.fMask = SIF_TRACKPOS;
1893 GetScrollInfo( descr->self, SB_VERT, &info );
1894 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1895 break;
1896 case SB_TOP:
1897 LISTBOX_SetTopItem( descr, 0, TRUE );
1898 break;
1899 case SB_BOTTOM:
1900 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1901 break;
1903 return 0;
1907 /***********************************************************************
1908 * LISTBOX_HandleHScroll
1910 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1912 SCROLLINFO info;
1913 INT page;
1915 if (descr->style & LBS_MULTICOLUMN)
1917 switch(scrollReq)
1919 case SB_LINELEFT:
1920 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1921 TRUE );
1922 break;
1923 case SB_LINERIGHT:
1924 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1925 TRUE );
1926 break;
1927 case SB_PAGELEFT:
1928 page = descr->width / descr->column_width;
1929 if (page < 1) page = 1;
1930 LISTBOX_SetTopItem( descr,
1931 descr->top_item - page * descr->page_size, TRUE );
1932 break;
1933 case SB_PAGERIGHT:
1934 page = descr->width / descr->column_width;
1935 if (page < 1) page = 1;
1936 LISTBOX_SetTopItem( descr,
1937 descr->top_item + page * descr->page_size, TRUE );
1938 break;
1939 case SB_THUMBPOSITION:
1940 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1941 break;
1942 case SB_THUMBTRACK:
1943 info.cbSize = sizeof(info);
1944 info.fMask = SIF_TRACKPOS;
1945 GetScrollInfo( descr->self, SB_VERT, &info );
1946 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1947 TRUE );
1948 break;
1949 case SB_LEFT:
1950 LISTBOX_SetTopItem( descr, 0, TRUE );
1951 break;
1952 case SB_RIGHT:
1953 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1954 break;
1957 else if (descr->horz_extent)
1959 switch(scrollReq)
1961 case SB_LINELEFT:
1962 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1963 break;
1964 case SB_LINERIGHT:
1965 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1966 break;
1967 case SB_PAGELEFT:
1968 LISTBOX_SetHorizontalPos( descr,
1969 descr->horz_pos - descr->width );
1970 break;
1971 case SB_PAGERIGHT:
1972 LISTBOX_SetHorizontalPos( descr,
1973 descr->horz_pos + descr->width );
1974 break;
1975 case SB_THUMBPOSITION:
1976 LISTBOX_SetHorizontalPos( descr, pos );
1977 break;
1978 case SB_THUMBTRACK:
1979 info.cbSize = sizeof(info);
1980 info.fMask = SIF_TRACKPOS;
1981 GetScrollInfo( descr->self, SB_HORZ, &info );
1982 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1983 break;
1984 case SB_LEFT:
1985 LISTBOX_SetHorizontalPos( descr, 0 );
1986 break;
1987 case SB_RIGHT:
1988 LISTBOX_SetHorizontalPos( descr,
1989 descr->horz_extent - descr->width );
1990 break;
1993 return 0;
1996 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1998 UINT pulScrollLines = 3;
2000 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2002 /* if scrolling changes direction, ignore left overs */
2003 if ((delta < 0 && descr->wheel_remain < 0) ||
2004 (delta > 0 && descr->wheel_remain > 0))
2005 descr->wheel_remain += delta;
2006 else
2007 descr->wheel_remain = delta;
2009 if (descr->wheel_remain && pulScrollLines)
2011 int cLineScroll;
2012 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2013 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2014 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2015 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2017 return 0;
2020 /***********************************************************************
2021 * LISTBOX_HandleLButtonDown
2023 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2025 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2027 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2028 descr->self, x, y, index, descr->focus_item);
2030 if (!descr->caret_on && (descr->in_focus)) return 0;
2032 if (!descr->in_focus)
2034 if( !descr->lphc ) SetFocus( descr->self );
2035 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2038 if (index == -1) return 0;
2040 if (!descr->lphc)
2042 if (descr->style & LBS_NOTIFY )
2043 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2044 MAKELPARAM( x, y ) );
2047 descr->captured = TRUE;
2048 SetCapture( descr->self );
2050 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2052 /* we should perhaps make sure that all items are deselected
2053 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2054 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2055 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2058 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2059 if (keys & MK_CONTROL)
2061 LISTBOX_SetCaretIndex( descr, index, FALSE );
2062 LISTBOX_SetSelection( descr, index,
2063 !descr->items[index].selected,
2064 (descr->style & LBS_NOTIFY) != 0);
2066 else
2068 LISTBOX_MoveCaret( descr, index, FALSE );
2070 if (descr->style & LBS_EXTENDEDSEL)
2072 LISTBOX_SetSelection( descr, index,
2073 descr->items[index].selected,
2074 (descr->style & LBS_NOTIFY) != 0 );
2076 else
2078 LISTBOX_SetSelection( descr, index,
2079 !descr->items[index].selected,
2080 (descr->style & LBS_NOTIFY) != 0 );
2084 else
2086 descr->anchor_item = index;
2087 LISTBOX_MoveCaret( descr, index, FALSE );
2088 LISTBOX_SetSelection( descr, index,
2089 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2092 if (!descr->lphc)
2094 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2096 POINT pt;
2098 pt.x = x;
2099 pt.y = y;
2101 if (DragDetect( descr->self, pt ))
2102 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2105 return 0;
2109 /*************************************************************************
2110 * LISTBOX_HandleLButtonDownCombo [Internal]
2112 * Process LButtonDown message for the ComboListBox
2114 * PARAMS
2115 * pWnd [I] The windows internal structure
2116 * pDescr [I] The ListBox internal structure
2117 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2118 * x [I] X Mouse Coordinate
2119 * y [I] Y Mouse Coordinate
2121 * RETURNS
2122 * 0 since we are processing the WM_LBUTTONDOWN Message
2124 * NOTES
2125 * This function is only to be used when a ListBox is a ComboListBox
2128 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2130 RECT clientRect, screenRect;
2131 POINT mousePos;
2133 mousePos.x = x;
2134 mousePos.y = y;
2136 GetClientRect(descr->self, &clientRect);
2138 if(PtInRect(&clientRect, mousePos))
2140 /* MousePos is in client, resume normal processing */
2141 if (msg == WM_LBUTTONDOWN)
2143 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2144 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2146 else if (descr->style & LBS_NOTIFY)
2147 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2149 else
2151 POINT screenMousePos;
2152 HWND hWndOldCapture;
2154 /* Check the Non-Client Area */
2155 screenMousePos = mousePos;
2156 hWndOldCapture = GetCapture();
2157 ReleaseCapture();
2158 GetWindowRect(descr->self, &screenRect);
2159 ClientToScreen(descr->self, &screenMousePos);
2161 if(!PtInRect(&screenRect, screenMousePos))
2163 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2164 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2165 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2167 else
2169 /* Check to see the NC is a scrollbar */
2170 INT nHitTestType=0;
2171 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2172 /* Check Vertical scroll bar */
2173 if (style & WS_VSCROLL)
2175 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2176 if (PtInRect( &clientRect, mousePos ))
2177 nHitTestType = HTVSCROLL;
2179 /* Check horizontal scroll bar */
2180 if (style & WS_HSCROLL)
2182 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2183 if (PtInRect( &clientRect, mousePos ))
2184 nHitTestType = HTHSCROLL;
2186 /* Windows sends this message when a scrollbar is clicked
2189 if(nHitTestType != 0)
2191 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2192 MAKELONG(screenMousePos.x, screenMousePos.y));
2194 /* Resume the Capture after scrolling is complete
2196 if(hWndOldCapture != 0)
2197 SetCapture(hWndOldCapture);
2200 return 0;
2203 /***********************************************************************
2204 * LISTBOX_HandleLButtonUp
2206 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2208 if (LISTBOX_Timer != LB_TIMER_NONE)
2209 KillSystemTimer( descr->self, LB_TIMER_ID );
2210 LISTBOX_Timer = LB_TIMER_NONE;
2211 if (descr->captured)
2213 descr->captured = FALSE;
2214 if (GetCapture() == descr->self) ReleaseCapture();
2215 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2216 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2218 return 0;
2222 /***********************************************************************
2223 * LISTBOX_HandleTimer
2225 * Handle scrolling upon a timer event.
2226 * Return TRUE if scrolling should continue.
2228 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2230 switch(dir)
2232 case LB_TIMER_UP:
2233 if (descr->top_item) index = descr->top_item - 1;
2234 else index = 0;
2235 break;
2236 case LB_TIMER_LEFT:
2237 if (descr->top_item) index -= descr->page_size;
2238 break;
2239 case LB_TIMER_DOWN:
2240 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2241 if (index == descr->focus_item) index++;
2242 if (index >= descr->nb_items) index = descr->nb_items - 1;
2243 break;
2244 case LB_TIMER_RIGHT:
2245 if (index + descr->page_size < descr->nb_items)
2246 index += descr->page_size;
2247 break;
2248 case LB_TIMER_NONE:
2249 break;
2251 if (index == descr->focus_item) return FALSE;
2252 LISTBOX_MoveCaret( descr, index, FALSE );
2253 return TRUE;
2257 /***********************************************************************
2258 * LISTBOX_HandleSystemTimer
2260 * WM_SYSTIMER handler.
2262 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2264 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2266 KillSystemTimer( descr->self, LB_TIMER_ID );
2267 LISTBOX_Timer = LB_TIMER_NONE;
2269 return 0;
2273 /***********************************************************************
2274 * LISTBOX_HandleMouseMove
2276 * WM_MOUSEMOVE handler.
2278 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2279 INT x, INT y )
2281 INT index;
2282 TIMER_DIRECTION dir = LB_TIMER_NONE;
2284 if (!descr->captured) return;
2286 if (descr->style & LBS_MULTICOLUMN)
2288 if (y < 0) y = 0;
2289 else if (y >= descr->item_height * descr->page_size)
2290 y = descr->item_height * descr->page_size - 1;
2292 if (x < 0)
2294 dir = LB_TIMER_LEFT;
2295 x = 0;
2297 else if (x >= descr->width)
2299 dir = LB_TIMER_RIGHT;
2300 x = descr->width - 1;
2303 else
2305 if (y < 0) dir = LB_TIMER_UP; /* above */
2306 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2309 index = LISTBOX_GetItemFromPoint( descr, x, y );
2310 if (index == -1) index = descr->focus_item;
2311 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2313 /* Start/stop the system timer */
2315 if (dir != LB_TIMER_NONE)
2316 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2317 else if (LISTBOX_Timer != LB_TIMER_NONE)
2318 KillSystemTimer( descr->self, LB_TIMER_ID );
2319 LISTBOX_Timer = dir;
2323 /***********************************************************************
2324 * LISTBOX_HandleKeyDown
2326 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2328 INT caret = -1;
2329 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2330 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2331 bForceSelection = FALSE; /* only for single select list */
2333 if (descr->style & LBS_WANTKEYBOARDINPUT)
2335 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2336 MAKEWPARAM(LOWORD(key), descr->focus_item),
2337 (LPARAM)descr->self );
2338 if (caret == -2) return 0;
2340 if (caret == -1) switch(key)
2342 case VK_LEFT:
2343 if (descr->style & LBS_MULTICOLUMN)
2345 bForceSelection = FALSE;
2346 if (descr->focus_item >= descr->page_size)
2347 caret = descr->focus_item - descr->page_size;
2348 break;
2350 /* fall through */
2351 case VK_UP:
2352 caret = descr->focus_item - 1;
2353 if (caret < 0) caret = 0;
2354 break;
2355 case VK_RIGHT:
2356 if (descr->style & LBS_MULTICOLUMN)
2358 bForceSelection = FALSE;
2359 if (descr->focus_item + descr->page_size < descr->nb_items)
2360 caret = descr->focus_item + descr->page_size;
2361 break;
2363 /* fall through */
2364 case VK_DOWN:
2365 caret = descr->focus_item + 1;
2366 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2367 break;
2369 case VK_PRIOR:
2370 if (descr->style & LBS_MULTICOLUMN)
2372 INT page = descr->width / descr->column_width;
2373 if (page < 1) page = 1;
2374 caret = descr->focus_item - (page * descr->page_size) + 1;
2376 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2377 if (caret < 0) caret = 0;
2378 break;
2379 case VK_NEXT:
2380 if (descr->style & LBS_MULTICOLUMN)
2382 INT page = descr->width / descr->column_width;
2383 if (page < 1) page = 1;
2384 caret = descr->focus_item + (page * descr->page_size) - 1;
2386 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2387 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2388 break;
2389 case VK_HOME:
2390 caret = 0;
2391 break;
2392 case VK_END:
2393 caret = descr->nb_items - 1;
2394 break;
2395 case VK_SPACE:
2396 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2397 else if (descr->style & LBS_MULTIPLESEL)
2399 LISTBOX_SetSelection( descr, descr->focus_item,
2400 !descr->items[descr->focus_item].selected,
2401 (descr->style & LBS_NOTIFY) != 0 );
2403 break;
2404 default:
2405 bForceSelection = FALSE;
2407 if (bForceSelection) /* focused item is used instead of key */
2408 caret = descr->focus_item;
2409 if (caret >= 0)
2411 if (((descr->style & LBS_EXTENDEDSEL) &&
2412 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2413 !IS_MULTISELECT(descr))
2414 descr->anchor_item = caret;
2415 LISTBOX_MoveCaret( descr, caret, TRUE );
2417 if (descr->style & LBS_MULTIPLESEL)
2418 descr->selected_item = caret;
2419 else
2420 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2421 if (descr->style & LBS_NOTIFY)
2423 if (descr->lphc && IsWindowVisible( descr->self ))
2425 /* make sure that combo parent doesn't hide us */
2426 descr->lphc->wState |= CBF_NOROLLUP;
2428 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2431 return 0;
2435 /***********************************************************************
2436 * LISTBOX_HandleChar
2438 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2440 INT caret = -1;
2441 WCHAR str[2];
2443 str[0] = charW;
2444 str[1] = '\0';
2446 if (descr->style & LBS_WANTKEYBOARDINPUT)
2448 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2449 MAKEWPARAM(charW, descr->focus_item),
2450 (LPARAM)descr->self );
2451 if (caret == -2) return 0;
2453 if (caret == -1)
2454 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2455 if (caret != -1)
2457 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2458 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2459 LISTBOX_MoveCaret( descr, caret, TRUE );
2460 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2461 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2463 return 0;
2467 /***********************************************************************
2468 * LISTBOX_Create
2470 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2472 LB_DESCR *descr;
2473 MEASUREITEMSTRUCT mis;
2474 RECT rect;
2476 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2477 return FALSE;
2479 GetClientRect( hwnd, &rect );
2480 descr->self = hwnd;
2481 descr->owner = GetParent( descr->self );
2482 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2483 descr->width = rect.right - rect.left;
2484 descr->height = rect.bottom - rect.top;
2485 descr->items = NULL;
2486 descr->nb_items = 0;
2487 descr->top_item = 0;
2488 descr->selected_item = -1;
2489 descr->focus_item = 0;
2490 descr->anchor_item = -1;
2491 descr->item_height = 1;
2492 descr->page_size = 1;
2493 descr->column_width = 150;
2494 descr->horz_extent = 0;
2495 descr->horz_pos = 0;
2496 descr->nb_tabs = 0;
2497 descr->tabs = NULL;
2498 descr->wheel_remain = 0;
2499 descr->caret_on = !lphc;
2500 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2501 descr->in_focus = FALSE;
2502 descr->captured = FALSE;
2503 descr->font = 0;
2504 descr->locale = GetUserDefaultLCID();
2505 descr->lphc = lphc;
2507 if( lphc )
2509 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2510 descr->owner = lphc->self;
2513 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2515 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2517 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2518 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2519 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2520 descr->item_height = LISTBOX_SetFont( descr, 0 );
2522 if (descr->style & LBS_OWNERDRAWFIXED)
2524 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2526 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2527 descr->item_height = lphc->fixedOwnerDrawHeight;
2529 else
2531 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2532 mis.CtlType = ODT_LISTBOX;
2533 mis.CtlID = id;
2534 mis.itemID = -1;
2535 mis.itemWidth = 0;
2536 mis.itemData = 0;
2537 mis.itemHeight = descr->item_height;
2538 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2539 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2543 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2544 return TRUE;
2548 /***********************************************************************
2549 * LISTBOX_Destroy
2551 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2553 LISTBOX_ResetContent( descr );
2554 SetWindowLongPtrW( descr->self, 0, 0 );
2555 HeapFree( GetProcessHeap(), 0, descr );
2556 return TRUE;
2560 /***********************************************************************
2561 * ListBoxWndProc_common
2563 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2565 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2566 LPHEADCOMBO lphc = 0;
2567 LRESULT ret;
2569 if (!descr)
2571 if (!IsWindow(hwnd)) return 0;
2573 if (msg == WM_CREATE)
2575 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2576 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2577 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2578 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2579 return 0;
2581 /* Ignore all other messages before we get a WM_CREATE */
2582 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2583 DefWindowProcA( hwnd, msg, wParam, lParam );
2585 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2587 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2588 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2590 switch(msg)
2592 case LB_RESETCONTENT:
2593 LISTBOX_ResetContent( descr );
2594 LISTBOX_UpdateScroll( descr );
2595 InvalidateRect( descr->self, NULL, TRUE );
2596 return 0;
2598 case LB_ADDSTRING:
2600 INT ret;
2601 LPWSTR textW;
2602 if(unicode || !HAS_STRINGS(descr))
2603 textW = (LPWSTR)lParam;
2604 else
2606 LPSTR textA = (LPSTR)lParam;
2607 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2608 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2609 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2610 else
2611 return LB_ERRSPACE;
2613 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2614 ret = LISTBOX_InsertString( descr, wParam, textW );
2615 if (!unicode && HAS_STRINGS(descr))
2616 HeapFree(GetProcessHeap(), 0, textW);
2617 return ret;
2620 case LB_INSERTSTRING:
2622 INT ret;
2623 LPWSTR textW;
2624 if(unicode || !HAS_STRINGS(descr))
2625 textW = (LPWSTR)lParam;
2626 else
2628 LPSTR textA = (LPSTR)lParam;
2629 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2630 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2631 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2632 else
2633 return LB_ERRSPACE;
2635 ret = LISTBOX_InsertString( descr, wParam, textW );
2636 if(!unicode && HAS_STRINGS(descr))
2637 HeapFree(GetProcessHeap(), 0, textW);
2638 return ret;
2641 case LB_ADDFILE:
2643 INT ret;
2644 LPWSTR textW;
2645 if(unicode || !HAS_STRINGS(descr))
2646 textW = (LPWSTR)lParam;
2647 else
2649 LPSTR textA = (LPSTR)lParam;
2650 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2651 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2652 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2653 else
2654 return LB_ERRSPACE;
2656 wParam = LISTBOX_FindFileStrPos( descr, textW );
2657 ret = LISTBOX_InsertString( descr, wParam, textW );
2658 if(!unicode && HAS_STRINGS(descr))
2659 HeapFree(GetProcessHeap(), 0, textW);
2660 return ret;
2663 case LB_DELETESTRING:
2664 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2665 return descr->nb_items;
2666 else
2668 SetLastError(ERROR_INVALID_INDEX);
2669 return LB_ERR;
2672 case LB_GETITEMDATA:
2673 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2675 SetLastError(ERROR_INVALID_INDEX);
2676 return LB_ERR;
2678 return descr->items[wParam].data;
2680 case LB_SETITEMDATA:
2681 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2683 SetLastError(ERROR_INVALID_INDEX);
2684 return LB_ERR;
2686 descr->items[wParam].data = lParam;
2687 /* undocumented: returns TRUE, not LB_OKAY (0) */
2688 return TRUE;
2690 case LB_GETCOUNT:
2691 return descr->nb_items;
2693 case LB_GETTEXT:
2694 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2696 case LB_GETTEXTLEN:
2697 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2699 SetLastError(ERROR_INVALID_INDEX);
2700 return LB_ERR;
2702 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2703 if (unicode) return strlenW( descr->items[wParam].str );
2704 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2705 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2707 case LB_GETCURSEL:
2708 if (descr->nb_items == 0)
2709 return LB_ERR;
2710 if (!IS_MULTISELECT(descr))
2711 return descr->selected_item;
2712 if (descr->selected_item != -1)
2713 return descr->selected_item;
2714 return descr->focus_item;
2715 /* otherwise, if the user tries to move the selection with the */
2716 /* arrow keys, we will give the application something to choke on */
2717 case LB_GETTOPINDEX:
2718 return descr->top_item;
2720 case LB_GETITEMHEIGHT:
2721 return LISTBOX_GetItemHeight( descr, wParam );
2723 case LB_SETITEMHEIGHT:
2724 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2726 case LB_ITEMFROMPOINT:
2728 POINT pt;
2729 RECT rect;
2730 int index;
2731 BOOL hit = TRUE;
2733 /* The hiword of the return value is not a client area
2734 hittest as suggested by MSDN, but rather a hittest on
2735 the returned listbox item. */
2737 if(descr->nb_items == 0)
2738 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2740 pt.x = (short)LOWORD(lParam);
2741 pt.y = (short)HIWORD(lParam);
2743 SetRect(&rect, 0, 0, descr->width, descr->height);
2745 if(!PtInRect(&rect, pt))
2747 pt.x = min(pt.x, rect.right - 1);
2748 pt.x = max(pt.x, 0);
2749 pt.y = min(pt.y, rect.bottom - 1);
2750 pt.y = max(pt.y, 0);
2751 hit = FALSE;
2754 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2756 if(index == -1)
2758 index = descr->nb_items - 1;
2759 hit = FALSE;
2761 return MAKELONG(index, hit ? 0 : 1);
2764 case LB_SETCARETINDEX:
2765 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2766 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2767 return LB_ERR;
2768 else if (ISWIN31)
2769 return wParam;
2770 else
2771 return LB_OKAY;
2773 case LB_GETCARETINDEX:
2774 return descr->focus_item;
2776 case LB_SETTOPINDEX:
2777 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2779 case LB_SETCOLUMNWIDTH:
2780 return LISTBOX_SetColumnWidth( descr, wParam );
2782 case LB_GETITEMRECT:
2783 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2785 case LB_FINDSTRING:
2787 INT ret;
2788 LPWSTR textW;
2789 if(unicode || !HAS_STRINGS(descr))
2790 textW = (LPWSTR)lParam;
2791 else
2793 LPSTR textA = (LPSTR)lParam;
2794 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2795 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2796 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2798 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2799 if(!unicode && HAS_STRINGS(descr))
2800 HeapFree(GetProcessHeap(), 0, textW);
2801 return ret;
2804 case LB_FINDSTRINGEXACT:
2806 INT ret;
2807 LPWSTR textW;
2808 if(unicode || !HAS_STRINGS(descr))
2809 textW = (LPWSTR)lParam;
2810 else
2812 LPSTR textA = (LPSTR)lParam;
2813 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2814 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2815 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2817 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2818 if(!unicode && HAS_STRINGS(descr))
2819 HeapFree(GetProcessHeap(), 0, textW);
2820 return ret;
2823 case LB_SELECTSTRING:
2825 INT index;
2826 LPWSTR textW;
2828 if(HAS_STRINGS(descr))
2829 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2830 debugstr_a((LPSTR)lParam));
2831 if(unicode || !HAS_STRINGS(descr))
2832 textW = (LPWSTR)lParam;
2833 else
2835 LPSTR textA = (LPSTR)lParam;
2836 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2837 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2838 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2840 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2841 if(!unicode && HAS_STRINGS(descr))
2842 HeapFree(GetProcessHeap(), 0, textW);
2843 if (index != LB_ERR)
2845 LISTBOX_MoveCaret( descr, index, TRUE );
2846 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2848 return index;
2851 case LB_GETSEL:
2852 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2853 return LB_ERR;
2854 return descr->items[wParam].selected;
2856 case LB_SETSEL:
2857 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2859 case LB_SETCURSEL:
2860 if (IS_MULTISELECT(descr)) return LB_ERR;
2861 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2862 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2863 if (ret != LB_ERR) ret = descr->selected_item;
2864 return ret;
2866 case LB_GETSELCOUNT:
2867 return LISTBOX_GetSelCount( descr );
2869 case LB_GETSELITEMS:
2870 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2872 case LB_SELITEMRANGE:
2873 if (LOWORD(lParam) <= HIWORD(lParam))
2874 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2875 HIWORD(lParam), wParam );
2876 else
2877 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2878 LOWORD(lParam), wParam );
2880 case LB_SELITEMRANGEEX:
2881 if ((INT)lParam >= (INT)wParam)
2882 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2883 else
2884 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2886 case LB_GETHORIZONTALEXTENT:
2887 return descr->horz_extent;
2889 case LB_SETHORIZONTALEXTENT:
2890 return LISTBOX_SetHorizontalExtent( descr, wParam );
2892 case LB_GETANCHORINDEX:
2893 return descr->anchor_item;
2895 case LB_SETANCHORINDEX:
2896 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2898 SetLastError(ERROR_INVALID_INDEX);
2899 return LB_ERR;
2901 descr->anchor_item = (INT)wParam;
2902 return LB_OKAY;
2904 case LB_DIR:
2906 INT ret;
2907 LPWSTR textW;
2908 if(unicode)
2909 textW = (LPWSTR)lParam;
2910 else
2912 LPSTR textA = (LPSTR)lParam;
2913 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2914 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2915 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2917 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2918 if(!unicode)
2919 HeapFree(GetProcessHeap(), 0, textW);
2920 return ret;
2923 case LB_GETLOCALE:
2924 return descr->locale;
2926 case LB_SETLOCALE:
2928 LCID ret;
2929 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2930 return LB_ERR;
2931 ret = descr->locale;
2932 descr->locale = (LCID)wParam;
2933 return ret;
2936 case LB_INITSTORAGE:
2937 return LISTBOX_InitStorage( descr, wParam );
2939 case LB_SETCOUNT:
2940 return LISTBOX_SetCount( descr, (INT)wParam );
2942 case LB_SETTABSTOPS:
2943 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2945 case LB_CARETON:
2946 if (descr->caret_on)
2947 return LB_OKAY;
2948 descr->caret_on = TRUE;
2949 if ((descr->focus_item != -1) && (descr->in_focus))
2950 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2951 return LB_OKAY;
2953 case LB_CARETOFF:
2954 if (!descr->caret_on)
2955 return LB_OKAY;
2956 descr->caret_on = FALSE;
2957 if ((descr->focus_item != -1) && (descr->in_focus))
2958 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2959 return LB_OKAY;
2961 case LB_GETLISTBOXINFO:
2962 return descr->page_size;
2964 case WM_DESTROY:
2965 return LISTBOX_Destroy( descr );
2967 case WM_ENABLE:
2968 InvalidateRect( descr->self, NULL, TRUE );
2969 return 0;
2971 case WM_SETREDRAW:
2972 LISTBOX_SetRedraw( descr, wParam != 0 );
2973 return 0;
2975 case WM_GETDLGCODE:
2976 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2978 case WM_PRINTCLIENT:
2979 case WM_PAINT:
2981 PAINTSTRUCT ps;
2982 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2983 ret = LISTBOX_Paint( descr, hdc );
2984 if( !wParam ) EndPaint( descr->self, &ps );
2986 return ret;
2987 case WM_SIZE:
2988 LISTBOX_UpdateSize( descr );
2989 return 0;
2990 case WM_GETFONT:
2991 return (LRESULT)descr->font;
2992 case WM_SETFONT:
2993 LISTBOX_SetFont( descr, (HFONT)wParam );
2994 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2995 return 0;
2996 case WM_SETFOCUS:
2997 descr->in_focus = TRUE;
2998 descr->caret_on = TRUE;
2999 if (descr->focus_item != -1)
3000 LISTBOX_DrawFocusRect( descr, TRUE );
3001 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3002 return 0;
3003 case WM_KILLFOCUS:
3004 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3005 descr->in_focus = FALSE;
3006 descr->wheel_remain = 0;
3007 if ((descr->focus_item != -1) && descr->caret_on)
3008 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3009 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3010 return 0;
3011 case WM_HSCROLL:
3012 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3013 case WM_VSCROLL:
3014 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3015 case WM_MOUSEWHEEL:
3016 if (wParam & (MK_SHIFT | MK_CONTROL))
3017 return DefWindowProcW( descr->self, msg, wParam, lParam );
3018 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3019 case WM_LBUTTONDOWN:
3020 if (lphc)
3021 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3022 (INT16)LOWORD(lParam),
3023 (INT16)HIWORD(lParam) );
3024 return LISTBOX_HandleLButtonDown( descr, wParam,
3025 (INT16)LOWORD(lParam),
3026 (INT16)HIWORD(lParam) );
3027 case WM_LBUTTONDBLCLK:
3028 if (lphc)
3029 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3030 (INT16)LOWORD(lParam),
3031 (INT16)HIWORD(lParam) );
3032 if (descr->style & LBS_NOTIFY)
3033 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3034 return 0;
3035 case WM_MOUSEMOVE:
3036 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3038 BOOL captured = descr->captured;
3039 POINT mousePos;
3040 RECT clientRect;
3042 mousePos.x = (INT16)LOWORD(lParam);
3043 mousePos.y = (INT16)HIWORD(lParam);
3046 * If we are in a dropdown combobox, we simulate that
3047 * the mouse is captured to show the tracking of the item.
3049 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3050 descr->captured = TRUE;
3052 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3054 descr->captured = captured;
3056 else if (GetCapture() == descr->self)
3058 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3059 (INT16)HIWORD(lParam) );
3061 return 0;
3062 case WM_LBUTTONUP:
3063 if (lphc)
3065 POINT mousePos;
3066 RECT clientRect;
3069 * If the mouse button "up" is not in the listbox,
3070 * we make sure there is no selection by re-selecting the
3071 * item that was selected when the listbox was made visible.
3073 mousePos.x = (INT16)LOWORD(lParam);
3074 mousePos.y = (INT16)HIWORD(lParam);
3076 GetClientRect(descr->self, &clientRect);
3079 * When the user clicks outside the combobox and the focus
3080 * is lost, the owning combobox will send a fake buttonup with
3081 * 0xFFFFFFF as the mouse location, we must also revert the
3082 * selection to the original selection.
3084 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3085 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3087 return LISTBOX_HandleLButtonUp( descr );
3088 case WM_KEYDOWN:
3089 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3091 /* for some reason Windows makes it possible to
3092 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3094 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3095 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3096 && (wParam == VK_DOWN || wParam == VK_UP)) )
3098 COMBO_FlipListbox( lphc, FALSE, FALSE );
3099 return 0;
3102 return LISTBOX_HandleKeyDown( descr, wParam );
3103 case WM_CHAR:
3105 WCHAR charW;
3106 if(unicode)
3107 charW = (WCHAR)wParam;
3108 else
3110 CHAR charA = (CHAR)wParam;
3111 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3113 return LISTBOX_HandleChar( descr, charW );
3115 case WM_SYSTIMER:
3116 return LISTBOX_HandleSystemTimer( descr );
3117 case WM_ERASEBKGND:
3118 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3120 RECT rect;
3121 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3122 wParam, (LPARAM)descr->self );
3123 TRACE("hbrush = %p\n", hbrush);
3124 if(!hbrush)
3125 hbrush = GetSysColorBrush(COLOR_WINDOW);
3126 if(hbrush)
3128 GetClientRect(descr->self, &rect);
3129 FillRect((HDC)wParam, &rect, hbrush);
3132 return 1;
3133 case WM_DROPFILES:
3134 if( lphc ) return 0;
3135 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3136 SendMessageA( descr->owner, msg, wParam, lParam );
3138 case WM_NCDESTROY:
3139 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3140 lphc->hWndLBox = 0;
3141 break;
3143 case WM_NCACTIVATE:
3144 if (lphc) return 0;
3145 break;
3147 default:
3148 if ((msg >= WM_USER) && (msg < 0xc000))
3149 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3150 hwnd, msg, wParam, lParam );
3153 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3154 DefWindowProcA( hwnd, msg, wParam, lParam );
3157 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3159 TRACE("%p\n", hwnd);
3160 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);