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