push(c) 7dcddf6204ed5518706a76fe66fd836d81e70dd4
[wine/hacks.git] / dlls / user32 / listbox.c
blobfad5a4831ff4681e0731fb7bd68545b326a50f6e
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( "cur_sel=%d index=%d notify=%s\n",
1421 descr->selected_item, index, send_notify ? "YES" : "NO" );
1423 if (descr->style & LBS_NOSEL)
1425 descr->selected_item = index;
1426 return LB_ERR;
1428 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1429 if (descr->style & LBS_MULTIPLESEL)
1431 if (index == -1) /* Select all items */
1432 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1433 else /* Only one item */
1434 return LISTBOX_SelectItemRange( descr, index, index, on );
1436 else
1438 INT oldsel = descr->selected_item;
1439 if (index == oldsel) return LB_OKAY;
1440 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1441 if (index != -1) descr->items[index].selected = TRUE;
1442 descr->selected_item = index;
1443 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1444 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1445 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1446 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1447 else
1448 if( descr->lphc ) /* set selection change flag for parent combo */
1449 descr->lphc->wState |= CBF_SELCHANGE;
1451 return LB_OKAY;
1455 /***********************************************************************
1456 * LISTBOX_MoveCaret
1458 * Change the caret position and extend the selection to the new caret.
1460 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1462 INT oldfocus = descr->focus_item;
1464 if ((index < 0) || (index >= descr->nb_items))
1465 return;
1467 /* Important, repaint needs to be done in this order if
1468 you want to mimic Windows behavior:
1469 1. Remove the focus and paint the item
1470 2. Remove the selection and paint the item(s)
1471 3. Set the selection and repaint the item(s)
1472 4. Set the focus to 'index' and repaint the item */
1474 /* 1. remove the focus and repaint the item */
1475 descr->focus_item = -1;
1476 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1477 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1479 /* 2. then turn off the previous selection */
1480 /* 3. repaint the new selected item */
1481 if (descr->style & LBS_EXTENDEDSEL)
1483 if (descr->anchor_item != -1)
1485 INT first = min( index, descr->anchor_item );
1486 INT last = max( index, descr->anchor_item );
1487 if (first > 0)
1488 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1489 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1490 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1493 else if (!(descr->style & LBS_MULTIPLESEL))
1495 /* Set selection to new caret item */
1496 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1499 /* 4. repaint the new item with the focus */
1500 descr->focus_item = index;
1501 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1502 if (descr->caret_on && (descr->in_focus))
1503 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1507 /***********************************************************************
1508 * LISTBOX_InsertItem
1510 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1511 LPWSTR str, ULONG_PTR data )
1513 LB_ITEMDATA *item;
1514 INT max_items;
1515 INT oldfocus = descr->focus_item;
1517 if (index == -1) index = descr->nb_items;
1518 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1519 if (!descr->items) max_items = 0;
1520 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1521 if (descr->nb_items == max_items)
1523 /* We need to grow the array */
1524 max_items += LB_ARRAY_GRANULARITY;
1525 if (descr->items)
1526 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1527 max_items * sizeof(LB_ITEMDATA) );
1528 else
1529 item = HeapAlloc( GetProcessHeap(), 0,
1530 max_items * sizeof(LB_ITEMDATA) );
1531 if (!item)
1533 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1534 return LB_ERRSPACE;
1536 descr->items = item;
1539 /* Insert the item structure */
1541 item = &descr->items[index];
1542 if (index < descr->nb_items)
1543 RtlMoveMemory( item + 1, item,
1544 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1545 item->str = str;
1546 item->data = data;
1547 item->height = 0;
1548 item->selected = FALSE;
1549 descr->nb_items++;
1551 /* Get item height */
1553 if (descr->style & LBS_OWNERDRAWVARIABLE)
1555 MEASUREITEMSTRUCT mis;
1556 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1558 mis.CtlType = ODT_LISTBOX;
1559 mis.CtlID = id;
1560 mis.itemID = index;
1561 mis.itemData = descr->items[index].data;
1562 mis.itemHeight = descr->item_height;
1563 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1564 item->height = mis.itemHeight ? mis.itemHeight : 1;
1565 TRACE("[%p]: measure item %d (%s) = %d\n",
1566 descr->self, index, str ? debugstr_w(str) : "", item->height );
1569 /* Repaint the items */
1571 LISTBOX_UpdateScroll( descr );
1572 LISTBOX_InvalidateItems( descr, index );
1574 /* Move selection and focused item */
1575 /* If listbox was empty, set focus to the first item */
1576 if (descr->nb_items == 1)
1577 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1578 /* single select don't change selection index in win31 */
1579 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1581 descr->selected_item++;
1582 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1584 else
1586 if (index <= descr->selected_item)
1588 descr->selected_item++;
1589 descr->focus_item = oldfocus; /* focus not changed */
1592 return LB_OKAY;
1596 /***********************************************************************
1597 * LISTBOX_InsertString
1599 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1601 LPWSTR new_str = NULL;
1602 ULONG_PTR data = 0;
1603 LRESULT ret;
1605 if (HAS_STRINGS(descr))
1607 static const WCHAR empty_stringW[] = { 0 };
1608 if (!str) str = empty_stringW;
1609 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1611 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1612 return LB_ERRSPACE;
1614 strcpyW(new_str, str);
1616 else data = (ULONG_PTR)str;
1618 if (index == -1) index = descr->nb_items;
1619 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1621 HeapFree( GetProcessHeap(), 0, new_str );
1622 return ret;
1625 TRACE("[%p]: added item %d %s\n",
1626 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1627 return index;
1631 /***********************************************************************
1632 * LISTBOX_DeleteItem
1634 * Delete the content of an item. 'index' must be a valid index.
1636 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1638 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1639 * while Win95 sends it for all items with user data.
1640 * It's probably better to send it too often than not
1641 * often enough, so this is what we do here.
1643 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1645 DELETEITEMSTRUCT dis;
1646 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1648 dis.CtlType = ODT_LISTBOX;
1649 dis.CtlID = id;
1650 dis.itemID = index;
1651 dis.hwndItem = descr->self;
1652 dis.itemData = descr->items[index].data;
1653 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1655 if (HAS_STRINGS(descr))
1656 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1660 /***********************************************************************
1661 * LISTBOX_RemoveItem
1663 * Remove an item from the listbox and delete its content.
1665 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1667 LB_ITEMDATA *item;
1668 INT max_items;
1670 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1672 /* We need to invalidate the original rect instead of the updated one. */
1673 LISTBOX_InvalidateItems( descr, index );
1675 LISTBOX_DeleteItem( descr, index );
1677 /* Remove the item */
1679 item = &descr->items[index];
1680 if (index < descr->nb_items-1)
1681 RtlMoveMemory( item, item + 1,
1682 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1683 descr->nb_items--;
1684 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1686 /* Shrink the item array if possible */
1688 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1689 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1691 max_items -= LB_ARRAY_GRANULARITY;
1692 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1693 max_items * sizeof(LB_ITEMDATA) );
1694 if (item) descr->items = item;
1696 /* Repaint the items */
1698 LISTBOX_UpdateScroll( descr );
1699 /* if we removed the scrollbar, reset the top of the list
1700 (correct for owner-drawn ???) */
1701 if (descr->nb_items == descr->page_size)
1702 LISTBOX_SetTopItem( descr, 0, TRUE );
1704 /* Move selection and focused item */
1705 if (!IS_MULTISELECT(descr))
1707 if (index == descr->selected_item)
1708 descr->selected_item = -1;
1709 else if (index < descr->selected_item)
1711 descr->selected_item--;
1712 if (ISWIN31) /* win 31 do not change the selected item number */
1713 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1717 if (descr->focus_item >= descr->nb_items)
1719 descr->focus_item = descr->nb_items - 1;
1720 if (descr->focus_item < 0) descr->focus_item = 0;
1722 return LB_OKAY;
1726 /***********************************************************************
1727 * LISTBOX_ResetContent
1729 static void LISTBOX_ResetContent( LB_DESCR *descr )
1731 INT i;
1733 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1734 HeapFree( GetProcessHeap(), 0, descr->items );
1735 descr->nb_items = 0;
1736 descr->top_item = 0;
1737 descr->selected_item = -1;
1738 descr->focus_item = 0;
1739 descr->anchor_item = -1;
1740 descr->items = NULL;
1744 /***********************************************************************
1745 * LISTBOX_SetCount
1747 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1749 LRESULT ret;
1751 if (HAS_STRINGS(descr))
1753 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1754 return LB_ERR;
1757 /* FIXME: this is far from optimal... */
1758 if (count > descr->nb_items)
1760 while (count > descr->nb_items)
1761 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1762 return ret;
1764 else if (count < descr->nb_items)
1766 while (count < descr->nb_items)
1767 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1768 return ret;
1770 return LB_OKAY;
1774 /***********************************************************************
1775 * LISTBOX_Directory
1777 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1778 LPCWSTR filespec, BOOL long_names )
1780 HANDLE handle;
1781 LRESULT ret = LB_OKAY;
1782 WIN32_FIND_DATAW entry;
1783 int pos;
1785 /* don't scan directory if we just want drives exclusively */
1786 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1787 /* scan directory */
1788 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1790 int le = GetLastError();
1791 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1793 else
1797 WCHAR buffer[270];
1798 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1800 static const WCHAR bracketW[] = { ']',0 };
1801 static const WCHAR dotW[] = { '.',0 };
1802 if (!(attrib & DDL_DIRECTORY) ||
1803 !strcmpW( entry.cFileName, dotW )) continue;
1804 buffer[0] = '[';
1805 if (!long_names && entry.cAlternateFileName[0])
1806 strcpyW( buffer + 1, entry.cAlternateFileName );
1807 else
1808 strcpyW( buffer + 1, entry.cFileName );
1809 strcatW(buffer, bracketW);
1811 else /* not a directory */
1813 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1814 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1816 if ((attrib & DDL_EXCLUSIVE) &&
1817 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1818 continue;
1819 #undef ATTRIBS
1820 if (!long_names && entry.cAlternateFileName[0])
1821 strcpyW( buffer, entry.cAlternateFileName );
1822 else
1823 strcpyW( buffer, entry.cFileName );
1825 if (!long_names) CharLowerW( buffer );
1826 pos = LISTBOX_FindFileStrPos( descr, buffer );
1827 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1828 break;
1829 } while (FindNextFileW( handle, &entry ));
1830 FindClose( handle );
1834 /* scan drives */
1835 if ((ret >= 0) && (attrib & DDL_DRIVES))
1837 WCHAR buffer[] = {'[','-','a','-',']',0};
1838 WCHAR root[] = {'A',':','\\',0};
1839 int drive;
1840 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1842 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1843 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1844 break;
1847 return ret;
1851 /***********************************************************************
1852 * LISTBOX_HandleVScroll
1854 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1856 SCROLLINFO info;
1858 if (descr->style & LBS_MULTICOLUMN) return 0;
1859 switch(scrollReq)
1861 case SB_LINEUP:
1862 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1863 break;
1864 case SB_LINEDOWN:
1865 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1866 break;
1867 case SB_PAGEUP:
1868 LISTBOX_SetTopItem( descr, descr->top_item -
1869 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1870 break;
1871 case SB_PAGEDOWN:
1872 LISTBOX_SetTopItem( descr, descr->top_item +
1873 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1874 break;
1875 case SB_THUMBPOSITION:
1876 LISTBOX_SetTopItem( descr, pos, TRUE );
1877 break;
1878 case SB_THUMBTRACK:
1879 info.cbSize = sizeof(info);
1880 info.fMask = SIF_TRACKPOS;
1881 GetScrollInfo( descr->self, SB_VERT, &info );
1882 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1883 break;
1884 case SB_TOP:
1885 LISTBOX_SetTopItem( descr, 0, TRUE );
1886 break;
1887 case SB_BOTTOM:
1888 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1889 break;
1891 return 0;
1895 /***********************************************************************
1896 * LISTBOX_HandleHScroll
1898 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1900 SCROLLINFO info;
1901 INT page;
1903 if (descr->style & LBS_MULTICOLUMN)
1905 switch(scrollReq)
1907 case SB_LINELEFT:
1908 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1909 TRUE );
1910 break;
1911 case SB_LINERIGHT:
1912 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1913 TRUE );
1914 break;
1915 case SB_PAGELEFT:
1916 page = descr->width / descr->column_width;
1917 if (page < 1) page = 1;
1918 LISTBOX_SetTopItem( descr,
1919 descr->top_item - page * descr->page_size, TRUE );
1920 break;
1921 case SB_PAGERIGHT:
1922 page = descr->width / descr->column_width;
1923 if (page < 1) page = 1;
1924 LISTBOX_SetTopItem( descr,
1925 descr->top_item + page * descr->page_size, TRUE );
1926 break;
1927 case SB_THUMBPOSITION:
1928 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1929 break;
1930 case SB_THUMBTRACK:
1931 info.cbSize = sizeof(info);
1932 info.fMask = SIF_TRACKPOS;
1933 GetScrollInfo( descr->self, SB_VERT, &info );
1934 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1935 TRUE );
1936 break;
1937 case SB_LEFT:
1938 LISTBOX_SetTopItem( descr, 0, TRUE );
1939 break;
1940 case SB_RIGHT:
1941 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1942 break;
1945 else if (descr->horz_extent)
1947 switch(scrollReq)
1949 case SB_LINELEFT:
1950 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1951 break;
1952 case SB_LINERIGHT:
1953 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1954 break;
1955 case SB_PAGELEFT:
1956 LISTBOX_SetHorizontalPos( descr,
1957 descr->horz_pos - descr->width );
1958 break;
1959 case SB_PAGERIGHT:
1960 LISTBOX_SetHorizontalPos( descr,
1961 descr->horz_pos + descr->width );
1962 break;
1963 case SB_THUMBPOSITION:
1964 LISTBOX_SetHorizontalPos( descr, pos );
1965 break;
1966 case SB_THUMBTRACK:
1967 info.cbSize = sizeof(info);
1968 info.fMask = SIF_TRACKPOS;
1969 GetScrollInfo( descr->self, SB_HORZ, &info );
1970 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1971 break;
1972 case SB_LEFT:
1973 LISTBOX_SetHorizontalPos( descr, 0 );
1974 break;
1975 case SB_RIGHT:
1976 LISTBOX_SetHorizontalPos( descr,
1977 descr->horz_extent - descr->width );
1978 break;
1981 return 0;
1984 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1986 short gcWheelDelta = 0;
1987 UINT pulScrollLines = 3;
1989 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1991 gcWheelDelta -= delta;
1993 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1995 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1996 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1997 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
1999 return 0;
2002 /***********************************************************************
2003 * LISTBOX_HandleLButtonDown
2005 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2007 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2008 TRACE("[%p]: lbuttondown %d,%d item %d\n", descr->self, x, y, index );
2009 if (!descr->caret_on && (descr->in_focus)) return 0;
2011 if (!descr->in_focus)
2013 if( !descr->lphc ) SetFocus( descr->self );
2014 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2017 if (index == -1) return 0;
2019 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2021 /* we should perhaps make sure that all items are deselected
2022 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2023 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2024 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2027 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2028 if (keys & MK_CONTROL)
2030 LISTBOX_SetCaretIndex( descr, index, FALSE );
2031 LISTBOX_SetSelection( descr, index,
2032 !descr->items[index].selected,
2033 (descr->style & LBS_NOTIFY) != 0);
2035 else
2037 LISTBOX_MoveCaret( descr, index, FALSE );
2039 if (descr->style & LBS_EXTENDEDSEL)
2041 LISTBOX_SetSelection( descr, index,
2042 descr->items[index].selected,
2043 (descr->style & LBS_NOTIFY) != 0 );
2045 else
2047 LISTBOX_SetSelection( descr, index,
2048 !descr->items[index].selected,
2049 (descr->style & LBS_NOTIFY) != 0 );
2053 else
2055 descr->anchor_item = index;
2056 LISTBOX_MoveCaret( descr, index, FALSE );
2057 LISTBOX_SetSelection( descr, index,
2058 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2061 descr->captured = TRUE;
2062 SetCapture( descr->self );
2064 if (!descr->lphc)
2066 if (descr->style & LBS_NOTIFY )
2067 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2068 MAKELPARAM( x, y ) );
2069 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2071 POINT pt;
2073 pt.x = x;
2074 pt.y = y;
2076 if (DragDetect( descr->self, pt ))
2077 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2080 return 0;
2084 /*************************************************************************
2085 * LISTBOX_HandleLButtonDownCombo [Internal]
2087 * Process LButtonDown message for the ComboListBox
2089 * PARAMS
2090 * pWnd [I] The windows internal structure
2091 * pDescr [I] The ListBox internal structure
2092 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2093 * x [I] X Mouse Coordinate
2094 * y [I] Y Mouse Coordinate
2096 * RETURNS
2097 * 0 since we are processing the WM_LBUTTONDOWN Message
2099 * NOTES
2100 * This function is only to be used when a ListBox is a ComboListBox
2103 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2105 RECT clientRect, screenRect;
2106 POINT mousePos;
2108 mousePos.x = x;
2109 mousePos.y = y;
2111 GetClientRect(descr->self, &clientRect);
2113 if(PtInRect(&clientRect, mousePos))
2115 /* MousePos is in client, resume normal processing */
2116 if (msg == WM_LBUTTONDOWN)
2118 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2119 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2121 else if (descr->style & LBS_NOTIFY)
2122 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2124 else
2126 POINT screenMousePos;
2127 HWND hWndOldCapture;
2129 /* Check the Non-Client Area */
2130 screenMousePos = mousePos;
2131 hWndOldCapture = GetCapture();
2132 ReleaseCapture();
2133 GetWindowRect(descr->self, &screenRect);
2134 ClientToScreen(descr->self, &screenMousePos);
2136 if(!PtInRect(&screenRect, screenMousePos))
2138 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2139 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2140 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2142 else
2144 /* Check to see the NC is a scrollbar */
2145 INT nHitTestType=0;
2146 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2147 /* Check Vertical scroll bar */
2148 if (style & WS_VSCROLL)
2150 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2151 if (PtInRect( &clientRect, mousePos ))
2152 nHitTestType = HTVSCROLL;
2154 /* Check horizontal scroll bar */
2155 if (style & WS_HSCROLL)
2157 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2158 if (PtInRect( &clientRect, mousePos ))
2159 nHitTestType = HTHSCROLL;
2161 /* Windows sends this message when a scrollbar is clicked
2164 if(nHitTestType != 0)
2166 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2167 MAKELONG(screenMousePos.x, screenMousePos.y));
2169 /* Resume the Capture after scrolling is complete
2171 if(hWndOldCapture != 0)
2172 SetCapture(hWndOldCapture);
2175 return 0;
2178 /***********************************************************************
2179 * LISTBOX_HandleLButtonUp
2181 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2183 if (LISTBOX_Timer != LB_TIMER_NONE)
2184 KillSystemTimer( descr->self, LB_TIMER_ID );
2185 LISTBOX_Timer = LB_TIMER_NONE;
2186 if (descr->captured)
2188 descr->captured = FALSE;
2189 if (GetCapture() == descr->self) ReleaseCapture();
2190 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2191 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2193 return 0;
2197 /***********************************************************************
2198 * LISTBOX_HandleTimer
2200 * Handle scrolling upon a timer event.
2201 * Return TRUE if scrolling should continue.
2203 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2205 switch(dir)
2207 case LB_TIMER_UP:
2208 if (descr->top_item) index = descr->top_item - 1;
2209 else index = 0;
2210 break;
2211 case LB_TIMER_LEFT:
2212 if (descr->top_item) index -= descr->page_size;
2213 break;
2214 case LB_TIMER_DOWN:
2215 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2216 if (index == descr->focus_item) index++;
2217 if (index >= descr->nb_items) index = descr->nb_items - 1;
2218 break;
2219 case LB_TIMER_RIGHT:
2220 if (index + descr->page_size < descr->nb_items)
2221 index += descr->page_size;
2222 break;
2223 case LB_TIMER_NONE:
2224 break;
2226 if (index == descr->focus_item) return FALSE;
2227 LISTBOX_MoveCaret( descr, index, FALSE );
2228 return TRUE;
2232 /***********************************************************************
2233 * LISTBOX_HandleSystemTimer
2235 * WM_SYSTIMER handler.
2237 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2239 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2241 KillSystemTimer( descr->self, LB_TIMER_ID );
2242 LISTBOX_Timer = LB_TIMER_NONE;
2244 return 0;
2248 /***********************************************************************
2249 * LISTBOX_HandleMouseMove
2251 * WM_MOUSEMOVE handler.
2253 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2254 INT x, INT y )
2256 INT index;
2257 TIMER_DIRECTION dir = LB_TIMER_NONE;
2259 if (!descr->captured) return;
2261 if (descr->style & LBS_MULTICOLUMN)
2263 if (y < 0) y = 0;
2264 else if (y >= descr->item_height * descr->page_size)
2265 y = descr->item_height * descr->page_size - 1;
2267 if (x < 0)
2269 dir = LB_TIMER_LEFT;
2270 x = 0;
2272 else if (x >= descr->width)
2274 dir = LB_TIMER_RIGHT;
2275 x = descr->width - 1;
2278 else
2280 if (y < 0) dir = LB_TIMER_UP; /* above */
2281 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2284 index = LISTBOX_GetItemFromPoint( descr, x, y );
2285 if (index == -1) index = descr->focus_item;
2286 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2288 /* Start/stop the system timer */
2290 if (dir != LB_TIMER_NONE)
2291 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2292 else if (LISTBOX_Timer != LB_TIMER_NONE)
2293 KillSystemTimer( descr->self, LB_TIMER_ID );
2294 LISTBOX_Timer = dir;
2298 /***********************************************************************
2299 * LISTBOX_HandleKeyDown
2301 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2303 INT caret = -1;
2304 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2305 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2306 bForceSelection = FALSE; /* only for single select list */
2308 if (descr->style & LBS_WANTKEYBOARDINPUT)
2310 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2311 MAKEWPARAM(LOWORD(key), descr->focus_item),
2312 (LPARAM)descr->self );
2313 if (caret == -2) return 0;
2315 if (caret == -1) switch(key)
2317 case VK_LEFT:
2318 if (descr->style & LBS_MULTICOLUMN)
2320 bForceSelection = FALSE;
2321 if (descr->focus_item >= descr->page_size)
2322 caret = descr->focus_item - descr->page_size;
2323 break;
2325 /* fall through */
2326 case VK_UP:
2327 caret = descr->focus_item - 1;
2328 if (caret < 0) caret = 0;
2329 break;
2330 case VK_RIGHT:
2331 if (descr->style & LBS_MULTICOLUMN)
2333 bForceSelection = FALSE;
2334 if (descr->focus_item + descr->page_size < descr->nb_items)
2335 caret = descr->focus_item + descr->page_size;
2336 break;
2338 /* fall through */
2339 case VK_DOWN:
2340 caret = descr->focus_item + 1;
2341 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2342 break;
2344 case VK_PRIOR:
2345 if (descr->style & LBS_MULTICOLUMN)
2347 INT page = descr->width / descr->column_width;
2348 if (page < 1) page = 1;
2349 caret = descr->focus_item - (page * descr->page_size) + 1;
2351 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2352 if (caret < 0) caret = 0;
2353 break;
2354 case VK_NEXT:
2355 if (descr->style & LBS_MULTICOLUMN)
2357 INT page = descr->width / descr->column_width;
2358 if (page < 1) page = 1;
2359 caret = descr->focus_item + (page * descr->page_size) - 1;
2361 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2362 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2363 break;
2364 case VK_HOME:
2365 caret = 0;
2366 break;
2367 case VK_END:
2368 caret = descr->nb_items - 1;
2369 break;
2370 case VK_SPACE:
2371 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2372 else if (descr->style & LBS_MULTIPLESEL)
2374 LISTBOX_SetSelection( descr, descr->focus_item,
2375 !descr->items[descr->focus_item].selected,
2376 (descr->style & LBS_NOTIFY) != 0 );
2378 break;
2379 default:
2380 bForceSelection = FALSE;
2382 if (bForceSelection) /* focused item is used instead of key */
2383 caret = descr->focus_item;
2384 if (caret >= 0)
2386 if (((descr->style & LBS_EXTENDEDSEL) &&
2387 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2388 !IS_MULTISELECT(descr))
2389 descr->anchor_item = caret;
2390 LISTBOX_MoveCaret( descr, caret, TRUE );
2392 if (descr->style & LBS_MULTIPLESEL)
2393 descr->selected_item = caret;
2394 else
2395 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2396 if (descr->style & LBS_NOTIFY)
2398 if( descr->lphc )
2400 /* make sure that combo parent doesn't hide us */
2401 descr->lphc->wState |= CBF_NOROLLUP;
2403 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2406 return 0;
2410 /***********************************************************************
2411 * LISTBOX_HandleChar
2413 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2415 INT caret = -1;
2416 WCHAR str[2];
2418 str[0] = charW;
2419 str[1] = '\0';
2421 if (descr->style & LBS_WANTKEYBOARDINPUT)
2423 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2424 MAKEWPARAM(charW, descr->focus_item),
2425 (LPARAM)descr->self );
2426 if (caret == -2) return 0;
2428 if (caret == -1)
2429 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2430 if (caret != -1)
2432 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2433 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2434 LISTBOX_MoveCaret( descr, caret, TRUE );
2435 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2436 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2438 return 0;
2442 /***********************************************************************
2443 * LISTBOX_Create
2445 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2447 LB_DESCR *descr;
2448 MEASUREITEMSTRUCT mis;
2449 RECT rect;
2451 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2452 return FALSE;
2454 GetClientRect( hwnd, &rect );
2455 descr->self = hwnd;
2456 descr->owner = GetParent( descr->self );
2457 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2458 descr->width = rect.right - rect.left;
2459 descr->height = rect.bottom - rect.top;
2460 descr->items = NULL;
2461 descr->nb_items = 0;
2462 descr->top_item = 0;
2463 descr->selected_item = -1;
2464 descr->focus_item = 0;
2465 descr->anchor_item = -1;
2466 descr->item_height = 1;
2467 descr->page_size = 1;
2468 descr->column_width = 150;
2469 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2470 descr->horz_pos = 0;
2471 descr->nb_tabs = 0;
2472 descr->tabs = NULL;
2473 descr->caret_on = lphc ? FALSE : TRUE;
2474 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2475 descr->in_focus = FALSE;
2476 descr->captured = FALSE;
2477 descr->font = 0;
2478 descr->locale = GetUserDefaultLCID();
2479 descr->lphc = lphc;
2481 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2483 /* Win95 document "List Box Differences" from MSDN:
2484 If a list box in a version 3.x application has either the
2485 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2486 horizontal and vertical scroll bars.
2488 descr->style |= WS_VSCROLL | WS_HSCROLL;
2491 if( lphc )
2493 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2494 descr->owner = lphc->self;
2497 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2499 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2501 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2502 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2503 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2504 descr->item_height = LISTBOX_SetFont( descr, 0 );
2506 if (descr->style & LBS_OWNERDRAWFIXED)
2508 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2510 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2511 descr->item_height = lphc->fixedOwnerDrawHeight;
2513 else
2515 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2516 mis.CtlType = ODT_LISTBOX;
2517 mis.CtlID = id;
2518 mis.itemID = -1;
2519 mis.itemWidth = 0;
2520 mis.itemData = 0;
2521 mis.itemHeight = descr->item_height;
2522 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2523 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2527 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2528 return TRUE;
2532 /***********************************************************************
2533 * LISTBOX_Destroy
2535 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2537 LISTBOX_ResetContent( descr );
2538 SetWindowLongPtrW( descr->self, 0, 0 );
2539 HeapFree( GetProcessHeap(), 0, descr );
2540 return TRUE;
2544 /***********************************************************************
2545 * ListBoxWndProc_common
2547 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2548 WPARAM wParam, LPARAM lParam, BOOL unicode )
2550 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2551 LPHEADCOMBO lphc = 0;
2552 LRESULT ret;
2554 if (!descr)
2556 if (!IsWindow(hwnd)) return 0;
2558 if (msg == WM_CREATE)
2560 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2561 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2562 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2563 TRACE("creating wnd=%p descr=%x\n", hwnd, GetWindowLongPtrW( hwnd, 0 ) );
2564 return 0;
2566 /* Ignore all other messages before we get a WM_CREATE */
2567 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2568 DefWindowProcA( hwnd, msg, wParam, lParam );
2570 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2572 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2573 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2575 switch(msg)
2577 case LB_RESETCONTENT16:
2578 case LB_RESETCONTENT:
2579 LISTBOX_ResetContent( descr );
2580 LISTBOX_UpdateScroll( descr );
2581 InvalidateRect( descr->self, NULL, TRUE );
2582 return 0;
2584 case LB_ADDSTRING16:
2585 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2586 /* fall through */
2587 case LB_ADDSTRING:
2589 INT ret;
2590 LPWSTR textW;
2591 if(unicode || !HAS_STRINGS(descr))
2592 textW = (LPWSTR)lParam;
2593 else
2595 LPSTR textA = (LPSTR)lParam;
2596 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2597 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2598 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2600 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2601 ret = LISTBOX_InsertString( descr, wParam, textW );
2602 if (!unicode && HAS_STRINGS(descr))
2603 HeapFree(GetProcessHeap(), 0, textW);
2604 return ret;
2607 case LB_INSERTSTRING16:
2608 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2609 wParam = (INT)(INT16)wParam;
2610 /* fall through */
2611 case LB_INSERTSTRING:
2613 INT ret;
2614 LPWSTR textW;
2615 if(unicode || !HAS_STRINGS(descr))
2616 textW = (LPWSTR)lParam;
2617 else
2619 LPSTR textA = (LPSTR)lParam;
2620 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2621 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2622 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2624 ret = LISTBOX_InsertString( descr, wParam, textW );
2625 if(!unicode && HAS_STRINGS(descr))
2626 HeapFree(GetProcessHeap(), 0, textW);
2627 return ret;
2630 case LB_ADDFILE16:
2631 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2632 /* fall through */
2633 case LB_ADDFILE:
2635 INT ret;
2636 LPWSTR textW;
2637 if(unicode || !HAS_STRINGS(descr))
2638 textW = (LPWSTR)lParam;
2639 else
2641 LPSTR textA = (LPSTR)lParam;
2642 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2643 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2644 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2646 wParam = LISTBOX_FindFileStrPos( descr, textW );
2647 ret = LISTBOX_InsertString( descr, wParam, textW );
2648 if(!unicode && HAS_STRINGS(descr))
2649 HeapFree(GetProcessHeap(), 0, textW);
2650 return ret;
2653 case LB_DELETESTRING16:
2654 case LB_DELETESTRING:
2655 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2656 return descr->nb_items;
2657 else
2659 SetLastError(ERROR_INVALID_INDEX);
2660 return LB_ERR;
2663 case LB_GETITEMDATA16:
2664 case LB_GETITEMDATA:
2665 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2667 SetLastError(ERROR_INVALID_INDEX);
2668 return LB_ERR;
2670 return descr->items[wParam].data;
2672 case LB_SETITEMDATA16:
2673 case LB_SETITEMDATA:
2674 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2676 SetLastError(ERROR_INVALID_INDEX);
2677 return LB_ERR;
2679 descr->items[wParam].data = lParam;
2680 /* undocumented: returns TRUE, not LB_OKAY (0) */
2681 return TRUE;
2683 case LB_GETCOUNT16:
2684 case LB_GETCOUNT:
2685 return descr->nb_items;
2687 case LB_GETTEXT16:
2688 lParam = (LPARAM)MapSL(lParam);
2689 /* fall through */
2690 case LB_GETTEXT:
2691 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2693 case LB_GETTEXTLEN16:
2694 /* fall through */
2695 case LB_GETTEXTLEN:
2696 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2698 SetLastError(ERROR_INVALID_INDEX);
2699 return LB_ERR;
2701 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2702 if (unicode) return strlenW( descr->items[wParam].str );
2703 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2704 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2706 case LB_GETCURSEL16:
2707 case LB_GETCURSEL:
2708 if (descr->nb_items == 0)
2709 return LB_ERR;
2710 if (!IS_MULTISELECT(descr))
2711 return descr->selected_item;
2712 if (descr->selected_item != -1)
2713 return descr->selected_item;
2714 return descr->focus_item;
2715 /* otherwise, if the user tries to move the selection with the */
2716 /* arrow keys, we will give the application something to choke on */
2717 case LB_GETTOPINDEX16:
2718 case LB_GETTOPINDEX:
2719 return descr->top_item;
2721 case LB_GETITEMHEIGHT16:
2722 case LB_GETITEMHEIGHT:
2723 return LISTBOX_GetItemHeight( descr, wParam );
2725 case LB_SETITEMHEIGHT16:
2726 lParam = LOWORD(lParam);
2727 /* fall through */
2728 case LB_SETITEMHEIGHT:
2729 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2731 case LB_ITEMFROMPOINT:
2733 POINT pt;
2734 RECT rect;
2735 int index;
2736 BOOL hit = TRUE;
2738 /* The hiword of the return value is not a client area
2739 hittest as suggested by MSDN, but rather a hittest on
2740 the returned listbox item. */
2742 if(descr->nb_items == 0)
2743 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2745 pt.x = (short)LOWORD(lParam);
2746 pt.y = (short)HIWORD(lParam);
2748 SetRect(&rect, 0, 0, descr->width, descr->height);
2750 if(!PtInRect(&rect, pt))
2752 pt.x = min(pt.x, rect.right - 1);
2753 pt.x = max(pt.x, 0);
2754 pt.y = min(pt.y, rect.bottom - 1);
2755 pt.y = max(pt.y, 0);
2756 hit = FALSE;
2759 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2761 if(index == -1)
2763 index = descr->nb_items - 1;
2764 hit = FALSE;
2766 return MAKELONG(index, hit ? 0 : 1);
2769 case LB_SETCARETINDEX16:
2770 case LB_SETCARETINDEX:
2771 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2772 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2773 return LB_ERR;
2774 else if (ISWIN31)
2775 return wParam;
2776 else
2777 return LB_OKAY;
2779 case LB_GETCARETINDEX16:
2780 case LB_GETCARETINDEX:
2781 return descr->focus_item;
2783 case LB_SETTOPINDEX16:
2784 case LB_SETTOPINDEX:
2785 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2787 case LB_SETCOLUMNWIDTH16:
2788 case LB_SETCOLUMNWIDTH:
2789 return LISTBOX_SetColumnWidth( descr, wParam );
2791 case LB_GETITEMRECT16:
2793 RECT rect;
2794 RECT16 *r16 = MapSL(lParam);
2795 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2796 r16->left = rect.left;
2797 r16->top = rect.top;
2798 r16->right = rect.right;
2799 r16->bottom = rect.bottom;
2801 return ret;
2803 case LB_GETITEMRECT:
2804 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2806 case LB_FINDSTRING16:
2807 wParam = (INT)(INT16)wParam;
2808 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2809 /* fall through */
2810 case LB_FINDSTRING:
2812 INT ret;
2813 LPWSTR textW;
2814 if(unicode || !HAS_STRINGS(descr))
2815 textW = (LPWSTR)lParam;
2816 else
2818 LPSTR textA = (LPSTR)lParam;
2819 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2820 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2821 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2823 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2824 if(!unicode && HAS_STRINGS(descr))
2825 HeapFree(GetProcessHeap(), 0, textW);
2826 return ret;
2829 case LB_FINDSTRINGEXACT16:
2830 wParam = (INT)(INT16)wParam;
2831 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2832 /* fall through */
2833 case LB_FINDSTRINGEXACT:
2835 INT ret;
2836 LPWSTR textW;
2837 if(unicode || !HAS_STRINGS(descr))
2838 textW = (LPWSTR)lParam;
2839 else
2841 LPSTR textA = (LPSTR)lParam;
2842 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2843 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2844 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2846 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2847 if(!unicode && HAS_STRINGS(descr))
2848 HeapFree(GetProcessHeap(), 0, textW);
2849 return ret;
2852 case LB_SELECTSTRING16:
2853 wParam = (INT)(INT16)wParam;
2854 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2855 /* fall through */
2856 case LB_SELECTSTRING:
2858 INT index;
2859 LPWSTR textW;
2861 if(HAS_STRINGS(descr))
2862 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2863 debugstr_a((LPSTR)lParam));
2864 if(unicode || !HAS_STRINGS(descr))
2865 textW = (LPWSTR)lParam;
2866 else
2868 LPSTR textA = (LPSTR)lParam;
2869 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2870 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2871 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2873 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2874 if(!unicode && HAS_STRINGS(descr))
2875 HeapFree(GetProcessHeap(), 0, textW);
2876 if (index != LB_ERR)
2878 LISTBOX_MoveCaret( descr, index, TRUE );
2879 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2881 return index;
2884 case LB_GETSEL16:
2885 wParam = (INT)(INT16)wParam;
2886 /* fall through */
2887 case LB_GETSEL:
2888 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2889 return LB_ERR;
2890 return descr->items[wParam].selected;
2892 case LB_SETSEL16:
2893 lParam = (INT)(INT16)lParam;
2894 /* fall through */
2895 case LB_SETSEL:
2896 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2898 case LB_SETCURSEL16:
2899 wParam = (INT)(INT16)wParam;
2900 /* fall through */
2901 case LB_SETCURSEL:
2902 if (IS_MULTISELECT(descr)) return LB_ERR;
2903 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2904 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2905 if (lphc && ret != LB_ERR) ret = descr->selected_item;
2906 return ret;
2908 case LB_GETSELCOUNT16:
2909 case LB_GETSELCOUNT:
2910 return LISTBOX_GetSelCount( descr );
2912 case LB_GETSELITEMS16:
2913 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2915 case LB_GETSELITEMS:
2916 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2918 case LB_SELITEMRANGE16:
2919 case LB_SELITEMRANGE:
2920 if (LOWORD(lParam) <= HIWORD(lParam))
2921 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2922 HIWORD(lParam), wParam );
2923 else
2924 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2925 LOWORD(lParam), wParam );
2927 case LB_SELITEMRANGEEX16:
2928 case LB_SELITEMRANGEEX:
2929 if ((INT)lParam >= (INT)wParam)
2930 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2931 else
2932 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2934 case LB_GETHORIZONTALEXTENT16:
2935 case LB_GETHORIZONTALEXTENT:
2936 return descr->horz_extent;
2938 case LB_SETHORIZONTALEXTENT16:
2939 case LB_SETHORIZONTALEXTENT:
2940 return LISTBOX_SetHorizontalExtent( descr, wParam );
2942 case LB_GETANCHORINDEX16:
2943 case LB_GETANCHORINDEX:
2944 return descr->anchor_item;
2946 case LB_SETANCHORINDEX16:
2947 wParam = (INT)(INT16)wParam;
2948 /* fall through */
2949 case LB_SETANCHORINDEX:
2950 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2952 SetLastError(ERROR_INVALID_INDEX);
2953 return LB_ERR;
2955 descr->anchor_item = (INT)wParam;
2956 return LB_OKAY;
2958 case LB_DIR16:
2959 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2960 * be set automatically (this is different in Win32) */
2961 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2962 lParam = (LPARAM)MapSL(lParam);
2963 /* fall through */
2964 case LB_DIR:
2966 INT ret;
2967 LPWSTR textW;
2968 if(unicode)
2969 textW = (LPWSTR)lParam;
2970 else
2972 LPSTR textA = (LPSTR)lParam;
2973 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2974 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2975 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2977 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2978 if(!unicode)
2979 HeapFree(GetProcessHeap(), 0, textW);
2980 return ret;
2983 case LB_GETLOCALE:
2984 return descr->locale;
2986 case LB_SETLOCALE:
2988 LCID ret;
2989 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2990 return LB_ERR;
2991 ret = descr->locale;
2992 descr->locale = (LCID)wParam;
2993 return ret;
2996 case LB_INITSTORAGE:
2997 return LISTBOX_InitStorage( descr, wParam );
2999 case LB_SETCOUNT:
3000 return LISTBOX_SetCount( descr, (INT)wParam );
3002 case LB_SETTABSTOPS16:
3003 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
3005 case LB_SETTABSTOPS:
3006 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
3008 case LB_CARETON16:
3009 case LB_CARETON:
3010 if (descr->caret_on)
3011 return LB_OKAY;
3012 descr->caret_on = TRUE;
3013 if ((descr->focus_item != -1) && (descr->in_focus))
3014 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3015 return LB_OKAY;
3017 case LB_CARETOFF16:
3018 case LB_CARETOFF:
3019 if (!descr->caret_on)
3020 return LB_OKAY;
3021 descr->caret_on = FALSE;
3022 if ((descr->focus_item != -1) && (descr->in_focus))
3023 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3024 return LB_OKAY;
3026 case LB_GETLISTBOXINFO:
3027 FIXME("LB_GETLISTBOXINFO: stub!\n");
3028 return 0;
3030 case WM_DESTROY:
3031 return LISTBOX_Destroy( descr );
3033 case WM_ENABLE:
3034 InvalidateRect( descr->self, NULL, TRUE );
3035 return 0;
3037 case WM_SETREDRAW:
3038 LISTBOX_SetRedraw( descr, wParam != 0 );
3039 return 0;
3041 case WM_GETDLGCODE:
3042 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3044 case WM_PRINTCLIENT:
3045 case WM_PAINT:
3047 PAINTSTRUCT ps;
3048 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3049 ret = LISTBOX_Paint( descr, hdc );
3050 if( !wParam ) EndPaint( descr->self, &ps );
3052 return ret;
3053 case WM_SIZE:
3054 LISTBOX_UpdateSize( descr );
3055 return 0;
3056 case WM_GETFONT:
3057 return (LRESULT)descr->font;
3058 case WM_SETFONT:
3059 LISTBOX_SetFont( descr, (HFONT)wParam );
3060 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3061 return 0;
3062 case WM_SETFOCUS:
3063 descr->in_focus = TRUE;
3064 descr->caret_on = TRUE;
3065 if (descr->focus_item != -1)
3066 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3067 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3068 return 0;
3069 case WM_KILLFOCUS:
3070 descr->in_focus = FALSE;
3071 if ((descr->focus_item != -1) && descr->caret_on)
3072 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3073 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3074 return 0;
3075 case WM_HSCROLL:
3076 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3077 case WM_VSCROLL:
3078 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3079 case WM_MOUSEWHEEL:
3080 if (wParam & (MK_SHIFT | MK_CONTROL))
3081 return DefWindowProcW( descr->self, msg, wParam, lParam );
3082 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3083 case WM_LBUTTONDOWN:
3084 if (lphc)
3085 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3086 (INT16)LOWORD(lParam),
3087 (INT16)HIWORD(lParam) );
3088 return LISTBOX_HandleLButtonDown( descr, wParam,
3089 (INT16)LOWORD(lParam),
3090 (INT16)HIWORD(lParam) );
3091 case WM_LBUTTONDBLCLK:
3092 if (lphc)
3093 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3094 (INT16)LOWORD(lParam),
3095 (INT16)HIWORD(lParam) );
3096 if (descr->style & LBS_NOTIFY)
3097 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3098 return 0;
3099 case WM_MOUSEMOVE:
3100 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3102 BOOL captured = descr->captured;
3103 POINT mousePos;
3104 RECT clientRect;
3106 mousePos.x = (INT16)LOWORD(lParam);
3107 mousePos.y = (INT16)HIWORD(lParam);
3110 * If we are in a dropdown combobox, we simulate that
3111 * the mouse is captured to show the tracking of the item.
3113 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3114 descr->captured = TRUE;
3116 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3118 descr->captured = captured;
3120 else if (GetCapture() == descr->self)
3122 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3123 (INT16)HIWORD(lParam) );
3125 return 0;
3126 case WM_LBUTTONUP:
3127 if (lphc)
3129 POINT mousePos;
3130 RECT clientRect;
3133 * If the mouse button "up" is not in the listbox,
3134 * we make sure there is no selection by re-selecting the
3135 * item that was selected when the listbox was made visible.
3137 mousePos.x = (INT16)LOWORD(lParam);
3138 mousePos.y = (INT16)HIWORD(lParam);
3140 GetClientRect(descr->self, &clientRect);
3143 * When the user clicks outside the combobox and the focus
3144 * is lost, the owning combobox will send a fake buttonup with
3145 * 0xFFFFFFF as the mouse location, we must also revert the
3146 * selection to the original selection.
3148 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3149 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3151 return LISTBOX_HandleLButtonUp( descr );
3152 case WM_KEYDOWN:
3153 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3155 /* for some reason Windows makes it possible to
3156 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3158 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3159 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3160 && (wParam == VK_DOWN || wParam == VK_UP)) )
3162 COMBO_FlipListbox( lphc, FALSE, FALSE );
3163 return 0;
3166 return LISTBOX_HandleKeyDown( descr, wParam );
3167 case WM_CHAR:
3169 WCHAR charW;
3170 if(unicode)
3171 charW = (WCHAR)wParam;
3172 else
3174 CHAR charA = (CHAR)wParam;
3175 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3177 return LISTBOX_HandleChar( descr, charW );
3179 case WM_SYSTIMER:
3180 return LISTBOX_HandleSystemTimer( descr );
3181 case WM_ERASEBKGND:
3182 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3184 RECT rect;
3185 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3186 wParam, (LPARAM)descr->self );
3187 TRACE("hbrush = %p\n", hbrush);
3188 if(!hbrush)
3189 hbrush = GetSysColorBrush(COLOR_WINDOW);
3190 if(hbrush)
3192 GetClientRect(descr->self, &rect);
3193 FillRect((HDC)wParam, &rect, hbrush);
3196 return 1;
3197 case WM_DROPFILES:
3198 if( lphc ) return 0;
3199 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3200 SendMessageA( descr->owner, msg, wParam, lParam );
3202 case WM_NCDESTROY:
3203 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3204 lphc->hWndLBox = 0;
3205 break;
3207 case WM_NCACTIVATE:
3208 if (lphc) return 0;
3209 break;
3211 default:
3212 if ((msg >= WM_USER) && (msg < 0xc000))
3213 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3214 hwnd, msg, wParam, lParam );
3217 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3218 DefWindowProcA( hwnd, msg, wParam, lParam );
3221 /***********************************************************************
3222 * ListBoxWndProcA
3224 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3226 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3229 /***********************************************************************
3230 * ListBoxWndProcW
3232 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3234 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );