win32u: Move NtUserTranslateMessage implementation from user32.
[wine.git] / dlls / comctl32 / listbox.c
blob08bc4db4362f95768d39d76a2618efed0d0ae957
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
5 * Copyright 2005 Frank Richter
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #include <string.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include "windef.h"
28 #include "winbase.h"
29 #include "wingdi.h"
30 #include "winuser.h"
31 #include "commctrl.h"
32 #include "uxtheme.h"
33 #include "vssym32.h"
34 #include "wine/exception.h"
35 #include "wine/debug.h"
36 #include "wine/heap.h"
38 #include "comctl32.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
42 /* Items array granularity (must be power of 2) */
43 #define LB_ARRAY_GRANULARITY 16
45 /* Scrolling timeout in ms */
46 #define LB_SCROLL_TIMEOUT 50
48 /* Listbox system timer id */
49 #define LB_TIMER_ID 2
51 /* flag listbox changed while setredraw false - internal style */
52 #define LBS_DISPLAYCHANGED 0x80000000
54 /* Item structure */
55 typedef struct
57 LPWSTR str; /* Item text */
58 BOOL selected; /* Is item selected? */
59 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
60 ULONG_PTR data; /* User data */
61 } LB_ITEMDATA;
63 /* Listbox structure */
64 typedef struct
66 HWND self; /* Our own window handle */
67 HWND owner; /* Owner window to send notifications to */
68 UINT style; /* Window style */
69 INT width; /* Window width */
70 INT height; /* Window height */
71 union
73 LB_ITEMDATA *items; /* Array of items */
74 BYTE *nodata_items; /* For multi-selection LBS_NODATA */
75 } u;
76 INT nb_items; /* Number of items */
77 UINT items_size; /* Total number of allocated items in the array */
78 INT top_item; /* Top visible item */
79 INT selected_item; /* Selected item */
80 INT focus_item; /* Item that has the focus */
81 INT anchor_item; /* Anchor item for extended selection */
82 INT item_height; /* Default item height */
83 INT page_size; /* Items per listbox page */
84 INT column_width; /* Column width for multi-column listboxes */
85 INT horz_extent; /* Horizontal extent */
86 INT horz_pos; /* Horizontal position */
87 INT nb_tabs; /* Number of tabs in array */
88 INT *tabs; /* Array of tabs */
89 INT avg_char_width; /* Average width of characters */
90 INT wheel_remain; /* Left over scroll amount */
91 BOOL caret_on; /* Is caret on? */
92 BOOL captured; /* Is mouse captured? */
93 BOOL in_focus;
94 HFONT font; /* Current font */
95 LCID locale; /* Current locale for string comparisons */
96 HEADCOMBO *lphc; /* ComboLBox */
97 } LB_DESCR;
100 #define IS_OWNERDRAW(descr) \
101 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
103 #define HAS_STRINGS(descr) \
104 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
107 #define IS_MULTISELECT(descr) \
108 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
109 !((descr)->style & LBS_NOSEL))
111 #define SEND_NOTIFICATION(descr,code) \
112 (SendMessageW( (descr)->owner, WM_COMMAND, \
113 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
115 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
117 /* Current timer status */
118 typedef enum
120 LB_TIMER_NONE,
121 LB_TIMER_UP,
122 LB_TIMER_LEFT,
123 LB_TIMER_DOWN,
124 LB_TIMER_RIGHT
125 } TIMER_DIRECTION;
127 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
129 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
132 For listboxes without LBS_NODATA, an array of LB_ITEMDATA is allocated
133 to store the states of each item into descr->u.items.
135 For single-selection LBS_NODATA listboxes, no storage is allocated,
136 and thus descr->u.nodata_items will always be NULL.
138 For multi-selection LBS_NODATA listboxes, one byte per item is stored
139 for the item's selection state into descr->u.nodata_items.
141 static size_t get_sizeof_item( const LB_DESCR *descr )
143 return (descr->style & LBS_NODATA) ? sizeof(BYTE) : sizeof(LB_ITEMDATA);
146 static BOOL resize_storage(LB_DESCR *descr, UINT items_size)
148 LB_ITEMDATA *items;
150 if (items_size > descr->items_size ||
151 items_size + LB_ARRAY_GRANULARITY * 2 < descr->items_size)
153 items_size = (items_size + LB_ARRAY_GRANULARITY - 1) & ~(LB_ARRAY_GRANULARITY - 1);
154 if ((descr->style & (LBS_NODATA | LBS_MULTIPLESEL | LBS_EXTENDEDSEL)) != LBS_NODATA)
156 items = heap_realloc(descr->u.items, items_size * get_sizeof_item(descr));
157 if (!items)
159 SEND_NOTIFICATION(descr, LBN_ERRSPACE);
160 return FALSE;
162 descr->u.items = items;
164 descr->items_size = items_size;
167 if ((descr->style & LBS_NODATA) && descr->u.nodata_items && items_size > descr->nb_items)
169 memset(descr->u.nodata_items + descr->nb_items, 0,
170 (items_size - descr->nb_items) * get_sizeof_item(descr));
172 return TRUE;
175 static ULONG_PTR get_item_data( const LB_DESCR *descr, UINT index )
177 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].data;
180 static void set_item_data( LB_DESCR *descr, UINT index, ULONG_PTR data )
182 if (!(descr->style & LBS_NODATA)) descr->u.items[index].data = data;
185 static WCHAR *get_item_string( const LB_DESCR *descr, UINT index )
187 return HAS_STRINGS(descr) ? descr->u.items[index].str : NULL;
190 static void set_item_string( const LB_DESCR *descr, UINT index, WCHAR *string )
192 if (!(descr->style & LBS_NODATA)) descr->u.items[index].str = string;
195 static UINT get_item_height( const LB_DESCR *descr, UINT index )
197 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].height;
200 static void set_item_height( LB_DESCR *descr, UINT index, UINT height )
202 if (!(descr->style & LBS_NODATA)) descr->u.items[index].height = height;
205 static BOOL is_item_selected( const LB_DESCR *descr, UINT index )
207 if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
208 return index == descr->selected_item;
209 if (descr->style & LBS_NODATA)
210 return descr->u.nodata_items[index];
211 else
212 return descr->u.items[index].selected;
215 static void set_item_selected_state(LB_DESCR *descr, UINT index, BOOL state)
217 if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
219 if (descr->style & LBS_NODATA)
220 descr->u.nodata_items[index] = state;
221 else
222 descr->u.items[index].selected = state;
226 static void insert_item_data(LB_DESCR *descr, UINT index)
228 size_t size = get_sizeof_item(descr);
229 BYTE *p = descr->u.nodata_items + index * size;
231 if (!descr->u.items) return;
233 if (index < descr->nb_items)
234 memmove(p + size, p, (descr->nb_items - index) * size);
237 static void remove_item_data(LB_DESCR *descr, UINT index)
239 size_t size = get_sizeof_item(descr);
240 BYTE *p = descr->u.nodata_items + index * size;
242 if (!descr->u.items) return;
244 if (index < descr->nb_items)
245 memmove(p, p + size, (descr->nb_items - index) * size);
248 /***********************************************************************
249 * LISTBOX_GetCurrentPageSize
251 * Return the current page size
253 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
255 INT i, height;
256 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
257 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
259 if ((height += get_item_height(descr, i)) > descr->height) break;
261 if (i == descr->top_item) return 1;
262 else return i - descr->top_item;
266 /***********************************************************************
267 * LISTBOX_GetMaxTopIndex
269 * Return the maximum possible index for the top of the listbox.
271 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
273 INT max, page;
275 if (descr->style & LBS_OWNERDRAWVARIABLE)
277 page = descr->height;
278 for (max = descr->nb_items - 1; max >= 0; max--)
279 if ((page -= get_item_height(descr, max)) < 0) break;
280 if (max < descr->nb_items - 1) max++;
282 else if (descr->style & LBS_MULTICOLUMN)
284 if ((page = descr->width / descr->column_width) < 1) page = 1;
285 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
286 max = (max - page) * descr->page_size;
288 else
290 max = descr->nb_items - descr->page_size;
292 if (max < 0) max = 0;
293 return max;
297 /***********************************************************************
298 * LISTBOX_UpdateScroll
300 * Update the scrollbars. Should be called whenever the content
301 * of the listbox changes.
303 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
305 SCROLLINFO info;
307 /* Check the listbox scroll bar flags individually before we call
308 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
309 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
310 scroll bar when we do not need one.
311 if (!(descr->style & WS_VSCROLL)) return;
314 /* It is important that we check descr->style, and not wnd->dwStyle,
315 for WS_VSCROLL, as the former is exactly the one passed in
316 argument to CreateWindow.
317 In Windows (and from now on in Wine :) a listbox created
318 with such a style (no WS_SCROLL) does not update
319 the scrollbar with listbox-related data, thus letting
320 the programmer use it for his/her own purposes. */
322 if (descr->style & LBS_NOREDRAW) return;
323 info.cbSize = sizeof(info);
325 if (descr->style & LBS_MULTICOLUMN)
327 info.nMin = 0;
328 info.nMax = (descr->nb_items - 1) / descr->page_size;
329 info.nPos = descr->top_item / descr->page_size;
330 info.nPage = descr->width / descr->column_width;
331 if (info.nPage < 1) info.nPage = 1;
332 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
333 if (descr->style & LBS_DISABLENOSCROLL)
334 info.fMask |= SIF_DISABLENOSCROLL;
335 if (descr->style & WS_HSCROLL)
336 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
337 info.nMax = 0;
338 info.fMask = SIF_RANGE;
339 if (descr->style & WS_VSCROLL)
340 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
342 else
344 info.nMin = 0;
345 info.nMax = descr->nb_items - 1;
346 info.nPos = descr->top_item;
347 info.nPage = LISTBOX_GetCurrentPageSize( descr );
348 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
349 if (descr->style & LBS_DISABLENOSCROLL)
350 info.fMask |= SIF_DISABLENOSCROLL;
351 if (descr->style & WS_VSCROLL)
352 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
354 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
356 info.nPos = descr->horz_pos;
357 info.nPage = descr->width;
358 info.fMask = SIF_POS | SIF_PAGE;
359 if (descr->style & LBS_DISABLENOSCROLL)
360 info.fMask |= SIF_DISABLENOSCROLL;
361 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
363 else
365 if (descr->style & LBS_DISABLENOSCROLL)
367 info.nMin = 0;
368 info.nMax = 0;
369 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
370 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
372 else
374 ShowScrollBar( descr->self, SB_HORZ, FALSE );
381 /***********************************************************************
382 * LISTBOX_SetTopItem
384 * Set the top item of the listbox, scrolling up or down if necessary.
386 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
388 INT max = LISTBOX_GetMaxTopIndex( descr );
390 TRACE("setting top item %d, scroll %d\n", index, scroll);
392 if (index > max) index = max;
393 if (index < 0) index = 0;
394 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
395 if (descr->top_item == index) return LB_OKAY;
396 if (scroll)
398 INT dx = 0, dy = 0;
399 if (descr->style & LBS_MULTICOLUMN)
400 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
401 else if (descr->style & LBS_OWNERDRAWVARIABLE)
403 INT i;
404 if (index > descr->top_item)
406 for (i = index - 1; i >= descr->top_item; i--)
407 dy -= get_item_height(descr, i);
409 else
411 for (i = index; i < descr->top_item; i++)
412 dy += get_item_height(descr, i);
415 else
416 dy = (descr->top_item - index) * descr->item_height;
418 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
419 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
421 else
422 InvalidateRect( descr->self, NULL, TRUE );
423 descr->top_item = index;
424 LISTBOX_UpdateScroll( descr );
425 return LB_OKAY;
429 /***********************************************************************
430 * LISTBOX_UpdatePage
432 * Update the page size. Should be called when the size of
433 * the client area or the item height changes.
435 static void LISTBOX_UpdatePage( LB_DESCR *descr )
437 INT page_size;
439 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
440 page_size = 1;
441 if (page_size == descr->page_size) return;
442 descr->page_size = page_size;
443 if (descr->style & LBS_MULTICOLUMN)
444 InvalidateRect( descr->self, NULL, TRUE );
445 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
449 /***********************************************************************
450 * LISTBOX_UpdateSize
452 * Update the size of the listbox. Should be called when the size of
453 * the client area changes.
455 static void LISTBOX_UpdateSize( LB_DESCR *descr )
457 RECT rect;
459 GetClientRect( descr->self, &rect );
460 descr->width = rect.right - rect.left;
461 descr->height = rect.bottom - rect.top;
462 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
464 INT remaining;
465 RECT rect;
467 GetWindowRect( descr->self, &rect );
468 if(descr->item_height != 0)
469 remaining = descr->height % descr->item_height;
470 else
471 remaining = 0;
472 if ((descr->height > descr->item_height) && remaining)
474 TRACE("[%p]: changing height %d -> %d\n",
475 descr->self, descr->height, descr->height - remaining );
476 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
477 rect.bottom - rect.top - remaining,
478 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
479 return;
482 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
483 LISTBOX_UpdatePage( descr );
484 LISTBOX_UpdateScroll( descr );
486 /* Invalidate the focused item so it will be repainted correctly */
487 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
489 InvalidateRect( descr->self, &rect, FALSE );
494 /***********************************************************************
495 * LISTBOX_GetItemRect
497 * Get the rectangle enclosing an item, in listbox client coordinates.
498 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
500 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
502 /* Index <= 0 is legal even on empty listboxes */
503 if (index && (index >= descr->nb_items))
505 SetRectEmpty(rect);
506 SetLastError(ERROR_INVALID_INDEX);
507 return LB_ERR;
509 SetRect( rect, 0, 0, descr->width, descr->height );
510 if (descr->style & LBS_MULTICOLUMN)
512 INT col = (index / descr->page_size) -
513 (descr->top_item / descr->page_size);
514 rect->left += col * descr->column_width;
515 rect->right = rect->left + descr->column_width;
516 rect->top += (index % descr->page_size) * descr->item_height;
517 rect->bottom = rect->top + descr->item_height;
519 else if (descr->style & LBS_OWNERDRAWVARIABLE)
521 INT i;
522 rect->right += descr->horz_pos;
523 if ((index >= 0) && (index < descr->nb_items))
525 if (index < descr->top_item)
527 for (i = descr->top_item-1; i >= index; i--)
528 rect->top -= get_item_height(descr, i);
530 else
532 for (i = descr->top_item; i < index; i++)
533 rect->top += get_item_height(descr, i);
535 rect->bottom = rect->top + get_item_height(descr, index);
539 else
541 rect->top += (index - descr->top_item) * descr->item_height;
542 rect->bottom = rect->top + descr->item_height;
543 rect->right += descr->horz_pos;
546 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
548 return ((rect->left < descr->width) && (rect->right > 0) &&
549 (rect->top < descr->height) && (rect->bottom > 0));
553 /***********************************************************************
554 * LISTBOX_GetItemFromPoint
556 * Return the item nearest from point (x,y) (in client coordinates).
558 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
560 INT index = descr->top_item;
562 if (!descr->nb_items) return -1; /* No items */
563 if (descr->style & LBS_OWNERDRAWVARIABLE)
565 INT pos = 0;
566 if (y >= 0)
568 while (index < descr->nb_items)
570 if ((pos += get_item_height(descr, index)) > y) break;
571 index++;
574 else
576 while (index > 0)
578 index--;
579 if ((pos -= get_item_height(descr, index)) <= y) break;
583 else if (descr->style & LBS_MULTICOLUMN)
585 if (y >= descr->item_height * descr->page_size) return -1;
586 if (y >= 0) index += y / descr->item_height;
587 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
588 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
590 else
592 index += (y / descr->item_height);
594 if (index < 0) return 0;
595 if (index >= descr->nb_items) return -1;
596 return index;
600 /***********************************************************************
601 * LISTBOX_PaintItem
603 * Paint an item.
605 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
606 INT index, UINT action, BOOL ignoreFocus )
608 BOOL selected = FALSE, focused;
609 WCHAR *item_str = NULL;
611 if (index < descr->nb_items)
613 item_str = get_item_string(descr, index);
614 selected = is_item_selected(descr, index);
617 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
619 if (IS_OWNERDRAW(descr))
621 DRAWITEMSTRUCT dis;
622 RECT r;
623 HRGN hrgn;
625 if (index >= descr->nb_items)
627 if (action == ODA_FOCUS)
628 DrawFocusRect( hdc, rect );
629 else
630 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
631 return;
634 /* some programs mess with the clipping region when
635 drawing the item, *and* restore the previous region
636 after they are done, so a region has better to exist
637 else everything ends clipped */
638 GetClientRect(descr->self, &r);
639 hrgn = set_control_clipping( hdc, &r );
641 dis.CtlType = ODT_LISTBOX;
642 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
643 dis.hwndItem = descr->self;
644 dis.itemAction = action;
645 dis.hDC = hdc;
646 dis.itemID = index;
647 dis.itemState = 0;
648 if (selected)
649 dis.itemState |= ODS_SELECTED;
650 if (focused)
651 dis.itemState |= ODS_FOCUS;
652 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
653 dis.itemData = get_item_data(descr, index);
654 dis.rcItem = *rect;
655 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
656 descr->self, index, debugstr_w(item_str), action,
657 dis.itemState, wine_dbgstr_rect(rect) );
658 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
659 SelectClipRgn( hdc, hrgn );
660 if (hrgn) DeleteObject( hrgn );
662 else
664 COLORREF oldText = 0, oldBk = 0;
666 if (action == ODA_FOCUS)
668 DrawFocusRect( hdc, rect );
669 return;
671 if (selected)
673 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
674 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
677 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
678 descr->self, index, debugstr_w(item_str), action,
679 wine_dbgstr_rect(rect) );
680 if (!item_str)
681 ExtTextOutW( hdc, rect->left + 1, rect->top,
682 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
683 else if (!(descr->style & LBS_USETABSTOPS))
684 ExtTextOutW( hdc, rect->left + 1, rect->top,
685 ETO_OPAQUE | ETO_CLIPPED, rect, item_str,
686 lstrlenW(item_str), NULL );
687 else
689 /* Output empty string to paint background in the full width. */
690 ExtTextOutW( hdc, rect->left + 1, rect->top,
691 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
692 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
693 item_str, lstrlenW(item_str),
694 descr->nb_tabs, descr->tabs, 0);
696 if (selected)
698 SetBkColor( hdc, oldBk );
699 SetTextColor( hdc, oldText );
701 if (focused)
702 DrawFocusRect( hdc, rect );
707 /***********************************************************************
708 * LISTBOX_SetRedraw
710 * Change the redraw flag.
712 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
714 if (on)
716 if (!(descr->style & LBS_NOREDRAW)) return;
717 descr->style &= ~LBS_NOREDRAW;
718 if (descr->style & LBS_DISPLAYCHANGED)
719 { /* page was changed while setredraw false, refresh automatically */
720 InvalidateRect(descr->self, NULL, TRUE);
721 if ((descr->top_item + descr->page_size) > descr->nb_items)
722 { /* reset top of page if less than number of items/page */
723 descr->top_item = descr->nb_items - descr->page_size;
724 if (descr->top_item < 0) descr->top_item = 0;
726 descr->style &= ~LBS_DISPLAYCHANGED;
728 LISTBOX_UpdateScroll( descr );
730 else descr->style |= LBS_NOREDRAW;
734 /***********************************************************************
735 * LISTBOX_RepaintItem
737 * Repaint a single item synchronously.
739 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
741 HDC hdc;
742 RECT rect;
743 HFONT oldFont = 0;
744 HBRUSH hbrush, oldBrush = 0;
746 /* Do not repaint the item if the item is not visible */
747 if (!IsWindowVisible(descr->self)) return;
748 if (descr->style & LBS_NOREDRAW)
750 descr->style |= LBS_DISPLAYCHANGED;
751 return;
753 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
754 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
755 if (descr->font) oldFont = SelectObject( hdc, descr->font );
756 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
757 (WPARAM)hdc, (LPARAM)descr->self );
758 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
759 if (!IsWindowEnabled(descr->self))
760 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
761 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
762 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
763 if (oldFont) SelectObject( hdc, oldFont );
764 if (oldBrush) SelectObject( hdc, oldBrush );
765 ReleaseDC( descr->self, hdc );
769 /***********************************************************************
770 * LISTBOX_DrawFocusRect
772 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
774 HDC hdc;
775 RECT rect;
776 HFONT oldFont = 0;
778 /* Do not repaint the item if the item is not visible */
779 if (!IsWindowVisible(descr->self)) return;
781 if (descr->focus_item == -1) return;
782 if (!descr->caret_on || !descr->in_focus) return;
784 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
785 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
786 if (descr->font) oldFont = SelectObject( hdc, descr->font );
787 if (!IsWindowEnabled(descr->self))
788 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
789 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
790 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
791 if (oldFont) SelectObject( hdc, oldFont );
792 ReleaseDC( descr->self, hdc );
796 /***********************************************************************
797 * LISTBOX_InitStorage
799 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
801 UINT new_size = descr->nb_items + nb_items;
803 if (new_size > descr->items_size && !resize_storage(descr, new_size))
804 return LB_ERRSPACE;
805 return descr->items_size;
809 /***********************************************************************
810 * LISTBOX_SetTabStops
812 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
814 INT i;
816 if (!(descr->style & LBS_USETABSTOPS))
818 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
819 return FALSE;
822 HeapFree( GetProcessHeap(), 0, descr->tabs );
823 if (!(descr->nb_tabs = count))
825 descr->tabs = NULL;
826 return TRUE;
828 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
829 descr->nb_tabs * sizeof(INT) )))
830 return FALSE;
831 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
833 /* convert into "dialog units"*/
834 for (i = 0; i < descr->nb_tabs; i++)
835 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
837 return TRUE;
841 /***********************************************************************
842 * LISTBOX_GetText
844 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
846 DWORD len;
848 if ((index < 0) || (index >= descr->nb_items))
850 SetLastError(ERROR_INVALID_INDEX);
851 return LB_ERR;
854 if (HAS_STRINGS(descr))
856 WCHAR *str = get_item_string(descr, index);
858 if (!buffer)
859 return lstrlenW(str);
861 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(str));
863 __TRY /* hide a Delphi bug that passes a read-only buffer */
865 lstrcpyW(buffer, str);
866 len = lstrlenW(buffer);
868 __EXCEPT_PAGE_FAULT
870 WARN( "got an invalid buffer (Delphi bug?)\n" );
871 SetLastError( ERROR_INVALID_PARAMETER );
872 return LB_ERR;
874 __ENDTRY
875 } else
877 if (buffer)
878 *((ULONG_PTR *)buffer) = get_item_data(descr, index);
879 len = sizeof(ULONG_PTR);
881 return len;
884 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
886 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
887 if (ret == CSTR_LESS_THAN)
888 return -1;
889 if (ret == CSTR_EQUAL)
890 return 0;
891 if (ret == CSTR_GREATER_THAN)
892 return 1;
893 return -1;
896 /***********************************************************************
897 * LISTBOX_FindStringPos
899 * Find the nearest string located before a given string in sort order.
900 * If 'exact' is TRUE, return an error if we don't get an exact match.
902 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
904 INT index, min, max, res;
906 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
908 min = 0;
909 max = descr->nb_items - 1;
910 while (min <= max)
912 index = (min + max) / 2;
913 if (HAS_STRINGS(descr))
914 res = LISTBOX_lstrcmpiW( descr->locale, get_item_string(descr, index), str );
915 else
917 COMPAREITEMSTRUCT cis;
918 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
920 cis.CtlType = ODT_LISTBOX;
921 cis.CtlID = id;
922 cis.hwndItem = descr->self;
923 /* note that some application (MetaStock) expects the second item
924 * to be in the listbox */
925 cis.itemID1 = index;
926 cis.itemData1 = get_item_data(descr, index);
927 cis.itemID2 = -1;
928 cis.itemData2 = (ULONG_PTR)str;
929 cis.dwLocaleId = descr->locale;
930 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
932 if (!res) return index;
933 if (res > 0) max = index - 1;
934 else min = index + 1;
936 return exact ? -1 : min;
940 /***********************************************************************
941 * LISTBOX_FindFileStrPos
943 * Find the nearest string located before a given string in directory
944 * sort order (i.e. first files, then directories, then drives).
946 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
948 INT min, max, res;
950 if (!HAS_STRINGS(descr))
951 return LISTBOX_FindStringPos( descr, str, FALSE );
952 min = 0;
953 max = descr->nb_items;
954 while (min != max)
956 INT index = (min + max) / 2;
957 LPCWSTR p = get_item_string(descr, index);
958 if (*p == '[') /* drive or directory */
960 if (*str != '[') res = -1;
961 else if (p[1] == '-') /* drive */
963 if (str[1] == '-') res = str[2] - p[2];
964 else res = -1;
966 else /* directory */
968 if (str[1] == '-') res = 1;
969 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
972 else /* filename */
974 if (*str == '[') res = 1;
975 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
977 if (!res) return index;
978 if (res < 0) max = index;
979 else min = index + 1;
981 return max;
985 /***********************************************************************
986 * LISTBOX_FindString
988 * Find the item beginning with a given string.
990 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
992 INT i, index;
994 if (descr->style & LBS_NODATA) return LB_ERR;
996 start++;
997 if (start >= descr->nb_items) start = 0;
998 if (HAS_STRINGS(descr))
1000 if (!str || ! str[0] ) return LB_ERR;
1001 if (exact)
1003 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1005 if (index == descr->nb_items) index = 0;
1006 if (!LISTBOX_lstrcmpiW(descr->locale, str, get_item_string(descr, index)))
1007 return index;
1010 else
1012 /* Special case for drives and directories: ignore prefix */
1013 INT len = lstrlenW(str);
1014 WCHAR *item_str;
1016 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1018 if (index == descr->nb_items) index = 0;
1019 item_str = get_item_string(descr, index);
1021 if (!wcsnicmp(str, item_str, len)) return index;
1022 if (item_str[0] == '[')
1024 if (!wcsnicmp(str, item_str + 1, len)) return index;
1025 if (item_str[1] == '-' && !wcsnicmp(str, item_str + 2, len)) return index;
1030 else
1032 if (exact && (descr->style & LBS_SORT))
1033 /* If sorted, use a WM_COMPAREITEM binary search */
1034 return LISTBOX_FindStringPos( descr, str, TRUE );
1036 /* Otherwise use a linear search */
1037 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1039 if (index == descr->nb_items) index = 0;
1040 if (get_item_data(descr, index) == (ULONG_PTR)str) return index;
1043 return LB_ERR;
1047 /***********************************************************************
1048 * LISTBOX_GetSelCount
1050 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1052 INT i, count;
1054 if (!(descr->style & LBS_MULTIPLESEL) ||
1055 (descr->style & LBS_NOSEL))
1056 return LB_ERR;
1057 for (i = count = 0; i < descr->nb_items; i++)
1058 if (is_item_selected(descr, i)) count++;
1059 return count;
1063 /***********************************************************************
1064 * LISTBOX_GetSelItems
1066 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1068 INT i, count;
1070 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1071 for (i = count = 0; (i < descr->nb_items) && (count < max); i++)
1072 if (is_item_selected(descr, i)) array[count++] = i;
1073 return count;
1077 /***********************************************************************
1078 * LISTBOX_Paint
1080 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1082 INT i, col_pos = descr->page_size - 1;
1083 RECT rect;
1084 RECT focusRect = {-1, -1, -1, -1};
1085 HFONT oldFont = 0;
1086 HBRUSH hbrush, oldBrush = 0;
1088 if (descr->style & LBS_NOREDRAW) return 0;
1090 SetRect( &rect, 0, 0, descr->width, descr->height );
1091 if (descr->style & LBS_MULTICOLUMN)
1092 rect.right = rect.left + descr->column_width;
1093 else if (descr->horz_pos)
1095 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1096 rect.right += descr->horz_pos;
1099 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1100 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1101 (WPARAM)hdc, (LPARAM)descr->self );
1102 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1103 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1105 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1106 (descr->in_focus))
1108 /* Special case for empty listbox: paint focus rect */
1109 rect.bottom = rect.top + descr->item_height;
1110 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1111 &rect, NULL, 0, NULL );
1112 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1113 rect.top = rect.bottom;
1116 /* Paint all the item, regarding the selection
1117 Focus state will be painted after */
1119 for (i = descr->top_item; i < descr->nb_items; i++)
1121 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1122 rect.bottom = rect.top + descr->item_height;
1123 else
1124 rect.bottom = rect.top + get_item_height(descr, i);
1126 /* keep the focus rect, to paint the focus item after */
1127 if (i == descr->focus_item)
1128 focusRect = rect;
1130 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1131 rect.top = rect.bottom;
1133 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1135 if (!IS_OWNERDRAW(descr))
1137 /* Clear the bottom of the column */
1138 if (rect.top < descr->height)
1140 rect.bottom = descr->height;
1141 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1142 &rect, NULL, 0, NULL );
1146 /* Go to the next column */
1147 rect.left += descr->column_width;
1148 rect.right += descr->column_width;
1149 rect.top = 0;
1150 col_pos = descr->page_size - 1;
1151 if (rect.left >= descr->width) break;
1153 else
1155 col_pos--;
1156 if (rect.top >= descr->height) break;
1160 /* Paint the focus item now */
1161 if (focusRect.top != focusRect.bottom &&
1162 descr->caret_on && descr->in_focus)
1163 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1165 if (!IS_OWNERDRAW(descr))
1167 /* Clear the remainder of the client area */
1168 if (rect.top < descr->height)
1170 rect.bottom = descr->height;
1171 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1172 &rect, NULL, 0, NULL );
1174 if (rect.right < descr->width)
1176 rect.left = rect.right;
1177 rect.right = descr->width;
1178 rect.top = 0;
1179 rect.bottom = descr->height;
1180 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1181 &rect, NULL, 0, NULL );
1184 if (oldFont) SelectObject( hdc, oldFont );
1185 if (oldBrush) SelectObject( hdc, oldBrush );
1186 return 0;
1189 static LRESULT LISTBOX_NCPaint( LB_DESCR *descr, HRGN region )
1191 DWORD exstyle = GetWindowLongW( descr->self, GWL_EXSTYLE);
1192 HTHEME theme = GetWindowTheme( descr->self );
1193 HRGN cliprgn = region;
1194 int cxEdge, cyEdge;
1195 HDC hdc;
1196 RECT r;
1198 if (!theme || !(exstyle & WS_EX_CLIENTEDGE))
1199 return DefWindowProcW(descr->self, WM_NCPAINT, (WPARAM)region, 0);
1201 cxEdge = GetSystemMetrics(SM_CXEDGE);
1202 cyEdge = GetSystemMetrics(SM_CYEDGE);
1204 GetWindowRect(descr->self, &r);
1206 /* New clipping region passed to default proc to exclude border */
1207 cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge,
1208 r.right - cxEdge, r.bottom - cyEdge);
1209 if (region != (HRGN)1)
1210 CombineRgn(cliprgn, cliprgn, region, RGN_AND);
1211 OffsetRect(&r, -r.left, -r.top);
1213 hdc = GetDCEx(descr->self, region, DCX_WINDOW|DCX_INTERSECTRGN);
1215 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
1216 DrawThemeParentBackground(descr->self, hdc, &r);
1217 DrawThemeBackground (theme, hdc, 0, 0, &r, 0);
1218 ReleaseDC(descr->self, hdc);
1220 /* Call default proc to get the scrollbars etc. also painted */
1221 DefWindowProcW(descr->self, WM_NCPAINT, (WPARAM)cliprgn, 0);
1222 DeleteObject(cliprgn);
1223 return 0;
1226 /***********************************************************************
1227 * LISTBOX_InvalidateItems
1229 * Invalidate all items from a given item. If the specified item is not
1230 * visible, nothing happens.
1232 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1234 RECT rect;
1236 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1238 if (descr->style & LBS_NOREDRAW)
1240 descr->style |= LBS_DISPLAYCHANGED;
1241 return;
1243 rect.bottom = descr->height;
1244 InvalidateRect( descr->self, &rect, TRUE );
1245 if (descr->style & LBS_MULTICOLUMN)
1247 /* Repaint the other columns */
1248 rect.left = rect.right;
1249 rect.right = descr->width;
1250 rect.top = 0;
1251 InvalidateRect( descr->self, &rect, TRUE );
1256 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1258 RECT rect;
1260 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1261 InvalidateRect( descr->self, &rect, TRUE );
1264 /***********************************************************************
1265 * LISTBOX_GetItemHeight
1267 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1269 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1271 if ((index < 0) || (index >= descr->nb_items))
1273 SetLastError(ERROR_INVALID_INDEX);
1274 return LB_ERR;
1276 return get_item_height(descr, index);
1278 else return descr->item_height;
1282 /***********************************************************************
1283 * LISTBOX_SetItemHeight
1285 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1287 if (height > MAXWORD)
1288 return -1;
1290 if (!height) height = 1;
1292 if (descr->style & LBS_OWNERDRAWVARIABLE)
1294 if ((index < 0) || (index >= descr->nb_items))
1296 SetLastError(ERROR_INVALID_INDEX);
1297 return LB_ERR;
1299 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1300 set_item_height(descr, index, height);
1301 LISTBOX_UpdateScroll( descr );
1302 if (repaint)
1303 LISTBOX_InvalidateItems( descr, index );
1305 else if (height != descr->item_height)
1307 TRACE("[%p]: new height = %d\n", descr->self, height );
1308 descr->item_height = height;
1309 LISTBOX_UpdatePage( descr );
1310 LISTBOX_UpdateScroll( descr );
1311 if (repaint)
1312 InvalidateRect( descr->self, 0, TRUE );
1314 return LB_OKAY;
1318 /***********************************************************************
1319 * LISTBOX_SetHorizontalPos
1321 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1323 INT diff;
1325 if (pos > descr->horz_extent - descr->width)
1326 pos = descr->horz_extent - descr->width;
1327 if (pos < 0) pos = 0;
1328 if (!(diff = descr->horz_pos - pos)) return;
1329 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1330 descr->horz_pos = pos;
1331 LISTBOX_UpdateScroll( descr );
1332 if (abs(diff) < descr->width)
1334 RECT rect;
1335 /* Invalidate the focused item so it will be repainted correctly */
1336 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1337 InvalidateRect( descr->self, &rect, TRUE );
1338 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1339 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1341 else
1342 InvalidateRect( descr->self, NULL, TRUE );
1346 /***********************************************************************
1347 * LISTBOX_SetHorizontalExtent
1349 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1351 if (descr->style & LBS_MULTICOLUMN)
1352 return LB_OKAY;
1353 if (extent == descr->horz_extent) return LB_OKAY;
1354 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1355 descr->horz_extent = extent;
1356 if (descr->style & WS_HSCROLL) {
1357 SCROLLINFO info;
1358 info.cbSize = sizeof(info);
1359 info.nMin = 0;
1360 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1361 info.fMask = SIF_RANGE;
1362 if (descr->style & LBS_DISABLENOSCROLL)
1363 info.fMask |= SIF_DISABLENOSCROLL;
1364 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1366 if (descr->horz_pos > extent - descr->width)
1367 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1368 return LB_OKAY;
1372 /***********************************************************************
1373 * LISTBOX_SetColumnWidth
1375 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1377 RECT rect;
1379 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1381 GetClientRect(descr->self, &rect);
1382 descr->width = rect.right - rect.left;
1383 descr->height = rect.bottom - rect.top;
1384 descr->column_width = column_width;
1386 LISTBOX_UpdatePage(descr);
1387 LISTBOX_UpdateScroll(descr);
1388 return LB_OKAY;
1392 /***********************************************************************
1393 * LISTBOX_SetFont
1395 * Returns the item height.
1397 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1399 HDC hdc;
1400 HFONT oldFont = 0;
1401 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1402 SIZE sz;
1404 descr->font = font;
1406 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1408 ERR("unable to get DC.\n" );
1409 return 16;
1411 if (font) oldFont = SelectObject( hdc, font );
1412 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1413 if (oldFont) SelectObject( hdc, oldFont );
1414 ReleaseDC( descr->self, hdc );
1416 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1417 if (!IS_OWNERDRAW(descr))
1418 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1419 return sz.cy;
1423 /***********************************************************************
1424 * LISTBOX_MakeItemVisible
1426 * Make sure that a given item is partially or fully visible.
1428 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1430 INT top;
1432 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1434 if (index <= descr->top_item) top = index;
1435 else if (descr->style & LBS_MULTICOLUMN)
1437 INT cols = descr->width;
1438 if (!fully) cols += descr->column_width - 1;
1439 if (cols >= descr->column_width) cols /= descr->column_width;
1440 else cols = 1;
1441 if (index < descr->top_item + (descr->page_size * cols)) return;
1442 top = index - descr->page_size * (cols - 1);
1444 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1446 INT height = fully ? get_item_height(descr, index) : 1;
1447 for (top = index; top > descr->top_item; top--)
1448 if ((height += get_item_height(descr, top - 1)) > descr->height) break;
1450 else
1452 if (index < descr->top_item + descr->page_size) return;
1453 if (!fully && (index == descr->top_item + descr->page_size) &&
1454 (descr->height > (descr->page_size * descr->item_height))) return;
1455 top = index - descr->page_size + 1;
1457 LISTBOX_SetTopItem( descr, top, TRUE );
1460 /***********************************************************************
1461 * LISTBOX_SetCaretIndex
1463 * NOTES
1464 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1467 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1469 BOOL focus_changed = descr->focus_item != index;
1471 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1473 if (descr->style & LBS_NOSEL) return LB_ERR;
1474 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1476 if (focus_changed)
1478 LISTBOX_DrawFocusRect( descr, FALSE );
1479 descr->focus_item = index;
1482 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1484 if (focus_changed)
1485 LISTBOX_DrawFocusRect( descr, TRUE );
1487 return LB_OKAY;
1491 /***********************************************************************
1492 * LISTBOX_SelectItemRange
1494 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1496 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1497 INT last, BOOL on )
1499 INT i;
1501 /* A few sanity checks */
1503 if (descr->style & LBS_NOSEL) return LB_ERR;
1504 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1506 if (!descr->nb_items) return LB_OKAY;
1508 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1509 if (first < 0) first = 0;
1510 if (last < first) return LB_OKAY;
1512 if (on) /* Turn selection on */
1514 for (i = first; i <= last; i++)
1516 if (is_item_selected(descr, i)) continue;
1517 set_item_selected_state(descr, i, TRUE);
1518 LISTBOX_InvalidateItemRect(descr, i);
1521 else /* Turn selection off */
1523 for (i = first; i <= last; i++)
1525 if (!is_item_selected(descr, i)) continue;
1526 set_item_selected_state(descr, i, FALSE);
1527 LISTBOX_InvalidateItemRect(descr, i);
1530 return LB_OKAY;
1533 /***********************************************************************
1534 * LISTBOX_SetSelection
1536 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1537 BOOL on, BOOL send_notify )
1539 TRACE( "cur_sel=%d index=%d notify=%s\n",
1540 descr->selected_item, index, send_notify ? "YES" : "NO" );
1542 if (descr->style & LBS_NOSEL)
1544 descr->selected_item = index;
1545 return LB_ERR;
1547 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1548 if (descr->style & LBS_MULTIPLESEL)
1550 if (index == -1) /* Select all items */
1551 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1552 else /* Only one item */
1553 return LISTBOX_SelectItemRange( descr, index, index, on );
1555 else
1557 INT oldsel = descr->selected_item;
1558 if (index == oldsel) return LB_OKAY;
1559 if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
1560 if (index != -1) set_item_selected_state(descr, index, TRUE);
1561 descr->selected_item = index;
1562 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1563 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1564 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1565 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1566 else
1567 if( descr->lphc ) /* set selection change flag for parent combo */
1568 descr->lphc->wState |= CBF_SELCHANGE;
1570 return LB_OKAY;
1574 /***********************************************************************
1575 * LISTBOX_MoveCaret
1577 * Change the caret position and extend the selection to the new caret.
1579 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1581 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1583 if ((index < 0) || (index >= descr->nb_items))
1584 return;
1586 /* Important, repaint needs to be done in this order if
1587 you want to mimic Windows behavior:
1588 1. Remove the focus and paint the item
1589 2. Remove the selection and paint the item(s)
1590 3. Set the selection and repaint the item(s)
1591 4. Set the focus to 'index' and repaint the item */
1593 /* 1. remove the focus and repaint the item */
1594 LISTBOX_DrawFocusRect( descr, FALSE );
1596 /* 2. then turn off the previous selection */
1597 /* 3. repaint the new selected item */
1598 if (descr->style & LBS_EXTENDEDSEL)
1600 if (descr->anchor_item != -1)
1602 INT first = min( index, descr->anchor_item );
1603 INT last = max( index, descr->anchor_item );
1604 if (first > 0)
1605 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1606 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1607 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1610 else if (!(descr->style & LBS_MULTIPLESEL))
1612 /* Set selection to new caret item */
1613 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1616 /* 4. repaint the new item with the focus */
1617 descr->focus_item = index;
1618 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1619 LISTBOX_DrawFocusRect( descr, TRUE );
1623 /***********************************************************************
1624 * LISTBOX_InsertItem
1626 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1627 LPWSTR str, ULONG_PTR data )
1629 INT oldfocus = descr->focus_item;
1631 if (index == -1) index = descr->nb_items;
1632 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1633 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1635 insert_item_data(descr, index);
1636 descr->nb_items++;
1637 set_item_string(descr, index, str);
1638 set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
1639 set_item_height(descr, index, 0);
1640 set_item_selected_state(descr, index, FALSE);
1642 /* Get item height */
1644 if (descr->style & LBS_OWNERDRAWVARIABLE)
1646 MEASUREITEMSTRUCT mis;
1647 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1649 mis.CtlType = ODT_LISTBOX;
1650 mis.CtlID = id;
1651 mis.itemID = index;
1652 mis.itemData = data;
1653 mis.itemHeight = descr->item_height;
1654 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1655 set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
1656 TRACE("[%p]: measure item %d (%s) = %d\n",
1657 descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
1660 /* Repaint the items */
1662 LISTBOX_UpdateScroll( descr );
1663 LISTBOX_InvalidateItems( descr, index );
1665 /* Move selection and focused item */
1666 /* If listbox was empty, set focus to the first item */
1667 if (descr->nb_items == 1)
1668 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1669 /* single select don't change selection index in win31 */
1670 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1672 descr->selected_item++;
1673 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1675 else
1677 if (index <= descr->selected_item)
1679 descr->selected_item++;
1680 descr->focus_item = oldfocus; /* focus not changed */
1683 return LB_OKAY;
1687 /***********************************************************************
1688 * LISTBOX_InsertString
1690 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1692 LPWSTR new_str = NULL;
1693 LRESULT ret;
1695 if (HAS_STRINGS(descr))
1697 if (!str) str = L"";
1698 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str) + 1) * sizeof(WCHAR) )))
1700 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1701 return LB_ERRSPACE;
1703 lstrcpyW(new_str, str);
1706 if (index == -1) index = descr->nb_items;
1707 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1709 HeapFree( GetProcessHeap(), 0, new_str );
1710 return ret;
1713 TRACE("[%p]: added item %d %s\n",
1714 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1715 return index;
1719 /***********************************************************************
1720 * LISTBOX_DeleteItem
1722 * Delete the content of an item. 'index' must be a valid index.
1724 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1726 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1727 * while Win95 sends it for all items with user data.
1728 * It's probably better to send it too often than not
1729 * often enough, so this is what we do here.
1731 if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
1733 DELETEITEMSTRUCT dis;
1734 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1736 dis.CtlType = ODT_LISTBOX;
1737 dis.CtlID = id;
1738 dis.itemID = index;
1739 dis.hwndItem = descr->self;
1740 dis.itemData = get_item_data(descr, index);
1741 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1743 HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
1747 /***********************************************************************
1748 * LISTBOX_RemoveItem
1750 * Remove an item from the listbox and delete its content.
1752 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1754 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1756 /* We need to invalidate the original rect instead of the updated one. */
1757 LISTBOX_InvalidateItems( descr, index );
1759 if (descr->nb_items == 1)
1761 SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1762 return LB_OKAY;
1764 descr->nb_items--;
1765 LISTBOX_DeleteItem( descr, index );
1766 remove_item_data(descr, index);
1768 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1769 resize_storage(descr, descr->nb_items);
1771 /* Repaint the items */
1773 LISTBOX_UpdateScroll( descr );
1774 /* if we removed the scrollbar, reset the top of the list
1775 (correct for owner-drawn ???) */
1776 if (descr->nb_items == descr->page_size)
1777 LISTBOX_SetTopItem( descr, 0, TRUE );
1779 /* Move selection and focused item */
1780 if (!IS_MULTISELECT(descr))
1782 if (index == descr->selected_item)
1783 descr->selected_item = -1;
1784 else if (index < descr->selected_item)
1786 descr->selected_item--;
1787 if (ISWIN31) /* win 31 do not change the selected item number */
1788 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1792 if (descr->focus_item >= descr->nb_items)
1794 descr->focus_item = descr->nb_items - 1;
1795 if (descr->focus_item < 0) descr->focus_item = 0;
1797 return LB_OKAY;
1801 /***********************************************************************
1802 * LISTBOX_ResetContent
1804 static void LISTBOX_ResetContent( LB_DESCR *descr )
1806 INT i;
1808 if (!(descr->style & LBS_NODATA))
1809 for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
1810 HeapFree( GetProcessHeap(), 0, descr->u.items );
1811 descr->nb_items = 0;
1812 descr->top_item = 0;
1813 descr->selected_item = -1;
1814 descr->focus_item = 0;
1815 descr->anchor_item = -1;
1816 descr->items_size = 0;
1817 descr->u.items = NULL;
1821 /***********************************************************************
1822 * LISTBOX_SetCount
1824 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1826 UINT orig_num = descr->nb_items;
1828 if (!(descr->style & LBS_NODATA)) return LB_ERR;
1830 if (!resize_storage(descr, count))
1831 return LB_ERRSPACE;
1832 descr->nb_items = count;
1833 if (descr->style & LBS_NOREDRAW)
1834 descr->style |= LBS_DISPLAYCHANGED;
1836 if (count)
1838 LISTBOX_UpdateScroll(descr);
1839 if (count < orig_num)
1841 descr->anchor_item = min(descr->anchor_item, count - 1);
1842 if (descr->selected_item >= count)
1843 descr->selected_item = -1;
1845 /* If we removed the scrollbar, reset the top of the list */
1846 if (count <= descr->page_size && orig_num > descr->page_size)
1847 LISTBOX_SetTopItem(descr, 0, TRUE);
1849 descr->focus_item = min(descr->focus_item, count - 1);
1852 /* If it was empty before growing, set focus to the first item */
1853 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1855 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1857 InvalidateRect( descr->self, NULL, TRUE );
1858 return LB_OKAY;
1862 /***********************************************************************
1863 * LISTBOX_Directory
1865 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1866 LPCWSTR filespec, BOOL long_names )
1868 HANDLE handle;
1869 LRESULT ret = LB_OKAY;
1870 WIN32_FIND_DATAW entry;
1871 int pos;
1872 LRESULT maxinsert = LB_ERR;
1874 /* don't scan directory if we just want drives exclusively */
1875 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1876 /* scan directory */
1877 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1879 int le = GetLastError();
1880 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1882 else
1886 WCHAR buffer[270];
1887 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1889 if (!(attrib & DDL_DIRECTORY) ||
1890 !lstrcmpW( entry.cFileName, L"." )) continue;
1891 buffer[0] = '[';
1892 if (!long_names && entry.cAlternateFileName[0])
1893 lstrcpyW( buffer + 1, entry.cAlternateFileName );
1894 else
1895 lstrcpyW( buffer + 1, entry.cFileName );
1896 lstrcatW(buffer, L"]");
1898 else /* not a directory */
1900 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1901 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1903 if ((attrib & DDL_EXCLUSIVE) &&
1904 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1905 continue;
1906 #undef ATTRIBS
1907 if (!long_names && entry.cAlternateFileName[0])
1908 lstrcpyW( buffer, entry.cAlternateFileName );
1909 else
1910 lstrcpyW( buffer, entry.cFileName );
1912 if (!long_names) CharLowerW( buffer );
1913 pos = LISTBOX_FindFileStrPos( descr, buffer );
1914 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1915 break;
1916 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1917 } while (FindNextFileW( handle, &entry ));
1918 FindClose( handle );
1921 if (ret >= 0)
1923 ret = maxinsert;
1925 /* scan drives */
1926 if (attrib & DDL_DRIVES)
1928 WCHAR buffer[] = L"[-a-]";
1929 WCHAR root[] = L"A:\\";
1930 int drive;
1931 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1933 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1934 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1935 break;
1939 return ret;
1943 /***********************************************************************
1944 * LISTBOX_HandleVScroll
1946 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1948 SCROLLINFO info;
1950 if (descr->style & LBS_MULTICOLUMN) return 0;
1951 switch(scrollReq)
1953 case SB_LINEUP:
1954 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1955 break;
1956 case SB_LINEDOWN:
1957 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1958 break;
1959 case SB_PAGEUP:
1960 LISTBOX_SetTopItem( descr, descr->top_item -
1961 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1962 break;
1963 case SB_PAGEDOWN:
1964 LISTBOX_SetTopItem( descr, descr->top_item +
1965 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1966 break;
1967 case SB_THUMBPOSITION:
1968 LISTBOX_SetTopItem( descr, pos, TRUE );
1969 break;
1970 case SB_THUMBTRACK:
1971 info.cbSize = sizeof(info);
1972 info.fMask = SIF_TRACKPOS;
1973 GetScrollInfo( descr->self, SB_VERT, &info );
1974 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1975 break;
1976 case SB_TOP:
1977 LISTBOX_SetTopItem( descr, 0, TRUE );
1978 break;
1979 case SB_BOTTOM:
1980 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1981 break;
1983 return 0;
1987 /***********************************************************************
1988 * LISTBOX_HandleHScroll
1990 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1992 SCROLLINFO info;
1993 INT page;
1995 if (descr->style & LBS_MULTICOLUMN)
1997 switch(scrollReq)
1999 case SB_LINELEFT:
2000 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
2001 TRUE );
2002 break;
2003 case SB_LINERIGHT:
2004 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
2005 TRUE );
2006 break;
2007 case SB_PAGELEFT:
2008 page = descr->width / descr->column_width;
2009 if (page < 1) page = 1;
2010 LISTBOX_SetTopItem( descr,
2011 descr->top_item - page * descr->page_size, TRUE );
2012 break;
2013 case SB_PAGERIGHT:
2014 page = descr->width / descr->column_width;
2015 if (page < 1) page = 1;
2016 LISTBOX_SetTopItem( descr,
2017 descr->top_item + page * descr->page_size, TRUE );
2018 break;
2019 case SB_THUMBPOSITION:
2020 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
2021 break;
2022 case SB_THUMBTRACK:
2023 info.cbSize = sizeof(info);
2024 info.fMask = SIF_TRACKPOS;
2025 GetScrollInfo( descr->self, SB_VERT, &info );
2026 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
2027 TRUE );
2028 break;
2029 case SB_LEFT:
2030 LISTBOX_SetTopItem( descr, 0, TRUE );
2031 break;
2032 case SB_RIGHT:
2033 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2034 break;
2037 else if (descr->horz_extent)
2039 switch(scrollReq)
2041 case SB_LINELEFT:
2042 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2043 break;
2044 case SB_LINERIGHT:
2045 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2046 break;
2047 case SB_PAGELEFT:
2048 LISTBOX_SetHorizontalPos( descr,
2049 descr->horz_pos - descr->width );
2050 break;
2051 case SB_PAGERIGHT:
2052 LISTBOX_SetHorizontalPos( descr,
2053 descr->horz_pos + descr->width );
2054 break;
2055 case SB_THUMBPOSITION:
2056 LISTBOX_SetHorizontalPos( descr, pos );
2057 break;
2058 case SB_THUMBTRACK:
2059 info.cbSize = sizeof(info);
2060 info.fMask = SIF_TRACKPOS;
2061 GetScrollInfo( descr->self, SB_HORZ, &info );
2062 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2063 break;
2064 case SB_LEFT:
2065 LISTBOX_SetHorizontalPos( descr, 0 );
2066 break;
2067 case SB_RIGHT:
2068 LISTBOX_SetHorizontalPos( descr,
2069 descr->horz_extent - descr->width );
2070 break;
2073 return 0;
2076 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2078 INT pulScrollLines = 3;
2080 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2082 /* if scrolling changes direction, ignore left overs */
2083 if ((delta < 0 && descr->wheel_remain < 0) ||
2084 (delta > 0 && descr->wheel_remain > 0))
2085 descr->wheel_remain += delta;
2086 else
2087 descr->wheel_remain = delta;
2089 if (descr->wheel_remain && pulScrollLines)
2091 int cLineScroll;
2092 if (descr->style & LBS_MULTICOLUMN)
2094 pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
2095 pulScrollLines = max(1, pulScrollLines);
2096 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2097 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2098 cLineScroll *= descr->page_size;
2100 else
2102 pulScrollLines = min(descr->page_size, pulScrollLines);
2103 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2104 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2106 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2108 return 0;
2111 /***********************************************************************
2112 * LISTBOX_HandleLButtonDown
2114 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2116 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2118 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2119 descr->self, x, y, index, descr->focus_item);
2121 if (!descr->caret_on && (descr->in_focus)) return 0;
2123 if (!descr->in_focus)
2125 if( !descr->lphc ) SetFocus( descr->self );
2126 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2129 if (index == -1) return 0;
2131 if (!descr->lphc)
2133 if (descr->style & LBS_NOTIFY )
2134 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2135 MAKELPARAM( x, y ) );
2138 descr->captured = TRUE;
2139 SetCapture( descr->self );
2141 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2143 /* we should perhaps make sure that all items are deselected
2144 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2145 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2146 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2149 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2150 if (keys & MK_CONTROL)
2152 LISTBOX_SetCaretIndex( descr, index, FALSE );
2153 LISTBOX_SetSelection( descr, index,
2154 !is_item_selected(descr, index),
2155 (descr->style & LBS_NOTIFY) != 0);
2157 else
2159 LISTBOX_MoveCaret( descr, index, FALSE );
2161 if (descr->style & LBS_EXTENDEDSEL)
2163 LISTBOX_SetSelection( descr, index,
2164 is_item_selected(descr, index),
2165 (descr->style & LBS_NOTIFY) != 0 );
2167 else
2169 LISTBOX_SetSelection( descr, index,
2170 !is_item_selected(descr, index),
2171 (descr->style & LBS_NOTIFY) != 0 );
2175 else
2177 descr->anchor_item = index;
2178 LISTBOX_MoveCaret( descr, index, FALSE );
2179 LISTBOX_SetSelection( descr, index,
2180 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2183 if (!descr->lphc)
2185 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2187 POINT pt;
2189 pt.x = x;
2190 pt.y = y;
2192 if (DragDetect( descr->self, pt ))
2193 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2196 return 0;
2200 /*************************************************************************
2201 * LISTBOX_HandleLButtonDownCombo [Internal]
2203 * Process LButtonDown message for the ComboListBox
2205 * PARAMS
2206 * pWnd [I] The windows internal structure
2207 * pDescr [I] The ListBox internal structure
2208 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2209 * x [I] X Mouse Coordinate
2210 * y [I] Y Mouse Coordinate
2212 * RETURNS
2213 * 0 since we are processing the WM_LBUTTONDOWN Message
2215 * NOTES
2216 * This function is only to be used when a ListBox is a ComboListBox
2219 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2221 RECT clientRect, screenRect;
2222 POINT mousePos;
2224 mousePos.x = x;
2225 mousePos.y = y;
2227 GetClientRect(descr->self, &clientRect);
2229 if(PtInRect(&clientRect, mousePos))
2231 /* MousePos is in client, resume normal processing */
2232 if (msg == WM_LBUTTONDOWN)
2234 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2235 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2237 else if (descr->style & LBS_NOTIFY)
2238 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2240 else
2242 POINT screenMousePos;
2243 HWND hWndOldCapture;
2245 /* Check the Non-Client Area */
2246 screenMousePos = mousePos;
2247 hWndOldCapture = GetCapture();
2248 ReleaseCapture();
2249 GetWindowRect(descr->self, &screenRect);
2250 ClientToScreen(descr->self, &screenMousePos);
2252 if(!PtInRect(&screenRect, screenMousePos))
2254 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2255 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2256 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2258 else
2260 /* Check to see the NC is a scrollbar */
2261 INT nHitTestType=0;
2262 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2263 /* Check Vertical scroll bar */
2264 if (style & WS_VSCROLL)
2266 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2267 if (PtInRect( &clientRect, mousePos ))
2268 nHitTestType = HTVSCROLL;
2270 /* Check horizontal scroll bar */
2271 if (style & WS_HSCROLL)
2273 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2274 if (PtInRect( &clientRect, mousePos ))
2275 nHitTestType = HTHSCROLL;
2277 /* Windows sends this message when a scrollbar is clicked
2280 if(nHitTestType != 0)
2282 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2283 MAKELONG(screenMousePos.x, screenMousePos.y));
2285 /* Resume the Capture after scrolling is complete
2287 if(hWndOldCapture != 0)
2288 SetCapture(hWndOldCapture);
2291 return 0;
2294 /***********************************************************************
2295 * LISTBOX_HandleLButtonUp
2297 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2299 if (LISTBOX_Timer != LB_TIMER_NONE)
2300 KillSystemTimer( descr->self, LB_TIMER_ID );
2301 LISTBOX_Timer = LB_TIMER_NONE;
2302 if (descr->captured)
2304 descr->captured = FALSE;
2305 if (GetCapture() == descr->self) ReleaseCapture();
2306 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2307 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2309 return 0;
2313 /***********************************************************************
2314 * LISTBOX_HandleTimer
2316 * Handle scrolling upon a timer event.
2317 * Return TRUE if scrolling should continue.
2319 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2321 switch(dir)
2323 case LB_TIMER_UP:
2324 if (descr->top_item) index = descr->top_item - 1;
2325 else index = 0;
2326 break;
2327 case LB_TIMER_LEFT:
2328 if (descr->top_item) index -= descr->page_size;
2329 break;
2330 case LB_TIMER_DOWN:
2331 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2332 if (index == descr->focus_item) index++;
2333 if (index >= descr->nb_items) index = descr->nb_items - 1;
2334 break;
2335 case LB_TIMER_RIGHT:
2336 if (index + descr->page_size < descr->nb_items)
2337 index += descr->page_size;
2338 break;
2339 case LB_TIMER_NONE:
2340 break;
2342 if (index == descr->focus_item) return FALSE;
2343 LISTBOX_MoveCaret( descr, index, FALSE );
2344 return TRUE;
2348 /***********************************************************************
2349 * LISTBOX_HandleSystemTimer
2351 * WM_SYSTIMER handler.
2353 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2355 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2357 KillSystemTimer( descr->self, LB_TIMER_ID );
2358 LISTBOX_Timer = LB_TIMER_NONE;
2360 return 0;
2364 /***********************************************************************
2365 * LISTBOX_HandleMouseMove
2367 * WM_MOUSEMOVE handler.
2369 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2370 INT x, INT y )
2372 INT index;
2373 TIMER_DIRECTION dir = LB_TIMER_NONE;
2375 if (!descr->captured) return;
2377 if (descr->style & LBS_MULTICOLUMN)
2379 if (y < 0) y = 0;
2380 else if (y >= descr->item_height * descr->page_size)
2381 y = descr->item_height * descr->page_size - 1;
2383 if (x < 0)
2385 dir = LB_TIMER_LEFT;
2386 x = 0;
2388 else if (x >= descr->width)
2390 dir = LB_TIMER_RIGHT;
2391 x = descr->width - 1;
2394 else
2396 if (y < 0) dir = LB_TIMER_UP; /* above */
2397 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2400 index = LISTBOX_GetItemFromPoint( descr, x, y );
2401 if (index == -1) index = descr->focus_item;
2402 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2404 /* Start/stop the system timer */
2406 if (dir != LB_TIMER_NONE)
2407 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2408 else if (LISTBOX_Timer != LB_TIMER_NONE)
2409 KillSystemTimer( descr->self, LB_TIMER_ID );
2410 LISTBOX_Timer = dir;
2414 /***********************************************************************
2415 * LISTBOX_HandleKeyDown
2417 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2419 INT caret = -1;
2420 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2421 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2422 bForceSelection = FALSE; /* only for single select list */
2424 if (descr->style & LBS_WANTKEYBOARDINPUT)
2426 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2427 MAKEWPARAM(LOWORD(key), descr->focus_item),
2428 (LPARAM)descr->self );
2429 if (caret == -2) return 0;
2431 if (caret == -1) switch(key)
2433 case VK_LEFT:
2434 if (descr->style & LBS_MULTICOLUMN)
2436 bForceSelection = FALSE;
2437 if (descr->focus_item >= descr->page_size)
2438 caret = descr->focus_item - descr->page_size;
2439 break;
2441 /* fall through */
2442 case VK_UP:
2443 caret = descr->focus_item - 1;
2444 if (caret < 0) caret = 0;
2445 break;
2446 case VK_RIGHT:
2447 if (descr->style & LBS_MULTICOLUMN)
2449 bForceSelection = FALSE;
2450 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2451 break;
2453 /* fall through */
2454 case VK_DOWN:
2455 caret = descr->focus_item + 1;
2456 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2457 break;
2459 case VK_PRIOR:
2460 if (descr->style & LBS_MULTICOLUMN)
2462 INT page = descr->width / descr->column_width;
2463 if (page < 1) page = 1;
2464 caret = descr->focus_item - (page * descr->page_size) + 1;
2466 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2467 if (caret < 0) caret = 0;
2468 break;
2469 case VK_NEXT:
2470 if (descr->style & LBS_MULTICOLUMN)
2472 INT page = descr->width / descr->column_width;
2473 if (page < 1) page = 1;
2474 caret = descr->focus_item + (page * descr->page_size) - 1;
2476 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2477 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2478 break;
2479 case VK_HOME:
2480 caret = 0;
2481 break;
2482 case VK_END:
2483 caret = descr->nb_items - 1;
2484 break;
2485 case VK_SPACE:
2486 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2487 else if (descr->style & LBS_MULTIPLESEL)
2489 LISTBOX_SetSelection( descr, descr->focus_item,
2490 !is_item_selected(descr, descr->focus_item),
2491 (descr->style & LBS_NOTIFY) != 0 );
2493 break;
2494 default:
2495 bForceSelection = FALSE;
2497 if (bForceSelection) /* focused item is used instead of key */
2498 caret = descr->focus_item;
2499 if (caret >= 0)
2501 if (((descr->style & LBS_EXTENDEDSEL) &&
2502 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2503 !IS_MULTISELECT(descr))
2504 descr->anchor_item = caret;
2505 LISTBOX_MoveCaret( descr, caret, TRUE );
2507 if (descr->style & LBS_MULTIPLESEL)
2508 descr->selected_item = caret;
2509 else
2510 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2511 if (descr->style & LBS_NOTIFY)
2513 if (descr->lphc && IsWindowVisible( descr->self ))
2515 /* make sure that combo parent doesn't hide us */
2516 descr->lphc->wState |= CBF_NOROLLUP;
2518 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2521 return 0;
2525 /***********************************************************************
2526 * LISTBOX_HandleChar
2528 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2530 INT caret = -1;
2531 WCHAR str[2];
2533 str[0] = charW;
2534 str[1] = '\0';
2536 if (descr->style & LBS_WANTKEYBOARDINPUT)
2538 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2539 MAKEWPARAM(charW, descr->focus_item),
2540 (LPARAM)descr->self );
2541 if (caret == -2) return 0;
2543 if (caret == -1)
2544 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2545 if (caret != -1)
2547 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2548 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2549 LISTBOX_MoveCaret( descr, caret, TRUE );
2550 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2551 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2553 return 0;
2557 /***********************************************************************
2558 * LISTBOX_Create
2560 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2562 LB_DESCR *descr;
2563 MEASUREITEMSTRUCT mis;
2564 RECT rect;
2566 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2567 return FALSE;
2569 GetClientRect( hwnd, &rect );
2570 descr->self = hwnd;
2571 descr->owner = GetParent( descr->self );
2572 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2573 descr->width = rect.right - rect.left;
2574 descr->height = rect.bottom - rect.top;
2575 descr->u.items = NULL;
2576 descr->items_size = 0;
2577 descr->nb_items = 0;
2578 descr->top_item = 0;
2579 descr->selected_item = -1;
2580 descr->focus_item = 0;
2581 descr->anchor_item = -1;
2582 descr->item_height = 1;
2583 descr->page_size = 1;
2584 descr->column_width = 150;
2585 descr->horz_extent = 0;
2586 descr->horz_pos = 0;
2587 descr->nb_tabs = 0;
2588 descr->tabs = NULL;
2589 descr->wheel_remain = 0;
2590 descr->caret_on = !lphc;
2591 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2592 descr->in_focus = FALSE;
2593 descr->captured = FALSE;
2594 descr->font = 0;
2595 descr->locale = GetUserDefaultLCID();
2596 descr->lphc = lphc;
2598 if( lphc )
2600 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2601 descr->owner = lphc->self;
2604 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2606 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2608 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2609 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2610 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2611 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2612 descr->style &= ~LBS_NODATA;
2613 descr->item_height = LISTBOX_SetFont( descr, 0 );
2615 if (descr->style & LBS_OWNERDRAWFIXED)
2617 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2619 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2621 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2622 descr->item_height = lphc->fixedOwnerDrawHeight;
2624 else
2626 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2627 mis.CtlType = ODT_LISTBOX;
2628 mis.CtlID = id;
2629 mis.itemID = -1;
2630 mis.itemWidth = 0;
2631 mis.itemData = 0;
2632 mis.itemHeight = descr->item_height;
2633 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2634 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2638 OpenThemeData( descr->self, WC_LISTBOXW );
2640 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2641 return TRUE;
2645 /***********************************************************************
2646 * LISTBOX_Destroy
2648 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2650 HTHEME theme = GetWindowTheme( descr->self );
2651 CloseThemeData( theme );
2652 LISTBOX_ResetContent( descr );
2653 SetWindowLongPtrW( descr->self, 0, 0 );
2654 HeapFree( GetProcessHeap(), 0, descr );
2655 return TRUE;
2659 /***********************************************************************
2660 * ListBoxWndProc_common
2662 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2664 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2665 HEADCOMBO *lphc = NULL;
2666 HTHEME theme;
2667 LRESULT ret;
2669 if (!descr)
2671 if (!IsWindow(hwnd)) return 0;
2673 if (msg == WM_CREATE)
2675 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2676 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2677 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2678 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2679 return 0;
2681 /* Ignore all other messages before we get a WM_CREATE */
2682 return DefWindowProcW( hwnd, msg, wParam, lParam );
2684 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2686 TRACE("[%p]: msg %#x, wp %Ix, lp %Ix\n", descr->self, msg, wParam, lParam );
2688 switch(msg)
2690 case LB_RESETCONTENT:
2691 LISTBOX_ResetContent( descr );
2692 LISTBOX_UpdateScroll( descr );
2693 InvalidateRect( descr->self, NULL, TRUE );
2694 return 0;
2696 case LB_ADDSTRING:
2698 const WCHAR *textW = (const WCHAR *)lParam;
2699 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2700 return LISTBOX_InsertString( descr, index, textW );
2703 case LB_INSERTSTRING:
2704 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2706 case LB_ADDFILE:
2708 const WCHAR *textW = (const WCHAR *)lParam;
2709 INT index = LISTBOX_FindFileStrPos( descr, textW );
2710 return LISTBOX_InsertString( descr, index, textW );
2713 case LB_DELETESTRING:
2714 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2715 return descr->nb_items;
2716 else
2718 SetLastError(ERROR_INVALID_INDEX);
2719 return LB_ERR;
2722 case LB_GETITEMDATA:
2723 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2725 SetLastError(ERROR_INVALID_INDEX);
2726 return LB_ERR;
2728 return get_item_data(descr, wParam);
2730 case LB_SETITEMDATA:
2731 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2733 SetLastError(ERROR_INVALID_INDEX);
2734 return LB_ERR;
2736 set_item_data(descr, wParam, lParam);
2737 /* undocumented: returns TRUE, not LB_OKAY (0) */
2738 return TRUE;
2740 case LB_GETCOUNT:
2741 return descr->nb_items;
2743 case LB_GETTEXT:
2744 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2746 case LB_GETTEXTLEN:
2747 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2749 SetLastError(ERROR_INVALID_INDEX);
2750 return LB_ERR;
2752 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2753 return lstrlenW(get_item_string(descr, wParam));
2755 case LB_GETCURSEL:
2756 if (descr->nb_items == 0)
2757 return LB_ERR;
2758 if (!IS_MULTISELECT(descr))
2759 return descr->selected_item;
2760 if (descr->selected_item != -1)
2761 return descr->selected_item;
2762 return descr->focus_item;
2763 /* otherwise, if the user tries to move the selection with the */
2764 /* arrow keys, we will give the application something to choke on */
2765 case LB_GETTOPINDEX:
2766 return descr->top_item;
2768 case LB_GETITEMHEIGHT:
2769 return LISTBOX_GetItemHeight( descr, wParam );
2771 case LB_SETITEMHEIGHT:
2772 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2774 case LB_ITEMFROMPOINT:
2776 POINT pt;
2777 RECT rect;
2778 int index;
2779 BOOL hit = TRUE;
2781 /* The hiword of the return value is not a client area
2782 hittest as suggested by MSDN, but rather a hittest on
2783 the returned listbox item. */
2785 if(descr->nb_items == 0)
2786 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2788 pt.x = (short)LOWORD(lParam);
2789 pt.y = (short)HIWORD(lParam);
2791 SetRect(&rect, 0, 0, descr->width, descr->height);
2793 if(!PtInRect(&rect, pt))
2795 pt.x = min(pt.x, rect.right - 1);
2796 pt.x = max(pt.x, 0);
2797 pt.y = min(pt.y, rect.bottom - 1);
2798 pt.y = max(pt.y, 0);
2799 hit = FALSE;
2802 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2804 if(index == -1)
2806 index = descr->nb_items - 1;
2807 hit = FALSE;
2809 return MAKELONG(index, hit ? 0 : 1);
2812 case LB_SETCARETINDEX:
2813 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2814 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2815 return LB_ERR;
2816 else if (ISWIN31)
2817 return wParam;
2818 else
2819 return LB_OKAY;
2821 case LB_GETCARETINDEX:
2822 return descr->focus_item;
2824 case LB_SETTOPINDEX:
2825 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2827 case LB_SETCOLUMNWIDTH:
2828 return LISTBOX_SetColumnWidth( descr, wParam );
2830 case LB_GETITEMRECT:
2831 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2833 case LB_FINDSTRING:
2834 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2836 case LB_FINDSTRINGEXACT:
2837 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2839 case LB_SELECTSTRING:
2841 const WCHAR *textW = (const WCHAR *)lParam;
2842 INT index;
2844 if (HAS_STRINGS(descr))
2845 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2847 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2848 if (index != LB_ERR)
2850 LISTBOX_MoveCaret( descr, index, TRUE );
2851 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2853 return index;
2856 case LB_GETSEL:
2857 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2858 return LB_ERR;
2859 return is_item_selected(descr, wParam);
2861 case LB_SETSEL:
2862 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2863 if (ret != LB_ERR && wParam)
2865 descr->anchor_item = lParam;
2866 if (lParam != -1)
2867 LISTBOX_SetCaretIndex( descr, lParam, TRUE );
2869 return ret;
2871 case LB_SETCURSEL:
2872 if (IS_MULTISELECT(descr)) return LB_ERR;
2873 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2874 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2875 if (ret != LB_ERR) ret = descr->selected_item;
2876 return ret;
2878 case LB_GETSELCOUNT:
2879 return LISTBOX_GetSelCount( descr );
2881 case LB_GETSELITEMS:
2882 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2884 case LB_SELITEMRANGE:
2885 if (LOWORD(lParam) <= HIWORD(lParam))
2886 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2887 HIWORD(lParam), wParam );
2888 else
2889 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2890 LOWORD(lParam), wParam );
2892 case LB_SELITEMRANGEEX:
2893 if ((INT)lParam >= (INT)wParam)
2894 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2895 else
2896 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2898 case LB_GETHORIZONTALEXTENT:
2899 return descr->horz_extent;
2901 case LB_SETHORIZONTALEXTENT:
2902 return LISTBOX_SetHorizontalExtent( descr, wParam );
2904 case LB_GETANCHORINDEX:
2905 return descr->anchor_item;
2907 case LB_SETANCHORINDEX:
2908 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2910 SetLastError(ERROR_INVALID_INDEX);
2911 return LB_ERR;
2913 descr->anchor_item = (INT)wParam;
2914 return LB_OKAY;
2916 case LB_DIR:
2917 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2919 case LB_GETLOCALE:
2920 return descr->locale;
2922 case LB_SETLOCALE:
2924 LCID ret;
2925 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2926 return LB_ERR;
2927 ret = descr->locale;
2928 descr->locale = (LCID)wParam;
2929 return ret;
2932 case LB_INITSTORAGE:
2933 return LISTBOX_InitStorage( descr, wParam );
2935 case LB_SETCOUNT:
2936 return LISTBOX_SetCount( descr, (INT)wParam );
2938 case LB_SETTABSTOPS:
2939 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2941 case LB_CARETON:
2942 if (descr->caret_on)
2943 return LB_OKAY;
2944 descr->caret_on = TRUE;
2945 if ((descr->focus_item != -1) && (descr->in_focus))
2946 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2947 return LB_OKAY;
2949 case LB_CARETOFF:
2950 if (!descr->caret_on)
2951 return LB_OKAY;
2952 descr->caret_on = FALSE;
2953 if ((descr->focus_item != -1) && (descr->in_focus))
2954 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2955 return LB_OKAY;
2957 case LB_GETLISTBOXINFO:
2958 return descr->page_size;
2960 case WM_DESTROY:
2961 return LISTBOX_Destroy( descr );
2963 case WM_ENABLE:
2964 InvalidateRect( descr->self, NULL, TRUE );
2965 return 0;
2967 case WM_SETREDRAW:
2968 LISTBOX_SetRedraw( descr, wParam != 0 );
2969 return 0;
2971 case WM_GETDLGCODE:
2972 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2974 case WM_PRINTCLIENT:
2975 case WM_PAINT:
2977 PAINTSTRUCT ps;
2978 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2979 ret = LISTBOX_Paint( descr, hdc );
2980 if( !wParam ) EndPaint( descr->self, &ps );
2982 return ret;
2984 case WM_NCPAINT:
2985 return LISTBOX_NCPaint( descr, (HRGN)wParam );
2987 case WM_SIZE:
2988 LISTBOX_UpdateSize( descr );
2989 return 0;
2990 case WM_GETFONT:
2991 return (LRESULT)descr->font;
2992 case WM_SETFONT:
2993 LISTBOX_SetFont( descr, (HFONT)wParam );
2994 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2995 return 0;
2996 case WM_SETFOCUS:
2997 descr->in_focus = TRUE;
2998 descr->caret_on = TRUE;
2999 if (descr->focus_item != -1)
3000 LISTBOX_DrawFocusRect( descr, TRUE );
3001 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3002 return 0;
3003 case WM_KILLFOCUS:
3004 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3005 descr->in_focus = FALSE;
3006 descr->wheel_remain = 0;
3007 if ((descr->focus_item != -1) && descr->caret_on)
3008 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3009 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3010 return 0;
3011 case WM_HSCROLL:
3012 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3013 case WM_VSCROLL:
3014 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3015 case WM_MOUSEWHEEL:
3016 if (wParam & (MK_SHIFT | MK_CONTROL))
3017 return DefWindowProcW( descr->self, msg, wParam, lParam );
3018 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3019 case WM_LBUTTONDOWN:
3020 if (lphc)
3021 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3022 (INT16)LOWORD(lParam),
3023 (INT16)HIWORD(lParam) );
3024 return LISTBOX_HandleLButtonDown( descr, wParam,
3025 (INT16)LOWORD(lParam),
3026 (INT16)HIWORD(lParam) );
3027 case WM_LBUTTONDBLCLK:
3028 if (lphc)
3029 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3030 (INT16)LOWORD(lParam),
3031 (INT16)HIWORD(lParam) );
3032 if (descr->style & LBS_NOTIFY)
3033 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3034 return 0;
3035 case WM_MOUSEMOVE:
3036 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3038 BOOL captured = descr->captured;
3039 POINT mousePos;
3040 RECT clientRect;
3042 mousePos.x = (INT16)LOWORD(lParam);
3043 mousePos.y = (INT16)HIWORD(lParam);
3046 * If we are in a dropdown combobox, we simulate that
3047 * the mouse is captured to show the tracking of the item.
3049 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3050 descr->captured = TRUE;
3052 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3054 descr->captured = captured;
3056 else if (GetCapture() == descr->self)
3058 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3059 (INT16)HIWORD(lParam) );
3061 return 0;
3062 case WM_LBUTTONUP:
3063 if (lphc)
3065 POINT mousePos;
3066 RECT clientRect;
3069 * If the mouse button "up" is not in the listbox,
3070 * we make sure there is no selection by re-selecting the
3071 * item that was selected when the listbox was made visible.
3073 mousePos.x = (INT16)LOWORD(lParam);
3074 mousePos.y = (INT16)HIWORD(lParam);
3076 GetClientRect(descr->self, &clientRect);
3079 * When the user clicks outside the combobox and the focus
3080 * is lost, the owning combobox will send a fake buttonup with
3081 * 0xFFFFFFF as the mouse location, we must also revert the
3082 * selection to the original selection.
3084 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3085 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3087 return LISTBOX_HandleLButtonUp( descr );
3088 case WM_KEYDOWN:
3089 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3091 /* for some reason Windows makes it possible to
3092 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3094 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3095 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3096 && (wParam == VK_DOWN || wParam == VK_UP)) )
3098 COMBO_FlipListbox( lphc, FALSE, FALSE );
3099 return 0;
3102 return LISTBOX_HandleKeyDown( descr, wParam );
3103 case WM_CHAR:
3104 return LISTBOX_HandleChar( descr, wParam );
3106 case WM_SYSTIMER:
3107 return LISTBOX_HandleSystemTimer( descr );
3108 case WM_ERASEBKGND:
3109 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3111 RECT rect;
3112 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3113 wParam, (LPARAM)descr->self );
3114 TRACE("hbrush = %p\n", hbrush);
3115 if(!hbrush)
3116 hbrush = GetSysColorBrush(COLOR_WINDOW);
3117 if(hbrush)
3119 GetClientRect(descr->self, &rect);
3120 FillRect((HDC)wParam, &rect, hbrush);
3123 return 1;
3124 case WM_DROPFILES:
3125 if( lphc ) return 0;
3126 return SendMessageW( descr->owner, msg, wParam, lParam );
3128 case WM_NCDESTROY:
3129 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3130 lphc->hWndLBox = 0;
3131 break;
3133 case WM_NCACTIVATE:
3134 if (lphc) return 0;
3135 break;
3137 case WM_THEMECHANGED:
3138 theme = GetWindowTheme( hwnd );
3139 CloseThemeData( theme );
3140 OpenThemeData( hwnd, WC_LISTBOXW );
3141 InvalidateRect( hwnd, NULL, TRUE );
3142 break;
3144 default:
3145 if ((msg >= WM_USER) && (msg < 0xc000))
3146 WARN("[%p]: unknown msg %04x, wp %Ix, lp %Ix\n", hwnd, msg, wParam, lParam );
3149 return DefWindowProcW( hwnd, msg, wParam, lParam );
3152 void LISTBOX_Register(void)
3154 WNDCLASSW wndClass;
3156 memset(&wndClass, 0, sizeof(wndClass));
3157 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3158 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3159 wndClass.cbClsExtra = 0;
3160 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3161 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3162 wndClass.hbrBackground = NULL;
3163 wndClass.lpszClassName = WC_LISTBOXW;
3164 RegisterClassW(&wndClass);
3167 void COMBOLBOX_Register(void)
3169 WNDCLASSW wndClass;
3171 memset(&wndClass, 0, sizeof(wndClass));
3172 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
3173 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3174 wndClass.cbClsExtra = 0;
3175 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3176 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3177 wndClass.hbrBackground = NULL;
3178 wndClass.lpszClassName = L"ComboLBox";
3179 RegisterClassW(&wndClass);