imagehlp: Use the IMAGE_FIRST_SECTION helper macro.
[wine.git] / dlls / comctl32 / listbox.c
blobf2f42c1d533cc0f9262d388b0ffbe3b178cc2600
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"
37 #include "comctl32.h"
39 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
41 /* Items array granularity (must be power of 2) */
42 #define LB_ARRAY_GRANULARITY 16
44 /* Scrolling timeout in ms */
45 #define LB_SCROLL_TIMEOUT 50
47 /* Listbox system timer id */
48 #define LB_TIMER_ID 2
50 /* flag listbox changed while setredraw false - internal style */
51 #define LBS_DISPLAYCHANGED 0x80000000
53 /* Item structure */
54 typedef struct
56 LPWSTR str; /* Item text */
57 BOOL selected; /* Is item selected? */
58 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
59 ULONG_PTR data; /* User data */
60 } LB_ITEMDATA;
62 /* Listbox structure */
63 typedef struct
65 HWND self; /* Our own window handle */
66 HWND owner; /* Owner window to send notifications to */
67 UINT style; /* Window style */
68 INT width; /* Window width */
69 INT height; /* Window height */
70 union
72 LB_ITEMDATA *items; /* Array of items */
73 BYTE *nodata_items; /* For multi-selection LBS_NODATA */
74 } u;
75 INT nb_items; /* Number of items */
76 UINT items_size; /* Total number of allocated items in the array */
77 INT top_item; /* Top visible item */
78 INT selected_item; /* Selected item */
79 INT focus_item; /* Item that has the focus */
80 INT anchor_item; /* Anchor item for extended selection */
81 INT item_height; /* Default item height */
82 INT page_size; /* Items per listbox page */
83 INT column_width; /* Column width for multi-column listboxes */
84 INT horz_extent; /* Horizontal extent */
85 INT horz_pos; /* Horizontal position */
86 INT nb_tabs; /* Number of tabs in array */
87 INT *tabs; /* Array of tabs */
88 INT avg_char_width; /* Average width of characters */
89 INT wheel_remain; /* Left over scroll amount */
90 BOOL caret_on; /* Is caret on? */
91 BOOL captured; /* Is mouse captured? */
92 BOOL in_focus;
93 HFONT font; /* Current font */
94 LCID locale; /* Current locale for string comparisons */
95 HEADCOMBO *lphc; /* ComboLBox */
96 } LB_DESCR;
99 #define IS_OWNERDRAW(descr) \
100 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
102 #define HAS_STRINGS(descr) \
103 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
106 #define IS_MULTISELECT(descr) \
107 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
108 !((descr)->style & LBS_NOSEL))
110 #define SEND_NOTIFICATION(descr,code) \
111 (SendMessageW( (descr)->owner, WM_COMMAND, \
112 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
114 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
116 /* Current timer status */
117 typedef enum
119 LB_TIMER_NONE,
120 LB_TIMER_UP,
121 LB_TIMER_LEFT,
122 LB_TIMER_DOWN,
123 LB_TIMER_RIGHT
124 } TIMER_DIRECTION;
126 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
128 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
131 For listboxes without LBS_NODATA, an array of LB_ITEMDATA is allocated
132 to store the states of each item into descr->u.items.
134 For single-selection LBS_NODATA listboxes, no storage is allocated,
135 and thus descr->u.nodata_items will always be NULL.
137 For multi-selection LBS_NODATA listboxes, one byte per item is stored
138 for the item's selection state into descr->u.nodata_items.
140 static size_t get_sizeof_item( const LB_DESCR *descr )
142 return (descr->style & LBS_NODATA) ? sizeof(BYTE) : sizeof(LB_ITEMDATA);
145 static BOOL resize_storage(LB_DESCR *descr, UINT items_size)
147 LB_ITEMDATA *items;
149 if (items_size > descr->items_size ||
150 items_size + LB_ARRAY_GRANULARITY * 2 < descr->items_size)
152 items_size = (items_size + LB_ARRAY_GRANULARITY - 1) & ~(LB_ARRAY_GRANULARITY - 1);
153 if ((descr->style & (LBS_NODATA | LBS_MULTIPLESEL | LBS_EXTENDEDSEL)) != LBS_NODATA)
155 items = ReAlloc(descr->u.items, items_size * get_sizeof_item(descr));
156 if (!items)
158 SEND_NOTIFICATION(descr, LBN_ERRSPACE);
159 return FALSE;
161 descr->u.items = items;
163 descr->items_size = items_size;
166 if ((descr->style & LBS_NODATA) && descr->u.nodata_items && items_size > descr->nb_items)
168 memset(descr->u.nodata_items + descr->nb_items, 0,
169 (items_size - descr->nb_items) * get_sizeof_item(descr));
171 return TRUE;
174 static ULONG_PTR get_item_data( const LB_DESCR *descr, UINT index )
176 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].data;
179 static void set_item_data( LB_DESCR *descr, UINT index, ULONG_PTR data )
181 if (!(descr->style & LBS_NODATA)) descr->u.items[index].data = data;
184 static WCHAR *get_item_string( const LB_DESCR *descr, UINT index )
186 return HAS_STRINGS(descr) ? descr->u.items[index].str : NULL;
189 static void set_item_string( const LB_DESCR *descr, UINT index, WCHAR *string )
191 if (!(descr->style & LBS_NODATA)) descr->u.items[index].str = string;
194 static UINT get_item_height( const LB_DESCR *descr, UINT index )
196 return (descr->style & LBS_NODATA) ? 0 : descr->u.items[index].height;
199 static void set_item_height( LB_DESCR *descr, UINT index, UINT height )
201 if (!(descr->style & LBS_NODATA)) descr->u.items[index].height = height;
204 static BOOL is_item_selected( const LB_DESCR *descr, UINT index )
206 if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
207 return index == descr->selected_item;
208 if (descr->style & LBS_NODATA)
209 return descr->u.nodata_items[index];
210 else
211 return descr->u.items[index].selected;
214 static void set_item_selected_state(LB_DESCR *descr, UINT index, BOOL state)
216 if (descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL))
218 if (descr->style & LBS_NODATA)
219 descr->u.nodata_items[index] = state;
220 else
221 descr->u.items[index].selected = state;
225 static void insert_item_data(LB_DESCR *descr, UINT index)
227 size_t size = get_sizeof_item(descr);
228 BYTE *p = descr->u.nodata_items + index * size;
230 if (!descr->u.items) return;
232 if (index < descr->nb_items)
233 memmove(p + size, p, (descr->nb_items - index) * size);
236 static void remove_item_data(LB_DESCR *descr, UINT index)
238 size_t size = get_sizeof_item(descr);
239 BYTE *p = descr->u.nodata_items + index * size;
241 if (!descr->u.items) return;
243 if (index < descr->nb_items)
244 memmove(p, p + size, (descr->nb_items - index) * size);
247 /***********************************************************************
248 * LISTBOX_GetCurrentPageSize
250 * Return the current page size
252 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
254 INT i, height;
255 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
256 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
258 if ((height += get_item_height(descr, i)) > descr->height) break;
260 if (i == descr->top_item) return 1;
261 else return i - descr->top_item;
265 /***********************************************************************
266 * LISTBOX_GetMaxTopIndex
268 * Return the maximum possible index for the top of the listbox.
270 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
272 INT max, page;
274 if (descr->style & LBS_OWNERDRAWVARIABLE)
276 page = descr->height;
277 for (max = descr->nb_items - 1; max >= 0; max--)
278 if ((page -= get_item_height(descr, max)) < 0) break;
279 if (max < descr->nb_items - 1) max++;
281 else if (descr->style & LBS_MULTICOLUMN)
283 if ((page = descr->width / descr->column_width) < 1) page = 1;
284 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
285 max = (max - page) * descr->page_size;
287 else
289 max = descr->nb_items - descr->page_size;
291 if (max < 0) max = 0;
292 return max;
296 /***********************************************************************
297 * LISTBOX_UpdateScroll
299 * Update the scrollbars. Should be called whenever the content
300 * of the listbox changes.
302 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
304 SCROLLINFO info;
306 /* Check the listbox scroll bar flags individually before we call
307 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
308 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
309 scroll bar when we do not need one.
310 if (!(descr->style & WS_VSCROLL)) return;
313 /* It is important that we check descr->style, and not wnd->dwStyle,
314 for WS_VSCROLL, as the former is exactly the one passed in
315 argument to CreateWindow.
316 In Windows (and from now on in Wine :) a listbox created
317 with such a style (no WS_SCROLL) does not update
318 the scrollbar with listbox-related data, thus letting
319 the programmer use it for his/her own purposes. */
321 if (descr->style & LBS_NOREDRAW) return;
322 info.cbSize = sizeof(info);
324 if (descr->style & LBS_MULTICOLUMN)
326 info.nMin = 0;
327 info.nMax = (descr->nb_items - 1) / descr->page_size;
328 info.nPos = descr->top_item / descr->page_size;
329 info.nPage = descr->width / descr->column_width;
330 if (info.nPage < 1) info.nPage = 1;
331 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
332 if (descr->style & LBS_DISABLENOSCROLL)
333 info.fMask |= SIF_DISABLENOSCROLL;
334 if (descr->style & WS_HSCROLL)
335 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
336 info.nMax = 0;
337 info.fMask = SIF_RANGE;
338 if (descr->style & WS_VSCROLL)
339 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
341 else
343 info.nMin = 0;
344 info.nMax = descr->nb_items - 1;
345 info.nPos = descr->top_item;
346 info.nPage = LISTBOX_GetCurrentPageSize( descr );
347 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
348 if (descr->style & LBS_DISABLENOSCROLL)
349 info.fMask |= SIF_DISABLENOSCROLL;
350 if (descr->style & WS_VSCROLL)
351 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
353 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
355 info.nPos = descr->horz_pos;
356 info.nPage = descr->width;
357 info.fMask = SIF_POS | SIF_PAGE;
358 if (descr->style & LBS_DISABLENOSCROLL)
359 info.fMask |= SIF_DISABLENOSCROLL;
360 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
362 else
364 if (descr->style & LBS_DISABLENOSCROLL)
366 info.nMin = 0;
367 info.nMax = 0;
368 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
369 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
371 else
373 ShowScrollBar( descr->self, SB_HORZ, FALSE );
380 /***********************************************************************
381 * LISTBOX_SetTopItem
383 * Set the top item of the listbox, scrolling up or down if necessary.
385 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
387 INT max = LISTBOX_GetMaxTopIndex( descr );
389 TRACE("setting top item %d, scroll %d\n", index, scroll);
391 if (index > max) index = max;
392 if (index < 0) index = 0;
393 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
394 if (descr->top_item == index) return LB_OKAY;
395 if (scroll)
397 INT dx = 0, dy = 0;
398 if (descr->style & LBS_MULTICOLUMN)
399 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
400 else if (descr->style & LBS_OWNERDRAWVARIABLE)
402 INT i;
403 if (index > descr->top_item)
405 for (i = index - 1; i >= descr->top_item; i--)
406 dy -= get_item_height(descr, i);
408 else
410 for (i = index; i < descr->top_item; i++)
411 dy += get_item_height(descr, i);
414 else
415 dy = (descr->top_item - index) * descr->item_height;
417 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
418 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
420 else
421 InvalidateRect( descr->self, NULL, TRUE );
422 descr->top_item = index;
423 LISTBOX_UpdateScroll( descr );
424 return LB_OKAY;
428 /***********************************************************************
429 * LISTBOX_UpdatePage
431 * Update the page size. Should be called when the size of
432 * the client area or the item height changes.
434 static void LISTBOX_UpdatePage( LB_DESCR *descr )
436 INT page_size;
438 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
439 page_size = 1;
440 if (page_size == descr->page_size) return;
441 descr->page_size = page_size;
442 if (descr->style & LBS_MULTICOLUMN)
443 InvalidateRect( descr->self, NULL, TRUE );
444 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
448 /***********************************************************************
449 * LISTBOX_UpdateSize
451 * Update the size of the listbox. Should be called when the size of
452 * the client area changes.
454 static void LISTBOX_UpdateSize( LB_DESCR *descr )
456 RECT rect;
458 GetClientRect( descr->self, &rect );
459 descr->width = rect.right - rect.left;
460 descr->height = rect.bottom - rect.top;
461 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
463 INT remaining;
464 RECT rect;
466 GetWindowRect( descr->self, &rect );
467 if(descr->item_height != 0)
468 remaining = descr->height % descr->item_height;
469 else
470 remaining = 0;
471 if ((descr->height > descr->item_height) && remaining)
473 TRACE("[%p]: changing height %d -> %d\n",
474 descr->self, descr->height, descr->height - remaining );
475 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
476 rect.bottom - rect.top - remaining,
477 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
478 return;
481 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
482 LISTBOX_UpdatePage( descr );
483 LISTBOX_UpdateScroll( descr );
485 /* Invalidate the focused item so it will be repainted correctly */
486 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
488 InvalidateRect( descr->self, &rect, FALSE );
493 /***********************************************************************
494 * LISTBOX_GetItemRect
496 * Get the rectangle enclosing an item, in listbox client coordinates.
497 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
499 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
501 /* Index <= 0 is legal even on empty listboxes */
502 if (index && (index >= descr->nb_items))
504 SetRectEmpty(rect);
505 SetLastError(ERROR_INVALID_INDEX);
506 return LB_ERR;
508 SetRect( rect, 0, 0, descr->width, descr->height );
509 if (descr->style & LBS_MULTICOLUMN)
511 INT col = (index / descr->page_size) -
512 (descr->top_item / descr->page_size);
513 rect->left += col * descr->column_width;
514 rect->right = rect->left + descr->column_width;
515 rect->top += (index % descr->page_size) * descr->item_height;
516 rect->bottom = rect->top + descr->item_height;
518 else if (descr->style & LBS_OWNERDRAWVARIABLE)
520 INT i;
521 rect->right += descr->horz_pos;
522 if ((index >= 0) && (index < descr->nb_items))
524 if (index < descr->top_item)
526 for (i = descr->top_item-1; i >= index; i--)
527 rect->top -= get_item_height(descr, i);
529 else
531 for (i = descr->top_item; i < index; i++)
532 rect->top += get_item_height(descr, i);
534 rect->bottom = rect->top + get_item_height(descr, index);
538 else
540 rect->top += (index - descr->top_item) * descr->item_height;
541 rect->bottom = rect->top + descr->item_height;
542 rect->right += descr->horz_pos;
545 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
547 return ((rect->left < descr->width) && (rect->right > 0) &&
548 (rect->top < descr->height) && (rect->bottom > 0));
552 /***********************************************************************
553 * LISTBOX_GetItemFromPoint
555 * Return the item nearest from point (x,y) (in client coordinates).
557 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
559 INT index = descr->top_item;
561 if (!descr->nb_items) return -1; /* No items */
562 if (descr->style & LBS_OWNERDRAWVARIABLE)
564 INT pos = 0;
565 if (y >= 0)
567 while (index < descr->nb_items)
569 if ((pos += get_item_height(descr, index)) > y) break;
570 index++;
573 else
575 while (index > 0)
577 index--;
578 if ((pos -= get_item_height(descr, index)) <= y) break;
582 else if (descr->style & LBS_MULTICOLUMN)
584 if (y >= descr->item_height * descr->page_size) return -1;
585 if (y >= 0) index += y / descr->item_height;
586 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
587 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
589 else
591 index += (y / descr->item_height);
593 if (index < 0) return 0;
594 if (index >= descr->nb_items) return -1;
595 return index;
599 /***********************************************************************
600 * LISTBOX_PaintItem
602 * Paint an item.
604 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
605 INT index, UINT action, BOOL ignoreFocus )
607 BOOL selected = FALSE, focused;
608 WCHAR *item_str = NULL;
610 if (index < descr->nb_items)
612 item_str = get_item_string(descr, index);
613 selected = is_item_selected(descr, index);
616 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
618 if (IS_OWNERDRAW(descr))
620 DRAWITEMSTRUCT dis;
621 RECT r;
622 HRGN hrgn;
624 if (index >= descr->nb_items)
626 if (action == ODA_FOCUS)
627 DrawFocusRect( hdc, rect );
628 else
629 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
630 return;
633 /* some programs mess with the clipping region when
634 drawing the item, *and* restore the previous region
635 after they are done, so a region has better to exist
636 else everything ends clipped */
637 GetClientRect(descr->self, &r);
638 hrgn = set_control_clipping( hdc, &r );
640 dis.CtlType = ODT_LISTBOX;
641 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
642 dis.hwndItem = descr->self;
643 dis.itemAction = action;
644 dis.hDC = hdc;
645 dis.itemID = index;
646 dis.itemState = 0;
647 if (selected)
648 dis.itemState |= ODS_SELECTED;
649 if (focused)
650 dis.itemState |= ODS_FOCUS;
651 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
652 dis.itemData = get_item_data(descr, index);
653 dis.rcItem = *rect;
654 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
655 descr->self, index, debugstr_w(item_str), action,
656 dis.itemState, wine_dbgstr_rect(rect) );
657 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
658 SelectClipRgn( hdc, hrgn );
659 if (hrgn) DeleteObject( hrgn );
661 else
663 COLORREF oldText = 0, oldBk = 0;
665 if (action == ODA_FOCUS)
667 DrawFocusRect( hdc, rect );
668 return;
670 if (selected)
672 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
673 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
676 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
677 descr->self, index, debugstr_w(item_str), action,
678 wine_dbgstr_rect(rect) );
679 if (!item_str)
680 ExtTextOutW( hdc, rect->left + 1, rect->top,
681 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
682 else if (!(descr->style & LBS_USETABSTOPS))
683 ExtTextOutW( hdc, rect->left + 1, rect->top,
684 ETO_OPAQUE | ETO_CLIPPED, rect, item_str,
685 lstrlenW(item_str), NULL );
686 else
688 /* Output empty string to paint background in the full width. */
689 ExtTextOutW( hdc, rect->left + 1, rect->top,
690 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
691 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
692 item_str, lstrlenW(item_str),
693 descr->nb_tabs, descr->tabs, 0);
695 if (selected)
697 SetBkColor( hdc, oldBk );
698 SetTextColor( hdc, oldText );
700 if (focused)
701 DrawFocusRect( hdc, rect );
706 /***********************************************************************
707 * LISTBOX_SetRedraw
709 * Change the redraw flag.
711 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
713 if (on)
715 if (!(descr->style & LBS_NOREDRAW)) return;
716 descr->style &= ~LBS_NOREDRAW;
717 if (descr->style & LBS_DISPLAYCHANGED)
718 { /* page was changed while setredraw false, refresh automatically */
719 InvalidateRect(descr->self, NULL, TRUE);
720 if ((descr->top_item + descr->page_size) > descr->nb_items)
721 { /* reset top of page if less than number of items/page */
722 descr->top_item = descr->nb_items - descr->page_size;
723 if (descr->top_item < 0) descr->top_item = 0;
725 descr->style &= ~LBS_DISPLAYCHANGED;
727 LISTBOX_UpdateScroll( descr );
729 else descr->style |= LBS_NOREDRAW;
733 /***********************************************************************
734 * LISTBOX_RepaintItem
736 * Repaint a single item synchronously.
738 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
740 HDC hdc;
741 RECT rect;
742 HFONT oldFont = 0;
743 HBRUSH hbrush, oldBrush = 0;
745 /* Do not repaint the item if the item is not visible */
746 if (!IsWindowVisible(descr->self)) return;
747 if (descr->style & LBS_NOREDRAW)
749 descr->style |= LBS_DISPLAYCHANGED;
750 return;
752 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
753 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
754 if (descr->font) oldFont = SelectObject( hdc, descr->font );
755 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
756 (WPARAM)hdc, (LPARAM)descr->self );
757 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
758 if (!IsWindowEnabled(descr->self))
759 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
760 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
761 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
762 if (oldFont) SelectObject( hdc, oldFont );
763 if (oldBrush) SelectObject( hdc, oldBrush );
764 ReleaseDC( descr->self, hdc );
768 /***********************************************************************
769 * LISTBOX_DrawFocusRect
771 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
773 HDC hdc;
774 RECT rect;
775 HFONT oldFont = 0;
777 /* Do not repaint the item if the item is not visible */
778 if (!IsWindowVisible(descr->self)) return;
780 if (descr->focus_item == -1) return;
781 if (!descr->caret_on || !descr->in_focus) return;
783 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
784 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
785 if (descr->font) oldFont = SelectObject( hdc, descr->font );
786 if (!IsWindowEnabled(descr->self))
787 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
788 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
789 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
790 if (oldFont) SelectObject( hdc, oldFont );
791 ReleaseDC( descr->self, hdc );
795 /***********************************************************************
796 * LISTBOX_InitStorage
798 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
800 UINT new_size = descr->nb_items + nb_items;
802 if (new_size > descr->items_size && !resize_storage(descr, new_size))
803 return LB_ERRSPACE;
804 return descr->items_size;
808 /***********************************************************************
809 * LISTBOX_SetTabStops
811 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
813 INT i;
815 if (!(descr->style & LBS_USETABSTOPS))
817 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
818 return FALSE;
821 HeapFree( GetProcessHeap(), 0, descr->tabs );
822 if (!(descr->nb_tabs = count))
824 descr->tabs = NULL;
825 return TRUE;
827 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
828 descr->nb_tabs * sizeof(INT) )))
829 return FALSE;
830 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
832 /* convert into "dialog units"*/
833 for (i = 0; i < descr->nb_tabs; i++)
834 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
836 return TRUE;
840 /***********************************************************************
841 * LISTBOX_GetText
843 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
845 DWORD len;
847 if ((index < 0) || (index >= descr->nb_items))
849 SetLastError(ERROR_INVALID_INDEX);
850 return LB_ERR;
853 if (HAS_STRINGS(descr))
855 WCHAR *str = get_item_string(descr, index);
857 if (!buffer)
858 return lstrlenW(str);
860 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(str));
862 __TRY /* hide a Delphi bug that passes a read-only buffer */
864 lstrcpyW(buffer, str);
865 len = lstrlenW(buffer);
867 __EXCEPT_PAGE_FAULT
869 WARN( "got an invalid buffer (Delphi bug?)\n" );
870 SetLastError( ERROR_INVALID_PARAMETER );
871 return LB_ERR;
873 __ENDTRY
874 } else
876 if (buffer)
877 *((ULONG_PTR *)buffer) = get_item_data(descr, index);
878 len = sizeof(ULONG_PTR);
880 return len;
883 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
885 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
886 if (ret == CSTR_LESS_THAN)
887 return -1;
888 if (ret == CSTR_EQUAL)
889 return 0;
890 if (ret == CSTR_GREATER_THAN)
891 return 1;
892 return -1;
895 /***********************************************************************
896 * LISTBOX_FindStringPos
898 * Find the nearest string located before a given string in sort order.
899 * If 'exact' is TRUE, return an error if we don't get an exact match.
901 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
903 INT index, min, max, res;
905 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
907 min = 0;
908 max = descr->nb_items - 1;
909 while (min <= max)
911 index = (min + max) / 2;
912 if (HAS_STRINGS(descr))
913 res = LISTBOX_lstrcmpiW( descr->locale, get_item_string(descr, index), str );
914 else
916 COMPAREITEMSTRUCT cis;
917 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
919 cis.CtlType = ODT_LISTBOX;
920 cis.CtlID = id;
921 cis.hwndItem = descr->self;
922 /* note that some application (MetaStock) expects the second item
923 * to be in the listbox */
924 cis.itemID1 = index;
925 cis.itemData1 = get_item_data(descr, index);
926 cis.itemID2 = -1;
927 cis.itemData2 = (ULONG_PTR)str;
928 cis.dwLocaleId = descr->locale;
929 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
931 if (!res) return index;
932 if (res > 0) max = index - 1;
933 else min = index + 1;
935 return exact ? -1 : min;
939 /***********************************************************************
940 * LISTBOX_FindFileStrPos
942 * Find the nearest string located before a given string in directory
943 * sort order (i.e. first files, then directories, then drives).
945 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
947 INT min, max, res;
949 if (!HAS_STRINGS(descr))
950 return LISTBOX_FindStringPos( descr, str, FALSE );
951 min = 0;
952 max = descr->nb_items;
953 while (min != max)
955 INT index = (min + max) / 2;
956 LPCWSTR p = get_item_string(descr, index);
957 if (*p == '[') /* drive or directory */
959 if (*str != '[') res = -1;
960 else if (p[1] == '-') /* drive */
962 if (str[1] == '-') res = str[2] - p[2];
963 else res = -1;
965 else /* directory */
967 if (str[1] == '-') res = 1;
968 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
971 else /* filename */
973 if (*str == '[') res = 1;
974 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
976 if (!res) return index;
977 if (res < 0) max = index;
978 else min = index + 1;
980 return max;
984 /***********************************************************************
985 * LISTBOX_FindString
987 * Find the item beginning with a given string.
989 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
991 INT i, index;
993 if (descr->style & LBS_NODATA) return LB_ERR;
995 start++;
996 if (start >= descr->nb_items) start = 0;
997 if (HAS_STRINGS(descr))
999 if (!str || ! str[0] ) return LB_ERR;
1000 if (exact)
1002 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1004 if (index == descr->nb_items) index = 0;
1005 if (!LISTBOX_lstrcmpiW(descr->locale, str, get_item_string(descr, index)))
1006 return index;
1009 else
1011 /* Special case for drives and directories: ignore prefix */
1012 INT len = lstrlenW(str);
1013 WCHAR *item_str;
1015 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1017 if (index == descr->nb_items) index = 0;
1018 item_str = get_item_string(descr, index);
1020 if (!wcsnicmp(str, item_str, len)) return index;
1021 if (item_str[0] == '[')
1023 if (!wcsnicmp(str, item_str + 1, len)) return index;
1024 if (item_str[1] == '-' && !wcsnicmp(str, item_str + 2, len)) return index;
1029 else
1031 if (exact && (descr->style & LBS_SORT))
1032 /* If sorted, use a WM_COMPAREITEM binary search */
1033 return LISTBOX_FindStringPos( descr, str, TRUE );
1035 /* Otherwise use a linear search */
1036 for (i = 0, index = start; i < descr->nb_items; i++, index++)
1038 if (index == descr->nb_items) index = 0;
1039 if (get_item_data(descr, index) == (ULONG_PTR)str) return index;
1042 return LB_ERR;
1046 /***********************************************************************
1047 * LISTBOX_GetSelCount
1049 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1051 INT i, count;
1053 if (!(descr->style & LBS_MULTIPLESEL) ||
1054 (descr->style & LBS_NOSEL))
1055 return LB_ERR;
1056 for (i = count = 0; i < descr->nb_items; i++)
1057 if (is_item_selected(descr, i)) count++;
1058 return count;
1062 /***********************************************************************
1063 * LISTBOX_GetSelItems
1065 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1067 INT i, count;
1069 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1070 for (i = count = 0; (i < descr->nb_items) && (count < max); i++)
1071 if (is_item_selected(descr, i)) array[count++] = i;
1072 return count;
1076 /***********************************************************************
1077 * LISTBOX_Paint
1079 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1081 INT i, col_pos = descr->page_size - 1;
1082 RECT rect;
1083 RECT focusRect = {-1, -1, -1, -1};
1084 HFONT oldFont = 0;
1085 HBRUSH hbrush, oldBrush = 0;
1087 if (descr->style & LBS_NOREDRAW) return 0;
1089 SetRect( &rect, 0, 0, descr->width, descr->height );
1090 if (descr->style & LBS_MULTICOLUMN)
1091 rect.right = rect.left + descr->column_width;
1092 else if (descr->horz_pos)
1094 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1095 rect.right += descr->horz_pos;
1098 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1099 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1100 (WPARAM)hdc, (LPARAM)descr->self );
1101 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1102 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1104 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1105 (descr->in_focus))
1107 /* Special case for empty listbox: paint focus rect */
1108 rect.bottom = rect.top + descr->item_height;
1109 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1110 &rect, NULL, 0, NULL );
1111 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1112 rect.top = rect.bottom;
1115 /* Paint all the item, regarding the selection
1116 Focus state will be painted after */
1118 for (i = descr->top_item; i < descr->nb_items; i++)
1120 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1121 rect.bottom = rect.top + descr->item_height;
1122 else
1123 rect.bottom = rect.top + get_item_height(descr, i);
1125 /* keep the focus rect, to paint the focus item after */
1126 if (i == descr->focus_item)
1127 focusRect = rect;
1129 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1130 rect.top = rect.bottom;
1132 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1134 if (!IS_OWNERDRAW(descr))
1136 /* Clear the bottom of the column */
1137 if (rect.top < descr->height)
1139 rect.bottom = descr->height;
1140 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1141 &rect, NULL, 0, NULL );
1145 /* Go to the next column */
1146 rect.left += descr->column_width;
1147 rect.right += descr->column_width;
1148 rect.top = 0;
1149 col_pos = descr->page_size - 1;
1150 if (rect.left >= descr->width) break;
1152 else
1154 col_pos--;
1155 if (rect.top >= descr->height) break;
1159 /* Paint the focus item now */
1160 if (focusRect.top != focusRect.bottom &&
1161 descr->caret_on && descr->in_focus)
1162 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1164 if (!IS_OWNERDRAW(descr))
1166 /* Clear the remainder of the client area */
1167 if (rect.top < descr->height)
1169 rect.bottom = descr->height;
1170 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1171 &rect, NULL, 0, NULL );
1173 if (rect.right < descr->width)
1175 rect.left = rect.right;
1176 rect.right = descr->width;
1177 rect.top = 0;
1178 rect.bottom = descr->height;
1179 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1180 &rect, NULL, 0, NULL );
1183 if (oldFont) SelectObject( hdc, oldFont );
1184 if (oldBrush) SelectObject( hdc, oldBrush );
1185 return 0;
1188 static LRESULT LISTBOX_NCPaint( LB_DESCR *descr, HRGN region )
1190 DWORD exstyle = GetWindowLongW( descr->self, GWL_EXSTYLE);
1191 HTHEME theme = GetWindowTheme( descr->self );
1192 HRGN cliprgn = region;
1193 int cxEdge, cyEdge;
1194 HDC hdc;
1195 RECT r;
1197 if (!theme || !(exstyle & WS_EX_CLIENTEDGE))
1198 return DefWindowProcW(descr->self, WM_NCPAINT, (WPARAM)region, 0);
1200 cxEdge = GetSystemMetrics(SM_CXEDGE);
1201 cyEdge = GetSystemMetrics(SM_CYEDGE);
1203 GetWindowRect(descr->self, &r);
1205 /* New clipping region passed to default proc to exclude border */
1206 cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge,
1207 r.right - cxEdge, r.bottom - cyEdge);
1208 if (region != (HRGN)1)
1209 CombineRgn(cliprgn, cliprgn, region, RGN_AND);
1210 OffsetRect(&r, -r.left, -r.top);
1212 hdc = GetDCEx(descr->self, region, DCX_WINDOW|DCX_INTERSECTRGN);
1214 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
1215 DrawThemeParentBackground(descr->self, hdc, &r);
1216 DrawThemeBackground (theme, hdc, 0, 0, &r, 0);
1217 ReleaseDC(descr->self, hdc);
1219 /* Call default proc to get the scrollbars etc. also painted */
1220 DefWindowProcW(descr->self, WM_NCPAINT, (WPARAM)cliprgn, 0);
1221 DeleteObject(cliprgn);
1222 return 0;
1225 /***********************************************************************
1226 * LISTBOX_InvalidateItems
1228 * Invalidate all items from a given item. If the specified item is not
1229 * visible, nothing happens.
1231 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1233 RECT rect;
1235 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1237 if (descr->style & LBS_NOREDRAW)
1239 descr->style |= LBS_DISPLAYCHANGED;
1240 return;
1242 rect.bottom = descr->height;
1243 InvalidateRect( descr->self, &rect, TRUE );
1244 if (descr->style & LBS_MULTICOLUMN)
1246 /* Repaint the other columns */
1247 rect.left = rect.right;
1248 rect.right = descr->width;
1249 rect.top = 0;
1250 InvalidateRect( descr->self, &rect, TRUE );
1255 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1257 RECT rect;
1259 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1260 InvalidateRect( descr->self, &rect, TRUE );
1263 /***********************************************************************
1264 * LISTBOX_GetItemHeight
1266 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1268 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1270 if ((index < 0) || (index >= descr->nb_items))
1272 SetLastError(ERROR_INVALID_INDEX);
1273 return LB_ERR;
1275 return get_item_height(descr, index);
1277 else return descr->item_height;
1281 /***********************************************************************
1282 * LISTBOX_SetItemHeight
1284 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1286 if (height > MAXWORD)
1287 return -1;
1289 if (!height) height = 1;
1291 if (descr->style & LBS_OWNERDRAWVARIABLE)
1293 if ((index < 0) || (index >= descr->nb_items))
1295 SetLastError(ERROR_INVALID_INDEX);
1296 return LB_ERR;
1298 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1299 set_item_height(descr, index, height);
1300 LISTBOX_UpdateScroll( descr );
1301 if (repaint)
1302 LISTBOX_InvalidateItems( descr, index );
1304 else if (height != descr->item_height)
1306 TRACE("[%p]: new height = %d\n", descr->self, height );
1307 descr->item_height = height;
1308 LISTBOX_UpdatePage( descr );
1309 LISTBOX_UpdateScroll( descr );
1310 if (repaint)
1311 InvalidateRect( descr->self, 0, TRUE );
1313 return LB_OKAY;
1317 /***********************************************************************
1318 * LISTBOX_SetHorizontalPos
1320 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1322 INT diff;
1324 if (pos > descr->horz_extent - descr->width)
1325 pos = descr->horz_extent - descr->width;
1326 if (pos < 0) pos = 0;
1327 if (!(diff = descr->horz_pos - pos)) return;
1328 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1329 descr->horz_pos = pos;
1330 LISTBOX_UpdateScroll( descr );
1331 if (abs(diff) < descr->width)
1333 RECT rect;
1334 /* Invalidate the focused item so it will be repainted correctly */
1335 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1336 InvalidateRect( descr->self, &rect, TRUE );
1337 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1338 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1340 else
1341 InvalidateRect( descr->self, NULL, TRUE );
1345 /***********************************************************************
1346 * LISTBOX_SetHorizontalExtent
1348 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1350 if (descr->style & LBS_MULTICOLUMN)
1351 return LB_OKAY;
1352 if (extent == descr->horz_extent) return LB_OKAY;
1353 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1354 descr->horz_extent = extent;
1355 if (descr->style & WS_HSCROLL) {
1356 SCROLLINFO info;
1357 info.cbSize = sizeof(info);
1358 info.nMin = 0;
1359 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1360 info.fMask = SIF_RANGE;
1361 if (descr->style & LBS_DISABLENOSCROLL)
1362 info.fMask |= SIF_DISABLENOSCROLL;
1363 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1365 if (descr->horz_pos > extent - descr->width)
1366 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1367 return LB_OKAY;
1371 /***********************************************************************
1372 * LISTBOX_SetColumnWidth
1374 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1376 RECT rect;
1378 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1380 GetClientRect(descr->self, &rect);
1381 descr->width = rect.right - rect.left;
1382 descr->height = rect.bottom - rect.top;
1383 descr->column_width = column_width;
1385 LISTBOX_UpdatePage(descr);
1386 LISTBOX_UpdateScroll(descr);
1387 return LB_OKAY;
1391 /***********************************************************************
1392 * LISTBOX_SetFont
1394 * Returns the item height.
1396 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1398 HDC hdc;
1399 HFONT oldFont = 0;
1400 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1401 SIZE sz;
1403 descr->font = font;
1405 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1407 ERR("unable to get DC.\n" );
1408 return 16;
1410 if (font) oldFont = SelectObject( hdc, font );
1411 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1412 if (oldFont) SelectObject( hdc, oldFont );
1413 ReleaseDC( descr->self, hdc );
1415 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1416 if (!IS_OWNERDRAW(descr))
1417 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1418 return sz.cy;
1422 /***********************************************************************
1423 * LISTBOX_MakeItemVisible
1425 * Make sure that a given item is partially or fully visible.
1427 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1429 INT top;
1431 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1433 if (index <= descr->top_item) top = index;
1434 else if (descr->style & LBS_MULTICOLUMN)
1436 INT cols = descr->width;
1437 if (!fully) cols += descr->column_width - 1;
1438 if (cols >= descr->column_width) cols /= descr->column_width;
1439 else cols = 1;
1440 if (index < descr->top_item + (descr->page_size * cols)) return;
1441 top = index - descr->page_size * (cols - 1);
1443 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1445 INT height = fully ? get_item_height(descr, index) : 1;
1446 for (top = index; top > descr->top_item; top--)
1447 if ((height += get_item_height(descr, top - 1)) > descr->height) break;
1449 else
1451 if (index < descr->top_item + descr->page_size) return;
1452 if (!fully && (index == descr->top_item + descr->page_size) &&
1453 (descr->height > (descr->page_size * descr->item_height))) return;
1454 top = index - descr->page_size + 1;
1456 LISTBOX_SetTopItem( descr, top, TRUE );
1459 /***********************************************************************
1460 * LISTBOX_SetCaretIndex
1462 * NOTES
1463 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1466 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1468 BOOL focus_changed = descr->focus_item != index;
1470 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1472 if (descr->style & LBS_NOSEL) return LB_ERR;
1473 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1475 if (focus_changed)
1477 LISTBOX_DrawFocusRect( descr, FALSE );
1478 descr->focus_item = index;
1481 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1483 if (focus_changed)
1484 LISTBOX_DrawFocusRect( descr, TRUE );
1486 return LB_OKAY;
1490 /***********************************************************************
1491 * LISTBOX_SelectItemRange
1493 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1495 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1496 INT last, BOOL on )
1498 INT i;
1500 /* A few sanity checks */
1502 if (descr->style & LBS_NOSEL) return LB_ERR;
1503 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1505 if (!descr->nb_items) return LB_OKAY;
1507 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1508 if (first < 0) first = 0;
1509 if (last < first) return LB_OKAY;
1511 if (on) /* Turn selection on */
1513 for (i = first; i <= last; i++)
1515 if (is_item_selected(descr, i)) continue;
1516 set_item_selected_state(descr, i, TRUE);
1517 LISTBOX_InvalidateItemRect(descr, i);
1520 else /* Turn selection off */
1522 for (i = first; i <= last; i++)
1524 if (!is_item_selected(descr, i)) continue;
1525 set_item_selected_state(descr, i, FALSE);
1526 LISTBOX_InvalidateItemRect(descr, i);
1529 return LB_OKAY;
1532 /***********************************************************************
1533 * LISTBOX_SetSelection
1535 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1536 BOOL on, BOOL send_notify )
1538 TRACE( "cur_sel=%d index=%d notify=%s\n",
1539 descr->selected_item, index, send_notify ? "YES" : "NO" );
1541 if (descr->style & LBS_NOSEL)
1543 descr->selected_item = index;
1544 return LB_ERR;
1546 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1547 if (descr->style & LBS_MULTIPLESEL)
1549 if (index == -1) /* Select all items */
1550 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1551 else /* Only one item */
1552 return LISTBOX_SelectItemRange( descr, index, index, on );
1554 else
1556 INT oldsel = descr->selected_item;
1557 if (index == oldsel) return LB_OKAY;
1558 if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
1559 if (index != -1) set_item_selected_state(descr, index, TRUE);
1560 descr->selected_item = index;
1561 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1562 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1563 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1564 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1565 else
1566 if( descr->lphc ) /* set selection change flag for parent combo */
1567 descr->lphc->wState |= CBF_SELCHANGE;
1569 return LB_OKAY;
1573 /***********************************************************************
1574 * LISTBOX_MoveCaret
1576 * Change the caret position and extend the selection to the new caret.
1578 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1580 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1582 if ((index < 0) || (index >= descr->nb_items))
1583 return;
1585 /* Important, repaint needs to be done in this order if
1586 you want to mimic Windows behavior:
1587 1. Remove the focus and paint the item
1588 2. Remove the selection and paint the item(s)
1589 3. Set the selection and repaint the item(s)
1590 4. Set the focus to 'index' and repaint the item */
1592 /* 1. remove the focus and repaint the item */
1593 LISTBOX_DrawFocusRect( descr, FALSE );
1595 /* 2. then turn off the previous selection */
1596 /* 3. repaint the new selected item */
1597 if (descr->style & LBS_EXTENDEDSEL)
1599 if (descr->anchor_item != -1)
1601 INT first = min( index, descr->anchor_item );
1602 INT last = max( index, descr->anchor_item );
1603 if (first > 0)
1604 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1605 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1606 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1609 else if (!(descr->style & LBS_MULTIPLESEL))
1611 /* Set selection to new caret item */
1612 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1615 /* 4. repaint the new item with the focus */
1616 descr->focus_item = index;
1617 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1618 LISTBOX_DrawFocusRect( descr, TRUE );
1622 /***********************************************************************
1623 * LISTBOX_InsertItem
1625 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1626 LPWSTR str, ULONG_PTR data )
1628 INT oldfocus = descr->focus_item;
1630 if (index == -1) index = descr->nb_items;
1631 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1632 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1634 insert_item_data(descr, index);
1635 descr->nb_items++;
1636 set_item_string(descr, index, str);
1637 set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
1638 set_item_height(descr, index, 0);
1639 set_item_selected_state(descr, index, FALSE);
1641 /* Get item height */
1643 if (descr->style & LBS_OWNERDRAWVARIABLE)
1645 MEASUREITEMSTRUCT mis;
1646 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1648 mis.CtlType = ODT_LISTBOX;
1649 mis.CtlID = id;
1650 mis.itemID = index;
1651 mis.itemData = data;
1652 mis.itemHeight = descr->item_height;
1653 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1654 set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
1655 TRACE("[%p]: measure item %d (%s) = %d\n",
1656 descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
1659 /* Repaint the items */
1661 LISTBOX_UpdateScroll( descr );
1662 LISTBOX_InvalidateItems( descr, index );
1664 /* Move selection and focused item */
1665 /* If listbox was empty, set focus to the first item */
1666 if (descr->nb_items == 1)
1667 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1668 /* single select don't change selection index in win31 */
1669 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1671 descr->selected_item++;
1672 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1674 else
1676 if (index <= descr->selected_item)
1678 descr->selected_item++;
1679 descr->focus_item = oldfocus; /* focus not changed */
1682 return LB_OKAY;
1686 /***********************************************************************
1687 * LISTBOX_InsertString
1689 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1691 LPWSTR new_str = NULL;
1692 LRESULT ret;
1694 if (HAS_STRINGS(descr))
1696 if (!str) str = L"";
1697 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str) + 1) * sizeof(WCHAR) )))
1699 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1700 return LB_ERRSPACE;
1702 lstrcpyW(new_str, str);
1705 if (index == -1) index = descr->nb_items;
1706 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1708 HeapFree( GetProcessHeap(), 0, new_str );
1709 return ret;
1712 TRACE("[%p]: added item %d %s\n",
1713 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1714 return index;
1718 /***********************************************************************
1719 * LISTBOX_DeleteItem
1721 * Delete the content of an item. 'index' must be a valid index.
1723 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1725 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1726 * while Win95 sends it for all items with user data.
1727 * It's probably better to send it too often than not
1728 * often enough, so this is what we do here.
1730 if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
1732 DELETEITEMSTRUCT dis;
1733 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1735 dis.CtlType = ODT_LISTBOX;
1736 dis.CtlID = id;
1737 dis.itemID = index;
1738 dis.hwndItem = descr->self;
1739 dis.itemData = get_item_data(descr, index);
1740 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1742 HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
1746 /***********************************************************************
1747 * LISTBOX_RemoveItem
1749 * Remove an item from the listbox and delete its content.
1751 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1753 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1755 /* We need to invalidate the original rect instead of the updated one. */
1756 LISTBOX_InvalidateItems( descr, index );
1758 if (descr->nb_items == 1)
1760 SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1761 return LB_OKAY;
1763 descr->nb_items--;
1764 LISTBOX_DeleteItem( descr, index );
1765 remove_item_data(descr, index);
1767 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1768 resize_storage(descr, descr->nb_items);
1770 /* Repaint the items */
1772 LISTBOX_UpdateScroll( descr );
1773 /* if we removed the scrollbar, reset the top of the list
1774 (correct for owner-drawn ???) */
1775 if (descr->nb_items == descr->page_size)
1776 LISTBOX_SetTopItem( descr, 0, TRUE );
1778 /* Move selection and focused item */
1779 if (!IS_MULTISELECT(descr))
1781 if (index == descr->selected_item)
1782 descr->selected_item = -1;
1783 else if (index < descr->selected_item)
1785 descr->selected_item--;
1786 if (ISWIN31) /* win 31 do not change the selected item number */
1787 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1791 if (descr->focus_item >= descr->nb_items)
1793 descr->focus_item = descr->nb_items - 1;
1794 if (descr->focus_item < 0) descr->focus_item = 0;
1796 return LB_OKAY;
1800 /***********************************************************************
1801 * LISTBOX_ResetContent
1803 static void LISTBOX_ResetContent( LB_DESCR *descr )
1805 INT i;
1807 if (!(descr->style & LBS_NODATA))
1808 for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
1809 HeapFree( GetProcessHeap(), 0, descr->u.items );
1810 descr->nb_items = 0;
1811 descr->top_item = 0;
1812 descr->selected_item = -1;
1813 descr->focus_item = 0;
1814 descr->anchor_item = -1;
1815 descr->items_size = 0;
1816 descr->u.items = NULL;
1820 /***********************************************************************
1821 * LISTBOX_SetCount
1823 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1825 UINT orig_num = descr->nb_items;
1827 if (!(descr->style & LBS_NODATA)) return LB_ERR;
1829 if (!resize_storage(descr, count))
1830 return LB_ERRSPACE;
1831 descr->nb_items = count;
1832 if (descr->style & LBS_NOREDRAW)
1833 descr->style |= LBS_DISPLAYCHANGED;
1835 if (count)
1837 LISTBOX_UpdateScroll(descr);
1838 if (count < orig_num)
1840 descr->anchor_item = min(descr->anchor_item, count - 1);
1841 if (descr->selected_item >= count)
1842 descr->selected_item = -1;
1844 /* If we removed the scrollbar, reset the top of the list */
1845 if (count <= descr->page_size && orig_num > descr->page_size)
1846 LISTBOX_SetTopItem(descr, 0, TRUE);
1848 descr->focus_item = min(descr->focus_item, count - 1);
1851 /* If it was empty before growing, set focus to the first item */
1852 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1854 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1856 InvalidateRect( descr->self, NULL, TRUE );
1857 return LB_OKAY;
1861 /***********************************************************************
1862 * LISTBOX_Directory
1864 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1865 LPCWSTR filespec, BOOL long_names )
1867 HANDLE handle;
1868 LRESULT ret = LB_OKAY;
1869 WIN32_FIND_DATAW entry;
1870 int pos;
1871 LRESULT maxinsert = LB_ERR;
1873 /* don't scan directory if we just want drives exclusively */
1874 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1875 /* scan directory */
1876 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1878 int le = GetLastError();
1879 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1881 else
1885 WCHAR buffer[270];
1886 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1888 if (!(attrib & DDL_DIRECTORY) ||
1889 !lstrcmpW( entry.cFileName, L"." )) continue;
1890 buffer[0] = '[';
1891 if (!long_names && entry.cAlternateFileName[0])
1892 lstrcpyW( buffer + 1, entry.cAlternateFileName );
1893 else
1894 lstrcpyW( buffer + 1, entry.cFileName );
1895 lstrcatW(buffer, L"]");
1897 else /* not a directory */
1899 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1900 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1902 if ((attrib & DDL_EXCLUSIVE) &&
1903 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1904 continue;
1905 #undef ATTRIBS
1906 if (!long_names && entry.cAlternateFileName[0])
1907 lstrcpyW( buffer, entry.cAlternateFileName );
1908 else
1909 lstrcpyW( buffer, entry.cFileName );
1911 if (!long_names) CharLowerW( buffer );
1912 pos = LISTBOX_FindFileStrPos( descr, buffer );
1913 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1914 break;
1915 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1916 } while (FindNextFileW( handle, &entry ));
1917 FindClose( handle );
1920 if (ret >= 0)
1922 ret = maxinsert;
1924 /* scan drives */
1925 if (attrib & DDL_DRIVES)
1927 WCHAR buffer[] = L"[-a-]";
1928 WCHAR root[] = L"A:\\";
1929 int drive;
1930 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1932 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1933 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1934 break;
1938 return ret;
1942 /***********************************************************************
1943 * LISTBOX_HandleVScroll
1945 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1947 SCROLLINFO info;
1949 if (descr->style & LBS_MULTICOLUMN) return 0;
1950 switch(scrollReq)
1952 case SB_LINEUP:
1953 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1954 break;
1955 case SB_LINEDOWN:
1956 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1957 break;
1958 case SB_PAGEUP:
1959 LISTBOX_SetTopItem( descr, descr->top_item -
1960 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1961 break;
1962 case SB_PAGEDOWN:
1963 LISTBOX_SetTopItem( descr, descr->top_item +
1964 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1965 break;
1966 case SB_THUMBPOSITION:
1967 LISTBOX_SetTopItem( descr, pos, TRUE );
1968 break;
1969 case SB_THUMBTRACK:
1970 info.cbSize = sizeof(info);
1971 info.fMask = SIF_TRACKPOS;
1972 GetScrollInfo( descr->self, SB_VERT, &info );
1973 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1974 break;
1975 case SB_TOP:
1976 LISTBOX_SetTopItem( descr, 0, TRUE );
1977 break;
1978 case SB_BOTTOM:
1979 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1980 break;
1982 return 0;
1986 /***********************************************************************
1987 * LISTBOX_HandleHScroll
1989 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1991 SCROLLINFO info;
1992 INT page;
1994 if (descr->style & LBS_MULTICOLUMN)
1996 switch(scrollReq)
1998 case SB_LINELEFT:
1999 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
2000 TRUE );
2001 break;
2002 case SB_LINERIGHT:
2003 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
2004 TRUE );
2005 break;
2006 case SB_PAGELEFT:
2007 page = descr->width / descr->column_width;
2008 if (page < 1) page = 1;
2009 LISTBOX_SetTopItem( descr,
2010 descr->top_item - page * descr->page_size, TRUE );
2011 break;
2012 case SB_PAGERIGHT:
2013 page = descr->width / descr->column_width;
2014 if (page < 1) page = 1;
2015 LISTBOX_SetTopItem( descr,
2016 descr->top_item + page * descr->page_size, TRUE );
2017 break;
2018 case SB_THUMBPOSITION:
2019 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
2020 break;
2021 case SB_THUMBTRACK:
2022 info.cbSize = sizeof(info);
2023 info.fMask = SIF_TRACKPOS;
2024 GetScrollInfo( descr->self, SB_VERT, &info );
2025 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
2026 TRUE );
2027 break;
2028 case SB_LEFT:
2029 LISTBOX_SetTopItem( descr, 0, TRUE );
2030 break;
2031 case SB_RIGHT:
2032 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2033 break;
2036 else if (descr->horz_extent)
2038 switch(scrollReq)
2040 case SB_LINELEFT:
2041 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2042 break;
2043 case SB_LINERIGHT:
2044 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2045 break;
2046 case SB_PAGELEFT:
2047 LISTBOX_SetHorizontalPos( descr,
2048 descr->horz_pos - descr->width );
2049 break;
2050 case SB_PAGERIGHT:
2051 LISTBOX_SetHorizontalPos( descr,
2052 descr->horz_pos + descr->width );
2053 break;
2054 case SB_THUMBPOSITION:
2055 LISTBOX_SetHorizontalPos( descr, pos );
2056 break;
2057 case SB_THUMBTRACK:
2058 info.cbSize = sizeof(info);
2059 info.fMask = SIF_TRACKPOS;
2060 GetScrollInfo( descr->self, SB_HORZ, &info );
2061 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2062 break;
2063 case SB_LEFT:
2064 LISTBOX_SetHorizontalPos( descr, 0 );
2065 break;
2066 case SB_RIGHT:
2067 LISTBOX_SetHorizontalPos( descr,
2068 descr->horz_extent - descr->width );
2069 break;
2072 return 0;
2075 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2077 INT pulScrollLines = 3;
2079 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2081 /* if scrolling changes direction, ignore left overs */
2082 if ((delta < 0 && descr->wheel_remain < 0) ||
2083 (delta > 0 && descr->wheel_remain > 0))
2084 descr->wheel_remain += delta;
2085 else
2086 descr->wheel_remain = delta;
2088 if (descr->wheel_remain && pulScrollLines)
2090 int cLineScroll;
2091 if (descr->style & LBS_MULTICOLUMN)
2093 pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
2094 pulScrollLines = max(1, pulScrollLines);
2095 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2096 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2097 cLineScroll *= descr->page_size;
2099 else
2101 pulScrollLines = min(descr->page_size, pulScrollLines);
2102 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2103 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2105 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2107 return 0;
2110 /***********************************************************************
2111 * LISTBOX_HandleLButtonDown
2113 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2115 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2117 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2118 descr->self, x, y, index, descr->focus_item);
2120 if (!descr->caret_on && (descr->in_focus)) return 0;
2122 if (!descr->in_focus)
2124 if( !descr->lphc ) SetFocus( descr->self );
2125 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2128 if (index == -1) return 0;
2130 if (!descr->lphc)
2132 if (descr->style & LBS_NOTIFY )
2133 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2134 MAKELPARAM( x, y ) );
2137 descr->captured = TRUE;
2138 SetCapture( descr->self );
2140 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2142 /* we should perhaps make sure that all items are deselected
2143 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2144 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2145 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2148 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2149 if (keys & MK_CONTROL)
2151 LISTBOX_SetCaretIndex( descr, index, FALSE );
2152 LISTBOX_SetSelection( descr, index,
2153 !is_item_selected(descr, index),
2154 (descr->style & LBS_NOTIFY) != 0);
2156 else
2158 LISTBOX_MoveCaret( descr, index, FALSE );
2160 if (descr->style & LBS_EXTENDEDSEL)
2162 LISTBOX_SetSelection( descr, index,
2163 is_item_selected(descr, index),
2164 (descr->style & LBS_NOTIFY) != 0 );
2166 else
2168 LISTBOX_SetSelection( descr, index,
2169 !is_item_selected(descr, index),
2170 (descr->style & LBS_NOTIFY) != 0 );
2174 else
2176 descr->anchor_item = index;
2177 LISTBOX_MoveCaret( descr, index, FALSE );
2178 LISTBOX_SetSelection( descr, index,
2179 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2182 if (!descr->lphc)
2184 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2186 POINT pt;
2188 pt.x = x;
2189 pt.y = y;
2191 if (DragDetect( descr->self, pt ))
2192 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2195 return 0;
2199 /*************************************************************************
2200 * LISTBOX_HandleLButtonDownCombo [Internal]
2202 * Process LButtonDown message for the ComboListBox
2204 * PARAMS
2205 * pWnd [I] The windows internal structure
2206 * pDescr [I] The ListBox internal structure
2207 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2208 * x [I] X Mouse Coordinate
2209 * y [I] Y Mouse Coordinate
2211 * RETURNS
2212 * 0 since we are processing the WM_LBUTTONDOWN Message
2214 * NOTES
2215 * This function is only to be used when a ListBox is a ComboListBox
2218 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2220 RECT clientRect, screenRect;
2221 POINT mousePos;
2223 mousePos.x = x;
2224 mousePos.y = y;
2226 GetClientRect(descr->self, &clientRect);
2228 if(PtInRect(&clientRect, mousePos))
2230 /* MousePos is in client, resume normal processing */
2231 if (msg == WM_LBUTTONDOWN)
2233 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2234 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2236 else if (descr->style & LBS_NOTIFY)
2237 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2239 else
2241 POINT screenMousePos;
2242 HWND hWndOldCapture;
2244 /* Check the Non-Client Area */
2245 screenMousePos = mousePos;
2246 hWndOldCapture = GetCapture();
2247 ReleaseCapture();
2248 GetWindowRect(descr->self, &screenRect);
2249 ClientToScreen(descr->self, &screenMousePos);
2251 if(!PtInRect(&screenRect, screenMousePos))
2253 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2254 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2255 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2257 else
2259 /* Check to see the NC is a scrollbar */
2260 INT nHitTestType=0;
2261 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2262 /* Check Vertical scroll bar */
2263 if (style & WS_VSCROLL)
2265 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2266 if (PtInRect( &clientRect, mousePos ))
2267 nHitTestType = HTVSCROLL;
2269 /* Check horizontal scroll bar */
2270 if (style & WS_HSCROLL)
2272 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2273 if (PtInRect( &clientRect, mousePos ))
2274 nHitTestType = HTHSCROLL;
2276 /* Windows sends this message when a scrollbar is clicked
2279 if(nHitTestType != 0)
2281 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2282 MAKELONG(screenMousePos.x, screenMousePos.y));
2284 /* Resume the Capture after scrolling is complete
2286 if(hWndOldCapture != 0)
2287 SetCapture(hWndOldCapture);
2290 return 0;
2293 /***********************************************************************
2294 * LISTBOX_HandleLButtonUp
2296 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2298 if (LISTBOX_Timer != LB_TIMER_NONE)
2299 KillSystemTimer( descr->self, LB_TIMER_ID );
2300 LISTBOX_Timer = LB_TIMER_NONE;
2301 if (descr->captured)
2303 descr->captured = FALSE;
2304 if (GetCapture() == descr->self) ReleaseCapture();
2305 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2306 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2308 return 0;
2312 /***********************************************************************
2313 * LISTBOX_HandleTimer
2315 * Handle scrolling upon a timer event.
2316 * Return TRUE if scrolling should continue.
2318 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2320 switch(dir)
2322 case LB_TIMER_UP:
2323 if (descr->top_item) index = descr->top_item - 1;
2324 else index = 0;
2325 break;
2326 case LB_TIMER_LEFT:
2327 if (descr->top_item) index -= descr->page_size;
2328 break;
2329 case LB_TIMER_DOWN:
2330 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2331 if (index == descr->focus_item) index++;
2332 if (index >= descr->nb_items) index = descr->nb_items - 1;
2333 break;
2334 case LB_TIMER_RIGHT:
2335 if (index + descr->page_size < descr->nb_items)
2336 index += descr->page_size;
2337 break;
2338 case LB_TIMER_NONE:
2339 break;
2341 if (index == descr->focus_item) return FALSE;
2342 LISTBOX_MoveCaret( descr, index, FALSE );
2343 return TRUE;
2347 /***********************************************************************
2348 * LISTBOX_HandleSystemTimer
2350 * WM_SYSTIMER handler.
2352 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2354 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2356 KillSystemTimer( descr->self, LB_TIMER_ID );
2357 LISTBOX_Timer = LB_TIMER_NONE;
2359 return 0;
2363 /***********************************************************************
2364 * LISTBOX_HandleMouseMove
2366 * WM_MOUSEMOVE handler.
2368 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2369 INT x, INT y )
2371 INT index;
2372 TIMER_DIRECTION dir = LB_TIMER_NONE;
2374 if (!descr->captured) return;
2376 if (descr->style & LBS_MULTICOLUMN)
2378 if (y < 0) y = 0;
2379 else if (y >= descr->item_height * descr->page_size)
2380 y = descr->item_height * descr->page_size - 1;
2382 if (x < 0)
2384 dir = LB_TIMER_LEFT;
2385 x = 0;
2387 else if (x >= descr->width)
2389 dir = LB_TIMER_RIGHT;
2390 x = descr->width - 1;
2393 else
2395 if (y < 0) dir = LB_TIMER_UP; /* above */
2396 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2399 index = LISTBOX_GetItemFromPoint( descr, x, y );
2400 if (index == -1) index = descr->focus_item;
2401 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2403 /* Start/stop the system timer */
2405 if (dir != LB_TIMER_NONE)
2406 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, 0 );
2407 else if (LISTBOX_Timer != LB_TIMER_NONE)
2408 KillSystemTimer( descr->self, LB_TIMER_ID );
2409 LISTBOX_Timer = dir;
2413 /***********************************************************************
2414 * LISTBOX_HandleKeyDown
2416 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2418 INT caret = -1;
2419 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2420 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2421 bForceSelection = FALSE; /* only for single select list */
2423 if (descr->style & LBS_WANTKEYBOARDINPUT)
2425 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2426 MAKEWPARAM(LOWORD(key), descr->focus_item),
2427 (LPARAM)descr->self );
2428 if (caret == -2) return 0;
2430 if (caret == -1) switch(key)
2432 case VK_LEFT:
2433 if (descr->style & LBS_MULTICOLUMN)
2435 bForceSelection = FALSE;
2436 if (descr->focus_item >= descr->page_size)
2437 caret = descr->focus_item - descr->page_size;
2438 break;
2440 /* fall through */
2441 case VK_UP:
2442 caret = descr->focus_item - 1;
2443 if (caret < 0) caret = 0;
2444 break;
2445 case VK_RIGHT:
2446 if (descr->style & LBS_MULTICOLUMN)
2448 bForceSelection = FALSE;
2449 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2450 break;
2452 /* fall through */
2453 case VK_DOWN:
2454 caret = descr->focus_item + 1;
2455 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2456 break;
2458 case VK_PRIOR:
2459 if (descr->style & LBS_MULTICOLUMN)
2461 INT page = descr->width / descr->column_width;
2462 if (page < 1) page = 1;
2463 caret = descr->focus_item - (page * descr->page_size) + 1;
2465 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2466 if (caret < 0) caret = 0;
2467 break;
2468 case VK_NEXT:
2469 if (descr->style & LBS_MULTICOLUMN)
2471 INT page = descr->width / descr->column_width;
2472 if (page < 1) page = 1;
2473 caret = descr->focus_item + (page * descr->page_size) - 1;
2475 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2476 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2477 break;
2478 case VK_HOME:
2479 caret = 0;
2480 break;
2481 case VK_END:
2482 caret = descr->nb_items - 1;
2483 break;
2484 case VK_SPACE:
2485 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2486 else if (descr->style & LBS_MULTIPLESEL)
2488 LISTBOX_SetSelection( descr, descr->focus_item,
2489 !is_item_selected(descr, descr->focus_item),
2490 (descr->style & LBS_NOTIFY) != 0 );
2492 break;
2493 default:
2494 bForceSelection = FALSE;
2496 if (bForceSelection) /* focused item is used instead of key */
2497 caret = descr->focus_item;
2498 if (caret >= 0)
2500 if (((descr->style & LBS_EXTENDEDSEL) &&
2501 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2502 !IS_MULTISELECT(descr))
2503 descr->anchor_item = caret;
2504 LISTBOX_MoveCaret( descr, caret, TRUE );
2506 if (descr->style & LBS_MULTIPLESEL)
2507 descr->selected_item = caret;
2508 else
2509 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2510 if (descr->style & LBS_NOTIFY)
2512 if (descr->lphc && IsWindowVisible( descr->self ))
2514 /* make sure that combo parent doesn't hide us */
2515 descr->lphc->wState |= CBF_NOROLLUP;
2517 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2520 return 0;
2524 /***********************************************************************
2525 * LISTBOX_HandleChar
2527 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2529 INT caret = -1;
2530 WCHAR str[2];
2532 str[0] = charW;
2533 str[1] = '\0';
2535 if (descr->style & LBS_WANTKEYBOARDINPUT)
2537 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2538 MAKEWPARAM(charW, descr->focus_item),
2539 (LPARAM)descr->self );
2540 if (caret == -2) return 0;
2542 if (caret == -1)
2543 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2544 if (caret != -1)
2546 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2547 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2548 LISTBOX_MoveCaret( descr, caret, TRUE );
2549 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2550 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2552 return 0;
2556 /***********************************************************************
2557 * LISTBOX_Create
2559 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2561 LB_DESCR *descr;
2562 MEASUREITEMSTRUCT mis;
2563 RECT rect;
2565 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2566 return FALSE;
2568 GetClientRect( hwnd, &rect );
2569 descr->self = hwnd;
2570 descr->owner = GetParent( descr->self );
2571 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2572 descr->width = rect.right - rect.left;
2573 descr->height = rect.bottom - rect.top;
2574 descr->u.items = NULL;
2575 descr->items_size = 0;
2576 descr->nb_items = 0;
2577 descr->top_item = 0;
2578 descr->selected_item = -1;
2579 descr->focus_item = 0;
2580 descr->anchor_item = -1;
2581 descr->item_height = 1;
2582 descr->page_size = 1;
2583 descr->column_width = 150;
2584 descr->horz_extent = 0;
2585 descr->horz_pos = 0;
2586 descr->nb_tabs = 0;
2587 descr->tabs = NULL;
2588 descr->wheel_remain = 0;
2589 descr->caret_on = !lphc;
2590 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2591 descr->in_focus = FALSE;
2592 descr->captured = FALSE;
2593 descr->font = 0;
2594 descr->locale = GetUserDefaultLCID();
2595 descr->lphc = lphc;
2597 if( lphc )
2599 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2600 descr->owner = lphc->self;
2603 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2605 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2607 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2608 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2609 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2610 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2611 descr->style &= ~LBS_NODATA;
2612 descr->item_height = LISTBOX_SetFont( descr, 0 );
2614 if (descr->style & LBS_OWNERDRAWFIXED)
2616 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2618 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2620 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2621 descr->item_height = lphc->fixedOwnerDrawHeight;
2623 else
2625 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2626 mis.CtlType = ODT_LISTBOX;
2627 mis.CtlID = id;
2628 mis.itemID = -1;
2629 mis.itemWidth = 0;
2630 mis.itemData = 0;
2631 mis.itemHeight = descr->item_height;
2632 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2633 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2637 OpenThemeData( descr->self, WC_LISTBOXW );
2639 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2640 return TRUE;
2644 /***********************************************************************
2645 * LISTBOX_Destroy
2647 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2649 HTHEME theme = GetWindowTheme( descr->self );
2650 CloseThemeData( theme );
2651 LISTBOX_ResetContent( descr );
2652 SetWindowLongPtrW( descr->self, 0, 0 );
2653 HeapFree( GetProcessHeap(), 0, descr );
2654 return TRUE;
2658 /***********************************************************************
2659 * ListBoxWndProc_common
2661 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2663 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2664 HEADCOMBO *lphc = NULL;
2665 HTHEME theme;
2666 LRESULT ret;
2668 if (!descr)
2670 if (!IsWindow(hwnd)) return 0;
2672 if (msg == WM_CREATE)
2674 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2675 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2676 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2677 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2678 return 0;
2680 /* Ignore all other messages before we get a WM_CREATE */
2681 return DefWindowProcW( hwnd, msg, wParam, lParam );
2683 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2685 TRACE("[%p]: msg %#x, wp %Ix, lp %Ix\n", descr->self, msg, wParam, lParam );
2687 switch(msg)
2689 case LB_RESETCONTENT:
2690 LISTBOX_ResetContent( descr );
2691 LISTBOX_UpdateScroll( descr );
2692 InvalidateRect( descr->self, NULL, TRUE );
2693 return 0;
2695 case LB_ADDSTRING:
2697 const WCHAR *textW = (const WCHAR *)lParam;
2698 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2699 return LISTBOX_InsertString( descr, index, textW );
2702 case LB_INSERTSTRING:
2703 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2705 case LB_ADDFILE:
2707 const WCHAR *textW = (const WCHAR *)lParam;
2708 INT index = LISTBOX_FindFileStrPos( descr, textW );
2709 return LISTBOX_InsertString( descr, index, textW );
2712 case LB_DELETESTRING:
2713 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2714 return descr->nb_items;
2715 else
2717 SetLastError(ERROR_INVALID_INDEX);
2718 return LB_ERR;
2721 case LB_GETITEMDATA:
2722 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2724 SetLastError(ERROR_INVALID_INDEX);
2725 return LB_ERR;
2727 return get_item_data(descr, wParam);
2729 case LB_SETITEMDATA:
2730 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2732 SetLastError(ERROR_INVALID_INDEX);
2733 return LB_ERR;
2735 set_item_data(descr, wParam, lParam);
2736 /* undocumented: returns TRUE, not LB_OKAY (0) */
2737 return TRUE;
2739 case LB_GETCOUNT:
2740 return descr->nb_items;
2742 case LB_GETTEXT:
2743 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2745 case LB_GETTEXTLEN:
2746 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2748 SetLastError(ERROR_INVALID_INDEX);
2749 return LB_ERR;
2751 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2752 return lstrlenW(get_item_string(descr, wParam));
2754 case LB_GETCURSEL:
2755 if (descr->nb_items == 0)
2756 return LB_ERR;
2757 if (!IS_MULTISELECT(descr))
2758 return descr->selected_item;
2759 if (descr->selected_item != -1)
2760 return descr->selected_item;
2761 return descr->focus_item;
2762 /* otherwise, if the user tries to move the selection with the */
2763 /* arrow keys, we will give the application something to choke on */
2764 case LB_GETTOPINDEX:
2765 return descr->top_item;
2767 case LB_GETITEMHEIGHT:
2768 return LISTBOX_GetItemHeight( descr, wParam );
2770 case LB_SETITEMHEIGHT:
2771 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2773 case LB_ITEMFROMPOINT:
2775 POINT pt;
2776 RECT rect;
2777 int index;
2778 BOOL hit = TRUE;
2780 /* The hiword of the return value is not a client area
2781 hittest as suggested by MSDN, but rather a hittest on
2782 the returned listbox item. */
2784 if(descr->nb_items == 0)
2785 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2787 pt.x = (short)LOWORD(lParam);
2788 pt.y = (short)HIWORD(lParam);
2790 SetRect(&rect, 0, 0, descr->width, descr->height);
2792 if(!PtInRect(&rect, pt))
2794 pt.x = min(pt.x, rect.right - 1);
2795 pt.x = max(pt.x, 0);
2796 pt.y = min(pt.y, rect.bottom - 1);
2797 pt.y = max(pt.y, 0);
2798 hit = FALSE;
2801 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2803 if(index == -1)
2805 index = descr->nb_items - 1;
2806 hit = FALSE;
2808 return MAKELONG(index, hit ? 0 : 1);
2811 case LB_SETCARETINDEX:
2812 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2813 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2814 return LB_ERR;
2815 else if (ISWIN31)
2816 return wParam;
2817 else
2818 return LB_OKAY;
2820 case LB_GETCARETINDEX:
2821 return descr->focus_item;
2823 case LB_SETTOPINDEX:
2824 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2826 case LB_SETCOLUMNWIDTH:
2827 return LISTBOX_SetColumnWidth( descr, wParam );
2829 case LB_GETITEMRECT:
2830 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2832 case LB_FINDSTRING:
2833 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2835 case LB_FINDSTRINGEXACT:
2836 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2838 case LB_SELECTSTRING:
2840 const WCHAR *textW = (const WCHAR *)lParam;
2841 INT index;
2843 if (HAS_STRINGS(descr))
2844 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2846 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2847 if (index != LB_ERR)
2849 LISTBOX_MoveCaret( descr, index, TRUE );
2850 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2852 return index;
2855 case LB_GETSEL:
2856 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2857 return LB_ERR;
2858 return is_item_selected(descr, wParam);
2860 case LB_SETSEL:
2861 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2862 if (ret != LB_ERR && wParam)
2864 descr->anchor_item = lParam;
2865 if (lParam != -1)
2866 LISTBOX_SetCaretIndex( descr, lParam, TRUE );
2868 return ret;
2870 case LB_SETCURSEL:
2871 if (IS_MULTISELECT(descr)) return LB_ERR;
2872 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2873 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2874 if (ret != LB_ERR) ret = descr->selected_item;
2875 return ret;
2877 case LB_GETSELCOUNT:
2878 return LISTBOX_GetSelCount( descr );
2880 case LB_GETSELITEMS:
2881 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2883 case LB_SELITEMRANGE:
2884 if (LOWORD(lParam) <= HIWORD(lParam))
2885 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2886 HIWORD(lParam), wParam );
2887 else
2888 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2889 LOWORD(lParam), wParam );
2891 case LB_SELITEMRANGEEX:
2892 if ((INT)lParam >= (INT)wParam)
2893 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2894 else
2895 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2897 case LB_GETHORIZONTALEXTENT:
2898 return descr->horz_extent;
2900 case LB_SETHORIZONTALEXTENT:
2901 return LISTBOX_SetHorizontalExtent( descr, wParam );
2903 case LB_GETANCHORINDEX:
2904 return descr->anchor_item;
2906 case LB_SETANCHORINDEX:
2907 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2909 SetLastError(ERROR_INVALID_INDEX);
2910 return LB_ERR;
2912 descr->anchor_item = (INT)wParam;
2913 return LB_OKAY;
2915 case LB_DIR:
2916 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2918 case LB_GETLOCALE:
2919 return descr->locale;
2921 case LB_SETLOCALE:
2923 LCID ret;
2924 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2925 return LB_ERR;
2926 ret = descr->locale;
2927 descr->locale = (LCID)wParam;
2928 return ret;
2931 case LB_INITSTORAGE:
2932 return LISTBOX_InitStorage( descr, wParam );
2934 case LB_SETCOUNT:
2935 return LISTBOX_SetCount( descr, (INT)wParam );
2937 case LB_SETTABSTOPS:
2938 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2940 case LB_CARETON:
2941 if (descr->caret_on)
2942 return LB_OKAY;
2943 descr->caret_on = TRUE;
2944 if ((descr->focus_item != -1) && (descr->in_focus))
2945 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2946 return LB_OKAY;
2948 case LB_CARETOFF:
2949 if (!descr->caret_on)
2950 return LB_OKAY;
2951 descr->caret_on = FALSE;
2952 if ((descr->focus_item != -1) && (descr->in_focus))
2953 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2954 return LB_OKAY;
2956 case LB_GETLISTBOXINFO:
2957 return descr->page_size;
2959 case WM_DESTROY:
2960 return LISTBOX_Destroy( descr );
2962 case WM_ENABLE:
2963 InvalidateRect( descr->self, NULL, TRUE );
2964 return 0;
2966 case WM_SETREDRAW:
2967 LISTBOX_SetRedraw( descr, wParam != 0 );
2968 return 0;
2970 case WM_GETDLGCODE:
2971 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2973 case WM_PRINTCLIENT:
2974 case WM_PAINT:
2976 PAINTSTRUCT ps;
2977 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2978 ret = LISTBOX_Paint( descr, hdc );
2979 if( !wParam ) EndPaint( descr->self, &ps );
2981 return ret;
2983 case WM_NCPAINT:
2984 return LISTBOX_NCPaint( descr, (HRGN)wParam );
2986 case WM_SIZE:
2987 LISTBOX_UpdateSize( descr );
2988 return 0;
2989 case WM_GETFONT:
2990 return (LRESULT)descr->font;
2991 case WM_SETFONT:
2992 LISTBOX_SetFont( descr, (HFONT)wParam );
2993 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2994 return 0;
2995 case WM_SETFOCUS:
2996 descr->in_focus = TRUE;
2997 descr->caret_on = TRUE;
2998 if (descr->focus_item != -1)
2999 LISTBOX_DrawFocusRect( descr, TRUE );
3000 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3001 return 0;
3002 case WM_KILLFOCUS:
3003 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3004 descr->in_focus = FALSE;
3005 descr->wheel_remain = 0;
3006 if ((descr->focus_item != -1) && descr->caret_on)
3007 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3008 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3009 return 0;
3010 case WM_HSCROLL:
3011 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3012 case WM_VSCROLL:
3013 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3014 case WM_MOUSEWHEEL:
3015 if (wParam & (MK_SHIFT | MK_CONTROL))
3016 return DefWindowProcW( descr->self, msg, wParam, lParam );
3017 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3018 case WM_LBUTTONDOWN:
3019 if (lphc)
3020 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3021 (INT16)LOWORD(lParam),
3022 (INT16)HIWORD(lParam) );
3023 return LISTBOX_HandleLButtonDown( descr, wParam,
3024 (INT16)LOWORD(lParam),
3025 (INT16)HIWORD(lParam) );
3026 case WM_LBUTTONDBLCLK:
3027 if (lphc)
3028 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3029 (INT16)LOWORD(lParam),
3030 (INT16)HIWORD(lParam) );
3031 if (descr->style & LBS_NOTIFY)
3032 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3033 return 0;
3034 case WM_MOUSEMOVE:
3035 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3037 BOOL captured = descr->captured;
3038 POINT mousePos;
3039 RECT clientRect;
3041 mousePos.x = (INT16)LOWORD(lParam);
3042 mousePos.y = (INT16)HIWORD(lParam);
3045 * If we are in a dropdown combobox, we simulate that
3046 * the mouse is captured to show the tracking of the item.
3048 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3049 descr->captured = TRUE;
3051 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3053 descr->captured = captured;
3055 else if (GetCapture() == descr->self)
3057 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3058 (INT16)HIWORD(lParam) );
3060 return 0;
3061 case WM_LBUTTONUP:
3062 if (lphc)
3064 POINT mousePos;
3065 RECT clientRect;
3068 * If the mouse button "up" is not in the listbox,
3069 * we make sure there is no selection by re-selecting the
3070 * item that was selected when the listbox was made visible.
3072 mousePos.x = (INT16)LOWORD(lParam);
3073 mousePos.y = (INT16)HIWORD(lParam);
3075 GetClientRect(descr->self, &clientRect);
3078 * When the user clicks outside the combobox and the focus
3079 * is lost, the owning combobox will send a fake buttonup with
3080 * 0xFFFFFFF as the mouse location, we must also revert the
3081 * selection to the original selection.
3083 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3084 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3086 return LISTBOX_HandleLButtonUp( descr );
3087 case WM_KEYDOWN:
3088 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3090 /* for some reason Windows makes it possible to
3091 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3093 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3094 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3095 && (wParam == VK_DOWN || wParam == VK_UP)) )
3097 COMBO_FlipListbox( lphc, FALSE, FALSE );
3098 return 0;
3101 return LISTBOX_HandleKeyDown( descr, wParam );
3102 case WM_CHAR:
3103 return LISTBOX_HandleChar( descr, wParam );
3105 case WM_SYSTIMER:
3106 return LISTBOX_HandleSystemTimer( descr );
3107 case WM_ERASEBKGND:
3108 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3110 RECT rect;
3111 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3112 wParam, (LPARAM)descr->self );
3113 TRACE("hbrush = %p\n", hbrush);
3114 if(!hbrush)
3115 hbrush = GetSysColorBrush(COLOR_WINDOW);
3116 if(hbrush)
3118 GetClientRect(descr->self, &rect);
3119 FillRect((HDC)wParam, &rect, hbrush);
3122 return 1;
3123 case WM_DROPFILES:
3124 if( lphc ) return 0;
3125 return SendMessageW( descr->owner, msg, wParam, lParam );
3127 case WM_NCDESTROY:
3128 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3129 lphc->hWndLBox = 0;
3130 break;
3132 case WM_NCACTIVATE:
3133 if (lphc) return 0;
3134 break;
3136 case WM_THEMECHANGED:
3137 theme = GetWindowTheme( hwnd );
3138 CloseThemeData( theme );
3139 OpenThemeData( hwnd, WC_LISTBOXW );
3140 InvalidateRect( hwnd, NULL, TRUE );
3141 break;
3143 default:
3144 if ((msg >= WM_USER) && (msg < 0xc000))
3145 WARN("[%p]: unknown msg %04x, wp %Ix, lp %Ix\n", hwnd, msg, wParam, lParam );
3148 return DefWindowProcW( hwnd, msg, wParam, lParam );
3151 void LISTBOX_Register(void)
3153 WNDCLASSW wndClass;
3155 memset(&wndClass, 0, sizeof(wndClass));
3156 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3157 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3158 wndClass.cbClsExtra = 0;
3159 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3160 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3161 wndClass.hbrBackground = NULL;
3162 wndClass.lpszClassName = WC_LISTBOXW;
3163 RegisterClassW(&wndClass);
3166 void COMBOLBOX_Register(void)
3168 WNDCLASSW wndClass;
3170 memset(&wndClass, 0, sizeof(wndClass));
3171 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
3172 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3173 wndClass.cbClsExtra = 0;
3174 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3175 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3176 wndClass.hbrBackground = NULL;
3177 wndClass.lpszClassName = L"ComboLBox";
3178 RegisterClassW(&wndClass);