d3d8: Stop setting the device state in d3d8_device_SetPixelShaderConstant().
[wine.git] / dlls / comctl32 / listbox.c
blobf9b4e0aeafe37a71f42b53711a5ac1b37cc57283
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 void 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;
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);
1214 OffsetRect(&r, -r.left, -r.top);
1216 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
1217 DrawThemeParentBackground(descr->self, hdc, &r);
1218 DrawThemeBackground (theme, hdc, 0, 0, &r, 0);
1219 ReleaseDC(descr->self, hdc);
1222 /***********************************************************************
1223 * LISTBOX_InvalidateItems
1225 * Invalidate all items from a given item. If the specified item is not
1226 * visible, nothing happens.
1228 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1230 RECT rect;
1232 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1234 if (descr->style & LBS_NOREDRAW)
1236 descr->style |= LBS_DISPLAYCHANGED;
1237 return;
1239 rect.bottom = descr->height;
1240 InvalidateRect( descr->self, &rect, TRUE );
1241 if (descr->style & LBS_MULTICOLUMN)
1243 /* Repaint the other columns */
1244 rect.left = rect.right;
1245 rect.right = descr->width;
1246 rect.top = 0;
1247 InvalidateRect( descr->self, &rect, TRUE );
1252 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1254 RECT rect;
1256 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1257 InvalidateRect( descr->self, &rect, TRUE );
1260 /***********************************************************************
1261 * LISTBOX_GetItemHeight
1263 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1265 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1267 if ((index < 0) || (index >= descr->nb_items))
1269 SetLastError(ERROR_INVALID_INDEX);
1270 return LB_ERR;
1272 return get_item_height(descr, index);
1274 else return descr->item_height;
1278 /***********************************************************************
1279 * LISTBOX_SetItemHeight
1281 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1283 if (height > MAXWORD)
1284 return -1;
1286 if (!height) height = 1;
1288 if (descr->style & LBS_OWNERDRAWVARIABLE)
1290 if ((index < 0) || (index >= descr->nb_items))
1292 SetLastError(ERROR_INVALID_INDEX);
1293 return LB_ERR;
1295 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1296 set_item_height(descr, index, height);
1297 LISTBOX_UpdateScroll( descr );
1298 if (repaint)
1299 LISTBOX_InvalidateItems( descr, index );
1301 else if (height != descr->item_height)
1303 TRACE("[%p]: new height = %d\n", descr->self, height );
1304 descr->item_height = height;
1305 LISTBOX_UpdatePage( descr );
1306 LISTBOX_UpdateScroll( descr );
1307 if (repaint)
1308 InvalidateRect( descr->self, 0, TRUE );
1310 return LB_OKAY;
1314 /***********************************************************************
1315 * LISTBOX_SetHorizontalPos
1317 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1319 INT diff;
1321 if (pos > descr->horz_extent - descr->width)
1322 pos = descr->horz_extent - descr->width;
1323 if (pos < 0) pos = 0;
1324 if (!(diff = descr->horz_pos - pos)) return;
1325 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1326 descr->horz_pos = pos;
1327 LISTBOX_UpdateScroll( descr );
1328 if (abs(diff) < descr->width)
1330 RECT rect;
1331 /* Invalidate the focused item so it will be repainted correctly */
1332 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1333 InvalidateRect( descr->self, &rect, TRUE );
1334 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1335 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1337 else
1338 InvalidateRect( descr->self, NULL, TRUE );
1342 /***********************************************************************
1343 * LISTBOX_SetHorizontalExtent
1345 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1347 if (descr->style & LBS_MULTICOLUMN)
1348 return LB_OKAY;
1349 if (extent == descr->horz_extent) return LB_OKAY;
1350 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1351 descr->horz_extent = extent;
1352 if (descr->style & WS_HSCROLL) {
1353 SCROLLINFO info;
1354 info.cbSize = sizeof(info);
1355 info.nMin = 0;
1356 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1357 info.fMask = SIF_RANGE;
1358 if (descr->style & LBS_DISABLENOSCROLL)
1359 info.fMask |= SIF_DISABLENOSCROLL;
1360 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1362 if (descr->horz_pos > extent - descr->width)
1363 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1364 return LB_OKAY;
1368 /***********************************************************************
1369 * LISTBOX_SetColumnWidth
1371 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1373 RECT rect;
1375 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1377 GetClientRect(descr->self, &rect);
1378 descr->width = rect.right - rect.left;
1379 descr->height = rect.bottom - rect.top;
1380 descr->column_width = column_width;
1382 LISTBOX_UpdatePage(descr);
1383 LISTBOX_UpdateScroll(descr);
1384 return LB_OKAY;
1388 /***********************************************************************
1389 * LISTBOX_SetFont
1391 * Returns the item height.
1393 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1395 HDC hdc;
1396 HFONT oldFont = 0;
1397 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1398 SIZE sz;
1400 descr->font = font;
1402 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1404 ERR("unable to get DC.\n" );
1405 return 16;
1407 if (font) oldFont = SelectObject( hdc, font );
1408 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1409 if (oldFont) SelectObject( hdc, oldFont );
1410 ReleaseDC( descr->self, hdc );
1412 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1413 if (!IS_OWNERDRAW(descr))
1414 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1415 return sz.cy;
1419 /***********************************************************************
1420 * LISTBOX_MakeItemVisible
1422 * Make sure that a given item is partially or fully visible.
1424 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1426 INT top;
1428 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1430 if (index <= descr->top_item) top = index;
1431 else if (descr->style & LBS_MULTICOLUMN)
1433 INT cols = descr->width;
1434 if (!fully) cols += descr->column_width - 1;
1435 if (cols >= descr->column_width) cols /= descr->column_width;
1436 else cols = 1;
1437 if (index < descr->top_item + (descr->page_size * cols)) return;
1438 top = index - descr->page_size * (cols - 1);
1440 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1442 INT height = fully ? get_item_height(descr, index) : 1;
1443 for (top = index; top > descr->top_item; top--)
1444 if ((height += get_item_height(descr, top - 1)) > descr->height) break;
1446 else
1448 if (index < descr->top_item + descr->page_size) return;
1449 if (!fully && (index == descr->top_item + descr->page_size) &&
1450 (descr->height > (descr->page_size * descr->item_height))) return;
1451 top = index - descr->page_size + 1;
1453 LISTBOX_SetTopItem( descr, top, TRUE );
1456 /***********************************************************************
1457 * LISTBOX_SetCaretIndex
1459 * NOTES
1460 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1463 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1465 BOOL focus_changed = descr->focus_item != index;
1467 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1469 if (descr->style & LBS_NOSEL) return LB_ERR;
1470 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1472 if (focus_changed)
1474 LISTBOX_DrawFocusRect( descr, FALSE );
1475 descr->focus_item = index;
1478 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1480 if (focus_changed)
1481 LISTBOX_DrawFocusRect( descr, TRUE );
1483 return LB_OKAY;
1487 /***********************************************************************
1488 * LISTBOX_SelectItemRange
1490 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1492 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1493 INT last, BOOL on )
1495 INT i;
1497 /* A few sanity checks */
1499 if (descr->style & LBS_NOSEL) return LB_ERR;
1500 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1502 if (!descr->nb_items) return LB_OKAY;
1504 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1505 if (first < 0) first = 0;
1506 if (last < first) return LB_OKAY;
1508 if (on) /* Turn selection on */
1510 for (i = first; i <= last; i++)
1512 if (is_item_selected(descr, i)) continue;
1513 set_item_selected_state(descr, i, TRUE);
1514 LISTBOX_InvalidateItemRect(descr, i);
1517 else /* Turn selection off */
1519 for (i = first; i <= last; i++)
1521 if (!is_item_selected(descr, i)) continue;
1522 set_item_selected_state(descr, i, FALSE);
1523 LISTBOX_InvalidateItemRect(descr, i);
1526 return LB_OKAY;
1529 /***********************************************************************
1530 * LISTBOX_SetSelection
1532 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1533 BOOL on, BOOL send_notify )
1535 TRACE( "cur_sel=%d index=%d notify=%s\n",
1536 descr->selected_item, index, send_notify ? "YES" : "NO" );
1538 if (descr->style & LBS_NOSEL)
1540 descr->selected_item = index;
1541 return LB_ERR;
1543 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1544 if (descr->style & LBS_MULTIPLESEL)
1546 if (index == -1) /* Select all items */
1547 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1548 else /* Only one item */
1549 return LISTBOX_SelectItemRange( descr, index, index, on );
1551 else
1553 INT oldsel = descr->selected_item;
1554 if (index == oldsel) return LB_OKAY;
1555 if (oldsel != -1) set_item_selected_state(descr, oldsel, FALSE);
1556 if (index != -1) set_item_selected_state(descr, index, TRUE);
1557 descr->selected_item = index;
1558 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1559 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1560 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1561 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1562 else
1563 if( descr->lphc ) /* set selection change flag for parent combo */
1564 descr->lphc->wState |= CBF_SELCHANGE;
1566 return LB_OKAY;
1570 /***********************************************************************
1571 * LISTBOX_MoveCaret
1573 * Change the caret position and extend the selection to the new caret.
1575 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1577 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1579 if ((index < 0) || (index >= descr->nb_items))
1580 return;
1582 /* Important, repaint needs to be done in this order if
1583 you want to mimic Windows behavior:
1584 1. Remove the focus and paint the item
1585 2. Remove the selection and paint the item(s)
1586 3. Set the selection and repaint the item(s)
1587 4. Set the focus to 'index' and repaint the item */
1589 /* 1. remove the focus and repaint the item */
1590 LISTBOX_DrawFocusRect( descr, FALSE );
1592 /* 2. then turn off the previous selection */
1593 /* 3. repaint the new selected item */
1594 if (descr->style & LBS_EXTENDEDSEL)
1596 if (descr->anchor_item != -1)
1598 INT first = min( index, descr->anchor_item );
1599 INT last = max( index, descr->anchor_item );
1600 if (first > 0)
1601 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1602 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1603 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1606 else if (!(descr->style & LBS_MULTIPLESEL))
1608 /* Set selection to new caret item */
1609 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1612 /* 4. repaint the new item with the focus */
1613 descr->focus_item = index;
1614 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1615 LISTBOX_DrawFocusRect( descr, TRUE );
1619 /***********************************************************************
1620 * LISTBOX_InsertItem
1622 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1623 LPWSTR str, ULONG_PTR data )
1625 INT oldfocus = descr->focus_item;
1627 if (index == -1) index = descr->nb_items;
1628 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1629 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1631 insert_item_data(descr, index);
1632 descr->nb_items++;
1633 set_item_string(descr, index, str);
1634 set_item_data(descr, index, HAS_STRINGS(descr) ? 0 : data);
1635 set_item_height(descr, index, 0);
1636 set_item_selected_state(descr, index, FALSE);
1638 /* Get item height */
1640 if (descr->style & LBS_OWNERDRAWVARIABLE)
1642 MEASUREITEMSTRUCT mis;
1643 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1645 mis.CtlType = ODT_LISTBOX;
1646 mis.CtlID = id;
1647 mis.itemID = index;
1648 mis.itemData = data;
1649 mis.itemHeight = descr->item_height;
1650 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1651 set_item_height(descr, index, mis.itemHeight ? mis.itemHeight : 1);
1652 TRACE("[%p]: measure item %d (%s) = %d\n",
1653 descr->self, index, str ? debugstr_w(str) : "", get_item_height(descr, index));
1656 /* Repaint the items */
1658 LISTBOX_UpdateScroll( descr );
1659 LISTBOX_InvalidateItems( descr, index );
1661 /* Move selection and focused item */
1662 /* If listbox was empty, set focus to the first item */
1663 if (descr->nb_items == 1)
1664 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1665 /* single select don't change selection index in win31 */
1666 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1668 descr->selected_item++;
1669 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1671 else
1673 if (index <= descr->selected_item)
1675 descr->selected_item++;
1676 descr->focus_item = oldfocus; /* focus not changed */
1679 return LB_OKAY;
1683 /***********************************************************************
1684 * LISTBOX_InsertString
1686 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1688 LPWSTR new_str = NULL;
1689 LRESULT ret;
1691 if (HAS_STRINGS(descr))
1693 static const WCHAR empty_stringW[] = { 0 };
1694 if (!str) str = empty_stringW;
1695 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str) + 1) * sizeof(WCHAR) )))
1697 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1698 return LB_ERRSPACE;
1700 lstrcpyW(new_str, str);
1703 if (index == -1) index = descr->nb_items;
1704 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1706 HeapFree( GetProcessHeap(), 0, new_str );
1707 return ret;
1710 TRACE("[%p]: added item %d %s\n",
1711 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1712 return index;
1716 /***********************************************************************
1717 * LISTBOX_DeleteItem
1719 * Delete the content of an item. 'index' must be a valid index.
1721 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1723 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1724 * while Win95 sends it for all items with user data.
1725 * It's probably better to send it too often than not
1726 * often enough, so this is what we do here.
1728 if (IS_OWNERDRAW(descr) || get_item_data(descr, index))
1730 DELETEITEMSTRUCT dis;
1731 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1733 dis.CtlType = ODT_LISTBOX;
1734 dis.CtlID = id;
1735 dis.itemID = index;
1736 dis.hwndItem = descr->self;
1737 dis.itemData = get_item_data(descr, index);
1738 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1740 HeapFree( GetProcessHeap(), 0, get_item_string(descr, index) );
1744 /***********************************************************************
1745 * LISTBOX_RemoveItem
1747 * Remove an item from the listbox and delete its content.
1749 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1751 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1753 /* We need to invalidate the original rect instead of the updated one. */
1754 LISTBOX_InvalidateItems( descr, index );
1756 if (descr->nb_items == 1)
1758 SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1759 return LB_OKAY;
1761 descr->nb_items--;
1762 LISTBOX_DeleteItem( descr, index );
1763 remove_item_data(descr, index);
1765 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1766 resize_storage(descr, descr->nb_items);
1768 /* Repaint the items */
1770 LISTBOX_UpdateScroll( descr );
1771 /* if we removed the scrollbar, reset the top of the list
1772 (correct for owner-drawn ???) */
1773 if (descr->nb_items == descr->page_size)
1774 LISTBOX_SetTopItem( descr, 0, TRUE );
1776 /* Move selection and focused item */
1777 if (!IS_MULTISELECT(descr))
1779 if (index == descr->selected_item)
1780 descr->selected_item = -1;
1781 else if (index < descr->selected_item)
1783 descr->selected_item--;
1784 if (ISWIN31) /* win 31 do not change the selected item number */
1785 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1789 if (descr->focus_item >= descr->nb_items)
1791 descr->focus_item = descr->nb_items - 1;
1792 if (descr->focus_item < 0) descr->focus_item = 0;
1794 return LB_OKAY;
1798 /***********************************************************************
1799 * LISTBOX_ResetContent
1801 static void LISTBOX_ResetContent( LB_DESCR *descr )
1803 INT i;
1805 if (!(descr->style & LBS_NODATA))
1806 for (i = descr->nb_items - 1; i >= 0; i--) LISTBOX_DeleteItem(descr, i);
1807 HeapFree( GetProcessHeap(), 0, descr->u.items );
1808 descr->nb_items = 0;
1809 descr->top_item = 0;
1810 descr->selected_item = -1;
1811 descr->focus_item = 0;
1812 descr->anchor_item = -1;
1813 descr->items_size = 0;
1814 descr->u.items = NULL;
1818 /***********************************************************************
1819 * LISTBOX_SetCount
1821 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1823 UINT orig_num = descr->nb_items;
1825 if (!(descr->style & LBS_NODATA)) return LB_ERR;
1827 if (!resize_storage(descr, count))
1828 return LB_ERRSPACE;
1829 descr->nb_items = count;
1831 if (count)
1833 LISTBOX_UpdateScroll(descr);
1834 if (count < orig_num)
1836 descr->anchor_item = min(descr->anchor_item, count - 1);
1837 if (descr->selected_item >= count)
1838 descr->selected_item = -1;
1840 /* If we removed the scrollbar, reset the top of the list */
1841 if (count <= descr->page_size && orig_num > descr->page_size)
1842 LISTBOX_SetTopItem(descr, 0, TRUE);
1844 descr->focus_item = min(descr->focus_item, count - 1);
1847 /* If it was empty before growing, set focus to the first item */
1848 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1850 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1852 InvalidateRect( descr->self, NULL, TRUE );
1853 return LB_OKAY;
1857 /***********************************************************************
1858 * LISTBOX_Directory
1860 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1861 LPCWSTR filespec, BOOL long_names )
1863 HANDLE handle;
1864 LRESULT ret = LB_OKAY;
1865 WIN32_FIND_DATAW entry;
1866 int pos;
1867 LRESULT maxinsert = LB_ERR;
1869 /* don't scan directory if we just want drives exclusively */
1870 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1871 /* scan directory */
1872 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1874 int le = GetLastError();
1875 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1877 else
1881 WCHAR buffer[270];
1882 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1884 static const WCHAR bracketW[] = { ']',0 };
1885 static const WCHAR dotW[] = { '.',0 };
1886 if (!(attrib & DDL_DIRECTORY) ||
1887 !lstrcmpW( entry.cFileName, dotW )) continue;
1888 buffer[0] = '[';
1889 if (!long_names && entry.cAlternateFileName[0])
1890 lstrcpyW( buffer + 1, entry.cAlternateFileName );
1891 else
1892 lstrcpyW( buffer + 1, entry.cFileName );
1893 lstrcatW(buffer, bracketW);
1895 else /* not a directory */
1897 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1898 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1900 if ((attrib & DDL_EXCLUSIVE) &&
1901 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1902 continue;
1903 #undef ATTRIBS
1904 if (!long_names && entry.cAlternateFileName[0])
1905 lstrcpyW( buffer, entry.cAlternateFileName );
1906 else
1907 lstrcpyW( buffer, entry.cFileName );
1909 if (!long_names) CharLowerW( buffer );
1910 pos = LISTBOX_FindFileStrPos( descr, buffer );
1911 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1912 break;
1913 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1914 } while (FindNextFileW( handle, &entry ));
1915 FindClose( handle );
1918 if (ret >= 0)
1920 ret = maxinsert;
1922 /* scan drives */
1923 if (attrib & DDL_DRIVES)
1925 WCHAR buffer[] = {'[','-','a','-',']',0};
1926 WCHAR root[] = {'A',':','\\',0};
1927 int drive;
1928 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1930 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1931 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1932 break;
1936 return ret;
1940 /***********************************************************************
1941 * LISTBOX_HandleVScroll
1943 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1945 SCROLLINFO info;
1947 if (descr->style & LBS_MULTICOLUMN) return 0;
1948 switch(scrollReq)
1950 case SB_LINEUP:
1951 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1952 break;
1953 case SB_LINEDOWN:
1954 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1955 break;
1956 case SB_PAGEUP:
1957 LISTBOX_SetTopItem( descr, descr->top_item -
1958 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1959 break;
1960 case SB_PAGEDOWN:
1961 LISTBOX_SetTopItem( descr, descr->top_item +
1962 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1963 break;
1964 case SB_THUMBPOSITION:
1965 LISTBOX_SetTopItem( descr, pos, TRUE );
1966 break;
1967 case SB_THUMBTRACK:
1968 info.cbSize = sizeof(info);
1969 info.fMask = SIF_TRACKPOS;
1970 GetScrollInfo( descr->self, SB_VERT, &info );
1971 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1972 break;
1973 case SB_TOP:
1974 LISTBOX_SetTopItem( descr, 0, TRUE );
1975 break;
1976 case SB_BOTTOM:
1977 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1978 break;
1980 return 0;
1984 /***********************************************************************
1985 * LISTBOX_HandleHScroll
1987 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1989 SCROLLINFO info;
1990 INT page;
1992 if (descr->style & LBS_MULTICOLUMN)
1994 switch(scrollReq)
1996 case SB_LINELEFT:
1997 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1998 TRUE );
1999 break;
2000 case SB_LINERIGHT:
2001 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
2002 TRUE );
2003 break;
2004 case SB_PAGELEFT:
2005 page = descr->width / descr->column_width;
2006 if (page < 1) page = 1;
2007 LISTBOX_SetTopItem( descr,
2008 descr->top_item - page * descr->page_size, TRUE );
2009 break;
2010 case SB_PAGERIGHT:
2011 page = descr->width / descr->column_width;
2012 if (page < 1) page = 1;
2013 LISTBOX_SetTopItem( descr,
2014 descr->top_item + page * descr->page_size, TRUE );
2015 break;
2016 case SB_THUMBPOSITION:
2017 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
2018 break;
2019 case SB_THUMBTRACK:
2020 info.cbSize = sizeof(info);
2021 info.fMask = SIF_TRACKPOS;
2022 GetScrollInfo( descr->self, SB_VERT, &info );
2023 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
2024 TRUE );
2025 break;
2026 case SB_LEFT:
2027 LISTBOX_SetTopItem( descr, 0, TRUE );
2028 break;
2029 case SB_RIGHT:
2030 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2031 break;
2034 else if (descr->horz_extent)
2036 switch(scrollReq)
2038 case SB_LINELEFT:
2039 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2040 break;
2041 case SB_LINERIGHT:
2042 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2043 break;
2044 case SB_PAGELEFT:
2045 LISTBOX_SetHorizontalPos( descr,
2046 descr->horz_pos - descr->width );
2047 break;
2048 case SB_PAGERIGHT:
2049 LISTBOX_SetHorizontalPos( descr,
2050 descr->horz_pos + descr->width );
2051 break;
2052 case SB_THUMBPOSITION:
2053 LISTBOX_SetHorizontalPos( descr, pos );
2054 break;
2055 case SB_THUMBTRACK:
2056 info.cbSize = sizeof(info);
2057 info.fMask = SIF_TRACKPOS;
2058 GetScrollInfo( descr->self, SB_HORZ, &info );
2059 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2060 break;
2061 case SB_LEFT:
2062 LISTBOX_SetHorizontalPos( descr, 0 );
2063 break;
2064 case SB_RIGHT:
2065 LISTBOX_SetHorizontalPos( descr,
2066 descr->horz_extent - descr->width );
2067 break;
2070 return 0;
2073 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2075 INT pulScrollLines = 3;
2077 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2079 /* if scrolling changes direction, ignore left overs */
2080 if ((delta < 0 && descr->wheel_remain < 0) ||
2081 (delta > 0 && descr->wheel_remain > 0))
2082 descr->wheel_remain += delta;
2083 else
2084 descr->wheel_remain = delta;
2086 if (descr->wheel_remain && pulScrollLines)
2088 int cLineScroll;
2089 if (descr->style & LBS_MULTICOLUMN)
2091 pulScrollLines = min(descr->width / descr->column_width, pulScrollLines);
2092 pulScrollLines = max(1, pulScrollLines);
2093 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2094 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2095 cLineScroll *= descr->page_size;
2097 else
2099 pulScrollLines = min(descr->page_size, pulScrollLines);
2100 cLineScroll = pulScrollLines * descr->wheel_remain / WHEEL_DELTA;
2101 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / pulScrollLines;
2103 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2105 return 0;
2108 /***********************************************************************
2109 * LISTBOX_HandleLButtonDown
2111 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2113 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2115 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2116 descr->self, x, y, index, descr->focus_item);
2118 if (!descr->caret_on && (descr->in_focus)) return 0;
2120 if (!descr->in_focus)
2122 if( !descr->lphc ) SetFocus( descr->self );
2123 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2126 if (index == -1) return 0;
2128 if (!descr->lphc)
2130 if (descr->style & LBS_NOTIFY )
2131 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2132 MAKELPARAM( x, y ) );
2135 descr->captured = TRUE;
2136 SetCapture( descr->self );
2138 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2140 /* we should perhaps make sure that all items are deselected
2141 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2142 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2143 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2146 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2147 if (keys & MK_CONTROL)
2149 LISTBOX_SetCaretIndex( descr, index, FALSE );
2150 LISTBOX_SetSelection( descr, index,
2151 !is_item_selected(descr, index),
2152 (descr->style & LBS_NOTIFY) != 0);
2154 else
2156 LISTBOX_MoveCaret( descr, index, FALSE );
2158 if (descr->style & LBS_EXTENDEDSEL)
2160 LISTBOX_SetSelection( descr, index,
2161 is_item_selected(descr, index),
2162 (descr->style & LBS_NOTIFY) != 0 );
2164 else
2166 LISTBOX_SetSelection( descr, index,
2167 !is_item_selected(descr, index),
2168 (descr->style & LBS_NOTIFY) != 0 );
2172 else
2174 descr->anchor_item = index;
2175 LISTBOX_MoveCaret( descr, index, FALSE );
2176 LISTBOX_SetSelection( descr, index,
2177 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2180 if (!descr->lphc)
2182 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2184 POINT pt;
2186 pt.x = x;
2187 pt.y = y;
2189 if (DragDetect( descr->self, pt ))
2190 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2193 return 0;
2197 /*************************************************************************
2198 * LISTBOX_HandleLButtonDownCombo [Internal]
2200 * Process LButtonDown message for the ComboListBox
2202 * PARAMS
2203 * pWnd [I] The windows internal structure
2204 * pDescr [I] The ListBox internal structure
2205 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2206 * x [I] X Mouse Coordinate
2207 * y [I] Y Mouse Coordinate
2209 * RETURNS
2210 * 0 since we are processing the WM_LBUTTONDOWN Message
2212 * NOTES
2213 * This function is only to be used when a ListBox is a ComboListBox
2216 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2218 RECT clientRect, screenRect;
2219 POINT mousePos;
2221 mousePos.x = x;
2222 mousePos.y = y;
2224 GetClientRect(descr->self, &clientRect);
2226 if(PtInRect(&clientRect, mousePos))
2228 /* MousePos is in client, resume normal processing */
2229 if (msg == WM_LBUTTONDOWN)
2231 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2232 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2234 else if (descr->style & LBS_NOTIFY)
2235 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2237 else
2239 POINT screenMousePos;
2240 HWND hWndOldCapture;
2242 /* Check the Non-Client Area */
2243 screenMousePos = mousePos;
2244 hWndOldCapture = GetCapture();
2245 ReleaseCapture();
2246 GetWindowRect(descr->self, &screenRect);
2247 ClientToScreen(descr->self, &screenMousePos);
2249 if(!PtInRect(&screenRect, screenMousePos))
2251 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2252 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2253 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2255 else
2257 /* Check to see the NC is a scrollbar */
2258 INT nHitTestType=0;
2259 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2260 /* Check Vertical scroll bar */
2261 if (style & WS_VSCROLL)
2263 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2264 if (PtInRect( &clientRect, mousePos ))
2265 nHitTestType = HTVSCROLL;
2267 /* Check horizontal scroll bar */
2268 if (style & WS_HSCROLL)
2270 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2271 if (PtInRect( &clientRect, mousePos ))
2272 nHitTestType = HTHSCROLL;
2274 /* Windows sends this message when a scrollbar is clicked
2277 if(nHitTestType != 0)
2279 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2280 MAKELONG(screenMousePos.x, screenMousePos.y));
2282 /* Resume the Capture after scrolling is complete
2284 if(hWndOldCapture != 0)
2285 SetCapture(hWndOldCapture);
2288 return 0;
2291 /***********************************************************************
2292 * LISTBOX_HandleLButtonUp
2294 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2296 if (LISTBOX_Timer != LB_TIMER_NONE)
2297 KillSystemTimer( descr->self, LB_TIMER_ID );
2298 LISTBOX_Timer = LB_TIMER_NONE;
2299 if (descr->captured)
2301 descr->captured = FALSE;
2302 if (GetCapture() == descr->self) ReleaseCapture();
2303 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2304 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2306 return 0;
2310 /***********************************************************************
2311 * LISTBOX_HandleTimer
2313 * Handle scrolling upon a timer event.
2314 * Return TRUE if scrolling should continue.
2316 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2318 switch(dir)
2320 case LB_TIMER_UP:
2321 if (descr->top_item) index = descr->top_item - 1;
2322 else index = 0;
2323 break;
2324 case LB_TIMER_LEFT:
2325 if (descr->top_item) index -= descr->page_size;
2326 break;
2327 case LB_TIMER_DOWN:
2328 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2329 if (index == descr->focus_item) index++;
2330 if (index >= descr->nb_items) index = descr->nb_items - 1;
2331 break;
2332 case LB_TIMER_RIGHT:
2333 if (index + descr->page_size < descr->nb_items)
2334 index += descr->page_size;
2335 break;
2336 case LB_TIMER_NONE:
2337 break;
2339 if (index == descr->focus_item) return FALSE;
2340 LISTBOX_MoveCaret( descr, index, FALSE );
2341 return TRUE;
2345 /***********************************************************************
2346 * LISTBOX_HandleSystemTimer
2348 * WM_SYSTIMER handler.
2350 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2352 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2354 KillSystemTimer( descr->self, LB_TIMER_ID );
2355 LISTBOX_Timer = LB_TIMER_NONE;
2357 return 0;
2361 /***********************************************************************
2362 * LISTBOX_HandleMouseMove
2364 * WM_MOUSEMOVE handler.
2366 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2367 INT x, INT y )
2369 INT index;
2370 TIMER_DIRECTION dir = LB_TIMER_NONE;
2372 if (!descr->captured) return;
2374 if (descr->style & LBS_MULTICOLUMN)
2376 if (y < 0) y = 0;
2377 else if (y >= descr->item_height * descr->page_size)
2378 y = descr->item_height * descr->page_size - 1;
2380 if (x < 0)
2382 dir = LB_TIMER_LEFT;
2383 x = 0;
2385 else if (x >= descr->width)
2387 dir = LB_TIMER_RIGHT;
2388 x = descr->width - 1;
2391 else
2393 if (y < 0) dir = LB_TIMER_UP; /* above */
2394 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2397 index = LISTBOX_GetItemFromPoint( descr, x, y );
2398 if (index == -1) index = descr->focus_item;
2399 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2401 /* Start/stop the system timer */
2403 if (dir != LB_TIMER_NONE)
2404 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2405 else if (LISTBOX_Timer != LB_TIMER_NONE)
2406 KillSystemTimer( descr->self, LB_TIMER_ID );
2407 LISTBOX_Timer = dir;
2411 /***********************************************************************
2412 * LISTBOX_HandleKeyDown
2414 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2416 INT caret = -1;
2417 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2418 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2419 bForceSelection = FALSE; /* only for single select list */
2421 if (descr->style & LBS_WANTKEYBOARDINPUT)
2423 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2424 MAKEWPARAM(LOWORD(key), descr->focus_item),
2425 (LPARAM)descr->self );
2426 if (caret == -2) return 0;
2428 if (caret == -1) switch(key)
2430 case VK_LEFT:
2431 if (descr->style & LBS_MULTICOLUMN)
2433 bForceSelection = FALSE;
2434 if (descr->focus_item >= descr->page_size)
2435 caret = descr->focus_item - descr->page_size;
2436 break;
2438 /* fall through */
2439 case VK_UP:
2440 caret = descr->focus_item - 1;
2441 if (caret < 0) caret = 0;
2442 break;
2443 case VK_RIGHT:
2444 if (descr->style & LBS_MULTICOLUMN)
2446 bForceSelection = FALSE;
2447 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2448 break;
2450 /* fall through */
2451 case VK_DOWN:
2452 caret = descr->focus_item + 1;
2453 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2454 break;
2456 case VK_PRIOR:
2457 if (descr->style & LBS_MULTICOLUMN)
2459 INT page = descr->width / descr->column_width;
2460 if (page < 1) page = 1;
2461 caret = descr->focus_item - (page * descr->page_size) + 1;
2463 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2464 if (caret < 0) caret = 0;
2465 break;
2466 case VK_NEXT:
2467 if (descr->style & LBS_MULTICOLUMN)
2469 INT page = descr->width / descr->column_width;
2470 if (page < 1) page = 1;
2471 caret = descr->focus_item + (page * descr->page_size) - 1;
2473 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2474 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2475 break;
2476 case VK_HOME:
2477 caret = 0;
2478 break;
2479 case VK_END:
2480 caret = descr->nb_items - 1;
2481 break;
2482 case VK_SPACE:
2483 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2484 else if (descr->style & LBS_MULTIPLESEL)
2486 LISTBOX_SetSelection( descr, descr->focus_item,
2487 !is_item_selected(descr, descr->focus_item),
2488 (descr->style & LBS_NOTIFY) != 0 );
2490 break;
2491 default:
2492 bForceSelection = FALSE;
2494 if (bForceSelection) /* focused item is used instead of key */
2495 caret = descr->focus_item;
2496 if (caret >= 0)
2498 if (((descr->style & LBS_EXTENDEDSEL) &&
2499 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2500 !IS_MULTISELECT(descr))
2501 descr->anchor_item = caret;
2502 LISTBOX_MoveCaret( descr, caret, TRUE );
2504 if (descr->style & LBS_MULTIPLESEL)
2505 descr->selected_item = caret;
2506 else
2507 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2508 if (descr->style & LBS_NOTIFY)
2510 if (descr->lphc && IsWindowVisible( descr->self ))
2512 /* make sure that combo parent doesn't hide us */
2513 descr->lphc->wState |= CBF_NOROLLUP;
2515 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2518 return 0;
2522 /***********************************************************************
2523 * LISTBOX_HandleChar
2525 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2527 INT caret = -1;
2528 WCHAR str[2];
2530 str[0] = charW;
2531 str[1] = '\0';
2533 if (descr->style & LBS_WANTKEYBOARDINPUT)
2535 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2536 MAKEWPARAM(charW, descr->focus_item),
2537 (LPARAM)descr->self );
2538 if (caret == -2) return 0;
2540 if (caret == -1)
2541 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2542 if (caret != -1)
2544 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2545 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2546 LISTBOX_MoveCaret( descr, caret, TRUE );
2547 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2548 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2550 return 0;
2554 /***********************************************************************
2555 * LISTBOX_Create
2557 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2559 LB_DESCR *descr;
2560 MEASUREITEMSTRUCT mis;
2561 RECT rect;
2563 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2564 return FALSE;
2566 GetClientRect( hwnd, &rect );
2567 descr->self = hwnd;
2568 descr->owner = GetParent( descr->self );
2569 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2570 descr->width = rect.right - rect.left;
2571 descr->height = rect.bottom - rect.top;
2572 descr->u.items = NULL;
2573 descr->items_size = 0;
2574 descr->nb_items = 0;
2575 descr->top_item = 0;
2576 descr->selected_item = -1;
2577 descr->focus_item = 0;
2578 descr->anchor_item = -1;
2579 descr->item_height = 1;
2580 descr->page_size = 1;
2581 descr->column_width = 150;
2582 descr->horz_extent = 0;
2583 descr->horz_pos = 0;
2584 descr->nb_tabs = 0;
2585 descr->tabs = NULL;
2586 descr->wheel_remain = 0;
2587 descr->caret_on = !lphc;
2588 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2589 descr->in_focus = FALSE;
2590 descr->captured = FALSE;
2591 descr->font = 0;
2592 descr->locale = GetUserDefaultLCID();
2593 descr->lphc = lphc;
2595 if( lphc )
2597 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2598 descr->owner = lphc->self;
2601 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2603 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2605 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2606 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2607 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2608 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2609 descr->style &= ~LBS_NODATA;
2610 descr->item_height = LISTBOX_SetFont( descr, 0 );
2612 if (descr->style & LBS_OWNERDRAWFIXED)
2614 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2616 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2618 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2619 descr->item_height = lphc->fixedOwnerDrawHeight;
2621 else
2623 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2624 mis.CtlType = ODT_LISTBOX;
2625 mis.CtlID = id;
2626 mis.itemID = -1;
2627 mis.itemWidth = 0;
2628 mis.itemData = 0;
2629 mis.itemHeight = descr->item_height;
2630 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2631 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2635 OpenThemeData( descr->self, WC_LISTBOXW );
2637 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2638 return TRUE;
2642 /***********************************************************************
2643 * LISTBOX_Destroy
2645 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2647 HTHEME theme = GetWindowTheme( descr->self );
2648 CloseThemeData( theme );
2649 LISTBOX_ResetContent( descr );
2650 SetWindowLongPtrW( descr->self, 0, 0 );
2651 HeapFree( GetProcessHeap(), 0, descr );
2652 return TRUE;
2656 /***********************************************************************
2657 * ListBoxWndProc_common
2659 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2661 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2662 HEADCOMBO *lphc = NULL;
2663 HTHEME theme;
2664 LRESULT ret;
2666 if (!descr)
2668 if (!IsWindow(hwnd)) return 0;
2670 if (msg == WM_CREATE)
2672 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2673 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2674 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2675 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2676 return 0;
2678 /* Ignore all other messages before we get a WM_CREATE */
2679 return DefWindowProcW( hwnd, msg, wParam, lParam );
2681 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2683 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr->self, msg, wParam, lParam );
2685 switch(msg)
2687 case LB_RESETCONTENT:
2688 LISTBOX_ResetContent( descr );
2689 LISTBOX_UpdateScroll( descr );
2690 InvalidateRect( descr->self, NULL, TRUE );
2691 return 0;
2693 case LB_ADDSTRING:
2695 const WCHAR *textW = (const WCHAR *)lParam;
2696 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2697 return LISTBOX_InsertString( descr, index, textW );
2700 case LB_INSERTSTRING:
2701 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2703 case LB_ADDFILE:
2705 const WCHAR *textW = (const WCHAR *)lParam;
2706 INT index = LISTBOX_FindFileStrPos( descr, textW );
2707 return LISTBOX_InsertString( descr, index, textW );
2710 case LB_DELETESTRING:
2711 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2712 return descr->nb_items;
2713 else
2715 SetLastError(ERROR_INVALID_INDEX);
2716 return LB_ERR;
2719 case LB_GETITEMDATA:
2720 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2722 SetLastError(ERROR_INVALID_INDEX);
2723 return LB_ERR;
2725 return get_item_data(descr, wParam);
2727 case LB_SETITEMDATA:
2728 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2730 SetLastError(ERROR_INVALID_INDEX);
2731 return LB_ERR;
2733 set_item_data(descr, wParam, lParam);
2734 /* undocumented: returns TRUE, not LB_OKAY (0) */
2735 return TRUE;
2737 case LB_GETCOUNT:
2738 return descr->nb_items;
2740 case LB_GETTEXT:
2741 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2743 case LB_GETTEXTLEN:
2744 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2746 SetLastError(ERROR_INVALID_INDEX);
2747 return LB_ERR;
2749 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2750 return lstrlenW(get_item_string(descr, wParam));
2752 case LB_GETCURSEL:
2753 if (descr->nb_items == 0)
2754 return LB_ERR;
2755 if (!IS_MULTISELECT(descr))
2756 return descr->selected_item;
2757 if (descr->selected_item != -1)
2758 return descr->selected_item;
2759 return descr->focus_item;
2760 /* otherwise, if the user tries to move the selection with the */
2761 /* arrow keys, we will give the application something to choke on */
2762 case LB_GETTOPINDEX:
2763 return descr->top_item;
2765 case LB_GETITEMHEIGHT:
2766 return LISTBOX_GetItemHeight( descr, wParam );
2768 case LB_SETITEMHEIGHT:
2769 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2771 case LB_ITEMFROMPOINT:
2773 POINT pt;
2774 RECT rect;
2775 int index;
2776 BOOL hit = TRUE;
2778 /* The hiword of the return value is not a client area
2779 hittest as suggested by MSDN, but rather a hittest on
2780 the returned listbox item. */
2782 if(descr->nb_items == 0)
2783 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2785 pt.x = (short)LOWORD(lParam);
2786 pt.y = (short)HIWORD(lParam);
2788 SetRect(&rect, 0, 0, descr->width, descr->height);
2790 if(!PtInRect(&rect, pt))
2792 pt.x = min(pt.x, rect.right - 1);
2793 pt.x = max(pt.x, 0);
2794 pt.y = min(pt.y, rect.bottom - 1);
2795 pt.y = max(pt.y, 0);
2796 hit = FALSE;
2799 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2801 if(index == -1)
2803 index = descr->nb_items - 1;
2804 hit = FALSE;
2806 return MAKELONG(index, hit ? 0 : 1);
2809 case LB_SETCARETINDEX:
2810 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2811 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2812 return LB_ERR;
2813 else if (ISWIN31)
2814 return wParam;
2815 else
2816 return LB_OKAY;
2818 case LB_GETCARETINDEX:
2819 return descr->focus_item;
2821 case LB_SETTOPINDEX:
2822 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2824 case LB_SETCOLUMNWIDTH:
2825 return LISTBOX_SetColumnWidth( descr, wParam );
2827 case LB_GETITEMRECT:
2828 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2830 case LB_FINDSTRING:
2831 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2833 case LB_FINDSTRINGEXACT:
2834 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2836 case LB_SELECTSTRING:
2838 const WCHAR *textW = (const WCHAR *)lParam;
2839 INT index;
2841 if (HAS_STRINGS(descr))
2842 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2844 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2845 if (index != LB_ERR)
2847 LISTBOX_MoveCaret( descr, index, TRUE );
2848 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2850 return index;
2853 case LB_GETSEL:
2854 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2855 return LB_ERR;
2856 return is_item_selected(descr, wParam);
2858 case LB_SETSEL:
2859 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2860 if (ret != LB_ERR && wParam)
2862 descr->anchor_item = lParam;
2863 if (lParam != -1)
2864 LISTBOX_SetCaretIndex( descr, lParam, TRUE );
2866 return ret;
2868 case LB_SETCURSEL:
2869 if (IS_MULTISELECT(descr)) return LB_ERR;
2870 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2871 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2872 if (ret != LB_ERR) ret = descr->selected_item;
2873 return ret;
2875 case LB_GETSELCOUNT:
2876 return LISTBOX_GetSelCount( descr );
2878 case LB_GETSELITEMS:
2879 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2881 case LB_SELITEMRANGE:
2882 if (LOWORD(lParam) <= HIWORD(lParam))
2883 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2884 HIWORD(lParam), wParam );
2885 else
2886 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2887 LOWORD(lParam), wParam );
2889 case LB_SELITEMRANGEEX:
2890 if ((INT)lParam >= (INT)wParam)
2891 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2892 else
2893 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2895 case LB_GETHORIZONTALEXTENT:
2896 return descr->horz_extent;
2898 case LB_SETHORIZONTALEXTENT:
2899 return LISTBOX_SetHorizontalExtent( descr, wParam );
2901 case LB_GETANCHORINDEX:
2902 return descr->anchor_item;
2904 case LB_SETANCHORINDEX:
2905 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2907 SetLastError(ERROR_INVALID_INDEX);
2908 return LB_ERR;
2910 descr->anchor_item = (INT)wParam;
2911 return LB_OKAY;
2913 case LB_DIR:
2914 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2916 case LB_GETLOCALE:
2917 return descr->locale;
2919 case LB_SETLOCALE:
2921 LCID ret;
2922 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2923 return LB_ERR;
2924 ret = descr->locale;
2925 descr->locale = (LCID)wParam;
2926 return ret;
2929 case LB_INITSTORAGE:
2930 return LISTBOX_InitStorage( descr, wParam );
2932 case LB_SETCOUNT:
2933 return LISTBOX_SetCount( descr, (INT)wParam );
2935 case LB_SETTABSTOPS:
2936 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2938 case LB_CARETON:
2939 if (descr->caret_on)
2940 return LB_OKAY;
2941 descr->caret_on = TRUE;
2942 if ((descr->focus_item != -1) && (descr->in_focus))
2943 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2944 return LB_OKAY;
2946 case LB_CARETOFF:
2947 if (!descr->caret_on)
2948 return LB_OKAY;
2949 descr->caret_on = FALSE;
2950 if ((descr->focus_item != -1) && (descr->in_focus))
2951 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2952 return LB_OKAY;
2954 case LB_GETLISTBOXINFO:
2955 return descr->page_size;
2957 case WM_DESTROY:
2958 return LISTBOX_Destroy( descr );
2960 case WM_ENABLE:
2961 InvalidateRect( descr->self, NULL, TRUE );
2962 return 0;
2964 case WM_SETREDRAW:
2965 LISTBOX_SetRedraw( descr, wParam != 0 );
2966 return 0;
2968 case WM_GETDLGCODE:
2969 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2971 case WM_PRINTCLIENT:
2972 case WM_PAINT:
2974 PAINTSTRUCT ps;
2975 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2976 ret = LISTBOX_Paint( descr, hdc );
2977 if( !wParam ) EndPaint( descr->self, &ps );
2979 return ret;
2981 case WM_NCPAINT:
2982 LISTBOX_NCPaint( descr, (HRGN)wParam );
2983 break;
2985 case WM_SIZE:
2986 LISTBOX_UpdateSize( descr );
2987 return 0;
2988 case WM_GETFONT:
2989 return (LRESULT)descr->font;
2990 case WM_SETFONT:
2991 LISTBOX_SetFont( descr, (HFONT)wParam );
2992 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2993 return 0;
2994 case WM_SETFOCUS:
2995 descr->in_focus = TRUE;
2996 descr->caret_on = TRUE;
2997 if (descr->focus_item != -1)
2998 LISTBOX_DrawFocusRect( descr, TRUE );
2999 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3000 return 0;
3001 case WM_KILLFOCUS:
3002 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3003 descr->in_focus = FALSE;
3004 descr->wheel_remain = 0;
3005 if ((descr->focus_item != -1) && descr->caret_on)
3006 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3007 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3008 return 0;
3009 case WM_HSCROLL:
3010 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3011 case WM_VSCROLL:
3012 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3013 case WM_MOUSEWHEEL:
3014 if (wParam & (MK_SHIFT | MK_CONTROL))
3015 return DefWindowProcW( descr->self, msg, wParam, lParam );
3016 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3017 case WM_LBUTTONDOWN:
3018 if (lphc)
3019 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3020 (INT16)LOWORD(lParam),
3021 (INT16)HIWORD(lParam) );
3022 return LISTBOX_HandleLButtonDown( descr, wParam,
3023 (INT16)LOWORD(lParam),
3024 (INT16)HIWORD(lParam) );
3025 case WM_LBUTTONDBLCLK:
3026 if (lphc)
3027 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3028 (INT16)LOWORD(lParam),
3029 (INT16)HIWORD(lParam) );
3030 if (descr->style & LBS_NOTIFY)
3031 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3032 return 0;
3033 case WM_MOUSEMOVE:
3034 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3036 BOOL captured = descr->captured;
3037 POINT mousePos;
3038 RECT clientRect;
3040 mousePos.x = (INT16)LOWORD(lParam);
3041 mousePos.y = (INT16)HIWORD(lParam);
3044 * If we are in a dropdown combobox, we simulate that
3045 * the mouse is captured to show the tracking of the item.
3047 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3048 descr->captured = TRUE;
3050 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3052 descr->captured = captured;
3054 else if (GetCapture() == descr->self)
3056 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3057 (INT16)HIWORD(lParam) );
3059 return 0;
3060 case WM_LBUTTONUP:
3061 if (lphc)
3063 POINT mousePos;
3064 RECT clientRect;
3067 * If the mouse button "up" is not in the listbox,
3068 * we make sure there is no selection by re-selecting the
3069 * item that was selected when the listbox was made visible.
3071 mousePos.x = (INT16)LOWORD(lParam);
3072 mousePos.y = (INT16)HIWORD(lParam);
3074 GetClientRect(descr->self, &clientRect);
3077 * When the user clicks outside the combobox and the focus
3078 * is lost, the owning combobox will send a fake buttonup with
3079 * 0xFFFFFFF as the mouse location, we must also revert the
3080 * selection to the original selection.
3082 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3083 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3085 return LISTBOX_HandleLButtonUp( descr );
3086 case WM_KEYDOWN:
3087 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3089 /* for some reason Windows makes it possible to
3090 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3092 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3093 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3094 && (wParam == VK_DOWN || wParam == VK_UP)) )
3096 COMBO_FlipListbox( lphc, FALSE, FALSE );
3097 return 0;
3100 return LISTBOX_HandleKeyDown( descr, wParam );
3101 case WM_CHAR:
3102 return LISTBOX_HandleChar( descr, wParam );
3104 case WM_SYSTIMER:
3105 return LISTBOX_HandleSystemTimer( descr );
3106 case WM_ERASEBKGND:
3107 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3109 RECT rect;
3110 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3111 wParam, (LPARAM)descr->self );
3112 TRACE("hbrush = %p\n", hbrush);
3113 if(!hbrush)
3114 hbrush = GetSysColorBrush(COLOR_WINDOW);
3115 if(hbrush)
3117 GetClientRect(descr->self, &rect);
3118 FillRect((HDC)wParam, &rect, hbrush);
3121 return 1;
3122 case WM_DROPFILES:
3123 if( lphc ) return 0;
3124 return SendMessageW( descr->owner, msg, wParam, lParam );
3126 case WM_NCDESTROY:
3127 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3128 lphc->hWndLBox = 0;
3129 break;
3131 case WM_NCACTIVATE:
3132 if (lphc) return 0;
3133 break;
3135 case WM_THEMECHANGED:
3136 theme = GetWindowTheme( hwnd );
3137 CloseThemeData( theme );
3138 OpenThemeData( hwnd, WC_LISTBOXW );
3139 break;
3141 default:
3142 if ((msg >= WM_USER) && (msg < 0xc000))
3143 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3144 hwnd, msg, wParam, lParam );
3147 return DefWindowProcW( hwnd, msg, wParam, lParam );
3150 void LISTBOX_Register(void)
3152 WNDCLASSW wndClass;
3154 memset(&wndClass, 0, sizeof(wndClass));
3155 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3156 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3157 wndClass.cbClsExtra = 0;
3158 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3159 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3160 wndClass.hbrBackground = NULL;
3161 wndClass.lpszClassName = WC_LISTBOXW;
3162 RegisterClassW(&wndClass);
3165 void COMBOLBOX_Register(void)
3167 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
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 = combolboxW;
3178 RegisterClassW(&wndClass);