riched20: Add tests for text limit behavior.
[wine/testsucceed.git] / dlls / user32 / listbox.c
blobe2bf43baf4cd96e116f852f420c27833e9a1e1bb
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 * NOTES
22 * This code was audited for completeness against the documented features
23 * of Comctl32.dll version 6.0 on Oct. 9, 2004, by Dimitrie O. Paun.
25 * Unless otherwise noted, we believe this code to be complete, as per
26 * the specification mentioned above.
27 * If you discover missing features, or bugs, please note them below.
29 * TODO:
30 * - GetListBoxInfo()
31 * - LB_GETLISTBOXINFO
32 * - LBS_NODATA
35 #include <string.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include "windef.h"
40 #include "winbase.h"
41 #include "wingdi.h"
42 #include "wine/winuser16.h"
43 #include "wine/unicode.h"
44 #include "user_private.h"
45 #include "controls.h"
46 #include "wine/debug.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
50 /* Items array granularity */
51 #define LB_ARRAY_GRANULARITY 16
53 /* Scrolling timeout in ms */
54 #define LB_SCROLL_TIMEOUT 50
56 /* Listbox system timer id */
57 #define LB_TIMER_ID 2
59 /* flag listbox changed while setredraw false - internal style */
60 #define LBS_DISPLAYCHANGED 0x80000000
62 /* Item structure */
63 typedef struct
65 LPWSTR str; /* Item text */
66 BOOL selected; /* Is item selected? */
67 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
68 ULONG_PTR data; /* User data */
69 } LB_ITEMDATA;
71 /* Listbox structure */
72 typedef struct
74 HWND self; /* Our own window handle */
75 HWND owner; /* Owner window to send notifications to */
76 UINT style; /* Window style */
77 INT width; /* Window width */
78 INT height; /* Window height */
79 LB_ITEMDATA *items; /* Array of items */
80 INT nb_items; /* Number of items */
81 INT top_item; /* Top visible item */
82 INT selected_item; /* Selected item */
83 INT focus_item; /* Item that has the focus */
84 INT anchor_item; /* Anchor item for extended selection */
85 INT item_height; /* Default item height */
86 INT page_size; /* Items per listbox page */
87 INT column_width; /* Column width for multi-column listboxes */
88 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
89 INT horz_pos; /* Horizontal position */
90 INT nb_tabs; /* Number of tabs in array */
91 INT *tabs; /* Array of tabs */
92 INT avg_char_width; /* Average width of characters */
93 BOOL caret_on; /* Is caret on? */
94 BOOL captured; /* Is mouse captured? */
95 BOOL in_focus;
96 HFONT font; /* Current font */
97 LCID locale; /* Current locale for string comparisons */
98 LPHEADCOMBO lphc; /* ComboLBox */
99 } LB_DESCR;
102 #define IS_OWNERDRAW(descr) \
103 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
105 #define HAS_STRINGS(descr) \
106 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
109 #define IS_MULTISELECT(descr) \
110 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
111 !((descr)->style & LBS_NOSEL))
113 #define SEND_NOTIFICATION(descr,code) \
114 (SendMessageW( (descr)->owner, WM_COMMAND, \
115 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
117 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
119 /* Current timer status */
120 typedef enum
122 LB_TIMER_NONE,
123 LB_TIMER_UP,
124 LB_TIMER_LEFT,
125 LB_TIMER_DOWN,
126 LB_TIMER_RIGHT
127 } TIMER_DIRECTION;
129 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
131 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
132 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
134 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
136 /*********************************************************************
137 * listbox class descriptor
139 const struct builtin_class_descr LISTBOX_builtin_class =
141 "ListBox", /* name */
142 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
143 ListBoxWndProcA, /* procA */
144 ListBoxWndProcW, /* procW */
145 sizeof(LB_DESCR *), /* extra */
146 IDC_ARROW, /* cursor */
147 0 /* brush */
151 /*********************************************************************
152 * combolbox class descriptor
154 const struct builtin_class_descr COMBOLBOX_builtin_class =
156 "ComboLBox", /* name */
157 CS_DBLCLKS | CS_SAVEBITS, /* style */
158 ListBoxWndProcA, /* procA */
159 ListBoxWndProcW, /* procW */
160 sizeof(LB_DESCR *), /* extra */
161 IDC_ARROW, /* cursor */
162 0 /* brush */
166 /* check whether app is a Win 3.1 app */
167 static inline BOOL is_old_app( LB_DESCR *descr )
169 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
173 /***********************************************************************
174 * LISTBOX_GetCurrentPageSize
176 * Return the current page size
178 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
180 INT i, height;
181 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
182 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
184 if ((height += descr->items[i].height) > descr->height) break;
186 if (i == descr->top_item) return 1;
187 else return i - descr->top_item;
191 /***********************************************************************
192 * LISTBOX_GetMaxTopIndex
194 * Return the maximum possible index for the top of the listbox.
196 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
198 INT max, page;
200 if (descr->style & LBS_OWNERDRAWVARIABLE)
202 page = descr->height;
203 for (max = descr->nb_items - 1; max >= 0; max--)
204 if ((page -= descr->items[max].height) < 0) break;
205 if (max < descr->nb_items - 1) max++;
207 else if (descr->style & LBS_MULTICOLUMN)
209 if ((page = descr->width / descr->column_width) < 1) page = 1;
210 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
211 max = (max - page) * descr->page_size;
213 else
215 max = descr->nb_items - descr->page_size;
217 if (max < 0) max = 0;
218 return max;
222 /***********************************************************************
223 * LISTBOX_UpdateScroll
225 * Update the scrollbars. Should be called whenever the content
226 * of the listbox changes.
228 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
230 SCROLLINFO info;
232 /* Check the listbox scroll bar flags individually before we call
233 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
234 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
235 scroll bar when we do not need one.
236 if (!(descr->style & WS_VSCROLL)) return;
239 /* It is important that we check descr->style, and not wnd->dwStyle,
240 for WS_VSCROLL, as the former is exactly the one passed in
241 argument to CreateWindow.
242 In Windows (and from now on in Wine :) a listbox created
243 with such a style (no WS_SCROLL) does not update
244 the scrollbar with listbox-related data, thus letting
245 the programmer use it for his/her own purposes. */
247 if (descr->style & LBS_NOREDRAW) return;
248 info.cbSize = sizeof(info);
250 if (descr->style & LBS_MULTICOLUMN)
252 info.nMin = 0;
253 info.nMax = (descr->nb_items - 1) / descr->page_size;
254 info.nPos = descr->top_item / descr->page_size;
255 info.nPage = descr->width / descr->column_width;
256 if (info.nPage < 1) info.nPage = 1;
257 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
258 if (descr->style & LBS_DISABLENOSCROLL)
259 info.fMask |= SIF_DISABLENOSCROLL;
260 if (descr->style & WS_HSCROLL)
261 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
262 info.nMax = 0;
263 info.fMask = SIF_RANGE;
264 if (descr->style & WS_VSCROLL)
265 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
267 else
269 info.nMin = 0;
270 info.nMax = descr->nb_items - 1;
271 info.nPos = descr->top_item;
272 info.nPage = LISTBOX_GetCurrentPageSize( descr );
273 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
274 if (descr->style & LBS_DISABLENOSCROLL)
275 info.fMask |= SIF_DISABLENOSCROLL;
276 if (descr->style & WS_VSCROLL)
277 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
279 if (descr->horz_extent)
281 info.nMin = 0;
282 info.nMax = descr->horz_extent - 1;
283 info.nPos = descr->horz_pos;
284 info.nPage = descr->width;
285 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
286 if (descr->style & LBS_DISABLENOSCROLL)
287 info.fMask |= SIF_DISABLENOSCROLL;
288 if (descr->style & WS_HSCROLL)
289 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
295 /***********************************************************************
296 * LISTBOX_SetTopItem
298 * Set the top item of the listbox, scrolling up or down if necessary.
300 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
302 INT max = LISTBOX_GetMaxTopIndex( descr );
303 if (index > max) index = max;
304 if (index < 0) index = 0;
305 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
306 if (descr->top_item == index) return LB_OKAY;
307 if (descr->style & LBS_MULTICOLUMN)
309 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
310 if (scroll && (abs(diff) < descr->width))
311 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
312 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
314 else
315 scroll = FALSE;
317 else if (scroll)
319 INT diff;
320 if (descr->style & LBS_OWNERDRAWVARIABLE)
322 INT i;
323 diff = 0;
324 if (index > descr->top_item)
326 for (i = index - 1; i >= descr->top_item; i--)
327 diff -= descr->items[i].height;
329 else
331 for (i = index; i < descr->top_item; i++)
332 diff += descr->items[i].height;
335 else
336 diff = (descr->top_item - index) * descr->item_height;
338 if (abs(diff) < descr->height)
339 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
340 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
341 else
342 scroll = FALSE;
344 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
345 descr->top_item = index;
346 LISTBOX_UpdateScroll( descr );
347 return LB_OKAY;
351 /***********************************************************************
352 * LISTBOX_UpdatePage
354 * Update the page size. Should be called when the size of
355 * the client area or the item height changes.
357 static void LISTBOX_UpdatePage( LB_DESCR *descr )
359 INT page_size;
361 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
362 page_size = 1;
363 if (page_size == descr->page_size) return;
364 descr->page_size = page_size;
365 if (descr->style & LBS_MULTICOLUMN)
366 InvalidateRect( descr->self, NULL, TRUE );
367 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
371 /***********************************************************************
372 * LISTBOX_UpdateSize
374 * Update the size of the listbox. Should be called when the size of
375 * the client area changes.
377 static void LISTBOX_UpdateSize( LB_DESCR *descr )
379 RECT rect;
381 GetClientRect( descr->self, &rect );
382 descr->width = rect.right - rect.left;
383 descr->height = rect.bottom - rect.top;
384 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
386 INT remaining;
387 RECT rect;
389 GetWindowRect( descr->self, &rect );
390 if(descr->item_height != 0)
391 remaining = descr->height % descr->item_height;
392 else
393 remaining = 0;
394 if ((descr->height > descr->item_height) && remaining)
396 if (is_old_app(descr))
397 { /* give a margin for error to 16 bits programs - if we need
398 less than the height of the nonclient area, round to the
399 *next* number of items */
400 int ncheight = rect.bottom - rect.top - descr->height;
401 if ((descr->item_height - remaining) <= ncheight)
402 remaining = remaining - descr->item_height;
404 TRACE("[%p]: changing height %d -> %d\n",
405 descr->self, descr->height, descr->height - remaining );
406 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
407 rect.bottom - rect.top - remaining,
408 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
409 return;
412 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
413 LISTBOX_UpdatePage( descr );
414 LISTBOX_UpdateScroll( descr );
416 /* Invalidate the focused item so it will be repainted correctly */
417 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
419 InvalidateRect( descr->self, &rect, FALSE );
424 /***********************************************************************
425 * LISTBOX_GetItemRect
427 * Get the rectangle enclosing an item, in listbox client coordinates.
428 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
430 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
432 /* Index <= 0 is legal even on empty listboxes */
433 if (index && (index >= descr->nb_items))
435 memset(rect, 0, sizeof(*rect));
436 SetLastError(ERROR_INVALID_INDEX);
437 return LB_ERR;
439 SetRect( rect, 0, 0, descr->width, descr->height );
440 if (descr->style & LBS_MULTICOLUMN)
442 INT col = (index / descr->page_size) -
443 (descr->top_item / descr->page_size);
444 rect->left += col * descr->column_width;
445 rect->right = rect->left + descr->column_width;
446 rect->top += (index % descr->page_size) * descr->item_height;
447 rect->bottom = rect->top + descr->item_height;
449 else if (descr->style & LBS_OWNERDRAWVARIABLE)
451 INT i;
452 rect->right += descr->horz_pos;
453 if ((index >= 0) && (index < descr->nb_items))
455 if (index < descr->top_item)
457 for (i = descr->top_item-1; i >= index; i--)
458 rect->top -= descr->items[i].height;
460 else
462 for (i = descr->top_item; i < index; i++)
463 rect->top += descr->items[i].height;
465 rect->bottom = rect->top + descr->items[index].height;
469 else
471 rect->top += (index - descr->top_item) * descr->item_height;
472 rect->bottom = rect->top + descr->item_height;
473 rect->right += descr->horz_pos;
476 return ((rect->left < descr->width) && (rect->right > 0) &&
477 (rect->top < descr->height) && (rect->bottom > 0));
481 /***********************************************************************
482 * LISTBOX_GetItemFromPoint
484 * Return the item nearest from point (x,y) (in client coordinates).
486 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
488 INT index = descr->top_item;
490 if (!descr->nb_items) return -1; /* No items */
491 if (descr->style & LBS_OWNERDRAWVARIABLE)
493 INT pos = 0;
494 if (y >= 0)
496 while (index < descr->nb_items)
498 if ((pos += descr->items[index].height) > y) break;
499 index++;
502 else
504 while (index > 0)
506 index--;
507 if ((pos -= descr->items[index].height) <= y) break;
511 else if (descr->style & LBS_MULTICOLUMN)
513 if (y >= descr->item_height * descr->page_size) return -1;
514 if (y >= 0) index += y / descr->item_height;
515 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
516 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
518 else
520 index += (y / descr->item_height);
522 if (index < 0) return 0;
523 if (index >= descr->nb_items) return -1;
524 return index;
528 /***********************************************************************
529 * LISTBOX_PaintItem
531 * Paint an item.
533 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
534 INT index, UINT action, BOOL ignoreFocus )
536 LB_ITEMDATA *item = NULL;
537 if (index < descr->nb_items) item = &descr->items[index];
539 if (IS_OWNERDRAW(descr))
541 DRAWITEMSTRUCT dis;
542 RECT r;
543 HRGN hrgn;
545 if (!item)
547 if (action == ODA_FOCUS)
548 DrawFocusRect( hdc, rect );
549 else
550 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
551 return;
554 /* some programs mess with the clipping region when
555 drawing the item, *and* restore the previous region
556 after they are done, so a region has better to exist
557 else everything ends clipped */
558 GetClientRect(descr->self, &r);
559 hrgn = CreateRectRgnIndirect(&r);
560 SelectClipRgn( hdc, hrgn);
561 DeleteObject( hrgn );
563 dis.CtlType = ODT_LISTBOX;
564 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
565 dis.hwndItem = descr->self;
566 dis.itemAction = action;
567 dis.hDC = hdc;
568 dis.itemID = index;
569 dis.itemState = 0;
570 if (item->selected) dis.itemState |= ODS_SELECTED;
571 if (!ignoreFocus && (descr->focus_item == index) &&
572 (descr->caret_on) &&
573 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
574 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
575 dis.itemData = item->data;
576 dis.rcItem = *rect;
577 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%d,%d-%d,%d\n",
578 descr->self, index, item ? debugstr_w(item->str) : "", action,
579 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
580 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
582 else
584 COLORREF oldText = 0, oldBk = 0;
586 if (action == ODA_FOCUS)
588 DrawFocusRect( hdc, rect );
589 return;
591 if (item && item->selected)
593 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
594 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
597 TRACE("[%p]: painting %d (%s) action=%02x rect=%d,%d-%d,%d\n",
598 descr->self, index, item ? debugstr_w(item->str) : "", action,
599 rect->left, rect->top, rect->right, rect->bottom );
600 if (!item)
601 ExtTextOutW( hdc, rect->left + 1, rect->top,
602 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
603 else if (!(descr->style & LBS_USETABSTOPS))
604 ExtTextOutW( hdc, rect->left + 1, rect->top,
605 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
606 strlenW(item->str), NULL );
607 else
609 /* Output empty string to paint background in the full width. */
610 ExtTextOutW( hdc, rect->left + 1, rect->top,
611 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
612 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
613 item->str, strlenW(item->str),
614 descr->nb_tabs, descr->tabs, 0);
616 if (item && item->selected)
618 SetBkColor( hdc, oldBk );
619 SetTextColor( hdc, oldText );
621 if (!ignoreFocus && (descr->focus_item == index) &&
622 (descr->caret_on) &&
623 (descr->in_focus)) DrawFocusRect( hdc, rect );
628 /***********************************************************************
629 * LISTBOX_SetRedraw
631 * Change the redraw flag.
633 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
635 if (on)
637 if (!(descr->style & LBS_NOREDRAW)) return;
638 descr->style &= ~LBS_NOREDRAW;
639 if (descr->style & LBS_DISPLAYCHANGED)
640 { /* page was changed while setredraw false, refresh automatically */
641 InvalidateRect(descr->self, NULL, TRUE);
642 if ((descr->top_item + descr->page_size) > descr->nb_items)
643 { /* reset top of page if less than number of items/page */
644 descr->top_item = descr->nb_items - descr->page_size;
645 if (descr->top_item < 0) descr->top_item = 0;
647 descr->style &= ~LBS_DISPLAYCHANGED;
649 LISTBOX_UpdateScroll( descr );
651 else descr->style |= LBS_NOREDRAW;
655 /***********************************************************************
656 * LISTBOX_RepaintItem
658 * Repaint a single item synchronously.
660 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
662 HDC hdc;
663 RECT rect;
664 HFONT oldFont = 0;
665 HBRUSH hbrush, oldBrush = 0;
667 /* Do not repaint the item if the item is not visible */
668 if (!IsWindowVisible(descr->self)) return;
669 if (descr->style & LBS_NOREDRAW)
671 descr->style |= LBS_DISPLAYCHANGED;
672 return;
674 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
675 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
676 if (descr->font) oldFont = SelectObject( hdc, descr->font );
677 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
678 (WPARAM)hdc, (LPARAM)descr->self );
679 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
680 if (!IsWindowEnabled(descr->self))
681 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
682 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
683 LISTBOX_PaintItem( descr, hdc, &rect, index, action, FALSE );
684 if (oldFont) SelectObject( hdc, oldFont );
685 if (oldBrush) SelectObject( hdc, oldBrush );
686 ReleaseDC( descr->self, hdc );
690 /***********************************************************************
691 * LISTBOX_InitStorage
693 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
695 LB_ITEMDATA *item;
697 nb_items += LB_ARRAY_GRANULARITY - 1;
698 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
699 if (descr->items) {
700 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
701 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
702 nb_items * sizeof(LB_ITEMDATA));
704 else {
705 item = HeapAlloc( GetProcessHeap(), 0,
706 nb_items * sizeof(LB_ITEMDATA));
709 if (!item)
711 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
712 return LB_ERRSPACE;
714 descr->items = item;
715 return LB_OKAY;
719 /***********************************************************************
720 * LISTBOX_SetTabStops
722 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
724 INT i;
726 if (!(descr->style & LBS_USETABSTOPS))
728 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
729 return FALSE;
732 HeapFree( GetProcessHeap(), 0, descr->tabs );
733 if (!(descr->nb_tabs = count))
735 descr->tabs = NULL;
736 return TRUE;
738 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
739 descr->nb_tabs * sizeof(INT) )))
740 return FALSE;
741 if (short_ints)
743 INT i;
744 LPINT16 p = (LPINT16)tabs;
746 TRACE("[%p]: settabstops ", descr->self );
747 for (i = 0; i < descr->nb_tabs; i++) {
748 descr->tabs[i] = *p++<<1; /* FIXME */
749 TRACE("%hd ", descr->tabs[i]);
751 TRACE("\n");
753 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
755 /* convert into "dialog units"*/
756 for (i = 0; i < descr->nb_tabs; i++)
757 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
759 return TRUE;
763 /***********************************************************************
764 * LISTBOX_GetText
766 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
768 if ((index < 0) || (index >= descr->nb_items))
770 SetLastError(ERROR_INVALID_INDEX);
771 return LB_ERR;
773 if (HAS_STRINGS(descr))
775 if (!buffer)
777 DWORD len = strlenW(descr->items[index].str);
778 if( unicode )
779 return len;
780 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
781 NULL, 0, NULL, NULL );
784 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
786 if(unicode)
788 strcpyW( buffer, descr->items[index].str );
789 return strlenW(buffer);
791 else
793 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
795 } else {
796 if (buffer)
797 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
798 return sizeof(DWORD);
802 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
804 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
805 if (ret == CSTR_LESS_THAN)
806 return -1;
807 if (ret == CSTR_EQUAL)
808 return 0;
809 if (ret == CSTR_GREATER_THAN)
810 return 1;
811 return -1;
814 /***********************************************************************
815 * LISTBOX_FindStringPos
817 * Find the nearest string located before a given string in sort order.
818 * If 'exact' is TRUE, return an error if we don't get an exact match.
820 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
822 INT index, min, max, res = -1;
824 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
825 min = 0;
826 max = descr->nb_items;
827 while (min != max)
829 index = (min + max) / 2;
830 if (HAS_STRINGS(descr))
831 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
832 else
834 COMPAREITEMSTRUCT cis;
835 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
837 cis.CtlType = ODT_LISTBOX;
838 cis.CtlID = id;
839 cis.hwndItem = descr->self;
840 /* note that some application (MetaStock) expects the second item
841 * to be in the listbox */
842 cis.itemID1 = -1;
843 cis.itemData1 = (ULONG_PTR)str;
844 cis.itemID2 = index;
845 cis.itemData2 = descr->items[index].data;
846 cis.dwLocaleId = descr->locale;
847 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
849 if (!res) return index;
850 if (res < 0) max = index;
851 else min = index + 1;
853 return exact ? -1 : max;
857 /***********************************************************************
858 * LISTBOX_FindFileStrPos
860 * Find the nearest string located before a given string in directory
861 * sort order (i.e. first files, then directories, then drives).
863 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
865 INT min, max, res = -1;
867 if (!HAS_STRINGS(descr))
868 return LISTBOX_FindStringPos( descr, str, FALSE );
869 min = 0;
870 max = descr->nb_items;
871 while (min != max)
873 INT index = (min + max) / 2;
874 LPCWSTR p = descr->items[index].str;
875 if (*p == '[') /* drive or directory */
877 if (*str != '[') res = -1;
878 else if (p[1] == '-') /* drive */
880 if (str[1] == '-') res = str[2] - p[2];
881 else res = -1;
883 else /* directory */
885 if (str[1] == '-') res = 1;
886 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
889 else /* filename */
891 if (*str == '[') res = 1;
892 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
894 if (!res) return index;
895 if (res < 0) max = index;
896 else min = index + 1;
898 return max;
902 /***********************************************************************
903 * LISTBOX_FindString
905 * Find the item beginning with a given string.
907 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
909 INT i;
910 LB_ITEMDATA *item;
912 if (start >= descr->nb_items) start = -1;
913 item = descr->items + start + 1;
914 if (HAS_STRINGS(descr))
916 if (!str || ! str[0] ) return LB_ERR;
917 if (exact)
919 for (i = start + 1; i < descr->nb_items; i++, item++)
920 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
921 for (i = 0, item = descr->items; i <= start; i++, item++)
922 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
924 else
926 /* Special case for drives and directories: ignore prefix */
927 #define CHECK_DRIVE(item) \
928 if ((item)->str[0] == '[') \
930 if (!strncmpiW( str, (item)->str+1, len )) return i; \
931 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
932 return i; \
935 INT len = strlenW(str);
936 for (i = start + 1; i < descr->nb_items; i++, item++)
938 if (!strncmpiW( str, item->str, len )) return i;
939 CHECK_DRIVE(item);
941 for (i = 0, item = descr->items; i <= start; i++, item++)
943 if (!strncmpiW( str, item->str, len )) return i;
944 CHECK_DRIVE(item);
946 #undef CHECK_DRIVE
949 else
951 if (exact && (descr->style & LBS_SORT))
952 /* If sorted, use a WM_COMPAREITEM binary search */
953 return LISTBOX_FindStringPos( descr, str, TRUE );
955 /* Otherwise use a linear search */
956 for (i = start + 1; i < descr->nb_items; i++, item++)
957 if (item->data == (ULONG_PTR)str) return i;
958 for (i = 0, item = descr->items; i <= start; i++, item++)
959 if (item->data == (ULONG_PTR)str) return i;
961 return LB_ERR;
965 /***********************************************************************
966 * LISTBOX_GetSelCount
968 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
970 INT i, count;
971 LB_ITEMDATA *item = descr->items;
973 if (!(descr->style & LBS_MULTIPLESEL) ||
974 (descr->style & LBS_NOSEL))
975 return LB_ERR;
976 for (i = count = 0; i < descr->nb_items; i++, item++)
977 if (item->selected) count++;
978 return count;
982 /***********************************************************************
983 * LISTBOX_GetSelItems16
985 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
987 INT i, count;
988 LB_ITEMDATA *item = descr->items;
990 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
991 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
992 if (item->selected) array[count++] = (INT16)i;
993 return count;
997 /***********************************************************************
998 * LISTBOX_GetSelItems
1000 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
1002 INT i, count;
1003 LB_ITEMDATA *item = descr->items;
1005 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1006 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1007 if (item->selected) array[count++] = i;
1008 return count;
1012 /***********************************************************************
1013 * LISTBOX_Paint
1015 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1017 INT i, col_pos = descr->page_size - 1;
1018 RECT rect;
1019 RECT focusRect = {-1, -1, -1, -1};
1020 HFONT oldFont = 0;
1021 HBRUSH hbrush, oldBrush = 0;
1023 if (descr->style & LBS_NOREDRAW) return 0;
1025 SetRect( &rect, 0, 0, descr->width, descr->height );
1026 if (descr->style & LBS_MULTICOLUMN)
1027 rect.right = rect.left + descr->column_width;
1028 else if (descr->horz_pos)
1030 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1031 rect.right += descr->horz_pos;
1034 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1035 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1036 (WPARAM)hdc, (LPARAM)descr->self );
1037 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1038 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1040 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1041 (descr->in_focus))
1043 /* Special case for empty listbox: paint focus rect */
1044 rect.bottom = rect.top + descr->item_height;
1045 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1046 &rect, NULL, 0, NULL );
1047 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1048 rect.top = rect.bottom;
1051 /* Paint all the item, regarding the selection
1052 Focus state will be painted after */
1054 for (i = descr->top_item; i < descr->nb_items; i++)
1056 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1057 rect.bottom = rect.top + descr->item_height;
1058 else
1059 rect.bottom = rect.top + descr->items[i].height;
1061 if (i == descr->focus_item)
1063 /* keep the focus rect, to paint the focus item after */
1064 focusRect.left = rect.left;
1065 focusRect.right = rect.right;
1066 focusRect.top = rect.top;
1067 focusRect.bottom = rect.bottom;
1069 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1070 rect.top = rect.bottom;
1072 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1074 if (!IS_OWNERDRAW(descr))
1076 /* Clear the bottom of the column */
1077 if (rect.top < descr->height)
1079 rect.bottom = descr->height;
1080 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1081 &rect, NULL, 0, NULL );
1085 /* Go to the next column */
1086 rect.left += descr->column_width;
1087 rect.right += descr->column_width;
1088 rect.top = 0;
1089 col_pos = descr->page_size - 1;
1091 else
1093 col_pos--;
1094 if (rect.top >= descr->height) break;
1098 /* Paint the focus item now */
1099 if (focusRect.top != focusRect.bottom &&
1100 descr->caret_on && descr->in_focus)
1101 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1103 if (!IS_OWNERDRAW(descr))
1105 /* Clear the remainder of the client area */
1106 if (rect.top < descr->height)
1108 rect.bottom = descr->height;
1109 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1110 &rect, NULL, 0, NULL );
1112 if (rect.right < descr->width)
1114 rect.left = rect.right;
1115 rect.right = descr->width;
1116 rect.top = 0;
1117 rect.bottom = descr->height;
1118 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1119 &rect, NULL, 0, NULL );
1122 if (oldFont) SelectObject( hdc, oldFont );
1123 if (oldBrush) SelectObject( hdc, oldBrush );
1124 return 0;
1128 /***********************************************************************
1129 * LISTBOX_InvalidateItems
1131 * Invalidate all items from a given item. If the specified item is not
1132 * visible, nothing happens.
1134 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1136 RECT rect;
1138 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1140 if (descr->style & LBS_NOREDRAW)
1142 descr->style |= LBS_DISPLAYCHANGED;
1143 return;
1145 rect.bottom = descr->height;
1146 InvalidateRect( descr->self, &rect, TRUE );
1147 if (descr->style & LBS_MULTICOLUMN)
1149 /* Repaint the other columns */
1150 rect.left = rect.right;
1151 rect.right = descr->width;
1152 rect.top = 0;
1153 InvalidateRect( descr->self, &rect, TRUE );
1158 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1160 RECT rect;
1162 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1163 InvalidateRect( descr->self, &rect, TRUE );
1166 /***********************************************************************
1167 * LISTBOX_GetItemHeight
1169 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1171 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1173 if ((index < 0) || (index >= descr->nb_items))
1175 SetLastError(ERROR_INVALID_INDEX);
1176 return LB_ERR;
1178 return descr->items[index].height;
1180 else return descr->item_height;
1184 /***********************************************************************
1185 * LISTBOX_SetItemHeight
1187 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1189 if (height > MAXBYTE)
1190 return -1;
1192 if (!height) height = 1;
1194 if (descr->style & LBS_OWNERDRAWVARIABLE)
1196 if ((index < 0) || (index >= descr->nb_items))
1198 SetLastError(ERROR_INVALID_INDEX);
1199 return LB_ERR;
1201 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1202 descr->items[index].height = height;
1203 LISTBOX_UpdateScroll( descr );
1204 if (repaint)
1205 LISTBOX_InvalidateItems( descr, index );
1207 else if (height != descr->item_height)
1209 TRACE("[%p]: new height = %d\n", descr->self, height );
1210 descr->item_height = height;
1211 LISTBOX_UpdatePage( descr );
1212 LISTBOX_UpdateScroll( descr );
1213 if (repaint)
1214 InvalidateRect( descr->self, 0, TRUE );
1216 return LB_OKAY;
1220 /***********************************************************************
1221 * LISTBOX_SetHorizontalPos
1223 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1225 INT diff;
1227 if (pos > descr->horz_extent - descr->width)
1228 pos = descr->horz_extent - descr->width;
1229 if (pos < 0) pos = 0;
1230 if (!(diff = descr->horz_pos - pos)) return;
1231 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1232 descr->horz_pos = pos;
1233 LISTBOX_UpdateScroll( descr );
1234 if (abs(diff) < descr->width)
1236 RECT rect;
1237 /* Invalidate the focused item so it will be repainted correctly */
1238 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1239 InvalidateRect( descr->self, &rect, TRUE );
1240 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1241 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1243 else
1244 InvalidateRect( descr->self, NULL, TRUE );
1248 /***********************************************************************
1249 * LISTBOX_SetHorizontalExtent
1251 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1253 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1254 return LB_OKAY;
1255 if (extent <= 0) extent = 1;
1256 if (extent == descr->horz_extent) return LB_OKAY;
1257 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1258 descr->horz_extent = extent;
1259 if (descr->horz_pos > extent - descr->width)
1260 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1261 else
1262 LISTBOX_UpdateScroll( descr );
1263 return LB_OKAY;
1267 /***********************************************************************
1268 * LISTBOX_SetColumnWidth
1270 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1272 if (width == descr->column_width) return LB_OKAY;
1273 TRACE("[%p]: new column width = %d\n", descr->self, width );
1274 descr->column_width = width;
1275 LISTBOX_UpdatePage( descr );
1276 return LB_OKAY;
1280 /***********************************************************************
1281 * LISTBOX_SetFont
1283 * Returns the item height.
1285 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1287 HDC hdc;
1288 HFONT oldFont = 0;
1289 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1290 SIZE sz;
1292 descr->font = font;
1294 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1296 ERR("unable to get DC.\n" );
1297 return 16;
1299 if (font) oldFont = SelectObject( hdc, font );
1300 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1301 if (oldFont) SelectObject( hdc, oldFont );
1302 ReleaseDC( descr->self, hdc );
1304 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1305 if (!IS_OWNERDRAW(descr))
1306 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1307 return sz.cy;
1311 /***********************************************************************
1312 * LISTBOX_MakeItemVisible
1314 * Make sure that a given item is partially or fully visible.
1316 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1318 INT top;
1320 if (index <= descr->top_item) top = index;
1321 else if (descr->style & LBS_MULTICOLUMN)
1323 INT cols = descr->width;
1324 if (!fully) cols += descr->column_width - 1;
1325 if (cols >= descr->column_width) cols /= descr->column_width;
1326 else cols = 1;
1327 if (index < descr->top_item + (descr->page_size * cols)) return;
1328 top = index - descr->page_size * (cols - 1);
1330 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1332 INT height = fully ? descr->items[index].height : 1;
1333 for (top = index; top > descr->top_item; top--)
1334 if ((height += descr->items[top-1].height) > descr->height) break;
1336 else
1338 if (index < descr->top_item + descr->page_size) return;
1339 if (!fully && (index == descr->top_item + descr->page_size) &&
1340 (descr->height > (descr->page_size * descr->item_height))) return;
1341 top = index - descr->page_size + 1;
1343 LISTBOX_SetTopItem( descr, top, TRUE );
1346 /***********************************************************************
1347 * LISTBOX_SetCaretIndex
1349 * NOTES
1350 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1353 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1355 INT oldfocus = descr->focus_item;
1357 if (descr->style & LBS_NOSEL) return LB_ERR;
1358 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1359 if (index == oldfocus) return LB_OKAY;
1360 descr->focus_item = index;
1361 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1362 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1364 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1365 if (descr->caret_on && (descr->in_focus))
1366 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1368 return LB_OKAY;
1372 /***********************************************************************
1373 * LISTBOX_SelectItemRange
1375 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1377 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1378 INT last, BOOL on )
1380 INT i;
1382 /* A few sanity checks */
1384 if (descr->style & LBS_NOSEL) return LB_ERR;
1385 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1387 if (!descr->nb_items) return LB_OKAY;
1389 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1390 if (first < 0) first = 0;
1391 if (last < first) return LB_OKAY;
1393 if (on) /* Turn selection on */
1395 for (i = first; i <= last; i++)
1397 if (descr->items[i].selected) continue;
1398 descr->items[i].selected = TRUE;
1399 LISTBOX_InvalidateItemRect(descr, i);
1402 else /* Turn selection off */
1404 for (i = first; i <= last; i++)
1406 if (!descr->items[i].selected) continue;
1407 descr->items[i].selected = FALSE;
1408 LISTBOX_InvalidateItemRect(descr, i);
1411 return LB_OKAY;
1414 /***********************************************************************
1415 * LISTBOX_SetSelection
1417 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1418 BOOL on, BOOL send_notify )
1420 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1422 if (descr->style & LBS_NOSEL)
1424 descr->selected_item = index;
1425 return LB_ERR;
1427 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1428 if (descr->style & LBS_MULTIPLESEL)
1430 if (index == -1) /* Select all items */
1431 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1432 else /* Only one item */
1433 return LISTBOX_SelectItemRange( descr, index, index, on );
1435 else
1437 INT oldsel = descr->selected_item;
1438 if (index == oldsel) return LB_OKAY;
1439 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1440 if (index != -1) descr->items[index].selected = TRUE;
1441 descr->selected_item = index;
1442 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1443 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1444 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1445 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1446 else
1447 if( descr->lphc ) /* set selection change flag for parent combo */
1448 descr->lphc->wState |= CBF_SELCHANGE;
1450 return LB_OKAY;
1454 /***********************************************************************
1455 * LISTBOX_MoveCaret
1457 * Change the caret position and extend the selection to the new caret.
1459 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1461 INT oldfocus = descr->focus_item;
1463 if ((index < 0) || (index >= descr->nb_items))
1464 return;
1466 /* Important, repaint needs to be done in this order if
1467 you want to mimic Windows behavior:
1468 1. Remove the focus and paint the item
1469 2. Remove the selection and paint the item(s)
1470 3. Set the selection and repaint the item(s)
1471 4. Set the focus to 'index' and repaint the item */
1473 /* 1. remove the focus and repaint the item */
1474 descr->focus_item = -1;
1475 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1476 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1478 /* 2. then turn off the previous selection */
1479 /* 3. repaint the new selected item */
1480 if (descr->style & LBS_EXTENDEDSEL)
1482 if (descr->anchor_item != -1)
1484 INT first = min( index, descr->anchor_item );
1485 INT last = max( index, descr->anchor_item );
1486 if (first > 0)
1487 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1488 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1489 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1492 else if (!(descr->style & LBS_MULTIPLESEL))
1494 /* Set selection to new caret item */
1495 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1498 /* 4. repaint the new item with the focus */
1499 descr->focus_item = index;
1500 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1501 if (descr->caret_on && (descr->in_focus))
1502 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1506 /***********************************************************************
1507 * LISTBOX_InsertItem
1509 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1510 LPWSTR str, ULONG_PTR data )
1512 LB_ITEMDATA *item;
1513 INT max_items;
1514 INT oldfocus = descr->focus_item;
1516 if (index == -1) index = descr->nb_items;
1517 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1518 if (!descr->items) max_items = 0;
1519 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1520 if (descr->nb_items == max_items)
1522 /* We need to grow the array */
1523 max_items += LB_ARRAY_GRANULARITY;
1524 if (descr->items)
1525 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1526 max_items * sizeof(LB_ITEMDATA) );
1527 else
1528 item = HeapAlloc( GetProcessHeap(), 0,
1529 max_items * sizeof(LB_ITEMDATA) );
1530 if (!item)
1532 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1533 return LB_ERRSPACE;
1535 descr->items = item;
1538 /* Insert the item structure */
1540 item = &descr->items[index];
1541 if (index < descr->nb_items)
1542 RtlMoveMemory( item + 1, item,
1543 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1544 item->str = str;
1545 item->data = data;
1546 item->height = 0;
1547 item->selected = FALSE;
1548 descr->nb_items++;
1550 /* Get item height */
1552 if (descr->style & LBS_OWNERDRAWVARIABLE)
1554 MEASUREITEMSTRUCT mis;
1555 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1557 mis.CtlType = ODT_LISTBOX;
1558 mis.CtlID = id;
1559 mis.itemID = index;
1560 mis.itemData = descr->items[index].data;
1561 mis.itemHeight = descr->item_height;
1562 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1563 item->height = mis.itemHeight ? mis.itemHeight : 1;
1564 TRACE("[%p]: measure item %d (%s) = %d\n",
1565 descr->self, index, str ? debugstr_w(str) : "", item->height );
1568 /* Repaint the items */
1570 LISTBOX_UpdateScroll( descr );
1571 LISTBOX_InvalidateItems( descr, index );
1573 /* Move selection and focused item */
1574 /* If listbox was empty, set focus to the first item */
1575 if (descr->nb_items == 1)
1576 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1577 /* single select don't change selection index in win31 */
1578 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1580 descr->selected_item++;
1581 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1583 else
1585 if (index <= descr->selected_item)
1587 descr->selected_item++;
1588 descr->focus_item = oldfocus; /* focus not changed */
1591 return LB_OKAY;
1595 /***********************************************************************
1596 * LISTBOX_InsertString
1598 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1600 LPWSTR new_str = NULL;
1601 ULONG_PTR data = 0;
1602 LRESULT ret;
1604 if (HAS_STRINGS(descr))
1606 static const WCHAR empty_stringW[] = { 0 };
1607 if (!str) str = empty_stringW;
1608 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1610 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1611 return LB_ERRSPACE;
1613 strcpyW(new_str, str);
1615 else data = (ULONG_PTR)str;
1617 if (index == -1) index = descr->nb_items;
1618 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1620 HeapFree( GetProcessHeap(), 0, new_str );
1621 return ret;
1624 TRACE("[%p]: added item %d %s\n",
1625 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1626 return index;
1630 /***********************************************************************
1631 * LISTBOX_DeleteItem
1633 * Delete the content of an item. 'index' must be a valid index.
1635 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1637 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1638 * while Win95 sends it for all items with user data.
1639 * It's probably better to send it too often than not
1640 * often enough, so this is what we do here.
1642 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1644 DELETEITEMSTRUCT dis;
1645 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1647 dis.CtlType = ODT_LISTBOX;
1648 dis.CtlID = id;
1649 dis.itemID = index;
1650 dis.hwndItem = descr->self;
1651 dis.itemData = descr->items[index].data;
1652 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1654 if (HAS_STRINGS(descr))
1655 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1659 /***********************************************************************
1660 * LISTBOX_RemoveItem
1662 * Remove an item from the listbox and delete its content.
1664 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1666 LB_ITEMDATA *item;
1667 INT max_items;
1669 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1671 /* We need to invalidate the original rect instead of the updated one. */
1672 LISTBOX_InvalidateItems( descr, index );
1674 LISTBOX_DeleteItem( descr, index );
1676 /* Remove the item */
1678 item = &descr->items[index];
1679 if (index < descr->nb_items-1)
1680 RtlMoveMemory( item, item + 1,
1681 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1682 descr->nb_items--;
1683 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1685 /* Shrink the item array if possible */
1687 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1688 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1690 max_items -= LB_ARRAY_GRANULARITY;
1691 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1692 max_items * sizeof(LB_ITEMDATA) );
1693 if (item) descr->items = item;
1695 /* Repaint the items */
1697 LISTBOX_UpdateScroll( descr );
1698 /* if we removed the scrollbar, reset the top of the list
1699 (correct for owner-drawn ???) */
1700 if (descr->nb_items == descr->page_size)
1701 LISTBOX_SetTopItem( descr, 0, TRUE );
1703 /* Move selection and focused item */
1704 if (!IS_MULTISELECT(descr))
1706 if (index == descr->selected_item)
1707 descr->selected_item = -1;
1708 else if (index < descr->selected_item)
1710 descr->selected_item--;
1711 if (ISWIN31) /* win 31 do not change the selected item number */
1712 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1716 if (descr->focus_item >= descr->nb_items)
1718 descr->focus_item = descr->nb_items - 1;
1719 if (descr->focus_item < 0) descr->focus_item = 0;
1721 return LB_OKAY;
1725 /***********************************************************************
1726 * LISTBOX_ResetContent
1728 static void LISTBOX_ResetContent( LB_DESCR *descr )
1730 INT i;
1732 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1733 HeapFree( GetProcessHeap(), 0, descr->items );
1734 descr->nb_items = 0;
1735 descr->top_item = 0;
1736 descr->selected_item = -1;
1737 descr->focus_item = 0;
1738 descr->anchor_item = -1;
1739 descr->items = NULL;
1743 /***********************************************************************
1744 * LISTBOX_SetCount
1746 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1748 LRESULT ret;
1750 if (HAS_STRINGS(descr))
1752 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1753 return LB_ERR;
1756 /* FIXME: this is far from optimal... */
1757 if (count > descr->nb_items)
1759 while (count > descr->nb_items)
1760 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1761 return ret;
1763 else if (count < descr->nb_items)
1765 while (count < descr->nb_items)
1766 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1767 return ret;
1769 return LB_OKAY;
1773 /***********************************************************************
1774 * LISTBOX_Directory
1776 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1777 LPCWSTR filespec, BOOL long_names )
1779 HANDLE handle;
1780 LRESULT ret = LB_OKAY;
1781 WIN32_FIND_DATAW entry;
1782 int pos;
1784 /* don't scan directory if we just want drives exclusively */
1785 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1786 /* scan directory */
1787 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1789 int le = GetLastError();
1790 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1792 else
1796 WCHAR buffer[270];
1797 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1799 static const WCHAR bracketW[] = { ']',0 };
1800 static const WCHAR dotW[] = { '.',0 };
1801 if (!(attrib & DDL_DIRECTORY) ||
1802 !strcmpW( entry.cFileName, dotW )) continue;
1803 buffer[0] = '[';
1804 if (!long_names && entry.cAlternateFileName[0])
1805 strcpyW( buffer + 1, entry.cAlternateFileName );
1806 else
1807 strcpyW( buffer + 1, entry.cFileName );
1808 strcatW(buffer, bracketW);
1810 else /* not a directory */
1812 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1813 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1815 if ((attrib & DDL_EXCLUSIVE) &&
1816 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1817 continue;
1818 #undef ATTRIBS
1819 if (!long_names && entry.cAlternateFileName[0])
1820 strcpyW( buffer, entry.cAlternateFileName );
1821 else
1822 strcpyW( buffer, entry.cFileName );
1824 if (!long_names) CharLowerW( buffer );
1825 pos = LISTBOX_FindFileStrPos( descr, buffer );
1826 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1827 break;
1828 } while (FindNextFileW( handle, &entry ));
1829 FindClose( handle );
1833 /* scan drives */
1834 if ((ret >= 0) && (attrib & DDL_DRIVES))
1836 WCHAR buffer[] = {'[','-','a','-',']',0};
1837 WCHAR root[] = {'A',':','\\',0};
1838 int drive;
1839 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1841 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1842 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1843 break;
1846 return ret;
1850 /***********************************************************************
1851 * LISTBOX_HandleVScroll
1853 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1855 SCROLLINFO info;
1857 if (descr->style & LBS_MULTICOLUMN) return 0;
1858 switch(scrollReq)
1860 case SB_LINEUP:
1861 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1862 break;
1863 case SB_LINEDOWN:
1864 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1865 break;
1866 case SB_PAGEUP:
1867 LISTBOX_SetTopItem( descr, descr->top_item -
1868 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1869 break;
1870 case SB_PAGEDOWN:
1871 LISTBOX_SetTopItem( descr, descr->top_item +
1872 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1873 break;
1874 case SB_THUMBPOSITION:
1875 LISTBOX_SetTopItem( descr, pos, TRUE );
1876 break;
1877 case SB_THUMBTRACK:
1878 info.cbSize = sizeof(info);
1879 info.fMask = SIF_TRACKPOS;
1880 GetScrollInfo( descr->self, SB_VERT, &info );
1881 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1882 break;
1883 case SB_TOP:
1884 LISTBOX_SetTopItem( descr, 0, TRUE );
1885 break;
1886 case SB_BOTTOM:
1887 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1888 break;
1890 return 0;
1894 /***********************************************************************
1895 * LISTBOX_HandleHScroll
1897 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1899 SCROLLINFO info;
1900 INT page;
1902 if (descr->style & LBS_MULTICOLUMN)
1904 switch(scrollReq)
1906 case SB_LINELEFT:
1907 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1908 TRUE );
1909 break;
1910 case SB_LINERIGHT:
1911 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1912 TRUE );
1913 break;
1914 case SB_PAGELEFT:
1915 page = descr->width / descr->column_width;
1916 if (page < 1) page = 1;
1917 LISTBOX_SetTopItem( descr,
1918 descr->top_item - page * descr->page_size, TRUE );
1919 break;
1920 case SB_PAGERIGHT:
1921 page = descr->width / descr->column_width;
1922 if (page < 1) page = 1;
1923 LISTBOX_SetTopItem( descr,
1924 descr->top_item + page * descr->page_size, TRUE );
1925 break;
1926 case SB_THUMBPOSITION:
1927 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1928 break;
1929 case SB_THUMBTRACK:
1930 info.cbSize = sizeof(info);
1931 info.fMask = SIF_TRACKPOS;
1932 GetScrollInfo( descr->self, SB_VERT, &info );
1933 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1934 TRUE );
1935 break;
1936 case SB_LEFT:
1937 LISTBOX_SetTopItem( descr, 0, TRUE );
1938 break;
1939 case SB_RIGHT:
1940 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1941 break;
1944 else if (descr->horz_extent)
1946 switch(scrollReq)
1948 case SB_LINELEFT:
1949 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1950 break;
1951 case SB_LINERIGHT:
1952 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1953 break;
1954 case SB_PAGELEFT:
1955 LISTBOX_SetHorizontalPos( descr,
1956 descr->horz_pos - descr->width );
1957 break;
1958 case SB_PAGERIGHT:
1959 LISTBOX_SetHorizontalPos( descr,
1960 descr->horz_pos + descr->width );
1961 break;
1962 case SB_THUMBPOSITION:
1963 LISTBOX_SetHorizontalPos( descr, pos );
1964 break;
1965 case SB_THUMBTRACK:
1966 info.cbSize = sizeof(info);
1967 info.fMask = SIF_TRACKPOS;
1968 GetScrollInfo( descr->self, SB_HORZ, &info );
1969 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1970 break;
1971 case SB_LEFT:
1972 LISTBOX_SetHorizontalPos( descr, 0 );
1973 break;
1974 case SB_RIGHT:
1975 LISTBOX_SetHorizontalPos( descr,
1976 descr->horz_extent - descr->width );
1977 break;
1980 return 0;
1983 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1985 short gcWheelDelta = 0;
1986 UINT pulScrollLines = 3;
1988 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1990 gcWheelDelta -= delta;
1992 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1994 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1995 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1996 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
1998 return 0;
2001 /***********************************************************************
2002 * LISTBOX_HandleLButtonDown
2004 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2006 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2007 TRACE("[%p]: lbuttondown %d,%d item %d\n", descr->self, x, y, index );
2008 if (!descr->caret_on && (descr->in_focus)) return 0;
2010 if (!descr->in_focus)
2012 if( !descr->lphc ) SetFocus( descr->self );
2013 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2016 if (index == -1) return 0;
2018 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2020 /* we should perhaps make sure that all items are deselected
2021 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2022 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2023 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2026 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2027 if (keys & MK_CONTROL)
2029 LISTBOX_SetCaretIndex( descr, index, FALSE );
2030 LISTBOX_SetSelection( descr, index,
2031 !descr->items[index].selected,
2032 (descr->style & LBS_NOTIFY) != 0);
2034 else
2036 LISTBOX_MoveCaret( descr, index, FALSE );
2038 if (descr->style & LBS_EXTENDEDSEL)
2040 LISTBOX_SetSelection( descr, index,
2041 descr->items[index].selected,
2042 (descr->style & LBS_NOTIFY) != 0 );
2044 else
2046 LISTBOX_SetSelection( descr, index,
2047 !descr->items[index].selected,
2048 (descr->style & LBS_NOTIFY) != 0 );
2052 else
2054 descr->anchor_item = index;
2055 LISTBOX_MoveCaret( descr, index, FALSE );
2056 LISTBOX_SetSelection( descr, index,
2057 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2060 descr->captured = TRUE;
2061 SetCapture( descr->self );
2063 if (!descr->lphc)
2065 if (descr->style & LBS_NOTIFY )
2066 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2067 MAKELPARAM( x, y ) );
2068 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2070 POINT pt;
2072 pt.x = x;
2073 pt.y = y;
2075 if (DragDetect( descr->self, pt ))
2076 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2079 return 0;
2083 /*************************************************************************
2084 * LISTBOX_HandleLButtonDownCombo [Internal]
2086 * Process LButtonDown message for the ComboListBox
2088 * PARAMS
2089 * pWnd [I] The windows internal structure
2090 * pDescr [I] The ListBox internal structure
2091 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2092 * x [I] X Mouse Coordinate
2093 * y [I] Y Mouse Coordinate
2095 * RETURNS
2096 * 0 since we are processing the WM_LBUTTONDOWN Message
2098 * NOTES
2099 * This function is only to be used when a ListBox is a ComboListBox
2102 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2104 RECT clientRect, screenRect;
2105 POINT mousePos;
2107 mousePos.x = x;
2108 mousePos.y = y;
2110 GetClientRect(descr->self, &clientRect);
2112 if(PtInRect(&clientRect, mousePos))
2114 /* MousePos is in client, resume normal processing */
2115 if (msg == WM_LBUTTONDOWN)
2117 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2118 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2120 else if (descr->style & LBS_NOTIFY)
2121 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2123 else
2125 POINT screenMousePos;
2126 HWND hWndOldCapture;
2128 /* Check the Non-Client Area */
2129 screenMousePos = mousePos;
2130 hWndOldCapture = GetCapture();
2131 ReleaseCapture();
2132 GetWindowRect(descr->self, &screenRect);
2133 ClientToScreen(descr->self, &screenMousePos);
2135 if(!PtInRect(&screenRect, screenMousePos))
2137 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2138 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2139 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2141 else
2143 /* Check to see the NC is a scrollbar */
2144 INT nHitTestType=0;
2145 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2146 /* Check Vertical scroll bar */
2147 if (style & WS_VSCROLL)
2149 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2150 if (PtInRect( &clientRect, mousePos ))
2151 nHitTestType = HTVSCROLL;
2153 /* Check horizontal scroll bar */
2154 if (style & WS_HSCROLL)
2156 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2157 if (PtInRect( &clientRect, mousePos ))
2158 nHitTestType = HTHSCROLL;
2160 /* Windows sends this message when a scrollbar is clicked
2163 if(nHitTestType != 0)
2165 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2166 MAKELONG(screenMousePos.x, screenMousePos.y));
2168 /* Resume the Capture after scrolling is complete
2170 if(hWndOldCapture != 0)
2171 SetCapture(hWndOldCapture);
2174 return 0;
2177 /***********************************************************************
2178 * LISTBOX_HandleLButtonUp
2180 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2182 if (LISTBOX_Timer != LB_TIMER_NONE)
2183 KillSystemTimer( descr->self, LB_TIMER_ID );
2184 LISTBOX_Timer = LB_TIMER_NONE;
2185 if (descr->captured)
2187 descr->captured = FALSE;
2188 if (GetCapture() == descr->self) ReleaseCapture();
2189 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2190 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2192 return 0;
2196 /***********************************************************************
2197 * LISTBOX_HandleTimer
2199 * Handle scrolling upon a timer event.
2200 * Return TRUE if scrolling should continue.
2202 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2204 switch(dir)
2206 case LB_TIMER_UP:
2207 if (descr->top_item) index = descr->top_item - 1;
2208 else index = 0;
2209 break;
2210 case LB_TIMER_LEFT:
2211 if (descr->top_item) index -= descr->page_size;
2212 break;
2213 case LB_TIMER_DOWN:
2214 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2215 if (index == descr->focus_item) index++;
2216 if (index >= descr->nb_items) index = descr->nb_items - 1;
2217 break;
2218 case LB_TIMER_RIGHT:
2219 if (index + descr->page_size < descr->nb_items)
2220 index += descr->page_size;
2221 break;
2222 case LB_TIMER_NONE:
2223 break;
2225 if (index == descr->focus_item) return FALSE;
2226 LISTBOX_MoveCaret( descr, index, FALSE );
2227 return TRUE;
2231 /***********************************************************************
2232 * LISTBOX_HandleSystemTimer
2234 * WM_SYSTIMER handler.
2236 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2238 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2240 KillSystemTimer( descr->self, LB_TIMER_ID );
2241 LISTBOX_Timer = LB_TIMER_NONE;
2243 return 0;
2247 /***********************************************************************
2248 * LISTBOX_HandleMouseMove
2250 * WM_MOUSEMOVE handler.
2252 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2253 INT x, INT y )
2255 INT index;
2256 TIMER_DIRECTION dir = LB_TIMER_NONE;
2258 if (!descr->captured) return;
2260 if (descr->style & LBS_MULTICOLUMN)
2262 if (y < 0) y = 0;
2263 else if (y >= descr->item_height * descr->page_size)
2264 y = descr->item_height * descr->page_size - 1;
2266 if (x < 0)
2268 dir = LB_TIMER_LEFT;
2269 x = 0;
2271 else if (x >= descr->width)
2273 dir = LB_TIMER_RIGHT;
2274 x = descr->width - 1;
2277 else
2279 if (y < 0) dir = LB_TIMER_UP; /* above */
2280 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2283 index = LISTBOX_GetItemFromPoint( descr, x, y );
2284 if (index == -1) index = descr->focus_item;
2285 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2287 /* Start/stop the system timer */
2289 if (dir != LB_TIMER_NONE)
2290 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2291 else if (LISTBOX_Timer != LB_TIMER_NONE)
2292 KillSystemTimer( descr->self, LB_TIMER_ID );
2293 LISTBOX_Timer = dir;
2297 /***********************************************************************
2298 * LISTBOX_HandleKeyDown
2300 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2302 INT caret = -1;
2303 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2304 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2305 bForceSelection = FALSE; /* only for single select list */
2307 if (descr->style & LBS_WANTKEYBOARDINPUT)
2309 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2310 MAKEWPARAM(LOWORD(key), descr->focus_item),
2311 (LPARAM)descr->self );
2312 if (caret == -2) return 0;
2314 if (caret == -1) switch(key)
2316 case VK_LEFT:
2317 if (descr->style & LBS_MULTICOLUMN)
2319 bForceSelection = FALSE;
2320 if (descr->focus_item >= descr->page_size)
2321 caret = descr->focus_item - descr->page_size;
2322 break;
2324 /* fall through */
2325 case VK_UP:
2326 caret = descr->focus_item - 1;
2327 if (caret < 0) caret = 0;
2328 break;
2329 case VK_RIGHT:
2330 if (descr->style & LBS_MULTICOLUMN)
2332 bForceSelection = FALSE;
2333 if (descr->focus_item + descr->page_size < descr->nb_items)
2334 caret = descr->focus_item + descr->page_size;
2335 break;
2337 /* fall through */
2338 case VK_DOWN:
2339 caret = descr->focus_item + 1;
2340 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2341 break;
2343 case VK_PRIOR:
2344 if (descr->style & LBS_MULTICOLUMN)
2346 INT page = descr->width / descr->column_width;
2347 if (page < 1) page = 1;
2348 caret = descr->focus_item - (page * descr->page_size) + 1;
2350 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2351 if (caret < 0) caret = 0;
2352 break;
2353 case VK_NEXT:
2354 if (descr->style & LBS_MULTICOLUMN)
2356 INT page = descr->width / descr->column_width;
2357 if (page < 1) page = 1;
2358 caret = descr->focus_item + (page * descr->page_size) - 1;
2360 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2361 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2362 break;
2363 case VK_HOME:
2364 caret = 0;
2365 break;
2366 case VK_END:
2367 caret = descr->nb_items - 1;
2368 break;
2369 case VK_SPACE:
2370 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2371 else if (descr->style & LBS_MULTIPLESEL)
2373 LISTBOX_SetSelection( descr, descr->focus_item,
2374 !descr->items[descr->focus_item].selected,
2375 (descr->style & LBS_NOTIFY) != 0 );
2377 break;
2378 default:
2379 bForceSelection = FALSE;
2381 if (bForceSelection) /* focused item is used instead of key */
2382 caret = descr->focus_item;
2383 if (caret >= 0)
2385 if (((descr->style & LBS_EXTENDEDSEL) &&
2386 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2387 !IS_MULTISELECT(descr))
2388 descr->anchor_item = caret;
2389 LISTBOX_MoveCaret( descr, caret, TRUE );
2391 if (descr->style & LBS_MULTIPLESEL)
2392 descr->selected_item = caret;
2393 else
2394 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2395 if (descr->style & LBS_NOTIFY)
2397 if( descr->lphc )
2399 /* make sure that combo parent doesn't hide us */
2400 descr->lphc->wState |= CBF_NOROLLUP;
2402 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2405 return 0;
2409 /***********************************************************************
2410 * LISTBOX_HandleChar
2412 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2414 INT caret = -1;
2415 WCHAR str[2];
2417 str[0] = charW;
2418 str[1] = '\0';
2420 if (descr->style & LBS_WANTKEYBOARDINPUT)
2422 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2423 MAKEWPARAM(charW, descr->focus_item),
2424 (LPARAM)descr->self );
2425 if (caret == -2) return 0;
2427 if (caret == -1)
2428 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2429 if (caret != -1)
2431 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2432 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2433 LISTBOX_MoveCaret( descr, caret, TRUE );
2434 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2435 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2437 return 0;
2441 /***********************************************************************
2442 * LISTBOX_Create
2444 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2446 LB_DESCR *descr;
2447 MEASUREITEMSTRUCT mis;
2448 RECT rect;
2450 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2451 return FALSE;
2453 GetClientRect( hwnd, &rect );
2454 descr->self = hwnd;
2455 descr->owner = GetParent( descr->self );
2456 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2457 descr->width = rect.right - rect.left;
2458 descr->height = rect.bottom - rect.top;
2459 descr->items = NULL;
2460 descr->nb_items = 0;
2461 descr->top_item = 0;
2462 descr->selected_item = -1;
2463 descr->focus_item = 0;
2464 descr->anchor_item = -1;
2465 descr->item_height = 1;
2466 descr->page_size = 1;
2467 descr->column_width = 150;
2468 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2469 descr->horz_pos = 0;
2470 descr->nb_tabs = 0;
2471 descr->tabs = NULL;
2472 descr->caret_on = lphc ? FALSE : TRUE;
2473 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2474 descr->in_focus = FALSE;
2475 descr->captured = FALSE;
2476 descr->font = 0;
2477 descr->locale = GetUserDefaultLCID();
2478 descr->lphc = lphc;
2480 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2482 /* Win95 document "List Box Differences" from MSDN:
2483 If a list box in a version 3.x application has either the
2484 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2485 horizontal and vertical scroll bars.
2487 descr->style |= WS_VSCROLL | WS_HSCROLL;
2490 if( lphc )
2492 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2493 descr->owner = lphc->self;
2496 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2498 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2500 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2501 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2502 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2503 descr->item_height = LISTBOX_SetFont( descr, 0 );
2505 if (descr->style & LBS_OWNERDRAWFIXED)
2507 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2509 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2510 descr->item_height = lphc->fixedOwnerDrawHeight;
2512 else
2514 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2515 mis.CtlType = ODT_LISTBOX;
2516 mis.CtlID = id;
2517 mis.itemID = -1;
2518 mis.itemWidth = 0;
2519 mis.itemData = 0;
2520 mis.itemHeight = descr->item_height;
2521 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2522 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2526 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2527 return TRUE;
2531 /***********************************************************************
2532 * LISTBOX_Destroy
2534 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2536 LISTBOX_ResetContent( descr );
2537 SetWindowLongPtrW( descr->self, 0, 0 );
2538 HeapFree( GetProcessHeap(), 0, descr );
2539 return TRUE;
2543 /***********************************************************************
2544 * ListBoxWndProc_common
2546 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2547 WPARAM wParam, LPARAM lParam, BOOL unicode )
2549 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2550 LPHEADCOMBO lphc = 0;
2551 LRESULT ret;
2553 if (!descr)
2555 if (!IsWindow(hwnd)) return 0;
2557 if (msg == WM_CREATE)
2559 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2560 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2561 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2562 TRACE("creating wnd=%p descr=%x\n", hwnd, GetWindowLongPtrW( hwnd, 0 ) );
2563 return 0;
2565 /* Ignore all other messages before we get a WM_CREATE */
2566 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2567 DefWindowProcA( hwnd, msg, wParam, lParam );
2569 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2571 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2572 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2574 switch(msg)
2576 case LB_RESETCONTENT16:
2577 case LB_RESETCONTENT:
2578 LISTBOX_ResetContent( descr );
2579 LISTBOX_UpdateScroll( descr );
2580 InvalidateRect( descr->self, NULL, TRUE );
2581 return 0;
2583 case LB_ADDSTRING16:
2584 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2585 /* fall through */
2586 case LB_ADDSTRING:
2588 INT ret;
2589 LPWSTR textW;
2590 if(unicode || !HAS_STRINGS(descr))
2591 textW = (LPWSTR)lParam;
2592 else
2594 LPSTR textA = (LPSTR)lParam;
2595 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2596 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2597 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2599 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2600 ret = LISTBOX_InsertString( descr, wParam, textW );
2601 if (!unicode && HAS_STRINGS(descr))
2602 HeapFree(GetProcessHeap(), 0, textW);
2603 return ret;
2606 case LB_INSERTSTRING16:
2607 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2608 wParam = (INT)(INT16)wParam;
2609 /* fall through */
2610 case LB_INSERTSTRING:
2612 INT ret;
2613 LPWSTR textW;
2614 if(unicode || !HAS_STRINGS(descr))
2615 textW = (LPWSTR)lParam;
2616 else
2618 LPSTR textA = (LPSTR)lParam;
2619 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2620 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2621 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2623 ret = LISTBOX_InsertString( descr, wParam, textW );
2624 if(!unicode && HAS_STRINGS(descr))
2625 HeapFree(GetProcessHeap(), 0, textW);
2626 return ret;
2629 case LB_ADDFILE16:
2630 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2631 /* fall through */
2632 case LB_ADDFILE:
2634 INT ret;
2635 LPWSTR textW;
2636 if(unicode || !HAS_STRINGS(descr))
2637 textW = (LPWSTR)lParam;
2638 else
2640 LPSTR textA = (LPSTR)lParam;
2641 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2642 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2643 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2645 wParam = LISTBOX_FindFileStrPos( descr, textW );
2646 ret = LISTBOX_InsertString( descr, wParam, textW );
2647 if(!unicode && HAS_STRINGS(descr))
2648 HeapFree(GetProcessHeap(), 0, textW);
2649 return ret;
2652 case LB_DELETESTRING16:
2653 case LB_DELETESTRING:
2654 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2655 return descr->nb_items;
2656 else
2658 SetLastError(ERROR_INVALID_INDEX);
2659 return LB_ERR;
2662 case LB_GETITEMDATA16:
2663 case LB_GETITEMDATA:
2664 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2666 SetLastError(ERROR_INVALID_INDEX);
2667 return LB_ERR;
2669 return descr->items[wParam].data;
2671 case LB_SETITEMDATA16:
2672 case LB_SETITEMDATA:
2673 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2675 SetLastError(ERROR_INVALID_INDEX);
2676 return LB_ERR;
2678 descr->items[wParam].data = lParam;
2679 /* undocumented: returns TRUE, not LB_OKAY (0) */
2680 return TRUE;
2682 case LB_GETCOUNT16:
2683 case LB_GETCOUNT:
2684 return descr->nb_items;
2686 case LB_GETTEXT16:
2687 lParam = (LPARAM)MapSL(lParam);
2688 /* fall through */
2689 case LB_GETTEXT:
2690 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2692 case LB_GETTEXTLEN16:
2693 /* fall through */
2694 case LB_GETTEXTLEN:
2695 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2697 SetLastError(ERROR_INVALID_INDEX);
2698 return LB_ERR;
2700 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2701 if (unicode) return strlenW( descr->items[wParam].str );
2702 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2703 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2705 case LB_GETCURSEL16:
2706 case LB_GETCURSEL:
2707 if (descr->nb_items == 0)
2708 return LB_ERR;
2709 if (!IS_MULTISELECT(descr))
2710 return descr->selected_item;
2711 if (descr->selected_item != -1)
2712 return descr->selected_item;
2713 return descr->focus_item;
2714 /* otherwise, if the user tries to move the selection with the */
2715 /* arrow keys, we will give the application something to choke on */
2716 case LB_GETTOPINDEX16:
2717 case LB_GETTOPINDEX:
2718 return descr->top_item;
2720 case LB_GETITEMHEIGHT16:
2721 case LB_GETITEMHEIGHT:
2722 return LISTBOX_GetItemHeight( descr, wParam );
2724 case LB_SETITEMHEIGHT16:
2725 lParam = LOWORD(lParam);
2726 /* fall through */
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_SETCARETINDEX16:
2769 case LB_SETCARETINDEX:
2770 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2771 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2772 return LB_ERR;
2773 else if (ISWIN31)
2774 return wParam;
2775 else
2776 return LB_OKAY;
2778 case LB_GETCARETINDEX16:
2779 case LB_GETCARETINDEX:
2780 return descr->focus_item;
2782 case LB_SETTOPINDEX16:
2783 case LB_SETTOPINDEX:
2784 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2786 case LB_SETCOLUMNWIDTH16:
2787 case LB_SETCOLUMNWIDTH:
2788 return LISTBOX_SetColumnWidth( descr, wParam );
2790 case LB_GETITEMRECT16:
2792 RECT rect;
2793 RECT16 *r16 = MapSL(lParam);
2794 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2795 r16->left = rect.left;
2796 r16->top = rect.top;
2797 r16->right = rect.right;
2798 r16->bottom = rect.bottom;
2800 return ret;
2802 case LB_GETITEMRECT:
2803 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2805 case LB_FINDSTRING16:
2806 wParam = (INT)(INT16)wParam;
2807 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2808 /* fall through */
2809 case LB_FINDSTRING:
2811 INT ret;
2812 LPWSTR textW;
2813 if(unicode || !HAS_STRINGS(descr))
2814 textW = (LPWSTR)lParam;
2815 else
2817 LPSTR textA = (LPSTR)lParam;
2818 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2819 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2820 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2822 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2823 if(!unicode && HAS_STRINGS(descr))
2824 HeapFree(GetProcessHeap(), 0, textW);
2825 return ret;
2828 case LB_FINDSTRINGEXACT16:
2829 wParam = (INT)(INT16)wParam;
2830 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2831 /* fall through */
2832 case LB_FINDSTRINGEXACT:
2834 INT ret;
2835 LPWSTR textW;
2836 if(unicode || !HAS_STRINGS(descr))
2837 textW = (LPWSTR)lParam;
2838 else
2840 LPSTR textA = (LPSTR)lParam;
2841 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2842 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2843 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2845 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2846 if(!unicode && HAS_STRINGS(descr))
2847 HeapFree(GetProcessHeap(), 0, textW);
2848 return ret;
2851 case LB_SELECTSTRING16:
2852 wParam = (INT)(INT16)wParam;
2853 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2854 /* fall through */
2855 case LB_SELECTSTRING:
2857 INT index;
2858 LPWSTR textW;
2860 if(HAS_STRINGS(descr))
2861 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2862 debugstr_a((LPSTR)lParam));
2863 if(unicode || !HAS_STRINGS(descr))
2864 textW = (LPWSTR)lParam;
2865 else
2867 LPSTR textA = (LPSTR)lParam;
2868 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2869 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2870 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2872 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2873 if(!unicode && HAS_STRINGS(descr))
2874 HeapFree(GetProcessHeap(), 0, textW);
2875 if (index != LB_ERR)
2877 LISTBOX_MoveCaret( descr, index, TRUE );
2878 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2880 return index;
2883 case LB_GETSEL16:
2884 wParam = (INT)(INT16)wParam;
2885 /* fall through */
2886 case LB_GETSEL:
2887 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2888 return LB_ERR;
2889 return descr->items[wParam].selected;
2891 case LB_SETSEL16:
2892 lParam = (INT)(INT16)lParam;
2893 /* fall through */
2894 case LB_SETSEL:
2895 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2897 case LB_SETCURSEL16:
2898 wParam = (INT)(INT16)wParam;
2899 /* fall through */
2900 case LB_SETCURSEL:
2901 if (IS_MULTISELECT(descr)) return LB_ERR;
2902 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2903 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2904 if (lphc && ret != LB_ERR) ret = descr->selected_item;
2905 return ret;
2907 case LB_GETSELCOUNT16:
2908 case LB_GETSELCOUNT:
2909 return LISTBOX_GetSelCount( descr );
2911 case LB_GETSELITEMS16:
2912 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2914 case LB_GETSELITEMS:
2915 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2917 case LB_SELITEMRANGE16:
2918 case LB_SELITEMRANGE:
2919 if (LOWORD(lParam) <= HIWORD(lParam))
2920 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2921 HIWORD(lParam), wParam );
2922 else
2923 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2924 LOWORD(lParam), wParam );
2926 case LB_SELITEMRANGEEX16:
2927 case LB_SELITEMRANGEEX:
2928 if ((INT)lParam >= (INT)wParam)
2929 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2930 else
2931 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2933 case LB_GETHORIZONTALEXTENT16:
2934 case LB_GETHORIZONTALEXTENT:
2935 return descr->horz_extent;
2937 case LB_SETHORIZONTALEXTENT16:
2938 case LB_SETHORIZONTALEXTENT:
2939 return LISTBOX_SetHorizontalExtent( descr, wParam );
2941 case LB_GETANCHORINDEX16:
2942 case LB_GETANCHORINDEX:
2943 return descr->anchor_item;
2945 case LB_SETANCHORINDEX16:
2946 wParam = (INT)(INT16)wParam;
2947 /* fall through */
2948 case LB_SETANCHORINDEX:
2949 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2951 SetLastError(ERROR_INVALID_INDEX);
2952 return LB_ERR;
2954 descr->anchor_item = (INT)wParam;
2955 return LB_OKAY;
2957 case LB_DIR16:
2958 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2959 * be set automatically (this is different in Win32) */
2960 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2961 lParam = (LPARAM)MapSL(lParam);
2962 /* fall through */
2963 case LB_DIR:
2965 INT ret;
2966 LPWSTR textW;
2967 if(unicode)
2968 textW = (LPWSTR)lParam;
2969 else
2971 LPSTR textA = (LPSTR)lParam;
2972 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2973 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2974 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2976 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2977 if(!unicode)
2978 HeapFree(GetProcessHeap(), 0, textW);
2979 return ret;
2982 case LB_GETLOCALE:
2983 return descr->locale;
2985 case LB_SETLOCALE:
2987 LCID ret;
2988 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2989 return LB_ERR;
2990 ret = descr->locale;
2991 descr->locale = (LCID)wParam;
2992 return ret;
2995 case LB_INITSTORAGE:
2996 return LISTBOX_InitStorage( descr, wParam );
2998 case LB_SETCOUNT:
2999 return LISTBOX_SetCount( descr, (INT)wParam );
3001 case LB_SETTABSTOPS16:
3002 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
3004 case LB_SETTABSTOPS:
3005 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
3007 case LB_CARETON16:
3008 case LB_CARETON:
3009 if (descr->caret_on)
3010 return LB_OKAY;
3011 descr->caret_on = TRUE;
3012 if ((descr->focus_item != -1) && (descr->in_focus))
3013 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3014 return LB_OKAY;
3016 case LB_CARETOFF16:
3017 case LB_CARETOFF:
3018 if (!descr->caret_on)
3019 return LB_OKAY;
3020 descr->caret_on = FALSE;
3021 if ((descr->focus_item != -1) && (descr->in_focus))
3022 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3023 return LB_OKAY;
3025 case LB_GETLISTBOXINFO:
3026 FIXME("LB_GETLISTBOXINFO: stub!\n");
3027 return 0;
3029 case WM_DESTROY:
3030 return LISTBOX_Destroy( descr );
3032 case WM_ENABLE:
3033 InvalidateRect( descr->self, NULL, TRUE );
3034 return 0;
3036 case WM_SETREDRAW:
3037 LISTBOX_SetRedraw( descr, wParam != 0 );
3038 return 0;
3040 case WM_GETDLGCODE:
3041 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3043 case WM_PRINTCLIENT:
3044 case WM_PAINT:
3046 PAINTSTRUCT ps;
3047 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3048 ret = LISTBOX_Paint( descr, hdc );
3049 if( !wParam ) EndPaint( descr->self, &ps );
3051 return ret;
3052 case WM_SIZE:
3053 LISTBOX_UpdateSize( descr );
3054 return 0;
3055 case WM_GETFONT:
3056 return (LRESULT)descr->font;
3057 case WM_SETFONT:
3058 LISTBOX_SetFont( descr, (HFONT)wParam );
3059 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3060 return 0;
3061 case WM_SETFOCUS:
3062 descr->in_focus = TRUE;
3063 descr->caret_on = TRUE;
3064 if (descr->focus_item != -1)
3065 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3066 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3067 return 0;
3068 case WM_KILLFOCUS:
3069 descr->in_focus = FALSE;
3070 if ((descr->focus_item != -1) && descr->caret_on)
3071 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3072 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3073 return 0;
3074 case WM_HSCROLL:
3075 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3076 case WM_VSCROLL:
3077 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3078 case WM_MOUSEWHEEL:
3079 if (wParam & (MK_SHIFT | MK_CONTROL))
3080 return DefWindowProcW( descr->self, msg, wParam, lParam );
3081 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3082 case WM_LBUTTONDOWN:
3083 if (lphc)
3084 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3085 (INT16)LOWORD(lParam),
3086 (INT16)HIWORD(lParam) );
3087 return LISTBOX_HandleLButtonDown( descr, wParam,
3088 (INT16)LOWORD(lParam),
3089 (INT16)HIWORD(lParam) );
3090 case WM_LBUTTONDBLCLK:
3091 if (lphc)
3092 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3093 (INT16)LOWORD(lParam),
3094 (INT16)HIWORD(lParam) );
3095 if (descr->style & LBS_NOTIFY)
3096 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3097 return 0;
3098 case WM_MOUSEMOVE:
3099 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3101 BOOL captured = descr->captured;
3102 POINT mousePos;
3103 RECT clientRect;
3105 mousePos.x = (INT16)LOWORD(lParam);
3106 mousePos.y = (INT16)HIWORD(lParam);
3109 * If we are in a dropdown combobox, we simulate that
3110 * the mouse is captured to show the tracking of the item.
3112 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3113 descr->captured = TRUE;
3115 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3117 descr->captured = captured;
3119 else if (GetCapture() == descr->self)
3121 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3122 (INT16)HIWORD(lParam) );
3124 return 0;
3125 case WM_LBUTTONUP:
3126 if (lphc)
3128 POINT mousePos;
3129 RECT clientRect;
3132 * If the mouse button "up" is not in the listbox,
3133 * we make sure there is no selection by re-selecting the
3134 * item that was selected when the listbox was made visible.
3136 mousePos.x = (INT16)LOWORD(lParam);
3137 mousePos.y = (INT16)HIWORD(lParam);
3139 GetClientRect(descr->self, &clientRect);
3142 * When the user clicks outside the combobox and the focus
3143 * is lost, the owning combobox will send a fake buttonup with
3144 * 0xFFFFFFF as the mouse location, we must also revert the
3145 * selection to the original selection.
3147 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3148 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3150 return LISTBOX_HandleLButtonUp( descr );
3151 case WM_KEYDOWN:
3152 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3154 /* for some reason Windows makes it possible to
3155 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3157 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3158 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3159 && (wParam == VK_DOWN || wParam == VK_UP)) )
3161 COMBO_FlipListbox( lphc, FALSE, FALSE );
3162 return 0;
3165 return LISTBOX_HandleKeyDown( descr, wParam );
3166 case WM_CHAR:
3168 WCHAR charW;
3169 if(unicode)
3170 charW = (WCHAR)wParam;
3171 else
3173 CHAR charA = (CHAR)wParam;
3174 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3176 return LISTBOX_HandleChar( descr, charW );
3178 case WM_SYSTIMER:
3179 return LISTBOX_HandleSystemTimer( descr );
3180 case WM_ERASEBKGND:
3181 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3183 RECT rect;
3184 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3185 wParam, (LPARAM)descr->self );
3186 TRACE("hbrush = %p\n", hbrush);
3187 if(!hbrush)
3188 hbrush = GetSysColorBrush(COLOR_WINDOW);
3189 if(hbrush)
3191 GetClientRect(descr->self, &rect);
3192 FillRect((HDC)wParam, &rect, hbrush);
3195 return 1;
3196 case WM_DROPFILES:
3197 if( lphc ) return 0;
3198 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3199 SendMessageA( descr->owner, msg, wParam, lParam );
3201 case WM_NCDESTROY:
3202 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3203 lphc->hWndLBox = 0;
3204 break;
3206 case WM_NCACTIVATE:
3207 if (lphc) return 0;
3208 break;
3210 default:
3211 if ((msg >= WM_USER) && (msg < 0xc000))
3212 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3213 hwnd, msg, wParam, lParam );
3216 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3217 DefWindowProcA( hwnd, msg, wParam, lParam );
3220 /***********************************************************************
3221 * ListBoxWndProcA
3223 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3225 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3228 /***********************************************************************
3229 * ListBoxWndProcW
3231 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3233 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );