user32: Ignore application-specified DPI awareness when DPI scaling is disabled.
[wine.git] / dlls / user32 / listbox.c
blobc8bd148d63eb52e8fd21287d4cbacb718e6e0eb5
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * TODO:
21 * - LBS_NODATA
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "wine/unicode.h"
32 #include "user_private.h"
33 #include "controls.h"
34 #include "wine/exception.h"
35 #include "wine/debug.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
39 /* Items array granularity */
40 #define LB_ARRAY_GRANULARITY 16
42 /* Scrolling timeout in ms */
43 #define LB_SCROLL_TIMEOUT 50
45 /* Listbox system timer id */
46 #define LB_TIMER_ID 2
48 /* flag listbox changed while setredraw false - internal style */
49 #define LBS_DISPLAYCHANGED 0x80000000
51 /* Item structure */
52 typedef struct
54 LPWSTR str; /* Item text */
55 BOOL selected; /* Is item selected? */
56 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
57 ULONG_PTR data; /* User data */
58 } LB_ITEMDATA;
60 /* Listbox structure */
61 typedef struct
63 HWND self; /* Our own window handle */
64 HWND owner; /* Owner window to send notifications to */
65 UINT style; /* Window style */
66 INT width; /* Window width */
67 INT height; /* Window height */
68 LB_ITEMDATA *items; /* Array of items */
69 INT nb_items; /* Number of items */
70 INT top_item; /* Top visible item */
71 INT selected_item; /* Selected item */
72 INT focus_item; /* Item that has the focus */
73 INT anchor_item; /* Anchor item for extended selection */
74 INT item_height; /* Default item height */
75 INT page_size; /* Items per listbox page */
76 INT column_width; /* Column width for multi-column listboxes */
77 INT horz_extent; /* Horizontal extent */
78 INT horz_pos; /* Horizontal position */
79 INT nb_tabs; /* Number of tabs in array */
80 INT *tabs; /* Array of tabs */
81 INT avg_char_width; /* Average width of characters */
82 INT wheel_remain; /* Left over scroll amount */
83 BOOL caret_on; /* Is caret on? */
84 BOOL captured; /* Is mouse captured? */
85 BOOL in_focus;
86 HFONT font; /* Current font */
87 LCID locale; /* Current locale for string comparisons */
88 LPHEADCOMBO lphc; /* ComboLBox */
89 } LB_DESCR;
92 #define IS_OWNERDRAW(descr) \
93 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
95 #define HAS_STRINGS(descr) \
96 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
99 #define IS_MULTISELECT(descr) \
100 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
101 !((descr)->style & LBS_NOSEL))
103 #define SEND_NOTIFICATION(descr,code) \
104 (SendMessageW( (descr)->owner, WM_COMMAND, \
105 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
107 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
109 /* Current timer status */
110 typedef enum
112 LB_TIMER_NONE,
113 LB_TIMER_UP,
114 LB_TIMER_LEFT,
115 LB_TIMER_DOWN,
116 LB_TIMER_RIGHT
117 } TIMER_DIRECTION;
119 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
121 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
123 /*********************************************************************
124 * listbox class descriptor
126 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
127 const struct builtin_class_descr LISTBOX_builtin_class =
129 listboxW, /* name */
130 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
131 WINPROC_LISTBOX, /* proc */
132 sizeof(LB_DESCR *), /* extra */
133 IDC_ARROW, /* cursor */
134 0 /* brush */
138 /*********************************************************************
139 * combolbox class descriptor
141 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
142 const struct builtin_class_descr COMBOLBOX_builtin_class =
144 combolboxW, /* name */
145 CS_DBLCLKS | CS_SAVEBITS, /* style */
146 WINPROC_LISTBOX, /* proc */
147 sizeof(LB_DESCR *), /* extra */
148 IDC_ARROW, /* cursor */
149 0 /* brush */
153 /***********************************************************************
154 * LISTBOX_GetCurrentPageSize
156 * Return the current page size
158 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
160 INT i, height;
161 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
162 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
164 if ((height += descr->items[i].height) > descr->height) break;
166 if (i == descr->top_item) return 1;
167 else return i - descr->top_item;
171 /***********************************************************************
172 * LISTBOX_GetMaxTopIndex
174 * Return the maximum possible index for the top of the listbox.
176 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
178 INT max, page;
180 if (descr->style & LBS_OWNERDRAWVARIABLE)
182 page = descr->height;
183 for (max = descr->nb_items - 1; max >= 0; max--)
184 if ((page -= descr->items[max].height) < 0) break;
185 if (max < descr->nb_items - 1) max++;
187 else if (descr->style & LBS_MULTICOLUMN)
189 if ((page = descr->width / descr->column_width) < 1) page = 1;
190 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
191 max = (max - page) * descr->page_size;
193 else
195 max = descr->nb_items - descr->page_size;
197 if (max < 0) max = 0;
198 return max;
202 /***********************************************************************
203 * LISTBOX_UpdateScroll
205 * Update the scrollbars. Should be called whenever the content
206 * of the listbox changes.
208 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
210 SCROLLINFO info;
212 /* Check the listbox scroll bar flags individually before we call
213 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
214 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
215 scroll bar when we do not need one.
216 if (!(descr->style & WS_VSCROLL)) return;
219 /* It is important that we check descr->style, and not wnd->dwStyle,
220 for WS_VSCROLL, as the former is exactly the one passed in
221 argument to CreateWindow.
222 In Windows (and from now on in Wine :) a listbox created
223 with such a style (no WS_SCROLL) does not update
224 the scrollbar with listbox-related data, thus letting
225 the programmer use it for his/her own purposes. */
227 if (descr->style & LBS_NOREDRAW) return;
228 info.cbSize = sizeof(info);
230 if (descr->style & LBS_MULTICOLUMN)
232 info.nMin = 0;
233 info.nMax = (descr->nb_items - 1) / descr->page_size;
234 info.nPos = descr->top_item / descr->page_size;
235 info.nPage = descr->width / descr->column_width;
236 if (info.nPage < 1) info.nPage = 1;
237 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
238 if (descr->style & LBS_DISABLENOSCROLL)
239 info.fMask |= SIF_DISABLENOSCROLL;
240 if (descr->style & WS_HSCROLL)
241 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
242 info.nMax = 0;
243 info.fMask = SIF_RANGE;
244 if (descr->style & WS_VSCROLL)
245 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
247 else
249 info.nMin = 0;
250 info.nMax = descr->nb_items - 1;
251 info.nPos = descr->top_item;
252 info.nPage = LISTBOX_GetCurrentPageSize( descr );
253 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
254 if (descr->style & LBS_DISABLENOSCROLL)
255 info.fMask |= SIF_DISABLENOSCROLL;
256 if (descr->style & WS_VSCROLL)
257 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
259 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
261 info.nPos = descr->horz_pos;
262 info.nPage = descr->width;
263 info.fMask = SIF_POS | SIF_PAGE;
264 if (descr->style & LBS_DISABLENOSCROLL)
265 info.fMask |= SIF_DISABLENOSCROLL;
266 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
268 else
270 if (descr->style & LBS_DISABLENOSCROLL)
272 info.nMin = 0;
273 info.nMax = 0;
274 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
275 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
277 else
279 ShowScrollBar( descr->self, SB_HORZ, FALSE );
286 /***********************************************************************
287 * LISTBOX_SetTopItem
289 * Set the top item of the listbox, scrolling up or down if necessary.
291 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
293 INT max = LISTBOX_GetMaxTopIndex( descr );
295 TRACE("setting top item %d, scroll %d\n", index, scroll);
297 if (index > max) index = max;
298 if (index < 0) index = 0;
299 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
300 if (descr->top_item == index) return LB_OKAY;
301 if (scroll)
303 INT dx = 0, dy = 0;
304 if (descr->style & LBS_MULTICOLUMN)
305 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
306 else if (descr->style & LBS_OWNERDRAWVARIABLE)
308 INT i;
309 if (index > descr->top_item)
311 for (i = index - 1; i >= descr->top_item; i--)
312 dy -= descr->items[i].height;
314 else
316 for (i = index; i < descr->top_item; i++)
317 dy += descr->items[i].height;
320 else
321 dy = (descr->top_item - index) * descr->item_height;
323 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
324 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
326 else
327 InvalidateRect( descr->self, NULL, TRUE );
328 descr->top_item = index;
329 LISTBOX_UpdateScroll( descr );
330 return LB_OKAY;
334 /***********************************************************************
335 * LISTBOX_UpdatePage
337 * Update the page size. Should be called when the size of
338 * the client area or the item height changes.
340 static void LISTBOX_UpdatePage( LB_DESCR *descr )
342 INT page_size;
344 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
345 page_size = 1;
346 if (page_size == descr->page_size) return;
347 descr->page_size = page_size;
348 if (descr->style & LBS_MULTICOLUMN)
349 InvalidateRect( descr->self, NULL, TRUE );
350 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
354 /***********************************************************************
355 * LISTBOX_UpdateSize
357 * Update the size of the listbox. Should be called when the size of
358 * the client area changes.
360 static void LISTBOX_UpdateSize( LB_DESCR *descr )
362 RECT rect;
364 GetClientRect( descr->self, &rect );
365 descr->width = rect.right - rect.left;
366 descr->height = rect.bottom - rect.top;
367 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
369 INT remaining;
370 RECT rect;
372 GetWindowRect( descr->self, &rect );
373 if(descr->item_height != 0)
374 remaining = descr->height % descr->item_height;
375 else
376 remaining = 0;
377 if ((descr->height > descr->item_height) && remaining)
379 TRACE("[%p]: changing height %d -> %d\n",
380 descr->self, descr->height, descr->height - remaining );
381 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
382 rect.bottom - rect.top - remaining,
383 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
384 return;
387 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
388 LISTBOX_UpdatePage( descr );
389 LISTBOX_UpdateScroll( descr );
391 /* Invalidate the focused item so it will be repainted correctly */
392 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
394 InvalidateRect( descr->self, &rect, FALSE );
399 /***********************************************************************
400 * LISTBOX_GetItemRect
402 * Get the rectangle enclosing an item, in listbox client coordinates.
403 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
405 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
407 /* Index <= 0 is legal even on empty listboxes */
408 if (index && (index >= descr->nb_items))
410 SetRectEmpty(rect);
411 SetLastError(ERROR_INVALID_INDEX);
412 return LB_ERR;
414 SetRect( rect, 0, 0, descr->width, descr->height );
415 if (descr->style & LBS_MULTICOLUMN)
417 INT col = (index / descr->page_size) -
418 (descr->top_item / descr->page_size);
419 rect->left += col * descr->column_width;
420 rect->right = rect->left + descr->column_width;
421 rect->top += (index % descr->page_size) * descr->item_height;
422 rect->bottom = rect->top + descr->item_height;
424 else if (descr->style & LBS_OWNERDRAWVARIABLE)
426 INT i;
427 rect->right += descr->horz_pos;
428 if ((index >= 0) && (index < descr->nb_items))
430 if (index < descr->top_item)
432 for (i = descr->top_item-1; i >= index; i--)
433 rect->top -= descr->items[i].height;
435 else
437 for (i = descr->top_item; i < index; i++)
438 rect->top += descr->items[i].height;
440 rect->bottom = rect->top + descr->items[index].height;
444 else
446 rect->top += (index - descr->top_item) * descr->item_height;
447 rect->bottom = rect->top + descr->item_height;
448 rect->right += descr->horz_pos;
451 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
453 return ((rect->left < descr->width) && (rect->right > 0) &&
454 (rect->top < descr->height) && (rect->bottom > 0));
458 /***********************************************************************
459 * LISTBOX_GetItemFromPoint
461 * Return the item nearest from point (x,y) (in client coordinates).
463 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
465 INT index = descr->top_item;
467 if (!descr->nb_items) return -1; /* No items */
468 if (descr->style & LBS_OWNERDRAWVARIABLE)
470 INT pos = 0;
471 if (y >= 0)
473 while (index < descr->nb_items)
475 if ((pos += descr->items[index].height) > y) break;
476 index++;
479 else
481 while (index > 0)
483 index--;
484 if ((pos -= descr->items[index].height) <= y) break;
488 else if (descr->style & LBS_MULTICOLUMN)
490 if (y >= descr->item_height * descr->page_size) return -1;
491 if (y >= 0) index += y / descr->item_height;
492 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
493 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
495 else
497 index += (y / descr->item_height);
499 if (index < 0) return 0;
500 if (index >= descr->nb_items) return -1;
501 return index;
505 /***********************************************************************
506 * LISTBOX_PaintItem
508 * Paint an item.
510 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
511 INT index, UINT action, BOOL ignoreFocus )
513 LB_ITEMDATA *item = NULL;
514 if (index < descr->nb_items) item = &descr->items[index];
516 if (IS_OWNERDRAW(descr))
518 DRAWITEMSTRUCT dis;
519 RECT r;
520 HRGN hrgn;
522 if (!item)
524 if (action == ODA_FOCUS)
525 DrawFocusRect( hdc, rect );
526 else
527 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
528 return;
531 /* some programs mess with the clipping region when
532 drawing the item, *and* restore the previous region
533 after they are done, so a region has better to exist
534 else everything ends clipped */
535 GetClientRect(descr->self, &r);
536 hrgn = set_control_clipping( hdc, &r );
538 dis.CtlType = ODT_LISTBOX;
539 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
540 dis.hwndItem = descr->self;
541 dis.itemAction = action;
542 dis.hDC = hdc;
543 dis.itemID = index;
544 dis.itemState = 0;
545 if (item->selected) dis.itemState |= ODS_SELECTED;
546 if (!ignoreFocus && (descr->focus_item == index) &&
547 (descr->caret_on) &&
548 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
549 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
550 dis.itemData = item->data;
551 dis.rcItem = *rect;
552 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
553 descr->self, index, debugstr_w(item->str), action,
554 dis.itemState, wine_dbgstr_rect(rect) );
555 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
556 SelectClipRgn( hdc, hrgn );
557 if (hrgn) DeleteObject( hrgn );
559 else
561 COLORREF oldText = 0, oldBk = 0;
563 if (action == ODA_FOCUS)
565 DrawFocusRect( hdc, rect );
566 return;
568 if (item && item->selected)
570 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
571 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
574 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
575 descr->self, index, item ? debugstr_w(item->str) : "", action,
576 wine_dbgstr_rect(rect) );
577 if (!item)
578 ExtTextOutW( hdc, rect->left + 1, rect->top,
579 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
580 else if (!(descr->style & LBS_USETABSTOPS))
581 ExtTextOutW( hdc, rect->left + 1, rect->top,
582 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
583 strlenW(item->str), NULL );
584 else
586 /* Output empty string to paint background in the full width. */
587 ExtTextOutW( hdc, rect->left + 1, rect->top,
588 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
589 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
590 item->str, strlenW(item->str),
591 descr->nb_tabs, descr->tabs, 0);
593 if (item && item->selected)
595 SetBkColor( hdc, oldBk );
596 SetTextColor( hdc, oldText );
598 if (!ignoreFocus && (descr->focus_item == index) &&
599 (descr->caret_on) &&
600 (descr->in_focus)) DrawFocusRect( hdc, rect );
605 /***********************************************************************
606 * LISTBOX_SetRedraw
608 * Change the redraw flag.
610 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
612 if (on)
614 if (!(descr->style & LBS_NOREDRAW)) return;
615 descr->style &= ~LBS_NOREDRAW;
616 if (descr->style & LBS_DISPLAYCHANGED)
617 { /* page was changed while setredraw false, refresh automatically */
618 InvalidateRect(descr->self, NULL, TRUE);
619 if ((descr->top_item + descr->page_size) > descr->nb_items)
620 { /* reset top of page if less than number of items/page */
621 descr->top_item = descr->nb_items - descr->page_size;
622 if (descr->top_item < 0) descr->top_item = 0;
624 descr->style &= ~LBS_DISPLAYCHANGED;
626 LISTBOX_UpdateScroll( descr );
628 else descr->style |= LBS_NOREDRAW;
632 /***********************************************************************
633 * LISTBOX_RepaintItem
635 * Repaint a single item synchronously.
637 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
639 HDC hdc;
640 RECT rect;
641 HFONT oldFont = 0;
642 HBRUSH hbrush, oldBrush = 0;
644 /* Do not repaint the item if the item is not visible */
645 if (!IsWindowVisible(descr->self)) return;
646 if (descr->style & LBS_NOREDRAW)
648 descr->style |= LBS_DISPLAYCHANGED;
649 return;
651 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
652 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
653 if (descr->font) oldFont = SelectObject( hdc, descr->font );
654 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
655 (WPARAM)hdc, (LPARAM)descr->self );
656 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
657 if (!IsWindowEnabled(descr->self))
658 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
659 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
660 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
661 if (oldFont) SelectObject( hdc, oldFont );
662 if (oldBrush) SelectObject( hdc, oldBrush );
663 ReleaseDC( descr->self, hdc );
667 /***********************************************************************
668 * LISTBOX_DrawFocusRect
670 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
672 HDC hdc;
673 RECT rect;
674 HFONT oldFont = 0;
676 /* Do not repaint the item if the item is not visible */
677 if (!IsWindowVisible(descr->self)) return;
679 if (descr->focus_item == -1) return;
680 if (!descr->caret_on || !descr->in_focus) return;
682 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
683 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
684 if (descr->font) oldFont = SelectObject( hdc, descr->font );
685 if (!IsWindowEnabled(descr->self))
686 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
687 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
688 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
689 if (oldFont) SelectObject( hdc, oldFont );
690 ReleaseDC( descr->self, hdc );
694 /***********************************************************************
695 * LISTBOX_InitStorage
697 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
699 LB_ITEMDATA *item;
701 nb_items += LB_ARRAY_GRANULARITY - 1;
702 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
703 if (descr->items) {
704 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
705 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
706 nb_items * sizeof(LB_ITEMDATA));
708 else {
709 item = HeapAlloc( GetProcessHeap(), 0,
710 nb_items * sizeof(LB_ITEMDATA));
713 if (!item)
715 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
716 return LB_ERRSPACE;
718 descr->items = item;
719 return LB_OKAY;
723 /***********************************************************************
724 * LISTBOX_SetTabStops
726 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
728 INT i;
730 if (!(descr->style & LBS_USETABSTOPS))
732 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
733 return FALSE;
736 HeapFree( GetProcessHeap(), 0, descr->tabs );
737 if (!(descr->nb_tabs = count))
739 descr->tabs = NULL;
740 return TRUE;
742 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
743 descr->nb_tabs * sizeof(INT) )))
744 return FALSE;
745 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
747 /* convert into "dialog units"*/
748 for (i = 0; i < descr->nb_tabs; i++)
749 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
751 return TRUE;
755 /***********************************************************************
756 * LISTBOX_GetText
758 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
760 DWORD len;
762 if ((index < 0) || (index >= descr->nb_items))
764 SetLastError(ERROR_INVALID_INDEX);
765 return LB_ERR;
767 if (HAS_STRINGS(descr))
769 if (!buffer)
771 len = strlenW(descr->items[index].str);
772 if( unicode )
773 return len;
774 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
775 NULL, 0, NULL, NULL );
778 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
780 __TRY /* hide a Delphi bug that passes a read-only buffer */
782 if(unicode)
784 strcpyW( buffer, descr->items[index].str );
785 len = strlenW(buffer);
787 else
789 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
790 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
793 __EXCEPT_PAGE_FAULT
795 WARN( "got an invalid buffer (Delphi bug?)\n" );
796 SetLastError( ERROR_INVALID_PARAMETER );
797 return LB_ERR;
799 __ENDTRY
800 } else {
801 if (buffer)
802 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
803 len = sizeof(DWORD);
805 return len;
808 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
810 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
811 if (ret == CSTR_LESS_THAN)
812 return -1;
813 if (ret == CSTR_EQUAL)
814 return 0;
815 if (ret == CSTR_GREATER_THAN)
816 return 1;
817 return -1;
820 /***********************************************************************
821 * LISTBOX_FindStringPos
823 * Find the nearest string located before a given string in sort order.
824 * If 'exact' is TRUE, return an error if we don't get an exact match.
826 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
828 INT index, min, max, res;
830 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
832 min = 0;
833 max = descr->nb_items - 1;
834 while (min <= max)
836 index = (min + max) / 2;
837 if (HAS_STRINGS(descr))
838 res = LISTBOX_lstrcmpiW( descr->locale, descr->items[index].str, str );
839 else
841 COMPAREITEMSTRUCT cis;
842 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
844 cis.CtlType = ODT_LISTBOX;
845 cis.CtlID = id;
846 cis.hwndItem = descr->self;
847 /* note that some application (MetaStock) expects the second item
848 * to be in the listbox */
849 cis.itemID1 = index;
850 cis.itemData1 = descr->items[index].data;
851 cis.itemID2 = -1;
852 cis.itemData2 = (ULONG_PTR)str;
853 cis.dwLocaleId = descr->locale;
854 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
856 if (!res) return index;
857 if (res > 0) max = index - 1;
858 else min = index + 1;
860 return exact ? -1 : min;
864 /***********************************************************************
865 * LISTBOX_FindFileStrPos
867 * Find the nearest string located before a given string in directory
868 * sort order (i.e. first files, then directories, then drives).
870 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
872 INT min, max, res;
874 if (!HAS_STRINGS(descr))
875 return LISTBOX_FindStringPos( descr, str, FALSE );
876 min = 0;
877 max = descr->nb_items;
878 while (min != max)
880 INT index = (min + max) / 2;
881 LPCWSTR p = descr->items[index].str;
882 if (*p == '[') /* drive or directory */
884 if (*str != '[') res = -1;
885 else if (p[1] == '-') /* drive */
887 if (str[1] == '-') res = str[2] - p[2];
888 else res = -1;
890 else /* directory */
892 if (str[1] == '-') res = 1;
893 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
896 else /* filename */
898 if (*str == '[') res = 1;
899 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
901 if (!res) return index;
902 if (res < 0) max = index;
903 else min = index + 1;
905 return max;
909 /***********************************************************************
910 * LISTBOX_FindString
912 * Find the item beginning with a given string.
914 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
916 INT i;
917 LB_ITEMDATA *item;
919 if (start >= descr->nb_items) start = -1;
920 item = descr->items + start + 1;
921 if (HAS_STRINGS(descr))
923 if (!str || ! str[0] ) return LB_ERR;
924 if (exact)
926 for (i = start + 1; i < descr->nb_items; i++, item++)
927 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
928 for (i = 0, item = descr->items; i <= start; i++, item++)
929 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
931 else
933 /* Special case for drives and directories: ignore prefix */
934 #define CHECK_DRIVE(item) \
935 if ((item)->str[0] == '[') \
937 if (!strncmpiW( str, (item)->str+1, len )) return i; \
938 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
939 return i; \
942 INT len = strlenW(str);
943 for (i = start + 1; i < descr->nb_items; i++, item++)
945 if (!strncmpiW( str, item->str, len )) return i;
946 CHECK_DRIVE(item);
948 for (i = 0, item = descr->items; i <= start; i++, item++)
950 if (!strncmpiW( str, item->str, len )) return i;
951 CHECK_DRIVE(item);
953 #undef CHECK_DRIVE
956 else
958 if (exact && (descr->style & LBS_SORT))
959 /* If sorted, use a WM_COMPAREITEM binary search */
960 return LISTBOX_FindStringPos( descr, str, TRUE );
962 /* Otherwise use a linear search */
963 for (i = start + 1; i < descr->nb_items; i++, item++)
964 if (item->data == (ULONG_PTR)str) return i;
965 for (i = 0, item = descr->items; i <= start; i++, item++)
966 if (item->data == (ULONG_PTR)str) return i;
968 return LB_ERR;
972 /***********************************************************************
973 * LISTBOX_GetSelCount
975 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
977 INT i, count;
978 const LB_ITEMDATA *item = descr->items;
980 if (!(descr->style & LBS_MULTIPLESEL) ||
981 (descr->style & LBS_NOSEL))
982 return LB_ERR;
983 for (i = count = 0; i < descr->nb_items; i++, item++)
984 if (item->selected) count++;
985 return count;
989 /***********************************************************************
990 * LISTBOX_GetSelItems
992 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
994 INT i, count;
995 const LB_ITEMDATA *item = descr->items;
997 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
998 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
999 if (item->selected) array[count++] = i;
1000 return count;
1004 /***********************************************************************
1005 * LISTBOX_Paint
1007 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1009 INT i, col_pos = descr->page_size - 1;
1010 RECT rect;
1011 RECT focusRect = {-1, -1, -1, -1};
1012 HFONT oldFont = 0;
1013 HBRUSH hbrush, oldBrush = 0;
1015 if (descr->style & LBS_NOREDRAW) return 0;
1017 SetRect( &rect, 0, 0, descr->width, descr->height );
1018 if (descr->style & LBS_MULTICOLUMN)
1019 rect.right = rect.left + descr->column_width;
1020 else if (descr->horz_pos)
1022 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1023 rect.right += descr->horz_pos;
1026 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1027 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1028 (WPARAM)hdc, (LPARAM)descr->self );
1029 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1030 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1032 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1033 (descr->in_focus))
1035 /* Special case for empty listbox: paint focus rect */
1036 rect.bottom = rect.top + descr->item_height;
1037 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1038 &rect, NULL, 0, NULL );
1039 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1040 rect.top = rect.bottom;
1043 /* Paint all the item, regarding the selection
1044 Focus state will be painted after */
1046 for (i = descr->top_item; i < descr->nb_items; i++)
1048 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1049 rect.bottom = rect.top + descr->item_height;
1050 else
1051 rect.bottom = rect.top + descr->items[i].height;
1053 /* keep the focus rect, to paint the focus item after */
1054 if (i == descr->focus_item)
1055 focusRect = rect;
1057 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1058 rect.top = rect.bottom;
1060 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1062 if (!IS_OWNERDRAW(descr))
1064 /* Clear the bottom of the column */
1065 if (rect.top < descr->height)
1067 rect.bottom = descr->height;
1068 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1069 &rect, NULL, 0, NULL );
1073 /* Go to the next column */
1074 rect.left += descr->column_width;
1075 rect.right += descr->column_width;
1076 rect.top = 0;
1077 col_pos = descr->page_size - 1;
1079 else
1081 col_pos--;
1082 if (rect.top >= descr->height) break;
1086 /* Paint the focus item now */
1087 if (focusRect.top != focusRect.bottom &&
1088 descr->caret_on && descr->in_focus)
1089 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1091 if (!IS_OWNERDRAW(descr))
1093 /* Clear the remainder of the client area */
1094 if (rect.top < descr->height)
1096 rect.bottom = descr->height;
1097 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1098 &rect, NULL, 0, NULL );
1100 if (rect.right < descr->width)
1102 rect.left = rect.right;
1103 rect.right = descr->width;
1104 rect.top = 0;
1105 rect.bottom = descr->height;
1106 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1107 &rect, NULL, 0, NULL );
1110 if (oldFont) SelectObject( hdc, oldFont );
1111 if (oldBrush) SelectObject( hdc, oldBrush );
1112 return 0;
1116 /***********************************************************************
1117 * LISTBOX_InvalidateItems
1119 * Invalidate all items from a given item. If the specified item is not
1120 * visible, nothing happens.
1122 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1124 RECT rect;
1126 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1128 if (descr->style & LBS_NOREDRAW)
1130 descr->style |= LBS_DISPLAYCHANGED;
1131 return;
1133 rect.bottom = descr->height;
1134 InvalidateRect( descr->self, &rect, TRUE );
1135 if (descr->style & LBS_MULTICOLUMN)
1137 /* Repaint the other columns */
1138 rect.left = rect.right;
1139 rect.right = descr->width;
1140 rect.top = 0;
1141 InvalidateRect( descr->self, &rect, TRUE );
1146 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1148 RECT rect;
1150 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1151 InvalidateRect( descr->self, &rect, TRUE );
1154 /***********************************************************************
1155 * LISTBOX_GetItemHeight
1157 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1159 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1161 if ((index < 0) || (index >= descr->nb_items))
1163 SetLastError(ERROR_INVALID_INDEX);
1164 return LB_ERR;
1166 return descr->items[index].height;
1168 else return descr->item_height;
1172 /***********************************************************************
1173 * LISTBOX_SetItemHeight
1175 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1177 if (height > MAXBYTE)
1178 return -1;
1180 if (!height) height = 1;
1182 if (descr->style & LBS_OWNERDRAWVARIABLE)
1184 if ((index < 0) || (index >= descr->nb_items))
1186 SetLastError(ERROR_INVALID_INDEX);
1187 return LB_ERR;
1189 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1190 descr->items[index].height = height;
1191 LISTBOX_UpdateScroll( descr );
1192 if (repaint)
1193 LISTBOX_InvalidateItems( descr, index );
1195 else if (height != descr->item_height)
1197 TRACE("[%p]: new height = %d\n", descr->self, height );
1198 descr->item_height = height;
1199 LISTBOX_UpdatePage( descr );
1200 LISTBOX_UpdateScroll( descr );
1201 if (repaint)
1202 InvalidateRect( descr->self, 0, TRUE );
1204 return LB_OKAY;
1208 /***********************************************************************
1209 * LISTBOX_SetHorizontalPos
1211 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1213 INT diff;
1215 if (pos > descr->horz_extent - descr->width)
1216 pos = descr->horz_extent - descr->width;
1217 if (pos < 0) pos = 0;
1218 if (!(diff = descr->horz_pos - pos)) return;
1219 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1220 descr->horz_pos = pos;
1221 LISTBOX_UpdateScroll( descr );
1222 if (abs(diff) < descr->width)
1224 RECT rect;
1225 /* Invalidate the focused item so it will be repainted correctly */
1226 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1227 InvalidateRect( descr->self, &rect, TRUE );
1228 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1229 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1231 else
1232 InvalidateRect( descr->self, NULL, TRUE );
1236 /***********************************************************************
1237 * LISTBOX_SetHorizontalExtent
1239 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1241 if (descr->style & LBS_MULTICOLUMN)
1242 return LB_OKAY;
1243 if (extent == descr->horz_extent) return LB_OKAY;
1244 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1245 descr->horz_extent = extent;
1246 if (descr->style & WS_HSCROLL) {
1247 SCROLLINFO info;
1248 info.cbSize = sizeof(info);
1249 info.nMin = 0;
1250 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1251 info.fMask = SIF_RANGE;
1252 if (descr->style & LBS_DISABLENOSCROLL)
1253 info.fMask |= SIF_DISABLENOSCROLL;
1254 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1256 if (descr->horz_pos > extent - descr->width)
1257 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1258 return LB_OKAY;
1262 /***********************************************************************
1263 * LISTBOX_SetColumnWidth
1265 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1267 RECT rect;
1269 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1271 GetClientRect(descr->self, &rect);
1272 descr->width = rect.right - rect.left;
1273 descr->height = rect.bottom - rect.top;
1274 descr->column_width = column_width;
1276 LISTBOX_UpdatePage(descr);
1277 LISTBOX_UpdateScroll(descr);
1278 return LB_OKAY;
1282 /***********************************************************************
1283 * LISTBOX_SetFont
1285 * Returns the item height.
1287 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1289 HDC hdc;
1290 HFONT oldFont = 0;
1291 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1292 SIZE sz;
1294 descr->font = font;
1296 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1298 ERR("unable to get DC.\n" );
1299 return 16;
1301 if (font) oldFont = SelectObject( hdc, font );
1302 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1303 if (oldFont) SelectObject( hdc, oldFont );
1304 ReleaseDC( descr->self, hdc );
1306 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1307 if (!IS_OWNERDRAW(descr))
1308 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1309 return sz.cy;
1313 /***********************************************************************
1314 * LISTBOX_MakeItemVisible
1316 * Make sure that a given item is partially or fully visible.
1318 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1320 INT top;
1322 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1324 if (index <= descr->top_item) top = index;
1325 else if (descr->style & LBS_MULTICOLUMN)
1327 INT cols = descr->width;
1328 if (!fully) cols += descr->column_width - 1;
1329 if (cols >= descr->column_width) cols /= descr->column_width;
1330 else cols = 1;
1331 if (index < descr->top_item + (descr->page_size * cols)) return;
1332 top = index - descr->page_size * (cols - 1);
1334 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1336 INT height = fully ? descr->items[index].height : 1;
1337 for (top = index; top > descr->top_item; top--)
1338 if ((height += descr->items[top-1].height) > descr->height) break;
1340 else
1342 if (index < descr->top_item + descr->page_size) return;
1343 if (!fully && (index == descr->top_item + descr->page_size) &&
1344 (descr->height > (descr->page_size * descr->item_height))) return;
1345 top = index - descr->page_size + 1;
1347 LISTBOX_SetTopItem( descr, top, TRUE );
1350 /***********************************************************************
1351 * LISTBOX_SetCaretIndex
1353 * NOTES
1354 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1357 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1359 INT oldfocus = descr->focus_item;
1361 TRACE("old focus %d, index %d\n", oldfocus, index);
1363 if (descr->style & LBS_NOSEL) return LB_ERR;
1364 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1365 if (index == oldfocus) return LB_OKAY;
1367 LISTBOX_DrawFocusRect( descr, FALSE );
1368 descr->focus_item = index;
1370 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1371 LISTBOX_DrawFocusRect( descr, TRUE );
1373 return LB_OKAY;
1377 /***********************************************************************
1378 * LISTBOX_SelectItemRange
1380 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1382 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1383 INT last, BOOL on )
1385 INT i;
1387 /* A few sanity checks */
1389 if (descr->style & LBS_NOSEL) return LB_ERR;
1390 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1392 if (!descr->nb_items) return LB_OKAY;
1394 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1395 if (first < 0) first = 0;
1396 if (last < first) return LB_OKAY;
1398 if (on) /* Turn selection on */
1400 for (i = first; i <= last; i++)
1402 if (descr->items[i].selected) continue;
1403 descr->items[i].selected = TRUE;
1404 LISTBOX_InvalidateItemRect(descr, i);
1407 else /* Turn selection off */
1409 for (i = first; i <= last; i++)
1411 if (!descr->items[i].selected) continue;
1412 descr->items[i].selected = FALSE;
1413 LISTBOX_InvalidateItemRect(descr, i);
1416 return LB_OKAY;
1419 /***********************************************************************
1420 * LISTBOX_SetSelection
1422 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1423 BOOL on, BOOL send_notify )
1425 TRACE( "cur_sel=%d index=%d notify=%s\n",
1426 descr->selected_item, index, send_notify ? "YES" : "NO" );
1428 if (descr->style & LBS_NOSEL)
1430 descr->selected_item = index;
1431 return LB_ERR;
1433 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1434 if (descr->style & LBS_MULTIPLESEL)
1436 if (index == -1) /* Select all items */
1437 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1438 else /* Only one item */
1439 return LISTBOX_SelectItemRange( descr, index, index, on );
1441 else
1443 INT oldsel = descr->selected_item;
1444 if (index == oldsel) return LB_OKAY;
1445 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1446 if (index != -1) descr->items[index].selected = TRUE;
1447 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1448 descr->selected_item = index;
1449 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1450 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1451 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1452 else
1453 if( descr->lphc ) /* set selection change flag for parent combo */
1454 descr->lphc->wState |= CBF_SELCHANGE;
1456 return LB_OKAY;
1460 /***********************************************************************
1461 * LISTBOX_MoveCaret
1463 * Change the caret position and extend the selection to the new caret.
1465 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1467 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1469 if ((index < 0) || (index >= descr->nb_items))
1470 return;
1472 /* Important, repaint needs to be done in this order if
1473 you want to mimic Windows behavior:
1474 1. Remove the focus and paint the item
1475 2. Remove the selection and paint the item(s)
1476 3. Set the selection and repaint the item(s)
1477 4. Set the focus to 'index' and repaint the item */
1479 /* 1. remove the focus and repaint the item */
1480 LISTBOX_DrawFocusRect( descr, FALSE );
1482 /* 2. then turn off the previous selection */
1483 /* 3. repaint the new selected item */
1484 if (descr->style & LBS_EXTENDEDSEL)
1486 if (descr->anchor_item != -1)
1488 INT first = min( index, descr->anchor_item );
1489 INT last = max( index, descr->anchor_item );
1490 if (first > 0)
1491 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1492 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1493 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1496 else if (!(descr->style & LBS_MULTIPLESEL))
1498 /* Set selection to new caret item */
1499 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1502 /* 4. repaint the new item with the focus */
1503 descr->focus_item = index;
1504 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1505 LISTBOX_DrawFocusRect( descr, TRUE );
1509 /***********************************************************************
1510 * LISTBOX_InsertItem
1512 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1513 LPWSTR str, ULONG_PTR data )
1515 LB_ITEMDATA *item;
1516 INT max_items;
1517 INT oldfocus = descr->focus_item;
1519 if (index == -1) index = descr->nb_items;
1520 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1521 if (!descr->items) max_items = 0;
1522 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1523 if (descr->nb_items == max_items)
1525 /* We need to grow the array */
1526 max_items += LB_ARRAY_GRANULARITY;
1527 if (descr->items)
1528 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1529 max_items * sizeof(LB_ITEMDATA) );
1530 else
1531 item = HeapAlloc( GetProcessHeap(), 0,
1532 max_items * sizeof(LB_ITEMDATA) );
1533 if (!item)
1535 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1536 return LB_ERRSPACE;
1538 descr->items = item;
1541 /* Insert the item structure */
1543 item = &descr->items[index];
1544 if (index < descr->nb_items)
1545 RtlMoveMemory( item + 1, item,
1546 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1547 item->str = str;
1548 item->data = HAS_STRINGS(descr) ? 0 : data;
1549 item->height = 0;
1550 item->selected = FALSE;
1551 descr->nb_items++;
1553 /* Get item height */
1555 if (descr->style & LBS_OWNERDRAWVARIABLE)
1557 MEASUREITEMSTRUCT mis;
1558 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1560 mis.CtlType = ODT_LISTBOX;
1561 mis.CtlID = id;
1562 mis.itemID = index;
1563 mis.itemData = str ? (ULONG_PTR)str : data;
1564 mis.itemHeight = descr->item_height;
1565 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1566 item->height = mis.itemHeight ? mis.itemHeight : 1;
1567 TRACE("[%p]: measure item %d (%s) = %d\n",
1568 descr->self, index, str ? debugstr_w(str) : "", item->height );
1571 /* Repaint the items */
1573 LISTBOX_UpdateScroll( descr );
1574 LISTBOX_InvalidateItems( descr, index );
1576 /* Move selection and focused item */
1577 /* If listbox was empty, set focus to the first item */
1578 if (descr->nb_items == 1)
1579 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1580 /* single select don't change selection index in win31 */
1581 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1583 descr->selected_item++;
1584 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1586 else
1588 if (index <= descr->selected_item)
1590 descr->selected_item++;
1591 descr->focus_item = oldfocus; /* focus not changed */
1594 return LB_OKAY;
1598 /***********************************************************************
1599 * LISTBOX_InsertString
1601 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1603 LPWSTR new_str = NULL;
1604 LRESULT ret;
1606 if (HAS_STRINGS(descr))
1608 static const WCHAR empty_stringW[] = { 0 };
1609 if (!str) str = empty_stringW;
1610 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1612 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1613 return LB_ERRSPACE;
1615 strcpyW(new_str, str);
1618 if (index == -1) index = descr->nb_items;
1619 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1621 HeapFree( GetProcessHeap(), 0, new_str );
1622 return ret;
1625 TRACE("[%p]: added item %d %s\n",
1626 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1627 return index;
1631 /***********************************************************************
1632 * LISTBOX_DeleteItem
1634 * Delete the content of an item. 'index' must be a valid index.
1636 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1638 /* save the item data before it gets freed by LB_RESETCONTENT */
1639 ULONG_PTR item_data = descr->items[index].data;
1640 LPWSTR item_str = descr->items[index].str;
1642 if (!descr->nb_items)
1643 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1645 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1646 * while Win95 sends it for all items with user data.
1647 * It's probably better to send it too often than not
1648 * often enough, so this is what we do here.
1650 if (IS_OWNERDRAW(descr) || item_data)
1652 DELETEITEMSTRUCT dis;
1653 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1655 dis.CtlType = ODT_LISTBOX;
1656 dis.CtlID = id;
1657 dis.itemID = index;
1658 dis.hwndItem = descr->self;
1659 dis.itemData = item_data;
1660 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1662 if (HAS_STRINGS(descr))
1663 HeapFree( GetProcessHeap(), 0, item_str );
1667 /***********************************************************************
1668 * LISTBOX_RemoveItem
1670 * Remove an item from the listbox and delete its content.
1672 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1674 LB_ITEMDATA *item;
1675 INT max_items;
1677 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1679 /* We need to invalidate the original rect instead of the updated one. */
1680 LISTBOX_InvalidateItems( descr, index );
1682 descr->nb_items--;
1683 LISTBOX_DeleteItem( descr, index );
1685 if (!descr->nb_items) return LB_OKAY;
1687 /* Remove the item */
1689 item = &descr->items[index];
1690 if (index < descr->nb_items)
1691 RtlMoveMemory( item, item + 1,
1692 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1693 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1695 /* Shrink the item array if possible */
1697 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1698 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1700 max_items -= LB_ARRAY_GRANULARITY;
1701 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1702 max_items * sizeof(LB_ITEMDATA) );
1703 if (item) descr->items = item;
1705 /* Repaint the items */
1707 LISTBOX_UpdateScroll( descr );
1708 /* if we removed the scrollbar, reset the top of the list
1709 (correct for owner-drawn ???) */
1710 if (descr->nb_items == descr->page_size)
1711 LISTBOX_SetTopItem( descr, 0, TRUE );
1713 /* Move selection and focused item */
1714 if (!IS_MULTISELECT(descr))
1716 if (index == descr->selected_item)
1717 descr->selected_item = -1;
1718 else if (index < descr->selected_item)
1720 descr->selected_item--;
1721 if (ISWIN31) /* win 31 do not change the selected item number */
1722 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1726 if (descr->focus_item >= descr->nb_items)
1728 descr->focus_item = descr->nb_items - 1;
1729 if (descr->focus_item < 0) descr->focus_item = 0;
1731 return LB_OKAY;
1735 /***********************************************************************
1736 * LISTBOX_ResetContent
1738 static void LISTBOX_ResetContent( LB_DESCR *descr )
1740 INT i;
1742 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1743 HeapFree( GetProcessHeap(), 0, descr->items );
1744 descr->nb_items = 0;
1745 descr->top_item = 0;
1746 descr->selected_item = -1;
1747 descr->focus_item = 0;
1748 descr->anchor_item = -1;
1749 descr->items = NULL;
1753 /***********************************************************************
1754 * LISTBOX_SetCount
1756 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1758 LRESULT ret;
1760 if (HAS_STRINGS(descr))
1762 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1763 return LB_ERR;
1766 /* FIXME: this is far from optimal... */
1767 if (count > descr->nb_items)
1769 while (count > descr->nb_items)
1770 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1771 return ret;
1773 else if (count < descr->nb_items)
1775 while (count < descr->nb_items)
1776 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1777 return ret;
1780 InvalidateRect( descr->self, NULL, TRUE );
1781 return LB_OKAY;
1785 /***********************************************************************
1786 * LISTBOX_Directory
1788 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1789 LPCWSTR filespec, BOOL long_names )
1791 HANDLE handle;
1792 LRESULT ret = LB_OKAY;
1793 WIN32_FIND_DATAW entry;
1794 int pos;
1795 LRESULT maxinsert = LB_ERR;
1797 /* don't scan directory if we just want drives exclusively */
1798 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1799 /* scan directory */
1800 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1802 int le = GetLastError();
1803 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1805 else
1809 WCHAR buffer[270];
1810 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1812 static const WCHAR bracketW[] = { ']',0 };
1813 static const WCHAR dotW[] = { '.',0 };
1814 if (!(attrib & DDL_DIRECTORY) ||
1815 !strcmpW( entry.cFileName, dotW )) continue;
1816 buffer[0] = '[';
1817 if (!long_names && entry.cAlternateFileName[0])
1818 strcpyW( buffer + 1, entry.cAlternateFileName );
1819 else
1820 strcpyW( buffer + 1, entry.cFileName );
1821 strcatW(buffer, bracketW);
1823 else /* not a directory */
1825 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1826 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1828 if ((attrib & DDL_EXCLUSIVE) &&
1829 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1830 continue;
1831 #undef ATTRIBS
1832 if (!long_names && entry.cAlternateFileName[0])
1833 strcpyW( buffer, entry.cAlternateFileName );
1834 else
1835 strcpyW( buffer, entry.cFileName );
1837 if (!long_names) CharLowerW( buffer );
1838 pos = LISTBOX_FindFileStrPos( descr, buffer );
1839 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1840 break;
1841 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1842 } while (FindNextFileW( handle, &entry ));
1843 FindClose( handle );
1846 if (ret >= 0)
1848 ret = maxinsert;
1850 /* scan drives */
1851 if (attrib & DDL_DRIVES)
1853 WCHAR buffer[] = {'[','-','a','-',']',0};
1854 WCHAR root[] = {'A',':','\\',0};
1855 int drive;
1856 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1858 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1859 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1860 break;
1864 return ret;
1868 /***********************************************************************
1869 * LISTBOX_HandleVScroll
1871 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1873 SCROLLINFO info;
1875 if (descr->style & LBS_MULTICOLUMN) return 0;
1876 switch(scrollReq)
1878 case SB_LINEUP:
1879 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1880 break;
1881 case SB_LINEDOWN:
1882 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1883 break;
1884 case SB_PAGEUP:
1885 LISTBOX_SetTopItem( descr, descr->top_item -
1886 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1887 break;
1888 case SB_PAGEDOWN:
1889 LISTBOX_SetTopItem( descr, descr->top_item +
1890 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1891 break;
1892 case SB_THUMBPOSITION:
1893 LISTBOX_SetTopItem( descr, pos, TRUE );
1894 break;
1895 case SB_THUMBTRACK:
1896 info.cbSize = sizeof(info);
1897 info.fMask = SIF_TRACKPOS;
1898 GetScrollInfo( descr->self, SB_VERT, &info );
1899 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1900 break;
1901 case SB_TOP:
1902 LISTBOX_SetTopItem( descr, 0, TRUE );
1903 break;
1904 case SB_BOTTOM:
1905 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1906 break;
1908 return 0;
1912 /***********************************************************************
1913 * LISTBOX_HandleHScroll
1915 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1917 SCROLLINFO info;
1918 INT page;
1920 if (descr->style & LBS_MULTICOLUMN)
1922 switch(scrollReq)
1924 case SB_LINELEFT:
1925 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1926 TRUE );
1927 break;
1928 case SB_LINERIGHT:
1929 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1930 TRUE );
1931 break;
1932 case SB_PAGELEFT:
1933 page = descr->width / descr->column_width;
1934 if (page < 1) page = 1;
1935 LISTBOX_SetTopItem( descr,
1936 descr->top_item - page * descr->page_size, TRUE );
1937 break;
1938 case SB_PAGERIGHT:
1939 page = descr->width / descr->column_width;
1940 if (page < 1) page = 1;
1941 LISTBOX_SetTopItem( descr,
1942 descr->top_item + page * descr->page_size, TRUE );
1943 break;
1944 case SB_THUMBPOSITION:
1945 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1946 break;
1947 case SB_THUMBTRACK:
1948 info.cbSize = sizeof(info);
1949 info.fMask = SIF_TRACKPOS;
1950 GetScrollInfo( descr->self, SB_VERT, &info );
1951 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1952 TRUE );
1953 break;
1954 case SB_LEFT:
1955 LISTBOX_SetTopItem( descr, 0, TRUE );
1956 break;
1957 case SB_RIGHT:
1958 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1959 break;
1962 else if (descr->horz_extent)
1964 switch(scrollReq)
1966 case SB_LINELEFT:
1967 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1968 break;
1969 case SB_LINERIGHT:
1970 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1971 break;
1972 case SB_PAGELEFT:
1973 LISTBOX_SetHorizontalPos( descr,
1974 descr->horz_pos - descr->width );
1975 break;
1976 case SB_PAGERIGHT:
1977 LISTBOX_SetHorizontalPos( descr,
1978 descr->horz_pos + descr->width );
1979 break;
1980 case SB_THUMBPOSITION:
1981 LISTBOX_SetHorizontalPos( descr, pos );
1982 break;
1983 case SB_THUMBTRACK:
1984 info.cbSize = sizeof(info);
1985 info.fMask = SIF_TRACKPOS;
1986 GetScrollInfo( descr->self, SB_HORZ, &info );
1987 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1988 break;
1989 case SB_LEFT:
1990 LISTBOX_SetHorizontalPos( descr, 0 );
1991 break;
1992 case SB_RIGHT:
1993 LISTBOX_SetHorizontalPos( descr,
1994 descr->horz_extent - descr->width );
1995 break;
1998 return 0;
2001 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2003 UINT pulScrollLines = 3;
2005 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2007 /* if scrolling changes direction, ignore left overs */
2008 if ((delta < 0 && descr->wheel_remain < 0) ||
2009 (delta > 0 && descr->wheel_remain > 0))
2010 descr->wheel_remain += delta;
2011 else
2012 descr->wheel_remain = delta;
2014 if (descr->wheel_remain && pulScrollLines)
2016 int cLineScroll;
2017 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2018 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2019 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2020 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2022 return 0;
2025 /***********************************************************************
2026 * LISTBOX_HandleLButtonDown
2028 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2030 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2032 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2033 descr->self, x, y, index, descr->focus_item);
2035 if (!descr->caret_on && (descr->in_focus)) return 0;
2037 if (!descr->in_focus)
2039 if( !descr->lphc ) SetFocus( descr->self );
2040 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2043 if (index == -1) return 0;
2045 if (!descr->lphc)
2047 if (descr->style & LBS_NOTIFY )
2048 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2049 MAKELPARAM( x, y ) );
2052 descr->captured = TRUE;
2053 SetCapture( descr->self );
2055 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2057 /* we should perhaps make sure that all items are deselected
2058 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2059 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2060 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2063 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2064 if (keys & MK_CONTROL)
2066 LISTBOX_SetCaretIndex( descr, index, FALSE );
2067 LISTBOX_SetSelection( descr, index,
2068 !descr->items[index].selected,
2069 (descr->style & LBS_NOTIFY) != 0);
2071 else
2073 LISTBOX_MoveCaret( descr, index, FALSE );
2075 if (descr->style & LBS_EXTENDEDSEL)
2077 LISTBOX_SetSelection( descr, index,
2078 descr->items[index].selected,
2079 (descr->style & LBS_NOTIFY) != 0 );
2081 else
2083 LISTBOX_SetSelection( descr, index,
2084 !descr->items[index].selected,
2085 (descr->style & LBS_NOTIFY) != 0 );
2089 else
2091 descr->anchor_item = index;
2092 LISTBOX_MoveCaret( descr, index, FALSE );
2093 LISTBOX_SetSelection( descr, index,
2094 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2097 if (!descr->lphc)
2099 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2101 POINT pt;
2103 pt.x = x;
2104 pt.y = y;
2106 if (DragDetect( descr->self, pt ))
2107 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2110 return 0;
2114 /*************************************************************************
2115 * LISTBOX_HandleLButtonDownCombo [Internal]
2117 * Process LButtonDown message for the ComboListBox
2119 * PARAMS
2120 * pWnd [I] The windows internal structure
2121 * pDescr [I] The ListBox internal structure
2122 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2123 * x [I] X Mouse Coordinate
2124 * y [I] Y Mouse Coordinate
2126 * RETURNS
2127 * 0 since we are processing the WM_LBUTTONDOWN Message
2129 * NOTES
2130 * This function is only to be used when a ListBox is a ComboListBox
2133 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2135 RECT clientRect, screenRect;
2136 POINT mousePos;
2138 mousePos.x = x;
2139 mousePos.y = y;
2141 GetClientRect(descr->self, &clientRect);
2143 if(PtInRect(&clientRect, mousePos))
2145 /* MousePos is in client, resume normal processing */
2146 if (msg == WM_LBUTTONDOWN)
2148 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2149 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2151 else if (descr->style & LBS_NOTIFY)
2152 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2154 else
2156 POINT screenMousePos;
2157 HWND hWndOldCapture;
2159 /* Check the Non-Client Area */
2160 screenMousePos = mousePos;
2161 hWndOldCapture = GetCapture();
2162 ReleaseCapture();
2163 GetWindowRect(descr->self, &screenRect);
2164 ClientToScreen(descr->self, &screenMousePos);
2166 if(!PtInRect(&screenRect, screenMousePos))
2168 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2169 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2170 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2172 else
2174 /* Check to see the NC is a scrollbar */
2175 INT nHitTestType=0;
2176 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2177 /* Check Vertical scroll bar */
2178 if (style & WS_VSCROLL)
2180 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2181 if (PtInRect( &clientRect, mousePos ))
2182 nHitTestType = HTVSCROLL;
2184 /* Check horizontal scroll bar */
2185 if (style & WS_HSCROLL)
2187 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2188 if (PtInRect( &clientRect, mousePos ))
2189 nHitTestType = HTHSCROLL;
2191 /* Windows sends this message when a scrollbar is clicked
2194 if(nHitTestType != 0)
2196 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2197 MAKELONG(screenMousePos.x, screenMousePos.y));
2199 /* Resume the Capture after scrolling is complete
2201 if(hWndOldCapture != 0)
2202 SetCapture(hWndOldCapture);
2205 return 0;
2208 /***********************************************************************
2209 * LISTBOX_HandleLButtonUp
2211 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2213 if (LISTBOX_Timer != LB_TIMER_NONE)
2214 KillSystemTimer( descr->self, LB_TIMER_ID );
2215 LISTBOX_Timer = LB_TIMER_NONE;
2216 if (descr->captured)
2218 descr->captured = FALSE;
2219 if (GetCapture() == descr->self) ReleaseCapture();
2220 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2221 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2223 return 0;
2227 /***********************************************************************
2228 * LISTBOX_HandleTimer
2230 * Handle scrolling upon a timer event.
2231 * Return TRUE if scrolling should continue.
2233 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2235 switch(dir)
2237 case LB_TIMER_UP:
2238 if (descr->top_item) index = descr->top_item - 1;
2239 else index = 0;
2240 break;
2241 case LB_TIMER_LEFT:
2242 if (descr->top_item) index -= descr->page_size;
2243 break;
2244 case LB_TIMER_DOWN:
2245 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2246 if (index == descr->focus_item) index++;
2247 if (index >= descr->nb_items) index = descr->nb_items - 1;
2248 break;
2249 case LB_TIMER_RIGHT:
2250 if (index + descr->page_size < descr->nb_items)
2251 index += descr->page_size;
2252 break;
2253 case LB_TIMER_NONE:
2254 break;
2256 if (index == descr->focus_item) return FALSE;
2257 LISTBOX_MoveCaret( descr, index, FALSE );
2258 return TRUE;
2262 /***********************************************************************
2263 * LISTBOX_HandleSystemTimer
2265 * WM_SYSTIMER handler.
2267 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2269 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2271 KillSystemTimer( descr->self, LB_TIMER_ID );
2272 LISTBOX_Timer = LB_TIMER_NONE;
2274 return 0;
2278 /***********************************************************************
2279 * LISTBOX_HandleMouseMove
2281 * WM_MOUSEMOVE handler.
2283 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2284 INT x, INT y )
2286 INT index;
2287 TIMER_DIRECTION dir = LB_TIMER_NONE;
2289 if (!descr->captured) return;
2291 if (descr->style & LBS_MULTICOLUMN)
2293 if (y < 0) y = 0;
2294 else if (y >= descr->item_height * descr->page_size)
2295 y = descr->item_height * descr->page_size - 1;
2297 if (x < 0)
2299 dir = LB_TIMER_LEFT;
2300 x = 0;
2302 else if (x >= descr->width)
2304 dir = LB_TIMER_RIGHT;
2305 x = descr->width - 1;
2308 else
2310 if (y < 0) dir = LB_TIMER_UP; /* above */
2311 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2314 index = LISTBOX_GetItemFromPoint( descr, x, y );
2315 if (index == -1) index = descr->focus_item;
2316 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2318 /* Start/stop the system timer */
2320 if (dir != LB_TIMER_NONE)
2321 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2322 else if (LISTBOX_Timer != LB_TIMER_NONE)
2323 KillSystemTimer( descr->self, LB_TIMER_ID );
2324 LISTBOX_Timer = dir;
2328 /***********************************************************************
2329 * LISTBOX_HandleKeyDown
2331 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2333 INT caret = -1;
2334 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2335 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2336 bForceSelection = FALSE; /* only for single select list */
2338 if (descr->style & LBS_WANTKEYBOARDINPUT)
2340 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2341 MAKEWPARAM(LOWORD(key), descr->focus_item),
2342 (LPARAM)descr->self );
2343 if (caret == -2) return 0;
2345 if (caret == -1) switch(key)
2347 case VK_LEFT:
2348 if (descr->style & LBS_MULTICOLUMN)
2350 bForceSelection = FALSE;
2351 if (descr->focus_item >= descr->page_size)
2352 caret = descr->focus_item - descr->page_size;
2353 break;
2355 /* fall through */
2356 case VK_UP:
2357 caret = descr->focus_item - 1;
2358 if (caret < 0) caret = 0;
2359 break;
2360 case VK_RIGHT:
2361 if (descr->style & LBS_MULTICOLUMN)
2363 bForceSelection = FALSE;
2364 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2365 break;
2367 /* fall through */
2368 case VK_DOWN:
2369 caret = descr->focus_item + 1;
2370 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2371 break;
2373 case VK_PRIOR:
2374 if (descr->style & LBS_MULTICOLUMN)
2376 INT page = descr->width / descr->column_width;
2377 if (page < 1) page = 1;
2378 caret = descr->focus_item - (page * descr->page_size) + 1;
2380 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2381 if (caret < 0) caret = 0;
2382 break;
2383 case VK_NEXT:
2384 if (descr->style & LBS_MULTICOLUMN)
2386 INT page = descr->width / descr->column_width;
2387 if (page < 1) page = 1;
2388 caret = descr->focus_item + (page * descr->page_size) - 1;
2390 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2391 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2392 break;
2393 case VK_HOME:
2394 caret = 0;
2395 break;
2396 case VK_END:
2397 caret = descr->nb_items - 1;
2398 break;
2399 case VK_SPACE:
2400 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2401 else if (descr->style & LBS_MULTIPLESEL)
2403 LISTBOX_SetSelection( descr, descr->focus_item,
2404 !descr->items[descr->focus_item].selected,
2405 (descr->style & LBS_NOTIFY) != 0 );
2407 break;
2408 default:
2409 bForceSelection = FALSE;
2411 if (bForceSelection) /* focused item is used instead of key */
2412 caret = descr->focus_item;
2413 if (caret >= 0)
2415 if (((descr->style & LBS_EXTENDEDSEL) &&
2416 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2417 !IS_MULTISELECT(descr))
2418 descr->anchor_item = caret;
2419 LISTBOX_MoveCaret( descr, caret, TRUE );
2421 if (descr->style & LBS_MULTIPLESEL)
2422 descr->selected_item = caret;
2423 else
2424 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2425 if (descr->style & LBS_NOTIFY)
2427 if (descr->lphc && IsWindowVisible( descr->self ))
2429 /* make sure that combo parent doesn't hide us */
2430 descr->lphc->wState |= CBF_NOROLLUP;
2432 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2435 return 0;
2439 /***********************************************************************
2440 * LISTBOX_HandleChar
2442 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2444 INT caret = -1;
2445 WCHAR str[2];
2447 str[0] = charW;
2448 str[1] = '\0';
2450 if (descr->style & LBS_WANTKEYBOARDINPUT)
2452 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2453 MAKEWPARAM(charW, descr->focus_item),
2454 (LPARAM)descr->self );
2455 if (caret == -2) return 0;
2457 if (caret == -1)
2458 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2459 if (caret != -1)
2461 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2462 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2463 LISTBOX_MoveCaret( descr, caret, TRUE );
2464 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2465 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2467 return 0;
2471 /***********************************************************************
2472 * LISTBOX_Create
2474 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2476 LB_DESCR *descr;
2477 MEASUREITEMSTRUCT mis;
2478 RECT rect;
2480 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2481 return FALSE;
2483 GetClientRect( hwnd, &rect );
2484 descr->self = hwnd;
2485 descr->owner = GetParent( descr->self );
2486 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2487 descr->width = rect.right - rect.left;
2488 descr->height = rect.bottom - rect.top;
2489 descr->items = NULL;
2490 descr->nb_items = 0;
2491 descr->top_item = 0;
2492 descr->selected_item = -1;
2493 descr->focus_item = 0;
2494 descr->anchor_item = -1;
2495 descr->item_height = 1;
2496 descr->page_size = 1;
2497 descr->column_width = 150;
2498 descr->horz_extent = 0;
2499 descr->horz_pos = 0;
2500 descr->nb_tabs = 0;
2501 descr->tabs = NULL;
2502 descr->wheel_remain = 0;
2503 descr->caret_on = !lphc;
2504 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2505 descr->in_focus = FALSE;
2506 descr->captured = FALSE;
2507 descr->font = 0;
2508 descr->locale = GetUserDefaultLCID();
2509 descr->lphc = lphc;
2511 if( lphc )
2513 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2514 descr->owner = lphc->self;
2517 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2519 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2521 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2522 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2523 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2524 descr->item_height = LISTBOX_SetFont( descr, 0 );
2526 if (descr->style & LBS_OWNERDRAWFIXED)
2528 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2530 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2531 descr->item_height = lphc->fixedOwnerDrawHeight;
2533 else
2535 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2536 mis.CtlType = ODT_LISTBOX;
2537 mis.CtlID = id;
2538 mis.itemID = -1;
2539 mis.itemWidth = 0;
2540 mis.itemData = 0;
2541 mis.itemHeight = descr->item_height;
2542 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2543 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2547 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2548 return TRUE;
2552 /***********************************************************************
2553 * LISTBOX_Destroy
2555 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2557 LISTBOX_ResetContent( descr );
2558 SetWindowLongPtrW( descr->self, 0, 0 );
2559 HeapFree( GetProcessHeap(), 0, descr );
2560 return TRUE;
2564 /***********************************************************************
2565 * ListBoxWndProc_common
2567 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2569 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2570 LPHEADCOMBO lphc = 0;
2571 LRESULT ret;
2573 if (!descr)
2575 if (!IsWindow(hwnd)) return 0;
2577 if (msg == WM_CREATE)
2579 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2580 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2581 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2582 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2583 return 0;
2585 /* Ignore all other messages before we get a WM_CREATE */
2586 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2587 DefWindowProcA( hwnd, msg, wParam, lParam );
2589 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2591 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2592 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2594 switch(msg)
2596 case LB_RESETCONTENT:
2597 LISTBOX_ResetContent( descr );
2598 LISTBOX_UpdateScroll( descr );
2599 InvalidateRect( descr->self, NULL, TRUE );
2600 return 0;
2602 case LB_ADDSTRING:
2604 INT ret;
2605 LPWSTR textW;
2606 if(unicode || !HAS_STRINGS(descr))
2607 textW = (LPWSTR)lParam;
2608 else
2610 LPSTR textA = (LPSTR)lParam;
2611 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2612 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2613 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2614 else
2615 return LB_ERRSPACE;
2617 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2618 ret = LISTBOX_InsertString( descr, wParam, textW );
2619 if (!unicode && HAS_STRINGS(descr))
2620 HeapFree(GetProcessHeap(), 0, textW);
2621 return ret;
2624 case LB_INSERTSTRING:
2626 INT ret;
2627 LPWSTR textW;
2628 if(unicode || !HAS_STRINGS(descr))
2629 textW = (LPWSTR)lParam;
2630 else
2632 LPSTR textA = (LPSTR)lParam;
2633 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2634 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2635 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2636 else
2637 return LB_ERRSPACE;
2639 ret = LISTBOX_InsertString( descr, wParam, textW );
2640 if(!unicode && HAS_STRINGS(descr))
2641 HeapFree(GetProcessHeap(), 0, textW);
2642 return ret;
2645 case LB_ADDFILE:
2647 INT ret;
2648 LPWSTR textW;
2649 if(unicode || !HAS_STRINGS(descr))
2650 textW = (LPWSTR)lParam;
2651 else
2653 LPSTR textA = (LPSTR)lParam;
2654 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2655 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2656 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2657 else
2658 return LB_ERRSPACE;
2660 wParam = LISTBOX_FindFileStrPos( descr, textW );
2661 ret = LISTBOX_InsertString( descr, wParam, textW );
2662 if(!unicode && HAS_STRINGS(descr))
2663 HeapFree(GetProcessHeap(), 0, textW);
2664 return ret;
2667 case LB_DELETESTRING:
2668 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2669 return descr->nb_items;
2670 else
2672 SetLastError(ERROR_INVALID_INDEX);
2673 return LB_ERR;
2676 case LB_GETITEMDATA:
2677 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2679 SetLastError(ERROR_INVALID_INDEX);
2680 return LB_ERR;
2682 return descr->items[wParam].data;
2684 case LB_SETITEMDATA:
2685 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2687 SetLastError(ERROR_INVALID_INDEX);
2688 return LB_ERR;
2690 descr->items[wParam].data = lParam;
2691 /* undocumented: returns TRUE, not LB_OKAY (0) */
2692 return TRUE;
2694 case LB_GETCOUNT:
2695 return descr->nb_items;
2697 case LB_GETTEXT:
2698 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2700 case LB_GETTEXTLEN:
2701 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2703 SetLastError(ERROR_INVALID_INDEX);
2704 return LB_ERR;
2706 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2707 if (unicode) return strlenW( descr->items[wParam].str );
2708 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2709 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2711 case LB_GETCURSEL:
2712 if (descr->nb_items == 0)
2713 return LB_ERR;
2714 if (!IS_MULTISELECT(descr))
2715 return descr->selected_item;
2716 if (descr->selected_item != -1)
2717 return descr->selected_item;
2718 return descr->focus_item;
2719 /* otherwise, if the user tries to move the selection with the */
2720 /* arrow keys, we will give the application something to choke on */
2721 case LB_GETTOPINDEX:
2722 return descr->top_item;
2724 case LB_GETITEMHEIGHT:
2725 return LISTBOX_GetItemHeight( descr, wParam );
2727 case LB_SETITEMHEIGHT:
2728 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2730 case LB_ITEMFROMPOINT:
2732 POINT pt;
2733 RECT rect;
2734 int index;
2735 BOOL hit = TRUE;
2737 /* The hiword of the return value is not a client area
2738 hittest as suggested by MSDN, but rather a hittest on
2739 the returned listbox item. */
2741 if(descr->nb_items == 0)
2742 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2744 pt.x = (short)LOWORD(lParam);
2745 pt.y = (short)HIWORD(lParam);
2747 SetRect(&rect, 0, 0, descr->width, descr->height);
2749 if(!PtInRect(&rect, pt))
2751 pt.x = min(pt.x, rect.right - 1);
2752 pt.x = max(pt.x, 0);
2753 pt.y = min(pt.y, rect.bottom - 1);
2754 pt.y = max(pt.y, 0);
2755 hit = FALSE;
2758 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2760 if(index == -1)
2762 index = descr->nb_items - 1;
2763 hit = FALSE;
2765 return MAKELONG(index, hit ? 0 : 1);
2768 case LB_SETCARETINDEX:
2769 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2770 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2771 return LB_ERR;
2772 else if (ISWIN31)
2773 return wParam;
2774 else
2775 return LB_OKAY;
2777 case LB_GETCARETINDEX:
2778 return descr->focus_item;
2780 case LB_SETTOPINDEX:
2781 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2783 case LB_SETCOLUMNWIDTH:
2784 return LISTBOX_SetColumnWidth( descr, wParam );
2786 case LB_GETITEMRECT:
2787 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2789 case LB_FINDSTRING:
2791 INT ret;
2792 LPWSTR textW;
2793 if(unicode || !HAS_STRINGS(descr))
2794 textW = (LPWSTR)lParam;
2795 else
2797 LPSTR textA = (LPSTR)lParam;
2798 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2799 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2800 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2802 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2803 if(!unicode && HAS_STRINGS(descr))
2804 HeapFree(GetProcessHeap(), 0, textW);
2805 return ret;
2808 case LB_FINDSTRINGEXACT:
2810 INT ret;
2811 LPWSTR textW;
2812 if(unicode || !HAS_STRINGS(descr))
2813 textW = (LPWSTR)lParam;
2814 else
2816 LPSTR textA = (LPSTR)lParam;
2817 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2818 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2819 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2821 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2822 if(!unicode && HAS_STRINGS(descr))
2823 HeapFree(GetProcessHeap(), 0, textW);
2824 return ret;
2827 case LB_SELECTSTRING:
2829 INT index;
2830 LPWSTR textW;
2832 if(HAS_STRINGS(descr))
2833 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2834 debugstr_a((LPSTR)lParam));
2835 if(unicode || !HAS_STRINGS(descr))
2836 textW = (LPWSTR)lParam;
2837 else
2839 LPSTR textA = (LPSTR)lParam;
2840 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2841 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2842 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2844 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2845 if(!unicode && HAS_STRINGS(descr))
2846 HeapFree(GetProcessHeap(), 0, textW);
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 descr->items[wParam].selected;
2860 case LB_SETSEL:
2861 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2862 if (ret != LB_ERR && wParam)
2863 descr->anchor_item = lParam;
2864 return ret;
2866 case LB_SETCURSEL:
2867 if (IS_MULTISELECT(descr)) return LB_ERR;
2868 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2869 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2870 if (ret != LB_ERR) ret = descr->selected_item;
2871 return ret;
2873 case LB_GETSELCOUNT:
2874 return LISTBOX_GetSelCount( descr );
2876 case LB_GETSELITEMS:
2877 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2879 case LB_SELITEMRANGE:
2880 if (LOWORD(lParam) <= HIWORD(lParam))
2881 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2882 HIWORD(lParam), wParam );
2883 else
2884 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2885 LOWORD(lParam), wParam );
2887 case LB_SELITEMRANGEEX:
2888 if ((INT)lParam >= (INT)wParam)
2889 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2890 else
2891 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2893 case LB_GETHORIZONTALEXTENT:
2894 return descr->horz_extent;
2896 case LB_SETHORIZONTALEXTENT:
2897 return LISTBOX_SetHorizontalExtent( descr, wParam );
2899 case LB_GETANCHORINDEX:
2900 return descr->anchor_item;
2902 case LB_SETANCHORINDEX:
2903 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2905 SetLastError(ERROR_INVALID_INDEX);
2906 return LB_ERR;
2908 descr->anchor_item = (INT)wParam;
2909 return LB_OKAY;
2911 case LB_DIR:
2913 INT ret;
2914 LPWSTR textW;
2915 if(unicode)
2916 textW = (LPWSTR)lParam;
2917 else
2919 LPSTR textA = (LPSTR)lParam;
2920 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2921 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2922 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2924 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2925 if(!unicode)
2926 HeapFree(GetProcessHeap(), 0, textW);
2927 return ret;
2930 case LB_GETLOCALE:
2931 return descr->locale;
2933 case LB_SETLOCALE:
2935 LCID ret;
2936 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2937 return LB_ERR;
2938 ret = descr->locale;
2939 descr->locale = (LCID)wParam;
2940 return ret;
2943 case LB_INITSTORAGE:
2944 return LISTBOX_InitStorage( descr, wParam );
2946 case LB_SETCOUNT:
2947 return LISTBOX_SetCount( descr, (INT)wParam );
2949 case LB_SETTABSTOPS:
2950 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2952 case LB_CARETON:
2953 if (descr->caret_on)
2954 return LB_OKAY;
2955 descr->caret_on = TRUE;
2956 if ((descr->focus_item != -1) && (descr->in_focus))
2957 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2958 return LB_OKAY;
2960 case LB_CARETOFF:
2961 if (!descr->caret_on)
2962 return LB_OKAY;
2963 descr->caret_on = FALSE;
2964 if ((descr->focus_item != -1) && (descr->in_focus))
2965 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2966 return LB_OKAY;
2968 case LB_GETLISTBOXINFO:
2969 return descr->page_size;
2971 case WM_DESTROY:
2972 return LISTBOX_Destroy( descr );
2974 case WM_ENABLE:
2975 InvalidateRect( descr->self, NULL, TRUE );
2976 return 0;
2978 case WM_SETREDRAW:
2979 LISTBOX_SetRedraw( descr, wParam != 0 );
2980 return 0;
2982 case WM_GETDLGCODE:
2983 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2985 case WM_PRINTCLIENT:
2986 case WM_PAINT:
2988 PAINTSTRUCT ps;
2989 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2990 ret = LISTBOX_Paint( descr, hdc );
2991 if( !wParam ) EndPaint( descr->self, &ps );
2993 return ret;
2994 case WM_SIZE:
2995 LISTBOX_UpdateSize( descr );
2996 return 0;
2997 case WM_GETFONT:
2998 return (LRESULT)descr->font;
2999 case WM_SETFONT:
3000 LISTBOX_SetFont( descr, (HFONT)wParam );
3001 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3002 return 0;
3003 case WM_SETFOCUS:
3004 descr->in_focus = TRUE;
3005 descr->caret_on = TRUE;
3006 if (descr->focus_item != -1)
3007 LISTBOX_DrawFocusRect( descr, TRUE );
3008 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3009 return 0;
3010 case WM_KILLFOCUS:
3011 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3012 descr->in_focus = FALSE;
3013 descr->wheel_remain = 0;
3014 if ((descr->focus_item != -1) && descr->caret_on)
3015 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3016 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3017 return 0;
3018 case WM_HSCROLL:
3019 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3020 case WM_VSCROLL:
3021 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3022 case WM_MOUSEWHEEL:
3023 if (wParam & (MK_SHIFT | MK_CONTROL))
3024 return DefWindowProcW( descr->self, msg, wParam, lParam );
3025 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3026 case WM_LBUTTONDOWN:
3027 if (lphc)
3028 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3029 (INT16)LOWORD(lParam),
3030 (INT16)HIWORD(lParam) );
3031 return LISTBOX_HandleLButtonDown( descr, wParam,
3032 (INT16)LOWORD(lParam),
3033 (INT16)HIWORD(lParam) );
3034 case WM_LBUTTONDBLCLK:
3035 if (lphc)
3036 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3037 (INT16)LOWORD(lParam),
3038 (INT16)HIWORD(lParam) );
3039 if (descr->style & LBS_NOTIFY)
3040 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3041 return 0;
3042 case WM_MOUSEMOVE:
3043 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3045 BOOL captured = descr->captured;
3046 POINT mousePos;
3047 RECT clientRect;
3049 mousePos.x = (INT16)LOWORD(lParam);
3050 mousePos.y = (INT16)HIWORD(lParam);
3053 * If we are in a dropdown combobox, we simulate that
3054 * the mouse is captured to show the tracking of the item.
3056 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3057 descr->captured = TRUE;
3059 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3061 descr->captured = captured;
3063 else if (GetCapture() == descr->self)
3065 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3066 (INT16)HIWORD(lParam) );
3068 return 0;
3069 case WM_LBUTTONUP:
3070 if (lphc)
3072 POINT mousePos;
3073 RECT clientRect;
3076 * If the mouse button "up" is not in the listbox,
3077 * we make sure there is no selection by re-selecting the
3078 * item that was selected when the listbox was made visible.
3080 mousePos.x = (INT16)LOWORD(lParam);
3081 mousePos.y = (INT16)HIWORD(lParam);
3083 GetClientRect(descr->self, &clientRect);
3086 * When the user clicks outside the combobox and the focus
3087 * is lost, the owning combobox will send a fake buttonup with
3088 * 0xFFFFFFF as the mouse location, we must also revert the
3089 * selection to the original selection.
3091 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3092 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3094 return LISTBOX_HandleLButtonUp( descr );
3095 case WM_KEYDOWN:
3096 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3098 /* for some reason Windows makes it possible to
3099 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3101 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3102 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3103 && (wParam == VK_DOWN || wParam == VK_UP)) )
3105 COMBO_FlipListbox( lphc, FALSE, FALSE );
3106 return 0;
3109 return LISTBOX_HandleKeyDown( descr, wParam );
3110 case WM_CHAR:
3112 WCHAR charW;
3113 if(unicode)
3114 charW = (WCHAR)wParam;
3115 else
3117 CHAR charA = (CHAR)wParam;
3118 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3120 return LISTBOX_HandleChar( descr, charW );
3122 case WM_SYSTIMER:
3123 return LISTBOX_HandleSystemTimer( descr );
3124 case WM_ERASEBKGND:
3125 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3127 RECT rect;
3128 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3129 wParam, (LPARAM)descr->self );
3130 TRACE("hbrush = %p\n", hbrush);
3131 if(!hbrush)
3132 hbrush = GetSysColorBrush(COLOR_WINDOW);
3133 if(hbrush)
3135 GetClientRect(descr->self, &rect);
3136 FillRect((HDC)wParam, &rect, hbrush);
3139 return 1;
3140 case WM_DROPFILES:
3141 if( lphc ) return 0;
3142 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3143 SendMessageA( descr->owner, msg, wParam, lParam );
3145 case WM_NCDESTROY:
3146 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3147 lphc->hWndLBox = 0;
3148 break;
3150 case WM_NCACTIVATE:
3151 if (lphc) return 0;
3152 break;
3154 default:
3155 if ((msg >= WM_USER) && (msg < 0xc000))
3156 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3157 hwnd, msg, wParam, lParam );
3160 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3161 DefWindowProcA( hwnd, msg, wParam, lParam );
3164 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3166 TRACE("%p\n", hwnd);
3167 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);