comctl32: Fix a typo in comment.
[wine.git] / dlls / user32 / listbox.c
blob6b47f053ae46fe2c07b872552173cb980726f7c3
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * NOTES
22 * This code was audited for completeness against the documented features
23 * of Comctl32.dll version 6.0 on Oct. 9, 2004, by Dimitrie O. Paun.
25 * Unless otherwise noted, we believe this code to be complete, as per
26 * the specification mentioned above.
27 * If you discover missing features, or bugs, please note them below.
29 * TODO:
30 * - LBS_NODATA
33 #include <string.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include "windef.h"
38 #include "winbase.h"
39 #include "wingdi.h"
40 #include "wine/unicode.h"
41 #include "user_private.h"
42 #include "controls.h"
43 #include "wine/exception.h"
44 #include "wine/debug.h"
46 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
48 /* Items array granularity */
49 #define LB_ARRAY_GRANULARITY 16
51 /* Scrolling timeout in ms */
52 #define LB_SCROLL_TIMEOUT 50
54 /* Listbox system timer id */
55 #define LB_TIMER_ID 2
57 /* flag listbox changed while setredraw false - internal style */
58 #define LBS_DISPLAYCHANGED 0x80000000
60 /* Item structure */
61 typedef struct
63 LPWSTR str; /* Item text */
64 BOOL selected; /* Is item selected? */
65 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
66 ULONG_PTR data; /* User data */
67 } LB_ITEMDATA;
69 /* Listbox structure */
70 typedef struct
72 HWND self; /* Our own window handle */
73 HWND owner; /* Owner window to send notifications to */
74 UINT style; /* Window style */
75 INT width; /* Window width */
76 INT height; /* Window height */
77 LB_ITEMDATA *items; /* Array of items */
78 INT nb_items; /* Number of items */
79 INT top_item; /* Top visible item */
80 INT selected_item; /* Selected item */
81 INT focus_item; /* Item that has the focus */
82 INT anchor_item; /* Anchor item for extended selection */
83 INT item_height; /* Default item height */
84 INT page_size; /* Items per listbox page */
85 INT column_width; /* Column width for multi-column listboxes */
86 INT horz_extent; /* Horizontal extent */
87 INT horz_pos; /* Horizontal position */
88 INT nb_tabs; /* Number of tabs in array */
89 INT *tabs; /* Array of tabs */
90 INT avg_char_width; /* Average width of characters */
91 INT wheel_remain; /* Left over scroll amount */
92 BOOL caret_on; /* Is caret on? */
93 BOOL captured; /* Is mouse captured? */
94 BOOL in_focus;
95 HFONT font; /* Current font */
96 LCID locale; /* Current locale for string comparisons */
97 LPHEADCOMBO lphc; /* ComboLBox */
98 } LB_DESCR;
101 #define IS_OWNERDRAW(descr) \
102 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
104 #define HAS_STRINGS(descr) \
105 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108 #define IS_MULTISELECT(descr) \
109 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
110 !((descr)->style & LBS_NOSEL))
112 #define SEND_NOTIFICATION(descr,code) \
113 (SendMessageW( (descr)->owner, WM_COMMAND, \
114 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
116 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
118 /* Current timer status */
119 typedef enum
121 LB_TIMER_NONE,
122 LB_TIMER_UP,
123 LB_TIMER_LEFT,
124 LB_TIMER_DOWN,
125 LB_TIMER_RIGHT
126 } TIMER_DIRECTION;
128 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
130 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
132 /*********************************************************************
133 * listbox class descriptor
135 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
136 const struct builtin_class_descr LISTBOX_builtin_class =
138 listboxW, /* name */
139 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
140 WINPROC_LISTBOX, /* proc */
141 sizeof(LB_DESCR *), /* extra */
142 IDC_ARROW, /* cursor */
143 0 /* brush */
147 /*********************************************************************
148 * combolbox class descriptor
150 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
151 const struct builtin_class_descr COMBOLBOX_builtin_class =
153 combolboxW, /* name */
154 CS_DBLCLKS | CS_SAVEBITS, /* style */
155 WINPROC_LISTBOX, /* proc */
156 sizeof(LB_DESCR *), /* extra */
157 IDC_ARROW, /* cursor */
158 0 /* brush */
162 /***********************************************************************
163 * LISTBOX_GetCurrentPageSize
165 * Return the current page size
167 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
169 INT i, height;
170 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
171 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
173 if ((height += descr->items[i].height) > descr->height) break;
175 if (i == descr->top_item) return 1;
176 else return i - descr->top_item;
180 /***********************************************************************
181 * LISTBOX_GetMaxTopIndex
183 * Return the maximum possible index for the top of the listbox.
185 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
187 INT max, page;
189 if (descr->style & LBS_OWNERDRAWVARIABLE)
191 page = descr->height;
192 for (max = descr->nb_items - 1; max >= 0; max--)
193 if ((page -= descr->items[max].height) < 0) break;
194 if (max < descr->nb_items - 1) max++;
196 else if (descr->style & LBS_MULTICOLUMN)
198 if ((page = descr->width / descr->column_width) < 1) page = 1;
199 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
200 max = (max - page) * descr->page_size;
202 else
204 max = descr->nb_items - descr->page_size;
206 if (max < 0) max = 0;
207 return max;
211 /***********************************************************************
212 * LISTBOX_UpdateScroll
214 * Update the scrollbars. Should be called whenever the content
215 * of the listbox changes.
217 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
219 SCROLLINFO info;
221 /* Check the listbox scroll bar flags individually before we call
222 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
223 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
224 scroll bar when we do not need one.
225 if (!(descr->style & WS_VSCROLL)) return;
228 /* It is important that we check descr->style, and not wnd->dwStyle,
229 for WS_VSCROLL, as the former is exactly the one passed in
230 argument to CreateWindow.
231 In Windows (and from now on in Wine :) a listbox created
232 with such a style (no WS_SCROLL) does not update
233 the scrollbar with listbox-related data, thus letting
234 the programmer use it for his/her own purposes. */
236 if (descr->style & LBS_NOREDRAW) return;
237 info.cbSize = sizeof(info);
239 if (descr->style & LBS_MULTICOLUMN)
241 info.nMin = 0;
242 info.nMax = (descr->nb_items - 1) / descr->page_size;
243 info.nPos = descr->top_item / descr->page_size;
244 info.nPage = descr->width / descr->column_width;
245 if (info.nPage < 1) info.nPage = 1;
246 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
247 if (descr->style & LBS_DISABLENOSCROLL)
248 info.fMask |= SIF_DISABLENOSCROLL;
249 if (descr->style & WS_HSCROLL)
250 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
251 info.nMax = 0;
252 info.fMask = SIF_RANGE;
253 if (descr->style & WS_VSCROLL)
254 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
256 else
258 info.nMin = 0;
259 info.nMax = descr->nb_items - 1;
260 info.nPos = descr->top_item;
261 info.nPage = LISTBOX_GetCurrentPageSize( descr );
262 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
263 if (descr->style & LBS_DISABLENOSCROLL)
264 info.fMask |= SIF_DISABLENOSCROLL;
265 if (descr->style & WS_VSCROLL)
266 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
268 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
270 info.nPos = descr->horz_pos;
271 info.nPage = descr->width;
272 info.fMask = SIF_POS | SIF_PAGE;
273 if (descr->style & LBS_DISABLENOSCROLL)
274 info.fMask |= SIF_DISABLENOSCROLL;
275 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
277 else
279 if (descr->style & LBS_DISABLENOSCROLL)
281 info.nMin = 0;
282 info.nMax = 0;
283 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
284 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
286 else
288 ShowScrollBar( descr->self, SB_HORZ, FALSE );
295 /***********************************************************************
296 * LISTBOX_SetTopItem
298 * Set the top item of the listbox, scrolling up or down if necessary.
300 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
302 INT max = LISTBOX_GetMaxTopIndex( descr );
304 TRACE("setting top item %d, scroll %d\n", index, scroll);
306 if (index > max) index = max;
307 if (index < 0) index = 0;
308 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
309 if (descr->top_item == index) return LB_OKAY;
310 if (scroll)
312 INT diff;
313 if (descr->style & LBS_MULTICOLUMN)
314 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
315 else if (descr->style & LBS_OWNERDRAWVARIABLE)
317 INT i;
318 diff = 0;
319 if (index > descr->top_item)
321 for (i = index - 1; i >= descr->top_item; i--)
322 diff -= descr->items[i].height;
324 else
326 for (i = index; i < descr->top_item; i++)
327 diff += descr->items[i].height;
330 else
331 diff = (descr->top_item - index) * descr->item_height;
333 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
334 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
336 else
337 InvalidateRect( descr->self, NULL, TRUE );
338 descr->top_item = index;
339 LISTBOX_UpdateScroll( descr );
340 return LB_OKAY;
344 /***********************************************************************
345 * LISTBOX_UpdatePage
347 * Update the page size. Should be called when the size of
348 * the client area or the item height changes.
350 static void LISTBOX_UpdatePage( LB_DESCR *descr )
352 INT page_size;
354 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
355 page_size = 1;
356 if (page_size == descr->page_size) return;
357 descr->page_size = page_size;
358 if (descr->style & LBS_MULTICOLUMN)
359 InvalidateRect( descr->self, NULL, TRUE );
360 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
364 /***********************************************************************
365 * LISTBOX_UpdateSize
367 * Update the size of the listbox. Should be called when the size of
368 * the client area changes.
370 static void LISTBOX_UpdateSize( LB_DESCR *descr )
372 RECT rect;
374 GetClientRect( descr->self, &rect );
375 descr->width = rect.right - rect.left;
376 descr->height = rect.bottom - rect.top;
377 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
379 INT remaining;
380 RECT rect;
382 GetWindowRect( descr->self, &rect );
383 if(descr->item_height != 0)
384 remaining = descr->height % descr->item_height;
385 else
386 remaining = 0;
387 if ((descr->height > descr->item_height) && remaining)
389 TRACE("[%p]: changing height %d -> %d\n",
390 descr->self, descr->height, descr->height - remaining );
391 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
392 rect.bottom - rect.top - remaining,
393 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
394 return;
397 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
398 LISTBOX_UpdatePage( descr );
399 LISTBOX_UpdateScroll( descr );
401 /* Invalidate the focused item so it will be repainted correctly */
402 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
404 InvalidateRect( descr->self, &rect, FALSE );
409 /***********************************************************************
410 * LISTBOX_GetItemRect
412 * Get the rectangle enclosing an item, in listbox client coordinates.
413 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
415 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
417 /* Index <= 0 is legal even on empty listboxes */
418 if (index && (index >= descr->nb_items))
420 SetRectEmpty(rect);
421 SetLastError(ERROR_INVALID_INDEX);
422 return LB_ERR;
424 SetRect( rect, 0, 0, descr->width, descr->height );
425 if (descr->style & LBS_MULTICOLUMN)
427 INT col = (index / descr->page_size) -
428 (descr->top_item / descr->page_size);
429 rect->left += col * descr->column_width;
430 rect->right = rect->left + descr->column_width;
431 rect->top += (index % descr->page_size) * descr->item_height;
432 rect->bottom = rect->top + descr->item_height;
434 else if (descr->style & LBS_OWNERDRAWVARIABLE)
436 INT i;
437 rect->right += descr->horz_pos;
438 if ((index >= 0) && (index < descr->nb_items))
440 if (index < descr->top_item)
442 for (i = descr->top_item-1; i >= index; i--)
443 rect->top -= descr->items[i].height;
445 else
447 for (i = descr->top_item; i < index; i++)
448 rect->top += descr->items[i].height;
450 rect->bottom = rect->top + descr->items[index].height;
454 else
456 rect->top += (index - descr->top_item) * descr->item_height;
457 rect->bottom = rect->top + descr->item_height;
458 rect->right += descr->horz_pos;
461 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
463 return ((rect->left < descr->width) && (rect->right > 0) &&
464 (rect->top < descr->height) && (rect->bottom > 0));
468 /***********************************************************************
469 * LISTBOX_GetItemFromPoint
471 * Return the item nearest from point (x,y) (in client coordinates).
473 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
475 INT index = descr->top_item;
477 if (!descr->nb_items) return -1; /* No items */
478 if (descr->style & LBS_OWNERDRAWVARIABLE)
480 INT pos = 0;
481 if (y >= 0)
483 while (index < descr->nb_items)
485 if ((pos += descr->items[index].height) > y) break;
486 index++;
489 else
491 while (index > 0)
493 index--;
494 if ((pos -= descr->items[index].height) <= y) break;
498 else if (descr->style & LBS_MULTICOLUMN)
500 if (y >= descr->item_height * descr->page_size) return -1;
501 if (y >= 0) index += y / descr->item_height;
502 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
503 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
505 else
507 index += (y / descr->item_height);
509 if (index < 0) return 0;
510 if (index >= descr->nb_items) return -1;
511 return index;
515 /***********************************************************************
516 * LISTBOX_PaintItem
518 * Paint an item.
520 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
521 INT index, UINT action, BOOL ignoreFocus )
523 LB_ITEMDATA *item = NULL;
524 if (index < descr->nb_items) item = &descr->items[index];
526 if (IS_OWNERDRAW(descr))
528 DRAWITEMSTRUCT dis;
529 RECT r;
530 HRGN hrgn;
532 if (!item)
534 if (action == ODA_FOCUS)
535 DrawFocusRect( hdc, rect );
536 else
537 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
538 return;
541 /* some programs mess with the clipping region when
542 drawing the item, *and* restore the previous region
543 after they are done, so a region has better to exist
544 else everything ends clipped */
545 GetClientRect(descr->self, &r);
546 hrgn = set_control_clipping( hdc, &r );
548 dis.CtlType = ODT_LISTBOX;
549 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
550 dis.hwndItem = descr->self;
551 dis.itemAction = action;
552 dis.hDC = hdc;
553 dis.itemID = index;
554 dis.itemState = 0;
555 if (item->selected) dis.itemState |= ODS_SELECTED;
556 if (!ignoreFocus && (descr->focus_item == index) &&
557 (descr->caret_on) &&
558 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
559 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
560 dis.itemData = item->data;
561 dis.rcItem = *rect;
562 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
563 descr->self, index, debugstr_w(item->str), action,
564 dis.itemState, wine_dbgstr_rect(rect) );
565 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
566 SelectClipRgn( hdc, hrgn );
567 if (hrgn) DeleteObject( hrgn );
569 else
571 COLORREF oldText = 0, oldBk = 0;
573 if (action == ODA_FOCUS)
575 DrawFocusRect( hdc, rect );
576 return;
578 if (item && item->selected)
580 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
581 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
584 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
585 descr->self, index, item ? debugstr_w(item->str) : "", action,
586 wine_dbgstr_rect(rect) );
587 if (!item)
588 ExtTextOutW( hdc, rect->left + 1, rect->top,
589 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
590 else if (!(descr->style & LBS_USETABSTOPS))
591 ExtTextOutW( hdc, rect->left + 1, rect->top,
592 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
593 strlenW(item->str), NULL );
594 else
596 /* Output empty string to paint background in the full width. */
597 ExtTextOutW( hdc, rect->left + 1, rect->top,
598 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
599 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
600 item->str, strlenW(item->str),
601 descr->nb_tabs, descr->tabs, 0);
603 if (item && item->selected)
605 SetBkColor( hdc, oldBk );
606 SetTextColor( hdc, oldText );
608 if (!ignoreFocus && (descr->focus_item == index) &&
609 (descr->caret_on) &&
610 (descr->in_focus)) DrawFocusRect( hdc, rect );
615 /***********************************************************************
616 * LISTBOX_SetRedraw
618 * Change the redraw flag.
620 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
622 if (on)
624 if (!(descr->style & LBS_NOREDRAW)) return;
625 descr->style &= ~LBS_NOREDRAW;
626 if (descr->style & LBS_DISPLAYCHANGED)
627 { /* page was changed while setredraw false, refresh automatically */
628 InvalidateRect(descr->self, NULL, TRUE);
629 if ((descr->top_item + descr->page_size) > descr->nb_items)
630 { /* reset top of page if less than number of items/page */
631 descr->top_item = descr->nb_items - descr->page_size;
632 if (descr->top_item < 0) descr->top_item = 0;
634 descr->style &= ~LBS_DISPLAYCHANGED;
636 LISTBOX_UpdateScroll( descr );
638 else descr->style |= LBS_NOREDRAW;
642 /***********************************************************************
643 * LISTBOX_RepaintItem
645 * Repaint a single item synchronously.
647 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
649 HDC hdc;
650 RECT rect;
651 HFONT oldFont = 0;
652 HBRUSH hbrush, oldBrush = 0;
654 /* Do not repaint the item if the item is not visible */
655 if (!IsWindowVisible(descr->self)) return;
656 if (descr->style & LBS_NOREDRAW)
658 descr->style |= LBS_DISPLAYCHANGED;
659 return;
661 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
662 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
663 if (descr->font) oldFont = SelectObject( hdc, descr->font );
664 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
665 (WPARAM)hdc, (LPARAM)descr->self );
666 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
667 if (!IsWindowEnabled(descr->self))
668 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
669 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
670 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
671 if (oldFont) SelectObject( hdc, oldFont );
672 if (oldBrush) SelectObject( hdc, oldBrush );
673 ReleaseDC( descr->self, hdc );
677 /***********************************************************************
678 * LISTBOX_DrawFocusRect
680 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
682 HDC hdc;
683 RECT rect;
684 HFONT oldFont = 0;
686 /* Do not repaint the item if the item is not visible */
687 if (!IsWindowVisible(descr->self)) return;
689 if (descr->focus_item == -1) return;
690 if (!descr->caret_on || !descr->in_focus) return;
692 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
693 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
694 if (descr->font) oldFont = SelectObject( hdc, descr->font );
695 if (!IsWindowEnabled(descr->self))
696 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
697 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
698 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
699 if (oldFont) SelectObject( hdc, oldFont );
700 ReleaseDC( descr->self, hdc );
704 /***********************************************************************
705 * LISTBOX_InitStorage
707 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
709 LB_ITEMDATA *item;
711 nb_items += LB_ARRAY_GRANULARITY - 1;
712 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
713 if (descr->items) {
714 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
715 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
716 nb_items * sizeof(LB_ITEMDATA));
718 else {
719 item = HeapAlloc( GetProcessHeap(), 0,
720 nb_items * sizeof(LB_ITEMDATA));
723 if (!item)
725 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
726 return LB_ERRSPACE;
728 descr->items = item;
729 return LB_OKAY;
733 /***********************************************************************
734 * LISTBOX_SetTabStops
736 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
738 INT i;
740 if (!(descr->style & LBS_USETABSTOPS))
742 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
743 return FALSE;
746 HeapFree( GetProcessHeap(), 0, descr->tabs );
747 if (!(descr->nb_tabs = count))
749 descr->tabs = NULL;
750 return TRUE;
752 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
753 descr->nb_tabs * sizeof(INT) )))
754 return FALSE;
755 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
757 /* convert into "dialog units"*/
758 for (i = 0; i < descr->nb_tabs; i++)
759 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
761 return TRUE;
765 /***********************************************************************
766 * LISTBOX_GetText
768 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
770 DWORD len;
772 if ((index < 0) || (index >= descr->nb_items))
774 SetLastError(ERROR_INVALID_INDEX);
775 return LB_ERR;
777 if (HAS_STRINGS(descr))
779 if (!buffer)
781 len = strlenW(descr->items[index].str);
782 if( unicode )
783 return len;
784 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
785 NULL, 0, NULL, NULL );
788 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
790 __TRY /* hide a Delphi bug that passes a read-only buffer */
792 if(unicode)
794 strcpyW( buffer, descr->items[index].str );
795 len = strlenW(buffer);
797 else
799 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
800 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
803 __EXCEPT_PAGE_FAULT
805 WARN( "got an invalid buffer (Delphi bug?)\n" );
806 SetLastError( ERROR_INVALID_PARAMETER );
807 return LB_ERR;
809 __ENDTRY
810 } else {
811 if (buffer)
812 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
813 len = sizeof(DWORD);
815 return len;
818 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
820 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
821 if (ret == CSTR_LESS_THAN)
822 return -1;
823 if (ret == CSTR_EQUAL)
824 return 0;
825 if (ret == CSTR_GREATER_THAN)
826 return 1;
827 return -1;
830 /***********************************************************************
831 * LISTBOX_FindStringPos
833 * Find the nearest string located before a given string in sort order.
834 * If 'exact' is TRUE, return an error if we don't get an exact match.
836 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
838 INT index, min, max, res;
840 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
841 min = 0;
842 max = descr->nb_items;
843 while (min != max)
845 index = (min + max) / 2;
846 if (HAS_STRINGS(descr))
847 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
848 else
850 COMPAREITEMSTRUCT cis;
851 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
853 cis.CtlType = ODT_LISTBOX;
854 cis.CtlID = id;
855 cis.hwndItem = descr->self;
856 /* note that some application (MetaStock) expects the second item
857 * to be in the listbox */
858 cis.itemID1 = -1;
859 cis.itemData1 = (ULONG_PTR)str;
860 cis.itemID2 = index;
861 cis.itemData2 = descr->items[index].data;
862 cis.dwLocaleId = descr->locale;
863 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
865 if (!res) return index;
866 if (res < 0) max = index;
867 else min = index + 1;
869 return exact ? -1 : max;
873 /***********************************************************************
874 * LISTBOX_FindFileStrPos
876 * Find the nearest string located before a given string in directory
877 * sort order (i.e. first files, then directories, then drives).
879 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
881 INT min, max, res;
883 if (!HAS_STRINGS(descr))
884 return LISTBOX_FindStringPos( descr, str, FALSE );
885 min = 0;
886 max = descr->nb_items;
887 while (min != max)
889 INT index = (min + max) / 2;
890 LPCWSTR p = descr->items[index].str;
891 if (*p == '[') /* drive or directory */
893 if (*str != '[') res = -1;
894 else if (p[1] == '-') /* drive */
896 if (str[1] == '-') res = str[2] - p[2];
897 else res = -1;
899 else /* directory */
901 if (str[1] == '-') res = 1;
902 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
905 else /* filename */
907 if (*str == '[') res = 1;
908 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
910 if (!res) return index;
911 if (res < 0) max = index;
912 else min = index + 1;
914 return max;
918 /***********************************************************************
919 * LISTBOX_FindString
921 * Find the item beginning with a given string.
923 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
925 INT i;
926 LB_ITEMDATA *item;
928 if (start >= descr->nb_items) start = -1;
929 item = descr->items + start + 1;
930 if (HAS_STRINGS(descr))
932 if (!str || ! str[0] ) return LB_ERR;
933 if (exact)
935 for (i = start + 1; i < descr->nb_items; i++, item++)
936 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
937 for (i = 0, item = descr->items; i <= start; i++, item++)
938 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
940 else
942 /* Special case for drives and directories: ignore prefix */
943 #define CHECK_DRIVE(item) \
944 if ((item)->str[0] == '[') \
946 if (!strncmpiW( str, (item)->str+1, len )) return i; \
947 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
948 return i; \
951 INT len = strlenW(str);
952 for (i = start + 1; i < descr->nb_items; i++, item++)
954 if (!strncmpiW( str, item->str, len )) return i;
955 CHECK_DRIVE(item);
957 for (i = 0, item = descr->items; i <= start; i++, item++)
959 if (!strncmpiW( str, item->str, len )) return i;
960 CHECK_DRIVE(item);
962 #undef CHECK_DRIVE
965 else
967 if (exact && (descr->style & LBS_SORT))
968 /* If sorted, use a WM_COMPAREITEM binary search */
969 return LISTBOX_FindStringPos( descr, str, TRUE );
971 /* Otherwise use a linear search */
972 for (i = start + 1; i < descr->nb_items; i++, item++)
973 if (item->data == (ULONG_PTR)str) return i;
974 for (i = 0, item = descr->items; i <= start; i++, item++)
975 if (item->data == (ULONG_PTR)str) return i;
977 return LB_ERR;
981 /***********************************************************************
982 * LISTBOX_GetSelCount
984 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
986 INT i, count;
987 const LB_ITEMDATA *item = descr->items;
989 if (!(descr->style & LBS_MULTIPLESEL) ||
990 (descr->style & LBS_NOSEL))
991 return LB_ERR;
992 for (i = count = 0; i < descr->nb_items; i++, item++)
993 if (item->selected) count++;
994 return count;
998 /***********************************************************************
999 * LISTBOX_GetSelItems
1001 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1003 INT i, count;
1004 const 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 /* keep the focus rect, to paint the focus item after */
1063 if (i == descr->focus_item)
1064 focusRect = rect;
1066 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1067 rect.top = rect.bottom;
1069 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1071 if (!IS_OWNERDRAW(descr))
1073 /* Clear the bottom of the column */
1074 if (rect.top < descr->height)
1076 rect.bottom = descr->height;
1077 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1078 &rect, NULL, 0, NULL );
1082 /* Go to the next column */
1083 rect.left += descr->column_width;
1084 rect.right += descr->column_width;
1085 rect.top = 0;
1086 col_pos = descr->page_size - 1;
1088 else
1090 col_pos--;
1091 if (rect.top >= descr->height) break;
1095 /* Paint the focus item now */
1096 if (focusRect.top != focusRect.bottom &&
1097 descr->caret_on && descr->in_focus)
1098 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1100 if (!IS_OWNERDRAW(descr))
1102 /* Clear the remainder of the client area */
1103 if (rect.top < descr->height)
1105 rect.bottom = descr->height;
1106 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1107 &rect, NULL, 0, NULL );
1109 if (rect.right < descr->width)
1111 rect.left = rect.right;
1112 rect.right = descr->width;
1113 rect.top = 0;
1114 rect.bottom = descr->height;
1115 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1116 &rect, NULL, 0, NULL );
1119 if (oldFont) SelectObject( hdc, oldFont );
1120 if (oldBrush) SelectObject( hdc, oldBrush );
1121 return 0;
1125 /***********************************************************************
1126 * LISTBOX_InvalidateItems
1128 * Invalidate all items from a given item. If the specified item is not
1129 * visible, nothing happens.
1131 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1133 RECT rect;
1135 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1137 if (descr->style & LBS_NOREDRAW)
1139 descr->style |= LBS_DISPLAYCHANGED;
1140 return;
1142 rect.bottom = descr->height;
1143 InvalidateRect( descr->self, &rect, TRUE );
1144 if (descr->style & LBS_MULTICOLUMN)
1146 /* Repaint the other columns */
1147 rect.left = rect.right;
1148 rect.right = descr->width;
1149 rect.top = 0;
1150 InvalidateRect( descr->self, &rect, TRUE );
1155 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1157 RECT rect;
1159 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1160 InvalidateRect( descr->self, &rect, TRUE );
1163 /***********************************************************************
1164 * LISTBOX_GetItemHeight
1166 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1168 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1170 if ((index < 0) || (index >= descr->nb_items))
1172 SetLastError(ERROR_INVALID_INDEX);
1173 return LB_ERR;
1175 return descr->items[index].height;
1177 else return descr->item_height;
1181 /***********************************************************************
1182 * LISTBOX_SetItemHeight
1184 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1186 if (height > MAXBYTE)
1187 return -1;
1189 if (!height) height = 1;
1191 if (descr->style & LBS_OWNERDRAWVARIABLE)
1193 if ((index < 0) || (index >= descr->nb_items))
1195 SetLastError(ERROR_INVALID_INDEX);
1196 return LB_ERR;
1198 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1199 descr->items[index].height = height;
1200 LISTBOX_UpdateScroll( descr );
1201 if (repaint)
1202 LISTBOX_InvalidateItems( descr, index );
1204 else if (height != descr->item_height)
1206 TRACE("[%p]: new height = %d\n", descr->self, height );
1207 descr->item_height = height;
1208 LISTBOX_UpdatePage( descr );
1209 LISTBOX_UpdateScroll( descr );
1210 if (repaint)
1211 InvalidateRect( descr->self, 0, TRUE );
1213 return LB_OKAY;
1217 /***********************************************************************
1218 * LISTBOX_SetHorizontalPos
1220 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1222 INT diff;
1224 if (pos > descr->horz_extent - descr->width)
1225 pos = descr->horz_extent - descr->width;
1226 if (pos < 0) pos = 0;
1227 if (!(diff = descr->horz_pos - pos)) return;
1228 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1229 descr->horz_pos = pos;
1230 LISTBOX_UpdateScroll( descr );
1231 if (abs(diff) < descr->width)
1233 RECT rect;
1234 /* Invalidate the focused item so it will be repainted correctly */
1235 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1236 InvalidateRect( descr->self, &rect, TRUE );
1237 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1238 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1240 else
1241 InvalidateRect( descr->self, NULL, TRUE );
1245 /***********************************************************************
1246 * LISTBOX_SetHorizontalExtent
1248 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1250 if (descr->style & LBS_MULTICOLUMN)
1251 return LB_OKAY;
1252 if (extent == descr->horz_extent) return LB_OKAY;
1253 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1254 descr->horz_extent = extent;
1255 if (descr->style & WS_HSCROLL) {
1256 SCROLLINFO info;
1257 info.cbSize = sizeof(info);
1258 info.nMin = 0;
1259 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1260 info.fMask = SIF_RANGE;
1261 if (descr->style & LBS_DISABLENOSCROLL)
1262 info.fMask |= SIF_DISABLENOSCROLL;
1263 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1265 if (descr->horz_pos > extent - descr->width)
1266 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1267 return LB_OKAY;
1271 /***********************************************************************
1272 * LISTBOX_SetColumnWidth
1274 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1276 if (width == descr->column_width) return LB_OKAY;
1277 TRACE("[%p]: new column width = %d\n", descr->self, width );
1278 descr->column_width = width;
1279 LISTBOX_UpdatePage( descr );
1280 return LB_OKAY;
1284 /***********************************************************************
1285 * LISTBOX_SetFont
1287 * Returns the item height.
1289 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1291 HDC hdc;
1292 HFONT oldFont = 0;
1293 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1294 SIZE sz;
1296 descr->font = font;
1298 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1300 ERR("unable to get DC.\n" );
1301 return 16;
1303 if (font) oldFont = SelectObject( hdc, font );
1304 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1305 if (oldFont) SelectObject( hdc, oldFont );
1306 ReleaseDC( descr->self, hdc );
1308 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1309 if (!IS_OWNERDRAW(descr))
1310 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1311 return sz.cy;
1315 /***********************************************************************
1316 * LISTBOX_MakeItemVisible
1318 * Make sure that a given item is partially or fully visible.
1320 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1322 INT top;
1324 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1326 if (index <= descr->top_item) top = index;
1327 else if (descr->style & LBS_MULTICOLUMN)
1329 INT cols = descr->width;
1330 if (!fully) cols += descr->column_width - 1;
1331 if (cols >= descr->column_width) cols /= descr->column_width;
1332 else cols = 1;
1333 if (index < descr->top_item + (descr->page_size * cols)) return;
1334 top = index - descr->page_size * (cols - 1);
1336 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1338 INT height = fully ? descr->items[index].height : 1;
1339 for (top = index; top > descr->top_item; top--)
1340 if ((height += descr->items[top-1].height) > descr->height) break;
1342 else
1344 if (index < descr->top_item + descr->page_size) return;
1345 if (!fully && (index == descr->top_item + descr->page_size) &&
1346 (descr->height > (descr->page_size * descr->item_height))) return;
1347 top = index - descr->page_size + 1;
1349 LISTBOX_SetTopItem( descr, top, TRUE );
1352 /***********************************************************************
1353 * LISTBOX_SetCaretIndex
1355 * NOTES
1356 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1359 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1361 INT oldfocus = descr->focus_item;
1363 TRACE("old focus %d, index %d\n", oldfocus, index);
1365 if (descr->style & LBS_NOSEL) return LB_ERR;
1366 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1367 if (index == oldfocus) return LB_OKAY;
1369 LISTBOX_DrawFocusRect( descr, FALSE );
1370 descr->focus_item = index;
1372 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1373 LISTBOX_DrawFocusRect( descr, TRUE );
1375 return LB_OKAY;
1379 /***********************************************************************
1380 * LISTBOX_SelectItemRange
1382 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1384 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1385 INT last, BOOL on )
1387 INT i;
1389 /* A few sanity checks */
1391 if (descr->style & LBS_NOSEL) return LB_ERR;
1392 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1394 if (!descr->nb_items) return LB_OKAY;
1396 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1397 if (first < 0) first = 0;
1398 if (last < first) return LB_OKAY;
1400 if (on) /* Turn selection on */
1402 for (i = first; i <= last; i++)
1404 if (descr->items[i].selected) continue;
1405 descr->items[i].selected = TRUE;
1406 LISTBOX_InvalidateItemRect(descr, i);
1409 else /* Turn selection off */
1411 for (i = first; i <= last; i++)
1413 if (!descr->items[i].selected) continue;
1414 descr->items[i].selected = FALSE;
1415 LISTBOX_InvalidateItemRect(descr, i);
1418 return LB_OKAY;
1421 /***********************************************************************
1422 * LISTBOX_SetSelection
1424 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1425 BOOL on, BOOL send_notify )
1427 TRACE( "cur_sel=%d index=%d notify=%s\n",
1428 descr->selected_item, index, send_notify ? "YES" : "NO" );
1430 if (descr->style & LBS_NOSEL)
1432 descr->selected_item = index;
1433 return LB_ERR;
1435 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1436 if (descr->style & LBS_MULTIPLESEL)
1438 if (index == -1) /* Select all items */
1439 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1440 else /* Only one item */
1441 return LISTBOX_SelectItemRange( descr, index, index, on );
1443 else
1445 INT oldsel = descr->selected_item;
1446 if (index == oldsel) return LB_OKAY;
1447 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1448 if (index != -1) descr->items[index].selected = TRUE;
1449 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1450 descr->selected_item = index;
1451 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1452 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1453 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1454 else
1455 if( descr->lphc ) /* set selection change flag for parent combo */
1456 descr->lphc->wState |= CBF_SELCHANGE;
1458 return LB_OKAY;
1462 /***********************************************************************
1463 * LISTBOX_MoveCaret
1465 * Change the caret position and extend the selection to the new caret.
1467 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1469 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1471 if ((index < 0) || (index >= descr->nb_items))
1472 return;
1474 /* Important, repaint needs to be done in this order if
1475 you want to mimic Windows behavior:
1476 1. Remove the focus and paint the item
1477 2. Remove the selection and paint the item(s)
1478 3. Set the selection and repaint the item(s)
1479 4. Set the focus to 'index' and repaint the item */
1481 /* 1. remove the focus and repaint the item */
1482 LISTBOX_DrawFocusRect( descr, FALSE );
1484 /* 2. then turn off the previous selection */
1485 /* 3. repaint the new selected item */
1486 if (descr->style & LBS_EXTENDEDSEL)
1488 if (descr->anchor_item != -1)
1490 INT first = min( index, descr->anchor_item );
1491 INT last = max( index, descr->anchor_item );
1492 if (first > 0)
1493 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1494 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1495 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1498 else if (!(descr->style & LBS_MULTIPLESEL))
1500 /* Set selection to new caret item */
1501 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1504 /* 4. repaint the new item with the focus */
1505 descr->focus_item = index;
1506 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1507 LISTBOX_DrawFocusRect( descr, TRUE );
1511 /***********************************************************************
1512 * LISTBOX_InsertItem
1514 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1515 LPWSTR str, ULONG_PTR data )
1517 LB_ITEMDATA *item;
1518 INT max_items;
1519 INT oldfocus = descr->focus_item;
1521 if (index == -1) index = descr->nb_items;
1522 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1523 if (!descr->items) max_items = 0;
1524 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1525 if (descr->nb_items == max_items)
1527 /* We need to grow the array */
1528 max_items += LB_ARRAY_GRANULARITY;
1529 if (descr->items)
1530 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1531 max_items * sizeof(LB_ITEMDATA) );
1532 else
1533 item = HeapAlloc( GetProcessHeap(), 0,
1534 max_items * sizeof(LB_ITEMDATA) );
1535 if (!item)
1537 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1538 return LB_ERRSPACE;
1540 descr->items = item;
1543 /* Insert the item structure */
1545 item = &descr->items[index];
1546 if (index < descr->nb_items)
1547 RtlMoveMemory( item + 1, item,
1548 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1549 item->str = str;
1550 item->data = data;
1551 item->height = 0;
1552 item->selected = FALSE;
1553 descr->nb_items++;
1555 /* Get item height */
1557 if (descr->style & LBS_OWNERDRAWVARIABLE)
1559 MEASUREITEMSTRUCT mis;
1560 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1562 mis.CtlType = ODT_LISTBOX;
1563 mis.CtlID = id;
1564 mis.itemID = index;
1565 mis.itemData = descr->items[index].data;
1566 mis.itemHeight = descr->item_height;
1567 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1568 item->height = mis.itemHeight ? mis.itemHeight : 1;
1569 TRACE("[%p]: measure item %d (%s) = %d\n",
1570 descr->self, index, str ? debugstr_w(str) : "", item->height );
1573 /* Repaint the items */
1575 LISTBOX_UpdateScroll( descr );
1576 LISTBOX_InvalidateItems( descr, index );
1578 /* Move selection and focused item */
1579 /* If listbox was empty, set focus to the first item */
1580 if (descr->nb_items == 1)
1581 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1582 /* single select don't change selection index in win31 */
1583 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1585 descr->selected_item++;
1586 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1588 else
1590 if (index <= descr->selected_item)
1592 descr->selected_item++;
1593 descr->focus_item = oldfocus; /* focus not changed */
1596 return LB_OKAY;
1600 /***********************************************************************
1601 * LISTBOX_InsertString
1603 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1605 LPWSTR new_str = NULL;
1606 ULONG_PTR data = 0;
1607 LRESULT ret;
1609 if (HAS_STRINGS(descr))
1611 static const WCHAR empty_stringW[] = { 0 };
1612 if (!str) str = empty_stringW;
1613 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1615 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1616 return LB_ERRSPACE;
1618 strcpyW(new_str, str);
1620 else data = (ULONG_PTR)str;
1622 if (index == -1) index = descr->nb_items;
1623 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1625 HeapFree( GetProcessHeap(), 0, new_str );
1626 return ret;
1629 TRACE("[%p]: added item %d %s\n",
1630 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1631 return index;
1635 /***********************************************************************
1636 * LISTBOX_DeleteItem
1638 * Delete the content of an item. 'index' must be a valid index.
1640 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1642 /* save the item data before it gets freed by LB_RESETCONTENT */
1643 ULONG_PTR item_data = descr->items[index].data;
1644 LPWSTR item_str = descr->items[index].str;
1646 if (!descr->nb_items)
1647 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1649 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1650 * while Win95 sends it for all items with user data.
1651 * It's probably better to send it too often than not
1652 * often enough, so this is what we do here.
1654 if (IS_OWNERDRAW(descr) || item_data)
1656 DELETEITEMSTRUCT dis;
1657 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1659 dis.CtlType = ODT_LISTBOX;
1660 dis.CtlID = id;
1661 dis.itemID = index;
1662 dis.hwndItem = descr->self;
1663 dis.itemData = item_data;
1664 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1666 if (HAS_STRINGS(descr))
1667 HeapFree( GetProcessHeap(), 0, item_str );
1671 /***********************************************************************
1672 * LISTBOX_RemoveItem
1674 * Remove an item from the listbox and delete its content.
1676 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1678 LB_ITEMDATA *item;
1679 INT max_items;
1681 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1683 /* We need to invalidate the original rect instead of the updated one. */
1684 LISTBOX_InvalidateItems( descr, index );
1686 descr->nb_items--;
1687 LISTBOX_DeleteItem( descr, index );
1689 if (!descr->nb_items) return LB_OKAY;
1691 /* Remove the item */
1693 item = &descr->items[index];
1694 if (index < descr->nb_items)
1695 RtlMoveMemory( item, item + 1,
1696 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1697 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1699 /* Shrink the item array if possible */
1701 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1702 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1704 max_items -= LB_ARRAY_GRANULARITY;
1705 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1706 max_items * sizeof(LB_ITEMDATA) );
1707 if (item) descr->items = item;
1709 /* Repaint the items */
1711 LISTBOX_UpdateScroll( descr );
1712 /* if we removed the scrollbar, reset the top of the list
1713 (correct for owner-drawn ???) */
1714 if (descr->nb_items == descr->page_size)
1715 LISTBOX_SetTopItem( descr, 0, TRUE );
1717 /* Move selection and focused item */
1718 if (!IS_MULTISELECT(descr))
1720 if (index == descr->selected_item)
1721 descr->selected_item = -1;
1722 else if (index < descr->selected_item)
1724 descr->selected_item--;
1725 if (ISWIN31) /* win 31 do not change the selected item number */
1726 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1730 if (descr->focus_item >= descr->nb_items)
1732 descr->focus_item = descr->nb_items - 1;
1733 if (descr->focus_item < 0) descr->focus_item = 0;
1735 return LB_OKAY;
1739 /***********************************************************************
1740 * LISTBOX_ResetContent
1742 static void LISTBOX_ResetContent( LB_DESCR *descr )
1744 INT i;
1746 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1747 HeapFree( GetProcessHeap(), 0, descr->items );
1748 descr->nb_items = 0;
1749 descr->top_item = 0;
1750 descr->selected_item = -1;
1751 descr->focus_item = 0;
1752 descr->anchor_item = -1;
1753 descr->items = NULL;
1757 /***********************************************************************
1758 * LISTBOX_SetCount
1760 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1762 LRESULT ret;
1764 if (HAS_STRINGS(descr))
1766 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1767 return LB_ERR;
1770 /* FIXME: this is far from optimal... */
1771 if (count > descr->nb_items)
1773 while (count > descr->nb_items)
1774 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1775 return ret;
1777 else if (count < descr->nb_items)
1779 while (count < descr->nb_items)
1780 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1781 return ret;
1784 InvalidateRect( descr->self, NULL, TRUE );
1785 return LB_OKAY;
1789 /***********************************************************************
1790 * LISTBOX_Directory
1792 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1793 LPCWSTR filespec, BOOL long_names )
1795 HANDLE handle;
1796 LRESULT ret = LB_OKAY;
1797 WIN32_FIND_DATAW entry;
1798 int pos;
1799 LRESULT maxinsert = LB_ERR;
1801 /* don't scan directory if we just want drives exclusively */
1802 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1803 /* scan directory */
1804 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1806 int le = GetLastError();
1807 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1809 else
1813 WCHAR buffer[270];
1814 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1816 static const WCHAR bracketW[] = { ']',0 };
1817 static const WCHAR dotW[] = { '.',0 };
1818 if (!(attrib & DDL_DIRECTORY) ||
1819 !strcmpW( entry.cFileName, dotW )) continue;
1820 buffer[0] = '[';
1821 if (!long_names && entry.cAlternateFileName[0])
1822 strcpyW( buffer + 1, entry.cAlternateFileName );
1823 else
1824 strcpyW( buffer + 1, entry.cFileName );
1825 strcatW(buffer, bracketW);
1827 else /* not a directory */
1829 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1830 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1832 if ((attrib & DDL_EXCLUSIVE) &&
1833 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1834 continue;
1835 #undef ATTRIBS
1836 if (!long_names && entry.cAlternateFileName[0])
1837 strcpyW( buffer, entry.cAlternateFileName );
1838 else
1839 strcpyW( buffer, entry.cFileName );
1841 if (!long_names) CharLowerW( buffer );
1842 pos = LISTBOX_FindFileStrPos( descr, buffer );
1843 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1844 break;
1845 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1846 } while (FindNextFileW( handle, &entry ));
1847 FindClose( handle );
1850 if (ret >= 0)
1852 ret = maxinsert;
1854 /* scan drives */
1855 if (attrib & DDL_DRIVES)
1857 WCHAR buffer[] = {'[','-','a','-',']',0};
1858 WCHAR root[] = {'A',':','\\',0};
1859 int drive;
1860 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1862 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1863 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1864 break;
1868 return ret;
1872 /***********************************************************************
1873 * LISTBOX_HandleVScroll
1875 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1877 SCROLLINFO info;
1879 if (descr->style & LBS_MULTICOLUMN) return 0;
1880 switch(scrollReq)
1882 case SB_LINEUP:
1883 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1884 break;
1885 case SB_LINEDOWN:
1886 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1887 break;
1888 case SB_PAGEUP:
1889 LISTBOX_SetTopItem( descr, descr->top_item -
1890 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1891 break;
1892 case SB_PAGEDOWN:
1893 LISTBOX_SetTopItem( descr, descr->top_item +
1894 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1895 break;
1896 case SB_THUMBPOSITION:
1897 LISTBOX_SetTopItem( descr, pos, TRUE );
1898 break;
1899 case SB_THUMBTRACK:
1900 info.cbSize = sizeof(info);
1901 info.fMask = SIF_TRACKPOS;
1902 GetScrollInfo( descr->self, SB_VERT, &info );
1903 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1904 break;
1905 case SB_TOP:
1906 LISTBOX_SetTopItem( descr, 0, TRUE );
1907 break;
1908 case SB_BOTTOM:
1909 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1910 break;
1912 return 0;
1916 /***********************************************************************
1917 * LISTBOX_HandleHScroll
1919 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1921 SCROLLINFO info;
1922 INT page;
1924 if (descr->style & LBS_MULTICOLUMN)
1926 switch(scrollReq)
1928 case SB_LINELEFT:
1929 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1930 TRUE );
1931 break;
1932 case SB_LINERIGHT:
1933 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1934 TRUE );
1935 break;
1936 case SB_PAGELEFT:
1937 page = descr->width / descr->column_width;
1938 if (page < 1) page = 1;
1939 LISTBOX_SetTopItem( descr,
1940 descr->top_item - page * descr->page_size, TRUE );
1941 break;
1942 case SB_PAGERIGHT:
1943 page = descr->width / descr->column_width;
1944 if (page < 1) page = 1;
1945 LISTBOX_SetTopItem( descr,
1946 descr->top_item + page * descr->page_size, TRUE );
1947 break;
1948 case SB_THUMBPOSITION:
1949 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1950 break;
1951 case SB_THUMBTRACK:
1952 info.cbSize = sizeof(info);
1953 info.fMask = SIF_TRACKPOS;
1954 GetScrollInfo( descr->self, SB_VERT, &info );
1955 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1956 TRUE );
1957 break;
1958 case SB_LEFT:
1959 LISTBOX_SetTopItem( descr, 0, TRUE );
1960 break;
1961 case SB_RIGHT:
1962 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1963 break;
1966 else if (descr->horz_extent)
1968 switch(scrollReq)
1970 case SB_LINELEFT:
1971 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1972 break;
1973 case SB_LINERIGHT:
1974 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1975 break;
1976 case SB_PAGELEFT:
1977 LISTBOX_SetHorizontalPos( descr,
1978 descr->horz_pos - descr->width );
1979 break;
1980 case SB_PAGERIGHT:
1981 LISTBOX_SetHorizontalPos( descr,
1982 descr->horz_pos + descr->width );
1983 break;
1984 case SB_THUMBPOSITION:
1985 LISTBOX_SetHorizontalPos( descr, pos );
1986 break;
1987 case SB_THUMBTRACK:
1988 info.cbSize = sizeof(info);
1989 info.fMask = SIF_TRACKPOS;
1990 GetScrollInfo( descr->self, SB_HORZ, &info );
1991 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1992 break;
1993 case SB_LEFT:
1994 LISTBOX_SetHorizontalPos( descr, 0 );
1995 break;
1996 case SB_RIGHT:
1997 LISTBOX_SetHorizontalPos( descr,
1998 descr->horz_extent - descr->width );
1999 break;
2002 return 0;
2005 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2007 UINT pulScrollLines = 3;
2009 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2011 /* if scrolling changes direction, ignore left overs */
2012 if ((delta < 0 && descr->wheel_remain < 0) ||
2013 (delta > 0 && descr->wheel_remain > 0))
2014 descr->wheel_remain += delta;
2015 else
2016 descr->wheel_remain = delta;
2018 if (descr->wheel_remain && pulScrollLines)
2020 int cLineScroll;
2021 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2022 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2023 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2024 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2026 return 0;
2029 /***********************************************************************
2030 * LISTBOX_HandleLButtonDown
2032 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2034 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2036 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2037 descr->self, x, y, index, descr->focus_item);
2039 if (!descr->caret_on && (descr->in_focus)) return 0;
2041 if (!descr->in_focus)
2043 if( !descr->lphc ) SetFocus( descr->self );
2044 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2047 if (index == -1) return 0;
2049 if (!descr->lphc)
2051 if (descr->style & LBS_NOTIFY )
2052 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2053 MAKELPARAM( x, y ) );
2056 descr->captured = TRUE;
2057 SetCapture( descr->self );
2059 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2061 /* we should perhaps make sure that all items are deselected
2062 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2063 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2064 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2067 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2068 if (keys & MK_CONTROL)
2070 LISTBOX_SetCaretIndex( descr, index, FALSE );
2071 LISTBOX_SetSelection( descr, index,
2072 !descr->items[index].selected,
2073 (descr->style & LBS_NOTIFY) != 0);
2075 else
2077 LISTBOX_MoveCaret( descr, index, FALSE );
2079 if (descr->style & LBS_EXTENDEDSEL)
2081 LISTBOX_SetSelection( descr, index,
2082 descr->items[index].selected,
2083 (descr->style & LBS_NOTIFY) != 0 );
2085 else
2087 LISTBOX_SetSelection( descr, index,
2088 !descr->items[index].selected,
2089 (descr->style & LBS_NOTIFY) != 0 );
2093 else
2095 descr->anchor_item = index;
2096 LISTBOX_MoveCaret( descr, index, FALSE );
2097 LISTBOX_SetSelection( descr, index,
2098 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2101 if (!descr->lphc)
2103 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2105 POINT pt;
2107 pt.x = x;
2108 pt.y = y;
2110 if (DragDetect( descr->self, pt ))
2111 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2114 return 0;
2118 /*************************************************************************
2119 * LISTBOX_HandleLButtonDownCombo [Internal]
2121 * Process LButtonDown message for the ComboListBox
2123 * PARAMS
2124 * pWnd [I] The windows internal structure
2125 * pDescr [I] The ListBox internal structure
2126 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2127 * x [I] X Mouse Coordinate
2128 * y [I] Y Mouse Coordinate
2130 * RETURNS
2131 * 0 since we are processing the WM_LBUTTONDOWN Message
2133 * NOTES
2134 * This function is only to be used when a ListBox is a ComboListBox
2137 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2139 RECT clientRect, screenRect;
2140 POINT mousePos;
2142 mousePos.x = x;
2143 mousePos.y = y;
2145 GetClientRect(descr->self, &clientRect);
2147 if(PtInRect(&clientRect, mousePos))
2149 /* MousePos is in client, resume normal processing */
2150 if (msg == WM_LBUTTONDOWN)
2152 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2153 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2155 else if (descr->style & LBS_NOTIFY)
2156 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2158 else
2160 POINT screenMousePos;
2161 HWND hWndOldCapture;
2163 /* Check the Non-Client Area */
2164 screenMousePos = mousePos;
2165 hWndOldCapture = GetCapture();
2166 ReleaseCapture();
2167 GetWindowRect(descr->self, &screenRect);
2168 ClientToScreen(descr->self, &screenMousePos);
2170 if(!PtInRect(&screenRect, screenMousePos))
2172 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2173 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2174 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2176 else
2178 /* Check to see the NC is a scrollbar */
2179 INT nHitTestType=0;
2180 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2181 /* Check Vertical scroll bar */
2182 if (style & WS_VSCROLL)
2184 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2185 if (PtInRect( &clientRect, mousePos ))
2186 nHitTestType = HTVSCROLL;
2188 /* Check horizontal scroll bar */
2189 if (style & WS_HSCROLL)
2191 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2192 if (PtInRect( &clientRect, mousePos ))
2193 nHitTestType = HTHSCROLL;
2195 /* Windows sends this message when a scrollbar is clicked
2198 if(nHitTestType != 0)
2200 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2201 MAKELONG(screenMousePos.x, screenMousePos.y));
2203 /* Resume the Capture after scrolling is complete
2205 if(hWndOldCapture != 0)
2206 SetCapture(hWndOldCapture);
2209 return 0;
2212 /***********************************************************************
2213 * LISTBOX_HandleLButtonUp
2215 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2217 if (LISTBOX_Timer != LB_TIMER_NONE)
2218 KillSystemTimer( descr->self, LB_TIMER_ID );
2219 LISTBOX_Timer = LB_TIMER_NONE;
2220 if (descr->captured)
2222 descr->captured = FALSE;
2223 if (GetCapture() == descr->self) ReleaseCapture();
2224 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2225 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2227 return 0;
2231 /***********************************************************************
2232 * LISTBOX_HandleTimer
2234 * Handle scrolling upon a timer event.
2235 * Return TRUE if scrolling should continue.
2237 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2239 switch(dir)
2241 case LB_TIMER_UP:
2242 if (descr->top_item) index = descr->top_item - 1;
2243 else index = 0;
2244 break;
2245 case LB_TIMER_LEFT:
2246 if (descr->top_item) index -= descr->page_size;
2247 break;
2248 case LB_TIMER_DOWN:
2249 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2250 if (index == descr->focus_item) index++;
2251 if (index >= descr->nb_items) index = descr->nb_items - 1;
2252 break;
2253 case LB_TIMER_RIGHT:
2254 if (index + descr->page_size < descr->nb_items)
2255 index += descr->page_size;
2256 break;
2257 case LB_TIMER_NONE:
2258 break;
2260 if (index == descr->focus_item) return FALSE;
2261 LISTBOX_MoveCaret( descr, index, FALSE );
2262 return TRUE;
2266 /***********************************************************************
2267 * LISTBOX_HandleSystemTimer
2269 * WM_SYSTIMER handler.
2271 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2273 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2275 KillSystemTimer( descr->self, LB_TIMER_ID );
2276 LISTBOX_Timer = LB_TIMER_NONE;
2278 return 0;
2282 /***********************************************************************
2283 * LISTBOX_HandleMouseMove
2285 * WM_MOUSEMOVE handler.
2287 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2288 INT x, INT y )
2290 INT index;
2291 TIMER_DIRECTION dir = LB_TIMER_NONE;
2293 if (!descr->captured) return;
2295 if (descr->style & LBS_MULTICOLUMN)
2297 if (y < 0) y = 0;
2298 else if (y >= descr->item_height * descr->page_size)
2299 y = descr->item_height * descr->page_size - 1;
2301 if (x < 0)
2303 dir = LB_TIMER_LEFT;
2304 x = 0;
2306 else if (x >= descr->width)
2308 dir = LB_TIMER_RIGHT;
2309 x = descr->width - 1;
2312 else
2314 if (y < 0) dir = LB_TIMER_UP; /* above */
2315 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2318 index = LISTBOX_GetItemFromPoint( descr, x, y );
2319 if (index == -1) index = descr->focus_item;
2320 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2322 /* Start/stop the system timer */
2324 if (dir != LB_TIMER_NONE)
2325 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2326 else if (LISTBOX_Timer != LB_TIMER_NONE)
2327 KillSystemTimer( descr->self, LB_TIMER_ID );
2328 LISTBOX_Timer = dir;
2332 /***********************************************************************
2333 * LISTBOX_HandleKeyDown
2335 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2337 INT caret = -1;
2338 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2339 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2340 bForceSelection = FALSE; /* only for single select list */
2342 if (descr->style & LBS_WANTKEYBOARDINPUT)
2344 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2345 MAKEWPARAM(LOWORD(key), descr->focus_item),
2346 (LPARAM)descr->self );
2347 if (caret == -2) return 0;
2349 if (caret == -1) switch(key)
2351 case VK_LEFT:
2352 if (descr->style & LBS_MULTICOLUMN)
2354 bForceSelection = FALSE;
2355 if (descr->focus_item >= descr->page_size)
2356 caret = descr->focus_item - descr->page_size;
2357 break;
2359 /* fall through */
2360 case VK_UP:
2361 caret = descr->focus_item - 1;
2362 if (caret < 0) caret = 0;
2363 break;
2364 case VK_RIGHT:
2365 if (descr->style & LBS_MULTICOLUMN)
2367 bForceSelection = FALSE;
2368 if (descr->focus_item + descr->page_size < descr->nb_items)
2369 caret = descr->focus_item + descr->page_size;
2370 break;
2372 /* fall through */
2373 case VK_DOWN:
2374 caret = descr->focus_item + 1;
2375 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2376 break;
2378 case VK_PRIOR:
2379 if (descr->style & LBS_MULTICOLUMN)
2381 INT page = descr->width / descr->column_width;
2382 if (page < 1) page = 1;
2383 caret = descr->focus_item - (page * descr->page_size) + 1;
2385 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2386 if (caret < 0) caret = 0;
2387 break;
2388 case VK_NEXT:
2389 if (descr->style & LBS_MULTICOLUMN)
2391 INT page = descr->width / descr->column_width;
2392 if (page < 1) page = 1;
2393 caret = descr->focus_item + (page * descr->page_size) - 1;
2395 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2396 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2397 break;
2398 case VK_HOME:
2399 caret = 0;
2400 break;
2401 case VK_END:
2402 caret = descr->nb_items - 1;
2403 break;
2404 case VK_SPACE:
2405 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2406 else if (descr->style & LBS_MULTIPLESEL)
2408 LISTBOX_SetSelection( descr, descr->focus_item,
2409 !descr->items[descr->focus_item].selected,
2410 (descr->style & LBS_NOTIFY) != 0 );
2412 break;
2413 default:
2414 bForceSelection = FALSE;
2416 if (bForceSelection) /* focused item is used instead of key */
2417 caret = descr->focus_item;
2418 if (caret >= 0)
2420 if (((descr->style & LBS_EXTENDEDSEL) &&
2421 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2422 !IS_MULTISELECT(descr))
2423 descr->anchor_item = caret;
2424 LISTBOX_MoveCaret( descr, caret, TRUE );
2426 if (descr->style & LBS_MULTIPLESEL)
2427 descr->selected_item = caret;
2428 else
2429 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2430 if (descr->style & LBS_NOTIFY)
2432 if (descr->lphc && IsWindowVisible( descr->self ))
2434 /* make sure that combo parent doesn't hide us */
2435 descr->lphc->wState |= CBF_NOROLLUP;
2437 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2440 return 0;
2444 /***********************************************************************
2445 * LISTBOX_HandleChar
2447 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2449 INT caret = -1;
2450 WCHAR str[2];
2452 str[0] = charW;
2453 str[1] = '\0';
2455 if (descr->style & LBS_WANTKEYBOARDINPUT)
2457 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2458 MAKEWPARAM(charW, descr->focus_item),
2459 (LPARAM)descr->self );
2460 if (caret == -2) return 0;
2462 if (caret == -1)
2463 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2464 if (caret != -1)
2466 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2467 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2468 LISTBOX_MoveCaret( descr, caret, TRUE );
2469 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2470 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2472 return 0;
2476 /***********************************************************************
2477 * LISTBOX_Create
2479 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2481 LB_DESCR *descr;
2482 MEASUREITEMSTRUCT mis;
2483 RECT rect;
2485 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2486 return FALSE;
2488 GetClientRect( hwnd, &rect );
2489 descr->self = hwnd;
2490 descr->owner = GetParent( descr->self );
2491 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2492 descr->width = rect.right - rect.left;
2493 descr->height = rect.bottom - rect.top;
2494 descr->items = NULL;
2495 descr->nb_items = 0;
2496 descr->top_item = 0;
2497 descr->selected_item = -1;
2498 descr->focus_item = 0;
2499 descr->anchor_item = -1;
2500 descr->item_height = 1;
2501 descr->page_size = 1;
2502 descr->column_width = 150;
2503 descr->horz_extent = 0;
2504 descr->horz_pos = 0;
2505 descr->nb_tabs = 0;
2506 descr->tabs = NULL;
2507 descr->wheel_remain = 0;
2508 descr->caret_on = !lphc;
2509 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2510 descr->in_focus = FALSE;
2511 descr->captured = FALSE;
2512 descr->font = 0;
2513 descr->locale = GetUserDefaultLCID();
2514 descr->lphc = lphc;
2516 if( lphc )
2518 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2519 descr->owner = lphc->self;
2522 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2524 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2526 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2527 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2528 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2529 descr->item_height = LISTBOX_SetFont( descr, 0 );
2531 if (descr->style & LBS_OWNERDRAWFIXED)
2533 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2535 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2536 descr->item_height = lphc->fixedOwnerDrawHeight;
2538 else
2540 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2541 mis.CtlType = ODT_LISTBOX;
2542 mis.CtlID = id;
2543 mis.itemID = -1;
2544 mis.itemWidth = 0;
2545 mis.itemData = 0;
2546 mis.itemHeight = descr->item_height;
2547 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2548 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2552 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2553 return TRUE;
2557 /***********************************************************************
2558 * LISTBOX_Destroy
2560 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2562 LISTBOX_ResetContent( descr );
2563 SetWindowLongPtrW( descr->self, 0, 0 );
2564 HeapFree( GetProcessHeap(), 0, descr );
2565 return TRUE;
2569 /***********************************************************************
2570 * ListBoxWndProc_common
2572 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2574 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2575 LPHEADCOMBO lphc = 0;
2576 LRESULT ret;
2578 if (!descr)
2580 if (!IsWindow(hwnd)) return 0;
2582 if (msg == WM_CREATE)
2584 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2585 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2586 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2587 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2588 return 0;
2590 /* Ignore all other messages before we get a WM_CREATE */
2591 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2592 DefWindowProcA( hwnd, msg, wParam, lParam );
2594 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2596 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2597 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2599 switch(msg)
2601 case LB_RESETCONTENT:
2602 LISTBOX_ResetContent( descr );
2603 LISTBOX_UpdateScroll( descr );
2604 InvalidateRect( descr->self, NULL, TRUE );
2605 return 0;
2607 case LB_ADDSTRING:
2609 INT ret;
2610 LPWSTR textW;
2611 if(unicode || !HAS_STRINGS(descr))
2612 textW = (LPWSTR)lParam;
2613 else
2615 LPSTR textA = (LPSTR)lParam;
2616 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2617 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2618 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2619 else
2620 return LB_ERRSPACE;
2622 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2623 ret = LISTBOX_InsertString( descr, wParam, textW );
2624 if (!unicode && HAS_STRINGS(descr))
2625 HeapFree(GetProcessHeap(), 0, textW);
2626 return ret;
2629 case LB_INSERTSTRING:
2631 INT ret;
2632 LPWSTR textW;
2633 if(unicode || !HAS_STRINGS(descr))
2634 textW = (LPWSTR)lParam;
2635 else
2637 LPSTR textA = (LPSTR)lParam;
2638 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2639 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2640 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2641 else
2642 return LB_ERRSPACE;
2644 ret = LISTBOX_InsertString( descr, wParam, textW );
2645 if(!unicode && HAS_STRINGS(descr))
2646 HeapFree(GetProcessHeap(), 0, textW);
2647 return ret;
2650 case LB_ADDFILE:
2652 INT ret;
2653 LPWSTR textW;
2654 if(unicode || !HAS_STRINGS(descr))
2655 textW = (LPWSTR)lParam;
2656 else
2658 LPSTR textA = (LPSTR)lParam;
2659 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2660 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2661 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2662 else
2663 return LB_ERRSPACE;
2665 wParam = LISTBOX_FindFileStrPos( descr, textW );
2666 ret = LISTBOX_InsertString( descr, wParam, textW );
2667 if(!unicode && HAS_STRINGS(descr))
2668 HeapFree(GetProcessHeap(), 0, textW);
2669 return ret;
2672 case LB_DELETESTRING:
2673 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2674 return descr->nb_items;
2675 else
2677 SetLastError(ERROR_INVALID_INDEX);
2678 return LB_ERR;
2681 case LB_GETITEMDATA:
2682 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2684 SetLastError(ERROR_INVALID_INDEX);
2685 return LB_ERR;
2687 return descr->items[wParam].data;
2689 case LB_SETITEMDATA:
2690 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2692 SetLastError(ERROR_INVALID_INDEX);
2693 return LB_ERR;
2695 descr->items[wParam].data = lParam;
2696 /* undocumented: returns TRUE, not LB_OKAY (0) */
2697 return TRUE;
2699 case LB_GETCOUNT:
2700 return descr->nb_items;
2702 case LB_GETTEXT:
2703 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2705 case LB_GETTEXTLEN:
2706 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2708 SetLastError(ERROR_INVALID_INDEX);
2709 return LB_ERR;
2711 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2712 if (unicode) return strlenW( descr->items[wParam].str );
2713 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2714 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2716 case LB_GETCURSEL:
2717 if (descr->nb_items == 0)
2718 return LB_ERR;
2719 if (!IS_MULTISELECT(descr))
2720 return descr->selected_item;
2721 if (descr->selected_item != -1)
2722 return descr->selected_item;
2723 return descr->focus_item;
2724 /* otherwise, if the user tries to move the selection with the */
2725 /* arrow keys, we will give the application something to choke on */
2726 case LB_GETTOPINDEX:
2727 return descr->top_item;
2729 case LB_GETITEMHEIGHT:
2730 return LISTBOX_GetItemHeight( descr, wParam );
2732 case LB_SETITEMHEIGHT:
2733 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2735 case LB_ITEMFROMPOINT:
2737 POINT pt;
2738 RECT rect;
2739 int index;
2740 BOOL hit = TRUE;
2742 /* The hiword of the return value is not a client area
2743 hittest as suggested by MSDN, but rather a hittest on
2744 the returned listbox item. */
2746 if(descr->nb_items == 0)
2747 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2749 pt.x = (short)LOWORD(lParam);
2750 pt.y = (short)HIWORD(lParam);
2752 SetRect(&rect, 0, 0, descr->width, descr->height);
2754 if(!PtInRect(&rect, pt))
2756 pt.x = min(pt.x, rect.right - 1);
2757 pt.x = max(pt.x, 0);
2758 pt.y = min(pt.y, rect.bottom - 1);
2759 pt.y = max(pt.y, 0);
2760 hit = FALSE;
2763 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2765 if(index == -1)
2767 index = descr->nb_items - 1;
2768 hit = FALSE;
2770 return MAKELONG(index, hit ? 0 : 1);
2773 case LB_SETCARETINDEX:
2774 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2775 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2776 return LB_ERR;
2777 else if (ISWIN31)
2778 return wParam;
2779 else
2780 return LB_OKAY;
2782 case LB_GETCARETINDEX:
2783 return descr->focus_item;
2785 case LB_SETTOPINDEX:
2786 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2788 case LB_SETCOLUMNWIDTH:
2789 return LISTBOX_SetColumnWidth( descr, wParam );
2791 case LB_GETITEMRECT:
2792 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2794 case LB_FINDSTRING:
2796 INT ret;
2797 LPWSTR textW;
2798 if(unicode || !HAS_STRINGS(descr))
2799 textW = (LPWSTR)lParam;
2800 else
2802 LPSTR textA = (LPSTR)lParam;
2803 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2804 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2805 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2807 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2808 if(!unicode && HAS_STRINGS(descr))
2809 HeapFree(GetProcessHeap(), 0, textW);
2810 return ret;
2813 case LB_FINDSTRINGEXACT:
2815 INT ret;
2816 LPWSTR textW;
2817 if(unicode || !HAS_STRINGS(descr))
2818 textW = (LPWSTR)lParam;
2819 else
2821 LPSTR textA = (LPSTR)lParam;
2822 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2823 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2824 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2826 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2827 if(!unicode && HAS_STRINGS(descr))
2828 HeapFree(GetProcessHeap(), 0, textW);
2829 return ret;
2832 case LB_SELECTSTRING:
2834 INT index;
2835 LPWSTR textW;
2837 if(HAS_STRINGS(descr))
2838 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2839 debugstr_a((LPSTR)lParam));
2840 if(unicode || !HAS_STRINGS(descr))
2841 textW = (LPWSTR)lParam;
2842 else
2844 LPSTR textA = (LPSTR)lParam;
2845 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2846 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2847 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2849 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2850 if(!unicode && HAS_STRINGS(descr))
2851 HeapFree(GetProcessHeap(), 0, textW);
2852 if (index != LB_ERR)
2854 LISTBOX_MoveCaret( descr, index, TRUE );
2855 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2857 return index;
2860 case LB_GETSEL:
2861 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2862 return LB_ERR;
2863 return descr->items[wParam].selected;
2865 case LB_SETSEL:
2866 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2868 case LB_SETCURSEL:
2869 if (IS_MULTISELECT(descr)) return LB_ERR;
2870 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2871 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2872 if (ret != LB_ERR) ret = descr->selected_item;
2873 return ret;
2875 case LB_GETSELCOUNT:
2876 return LISTBOX_GetSelCount( descr );
2878 case LB_GETSELITEMS:
2879 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2881 case LB_SELITEMRANGE:
2882 if (LOWORD(lParam) <= HIWORD(lParam))
2883 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2884 HIWORD(lParam), wParam );
2885 else
2886 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2887 LOWORD(lParam), wParam );
2889 case LB_SELITEMRANGEEX:
2890 if ((INT)lParam >= (INT)wParam)
2891 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2892 else
2893 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2895 case LB_GETHORIZONTALEXTENT:
2896 return descr->horz_extent;
2898 case LB_SETHORIZONTALEXTENT:
2899 return LISTBOX_SetHorizontalExtent( descr, wParam );
2901 case LB_GETANCHORINDEX:
2902 return descr->anchor_item;
2904 case LB_SETANCHORINDEX:
2905 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2907 SetLastError(ERROR_INVALID_INDEX);
2908 return LB_ERR;
2910 descr->anchor_item = (INT)wParam;
2911 return LB_OKAY;
2913 case LB_DIR:
2915 INT ret;
2916 LPWSTR textW;
2917 if(unicode)
2918 textW = (LPWSTR)lParam;
2919 else
2921 LPSTR textA = (LPSTR)lParam;
2922 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2923 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2924 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2926 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2927 if(!unicode)
2928 HeapFree(GetProcessHeap(), 0, textW);
2929 return ret;
2932 case LB_GETLOCALE:
2933 return descr->locale;
2935 case LB_SETLOCALE:
2937 LCID ret;
2938 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2939 return LB_ERR;
2940 ret = descr->locale;
2941 descr->locale = (LCID)wParam;
2942 return ret;
2945 case LB_INITSTORAGE:
2946 return LISTBOX_InitStorage( descr, wParam );
2948 case LB_SETCOUNT:
2949 return LISTBOX_SetCount( descr, (INT)wParam );
2951 case LB_SETTABSTOPS:
2952 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2954 case LB_CARETON:
2955 if (descr->caret_on)
2956 return LB_OKAY;
2957 descr->caret_on = TRUE;
2958 if ((descr->focus_item != -1) && (descr->in_focus))
2959 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2960 return LB_OKAY;
2962 case LB_CARETOFF:
2963 if (!descr->caret_on)
2964 return LB_OKAY;
2965 descr->caret_on = FALSE;
2966 if ((descr->focus_item != -1) && (descr->in_focus))
2967 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2968 return LB_OKAY;
2970 case LB_GETLISTBOXINFO:
2971 return descr->page_size;
2973 case WM_DESTROY:
2974 return LISTBOX_Destroy( descr );
2976 case WM_ENABLE:
2977 InvalidateRect( descr->self, NULL, TRUE );
2978 return 0;
2980 case WM_SETREDRAW:
2981 LISTBOX_SetRedraw( descr, wParam != 0 );
2982 return 0;
2984 case WM_GETDLGCODE:
2985 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2987 case WM_PRINTCLIENT:
2988 case WM_PAINT:
2990 PAINTSTRUCT ps;
2991 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2992 ret = LISTBOX_Paint( descr, hdc );
2993 if( !wParam ) EndPaint( descr->self, &ps );
2995 return ret;
2996 case WM_SIZE:
2997 LISTBOX_UpdateSize( descr );
2998 return 0;
2999 case WM_GETFONT:
3000 return (LRESULT)descr->font;
3001 case WM_SETFONT:
3002 LISTBOX_SetFont( descr, (HFONT)wParam );
3003 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3004 return 0;
3005 case WM_SETFOCUS:
3006 descr->in_focus = TRUE;
3007 descr->caret_on = TRUE;
3008 if (descr->focus_item != -1)
3009 LISTBOX_DrawFocusRect( descr, TRUE );
3010 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3011 return 0;
3012 case WM_KILLFOCUS:
3013 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3014 descr->in_focus = FALSE;
3015 descr->wheel_remain = 0;
3016 if ((descr->focus_item != -1) && descr->caret_on)
3017 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3018 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3019 return 0;
3020 case WM_HSCROLL:
3021 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3022 case WM_VSCROLL:
3023 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3024 case WM_MOUSEWHEEL:
3025 if (wParam & (MK_SHIFT | MK_CONTROL))
3026 return DefWindowProcW( descr->self, msg, wParam, lParam );
3027 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3028 case WM_LBUTTONDOWN:
3029 if (lphc)
3030 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3031 (INT16)LOWORD(lParam),
3032 (INT16)HIWORD(lParam) );
3033 return LISTBOX_HandleLButtonDown( descr, wParam,
3034 (INT16)LOWORD(lParam),
3035 (INT16)HIWORD(lParam) );
3036 case WM_LBUTTONDBLCLK:
3037 if (lphc)
3038 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3039 (INT16)LOWORD(lParam),
3040 (INT16)HIWORD(lParam) );
3041 if (descr->style & LBS_NOTIFY)
3042 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3043 return 0;
3044 case WM_MOUSEMOVE:
3045 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3047 BOOL captured = descr->captured;
3048 POINT mousePos;
3049 RECT clientRect;
3051 mousePos.x = (INT16)LOWORD(lParam);
3052 mousePos.y = (INT16)HIWORD(lParam);
3055 * If we are in a dropdown combobox, we simulate that
3056 * the mouse is captured to show the tracking of the item.
3058 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3059 descr->captured = TRUE;
3061 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3063 descr->captured = captured;
3065 else if (GetCapture() == descr->self)
3067 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3068 (INT16)HIWORD(lParam) );
3070 return 0;
3071 case WM_LBUTTONUP:
3072 if (lphc)
3074 POINT mousePos;
3075 RECT clientRect;
3078 * If the mouse button "up" is not in the listbox,
3079 * we make sure there is no selection by re-selecting the
3080 * item that was selected when the listbox was made visible.
3082 mousePos.x = (INT16)LOWORD(lParam);
3083 mousePos.y = (INT16)HIWORD(lParam);
3085 GetClientRect(descr->self, &clientRect);
3088 * When the user clicks outside the combobox and the focus
3089 * is lost, the owning combobox will send a fake buttonup with
3090 * 0xFFFFFFF as the mouse location, we must also revert the
3091 * selection to the original selection.
3093 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3094 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3096 return LISTBOX_HandleLButtonUp( descr );
3097 case WM_KEYDOWN:
3098 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3100 /* for some reason Windows makes it possible to
3101 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3103 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3104 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3105 && (wParam == VK_DOWN || wParam == VK_UP)) )
3107 COMBO_FlipListbox( lphc, FALSE, FALSE );
3108 return 0;
3111 return LISTBOX_HandleKeyDown( descr, wParam );
3112 case WM_CHAR:
3114 WCHAR charW;
3115 if(unicode)
3116 charW = (WCHAR)wParam;
3117 else
3119 CHAR charA = (CHAR)wParam;
3120 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3122 return LISTBOX_HandleChar( descr, charW );
3124 case WM_SYSTIMER:
3125 return LISTBOX_HandleSystemTimer( descr );
3126 case WM_ERASEBKGND:
3127 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3129 RECT rect;
3130 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3131 wParam, (LPARAM)descr->self );
3132 TRACE("hbrush = %p\n", hbrush);
3133 if(!hbrush)
3134 hbrush = GetSysColorBrush(COLOR_WINDOW);
3135 if(hbrush)
3137 GetClientRect(descr->self, &rect);
3138 FillRect((HDC)wParam, &rect, hbrush);
3141 return 1;
3142 case WM_DROPFILES:
3143 if( lphc ) return 0;
3144 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3145 SendMessageA( descr->owner, msg, wParam, lParam );
3147 case WM_NCDESTROY:
3148 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3149 lphc->hWndLBox = 0;
3150 break;
3152 case WM_NCACTIVATE:
3153 if (lphc) return 0;
3154 break;
3156 default:
3157 if ((msg >= WM_USER) && (msg < 0xc000))
3158 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3159 hwnd, msg, wParam, lParam );
3162 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3163 DefWindowProcA( hwnd, msg, wParam, lParam );
3166 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3168 TRACE("%p\n", hwnd);
3169 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);