Add some missing USER definitions, add a couple of FIXMEs for not
[wine/wine-kai.git] / dlls / user / listbox.c
blob167effd9d21b259bdc0625102352bfa8fe3d3f89
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/winbase16.h"
44 #include "wine/unicode.h"
45 #include "user_private.h"
46 #include "controls.h"
47 #include "wine/debug.h"
49 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
51 /* Items array granularity */
52 #define LB_ARRAY_GRANULARITY 16
54 /* Scrolling timeout in ms */
55 #define LB_SCROLL_TIMEOUT 50
57 /* Listbox system timer id */
58 #define LB_TIMER_ID 2
60 /* flag listbox changed while setredraw false - internal style */
61 #define LBS_DISPLAYCHANGED 0x80000000
63 /* Item structure */
64 typedef struct
66 LPWSTR str; /* Item text */
67 BOOL selected; /* Is item selected? */
68 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
69 ULONG_PTR data; /* User data */
70 } LB_ITEMDATA;
72 /* Listbox structure */
73 typedef struct
75 HWND self; /* Our own window handle */
76 HWND owner; /* Owner window to send notifications to */
77 UINT style; /* Window style */
78 INT width; /* Window width */
79 INT height; /* Window height */
80 LB_ITEMDATA *items; /* Array of items */
81 INT nb_items; /* Number of items */
82 INT top_item; /* Top visible item */
83 INT selected_item; /* Selected item */
84 INT focus_item; /* Item that has the focus */
85 INT anchor_item; /* Anchor item for extended selection */
86 INT item_height; /* Default item height */
87 INT page_size; /* Items per listbox page */
88 INT column_width; /* Column width for multi-column listboxes */
89 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
90 INT horz_pos; /* Horizontal position */
91 INT nb_tabs; /* Number of tabs in array */
92 INT *tabs; /* Array of tabs */
93 INT avg_char_width; /* Average width of characters */
94 BOOL caret_on; /* Is caret on? */
95 BOOL captured; /* Is mouse captured? */
96 BOOL in_focus;
97 HFONT font; /* Current font */
98 LCID locale; /* Current locale for string comparisons */
99 LPHEADCOMBO lphc; /* ComboLBox */
100 } LB_DESCR;
103 #define IS_OWNERDRAW(descr) \
104 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
106 #define HAS_STRINGS(descr) \
107 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
110 #define IS_MULTISELECT(descr) \
111 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
112 !((descr)->style & LBS_NOSEL))
114 #define SEND_NOTIFICATION(descr,code) \
115 (SendMessageW( (descr)->owner, WM_COMMAND, \
116 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
118 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
120 /* Current timer status */
121 typedef enum
123 LB_TIMER_NONE,
124 LB_TIMER_UP,
125 LB_TIMER_LEFT,
126 LB_TIMER_DOWN,
127 LB_TIMER_RIGHT
128 } TIMER_DIRECTION;
130 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
132 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
133 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
135 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
137 /*********************************************************************
138 * listbox class descriptor
140 const struct builtin_class_descr LISTBOX_builtin_class =
142 "ListBox", /* name */
143 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
144 ListBoxWndProcA, /* procA */
145 ListBoxWndProcW, /* procW */
146 sizeof(LB_DESCR *), /* extra */
147 IDC_ARROW, /* cursor */
148 0 /* brush */
152 /*********************************************************************
153 * combolbox class descriptor
155 const struct builtin_class_descr COMBOLBOX_builtin_class =
157 "ComboLBox", /* name */
158 CS_DBLCLKS | CS_SAVEBITS, /* style */
159 ListBoxWndProcA, /* procA */
160 ListBoxWndProcW, /* procW */
161 sizeof(LB_DESCR *), /* extra */
162 IDC_ARROW, /* cursor */
163 0 /* brush */
167 /* check whether app is a Win 3.1 app */
168 inline static BOOL is_old_app( LB_DESCR *descr )
170 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
174 /***********************************************************************
175 * LISTBOX_GetCurrentPageSize
177 * Return the current page size
179 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
181 INT i, height;
182 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
183 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
185 if ((height += descr->items[i].height) > descr->height) break;
187 if (i == descr->top_item) return 1;
188 else return i - descr->top_item;
192 /***********************************************************************
193 * LISTBOX_GetMaxTopIndex
195 * Return the maximum possible index for the top of the listbox.
197 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
199 INT max, page;
201 if (descr->style & LBS_OWNERDRAWVARIABLE)
203 page = descr->height;
204 for (max = descr->nb_items - 1; max >= 0; max--)
205 if ((page -= descr->items[max].height) < 0) break;
206 if (max < descr->nb_items - 1) max++;
208 else if (descr->style & LBS_MULTICOLUMN)
210 if ((page = descr->width / descr->column_width) < 1) page = 1;
211 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
212 max = (max - page) * descr->page_size;
214 else
216 max = descr->nb_items - descr->page_size;
218 if (max < 0) max = 0;
219 return max;
223 /***********************************************************************
224 * LISTBOX_UpdateScroll
226 * Update the scrollbars. Should be called whenever the content
227 * of the listbox changes.
229 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
231 SCROLLINFO info;
233 /* Check the listbox scroll bar flags individually before we call
234 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
235 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
236 scroll bar when we do not need one.
237 if (!(descr->style & WS_VSCROLL)) return;
240 /* It is important that we check descr->style, and not wnd->dwStyle,
241 for WS_VSCROLL, as the former is exactly the one passed in
242 argument to CreateWindow.
243 In Windows (and from now on in Wine :) a listbox created
244 with such a style (no WS_SCROLL) does not update
245 the scrollbar with listbox-related data, thus letting
246 the programmer use it for his/her own purposes. */
248 if (descr->style & LBS_NOREDRAW) return;
249 info.cbSize = sizeof(info);
251 if (descr->style & LBS_MULTICOLUMN)
253 info.nMin = 0;
254 info.nMax = (descr->nb_items - 1) / descr->page_size;
255 info.nPos = descr->top_item / descr->page_size;
256 info.nPage = descr->width / descr->column_width;
257 if (info.nPage < 1) info.nPage = 1;
258 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
259 if (descr->style & LBS_DISABLENOSCROLL)
260 info.fMask |= SIF_DISABLENOSCROLL;
261 if (descr->style & WS_HSCROLL)
262 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
263 info.nMax = 0;
264 info.fMask = SIF_RANGE;
265 if (descr->style & WS_VSCROLL)
266 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
268 else
270 info.nMin = 0;
271 info.nMax = descr->nb_items - 1;
272 info.nPos = descr->top_item;
273 info.nPage = LISTBOX_GetCurrentPageSize( descr );
274 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
275 if (descr->style & LBS_DISABLENOSCROLL)
276 info.fMask |= SIF_DISABLENOSCROLL;
277 if (descr->style & WS_VSCROLL)
278 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
280 if (descr->horz_extent)
282 info.nMin = 0;
283 info.nMax = descr->horz_extent - 1;
284 info.nPos = descr->horz_pos;
285 info.nPage = descr->width;
286 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
287 if (descr->style & LBS_DISABLENOSCROLL)
288 info.fMask |= SIF_DISABLENOSCROLL;
289 if (descr->style & WS_HSCROLL)
290 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
296 /***********************************************************************
297 * LISTBOX_SetTopItem
299 * Set the top item of the listbox, scrolling up or down if necessary.
301 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
303 INT max = LISTBOX_GetMaxTopIndex( descr );
304 if (index > max) index = max;
305 if (index < 0) index = 0;
306 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
307 if (descr->top_item == index) return LB_OKAY;
308 if (descr->style & LBS_MULTICOLUMN)
310 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
311 if (scroll && (abs(diff) < descr->width))
312 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
313 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
315 else
316 scroll = FALSE;
318 else if (scroll)
320 INT diff;
321 if (descr->style & LBS_OWNERDRAWVARIABLE)
323 INT i;
324 diff = 0;
325 if (index > descr->top_item)
327 for (i = index - 1; i >= descr->top_item; i--)
328 diff -= descr->items[i].height;
330 else
332 for (i = index; i < descr->top_item; i++)
333 diff += descr->items[i].height;
336 else
337 diff = (descr->top_item - index) * descr->item_height;
339 if (abs(diff) < descr->height)
340 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
341 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
342 else
343 scroll = FALSE;
345 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
346 descr->top_item = index;
347 LISTBOX_UpdateScroll( descr );
348 return LB_OKAY;
352 /***********************************************************************
353 * LISTBOX_UpdatePage
355 * Update the page size. Should be called when the size of
356 * the client area or the item height changes.
358 static void LISTBOX_UpdatePage( LB_DESCR *descr )
360 INT page_size;
362 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
363 page_size = 1;
364 if (page_size == descr->page_size) return;
365 descr->page_size = page_size;
366 if (descr->style & LBS_MULTICOLUMN)
367 InvalidateRect( descr->self, NULL, TRUE );
368 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
372 /***********************************************************************
373 * LISTBOX_UpdateSize
375 * Update the size of the listbox. Should be called when the size of
376 * the client area changes.
378 static void LISTBOX_UpdateSize( LB_DESCR *descr )
380 RECT rect;
382 GetClientRect( descr->self, &rect );
383 descr->width = rect.right - rect.left;
384 descr->height = rect.bottom - rect.top;
385 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
387 INT remaining;
388 RECT rect;
390 GetWindowRect( descr->self, &rect );
391 if(descr->item_height != 0)
392 remaining = descr->height % descr->item_height;
393 else
394 remaining = 0;
395 if ((descr->height > descr->item_height) && remaining)
397 if (is_old_app(descr))
398 { /* give a margin for error to 16 bits programs - if we need
399 less than the height of the nonclient area, round to the
400 *next* number of items */
401 int ncheight = rect.bottom - rect.top - descr->height;
402 if ((descr->item_height - remaining) <= ncheight)
403 remaining = remaining - descr->item_height;
405 TRACE("[%p]: changing height %d -> %d\n",
406 descr->self, descr->height, descr->height - remaining );
407 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
408 rect.bottom - rect.top - remaining,
409 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
410 return;
413 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
414 LISTBOX_UpdatePage( descr );
415 LISTBOX_UpdateScroll( descr );
417 /* Invalidate the focused item so it will be repainted correctly */
418 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
420 InvalidateRect( descr->self, &rect, FALSE );
425 /***********************************************************************
426 * LISTBOX_GetItemRect
428 * Get the rectangle enclosing an item, in listbox client coordinates.
429 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
431 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
433 /* Index <= 0 is legal even on empty listboxes */
434 if (index && (index >= descr->nb_items))
436 memset(rect, 0, sizeof(*rect));
437 SetLastError(ERROR_INVALID_INDEX);
438 return LB_ERR;
440 SetRect( rect, 0, 0, descr->width, descr->height );
441 if (descr->style & LBS_MULTICOLUMN)
443 INT col = (index / descr->page_size) -
444 (descr->top_item / descr->page_size);
445 rect->left += col * descr->column_width;
446 rect->right = rect->left + descr->column_width;
447 rect->top += (index % descr->page_size) * descr->item_height;
448 rect->bottom = rect->top + descr->item_height;
450 else if (descr->style & LBS_OWNERDRAWVARIABLE)
452 INT i;
453 rect->right += descr->horz_pos;
454 if ((index >= 0) && (index < descr->nb_items))
456 if (index < descr->top_item)
458 for (i = descr->top_item-1; i >= index; i--)
459 rect->top -= descr->items[i].height;
461 else
463 for (i = descr->top_item; i < index; i++)
464 rect->top += descr->items[i].height;
466 rect->bottom = rect->top + descr->items[index].height;
470 else
472 rect->top += (index - descr->top_item) * descr->item_height;
473 rect->bottom = rect->top + descr->item_height;
474 rect->right += descr->horz_pos;
477 return ((rect->left < descr->width) && (rect->right > 0) &&
478 (rect->top < descr->height) && (rect->bottom > 0));
482 /***********************************************************************
483 * LISTBOX_GetItemFromPoint
485 * Return the item nearest from point (x,y) (in client coordinates).
487 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
489 INT index = descr->top_item;
491 if (!descr->nb_items) return -1; /* No items */
492 if (descr->style & LBS_OWNERDRAWVARIABLE)
494 INT pos = 0;
495 if (y >= 0)
497 while (index < descr->nb_items)
499 if ((pos += descr->items[index].height) > y) break;
500 index++;
503 else
505 while (index > 0)
507 index--;
508 if ((pos -= descr->items[index].height) <= y) break;
512 else if (descr->style & LBS_MULTICOLUMN)
514 if (y >= descr->item_height * descr->page_size) return -1;
515 if (y >= 0) index += y / descr->item_height;
516 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
517 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
519 else
521 index += (y / descr->item_height);
523 if (index < 0) return 0;
524 if (index >= descr->nb_items) return -1;
525 return index;
529 /***********************************************************************
530 * LISTBOX_PaintItem
532 * Paint an item.
534 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
535 INT index, UINT action, BOOL ignoreFocus )
537 LB_ITEMDATA *item = NULL;
538 if (index < descr->nb_items) item = &descr->items[index];
540 if (IS_OWNERDRAW(descr))
542 DRAWITEMSTRUCT dis;
543 RECT r;
544 HRGN hrgn;
546 if (!item)
548 if (action == ODA_FOCUS)
549 DrawFocusRect( hdc, rect );
550 else
551 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
552 return;
555 /* some programs mess with the clipping region when
556 drawing the item, *and* restore the previous region
557 after they are done, so a region has better to exist
558 else everything ends clipped */
559 GetClientRect(descr->self, &r);
560 hrgn = CreateRectRgnIndirect(&r);
561 SelectClipRgn( hdc, hrgn);
562 DeleteObject( hrgn );
564 dis.CtlType = ODT_LISTBOX;
565 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
566 dis.hwndItem = descr->self;
567 dis.itemAction = action;
568 dis.hDC = hdc;
569 dis.itemID = index;
570 dis.itemState = 0;
571 if (item && item->selected) dis.itemState |= ODS_SELECTED;
572 if (!ignoreFocus && (descr->focus_item == index) &&
573 (descr->caret_on) &&
574 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
575 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
576 dis.itemData = item ? item->data : 0;
577 dis.rcItem = *rect;
578 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
579 descr->self, index, item ? debugstr_w(item->str) : "", action,
580 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
581 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
583 else
585 COLORREF oldText = 0, oldBk = 0;
587 if (action == ODA_FOCUS)
589 DrawFocusRect( hdc, rect );
590 return;
592 if (item && item->selected)
594 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
595 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
598 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
599 descr->self, index, item ? debugstr_w(item->str) : "", action,
600 rect->left, rect->top, rect->right, rect->bottom );
601 if (!item)
602 ExtTextOutW( hdc, rect->left + 1, rect->top,
603 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
604 else if (!(descr->style & LBS_USETABSTOPS))
605 ExtTextOutW( hdc, rect->left + 1, rect->top,
606 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
607 strlenW(item->str), NULL );
608 else
610 /* Output empty string to paint background in the full width. */
611 ExtTextOutW( hdc, rect->left + 1, rect->top,
612 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
613 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
614 item->str, strlenW(item->str),
615 descr->nb_tabs, descr->tabs, 0);
617 if (item && item->selected)
619 SetBkColor( hdc, oldBk );
620 SetTextColor( hdc, oldText );
622 if (!ignoreFocus && (descr->focus_item == index) &&
623 (descr->caret_on) &&
624 (descr->in_focus)) DrawFocusRect( hdc, rect );
629 /***********************************************************************
630 * LISTBOX_SetRedraw
632 * Change the redraw flag.
634 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
636 if (on)
638 if (!(descr->style & LBS_NOREDRAW)) return;
639 descr->style &= ~LBS_NOREDRAW;
640 if (descr->style & LBS_DISPLAYCHANGED)
641 { /* page was changed while setredraw false, refresh automatically */
642 InvalidateRect(descr->self, NULL, TRUE);
643 if ((descr->top_item + descr->page_size) > descr->nb_items)
644 { /* reset top of page if less than number of items/page */
645 descr->top_item = descr->nb_items - descr->page_size;
646 if (descr->top_item < 0) descr->top_item = 0;
648 descr->style &= ~LBS_DISPLAYCHANGED;
650 LISTBOX_UpdateScroll( descr );
652 else descr->style |= LBS_NOREDRAW;
656 /***********************************************************************
657 * LISTBOX_RepaintItem
659 * Repaint a single item synchronously.
661 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
663 HDC hdc;
664 RECT rect;
665 HFONT oldFont = 0;
666 HBRUSH hbrush, oldBrush = 0;
668 /* Do not repaint the item if the item is not visible */
669 if (!IsWindowVisible(descr->self)) return;
670 if (descr->style & LBS_NOREDRAW)
672 descr->style |= LBS_DISPLAYCHANGED;
673 return;
675 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
676 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
677 if (descr->font) oldFont = SelectObject( hdc, descr->font );
678 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
679 (WPARAM)hdc, (LPARAM)descr->self );
680 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
681 if (!IsWindowEnabled(descr->self))
682 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
683 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
684 LISTBOX_PaintItem( descr, hdc, &rect, index, action, FALSE );
685 if (oldFont) SelectObject( hdc, oldFont );
686 if (oldBrush) SelectObject( hdc, oldBrush );
687 ReleaseDC( descr->self, hdc );
691 /***********************************************************************
692 * LISTBOX_InitStorage
694 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
696 LB_ITEMDATA *item;
698 nb_items += LB_ARRAY_GRANULARITY - 1;
699 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
700 if (descr->items) {
701 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
702 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
703 nb_items * sizeof(LB_ITEMDATA));
705 else {
706 item = HeapAlloc( GetProcessHeap(), 0,
707 nb_items * sizeof(LB_ITEMDATA));
710 if (!item)
712 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
713 return LB_ERRSPACE;
715 descr->items = item;
716 return LB_OKAY;
720 /***********************************************************************
721 * LISTBOX_SetTabStops
723 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
725 INT i;
727 if (!(descr->style & LBS_USETABSTOPS))
729 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
730 return FALSE;
733 HeapFree( GetProcessHeap(), 0, descr->tabs );
734 if (!(descr->nb_tabs = count))
736 descr->tabs = NULL;
737 return TRUE;
739 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
740 descr->nb_tabs * sizeof(INT) )))
741 return FALSE;
742 if (short_ints)
744 INT i;
745 LPINT16 p = (LPINT16)tabs;
747 TRACE("[%p]: settabstops ", descr->self );
748 for (i = 0; i < descr->nb_tabs; i++) {
749 descr->tabs[i] = *p++<<1; /* FIXME */
750 TRACE("%hd ", descr->tabs[i]);
752 TRACE("\n");
754 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
756 /* convert into "dialog units"*/
757 for (i = 0; i < descr->nb_tabs; i++)
758 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
760 return TRUE;
764 /***********************************************************************
765 * LISTBOX_GetText
767 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
769 if ((index < 0) || (index >= descr->nb_items))
771 SetLastError(ERROR_INVALID_INDEX);
772 return LB_ERR;
774 if (HAS_STRINGS(descr))
776 if (!buffer)
778 DWORD len = strlenW(descr->items[index].str);
779 if( unicode )
780 return len;
781 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
782 NULL, 0, NULL, NULL );
785 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
787 if(unicode)
789 strcpyW( buffer, descr->items[index].str );
790 return strlenW(buffer);
792 else
794 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
796 } else {
797 if (buffer)
798 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
799 return sizeof(DWORD);
803 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
805 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
806 if (ret == CSTR_LESS_THAN)
807 return -1;
808 if (ret == CSTR_EQUAL)
809 return 0;
810 if (ret == CSTR_GREATER_THAN)
811 return 1;
812 return -1;
815 /***********************************************************************
816 * LISTBOX_FindStringPos
818 * Find the nearest string located before a given string in sort order.
819 * If 'exact' is TRUE, return an error if we don't get an exact match.
821 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
823 INT index, min, max, res = -1;
825 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
826 min = 0;
827 max = descr->nb_items;
828 while (min != max)
830 index = (min + max) / 2;
831 if (HAS_STRINGS(descr))
832 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
833 else
835 COMPAREITEMSTRUCT cis;
836 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
838 cis.CtlType = ODT_LISTBOX;
839 cis.CtlID = id;
840 cis.hwndItem = descr->self;
841 /* note that some application (MetaStock) expects the second item
842 * to be in the listbox */
843 cis.itemID1 = -1;
844 cis.itemData1 = (ULONG_PTR)str;
845 cis.itemID2 = index;
846 cis.itemData2 = descr->items[index].data;
847 cis.dwLocaleId = descr->locale;
848 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
850 if (!res) return index;
851 if (res < 0) max = index;
852 else min = index + 1;
854 return exact ? -1 : max;
858 /***********************************************************************
859 * LISTBOX_FindFileStrPos
861 * Find the nearest string located before a given string in directory
862 * sort order (i.e. first files, then directories, then drives).
864 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
866 INT min, max, res = -1;
868 if (!HAS_STRINGS(descr))
869 return LISTBOX_FindStringPos( descr, str, FALSE );
870 min = 0;
871 max = descr->nb_items;
872 while (min != max)
874 INT index = (min + max) / 2;
875 LPCWSTR p = descr->items[index].str;
876 if (*p == '[') /* drive or directory */
878 if (*str != '[') res = -1;
879 else if (p[1] == '-') /* drive */
881 if (str[1] == '-') res = str[2] - p[2];
882 else res = -1;
884 else /* directory */
886 if (str[1] == '-') res = 1;
887 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
890 else /* filename */
892 if (*str == '[') res = 1;
893 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
895 if (!res) return index;
896 if (res < 0) max = index;
897 else min = index + 1;
899 return max;
903 /***********************************************************************
904 * LISTBOX_FindString
906 * Find the item beginning with a given string.
908 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
910 INT i;
911 LB_ITEMDATA *item;
913 if (start >= descr->nb_items) start = -1;
914 item = descr->items + start + 1;
915 if (HAS_STRINGS(descr))
917 if (!str || ! str[0] ) return LB_ERR;
918 if (exact)
920 for (i = start + 1; i < descr->nb_items; i++, item++)
921 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
922 for (i = 0, item = descr->items; i <= start; i++, item++)
923 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
925 else
927 /* Special case for drives and directories: ignore prefix */
928 #define CHECK_DRIVE(item) \
929 if ((item)->str[0] == '[') \
931 if (!strncmpiW( str, (item)->str+1, len )) return i; \
932 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
933 return i; \
936 INT len = strlenW(str);
937 for (i = start + 1; i < descr->nb_items; i++, item++)
939 if (!strncmpiW( str, item->str, len )) return i;
940 CHECK_DRIVE(item);
942 for (i = 0, item = descr->items; i <= start; i++, item++)
944 if (!strncmpiW( str, item->str, len )) return i;
945 CHECK_DRIVE(item);
947 #undef CHECK_DRIVE
950 else
952 if (exact && (descr->style & LBS_SORT))
953 /* If sorted, use a WM_COMPAREITEM binary search */
954 return LISTBOX_FindStringPos( descr, str, TRUE );
956 /* Otherwise use a linear search */
957 for (i = start + 1; i < descr->nb_items; i++, item++)
958 if (item->data == (ULONG_PTR)str) return i;
959 for (i = 0, item = descr->items; i <= start; i++, item++)
960 if (item->data == (ULONG_PTR)str) return i;
962 return LB_ERR;
966 /***********************************************************************
967 * LISTBOX_GetSelCount
969 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
971 INT i, count;
972 LB_ITEMDATA *item = descr->items;
974 if (!(descr->style & LBS_MULTIPLESEL) ||
975 (descr->style & LBS_NOSEL))
976 return LB_ERR;
977 for (i = count = 0; i < descr->nb_items; i++, item++)
978 if (item->selected) count++;
979 return count;
983 /***********************************************************************
984 * LISTBOX_GetSelItems16
986 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
988 INT i, count;
989 LB_ITEMDATA *item = descr->items;
991 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
992 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
993 if (item->selected) array[count++] = (INT16)i;
994 return count;
998 /***********************************************************************
999 * LISTBOX_GetSelItems
1001 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
1003 INT i, count;
1004 LB_ITEMDATA *item = descr->items;
1006 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1007 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1008 if (item->selected) array[count++] = i;
1009 return count;
1013 /***********************************************************************
1014 * LISTBOX_Paint
1016 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1018 INT i, col_pos = descr->page_size - 1;
1019 RECT rect;
1020 RECT focusRect = {-1, -1, -1, -1};
1021 HFONT oldFont = 0;
1022 HBRUSH hbrush, oldBrush = 0;
1024 if (descr->style & LBS_NOREDRAW) return 0;
1026 SetRect( &rect, 0, 0, descr->width, descr->height );
1027 if (descr->style & LBS_MULTICOLUMN)
1028 rect.right = rect.left + descr->column_width;
1029 else if (descr->horz_pos)
1031 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1032 rect.right += descr->horz_pos;
1035 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1036 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1037 (WPARAM)hdc, (LPARAM)descr->self );
1038 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1039 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1041 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1042 (descr->in_focus))
1044 /* Special case for empty listbox: paint focus rect */
1045 rect.bottom = rect.top + descr->item_height;
1046 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1047 &rect, NULL, 0, NULL );
1048 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1049 rect.top = rect.bottom;
1052 /* Paint all the item, regarding the selection
1053 Focus state will be painted after */
1055 for (i = descr->top_item; i < descr->nb_items; i++)
1057 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1058 rect.bottom = rect.top + descr->item_height;
1059 else
1060 rect.bottom = rect.top + descr->items[i].height;
1062 if (i == descr->focus_item)
1064 /* keep the focus rect, to paint the focus item after */
1065 focusRect.left = rect.left;
1066 focusRect.right = rect.right;
1067 focusRect.top = rect.top;
1068 focusRect.bottom = rect.bottom;
1070 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1071 rect.top = rect.bottom;
1073 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1075 if (!IS_OWNERDRAW(descr))
1077 /* Clear the bottom of the column */
1078 if (rect.top < descr->height)
1080 rect.bottom = descr->height;
1081 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1082 &rect, NULL, 0, NULL );
1086 /* Go to the next column */
1087 rect.left += descr->column_width;
1088 rect.right += descr->column_width;
1089 rect.top = 0;
1090 col_pos = descr->page_size - 1;
1092 else
1094 col_pos--;
1095 if (rect.top >= descr->height) break;
1099 /* Paint the focus item now */
1100 if (focusRect.top != focusRect.bottom &&
1101 descr->caret_on && descr->in_focus)
1102 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1104 if (!IS_OWNERDRAW(descr))
1106 /* Clear the remainder of the client area */
1107 if (rect.top < descr->height)
1109 rect.bottom = descr->height;
1110 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1111 &rect, NULL, 0, NULL );
1113 if (rect.right < descr->width)
1115 rect.left = rect.right;
1116 rect.right = descr->width;
1117 rect.top = 0;
1118 rect.bottom = descr->height;
1119 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1120 &rect, NULL, 0, NULL );
1123 if (oldFont) SelectObject( hdc, oldFont );
1124 if (oldBrush) SelectObject( hdc, oldBrush );
1125 return 0;
1129 /***********************************************************************
1130 * LISTBOX_InvalidateItems
1132 * Invalidate all items from a given item. If the specified item is not
1133 * visible, nothing happens.
1135 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1137 RECT rect;
1139 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1141 if (descr->style & LBS_NOREDRAW)
1143 descr->style |= LBS_DISPLAYCHANGED;
1144 return;
1146 rect.bottom = descr->height;
1147 InvalidateRect( descr->self, &rect, TRUE );
1148 if (descr->style & LBS_MULTICOLUMN)
1150 /* Repaint the other columns */
1151 rect.left = rect.right;
1152 rect.right = descr->width;
1153 rect.top = 0;
1154 InvalidateRect( descr->self, &rect, TRUE );
1159 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1161 RECT rect;
1163 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1164 InvalidateRect( descr->self, &rect, TRUE );
1167 /***********************************************************************
1168 * LISTBOX_GetItemHeight
1170 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1172 if (descr->style & LBS_OWNERDRAWVARIABLE)
1174 if ((index < 0) || (index >= descr->nb_items))
1176 SetLastError(ERROR_INVALID_INDEX);
1177 return LB_ERR;
1179 return descr->items[index].height;
1181 else return descr->item_height;
1185 /***********************************************************************
1186 * LISTBOX_SetItemHeight
1188 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1190 if (!height) height = 1;
1192 if (descr->style & LBS_OWNERDRAWVARIABLE)
1194 if ((index < 0) || (index >= descr->nb_items))
1196 SetLastError(ERROR_INVALID_INDEX);
1197 return LB_ERR;
1199 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1200 descr->items[index].height = height;
1201 LISTBOX_UpdateScroll( descr );
1202 if (repaint)
1203 LISTBOX_InvalidateItems( descr, index );
1205 else if (height != descr->item_height)
1207 TRACE("[%p]: new height = %d\n", descr->self, height );
1208 descr->item_height = height;
1209 LISTBOX_UpdatePage( descr );
1210 LISTBOX_UpdateScroll( descr );
1211 if (repaint)
1212 InvalidateRect( descr->self, 0, TRUE );
1214 return LB_OKAY;
1218 /***********************************************************************
1219 * LISTBOX_SetHorizontalPos
1221 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1223 INT diff;
1225 if (pos > descr->horz_extent - descr->width)
1226 pos = descr->horz_extent - descr->width;
1227 if (pos < 0) pos = 0;
1228 if (!(diff = descr->horz_pos - pos)) return;
1229 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1230 descr->horz_pos = pos;
1231 LISTBOX_UpdateScroll( descr );
1232 if (abs(diff) < descr->width)
1234 RECT rect;
1235 /* Invalidate the focused item so it will be repainted correctly */
1236 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1237 InvalidateRect( descr->self, &rect, TRUE );
1238 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1239 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1241 else
1242 InvalidateRect( descr->self, NULL, TRUE );
1246 /***********************************************************************
1247 * LISTBOX_SetHorizontalExtent
1249 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1251 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1252 return LB_OKAY;
1253 if (extent <= 0) extent = 1;
1254 if (extent == descr->horz_extent) return LB_OKAY;
1255 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1256 descr->horz_extent = extent;
1257 if (descr->horz_pos > extent - descr->width)
1258 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1259 else
1260 LISTBOX_UpdateScroll( descr );
1261 return LB_OKAY;
1265 /***********************************************************************
1266 * LISTBOX_SetColumnWidth
1268 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1270 if (width == descr->column_width) return LB_OKAY;
1271 TRACE("[%p]: new column width = %d\n", descr->self, width );
1272 descr->column_width = width;
1273 LISTBOX_UpdatePage( descr );
1274 return LB_OKAY;
1278 /***********************************************************************
1279 * LISTBOX_SetFont
1281 * Returns the item height.
1283 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1285 HDC hdc;
1286 HFONT oldFont = 0;
1287 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1288 SIZE sz;
1290 descr->font = font;
1292 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1294 ERR("unable to get DC.\n" );
1295 return 16;
1297 if (font) oldFont = SelectObject( hdc, font );
1298 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1299 if (oldFont) SelectObject( hdc, oldFont );
1300 ReleaseDC( descr->self, hdc );
1302 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1303 if (!IS_OWNERDRAW(descr))
1304 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1305 return sz.cy;
1309 /***********************************************************************
1310 * LISTBOX_MakeItemVisible
1312 * Make sure that a given item is partially or fully visible.
1314 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1316 INT top;
1318 if (index <= descr->top_item) top = index;
1319 else if (descr->style & LBS_MULTICOLUMN)
1321 INT cols = descr->width;
1322 if (!fully) cols += descr->column_width - 1;
1323 if (cols >= descr->column_width) cols /= descr->column_width;
1324 else cols = 1;
1325 if (index < descr->top_item + (descr->page_size * cols)) return;
1326 top = index - descr->page_size * (cols - 1);
1328 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1330 INT height = fully ? descr->items[index].height : 1;
1331 for (top = index; top > descr->top_item; top--)
1332 if ((height += descr->items[top-1].height) > descr->height) break;
1334 else
1336 if (index < descr->top_item + descr->page_size) return;
1337 if (!fully && (index == descr->top_item + descr->page_size) &&
1338 (descr->height > (descr->page_size * descr->item_height))) return;
1339 top = index - descr->page_size + 1;
1341 LISTBOX_SetTopItem( descr, top, TRUE );
1344 /***********************************************************************
1345 * LISTBOX_SetCaretIndex
1347 * NOTES
1348 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1351 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1353 INT oldfocus = descr->focus_item;
1355 if (descr->style & LBS_NOSEL) return LB_ERR;
1356 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1357 if (index == oldfocus) return LB_OKAY;
1358 descr->focus_item = index;
1359 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1360 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1362 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1363 if (descr->caret_on && (descr->in_focus))
1364 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1366 return LB_OKAY;
1370 /***********************************************************************
1371 * LISTBOX_SelectItemRange
1373 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1375 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1376 INT last, BOOL on )
1378 INT i;
1380 /* A few sanity checks */
1382 if (descr->style & LBS_NOSEL) return LB_ERR;
1383 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1384 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1385 if (last == -1) last = descr->nb_items - 1;
1386 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1387 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1389 if (on) /* Turn selection on */
1391 for (i = first; i <= last; i++)
1393 if (descr->items[i].selected) continue;
1394 descr->items[i].selected = TRUE;
1395 LISTBOX_InvalidateItemRect(descr, i);
1398 else /* Turn selection off */
1400 for (i = first; i <= last; i++)
1402 if (!descr->items[i].selected) continue;
1403 descr->items[i].selected = FALSE;
1404 LISTBOX_InvalidateItemRect(descr, i);
1407 return LB_OKAY;
1410 /***********************************************************************
1411 * LISTBOX_SetSelection
1413 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1414 BOOL on, BOOL send_notify )
1416 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1418 if (descr->style & LBS_NOSEL)
1420 descr->selected_item = index;
1421 return LB_ERR;
1423 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1424 if (descr->style & LBS_MULTIPLESEL)
1426 if (index == -1) /* Select all items */
1427 return LISTBOX_SelectItemRange( descr, 0, -1, on );
1428 else /* Only one item */
1429 return LISTBOX_SelectItemRange( descr, index, index, on );
1431 else
1433 INT oldsel = descr->selected_item;
1434 if (index == oldsel) return LB_OKAY;
1435 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1436 if (index != -1) descr->items[index].selected = TRUE;
1437 descr->selected_item = index;
1438 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1439 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1440 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1441 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1442 else
1443 if( descr->lphc ) /* set selection change flag for parent combo */
1444 descr->lphc->wState |= CBF_SELCHANGE;
1446 return LB_OKAY;
1450 /***********************************************************************
1451 * LISTBOX_MoveCaret
1453 * Change the caret position and extend the selection to the new caret.
1455 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1457 INT oldfocus = descr->focus_item;
1459 if ((index < 0) || (index >= descr->nb_items))
1460 return;
1462 /* Important, repaint needs to be done in this order if
1463 you want to mimic Windows behavior:
1464 1. Remove the focus and paint the item
1465 2. Remove the selection and paint the item(s)
1466 3. Set the selection and repaint the item(s)
1467 4. Set the focus to 'index' and repaint the item */
1469 /* 1. remove the focus and repaint the item */
1470 descr->focus_item = -1;
1471 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1472 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1474 /* 2. then turn off the previous selection */
1475 /* 3. repaint the new selected item */
1476 if (descr->style & LBS_EXTENDEDSEL)
1478 if (descr->anchor_item != -1)
1480 INT first = min( index, descr->anchor_item );
1481 INT last = max( index, descr->anchor_item );
1482 if (first > 0)
1483 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1484 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1485 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1488 else if (!(descr->style & LBS_MULTIPLESEL))
1490 /* Set selection to new caret item */
1491 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1494 /* 4. repaint the new item with the focus */
1495 descr->focus_item = index;
1496 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1497 if (descr->caret_on && (descr->in_focus))
1498 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1502 /***********************************************************************
1503 * LISTBOX_InsertItem
1505 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1506 LPWSTR str, ULONG_PTR data )
1508 LB_ITEMDATA *item;
1509 INT max_items;
1510 INT oldfocus = descr->focus_item;
1512 if (index == -1) index = descr->nb_items;
1513 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1514 if (!descr->items) max_items = 0;
1515 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1516 if (descr->nb_items == max_items)
1518 /* We need to grow the array */
1519 max_items += LB_ARRAY_GRANULARITY;
1520 if (descr->items)
1521 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1522 max_items * sizeof(LB_ITEMDATA) );
1523 else
1524 item = HeapAlloc( GetProcessHeap(), 0,
1525 max_items * sizeof(LB_ITEMDATA) );
1526 if (!item)
1528 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1529 return LB_ERRSPACE;
1531 descr->items = item;
1534 /* Insert the item structure */
1536 item = &descr->items[index];
1537 if (index < descr->nb_items)
1538 RtlMoveMemory( item + 1, item,
1539 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1540 item->str = str;
1541 item->data = data;
1542 item->height = 0;
1543 item->selected = FALSE;
1544 descr->nb_items++;
1546 /* Get item height */
1548 if (descr->style & LBS_OWNERDRAWVARIABLE)
1550 MEASUREITEMSTRUCT mis;
1551 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1553 mis.CtlType = ODT_LISTBOX;
1554 mis.CtlID = id;
1555 mis.itemID = index;
1556 mis.itemData = descr->items[index].data;
1557 mis.itemHeight = descr->item_height;
1558 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1559 item->height = mis.itemHeight ? mis.itemHeight : 1;
1560 TRACE("[%p]: measure item %d (%s) = %d\n",
1561 descr->self, index, str ? debugstr_w(str) : "", item->height );
1564 /* Repaint the items */
1566 LISTBOX_UpdateScroll( descr );
1567 LISTBOX_InvalidateItems( descr, index );
1569 /* Move selection and focused item */
1570 /* If listbox was empty, set focus to the first item */
1571 if (descr->nb_items == 1)
1572 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1573 /* single select don't change selection index in win31 */
1574 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1576 descr->selected_item++;
1577 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1579 else
1581 if (index <= descr->selected_item)
1583 descr->selected_item++;
1584 descr->focus_item = oldfocus; /* focus not changed */
1587 return LB_OKAY;
1591 /***********************************************************************
1592 * LISTBOX_InsertString
1594 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1596 LPWSTR new_str = NULL;
1597 ULONG_PTR data = 0;
1598 LRESULT ret;
1600 if (HAS_STRINGS(descr))
1602 static const WCHAR empty_stringW[] = { 0 };
1603 if (!str) str = empty_stringW;
1604 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1606 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1607 return LB_ERRSPACE;
1609 strcpyW(new_str, str);
1611 else data = (ULONG_PTR)str;
1613 if (index == -1) index = descr->nb_items;
1614 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1616 HeapFree( GetProcessHeap(), 0, new_str );
1617 return ret;
1620 TRACE("[%p]: added item %d %s\n",
1621 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1622 return index;
1626 /***********************************************************************
1627 * LISTBOX_DeleteItem
1629 * Delete the content of an item. 'index' must be a valid index.
1631 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1633 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1634 * while Win95 sends it for all items with user data.
1635 * It's probably better to send it too often than not
1636 * often enough, so this is what we do here.
1638 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1640 DELETEITEMSTRUCT dis;
1641 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1643 dis.CtlType = ODT_LISTBOX;
1644 dis.CtlID = id;
1645 dis.itemID = index;
1646 dis.hwndItem = descr->self;
1647 dis.itemData = descr->items[index].data;
1648 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1650 if (HAS_STRINGS(descr) && descr->items[index].str)
1651 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1655 /***********************************************************************
1656 * LISTBOX_RemoveItem
1658 * Remove an item from the listbox and delete its content.
1660 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1662 LB_ITEMDATA *item;
1663 INT max_items;
1665 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1667 /* We need to invalidate the original rect instead of the updated one. */
1668 LISTBOX_InvalidateItems( descr, index );
1670 LISTBOX_DeleteItem( descr, index );
1672 /* Remove the item */
1674 item = &descr->items[index];
1675 if (index < descr->nb_items-1)
1676 RtlMoveMemory( item, item + 1,
1677 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1678 descr->nb_items--;
1679 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1681 /* Shrink the item array if possible */
1683 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1684 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1686 max_items -= LB_ARRAY_GRANULARITY;
1687 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1688 max_items * sizeof(LB_ITEMDATA) );
1689 if (item) descr->items = item;
1691 /* Repaint the items */
1693 LISTBOX_UpdateScroll( descr );
1694 /* if we removed the scrollbar, reset the top of the list
1695 (correct for owner-drawn ???) */
1696 if (descr->nb_items == descr->page_size)
1697 LISTBOX_SetTopItem( descr, 0, TRUE );
1699 /* Move selection and focused item */
1700 if (!IS_MULTISELECT(descr))
1702 if (index == descr->selected_item)
1703 descr->selected_item = -1;
1704 else if (index < descr->selected_item)
1706 descr->selected_item--;
1707 if (ISWIN31) /* win 31 do not change the selected item number */
1708 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1712 if (descr->focus_item >= descr->nb_items)
1714 descr->focus_item = descr->nb_items - 1;
1715 if (descr->focus_item < 0) descr->focus_item = 0;
1717 return LB_OKAY;
1721 /***********************************************************************
1722 * LISTBOX_ResetContent
1724 static void LISTBOX_ResetContent( LB_DESCR *descr )
1726 INT i;
1728 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1729 HeapFree( GetProcessHeap(), 0, descr->items );
1730 descr->nb_items = 0;
1731 descr->top_item = 0;
1732 descr->selected_item = -1;
1733 descr->focus_item = 0;
1734 descr->anchor_item = -1;
1735 descr->items = NULL;
1739 /***********************************************************************
1740 * LISTBOX_SetCount
1742 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1744 LRESULT ret;
1746 if (HAS_STRINGS(descr))
1748 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1749 return LB_ERR;
1752 /* FIXME: this is far from optimal... */
1753 if (count > descr->nb_items)
1755 while (count > descr->nb_items)
1756 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1757 return ret;
1759 else if (count < descr->nb_items)
1761 while (count < descr->nb_items)
1762 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1763 return ret;
1765 return LB_OKAY;
1769 /***********************************************************************
1770 * LISTBOX_Directory
1772 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1773 LPCWSTR filespec, BOOL long_names )
1775 HANDLE handle;
1776 LRESULT ret = LB_OKAY;
1777 WIN32_FIND_DATAW entry;
1778 int pos;
1780 /* don't scan directory if we just want drives exclusively */
1781 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1782 /* scan directory */
1783 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1785 int le = GetLastError();
1786 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1788 else
1792 WCHAR buffer[270];
1793 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1795 static const WCHAR bracketW[] = { ']',0 };
1796 static const WCHAR dotW[] = { '.',0 };
1797 if (!(attrib & DDL_DIRECTORY) ||
1798 !strcmpW( entry.cFileName, dotW )) continue;
1799 buffer[0] = '[';
1800 if (!long_names && entry.cAlternateFileName[0])
1801 strcpyW( buffer + 1, entry.cAlternateFileName );
1802 else
1803 strcpyW( buffer + 1, entry.cFileName );
1804 strcatW(buffer, bracketW);
1806 else /* not a directory */
1808 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1809 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1811 if ((attrib & DDL_EXCLUSIVE) &&
1812 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1813 continue;
1814 #undef ATTRIBS
1815 if (!long_names && entry.cAlternateFileName[0])
1816 strcpyW( buffer, entry.cAlternateFileName );
1817 else
1818 strcpyW( buffer, entry.cFileName );
1820 if (!long_names) CharLowerW( buffer );
1821 pos = LISTBOX_FindFileStrPos( descr, buffer );
1822 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1823 break;
1824 } while (FindNextFileW( handle, &entry ));
1825 FindClose( handle );
1829 /* scan drives */
1830 if ((ret >= 0) && (attrib & DDL_DRIVES))
1832 WCHAR buffer[] = {'[','-','a','-',']',0};
1833 WCHAR root[] = {'A',':','\\',0};
1834 int drive;
1835 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1837 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1838 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1839 break;
1842 return ret;
1846 /***********************************************************************
1847 * LISTBOX_HandleVScroll
1849 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1851 SCROLLINFO info;
1853 if (descr->style & LBS_MULTICOLUMN) return 0;
1854 switch(scrollReq)
1856 case SB_LINEUP:
1857 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1858 break;
1859 case SB_LINEDOWN:
1860 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1861 break;
1862 case SB_PAGEUP:
1863 LISTBOX_SetTopItem( descr, descr->top_item -
1864 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1865 break;
1866 case SB_PAGEDOWN:
1867 LISTBOX_SetTopItem( descr, descr->top_item +
1868 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1869 break;
1870 case SB_THUMBPOSITION:
1871 LISTBOX_SetTopItem( descr, pos, TRUE );
1872 break;
1873 case SB_THUMBTRACK:
1874 info.cbSize = sizeof(info);
1875 info.fMask = SIF_TRACKPOS;
1876 GetScrollInfo( descr->self, SB_VERT, &info );
1877 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1878 break;
1879 case SB_TOP:
1880 LISTBOX_SetTopItem( descr, 0, TRUE );
1881 break;
1882 case SB_BOTTOM:
1883 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1884 break;
1886 return 0;
1890 /***********************************************************************
1891 * LISTBOX_HandleHScroll
1893 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1895 SCROLLINFO info;
1896 INT page;
1898 if (descr->style & LBS_MULTICOLUMN)
1900 switch(scrollReq)
1902 case SB_LINELEFT:
1903 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1904 TRUE );
1905 break;
1906 case SB_LINERIGHT:
1907 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1908 TRUE );
1909 break;
1910 case SB_PAGELEFT:
1911 page = descr->width / descr->column_width;
1912 if (page < 1) page = 1;
1913 LISTBOX_SetTopItem( descr,
1914 descr->top_item - page * descr->page_size, TRUE );
1915 break;
1916 case SB_PAGERIGHT:
1917 page = descr->width / descr->column_width;
1918 if (page < 1) page = 1;
1919 LISTBOX_SetTopItem( descr,
1920 descr->top_item + page * descr->page_size, TRUE );
1921 break;
1922 case SB_THUMBPOSITION:
1923 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1924 break;
1925 case SB_THUMBTRACK:
1926 info.cbSize = sizeof(info);
1927 info.fMask = SIF_TRACKPOS;
1928 GetScrollInfo( descr->self, SB_VERT, &info );
1929 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1930 TRUE );
1931 break;
1932 case SB_LEFT:
1933 LISTBOX_SetTopItem( descr, 0, TRUE );
1934 break;
1935 case SB_RIGHT:
1936 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1937 break;
1940 else if (descr->horz_extent)
1942 switch(scrollReq)
1944 case SB_LINELEFT:
1945 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1946 break;
1947 case SB_LINERIGHT:
1948 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1949 break;
1950 case SB_PAGELEFT:
1951 LISTBOX_SetHorizontalPos( descr,
1952 descr->horz_pos - descr->width );
1953 break;
1954 case SB_PAGERIGHT:
1955 LISTBOX_SetHorizontalPos( descr,
1956 descr->horz_pos + descr->width );
1957 break;
1958 case SB_THUMBPOSITION:
1959 LISTBOX_SetHorizontalPos( descr, pos );
1960 break;
1961 case SB_THUMBTRACK:
1962 info.cbSize = sizeof(info);
1963 info.fMask = SIF_TRACKPOS;
1964 GetScrollInfo( descr->self, SB_HORZ, &info );
1965 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1966 break;
1967 case SB_LEFT:
1968 LISTBOX_SetHorizontalPos( descr, 0 );
1969 break;
1970 case SB_RIGHT:
1971 LISTBOX_SetHorizontalPos( descr,
1972 descr->horz_extent - descr->width );
1973 break;
1976 return 0;
1979 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1981 short gcWheelDelta = 0;
1982 UINT pulScrollLines = 3;
1984 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1986 gcWheelDelta -= delta;
1988 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1990 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1991 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1992 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
1994 return 0;
1997 /***********************************************************************
1998 * LISTBOX_HandleLButtonDown
2000 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2002 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2003 TRACE("[%p]: lbuttondown %d,%d item %d\n", descr->self, x, y, index );
2004 if (!descr->caret_on && (descr->in_focus)) return 0;
2006 if (!descr->in_focus)
2008 if( !descr->lphc ) SetFocus( descr->self );
2009 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2012 if (index == -1) return 0;
2014 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2016 /* we should perhaps make sure that all items are deselected
2017 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2018 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2019 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2022 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2023 if (keys & MK_CONTROL)
2025 LISTBOX_SetCaretIndex( descr, index, FALSE );
2026 LISTBOX_SetSelection( descr, index,
2027 !descr->items[index].selected,
2028 (descr->style & LBS_NOTIFY) != 0);
2030 else
2032 LISTBOX_MoveCaret( descr, index, FALSE );
2034 if (descr->style & LBS_EXTENDEDSEL)
2036 LISTBOX_SetSelection( descr, index,
2037 descr->items[index].selected,
2038 (descr->style & LBS_NOTIFY) != 0 );
2040 else
2042 LISTBOX_SetSelection( descr, index,
2043 !descr->items[index].selected,
2044 (descr->style & LBS_NOTIFY) != 0 );
2048 else
2050 descr->anchor_item = index;
2051 LISTBOX_MoveCaret( descr, index, FALSE );
2052 LISTBOX_SetSelection( descr, index,
2053 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2056 descr->captured = TRUE;
2057 SetCapture( descr->self );
2059 if (!descr->lphc)
2061 if (descr->style & LBS_NOTIFY )
2062 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2063 MAKELPARAM( x, y ) );
2064 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2066 POINT pt;
2068 pt.x = x;
2069 pt.y = y;
2071 if (DragDetect( descr->self, pt ))
2072 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2075 return 0;
2079 /*************************************************************************
2080 * LISTBOX_HandleLButtonDownCombo [Internal]
2082 * Process LButtonDown message for the ComboListBox
2084 * PARAMS
2085 * pWnd [I] The windows internal structure
2086 * pDescr [I] The ListBox internal structure
2087 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2088 * x [I] X Mouse Coordinate
2089 * y [I] Y Mouse Coordinate
2091 * RETURNS
2092 * 0 since we are processing the WM_LBUTTONDOWN Message
2094 * NOTES
2095 * This function is only to be used when a ListBox is a ComboListBox
2098 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2100 RECT clientRect, screenRect;
2101 POINT mousePos;
2103 mousePos.x = x;
2104 mousePos.y = y;
2106 GetClientRect(descr->self, &clientRect);
2108 if(PtInRect(&clientRect, mousePos))
2110 /* MousePos is in client, resume normal processing */
2111 if (msg == WM_LBUTTONDOWN)
2113 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2114 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2116 else if (descr->style & LBS_NOTIFY)
2117 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2119 else
2121 POINT screenMousePos;
2122 HWND hWndOldCapture;
2124 /* Check the Non-Client Area */
2125 screenMousePos = mousePos;
2126 hWndOldCapture = GetCapture();
2127 ReleaseCapture();
2128 GetWindowRect(descr->self, &screenRect);
2129 ClientToScreen(descr->self, &screenMousePos);
2131 if(!PtInRect(&screenRect, screenMousePos))
2133 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2134 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2135 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2137 else
2139 /* Check to see the NC is a scrollbar */
2140 INT nHitTestType=0;
2141 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2142 /* Check Vertical scroll bar */
2143 if (style & WS_VSCROLL)
2145 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2146 if (PtInRect( &clientRect, mousePos ))
2147 nHitTestType = HTVSCROLL;
2149 /* Check horizontal scroll bar */
2150 if (style & WS_HSCROLL)
2152 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2153 if (PtInRect( &clientRect, mousePos ))
2154 nHitTestType = HTHSCROLL;
2156 /* Windows sends this message when a scrollbar is clicked
2159 if(nHitTestType != 0)
2161 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2162 MAKELONG(screenMousePos.x, screenMousePos.y));
2164 /* Resume the Capture after scrolling is complete
2166 if(hWndOldCapture != 0)
2167 SetCapture(hWndOldCapture);
2170 return 0;
2173 /***********************************************************************
2174 * LISTBOX_HandleLButtonUp
2176 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2178 if (LISTBOX_Timer != LB_TIMER_NONE)
2179 KillSystemTimer( descr->self, LB_TIMER_ID );
2180 LISTBOX_Timer = LB_TIMER_NONE;
2181 if (descr->captured)
2183 descr->captured = FALSE;
2184 if (GetCapture() == descr->self) ReleaseCapture();
2185 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2186 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2188 return 0;
2192 /***********************************************************************
2193 * LISTBOX_HandleTimer
2195 * Handle scrolling upon a timer event.
2196 * Return TRUE if scrolling should continue.
2198 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2200 switch(dir)
2202 case LB_TIMER_UP:
2203 if (descr->top_item) index = descr->top_item - 1;
2204 else index = 0;
2205 break;
2206 case LB_TIMER_LEFT:
2207 if (descr->top_item) index -= descr->page_size;
2208 break;
2209 case LB_TIMER_DOWN:
2210 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2211 if (index == descr->focus_item) index++;
2212 if (index >= descr->nb_items) index = descr->nb_items - 1;
2213 break;
2214 case LB_TIMER_RIGHT:
2215 if (index + descr->page_size < descr->nb_items)
2216 index += descr->page_size;
2217 break;
2218 case LB_TIMER_NONE:
2219 break;
2221 if (index == descr->focus_item) return FALSE;
2222 LISTBOX_MoveCaret( descr, index, FALSE );
2223 return TRUE;
2227 /***********************************************************************
2228 * LISTBOX_HandleSystemTimer
2230 * WM_SYSTIMER handler.
2232 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2234 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2236 KillSystemTimer( descr->self, LB_TIMER_ID );
2237 LISTBOX_Timer = LB_TIMER_NONE;
2239 return 0;
2243 /***********************************************************************
2244 * LISTBOX_HandleMouseMove
2246 * WM_MOUSEMOVE handler.
2248 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2249 INT x, INT y )
2251 INT index;
2252 TIMER_DIRECTION dir = LB_TIMER_NONE;
2254 if (!descr->captured) return;
2256 if (descr->style & LBS_MULTICOLUMN)
2258 if (y < 0) y = 0;
2259 else if (y >= descr->item_height * descr->page_size)
2260 y = descr->item_height * descr->page_size - 1;
2262 if (x < 0)
2264 dir = LB_TIMER_LEFT;
2265 x = 0;
2267 else if (x >= descr->width)
2269 dir = LB_TIMER_RIGHT;
2270 x = descr->width - 1;
2273 else
2275 if (y < 0) dir = LB_TIMER_UP; /* above */
2276 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2279 index = LISTBOX_GetItemFromPoint( descr, x, y );
2280 if (index == -1) index = descr->focus_item;
2281 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2283 /* Start/stop the system timer */
2285 if (dir != LB_TIMER_NONE)
2286 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2287 else if (LISTBOX_Timer != LB_TIMER_NONE)
2288 KillSystemTimer( descr->self, LB_TIMER_ID );
2289 LISTBOX_Timer = dir;
2293 /***********************************************************************
2294 * LISTBOX_HandleKeyDown
2296 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2298 INT caret = -1;
2299 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2300 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2301 bForceSelection = FALSE; /* only for single select list */
2303 if (descr->style & LBS_WANTKEYBOARDINPUT)
2305 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2306 MAKEWPARAM(LOWORD(key), descr->focus_item),
2307 (LPARAM)descr->self );
2308 if (caret == -2) return 0;
2310 if (caret == -1) switch(key)
2312 case VK_LEFT:
2313 if (descr->style & LBS_MULTICOLUMN)
2315 bForceSelection = FALSE;
2316 if (descr->focus_item >= descr->page_size)
2317 caret = descr->focus_item - descr->page_size;
2318 break;
2320 /* fall through */
2321 case VK_UP:
2322 caret = descr->focus_item - 1;
2323 if (caret < 0) caret = 0;
2324 break;
2325 case VK_RIGHT:
2326 if (descr->style & LBS_MULTICOLUMN)
2328 bForceSelection = FALSE;
2329 if (descr->focus_item + descr->page_size < descr->nb_items)
2330 caret = descr->focus_item + descr->page_size;
2331 break;
2333 /* fall through */
2334 case VK_DOWN:
2335 caret = descr->focus_item + 1;
2336 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2337 break;
2339 case VK_PRIOR:
2340 if (descr->style & LBS_MULTICOLUMN)
2342 INT page = descr->width / descr->column_width;
2343 if (page < 1) page = 1;
2344 caret = descr->focus_item - (page * descr->page_size) + 1;
2346 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2347 if (caret < 0) caret = 0;
2348 break;
2349 case VK_NEXT:
2350 if (descr->style & LBS_MULTICOLUMN)
2352 INT page = descr->width / descr->column_width;
2353 if (page < 1) page = 1;
2354 caret = descr->focus_item + (page * descr->page_size) - 1;
2356 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2357 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2358 break;
2359 case VK_HOME:
2360 caret = 0;
2361 break;
2362 case VK_END:
2363 caret = descr->nb_items - 1;
2364 break;
2365 case VK_SPACE:
2366 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2367 else if (descr->style & LBS_MULTIPLESEL)
2369 LISTBOX_SetSelection( descr, descr->focus_item,
2370 !descr->items[descr->focus_item].selected,
2371 (descr->style & LBS_NOTIFY) != 0 );
2373 break;
2374 default:
2375 bForceSelection = FALSE;
2377 if (bForceSelection) /* focused item is used instead of key */
2378 caret = descr->focus_item;
2379 if (caret >= 0)
2381 if (((descr->style & LBS_EXTENDEDSEL) &&
2382 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2383 !IS_MULTISELECT(descr))
2384 descr->anchor_item = caret;
2385 LISTBOX_MoveCaret( descr, caret, TRUE );
2387 if (descr->style & LBS_MULTIPLESEL)
2388 descr->selected_item = caret;
2389 else
2390 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2391 if (descr->style & LBS_NOTIFY)
2393 if( descr->lphc )
2395 /* make sure that combo parent doesn't hide us */
2396 descr->lphc->wState |= CBF_NOROLLUP;
2398 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2401 return 0;
2405 /***********************************************************************
2406 * LISTBOX_HandleChar
2408 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2410 INT caret = -1;
2411 WCHAR str[2];
2413 str[0] = charW;
2414 str[1] = '\0';
2416 if (descr->style & LBS_WANTKEYBOARDINPUT)
2418 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2419 MAKEWPARAM(charW, descr->focus_item),
2420 (LPARAM)descr->self );
2421 if (caret == -2) return 0;
2423 if (caret == -1)
2424 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2425 if (caret != -1)
2427 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2428 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2429 LISTBOX_MoveCaret( descr, caret, TRUE );
2430 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2431 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2433 return 0;
2437 /***********************************************************************
2438 * LISTBOX_Create
2440 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2442 LB_DESCR *descr;
2443 MEASUREITEMSTRUCT mis;
2444 RECT rect;
2446 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2447 return FALSE;
2449 GetClientRect( hwnd, &rect );
2450 descr->self = hwnd;
2451 descr->owner = GetParent( descr->self );
2452 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2453 descr->width = rect.right - rect.left;
2454 descr->height = rect.bottom - rect.top;
2455 descr->items = NULL;
2456 descr->nb_items = 0;
2457 descr->top_item = 0;
2458 descr->selected_item = -1;
2459 descr->focus_item = 0;
2460 descr->anchor_item = -1;
2461 descr->item_height = 1;
2462 descr->page_size = 1;
2463 descr->column_width = 150;
2464 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2465 descr->horz_pos = 0;
2466 descr->nb_tabs = 0;
2467 descr->tabs = NULL;
2468 descr->caret_on = lphc ? FALSE : TRUE;
2469 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2470 descr->in_focus = FALSE;
2471 descr->captured = FALSE;
2472 descr->font = 0;
2473 descr->locale = GetUserDefaultLCID();
2474 descr->lphc = lphc;
2476 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2478 /* Win95 document "List Box Differences" from MSDN:
2479 If a list box in a version 3.x application has either the
2480 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2481 horizontal and vertical scroll bars.
2483 descr->style |= WS_VSCROLL | WS_HSCROLL;
2486 if( lphc )
2488 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2489 descr->owner = lphc->self;
2492 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2494 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2496 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2497 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2498 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2499 descr->item_height = LISTBOX_SetFont( descr, 0 );
2501 if (descr->style & LBS_OWNERDRAWFIXED)
2503 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2505 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2506 descr->item_height = lphc->fixedOwnerDrawHeight;
2508 else
2510 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2511 mis.CtlType = ODT_LISTBOX;
2512 mis.CtlID = id;
2513 mis.itemID = -1;
2514 mis.itemWidth = 0;
2515 mis.itemData = 0;
2516 mis.itemHeight = descr->item_height;
2517 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2518 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2522 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2523 return TRUE;
2527 /***********************************************************************
2528 * LISTBOX_Destroy
2530 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2532 LISTBOX_ResetContent( descr );
2533 SetWindowLongPtrW( descr->self, 0, 0 );
2534 HeapFree( GetProcessHeap(), 0, descr );
2535 return TRUE;
2539 /***********************************************************************
2540 * ListBoxWndProc_common
2542 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2543 WPARAM wParam, LPARAM lParam, BOOL unicode )
2545 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2546 LPHEADCOMBO lphc = 0;
2547 LRESULT ret;
2549 if (!descr)
2551 if (!IsWindow(hwnd)) return 0;
2553 if (msg == WM_CREATE)
2555 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2556 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2557 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2558 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongPtrW( hwnd, 0 ) );
2559 return 0;
2561 /* Ignore all other messages before we get a WM_CREATE */
2562 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2563 DefWindowProcA( hwnd, msg, wParam, lParam );
2565 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2567 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2568 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2570 switch(msg)
2572 case LB_RESETCONTENT16:
2573 case LB_RESETCONTENT:
2574 LISTBOX_ResetContent( descr );
2575 LISTBOX_UpdateScroll( descr );
2576 InvalidateRect( descr->self, NULL, TRUE );
2577 return 0;
2579 case LB_ADDSTRING16:
2580 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2581 /* fall through */
2582 case LB_ADDSTRING:
2584 INT ret;
2585 LPWSTR textW;
2586 if(unicode || !HAS_STRINGS(descr))
2587 textW = (LPWSTR)lParam;
2588 else
2590 LPSTR textA = (LPSTR)lParam;
2591 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2592 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2593 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2595 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2596 ret = LISTBOX_InsertString( descr, wParam, textW );
2597 if (!unicode && HAS_STRINGS(descr))
2598 HeapFree(GetProcessHeap(), 0, textW);
2599 return ret;
2602 case LB_INSERTSTRING16:
2603 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2604 wParam = (INT)(INT16)wParam;
2605 /* fall through */
2606 case LB_INSERTSTRING:
2608 INT ret;
2609 LPWSTR textW;
2610 if(unicode || !HAS_STRINGS(descr))
2611 textW = (LPWSTR)lParam;
2612 else
2614 LPSTR textA = (LPSTR)lParam;
2615 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2616 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2617 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2619 ret = LISTBOX_InsertString( descr, wParam, textW );
2620 if(!unicode && HAS_STRINGS(descr))
2621 HeapFree(GetProcessHeap(), 0, textW);
2622 return ret;
2625 case LB_ADDFILE16:
2626 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2627 /* fall through */
2628 case LB_ADDFILE:
2630 INT ret;
2631 LPWSTR textW;
2632 if(unicode || !HAS_STRINGS(descr))
2633 textW = (LPWSTR)lParam;
2634 else
2636 LPSTR textA = (LPSTR)lParam;
2637 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2638 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2639 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2641 wParam = LISTBOX_FindFileStrPos( descr, textW );
2642 ret = LISTBOX_InsertString( descr, wParam, textW );
2643 if(!unicode && HAS_STRINGS(descr))
2644 HeapFree(GetProcessHeap(), 0, textW);
2645 return ret;
2648 case LB_DELETESTRING16:
2649 case LB_DELETESTRING:
2650 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2651 return descr->nb_items;
2652 else
2654 SetLastError(ERROR_INVALID_INDEX);
2655 return LB_ERR;
2658 case LB_GETITEMDATA16:
2659 case LB_GETITEMDATA:
2660 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2662 SetLastError(ERROR_INVALID_INDEX);
2663 return LB_ERR;
2665 return descr->items[wParam].data;
2667 case LB_SETITEMDATA16:
2668 case LB_SETITEMDATA:
2669 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2671 SetLastError(ERROR_INVALID_INDEX);
2672 return LB_ERR;
2674 descr->items[wParam].data = lParam;
2675 return LB_OKAY;
2677 case LB_GETCOUNT16:
2678 case LB_GETCOUNT:
2679 return descr->nb_items;
2681 case LB_GETTEXT16:
2682 lParam = (LPARAM)MapSL(lParam);
2683 /* fall through */
2684 case LB_GETTEXT:
2685 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2687 case LB_GETTEXTLEN16:
2688 /* fall through */
2689 case LB_GETTEXTLEN:
2690 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2692 SetLastError(ERROR_INVALID_INDEX);
2693 return LB_ERR;
2695 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2696 if (unicode) return strlenW( descr->items[wParam].str );
2697 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2698 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2700 case LB_GETCURSEL16:
2701 case LB_GETCURSEL:
2702 if (descr->nb_items == 0)
2703 return LB_ERR;
2704 if (!IS_MULTISELECT(descr))
2705 return descr->selected_item;
2706 if (descr->selected_item != -1)
2707 return descr->selected_item;
2708 return descr->focus_item;
2709 /* otherwise, if the user tries to move the selection with the */
2710 /* arrow keys, we will give the application something to choke on */
2711 case LB_GETTOPINDEX16:
2712 case LB_GETTOPINDEX:
2713 return descr->top_item;
2715 case LB_GETITEMHEIGHT16:
2716 case LB_GETITEMHEIGHT:
2717 return LISTBOX_GetItemHeight( descr, wParam );
2719 case LB_SETITEMHEIGHT16:
2720 lParam = LOWORD(lParam);
2721 /* fall through */
2722 case LB_SETITEMHEIGHT:
2723 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2725 case LB_ITEMFROMPOINT:
2727 POINT pt;
2728 RECT rect;
2730 pt.x = LOWORD(lParam);
2731 pt.y = HIWORD(lParam);
2732 rect.left = 0;
2733 rect.top = 0;
2734 rect.right = descr->width;
2735 rect.bottom = descr->height;
2737 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2738 !PtInRect( &rect, pt ) );
2741 case LB_SETCARETINDEX16:
2742 case LB_SETCARETINDEX:
2743 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2744 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2745 return LB_ERR;
2746 else if (ISWIN31)
2747 return wParam;
2748 else
2749 return LB_OKAY;
2751 case LB_GETCARETINDEX16:
2752 case LB_GETCARETINDEX:
2753 return descr->focus_item;
2755 case LB_SETTOPINDEX16:
2756 case LB_SETTOPINDEX:
2757 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2759 case LB_SETCOLUMNWIDTH16:
2760 case LB_SETCOLUMNWIDTH:
2761 return LISTBOX_SetColumnWidth( descr, wParam );
2763 case LB_GETITEMRECT16:
2765 RECT rect;
2766 RECT16 *r16 = MapSL(lParam);
2767 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2768 r16->left = rect.left;
2769 r16->top = rect.top;
2770 r16->right = rect.right;
2771 r16->bottom = rect.bottom;
2773 return ret;
2775 case LB_GETITEMRECT:
2776 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2778 case LB_FINDSTRING16:
2779 wParam = (INT)(INT16)wParam;
2780 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2781 /* fall through */
2782 case LB_FINDSTRING:
2784 INT ret;
2785 LPWSTR textW;
2786 if(unicode || !HAS_STRINGS(descr))
2787 textW = (LPWSTR)lParam;
2788 else
2790 LPSTR textA = (LPSTR)lParam;
2791 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2792 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2793 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2795 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2796 if(!unicode && HAS_STRINGS(descr))
2797 HeapFree(GetProcessHeap(), 0, textW);
2798 return ret;
2801 case LB_FINDSTRINGEXACT16:
2802 wParam = (INT)(INT16)wParam;
2803 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2804 /* fall through */
2805 case LB_FINDSTRINGEXACT:
2807 INT ret;
2808 LPWSTR textW;
2809 if(unicode || !HAS_STRINGS(descr))
2810 textW = (LPWSTR)lParam;
2811 else
2813 LPSTR textA = (LPSTR)lParam;
2814 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2815 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2816 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2818 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2819 if(!unicode && HAS_STRINGS(descr))
2820 HeapFree(GetProcessHeap(), 0, textW);
2821 return ret;
2824 case LB_SELECTSTRING16:
2825 wParam = (INT)(INT16)wParam;
2826 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2827 /* fall through */
2828 case LB_SELECTSTRING:
2830 INT index;
2831 LPWSTR textW;
2833 if(HAS_STRINGS(descr))
2834 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2835 debugstr_a((LPSTR)lParam));
2836 if(unicode || !HAS_STRINGS(descr))
2837 textW = (LPWSTR)lParam;
2838 else
2840 LPSTR textA = (LPSTR)lParam;
2841 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2842 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2843 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2845 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2846 if(!unicode && HAS_STRINGS(descr))
2847 HeapFree(GetProcessHeap(), 0, textW);
2848 if (index != LB_ERR)
2850 LISTBOX_MoveCaret( descr, index, TRUE );
2851 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2853 return index;
2856 case LB_GETSEL16:
2857 wParam = (INT)(INT16)wParam;
2858 /* fall through */
2859 case LB_GETSEL:
2860 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2861 return LB_ERR;
2862 return descr->items[wParam].selected;
2864 case LB_SETSEL16:
2865 lParam = (INT)(INT16)lParam;
2866 /* fall through */
2867 case LB_SETSEL:
2868 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2870 case LB_SETCURSEL16:
2871 wParam = (INT)(INT16)wParam;
2872 /* fall through */
2873 case LB_SETCURSEL:
2874 if (IS_MULTISELECT(descr)) return LB_ERR;
2875 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2876 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2877 if (lphc && ret != LB_ERR) ret = descr->selected_item;
2878 return ret;
2880 case LB_GETSELCOUNT16:
2881 case LB_GETSELCOUNT:
2882 return LISTBOX_GetSelCount( descr );
2884 case LB_GETSELITEMS16:
2885 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2887 case LB_GETSELITEMS:
2888 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2890 case LB_SELITEMRANGE16:
2891 case LB_SELITEMRANGE:
2892 if (LOWORD(lParam) <= HIWORD(lParam))
2893 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2894 HIWORD(lParam), wParam );
2895 else
2896 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2897 LOWORD(lParam), wParam );
2899 case LB_SELITEMRANGEEX16:
2900 case LB_SELITEMRANGEEX:
2901 if ((INT)lParam >= (INT)wParam)
2902 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2903 else
2904 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2906 case LB_GETHORIZONTALEXTENT16:
2907 case LB_GETHORIZONTALEXTENT:
2908 return descr->horz_extent;
2910 case LB_SETHORIZONTALEXTENT16:
2911 case LB_SETHORIZONTALEXTENT:
2912 return LISTBOX_SetHorizontalExtent( descr, wParam );
2914 case LB_GETANCHORINDEX16:
2915 case LB_GETANCHORINDEX:
2916 return descr->anchor_item;
2918 case LB_SETANCHORINDEX16:
2919 wParam = (INT)(INT16)wParam;
2920 /* fall through */
2921 case LB_SETANCHORINDEX:
2922 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2924 SetLastError(ERROR_INVALID_INDEX);
2925 return LB_ERR;
2927 descr->anchor_item = (INT)wParam;
2928 return LB_OKAY;
2930 case LB_DIR16:
2931 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2932 * be set automatically (this is different in Win32) */
2933 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2934 lParam = (LPARAM)MapSL(lParam);
2935 /* fall through */
2936 case LB_DIR:
2938 INT ret;
2939 LPWSTR textW;
2940 if(unicode)
2941 textW = (LPWSTR)lParam;
2942 else
2944 LPSTR textA = (LPSTR)lParam;
2945 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2946 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2947 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2949 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2950 if(!unicode)
2951 HeapFree(GetProcessHeap(), 0, textW);
2952 return ret;
2955 case LB_GETLOCALE:
2956 return descr->locale;
2958 case LB_SETLOCALE:
2960 LCID ret;
2961 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2962 return LB_ERR;
2963 ret = descr->locale;
2964 descr->locale = (LCID)wParam;
2965 return ret;
2968 case LB_INITSTORAGE:
2969 return LISTBOX_InitStorage( descr, wParam );
2971 case LB_SETCOUNT:
2972 return LISTBOX_SetCount( descr, (INT)wParam );
2974 case LB_SETTABSTOPS16:
2975 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2977 case LB_SETTABSTOPS:
2978 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
2980 case LB_CARETON16:
2981 case LB_CARETON:
2982 if (descr->caret_on)
2983 return LB_OKAY;
2984 descr->caret_on = TRUE;
2985 if ((descr->focus_item != -1) && (descr->in_focus))
2986 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2987 return LB_OKAY;
2989 case LB_CARETOFF16:
2990 case LB_CARETOFF:
2991 if (!descr->caret_on)
2992 return LB_OKAY;
2993 descr->caret_on = FALSE;
2994 if ((descr->focus_item != -1) && (descr->in_focus))
2995 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2996 return LB_OKAY;
2998 case LB_GETLISTBOXINFO:
2999 FIXME("LB_GETLISTBOXINFO: stub!\n");
3000 return 0;
3002 case WM_DESTROY:
3003 return LISTBOX_Destroy( descr );
3005 case WM_ENABLE:
3006 InvalidateRect( descr->self, NULL, TRUE );
3007 return 0;
3009 case WM_SETREDRAW:
3010 LISTBOX_SetRedraw( descr, wParam != 0 );
3011 return 0;
3013 case WM_GETDLGCODE:
3014 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3016 case WM_PAINT:
3018 PAINTSTRUCT ps;
3019 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3020 ret = LISTBOX_Paint( descr, hdc );
3021 if( !wParam ) EndPaint( descr->self, &ps );
3023 return ret;
3024 case WM_SIZE:
3025 LISTBOX_UpdateSize( descr );
3026 return 0;
3027 case WM_GETFONT:
3028 return (LRESULT)descr->font;
3029 case WM_SETFONT:
3030 LISTBOX_SetFont( descr, (HFONT)wParam );
3031 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3032 return 0;
3033 case WM_SETFOCUS:
3034 descr->in_focus = TRUE;
3035 descr->caret_on = TRUE;
3036 if (descr->focus_item != -1)
3037 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3038 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3039 return 0;
3040 case WM_KILLFOCUS:
3041 descr->in_focus = FALSE;
3042 if ((descr->focus_item != -1) && descr->caret_on)
3043 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3044 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3045 return 0;
3046 case WM_HSCROLL:
3047 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3048 case WM_VSCROLL:
3049 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3050 case WM_MOUSEWHEEL:
3051 if (wParam & (MK_SHIFT | MK_CONTROL))
3052 return DefWindowProcW( descr->self, msg, wParam, lParam );
3053 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3054 case WM_LBUTTONDOWN:
3055 if (lphc)
3056 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3057 (INT16)LOWORD(lParam),
3058 (INT16)HIWORD(lParam) );
3059 return LISTBOX_HandleLButtonDown( descr, wParam,
3060 (INT16)LOWORD(lParam),
3061 (INT16)HIWORD(lParam) );
3062 case WM_LBUTTONDBLCLK:
3063 if (lphc)
3064 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3065 (INT16)LOWORD(lParam),
3066 (INT16)HIWORD(lParam) );
3067 if (descr->style & LBS_NOTIFY)
3068 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3069 return 0;
3070 case WM_MOUSEMOVE:
3071 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3073 BOOL captured = descr->captured;
3074 POINT mousePos;
3075 RECT clientRect;
3077 mousePos.x = (INT16)LOWORD(lParam);
3078 mousePos.y = (INT16)HIWORD(lParam);
3081 * If we are in a dropdown combobox, we simulate that
3082 * the mouse is captured to show the tracking of the item.
3084 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3085 descr->captured = TRUE;
3087 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3089 descr->captured = captured;
3091 else if (GetCapture() == descr->self)
3093 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3094 (INT16)HIWORD(lParam) );
3096 return 0;
3097 case WM_LBUTTONUP:
3098 if (lphc)
3100 POINT mousePos;
3101 RECT clientRect;
3104 * If the mouse button "up" is not in the listbox,
3105 * we make sure there is no selection by re-selecting the
3106 * item that was selected when the listbox was made visible.
3108 mousePos.x = (INT16)LOWORD(lParam);
3109 mousePos.y = (INT16)HIWORD(lParam);
3111 GetClientRect(descr->self, &clientRect);
3114 * When the user clicks outside the combobox and the focus
3115 * is lost, the owning combobox will send a fake buttonup with
3116 * 0xFFFFFFF as the mouse location, we must also revert the
3117 * selection to the original selection.
3119 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3120 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3122 return LISTBOX_HandleLButtonUp( descr );
3123 case WM_KEYDOWN:
3124 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3126 /* for some reason Windows makes it possible to
3127 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3129 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3130 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3131 && (wParam == VK_DOWN || wParam == VK_UP)) )
3133 COMBO_FlipListbox( lphc, FALSE, FALSE );
3134 return 0;
3137 return LISTBOX_HandleKeyDown( descr, wParam );
3138 case WM_CHAR:
3140 WCHAR charW;
3141 if(unicode)
3142 charW = (WCHAR)wParam;
3143 else
3145 CHAR charA = (CHAR)wParam;
3146 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3148 return LISTBOX_HandleChar( descr, charW );
3150 case WM_SYSTIMER:
3151 return LISTBOX_HandleSystemTimer( descr );
3152 case WM_ERASEBKGND:
3153 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3155 RECT rect;
3156 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3157 wParam, (LPARAM)descr->self );
3158 TRACE("hbrush = %p\n", hbrush);
3159 if(!hbrush)
3160 hbrush = GetSysColorBrush(COLOR_WINDOW);
3161 if(hbrush)
3163 GetClientRect(descr->self, &rect);
3164 FillRect((HDC)wParam, &rect, hbrush);
3167 return 1;
3168 case WM_DROPFILES:
3169 if( lphc ) return 0;
3170 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3171 SendMessageA( descr->owner, msg, wParam, lParam );
3173 case WM_NCDESTROY:
3174 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3175 lphc->hWndLBox = 0;
3176 break;
3178 case WM_NCACTIVATE:
3179 if (lphc) return 0;
3180 break;
3182 default:
3183 if ((msg >= WM_USER) && (msg < 0xc000))
3184 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3185 hwnd, msg, wParam, lParam );
3188 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3189 DefWindowProcA( hwnd, msg, wParam, lParam );
3192 /***********************************************************************
3193 * ListBoxWndProcA
3195 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3197 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3200 /***********************************************************************
3201 * ListBoxWndProcW
3203 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3205 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );