msi: Don't defer custom actions in the UI sequence if they match the currently runnin...
[wine/multimedia.git] / dlls / user32 / listbox.c
blob5a78ddaa81474e13a5d6aba843b2588225480bdc
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 (0 if no hscroll) */
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->horz_extent)
270 info.nMin = 0;
271 info.nMax = descr->horz_extent - 1;
272 info.nPos = descr->horz_pos;
273 info.nPage = descr->width;
274 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
275 if (descr->style & LBS_DISABLENOSCROLL)
276 info.fMask |= SIF_DISABLENOSCROLL;
277 if (descr->style & WS_HSCROLL)
278 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
284 /***********************************************************************
285 * LISTBOX_SetTopItem
287 * Set the top item of the listbox, scrolling up or down if necessary.
289 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
291 INT max = LISTBOX_GetMaxTopIndex( descr );
293 TRACE("setting top item %d, scroll %d\n", index, scroll);
295 if (index > max) index = max;
296 if (index < 0) index = 0;
297 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
298 if (descr->top_item == index) return LB_OKAY;
299 if (scroll)
301 INT diff;
302 if (descr->style & LBS_MULTICOLUMN)
303 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
304 else if (descr->style & LBS_OWNERDRAWVARIABLE)
306 INT i;
307 diff = 0;
308 if (index > descr->top_item)
310 for (i = index - 1; i >= descr->top_item; i--)
311 diff -= descr->items[i].height;
313 else
315 for (i = index; i < descr->top_item; i++)
316 diff += descr->items[i].height;
319 else
320 diff = (descr->top_item - index) * descr->item_height;
322 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
323 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
325 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
326 descr->top_item = index;
327 LISTBOX_UpdateScroll( descr );
328 return LB_OKAY;
332 /***********************************************************************
333 * LISTBOX_UpdatePage
335 * Update the page size. Should be called when the size of
336 * the client area or the item height changes.
338 static void LISTBOX_UpdatePage( LB_DESCR *descr )
340 INT page_size;
342 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
343 page_size = 1;
344 if (page_size == descr->page_size) return;
345 descr->page_size = page_size;
346 if (descr->style & LBS_MULTICOLUMN)
347 InvalidateRect( descr->self, NULL, TRUE );
348 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
352 /***********************************************************************
353 * LISTBOX_UpdateSize
355 * Update the size of the listbox. Should be called when the size of
356 * the client area changes.
358 static void LISTBOX_UpdateSize( LB_DESCR *descr )
360 RECT rect;
362 GetClientRect( descr->self, &rect );
363 descr->width = rect.right - rect.left;
364 descr->height = rect.bottom - rect.top;
365 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
367 INT remaining;
368 RECT rect;
370 GetWindowRect( descr->self, &rect );
371 if(descr->item_height != 0)
372 remaining = descr->height % descr->item_height;
373 else
374 remaining = 0;
375 if ((descr->height > descr->item_height) && remaining)
377 TRACE("[%p]: changing height %d -> %d\n",
378 descr->self, descr->height, descr->height - remaining );
379 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
380 rect.bottom - rect.top - remaining,
381 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
382 return;
385 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
386 LISTBOX_UpdatePage( descr );
387 LISTBOX_UpdateScroll( descr );
389 /* Invalidate the focused item so it will be repainted correctly */
390 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
392 InvalidateRect( descr->self, &rect, FALSE );
397 /***********************************************************************
398 * LISTBOX_GetItemRect
400 * Get the rectangle enclosing an item, in listbox client coordinates.
401 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
403 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
405 /* Index <= 0 is legal even on empty listboxes */
406 if (index && (index >= descr->nb_items))
408 memset(rect, 0, sizeof(*rect));
409 SetLastError(ERROR_INVALID_INDEX);
410 return LB_ERR;
412 SetRect( rect, 0, 0, descr->width, descr->height );
413 if (descr->style & LBS_MULTICOLUMN)
415 INT col = (index / descr->page_size) -
416 (descr->top_item / descr->page_size);
417 rect->left += col * descr->column_width;
418 rect->right = rect->left + descr->column_width;
419 rect->top += (index % descr->page_size) * descr->item_height;
420 rect->bottom = rect->top + descr->item_height;
422 else if (descr->style & LBS_OWNERDRAWVARIABLE)
424 INT i;
425 rect->right += descr->horz_pos;
426 if ((index >= 0) && (index < descr->nb_items))
428 if (index < descr->top_item)
430 for (i = descr->top_item-1; i >= index; i--)
431 rect->top -= descr->items[i].height;
433 else
435 for (i = descr->top_item; i < index; i++)
436 rect->top += descr->items[i].height;
438 rect->bottom = rect->top + descr->items[index].height;
442 else
444 rect->top += (index - descr->top_item) * descr->item_height;
445 rect->bottom = rect->top + descr->item_height;
446 rect->right += descr->horz_pos;
449 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
451 return ((rect->left < descr->width) && (rect->right > 0) &&
452 (rect->top < descr->height) && (rect->bottom > 0));
456 /***********************************************************************
457 * LISTBOX_GetItemFromPoint
459 * Return the item nearest from point (x,y) (in client coordinates).
461 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
463 INT index = descr->top_item;
465 if (!descr->nb_items) return -1; /* No items */
466 if (descr->style & LBS_OWNERDRAWVARIABLE)
468 INT pos = 0;
469 if (y >= 0)
471 while (index < descr->nb_items)
473 if ((pos += descr->items[index].height) > y) break;
474 index++;
477 else
479 while (index > 0)
481 index--;
482 if ((pos -= descr->items[index].height) <= y) break;
486 else if (descr->style & LBS_MULTICOLUMN)
488 if (y >= descr->item_height * descr->page_size) return -1;
489 if (y >= 0) index += y / descr->item_height;
490 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
491 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
493 else
495 index += (y / descr->item_height);
497 if (index < 0) return 0;
498 if (index >= descr->nb_items) return -1;
499 return index;
503 /***********************************************************************
504 * LISTBOX_PaintItem
506 * Paint an item.
508 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
509 INT index, UINT action, BOOL ignoreFocus )
511 LB_ITEMDATA *item = NULL;
512 if (index < descr->nb_items) item = &descr->items[index];
514 if (IS_OWNERDRAW(descr))
516 DRAWITEMSTRUCT dis;
517 RECT r;
518 HRGN hrgn;
520 if (!item)
522 if (action == ODA_FOCUS)
523 DrawFocusRect( hdc, rect );
524 else
525 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
526 return;
529 /* some programs mess with the clipping region when
530 drawing the item, *and* restore the previous region
531 after they are done, so a region has better to exist
532 else everything ends clipped */
533 GetClientRect(descr->self, &r);
534 hrgn = set_control_clipping( hdc, &r );
536 dis.CtlType = ODT_LISTBOX;
537 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
538 dis.hwndItem = descr->self;
539 dis.itemAction = action;
540 dis.hDC = hdc;
541 dis.itemID = index;
542 dis.itemState = 0;
543 if (item->selected) dis.itemState |= ODS_SELECTED;
544 if (!ignoreFocus && (descr->focus_item == index) &&
545 (descr->caret_on) &&
546 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
547 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
548 dis.itemData = item->data;
549 dis.rcItem = *rect;
550 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
551 descr->self, index, debugstr_w(item->str), action,
552 dis.itemState, wine_dbgstr_rect(rect) );
553 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
554 SelectClipRgn( hdc, hrgn );
555 if (hrgn) DeleteObject( hrgn );
557 else
559 COLORREF oldText = 0, oldBk = 0;
561 if (action == ODA_FOCUS)
563 DrawFocusRect( hdc, rect );
564 return;
566 if (item && item->selected)
568 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
569 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
572 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
573 descr->self, index, item ? debugstr_w(item->str) : "", action,
574 wine_dbgstr_rect(rect) );
575 if (!item)
576 ExtTextOutW( hdc, rect->left + 1, rect->top,
577 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
578 else if (!(descr->style & LBS_USETABSTOPS))
579 ExtTextOutW( hdc, rect->left + 1, rect->top,
580 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
581 strlenW(item->str), NULL );
582 else
584 /* Output empty string to paint background in the full width. */
585 ExtTextOutW( hdc, rect->left + 1, rect->top,
586 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
587 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
588 item->str, strlenW(item->str),
589 descr->nb_tabs, descr->tabs, 0);
591 if (item && item->selected)
593 SetBkColor( hdc, oldBk );
594 SetTextColor( hdc, oldText );
596 if (!ignoreFocus && (descr->focus_item == index) &&
597 (descr->caret_on) &&
598 (descr->in_focus)) DrawFocusRect( hdc, rect );
603 /***********************************************************************
604 * LISTBOX_SetRedraw
606 * Change the redraw flag.
608 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
610 if (on)
612 if (!(descr->style & LBS_NOREDRAW)) return;
613 descr->style &= ~LBS_NOREDRAW;
614 if (descr->style & LBS_DISPLAYCHANGED)
615 { /* page was changed while setredraw false, refresh automatically */
616 InvalidateRect(descr->self, NULL, TRUE);
617 if ((descr->top_item + descr->page_size) > descr->nb_items)
618 { /* reset top of page if less than number of items/page */
619 descr->top_item = descr->nb_items - descr->page_size;
620 if (descr->top_item < 0) descr->top_item = 0;
622 descr->style &= ~LBS_DISPLAYCHANGED;
624 LISTBOX_UpdateScroll( descr );
626 else descr->style |= LBS_NOREDRAW;
630 /***********************************************************************
631 * LISTBOX_RepaintItem
633 * Repaint a single item synchronously.
635 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
637 HDC hdc;
638 RECT rect;
639 HFONT oldFont = 0;
640 HBRUSH hbrush, oldBrush = 0;
642 /* Do not repaint the item if the item is not visible */
643 if (!IsWindowVisible(descr->self)) return;
644 if (descr->style & LBS_NOREDRAW)
646 descr->style |= LBS_DISPLAYCHANGED;
647 return;
649 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
650 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
651 if (descr->font) oldFont = SelectObject( hdc, descr->font );
652 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
653 (WPARAM)hdc, (LPARAM)descr->self );
654 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
655 if (!IsWindowEnabled(descr->self))
656 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
657 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
658 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
659 if (oldFont) SelectObject( hdc, oldFont );
660 if (oldBrush) SelectObject( hdc, oldBrush );
661 ReleaseDC( descr->self, hdc );
665 /***********************************************************************
666 * LISTBOX_DrawFocusRect
668 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
670 HDC hdc;
671 RECT rect;
672 HFONT oldFont = 0;
674 /* Do not repaint the item if the item is not visible */
675 if (!IsWindowVisible(descr->self)) return;
677 if (descr->focus_item == -1) return;
678 if (!descr->caret_on || !descr->in_focus) return;
680 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
681 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
682 if (descr->font) oldFont = SelectObject( hdc, descr->font );
683 if (!IsWindowEnabled(descr->self))
684 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
685 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
686 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
687 if (oldFont) SelectObject( hdc, oldFont );
688 ReleaseDC( descr->self, hdc );
692 /***********************************************************************
693 * LISTBOX_InitStorage
695 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
697 LB_ITEMDATA *item;
699 nb_items += LB_ARRAY_GRANULARITY - 1;
700 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
701 if (descr->items) {
702 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
703 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
704 nb_items * sizeof(LB_ITEMDATA));
706 else {
707 item = HeapAlloc( GetProcessHeap(), 0,
708 nb_items * sizeof(LB_ITEMDATA));
711 if (!item)
713 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
714 return LB_ERRSPACE;
716 descr->items = item;
717 return LB_OKAY;
721 /***********************************************************************
722 * LISTBOX_SetTabStops
724 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
726 INT i;
728 if (!(descr->style & LBS_USETABSTOPS))
730 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
731 return FALSE;
734 HeapFree( GetProcessHeap(), 0, descr->tabs );
735 if (!(descr->nb_tabs = count))
737 descr->tabs = NULL;
738 return TRUE;
740 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
741 descr->nb_tabs * sizeof(INT) )))
742 return FALSE;
743 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
745 /* convert into "dialog units"*/
746 for (i = 0; i < descr->nb_tabs; i++)
747 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
749 return TRUE;
753 /***********************************************************************
754 * LISTBOX_GetText
756 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
758 DWORD len;
760 if ((index < 0) || (index >= descr->nb_items))
762 SetLastError(ERROR_INVALID_INDEX);
763 return LB_ERR;
765 if (HAS_STRINGS(descr))
767 if (!buffer)
769 len = strlenW(descr->items[index].str);
770 if( unicode )
771 return len;
772 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
773 NULL, 0, NULL, NULL );
776 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
778 __TRY /* hide a Delphi bug that passes a read-only buffer */
780 if(unicode)
782 strcpyW( buffer, descr->items[index].str );
783 len = strlenW(buffer);
785 else
787 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
788 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
791 __EXCEPT_PAGE_FAULT
793 WARN( "got an invalid buffer (Delphi bug?)\n" );
794 SetLastError( ERROR_INVALID_PARAMETER );
795 return LB_ERR;
797 __ENDTRY
798 } else {
799 if (buffer)
800 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
801 len = sizeof(DWORD);
803 return len;
806 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
808 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
809 if (ret == CSTR_LESS_THAN)
810 return -1;
811 if (ret == CSTR_EQUAL)
812 return 0;
813 if (ret == CSTR_GREATER_THAN)
814 return 1;
815 return -1;
818 /***********************************************************************
819 * LISTBOX_FindStringPos
821 * Find the nearest string located before a given string in sort order.
822 * If 'exact' is TRUE, return an error if we don't get an exact match.
824 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
826 INT index, min, max, res = -1;
828 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
829 min = 0;
830 max = descr->nb_items;
831 while (min != max)
833 index = (min + max) / 2;
834 if (HAS_STRINGS(descr))
835 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
836 else
838 COMPAREITEMSTRUCT cis;
839 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
841 cis.CtlType = ODT_LISTBOX;
842 cis.CtlID = id;
843 cis.hwndItem = descr->self;
844 /* note that some application (MetaStock) expects the second item
845 * to be in the listbox */
846 cis.itemID1 = -1;
847 cis.itemData1 = (ULONG_PTR)str;
848 cis.itemID2 = index;
849 cis.itemData2 = descr->items[index].data;
850 cis.dwLocaleId = descr->locale;
851 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
853 if (!res) return index;
854 if (res < 0) max = index;
855 else min = index + 1;
857 return exact ? -1 : max;
861 /***********************************************************************
862 * LISTBOX_FindFileStrPos
864 * Find the nearest string located before a given string in directory
865 * sort order (i.e. first files, then directories, then drives).
867 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
869 INT min, max, res = -1;
871 if (!HAS_STRINGS(descr))
872 return LISTBOX_FindStringPos( descr, str, FALSE );
873 min = 0;
874 max = descr->nb_items;
875 while (min != max)
877 INT index = (min + max) / 2;
878 LPCWSTR p = descr->items[index].str;
879 if (*p == '[') /* drive or directory */
881 if (*str != '[') res = -1;
882 else if (p[1] == '-') /* drive */
884 if (str[1] == '-') res = str[2] - p[2];
885 else res = -1;
887 else /* directory */
889 if (str[1] == '-') res = 1;
890 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
893 else /* filename */
895 if (*str == '[') res = 1;
896 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
898 if (!res) return index;
899 if (res < 0) max = index;
900 else min = index + 1;
902 return max;
906 /***********************************************************************
907 * LISTBOX_FindString
909 * Find the item beginning with a given string.
911 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
913 INT i;
914 LB_ITEMDATA *item;
916 if (start >= descr->nb_items) start = -1;
917 item = descr->items + start + 1;
918 if (HAS_STRINGS(descr))
920 if (!str || ! str[0] ) return LB_ERR;
921 if (exact)
923 for (i = start + 1; i < descr->nb_items; i++, item++)
924 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
925 for (i = 0, item = descr->items; i <= start; i++, item++)
926 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
928 else
930 /* Special case for drives and directories: ignore prefix */
931 #define CHECK_DRIVE(item) \
932 if ((item)->str[0] == '[') \
934 if (!strncmpiW( str, (item)->str+1, len )) return i; \
935 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
936 return i; \
939 INT len = strlenW(str);
940 for (i = start + 1; i < descr->nb_items; i++, item++)
942 if (!strncmpiW( str, item->str, len )) return i;
943 CHECK_DRIVE(item);
945 for (i = 0, item = descr->items; i <= start; i++, item++)
947 if (!strncmpiW( str, item->str, len )) return i;
948 CHECK_DRIVE(item);
950 #undef CHECK_DRIVE
953 else
955 if (exact && (descr->style & LBS_SORT))
956 /* If sorted, use a WM_COMPAREITEM binary search */
957 return LISTBOX_FindStringPos( descr, str, TRUE );
959 /* Otherwise use a linear search */
960 for (i = start + 1; i < descr->nb_items; i++, item++)
961 if (item->data == (ULONG_PTR)str) return i;
962 for (i = 0, item = descr->items; i <= start; i++, item++)
963 if (item->data == (ULONG_PTR)str) return i;
965 return LB_ERR;
969 /***********************************************************************
970 * LISTBOX_GetSelCount
972 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
974 INT i, count;
975 const LB_ITEMDATA *item = descr->items;
977 if (!(descr->style & LBS_MULTIPLESEL) ||
978 (descr->style & LBS_NOSEL))
979 return LB_ERR;
980 for (i = count = 0; i < descr->nb_items; i++, item++)
981 if (item->selected) count++;
982 return count;
986 /***********************************************************************
987 * LISTBOX_GetSelItems
989 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
991 INT i, count;
992 const LB_ITEMDATA *item = descr->items;
994 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
995 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
996 if (item->selected) array[count++] = i;
997 return count;
1001 /***********************************************************************
1002 * LISTBOX_Paint
1004 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1006 INT i, col_pos = descr->page_size - 1;
1007 RECT rect;
1008 RECT focusRect = {-1, -1, -1, -1};
1009 HFONT oldFont = 0;
1010 HBRUSH hbrush, oldBrush = 0;
1012 if (descr->style & LBS_NOREDRAW) return 0;
1014 SetRect( &rect, 0, 0, descr->width, descr->height );
1015 if (descr->style & LBS_MULTICOLUMN)
1016 rect.right = rect.left + descr->column_width;
1017 else if (descr->horz_pos)
1019 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1020 rect.right += descr->horz_pos;
1023 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1024 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1025 (WPARAM)hdc, (LPARAM)descr->self );
1026 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1027 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1029 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1030 (descr->in_focus))
1032 /* Special case for empty listbox: paint focus rect */
1033 rect.bottom = rect.top + descr->item_height;
1034 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1035 &rect, NULL, 0, NULL );
1036 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1037 rect.top = rect.bottom;
1040 /* Paint all the item, regarding the selection
1041 Focus state will be painted after */
1043 for (i = descr->top_item; i < descr->nb_items; i++)
1045 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1046 rect.bottom = rect.top + descr->item_height;
1047 else
1048 rect.bottom = rect.top + descr->items[i].height;
1050 if (i == descr->focus_item)
1052 /* keep the focus rect, to paint the focus item after */
1053 focusRect.left = rect.left;
1054 focusRect.right = rect.right;
1055 focusRect.top = rect.top;
1056 focusRect.bottom = rect.bottom;
1058 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1059 rect.top = rect.bottom;
1061 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1063 if (!IS_OWNERDRAW(descr))
1065 /* Clear the bottom of the column */
1066 if (rect.top < descr->height)
1068 rect.bottom = descr->height;
1069 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1070 &rect, NULL, 0, NULL );
1074 /* Go to the next column */
1075 rect.left += descr->column_width;
1076 rect.right += descr->column_width;
1077 rect.top = 0;
1078 col_pos = descr->page_size - 1;
1080 else
1082 col_pos--;
1083 if (rect.top >= descr->height) break;
1087 /* Paint the focus item now */
1088 if (focusRect.top != focusRect.bottom &&
1089 descr->caret_on && descr->in_focus)
1090 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1092 if (!IS_OWNERDRAW(descr))
1094 /* Clear the remainder of the client area */
1095 if (rect.top < descr->height)
1097 rect.bottom = descr->height;
1098 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1099 &rect, NULL, 0, NULL );
1101 if (rect.right < descr->width)
1103 rect.left = rect.right;
1104 rect.right = descr->width;
1105 rect.top = 0;
1106 rect.bottom = descr->height;
1107 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1108 &rect, NULL, 0, NULL );
1111 if (oldFont) SelectObject( hdc, oldFont );
1112 if (oldBrush) SelectObject( hdc, oldBrush );
1113 return 0;
1117 /***********************************************************************
1118 * LISTBOX_InvalidateItems
1120 * Invalidate all items from a given item. If the specified item is not
1121 * visible, nothing happens.
1123 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1125 RECT rect;
1127 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1129 if (descr->style & LBS_NOREDRAW)
1131 descr->style |= LBS_DISPLAYCHANGED;
1132 return;
1134 rect.bottom = descr->height;
1135 InvalidateRect( descr->self, &rect, TRUE );
1136 if (descr->style & LBS_MULTICOLUMN)
1138 /* Repaint the other columns */
1139 rect.left = rect.right;
1140 rect.right = descr->width;
1141 rect.top = 0;
1142 InvalidateRect( descr->self, &rect, TRUE );
1147 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1149 RECT rect;
1151 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1152 InvalidateRect( descr->self, &rect, TRUE );
1155 /***********************************************************************
1156 * LISTBOX_GetItemHeight
1158 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1160 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1162 if ((index < 0) || (index >= descr->nb_items))
1164 SetLastError(ERROR_INVALID_INDEX);
1165 return LB_ERR;
1167 return descr->items[index].height;
1169 else return descr->item_height;
1173 /***********************************************************************
1174 * LISTBOX_SetItemHeight
1176 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1178 if (height > MAXBYTE)
1179 return -1;
1181 if (!height) height = 1;
1183 if (descr->style & LBS_OWNERDRAWVARIABLE)
1185 if ((index < 0) || (index >= descr->nb_items))
1187 SetLastError(ERROR_INVALID_INDEX);
1188 return LB_ERR;
1190 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1191 descr->items[index].height = height;
1192 LISTBOX_UpdateScroll( descr );
1193 if (repaint)
1194 LISTBOX_InvalidateItems( descr, index );
1196 else if (height != descr->item_height)
1198 TRACE("[%p]: new height = %d\n", descr->self, height );
1199 descr->item_height = height;
1200 LISTBOX_UpdatePage( descr );
1201 LISTBOX_UpdateScroll( descr );
1202 if (repaint)
1203 InvalidateRect( descr->self, 0, TRUE );
1205 return LB_OKAY;
1209 /***********************************************************************
1210 * LISTBOX_SetHorizontalPos
1212 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1214 INT diff;
1216 if (pos > descr->horz_extent - descr->width)
1217 pos = descr->horz_extent - descr->width;
1218 if (pos < 0) pos = 0;
1219 if (!(diff = descr->horz_pos - pos)) return;
1220 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1221 descr->horz_pos = pos;
1222 LISTBOX_UpdateScroll( descr );
1223 if (abs(diff) < descr->width)
1225 RECT rect;
1226 /* Invalidate the focused item so it will be repainted correctly */
1227 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1228 InvalidateRect( descr->self, &rect, TRUE );
1229 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1230 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1232 else
1233 InvalidateRect( descr->self, NULL, TRUE );
1237 /***********************************************************************
1238 * LISTBOX_SetHorizontalExtent
1240 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1242 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1243 return LB_OKAY;
1244 if (extent <= 0) extent = 1;
1245 if (extent == descr->horz_extent) return LB_OKAY;
1246 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1247 descr->horz_extent = extent;
1248 if (descr->horz_pos > extent - descr->width)
1249 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1250 else
1251 LISTBOX_UpdateScroll( descr );
1252 return LB_OKAY;
1256 /***********************************************************************
1257 * LISTBOX_SetColumnWidth
1259 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1261 if (width == descr->column_width) return LB_OKAY;
1262 TRACE("[%p]: new column width = %d\n", descr->self, width );
1263 descr->column_width = width;
1264 LISTBOX_UpdatePage( descr );
1265 return LB_OKAY;
1269 /***********************************************************************
1270 * LISTBOX_SetFont
1272 * Returns the item height.
1274 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1276 HDC hdc;
1277 HFONT oldFont = 0;
1278 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1279 SIZE sz;
1281 descr->font = font;
1283 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1285 ERR("unable to get DC.\n" );
1286 return 16;
1288 if (font) oldFont = SelectObject( hdc, font );
1289 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1290 if (oldFont) SelectObject( hdc, oldFont );
1291 ReleaseDC( descr->self, hdc );
1293 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1294 if (!IS_OWNERDRAW(descr))
1295 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1296 return sz.cy;
1300 /***********************************************************************
1301 * LISTBOX_MakeItemVisible
1303 * Make sure that a given item is partially or fully visible.
1305 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1307 INT top;
1309 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1311 if (index <= descr->top_item) top = index;
1312 else if (descr->style & LBS_MULTICOLUMN)
1314 INT cols = descr->width;
1315 if (!fully) cols += descr->column_width - 1;
1316 if (cols >= descr->column_width) cols /= descr->column_width;
1317 else cols = 1;
1318 if (index < descr->top_item + (descr->page_size * cols)) return;
1319 top = index - descr->page_size * (cols - 1);
1321 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1323 INT height = fully ? descr->items[index].height : 1;
1324 for (top = index; top > descr->top_item; top--)
1325 if ((height += descr->items[top-1].height) > descr->height) break;
1327 else
1329 if (index < descr->top_item + descr->page_size) return;
1330 if (!fully && (index == descr->top_item + descr->page_size) &&
1331 (descr->height > (descr->page_size * descr->item_height))) return;
1332 top = index - descr->page_size + 1;
1334 LISTBOX_SetTopItem( descr, top, TRUE );
1337 /***********************************************************************
1338 * LISTBOX_SetCaretIndex
1340 * NOTES
1341 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1344 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1346 INT oldfocus = descr->focus_item;
1348 TRACE("old focus %d, index %d\n", oldfocus, index);
1350 if (descr->style & LBS_NOSEL) return LB_ERR;
1351 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1352 if (index == oldfocus) return LB_OKAY;
1354 LISTBOX_DrawFocusRect( descr, FALSE );
1355 descr->focus_item = index;
1357 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1358 LISTBOX_DrawFocusRect( descr, TRUE );
1360 return LB_OKAY;
1364 /***********************************************************************
1365 * LISTBOX_SelectItemRange
1367 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1369 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1370 INT last, BOOL on )
1372 INT i;
1374 /* A few sanity checks */
1376 if (descr->style & LBS_NOSEL) return LB_ERR;
1377 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1379 if (!descr->nb_items) return LB_OKAY;
1381 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1382 if (first < 0) first = 0;
1383 if (last < first) return LB_OKAY;
1385 if (on) /* Turn selection on */
1387 for (i = first; i <= last; i++)
1389 if (descr->items[i].selected) continue;
1390 descr->items[i].selected = TRUE;
1391 LISTBOX_InvalidateItemRect(descr, i);
1394 else /* Turn selection off */
1396 for (i = first; i <= last; i++)
1398 if (!descr->items[i].selected) continue;
1399 descr->items[i].selected = FALSE;
1400 LISTBOX_InvalidateItemRect(descr, i);
1403 return LB_OKAY;
1406 /***********************************************************************
1407 * LISTBOX_SetSelection
1409 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1410 BOOL on, BOOL send_notify )
1412 TRACE( "cur_sel=%d index=%d notify=%s\n",
1413 descr->selected_item, index, send_notify ? "YES" : "NO" );
1415 if (descr->style & LBS_NOSEL)
1417 descr->selected_item = index;
1418 return LB_ERR;
1420 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1421 if (descr->style & LBS_MULTIPLESEL)
1423 if (index == -1) /* Select all items */
1424 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1425 else /* Only one item */
1426 return LISTBOX_SelectItemRange( descr, index, index, on );
1428 else
1430 INT oldsel = descr->selected_item;
1431 if (index == oldsel) return LB_OKAY;
1432 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1433 if (index != -1) descr->items[index].selected = TRUE;
1434 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1435 descr->selected_item = index;
1436 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1437 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1438 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1439 else
1440 if( descr->lphc ) /* set selection change flag for parent combo */
1441 descr->lphc->wState |= CBF_SELCHANGE;
1443 return LB_OKAY;
1447 /***********************************************************************
1448 * LISTBOX_MoveCaret
1450 * Change the caret position and extend the selection to the new caret.
1452 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1454 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1456 if ((index < 0) || (index >= descr->nb_items))
1457 return;
1459 /* Important, repaint needs to be done in this order if
1460 you want to mimic Windows behavior:
1461 1. Remove the focus and paint the item
1462 2. Remove the selection and paint the item(s)
1463 3. Set the selection and repaint the item(s)
1464 4. Set the focus to 'index' and repaint the item */
1466 /* 1. remove the focus and repaint the item */
1467 LISTBOX_DrawFocusRect( descr, FALSE );
1469 /* 2. then turn off the previous selection */
1470 /* 3. repaint the new selected item */
1471 if (descr->style & LBS_EXTENDEDSEL)
1473 if (descr->anchor_item != -1)
1475 INT first = min( index, descr->anchor_item );
1476 INT last = max( index, descr->anchor_item );
1477 if (first > 0)
1478 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1479 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1480 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1483 else if (!(descr->style & LBS_MULTIPLESEL))
1485 /* Set selection to new caret item */
1486 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1489 /* 4. repaint the new item with the focus */
1490 descr->focus_item = index;
1491 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1492 LISTBOX_DrawFocusRect( descr, TRUE );
1496 /***********************************************************************
1497 * LISTBOX_InsertItem
1499 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1500 LPWSTR str, ULONG_PTR data )
1502 LB_ITEMDATA *item;
1503 INT max_items;
1504 INT oldfocus = descr->focus_item;
1506 if (index == -1) index = descr->nb_items;
1507 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1508 if (!descr->items) max_items = 0;
1509 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1510 if (descr->nb_items == max_items)
1512 /* We need to grow the array */
1513 max_items += LB_ARRAY_GRANULARITY;
1514 if (descr->items)
1515 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1516 max_items * sizeof(LB_ITEMDATA) );
1517 else
1518 item = HeapAlloc( GetProcessHeap(), 0,
1519 max_items * sizeof(LB_ITEMDATA) );
1520 if (!item)
1522 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1523 return LB_ERRSPACE;
1525 descr->items = item;
1528 /* Insert the item structure */
1530 item = &descr->items[index];
1531 if (index < descr->nb_items)
1532 RtlMoveMemory( item + 1, item,
1533 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1534 item->str = str;
1535 item->data = data;
1536 item->height = 0;
1537 item->selected = FALSE;
1538 descr->nb_items++;
1540 /* Get item height */
1542 if (descr->style & LBS_OWNERDRAWVARIABLE)
1544 MEASUREITEMSTRUCT mis;
1545 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1547 mis.CtlType = ODT_LISTBOX;
1548 mis.CtlID = id;
1549 mis.itemID = index;
1550 mis.itemData = descr->items[index].data;
1551 mis.itemHeight = descr->item_height;
1552 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1553 item->height = mis.itemHeight ? mis.itemHeight : 1;
1554 TRACE("[%p]: measure item %d (%s) = %d\n",
1555 descr->self, index, str ? debugstr_w(str) : "", item->height );
1558 /* Repaint the items */
1560 LISTBOX_UpdateScroll( descr );
1561 LISTBOX_InvalidateItems( descr, index );
1563 /* Move selection and focused item */
1564 /* If listbox was empty, set focus to the first item */
1565 if (descr->nb_items == 1)
1566 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1567 /* single select don't change selection index in win31 */
1568 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1570 descr->selected_item++;
1571 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1573 else
1575 if (index <= descr->selected_item)
1577 descr->selected_item++;
1578 descr->focus_item = oldfocus; /* focus not changed */
1581 return LB_OKAY;
1585 /***********************************************************************
1586 * LISTBOX_InsertString
1588 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1590 LPWSTR new_str = NULL;
1591 ULONG_PTR data = 0;
1592 LRESULT ret;
1594 if (HAS_STRINGS(descr))
1596 static const WCHAR empty_stringW[] = { 0 };
1597 if (!str) str = empty_stringW;
1598 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1600 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1601 return LB_ERRSPACE;
1603 strcpyW(new_str, str);
1605 else data = (ULONG_PTR)str;
1607 if (index == -1) index = descr->nb_items;
1608 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1610 HeapFree( GetProcessHeap(), 0, new_str );
1611 return ret;
1614 TRACE("[%p]: added item %d %s\n",
1615 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1616 return index;
1620 /***********************************************************************
1621 * LISTBOX_DeleteItem
1623 * Delete the content of an item. 'index' must be a valid index.
1625 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1627 /* save the item data before it gets freed by LB_RESETCONTENT */
1628 ULONG_PTR item_data = descr->items[index].data;
1629 LPWSTR item_str = descr->items[index].str;
1631 if (!descr->nb_items)
1632 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1634 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1635 * while Win95 sends it for all items with user data.
1636 * It's probably better to send it too often than not
1637 * often enough, so this is what we do here.
1639 if (IS_OWNERDRAW(descr) || item_data)
1641 DELETEITEMSTRUCT dis;
1642 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1644 dis.CtlType = ODT_LISTBOX;
1645 dis.CtlID = id;
1646 dis.itemID = index;
1647 dis.hwndItem = descr->self;
1648 dis.itemData = item_data;
1649 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1651 if (HAS_STRINGS(descr))
1652 HeapFree( GetProcessHeap(), 0, item_str );
1656 /***********************************************************************
1657 * LISTBOX_RemoveItem
1659 * Remove an item from the listbox and delete its content.
1661 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1663 LB_ITEMDATA *item;
1664 INT max_items;
1666 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1668 /* We need to invalidate the original rect instead of the updated one. */
1669 LISTBOX_InvalidateItems( descr, index );
1671 descr->nb_items--;
1672 LISTBOX_DeleteItem( descr, index );
1674 if (!descr->nb_items) return LB_OKAY;
1676 /* Remove the item */
1678 item = &descr->items[index];
1679 if (index < descr->nb_items)
1680 RtlMoveMemory( item, item + 1,
1681 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1682 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1684 /* Shrink the item array if possible */
1686 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1687 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1689 max_items -= LB_ARRAY_GRANULARITY;
1690 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1691 max_items * sizeof(LB_ITEMDATA) );
1692 if (item) descr->items = item;
1694 /* Repaint the items */
1696 LISTBOX_UpdateScroll( descr );
1697 /* if we removed the scrollbar, reset the top of the list
1698 (correct for owner-drawn ???) */
1699 if (descr->nb_items == descr->page_size)
1700 LISTBOX_SetTopItem( descr, 0, TRUE );
1702 /* Move selection and focused item */
1703 if (!IS_MULTISELECT(descr))
1705 if (index == descr->selected_item)
1706 descr->selected_item = -1;
1707 else if (index < descr->selected_item)
1709 descr->selected_item--;
1710 if (ISWIN31) /* win 31 do not change the selected item number */
1711 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1715 if (descr->focus_item >= descr->nb_items)
1717 descr->focus_item = descr->nb_items - 1;
1718 if (descr->focus_item < 0) descr->focus_item = 0;
1720 return LB_OKAY;
1724 /***********************************************************************
1725 * LISTBOX_ResetContent
1727 static void LISTBOX_ResetContent( LB_DESCR *descr )
1729 INT i;
1731 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1732 HeapFree( GetProcessHeap(), 0, descr->items );
1733 descr->nb_items = 0;
1734 descr->top_item = 0;
1735 descr->selected_item = -1;
1736 descr->focus_item = 0;
1737 descr->anchor_item = -1;
1738 descr->items = NULL;
1742 /***********************************************************************
1743 * LISTBOX_SetCount
1745 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1747 LRESULT ret;
1749 if (HAS_STRINGS(descr))
1751 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1752 return LB_ERR;
1755 /* FIXME: this is far from optimal... */
1756 if (count > descr->nb_items)
1758 while (count > descr->nb_items)
1759 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1760 return ret;
1762 else if (count < descr->nb_items)
1764 while (count < descr->nb_items)
1765 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1766 return ret;
1769 InvalidateRect( descr->self, NULL, TRUE );
1770 return LB_OKAY;
1774 /***********************************************************************
1775 * LISTBOX_Directory
1777 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1778 LPCWSTR filespec, BOOL long_names )
1780 HANDLE handle;
1781 LRESULT ret = LB_OKAY;
1782 WIN32_FIND_DATAW entry;
1783 int pos;
1784 LRESULT maxinsert = LB_ERR;
1786 /* don't scan directory if we just want drives exclusively */
1787 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1788 /* scan directory */
1789 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1791 int le = GetLastError();
1792 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1794 else
1798 WCHAR buffer[270];
1799 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1801 static const WCHAR bracketW[] = { ']',0 };
1802 static const WCHAR dotW[] = { '.',0 };
1803 if (!(attrib & DDL_DIRECTORY) ||
1804 !strcmpW( entry.cFileName, dotW )) continue;
1805 buffer[0] = '[';
1806 if (!long_names && entry.cAlternateFileName[0])
1807 strcpyW( buffer + 1, entry.cAlternateFileName );
1808 else
1809 strcpyW( buffer + 1, entry.cFileName );
1810 strcatW(buffer, bracketW);
1812 else /* not a directory */
1814 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1815 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1817 if ((attrib & DDL_EXCLUSIVE) &&
1818 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1819 continue;
1820 #undef ATTRIBS
1821 if (!long_names && entry.cAlternateFileName[0])
1822 strcpyW( buffer, entry.cAlternateFileName );
1823 else
1824 strcpyW( buffer, entry.cFileName );
1826 if (!long_names) CharLowerW( buffer );
1827 pos = LISTBOX_FindFileStrPos( descr, buffer );
1828 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1829 break;
1830 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1831 } while (FindNextFileW( handle, &entry ));
1832 FindClose( handle );
1835 if (ret >= 0)
1837 ret = maxinsert;
1839 /* scan drives */
1840 if (attrib & DDL_DRIVES)
1842 WCHAR buffer[] = {'[','-','a','-',']',0};
1843 WCHAR root[] = {'A',':','\\',0};
1844 int drive;
1845 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1847 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1848 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1849 break;
1853 return ret;
1857 /***********************************************************************
1858 * LISTBOX_HandleVScroll
1860 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1862 SCROLLINFO info;
1864 if (descr->style & LBS_MULTICOLUMN) return 0;
1865 switch(scrollReq)
1867 case SB_LINEUP:
1868 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1869 break;
1870 case SB_LINEDOWN:
1871 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1872 break;
1873 case SB_PAGEUP:
1874 LISTBOX_SetTopItem( descr, descr->top_item -
1875 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1876 break;
1877 case SB_PAGEDOWN:
1878 LISTBOX_SetTopItem( descr, descr->top_item +
1879 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1880 break;
1881 case SB_THUMBPOSITION:
1882 LISTBOX_SetTopItem( descr, pos, TRUE );
1883 break;
1884 case SB_THUMBTRACK:
1885 info.cbSize = sizeof(info);
1886 info.fMask = SIF_TRACKPOS;
1887 GetScrollInfo( descr->self, SB_VERT, &info );
1888 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1889 break;
1890 case SB_TOP:
1891 LISTBOX_SetTopItem( descr, 0, TRUE );
1892 break;
1893 case SB_BOTTOM:
1894 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1895 break;
1897 return 0;
1901 /***********************************************************************
1902 * LISTBOX_HandleHScroll
1904 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1906 SCROLLINFO info;
1907 INT page;
1909 if (descr->style & LBS_MULTICOLUMN)
1911 switch(scrollReq)
1913 case SB_LINELEFT:
1914 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1915 TRUE );
1916 break;
1917 case SB_LINERIGHT:
1918 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1919 TRUE );
1920 break;
1921 case SB_PAGELEFT:
1922 page = descr->width / descr->column_width;
1923 if (page < 1) page = 1;
1924 LISTBOX_SetTopItem( descr,
1925 descr->top_item - page * descr->page_size, TRUE );
1926 break;
1927 case SB_PAGERIGHT:
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_THUMBPOSITION:
1934 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1935 break;
1936 case SB_THUMBTRACK:
1937 info.cbSize = sizeof(info);
1938 info.fMask = SIF_TRACKPOS;
1939 GetScrollInfo( descr->self, SB_VERT, &info );
1940 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1941 TRUE );
1942 break;
1943 case SB_LEFT:
1944 LISTBOX_SetTopItem( descr, 0, TRUE );
1945 break;
1946 case SB_RIGHT:
1947 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1948 break;
1951 else if (descr->horz_extent)
1953 switch(scrollReq)
1955 case SB_LINELEFT:
1956 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1957 break;
1958 case SB_LINERIGHT:
1959 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1960 break;
1961 case SB_PAGELEFT:
1962 LISTBOX_SetHorizontalPos( descr,
1963 descr->horz_pos - descr->width );
1964 break;
1965 case SB_PAGERIGHT:
1966 LISTBOX_SetHorizontalPos( descr,
1967 descr->horz_pos + descr->width );
1968 break;
1969 case SB_THUMBPOSITION:
1970 LISTBOX_SetHorizontalPos( descr, pos );
1971 break;
1972 case SB_THUMBTRACK:
1973 info.cbSize = sizeof(info);
1974 info.fMask = SIF_TRACKPOS;
1975 GetScrollInfo( descr->self, SB_HORZ, &info );
1976 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1977 break;
1978 case SB_LEFT:
1979 LISTBOX_SetHorizontalPos( descr, 0 );
1980 break;
1981 case SB_RIGHT:
1982 LISTBOX_SetHorizontalPos( descr,
1983 descr->horz_extent - descr->width );
1984 break;
1987 return 0;
1990 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1992 UINT pulScrollLines = 3;
1994 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1996 /* if scrolling changes direction, ignore left overs */
1997 if ((delta < 0 && descr->wheel_remain < 0) ||
1998 (delta > 0 && descr->wheel_remain > 0))
1999 descr->wheel_remain += delta;
2000 else
2001 descr->wheel_remain = delta;
2003 if (descr->wheel_remain && pulScrollLines)
2005 int cLineScroll;
2006 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2007 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2008 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2009 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2011 return 0;
2014 /***********************************************************************
2015 * LISTBOX_HandleLButtonDown
2017 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2019 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2021 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2022 descr->self, x, y, index, descr->focus_item);
2024 if (!descr->caret_on && (descr->in_focus)) return 0;
2026 if (!descr->in_focus)
2028 if( !descr->lphc ) SetFocus( descr->self );
2029 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2032 if (index == -1) return 0;
2034 if (!descr->lphc)
2036 if (descr->style & LBS_NOTIFY )
2037 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2038 MAKELPARAM( x, y ) );
2041 descr->captured = TRUE;
2042 SetCapture( descr->self );
2044 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2046 /* we should perhaps make sure that all items are deselected
2047 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2048 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2049 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2052 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2053 if (keys & MK_CONTROL)
2055 LISTBOX_SetCaretIndex( descr, index, FALSE );
2056 LISTBOX_SetSelection( descr, index,
2057 !descr->items[index].selected,
2058 (descr->style & LBS_NOTIFY) != 0);
2060 else
2062 LISTBOX_MoveCaret( descr, index, FALSE );
2064 if (descr->style & LBS_EXTENDEDSEL)
2066 LISTBOX_SetSelection( descr, index,
2067 descr->items[index].selected,
2068 (descr->style & LBS_NOTIFY) != 0 );
2070 else
2072 LISTBOX_SetSelection( descr, index,
2073 !descr->items[index].selected,
2074 (descr->style & LBS_NOTIFY) != 0 );
2078 else
2080 descr->anchor_item = index;
2081 LISTBOX_MoveCaret( descr, index, FALSE );
2082 LISTBOX_SetSelection( descr, index,
2083 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2086 if (!descr->lphc)
2088 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2090 POINT pt;
2092 pt.x = x;
2093 pt.y = y;
2095 if (DragDetect( descr->self, pt ))
2096 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2099 return 0;
2103 /*************************************************************************
2104 * LISTBOX_HandleLButtonDownCombo [Internal]
2106 * Process LButtonDown message for the ComboListBox
2108 * PARAMS
2109 * pWnd [I] The windows internal structure
2110 * pDescr [I] The ListBox internal structure
2111 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2112 * x [I] X Mouse Coordinate
2113 * y [I] Y Mouse Coordinate
2115 * RETURNS
2116 * 0 since we are processing the WM_LBUTTONDOWN Message
2118 * NOTES
2119 * This function is only to be used when a ListBox is a ComboListBox
2122 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2124 RECT clientRect, screenRect;
2125 POINT mousePos;
2127 mousePos.x = x;
2128 mousePos.y = y;
2130 GetClientRect(descr->self, &clientRect);
2132 if(PtInRect(&clientRect, mousePos))
2134 /* MousePos is in client, resume normal processing */
2135 if (msg == WM_LBUTTONDOWN)
2137 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2138 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2140 else if (descr->style & LBS_NOTIFY)
2141 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2143 else
2145 POINT screenMousePos;
2146 HWND hWndOldCapture;
2148 /* Check the Non-Client Area */
2149 screenMousePos = mousePos;
2150 hWndOldCapture = GetCapture();
2151 ReleaseCapture();
2152 GetWindowRect(descr->self, &screenRect);
2153 ClientToScreen(descr->self, &screenMousePos);
2155 if(!PtInRect(&screenRect, screenMousePos))
2157 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2158 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2159 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2161 else
2163 /* Check to see the NC is a scrollbar */
2164 INT nHitTestType=0;
2165 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2166 /* Check Vertical scroll bar */
2167 if (style & WS_VSCROLL)
2169 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2170 if (PtInRect( &clientRect, mousePos ))
2171 nHitTestType = HTVSCROLL;
2173 /* Check horizontal scroll bar */
2174 if (style & WS_HSCROLL)
2176 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2177 if (PtInRect( &clientRect, mousePos ))
2178 nHitTestType = HTHSCROLL;
2180 /* Windows sends this message when a scrollbar is clicked
2183 if(nHitTestType != 0)
2185 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2186 MAKELONG(screenMousePos.x, screenMousePos.y));
2188 /* Resume the Capture after scrolling is complete
2190 if(hWndOldCapture != 0)
2191 SetCapture(hWndOldCapture);
2194 return 0;
2197 /***********************************************************************
2198 * LISTBOX_HandleLButtonUp
2200 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2202 if (LISTBOX_Timer != LB_TIMER_NONE)
2203 KillSystemTimer( descr->self, LB_TIMER_ID );
2204 LISTBOX_Timer = LB_TIMER_NONE;
2205 if (descr->captured)
2207 descr->captured = FALSE;
2208 if (GetCapture() == descr->self) ReleaseCapture();
2209 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2210 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2212 return 0;
2216 /***********************************************************************
2217 * LISTBOX_HandleTimer
2219 * Handle scrolling upon a timer event.
2220 * Return TRUE if scrolling should continue.
2222 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2224 switch(dir)
2226 case LB_TIMER_UP:
2227 if (descr->top_item) index = descr->top_item - 1;
2228 else index = 0;
2229 break;
2230 case LB_TIMER_LEFT:
2231 if (descr->top_item) index -= descr->page_size;
2232 break;
2233 case LB_TIMER_DOWN:
2234 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2235 if (index == descr->focus_item) index++;
2236 if (index >= descr->nb_items) index = descr->nb_items - 1;
2237 break;
2238 case LB_TIMER_RIGHT:
2239 if (index + descr->page_size < descr->nb_items)
2240 index += descr->page_size;
2241 break;
2242 case LB_TIMER_NONE:
2243 break;
2245 if (index == descr->focus_item) return FALSE;
2246 LISTBOX_MoveCaret( descr, index, FALSE );
2247 return TRUE;
2251 /***********************************************************************
2252 * LISTBOX_HandleSystemTimer
2254 * WM_SYSTIMER handler.
2256 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2258 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2260 KillSystemTimer( descr->self, LB_TIMER_ID );
2261 LISTBOX_Timer = LB_TIMER_NONE;
2263 return 0;
2267 /***********************************************************************
2268 * LISTBOX_HandleMouseMove
2270 * WM_MOUSEMOVE handler.
2272 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2273 INT x, INT y )
2275 INT index;
2276 TIMER_DIRECTION dir = LB_TIMER_NONE;
2278 if (!descr->captured) return;
2280 if (descr->style & LBS_MULTICOLUMN)
2282 if (y < 0) y = 0;
2283 else if (y >= descr->item_height * descr->page_size)
2284 y = descr->item_height * descr->page_size - 1;
2286 if (x < 0)
2288 dir = LB_TIMER_LEFT;
2289 x = 0;
2291 else if (x >= descr->width)
2293 dir = LB_TIMER_RIGHT;
2294 x = descr->width - 1;
2297 else
2299 if (y < 0) dir = LB_TIMER_UP; /* above */
2300 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2303 index = LISTBOX_GetItemFromPoint( descr, x, y );
2304 if (index == -1) index = descr->focus_item;
2305 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2307 /* Start/stop the system timer */
2309 if (dir != LB_TIMER_NONE)
2310 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2311 else if (LISTBOX_Timer != LB_TIMER_NONE)
2312 KillSystemTimer( descr->self, LB_TIMER_ID );
2313 LISTBOX_Timer = dir;
2317 /***********************************************************************
2318 * LISTBOX_HandleKeyDown
2320 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2322 INT caret = -1;
2323 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2324 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2325 bForceSelection = FALSE; /* only for single select list */
2327 if (descr->style & LBS_WANTKEYBOARDINPUT)
2329 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2330 MAKEWPARAM(LOWORD(key), descr->focus_item),
2331 (LPARAM)descr->self );
2332 if (caret == -2) return 0;
2334 if (caret == -1) switch(key)
2336 case VK_LEFT:
2337 if (descr->style & LBS_MULTICOLUMN)
2339 bForceSelection = FALSE;
2340 if (descr->focus_item >= descr->page_size)
2341 caret = descr->focus_item - descr->page_size;
2342 break;
2344 /* fall through */
2345 case VK_UP:
2346 caret = descr->focus_item - 1;
2347 if (caret < 0) caret = 0;
2348 break;
2349 case VK_RIGHT:
2350 if (descr->style & LBS_MULTICOLUMN)
2352 bForceSelection = FALSE;
2353 if (descr->focus_item + descr->page_size < descr->nb_items)
2354 caret = descr->focus_item + descr->page_size;
2355 break;
2357 /* fall through */
2358 case VK_DOWN:
2359 caret = descr->focus_item + 1;
2360 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2361 break;
2363 case VK_PRIOR:
2364 if (descr->style & LBS_MULTICOLUMN)
2366 INT page = descr->width / descr->column_width;
2367 if (page < 1) page = 1;
2368 caret = descr->focus_item - (page * descr->page_size) + 1;
2370 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2371 if (caret < 0) caret = 0;
2372 break;
2373 case VK_NEXT:
2374 if (descr->style & LBS_MULTICOLUMN)
2376 INT page = descr->width / descr->column_width;
2377 if (page < 1) page = 1;
2378 caret = descr->focus_item + (page * descr->page_size) - 1;
2380 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2381 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2382 break;
2383 case VK_HOME:
2384 caret = 0;
2385 break;
2386 case VK_END:
2387 caret = descr->nb_items - 1;
2388 break;
2389 case VK_SPACE:
2390 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2391 else if (descr->style & LBS_MULTIPLESEL)
2393 LISTBOX_SetSelection( descr, descr->focus_item,
2394 !descr->items[descr->focus_item].selected,
2395 (descr->style & LBS_NOTIFY) != 0 );
2397 break;
2398 default:
2399 bForceSelection = FALSE;
2401 if (bForceSelection) /* focused item is used instead of key */
2402 caret = descr->focus_item;
2403 if (caret >= 0)
2405 if (((descr->style & LBS_EXTENDEDSEL) &&
2406 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2407 !IS_MULTISELECT(descr))
2408 descr->anchor_item = caret;
2409 LISTBOX_MoveCaret( descr, caret, TRUE );
2411 if (descr->style & LBS_MULTIPLESEL)
2412 descr->selected_item = caret;
2413 else
2414 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2415 if (descr->style & LBS_NOTIFY)
2417 if (descr->lphc && IsWindowVisible( descr->self ))
2419 /* make sure that combo parent doesn't hide us */
2420 descr->lphc->wState |= CBF_NOROLLUP;
2422 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2425 return 0;
2429 /***********************************************************************
2430 * LISTBOX_HandleChar
2432 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2434 INT caret = -1;
2435 WCHAR str[2];
2437 str[0] = charW;
2438 str[1] = '\0';
2440 if (descr->style & LBS_WANTKEYBOARDINPUT)
2442 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2443 MAKEWPARAM(charW, descr->focus_item),
2444 (LPARAM)descr->self );
2445 if (caret == -2) return 0;
2447 if (caret == -1)
2448 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2449 if (caret != -1)
2451 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2452 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2453 LISTBOX_MoveCaret( descr, caret, TRUE );
2454 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2455 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2457 return 0;
2461 /***********************************************************************
2462 * LISTBOX_Create
2464 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2466 LB_DESCR *descr;
2467 MEASUREITEMSTRUCT mis;
2468 RECT rect;
2470 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2471 return FALSE;
2473 GetClientRect( hwnd, &rect );
2474 descr->self = hwnd;
2475 descr->owner = GetParent( descr->self );
2476 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2477 descr->width = rect.right - rect.left;
2478 descr->height = rect.bottom - rect.top;
2479 descr->items = NULL;
2480 descr->nb_items = 0;
2481 descr->top_item = 0;
2482 descr->selected_item = -1;
2483 descr->focus_item = 0;
2484 descr->anchor_item = -1;
2485 descr->item_height = 1;
2486 descr->page_size = 1;
2487 descr->column_width = 150;
2488 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2489 descr->horz_pos = 0;
2490 descr->nb_tabs = 0;
2491 descr->tabs = NULL;
2492 descr->wheel_remain = 0;
2493 descr->caret_on = !lphc;
2494 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2495 descr->in_focus = FALSE;
2496 descr->captured = FALSE;
2497 descr->font = 0;
2498 descr->locale = GetUserDefaultLCID();
2499 descr->lphc = lphc;
2501 if( lphc )
2503 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2504 descr->owner = lphc->self;
2507 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2509 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2511 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2512 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2513 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2514 descr->item_height = LISTBOX_SetFont( descr, 0 );
2516 if (descr->style & LBS_OWNERDRAWFIXED)
2518 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2520 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2521 descr->item_height = lphc->fixedOwnerDrawHeight;
2523 else
2525 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2526 mis.CtlType = ODT_LISTBOX;
2527 mis.CtlID = id;
2528 mis.itemID = -1;
2529 mis.itemWidth = 0;
2530 mis.itemData = 0;
2531 mis.itemHeight = descr->item_height;
2532 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2533 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2537 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2538 return TRUE;
2542 /***********************************************************************
2543 * LISTBOX_Destroy
2545 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2547 LISTBOX_ResetContent( descr );
2548 SetWindowLongPtrW( descr->self, 0, 0 );
2549 HeapFree( GetProcessHeap(), 0, descr );
2550 return TRUE;
2554 /***********************************************************************
2555 * ListBoxWndProc_common
2557 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2559 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2560 LPHEADCOMBO lphc = 0;
2561 LRESULT ret;
2563 if (!descr)
2565 if (!IsWindow(hwnd)) return 0;
2567 if (msg == WM_CREATE)
2569 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2570 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2571 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2572 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2573 return 0;
2575 /* Ignore all other messages before we get a WM_CREATE */
2576 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2577 DefWindowProcA( hwnd, msg, wParam, lParam );
2579 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2581 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2582 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2584 switch(msg)
2586 case LB_RESETCONTENT:
2587 LISTBOX_ResetContent( descr );
2588 LISTBOX_UpdateScroll( descr );
2589 InvalidateRect( descr->self, NULL, TRUE );
2590 return 0;
2592 case LB_ADDSTRING:
2594 INT ret;
2595 LPWSTR textW;
2596 if(unicode || !HAS_STRINGS(descr))
2597 textW = (LPWSTR)lParam;
2598 else
2600 LPSTR textA = (LPSTR)lParam;
2601 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2602 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2603 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2604 else
2605 return LB_ERRSPACE;
2607 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2608 ret = LISTBOX_InsertString( descr, wParam, textW );
2609 if (!unicode && HAS_STRINGS(descr))
2610 HeapFree(GetProcessHeap(), 0, textW);
2611 return ret;
2614 case LB_INSERTSTRING:
2616 INT ret;
2617 LPWSTR textW;
2618 if(unicode || !HAS_STRINGS(descr))
2619 textW = (LPWSTR)lParam;
2620 else
2622 LPSTR textA = (LPSTR)lParam;
2623 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2624 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2625 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2626 else
2627 return LB_ERRSPACE;
2629 ret = LISTBOX_InsertString( descr, wParam, textW );
2630 if(!unicode && HAS_STRINGS(descr))
2631 HeapFree(GetProcessHeap(), 0, textW);
2632 return ret;
2635 case LB_ADDFILE:
2637 INT ret;
2638 LPWSTR textW;
2639 if(unicode || !HAS_STRINGS(descr))
2640 textW = (LPWSTR)lParam;
2641 else
2643 LPSTR textA = (LPSTR)lParam;
2644 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2645 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2646 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2647 else
2648 return LB_ERRSPACE;
2650 wParam = LISTBOX_FindFileStrPos( descr, textW );
2651 ret = LISTBOX_InsertString( descr, wParam, textW );
2652 if(!unicode && HAS_STRINGS(descr))
2653 HeapFree(GetProcessHeap(), 0, textW);
2654 return ret;
2657 case LB_DELETESTRING:
2658 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2659 return descr->nb_items;
2660 else
2662 SetLastError(ERROR_INVALID_INDEX);
2663 return LB_ERR;
2666 case LB_GETITEMDATA:
2667 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2669 SetLastError(ERROR_INVALID_INDEX);
2670 return LB_ERR;
2672 return descr->items[wParam].data;
2674 case LB_SETITEMDATA:
2675 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2677 SetLastError(ERROR_INVALID_INDEX);
2678 return LB_ERR;
2680 descr->items[wParam].data = lParam;
2681 /* undocumented: returns TRUE, not LB_OKAY (0) */
2682 return TRUE;
2684 case LB_GETCOUNT:
2685 return descr->nb_items;
2687 case LB_GETTEXT:
2688 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2690 case LB_GETTEXTLEN:
2691 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2693 SetLastError(ERROR_INVALID_INDEX);
2694 return LB_ERR;
2696 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2697 if (unicode) return strlenW( descr->items[wParam].str );
2698 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2699 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2701 case LB_GETCURSEL:
2702 if (descr->nb_items == 0)
2703 return LB_ERR;
2704 if (!IS_MULTISELECT(descr))
2705 return descr->selected_item;
2706 if (descr->selected_item != -1)
2707 return descr->selected_item;
2708 return descr->focus_item;
2709 /* otherwise, if the user tries to move the selection with the */
2710 /* arrow keys, we will give the application something to choke on */
2711 case LB_GETTOPINDEX:
2712 return descr->top_item;
2714 case LB_GETITEMHEIGHT:
2715 return LISTBOX_GetItemHeight( descr, wParam );
2717 case LB_SETITEMHEIGHT:
2718 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2720 case LB_ITEMFROMPOINT:
2722 POINT pt;
2723 RECT rect;
2724 int index;
2725 BOOL hit = TRUE;
2727 /* The hiword of the return value is not a client area
2728 hittest as suggested by MSDN, but rather a hittest on
2729 the returned listbox item. */
2731 if(descr->nb_items == 0)
2732 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2734 pt.x = (short)LOWORD(lParam);
2735 pt.y = (short)HIWORD(lParam);
2737 SetRect(&rect, 0, 0, descr->width, descr->height);
2739 if(!PtInRect(&rect, pt))
2741 pt.x = min(pt.x, rect.right - 1);
2742 pt.x = max(pt.x, 0);
2743 pt.y = min(pt.y, rect.bottom - 1);
2744 pt.y = max(pt.y, 0);
2745 hit = FALSE;
2748 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2750 if(index == -1)
2752 index = descr->nb_items - 1;
2753 hit = FALSE;
2755 return MAKELONG(index, hit ? 0 : 1);
2758 case LB_SETCARETINDEX:
2759 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2760 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2761 return LB_ERR;
2762 else if (ISWIN31)
2763 return wParam;
2764 else
2765 return LB_OKAY;
2767 case LB_GETCARETINDEX:
2768 return descr->focus_item;
2770 case LB_SETTOPINDEX:
2771 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2773 case LB_SETCOLUMNWIDTH:
2774 return LISTBOX_SetColumnWidth( descr, wParam );
2776 case LB_GETITEMRECT:
2777 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2779 case LB_FINDSTRING:
2781 INT ret;
2782 LPWSTR textW;
2783 if(unicode || !HAS_STRINGS(descr))
2784 textW = (LPWSTR)lParam;
2785 else
2787 LPSTR textA = (LPSTR)lParam;
2788 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2789 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2790 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2792 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2793 if(!unicode && HAS_STRINGS(descr))
2794 HeapFree(GetProcessHeap(), 0, textW);
2795 return ret;
2798 case LB_FINDSTRINGEXACT:
2800 INT ret;
2801 LPWSTR textW;
2802 if(unicode || !HAS_STRINGS(descr))
2803 textW = (LPWSTR)lParam;
2804 else
2806 LPSTR textA = (LPSTR)lParam;
2807 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2808 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2809 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2811 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2812 if(!unicode && HAS_STRINGS(descr))
2813 HeapFree(GetProcessHeap(), 0, textW);
2814 return ret;
2817 case LB_SELECTSTRING:
2819 INT index;
2820 LPWSTR textW;
2822 if(HAS_STRINGS(descr))
2823 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2824 debugstr_a((LPSTR)lParam));
2825 if(unicode || !HAS_STRINGS(descr))
2826 textW = (LPWSTR)lParam;
2827 else
2829 LPSTR textA = (LPSTR)lParam;
2830 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2831 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2832 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2834 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2835 if(!unicode && HAS_STRINGS(descr))
2836 HeapFree(GetProcessHeap(), 0, textW);
2837 if (index != LB_ERR)
2839 LISTBOX_MoveCaret( descr, index, TRUE );
2840 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2842 return index;
2845 case LB_GETSEL:
2846 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2847 return LB_ERR;
2848 return descr->items[wParam].selected;
2850 case LB_SETSEL:
2851 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2853 case LB_SETCURSEL:
2854 if (IS_MULTISELECT(descr)) return LB_ERR;
2855 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2856 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2857 if (ret != LB_ERR) ret = descr->selected_item;
2858 return ret;
2860 case LB_GETSELCOUNT:
2861 return LISTBOX_GetSelCount( descr );
2863 case LB_GETSELITEMS:
2864 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2866 case LB_SELITEMRANGE:
2867 if (LOWORD(lParam) <= HIWORD(lParam))
2868 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2869 HIWORD(lParam), wParam );
2870 else
2871 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2872 LOWORD(lParam), wParam );
2874 case LB_SELITEMRANGEEX:
2875 if ((INT)lParam >= (INT)wParam)
2876 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2877 else
2878 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2880 case LB_GETHORIZONTALEXTENT:
2881 return descr->horz_extent;
2883 case LB_SETHORIZONTALEXTENT:
2884 return LISTBOX_SetHorizontalExtent( descr, wParam );
2886 case LB_GETANCHORINDEX:
2887 return descr->anchor_item;
2889 case LB_SETANCHORINDEX:
2890 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2892 SetLastError(ERROR_INVALID_INDEX);
2893 return LB_ERR;
2895 descr->anchor_item = (INT)wParam;
2896 return LB_OKAY;
2898 case LB_DIR:
2900 INT ret;
2901 LPWSTR textW;
2902 if(unicode)
2903 textW = (LPWSTR)lParam;
2904 else
2906 LPSTR textA = (LPSTR)lParam;
2907 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2908 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2909 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2911 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2912 if(!unicode)
2913 HeapFree(GetProcessHeap(), 0, textW);
2914 return ret;
2917 case LB_GETLOCALE:
2918 return descr->locale;
2920 case LB_SETLOCALE:
2922 LCID ret;
2923 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2924 return LB_ERR;
2925 ret = descr->locale;
2926 descr->locale = (LCID)wParam;
2927 return ret;
2930 case LB_INITSTORAGE:
2931 return LISTBOX_InitStorage( descr, wParam );
2933 case LB_SETCOUNT:
2934 return LISTBOX_SetCount( descr, (INT)wParam );
2936 case LB_SETTABSTOPS:
2937 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2939 case LB_CARETON:
2940 if (descr->caret_on)
2941 return LB_OKAY;
2942 descr->caret_on = TRUE;
2943 if ((descr->focus_item != -1) && (descr->in_focus))
2944 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2945 return LB_OKAY;
2947 case LB_CARETOFF:
2948 if (!descr->caret_on)
2949 return LB_OKAY;
2950 descr->caret_on = FALSE;
2951 if ((descr->focus_item != -1) && (descr->in_focus))
2952 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2953 return LB_OKAY;
2955 case LB_GETLISTBOXINFO:
2956 return descr->page_size;
2958 case WM_DESTROY:
2959 return LISTBOX_Destroy( descr );
2961 case WM_ENABLE:
2962 InvalidateRect( descr->self, NULL, TRUE );
2963 return 0;
2965 case WM_SETREDRAW:
2966 LISTBOX_SetRedraw( descr, wParam != 0 );
2967 return 0;
2969 case WM_GETDLGCODE:
2970 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2972 case WM_PRINTCLIENT:
2973 case WM_PAINT:
2975 PAINTSTRUCT ps;
2976 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2977 ret = LISTBOX_Paint( descr, hdc );
2978 if( !wParam ) EndPaint( descr->self, &ps );
2980 return ret;
2981 case WM_SIZE:
2982 LISTBOX_UpdateSize( descr );
2983 return 0;
2984 case WM_GETFONT:
2985 return (LRESULT)descr->font;
2986 case WM_SETFONT:
2987 LISTBOX_SetFont( descr, (HFONT)wParam );
2988 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2989 return 0;
2990 case WM_SETFOCUS:
2991 descr->in_focus = TRUE;
2992 descr->caret_on = TRUE;
2993 if (descr->focus_item != -1)
2994 LISTBOX_DrawFocusRect( descr, TRUE );
2995 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2996 return 0;
2997 case WM_KILLFOCUS:
2998 descr->in_focus = FALSE;
2999 descr->wheel_remain = 0;
3000 if ((descr->focus_item != -1) && descr->caret_on)
3001 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3002 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3003 return 0;
3004 case WM_HSCROLL:
3005 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3006 case WM_VSCROLL:
3007 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3008 case WM_MOUSEWHEEL:
3009 if (wParam & (MK_SHIFT | MK_CONTROL))
3010 return DefWindowProcW( descr->self, msg, wParam, lParam );
3011 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3012 case WM_LBUTTONDOWN:
3013 if (lphc)
3014 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3015 (INT16)LOWORD(lParam),
3016 (INT16)HIWORD(lParam) );
3017 return LISTBOX_HandleLButtonDown( descr, wParam,
3018 (INT16)LOWORD(lParam),
3019 (INT16)HIWORD(lParam) );
3020 case WM_LBUTTONDBLCLK:
3021 if (lphc)
3022 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3023 (INT16)LOWORD(lParam),
3024 (INT16)HIWORD(lParam) );
3025 if (descr->style & LBS_NOTIFY)
3026 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3027 return 0;
3028 case WM_MOUSEMOVE:
3029 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3031 BOOL captured = descr->captured;
3032 POINT mousePos;
3033 RECT clientRect;
3035 mousePos.x = (INT16)LOWORD(lParam);
3036 mousePos.y = (INT16)HIWORD(lParam);
3039 * If we are in a dropdown combobox, we simulate that
3040 * the mouse is captured to show the tracking of the item.
3042 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3043 descr->captured = TRUE;
3045 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3047 descr->captured = captured;
3049 else if (GetCapture() == descr->self)
3051 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3052 (INT16)HIWORD(lParam) );
3054 return 0;
3055 case WM_LBUTTONUP:
3056 if (lphc)
3058 POINT mousePos;
3059 RECT clientRect;
3062 * If the mouse button "up" is not in the listbox,
3063 * we make sure there is no selection by re-selecting the
3064 * item that was selected when the listbox was made visible.
3066 mousePos.x = (INT16)LOWORD(lParam);
3067 mousePos.y = (INT16)HIWORD(lParam);
3069 GetClientRect(descr->self, &clientRect);
3072 * When the user clicks outside the combobox and the focus
3073 * is lost, the owning combobox will send a fake buttonup with
3074 * 0xFFFFFFF as the mouse location, we must also revert the
3075 * selection to the original selection.
3077 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3078 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3080 return LISTBOX_HandleLButtonUp( descr );
3081 case WM_KEYDOWN:
3082 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3084 /* for some reason Windows makes it possible to
3085 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3087 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3088 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3089 && (wParam == VK_DOWN || wParam == VK_UP)) )
3091 COMBO_FlipListbox( lphc, FALSE, FALSE );
3092 return 0;
3095 return LISTBOX_HandleKeyDown( descr, wParam );
3096 case WM_CHAR:
3098 WCHAR charW;
3099 if(unicode)
3100 charW = (WCHAR)wParam;
3101 else
3103 CHAR charA = (CHAR)wParam;
3104 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3106 return LISTBOX_HandleChar( descr, charW );
3108 case WM_SYSTIMER:
3109 return LISTBOX_HandleSystemTimer( descr );
3110 case WM_ERASEBKGND:
3111 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3113 RECT rect;
3114 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3115 wParam, (LPARAM)descr->self );
3116 TRACE("hbrush = %p\n", hbrush);
3117 if(!hbrush)
3118 hbrush = GetSysColorBrush(COLOR_WINDOW);
3119 if(hbrush)
3121 GetClientRect(descr->self, &rect);
3122 FillRect((HDC)wParam, &rect, hbrush);
3125 return 1;
3126 case WM_DROPFILES:
3127 if( lphc ) return 0;
3128 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3129 SendMessageA( descr->owner, msg, wParam, lParam );
3131 case WM_NCDESTROY:
3132 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3133 lphc->hWndLBox = 0;
3134 break;
3136 case WM_NCACTIVATE:
3137 if (lphc) return 0;
3138 break;
3140 default:
3141 if ((msg >= WM_USER) && (msg < 0xc000))
3142 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3143 hwnd, msg, wParam, lParam );
3146 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3147 DefWindowProcA( hwnd, msg, wParam, lParam );
3150 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3152 TRACE("%p\n", hwnd);
3153 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);