d2d1: Add a pixel shader for triangle/solid/bitmap draws.
[wine.git] / dlls / user32 / listbox.c
blobb73aa79e0d1333e787f96f4b3b21308226436b7c
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 memset(rect, 0, sizeof(*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 if (i == descr->focus_item)
1064 /* keep the focus rect, to paint the focus item after */
1065 focusRect.left = rect.left;
1066 focusRect.right = rect.right;
1067 focusRect.top = rect.top;
1068 focusRect.bottom = rect.bottom;
1070 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1071 rect.top = rect.bottom;
1073 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1075 if (!IS_OWNERDRAW(descr))
1077 /* Clear the bottom of the column */
1078 if (rect.top < descr->height)
1080 rect.bottom = descr->height;
1081 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1082 &rect, NULL, 0, NULL );
1086 /* Go to the next column */
1087 rect.left += descr->column_width;
1088 rect.right += descr->column_width;
1089 rect.top = 0;
1090 col_pos = descr->page_size - 1;
1092 else
1094 col_pos--;
1095 if (rect.top >= descr->height) break;
1099 /* Paint the focus item now */
1100 if (focusRect.top != focusRect.bottom &&
1101 descr->caret_on && descr->in_focus)
1102 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1104 if (!IS_OWNERDRAW(descr))
1106 /* Clear the remainder of the client area */
1107 if (rect.top < descr->height)
1109 rect.bottom = descr->height;
1110 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1111 &rect, NULL, 0, NULL );
1113 if (rect.right < descr->width)
1115 rect.left = rect.right;
1116 rect.right = descr->width;
1117 rect.top = 0;
1118 rect.bottom = descr->height;
1119 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1120 &rect, NULL, 0, NULL );
1123 if (oldFont) SelectObject( hdc, oldFont );
1124 if (oldBrush) SelectObject( hdc, oldBrush );
1125 return 0;
1129 /***********************************************************************
1130 * LISTBOX_InvalidateItems
1132 * Invalidate all items from a given item. If the specified item is not
1133 * visible, nothing happens.
1135 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1137 RECT rect;
1139 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1141 if (descr->style & LBS_NOREDRAW)
1143 descr->style |= LBS_DISPLAYCHANGED;
1144 return;
1146 rect.bottom = descr->height;
1147 InvalidateRect( descr->self, &rect, TRUE );
1148 if (descr->style & LBS_MULTICOLUMN)
1150 /* Repaint the other columns */
1151 rect.left = rect.right;
1152 rect.right = descr->width;
1153 rect.top = 0;
1154 InvalidateRect( descr->self, &rect, TRUE );
1159 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1161 RECT rect;
1163 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1164 InvalidateRect( descr->self, &rect, TRUE );
1167 /***********************************************************************
1168 * LISTBOX_GetItemHeight
1170 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1172 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1174 if ((index < 0) || (index >= descr->nb_items))
1176 SetLastError(ERROR_INVALID_INDEX);
1177 return LB_ERR;
1179 return descr->items[index].height;
1181 else return descr->item_height;
1185 /***********************************************************************
1186 * LISTBOX_SetItemHeight
1188 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1190 if (height > MAXBYTE)
1191 return -1;
1193 if (!height) height = 1;
1195 if (descr->style & LBS_OWNERDRAWVARIABLE)
1197 if ((index < 0) || (index >= descr->nb_items))
1199 SetLastError(ERROR_INVALID_INDEX);
1200 return LB_ERR;
1202 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1203 descr->items[index].height = height;
1204 LISTBOX_UpdateScroll( descr );
1205 if (repaint)
1206 LISTBOX_InvalidateItems( descr, index );
1208 else if (height != descr->item_height)
1210 TRACE("[%p]: new height = %d\n", descr->self, height );
1211 descr->item_height = height;
1212 LISTBOX_UpdatePage( descr );
1213 LISTBOX_UpdateScroll( descr );
1214 if (repaint)
1215 InvalidateRect( descr->self, 0, TRUE );
1217 return LB_OKAY;
1221 /***********************************************************************
1222 * LISTBOX_SetHorizontalPos
1224 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1226 INT diff;
1228 if (pos > descr->horz_extent - descr->width)
1229 pos = descr->horz_extent - descr->width;
1230 if (pos < 0) pos = 0;
1231 if (!(diff = descr->horz_pos - pos)) return;
1232 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1233 descr->horz_pos = pos;
1234 LISTBOX_UpdateScroll( descr );
1235 if (abs(diff) < descr->width)
1237 RECT rect;
1238 /* Invalidate the focused item so it will be repainted correctly */
1239 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1240 InvalidateRect( descr->self, &rect, TRUE );
1241 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1242 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1244 else
1245 InvalidateRect( descr->self, NULL, TRUE );
1249 /***********************************************************************
1250 * LISTBOX_SetHorizontalExtent
1252 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1254 if (descr->style & LBS_MULTICOLUMN)
1255 return LB_OKAY;
1256 if (extent == descr->horz_extent) return LB_OKAY;
1257 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1258 descr->horz_extent = extent;
1259 if (descr->style & WS_HSCROLL) {
1260 SCROLLINFO info;
1261 info.cbSize = sizeof(info);
1262 info.nMin = 0;
1263 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1264 info.fMask = SIF_RANGE;
1265 if (descr->style & LBS_DISABLENOSCROLL)
1266 info.fMask |= SIF_DISABLENOSCROLL;
1267 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1269 if (descr->horz_pos > extent - descr->width)
1270 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1271 return LB_OKAY;
1275 /***********************************************************************
1276 * LISTBOX_SetColumnWidth
1278 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1280 if (width == descr->column_width) return LB_OKAY;
1281 TRACE("[%p]: new column width = %d\n", descr->self, width );
1282 descr->column_width = width;
1283 LISTBOX_UpdatePage( descr );
1284 return LB_OKAY;
1288 /***********************************************************************
1289 * LISTBOX_SetFont
1291 * Returns the item height.
1293 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1295 HDC hdc;
1296 HFONT oldFont = 0;
1297 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1298 SIZE sz;
1300 descr->font = font;
1302 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1304 ERR("unable to get DC.\n" );
1305 return 16;
1307 if (font) oldFont = SelectObject( hdc, font );
1308 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1309 if (oldFont) SelectObject( hdc, oldFont );
1310 ReleaseDC( descr->self, hdc );
1312 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1313 if (!IS_OWNERDRAW(descr))
1314 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1315 return sz.cy;
1319 /***********************************************************************
1320 * LISTBOX_MakeItemVisible
1322 * Make sure that a given item is partially or fully visible.
1324 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1326 INT top;
1328 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1330 if (index <= descr->top_item) top = index;
1331 else if (descr->style & LBS_MULTICOLUMN)
1333 INT cols = descr->width;
1334 if (!fully) cols += descr->column_width - 1;
1335 if (cols >= descr->column_width) cols /= descr->column_width;
1336 else cols = 1;
1337 if (index < descr->top_item + (descr->page_size * cols)) return;
1338 top = index - descr->page_size * (cols - 1);
1340 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1342 INT height = fully ? descr->items[index].height : 1;
1343 for (top = index; top > descr->top_item; top--)
1344 if ((height += descr->items[top-1].height) > descr->height) break;
1346 else
1348 if (index < descr->top_item + descr->page_size) return;
1349 if (!fully && (index == descr->top_item + descr->page_size) &&
1350 (descr->height > (descr->page_size * descr->item_height))) return;
1351 top = index - descr->page_size + 1;
1353 LISTBOX_SetTopItem( descr, top, TRUE );
1356 /***********************************************************************
1357 * LISTBOX_SetCaretIndex
1359 * NOTES
1360 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1363 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1365 INT oldfocus = descr->focus_item;
1367 TRACE("old focus %d, index %d\n", oldfocus, index);
1369 if (descr->style & LBS_NOSEL) return LB_ERR;
1370 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1371 if (index == oldfocus) return LB_OKAY;
1373 LISTBOX_DrawFocusRect( descr, FALSE );
1374 descr->focus_item = index;
1376 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1377 LISTBOX_DrawFocusRect( descr, TRUE );
1379 return LB_OKAY;
1383 /***********************************************************************
1384 * LISTBOX_SelectItemRange
1386 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1388 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1389 INT last, BOOL on )
1391 INT i;
1393 /* A few sanity checks */
1395 if (descr->style & LBS_NOSEL) return LB_ERR;
1396 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1398 if (!descr->nb_items) return LB_OKAY;
1400 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1401 if (first < 0) first = 0;
1402 if (last < first) return LB_OKAY;
1404 if (on) /* Turn selection on */
1406 for (i = first; i <= last; i++)
1408 if (descr->items[i].selected) continue;
1409 descr->items[i].selected = TRUE;
1410 LISTBOX_InvalidateItemRect(descr, i);
1413 else /* Turn selection off */
1415 for (i = first; i <= last; i++)
1417 if (!descr->items[i].selected) continue;
1418 descr->items[i].selected = FALSE;
1419 LISTBOX_InvalidateItemRect(descr, i);
1422 return LB_OKAY;
1425 /***********************************************************************
1426 * LISTBOX_SetSelection
1428 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1429 BOOL on, BOOL send_notify )
1431 TRACE( "cur_sel=%d index=%d notify=%s\n",
1432 descr->selected_item, index, send_notify ? "YES" : "NO" );
1434 if (descr->style & LBS_NOSEL)
1436 descr->selected_item = index;
1437 return LB_ERR;
1439 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1440 if (descr->style & LBS_MULTIPLESEL)
1442 if (index == -1) /* Select all items */
1443 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1444 else /* Only one item */
1445 return LISTBOX_SelectItemRange( descr, index, index, on );
1447 else
1449 INT oldsel = descr->selected_item;
1450 if (index == oldsel) return LB_OKAY;
1451 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1452 if (index != -1) descr->items[index].selected = TRUE;
1453 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1454 descr->selected_item = index;
1455 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1456 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1457 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1458 else
1459 if( descr->lphc ) /* set selection change flag for parent combo */
1460 descr->lphc->wState |= CBF_SELCHANGE;
1462 return LB_OKAY;
1466 /***********************************************************************
1467 * LISTBOX_MoveCaret
1469 * Change the caret position and extend the selection to the new caret.
1471 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1473 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1475 if ((index < 0) || (index >= descr->nb_items))
1476 return;
1478 /* Important, repaint needs to be done in this order if
1479 you want to mimic Windows behavior:
1480 1. Remove the focus and paint the item
1481 2. Remove the selection and paint the item(s)
1482 3. Set the selection and repaint the item(s)
1483 4. Set the focus to 'index' and repaint the item */
1485 /* 1. remove the focus and repaint the item */
1486 LISTBOX_DrawFocusRect( descr, FALSE );
1488 /* 2. then turn off the previous selection */
1489 /* 3. repaint the new selected item */
1490 if (descr->style & LBS_EXTENDEDSEL)
1492 if (descr->anchor_item != -1)
1494 INT first = min( index, descr->anchor_item );
1495 INT last = max( index, descr->anchor_item );
1496 if (first > 0)
1497 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1498 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1499 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1502 else if (!(descr->style & LBS_MULTIPLESEL))
1504 /* Set selection to new caret item */
1505 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1508 /* 4. repaint the new item with the focus */
1509 descr->focus_item = index;
1510 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1511 LISTBOX_DrawFocusRect( descr, TRUE );
1515 /***********************************************************************
1516 * LISTBOX_InsertItem
1518 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1519 LPWSTR str, ULONG_PTR data )
1521 LB_ITEMDATA *item;
1522 INT max_items;
1523 INT oldfocus = descr->focus_item;
1525 if (index == -1) index = descr->nb_items;
1526 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1527 if (!descr->items) max_items = 0;
1528 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1529 if (descr->nb_items == max_items)
1531 /* We need to grow the array */
1532 max_items += LB_ARRAY_GRANULARITY;
1533 if (descr->items)
1534 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1535 max_items * sizeof(LB_ITEMDATA) );
1536 else
1537 item = HeapAlloc( GetProcessHeap(), 0,
1538 max_items * sizeof(LB_ITEMDATA) );
1539 if (!item)
1541 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1542 return LB_ERRSPACE;
1544 descr->items = item;
1547 /* Insert the item structure */
1549 item = &descr->items[index];
1550 if (index < descr->nb_items)
1551 RtlMoveMemory( item + 1, item,
1552 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1553 item->str = str;
1554 item->data = data;
1555 item->height = 0;
1556 item->selected = FALSE;
1557 descr->nb_items++;
1559 /* Get item height */
1561 if (descr->style & LBS_OWNERDRAWVARIABLE)
1563 MEASUREITEMSTRUCT mis;
1564 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1566 mis.CtlType = ODT_LISTBOX;
1567 mis.CtlID = id;
1568 mis.itemID = index;
1569 mis.itemData = descr->items[index].data;
1570 mis.itemHeight = descr->item_height;
1571 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1572 item->height = mis.itemHeight ? mis.itemHeight : 1;
1573 TRACE("[%p]: measure item %d (%s) = %d\n",
1574 descr->self, index, str ? debugstr_w(str) : "", item->height );
1577 /* Repaint the items */
1579 LISTBOX_UpdateScroll( descr );
1580 LISTBOX_InvalidateItems( descr, index );
1582 /* Move selection and focused item */
1583 /* If listbox was empty, set focus to the first item */
1584 if (descr->nb_items == 1)
1585 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1586 /* single select don't change selection index in win31 */
1587 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1589 descr->selected_item++;
1590 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1592 else
1594 if (index <= descr->selected_item)
1596 descr->selected_item++;
1597 descr->focus_item = oldfocus; /* focus not changed */
1600 return LB_OKAY;
1604 /***********************************************************************
1605 * LISTBOX_InsertString
1607 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1609 LPWSTR new_str = NULL;
1610 ULONG_PTR data = 0;
1611 LRESULT ret;
1613 if (HAS_STRINGS(descr))
1615 static const WCHAR empty_stringW[] = { 0 };
1616 if (!str) str = empty_stringW;
1617 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1619 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1620 return LB_ERRSPACE;
1622 strcpyW(new_str, str);
1624 else data = (ULONG_PTR)str;
1626 if (index == -1) index = descr->nb_items;
1627 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1629 HeapFree( GetProcessHeap(), 0, new_str );
1630 return ret;
1633 TRACE("[%p]: added item %d %s\n",
1634 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1635 return index;
1639 /***********************************************************************
1640 * LISTBOX_DeleteItem
1642 * Delete the content of an item. 'index' must be a valid index.
1644 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1646 /* save the item data before it gets freed by LB_RESETCONTENT */
1647 ULONG_PTR item_data = descr->items[index].data;
1648 LPWSTR item_str = descr->items[index].str;
1650 if (!descr->nb_items)
1651 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1653 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1654 * while Win95 sends it for all items with user data.
1655 * It's probably better to send it too often than not
1656 * often enough, so this is what we do here.
1658 if (IS_OWNERDRAW(descr) || item_data)
1660 DELETEITEMSTRUCT dis;
1661 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1663 dis.CtlType = ODT_LISTBOX;
1664 dis.CtlID = id;
1665 dis.itemID = index;
1666 dis.hwndItem = descr->self;
1667 dis.itemData = item_data;
1668 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1670 if (HAS_STRINGS(descr))
1671 HeapFree( GetProcessHeap(), 0, item_str );
1675 /***********************************************************************
1676 * LISTBOX_RemoveItem
1678 * Remove an item from the listbox and delete its content.
1680 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1682 LB_ITEMDATA *item;
1683 INT max_items;
1685 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1687 /* We need to invalidate the original rect instead of the updated one. */
1688 LISTBOX_InvalidateItems( descr, index );
1690 descr->nb_items--;
1691 LISTBOX_DeleteItem( descr, index );
1693 if (!descr->nb_items) return LB_OKAY;
1695 /* Remove the item */
1697 item = &descr->items[index];
1698 if (index < descr->nb_items)
1699 RtlMoveMemory( item, item + 1,
1700 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1701 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1703 /* Shrink the item array if possible */
1705 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1706 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1708 max_items -= LB_ARRAY_GRANULARITY;
1709 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1710 max_items * sizeof(LB_ITEMDATA) );
1711 if (item) descr->items = item;
1713 /* Repaint the items */
1715 LISTBOX_UpdateScroll( descr );
1716 /* if we removed the scrollbar, reset the top of the list
1717 (correct for owner-drawn ???) */
1718 if (descr->nb_items == descr->page_size)
1719 LISTBOX_SetTopItem( descr, 0, TRUE );
1721 /* Move selection and focused item */
1722 if (!IS_MULTISELECT(descr))
1724 if (index == descr->selected_item)
1725 descr->selected_item = -1;
1726 else if (index < descr->selected_item)
1728 descr->selected_item--;
1729 if (ISWIN31) /* win 31 do not change the selected item number */
1730 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1734 if (descr->focus_item >= descr->nb_items)
1736 descr->focus_item = descr->nb_items - 1;
1737 if (descr->focus_item < 0) descr->focus_item = 0;
1739 return LB_OKAY;
1743 /***********************************************************************
1744 * LISTBOX_ResetContent
1746 static void LISTBOX_ResetContent( LB_DESCR *descr )
1748 INT i;
1750 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1751 HeapFree( GetProcessHeap(), 0, descr->items );
1752 descr->nb_items = 0;
1753 descr->top_item = 0;
1754 descr->selected_item = -1;
1755 descr->focus_item = 0;
1756 descr->anchor_item = -1;
1757 descr->items = NULL;
1761 /***********************************************************************
1762 * LISTBOX_SetCount
1764 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1766 LRESULT ret;
1768 if (HAS_STRINGS(descr))
1770 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1771 return LB_ERR;
1774 /* FIXME: this is far from optimal... */
1775 if (count > descr->nb_items)
1777 while (count > descr->nb_items)
1778 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1779 return ret;
1781 else if (count < descr->nb_items)
1783 while (count < descr->nb_items)
1784 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1785 return ret;
1788 InvalidateRect( descr->self, NULL, TRUE );
1789 return LB_OKAY;
1793 /***********************************************************************
1794 * LISTBOX_Directory
1796 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1797 LPCWSTR filespec, BOOL long_names )
1799 HANDLE handle;
1800 LRESULT ret = LB_OKAY;
1801 WIN32_FIND_DATAW entry;
1802 int pos;
1803 LRESULT maxinsert = LB_ERR;
1805 /* don't scan directory if we just want drives exclusively */
1806 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1807 /* scan directory */
1808 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1810 int le = GetLastError();
1811 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1813 else
1817 WCHAR buffer[270];
1818 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1820 static const WCHAR bracketW[] = { ']',0 };
1821 static const WCHAR dotW[] = { '.',0 };
1822 if (!(attrib & DDL_DIRECTORY) ||
1823 !strcmpW( entry.cFileName, dotW )) continue;
1824 buffer[0] = '[';
1825 if (!long_names && entry.cAlternateFileName[0])
1826 strcpyW( buffer + 1, entry.cAlternateFileName );
1827 else
1828 strcpyW( buffer + 1, entry.cFileName );
1829 strcatW(buffer, bracketW);
1831 else /* not a directory */
1833 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1834 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1836 if ((attrib & DDL_EXCLUSIVE) &&
1837 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1838 continue;
1839 #undef ATTRIBS
1840 if (!long_names && entry.cAlternateFileName[0])
1841 strcpyW( buffer, entry.cAlternateFileName );
1842 else
1843 strcpyW( buffer, entry.cFileName );
1845 if (!long_names) CharLowerW( buffer );
1846 pos = LISTBOX_FindFileStrPos( descr, buffer );
1847 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1848 break;
1849 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1850 } while (FindNextFileW( handle, &entry ));
1851 FindClose( handle );
1854 if (ret >= 0)
1856 ret = maxinsert;
1858 /* scan drives */
1859 if (attrib & DDL_DRIVES)
1861 WCHAR buffer[] = {'[','-','a','-',']',0};
1862 WCHAR root[] = {'A',':','\\',0};
1863 int drive;
1864 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1866 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1867 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1868 break;
1872 return ret;
1876 /***********************************************************************
1877 * LISTBOX_HandleVScroll
1879 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1881 SCROLLINFO info;
1883 if (descr->style & LBS_MULTICOLUMN) return 0;
1884 switch(scrollReq)
1886 case SB_LINEUP:
1887 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1888 break;
1889 case SB_LINEDOWN:
1890 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1891 break;
1892 case SB_PAGEUP:
1893 LISTBOX_SetTopItem( descr, descr->top_item -
1894 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1895 break;
1896 case SB_PAGEDOWN:
1897 LISTBOX_SetTopItem( descr, descr->top_item +
1898 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1899 break;
1900 case SB_THUMBPOSITION:
1901 LISTBOX_SetTopItem( descr, pos, TRUE );
1902 break;
1903 case SB_THUMBTRACK:
1904 info.cbSize = sizeof(info);
1905 info.fMask = SIF_TRACKPOS;
1906 GetScrollInfo( descr->self, SB_VERT, &info );
1907 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1908 break;
1909 case SB_TOP:
1910 LISTBOX_SetTopItem( descr, 0, TRUE );
1911 break;
1912 case SB_BOTTOM:
1913 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1914 break;
1916 return 0;
1920 /***********************************************************************
1921 * LISTBOX_HandleHScroll
1923 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1925 SCROLLINFO info;
1926 INT page;
1928 if (descr->style & LBS_MULTICOLUMN)
1930 switch(scrollReq)
1932 case SB_LINELEFT:
1933 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1934 TRUE );
1935 break;
1936 case SB_LINERIGHT:
1937 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1938 TRUE );
1939 break;
1940 case SB_PAGELEFT:
1941 page = descr->width / descr->column_width;
1942 if (page < 1) page = 1;
1943 LISTBOX_SetTopItem( descr,
1944 descr->top_item - page * descr->page_size, TRUE );
1945 break;
1946 case SB_PAGERIGHT:
1947 page = descr->width / descr->column_width;
1948 if (page < 1) page = 1;
1949 LISTBOX_SetTopItem( descr,
1950 descr->top_item + page * descr->page_size, TRUE );
1951 break;
1952 case SB_THUMBPOSITION:
1953 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1954 break;
1955 case SB_THUMBTRACK:
1956 info.cbSize = sizeof(info);
1957 info.fMask = SIF_TRACKPOS;
1958 GetScrollInfo( descr->self, SB_VERT, &info );
1959 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1960 TRUE );
1961 break;
1962 case SB_LEFT:
1963 LISTBOX_SetTopItem( descr, 0, TRUE );
1964 break;
1965 case SB_RIGHT:
1966 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1967 break;
1970 else if (descr->horz_extent)
1972 switch(scrollReq)
1974 case SB_LINELEFT:
1975 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1976 break;
1977 case SB_LINERIGHT:
1978 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1979 break;
1980 case SB_PAGELEFT:
1981 LISTBOX_SetHorizontalPos( descr,
1982 descr->horz_pos - descr->width );
1983 break;
1984 case SB_PAGERIGHT:
1985 LISTBOX_SetHorizontalPos( descr,
1986 descr->horz_pos + descr->width );
1987 break;
1988 case SB_THUMBPOSITION:
1989 LISTBOX_SetHorizontalPos( descr, pos );
1990 break;
1991 case SB_THUMBTRACK:
1992 info.cbSize = sizeof(info);
1993 info.fMask = SIF_TRACKPOS;
1994 GetScrollInfo( descr->self, SB_HORZ, &info );
1995 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1996 break;
1997 case SB_LEFT:
1998 LISTBOX_SetHorizontalPos( descr, 0 );
1999 break;
2000 case SB_RIGHT:
2001 LISTBOX_SetHorizontalPos( descr,
2002 descr->horz_extent - descr->width );
2003 break;
2006 return 0;
2009 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2011 UINT pulScrollLines = 3;
2013 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2015 /* if scrolling changes direction, ignore left overs */
2016 if ((delta < 0 && descr->wheel_remain < 0) ||
2017 (delta > 0 && descr->wheel_remain > 0))
2018 descr->wheel_remain += delta;
2019 else
2020 descr->wheel_remain = delta;
2022 if (descr->wheel_remain && pulScrollLines)
2024 int cLineScroll;
2025 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2026 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2027 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2028 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2030 return 0;
2033 /***********************************************************************
2034 * LISTBOX_HandleLButtonDown
2036 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2038 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2040 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2041 descr->self, x, y, index, descr->focus_item);
2043 if (!descr->caret_on && (descr->in_focus)) return 0;
2045 if (!descr->in_focus)
2047 if( !descr->lphc ) SetFocus( descr->self );
2048 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2051 if (index == -1) return 0;
2053 if (!descr->lphc)
2055 if (descr->style & LBS_NOTIFY )
2056 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2057 MAKELPARAM( x, y ) );
2060 descr->captured = TRUE;
2061 SetCapture( descr->self );
2063 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2065 /* we should perhaps make sure that all items are deselected
2066 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2067 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2068 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2071 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2072 if (keys & MK_CONTROL)
2074 LISTBOX_SetCaretIndex( descr, index, FALSE );
2075 LISTBOX_SetSelection( descr, index,
2076 !descr->items[index].selected,
2077 (descr->style & LBS_NOTIFY) != 0);
2079 else
2081 LISTBOX_MoveCaret( descr, index, FALSE );
2083 if (descr->style & LBS_EXTENDEDSEL)
2085 LISTBOX_SetSelection( descr, index,
2086 descr->items[index].selected,
2087 (descr->style & LBS_NOTIFY) != 0 );
2089 else
2091 LISTBOX_SetSelection( descr, index,
2092 !descr->items[index].selected,
2093 (descr->style & LBS_NOTIFY) != 0 );
2097 else
2099 descr->anchor_item = index;
2100 LISTBOX_MoveCaret( descr, index, FALSE );
2101 LISTBOX_SetSelection( descr, index,
2102 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2105 if (!descr->lphc)
2107 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2109 POINT pt;
2111 pt.x = x;
2112 pt.y = y;
2114 if (DragDetect( descr->self, pt ))
2115 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2118 return 0;
2122 /*************************************************************************
2123 * LISTBOX_HandleLButtonDownCombo [Internal]
2125 * Process LButtonDown message for the ComboListBox
2127 * PARAMS
2128 * pWnd [I] The windows internal structure
2129 * pDescr [I] The ListBox internal structure
2130 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2131 * x [I] X Mouse Coordinate
2132 * y [I] Y Mouse Coordinate
2134 * RETURNS
2135 * 0 since we are processing the WM_LBUTTONDOWN Message
2137 * NOTES
2138 * This function is only to be used when a ListBox is a ComboListBox
2141 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2143 RECT clientRect, screenRect;
2144 POINT mousePos;
2146 mousePos.x = x;
2147 mousePos.y = y;
2149 GetClientRect(descr->self, &clientRect);
2151 if(PtInRect(&clientRect, mousePos))
2153 /* MousePos is in client, resume normal processing */
2154 if (msg == WM_LBUTTONDOWN)
2156 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2157 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2159 else if (descr->style & LBS_NOTIFY)
2160 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2162 else
2164 POINT screenMousePos;
2165 HWND hWndOldCapture;
2167 /* Check the Non-Client Area */
2168 screenMousePos = mousePos;
2169 hWndOldCapture = GetCapture();
2170 ReleaseCapture();
2171 GetWindowRect(descr->self, &screenRect);
2172 ClientToScreen(descr->self, &screenMousePos);
2174 if(!PtInRect(&screenRect, screenMousePos))
2176 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2177 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2178 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2180 else
2182 /* Check to see the NC is a scrollbar */
2183 INT nHitTestType=0;
2184 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2185 /* Check Vertical scroll bar */
2186 if (style & WS_VSCROLL)
2188 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2189 if (PtInRect( &clientRect, mousePos ))
2190 nHitTestType = HTVSCROLL;
2192 /* Check horizontal scroll bar */
2193 if (style & WS_HSCROLL)
2195 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2196 if (PtInRect( &clientRect, mousePos ))
2197 nHitTestType = HTHSCROLL;
2199 /* Windows sends this message when a scrollbar is clicked
2202 if(nHitTestType != 0)
2204 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2205 MAKELONG(screenMousePos.x, screenMousePos.y));
2207 /* Resume the Capture after scrolling is complete
2209 if(hWndOldCapture != 0)
2210 SetCapture(hWndOldCapture);
2213 return 0;
2216 /***********************************************************************
2217 * LISTBOX_HandleLButtonUp
2219 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2221 if (LISTBOX_Timer != LB_TIMER_NONE)
2222 KillSystemTimer( descr->self, LB_TIMER_ID );
2223 LISTBOX_Timer = LB_TIMER_NONE;
2224 if (descr->captured)
2226 descr->captured = FALSE;
2227 if (GetCapture() == descr->self) ReleaseCapture();
2228 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2229 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2231 return 0;
2235 /***********************************************************************
2236 * LISTBOX_HandleTimer
2238 * Handle scrolling upon a timer event.
2239 * Return TRUE if scrolling should continue.
2241 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2243 switch(dir)
2245 case LB_TIMER_UP:
2246 if (descr->top_item) index = descr->top_item - 1;
2247 else index = 0;
2248 break;
2249 case LB_TIMER_LEFT:
2250 if (descr->top_item) index -= descr->page_size;
2251 break;
2252 case LB_TIMER_DOWN:
2253 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2254 if (index == descr->focus_item) index++;
2255 if (index >= descr->nb_items) index = descr->nb_items - 1;
2256 break;
2257 case LB_TIMER_RIGHT:
2258 if (index + descr->page_size < descr->nb_items)
2259 index += descr->page_size;
2260 break;
2261 case LB_TIMER_NONE:
2262 break;
2264 if (index == descr->focus_item) return FALSE;
2265 LISTBOX_MoveCaret( descr, index, FALSE );
2266 return TRUE;
2270 /***********************************************************************
2271 * LISTBOX_HandleSystemTimer
2273 * WM_SYSTIMER handler.
2275 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2277 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2279 KillSystemTimer( descr->self, LB_TIMER_ID );
2280 LISTBOX_Timer = LB_TIMER_NONE;
2282 return 0;
2286 /***********************************************************************
2287 * LISTBOX_HandleMouseMove
2289 * WM_MOUSEMOVE handler.
2291 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2292 INT x, INT y )
2294 INT index;
2295 TIMER_DIRECTION dir = LB_TIMER_NONE;
2297 if (!descr->captured) return;
2299 if (descr->style & LBS_MULTICOLUMN)
2301 if (y < 0) y = 0;
2302 else if (y >= descr->item_height * descr->page_size)
2303 y = descr->item_height * descr->page_size - 1;
2305 if (x < 0)
2307 dir = LB_TIMER_LEFT;
2308 x = 0;
2310 else if (x >= descr->width)
2312 dir = LB_TIMER_RIGHT;
2313 x = descr->width - 1;
2316 else
2318 if (y < 0) dir = LB_TIMER_UP; /* above */
2319 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2322 index = LISTBOX_GetItemFromPoint( descr, x, y );
2323 if (index == -1) index = descr->focus_item;
2324 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2326 /* Start/stop the system timer */
2328 if (dir != LB_TIMER_NONE)
2329 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2330 else if (LISTBOX_Timer != LB_TIMER_NONE)
2331 KillSystemTimer( descr->self, LB_TIMER_ID );
2332 LISTBOX_Timer = dir;
2336 /***********************************************************************
2337 * LISTBOX_HandleKeyDown
2339 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2341 INT caret = -1;
2342 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2343 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2344 bForceSelection = FALSE; /* only for single select list */
2346 if (descr->style & LBS_WANTKEYBOARDINPUT)
2348 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2349 MAKEWPARAM(LOWORD(key), descr->focus_item),
2350 (LPARAM)descr->self );
2351 if (caret == -2) return 0;
2353 if (caret == -1) switch(key)
2355 case VK_LEFT:
2356 if (descr->style & LBS_MULTICOLUMN)
2358 bForceSelection = FALSE;
2359 if (descr->focus_item >= descr->page_size)
2360 caret = descr->focus_item - descr->page_size;
2361 break;
2363 /* fall through */
2364 case VK_UP:
2365 caret = descr->focus_item - 1;
2366 if (caret < 0) caret = 0;
2367 break;
2368 case VK_RIGHT:
2369 if (descr->style & LBS_MULTICOLUMN)
2371 bForceSelection = FALSE;
2372 if (descr->focus_item + descr->page_size < descr->nb_items)
2373 caret = descr->focus_item + descr->page_size;
2374 break;
2376 /* fall through */
2377 case VK_DOWN:
2378 caret = descr->focus_item + 1;
2379 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2380 break;
2382 case VK_PRIOR:
2383 if (descr->style & LBS_MULTICOLUMN)
2385 INT page = descr->width / descr->column_width;
2386 if (page < 1) page = 1;
2387 caret = descr->focus_item - (page * descr->page_size) + 1;
2389 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2390 if (caret < 0) caret = 0;
2391 break;
2392 case VK_NEXT:
2393 if (descr->style & LBS_MULTICOLUMN)
2395 INT page = descr->width / descr->column_width;
2396 if (page < 1) page = 1;
2397 caret = descr->focus_item + (page * descr->page_size) - 1;
2399 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2400 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2401 break;
2402 case VK_HOME:
2403 caret = 0;
2404 break;
2405 case VK_END:
2406 caret = descr->nb_items - 1;
2407 break;
2408 case VK_SPACE:
2409 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2410 else if (descr->style & LBS_MULTIPLESEL)
2412 LISTBOX_SetSelection( descr, descr->focus_item,
2413 !descr->items[descr->focus_item].selected,
2414 (descr->style & LBS_NOTIFY) != 0 );
2416 break;
2417 default:
2418 bForceSelection = FALSE;
2420 if (bForceSelection) /* focused item is used instead of key */
2421 caret = descr->focus_item;
2422 if (caret >= 0)
2424 if (((descr->style & LBS_EXTENDEDSEL) &&
2425 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2426 !IS_MULTISELECT(descr))
2427 descr->anchor_item = caret;
2428 LISTBOX_MoveCaret( descr, caret, TRUE );
2430 if (descr->style & LBS_MULTIPLESEL)
2431 descr->selected_item = caret;
2432 else
2433 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2434 if (descr->style & LBS_NOTIFY)
2436 if (descr->lphc && IsWindowVisible( descr->self ))
2438 /* make sure that combo parent doesn't hide us */
2439 descr->lphc->wState |= CBF_NOROLLUP;
2441 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2444 return 0;
2448 /***********************************************************************
2449 * LISTBOX_HandleChar
2451 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2453 INT caret = -1;
2454 WCHAR str[2];
2456 str[0] = charW;
2457 str[1] = '\0';
2459 if (descr->style & LBS_WANTKEYBOARDINPUT)
2461 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2462 MAKEWPARAM(charW, descr->focus_item),
2463 (LPARAM)descr->self );
2464 if (caret == -2) return 0;
2466 if (caret == -1)
2467 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2468 if (caret != -1)
2470 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2471 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2472 LISTBOX_MoveCaret( descr, caret, TRUE );
2473 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2474 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2476 return 0;
2480 /***********************************************************************
2481 * LISTBOX_Create
2483 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2485 LB_DESCR *descr;
2486 MEASUREITEMSTRUCT mis;
2487 RECT rect;
2489 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2490 return FALSE;
2492 GetClientRect( hwnd, &rect );
2493 descr->self = hwnd;
2494 descr->owner = GetParent( descr->self );
2495 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2496 descr->width = rect.right - rect.left;
2497 descr->height = rect.bottom - rect.top;
2498 descr->items = NULL;
2499 descr->nb_items = 0;
2500 descr->top_item = 0;
2501 descr->selected_item = -1;
2502 descr->focus_item = 0;
2503 descr->anchor_item = -1;
2504 descr->item_height = 1;
2505 descr->page_size = 1;
2506 descr->column_width = 150;
2507 descr->horz_extent = 0;
2508 descr->horz_pos = 0;
2509 descr->nb_tabs = 0;
2510 descr->tabs = NULL;
2511 descr->wheel_remain = 0;
2512 descr->caret_on = !lphc;
2513 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2514 descr->in_focus = FALSE;
2515 descr->captured = FALSE;
2516 descr->font = 0;
2517 descr->locale = GetUserDefaultLCID();
2518 descr->lphc = lphc;
2520 if( lphc )
2522 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2523 descr->owner = lphc->self;
2526 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2528 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2530 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2531 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2532 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2533 descr->item_height = LISTBOX_SetFont( descr, 0 );
2535 if (descr->style & LBS_OWNERDRAWFIXED)
2537 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2539 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2540 descr->item_height = lphc->fixedOwnerDrawHeight;
2542 else
2544 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2545 mis.CtlType = ODT_LISTBOX;
2546 mis.CtlID = id;
2547 mis.itemID = -1;
2548 mis.itemWidth = 0;
2549 mis.itemData = 0;
2550 mis.itemHeight = descr->item_height;
2551 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2552 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2556 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2557 return TRUE;
2561 /***********************************************************************
2562 * LISTBOX_Destroy
2564 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2566 LISTBOX_ResetContent( descr );
2567 SetWindowLongPtrW( descr->self, 0, 0 );
2568 HeapFree( GetProcessHeap(), 0, descr );
2569 return TRUE;
2573 /***********************************************************************
2574 * ListBoxWndProc_common
2576 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2578 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2579 LPHEADCOMBO lphc = 0;
2580 LRESULT ret;
2582 if (!descr)
2584 if (!IsWindow(hwnd)) return 0;
2586 if (msg == WM_CREATE)
2588 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2589 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2590 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2591 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2592 return 0;
2594 /* Ignore all other messages before we get a WM_CREATE */
2595 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2596 DefWindowProcA( hwnd, msg, wParam, lParam );
2598 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2600 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2601 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2603 switch(msg)
2605 case LB_RESETCONTENT:
2606 LISTBOX_ResetContent( descr );
2607 LISTBOX_UpdateScroll( descr );
2608 InvalidateRect( descr->self, NULL, TRUE );
2609 return 0;
2611 case LB_ADDSTRING:
2613 INT ret;
2614 LPWSTR textW;
2615 if(unicode || !HAS_STRINGS(descr))
2616 textW = (LPWSTR)lParam;
2617 else
2619 LPSTR textA = (LPSTR)lParam;
2620 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2621 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2622 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2623 else
2624 return LB_ERRSPACE;
2626 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2627 ret = LISTBOX_InsertString( descr, wParam, textW );
2628 if (!unicode && HAS_STRINGS(descr))
2629 HeapFree(GetProcessHeap(), 0, textW);
2630 return ret;
2633 case LB_INSERTSTRING:
2635 INT ret;
2636 LPWSTR textW;
2637 if(unicode || !HAS_STRINGS(descr))
2638 textW = (LPWSTR)lParam;
2639 else
2641 LPSTR textA = (LPSTR)lParam;
2642 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2643 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2644 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2645 else
2646 return LB_ERRSPACE;
2648 ret = LISTBOX_InsertString( descr, wParam, textW );
2649 if(!unicode && HAS_STRINGS(descr))
2650 HeapFree(GetProcessHeap(), 0, textW);
2651 return ret;
2654 case LB_ADDFILE:
2656 INT ret;
2657 LPWSTR textW;
2658 if(unicode || !HAS_STRINGS(descr))
2659 textW = (LPWSTR)lParam;
2660 else
2662 LPSTR textA = (LPSTR)lParam;
2663 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2664 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2665 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2666 else
2667 return LB_ERRSPACE;
2669 wParam = LISTBOX_FindFileStrPos( descr, textW );
2670 ret = LISTBOX_InsertString( descr, wParam, textW );
2671 if(!unicode && HAS_STRINGS(descr))
2672 HeapFree(GetProcessHeap(), 0, textW);
2673 return ret;
2676 case LB_DELETESTRING:
2677 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2678 return descr->nb_items;
2679 else
2681 SetLastError(ERROR_INVALID_INDEX);
2682 return LB_ERR;
2685 case LB_GETITEMDATA:
2686 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2688 SetLastError(ERROR_INVALID_INDEX);
2689 return LB_ERR;
2691 return descr->items[wParam].data;
2693 case LB_SETITEMDATA:
2694 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2696 SetLastError(ERROR_INVALID_INDEX);
2697 return LB_ERR;
2699 descr->items[wParam].data = lParam;
2700 /* undocumented: returns TRUE, not LB_OKAY (0) */
2701 return TRUE;
2703 case LB_GETCOUNT:
2704 return descr->nb_items;
2706 case LB_GETTEXT:
2707 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2709 case LB_GETTEXTLEN:
2710 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2712 SetLastError(ERROR_INVALID_INDEX);
2713 return LB_ERR;
2715 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2716 if (unicode) return strlenW( descr->items[wParam].str );
2717 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2718 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2720 case LB_GETCURSEL:
2721 if (descr->nb_items == 0)
2722 return LB_ERR;
2723 if (!IS_MULTISELECT(descr))
2724 return descr->selected_item;
2725 if (descr->selected_item != -1)
2726 return descr->selected_item;
2727 return descr->focus_item;
2728 /* otherwise, if the user tries to move the selection with the */
2729 /* arrow keys, we will give the application something to choke on */
2730 case LB_GETTOPINDEX:
2731 return descr->top_item;
2733 case LB_GETITEMHEIGHT:
2734 return LISTBOX_GetItemHeight( descr, wParam );
2736 case LB_SETITEMHEIGHT:
2737 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2739 case LB_ITEMFROMPOINT:
2741 POINT pt;
2742 RECT rect;
2743 int index;
2744 BOOL hit = TRUE;
2746 /* The hiword of the return value is not a client area
2747 hittest as suggested by MSDN, but rather a hittest on
2748 the returned listbox item. */
2750 if(descr->nb_items == 0)
2751 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2753 pt.x = (short)LOWORD(lParam);
2754 pt.y = (short)HIWORD(lParam);
2756 SetRect(&rect, 0, 0, descr->width, descr->height);
2758 if(!PtInRect(&rect, pt))
2760 pt.x = min(pt.x, rect.right - 1);
2761 pt.x = max(pt.x, 0);
2762 pt.y = min(pt.y, rect.bottom - 1);
2763 pt.y = max(pt.y, 0);
2764 hit = FALSE;
2767 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2769 if(index == -1)
2771 index = descr->nb_items - 1;
2772 hit = FALSE;
2774 return MAKELONG(index, hit ? 0 : 1);
2777 case LB_SETCARETINDEX:
2778 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2779 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2780 return LB_ERR;
2781 else if (ISWIN31)
2782 return wParam;
2783 else
2784 return LB_OKAY;
2786 case LB_GETCARETINDEX:
2787 return descr->focus_item;
2789 case LB_SETTOPINDEX:
2790 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2792 case LB_SETCOLUMNWIDTH:
2793 return LISTBOX_SetColumnWidth( descr, wParam );
2795 case LB_GETITEMRECT:
2796 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2798 case LB_FINDSTRING:
2800 INT ret;
2801 LPWSTR textW;
2802 if(unicode || !HAS_STRINGS(descr))
2803 textW = (LPWSTR)lParam;
2804 else
2806 LPSTR textA = (LPSTR)lParam;
2807 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2808 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2809 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2811 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2812 if(!unicode && HAS_STRINGS(descr))
2813 HeapFree(GetProcessHeap(), 0, textW);
2814 return ret;
2817 case LB_FINDSTRINGEXACT:
2819 INT ret;
2820 LPWSTR textW;
2821 if(unicode || !HAS_STRINGS(descr))
2822 textW = (LPWSTR)lParam;
2823 else
2825 LPSTR textA = (LPSTR)lParam;
2826 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2827 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2828 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2830 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2831 if(!unicode && HAS_STRINGS(descr))
2832 HeapFree(GetProcessHeap(), 0, textW);
2833 return ret;
2836 case LB_SELECTSTRING:
2838 INT index;
2839 LPWSTR textW;
2841 if(HAS_STRINGS(descr))
2842 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2843 debugstr_a((LPSTR)lParam));
2844 if(unicode || !HAS_STRINGS(descr))
2845 textW = (LPWSTR)lParam;
2846 else
2848 LPSTR textA = (LPSTR)lParam;
2849 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2850 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2851 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2853 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2854 if(!unicode && HAS_STRINGS(descr))
2855 HeapFree(GetProcessHeap(), 0, textW);
2856 if (index != LB_ERR)
2858 LISTBOX_MoveCaret( descr, index, TRUE );
2859 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2861 return index;
2864 case LB_GETSEL:
2865 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2866 return LB_ERR;
2867 return descr->items[wParam].selected;
2869 case LB_SETSEL:
2870 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2872 case LB_SETCURSEL:
2873 if (IS_MULTISELECT(descr)) return LB_ERR;
2874 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2875 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2876 if (ret != LB_ERR) ret = descr->selected_item;
2877 return ret;
2879 case LB_GETSELCOUNT:
2880 return LISTBOX_GetSelCount( descr );
2882 case LB_GETSELITEMS:
2883 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2885 case LB_SELITEMRANGE:
2886 if (LOWORD(lParam) <= HIWORD(lParam))
2887 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2888 HIWORD(lParam), wParam );
2889 else
2890 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2891 LOWORD(lParam), wParam );
2893 case LB_SELITEMRANGEEX:
2894 if ((INT)lParam >= (INT)wParam)
2895 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2896 else
2897 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2899 case LB_GETHORIZONTALEXTENT:
2900 return descr->horz_extent;
2902 case LB_SETHORIZONTALEXTENT:
2903 return LISTBOX_SetHorizontalExtent( descr, wParam );
2905 case LB_GETANCHORINDEX:
2906 return descr->anchor_item;
2908 case LB_SETANCHORINDEX:
2909 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2911 SetLastError(ERROR_INVALID_INDEX);
2912 return LB_ERR;
2914 descr->anchor_item = (INT)wParam;
2915 return LB_OKAY;
2917 case LB_DIR:
2919 INT ret;
2920 LPWSTR textW;
2921 if(unicode)
2922 textW = (LPWSTR)lParam;
2923 else
2925 LPSTR textA = (LPSTR)lParam;
2926 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2927 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2928 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2930 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2931 if(!unicode)
2932 HeapFree(GetProcessHeap(), 0, textW);
2933 return ret;
2936 case LB_GETLOCALE:
2937 return descr->locale;
2939 case LB_SETLOCALE:
2941 LCID ret;
2942 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2943 return LB_ERR;
2944 ret = descr->locale;
2945 descr->locale = (LCID)wParam;
2946 return ret;
2949 case LB_INITSTORAGE:
2950 return LISTBOX_InitStorage( descr, wParam );
2952 case LB_SETCOUNT:
2953 return LISTBOX_SetCount( descr, (INT)wParam );
2955 case LB_SETTABSTOPS:
2956 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2958 case LB_CARETON:
2959 if (descr->caret_on)
2960 return LB_OKAY;
2961 descr->caret_on = TRUE;
2962 if ((descr->focus_item != -1) && (descr->in_focus))
2963 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2964 return LB_OKAY;
2966 case LB_CARETOFF:
2967 if (!descr->caret_on)
2968 return LB_OKAY;
2969 descr->caret_on = FALSE;
2970 if ((descr->focus_item != -1) && (descr->in_focus))
2971 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2972 return LB_OKAY;
2974 case LB_GETLISTBOXINFO:
2975 return descr->page_size;
2977 case WM_DESTROY:
2978 return LISTBOX_Destroy( descr );
2980 case WM_ENABLE:
2981 InvalidateRect( descr->self, NULL, TRUE );
2982 return 0;
2984 case WM_SETREDRAW:
2985 LISTBOX_SetRedraw( descr, wParam != 0 );
2986 return 0;
2988 case WM_GETDLGCODE:
2989 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2991 case WM_PRINTCLIENT:
2992 case WM_PAINT:
2994 PAINTSTRUCT ps;
2995 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2996 ret = LISTBOX_Paint( descr, hdc );
2997 if( !wParam ) EndPaint( descr->self, &ps );
2999 return ret;
3000 case WM_SIZE:
3001 LISTBOX_UpdateSize( descr );
3002 return 0;
3003 case WM_GETFONT:
3004 return (LRESULT)descr->font;
3005 case WM_SETFONT:
3006 LISTBOX_SetFont( descr, (HFONT)wParam );
3007 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3008 return 0;
3009 case WM_SETFOCUS:
3010 descr->in_focus = TRUE;
3011 descr->caret_on = TRUE;
3012 if (descr->focus_item != -1)
3013 LISTBOX_DrawFocusRect( descr, TRUE );
3014 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3015 return 0;
3016 case WM_KILLFOCUS:
3017 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3018 descr->in_focus = FALSE;
3019 descr->wheel_remain = 0;
3020 if ((descr->focus_item != -1) && descr->caret_on)
3021 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3022 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3023 return 0;
3024 case WM_HSCROLL:
3025 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3026 case WM_VSCROLL:
3027 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3028 case WM_MOUSEWHEEL:
3029 if (wParam & (MK_SHIFT | MK_CONTROL))
3030 return DefWindowProcW( descr->self, msg, wParam, lParam );
3031 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3032 case WM_LBUTTONDOWN:
3033 if (lphc)
3034 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3035 (INT16)LOWORD(lParam),
3036 (INT16)HIWORD(lParam) );
3037 return LISTBOX_HandleLButtonDown( descr, wParam,
3038 (INT16)LOWORD(lParam),
3039 (INT16)HIWORD(lParam) );
3040 case WM_LBUTTONDBLCLK:
3041 if (lphc)
3042 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3043 (INT16)LOWORD(lParam),
3044 (INT16)HIWORD(lParam) );
3045 if (descr->style & LBS_NOTIFY)
3046 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3047 return 0;
3048 case WM_MOUSEMOVE:
3049 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3051 BOOL captured = descr->captured;
3052 POINT mousePos;
3053 RECT clientRect;
3055 mousePos.x = (INT16)LOWORD(lParam);
3056 mousePos.y = (INT16)HIWORD(lParam);
3059 * If we are in a dropdown combobox, we simulate that
3060 * the mouse is captured to show the tracking of the item.
3062 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3063 descr->captured = TRUE;
3065 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3067 descr->captured = captured;
3069 else if (GetCapture() == descr->self)
3071 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3072 (INT16)HIWORD(lParam) );
3074 return 0;
3075 case WM_LBUTTONUP:
3076 if (lphc)
3078 POINT mousePos;
3079 RECT clientRect;
3082 * If the mouse button "up" is not in the listbox,
3083 * we make sure there is no selection by re-selecting the
3084 * item that was selected when the listbox was made visible.
3086 mousePos.x = (INT16)LOWORD(lParam);
3087 mousePos.y = (INT16)HIWORD(lParam);
3089 GetClientRect(descr->self, &clientRect);
3092 * When the user clicks outside the combobox and the focus
3093 * is lost, the owning combobox will send a fake buttonup with
3094 * 0xFFFFFFF as the mouse location, we must also revert the
3095 * selection to the original selection.
3097 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3098 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3100 return LISTBOX_HandleLButtonUp( descr );
3101 case WM_KEYDOWN:
3102 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3104 /* for some reason Windows makes it possible to
3105 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3107 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3108 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3109 && (wParam == VK_DOWN || wParam == VK_UP)) )
3111 COMBO_FlipListbox( lphc, FALSE, FALSE );
3112 return 0;
3115 return LISTBOX_HandleKeyDown( descr, wParam );
3116 case WM_CHAR:
3118 WCHAR charW;
3119 if(unicode)
3120 charW = (WCHAR)wParam;
3121 else
3123 CHAR charA = (CHAR)wParam;
3124 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3126 return LISTBOX_HandleChar( descr, charW );
3128 case WM_SYSTIMER:
3129 return LISTBOX_HandleSystemTimer( descr );
3130 case WM_ERASEBKGND:
3131 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3133 RECT rect;
3134 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3135 wParam, (LPARAM)descr->self );
3136 TRACE("hbrush = %p\n", hbrush);
3137 if(!hbrush)
3138 hbrush = GetSysColorBrush(COLOR_WINDOW);
3139 if(hbrush)
3141 GetClientRect(descr->self, &rect);
3142 FillRect((HDC)wParam, &rect, hbrush);
3145 return 1;
3146 case WM_DROPFILES:
3147 if( lphc ) return 0;
3148 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3149 SendMessageA( descr->owner, msg, wParam, lParam );
3151 case WM_NCDESTROY:
3152 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3153 lphc->hWndLBox = 0;
3154 break;
3156 case WM_NCACTIVATE:
3157 if (lphc) return 0;
3158 break;
3160 default:
3161 if ((msg >= WM_USER) && (msg < 0xc000))
3162 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3163 hwnd, msg, wParam, lParam );
3166 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3167 DefWindowProcA( hwnd, msg, wParam, lParam );
3170 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3172 TRACE("%p\n", hwnd);
3173 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);