d3d11: Report D3D11_FORMAT_SUPPORT_TYPED_UNORDERED_ACCESS_VIEW in CheckFormatSupport().
[wine.git] / dlls / user32 / listbox.c
blob0122f928ba8dca5972547e0293e917080e0d6a06
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);
244 /*********************************************************************
245 * listbox class descriptor
247 const struct builtin_class_descr LISTBOX_builtin_class =
249 L"ListBox", /* name */
250 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
251 WINPROC_LISTBOX, /* proc */
252 sizeof(LB_DESCR *), /* extra */
253 IDC_ARROW, /* cursor */
254 0 /* brush */
258 /*********************************************************************
259 * combolbox class descriptor
261 const struct builtin_class_descr COMBOLBOX_builtin_class =
263 L"ComboLBox", /* name */
264 CS_DBLCLKS | CS_SAVEBITS, /* style */
265 WINPROC_LISTBOX, /* proc */
266 sizeof(LB_DESCR *), /* extra */
267 IDC_ARROW, /* cursor */
268 0 /* brush */
272 /***********************************************************************
273 * LISTBOX_GetCurrentPageSize
275 * Return the current page size
277 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
279 INT i, height;
280 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
281 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
283 if ((height += get_item_height(descr, i)) > descr->height) break;
285 if (i == descr->top_item) return 1;
286 else return i - descr->top_item;
290 /***********************************************************************
291 * LISTBOX_GetMaxTopIndex
293 * Return the maximum possible index for the top of the listbox.
295 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
297 INT max, page;
299 if (descr->style & LBS_OWNERDRAWVARIABLE)
301 page = descr->height;
302 for (max = descr->nb_items - 1; max >= 0; max--)
303 if ((page -= get_item_height(descr, max)) < 0) break;
304 if (max < descr->nb_items - 1) max++;
306 else if (descr->style & LBS_MULTICOLUMN)
308 if ((page = descr->width / descr->column_width) < 1) page = 1;
309 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
310 max = (max - page) * descr->page_size;
312 else
314 max = descr->nb_items - descr->page_size;
316 if (max < 0) max = 0;
317 return max;
321 /***********************************************************************
322 * LISTBOX_UpdateScroll
324 * Update the scrollbars. Should be called whenever the content
325 * of the listbox changes.
327 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
329 SCROLLINFO info;
331 /* Check the listbox scroll bar flags individually before we call
332 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
333 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
334 scroll bar when we do not need one.
335 if (!(descr->style & WS_VSCROLL)) return;
338 /* It is important that we check descr->style, and not wnd->dwStyle,
339 for WS_VSCROLL, as the former is exactly the one passed in
340 argument to CreateWindow.
341 In Windows (and from now on in Wine :) a listbox created
342 with such a style (no WS_SCROLL) does not update
343 the scrollbar with listbox-related data, thus letting
344 the programmer use it for his/her own purposes. */
346 if (descr->style & LBS_NOREDRAW) return;
347 info.cbSize = sizeof(info);
349 if (descr->style & LBS_MULTICOLUMN)
351 info.nMin = 0;
352 info.nMax = (descr->nb_items - 1) / descr->page_size;
353 info.nPos = descr->top_item / descr->page_size;
354 info.nPage = descr->width / descr->column_width;
355 if (info.nPage < 1) info.nPage = 1;
356 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
357 if (descr->style & LBS_DISABLENOSCROLL)
358 info.fMask |= SIF_DISABLENOSCROLL;
359 if (descr->style & WS_HSCROLL)
360 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
361 info.nMax = 0;
362 info.fMask = SIF_RANGE;
363 if (descr->style & WS_VSCROLL)
364 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
366 else
368 info.nMin = 0;
369 info.nMax = descr->nb_items - 1;
370 info.nPos = descr->top_item;
371 info.nPage = LISTBOX_GetCurrentPageSize( descr );
372 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
373 if (descr->style & LBS_DISABLENOSCROLL)
374 info.fMask |= SIF_DISABLENOSCROLL;
375 if (descr->style & WS_VSCROLL)
376 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
378 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
380 info.nPos = descr->horz_pos;
381 info.nPage = descr->width;
382 info.fMask = SIF_POS | SIF_PAGE;
383 if (descr->style & LBS_DISABLENOSCROLL)
384 info.fMask |= SIF_DISABLENOSCROLL;
385 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
387 else
389 if (descr->style & LBS_DISABLENOSCROLL)
391 info.nMin = 0;
392 info.nMax = 0;
393 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
394 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
396 else
398 ShowScrollBar( descr->self, SB_HORZ, FALSE );
405 /***********************************************************************
406 * LISTBOX_SetTopItem
408 * Set the top item of the listbox, scrolling up or down if necessary.
410 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
412 INT max = LISTBOX_GetMaxTopIndex( descr );
414 TRACE("setting top item %d, scroll %d\n", index, scroll);
416 if (index > max) index = max;
417 if (index < 0) index = 0;
418 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
419 if (descr->top_item == index) return LB_OKAY;
420 if (scroll)
422 INT dx = 0, dy = 0;
423 if (descr->style & LBS_MULTICOLUMN)
424 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
425 else if (descr->style & LBS_OWNERDRAWVARIABLE)
427 INT i;
428 if (index > descr->top_item)
430 for (i = index - 1; i >= descr->top_item; i--)
431 dy -= get_item_height(descr, i);
433 else
435 for (i = index; i < descr->top_item; i++)
436 dy += get_item_height(descr, i);
439 else
440 dy = (descr->top_item - index) * descr->item_height;
442 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
443 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
445 else
446 InvalidateRect( descr->self, NULL, TRUE );
447 descr->top_item = index;
448 LISTBOX_UpdateScroll( descr );
449 return LB_OKAY;
453 /***********************************************************************
454 * LISTBOX_UpdatePage
456 * Update the page size. Should be called when the size of
457 * the client area or the item height changes.
459 static void LISTBOX_UpdatePage( LB_DESCR *descr )
461 INT page_size;
463 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
464 page_size = 1;
465 if (page_size == descr->page_size) return;
466 descr->page_size = page_size;
467 if (descr->style & LBS_MULTICOLUMN)
468 InvalidateRect( descr->self, NULL, TRUE );
469 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
473 /***********************************************************************
474 * LISTBOX_UpdateSize
476 * Update the size of the listbox. Should be called when the size of
477 * the client area changes.
479 static void LISTBOX_UpdateSize( LB_DESCR *descr )
481 RECT rect;
483 GetClientRect( descr->self, &rect );
484 descr->width = rect.right - rect.left;
485 descr->height = rect.bottom - rect.top;
486 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
488 INT remaining;
489 RECT rect;
491 GetWindowRect( descr->self, &rect );
492 if(descr->item_height != 0)
493 remaining = descr->height % descr->item_height;
494 else
495 remaining = 0;
496 if ((descr->height > descr->item_height) && remaining)
498 TRACE("[%p]: changing height %d -> %d\n",
499 descr->self, descr->height, descr->height - remaining );
500 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
501 rect.bottom - rect.top - remaining,
502 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
503 return;
506 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
507 LISTBOX_UpdatePage( descr );
508 LISTBOX_UpdateScroll( descr );
510 /* Invalidate the focused item so it will be repainted correctly */
511 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
513 InvalidateRect( descr->self, &rect, FALSE );
518 /***********************************************************************
519 * LISTBOX_GetItemRect
521 * Get the rectangle enclosing an item, in listbox client coordinates.
522 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
524 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
526 /* Index <= 0 is legal even on empty listboxes */
527 if (index && (index >= descr->nb_items))
529 SetRectEmpty(rect);
530 SetLastError(ERROR_INVALID_INDEX);
531 return LB_ERR;
533 SetRect( rect, 0, 0, descr->width, descr->height );
534 if (descr->style & LBS_MULTICOLUMN)
536 INT col = (index / descr->page_size) -
537 (descr->top_item / descr->page_size);
538 rect->left += col * descr->column_width;
539 rect->right = rect->left + descr->column_width;
540 rect->top += (index % descr->page_size) * descr->item_height;
541 rect->bottom = rect->top + descr->item_height;
543 else if (descr->style & LBS_OWNERDRAWVARIABLE)
545 INT i;
546 rect->right += descr->horz_pos;
547 if ((index >= 0) && (index < descr->nb_items))
549 if (index < descr->top_item)
551 for (i = descr->top_item-1; i >= index; i--)
552 rect->top -= get_item_height(descr, i);
554 else
556 for (i = descr->top_item; i < index; i++)
557 rect->top += get_item_height(descr, i);
559 rect->bottom = rect->top + get_item_height(descr, index);
563 else
565 rect->top += (index - descr->top_item) * descr->item_height;
566 rect->bottom = rect->top + descr->item_height;
567 rect->right += descr->horz_pos;
570 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
572 return ((rect->left < descr->width) && (rect->right > 0) &&
573 (rect->top < descr->height) && (rect->bottom > 0));
577 /***********************************************************************
578 * LISTBOX_GetItemFromPoint
580 * Return the item nearest from point (x,y) (in client coordinates).
582 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
584 INT index = descr->top_item;
586 if (!descr->nb_items) return -1; /* No items */
587 if (descr->style & LBS_OWNERDRAWVARIABLE)
589 INT pos = 0;
590 if (y >= 0)
592 while (index < descr->nb_items)
594 if ((pos += get_item_height(descr, index)) > y) break;
595 index++;
598 else
600 while (index > 0)
602 index--;
603 if ((pos -= get_item_height(descr, index)) <= y) break;
607 else if (descr->style & LBS_MULTICOLUMN)
609 if (y >= descr->item_height * descr->page_size) return -1;
610 if (y >= 0) index += y / descr->item_height;
611 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
612 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
614 else
616 index += (y / descr->item_height);
618 if (index < 0) return 0;
619 if (index >= descr->nb_items) return -1;
620 return index;
624 /***********************************************************************
625 * LISTBOX_PaintItem
627 * Paint an item.
629 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
630 INT index, UINT action, BOOL ignoreFocus )
632 BOOL selected = FALSE, focused;
633 WCHAR *item_str = NULL;
635 if (index < descr->nb_items)
637 item_str = get_item_string(descr, index);
638 selected = is_item_selected(descr, index);
641 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
643 if (IS_OWNERDRAW(descr))
645 DRAWITEMSTRUCT dis;
646 RECT r;
647 HRGN hrgn;
649 if (index >= descr->nb_items)
651 if (action == ODA_FOCUS)
652 DrawFocusRect( hdc, rect );
653 else
654 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
655 return;
658 /* some programs mess with the clipping region when
659 drawing the item, *and* restore the previous region
660 after they are done, so a region has better to exist
661 else everything ends clipped */
662 GetClientRect(descr->self, &r);
663 hrgn = set_control_clipping( hdc, &r );
665 dis.CtlType = ODT_LISTBOX;
666 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
667 dis.hwndItem = descr->self;
668 dis.itemAction = action;
669 dis.hDC = hdc;
670 dis.itemID = index;
671 dis.itemState = 0;
672 if (selected)
673 dis.itemState |= ODS_SELECTED;
674 if (focused)
675 dis.itemState |= ODS_FOCUS;
676 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
677 dis.itemData = get_item_data(descr, index);
678 dis.rcItem = *rect;
679 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
680 descr->self, index, debugstr_w(item_str), action,
681 dis.itemState, wine_dbgstr_rect(rect) );
682 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
683 SelectClipRgn( hdc, hrgn );
684 if (hrgn) DeleteObject( hrgn );
686 else
688 COLORREF oldText = 0, oldBk = 0;
690 if (action == ODA_FOCUS)
692 DrawFocusRect( hdc, rect );
693 return;
695 if (selected)
697 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
698 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
701 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
702 descr->self, index, debugstr_w(item_str), action,
703 wine_dbgstr_rect(rect) );
704 if (!item_str)
705 ExtTextOutW( hdc, rect->left + 1, rect->top,
706 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
707 else if (!(descr->style & LBS_USETABSTOPS))
708 ExtTextOutW( hdc, rect->left + 1, rect->top,
709 ETO_OPAQUE | ETO_CLIPPED, rect, item_str,
710 lstrlenW(item_str), NULL );
711 else
713 /* Output empty string to paint background in the full width. */
714 ExtTextOutW( hdc, rect->left + 1, rect->top,
715 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
716 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
717 item_str, lstrlenW(item_str),
718 descr->nb_tabs, descr->tabs, 0);
720 if (selected)
722 SetBkColor( hdc, oldBk );
723 SetTextColor( hdc, oldText );
725 if (focused)
726 DrawFocusRect( hdc, rect );
731 /***********************************************************************
732 * LISTBOX_SetRedraw
734 * Change the redraw flag.
736 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
738 if (on)
740 if (!(descr->style & LBS_NOREDRAW)) return;
741 descr->style &= ~LBS_NOREDRAW;
742 if (descr->style & LBS_DISPLAYCHANGED)
743 { /* page was changed while setredraw false, refresh automatically */
744 InvalidateRect(descr->self, NULL, TRUE);
745 if ((descr->top_item + descr->page_size) > descr->nb_items)
746 { /* reset top of page if less than number of items/page */
747 descr->top_item = descr->nb_items - descr->page_size;
748 if (descr->top_item < 0) descr->top_item = 0;
750 descr->style &= ~LBS_DISPLAYCHANGED;
752 LISTBOX_UpdateScroll( descr );
754 else descr->style |= LBS_NOREDRAW;
758 /***********************************************************************
759 * LISTBOX_RepaintItem
761 * Repaint a single item synchronously.
763 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
765 HDC hdc;
766 RECT rect;
767 HFONT oldFont = 0;
768 HBRUSH hbrush, oldBrush = 0;
770 /* Do not repaint the item if the item is not visible */
771 if (!IsWindowVisible(descr->self)) return;
772 if (descr->style & LBS_NOREDRAW)
774 descr->style |= LBS_DISPLAYCHANGED;
775 return;
777 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
778 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
779 if (descr->font) oldFont = SelectObject( hdc, descr->font );
780 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
781 (WPARAM)hdc, (LPARAM)descr->self );
782 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
783 if (!IsWindowEnabled(descr->self))
784 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
785 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
786 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
787 if (oldFont) SelectObject( hdc, oldFont );
788 if (oldBrush) SelectObject( hdc, oldBrush );
789 ReleaseDC( descr->self, hdc );
793 /***********************************************************************
794 * LISTBOX_DrawFocusRect
796 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
798 HDC hdc;
799 RECT rect;
800 HFONT oldFont = 0;
802 /* Do not repaint the item if the item is not visible */
803 if (!IsWindowVisible(descr->self)) return;
805 if (descr->focus_item == -1) return;
806 if (!descr->caret_on || !descr->in_focus) return;
808 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
809 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
810 if (descr->font) oldFont = SelectObject( hdc, descr->font );
811 if (!IsWindowEnabled(descr->self))
812 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
813 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
814 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
815 if (oldFont) SelectObject( hdc, oldFont );
816 ReleaseDC( descr->self, hdc );
820 /***********************************************************************
821 * LISTBOX_InitStorage
823 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
825 UINT new_size = descr->nb_items + nb_items;
827 if (new_size > descr->items_size && !resize_storage(descr, new_size))
828 return LB_ERRSPACE;
829 return descr->items_size;
833 /***********************************************************************
834 * LISTBOX_SetTabStops
836 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
838 INT i;
840 if (!(descr->style & LBS_USETABSTOPS))
842 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
843 return FALSE;
846 HeapFree( GetProcessHeap(), 0, descr->tabs );
847 if (!(descr->nb_tabs = count))
849 descr->tabs = NULL;
850 return TRUE;
852 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
853 descr->nb_tabs * sizeof(INT) )))
854 return FALSE;
855 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
857 /* convert into "dialog units"*/
858 for (i = 0; i < descr->nb_tabs; i++)
859 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
861 return TRUE;
865 /***********************************************************************
866 * LISTBOX_GetText
868 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
870 DWORD len;
872 if ((index < 0) || (index >= descr->nb_items))
874 SetLastError(ERROR_INVALID_INDEX);
875 return LB_ERR;
878 if (HAS_STRINGS(descr))
880 WCHAR *str = get_item_string(descr, index);
882 if (!buffer)
884 len = lstrlenW(str);
885 if( unicode )
886 return len;
887 return WideCharToMultiByte( CP_ACP, 0, str, len, NULL, 0, NULL, NULL );
890 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(str));
892 __TRY /* hide a Delphi bug that passes a read-only buffer */
894 if(unicode)
896 lstrcpyW(buffer, str);
897 len = lstrlenW(buffer);
899 else
901 len = WideCharToMultiByte(CP_ACP, 0, str, -1, (LPSTR)buffer,
902 0x7FFFFFFF, NULL, NULL) - 1;
905 __EXCEPT_PAGE_FAULT
907 WARN( "got an invalid buffer (Delphi bug?)\n" );
908 SetLastError( ERROR_INVALID_PARAMETER );
909 return LB_ERR;
911 __ENDTRY
912 } else
914 if (buffer)
915 *((ULONG_PTR *)buffer) = get_item_data(descr, index);
916 len = sizeof(ULONG_PTR);
918 return len;
921 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
923 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
924 if (ret == CSTR_LESS_THAN)
925 return -1;
926 if (ret == CSTR_EQUAL)
927 return 0;
928 if (ret == CSTR_GREATER_THAN)
929 return 1;
930 return -1;
933 /***********************************************************************
934 * LISTBOX_FindStringPos
936 * Find the nearest string located before a given string in sort order.
937 * If 'exact' is TRUE, return an error if we don't get an exact match.
939 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
941 INT index, min, max, res;
943 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
945 min = 0;
946 max = descr->nb_items - 1;
947 while (min <= max)
949 index = (min + max) / 2;
950 if (HAS_STRINGS(descr))
951 res = LISTBOX_lstrcmpiW( descr->locale, get_item_string(descr, index), str );
952 else
954 COMPAREITEMSTRUCT cis;
955 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
957 cis.CtlType = ODT_LISTBOX;
958 cis.CtlID = id;
959 cis.hwndItem = descr->self;
960 /* note that some application (MetaStock) expects the second item
961 * to be in the listbox */
962 cis.itemID1 = index;
963 cis.itemData1 = get_item_data(descr, index);
964 cis.itemID2 = -1;
965 cis.itemData2 = (ULONG_PTR)str;
966 cis.dwLocaleId = descr->locale;
967 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
969 if (!res) return index;
970 if (res > 0) max = index - 1;
971 else min = index + 1;
973 return exact ? -1 : min;
977 /***********************************************************************
978 * LISTBOX_FindFileStrPos
980 * Find the nearest string located before a given string in directory
981 * sort order (i.e. first files, then directories, then drives).
983 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
985 INT min, max, res;
987 if (!HAS_STRINGS(descr))
988 return LISTBOX_FindStringPos( descr, str, FALSE );
989 min = 0;
990 max = descr->nb_items;
991 while (min != max)
993 INT index = (min + max) / 2;
994 LPCWSTR p = get_item_string(descr, index);
995 if (*p == '[') /* drive or directory */
997 if (*str != '[') res = -1;
998 else if (p[1] == '-') /* drive */
1000 if (str[1] == '-') res = str[2] - p[2];
1001 else res = -1;
1003 else /* directory */
1005 if (str[1] == '-') res = 1;
1006 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
1009 else /* filename */
1011 if (*str == '[') res = 1;
1012 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
1014 if (!res) return index;
1015 if (res < 0) max = index;
1016 else min = index + 1;
1018 return max;
1022 /***********************************************************************
1023 * LISTBOX_FindString
1025 * Find the item beginning with a given string.
1027 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
1029 INT i, index;
1031 if (descr->style & LBS_NODATA)
1033 SetLastError(ERROR_INVALID_PARAMETER);
1034 return LB_ERR;
1037 start++;
1038 if (start >= descr->nb_items) start = 0;
1039 if (HAS_STRINGS(descr))
1041 if (!str || ! str[0] ) return LB_ERR;
1042 if (exact)
1044 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1046 if (index == descr->nb_items) index = 0;
1047 if (!LISTBOX_lstrcmpiW(descr->locale, str, get_item_string(descr, index)))
1048 return index;
1051 else
1053 /* Special case for drives and directories: ignore prefix */
1054 INT len = lstrlenW(str);
1055 WCHAR *item_str;
1057 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1059 if (index == descr->nb_items) index = 0;
1060 item_str = get_item_string(descr, index);
1062 if (!wcsnicmp(str, item_str, len)) return index;
1063 if (item_str[0] == '[')
1065 if (!wcsnicmp(str, item_str + 1, len)) return index;
1066 if (item_str[1] == '-' && !wcsnicmp(str, item_str + 2, len)) return index;
1071 else
1073 if (exact && (descr->style & LBS_SORT))
1074 /* If sorted, use a WM_COMPAREITEM binary search */
1075 return LISTBOX_FindStringPos( descr, str, TRUE );
1077 /* Otherwise use a linear search */
1078 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1080 if (index == descr->nb_items) index = 0;
1081 if (get_item_data(descr, index) == (ULONG_PTR)str) return index;
1084 return LB_ERR;
1088 /***********************************************************************
1089 * LISTBOX_GetSelCount
1091 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1093 INT i, count;
1095 if (!(descr->style & LBS_MULTIPLESEL) ||
1096 (descr->style & LBS_NOSEL))
1097 return LB_ERR;
1098 for (i = count = 0; i < descr->nb_items; i++)
1099 if (is_item_selected(descr, i)) count++;
1100 return count;
1104 /***********************************************************************
1105 * LISTBOX_GetSelItems
1107 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1109 INT i, count;
1111 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1112 for (i = count = 0; (i < descr->nb_items) && (count < max); i++)
1113 if (is_item_selected(descr, i)) array[count++] = i;
1114 return count;
1118 /***********************************************************************
1119 * LISTBOX_Paint
1121 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1123 INT i, col_pos = descr->page_size - 1;
1124 RECT rect;
1125 RECT focusRect = {-1, -1, -1, -1};
1126 HFONT oldFont = 0;
1127 HBRUSH hbrush, oldBrush = 0;
1129 if (descr->style & LBS_NOREDRAW) return 0;
1131 SetRect( &rect, 0, 0, descr->width, descr->height );
1132 if (descr->style & LBS_MULTICOLUMN)
1133 rect.right = rect.left + descr->column_width;
1134 else if (descr->horz_pos)
1136 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1137 rect.right += descr->horz_pos;
1140 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1141 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1142 (WPARAM)hdc, (LPARAM)descr->self );
1143 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1144 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1146 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1147 (descr->in_focus))
1149 /* Special case for empty listbox: paint focus rect */
1150 rect.bottom = rect.top + descr->item_height;
1151 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1152 &rect, NULL, 0, NULL );
1153 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1154 rect.top = rect.bottom;
1157 /* Paint all the item, regarding the selection
1158 Focus state will be painted after */
1160 for (i = descr->top_item; i < descr->nb_items; i++)
1162 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1163 rect.bottom = rect.top + descr->item_height;
1164 else
1165 rect.bottom = rect.top + get_item_height(descr, i);
1167 /* keep the focus rect, to paint the focus item after */
1168 if (i == descr->focus_item)
1169 focusRect = rect;
1171 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1172 rect.top = rect.bottom;
1174 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1176 if (!IS_OWNERDRAW(descr))
1178 /* Clear the bottom of the column */
1179 if (rect.top < descr->height)
1181 rect.bottom = descr->height;
1182 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1183 &rect, NULL, 0, NULL );
1187 /* Go to the next column */
1188 rect.left += descr->column_width;
1189 rect.right += descr->column_width;
1190 rect.top = 0;
1191 col_pos = descr->page_size - 1;
1192 if (rect.left >= descr->width) break;
1194 else
1196 col_pos--;
1197 if (rect.top >= descr->height) break;
1201 /* Paint the focus item now */
1202 if (focusRect.top != focusRect.bottom &&
1203 descr->caret_on && descr->in_focus)
1204 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1206 if (!IS_OWNERDRAW(descr))
1208 /* Clear the remainder of the client area */
1209 if (rect.top < descr->height)
1211 rect.bottom = descr->height;
1212 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1213 &rect, NULL, 0, NULL );
1215 if (rect.right < descr->width)
1217 rect.left = rect.right;
1218 rect.right = descr->width;
1219 rect.top = 0;
1220 rect.bottom = descr->height;
1221 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1222 &rect, NULL, 0, NULL );
1225 if (oldFont) SelectObject( hdc, oldFont );
1226 if (oldBrush) SelectObject( hdc, oldBrush );
1227 return 0;
1231 /***********************************************************************
1232 * LISTBOX_InvalidateItems
1234 * Invalidate all items from a given item. If the specified item is not
1235 * visible, nothing happens.
1237 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1239 RECT rect;
1241 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1243 if (descr->style & LBS_NOREDRAW)
1245 descr->style |= LBS_DISPLAYCHANGED;
1246 return;
1248 rect.bottom = descr->height;
1249 InvalidateRect( descr->self, &rect, TRUE );
1250 if (descr->style & LBS_MULTICOLUMN)
1252 /* Repaint the other columns */
1253 rect.left = rect.right;
1254 rect.right = descr->width;
1255 rect.top = 0;
1256 InvalidateRect( descr->self, &rect, TRUE );
1261 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1263 RECT rect;
1265 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1266 InvalidateRect( descr->self, &rect, TRUE );
1269 /***********************************************************************
1270 * LISTBOX_GetItemHeight
1272 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1274 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1276 if ((index < 0) || (index >= descr->nb_items))
1278 SetLastError(ERROR_INVALID_INDEX);
1279 return LB_ERR;
1281 return get_item_height(descr, index);
1283 else return descr->item_height;
1287 /***********************************************************************
1288 * LISTBOX_SetItemHeight
1290 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1292 if (height > MAXBYTE)
1293 return -1;
1295 if (!height) height = 1;
1297 if (descr->style & LBS_OWNERDRAWVARIABLE)
1299 if ((index < 0) || (index >= descr->nb_items))
1301 SetLastError(ERROR_INVALID_INDEX);
1302 return LB_ERR;
1304 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1305 set_item_height(descr, index, height);
1306 LISTBOX_UpdateScroll( descr );
1307 if (repaint)
1308 LISTBOX_InvalidateItems( descr, index );
1310 else if (height != descr->item_height)
1312 TRACE("[%p]: new height = %d\n", descr->self, height );
1313 descr->item_height = height;
1314 LISTBOX_UpdatePage( descr );
1315 LISTBOX_UpdateScroll( descr );
1316 if (repaint)
1317 InvalidateRect( descr->self, 0, TRUE );
1319 return LB_OKAY;
1323 /***********************************************************************
1324 * LISTBOX_SetHorizontalPos
1326 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1328 INT diff;
1330 if (pos > descr->horz_extent - descr->width)
1331 pos = descr->horz_extent - descr->width;
1332 if (pos < 0) pos = 0;
1333 if (!(diff = descr->horz_pos - pos)) return;
1334 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1335 descr->horz_pos = pos;
1336 LISTBOX_UpdateScroll( descr );
1337 if (abs(diff) < descr->width)
1339 RECT rect;
1340 /* Invalidate the focused item so it will be repainted correctly */
1341 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1342 InvalidateRect( descr->self, &rect, TRUE );
1343 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1344 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1346 else
1347 InvalidateRect( descr->self, NULL, TRUE );
1351 /***********************************************************************
1352 * LISTBOX_SetHorizontalExtent
1354 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1356 if (descr->style & LBS_MULTICOLUMN)
1357 return LB_OKAY;
1358 if (extent == descr->horz_extent) return LB_OKAY;
1359 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1360 descr->horz_extent = extent;
1361 if (descr->style & WS_HSCROLL) {
1362 SCROLLINFO info;
1363 info.cbSize = sizeof(info);
1364 info.nMin = 0;
1365 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1366 info.fMask = SIF_RANGE;
1367 if (descr->style & LBS_DISABLENOSCROLL)
1368 info.fMask |= SIF_DISABLENOSCROLL;
1369 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1371 if (descr->horz_pos > extent - descr->width)
1372 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1373 return LB_OKAY;
1377 /***********************************************************************
1378 * LISTBOX_SetColumnWidth
1380 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1382 RECT rect;
1384 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1386 GetClientRect(descr->self, &rect);
1387 descr->width = rect.right - rect.left;
1388 descr->height = rect.bottom - rect.top;
1389 descr->column_width = column_width;
1391 LISTBOX_UpdatePage(descr);
1392 LISTBOX_UpdateScroll(descr);
1393 return LB_OKAY;
1397 /***********************************************************************
1398 * LISTBOX_SetFont
1400 * Returns the item height.
1402 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1404 HDC hdc;
1405 HFONT oldFont = 0;
1406 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1407 SIZE sz;
1409 descr->font = font;
1411 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1413 ERR("unable to get DC.\n" );
1414 return 16;
1416 if (font) oldFont = SelectObject( hdc, font );
1417 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1418 if (oldFont) SelectObject( hdc, oldFont );
1419 ReleaseDC( descr->self, hdc );
1421 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1422 if (!IS_OWNERDRAW(descr))
1423 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1424 return sz.cy;
1428 /***********************************************************************
1429 * LISTBOX_MakeItemVisible
1431 * Make sure that a given item is partially or fully visible.
1433 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1435 INT top;
1437 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1439 if (index <= descr->top_item) top = index;
1440 else if (descr->style & LBS_MULTICOLUMN)
1442 INT cols = descr->width;
1443 if (!fully) cols += descr->column_width - 1;
1444 if (cols >= descr->column_width) cols /= descr->column_width;
1445 else cols = 1;
1446 if (index < descr->top_item + (descr->page_size * cols)) return;
1447 top = index - descr->page_size * (cols - 1);
1449 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1451 INT height = fully ? get_item_height(descr, index) : 1;
1452 for (top = index; top > descr->top_item; top--)
1453 if ((height += get_item_height(descr, top - 1)) > descr->height) break;
1455 else
1457 if (index < descr->top_item + descr->page_size) return;
1458 if (!fully && (index == descr->top_item + descr->page_size) &&
1459 (descr->height > (descr->page_size * descr->item_height))) return;
1460 top = index - descr->page_size + 1;
1462 LISTBOX_SetTopItem( descr, top, TRUE );
1465 /***********************************************************************
1466 * LISTBOX_SetCaretIndex
1468 * NOTES
1469 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1472 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1474 BOOL focus_changed = descr->focus_item != index;
1476 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1478 if (descr->style & LBS_NOSEL) return LB_ERR;
1479 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1481 if (focus_changed)
1483 LISTBOX_DrawFocusRect( descr, FALSE );
1484 descr->focus_item = index;
1487 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1489 if (focus_changed)
1490 LISTBOX_DrawFocusRect( descr, TRUE );
1492 return LB_OKAY;
1496 /***********************************************************************
1497 * LISTBOX_SelectItemRange
1499 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1501 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1502 INT last, BOOL on )
1504 INT i;
1506 /* A few sanity checks */
1508 if (descr->style & LBS_NOSEL) return LB_ERR;
1509 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1511 if (!descr->nb_items) return LB_OKAY;
1513 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1514 if (first < 0) first = 0;
1515 if (last < first) return LB_OKAY;
1517 if (on) /* Turn selection on */
1519 for (i = first; i <= last; i++)
1521 if (is_item_selected(descr, i)) continue;
1522 set_item_selected_state(descr, i, TRUE);
1523 LISTBOX_InvalidateItemRect(descr, i);
1526 else /* Turn selection off */
1528 for (i = first; i <= last; i++)
1530 if (!is_item_selected(descr, i)) continue;
1531 set_item_selected_state(descr, i, FALSE);
1532 LISTBOX_InvalidateItemRect(descr, i);
1535 return LB_OKAY;
1538 /***********************************************************************
1539 * LISTBOX_SetSelection
1541 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1542 BOOL on, BOOL send_notify )
1544 TRACE( "cur_sel=%d index=%d notify=%s\n",
1545 descr->selected_item, index, send_notify ? "YES" : "NO" );
1547 if (descr->style & LBS_NOSEL)
1549 descr->selected_item = index;
1550 return LB_ERR;
1552 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1553 if (descr->style & LBS_MULTIPLESEL)
1555 if (index == -1) /* Select all items */
1556 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1557 else /* Only one item */
1558 return LISTBOX_SelectItemRange( descr, index, index, on );
1560 else
1562 INT oldsel = descr->selected_item;
1563 if (index == oldsel) return LB_OKAY;
1564 if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
1565 if (index != -1) set_item_selected_state(descr, index, TRUE);
1566 descr->selected_item = index;
1567 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1568 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1569 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1570 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1571 else
1572 if( descr->lphc ) /* set selection change flag for parent combo */
1573 descr->lphc->wState |= CBF_SELCHANGE;
1575 return LB_OKAY;
1579 /***********************************************************************
1580 * LISTBOX_MoveCaret
1582 * Change the caret position and extend the selection to the new caret.
1584 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1586 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1588 if ((index < 0) || (index >= descr->nb_items))
1589 return;
1591 /* Important, repaint needs to be done in this order if
1592 you want to mimic Windows behavior:
1593 1. Remove the focus and paint the item
1594 2. Remove the selection and paint the item(s)
1595 3. Set the selection and repaint the item(s)
1596 4. Set the focus to 'index' and repaint the item */
1598 /* 1. remove the focus and repaint the item */
1599 LISTBOX_DrawFocusRect( descr, FALSE );
1601 /* 2. then turn off the previous selection */
1602 /* 3. repaint the new selected item */
1603 if (descr->style & LBS_EXTENDEDSEL)
1605 if (descr->anchor_item != -1)
1607 INT first = min( index, descr->anchor_item );
1608 INT last = max( index, descr->anchor_item );
1609 if (first > 0)
1610 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1611 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1612 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1615 else if (!(descr->style & LBS_MULTIPLESEL))
1617 /* Set selection to new caret item */
1618 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1621 /* 4. repaint the new item with the focus */
1622 descr->focus_item = index;
1623 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1624 LISTBOX_DrawFocusRect( descr, TRUE );
1628 /***********************************************************************
1629 * LISTBOX_InsertItem
1631 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1632 LPWSTR str, ULONG_PTR data )
1634 INT oldfocus = descr->focus_item;
1636 if (index == -1) index = descr->nb_items;
1637 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1638 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1640 insert_item_data(descr, index);
1641 descr->nb_items++;
1642 set_item_string(descr, index, str);
1643 set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
1644 set_item_height(descr, index, 0);
1645 set_item_selected_state(descr, index, FALSE);
1647 /* Get item height */
1649 if (descr->style & LBS_OWNERDRAWVARIABLE)
1651 MEASUREITEMSTRUCT mis;
1652 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1654 mis.CtlType = ODT_LISTBOX;
1655 mis.CtlID = id;
1656 mis.itemID = index;
1657 mis.itemData = str ? (ULONG_PTR)str : data;
1658 mis.itemHeight = descr->item_height;
1659 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1660 set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
1661 TRACE("[%p]: measure item %d (%s) = %d\n",
1662 descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
1665 /* Repaint the items */
1667 LISTBOX_UpdateScroll( descr );
1668 LISTBOX_InvalidateItems( descr, index );
1670 /* Move selection and focused item */
1671 /* If listbox was empty, set focus to the first item */
1672 if (descr->nb_items == 1)
1673 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1674 /* single select don't change selection index in win31 */
1675 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1677 descr->selected_item++;
1678 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1680 else
1682 if (index <= descr->selected_item)
1684 descr->selected_item++;
1685 descr->focus_item = oldfocus; /* focus not changed */
1688 return LB_OKAY;
1692 /***********************************************************************
1693 * LISTBOX_InsertString
1695 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1697 LPWSTR new_str = NULL;
1698 LRESULT ret;
1700 if (HAS_STRINGS(descr))
1702 if (!str) str = L"";
1703 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str) + 1) * sizeof(WCHAR) )))
1705 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1706 return LB_ERRSPACE;
1708 lstrcpyW(new_str, str);
1711 if (index == -1) index = descr->nb_items;
1712 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1714 HeapFree( GetProcessHeap(), 0, new_str );
1715 return ret;
1718 TRACE("[%p]: added item %d %s\n",
1719 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1720 return index;
1724 /***********************************************************************
1725 * LISTBOX_DeleteItem
1727 * Delete the content of an item. 'index' must be a valid index.
1729 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1731 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1732 * while Win95 sends it for all items with user data.
1733 * It's probably better to send it too often than not
1734 * often enough, so this is what we do here.
1736 if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
1738 DELETEITEMSTRUCT dis;
1739 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1741 dis.CtlType = ODT_LISTBOX;
1742 dis.CtlID = id;
1743 dis.itemID = index;
1744 dis.hwndItem = descr->self;
1745 dis.itemData = get_item_data(descr, index);
1746 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1748 HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
1752 /***********************************************************************
1753 * LISTBOX_RemoveItem
1755 * Remove an item from the listbox and delete its content.
1757 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1759 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1761 /* We need to invalidate the original rect instead of the updated one. */
1762 LISTBOX_InvalidateItems( descr, index );
1764 if (descr->nb_items == 1)
1766 SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1767 return LB_OKAY;
1769 descr->nb_items--;
1770 LISTBOX_DeleteItem( descr, index );
1771 remove_item_data(descr, index);
1773 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1774 resize_storage(descr, descr->nb_items);
1776 /* Repaint the items */
1778 LISTBOX_UpdateScroll( descr );
1779 /* if we removed the scrollbar, reset the top of the list
1780 (correct for owner-drawn ???) */
1781 if (descr->nb_items == descr->page_size)
1782 LISTBOX_SetTopItem( descr, 0, TRUE );
1784 /* Move selection and focused item */
1785 if (!IS_MULTISELECT(descr))
1787 if (index == descr->selected_item)
1788 descr->selected_item = -1;
1789 else if (index < descr->selected_item)
1791 descr->selected_item--;
1792 if (ISWIN31) /* win 31 do not change the selected item number */
1793 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1797 if (descr->focus_item >= descr->nb_items)
1799 descr->focus_item = descr->nb_items - 1;
1800 if (descr->focus_item < 0) descr->focus_item = 0;
1802 return LB_OKAY;
1806 /***********************************************************************
1807 * LISTBOX_ResetContent
1809 static void LISTBOX_ResetContent( LB_DESCR *descr )
1811 INT i;
1813 if (!(descr->style & LBS_NODATA))
1814 for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
1815 HeapFree( GetProcessHeap(), 0, descr->u.items );
1816 descr->nb_items = 0;
1817 descr->top_item = 0;
1818 descr->selected_item = -1;
1819 descr->focus_item = 0;
1820 descr->anchor_item = -1;
1821 descr->items_size = 0;
1822 descr->u.items = NULL;
1826 /***********************************************************************
1827 * LISTBOX_SetCount
1829 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1831 UINT orig_num = descr->nb_items;
1833 if (!(descr->style & LBS_NODATA))
1835 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1836 return LB_ERR;
1839 if (!resize_storage(descr, count))
1840 return LB_ERRSPACE;
1841 descr->nb_items = count;
1843 if (count)
1845 LISTBOX_UpdateScroll(descr);
1846 if (count < orig_num)
1848 descr->anchor_item = min(descr->anchor_item, count - 1);
1849 if (descr->selected_item >= count)
1850 descr->selected_item = -1;
1852 /* If we removed the scrollbar, reset the top of the list */
1853 if (count <= descr->page_size && orig_num > descr->page_size)
1854 LISTBOX_SetTopItem(descr, 0, TRUE);
1856 descr->focus_item = min(descr->focus_item, count - 1);
1859 /* If it was empty before growing, set focus to the first item */
1860 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1862 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1864 InvalidateRect( descr->self, NULL, TRUE );
1865 return LB_OKAY;
1869 /***********************************************************************
1870 * LISTBOX_Directory
1872 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1873 LPCWSTR filespec, BOOL long_names )
1875 HANDLE handle;
1876 LRESULT ret = LB_OKAY;
1877 WIN32_FIND_DATAW entry;
1878 int pos;
1879 LRESULT maxinsert = LB_ERR;
1881 /* don't scan directory if we just want drives exclusively */
1882 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1883 /* scan directory */
1884 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1886 int le = GetLastError();
1887 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1889 else
1893 WCHAR buffer[270];
1894 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1896 if (!(attrib & DDL_DIRECTORY) ||
1897 !wcscmp( entry.cFileName, L"." )) continue;
1898 buffer[0] = '[';
1899 if (!long_names && entry.cAlternateFileName[0])
1900 lstrcpyW( buffer + 1, entry.cAlternateFileName );
1901 else
1902 lstrcpyW( buffer + 1, entry.cFileName );
1903 lstrcatW(buffer, L"]");
1905 else /* not a directory */
1907 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1908 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1910 if ((attrib & DDL_EXCLUSIVE) &&
1911 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1912 continue;
1913 #undef ATTRIBS
1914 if (!long_names && entry.cAlternateFileName[0])
1915 lstrcpyW( buffer, entry.cAlternateFileName );
1916 else
1917 lstrcpyW( buffer, entry.cFileName );
1919 if (!long_names) CharLowerW( buffer );
1920 pos = LISTBOX_FindFileStrPos( descr, buffer );
1921 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1922 break;
1923 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1924 } while (FindNextFileW( handle, &entry ));
1925 FindClose( handle );
1928 if (ret >= 0)
1930 ret = maxinsert;
1932 /* scan drives */
1933 if (attrib & DDL_DRIVES)
1935 WCHAR buffer[] = L"[-a-]";
1936 WCHAR root[] = L"A:\\";
1937 int drive;
1938 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1940 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1941 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1942 break;
1946 return ret;
1950 /***********************************************************************
1951 * LISTBOX_HandleVScroll
1953 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1955 SCROLLINFO info;
1957 if (descr->style & LBS_MULTICOLUMN) return 0;
1958 switch(scrollReq)
1960 case SB_LINEUP:
1961 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1962 break;
1963 case SB_LINEDOWN:
1964 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1965 break;
1966 case SB_PAGEUP:
1967 LISTBOX_SetTopItem( descr, descr->top_item -
1968 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1969 break;
1970 case SB_PAGEDOWN:
1971 LISTBOX_SetTopItem( descr, descr->top_item +
1972 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1973 break;
1974 case SB_THUMBPOSITION:
1975 LISTBOX_SetTopItem( descr, pos, TRUE );
1976 break;
1977 case SB_THUMBTRACK:
1978 info.cbSize = sizeof(info);
1979 info.fMask = SIF_TRACKPOS;
1980 GetScrollInfo( descr->self, SB_VERT, &info );
1981 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1982 break;
1983 case SB_TOP:
1984 LISTBOX_SetTopItem( descr, 0, TRUE );
1985 break;
1986 case SB_BOTTOM:
1987 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1988 break;
1990 return 0;
1994 /***********************************************************************
1995 * LISTBOX_HandleHScroll
1997 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1999 SCROLLINFO info;
2000 INT page;
2002 if (descr->style & LBS_MULTICOLUMN)
2004 switch(scrollReq)
2006 case SB_LINELEFT:
2007 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
2008 TRUE );
2009 break;
2010 case SB_LINERIGHT:
2011 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
2012 TRUE );
2013 break;
2014 case SB_PAGELEFT:
2015 page = descr->width / descr->column_width;
2016 if (page < 1) page = 1;
2017 LISTBOX_SetTopItem( descr,
2018 descr->top_item - page * descr->page_size, TRUE );
2019 break;
2020 case SB_PAGERIGHT:
2021 page = descr->width / descr->column_width;
2022 if (page < 1) page = 1;
2023 LISTBOX_SetTopItem( descr,
2024 descr->top_item + page * descr->page_size, TRUE );
2025 break;
2026 case SB_THUMBPOSITION:
2027 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
2028 break;
2029 case SB_THUMBTRACK:
2030 info.cbSize = sizeof(info);
2031 info.fMask = SIF_TRACKPOS;
2032 GetScrollInfo( descr->self, SB_VERT, &info );
2033 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
2034 TRUE );
2035 break;
2036 case SB_LEFT:
2037 LISTBOX_SetTopItem( descr, 0, TRUE );
2038 break;
2039 case SB_RIGHT:
2040 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2041 break;
2044 else if (descr->horz_extent)
2046 switch(scrollReq)
2048 case SB_LINELEFT:
2049 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2050 break;
2051 case SB_LINERIGHT:
2052 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2053 break;
2054 case SB_PAGELEFT:
2055 LISTBOX_SetHorizontalPos( descr,
2056 descr->horz_pos - descr->width );
2057 break;
2058 case SB_PAGERIGHT:
2059 LISTBOX_SetHorizontalPos( descr,
2060 descr->horz_pos + descr->width );
2061 break;
2062 case SB_THUMBPOSITION:
2063 LISTBOX_SetHorizontalPos( descr, pos );
2064 break;
2065 case SB_THUMBTRACK:
2066 info.cbSize = sizeof(info);
2067 info.fMask = SIF_TRACKPOS;
2068 GetScrollInfo( descr->self, SB_HORZ, &info );
2069 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2070 break;
2071 case SB_LEFT:
2072 LISTBOX_SetHorizontalPos( descr, 0 );
2073 break;
2074 case SB_RIGHT:
2075 LISTBOX_SetHorizontalPos( descr,
2076 descr->horz_extent - descr->width );
2077 break;
2080 return 0;
2083 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2085 INT pulScrollLines = 3;
2087 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2089 /* if scrolling changes direction, ignore left overs */
2090 if ((delta < 0 && descr->wheel_remain < 0) ||
2091 (delta > 0 && descr->wheel_remain > 0))
2092 descr->wheel_remain += delta;
2093 else
2094 descr->wheel_remain = delta;
2096 if (descr->wheel_remain && pulScrollLines)
2098 int cLineScroll;
2099 if (descr->style & LBS_MULTICOLUMN)
2101 pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
2102 pulScrollLines = max(1, pulScrollLines);
2103 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2104 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2105 cLineScroll *= descr->page_size;
2107 else
2109 pulScrollLines = min(descr->page_size, pulScrollLines);
2110 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2111 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2113 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2115 return 0;
2118 /***********************************************************************
2119 * LISTBOX_HandleLButtonDown
2121 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2123 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2125 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2126 descr->self, x, y, index, descr->focus_item);
2128 if (!descr->caret_on && (descr->in_focus)) return 0;
2130 if (!descr->in_focus)
2132 if( !descr->lphc ) SetFocus( descr->self );
2133 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2136 if (index == -1) return 0;
2138 if (!descr->lphc)
2140 if (descr->style & LBS_NOTIFY )
2141 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2142 MAKELPARAM( x, y ) );
2145 descr->captured = TRUE;
2146 SetCapture( descr->self );
2148 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2150 /* we should perhaps make sure that all items are deselected
2151 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2152 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2153 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2156 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2157 if (keys & MK_CONTROL)
2159 LISTBOX_SetCaretIndex( descr, index, FALSE );
2160 LISTBOX_SetSelection( descr, index,
2161 !is_item_selected(descr, index),
2162 (descr->style & LBS_NOTIFY) != 0);
2164 else
2166 LISTBOX_MoveCaret( descr, index, FALSE );
2168 if (descr->style & LBS_EXTENDEDSEL)
2170 LISTBOX_SetSelection( descr, index,
2171 is_item_selected(descr, index),
2172 (descr->style & LBS_NOTIFY) != 0 );
2174 else
2176 LISTBOX_SetSelection( descr, index,
2177 !is_item_selected(descr, index),
2178 (descr->style & LBS_NOTIFY) != 0 );
2182 else
2184 descr->anchor_item = index;
2185 LISTBOX_MoveCaret( descr, index, FALSE );
2186 LISTBOX_SetSelection( descr, index,
2187 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2190 if (!descr->lphc)
2192 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2194 POINT pt;
2196 pt.x = x;
2197 pt.y = y;
2199 if (DragDetect( descr->self, pt ))
2200 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2203 return 0;
2207 /*************************************************************************
2208 * LISTBOX_HandleLButtonDownCombo [Internal]
2210 * Process LButtonDown message for the ComboListBox
2212 * PARAMS
2213 * pWnd [I] The windows internal structure
2214 * pDescr [I] The ListBox internal structure
2215 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2216 * x [I] X Mouse Coordinate
2217 * y [I] Y Mouse Coordinate
2219 * RETURNS
2220 * 0 since we are processing the WM_LBUTTONDOWN Message
2222 * NOTES
2223 * This function is only to be used when a ListBox is a ComboListBox
2226 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2228 RECT clientRect, screenRect;
2229 POINT mousePos;
2231 mousePos.x = x;
2232 mousePos.y = y;
2234 GetClientRect(descr->self, &clientRect);
2236 if(PtInRect(&clientRect, mousePos))
2238 /* MousePos is in client, resume normal processing */
2239 if (msg == WM_LBUTTONDOWN)
2241 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2242 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2244 else if (descr->style & LBS_NOTIFY)
2245 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2247 else
2249 POINT screenMousePos;
2250 HWND hWndOldCapture;
2252 /* Check the Non-Client Area */
2253 screenMousePos = mousePos;
2254 hWndOldCapture = GetCapture();
2255 ReleaseCapture();
2256 GetWindowRect(descr->self, &screenRect);
2257 ClientToScreen(descr->self, &screenMousePos);
2259 if(!PtInRect(&screenRect, screenMousePos))
2261 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2262 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2263 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2265 else
2267 /* Check to see the NC is a scrollbar */
2268 INT nHitTestType=0;
2269 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2270 /* Check Vertical scroll bar */
2271 if (style & WS_VSCROLL)
2273 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2274 if (PtInRect( &clientRect, mousePos ))
2275 nHitTestType = HTVSCROLL;
2277 /* Check horizontal scroll bar */
2278 if (style & WS_HSCROLL)
2280 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2281 if (PtInRect( &clientRect, mousePos ))
2282 nHitTestType = HTHSCROLL;
2284 /* Windows sends this message when a scrollbar is clicked
2287 if(nHitTestType != 0)
2289 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2290 MAKELONG(screenMousePos.x, screenMousePos.y));
2292 /* Resume the Capture after scrolling is complete
2294 if(hWndOldCapture != 0)
2295 SetCapture(hWndOldCapture);
2298 return 0;
2301 /***********************************************************************
2302 * LISTBOX_HandleLButtonUp
2304 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2306 if (LISTBOX_Timer != LB_TIMER_NONE)
2307 KillSystemTimer( descr->self, LB_TIMER_ID );
2308 LISTBOX_Timer = LB_TIMER_NONE;
2309 if (descr->captured)
2311 descr->captured = FALSE;
2312 if (GetCapture() == descr->self) ReleaseCapture();
2313 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2314 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2316 return 0;
2320 /***********************************************************************
2321 * LISTBOX_HandleTimer
2323 * Handle scrolling upon a timer event.
2324 * Return TRUE if scrolling should continue.
2326 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2328 switch(dir)
2330 case LB_TIMER_UP:
2331 if (descr->top_item) index = descr->top_item - 1;
2332 else index = 0;
2333 break;
2334 case LB_TIMER_LEFT:
2335 if (descr->top_item) index -= descr->page_size;
2336 break;
2337 case LB_TIMER_DOWN:
2338 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2339 if (index == descr->focus_item) index++;
2340 if (index >= descr->nb_items) index = descr->nb_items - 1;
2341 break;
2342 case LB_TIMER_RIGHT:
2343 if (index + descr->page_size < descr->nb_items)
2344 index += descr->page_size;
2345 break;
2346 case LB_TIMER_NONE:
2347 break;
2349 if (index == descr->focus_item) return FALSE;
2350 LISTBOX_MoveCaret( descr, index, FALSE );
2351 return TRUE;
2355 /***********************************************************************
2356 * LISTBOX_HandleSystemTimer
2358 * WM_SYSTIMER handler.
2360 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2362 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2364 KillSystemTimer( descr->self, LB_TIMER_ID );
2365 LISTBOX_Timer = LB_TIMER_NONE;
2367 return 0;
2371 /***********************************************************************
2372 * LISTBOX_HandleMouseMove
2374 * WM_MOUSEMOVE handler.
2376 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2377 INT x, INT y )
2379 INT index;
2380 TIMER_DIRECTION dir = LB_TIMER_NONE;
2382 if (!descr->captured) return;
2384 if (descr->style & LBS_MULTICOLUMN)
2386 if (y < 0) y = 0;
2387 else if (y >= descr->item_height * descr->page_size)
2388 y = descr->item_height * descr->page_size - 1;
2390 if (x < 0)
2392 dir = LB_TIMER_LEFT;
2393 x = 0;
2395 else if (x >= descr->width)
2397 dir = LB_TIMER_RIGHT;
2398 x = descr->width - 1;
2401 else
2403 if (y < 0) dir = LB_TIMER_UP; /* above */
2404 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2407 index = LISTBOX_GetItemFromPoint( descr, x, y );
2408 if (index == -1) index = descr->focus_item;
2409 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2411 /* Start/stop the system timer */
2413 if (dir != LB_TIMER_NONE)
2414 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2415 else if (LISTBOX_Timer != LB_TIMER_NONE)
2416 KillSystemTimer( descr->self, LB_TIMER_ID );
2417 LISTBOX_Timer = dir;
2421 /***********************************************************************
2422 * LISTBOX_HandleKeyDown
2424 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2426 INT caret = -1;
2427 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2428 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2429 bForceSelection = FALSE; /* only for single select list */
2431 if (descr->style & LBS_WANTKEYBOARDINPUT)
2433 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2434 MAKEWPARAM(LOWORD(key), descr->focus_item),
2435 (LPARAM)descr->self );
2436 if (caret == -2) return 0;
2438 if (caret == -1) switch(key)
2440 case VK_LEFT:
2441 if (descr->style & LBS_MULTICOLUMN)
2443 bForceSelection = FALSE;
2444 if (descr->focus_item >= descr->page_size)
2445 caret = descr->focus_item - descr->page_size;
2446 break;
2448 /* fall through */
2449 case VK_UP:
2450 caret = descr->focus_item - 1;
2451 if (caret < 0) caret = 0;
2452 break;
2453 case VK_RIGHT:
2454 if (descr->style & LBS_MULTICOLUMN)
2456 bForceSelection = FALSE;
2457 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2458 break;
2460 /* fall through */
2461 case VK_DOWN:
2462 caret = descr->focus_item + 1;
2463 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2464 break;
2466 case VK_PRIOR:
2467 if (descr->style & LBS_MULTICOLUMN)
2469 INT page = descr->width / descr->column_width;
2470 if (page < 1) page = 1;
2471 caret = descr->focus_item - (page * descr->page_size) + 1;
2473 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2474 if (caret < 0) caret = 0;
2475 break;
2476 case VK_NEXT:
2477 if (descr->style & LBS_MULTICOLUMN)
2479 INT page = descr->width / descr->column_width;
2480 if (page < 1) page = 1;
2481 caret = descr->focus_item + (page * descr->page_size) - 1;
2483 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2484 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2485 break;
2486 case VK_HOME:
2487 caret = 0;
2488 break;
2489 case VK_END:
2490 caret = descr->nb_items - 1;
2491 break;
2492 case VK_SPACE:
2493 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2494 else if (descr->style & LBS_MULTIPLESEL)
2496 LISTBOX_SetSelection( descr, descr->focus_item,
2497 !is_item_selected(descr, descr->focus_item),
2498 (descr->style & LBS_NOTIFY) != 0 );
2500 break;
2501 default:
2502 bForceSelection = FALSE;
2504 if (bForceSelection) /* focused item is used instead of key */
2505 caret = descr->focus_item;
2506 if (caret >= 0)
2508 if (((descr->style & LBS_EXTENDEDSEL) &&
2509 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2510 !IS_MULTISELECT(descr))
2511 descr->anchor_item = caret;
2512 LISTBOX_MoveCaret( descr, caret, TRUE );
2514 if (descr->style & LBS_MULTIPLESEL)
2515 descr->selected_item = caret;
2516 else
2517 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2518 if (descr->style & LBS_NOTIFY)
2520 if (descr->lphc && IsWindowVisible( descr->self ))
2522 /* make sure that combo parent doesn't hide us */
2523 descr->lphc->wState |= CBF_NOROLLUP;
2525 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2528 return 0;
2532 /***********************************************************************
2533 * LISTBOX_HandleChar
2535 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2537 INT caret = -1;
2538 WCHAR str[2];
2540 str[0] = charW;
2541 str[1] = '\0';
2543 if (descr->style & LBS_WANTKEYBOARDINPUT)
2545 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2546 MAKEWPARAM(charW, descr->focus_item),
2547 (LPARAM)descr->self );
2548 if (caret == -2) return 0;
2550 if (caret == -1 && !(descr->style & LBS_NODATA))
2551 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2552 if (caret != -1)
2554 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2555 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2556 LISTBOX_MoveCaret( descr, caret, TRUE );
2557 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2558 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2560 return 0;
2564 /***********************************************************************
2565 * LISTBOX_Create
2567 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2569 LB_DESCR *descr;
2570 MEASUREITEMSTRUCT mis;
2571 RECT rect;
2573 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2574 return FALSE;
2576 GetClientRect( hwnd, &rect );
2577 descr->self = hwnd;
2578 descr->owner = GetParent( descr->self );
2579 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2580 descr->width = rect.right - rect.left;
2581 descr->height = rect.bottom - rect.top;
2582 descr->u.items = NULL;
2583 descr->items_size = 0;
2584 descr->nb_items = 0;
2585 descr->top_item = 0;
2586 descr->selected_item = -1;
2587 descr->focus_item = 0;
2588 descr->anchor_item = -1;
2589 descr->item_height = 1;
2590 descr->page_size = 1;
2591 descr->column_width = 150;
2592 descr->horz_extent = 0;
2593 descr->horz_pos = 0;
2594 descr->nb_tabs = 0;
2595 descr->tabs = NULL;
2596 descr->wheel_remain = 0;
2597 descr->caret_on = !lphc;
2598 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2599 descr->in_focus = FALSE;
2600 descr->captured = FALSE;
2601 descr->font = 0;
2602 descr->locale = GetUserDefaultLCID();
2603 descr->lphc = lphc;
2605 if( lphc )
2607 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2608 descr->owner = lphc->self;
2611 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2613 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2615 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2616 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2617 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2618 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2619 descr->style &= ~LBS_NODATA;
2620 descr->item_height = LISTBOX_SetFont( descr, 0 );
2622 if (descr->style & LBS_OWNERDRAWFIXED)
2624 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2626 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2628 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2629 descr->item_height = lphc->fixedOwnerDrawHeight;
2631 else
2633 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2634 mis.CtlType = ODT_LISTBOX;
2635 mis.CtlID = id;
2636 mis.itemID = -1;
2637 mis.itemWidth = 0;
2638 mis.itemData = 0;
2639 mis.itemHeight = descr->item_height;
2640 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2641 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2645 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2646 return TRUE;
2650 /***********************************************************************
2651 * LISTBOX_Destroy
2653 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2655 LISTBOX_ResetContent( descr );
2656 SetWindowLongPtrW( descr->self, 0, 0 );
2657 HeapFree( GetProcessHeap(), 0, descr );
2658 return TRUE;
2662 /***********************************************************************
2663 * ListBoxWndProc_common
2665 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2667 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2668 HEADCOMBO *lphc = NULL;
2669 LRESULT ret;
2671 if (!descr)
2673 if (!IsWindow(hwnd)) return 0;
2675 if (msg == WM_CREATE)
2677 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2678 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2679 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2680 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2681 return 0;
2683 /* Ignore all other messages before we get a WM_CREATE */
2684 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2685 DefWindowProcA( hwnd, msg, wParam, lParam );
2687 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2689 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2690 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2692 switch(msg)
2694 case LB_RESETCONTENT:
2695 LISTBOX_ResetContent( descr );
2696 LISTBOX_UpdateScroll( descr );
2697 InvalidateRect( descr->self, NULL, TRUE );
2698 return 0;
2700 case LB_ADDSTRING:
2702 INT ret;
2703 LPWSTR textW;
2704 if(unicode || !HAS_STRINGS(descr))
2705 textW = (LPWSTR)lParam;
2706 else
2708 LPSTR textA = (LPSTR)lParam;
2709 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2710 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2711 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2712 else
2713 return LB_ERRSPACE;
2715 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2716 ret = LISTBOX_InsertString( descr, wParam, textW );
2717 if (!unicode && HAS_STRINGS(descr))
2718 HeapFree(GetProcessHeap(), 0, textW);
2719 return ret;
2722 case LB_INSERTSTRING:
2724 INT ret;
2725 LPWSTR textW;
2726 if(unicode || !HAS_STRINGS(descr))
2727 textW = (LPWSTR)lParam;
2728 else
2730 LPSTR textA = (LPSTR)lParam;
2731 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2732 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2733 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2734 else
2735 return LB_ERRSPACE;
2737 ret = LISTBOX_InsertString( descr, wParam, textW );
2738 if(!unicode && HAS_STRINGS(descr))
2739 HeapFree(GetProcessHeap(), 0, textW);
2740 return ret;
2743 case LB_ADDFILE:
2745 INT ret;
2746 LPWSTR textW;
2747 if(unicode || !HAS_STRINGS(descr))
2748 textW = (LPWSTR)lParam;
2749 else
2751 LPSTR textA = (LPSTR)lParam;
2752 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2753 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2754 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2755 else
2756 return LB_ERRSPACE;
2758 wParam = LISTBOX_FindFileStrPos( descr, textW );
2759 ret = LISTBOX_InsertString( descr, wParam, textW );
2760 if(!unicode && HAS_STRINGS(descr))
2761 HeapFree(GetProcessHeap(), 0, textW);
2762 return ret;
2765 case LB_DELETESTRING:
2766 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2767 return descr->nb_items;
2768 else
2770 SetLastError(ERROR_INVALID_INDEX);
2771 return LB_ERR;
2774 case LB_GETITEMDATA:
2775 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2777 SetLastError(ERROR_INVALID_INDEX);
2778 return LB_ERR;
2780 return get_item_data(descr, wParam);
2782 case LB_SETITEMDATA:
2783 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2785 SetLastError(ERROR_INVALID_INDEX);
2786 return LB_ERR;
2788 set_item_data(descr, wParam, lParam);
2789 /* undocumented: returns TRUE, not LB_OKAY (0) */
2790 return TRUE;
2792 case LB_GETCOUNT:
2793 return descr->nb_items;
2795 case LB_GETTEXT:
2796 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2798 case LB_GETTEXTLEN:
2799 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2801 SetLastError(ERROR_INVALID_INDEX);
2802 return LB_ERR;
2804 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2805 if (unicode) return lstrlenW(get_item_string(descr, wParam));
2806 return WideCharToMultiByte( CP_ACP, 0, get_item_string(descr, wParam),
2807 lstrlenW(get_item_string(descr, wParam)), NULL, 0, NULL, NULL );
2809 case LB_GETCURSEL:
2810 if (descr->nb_items == 0)
2811 return LB_ERR;
2812 if (!IS_MULTISELECT(descr))
2813 return descr->selected_item;
2814 if (descr->selected_item != -1)
2815 return descr->selected_item;
2816 return descr->focus_item;
2817 /* otherwise, if the user tries to move the selection with the */
2818 /* arrow keys, we will give the application something to choke on */
2819 case LB_GETTOPINDEX:
2820 return descr->top_item;
2822 case LB_GETITEMHEIGHT:
2823 return LISTBOX_GetItemHeight( descr, wParam );
2825 case LB_SETITEMHEIGHT:
2826 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2828 case LB_ITEMFROMPOINT:
2830 POINT pt;
2831 RECT rect;
2832 int index;
2833 BOOL hit = TRUE;
2835 /* The hiword of the return value is not a client area
2836 hittest as suggested by MSDN, but rather a hittest on
2837 the returned listbox item. */
2839 if(descr->nb_items == 0)
2840 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2842 pt.x = (short)LOWORD(lParam);
2843 pt.y = (short)HIWORD(lParam);
2845 SetRect(&rect, 0, 0, descr->width, descr->height);
2847 if(!PtInRect(&rect, pt))
2849 pt.x = min(pt.x, rect.right - 1);
2850 pt.x = max(pt.x, 0);
2851 pt.y = min(pt.y, rect.bottom - 1);
2852 pt.y = max(pt.y, 0);
2853 hit = FALSE;
2856 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2858 if(index == -1)
2860 index = descr->nb_items - 1;
2861 hit = FALSE;
2863 return MAKELONG(index, hit ? 0 : 1);
2866 case LB_SETCARETINDEX:
2867 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2868 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2869 return LB_ERR;
2870 else if (ISWIN31)
2871 return wParam;
2872 else
2873 return LB_OKAY;
2875 case LB_GETCARETINDEX:
2876 return descr->focus_item;
2878 case LB_SETTOPINDEX:
2879 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2881 case LB_SETCOLUMNWIDTH:
2882 return LISTBOX_SetColumnWidth( descr, wParam );
2884 case LB_GETITEMRECT:
2885 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2887 case LB_FINDSTRING:
2889 INT ret;
2890 LPWSTR textW;
2891 if(unicode || !HAS_STRINGS(descr))
2892 textW = (LPWSTR)lParam;
2893 else
2895 LPSTR textA = (LPSTR)lParam;
2896 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2897 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2898 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2900 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2901 if(!unicode && HAS_STRINGS(descr))
2902 HeapFree(GetProcessHeap(), 0, textW);
2903 return ret;
2906 case LB_FINDSTRINGEXACT:
2908 INT ret;
2909 LPWSTR textW;
2910 if(unicode || !HAS_STRINGS(descr))
2911 textW = (LPWSTR)lParam;
2912 else
2914 LPSTR textA = (LPSTR)lParam;
2915 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2916 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2917 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2919 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2920 if(!unicode && HAS_STRINGS(descr))
2921 HeapFree(GetProcessHeap(), 0, textW);
2922 return ret;
2925 case LB_SELECTSTRING:
2927 INT index;
2928 LPWSTR textW;
2930 if(HAS_STRINGS(descr))
2931 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2932 debugstr_a((LPSTR)lParam));
2933 if(unicode || !HAS_STRINGS(descr))
2934 textW = (LPWSTR)lParam;
2935 else
2937 LPSTR textA = (LPSTR)lParam;
2938 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2939 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2940 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2942 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2943 if(!unicode && HAS_STRINGS(descr))
2944 HeapFree(GetProcessHeap(), 0, textW);
2945 if (index != LB_ERR)
2947 LISTBOX_MoveCaret( descr, index, TRUE );
2948 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2950 return index;
2953 case LB_GETSEL:
2954 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2955 return LB_ERR;
2956 return is_item_selected(descr, wParam);
2958 case LB_SETSEL:
2959 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2960 if (ret != LB_ERR && wParam)
2962 descr->anchor_item = lParam;
2963 if (lParam != -1)
2964 LISTBOX_SetCaretIndex( descr, lParam, TRUE );
2966 return ret;
2968 case LB_SETCURSEL:
2969 if (IS_MULTISELECT(descr)) return LB_ERR;
2970 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2971 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2972 if (ret != LB_ERR) ret = descr->selected_item;
2973 return ret;
2975 case LB_GETSELCOUNT:
2976 return LISTBOX_GetSelCount( descr );
2978 case LB_GETSELITEMS:
2979 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2981 case LB_SELITEMRANGE:
2982 if (LOWORD(lParam) <= HIWORD(lParam))
2983 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2984 HIWORD(lParam), wParam );
2985 else
2986 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2987 LOWORD(lParam), wParam );
2989 case LB_SELITEMRANGEEX:
2990 if ((INT)lParam >= (INT)wParam)
2991 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2992 else
2993 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2995 case LB_GETHORIZONTALEXTENT:
2996 return descr->horz_extent;
2998 case LB_SETHORIZONTALEXTENT:
2999 return LISTBOX_SetHorizontalExtent( descr, wParam );
3001 case LB_GETANCHORINDEX:
3002 return descr->anchor_item;
3004 case LB_SETANCHORINDEX:
3005 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
3007 SetLastError(ERROR_INVALID_INDEX);
3008 return LB_ERR;
3010 descr->anchor_item = (INT)wParam;
3011 return LB_OKAY;
3013 case LB_DIR:
3015 INT ret;
3016 LPWSTR textW;
3017 if(unicode)
3018 textW = (LPWSTR)lParam;
3019 else
3021 LPSTR textA = (LPSTR)lParam;
3022 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3023 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3024 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3026 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3027 if(!unicode)
3028 HeapFree(GetProcessHeap(), 0, textW);
3029 return ret;
3032 case LB_GETLOCALE:
3033 return descr->locale;
3035 case LB_SETLOCALE:
3037 LCID ret;
3038 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3039 return LB_ERR;
3040 ret = descr->locale;
3041 descr->locale = (LCID)wParam;
3042 return ret;
3045 case LB_INITSTORAGE:
3046 return LISTBOX_InitStorage( descr, wParam );
3048 case LB_SETCOUNT:
3049 return LISTBOX_SetCount( descr, (INT)wParam );
3051 case LB_SETTABSTOPS:
3052 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
3054 case LB_CARETON:
3055 if (descr->caret_on)
3056 return LB_OKAY;
3057 descr->caret_on = TRUE;
3058 if ((descr->focus_item != -1) && (descr->in_focus))
3059 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3060 return LB_OKAY;
3062 case LB_CARETOFF:
3063 if (!descr->caret_on)
3064 return LB_OKAY;
3065 descr->caret_on = FALSE;
3066 if ((descr->focus_item != -1) && (descr->in_focus))
3067 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3068 return LB_OKAY;
3070 case LB_GETLISTBOXINFO:
3071 return descr->page_size;
3073 case WM_DESTROY:
3074 return LISTBOX_Destroy( descr );
3076 case WM_ENABLE:
3077 InvalidateRect( descr->self, NULL, TRUE );
3078 return 0;
3080 case WM_SETREDRAW:
3081 LISTBOX_SetRedraw( descr, wParam != 0 );
3082 return 0;
3084 case WM_GETDLGCODE:
3085 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3087 case WM_PRINTCLIENT:
3088 case WM_PAINT:
3090 PAINTSTRUCT ps;
3091 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3092 ret = LISTBOX_Paint( descr, hdc );
3093 if( !wParam ) EndPaint( descr->self, &ps );
3095 return ret;
3096 case WM_SIZE:
3097 LISTBOX_UpdateSize( descr );
3098 return 0;
3099 case WM_GETFONT:
3100 return (LRESULT)descr->font;
3101 case WM_SETFONT:
3102 LISTBOX_SetFont( descr, (HFONT)wParam );
3103 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3104 return 0;
3105 case WM_SETFOCUS:
3106 descr->in_focus = TRUE;
3107 descr->caret_on = TRUE;
3108 if (descr->focus_item != -1)
3109 LISTBOX_DrawFocusRect( descr, TRUE );
3110 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3111 return 0;
3112 case WM_KILLFOCUS:
3113 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3114 descr->in_focus = FALSE;
3115 descr->wheel_remain = 0;
3116 if ((descr->focus_item != -1) && descr->caret_on)
3117 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3118 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3119 return 0;
3120 case WM_HSCROLL:
3121 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3122 case WM_VSCROLL:
3123 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3124 case WM_MOUSEWHEEL:
3125 if (wParam & (MK_SHIFT | MK_CONTROL))
3126 return DefWindowProcW( descr->self, msg, wParam, lParam );
3127 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3128 case WM_LBUTTONDOWN:
3129 if (lphc)
3130 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3131 (INT16)LOWORD(lParam),
3132 (INT16)HIWORD(lParam) );
3133 return LISTBOX_HandleLButtonDown( descr, wParam,
3134 (INT16)LOWORD(lParam),
3135 (INT16)HIWORD(lParam) );
3136 case WM_LBUTTONDBLCLK:
3137 if (lphc)
3138 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3139 (INT16)LOWORD(lParam),
3140 (INT16)HIWORD(lParam) );
3141 if (descr->style & LBS_NOTIFY)
3142 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3143 return 0;
3144 case WM_MOUSEMOVE:
3145 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3147 BOOL captured = descr->captured;
3148 POINT mousePos;
3149 RECT clientRect;
3151 mousePos.x = (INT16)LOWORD(lParam);
3152 mousePos.y = (INT16)HIWORD(lParam);
3155 * If we are in a dropdown combobox, we simulate that
3156 * the mouse is captured to show the tracking of the item.
3158 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3159 descr->captured = TRUE;
3161 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3163 descr->captured = captured;
3165 else if (GetCapture() == descr->self)
3167 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3168 (INT16)HIWORD(lParam) );
3170 return 0;
3171 case WM_LBUTTONUP:
3172 if (lphc)
3174 POINT mousePos;
3175 RECT clientRect;
3178 * If the mouse button "up" is not in the listbox,
3179 * we make sure there is no selection by re-selecting the
3180 * item that was selected when the listbox was made visible.
3182 mousePos.x = (INT16)LOWORD(lParam);
3183 mousePos.y = (INT16)HIWORD(lParam);
3185 GetClientRect(descr->self, &clientRect);
3188 * When the user clicks outside the combobox and the focus
3189 * is lost, the owning combobox will send a fake buttonup with
3190 * 0xFFFFFFF as the mouse location, we must also revert the
3191 * selection to the original selection.
3193 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3194 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3196 return LISTBOX_HandleLButtonUp( descr );
3197 case WM_KEYDOWN:
3198 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3200 /* for some reason Windows makes it possible to
3201 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3203 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3204 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3205 && (wParam == VK_DOWN || wParam == VK_UP)) )
3207 COMBO_FlipListbox( lphc, FALSE, FALSE );
3208 return 0;
3211 return LISTBOX_HandleKeyDown( descr, wParam );
3212 case WM_CHAR:
3214 WCHAR charW;
3215 if(unicode)
3216 charW = (WCHAR)wParam;
3217 else
3219 CHAR charA = (CHAR)wParam;
3220 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3222 return LISTBOX_HandleChar( descr, charW );
3224 case WM_SYSTIMER:
3225 return LISTBOX_HandleSystemTimer( descr );
3226 case WM_ERASEBKGND:
3227 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3229 RECT rect;
3230 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3231 wParam, (LPARAM)descr->self );
3232 TRACE("hbrush = %p\n", hbrush);
3233 if(!hbrush)
3234 hbrush = GetSysColorBrush(COLOR_WINDOW);
3235 if(hbrush)
3237 GetClientRect(descr->self, &rect);
3238 FillRect((HDC)wParam, &rect, hbrush);
3241 return 1;
3242 case WM_DROPFILES:
3243 if( lphc ) return 0;
3244 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3245 SendMessageA( descr->owner, msg, wParam, lParam );
3247 case WM_NCDESTROY:
3248 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3249 lphc->hWndLBox = 0;
3250 break;
3252 case WM_NCACTIVATE:
3253 if (lphc) return 0;
3254 break;
3256 default:
3257 if ((msg >= WM_USER) && (msg < 0xc000))
3258 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3259 hwnd, msg, wParam, lParam );
3262 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3263 DefWindowProcA( hwnd, msg, wParam, lParam );
3266 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3268 TRACE("%p\n", hwnd);
3269 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);