wined3d: Merge wined3d_surface_upload_data() into texture2d_upload_data().
[wine.git] / dlls / user32 / listbox.c
blob8df67634d3c54a5e1f07206dc688163bd04eee47
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * TODO:
21 * - LBS_NODATA
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "wine/unicode.h"
32 #include "user_private.h"
33 #include "controls.h"
34 #include "wine/exception.h"
35 #include "wine/debug.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
39 /* Items array granularity */
40 #define LB_ARRAY_GRANULARITY 16
42 /* Scrolling timeout in ms */
43 #define LB_SCROLL_TIMEOUT 50
45 /* Listbox system timer id */
46 #define LB_TIMER_ID 2
48 /* flag listbox changed while setredraw false - internal style */
49 #define LBS_DISPLAYCHANGED 0x80000000
51 /* Item structure */
52 typedef struct
54 LPWSTR str; /* Item text */
55 BOOL selected; /* Is item selected? */
56 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
57 ULONG_PTR data; /* User data */
58 } LB_ITEMDATA;
60 /* Listbox structure */
61 typedef struct
63 HWND self; /* Our own window handle */
64 HWND owner; /* Owner window to send notifications to */
65 UINT style; /* Window style */
66 INT width; /* Window width */
67 INT height; /* Window height */
68 LB_ITEMDATA *items; /* Array of items */
69 INT nb_items; /* Number of items */
70 INT top_item; /* Top visible item */
71 INT selected_item; /* Selected item */
72 INT focus_item; /* Item that has the focus */
73 INT anchor_item; /* Anchor item for extended selection */
74 INT item_height; /* Default item height */
75 INT page_size; /* Items per listbox page */
76 INT column_width; /* Column width for multi-column listboxes */
77 INT horz_extent; /* Horizontal extent */
78 INT horz_pos; /* Horizontal position */
79 INT nb_tabs; /* Number of tabs in array */
80 INT *tabs; /* Array of tabs */
81 INT avg_char_width; /* Average width of characters */
82 INT wheel_remain; /* Left over scroll amount */
83 BOOL caret_on; /* Is caret on? */
84 BOOL captured; /* Is mouse captured? */
85 BOOL in_focus;
86 HFONT font; /* Current font */
87 LCID locale; /* Current locale for string comparisons */
88 LPHEADCOMBO lphc; /* ComboLBox */
89 } LB_DESCR;
92 #define IS_OWNERDRAW(descr) \
93 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
95 #define HAS_STRINGS(descr) \
96 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
99 #define IS_MULTISELECT(descr) \
100 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
101 !((descr)->style & LBS_NOSEL))
103 #define SEND_NOTIFICATION(descr,code) \
104 (SendMessageW( (descr)->owner, WM_COMMAND, \
105 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
107 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
109 /* Current timer status */
110 typedef enum
112 LB_TIMER_NONE,
113 LB_TIMER_UP,
114 LB_TIMER_LEFT,
115 LB_TIMER_DOWN,
116 LB_TIMER_RIGHT
117 } TIMER_DIRECTION;
119 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
121 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
123 /*********************************************************************
124 * listbox class descriptor
126 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
127 const struct builtin_class_descr LISTBOX_builtin_class =
129 listboxW, /* name */
130 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
131 WINPROC_LISTBOX, /* proc */
132 sizeof(LB_DESCR *), /* extra */
133 IDC_ARROW, /* cursor */
134 0 /* brush */
138 /*********************************************************************
139 * combolbox class descriptor
141 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
142 const struct builtin_class_descr COMBOLBOX_builtin_class =
144 combolboxW, /* name */
145 CS_DBLCLKS | CS_SAVEBITS, /* style */
146 WINPROC_LISTBOX, /* proc */
147 sizeof(LB_DESCR *), /* extra */
148 IDC_ARROW, /* cursor */
149 0 /* brush */
153 /***********************************************************************
154 * LISTBOX_GetCurrentPageSize
156 * Return the current page size
158 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
160 INT i, height;
161 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
162 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
164 if ((height += descr->items[i].height) > descr->height) break;
166 if (i == descr->top_item) return 1;
167 else return i - descr->top_item;
171 /***********************************************************************
172 * LISTBOX_GetMaxTopIndex
174 * Return the maximum possible index for the top of the listbox.
176 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
178 INT max, page;
180 if (descr->style & LBS_OWNERDRAWVARIABLE)
182 page = descr->height;
183 for (max = descr->nb_items - 1; max >= 0; max--)
184 if ((page -= descr->items[max].height) < 0) break;
185 if (max < descr->nb_items - 1) max++;
187 else if (descr->style & LBS_MULTICOLUMN)
189 if ((page = descr->width / descr->column_width) < 1) page = 1;
190 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
191 max = (max - page) * descr->page_size;
193 else
195 max = descr->nb_items - descr->page_size;
197 if (max < 0) max = 0;
198 return max;
202 /***********************************************************************
203 * LISTBOX_UpdateScroll
205 * Update the scrollbars. Should be called whenever the content
206 * of the listbox changes.
208 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
210 SCROLLINFO info;
212 /* Check the listbox scroll bar flags individually before we call
213 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
214 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
215 scroll bar when we do not need one.
216 if (!(descr->style & WS_VSCROLL)) return;
219 /* It is important that we check descr->style, and not wnd->dwStyle,
220 for WS_VSCROLL, as the former is exactly the one passed in
221 argument to CreateWindow.
222 In Windows (and from now on in Wine :) a listbox created
223 with such a style (no WS_SCROLL) does not update
224 the scrollbar with listbox-related data, thus letting
225 the programmer use it for his/her own purposes. */
227 if (descr->style & LBS_NOREDRAW) return;
228 info.cbSize = sizeof(info);
230 if (descr->style & LBS_MULTICOLUMN)
232 info.nMin = 0;
233 info.nMax = (descr->nb_items - 1) / descr->page_size;
234 info.nPos = descr->top_item / descr->page_size;
235 info.nPage = descr->width / descr->column_width;
236 if (info.nPage < 1) info.nPage = 1;
237 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
238 if (descr->style & LBS_DISABLENOSCROLL)
239 info.fMask |= SIF_DISABLENOSCROLL;
240 if (descr->style & WS_HSCROLL)
241 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
242 info.nMax = 0;
243 info.fMask = SIF_RANGE;
244 if (descr->style & WS_VSCROLL)
245 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
247 else
249 info.nMin = 0;
250 info.nMax = descr->nb_items - 1;
251 info.nPos = descr->top_item;
252 info.nPage = LISTBOX_GetCurrentPageSize( descr );
253 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
254 if (descr->style & LBS_DISABLENOSCROLL)
255 info.fMask |= SIF_DISABLENOSCROLL;
256 if (descr->style & WS_VSCROLL)
257 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
259 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
261 info.nPos = descr->horz_pos;
262 info.nPage = descr->width;
263 info.fMask = SIF_POS | SIF_PAGE;
264 if (descr->style & LBS_DISABLENOSCROLL)
265 info.fMask |= SIF_DISABLENOSCROLL;
266 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
268 else
270 if (descr->style & LBS_DISABLENOSCROLL)
272 info.nMin = 0;
273 info.nMax = 0;
274 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
275 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
277 else
279 ShowScrollBar( descr->self, SB_HORZ, FALSE );
286 /***********************************************************************
287 * LISTBOX_SetTopItem
289 * Set the top item of the listbox, scrolling up or down if necessary.
291 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
293 INT max = LISTBOX_GetMaxTopIndex( descr );
295 TRACE("setting top item %d, scroll %d\n", index, scroll);
297 if (index > max) index = max;
298 if (index < 0) index = 0;
299 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
300 if (descr->top_item == index) return LB_OKAY;
301 if (scroll)
303 INT diff;
304 if (descr->style & LBS_MULTICOLUMN)
305 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
306 else if (descr->style & LBS_OWNERDRAWVARIABLE)
308 INT i;
309 diff = 0;
310 if (index > descr->top_item)
312 for (i = index - 1; i >= descr->top_item; i--)
313 diff -= descr->items[i].height;
315 else
317 for (i = index; i < descr->top_item; i++)
318 diff += descr->items[i].height;
321 else
322 diff = (descr->top_item - index) * descr->item_height;
324 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
325 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
327 else
328 InvalidateRect( descr->self, NULL, TRUE );
329 descr->top_item = index;
330 LISTBOX_UpdateScroll( descr );
331 return LB_OKAY;
335 /***********************************************************************
336 * LISTBOX_UpdatePage
338 * Update the page size. Should be called when the size of
339 * the client area or the item height changes.
341 static void LISTBOX_UpdatePage( LB_DESCR *descr )
343 INT page_size;
345 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
346 page_size = 1;
347 if (page_size == descr->page_size) return;
348 descr->page_size = page_size;
349 if (descr->style & LBS_MULTICOLUMN)
350 InvalidateRect( descr->self, NULL, TRUE );
351 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
355 /***********************************************************************
356 * LISTBOX_UpdateSize
358 * Update the size of the listbox. Should be called when the size of
359 * the client area changes.
361 static void LISTBOX_UpdateSize( LB_DESCR *descr )
363 RECT rect;
365 GetClientRect( descr->self, &rect );
366 descr->width = rect.right - rect.left;
367 descr->height = rect.bottom - rect.top;
368 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
370 INT remaining;
371 RECT rect;
373 GetWindowRect( descr->self, &rect );
374 if(descr->item_height != 0)
375 remaining = descr->height % descr->item_height;
376 else
377 remaining = 0;
378 if ((descr->height > descr->item_height) && remaining)
380 TRACE("[%p]: changing height %d -> %d\n",
381 descr->self, descr->height, descr->height - remaining );
382 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
383 rect.bottom - rect.top - remaining,
384 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
385 return;
388 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
389 LISTBOX_UpdatePage( descr );
390 LISTBOX_UpdateScroll( descr );
392 /* Invalidate the focused item so it will be repainted correctly */
393 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
395 InvalidateRect( descr->self, &rect, FALSE );
400 /***********************************************************************
401 * LISTBOX_GetItemRect
403 * Get the rectangle enclosing an item, in listbox client coordinates.
404 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
406 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
408 /* Index <= 0 is legal even on empty listboxes */
409 if (index && (index >= descr->nb_items))
411 SetRectEmpty(rect);
412 SetLastError(ERROR_INVALID_INDEX);
413 return LB_ERR;
415 SetRect( rect, 0, 0, descr->width, descr->height );
416 if (descr->style & LBS_MULTICOLUMN)
418 INT col = (index / descr->page_size) -
419 (descr->top_item / descr->page_size);
420 rect->left += col * descr->column_width;
421 rect->right = rect->left + descr->column_width;
422 rect->top += (index % descr->page_size) * descr->item_height;
423 rect->bottom = rect->top + descr->item_height;
425 else if (descr->style & LBS_OWNERDRAWVARIABLE)
427 INT i;
428 rect->right += descr->horz_pos;
429 if ((index >= 0) && (index < descr->nb_items))
431 if (index < descr->top_item)
433 for (i = descr->top_item-1; i >= index; i--)
434 rect->top -= descr->items[i].height;
436 else
438 for (i = descr->top_item; i < index; i++)
439 rect->top += descr->items[i].height;
441 rect->bottom = rect->top + descr->items[index].height;
445 else
447 rect->top += (index - descr->top_item) * descr->item_height;
448 rect->bottom = rect->top + descr->item_height;
449 rect->right += descr->horz_pos;
452 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
454 return ((rect->left < descr->width) && (rect->right > 0) &&
455 (rect->top < descr->height) && (rect->bottom > 0));
459 /***********************************************************************
460 * LISTBOX_GetItemFromPoint
462 * Return the item nearest from point (x,y) (in client coordinates).
464 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
466 INT index = descr->top_item;
468 if (!descr->nb_items) return -1; /* No items */
469 if (descr->style & LBS_OWNERDRAWVARIABLE)
471 INT pos = 0;
472 if (y >= 0)
474 while (index < descr->nb_items)
476 if ((pos += descr->items[index].height) > y) break;
477 index++;
480 else
482 while (index > 0)
484 index--;
485 if ((pos -= descr->items[index].height) <= y) break;
489 else if (descr->style & LBS_MULTICOLUMN)
491 if (y >= descr->item_height * descr->page_size) return -1;
492 if (y >= 0) index += y / descr->item_height;
493 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
494 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
496 else
498 index += (y / descr->item_height);
500 if (index < 0) return 0;
501 if (index >= descr->nb_items) return -1;
502 return index;
506 /***********************************************************************
507 * LISTBOX_PaintItem
509 * Paint an item.
511 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
512 INT index, UINT action, BOOL ignoreFocus )
514 LB_ITEMDATA *item = NULL;
515 if (index < descr->nb_items) item = &descr->items[index];
517 if (IS_OWNERDRAW(descr))
519 DRAWITEMSTRUCT dis;
520 RECT r;
521 HRGN hrgn;
523 if (!item)
525 if (action == ODA_FOCUS)
526 DrawFocusRect( hdc, rect );
527 else
528 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
529 return;
532 /* some programs mess with the clipping region when
533 drawing the item, *and* restore the previous region
534 after they are done, so a region has better to exist
535 else everything ends clipped */
536 GetClientRect(descr->self, &r);
537 hrgn = set_control_clipping( hdc, &r );
539 dis.CtlType = ODT_LISTBOX;
540 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
541 dis.hwndItem = descr->self;
542 dis.itemAction = action;
543 dis.hDC = hdc;
544 dis.itemID = index;
545 dis.itemState = 0;
546 if (item->selected) dis.itemState |= ODS_SELECTED;
547 if (!ignoreFocus && (descr->focus_item == index) &&
548 (descr->caret_on) &&
549 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
550 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
551 dis.itemData = item->data;
552 dis.rcItem = *rect;
553 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
554 descr->self, index, debugstr_w(item->str), action,
555 dis.itemState, wine_dbgstr_rect(rect) );
556 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
557 SelectClipRgn( hdc, hrgn );
558 if (hrgn) DeleteObject( hrgn );
560 else
562 COLORREF oldText = 0, oldBk = 0;
564 if (action == ODA_FOCUS)
566 DrawFocusRect( hdc, rect );
567 return;
569 if (item && item->selected)
571 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
572 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
575 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
576 descr->self, index, item ? debugstr_w(item->str) : "", action,
577 wine_dbgstr_rect(rect) );
578 if (!item)
579 ExtTextOutW( hdc, rect->left + 1, rect->top,
580 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
581 else if (!(descr->style & LBS_USETABSTOPS))
582 ExtTextOutW( hdc, rect->left + 1, rect->top,
583 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
584 strlenW(item->str), NULL );
585 else
587 /* Output empty string to paint background in the full width. */
588 ExtTextOutW( hdc, rect->left + 1, rect->top,
589 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
590 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
591 item->str, strlenW(item->str),
592 descr->nb_tabs, descr->tabs, 0);
594 if (item && item->selected)
596 SetBkColor( hdc, oldBk );
597 SetTextColor( hdc, oldText );
599 if (!ignoreFocus && (descr->focus_item == index) &&
600 (descr->caret_on) &&
601 (descr->in_focus)) DrawFocusRect( hdc, rect );
606 /***********************************************************************
607 * LISTBOX_SetRedraw
609 * Change the redraw flag.
611 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
613 if (on)
615 if (!(descr->style & LBS_NOREDRAW)) return;
616 descr->style &= ~LBS_NOREDRAW;
617 if (descr->style & LBS_DISPLAYCHANGED)
618 { /* page was changed while setredraw false, refresh automatically */
619 InvalidateRect(descr->self, NULL, TRUE);
620 if ((descr->top_item + descr->page_size) > descr->nb_items)
621 { /* reset top of page if less than number of items/page */
622 descr->top_item = descr->nb_items - descr->page_size;
623 if (descr->top_item < 0) descr->top_item = 0;
625 descr->style &= ~LBS_DISPLAYCHANGED;
627 LISTBOX_UpdateScroll( descr );
629 else descr->style |= LBS_NOREDRAW;
633 /***********************************************************************
634 * LISTBOX_RepaintItem
636 * Repaint a single item synchronously.
638 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
640 HDC hdc;
641 RECT rect;
642 HFONT oldFont = 0;
643 HBRUSH hbrush, oldBrush = 0;
645 /* Do not repaint the item if the item is not visible */
646 if (!IsWindowVisible(descr->self)) return;
647 if (descr->style & LBS_NOREDRAW)
649 descr->style |= LBS_DISPLAYCHANGED;
650 return;
652 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
653 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
654 if (descr->font) oldFont = SelectObject( hdc, descr->font );
655 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
656 (WPARAM)hdc, (LPARAM)descr->self );
657 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
658 if (!IsWindowEnabled(descr->self))
659 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
660 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
661 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
662 if (oldFont) SelectObject( hdc, oldFont );
663 if (oldBrush) SelectObject( hdc, oldBrush );
664 ReleaseDC( descr->self, hdc );
668 /***********************************************************************
669 * LISTBOX_DrawFocusRect
671 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
673 HDC hdc;
674 RECT rect;
675 HFONT oldFont = 0;
677 /* Do not repaint the item if the item is not visible */
678 if (!IsWindowVisible(descr->self)) return;
680 if (descr->focus_item == -1) return;
681 if (!descr->caret_on || !descr->in_focus) return;
683 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
684 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
685 if (descr->font) oldFont = SelectObject( hdc, descr->font );
686 if (!IsWindowEnabled(descr->self))
687 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
688 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
689 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
690 if (oldFont) SelectObject( hdc, oldFont );
691 ReleaseDC( descr->self, hdc );
695 /***********************************************************************
696 * LISTBOX_InitStorage
698 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
700 LB_ITEMDATA *item;
702 nb_items += LB_ARRAY_GRANULARITY - 1;
703 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
704 if (descr->items) {
705 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
706 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
707 nb_items * sizeof(LB_ITEMDATA));
709 else {
710 item = HeapAlloc( GetProcessHeap(), 0,
711 nb_items * sizeof(LB_ITEMDATA));
714 if (!item)
716 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
717 return LB_ERRSPACE;
719 descr->items = item;
720 return LB_OKAY;
724 /***********************************************************************
725 * LISTBOX_SetTabStops
727 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
729 INT i;
731 if (!(descr->style & LBS_USETABSTOPS))
733 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
734 return FALSE;
737 HeapFree( GetProcessHeap(), 0, descr->tabs );
738 if (!(descr->nb_tabs = count))
740 descr->tabs = NULL;
741 return TRUE;
743 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
744 descr->nb_tabs * sizeof(INT) )))
745 return FALSE;
746 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
748 /* convert into "dialog units"*/
749 for (i = 0; i < descr->nb_tabs; i++)
750 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
752 return TRUE;
756 /***********************************************************************
757 * LISTBOX_GetText
759 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
761 DWORD len;
763 if ((index < 0) || (index >= descr->nb_items))
765 SetLastError(ERROR_INVALID_INDEX);
766 return LB_ERR;
768 if (HAS_STRINGS(descr))
770 if (!buffer)
772 len = strlenW(descr->items[index].str);
773 if( unicode )
774 return len;
775 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
776 NULL, 0, NULL, NULL );
779 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
781 __TRY /* hide a Delphi bug that passes a read-only buffer */
783 if(unicode)
785 strcpyW( buffer, descr->items[index].str );
786 len = strlenW(buffer);
788 else
790 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
791 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
794 __EXCEPT_PAGE_FAULT
796 WARN( "got an invalid buffer (Delphi bug?)\n" );
797 SetLastError( ERROR_INVALID_PARAMETER );
798 return LB_ERR;
800 __ENDTRY
801 } else {
802 if (buffer)
803 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
804 len = sizeof(DWORD);
806 return len;
809 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
811 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
812 if (ret == CSTR_LESS_THAN)
813 return -1;
814 if (ret == CSTR_EQUAL)
815 return 0;
816 if (ret == CSTR_GREATER_THAN)
817 return 1;
818 return -1;
821 /***********************************************************************
822 * LISTBOX_FindStringPos
824 * Find the nearest string located before a given string in sort order.
825 * If 'exact' is TRUE, return an error if we don't get an exact match.
827 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
829 INT index, min, max, res;
831 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
832 min = 0;
833 max = descr->nb_items;
834 while (min != max)
836 index = (min + max) / 2;
837 if (HAS_STRINGS(descr))
838 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
839 else
841 COMPAREITEMSTRUCT cis;
842 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
844 cis.CtlType = ODT_LISTBOX;
845 cis.CtlID = id;
846 cis.hwndItem = descr->self;
847 /* note that some application (MetaStock) expects the second item
848 * to be in the listbox */
849 cis.itemID1 = -1;
850 cis.itemData1 = (ULONG_PTR)str;
851 cis.itemID2 = index;
852 cis.itemData2 = descr->items[index].data;
853 cis.dwLocaleId = descr->locale;
854 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
856 if (!res) return index;
857 if (res < 0) max = index;
858 else min = index + 1;
860 return exact ? -1 : max;
864 /***********************************************************************
865 * LISTBOX_FindFileStrPos
867 * Find the nearest string located before a given string in directory
868 * sort order (i.e. first files, then directories, then drives).
870 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
872 INT min, max, res;
874 if (!HAS_STRINGS(descr))
875 return LISTBOX_FindStringPos( descr, str, FALSE );
876 min = 0;
877 max = descr->nb_items;
878 while (min != max)
880 INT index = (min + max) / 2;
881 LPCWSTR p = descr->items[index].str;
882 if (*p == '[') /* drive or directory */
884 if (*str != '[') res = -1;
885 else if (p[1] == '-') /* drive */
887 if (str[1] == '-') res = str[2] - p[2];
888 else res = -1;
890 else /* directory */
892 if (str[1] == '-') res = 1;
893 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
896 else /* filename */
898 if (*str == '[') res = 1;
899 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
901 if (!res) return index;
902 if (res < 0) max = index;
903 else min = index + 1;
905 return max;
909 /***********************************************************************
910 * LISTBOX_FindString
912 * Find the item beginning with a given string.
914 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
916 INT i;
917 LB_ITEMDATA *item;
919 if (start >= descr->nb_items) start = -1;
920 item = descr->items + start + 1;
921 if (HAS_STRINGS(descr))
923 if (!str || ! str[0] ) return LB_ERR;
924 if (exact)
926 for (i = start + 1; i < descr->nb_items; i++, item++)
927 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
928 for (i = 0, item = descr->items; i <= start; i++, item++)
929 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
931 else
933 /* Special case for drives and directories: ignore prefix */
934 #define CHECK_DRIVE(item) \
935 if ((item)->str[0] == '[') \
937 if (!strncmpiW( str, (item)->str+1, len )) return i; \
938 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
939 return i; \
942 INT len = strlenW(str);
943 for (i = start + 1; i < descr->nb_items; i++, item++)
945 if (!strncmpiW( str, item->str, len )) return i;
946 CHECK_DRIVE(item);
948 for (i = 0, item = descr->items; i <= start; i++, item++)
950 if (!strncmpiW( str, item->str, len )) return i;
951 CHECK_DRIVE(item);
953 #undef CHECK_DRIVE
956 else
958 if (exact && (descr->style & LBS_SORT))
959 /* If sorted, use a WM_COMPAREITEM binary search */
960 return LISTBOX_FindStringPos( descr, str, TRUE );
962 /* Otherwise use a linear search */
963 for (i = start + 1; i < descr->nb_items; i++, item++)
964 if (item->data == (ULONG_PTR)str) return i;
965 for (i = 0, item = descr->items; i <= start; i++, item++)
966 if (item->data == (ULONG_PTR)str) return i;
968 return LB_ERR;
972 /***********************************************************************
973 * LISTBOX_GetSelCount
975 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
977 INT i, count;
978 const LB_ITEMDATA *item = descr->items;
980 if (!(descr->style & LBS_MULTIPLESEL) ||
981 (descr->style & LBS_NOSEL))
982 return LB_ERR;
983 for (i = count = 0; i < descr->nb_items; i++, item++)
984 if (item->selected) count++;
985 return count;
989 /***********************************************************************
990 * LISTBOX_GetSelItems
992 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
994 INT i, count;
995 const LB_ITEMDATA *item = descr->items;
997 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
998 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
999 if (item->selected) array[count++] = i;
1000 return count;
1004 /***********************************************************************
1005 * LISTBOX_Paint
1007 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1009 INT i, col_pos = descr->page_size - 1;
1010 RECT rect;
1011 RECT focusRect = {-1, -1, -1, -1};
1012 HFONT oldFont = 0;
1013 HBRUSH hbrush, oldBrush = 0;
1015 if (descr->style & LBS_NOREDRAW) return 0;
1017 SetRect( &rect, 0, 0, descr->width, descr->height );
1018 if (descr->style & LBS_MULTICOLUMN)
1019 rect.right = rect.left + descr->column_width;
1020 else if (descr->horz_pos)
1022 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1023 rect.right += descr->horz_pos;
1026 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1027 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1028 (WPARAM)hdc, (LPARAM)descr->self );
1029 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1030 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1032 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1033 (descr->in_focus))
1035 /* Special case for empty listbox: paint focus rect */
1036 rect.bottom = rect.top + descr->item_height;
1037 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1038 &rect, NULL, 0, NULL );
1039 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1040 rect.top = rect.bottom;
1043 /* Paint all the item, regarding the selection
1044 Focus state will be painted after */
1046 for (i = descr->top_item; i < descr->nb_items; i++)
1048 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1049 rect.bottom = rect.top + descr->item_height;
1050 else
1051 rect.bottom = rect.top + descr->items[i].height;
1053 /* keep the focus rect, to paint the focus item after */
1054 if (i == descr->focus_item)
1055 focusRect = rect;
1057 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1058 rect.top = rect.bottom;
1060 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1062 if (!IS_OWNERDRAW(descr))
1064 /* Clear the bottom of the column */
1065 if (rect.top < descr->height)
1067 rect.bottom = descr->height;
1068 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1069 &rect, NULL, 0, NULL );
1073 /* Go to the next column */
1074 rect.left += descr->column_width;
1075 rect.right += descr->column_width;
1076 rect.top = 0;
1077 col_pos = descr->page_size - 1;
1079 else
1081 col_pos--;
1082 if (rect.top >= descr->height) break;
1086 /* Paint the focus item now */
1087 if (focusRect.top != focusRect.bottom &&
1088 descr->caret_on && descr->in_focus)
1089 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1091 if (!IS_OWNERDRAW(descr))
1093 /* Clear the remainder of the client area */
1094 if (rect.top < descr->height)
1096 rect.bottom = descr->height;
1097 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1098 &rect, NULL, 0, NULL );
1100 if (rect.right < descr->width)
1102 rect.left = rect.right;
1103 rect.right = descr->width;
1104 rect.top = 0;
1105 rect.bottom = descr->height;
1106 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1107 &rect, NULL, 0, NULL );
1110 if (oldFont) SelectObject( hdc, oldFont );
1111 if (oldBrush) SelectObject( hdc, oldBrush );
1112 return 0;
1116 /***********************************************************************
1117 * LISTBOX_InvalidateItems
1119 * Invalidate all items from a given item. If the specified item is not
1120 * visible, nothing happens.
1122 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1124 RECT rect;
1126 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1128 if (descr->style & LBS_NOREDRAW)
1130 descr->style |= LBS_DISPLAYCHANGED;
1131 return;
1133 rect.bottom = descr->height;
1134 InvalidateRect( descr->self, &rect, TRUE );
1135 if (descr->style & LBS_MULTICOLUMN)
1137 /* Repaint the other columns */
1138 rect.left = rect.right;
1139 rect.right = descr->width;
1140 rect.top = 0;
1141 InvalidateRect( descr->self, &rect, TRUE );
1146 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1148 RECT rect;
1150 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1151 InvalidateRect( descr->self, &rect, TRUE );
1154 /***********************************************************************
1155 * LISTBOX_GetItemHeight
1157 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1159 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1161 if ((index < 0) || (index >= descr->nb_items))
1163 SetLastError(ERROR_INVALID_INDEX);
1164 return LB_ERR;
1166 return descr->items[index].height;
1168 else return descr->item_height;
1172 /***********************************************************************
1173 * LISTBOX_SetItemHeight
1175 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1177 if (height > MAXBYTE)
1178 return -1;
1180 if (!height) height = 1;
1182 if (descr->style & LBS_OWNERDRAWVARIABLE)
1184 if ((index < 0) || (index >= descr->nb_items))
1186 SetLastError(ERROR_INVALID_INDEX);
1187 return LB_ERR;
1189 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1190 descr->items[index].height = height;
1191 LISTBOX_UpdateScroll( descr );
1192 if (repaint)
1193 LISTBOX_InvalidateItems( descr, index );
1195 else if (height != descr->item_height)
1197 TRACE("[%p]: new height = %d\n", descr->self, height );
1198 descr->item_height = height;
1199 LISTBOX_UpdatePage( descr );
1200 LISTBOX_UpdateScroll( descr );
1201 if (repaint)
1202 InvalidateRect( descr->self, 0, TRUE );
1204 return LB_OKAY;
1208 /***********************************************************************
1209 * LISTBOX_SetHorizontalPos
1211 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1213 INT diff;
1215 if (pos > descr->horz_extent - descr->width)
1216 pos = descr->horz_extent - descr->width;
1217 if (pos < 0) pos = 0;
1218 if (!(diff = descr->horz_pos - pos)) return;
1219 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1220 descr->horz_pos = pos;
1221 LISTBOX_UpdateScroll( descr );
1222 if (abs(diff) < descr->width)
1224 RECT rect;
1225 /* Invalidate the focused item so it will be repainted correctly */
1226 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1227 InvalidateRect( descr->self, &rect, TRUE );
1228 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1229 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1231 else
1232 InvalidateRect( descr->self, NULL, TRUE );
1236 /***********************************************************************
1237 * LISTBOX_SetHorizontalExtent
1239 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1241 if (descr->style & LBS_MULTICOLUMN)
1242 return LB_OKAY;
1243 if (extent == descr->horz_extent) return LB_OKAY;
1244 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1245 descr->horz_extent = extent;
1246 if (descr->style & WS_HSCROLL) {
1247 SCROLLINFO info;
1248 info.cbSize = sizeof(info);
1249 info.nMin = 0;
1250 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1251 info.fMask = SIF_RANGE;
1252 if (descr->style & LBS_DISABLENOSCROLL)
1253 info.fMask |= SIF_DISABLENOSCROLL;
1254 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1256 if (descr->horz_pos > extent - descr->width)
1257 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1258 return LB_OKAY;
1262 /***********************************************************************
1263 * LISTBOX_SetColumnWidth
1265 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1267 if (width == descr->column_width) return LB_OKAY;
1268 TRACE("[%p]: new column width = %d\n", descr->self, width );
1269 descr->column_width = width;
1270 LISTBOX_UpdatePage( descr );
1271 return LB_OKAY;
1275 /***********************************************************************
1276 * LISTBOX_SetFont
1278 * Returns the item height.
1280 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1282 HDC hdc;
1283 HFONT oldFont = 0;
1284 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1285 SIZE sz;
1287 descr->font = font;
1289 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1291 ERR("unable to get DC.\n" );
1292 return 16;
1294 if (font) oldFont = SelectObject( hdc, font );
1295 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1296 if (oldFont) SelectObject( hdc, oldFont );
1297 ReleaseDC( descr->self, hdc );
1299 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1300 if (!IS_OWNERDRAW(descr))
1301 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1302 return sz.cy;
1306 /***********************************************************************
1307 * LISTBOX_MakeItemVisible
1309 * Make sure that a given item is partially or fully visible.
1311 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1313 INT top;
1315 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1317 if (index <= descr->top_item) top = index;
1318 else if (descr->style & LBS_MULTICOLUMN)
1320 INT cols = descr->width;
1321 if (!fully) cols += descr->column_width - 1;
1322 if (cols >= descr->column_width) cols /= descr->column_width;
1323 else cols = 1;
1324 if (index < descr->top_item + (descr->page_size * cols)) return;
1325 top = index - descr->page_size * (cols - 1);
1327 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1329 INT height = fully ? descr->items[index].height : 1;
1330 for (top = index; top > descr->top_item; top--)
1331 if ((height += descr->items[top-1].height) > descr->height) break;
1333 else
1335 if (index < descr->top_item + descr->page_size) return;
1336 if (!fully && (index == descr->top_item + descr->page_size) &&
1337 (descr->height > (descr->page_size * descr->item_height))) return;
1338 top = index - descr->page_size + 1;
1340 LISTBOX_SetTopItem( descr, top, TRUE );
1343 /***********************************************************************
1344 * LISTBOX_SetCaretIndex
1346 * NOTES
1347 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1350 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1352 INT oldfocus = descr->focus_item;
1354 TRACE("old focus %d, index %d\n", oldfocus, index);
1356 if (descr->style & LBS_NOSEL) return LB_ERR;
1357 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1358 if (index == oldfocus) return LB_OKAY;
1360 LISTBOX_DrawFocusRect( descr, FALSE );
1361 descr->focus_item = index;
1363 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1364 LISTBOX_DrawFocusRect( descr, TRUE );
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 (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1385 if (!descr->nb_items) return LB_OKAY;
1387 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1388 if (first < 0) first = 0;
1389 if (last < first) return LB_OKAY;
1391 if (on) /* Turn selection on */
1393 for (i = first; i <= last; i++)
1395 if (descr->items[i].selected) continue;
1396 descr->items[i].selected = TRUE;
1397 LISTBOX_InvalidateItemRect(descr, i);
1400 else /* Turn selection off */
1402 for (i = first; i <= last; i++)
1404 if (!descr->items[i].selected) continue;
1405 descr->items[i].selected = FALSE;
1406 LISTBOX_InvalidateItemRect(descr, i);
1409 return LB_OKAY;
1412 /***********************************************************************
1413 * LISTBOX_SetSelection
1415 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1416 BOOL on, BOOL send_notify )
1418 TRACE( "cur_sel=%d index=%d notify=%s\n",
1419 descr->selected_item, index, send_notify ? "YES" : "NO" );
1421 if (descr->style & LBS_NOSEL)
1423 descr->selected_item = index;
1424 return LB_ERR;
1426 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1427 if (descr->style & LBS_MULTIPLESEL)
1429 if (index == -1) /* Select all items */
1430 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1431 else /* Only one item */
1432 return LISTBOX_SelectItemRange( descr, index, index, on );
1434 else
1436 INT oldsel = descr->selected_item;
1437 if (index == oldsel) return LB_OKAY;
1438 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1439 if (index != -1) descr->items[index].selected = TRUE;
1440 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1441 descr->selected_item = index;
1442 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1443 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1444 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1445 else
1446 if( descr->lphc ) /* set selection change flag for parent combo */
1447 descr->lphc->wState |= CBF_SELCHANGE;
1449 return LB_OKAY;
1453 /***********************************************************************
1454 * LISTBOX_MoveCaret
1456 * Change the caret position and extend the selection to the new caret.
1458 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1460 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1462 if ((index < 0) || (index >= descr->nb_items))
1463 return;
1465 /* Important, repaint needs to be done in this order if
1466 you want to mimic Windows behavior:
1467 1. Remove the focus and paint the item
1468 2. Remove the selection and paint the item(s)
1469 3. Set the selection and repaint the item(s)
1470 4. Set the focus to 'index' and repaint the item */
1472 /* 1. remove the focus and repaint the item */
1473 LISTBOX_DrawFocusRect( descr, FALSE );
1475 /* 2. then turn off the previous selection */
1476 /* 3. repaint the new selected item */
1477 if (descr->style & LBS_EXTENDEDSEL)
1479 if (descr->anchor_item != -1)
1481 INT first = min( index, descr->anchor_item );
1482 INT last = max( index, descr->anchor_item );
1483 if (first > 0)
1484 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1485 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1486 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1489 else if (!(descr->style & LBS_MULTIPLESEL))
1491 /* Set selection to new caret item */
1492 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1495 /* 4. repaint the new item with the focus */
1496 descr->focus_item = index;
1497 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1498 LISTBOX_DrawFocusRect( descr, TRUE );
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 = HAS_STRINGS(descr) ? 0 : 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 = 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 LRESULT ret;
1599 if (HAS_STRINGS(descr))
1601 static const WCHAR empty_stringW[] = { 0 };
1602 if (!str) str = empty_stringW;
1603 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1605 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1606 return LB_ERRSPACE;
1608 strcpyW(new_str, str);
1611 if (index == -1) index = descr->nb_items;
1612 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1614 HeapFree( GetProcessHeap(), 0, new_str );
1615 return ret;
1618 TRACE("[%p]: added item %d %s\n",
1619 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1620 return index;
1624 /***********************************************************************
1625 * LISTBOX_DeleteItem
1627 * Delete the content of an item. 'index' must be a valid index.
1629 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1631 /* save the item data before it gets freed by LB_RESETCONTENT */
1632 ULONG_PTR item_data = descr->items[index].data;
1633 LPWSTR item_str = descr->items[index].str;
1635 if (!descr->nb_items)
1636 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
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) || item_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 = item_data;
1653 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1655 if (HAS_STRINGS(descr))
1656 HeapFree( GetProcessHeap(), 0, item_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 descr->nb_items--;
1676 LISTBOX_DeleteItem( descr, index );
1678 if (!descr->nb_items) return LB_OKAY;
1680 /* Remove the item */
1682 item = &descr->items[index];
1683 if (index < descr->nb_items)
1684 RtlMoveMemory( item, item + 1,
1685 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1686 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1688 /* Shrink the item array if possible */
1690 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1691 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1693 max_items -= LB_ARRAY_GRANULARITY;
1694 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1695 max_items * sizeof(LB_ITEMDATA) );
1696 if (item) descr->items = item;
1698 /* Repaint the items */
1700 LISTBOX_UpdateScroll( descr );
1701 /* if we removed the scrollbar, reset the top of the list
1702 (correct for owner-drawn ???) */
1703 if (descr->nb_items == descr->page_size)
1704 LISTBOX_SetTopItem( descr, 0, TRUE );
1706 /* Move selection and focused item */
1707 if (!IS_MULTISELECT(descr))
1709 if (index == descr->selected_item)
1710 descr->selected_item = -1;
1711 else if (index < descr->selected_item)
1713 descr->selected_item--;
1714 if (ISWIN31) /* win 31 do not change the selected item number */
1715 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1719 if (descr->focus_item >= descr->nb_items)
1721 descr->focus_item = descr->nb_items - 1;
1722 if (descr->focus_item < 0) descr->focus_item = 0;
1724 return LB_OKAY;
1728 /***********************************************************************
1729 * LISTBOX_ResetContent
1731 static void LISTBOX_ResetContent( LB_DESCR *descr )
1733 INT i;
1735 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1736 HeapFree( GetProcessHeap(), 0, descr->items );
1737 descr->nb_items = 0;
1738 descr->top_item = 0;
1739 descr->selected_item = -1;
1740 descr->focus_item = 0;
1741 descr->anchor_item = -1;
1742 descr->items = NULL;
1746 /***********************************************************************
1747 * LISTBOX_SetCount
1749 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1751 LRESULT ret;
1753 if (HAS_STRINGS(descr))
1755 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1756 return LB_ERR;
1759 /* FIXME: this is far from optimal... */
1760 if (count > descr->nb_items)
1762 while (count > descr->nb_items)
1763 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1764 return ret;
1766 else if (count < descr->nb_items)
1768 while (count < descr->nb_items)
1769 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1770 return ret;
1773 InvalidateRect( descr->self, NULL, TRUE );
1774 return LB_OKAY;
1778 /***********************************************************************
1779 * LISTBOX_Directory
1781 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1782 LPCWSTR filespec, BOOL long_names )
1784 HANDLE handle;
1785 LRESULT ret = LB_OKAY;
1786 WIN32_FIND_DATAW entry;
1787 int pos;
1788 LRESULT maxinsert = LB_ERR;
1790 /* don't scan directory if we just want drives exclusively */
1791 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1792 /* scan directory */
1793 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1795 int le = GetLastError();
1796 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1798 else
1802 WCHAR buffer[270];
1803 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1805 static const WCHAR bracketW[] = { ']',0 };
1806 static const WCHAR dotW[] = { '.',0 };
1807 if (!(attrib & DDL_DIRECTORY) ||
1808 !strcmpW( entry.cFileName, dotW )) continue;
1809 buffer[0] = '[';
1810 if (!long_names && entry.cAlternateFileName[0])
1811 strcpyW( buffer + 1, entry.cAlternateFileName );
1812 else
1813 strcpyW( buffer + 1, entry.cFileName );
1814 strcatW(buffer, bracketW);
1816 else /* not a directory */
1818 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1819 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1821 if ((attrib & DDL_EXCLUSIVE) &&
1822 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1823 continue;
1824 #undef ATTRIBS
1825 if (!long_names && entry.cAlternateFileName[0])
1826 strcpyW( buffer, entry.cAlternateFileName );
1827 else
1828 strcpyW( buffer, entry.cFileName );
1830 if (!long_names) CharLowerW( buffer );
1831 pos = LISTBOX_FindFileStrPos( descr, buffer );
1832 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1833 break;
1834 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1835 } while (FindNextFileW( handle, &entry ));
1836 FindClose( handle );
1839 if (ret >= 0)
1841 ret = maxinsert;
1843 /* scan drives */
1844 if (attrib & DDL_DRIVES)
1846 WCHAR buffer[] = {'[','-','a','-',']',0};
1847 WCHAR root[] = {'A',':','\\',0};
1848 int drive;
1849 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1851 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1852 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1853 break;
1857 return ret;
1861 /***********************************************************************
1862 * LISTBOX_HandleVScroll
1864 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1866 SCROLLINFO info;
1868 if (descr->style & LBS_MULTICOLUMN) return 0;
1869 switch(scrollReq)
1871 case SB_LINEUP:
1872 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1873 break;
1874 case SB_LINEDOWN:
1875 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1876 break;
1877 case SB_PAGEUP:
1878 LISTBOX_SetTopItem( descr, descr->top_item -
1879 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1880 break;
1881 case SB_PAGEDOWN:
1882 LISTBOX_SetTopItem( descr, descr->top_item +
1883 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1884 break;
1885 case SB_THUMBPOSITION:
1886 LISTBOX_SetTopItem( descr, pos, TRUE );
1887 break;
1888 case SB_THUMBTRACK:
1889 info.cbSize = sizeof(info);
1890 info.fMask = SIF_TRACKPOS;
1891 GetScrollInfo( descr->self, SB_VERT, &info );
1892 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1893 break;
1894 case SB_TOP:
1895 LISTBOX_SetTopItem( descr, 0, TRUE );
1896 break;
1897 case SB_BOTTOM:
1898 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1899 break;
1901 return 0;
1905 /***********************************************************************
1906 * LISTBOX_HandleHScroll
1908 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1910 SCROLLINFO info;
1911 INT page;
1913 if (descr->style & LBS_MULTICOLUMN)
1915 switch(scrollReq)
1917 case SB_LINELEFT:
1918 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1919 TRUE );
1920 break;
1921 case SB_LINERIGHT:
1922 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1923 TRUE );
1924 break;
1925 case SB_PAGELEFT:
1926 page = descr->width / descr->column_width;
1927 if (page < 1) page = 1;
1928 LISTBOX_SetTopItem( descr,
1929 descr->top_item - page * descr->page_size, TRUE );
1930 break;
1931 case SB_PAGERIGHT:
1932 page = descr->width / descr->column_width;
1933 if (page < 1) page = 1;
1934 LISTBOX_SetTopItem( descr,
1935 descr->top_item + page * descr->page_size, TRUE );
1936 break;
1937 case SB_THUMBPOSITION:
1938 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1939 break;
1940 case SB_THUMBTRACK:
1941 info.cbSize = sizeof(info);
1942 info.fMask = SIF_TRACKPOS;
1943 GetScrollInfo( descr->self, SB_VERT, &info );
1944 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1945 TRUE );
1946 break;
1947 case SB_LEFT:
1948 LISTBOX_SetTopItem( descr, 0, TRUE );
1949 break;
1950 case SB_RIGHT:
1951 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1952 break;
1955 else if (descr->horz_extent)
1957 switch(scrollReq)
1959 case SB_LINELEFT:
1960 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1961 break;
1962 case SB_LINERIGHT:
1963 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1964 break;
1965 case SB_PAGELEFT:
1966 LISTBOX_SetHorizontalPos( descr,
1967 descr->horz_pos - descr->width );
1968 break;
1969 case SB_PAGERIGHT:
1970 LISTBOX_SetHorizontalPos( descr,
1971 descr->horz_pos + descr->width );
1972 break;
1973 case SB_THUMBPOSITION:
1974 LISTBOX_SetHorizontalPos( descr, pos );
1975 break;
1976 case SB_THUMBTRACK:
1977 info.cbSize = sizeof(info);
1978 info.fMask = SIF_TRACKPOS;
1979 GetScrollInfo( descr->self, SB_HORZ, &info );
1980 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1981 break;
1982 case SB_LEFT:
1983 LISTBOX_SetHorizontalPos( descr, 0 );
1984 break;
1985 case SB_RIGHT:
1986 LISTBOX_SetHorizontalPos( descr,
1987 descr->horz_extent - descr->width );
1988 break;
1991 return 0;
1994 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1996 UINT pulScrollLines = 3;
1998 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2000 /* if scrolling changes direction, ignore left overs */
2001 if ((delta < 0 && descr->wheel_remain < 0) ||
2002 (delta > 0 && descr->wheel_remain > 0))
2003 descr->wheel_remain += delta;
2004 else
2005 descr->wheel_remain = delta;
2007 if (descr->wheel_remain && pulScrollLines)
2009 int cLineScroll;
2010 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2011 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2012 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2013 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2015 return 0;
2018 /***********************************************************************
2019 * LISTBOX_HandleLButtonDown
2021 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2023 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2025 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2026 descr->self, x, y, index, descr->focus_item);
2028 if (!descr->caret_on && (descr->in_focus)) return 0;
2030 if (!descr->in_focus)
2032 if( !descr->lphc ) SetFocus( descr->self );
2033 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2036 if (index == -1) return 0;
2038 if (!descr->lphc)
2040 if (descr->style & LBS_NOTIFY )
2041 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2042 MAKELPARAM( x, y ) );
2045 descr->captured = TRUE;
2046 SetCapture( descr->self );
2048 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2050 /* we should perhaps make sure that all items are deselected
2051 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2052 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2053 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2056 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2057 if (keys & MK_CONTROL)
2059 LISTBOX_SetCaretIndex( descr, index, FALSE );
2060 LISTBOX_SetSelection( descr, index,
2061 !descr->items[index].selected,
2062 (descr->style & LBS_NOTIFY) != 0);
2064 else
2066 LISTBOX_MoveCaret( descr, index, FALSE );
2068 if (descr->style & LBS_EXTENDEDSEL)
2070 LISTBOX_SetSelection( descr, index,
2071 descr->items[index].selected,
2072 (descr->style & LBS_NOTIFY) != 0 );
2074 else
2076 LISTBOX_SetSelection( descr, index,
2077 !descr->items[index].selected,
2078 (descr->style & LBS_NOTIFY) != 0 );
2082 else
2084 descr->anchor_item = index;
2085 LISTBOX_MoveCaret( descr, index, FALSE );
2086 LISTBOX_SetSelection( descr, index,
2087 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2090 if (!descr->lphc)
2092 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2094 POINT pt;
2096 pt.x = x;
2097 pt.y = y;
2099 if (DragDetect( descr->self, pt ))
2100 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2103 return 0;
2107 /*************************************************************************
2108 * LISTBOX_HandleLButtonDownCombo [Internal]
2110 * Process LButtonDown message for the ComboListBox
2112 * PARAMS
2113 * pWnd [I] The windows internal structure
2114 * pDescr [I] The ListBox internal structure
2115 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2116 * x [I] X Mouse Coordinate
2117 * y [I] Y Mouse Coordinate
2119 * RETURNS
2120 * 0 since we are processing the WM_LBUTTONDOWN Message
2122 * NOTES
2123 * This function is only to be used when a ListBox is a ComboListBox
2126 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2128 RECT clientRect, screenRect;
2129 POINT mousePos;
2131 mousePos.x = x;
2132 mousePos.y = y;
2134 GetClientRect(descr->self, &clientRect);
2136 if(PtInRect(&clientRect, mousePos))
2138 /* MousePos is in client, resume normal processing */
2139 if (msg == WM_LBUTTONDOWN)
2141 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2142 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2144 else if (descr->style & LBS_NOTIFY)
2145 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2147 else
2149 POINT screenMousePos;
2150 HWND hWndOldCapture;
2152 /* Check the Non-Client Area */
2153 screenMousePos = mousePos;
2154 hWndOldCapture = GetCapture();
2155 ReleaseCapture();
2156 GetWindowRect(descr->self, &screenRect);
2157 ClientToScreen(descr->self, &screenMousePos);
2159 if(!PtInRect(&screenRect, screenMousePos))
2161 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2162 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2163 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2165 else
2167 /* Check to see the NC is a scrollbar */
2168 INT nHitTestType=0;
2169 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2170 /* Check Vertical scroll bar */
2171 if (style & WS_VSCROLL)
2173 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2174 if (PtInRect( &clientRect, mousePos ))
2175 nHitTestType = HTVSCROLL;
2177 /* Check horizontal scroll bar */
2178 if (style & WS_HSCROLL)
2180 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2181 if (PtInRect( &clientRect, mousePos ))
2182 nHitTestType = HTHSCROLL;
2184 /* Windows sends this message when a scrollbar is clicked
2187 if(nHitTestType != 0)
2189 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2190 MAKELONG(screenMousePos.x, screenMousePos.y));
2192 /* Resume the Capture after scrolling is complete
2194 if(hWndOldCapture != 0)
2195 SetCapture(hWndOldCapture);
2198 return 0;
2201 /***********************************************************************
2202 * LISTBOX_HandleLButtonUp
2204 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2206 if (LISTBOX_Timer != LB_TIMER_NONE)
2207 KillSystemTimer( descr->self, LB_TIMER_ID );
2208 LISTBOX_Timer = LB_TIMER_NONE;
2209 if (descr->captured)
2211 descr->captured = FALSE;
2212 if (GetCapture() == descr->self) ReleaseCapture();
2213 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2214 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2216 return 0;
2220 /***********************************************************************
2221 * LISTBOX_HandleTimer
2223 * Handle scrolling upon a timer event.
2224 * Return TRUE if scrolling should continue.
2226 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2228 switch(dir)
2230 case LB_TIMER_UP:
2231 if (descr->top_item) index = descr->top_item - 1;
2232 else index = 0;
2233 break;
2234 case LB_TIMER_LEFT:
2235 if (descr->top_item) index -= descr->page_size;
2236 break;
2237 case LB_TIMER_DOWN:
2238 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2239 if (index == descr->focus_item) index++;
2240 if (index >= descr->nb_items) index = descr->nb_items - 1;
2241 break;
2242 case LB_TIMER_RIGHT:
2243 if (index + descr->page_size < descr->nb_items)
2244 index += descr->page_size;
2245 break;
2246 case LB_TIMER_NONE:
2247 break;
2249 if (index == descr->focus_item) return FALSE;
2250 LISTBOX_MoveCaret( descr, index, FALSE );
2251 return TRUE;
2255 /***********************************************************************
2256 * LISTBOX_HandleSystemTimer
2258 * WM_SYSTIMER handler.
2260 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2262 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2264 KillSystemTimer( descr->self, LB_TIMER_ID );
2265 LISTBOX_Timer = LB_TIMER_NONE;
2267 return 0;
2271 /***********************************************************************
2272 * LISTBOX_HandleMouseMove
2274 * WM_MOUSEMOVE handler.
2276 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2277 INT x, INT y )
2279 INT index;
2280 TIMER_DIRECTION dir = LB_TIMER_NONE;
2282 if (!descr->captured) return;
2284 if (descr->style & LBS_MULTICOLUMN)
2286 if (y < 0) y = 0;
2287 else if (y >= descr->item_height * descr->page_size)
2288 y = descr->item_height * descr->page_size - 1;
2290 if (x < 0)
2292 dir = LB_TIMER_LEFT;
2293 x = 0;
2295 else if (x >= descr->width)
2297 dir = LB_TIMER_RIGHT;
2298 x = descr->width - 1;
2301 else
2303 if (y < 0) dir = LB_TIMER_UP; /* above */
2304 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2307 index = LISTBOX_GetItemFromPoint( descr, x, y );
2308 if (index == -1) index = descr->focus_item;
2309 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2311 /* Start/stop the system timer */
2313 if (dir != LB_TIMER_NONE)
2314 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2315 else if (LISTBOX_Timer != LB_TIMER_NONE)
2316 KillSystemTimer( descr->self, LB_TIMER_ID );
2317 LISTBOX_Timer = dir;
2321 /***********************************************************************
2322 * LISTBOX_HandleKeyDown
2324 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2326 INT caret = -1;
2327 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2328 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2329 bForceSelection = FALSE; /* only for single select list */
2331 if (descr->style & LBS_WANTKEYBOARDINPUT)
2333 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2334 MAKEWPARAM(LOWORD(key), descr->focus_item),
2335 (LPARAM)descr->self );
2336 if (caret == -2) return 0;
2338 if (caret == -1) switch(key)
2340 case VK_LEFT:
2341 if (descr->style & LBS_MULTICOLUMN)
2343 bForceSelection = FALSE;
2344 if (descr->focus_item >= descr->page_size)
2345 caret = descr->focus_item - descr->page_size;
2346 break;
2348 /* fall through */
2349 case VK_UP:
2350 caret = descr->focus_item - 1;
2351 if (caret < 0) caret = 0;
2352 break;
2353 case VK_RIGHT:
2354 if (descr->style & LBS_MULTICOLUMN)
2356 bForceSelection = FALSE;
2357 if (descr->focus_item + descr->page_size < descr->nb_items)
2358 caret = descr->focus_item + descr->page_size;
2359 break;
2361 /* fall through */
2362 case VK_DOWN:
2363 caret = descr->focus_item + 1;
2364 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2365 break;
2367 case VK_PRIOR:
2368 if (descr->style & LBS_MULTICOLUMN)
2370 INT page = descr->width / descr->column_width;
2371 if (page < 1) page = 1;
2372 caret = descr->focus_item - (page * descr->page_size) + 1;
2374 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2375 if (caret < 0) caret = 0;
2376 break;
2377 case VK_NEXT:
2378 if (descr->style & LBS_MULTICOLUMN)
2380 INT page = descr->width / descr->column_width;
2381 if (page < 1) page = 1;
2382 caret = descr->focus_item + (page * descr->page_size) - 1;
2384 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2385 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2386 break;
2387 case VK_HOME:
2388 caret = 0;
2389 break;
2390 case VK_END:
2391 caret = descr->nb_items - 1;
2392 break;
2393 case VK_SPACE:
2394 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2395 else if (descr->style & LBS_MULTIPLESEL)
2397 LISTBOX_SetSelection( descr, descr->focus_item,
2398 !descr->items[descr->focus_item].selected,
2399 (descr->style & LBS_NOTIFY) != 0 );
2401 break;
2402 default:
2403 bForceSelection = FALSE;
2405 if (bForceSelection) /* focused item is used instead of key */
2406 caret = descr->focus_item;
2407 if (caret >= 0)
2409 if (((descr->style & LBS_EXTENDEDSEL) &&
2410 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2411 !IS_MULTISELECT(descr))
2412 descr->anchor_item = caret;
2413 LISTBOX_MoveCaret( descr, caret, TRUE );
2415 if (descr->style & LBS_MULTIPLESEL)
2416 descr->selected_item = caret;
2417 else
2418 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2419 if (descr->style & LBS_NOTIFY)
2421 if (descr->lphc && IsWindowVisible( descr->self ))
2423 /* make sure that combo parent doesn't hide us */
2424 descr->lphc->wState |= CBF_NOROLLUP;
2426 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2429 return 0;
2433 /***********************************************************************
2434 * LISTBOX_HandleChar
2436 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2438 INT caret = -1;
2439 WCHAR str[2];
2441 str[0] = charW;
2442 str[1] = '\0';
2444 if (descr->style & LBS_WANTKEYBOARDINPUT)
2446 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2447 MAKEWPARAM(charW, descr->focus_item),
2448 (LPARAM)descr->self );
2449 if (caret == -2) return 0;
2451 if (caret == -1)
2452 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2453 if (caret != -1)
2455 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2456 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2457 LISTBOX_MoveCaret( descr, caret, TRUE );
2458 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2459 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2461 return 0;
2465 /***********************************************************************
2466 * LISTBOX_Create
2468 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2470 LB_DESCR *descr;
2471 MEASUREITEMSTRUCT mis;
2472 RECT rect;
2474 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2475 return FALSE;
2477 GetClientRect( hwnd, &rect );
2478 descr->self = hwnd;
2479 descr->owner = GetParent( descr->self );
2480 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2481 descr->width = rect.right - rect.left;
2482 descr->height = rect.bottom - rect.top;
2483 descr->items = NULL;
2484 descr->nb_items = 0;
2485 descr->top_item = 0;
2486 descr->selected_item = -1;
2487 descr->focus_item = 0;
2488 descr->anchor_item = -1;
2489 descr->item_height = 1;
2490 descr->page_size = 1;
2491 descr->column_width = 150;
2492 descr->horz_extent = 0;
2493 descr->horz_pos = 0;
2494 descr->nb_tabs = 0;
2495 descr->tabs = NULL;
2496 descr->wheel_remain = 0;
2497 descr->caret_on = !lphc;
2498 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2499 descr->in_focus = FALSE;
2500 descr->captured = FALSE;
2501 descr->font = 0;
2502 descr->locale = GetUserDefaultLCID();
2503 descr->lphc = lphc;
2505 if( lphc )
2507 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2508 descr->owner = lphc->self;
2511 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2513 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2515 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2516 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2517 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2518 descr->item_height = LISTBOX_SetFont( descr, 0 );
2520 if (descr->style & LBS_OWNERDRAWFIXED)
2522 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2524 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2525 descr->item_height = lphc->fixedOwnerDrawHeight;
2527 else
2529 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2530 mis.CtlType = ODT_LISTBOX;
2531 mis.CtlID = id;
2532 mis.itemID = -1;
2533 mis.itemWidth = 0;
2534 mis.itemData = 0;
2535 mis.itemHeight = descr->item_height;
2536 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2537 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2541 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2542 return TRUE;
2546 /***********************************************************************
2547 * LISTBOX_Destroy
2549 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2551 LISTBOX_ResetContent( descr );
2552 SetWindowLongPtrW( descr->self, 0, 0 );
2553 HeapFree( GetProcessHeap(), 0, descr );
2554 return TRUE;
2558 /***********************************************************************
2559 * ListBoxWndProc_common
2561 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2563 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2564 LPHEADCOMBO lphc = 0;
2565 LRESULT ret;
2567 if (!descr)
2569 if (!IsWindow(hwnd)) return 0;
2571 if (msg == WM_CREATE)
2573 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2574 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2575 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2576 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2577 return 0;
2579 /* Ignore all other messages before we get a WM_CREATE */
2580 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2581 DefWindowProcA( hwnd, msg, wParam, lParam );
2583 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2585 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2586 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2588 switch(msg)
2590 case LB_RESETCONTENT:
2591 LISTBOX_ResetContent( descr );
2592 LISTBOX_UpdateScroll( descr );
2593 InvalidateRect( descr->self, NULL, TRUE );
2594 return 0;
2596 case LB_ADDSTRING:
2598 INT ret;
2599 LPWSTR textW;
2600 if(unicode || !HAS_STRINGS(descr))
2601 textW = (LPWSTR)lParam;
2602 else
2604 LPSTR textA = (LPSTR)lParam;
2605 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2606 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2607 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2608 else
2609 return LB_ERRSPACE;
2611 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2612 ret = LISTBOX_InsertString( descr, wParam, textW );
2613 if (!unicode && HAS_STRINGS(descr))
2614 HeapFree(GetProcessHeap(), 0, textW);
2615 return ret;
2618 case LB_INSERTSTRING:
2620 INT ret;
2621 LPWSTR textW;
2622 if(unicode || !HAS_STRINGS(descr))
2623 textW = (LPWSTR)lParam;
2624 else
2626 LPSTR textA = (LPSTR)lParam;
2627 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2628 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2629 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2630 else
2631 return LB_ERRSPACE;
2633 ret = LISTBOX_InsertString( descr, wParam, textW );
2634 if(!unicode && HAS_STRINGS(descr))
2635 HeapFree(GetProcessHeap(), 0, textW);
2636 return ret;
2639 case LB_ADDFILE:
2641 INT ret;
2642 LPWSTR textW;
2643 if(unicode || !HAS_STRINGS(descr))
2644 textW = (LPWSTR)lParam;
2645 else
2647 LPSTR textA = (LPSTR)lParam;
2648 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2649 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2650 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2651 else
2652 return LB_ERRSPACE;
2654 wParam = LISTBOX_FindFileStrPos( descr, textW );
2655 ret = LISTBOX_InsertString( descr, wParam, textW );
2656 if(!unicode && HAS_STRINGS(descr))
2657 HeapFree(GetProcessHeap(), 0, textW);
2658 return ret;
2661 case LB_DELETESTRING:
2662 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2663 return descr->nb_items;
2664 else
2666 SetLastError(ERROR_INVALID_INDEX);
2667 return LB_ERR;
2670 case LB_GETITEMDATA:
2671 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2673 SetLastError(ERROR_INVALID_INDEX);
2674 return LB_ERR;
2676 return descr->items[wParam].data;
2678 case LB_SETITEMDATA:
2679 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2681 SetLastError(ERROR_INVALID_INDEX);
2682 return LB_ERR;
2684 descr->items[wParam].data = lParam;
2685 /* undocumented: returns TRUE, not LB_OKAY (0) */
2686 return TRUE;
2688 case LB_GETCOUNT:
2689 return descr->nb_items;
2691 case LB_GETTEXT:
2692 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2694 case LB_GETTEXTLEN:
2695 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2697 SetLastError(ERROR_INVALID_INDEX);
2698 return LB_ERR;
2700 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2701 if (unicode) return strlenW( descr->items[wParam].str );
2702 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2703 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2705 case LB_GETCURSEL:
2706 if (descr->nb_items == 0)
2707 return LB_ERR;
2708 if (!IS_MULTISELECT(descr))
2709 return descr->selected_item;
2710 if (descr->selected_item != -1)
2711 return descr->selected_item;
2712 return descr->focus_item;
2713 /* otherwise, if the user tries to move the selection with the */
2714 /* arrow keys, we will give the application something to choke on */
2715 case LB_GETTOPINDEX:
2716 return descr->top_item;
2718 case LB_GETITEMHEIGHT:
2719 return LISTBOX_GetItemHeight( descr, wParam );
2721 case LB_SETITEMHEIGHT:
2722 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2724 case LB_ITEMFROMPOINT:
2726 POINT pt;
2727 RECT rect;
2728 int index;
2729 BOOL hit = TRUE;
2731 /* The hiword of the return value is not a client area
2732 hittest as suggested by MSDN, but rather a hittest on
2733 the returned listbox item. */
2735 if(descr->nb_items == 0)
2736 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2738 pt.x = (short)LOWORD(lParam);
2739 pt.y = (short)HIWORD(lParam);
2741 SetRect(&rect, 0, 0, descr->width, descr->height);
2743 if(!PtInRect(&rect, pt))
2745 pt.x = min(pt.x, rect.right - 1);
2746 pt.x = max(pt.x, 0);
2747 pt.y = min(pt.y, rect.bottom - 1);
2748 pt.y = max(pt.y, 0);
2749 hit = FALSE;
2752 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2754 if(index == -1)
2756 index = descr->nb_items - 1;
2757 hit = FALSE;
2759 return MAKELONG(index, hit ? 0 : 1);
2762 case LB_SETCARETINDEX:
2763 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2764 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2765 return LB_ERR;
2766 else if (ISWIN31)
2767 return wParam;
2768 else
2769 return LB_OKAY;
2771 case LB_GETCARETINDEX:
2772 return descr->focus_item;
2774 case LB_SETTOPINDEX:
2775 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2777 case LB_SETCOLUMNWIDTH:
2778 return LISTBOX_SetColumnWidth( descr, wParam );
2780 case LB_GETITEMRECT:
2781 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2783 case LB_FINDSTRING:
2785 INT ret;
2786 LPWSTR textW;
2787 if(unicode || !HAS_STRINGS(descr))
2788 textW = (LPWSTR)lParam;
2789 else
2791 LPSTR textA = (LPSTR)lParam;
2792 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2793 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2794 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2796 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2797 if(!unicode && HAS_STRINGS(descr))
2798 HeapFree(GetProcessHeap(), 0, textW);
2799 return ret;
2802 case LB_FINDSTRINGEXACT:
2804 INT ret;
2805 LPWSTR textW;
2806 if(unicode || !HAS_STRINGS(descr))
2807 textW = (LPWSTR)lParam;
2808 else
2810 LPSTR textA = (LPSTR)lParam;
2811 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2812 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2813 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2815 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2816 if(!unicode && HAS_STRINGS(descr))
2817 HeapFree(GetProcessHeap(), 0, textW);
2818 return ret;
2821 case LB_SELECTSTRING:
2823 INT index;
2824 LPWSTR textW;
2826 if(HAS_STRINGS(descr))
2827 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2828 debugstr_a((LPSTR)lParam));
2829 if(unicode || !HAS_STRINGS(descr))
2830 textW = (LPWSTR)lParam;
2831 else
2833 LPSTR textA = (LPSTR)lParam;
2834 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2835 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2836 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2838 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2839 if(!unicode && HAS_STRINGS(descr))
2840 HeapFree(GetProcessHeap(), 0, textW);
2841 if (index != LB_ERR)
2843 LISTBOX_MoveCaret( descr, index, TRUE );
2844 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2846 return index;
2849 case LB_GETSEL:
2850 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2851 return LB_ERR;
2852 return descr->items[wParam].selected;
2854 case LB_SETSEL:
2855 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2857 case LB_SETCURSEL:
2858 if (IS_MULTISELECT(descr)) return LB_ERR;
2859 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2860 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2861 if (ret != LB_ERR) ret = descr->selected_item;
2862 return ret;
2864 case LB_GETSELCOUNT:
2865 return LISTBOX_GetSelCount( descr );
2867 case LB_GETSELITEMS:
2868 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2870 case LB_SELITEMRANGE:
2871 if (LOWORD(lParam) <= HIWORD(lParam))
2872 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2873 HIWORD(lParam), wParam );
2874 else
2875 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2876 LOWORD(lParam), wParam );
2878 case LB_SELITEMRANGEEX:
2879 if ((INT)lParam >= (INT)wParam)
2880 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2881 else
2882 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2884 case LB_GETHORIZONTALEXTENT:
2885 return descr->horz_extent;
2887 case LB_SETHORIZONTALEXTENT:
2888 return LISTBOX_SetHorizontalExtent( descr, wParam );
2890 case LB_GETANCHORINDEX:
2891 return descr->anchor_item;
2893 case LB_SETANCHORINDEX:
2894 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2896 SetLastError(ERROR_INVALID_INDEX);
2897 return LB_ERR;
2899 descr->anchor_item = (INT)wParam;
2900 return LB_OKAY;
2902 case LB_DIR:
2904 INT ret;
2905 LPWSTR textW;
2906 if(unicode)
2907 textW = (LPWSTR)lParam;
2908 else
2910 LPSTR textA = (LPSTR)lParam;
2911 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2912 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2913 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2915 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2916 if(!unicode)
2917 HeapFree(GetProcessHeap(), 0, textW);
2918 return ret;
2921 case LB_GETLOCALE:
2922 return descr->locale;
2924 case LB_SETLOCALE:
2926 LCID ret;
2927 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2928 return LB_ERR;
2929 ret = descr->locale;
2930 descr->locale = (LCID)wParam;
2931 return ret;
2934 case LB_INITSTORAGE:
2935 return LISTBOX_InitStorage( descr, wParam );
2937 case LB_SETCOUNT:
2938 return LISTBOX_SetCount( descr, (INT)wParam );
2940 case LB_SETTABSTOPS:
2941 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2943 case LB_CARETON:
2944 if (descr->caret_on)
2945 return LB_OKAY;
2946 descr->caret_on = TRUE;
2947 if ((descr->focus_item != -1) && (descr->in_focus))
2948 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2949 return LB_OKAY;
2951 case LB_CARETOFF:
2952 if (!descr->caret_on)
2953 return LB_OKAY;
2954 descr->caret_on = FALSE;
2955 if ((descr->focus_item != -1) && (descr->in_focus))
2956 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2957 return LB_OKAY;
2959 case LB_GETLISTBOXINFO:
2960 return descr->page_size;
2962 case WM_DESTROY:
2963 return LISTBOX_Destroy( descr );
2965 case WM_ENABLE:
2966 InvalidateRect( descr->self, NULL, TRUE );
2967 return 0;
2969 case WM_SETREDRAW:
2970 LISTBOX_SetRedraw( descr, wParam != 0 );
2971 return 0;
2973 case WM_GETDLGCODE:
2974 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2976 case WM_PRINTCLIENT:
2977 case WM_PAINT:
2979 PAINTSTRUCT ps;
2980 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2981 ret = LISTBOX_Paint( descr, hdc );
2982 if( !wParam ) EndPaint( descr->self, &ps );
2984 return ret;
2985 case WM_SIZE:
2986 LISTBOX_UpdateSize( descr );
2987 return 0;
2988 case WM_GETFONT:
2989 return (LRESULT)descr->font;
2990 case WM_SETFONT:
2991 LISTBOX_SetFont( descr, (HFONT)wParam );
2992 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2993 return 0;
2994 case WM_SETFOCUS:
2995 descr->in_focus = TRUE;
2996 descr->caret_on = TRUE;
2997 if (descr->focus_item != -1)
2998 LISTBOX_DrawFocusRect( descr, TRUE );
2999 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3000 return 0;
3001 case WM_KILLFOCUS:
3002 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3003 descr->in_focus = FALSE;
3004 descr->wheel_remain = 0;
3005 if ((descr->focus_item != -1) && descr->caret_on)
3006 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3007 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3008 return 0;
3009 case WM_HSCROLL:
3010 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3011 case WM_VSCROLL:
3012 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3013 case WM_MOUSEWHEEL:
3014 if (wParam & (MK_SHIFT | MK_CONTROL))
3015 return DefWindowProcW( descr->self, msg, wParam, lParam );
3016 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3017 case WM_LBUTTONDOWN:
3018 if (lphc)
3019 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3020 (INT16)LOWORD(lParam),
3021 (INT16)HIWORD(lParam) );
3022 return LISTBOX_HandleLButtonDown( descr, wParam,
3023 (INT16)LOWORD(lParam),
3024 (INT16)HIWORD(lParam) );
3025 case WM_LBUTTONDBLCLK:
3026 if (lphc)
3027 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3028 (INT16)LOWORD(lParam),
3029 (INT16)HIWORD(lParam) );
3030 if (descr->style & LBS_NOTIFY)
3031 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3032 return 0;
3033 case WM_MOUSEMOVE:
3034 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3036 BOOL captured = descr->captured;
3037 POINT mousePos;
3038 RECT clientRect;
3040 mousePos.x = (INT16)LOWORD(lParam);
3041 mousePos.y = (INT16)HIWORD(lParam);
3044 * If we are in a dropdown combobox, we simulate that
3045 * the mouse is captured to show the tracking of the item.
3047 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3048 descr->captured = TRUE;
3050 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3052 descr->captured = captured;
3054 else if (GetCapture() == descr->self)
3056 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3057 (INT16)HIWORD(lParam) );
3059 return 0;
3060 case WM_LBUTTONUP:
3061 if (lphc)
3063 POINT mousePos;
3064 RECT clientRect;
3067 * If the mouse button "up" is not in the listbox,
3068 * we make sure there is no selection by re-selecting the
3069 * item that was selected when the listbox was made visible.
3071 mousePos.x = (INT16)LOWORD(lParam);
3072 mousePos.y = (INT16)HIWORD(lParam);
3074 GetClientRect(descr->self, &clientRect);
3077 * When the user clicks outside the combobox and the focus
3078 * is lost, the owning combobox will send a fake buttonup with
3079 * 0xFFFFFFF as the mouse location, we must also revert the
3080 * selection to the original selection.
3082 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3083 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3085 return LISTBOX_HandleLButtonUp( descr );
3086 case WM_KEYDOWN:
3087 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3089 /* for some reason Windows makes it possible to
3090 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3092 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3093 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3094 && (wParam == VK_DOWN || wParam == VK_UP)) )
3096 COMBO_FlipListbox( lphc, FALSE, FALSE );
3097 return 0;
3100 return LISTBOX_HandleKeyDown( descr, wParam );
3101 case WM_CHAR:
3103 WCHAR charW;
3104 if(unicode)
3105 charW = (WCHAR)wParam;
3106 else
3108 CHAR charA = (CHAR)wParam;
3109 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3111 return LISTBOX_HandleChar( descr, charW );
3113 case WM_SYSTIMER:
3114 return LISTBOX_HandleSystemTimer( descr );
3115 case WM_ERASEBKGND:
3116 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3118 RECT rect;
3119 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3120 wParam, (LPARAM)descr->self );
3121 TRACE("hbrush = %p\n", hbrush);
3122 if(!hbrush)
3123 hbrush = GetSysColorBrush(COLOR_WINDOW);
3124 if(hbrush)
3126 GetClientRect(descr->self, &rect);
3127 FillRect((HDC)wParam, &rect, hbrush);
3130 return 1;
3131 case WM_DROPFILES:
3132 if( lphc ) return 0;
3133 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3134 SendMessageA( descr->owner, msg, wParam, lParam );
3136 case WM_NCDESTROY:
3137 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3138 lphc->hWndLBox = 0;
3139 break;
3141 case WM_NCACTIVATE:
3142 if (lphc) return 0;
3143 break;
3145 default:
3146 if ((msg >= WM_USER) && (msg < 0xc000))
3147 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3148 hwnd, msg, wParam, lParam );
3151 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3152 DefWindowProcA( hwnd, msg, wParam, lParam );
3155 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3157 TRACE("%p\n", hwnd);
3158 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);