d3dcompiler: Allow D3DCompile2() to succeed with null output shader blob pointer.
[wine.git] / dlls / user32 / listbox.c
blobf6b5a3fb7db15fee6110f7868e7e47690e9c2982
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
22 #include <string.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winnls.h"
29 #include "wingdi.h"
30 #include "user_private.h"
31 #include "controls.h"
32 #include "wine/exception.h"
33 #include "wine/debug.h"
34 #include "wine/heap.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
38 /* Items array granularity (must be power of 2) */
39 #define LB_ARRAY_GRANULARITY 16
41 /* Scrolling timeout in ms */
42 #define LB_SCROLL_TIMEOUT 50
44 /* Listbox system timer id */
45 #define LB_TIMER_ID 2
47 /* flag listbox changed while setredraw false - internal style */
48 #define LBS_DISPLAYCHANGED 0x80000000
50 /* Item structure */
51 typedef struct
53 LPWSTR str; /* Item text */
54 BOOL selected; /* Is item selected? */
55 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
56 ULONG_PTR data; /* User data */
57 } LB_ITEMDATA;
59 /* Listbox structure */
60 typedef struct
62 HWND self; /* Our own window handle */
63 HWND owner; /* Owner window to send notifications to */
64 UINT style; /* Window style */
65 INT width; /* Window width */
66 INT height; /* Window height */
67 union
69 LB_ITEMDATA *items; /* Array of items */
70 BYTE *nodata_items; /* For multi-selection LBS_NODATA */
71 } u;
72 INT nb_items; /* Number of items */
73 UINT items_size; /* Total number of allocated items in the array */
74 INT top_item; /* Top visible item */
75 INT selected_item; /* Selected item */
76 INT focus_item; /* Item that has the focus */
77 INT anchor_item; /* Anchor item for extended selection */
78 INT item_height; /* Default item height */
79 INT page_size; /* Items per listbox page */
80 INT column_width; /* Column width for multi-column listboxes */
81 INT horz_extent; /* Horizontal extent */
82 INT horz_pos; /* Horizontal position */
83 INT nb_tabs; /* Number of tabs in array */
84 INT *tabs; /* Array of tabs */
85 INT avg_char_width; /* Average width of characters */
86 INT wheel_remain; /* Left over scroll amount */
87 BOOL caret_on; /* Is caret on? */
88 BOOL captured; /* Is mouse captured? */
89 BOOL in_focus;
90 HFONT font; /* Current font */
91 LCID locale; /* Current locale for string comparisons */
92 HEADCOMBO *lphc; /* ComboLBox */
93 } LB_DESCR;
96 #define IS_OWNERDRAW(descr) \
97 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
99 #define HAS_STRINGS(descr) \
100 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
103 #define IS_MULTISELECT(descr) \
104 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
105 !((descr)->style & LBS_NOSEL))
107 #define SEND_NOTIFICATION(descr,code) \
108 (SendMessageW( (descr)->owner, WM_COMMAND, \
109 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
111 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
113 /* Current timer status */
114 typedef enum
116 LB_TIMER_NONE,
117 LB_TIMER_UP,
118 LB_TIMER_LEFT,
119 LB_TIMER_DOWN,
120 LB_TIMER_RIGHT
121 } TIMER_DIRECTION;
123 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
125 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
128 For listboxes without LBS_NODATA, an array of LB_ITEMDATA is allocated
129 to store the states of each item into descr->u.items.
131 For single-selection LBS_NODATA listboxes, no storage is allocated,
132 and thus descr->u.nodata_items will always be NULL.
134 For multi-selection LBS_NODATA listboxes, one byte per item is stored
135 for the item's selection state into descr->u.nodata_items.
137 static size_t get_sizeof_item( const LB_DESCR *descr )
139 return (descr->style & LBS_NODATA) ? sizeof(BYTE) : sizeof(LB_ITEMDATA);
142 static BOOL resize_storage(LB_DESCR *descr, UINT items_size)
144 LB_ITEMDATA *items;
146 if (items_size > descr->items_size ||
147 items_size + LB_ARRAY_GRANULARITY * 2 < descr->items_size)
149 items_size = (items_size + LB_ARRAY_GRANULARITY - 1) & ~(LB_ARRAY_GRANULARITY - 1);
150 if ((descr->style & (LBS_NODATA | LBS_MULTIPLESEL | LBS_EXTENDEDSEL)) != LBS_NODATA)
152 items = heap_realloc(descr->u.items, items_size * get_sizeof_item(descr));
153 if (!items)
155 SEND_NOTIFICATION(descr, LBN_ERRSPACE);
156 return FALSE;
158 descr->u.items = items;
160 descr->items_size = items_size;
163 if ((descr->style & LBS_NODATA) && descr->u.nodata_items && items_size > descr->nb_items)
165 memset(descr->u.nodata_items + descr->nb_items, 0,
166 (items_size - descr->nb_items) * get_sizeof_item(descr));
168 return TRUE;
171 static ULONG_PTR get_item_data( const LB_DESCR *descr, UINT index )
173 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].data;
176 static void set_item_data( LB_DESCR *descr, UINT index, ULONG_PTR data )
178 if (!(descr->style & LBS_NODATA)) descr->u.items[index].data = data;
181 static WCHAR *get_item_string( const LB_DESCR *descr, UINT index )
183 return HAS_STRINGS(descr) ? descr->u.items[index].str : NULL;
186 static void set_item_string( const LB_DESCR *descr, UINT index, WCHAR *string )
188 if (!(descr->style & LBS_NODATA)) descr->u.items[index].str = string;
191 static UINT get_item_height( const LB_DESCR *descr, UINT index )
193 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].height;
196 static void set_item_height( LB_DESCR *descr, UINT index, UINT height )
198 if (!(descr->style & LBS_NODATA)) descr->u.items[index].height = height;
201 static BOOL is_item_selected( const LB_DESCR *descr, UINT index )
203 if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
204 return index == descr->selected_item;
205 if (descr->style & LBS_NODATA)
206 return descr->u.nodata_items[index];
207 else
208 return descr->u.items[index].selected;
211 static void set_item_selected_state(LB_DESCR *descr, UINT index, BOOL state)
213 if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
215 if (descr->style & LBS_NODATA)
216 descr->u.nodata_items[index] = state;
217 else
218 descr->u.items[index].selected = state;
222 static void insert_item_data(LB_DESCR *descr, UINT index)
224 size_t size = get_sizeof_item(descr);
225 BYTE *p = descr->u.nodata_items + index * size;
227 if (!descr->u.items) return;
229 if (index < descr->nb_items)
230 memmove(p + size, p, (descr->nb_items - index) * size);
233 static void remove_item_data(LB_DESCR *descr, UINT index)
235 size_t size = get_sizeof_item(descr);
236 BYTE *p = descr->u.nodata_items + index * size;
238 if (!descr->u.items) return;
240 if (index < descr->nb_items)
241 memmove(p, p + size, (descr->nb_items - index) * size);
245 /***********************************************************************
246 * LISTBOX_GetCurrentPageSize
248 * Return the current page size
250 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
252 INT i, height;
253 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
254 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
256 if ((height += get_item_height(descr, i)) > descr->height) break;
258 if (i == descr->top_item) return 1;
259 else return i - descr->top_item;
263 /***********************************************************************
264 * LISTBOX_GetMaxTopIndex
266 * Return the maximum possible index for the top of the listbox.
268 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
270 INT max, page;
272 if (descr->style & LBS_OWNERDRAWVARIABLE)
274 page = descr->height;
275 for (max = descr->nb_items - 1; max >= 0; max--)
276 if ((page -= get_item_height(descr, max)) < 0) break;
277 if (max < descr->nb_items - 1) max++;
279 else if (descr->style & LBS_MULTICOLUMN)
281 if ((page = descr->width / descr->column_width) < 1) page = 1;
282 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
283 max = (max - page) * descr->page_size;
285 else
287 max = descr->nb_items - descr->page_size;
289 if (max < 0) max = 0;
290 return max;
294 /***********************************************************************
295 * LISTBOX_UpdateScroll
297 * Update the scrollbars. Should be called whenever the content
298 * of the listbox changes.
300 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
302 SCROLLINFO info;
304 /* Check the listbox scroll bar flags individually before we call
305 NtUserSetScrollInfo otherwise when the listbox style is WS_HSCROLL and
306 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
307 scroll bar when we do not need one.
308 if (!(descr->style & WS_VSCROLL)) return;
311 /* It is important that we check descr->style, and not wnd->dwStyle,
312 for WS_VSCROLL, as the former is exactly the one passed in
313 argument to CreateWindow.
314 In Windows (and from now on in Wine :) a listbox created
315 with such a style (no WS_SCROLL) does not update
316 the scrollbar with listbox-related data, thus letting
317 the programmer use it for his/her own purposes. */
319 if (descr->style & LBS_NOREDRAW) return;
320 info.cbSize = sizeof(info);
322 if (descr->style & LBS_MULTICOLUMN)
324 info.nMin = 0;
325 info.nMax = (descr->nb_items - 1) / descr->page_size;
326 info.nPos = descr->top_item / descr->page_size;
327 info.nPage = descr->width / descr->column_width;
328 if (info.nPage < 1) info.nPage = 1;
329 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
330 if (descr->style & LBS_DISABLENOSCROLL)
331 info.fMask |= SIF_DISABLENOSCROLL;
332 if (descr->style & WS_HSCROLL)
333 NtUserSetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
334 info.nMax = 0;
335 info.fMask = SIF_RANGE;
336 if (descr->style & WS_VSCROLL)
337 NtUserSetScrollInfo( descr->self, SB_VERT, &info, TRUE );
339 else
341 info.nMin = 0;
342 info.nMax = descr->nb_items - 1;
343 info.nPos = descr->top_item;
344 info.nPage = LISTBOX_GetCurrentPageSize( descr );
345 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
346 if (descr->style & LBS_DISABLENOSCROLL)
347 info.fMask |= SIF_DISABLENOSCROLL;
348 if (descr->style & WS_VSCROLL)
349 NtUserSetScrollInfo( descr->self, SB_VERT, &info, TRUE );
351 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
353 info.nPos = descr->horz_pos;
354 info.nPage = descr->width;
355 info.fMask = SIF_POS | SIF_PAGE;
356 if (descr->style & LBS_DISABLENOSCROLL)
357 info.fMask |= SIF_DISABLENOSCROLL;
358 NtUserSetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
360 else
362 if (descr->style & LBS_DISABLENOSCROLL)
364 info.nMin = 0;
365 info.nMax = 0;
366 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
367 NtUserSetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
369 else
371 NtUserShowScrollBar( descr->self, SB_HORZ, FALSE );
378 /***********************************************************************
379 * LISTBOX_SetTopItem
381 * Set the top item of the listbox, scrolling up or down if necessary.
383 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
385 INT max = LISTBOX_GetMaxTopIndex( descr );
387 TRACE("setting top item %d, scroll %d\n", index, scroll);
389 if (index > max) index = max;
390 if (index < 0) index = 0;
391 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
392 if (descr->top_item == index) return LB_OKAY;
393 if (scroll)
395 INT dx = 0, dy = 0;
396 if (descr->style & LBS_MULTICOLUMN)
397 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
398 else if (descr->style & LBS_OWNERDRAWVARIABLE)
400 INT i;
401 if (index > descr->top_item)
403 for (i = index - 1; i >= descr->top_item; i--)
404 dy -= get_item_height(descr, i);
406 else
408 for (i = index; i < descr->top_item; i++)
409 dy += get_item_height(descr, i);
412 else
413 dy = (descr->top_item - index) * descr->item_height;
415 NtUserScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
416 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
418 else
419 NtUserInvalidateRect( descr->self, NULL, TRUE );
420 descr->top_item = index;
421 LISTBOX_UpdateScroll( descr );
422 return LB_OKAY;
426 /***********************************************************************
427 * LISTBOX_UpdatePage
429 * Update the page size. Should be called when the size of
430 * the client area or the item height changes.
432 static void LISTBOX_UpdatePage( LB_DESCR *descr )
434 INT page_size;
436 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
437 page_size = 1;
438 if (page_size == descr->page_size) return;
439 descr->page_size = page_size;
440 if (descr->style & LBS_MULTICOLUMN)
441 NtUserInvalidateRect( descr->self, NULL, TRUE );
442 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
446 /***********************************************************************
447 * LISTBOX_UpdateSize
449 * Update the size of the listbox. Should be called when the size of
450 * the client area changes.
452 static void LISTBOX_UpdateSize( LB_DESCR *descr )
454 RECT rect;
456 GetClientRect( descr->self, &rect );
457 descr->width = rect.right - rect.left;
458 descr->height = rect.bottom - rect.top;
459 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
461 INT remaining;
462 RECT rect;
464 GetWindowRect( descr->self, &rect );
465 if(descr->item_height != 0)
466 remaining = descr->height % descr->item_height;
467 else
468 remaining = 0;
469 if ((descr->height > descr->item_height) && remaining)
471 TRACE("[%p]: changing height %d -> %d\n",
472 descr->self, descr->height, descr->height - remaining );
473 NtUserSetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
474 rect.bottom - rect.top - remaining,
475 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
476 return;
479 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
480 LISTBOX_UpdatePage( descr );
481 LISTBOX_UpdateScroll( descr );
483 /* Invalidate the focused item so it will be repainted correctly */
484 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
486 NtUserInvalidateRect( descr->self, &rect, FALSE );
491 /***********************************************************************
492 * LISTBOX_GetItemRect
494 * Get the rectangle enclosing an item, in listbox client coordinates.
495 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
497 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
499 /* Index <= 0 is legal even on empty listboxes */
500 if (index && (index >= descr->nb_items))
502 SetRectEmpty(rect);
503 SetLastError(ERROR_INVALID_INDEX);
504 return LB_ERR;
506 SetRect( rect, 0, 0, descr->width, descr->height );
507 if (descr->style & LBS_MULTICOLUMN)
509 INT col = (index / descr->page_size) -
510 (descr->top_item / descr->page_size);
511 rect->left += col * descr->column_width;
512 rect->right = rect->left + descr->column_width;
513 rect->top += (index % descr->page_size) * descr->item_height;
514 rect->bottom = rect->top + descr->item_height;
516 else if (descr->style & LBS_OWNERDRAWVARIABLE)
518 INT i;
519 rect->right += descr->horz_pos;
520 if ((index >= 0) && (index < descr->nb_items))
522 if (index < descr->top_item)
524 for (i = descr->top_item-1; i >= index; i--)
525 rect->top -= get_item_height(descr, i);
527 else
529 for (i = descr->top_item; i < index; i++)
530 rect->top += get_item_height(descr, i);
532 rect->bottom = rect->top + get_item_height(descr, index);
536 else
538 rect->top += (index - descr->top_item) * descr->item_height;
539 rect->bottom = rect->top + descr->item_height;
540 rect->right += descr->horz_pos;
543 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
545 return ((rect->left < descr->width) && (rect->right > 0) &&
546 (rect->top < descr->height) && (rect->bottom > 0));
550 /***********************************************************************
551 * LISTBOX_GetItemFromPoint
553 * Return the item nearest from point (x,y) (in client coordinates).
555 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
557 INT index = descr->top_item;
559 if (!descr->nb_items) return -1; /* No items */
560 if (descr->style & LBS_OWNERDRAWVARIABLE)
562 INT pos = 0;
563 if (y >= 0)
565 while (index < descr->nb_items)
567 if ((pos += get_item_height(descr, index)) > y) break;
568 index++;
571 else
573 while (index > 0)
575 index--;
576 if ((pos -= get_item_height(descr, index)) <= y) break;
580 else if (descr->style & LBS_MULTICOLUMN)
582 if (y >= descr->item_height * descr->page_size) return -1;
583 if (y >= 0) index += y / descr->item_height;
584 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
585 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
587 else
589 index += (y / descr->item_height);
591 if (index < 0) return 0;
592 if (index >= descr->nb_items) return -1;
593 return index;
597 /***********************************************************************
598 * LISTBOX_PaintItem
600 * Paint an item.
602 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
603 INT index, UINT action, BOOL ignoreFocus )
605 BOOL selected = FALSE, focused;
606 WCHAR *item_str = NULL;
608 if (index < descr->nb_items)
610 item_str = get_item_string(descr, index);
611 selected = is_item_selected(descr, index);
614 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
616 if (IS_OWNERDRAW(descr))
618 DRAWITEMSTRUCT dis;
619 RECT r;
620 HRGN hrgn;
622 if (index >= descr->nb_items)
624 if (action == ODA_FOCUS)
625 DrawFocusRect( hdc, rect );
626 else
627 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
628 return;
631 /* some programs mess with the clipping region when
632 drawing the item, *and* restore the previous region
633 after they are done, so a region has better to exist
634 else everything ends clipped */
635 GetClientRect(descr->self, &r);
636 hrgn = set_control_clipping( hdc, &r );
638 dis.CtlType = ODT_LISTBOX;
639 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
640 dis.hwndItem = descr->self;
641 dis.itemAction = action;
642 dis.hDC = hdc;
643 dis.itemID = index;
644 dis.itemState = 0;
645 if (selected)
646 dis.itemState |= ODS_SELECTED;
647 if (focused)
648 dis.itemState |= ODS_FOCUS;
649 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
650 dis.itemData = get_item_data(descr, index);
651 dis.rcItem = *rect;
652 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
653 descr->self, index, debugstr_w(item_str), action,
654 dis.itemState, wine_dbgstr_rect(rect) );
655 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
656 SelectClipRgn( hdc, hrgn );
657 if (hrgn) DeleteObject( hrgn );
659 else
661 COLORREF oldText = 0, oldBk = 0;
663 if (action == ODA_FOCUS)
665 DrawFocusRect( hdc, rect );
666 return;
668 if (selected)
670 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
671 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
674 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
675 descr->self, index, debugstr_w(item_str), action,
676 wine_dbgstr_rect(rect) );
677 if (!item_str)
678 ExtTextOutW( hdc, rect->left + 1, rect->top,
679 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
680 else if (!(descr->style & LBS_USETABSTOPS))
681 ExtTextOutW( hdc, rect->left + 1, rect->top,
682 ETO_OPAQUE | ETO_CLIPPED, rect, item_str,
683 lstrlenW(item_str), NULL );
684 else
686 /* Output empty string to paint background in the full width. */
687 ExtTextOutW( hdc, rect->left + 1, rect->top,
688 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
689 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
690 item_str, lstrlenW(item_str),
691 descr->nb_tabs, descr->tabs, 0);
693 if (selected)
695 SetBkColor( hdc, oldBk );
696 SetTextColor( hdc, oldText );
698 if (focused)
699 DrawFocusRect( hdc, rect );
704 /***********************************************************************
705 * LISTBOX_SetRedraw
707 * Change the redraw flag.
709 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
711 if (on)
713 if (!(descr->style & LBS_NOREDRAW)) return;
714 descr->style &= ~LBS_NOREDRAW;
715 if (descr->style & LBS_DISPLAYCHANGED)
716 { /* page was changed while setredraw false, refresh automatically */
717 NtUserInvalidateRect(descr->self, NULL, TRUE);
718 if ((descr->top_item + descr->page_size) > descr->nb_items)
719 { /* reset top of page if less than number of items/page */
720 descr->top_item = descr->nb_items - descr->page_size;
721 if (descr->top_item < 0) descr->top_item = 0;
723 descr->style &= ~LBS_DISPLAYCHANGED;
725 LISTBOX_UpdateScroll( descr );
727 else descr->style |= LBS_NOREDRAW;
731 /***********************************************************************
732 * LISTBOX_RepaintItem
734 * Repaint a single item synchronously.
736 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
738 HDC hdc;
739 RECT rect;
740 HFONT oldFont = 0;
741 HBRUSH hbrush, oldBrush = 0;
743 /* Do not repaint the item if the item is not visible */
744 if (!IsWindowVisible(descr->self)) return;
745 if (descr->style & LBS_NOREDRAW)
747 descr->style |= LBS_DISPLAYCHANGED;
748 return;
750 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
751 if (!(hdc = NtUserGetDCEx( descr->self, 0, DCX_CACHE ))) return;
752 if (descr->font) oldFont = SelectObject( hdc, descr->font );
753 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
754 (WPARAM)hdc, (LPARAM)descr->self );
755 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
756 if (!IsWindowEnabled(descr->self))
757 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
758 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
759 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
760 if (oldFont) SelectObject( hdc, oldFont );
761 if (oldBrush) SelectObject( hdc, oldBrush );
762 NtUserReleaseDC( descr->self, hdc );
766 /***********************************************************************
767 * LISTBOX_DrawFocusRect
769 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
771 HDC hdc;
772 RECT rect;
773 HFONT oldFont = 0;
775 /* Do not repaint the item if the item is not visible */
776 if (!IsWindowVisible(descr->self)) return;
778 if (descr->focus_item == -1) return;
779 if (!descr->caret_on || !descr->in_focus) return;
781 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
782 if (!(hdc = NtUserGetDCEx( descr->self, 0, DCX_CACHE ))) return;
783 if (descr->font) oldFont = SelectObject( hdc, descr->font );
784 if (!IsWindowEnabled(descr->self))
785 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
786 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
787 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
788 if (oldFont) SelectObject( hdc, oldFont );
789 NtUserReleaseDC( descr->self, hdc );
793 /***********************************************************************
794 * LISTBOX_InitStorage
796 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
798 UINT new_size = descr->nb_items + nb_items;
800 if (new_size > descr->items_size && !resize_storage(descr, new_size))
801 return LB_ERRSPACE;
802 return descr->items_size;
806 /***********************************************************************
807 * LISTBOX_SetTabStops
809 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
811 INT i;
813 if (!(descr->style & LBS_USETABSTOPS))
815 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
816 return FALSE;
819 HeapFree( GetProcessHeap(), 0, descr->tabs );
820 if (!(descr->nb_tabs = count))
822 descr->tabs = NULL;
823 return TRUE;
825 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
826 descr->nb_tabs * sizeof(INT) )))
827 return FALSE;
828 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
830 /* convert into "dialog units"*/
831 for (i = 0; i < descr->nb_tabs; i++)
832 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
834 return TRUE;
838 /***********************************************************************
839 * LISTBOX_GetText
841 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
843 DWORD len;
845 if ((index < 0) || (index >= descr->nb_items))
847 SetLastError(ERROR_INVALID_INDEX);
848 return LB_ERR;
851 if (HAS_STRINGS(descr))
853 WCHAR *str = get_item_string(descr, index);
855 if (!buffer)
857 len = lstrlenW(str);
858 if( unicode )
859 return len;
860 return WideCharToMultiByte( CP_ACP, 0, str, len, NULL, 0, NULL, NULL );
863 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(str));
865 __TRY /* hide a Delphi bug that passes a read-only buffer */
867 if(unicode)
869 lstrcpyW(buffer, str);
870 len = lstrlenW(buffer);
872 else
874 len = WideCharToMultiByte(CP_ACP, 0, str, -1, (LPSTR)buffer,
875 0x7FFFFFFF, NULL, NULL) - 1;
878 __EXCEPT_PAGE_FAULT
880 WARN( "got an invalid buffer (Delphi bug?)\n" );
881 SetLastError( ERROR_INVALID_PARAMETER );
882 return LB_ERR;
884 __ENDTRY
885 } else
887 if (buffer)
888 *((ULONG_PTR *)buffer) = get_item_data(descr, index);
889 len = sizeof(ULONG_PTR);
891 return len;
894 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
896 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
897 if (ret == CSTR_LESS_THAN)
898 return -1;
899 if (ret == CSTR_EQUAL)
900 return 0;
901 if (ret == CSTR_GREATER_THAN)
902 return 1;
903 return -1;
906 /***********************************************************************
907 * LISTBOX_FindStringPos
909 * Find the nearest string located before a given string in sort order.
910 * If 'exact' is TRUE, return an error if we don't get an exact match.
912 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
914 INT index, min, max, res;
916 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
918 min = 0;
919 max = descr->nb_items - 1;
920 while (min <= max)
922 index = (min + max) / 2;
923 if (HAS_STRINGS(descr))
924 res = LISTBOX_lstrcmpiW( descr->locale, get_item_string(descr, index), str );
925 else
927 COMPAREITEMSTRUCT cis;
928 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
930 cis.CtlType = ODT_LISTBOX;
931 cis.CtlID = id;
932 cis.hwndItem = descr->self;
933 /* note that some application (MetaStock) expects the second item
934 * to be in the listbox */
935 cis.itemID1 = index;
936 cis.itemData1 = get_item_data(descr, index);
937 cis.itemID2 = -1;
938 cis.itemData2 = (ULONG_PTR)str;
939 cis.dwLocaleId = descr->locale;
940 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
942 if (!res) return index;
943 if (res > 0) max = index - 1;
944 else min = index + 1;
946 return exact ? -1 : min;
950 /***********************************************************************
951 * LISTBOX_FindFileStrPos
953 * Find the nearest string located before a given string in directory
954 * sort order (i.e. first files, then directories, then drives).
956 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
958 INT min, max, res;
960 if (!HAS_STRINGS(descr))
961 return LISTBOX_FindStringPos( descr, str, FALSE );
962 min = 0;
963 max = descr->nb_items;
964 while (min != max)
966 INT index = (min + max) / 2;
967 LPCWSTR p = get_item_string(descr, index);
968 if (*p == '[') /* drive or directory */
970 if (*str != '[') res = -1;
971 else if (p[1] == '-') /* drive */
973 if (str[1] == '-') res = str[2] - p[2];
974 else res = -1;
976 else /* directory */
978 if (str[1] == '-') res = 1;
979 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
982 else /* filename */
984 if (*str == '[') res = 1;
985 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
987 if (!res) return index;
988 if (res < 0) max = index;
989 else min = index + 1;
991 return max;
995 /***********************************************************************
996 * LISTBOX_FindString
998 * Find the item beginning with a given string.
1000 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
1002 INT i, index;
1004 if (descr->style & LBS_NODATA)
1006 SetLastError(ERROR_INVALID_PARAMETER);
1007 return LB_ERR;
1010 start++;
1011 if (start >= descr->nb_items) start = 0;
1012 if (HAS_STRINGS(descr))
1014 if (!str || ! str[0] ) return LB_ERR;
1015 if (exact)
1017 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1019 if (index == descr->nb_items) index = 0;
1020 if (!LISTBOX_lstrcmpiW(descr->locale, str, get_item_string(descr, index)))
1021 return index;
1024 else
1026 /* Special case for drives and directories: ignore prefix */
1027 INT len = lstrlenW(str);
1028 WCHAR *item_str;
1030 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1032 if (index == descr->nb_items) index = 0;
1033 item_str = get_item_string(descr, index);
1035 if (!wcsnicmp(str, item_str, len)) return index;
1036 if (item_str[0] == '[')
1038 if (!wcsnicmp(str, item_str + 1, len)) return index;
1039 if (item_str[1] == '-' && !wcsnicmp(str, item_str + 2, len)) return index;
1044 else
1046 if (exact && (descr->style & LBS_SORT))
1047 /* If sorted, use a WM_COMPAREITEM binary search */
1048 return LISTBOX_FindStringPos( descr, str, TRUE );
1050 /* Otherwise use a linear search */
1051 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1053 if (index == descr->nb_items) index = 0;
1054 if (get_item_data(descr, index) == (ULONG_PTR)str) return index;
1057 return LB_ERR;
1061 /***********************************************************************
1062 * LISTBOX_GetSelCount
1064 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1066 INT i, count;
1068 if (!(descr->style & LBS_MULTIPLESEL) ||
1069 (descr->style & LBS_NOSEL))
1070 return LB_ERR;
1071 for (i = count = 0; i < descr->nb_items; i++)
1072 if (is_item_selected(descr, i)) count++;
1073 return count;
1077 /***********************************************************************
1078 * LISTBOX_GetSelItems
1080 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1082 INT i, count;
1084 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1085 for (i = count = 0; (i < descr->nb_items) && (count < max); i++)
1086 if (is_item_selected(descr, i)) array[count++] = i;
1087 return count;
1091 /***********************************************************************
1092 * LISTBOX_Paint
1094 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1096 INT i, col_pos = descr->page_size - 1;
1097 RECT rect;
1098 RECT focusRect = {-1, -1, -1, -1};
1099 HFONT oldFont = 0;
1100 HBRUSH hbrush, oldBrush = 0;
1102 if (descr->style & LBS_NOREDRAW) return 0;
1104 SetRect( &rect, 0, 0, descr->width, descr->height );
1105 if (descr->style & LBS_MULTICOLUMN)
1106 rect.right = rect.left + descr->column_width;
1107 else if (descr->horz_pos)
1109 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1110 rect.right += descr->horz_pos;
1113 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1114 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1115 (WPARAM)hdc, (LPARAM)descr->self );
1116 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1117 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1119 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1120 (descr->in_focus))
1122 /* Special case for empty listbox: paint focus rect */
1123 rect.bottom = rect.top + descr->item_height;
1124 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1125 &rect, NULL, 0, NULL );
1126 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1127 rect.top = rect.bottom;
1130 /* Paint all the item, regarding the selection
1131 Focus state will be painted after */
1133 for (i = descr->top_item; i < descr->nb_items; i++)
1135 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1136 rect.bottom = rect.top + descr->item_height;
1137 else
1138 rect.bottom = rect.top + get_item_height(descr, i);
1140 /* keep the focus rect, to paint the focus item after */
1141 if (i == descr->focus_item)
1142 focusRect = rect;
1144 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1145 rect.top = rect.bottom;
1147 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1149 if (!IS_OWNERDRAW(descr))
1151 /* Clear the bottom of the column */
1152 if (rect.top < descr->height)
1154 rect.bottom = descr->height;
1155 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1156 &rect, NULL, 0, NULL );
1160 /* Go to the next column */
1161 rect.left += descr->column_width;
1162 rect.right += descr->column_width;
1163 rect.top = 0;
1164 col_pos = descr->page_size - 1;
1165 if (rect.left >= descr->width) break;
1167 else
1169 col_pos--;
1170 if (rect.top >= descr->height) break;
1174 /* Paint the focus item now */
1175 if (focusRect.top != focusRect.bottom &&
1176 descr->caret_on && descr->in_focus)
1177 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1179 if (!IS_OWNERDRAW(descr))
1181 /* Clear the remainder of the client area */
1182 if (rect.top < descr->height)
1184 rect.bottom = descr->height;
1185 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1186 &rect, NULL, 0, NULL );
1188 if (rect.right < descr->width)
1190 rect.left = rect.right;
1191 rect.right = descr->width;
1192 rect.top = 0;
1193 rect.bottom = descr->height;
1194 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1195 &rect, NULL, 0, NULL );
1198 if (oldFont) SelectObject( hdc, oldFont );
1199 if (oldBrush) SelectObject( hdc, oldBrush );
1200 return 0;
1204 /***********************************************************************
1205 * LISTBOX_InvalidateItems
1207 * Invalidate all items from a given item. If the specified item is not
1208 * visible, nothing happens.
1210 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1212 RECT rect;
1214 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1216 if (descr->style & LBS_NOREDRAW)
1218 descr->style |= LBS_DISPLAYCHANGED;
1219 return;
1221 rect.bottom = descr->height;
1222 NtUserInvalidateRect( descr->self, &rect, TRUE );
1223 if (descr->style & LBS_MULTICOLUMN)
1225 /* Repaint the other columns */
1226 rect.left = rect.right;
1227 rect.right = descr->width;
1228 rect.top = 0;
1229 NtUserInvalidateRect( descr->self, &rect, TRUE );
1234 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1236 RECT rect;
1238 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1239 NtUserInvalidateRect( descr->self, &rect, TRUE );
1242 /***********************************************************************
1243 * LISTBOX_GetItemHeight
1245 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1247 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1249 if ((index < 0) || (index >= descr->nb_items))
1251 SetLastError(ERROR_INVALID_INDEX);
1252 return LB_ERR;
1254 return get_item_height(descr, index);
1256 else return descr->item_height;
1260 /***********************************************************************
1261 * LISTBOX_SetItemHeight
1263 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1265 if (height > MAXBYTE)
1266 return -1;
1268 if (!height) height = 1;
1270 if (descr->style & LBS_OWNERDRAWVARIABLE)
1272 if ((index < 0) || (index >= descr->nb_items))
1274 SetLastError(ERROR_INVALID_INDEX);
1275 return LB_ERR;
1277 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1278 set_item_height(descr, index, height);
1279 LISTBOX_UpdateScroll( descr );
1280 if (repaint)
1281 LISTBOX_InvalidateItems( descr, index );
1283 else if (height != descr->item_height)
1285 TRACE("[%p]: new height = %d\n", descr->self, height );
1286 descr->item_height = height;
1287 LISTBOX_UpdatePage( descr );
1288 LISTBOX_UpdateScroll( descr );
1289 if (repaint)
1290 NtUserInvalidateRect( descr->self, 0, TRUE );
1292 return LB_OKAY;
1296 /***********************************************************************
1297 * LISTBOX_SetHorizontalPos
1299 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1301 INT diff;
1303 if (pos > descr->horz_extent - descr->width)
1304 pos = descr->horz_extent - descr->width;
1305 if (pos < 0) pos = 0;
1306 if (!(diff = descr->horz_pos - pos)) return;
1307 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1308 descr->horz_pos = pos;
1309 LISTBOX_UpdateScroll( descr );
1310 if (abs(diff) < descr->width)
1312 RECT rect;
1313 /* Invalidate the focused item so it will be repainted correctly */
1314 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1315 NtUserInvalidateRect( descr->self, &rect, TRUE );
1316 NtUserScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1317 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1319 else
1320 NtUserInvalidateRect( descr->self, NULL, TRUE );
1324 /***********************************************************************
1325 * LISTBOX_SetHorizontalExtent
1327 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1329 if (descr->style & LBS_MULTICOLUMN)
1330 return LB_OKAY;
1331 if (extent == descr->horz_extent) return LB_OKAY;
1332 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1333 descr->horz_extent = extent;
1334 if (descr->style & WS_HSCROLL) {
1335 SCROLLINFO info;
1336 info.cbSize = sizeof(info);
1337 info.nMin = 0;
1338 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1339 info.fMask = SIF_RANGE;
1340 if (descr->style & LBS_DISABLENOSCROLL)
1341 info.fMask |= SIF_DISABLENOSCROLL;
1342 NtUserSetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1344 if (descr->horz_pos > extent - descr->width)
1345 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1346 return LB_OKAY;
1350 /***********************************************************************
1351 * LISTBOX_SetColumnWidth
1353 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1355 RECT rect;
1357 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1359 GetClientRect(descr->self, &rect);
1360 descr->width = rect.right - rect.left;
1361 descr->height = rect.bottom - rect.top;
1362 descr->column_width = column_width;
1364 LISTBOX_UpdatePage(descr);
1365 LISTBOX_UpdateScroll(descr);
1366 return LB_OKAY;
1370 /***********************************************************************
1371 * LISTBOX_SetFont
1373 * Returns the item height.
1375 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1377 HDC hdc;
1378 HFONT oldFont = 0;
1379 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1380 SIZE sz;
1382 descr->font = font;
1384 if (!(hdc = NtUserGetDCEx( descr->self, 0, DCX_CACHE )))
1386 ERR("unable to get DC.\n" );
1387 return 16;
1389 if (font) oldFont = SelectObject( hdc, font );
1390 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1391 if (oldFont) SelectObject( hdc, oldFont );
1392 NtUserReleaseDC( descr->self, hdc );
1394 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1395 if (!IS_OWNERDRAW(descr))
1396 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1397 return sz.cy;
1401 /***********************************************************************
1402 * LISTBOX_MakeItemVisible
1404 * Make sure that a given item is partially or fully visible.
1406 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1408 INT top;
1410 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1412 if (index <= descr->top_item) top = index;
1413 else if (descr->style & LBS_MULTICOLUMN)
1415 INT cols = descr->width;
1416 if (!fully) cols += descr->column_width - 1;
1417 if (cols >= descr->column_width) cols /= descr->column_width;
1418 else cols = 1;
1419 if (index < descr->top_item + (descr->page_size * cols)) return;
1420 top = index - descr->page_size * (cols - 1);
1422 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1424 INT height = fully ? get_item_height(descr, index) : 1;
1425 for (top = index; top > descr->top_item; top--)
1426 if ((height += get_item_height(descr, top - 1)) > descr->height) break;
1428 else
1430 if (index < descr->top_item + descr->page_size) return;
1431 if (!fully && (index == descr->top_item + descr->page_size) &&
1432 (descr->height > (descr->page_size * descr->item_height))) return;
1433 top = index - descr->page_size + 1;
1435 LISTBOX_SetTopItem( descr, top, TRUE );
1438 /***********************************************************************
1439 * LISTBOX_SetCaretIndex
1441 * NOTES
1442 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1445 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1447 BOOL focus_changed = descr->focus_item != index;
1449 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1451 if (descr->style & LBS_NOSEL) return LB_ERR;
1452 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1454 if (focus_changed)
1456 LISTBOX_DrawFocusRect( descr, FALSE );
1457 descr->focus_item = index;
1460 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1462 if (focus_changed)
1463 LISTBOX_DrawFocusRect( descr, TRUE );
1465 return LB_OKAY;
1469 /***********************************************************************
1470 * LISTBOX_SelectItemRange
1472 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1474 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1475 INT last, BOOL on )
1477 INT i;
1479 /* A few sanity checks */
1481 if (descr->style & LBS_NOSEL) return LB_ERR;
1482 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1484 if (!descr->nb_items) return LB_OKAY;
1486 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1487 if (first < 0) first = 0;
1488 if (last < first) return LB_OKAY;
1490 if (on) /* Turn selection on */
1492 for (i = first; i <= last; i++)
1494 if (is_item_selected(descr, i)) continue;
1495 set_item_selected_state(descr, i, TRUE);
1496 LISTBOX_InvalidateItemRect(descr, i);
1499 else /* Turn selection off */
1501 for (i = first; i <= last; i++)
1503 if (!is_item_selected(descr, i)) continue;
1504 set_item_selected_state(descr, i, FALSE);
1505 LISTBOX_InvalidateItemRect(descr, i);
1508 return LB_OKAY;
1511 /***********************************************************************
1512 * LISTBOX_SetSelection
1514 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1515 BOOL on, BOOL send_notify )
1517 TRACE( "cur_sel=%d index=%d notify=%s\n",
1518 descr->selected_item, index, send_notify ? "YES" : "NO" );
1520 if (descr->style & LBS_NOSEL)
1522 descr->selected_item = index;
1523 return LB_ERR;
1525 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1526 if (descr->style & LBS_MULTIPLESEL)
1528 if (index == -1) /* Select all items */
1529 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1530 else /* Only one item */
1531 return LISTBOX_SelectItemRange( descr, index, index, on );
1533 else
1535 INT oldsel = descr->selected_item;
1536 if (index == oldsel) return LB_OKAY;
1537 if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
1538 if (index != -1) set_item_selected_state(descr, index, TRUE);
1539 descr->selected_item = index;
1540 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1541 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1542 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1543 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1544 else
1545 if( descr->lphc ) /* set selection change flag for parent combo */
1546 descr->lphc->wState |= CBF_SELCHANGE;
1548 return LB_OKAY;
1552 /***********************************************************************
1553 * LISTBOX_MoveCaret
1555 * Change the caret position and extend the selection to the new caret.
1557 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1559 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1561 if ((index < 0) || (index >= descr->nb_items))
1562 return;
1564 /* Important, repaint needs to be done in this order if
1565 you want to mimic Windows behavior:
1566 1. Remove the focus and paint the item
1567 2. Remove the selection and paint the item(s)
1568 3. Set the selection and repaint the item(s)
1569 4. Set the focus to 'index' and repaint the item */
1571 /* 1. remove the focus and repaint the item */
1572 LISTBOX_DrawFocusRect( descr, FALSE );
1574 /* 2. then turn off the previous selection */
1575 /* 3. repaint the new selected item */
1576 if (descr->style & LBS_EXTENDEDSEL)
1578 if (descr->anchor_item != -1)
1580 INT first = min( index, descr->anchor_item );
1581 INT last = max( index, descr->anchor_item );
1582 if (first > 0)
1583 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1584 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1585 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1588 else if (!(descr->style & LBS_MULTIPLESEL))
1590 /* Set selection to new caret item */
1591 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1594 /* 4. repaint the new item with the focus */
1595 descr->focus_item = index;
1596 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1597 LISTBOX_DrawFocusRect( descr, TRUE );
1601 /***********************************************************************
1602 * LISTBOX_InsertItem
1604 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1605 LPWSTR str, ULONG_PTR data )
1607 INT oldfocus = descr->focus_item;
1609 if (index == -1) index = descr->nb_items;
1610 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1611 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1613 insert_item_data(descr, index);
1614 descr->nb_items++;
1615 set_item_string(descr, index, str);
1616 set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
1617 set_item_height(descr, index, 0);
1618 set_item_selected_state(descr, index, FALSE);
1620 /* Get item height */
1622 if (descr->style & LBS_OWNERDRAWVARIABLE)
1624 MEASUREITEMSTRUCT mis;
1625 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1627 mis.CtlType = ODT_LISTBOX;
1628 mis.CtlID = id;
1629 mis.itemID = index;
1630 mis.itemData = str ? (ULONG_PTR)str : data;
1631 mis.itemHeight = descr->item_height;
1632 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1633 set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
1634 TRACE("[%p]: measure item %d (%s) = %d\n",
1635 descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
1638 /* Repaint the items */
1640 LISTBOX_UpdateScroll( descr );
1641 LISTBOX_InvalidateItems( descr, index );
1643 /* Move selection and focused item */
1644 /* If listbox was empty, set focus to the first item */
1645 if (descr->nb_items == 1)
1646 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1647 /* single select don't change selection index in win31 */
1648 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1650 descr->selected_item++;
1651 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1653 else
1655 if (index <= descr->selected_item)
1657 descr->selected_item++;
1658 descr->focus_item = oldfocus; /* focus not changed */
1661 return LB_OKAY;
1665 /***********************************************************************
1666 * LISTBOX_InsertString
1668 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1670 LPWSTR new_str = NULL;
1671 LRESULT ret;
1673 if (HAS_STRINGS(descr))
1675 if (!str) str = L"";
1676 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str) + 1) * sizeof(WCHAR) )))
1678 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1679 return LB_ERRSPACE;
1681 lstrcpyW(new_str, str);
1684 if (index == -1) index = descr->nb_items;
1685 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1687 HeapFree( GetProcessHeap(), 0, new_str );
1688 return ret;
1691 TRACE("[%p]: added item %d %s\n",
1692 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1693 return index;
1697 /***********************************************************************
1698 * LISTBOX_DeleteItem
1700 * Delete the content of an item. 'index' must be a valid index.
1702 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1704 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1705 * while Win95 sends it for all items with user data.
1706 * It's probably better to send it too often than not
1707 * often enough, so this is what we do here.
1709 if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
1711 DELETEITEMSTRUCT dis;
1712 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1714 dis.CtlType = ODT_LISTBOX;
1715 dis.CtlID = id;
1716 dis.itemID = index;
1717 dis.hwndItem = descr->self;
1718 dis.itemData = get_item_data(descr, index);
1719 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1721 HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
1725 /***********************************************************************
1726 * LISTBOX_RemoveItem
1728 * Remove an item from the listbox and delete its content.
1730 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1732 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1734 /* We need to invalidate the original rect instead of the updated one. */
1735 LISTBOX_InvalidateItems( descr, index );
1737 if (descr->nb_items == 1)
1739 SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1740 return LB_OKAY;
1742 descr->nb_items--;
1743 LISTBOX_DeleteItem( descr, index );
1744 remove_item_data(descr, index);
1746 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1747 resize_storage(descr, descr->nb_items);
1749 /* Repaint the items */
1751 LISTBOX_UpdateScroll( descr );
1752 /* if we removed the scrollbar, reset the top of the list
1753 (correct for owner-drawn ???) */
1754 if (descr->nb_items == descr->page_size)
1755 LISTBOX_SetTopItem( descr, 0, TRUE );
1757 /* Move selection and focused item */
1758 if (!IS_MULTISELECT(descr))
1760 if (index == descr->selected_item)
1761 descr->selected_item = -1;
1762 else if (index < descr->selected_item)
1764 descr->selected_item--;
1765 if (ISWIN31) /* win 31 do not change the selected item number */
1766 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1770 if (descr->focus_item >= descr->nb_items)
1772 descr->focus_item = descr->nb_items - 1;
1773 if (descr->focus_item < 0) descr->focus_item = 0;
1775 return LB_OKAY;
1779 /***********************************************************************
1780 * LISTBOX_ResetContent
1782 static void LISTBOX_ResetContent( LB_DESCR *descr )
1784 INT i;
1786 if (!(descr->style & LBS_NODATA))
1787 for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
1788 HeapFree( GetProcessHeap(), 0, descr->u.items );
1789 descr->nb_items = 0;
1790 descr->top_item = 0;
1791 descr->selected_item = -1;
1792 descr->focus_item = 0;
1793 descr->anchor_item = -1;
1794 descr->items_size = 0;
1795 descr->u.items = NULL;
1799 /***********************************************************************
1800 * LISTBOX_SetCount
1802 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1804 UINT orig_num = descr->nb_items;
1806 if (!(descr->style & LBS_NODATA))
1808 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1809 return LB_ERR;
1812 if (!resize_storage(descr, count))
1813 return LB_ERRSPACE;
1814 descr->nb_items = count;
1815 if (descr->style & LBS_NOREDRAW)
1816 descr->style |= LBS_DISPLAYCHANGED;
1818 if (count)
1820 LISTBOX_UpdateScroll(descr);
1821 if (count < orig_num)
1823 descr->anchor_item = min(descr->anchor_item, count - 1);
1824 if (descr->selected_item >= count)
1825 descr->selected_item = -1;
1827 /* If we removed the scrollbar, reset the top of the list */
1828 if (count <= descr->page_size && orig_num > descr->page_size)
1829 LISTBOX_SetTopItem(descr, 0, TRUE);
1831 descr->focus_item = min(descr->focus_item, count - 1);
1834 /* If it was empty before growing, set focus to the first item */
1835 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1837 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1839 NtUserInvalidateRect( descr->self, NULL, TRUE );
1840 return LB_OKAY;
1844 /***********************************************************************
1845 * LISTBOX_Directory
1847 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1848 LPCWSTR filespec, BOOL long_names )
1850 HANDLE handle;
1851 LRESULT ret = LB_OKAY;
1852 WIN32_FIND_DATAW entry;
1853 int pos;
1854 LRESULT maxinsert = LB_ERR;
1856 /* don't scan directory if we just want drives exclusively */
1857 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1858 /* scan directory */
1859 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1861 int le = GetLastError();
1862 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1864 else
1868 WCHAR buffer[270];
1869 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1871 if (!(attrib & DDL_DIRECTORY) ||
1872 !wcscmp( entry.cFileName, L"." )) continue;
1873 buffer[0] = '[';
1874 if (!long_names && entry.cAlternateFileName[0])
1875 lstrcpyW( buffer + 1, entry.cAlternateFileName );
1876 else
1877 lstrcpyW( buffer + 1, entry.cFileName );
1878 lstrcatW(buffer, L"]");
1880 else /* not a directory */
1882 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1883 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1885 if ((attrib & DDL_EXCLUSIVE) &&
1886 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1887 continue;
1888 #undef ATTRIBS
1889 if (!long_names && entry.cAlternateFileName[0])
1890 lstrcpyW( buffer, entry.cAlternateFileName );
1891 else
1892 lstrcpyW( buffer, entry.cFileName );
1894 if (!long_names) CharLowerW( buffer );
1895 pos = LISTBOX_FindFileStrPos( descr, buffer );
1896 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1897 break;
1898 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1899 } while (FindNextFileW( handle, &entry ));
1900 FindClose( handle );
1903 if (ret >= 0)
1905 ret = maxinsert;
1907 /* scan drives */
1908 if (attrib & DDL_DRIVES)
1910 WCHAR buffer[] = L"[-a-]";
1911 WCHAR root[] = L"A:\\";
1912 int drive;
1913 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1915 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1916 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1917 break;
1921 return ret;
1925 /***********************************************************************
1926 * LISTBOX_HandleVScroll
1928 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1930 SCROLLINFO info;
1932 if (descr->style & LBS_MULTICOLUMN) return 0;
1933 switch(scrollReq)
1935 case SB_LINEUP:
1936 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1937 break;
1938 case SB_LINEDOWN:
1939 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1940 break;
1941 case SB_PAGEUP:
1942 LISTBOX_SetTopItem( descr, descr->top_item -
1943 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1944 break;
1945 case SB_PAGEDOWN:
1946 LISTBOX_SetTopItem( descr, descr->top_item +
1947 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1948 break;
1949 case SB_THUMBPOSITION:
1950 LISTBOX_SetTopItem( descr, pos, TRUE );
1951 break;
1952 case SB_THUMBTRACK:
1953 info.cbSize = sizeof(info);
1954 info.fMask = SIF_TRACKPOS;
1955 GetScrollInfo( descr->self, SB_VERT, &info );
1956 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1957 break;
1958 case SB_TOP:
1959 LISTBOX_SetTopItem( descr, 0, TRUE );
1960 break;
1961 case SB_BOTTOM:
1962 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1963 break;
1965 return 0;
1969 /***********************************************************************
1970 * LISTBOX_HandleHScroll
1972 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1974 SCROLLINFO info;
1975 INT page;
1977 if (descr->style & LBS_MULTICOLUMN)
1979 switch(scrollReq)
1981 case SB_LINELEFT:
1982 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1983 TRUE );
1984 break;
1985 case SB_LINERIGHT:
1986 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1987 TRUE );
1988 break;
1989 case SB_PAGELEFT:
1990 page = descr->width / descr->column_width;
1991 if (page < 1) page = 1;
1992 LISTBOX_SetTopItem( descr,
1993 descr->top_item - page * descr->page_size, TRUE );
1994 break;
1995 case SB_PAGERIGHT:
1996 page = descr->width / descr->column_width;
1997 if (page < 1) page = 1;
1998 LISTBOX_SetTopItem( descr,
1999 descr->top_item + page * descr->page_size, TRUE );
2000 break;
2001 case SB_THUMBPOSITION:
2002 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
2003 break;
2004 case SB_THUMBTRACK:
2005 info.cbSize = sizeof(info);
2006 info.fMask = SIF_TRACKPOS;
2007 GetScrollInfo( descr->self, SB_VERT, &info );
2008 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
2009 TRUE );
2010 break;
2011 case SB_LEFT:
2012 LISTBOX_SetTopItem( descr, 0, TRUE );
2013 break;
2014 case SB_RIGHT:
2015 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2016 break;
2019 else if (descr->horz_extent)
2021 switch(scrollReq)
2023 case SB_LINELEFT:
2024 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2025 break;
2026 case SB_LINERIGHT:
2027 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2028 break;
2029 case SB_PAGELEFT:
2030 LISTBOX_SetHorizontalPos( descr,
2031 descr->horz_pos - descr->width );
2032 break;
2033 case SB_PAGERIGHT:
2034 LISTBOX_SetHorizontalPos( descr,
2035 descr->horz_pos + descr->width );
2036 break;
2037 case SB_THUMBPOSITION:
2038 LISTBOX_SetHorizontalPos( descr, pos );
2039 break;
2040 case SB_THUMBTRACK:
2041 info.cbSize = sizeof(info);
2042 info.fMask = SIF_TRACKPOS;
2043 GetScrollInfo( descr->self, SB_HORZ, &info );
2044 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2045 break;
2046 case SB_LEFT:
2047 LISTBOX_SetHorizontalPos( descr, 0 );
2048 break;
2049 case SB_RIGHT:
2050 LISTBOX_SetHorizontalPos( descr,
2051 descr->horz_extent - descr->width );
2052 break;
2055 return 0;
2058 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2060 INT pulScrollLines = 3;
2062 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2064 /* if scrolling changes direction, ignore left overs */
2065 if ((delta < 0 && descr->wheel_remain < 0) ||
2066 (delta > 0 && descr->wheel_remain > 0))
2067 descr->wheel_remain += delta;
2068 else
2069 descr->wheel_remain = delta;
2071 if (descr->wheel_remain && pulScrollLines)
2073 int cLineScroll;
2074 if (descr->style & LBS_MULTICOLUMN)
2076 pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
2077 pulScrollLines = max(1, pulScrollLines);
2078 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2079 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2080 cLineScroll *= descr->page_size;
2082 else
2084 pulScrollLines = min(descr->page_size, pulScrollLines);
2085 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2086 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2088 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2090 return 0;
2093 /***********************************************************************
2094 * LISTBOX_HandleLButtonDown
2096 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2098 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2100 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2101 descr->self, x, y, index, descr->focus_item);
2103 if (!descr->caret_on && (descr->in_focus)) return 0;
2105 if (!descr->in_focus)
2107 if( !descr->lphc ) NtUserSetFocus( descr->self );
2108 else NtUserSetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2111 if (index == -1) return 0;
2113 if (!descr->lphc)
2115 if (descr->style & LBS_NOTIFY )
2116 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2117 MAKELPARAM( x, y ) );
2120 descr->captured = TRUE;
2121 NtUserSetCapture( descr->self );
2123 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2125 /* we should perhaps make sure that all items are deselected
2126 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2127 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2128 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2131 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2132 if (keys & MK_CONTROL)
2134 LISTBOX_SetCaretIndex( descr, index, FALSE );
2135 LISTBOX_SetSelection( descr, index,
2136 !is_item_selected(descr, index),
2137 (descr->style & LBS_NOTIFY) != 0);
2139 else
2141 LISTBOX_MoveCaret( descr, index, FALSE );
2143 if (descr->style & LBS_EXTENDEDSEL)
2145 LISTBOX_SetSelection( descr, index,
2146 is_item_selected(descr, index),
2147 (descr->style & LBS_NOTIFY) != 0 );
2149 else
2151 LISTBOX_SetSelection( descr, index,
2152 !is_item_selected(descr, index),
2153 (descr->style & LBS_NOTIFY) != 0 );
2157 else
2159 descr->anchor_item = index;
2160 LISTBOX_MoveCaret( descr, index, FALSE );
2161 LISTBOX_SetSelection( descr, index,
2162 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2165 if (!descr->lphc)
2167 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2169 POINT pt;
2171 pt.x = x;
2172 pt.y = y;
2174 if (DragDetect( descr->self, pt ))
2175 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2178 return 0;
2182 /*************************************************************************
2183 * LISTBOX_HandleLButtonDownCombo [Internal]
2185 * Process LButtonDown message for the ComboListBox
2187 * PARAMS
2188 * pWnd [I] The windows internal structure
2189 * pDescr [I] The ListBox internal structure
2190 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2191 * x [I] X Mouse Coordinate
2192 * y [I] Y Mouse Coordinate
2194 * RETURNS
2195 * 0 since we are processing the WM_LBUTTONDOWN Message
2197 * NOTES
2198 * This function is only to be used when a ListBox is a ComboListBox
2201 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2203 RECT clientRect, screenRect;
2204 POINT mousePos;
2206 mousePos.x = x;
2207 mousePos.y = y;
2209 GetClientRect(descr->self, &clientRect);
2211 if(PtInRect(&clientRect, mousePos))
2213 /* MousePos is in client, resume normal processing */
2214 if (msg == WM_LBUTTONDOWN)
2216 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2217 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2219 else if (descr->style & LBS_NOTIFY)
2220 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2222 else
2224 POINT screenMousePos;
2225 HWND hWndOldCapture;
2227 /* Check the Non-Client Area */
2228 screenMousePos = mousePos;
2229 hWndOldCapture = GetCapture();
2230 ReleaseCapture();
2231 GetWindowRect(descr->self, &screenRect);
2232 ClientToScreen(descr->self, &screenMousePos);
2234 if(!PtInRect(&screenRect, screenMousePos))
2236 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2237 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2238 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2240 else
2242 /* Check to see the NC is a scrollbar */
2243 INT nHitTestType=0;
2244 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2245 /* Check Vertical scroll bar */
2246 if (style & WS_VSCROLL)
2248 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2249 if (PtInRect( &clientRect, mousePos ))
2250 nHitTestType = HTVSCROLL;
2252 /* Check horizontal scroll bar */
2253 if (style & WS_HSCROLL)
2255 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2256 if (PtInRect( &clientRect, mousePos ))
2257 nHitTestType = HTHSCROLL;
2259 /* Windows sends this message when a scrollbar is clicked
2262 if(nHitTestType != 0)
2264 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2265 MAKELONG(screenMousePos.x, screenMousePos.y));
2267 /* Resume the Capture after scrolling is complete
2269 if(hWndOldCapture != 0)
2270 NtUserSetCapture(hWndOldCapture);
2273 return 0;
2276 /***********************************************************************
2277 * LISTBOX_HandleLButtonUp
2279 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2281 if (LISTBOX_Timer != LB_TIMER_NONE)
2282 KillSystemTimer( descr->self, LB_TIMER_ID );
2283 LISTBOX_Timer = LB_TIMER_NONE;
2284 if (descr->captured)
2286 descr->captured = FALSE;
2287 if (GetCapture() == descr->self) ReleaseCapture();
2288 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2289 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2291 return 0;
2295 /***********************************************************************
2296 * LISTBOX_HandleTimer
2298 * Handle scrolling upon a timer event.
2299 * Return TRUE if scrolling should continue.
2301 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2303 switch(dir)
2305 case LB_TIMER_UP:
2306 if (descr->top_item) index = descr->top_item - 1;
2307 else index = 0;
2308 break;
2309 case LB_TIMER_LEFT:
2310 if (descr->top_item) index -= descr->page_size;
2311 break;
2312 case LB_TIMER_DOWN:
2313 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2314 if (index == descr->focus_item) index++;
2315 if (index >= descr->nb_items) index = descr->nb_items - 1;
2316 break;
2317 case LB_TIMER_RIGHT:
2318 if (index + descr->page_size < descr->nb_items)
2319 index += descr->page_size;
2320 break;
2321 case LB_TIMER_NONE:
2322 break;
2324 if (index == descr->focus_item) return FALSE;
2325 LISTBOX_MoveCaret( descr, index, FALSE );
2326 return TRUE;
2330 /***********************************************************************
2331 * LISTBOX_HandleSystemTimer
2333 * WM_SYSTIMER handler.
2335 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2337 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2339 KillSystemTimer( descr->self, LB_TIMER_ID );
2340 LISTBOX_Timer = LB_TIMER_NONE;
2342 return 0;
2346 /***********************************************************************
2347 * LISTBOX_HandleMouseMove
2349 * WM_MOUSEMOVE handler.
2351 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2352 INT x, INT y )
2354 INT index;
2355 TIMER_DIRECTION dir = LB_TIMER_NONE;
2357 if (!descr->captured) return;
2359 if (descr->style & LBS_MULTICOLUMN)
2361 if (y < 0) y = 0;
2362 else if (y >= descr->item_height * descr->page_size)
2363 y = descr->item_height * descr->page_size - 1;
2365 if (x < 0)
2367 dir = LB_TIMER_LEFT;
2368 x = 0;
2370 else if (x >= descr->width)
2372 dir = LB_TIMER_RIGHT;
2373 x = descr->width - 1;
2376 else
2378 if (y < 0) dir = LB_TIMER_UP; /* above */
2379 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2382 index = LISTBOX_GetItemFromPoint( descr, x, y );
2383 if (index == -1) index = descr->focus_item;
2384 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2386 /* Start/stop the system timer */
2388 if (dir != LB_TIMER_NONE)
2389 NtUserSetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT );
2390 else if (LISTBOX_Timer != LB_TIMER_NONE)
2391 KillSystemTimer( descr->self, LB_TIMER_ID );
2392 LISTBOX_Timer = dir;
2396 /***********************************************************************
2397 * LISTBOX_HandleKeyDown
2399 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2401 INT caret = -1;
2402 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2403 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2404 bForceSelection = FALSE; /* only for single select list */
2406 if (descr->style & LBS_WANTKEYBOARDINPUT)
2408 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2409 MAKEWPARAM(LOWORD(key), descr->focus_item),
2410 (LPARAM)descr->self );
2411 if (caret == -2) return 0;
2413 if (caret == -1) switch(key)
2415 case VK_LEFT:
2416 if (descr->style & LBS_MULTICOLUMN)
2418 bForceSelection = FALSE;
2419 if (descr->focus_item >= descr->page_size)
2420 caret = descr->focus_item - descr->page_size;
2421 break;
2423 /* fall through */
2424 case VK_UP:
2425 caret = descr->focus_item - 1;
2426 if (caret < 0) caret = 0;
2427 break;
2428 case VK_RIGHT:
2429 if (descr->style & LBS_MULTICOLUMN)
2431 bForceSelection = FALSE;
2432 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2433 break;
2435 /* fall through */
2436 case VK_DOWN:
2437 caret = descr->focus_item + 1;
2438 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2439 break;
2441 case VK_PRIOR:
2442 if (descr->style & LBS_MULTICOLUMN)
2444 INT page = descr->width / descr->column_width;
2445 if (page < 1) page = 1;
2446 caret = descr->focus_item - (page * descr->page_size) + 1;
2448 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2449 if (caret < 0) caret = 0;
2450 break;
2451 case VK_NEXT:
2452 if (descr->style & LBS_MULTICOLUMN)
2454 INT page = descr->width / descr->column_width;
2455 if (page < 1) page = 1;
2456 caret = descr->focus_item + (page * descr->page_size) - 1;
2458 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2459 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2460 break;
2461 case VK_HOME:
2462 caret = 0;
2463 break;
2464 case VK_END:
2465 caret = descr->nb_items - 1;
2466 break;
2467 case VK_SPACE:
2468 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2469 else if (descr->style & LBS_MULTIPLESEL)
2471 LISTBOX_SetSelection( descr, descr->focus_item,
2472 !is_item_selected(descr, descr->focus_item),
2473 (descr->style & LBS_NOTIFY) != 0 );
2475 break;
2476 default:
2477 bForceSelection = FALSE;
2479 if (bForceSelection) /* focused item is used instead of key */
2480 caret = descr->focus_item;
2481 if (caret >= 0)
2483 if (((descr->style & LBS_EXTENDEDSEL) &&
2484 !(NtUserGetKeyState( VK_SHIFT ) & 0x8000)) ||
2485 !IS_MULTISELECT(descr))
2486 descr->anchor_item = caret;
2487 LISTBOX_MoveCaret( descr, caret, TRUE );
2489 if (descr->style & LBS_MULTIPLESEL)
2490 descr->selected_item = caret;
2491 else
2492 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2493 if (descr->style & LBS_NOTIFY)
2495 if (descr->lphc && IsWindowVisible( descr->self ))
2497 /* make sure that combo parent doesn't hide us */
2498 descr->lphc->wState |= CBF_NOROLLUP;
2500 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2503 return 0;
2507 /***********************************************************************
2508 * LISTBOX_HandleChar
2510 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2512 INT caret = -1;
2513 WCHAR str[2];
2515 str[0] = charW;
2516 str[1] = '\0';
2518 if (descr->style & LBS_WANTKEYBOARDINPUT)
2520 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2521 MAKEWPARAM(charW, descr->focus_item),
2522 (LPARAM)descr->self );
2523 if (caret == -2) return 0;
2525 if (caret == -1 && !(descr->style & LBS_NODATA))
2526 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2527 if (caret != -1)
2529 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2530 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2531 LISTBOX_MoveCaret( descr, caret, TRUE );
2532 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2533 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2535 return 0;
2539 /***********************************************************************
2540 * LISTBOX_Create
2542 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2544 LB_DESCR *descr;
2545 MEASUREITEMSTRUCT mis;
2546 RECT rect;
2548 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2549 return FALSE;
2551 GetClientRect( hwnd, &rect );
2552 descr->self = hwnd;
2553 descr->owner = GetParent( descr->self );
2554 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2555 descr->width = rect.right - rect.left;
2556 descr->height = rect.bottom - rect.top;
2557 descr->u.items = NULL;
2558 descr->items_size = 0;
2559 descr->nb_items = 0;
2560 descr->top_item = 0;
2561 descr->selected_item = -1;
2562 descr->focus_item = 0;
2563 descr->anchor_item = -1;
2564 descr->item_height = 1;
2565 descr->page_size = 1;
2566 descr->column_width = 150;
2567 descr->horz_extent = 0;
2568 descr->horz_pos = 0;
2569 descr->nb_tabs = 0;
2570 descr->tabs = NULL;
2571 descr->wheel_remain = 0;
2572 descr->caret_on = !lphc;
2573 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2574 descr->in_focus = FALSE;
2575 descr->captured = FALSE;
2576 descr->font = 0;
2577 descr->locale = GetUserDefaultLCID();
2578 descr->lphc = lphc;
2580 if( lphc )
2582 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2583 descr->owner = lphc->self;
2586 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2588 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2590 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2591 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2592 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2593 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2594 descr->style &= ~LBS_NODATA;
2595 descr->item_height = LISTBOX_SetFont( descr, 0 );
2597 if (descr->style & LBS_OWNERDRAWFIXED)
2599 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2601 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2603 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2604 descr->item_height = lphc->fixedOwnerDrawHeight;
2606 else
2608 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2609 mis.CtlType = ODT_LISTBOX;
2610 mis.CtlID = id;
2611 mis.itemID = -1;
2612 mis.itemWidth = 0;
2613 mis.itemData = 0;
2614 mis.itemHeight = descr->item_height;
2615 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2616 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2620 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2621 return TRUE;
2625 /***********************************************************************
2626 * LISTBOX_Destroy
2628 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2630 LISTBOX_ResetContent( descr );
2631 SetWindowLongPtrW( descr->self, 0, 0 );
2632 HeapFree( GetProcessHeap(), 0, descr );
2633 return TRUE;
2637 /***********************************************************************
2638 * ListBoxWndProc_common
2640 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2642 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2643 HEADCOMBO *lphc = NULL;
2644 LRESULT ret;
2646 if (!descr)
2648 if (!IsWindow(hwnd)) return 0;
2650 if (msg == WM_CREATE)
2652 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2653 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2654 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2655 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2656 return 0;
2658 /* Ignore all other messages before we get a WM_CREATE */
2659 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2660 DefWindowProcA( hwnd, msg, wParam, lParam );
2662 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2664 TRACE("[%p]: msg %s wp %08Ix lp %08Ix\n",
2665 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2667 switch(msg)
2669 case LB_RESETCONTENT:
2670 LISTBOX_ResetContent( descr );
2671 LISTBOX_UpdateScroll( descr );
2672 NtUserInvalidateRect( descr->self, NULL, TRUE );
2673 return 0;
2675 case LB_ADDSTRING:
2677 INT ret;
2678 LPWSTR textW;
2679 if(unicode || !HAS_STRINGS(descr))
2680 textW = (LPWSTR)lParam;
2681 else
2683 LPSTR textA = (LPSTR)lParam;
2684 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2685 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2686 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2687 else
2688 return LB_ERRSPACE;
2690 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2691 ret = LISTBOX_InsertString( descr, wParam, textW );
2692 if (!unicode && HAS_STRINGS(descr))
2693 HeapFree(GetProcessHeap(), 0, textW);
2694 return ret;
2697 case LB_INSERTSTRING:
2699 INT ret;
2700 LPWSTR textW;
2701 if(unicode || !HAS_STRINGS(descr))
2702 textW = (LPWSTR)lParam;
2703 else
2705 LPSTR textA = (LPSTR)lParam;
2706 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2707 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2708 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2709 else
2710 return LB_ERRSPACE;
2712 ret = LISTBOX_InsertString( descr, wParam, textW );
2713 if(!unicode && HAS_STRINGS(descr))
2714 HeapFree(GetProcessHeap(), 0, textW);
2715 return ret;
2718 case LB_ADDFILE:
2720 INT ret;
2721 LPWSTR textW;
2722 if(unicode || !HAS_STRINGS(descr))
2723 textW = (LPWSTR)lParam;
2724 else
2726 LPSTR textA = (LPSTR)lParam;
2727 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2728 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2729 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2730 else
2731 return LB_ERRSPACE;
2733 wParam = LISTBOX_FindFileStrPos( descr, textW );
2734 ret = LISTBOX_InsertString( descr, wParam, textW );
2735 if(!unicode && HAS_STRINGS(descr))
2736 HeapFree(GetProcessHeap(), 0, textW);
2737 return ret;
2740 case LB_DELETESTRING:
2741 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2742 return descr->nb_items;
2743 else
2745 SetLastError(ERROR_INVALID_INDEX);
2746 return LB_ERR;
2749 case LB_GETITEMDATA:
2750 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2752 SetLastError(ERROR_INVALID_INDEX);
2753 return LB_ERR;
2755 return get_item_data(descr, wParam);
2757 case LB_SETITEMDATA:
2758 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2760 SetLastError(ERROR_INVALID_INDEX);
2761 return LB_ERR;
2763 set_item_data(descr, wParam, lParam);
2764 /* undocumented: returns TRUE, not LB_OKAY (0) */
2765 return TRUE;
2767 case LB_GETCOUNT:
2768 return descr->nb_items;
2770 case LB_GETTEXT:
2771 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2773 case LB_GETTEXTLEN:
2774 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2776 SetLastError(ERROR_INVALID_INDEX);
2777 return LB_ERR;
2779 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2780 if (unicode) return lstrlenW(get_item_string(descr, wParam));
2781 return WideCharToMultiByte( CP_ACP, 0, get_item_string(descr, wParam),
2782 lstrlenW(get_item_string(descr, wParam)), NULL, 0, NULL, NULL );
2784 case LB_GETCURSEL:
2785 if (descr->nb_items == 0)
2786 return LB_ERR;
2787 if (!IS_MULTISELECT(descr))
2788 return descr->selected_item;
2789 if (descr->selected_item != -1)
2790 return descr->selected_item;
2791 return descr->focus_item;
2792 /* otherwise, if the user tries to move the selection with the */
2793 /* arrow keys, we will give the application something to choke on */
2794 case LB_GETTOPINDEX:
2795 return descr->top_item;
2797 case LB_GETITEMHEIGHT:
2798 return LISTBOX_GetItemHeight( descr, wParam );
2800 case LB_SETITEMHEIGHT:
2801 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2803 case LB_ITEMFROMPOINT:
2805 POINT pt;
2806 RECT rect;
2807 int index;
2808 BOOL hit = TRUE;
2810 /* The hiword of the return value is not a client area
2811 hittest as suggested by MSDN, but rather a hittest on
2812 the returned listbox item. */
2814 if(descr->nb_items == 0)
2815 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2817 pt.x = (short)LOWORD(lParam);
2818 pt.y = (short)HIWORD(lParam);
2820 SetRect(&rect, 0, 0, descr->width, descr->height);
2822 if(!PtInRect(&rect, pt))
2824 pt.x = min(pt.x, rect.right - 1);
2825 pt.x = max(pt.x, 0);
2826 pt.y = min(pt.y, rect.bottom - 1);
2827 pt.y = max(pt.y, 0);
2828 hit = FALSE;
2831 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2833 if(index == -1)
2835 index = descr->nb_items - 1;
2836 hit = FALSE;
2838 return MAKELONG(index, hit ? 0 : 1);
2841 case LB_SETCARETINDEX:
2842 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2843 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2844 return LB_ERR;
2845 else if (ISWIN31)
2846 return wParam;
2847 else
2848 return LB_OKAY;
2850 case LB_GETCARETINDEX:
2851 return descr->focus_item;
2853 case LB_SETTOPINDEX:
2854 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2856 case LB_SETCOLUMNWIDTH:
2857 return LISTBOX_SetColumnWidth( descr, wParam );
2859 case LB_GETITEMRECT:
2860 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2862 case LB_FINDSTRING:
2864 INT ret;
2865 LPWSTR textW;
2866 if(unicode || !HAS_STRINGS(descr))
2867 textW = (LPWSTR)lParam;
2868 else
2870 LPSTR textA = (LPSTR)lParam;
2871 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2872 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2873 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2875 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2876 if(!unicode && HAS_STRINGS(descr))
2877 HeapFree(GetProcessHeap(), 0, textW);
2878 return ret;
2881 case LB_FINDSTRINGEXACT:
2883 INT ret;
2884 LPWSTR textW;
2885 if(unicode || !HAS_STRINGS(descr))
2886 textW = (LPWSTR)lParam;
2887 else
2889 LPSTR textA = (LPSTR)lParam;
2890 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2891 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2892 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2894 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2895 if(!unicode && HAS_STRINGS(descr))
2896 HeapFree(GetProcessHeap(), 0, textW);
2897 return ret;
2900 case LB_SELECTSTRING:
2902 INT index;
2903 LPWSTR textW;
2905 if(HAS_STRINGS(descr))
2906 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2907 debugstr_a((LPSTR)lParam));
2908 if(unicode || !HAS_STRINGS(descr))
2909 textW = (LPWSTR)lParam;
2910 else
2912 LPSTR textA = (LPSTR)lParam;
2913 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2914 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2915 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2917 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2918 if(!unicode && HAS_STRINGS(descr))
2919 HeapFree(GetProcessHeap(), 0, textW);
2920 if (index != LB_ERR)
2922 LISTBOX_MoveCaret( descr, index, TRUE );
2923 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2925 return index;
2928 case LB_GETSEL:
2929 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2930 return LB_ERR;
2931 return is_item_selected(descr, wParam);
2933 case LB_SETSEL:
2934 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2935 if (ret != LB_ERR && wParam)
2937 descr->anchor_item = lParam;
2938 if (lParam != -1)
2939 LISTBOX_SetCaretIndex( descr, lParam, TRUE );
2941 return ret;
2943 case LB_SETCURSEL:
2944 if (IS_MULTISELECT(descr)) return LB_ERR;
2945 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2946 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2947 if (ret != LB_ERR) ret = descr->selected_item;
2948 return ret;
2950 case LB_GETSELCOUNT:
2951 return LISTBOX_GetSelCount( descr );
2953 case LB_GETSELITEMS:
2954 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2956 case LB_SELITEMRANGE:
2957 if (LOWORD(lParam) <= HIWORD(lParam))
2958 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2959 HIWORD(lParam), wParam );
2960 else
2961 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2962 LOWORD(lParam), wParam );
2964 case LB_SELITEMRANGEEX:
2965 if ((INT)lParam >= (INT)wParam)
2966 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2967 else
2968 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2970 case LB_GETHORIZONTALEXTENT:
2971 return descr->horz_extent;
2973 case LB_SETHORIZONTALEXTENT:
2974 return LISTBOX_SetHorizontalExtent( descr, wParam );
2976 case LB_GETANCHORINDEX:
2977 return descr->anchor_item;
2979 case LB_SETANCHORINDEX:
2980 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2982 SetLastError(ERROR_INVALID_INDEX);
2983 return LB_ERR;
2985 descr->anchor_item = (INT)wParam;
2986 return LB_OKAY;
2988 case LB_DIR:
2990 INT ret;
2991 LPWSTR textW;
2992 if(unicode)
2993 textW = (LPWSTR)lParam;
2994 else
2996 LPSTR textA = (LPSTR)lParam;
2997 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2998 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2999 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3001 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3002 if(!unicode)
3003 HeapFree(GetProcessHeap(), 0, textW);
3004 return ret;
3007 case LB_GETLOCALE:
3008 return descr->locale;
3010 case LB_SETLOCALE:
3012 LCID ret;
3013 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3014 return LB_ERR;
3015 ret = descr->locale;
3016 descr->locale = (LCID)wParam;
3017 return ret;
3020 case LB_INITSTORAGE:
3021 return LISTBOX_InitStorage( descr, wParam );
3023 case LB_SETCOUNT:
3024 return LISTBOX_SetCount( descr, (INT)wParam );
3026 case LB_SETTABSTOPS:
3027 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
3029 case LB_CARETON:
3030 if (descr->caret_on)
3031 return LB_OKAY;
3032 descr->caret_on = TRUE;
3033 if ((descr->focus_item != -1) && (descr->in_focus))
3034 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3035 return LB_OKAY;
3037 case LB_CARETOFF:
3038 if (!descr->caret_on)
3039 return LB_OKAY;
3040 descr->caret_on = FALSE;
3041 if ((descr->focus_item != -1) && (descr->in_focus))
3042 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3043 return LB_OKAY;
3045 case LB_GETLISTBOXINFO:
3046 return descr->page_size;
3048 case WM_DESTROY:
3049 return LISTBOX_Destroy( descr );
3051 case WM_ENABLE:
3052 NtUserInvalidateRect( descr->self, NULL, TRUE );
3053 return 0;
3055 case WM_SETREDRAW:
3056 LISTBOX_SetRedraw( descr, wParam != 0 );
3057 return 0;
3059 case WM_GETDLGCODE:
3060 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3062 case WM_PRINTCLIENT:
3063 case WM_PAINT:
3065 PAINTSTRUCT ps;
3066 HDC hdc = ( wParam ) ? ((HDC)wParam) : NtUserBeginPaint( descr->self, &ps );
3067 ret = LISTBOX_Paint( descr, hdc );
3068 if (!wParam) NtUserEndPaint( descr->self, &ps );
3070 return ret;
3071 case WM_SIZE:
3072 LISTBOX_UpdateSize( descr );
3073 return 0;
3074 case WM_GETFONT:
3075 return (LRESULT)descr->font;
3076 case WM_SETFONT:
3077 LISTBOX_SetFont( descr, (HFONT)wParam );
3078 if (lParam) NtUserInvalidateRect( descr->self, 0, TRUE );
3079 return 0;
3080 case WM_SETFOCUS:
3081 descr->in_focus = TRUE;
3082 descr->caret_on = TRUE;
3083 if (descr->focus_item != -1)
3084 LISTBOX_DrawFocusRect( descr, TRUE );
3085 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3086 return 0;
3087 case WM_KILLFOCUS:
3088 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3089 descr->in_focus = FALSE;
3090 descr->wheel_remain = 0;
3091 if ((descr->focus_item != -1) && descr->caret_on)
3092 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3093 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3094 return 0;
3095 case WM_HSCROLL:
3096 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3097 case WM_VSCROLL:
3098 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3099 case WM_MOUSEWHEEL:
3100 if (wParam & (MK_SHIFT | MK_CONTROL))
3101 return DefWindowProcW( descr->self, msg, wParam, lParam );
3102 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3103 case WM_LBUTTONDOWN:
3104 if (lphc)
3105 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3106 (INT16)LOWORD(lParam),
3107 (INT16)HIWORD(lParam) );
3108 return LISTBOX_HandleLButtonDown( descr, wParam,
3109 (INT16)LOWORD(lParam),
3110 (INT16)HIWORD(lParam) );
3111 case WM_LBUTTONDBLCLK:
3112 if (lphc)
3113 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3114 (INT16)LOWORD(lParam),
3115 (INT16)HIWORD(lParam) );
3116 if (descr->style & LBS_NOTIFY)
3117 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3118 return 0;
3119 case WM_MOUSEMOVE:
3120 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3122 BOOL captured = descr->captured;
3123 POINT mousePos;
3124 RECT clientRect;
3126 mousePos.x = (INT16)LOWORD(lParam);
3127 mousePos.y = (INT16)HIWORD(lParam);
3130 * If we are in a dropdown combobox, we simulate that
3131 * the mouse is captured to show the tracking of the item.
3133 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3134 descr->captured = TRUE;
3136 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3138 descr->captured = captured;
3140 else if (GetCapture() == descr->self)
3142 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3143 (INT16)HIWORD(lParam) );
3145 return 0;
3146 case WM_LBUTTONUP:
3147 if (lphc)
3149 POINT mousePos;
3150 RECT clientRect;
3153 * If the mouse button "up" is not in the listbox,
3154 * we make sure there is no selection by re-selecting the
3155 * item that was selected when the listbox was made visible.
3157 mousePos.x = (INT16)LOWORD(lParam);
3158 mousePos.y = (INT16)HIWORD(lParam);
3160 GetClientRect(descr->self, &clientRect);
3163 * When the user clicks outside the combobox and the focus
3164 * is lost, the owning combobox will send a fake buttonup with
3165 * 0xFFFFFFF as the mouse location, we must also revert the
3166 * selection to the original selection.
3168 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3169 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3171 return LISTBOX_HandleLButtonUp( descr );
3172 case WM_KEYDOWN:
3173 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3175 /* for some reason Windows makes it possible to
3176 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3178 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3179 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3180 && (wParam == VK_DOWN || wParam == VK_UP)) )
3182 COMBO_FlipListbox( lphc, FALSE, FALSE );
3183 return 0;
3186 return LISTBOX_HandleKeyDown( descr, wParam );
3187 case WM_CHAR:
3189 WCHAR charW;
3190 if(unicode)
3191 charW = (WCHAR)wParam;
3192 else
3194 CHAR charA = (CHAR)wParam;
3195 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3197 return LISTBOX_HandleChar( descr, charW );
3199 case WM_SYSTIMER:
3200 return LISTBOX_HandleSystemTimer( descr );
3201 case WM_ERASEBKGND:
3202 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3204 RECT rect;
3205 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3206 wParam, (LPARAM)descr->self );
3207 TRACE("hbrush = %p\n", hbrush);
3208 if(!hbrush)
3209 hbrush = GetSysColorBrush(COLOR_WINDOW);
3210 if(hbrush)
3212 GetClientRect(descr->self, &rect);
3213 FillRect((HDC)wParam, &rect, hbrush);
3216 return 1;
3217 case WM_DROPFILES:
3218 if( lphc ) return 0;
3219 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3220 SendMessageA( descr->owner, msg, wParam, lParam );
3222 case WM_NCDESTROY:
3223 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3224 lphc->hWndLBox = 0;
3225 break;
3227 case WM_NCACTIVATE:
3228 if (lphc) return 0;
3229 break;
3231 default:
3232 if ((msg >= WM_USER) && (msg < 0xc000))
3233 WARN("[%p]: unknown msg %04x wp %08Ix lp %08Ix\n",
3234 hwnd, msg, wParam, lParam );
3237 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3238 DefWindowProcA( hwnd, msg, wParam, lParam );
3241 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3243 TRACE("%p\n", hwnd);
3244 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);