windowscodecs/tests: Handle failure to create TIFF decoder.
[wine.git] / dlls / user32 / listbox.c
blob2902ac9c05c62685734389c5649b220c6d30554b
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)
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 );
281 /***********************************************************************
282 * LISTBOX_SetTopItem
284 * Set the top item of the listbox, scrolling up or down if necessary.
286 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
288 INT max = LISTBOX_GetMaxTopIndex( descr );
290 TRACE("setting top item %d, scroll %d\n", index, scroll);
292 if (index > max) index = max;
293 if (index < 0) index = 0;
294 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
295 if (descr->top_item == index) return LB_OKAY;
296 if (scroll)
298 INT diff;
299 if (descr->style & LBS_MULTICOLUMN)
300 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
301 else if (descr->style & LBS_OWNERDRAWVARIABLE)
303 INT i;
304 diff = 0;
305 if (index > descr->top_item)
307 for (i = index - 1; i >= descr->top_item; i--)
308 diff -= descr->items[i].height;
310 else
312 for (i = index; i < descr->top_item; i++)
313 diff += descr->items[i].height;
316 else
317 diff = (descr->top_item - index) * descr->item_height;
319 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
320 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
322 else
323 InvalidateRect( descr->self, NULL, TRUE );
324 descr->top_item = index;
325 LISTBOX_UpdateScroll( descr );
326 return LB_OKAY;
330 /***********************************************************************
331 * LISTBOX_UpdatePage
333 * Update the page size. Should be called when the size of
334 * the client area or the item height changes.
336 static void LISTBOX_UpdatePage( LB_DESCR *descr )
338 INT page_size;
340 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
341 page_size = 1;
342 if (page_size == descr->page_size) return;
343 descr->page_size = page_size;
344 if (descr->style & LBS_MULTICOLUMN)
345 InvalidateRect( descr->self, NULL, TRUE );
346 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
350 /***********************************************************************
351 * LISTBOX_UpdateSize
353 * Update the size of the listbox. Should be called when the size of
354 * the client area changes.
356 static void LISTBOX_UpdateSize( LB_DESCR *descr )
358 RECT rect;
360 GetClientRect( descr->self, &rect );
361 descr->width = rect.right - rect.left;
362 descr->height = rect.bottom - rect.top;
363 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
365 INT remaining;
366 RECT rect;
368 GetWindowRect( descr->self, &rect );
369 if(descr->item_height != 0)
370 remaining = descr->height % descr->item_height;
371 else
372 remaining = 0;
373 if ((descr->height > descr->item_height) && remaining)
375 TRACE("[%p]: changing height %d -> %d\n",
376 descr->self, descr->height, descr->height - remaining );
377 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
378 rect.bottom - rect.top - remaining,
379 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
380 return;
383 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
384 LISTBOX_UpdatePage( descr );
385 LISTBOX_UpdateScroll( descr );
387 /* Invalidate the focused item so it will be repainted correctly */
388 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
390 InvalidateRect( descr->self, &rect, FALSE );
395 /***********************************************************************
396 * LISTBOX_GetItemRect
398 * Get the rectangle enclosing an item, in listbox client coordinates.
399 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
401 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
403 /* Index <= 0 is legal even on empty listboxes */
404 if (index && (index >= descr->nb_items))
406 memset(rect, 0, sizeof(*rect));
407 SetLastError(ERROR_INVALID_INDEX);
408 return LB_ERR;
410 SetRect( rect, 0, 0, descr->width, descr->height );
411 if (descr->style & LBS_MULTICOLUMN)
413 INT col = (index / descr->page_size) -
414 (descr->top_item / descr->page_size);
415 rect->left += col * descr->column_width;
416 rect->right = rect->left + descr->column_width;
417 rect->top += (index % descr->page_size) * descr->item_height;
418 rect->bottom = rect->top + descr->item_height;
420 else if (descr->style & LBS_OWNERDRAWVARIABLE)
422 INT i;
423 rect->right += descr->horz_pos;
424 if ((index >= 0) && (index < descr->nb_items))
426 if (index < descr->top_item)
428 for (i = descr->top_item-1; i >= index; i--)
429 rect->top -= descr->items[i].height;
431 else
433 for (i = descr->top_item; i < index; i++)
434 rect->top += descr->items[i].height;
436 rect->bottom = rect->top + descr->items[index].height;
440 else
442 rect->top += (index - descr->top_item) * descr->item_height;
443 rect->bottom = rect->top + descr->item_height;
444 rect->right += descr->horz_pos;
447 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
449 return ((rect->left < descr->width) && (rect->right > 0) &&
450 (rect->top < descr->height) && (rect->bottom > 0));
454 /***********************************************************************
455 * LISTBOX_GetItemFromPoint
457 * Return the item nearest from point (x,y) (in client coordinates).
459 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
461 INT index = descr->top_item;
463 if (!descr->nb_items) return -1; /* No items */
464 if (descr->style & LBS_OWNERDRAWVARIABLE)
466 INT pos = 0;
467 if (y >= 0)
469 while (index < descr->nb_items)
471 if ((pos += descr->items[index].height) > y) break;
472 index++;
475 else
477 while (index > 0)
479 index--;
480 if ((pos -= descr->items[index].height) <= y) break;
484 else if (descr->style & LBS_MULTICOLUMN)
486 if (y >= descr->item_height * descr->page_size) return -1;
487 if (y >= 0) index += y / descr->item_height;
488 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
489 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
491 else
493 index += (y / descr->item_height);
495 if (index < 0) return 0;
496 if (index >= descr->nb_items) return -1;
497 return index;
501 /***********************************************************************
502 * LISTBOX_PaintItem
504 * Paint an item.
506 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
507 INT index, UINT action, BOOL ignoreFocus )
509 LB_ITEMDATA *item = NULL;
510 if (index < descr->nb_items) item = &descr->items[index];
512 if (IS_OWNERDRAW(descr))
514 DRAWITEMSTRUCT dis;
515 RECT r;
516 HRGN hrgn;
518 if (!item)
520 if (action == ODA_FOCUS)
521 DrawFocusRect( hdc, rect );
522 else
523 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
524 return;
527 /* some programs mess with the clipping region when
528 drawing the item, *and* restore the previous region
529 after they are done, so a region has better to exist
530 else everything ends clipped */
531 GetClientRect(descr->self, &r);
532 hrgn = set_control_clipping( hdc, &r );
534 dis.CtlType = ODT_LISTBOX;
535 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
536 dis.hwndItem = descr->self;
537 dis.itemAction = action;
538 dis.hDC = hdc;
539 dis.itemID = index;
540 dis.itemState = 0;
541 if (item->selected) dis.itemState |= ODS_SELECTED;
542 if (!ignoreFocus && (descr->focus_item == index) &&
543 (descr->caret_on) &&
544 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
545 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
546 dis.itemData = item->data;
547 dis.rcItem = *rect;
548 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
549 descr->self, index, debugstr_w(item->str), action,
550 dis.itemState, wine_dbgstr_rect(rect) );
551 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
552 SelectClipRgn( hdc, hrgn );
553 if (hrgn) DeleteObject( hrgn );
555 else
557 COLORREF oldText = 0, oldBk = 0;
559 if (action == ODA_FOCUS)
561 DrawFocusRect( hdc, rect );
562 return;
564 if (item && item->selected)
566 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
567 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
570 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
571 descr->self, index, item ? debugstr_w(item->str) : "", action,
572 wine_dbgstr_rect(rect) );
573 if (!item)
574 ExtTextOutW( hdc, rect->left + 1, rect->top,
575 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
576 else if (!(descr->style & LBS_USETABSTOPS))
577 ExtTextOutW( hdc, rect->left + 1, rect->top,
578 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
579 strlenW(item->str), NULL );
580 else
582 /* Output empty string to paint background in the full width. */
583 ExtTextOutW( hdc, rect->left + 1, rect->top,
584 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
585 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
586 item->str, strlenW(item->str),
587 descr->nb_tabs, descr->tabs, 0);
589 if (item && item->selected)
591 SetBkColor( hdc, oldBk );
592 SetTextColor( hdc, oldText );
594 if (!ignoreFocus && (descr->focus_item == index) &&
595 (descr->caret_on) &&
596 (descr->in_focus)) DrawFocusRect( hdc, rect );
601 /***********************************************************************
602 * LISTBOX_SetRedraw
604 * Change the redraw flag.
606 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
608 if (on)
610 if (!(descr->style & LBS_NOREDRAW)) return;
611 descr->style &= ~LBS_NOREDRAW;
612 if (descr->style & LBS_DISPLAYCHANGED)
613 { /* page was changed while setredraw false, refresh automatically */
614 InvalidateRect(descr->self, NULL, TRUE);
615 if ((descr->top_item + descr->page_size) > descr->nb_items)
616 { /* reset top of page if less than number of items/page */
617 descr->top_item = descr->nb_items - descr->page_size;
618 if (descr->top_item < 0) descr->top_item = 0;
620 descr->style &= ~LBS_DISPLAYCHANGED;
622 LISTBOX_UpdateScroll( descr );
624 else descr->style |= LBS_NOREDRAW;
628 /***********************************************************************
629 * LISTBOX_RepaintItem
631 * Repaint a single item synchronously.
633 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
635 HDC hdc;
636 RECT rect;
637 HFONT oldFont = 0;
638 HBRUSH hbrush, oldBrush = 0;
640 /* Do not repaint the item if the item is not visible */
641 if (!IsWindowVisible(descr->self)) return;
642 if (descr->style & LBS_NOREDRAW)
644 descr->style |= LBS_DISPLAYCHANGED;
645 return;
647 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
648 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
649 if (descr->font) oldFont = SelectObject( hdc, descr->font );
650 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
651 (WPARAM)hdc, (LPARAM)descr->self );
652 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
653 if (!IsWindowEnabled(descr->self))
654 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
655 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
656 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
657 if (oldFont) SelectObject( hdc, oldFont );
658 if (oldBrush) SelectObject( hdc, oldBrush );
659 ReleaseDC( descr->self, hdc );
663 /***********************************************************************
664 * LISTBOX_DrawFocusRect
666 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
668 HDC hdc;
669 RECT rect;
670 HFONT oldFont = 0;
672 /* Do not repaint the item if the item is not visible */
673 if (!IsWindowVisible(descr->self)) return;
675 if (descr->focus_item == -1) return;
676 if (!descr->caret_on || !descr->in_focus) return;
678 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
679 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
680 if (descr->font) oldFont = SelectObject( hdc, descr->font );
681 if (!IsWindowEnabled(descr->self))
682 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
683 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
684 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
685 if (oldFont) SelectObject( hdc, oldFont );
686 ReleaseDC( descr->self, hdc );
690 /***********************************************************************
691 * LISTBOX_InitStorage
693 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
695 LB_ITEMDATA *item;
697 nb_items += LB_ARRAY_GRANULARITY - 1;
698 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
699 if (descr->items) {
700 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
701 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
702 nb_items * sizeof(LB_ITEMDATA));
704 else {
705 item = HeapAlloc( GetProcessHeap(), 0,
706 nb_items * sizeof(LB_ITEMDATA));
709 if (!item)
711 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
712 return LB_ERRSPACE;
714 descr->items = item;
715 return LB_OKAY;
719 /***********************************************************************
720 * LISTBOX_SetTabStops
722 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
724 INT i;
726 if (!(descr->style & LBS_USETABSTOPS))
728 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
729 return FALSE;
732 HeapFree( GetProcessHeap(), 0, descr->tabs );
733 if (!(descr->nb_tabs = count))
735 descr->tabs = NULL;
736 return TRUE;
738 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
739 descr->nb_tabs * sizeof(INT) )))
740 return FALSE;
741 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
743 /* convert into "dialog units"*/
744 for (i = 0; i < descr->nb_tabs; i++)
745 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
747 return TRUE;
751 /***********************************************************************
752 * LISTBOX_GetText
754 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
756 DWORD len;
758 if ((index < 0) || (index >= descr->nb_items))
760 SetLastError(ERROR_INVALID_INDEX);
761 return LB_ERR;
763 if (HAS_STRINGS(descr))
765 if (!buffer)
767 len = strlenW(descr->items[index].str);
768 if( unicode )
769 return len;
770 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
771 NULL, 0, NULL, NULL );
774 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
776 __TRY /* hide a Delphi bug that passes a read-only buffer */
778 if(unicode)
780 strcpyW( buffer, descr->items[index].str );
781 len = strlenW(buffer);
783 else
785 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
786 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
789 __EXCEPT_PAGE_FAULT
791 WARN( "got an invalid buffer (Delphi bug?)\n" );
792 SetLastError( ERROR_INVALID_PARAMETER );
793 return LB_ERR;
795 __ENDTRY
796 } else {
797 if (buffer)
798 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
799 len = sizeof(DWORD);
801 return len;
804 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
806 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
807 if (ret == CSTR_LESS_THAN)
808 return -1;
809 if (ret == CSTR_EQUAL)
810 return 0;
811 if (ret == CSTR_GREATER_THAN)
812 return 1;
813 return -1;
816 /***********************************************************************
817 * LISTBOX_FindStringPos
819 * Find the nearest string located before a given string in sort order.
820 * If 'exact' is TRUE, return an error if we don't get an exact match.
822 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
824 INT index, min, max, res = -1;
826 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
827 min = 0;
828 max = descr->nb_items;
829 while (min != max)
831 index = (min + max) / 2;
832 if (HAS_STRINGS(descr))
833 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
834 else
836 COMPAREITEMSTRUCT cis;
837 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
839 cis.CtlType = ODT_LISTBOX;
840 cis.CtlID = id;
841 cis.hwndItem = descr->self;
842 /* note that some application (MetaStock) expects the second item
843 * to be in the listbox */
844 cis.itemID1 = -1;
845 cis.itemData1 = (ULONG_PTR)str;
846 cis.itemID2 = index;
847 cis.itemData2 = descr->items[index].data;
848 cis.dwLocaleId = descr->locale;
849 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
851 if (!res) return index;
852 if (res < 0) max = index;
853 else min = index + 1;
855 return exact ? -1 : max;
859 /***********************************************************************
860 * LISTBOX_FindFileStrPos
862 * Find the nearest string located before a given string in directory
863 * sort order (i.e. first files, then directories, then drives).
865 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
867 INT min, max, res = -1;
869 if (!HAS_STRINGS(descr))
870 return LISTBOX_FindStringPos( descr, str, FALSE );
871 min = 0;
872 max = descr->nb_items;
873 while (min != max)
875 INT index = (min + max) / 2;
876 LPCWSTR p = descr->items[index].str;
877 if (*p == '[') /* drive or directory */
879 if (*str != '[') res = -1;
880 else if (p[1] == '-') /* drive */
882 if (str[1] == '-') res = str[2] - p[2];
883 else res = -1;
885 else /* directory */
887 if (str[1] == '-') res = 1;
888 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
891 else /* filename */
893 if (*str == '[') res = 1;
894 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
896 if (!res) return index;
897 if (res < 0) max = index;
898 else min = index + 1;
900 return max;
904 /***********************************************************************
905 * LISTBOX_FindString
907 * Find the item beginning with a given string.
909 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
911 INT i;
912 LB_ITEMDATA *item;
914 if (start >= descr->nb_items) start = -1;
915 item = descr->items + start + 1;
916 if (HAS_STRINGS(descr))
918 if (!str || ! str[0] ) return LB_ERR;
919 if (exact)
921 for (i = start + 1; i < descr->nb_items; i++, item++)
922 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
923 for (i = 0, item = descr->items; i <= start; i++, item++)
924 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
926 else
928 /* Special case for drives and directories: ignore prefix */
929 #define CHECK_DRIVE(item) \
930 if ((item)->str[0] == '[') \
932 if (!strncmpiW( str, (item)->str+1, len )) return i; \
933 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
934 return i; \
937 INT len = strlenW(str);
938 for (i = start + 1; i < descr->nb_items; i++, item++)
940 if (!strncmpiW( str, item->str, len )) return i;
941 CHECK_DRIVE(item);
943 for (i = 0, item = descr->items; i <= start; i++, item++)
945 if (!strncmpiW( str, item->str, len )) return i;
946 CHECK_DRIVE(item);
948 #undef CHECK_DRIVE
951 else
953 if (exact && (descr->style & LBS_SORT))
954 /* If sorted, use a WM_COMPAREITEM binary search */
955 return LISTBOX_FindStringPos( descr, str, TRUE );
957 /* Otherwise use a linear search */
958 for (i = start + 1; i < descr->nb_items; i++, item++)
959 if (item->data == (ULONG_PTR)str) return i;
960 for (i = 0, item = descr->items; i <= start; i++, item++)
961 if (item->data == (ULONG_PTR)str) return i;
963 return LB_ERR;
967 /***********************************************************************
968 * LISTBOX_GetSelCount
970 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
972 INT i, count;
973 const LB_ITEMDATA *item = descr->items;
975 if (!(descr->style & LBS_MULTIPLESEL) ||
976 (descr->style & LBS_NOSEL))
977 return LB_ERR;
978 for (i = count = 0; i < descr->nb_items; i++, item++)
979 if (item->selected) count++;
980 return count;
984 /***********************************************************************
985 * LISTBOX_GetSelItems
987 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
989 INT i, count;
990 const LB_ITEMDATA *item = descr->items;
992 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
993 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
994 if (item->selected) array[count++] = i;
995 return count;
999 /***********************************************************************
1000 * LISTBOX_Paint
1002 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1004 INT i, col_pos = descr->page_size - 1;
1005 RECT rect;
1006 RECT focusRect = {-1, -1, -1, -1};
1007 HFONT oldFont = 0;
1008 HBRUSH hbrush, oldBrush = 0;
1010 if (descr->style & LBS_NOREDRAW) return 0;
1012 SetRect( &rect, 0, 0, descr->width, descr->height );
1013 if (descr->style & LBS_MULTICOLUMN)
1014 rect.right = rect.left + descr->column_width;
1015 else if (descr->horz_pos)
1017 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1018 rect.right += descr->horz_pos;
1021 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1022 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1023 (WPARAM)hdc, (LPARAM)descr->self );
1024 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1025 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1027 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1028 (descr->in_focus))
1030 /* Special case for empty listbox: paint focus rect */
1031 rect.bottom = rect.top + descr->item_height;
1032 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1033 &rect, NULL, 0, NULL );
1034 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1035 rect.top = rect.bottom;
1038 /* Paint all the item, regarding the selection
1039 Focus state will be painted after */
1041 for (i = descr->top_item; i < descr->nb_items; i++)
1043 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1044 rect.bottom = rect.top + descr->item_height;
1045 else
1046 rect.bottom = rect.top + descr->items[i].height;
1048 if (i == descr->focus_item)
1050 /* keep the focus rect, to paint the focus item after */
1051 focusRect.left = rect.left;
1052 focusRect.right = rect.right;
1053 focusRect.top = rect.top;
1054 focusRect.bottom = rect.bottom;
1056 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1057 rect.top = rect.bottom;
1059 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1061 if (!IS_OWNERDRAW(descr))
1063 /* Clear the bottom of the column */
1064 if (rect.top < descr->height)
1066 rect.bottom = descr->height;
1067 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1068 &rect, NULL, 0, NULL );
1072 /* Go to the next column */
1073 rect.left += descr->column_width;
1074 rect.right += descr->column_width;
1075 rect.top = 0;
1076 col_pos = descr->page_size - 1;
1078 else
1080 col_pos--;
1081 if (rect.top >= descr->height) break;
1085 /* Paint the focus item now */
1086 if (focusRect.top != focusRect.bottom &&
1087 descr->caret_on && descr->in_focus)
1088 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1090 if (!IS_OWNERDRAW(descr))
1092 /* Clear the remainder of the client area */
1093 if (rect.top < descr->height)
1095 rect.bottom = descr->height;
1096 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1097 &rect, NULL, 0, NULL );
1099 if (rect.right < descr->width)
1101 rect.left = rect.right;
1102 rect.right = descr->width;
1103 rect.top = 0;
1104 rect.bottom = descr->height;
1105 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1106 &rect, NULL, 0, NULL );
1109 if (oldFont) SelectObject( hdc, oldFont );
1110 if (oldBrush) SelectObject( hdc, oldBrush );
1111 return 0;
1115 /***********************************************************************
1116 * LISTBOX_InvalidateItems
1118 * Invalidate all items from a given item. If the specified item is not
1119 * visible, nothing happens.
1121 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1123 RECT rect;
1125 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1127 if (descr->style & LBS_NOREDRAW)
1129 descr->style |= LBS_DISPLAYCHANGED;
1130 return;
1132 rect.bottom = descr->height;
1133 InvalidateRect( descr->self, &rect, TRUE );
1134 if (descr->style & LBS_MULTICOLUMN)
1136 /* Repaint the other columns */
1137 rect.left = rect.right;
1138 rect.right = descr->width;
1139 rect.top = 0;
1140 InvalidateRect( descr->self, &rect, TRUE );
1145 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1147 RECT rect;
1149 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1150 InvalidateRect( descr->self, &rect, TRUE );
1153 /***********************************************************************
1154 * LISTBOX_GetItemHeight
1156 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1158 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1160 if ((index < 0) || (index >= descr->nb_items))
1162 SetLastError(ERROR_INVALID_INDEX);
1163 return LB_ERR;
1165 return descr->items[index].height;
1167 else return descr->item_height;
1171 /***********************************************************************
1172 * LISTBOX_SetItemHeight
1174 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1176 if (height > MAXBYTE)
1177 return -1;
1179 if (!height) height = 1;
1181 if (descr->style & LBS_OWNERDRAWVARIABLE)
1183 if ((index < 0) || (index >= descr->nb_items))
1185 SetLastError(ERROR_INVALID_INDEX);
1186 return LB_ERR;
1188 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1189 descr->items[index].height = height;
1190 LISTBOX_UpdateScroll( descr );
1191 if (repaint)
1192 LISTBOX_InvalidateItems( descr, index );
1194 else if (height != descr->item_height)
1196 TRACE("[%p]: new height = %d\n", descr->self, height );
1197 descr->item_height = height;
1198 LISTBOX_UpdatePage( descr );
1199 LISTBOX_UpdateScroll( descr );
1200 if (repaint)
1201 InvalidateRect( descr->self, 0, TRUE );
1203 return LB_OKAY;
1207 /***********************************************************************
1208 * LISTBOX_SetHorizontalPos
1210 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1212 INT diff;
1214 if (pos > descr->horz_extent - descr->width)
1215 pos = descr->horz_extent - descr->width;
1216 if (pos < 0) pos = 0;
1217 if (!(diff = descr->horz_pos - pos)) return;
1218 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1219 descr->horz_pos = pos;
1220 LISTBOX_UpdateScroll( descr );
1221 if (abs(diff) < descr->width)
1223 RECT rect;
1224 /* Invalidate the focused item so it will be repainted correctly */
1225 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1226 InvalidateRect( descr->self, &rect, TRUE );
1227 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1228 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1230 else
1231 InvalidateRect( descr->self, NULL, TRUE );
1235 /***********************************************************************
1236 * LISTBOX_SetHorizontalExtent
1238 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1240 if (descr->style & LBS_MULTICOLUMN)
1241 return LB_OKAY;
1242 if (extent == descr->horz_extent) return LB_OKAY;
1243 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1244 descr->horz_extent = extent;
1245 if (descr->style & WS_HSCROLL) {
1246 SCROLLINFO info;
1247 info.cbSize = sizeof(info);
1248 info.nMin = 0;
1249 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1250 info.fMask = SIF_RANGE;
1251 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1253 if (descr->horz_pos > extent - descr->width)
1254 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1255 return LB_OKAY;
1259 /***********************************************************************
1260 * LISTBOX_SetColumnWidth
1262 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1264 if (width == descr->column_width) return LB_OKAY;
1265 TRACE("[%p]: new column width = %d\n", descr->self, width );
1266 descr->column_width = width;
1267 LISTBOX_UpdatePage( descr );
1268 return LB_OKAY;
1272 /***********************************************************************
1273 * LISTBOX_SetFont
1275 * Returns the item height.
1277 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1279 HDC hdc;
1280 HFONT oldFont = 0;
1281 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1282 SIZE sz;
1284 descr->font = font;
1286 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1288 ERR("unable to get DC.\n" );
1289 return 16;
1291 if (font) oldFont = SelectObject( hdc, font );
1292 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1293 if (oldFont) SelectObject( hdc, oldFont );
1294 ReleaseDC( descr->self, hdc );
1296 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1297 if (!IS_OWNERDRAW(descr))
1298 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1299 return sz.cy;
1303 /***********************************************************************
1304 * LISTBOX_MakeItemVisible
1306 * Make sure that a given item is partially or fully visible.
1308 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1310 INT top;
1312 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1314 if (index <= descr->top_item) top = index;
1315 else if (descr->style & LBS_MULTICOLUMN)
1317 INT cols = descr->width;
1318 if (!fully) cols += descr->column_width - 1;
1319 if (cols >= descr->column_width) cols /= descr->column_width;
1320 else cols = 1;
1321 if (index < descr->top_item + (descr->page_size * cols)) return;
1322 top = index - descr->page_size * (cols - 1);
1324 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1326 INT height = fully ? descr->items[index].height : 1;
1327 for (top = index; top > descr->top_item; top--)
1328 if ((height += descr->items[top-1].height) > descr->height) break;
1330 else
1332 if (index < descr->top_item + descr->page_size) return;
1333 if (!fully && (index == descr->top_item + descr->page_size) &&
1334 (descr->height > (descr->page_size * descr->item_height))) return;
1335 top = index - descr->page_size + 1;
1337 LISTBOX_SetTopItem( descr, top, TRUE );
1340 /***********************************************************************
1341 * LISTBOX_SetCaretIndex
1343 * NOTES
1344 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1347 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1349 INT oldfocus = descr->focus_item;
1351 TRACE("old focus %d, index %d\n", oldfocus, index);
1353 if (descr->style & LBS_NOSEL) return LB_ERR;
1354 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1355 if (index == oldfocus) return LB_OKAY;
1357 LISTBOX_DrawFocusRect( descr, FALSE );
1358 descr->focus_item = index;
1360 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1361 LISTBOX_DrawFocusRect( descr, TRUE );
1363 return LB_OKAY;
1367 /***********************************************************************
1368 * LISTBOX_SelectItemRange
1370 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1372 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1373 INT last, BOOL on )
1375 INT i;
1377 /* A few sanity checks */
1379 if (descr->style & LBS_NOSEL) return LB_ERR;
1380 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1382 if (!descr->nb_items) return LB_OKAY;
1384 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1385 if (first < 0) first = 0;
1386 if (last < first) return LB_OKAY;
1388 if (on) /* Turn selection on */
1390 for (i = first; i <= last; i++)
1392 if (descr->items[i].selected) continue;
1393 descr->items[i].selected = TRUE;
1394 LISTBOX_InvalidateItemRect(descr, i);
1397 else /* Turn selection off */
1399 for (i = first; i <= last; i++)
1401 if (!descr->items[i].selected) continue;
1402 descr->items[i].selected = FALSE;
1403 LISTBOX_InvalidateItemRect(descr, i);
1406 return LB_OKAY;
1409 /***********************************************************************
1410 * LISTBOX_SetSelection
1412 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1413 BOOL on, BOOL send_notify )
1415 TRACE( "cur_sel=%d index=%d notify=%s\n",
1416 descr->selected_item, index, send_notify ? "YES" : "NO" );
1418 if (descr->style & LBS_NOSEL)
1420 descr->selected_item = index;
1421 return LB_ERR;
1423 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1424 if (descr->style & LBS_MULTIPLESEL)
1426 if (index == -1) /* Select all items */
1427 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1428 else /* Only one item */
1429 return LISTBOX_SelectItemRange( descr, index, index, on );
1431 else
1433 INT oldsel = descr->selected_item;
1434 if (index == oldsel) return LB_OKAY;
1435 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1436 if (index != -1) descr->items[index].selected = TRUE;
1437 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1438 descr->selected_item = index;
1439 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1440 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1441 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1442 else
1443 if( descr->lphc ) /* set selection change flag for parent combo */
1444 descr->lphc->wState |= CBF_SELCHANGE;
1446 return LB_OKAY;
1450 /***********************************************************************
1451 * LISTBOX_MoveCaret
1453 * Change the caret position and extend the selection to the new caret.
1455 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1457 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1459 if ((index < 0) || (index >= descr->nb_items))
1460 return;
1462 /* Important, repaint needs to be done in this order if
1463 you want to mimic Windows behavior:
1464 1. Remove the focus and paint the item
1465 2. Remove the selection and paint the item(s)
1466 3. Set the selection and repaint the item(s)
1467 4. Set the focus to 'index' and repaint the item */
1469 /* 1. remove the focus and repaint the item */
1470 LISTBOX_DrawFocusRect( descr, FALSE );
1472 /* 2. then turn off the previous selection */
1473 /* 3. repaint the new selected item */
1474 if (descr->style & LBS_EXTENDEDSEL)
1476 if (descr->anchor_item != -1)
1478 INT first = min( index, descr->anchor_item );
1479 INT last = max( index, descr->anchor_item );
1480 if (first > 0)
1481 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1482 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1483 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1486 else if (!(descr->style & LBS_MULTIPLESEL))
1488 /* Set selection to new caret item */
1489 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1492 /* 4. repaint the new item with the focus */
1493 descr->focus_item = index;
1494 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1495 LISTBOX_DrawFocusRect( descr, TRUE );
1499 /***********************************************************************
1500 * LISTBOX_InsertItem
1502 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1503 LPWSTR str, ULONG_PTR data )
1505 LB_ITEMDATA *item;
1506 INT max_items;
1507 INT oldfocus = descr->focus_item;
1509 if (index == -1) index = descr->nb_items;
1510 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1511 if (!descr->items) max_items = 0;
1512 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1513 if (descr->nb_items == max_items)
1515 /* We need to grow the array */
1516 max_items += LB_ARRAY_GRANULARITY;
1517 if (descr->items)
1518 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1519 max_items * sizeof(LB_ITEMDATA) );
1520 else
1521 item = HeapAlloc( GetProcessHeap(), 0,
1522 max_items * sizeof(LB_ITEMDATA) );
1523 if (!item)
1525 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1526 return LB_ERRSPACE;
1528 descr->items = item;
1531 /* Insert the item structure */
1533 item = &descr->items[index];
1534 if (index < descr->nb_items)
1535 RtlMoveMemory( item + 1, item,
1536 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1537 item->str = str;
1538 item->data = data;
1539 item->height = 0;
1540 item->selected = FALSE;
1541 descr->nb_items++;
1543 /* Get item height */
1545 if (descr->style & LBS_OWNERDRAWVARIABLE)
1547 MEASUREITEMSTRUCT mis;
1548 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1550 mis.CtlType = ODT_LISTBOX;
1551 mis.CtlID = id;
1552 mis.itemID = index;
1553 mis.itemData = descr->items[index].data;
1554 mis.itemHeight = descr->item_height;
1555 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1556 item->height = mis.itemHeight ? mis.itemHeight : 1;
1557 TRACE("[%p]: measure item %d (%s) = %d\n",
1558 descr->self, index, str ? debugstr_w(str) : "", item->height );
1561 /* Repaint the items */
1563 LISTBOX_UpdateScroll( descr );
1564 LISTBOX_InvalidateItems( descr, index );
1566 /* Move selection and focused item */
1567 /* If listbox was empty, set focus to the first item */
1568 if (descr->nb_items == 1)
1569 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1570 /* single select don't change selection index in win31 */
1571 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1573 descr->selected_item++;
1574 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1576 else
1578 if (index <= descr->selected_item)
1580 descr->selected_item++;
1581 descr->focus_item = oldfocus; /* focus not changed */
1584 return LB_OKAY;
1588 /***********************************************************************
1589 * LISTBOX_InsertString
1591 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1593 LPWSTR new_str = NULL;
1594 ULONG_PTR data = 0;
1595 LRESULT ret;
1597 if (HAS_STRINGS(descr))
1599 static const WCHAR empty_stringW[] = { 0 };
1600 if (!str) str = empty_stringW;
1601 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1603 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1604 return LB_ERRSPACE;
1606 strcpyW(new_str, str);
1608 else data = (ULONG_PTR)str;
1610 if (index == -1) index = descr->nb_items;
1611 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1613 HeapFree( GetProcessHeap(), 0, new_str );
1614 return ret;
1617 TRACE("[%p]: added item %d %s\n",
1618 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1619 return index;
1623 /***********************************************************************
1624 * LISTBOX_DeleteItem
1626 * Delete the content of an item. 'index' must be a valid index.
1628 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1630 /* save the item data before it gets freed by LB_RESETCONTENT */
1631 ULONG_PTR item_data = descr->items[index].data;
1632 LPWSTR item_str = descr->items[index].str;
1634 if (!descr->nb_items)
1635 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1637 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1638 * while Win95 sends it for all items with user data.
1639 * It's probably better to send it too often than not
1640 * often enough, so this is what we do here.
1642 if (IS_OWNERDRAW(descr) || item_data)
1644 DELETEITEMSTRUCT dis;
1645 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1647 dis.CtlType = ODT_LISTBOX;
1648 dis.CtlID = id;
1649 dis.itemID = index;
1650 dis.hwndItem = descr->self;
1651 dis.itemData = item_data;
1652 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1654 if (HAS_STRINGS(descr))
1655 HeapFree( GetProcessHeap(), 0, item_str );
1659 /***********************************************************************
1660 * LISTBOX_RemoveItem
1662 * Remove an item from the listbox and delete its content.
1664 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1666 LB_ITEMDATA *item;
1667 INT max_items;
1669 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1671 /* We need to invalidate the original rect instead of the updated one. */
1672 LISTBOX_InvalidateItems( descr, index );
1674 descr->nb_items--;
1675 LISTBOX_DeleteItem( descr, index );
1677 if (!descr->nb_items) return LB_OKAY;
1679 /* Remove the item */
1681 item = &descr->items[index];
1682 if (index < descr->nb_items)
1683 RtlMoveMemory( item, item + 1,
1684 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1685 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1687 /* Shrink the item array if possible */
1689 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1690 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1692 max_items -= LB_ARRAY_GRANULARITY;
1693 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1694 max_items * sizeof(LB_ITEMDATA) );
1695 if (item) descr->items = item;
1697 /* Repaint the items */
1699 LISTBOX_UpdateScroll( descr );
1700 /* if we removed the scrollbar, reset the top of the list
1701 (correct for owner-drawn ???) */
1702 if (descr->nb_items == descr->page_size)
1703 LISTBOX_SetTopItem( descr, 0, TRUE );
1705 /* Move selection and focused item */
1706 if (!IS_MULTISELECT(descr))
1708 if (index == descr->selected_item)
1709 descr->selected_item = -1;
1710 else if (index < descr->selected_item)
1712 descr->selected_item--;
1713 if (ISWIN31) /* win 31 do not change the selected item number */
1714 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1718 if (descr->focus_item >= descr->nb_items)
1720 descr->focus_item = descr->nb_items - 1;
1721 if (descr->focus_item < 0) descr->focus_item = 0;
1723 return LB_OKAY;
1727 /***********************************************************************
1728 * LISTBOX_ResetContent
1730 static void LISTBOX_ResetContent( LB_DESCR *descr )
1732 INT i;
1734 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1735 HeapFree( GetProcessHeap(), 0, descr->items );
1736 descr->nb_items = 0;
1737 descr->top_item = 0;
1738 descr->selected_item = -1;
1739 descr->focus_item = 0;
1740 descr->anchor_item = -1;
1741 descr->items = NULL;
1745 /***********************************************************************
1746 * LISTBOX_SetCount
1748 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1750 LRESULT ret;
1752 if (HAS_STRINGS(descr))
1754 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1755 return LB_ERR;
1758 /* FIXME: this is far from optimal... */
1759 if (count > descr->nb_items)
1761 while (count > descr->nb_items)
1762 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1763 return ret;
1765 else if (count < descr->nb_items)
1767 while (count < descr->nb_items)
1768 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1769 return ret;
1772 InvalidateRect( descr->self, NULL, TRUE );
1773 return LB_OKAY;
1777 /***********************************************************************
1778 * LISTBOX_Directory
1780 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1781 LPCWSTR filespec, BOOL long_names )
1783 HANDLE handle;
1784 LRESULT ret = LB_OKAY;
1785 WIN32_FIND_DATAW entry;
1786 int pos;
1787 LRESULT maxinsert = LB_ERR;
1789 /* don't scan directory if we just want drives exclusively */
1790 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1791 /* scan directory */
1792 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1794 int le = GetLastError();
1795 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1797 else
1801 WCHAR buffer[270];
1802 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1804 static const WCHAR bracketW[] = { ']',0 };
1805 static const WCHAR dotW[] = { '.',0 };
1806 if (!(attrib & DDL_DIRECTORY) ||
1807 !strcmpW( entry.cFileName, dotW )) continue;
1808 buffer[0] = '[';
1809 if (!long_names && entry.cAlternateFileName[0])
1810 strcpyW( buffer + 1, entry.cAlternateFileName );
1811 else
1812 strcpyW( buffer + 1, entry.cFileName );
1813 strcatW(buffer, bracketW);
1815 else /* not a directory */
1817 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1818 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1820 if ((attrib & DDL_EXCLUSIVE) &&
1821 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1822 continue;
1823 #undef ATTRIBS
1824 if (!long_names && entry.cAlternateFileName[0])
1825 strcpyW( buffer, entry.cAlternateFileName );
1826 else
1827 strcpyW( buffer, entry.cFileName );
1829 if (!long_names) CharLowerW( buffer );
1830 pos = LISTBOX_FindFileStrPos( descr, buffer );
1831 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1832 break;
1833 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1834 } while (FindNextFileW( handle, &entry ));
1835 FindClose( handle );
1838 if (ret >= 0)
1840 ret = maxinsert;
1842 /* scan drives */
1843 if (attrib & DDL_DRIVES)
1845 WCHAR buffer[] = {'[','-','a','-',']',0};
1846 WCHAR root[] = {'A',':','\\',0};
1847 int drive;
1848 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1850 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1851 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1852 break;
1856 return ret;
1860 /***********************************************************************
1861 * LISTBOX_HandleVScroll
1863 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1865 SCROLLINFO info;
1867 if (descr->style & LBS_MULTICOLUMN) return 0;
1868 switch(scrollReq)
1870 case SB_LINEUP:
1871 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1872 break;
1873 case SB_LINEDOWN:
1874 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1875 break;
1876 case SB_PAGEUP:
1877 LISTBOX_SetTopItem( descr, descr->top_item -
1878 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1879 break;
1880 case SB_PAGEDOWN:
1881 LISTBOX_SetTopItem( descr, descr->top_item +
1882 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1883 break;
1884 case SB_THUMBPOSITION:
1885 LISTBOX_SetTopItem( descr, pos, TRUE );
1886 break;
1887 case SB_THUMBTRACK:
1888 info.cbSize = sizeof(info);
1889 info.fMask = SIF_TRACKPOS;
1890 GetScrollInfo( descr->self, SB_VERT, &info );
1891 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1892 break;
1893 case SB_TOP:
1894 LISTBOX_SetTopItem( descr, 0, TRUE );
1895 break;
1896 case SB_BOTTOM:
1897 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1898 break;
1900 return 0;
1904 /***********************************************************************
1905 * LISTBOX_HandleHScroll
1907 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1909 SCROLLINFO info;
1910 INT page;
1912 if (descr->style & LBS_MULTICOLUMN)
1914 switch(scrollReq)
1916 case SB_LINELEFT:
1917 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1918 TRUE );
1919 break;
1920 case SB_LINERIGHT:
1921 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1922 TRUE );
1923 break;
1924 case SB_PAGELEFT:
1925 page = descr->width / descr->column_width;
1926 if (page < 1) page = 1;
1927 LISTBOX_SetTopItem( descr,
1928 descr->top_item - page * descr->page_size, TRUE );
1929 break;
1930 case SB_PAGERIGHT:
1931 page = descr->width / descr->column_width;
1932 if (page < 1) page = 1;
1933 LISTBOX_SetTopItem( descr,
1934 descr->top_item + page * descr->page_size, TRUE );
1935 break;
1936 case SB_THUMBPOSITION:
1937 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1938 break;
1939 case SB_THUMBTRACK:
1940 info.cbSize = sizeof(info);
1941 info.fMask = SIF_TRACKPOS;
1942 GetScrollInfo( descr->self, SB_VERT, &info );
1943 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1944 TRUE );
1945 break;
1946 case SB_LEFT:
1947 LISTBOX_SetTopItem( descr, 0, TRUE );
1948 break;
1949 case SB_RIGHT:
1950 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1951 break;
1954 else if (descr->horz_extent)
1956 switch(scrollReq)
1958 case SB_LINELEFT:
1959 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1960 break;
1961 case SB_LINERIGHT:
1962 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1963 break;
1964 case SB_PAGELEFT:
1965 LISTBOX_SetHorizontalPos( descr,
1966 descr->horz_pos - descr->width );
1967 break;
1968 case SB_PAGERIGHT:
1969 LISTBOX_SetHorizontalPos( descr,
1970 descr->horz_pos + descr->width );
1971 break;
1972 case SB_THUMBPOSITION:
1973 LISTBOX_SetHorizontalPos( descr, pos );
1974 break;
1975 case SB_THUMBTRACK:
1976 info.cbSize = sizeof(info);
1977 info.fMask = SIF_TRACKPOS;
1978 GetScrollInfo( descr->self, SB_HORZ, &info );
1979 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1980 break;
1981 case SB_LEFT:
1982 LISTBOX_SetHorizontalPos( descr, 0 );
1983 break;
1984 case SB_RIGHT:
1985 LISTBOX_SetHorizontalPos( descr,
1986 descr->horz_extent - descr->width );
1987 break;
1990 return 0;
1993 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1995 UINT pulScrollLines = 3;
1997 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1999 /* if scrolling changes direction, ignore left overs */
2000 if ((delta < 0 && descr->wheel_remain < 0) ||
2001 (delta > 0 && descr->wheel_remain > 0))
2002 descr->wheel_remain += delta;
2003 else
2004 descr->wheel_remain = delta;
2006 if (descr->wheel_remain && pulScrollLines)
2008 int cLineScroll;
2009 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2010 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2011 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2012 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2014 return 0;
2017 /***********************************************************************
2018 * LISTBOX_HandleLButtonDown
2020 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2022 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2024 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2025 descr->self, x, y, index, descr->focus_item);
2027 if (!descr->caret_on && (descr->in_focus)) return 0;
2029 if (!descr->in_focus)
2031 if( !descr->lphc ) SetFocus( descr->self );
2032 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2035 if (index == -1) return 0;
2037 if (!descr->lphc)
2039 if (descr->style & LBS_NOTIFY )
2040 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2041 MAKELPARAM( x, y ) );
2044 descr->captured = TRUE;
2045 SetCapture( descr->self );
2047 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2049 /* we should perhaps make sure that all items are deselected
2050 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2051 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2052 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2055 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2056 if (keys & MK_CONTROL)
2058 LISTBOX_SetCaretIndex( descr, index, FALSE );
2059 LISTBOX_SetSelection( descr, index,
2060 !descr->items[index].selected,
2061 (descr->style & LBS_NOTIFY) != 0);
2063 else
2065 LISTBOX_MoveCaret( descr, index, FALSE );
2067 if (descr->style & LBS_EXTENDEDSEL)
2069 LISTBOX_SetSelection( descr, index,
2070 descr->items[index].selected,
2071 (descr->style & LBS_NOTIFY) != 0 );
2073 else
2075 LISTBOX_SetSelection( descr, index,
2076 !descr->items[index].selected,
2077 (descr->style & LBS_NOTIFY) != 0 );
2081 else
2083 descr->anchor_item = index;
2084 LISTBOX_MoveCaret( descr, index, FALSE );
2085 LISTBOX_SetSelection( descr, index,
2086 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2089 if (!descr->lphc)
2091 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2093 POINT pt;
2095 pt.x = x;
2096 pt.y = y;
2098 if (DragDetect( descr->self, pt ))
2099 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2102 return 0;
2106 /*************************************************************************
2107 * LISTBOX_HandleLButtonDownCombo [Internal]
2109 * Process LButtonDown message for the ComboListBox
2111 * PARAMS
2112 * pWnd [I] The windows internal structure
2113 * pDescr [I] The ListBox internal structure
2114 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2115 * x [I] X Mouse Coordinate
2116 * y [I] Y Mouse Coordinate
2118 * RETURNS
2119 * 0 since we are processing the WM_LBUTTONDOWN Message
2121 * NOTES
2122 * This function is only to be used when a ListBox is a ComboListBox
2125 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2127 RECT clientRect, screenRect;
2128 POINT mousePos;
2130 mousePos.x = x;
2131 mousePos.y = y;
2133 GetClientRect(descr->self, &clientRect);
2135 if(PtInRect(&clientRect, mousePos))
2137 /* MousePos is in client, resume normal processing */
2138 if (msg == WM_LBUTTONDOWN)
2140 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2141 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2143 else if (descr->style & LBS_NOTIFY)
2144 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2146 else
2148 POINT screenMousePos;
2149 HWND hWndOldCapture;
2151 /* Check the Non-Client Area */
2152 screenMousePos = mousePos;
2153 hWndOldCapture = GetCapture();
2154 ReleaseCapture();
2155 GetWindowRect(descr->self, &screenRect);
2156 ClientToScreen(descr->self, &screenMousePos);
2158 if(!PtInRect(&screenRect, screenMousePos))
2160 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2161 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2162 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2164 else
2166 /* Check to see the NC is a scrollbar */
2167 INT nHitTestType=0;
2168 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2169 /* Check Vertical scroll bar */
2170 if (style & WS_VSCROLL)
2172 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2173 if (PtInRect( &clientRect, mousePos ))
2174 nHitTestType = HTVSCROLL;
2176 /* Check horizontal scroll bar */
2177 if (style & WS_HSCROLL)
2179 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2180 if (PtInRect( &clientRect, mousePos ))
2181 nHitTestType = HTHSCROLL;
2183 /* Windows sends this message when a scrollbar is clicked
2186 if(nHitTestType != 0)
2188 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2189 MAKELONG(screenMousePos.x, screenMousePos.y));
2191 /* Resume the Capture after scrolling is complete
2193 if(hWndOldCapture != 0)
2194 SetCapture(hWndOldCapture);
2197 return 0;
2200 /***********************************************************************
2201 * LISTBOX_HandleLButtonUp
2203 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2205 if (LISTBOX_Timer != LB_TIMER_NONE)
2206 KillSystemTimer( descr->self, LB_TIMER_ID );
2207 LISTBOX_Timer = LB_TIMER_NONE;
2208 if (descr->captured)
2210 descr->captured = FALSE;
2211 if (GetCapture() == descr->self) ReleaseCapture();
2212 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2213 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2215 return 0;
2219 /***********************************************************************
2220 * LISTBOX_HandleTimer
2222 * Handle scrolling upon a timer event.
2223 * Return TRUE if scrolling should continue.
2225 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2227 switch(dir)
2229 case LB_TIMER_UP:
2230 if (descr->top_item) index = descr->top_item - 1;
2231 else index = 0;
2232 break;
2233 case LB_TIMER_LEFT:
2234 if (descr->top_item) index -= descr->page_size;
2235 break;
2236 case LB_TIMER_DOWN:
2237 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2238 if (index == descr->focus_item) index++;
2239 if (index >= descr->nb_items) index = descr->nb_items - 1;
2240 break;
2241 case LB_TIMER_RIGHT:
2242 if (index + descr->page_size < descr->nb_items)
2243 index += descr->page_size;
2244 break;
2245 case LB_TIMER_NONE:
2246 break;
2248 if (index == descr->focus_item) return FALSE;
2249 LISTBOX_MoveCaret( descr, index, FALSE );
2250 return TRUE;
2254 /***********************************************************************
2255 * LISTBOX_HandleSystemTimer
2257 * WM_SYSTIMER handler.
2259 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2261 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2263 KillSystemTimer( descr->self, LB_TIMER_ID );
2264 LISTBOX_Timer = LB_TIMER_NONE;
2266 return 0;
2270 /***********************************************************************
2271 * LISTBOX_HandleMouseMove
2273 * WM_MOUSEMOVE handler.
2275 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2276 INT x, INT y )
2278 INT index;
2279 TIMER_DIRECTION dir = LB_TIMER_NONE;
2281 if (!descr->captured) return;
2283 if (descr->style & LBS_MULTICOLUMN)
2285 if (y < 0) y = 0;
2286 else if (y >= descr->item_height * descr->page_size)
2287 y = descr->item_height * descr->page_size - 1;
2289 if (x < 0)
2291 dir = LB_TIMER_LEFT;
2292 x = 0;
2294 else if (x >= descr->width)
2296 dir = LB_TIMER_RIGHT;
2297 x = descr->width - 1;
2300 else
2302 if (y < 0) dir = LB_TIMER_UP; /* above */
2303 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2306 index = LISTBOX_GetItemFromPoint( descr, x, y );
2307 if (index == -1) index = descr->focus_item;
2308 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2310 /* Start/stop the system timer */
2312 if (dir != LB_TIMER_NONE)
2313 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2314 else if (LISTBOX_Timer != LB_TIMER_NONE)
2315 KillSystemTimer( descr->self, LB_TIMER_ID );
2316 LISTBOX_Timer = dir;
2320 /***********************************************************************
2321 * LISTBOX_HandleKeyDown
2323 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2325 INT caret = -1;
2326 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2327 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2328 bForceSelection = FALSE; /* only for single select list */
2330 if (descr->style & LBS_WANTKEYBOARDINPUT)
2332 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2333 MAKEWPARAM(LOWORD(key), descr->focus_item),
2334 (LPARAM)descr->self );
2335 if (caret == -2) return 0;
2337 if (caret == -1) switch(key)
2339 case VK_LEFT:
2340 if (descr->style & LBS_MULTICOLUMN)
2342 bForceSelection = FALSE;
2343 if (descr->focus_item >= descr->page_size)
2344 caret = descr->focus_item - descr->page_size;
2345 break;
2347 /* fall through */
2348 case VK_UP:
2349 caret = descr->focus_item - 1;
2350 if (caret < 0) caret = 0;
2351 break;
2352 case VK_RIGHT:
2353 if (descr->style & LBS_MULTICOLUMN)
2355 bForceSelection = FALSE;
2356 if (descr->focus_item + descr->page_size < descr->nb_items)
2357 caret = descr->focus_item + descr->page_size;
2358 break;
2360 /* fall through */
2361 case VK_DOWN:
2362 caret = descr->focus_item + 1;
2363 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2364 break;
2366 case VK_PRIOR:
2367 if (descr->style & LBS_MULTICOLUMN)
2369 INT page = descr->width / descr->column_width;
2370 if (page < 1) page = 1;
2371 caret = descr->focus_item - (page * descr->page_size) + 1;
2373 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2374 if (caret < 0) caret = 0;
2375 break;
2376 case VK_NEXT:
2377 if (descr->style & LBS_MULTICOLUMN)
2379 INT page = descr->width / descr->column_width;
2380 if (page < 1) page = 1;
2381 caret = descr->focus_item + (page * descr->page_size) - 1;
2383 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2384 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2385 break;
2386 case VK_HOME:
2387 caret = 0;
2388 break;
2389 case VK_END:
2390 caret = descr->nb_items - 1;
2391 break;
2392 case VK_SPACE:
2393 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2394 else if (descr->style & LBS_MULTIPLESEL)
2396 LISTBOX_SetSelection( descr, descr->focus_item,
2397 !descr->items[descr->focus_item].selected,
2398 (descr->style & LBS_NOTIFY) != 0 );
2400 break;
2401 default:
2402 bForceSelection = FALSE;
2404 if (bForceSelection) /* focused item is used instead of key */
2405 caret = descr->focus_item;
2406 if (caret >= 0)
2408 if (((descr->style & LBS_EXTENDEDSEL) &&
2409 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2410 !IS_MULTISELECT(descr))
2411 descr->anchor_item = caret;
2412 LISTBOX_MoveCaret( descr, caret, TRUE );
2414 if (descr->style & LBS_MULTIPLESEL)
2415 descr->selected_item = caret;
2416 else
2417 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2418 if (descr->style & LBS_NOTIFY)
2420 if (descr->lphc && IsWindowVisible( descr->self ))
2422 /* make sure that combo parent doesn't hide us */
2423 descr->lphc->wState |= CBF_NOROLLUP;
2425 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2428 return 0;
2432 /***********************************************************************
2433 * LISTBOX_HandleChar
2435 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2437 INT caret = -1;
2438 WCHAR str[2];
2440 str[0] = charW;
2441 str[1] = '\0';
2443 if (descr->style & LBS_WANTKEYBOARDINPUT)
2445 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2446 MAKEWPARAM(charW, descr->focus_item),
2447 (LPARAM)descr->self );
2448 if (caret == -2) return 0;
2450 if (caret == -1)
2451 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2452 if (caret != -1)
2454 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2455 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2456 LISTBOX_MoveCaret( descr, caret, TRUE );
2457 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2458 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2460 return 0;
2464 /***********************************************************************
2465 * LISTBOX_Create
2467 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2469 LB_DESCR *descr;
2470 MEASUREITEMSTRUCT mis;
2471 RECT rect;
2473 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2474 return FALSE;
2476 GetClientRect( hwnd, &rect );
2477 descr->self = hwnd;
2478 descr->owner = GetParent( descr->self );
2479 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2480 descr->width = rect.right - rect.left;
2481 descr->height = rect.bottom - rect.top;
2482 descr->items = NULL;
2483 descr->nb_items = 0;
2484 descr->top_item = 0;
2485 descr->selected_item = -1;
2486 descr->focus_item = 0;
2487 descr->anchor_item = -1;
2488 descr->item_height = 1;
2489 descr->page_size = 1;
2490 descr->column_width = 150;
2491 descr->horz_extent = 0;
2492 descr->horz_pos = 0;
2493 descr->nb_tabs = 0;
2494 descr->tabs = NULL;
2495 descr->wheel_remain = 0;
2496 descr->caret_on = !lphc;
2497 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2498 descr->in_focus = FALSE;
2499 descr->captured = FALSE;
2500 descr->font = 0;
2501 descr->locale = GetUserDefaultLCID();
2502 descr->lphc = lphc;
2504 if( lphc )
2506 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2507 descr->owner = lphc->self;
2510 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2512 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2514 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2515 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2516 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2517 descr->item_height = LISTBOX_SetFont( descr, 0 );
2519 if (descr->style & LBS_OWNERDRAWFIXED)
2521 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2523 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2524 descr->item_height = lphc->fixedOwnerDrawHeight;
2526 else
2528 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2529 mis.CtlType = ODT_LISTBOX;
2530 mis.CtlID = id;
2531 mis.itemID = -1;
2532 mis.itemWidth = 0;
2533 mis.itemData = 0;
2534 mis.itemHeight = descr->item_height;
2535 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2536 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2540 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2541 return TRUE;
2545 /***********************************************************************
2546 * LISTBOX_Destroy
2548 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2550 LISTBOX_ResetContent( descr );
2551 SetWindowLongPtrW( descr->self, 0, 0 );
2552 HeapFree( GetProcessHeap(), 0, descr );
2553 return TRUE;
2557 /***********************************************************************
2558 * ListBoxWndProc_common
2560 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2562 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2563 LPHEADCOMBO lphc = 0;
2564 LRESULT ret;
2566 if (!descr)
2568 if (!IsWindow(hwnd)) return 0;
2570 if (msg == WM_CREATE)
2572 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2573 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2574 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2575 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2576 return 0;
2578 /* Ignore all other messages before we get a WM_CREATE */
2579 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2580 DefWindowProcA( hwnd, msg, wParam, lParam );
2582 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2584 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2585 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2587 switch(msg)
2589 case LB_RESETCONTENT:
2590 LISTBOX_ResetContent( descr );
2591 LISTBOX_UpdateScroll( descr );
2592 InvalidateRect( descr->self, NULL, TRUE );
2593 return 0;
2595 case LB_ADDSTRING:
2597 INT ret;
2598 LPWSTR textW;
2599 if(unicode || !HAS_STRINGS(descr))
2600 textW = (LPWSTR)lParam;
2601 else
2603 LPSTR textA = (LPSTR)lParam;
2604 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2605 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2606 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2607 else
2608 return LB_ERRSPACE;
2610 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2611 ret = LISTBOX_InsertString( descr, wParam, textW );
2612 if (!unicode && HAS_STRINGS(descr))
2613 HeapFree(GetProcessHeap(), 0, textW);
2614 return ret;
2617 case LB_INSERTSTRING:
2619 INT ret;
2620 LPWSTR textW;
2621 if(unicode || !HAS_STRINGS(descr))
2622 textW = (LPWSTR)lParam;
2623 else
2625 LPSTR textA = (LPSTR)lParam;
2626 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2627 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2628 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2629 else
2630 return LB_ERRSPACE;
2632 ret = LISTBOX_InsertString( descr, wParam, textW );
2633 if(!unicode && HAS_STRINGS(descr))
2634 HeapFree(GetProcessHeap(), 0, textW);
2635 return ret;
2638 case LB_ADDFILE:
2640 INT ret;
2641 LPWSTR textW;
2642 if(unicode || !HAS_STRINGS(descr))
2643 textW = (LPWSTR)lParam;
2644 else
2646 LPSTR textA = (LPSTR)lParam;
2647 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2648 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2649 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2650 else
2651 return LB_ERRSPACE;
2653 wParam = LISTBOX_FindFileStrPos( descr, textW );
2654 ret = LISTBOX_InsertString( descr, wParam, textW );
2655 if(!unicode && HAS_STRINGS(descr))
2656 HeapFree(GetProcessHeap(), 0, textW);
2657 return ret;
2660 case LB_DELETESTRING:
2661 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2662 return descr->nb_items;
2663 else
2665 SetLastError(ERROR_INVALID_INDEX);
2666 return LB_ERR;
2669 case LB_GETITEMDATA:
2670 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2672 SetLastError(ERROR_INVALID_INDEX);
2673 return LB_ERR;
2675 return descr->items[wParam].data;
2677 case LB_SETITEMDATA:
2678 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2680 SetLastError(ERROR_INVALID_INDEX);
2681 return LB_ERR;
2683 descr->items[wParam].data = lParam;
2684 /* undocumented: returns TRUE, not LB_OKAY (0) */
2685 return TRUE;
2687 case LB_GETCOUNT:
2688 return descr->nb_items;
2690 case LB_GETTEXT:
2691 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2693 case LB_GETTEXTLEN:
2694 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2696 SetLastError(ERROR_INVALID_INDEX);
2697 return LB_ERR;
2699 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2700 if (unicode) return strlenW( descr->items[wParam].str );
2701 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2702 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2704 case LB_GETCURSEL:
2705 if (descr->nb_items == 0)
2706 return LB_ERR;
2707 if (!IS_MULTISELECT(descr))
2708 return descr->selected_item;
2709 if (descr->selected_item != -1)
2710 return descr->selected_item;
2711 return descr->focus_item;
2712 /* otherwise, if the user tries to move the selection with the */
2713 /* arrow keys, we will give the application something to choke on */
2714 case LB_GETTOPINDEX:
2715 return descr->top_item;
2717 case LB_GETITEMHEIGHT:
2718 return LISTBOX_GetItemHeight( descr, wParam );
2720 case LB_SETITEMHEIGHT:
2721 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2723 case LB_ITEMFROMPOINT:
2725 POINT pt;
2726 RECT rect;
2727 int index;
2728 BOOL hit = TRUE;
2730 /* The hiword of the return value is not a client area
2731 hittest as suggested by MSDN, but rather a hittest on
2732 the returned listbox item. */
2734 if(descr->nb_items == 0)
2735 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2737 pt.x = (short)LOWORD(lParam);
2738 pt.y = (short)HIWORD(lParam);
2740 SetRect(&rect, 0, 0, descr->width, descr->height);
2742 if(!PtInRect(&rect, pt))
2744 pt.x = min(pt.x, rect.right - 1);
2745 pt.x = max(pt.x, 0);
2746 pt.y = min(pt.y, rect.bottom - 1);
2747 pt.y = max(pt.y, 0);
2748 hit = FALSE;
2751 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2753 if(index == -1)
2755 index = descr->nb_items - 1;
2756 hit = FALSE;
2758 return MAKELONG(index, hit ? 0 : 1);
2761 case LB_SETCARETINDEX:
2762 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2763 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2764 return LB_ERR;
2765 else if (ISWIN31)
2766 return wParam;
2767 else
2768 return LB_OKAY;
2770 case LB_GETCARETINDEX:
2771 return descr->focus_item;
2773 case LB_SETTOPINDEX:
2774 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2776 case LB_SETCOLUMNWIDTH:
2777 return LISTBOX_SetColumnWidth( descr, wParam );
2779 case LB_GETITEMRECT:
2780 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2782 case LB_FINDSTRING:
2784 INT ret;
2785 LPWSTR textW;
2786 if(unicode || !HAS_STRINGS(descr))
2787 textW = (LPWSTR)lParam;
2788 else
2790 LPSTR textA = (LPSTR)lParam;
2791 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2792 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2793 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2795 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2796 if(!unicode && HAS_STRINGS(descr))
2797 HeapFree(GetProcessHeap(), 0, textW);
2798 return ret;
2801 case LB_FINDSTRINGEXACT:
2803 INT ret;
2804 LPWSTR textW;
2805 if(unicode || !HAS_STRINGS(descr))
2806 textW = (LPWSTR)lParam;
2807 else
2809 LPSTR textA = (LPSTR)lParam;
2810 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2811 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2812 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2814 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2815 if(!unicode && HAS_STRINGS(descr))
2816 HeapFree(GetProcessHeap(), 0, textW);
2817 return ret;
2820 case LB_SELECTSTRING:
2822 INT index;
2823 LPWSTR textW;
2825 if(HAS_STRINGS(descr))
2826 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2827 debugstr_a((LPSTR)lParam));
2828 if(unicode || !HAS_STRINGS(descr))
2829 textW = (LPWSTR)lParam;
2830 else
2832 LPSTR textA = (LPSTR)lParam;
2833 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2834 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2835 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2837 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2838 if(!unicode && HAS_STRINGS(descr))
2839 HeapFree(GetProcessHeap(), 0, textW);
2840 if (index != LB_ERR)
2842 LISTBOX_MoveCaret( descr, index, TRUE );
2843 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2845 return index;
2848 case LB_GETSEL:
2849 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2850 return LB_ERR;
2851 return descr->items[wParam].selected;
2853 case LB_SETSEL:
2854 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2856 case LB_SETCURSEL:
2857 if (IS_MULTISELECT(descr)) return LB_ERR;
2858 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2859 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2860 if (ret != LB_ERR) ret = descr->selected_item;
2861 return ret;
2863 case LB_GETSELCOUNT:
2864 return LISTBOX_GetSelCount( descr );
2866 case LB_GETSELITEMS:
2867 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2869 case LB_SELITEMRANGE:
2870 if (LOWORD(lParam) <= HIWORD(lParam))
2871 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2872 HIWORD(lParam), wParam );
2873 else
2874 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2875 LOWORD(lParam), wParam );
2877 case LB_SELITEMRANGEEX:
2878 if ((INT)lParam >= (INT)wParam)
2879 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2880 else
2881 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2883 case LB_GETHORIZONTALEXTENT:
2884 return descr->horz_extent;
2886 case LB_SETHORIZONTALEXTENT:
2887 return LISTBOX_SetHorizontalExtent( descr, wParam );
2889 case LB_GETANCHORINDEX:
2890 return descr->anchor_item;
2892 case LB_SETANCHORINDEX:
2893 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2895 SetLastError(ERROR_INVALID_INDEX);
2896 return LB_ERR;
2898 descr->anchor_item = (INT)wParam;
2899 return LB_OKAY;
2901 case LB_DIR:
2903 INT ret;
2904 LPWSTR textW;
2905 if(unicode)
2906 textW = (LPWSTR)lParam;
2907 else
2909 LPSTR textA = (LPSTR)lParam;
2910 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2911 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2912 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2914 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2915 if(!unicode)
2916 HeapFree(GetProcessHeap(), 0, textW);
2917 return ret;
2920 case LB_GETLOCALE:
2921 return descr->locale;
2923 case LB_SETLOCALE:
2925 LCID ret;
2926 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2927 return LB_ERR;
2928 ret = descr->locale;
2929 descr->locale = (LCID)wParam;
2930 return ret;
2933 case LB_INITSTORAGE:
2934 return LISTBOX_InitStorage( descr, wParam );
2936 case LB_SETCOUNT:
2937 return LISTBOX_SetCount( descr, (INT)wParam );
2939 case LB_SETTABSTOPS:
2940 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2942 case LB_CARETON:
2943 if (descr->caret_on)
2944 return LB_OKAY;
2945 descr->caret_on = TRUE;
2946 if ((descr->focus_item != -1) && (descr->in_focus))
2947 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2948 return LB_OKAY;
2950 case LB_CARETOFF:
2951 if (!descr->caret_on)
2952 return LB_OKAY;
2953 descr->caret_on = FALSE;
2954 if ((descr->focus_item != -1) && (descr->in_focus))
2955 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2956 return LB_OKAY;
2958 case LB_GETLISTBOXINFO:
2959 return descr->page_size;
2961 case WM_DESTROY:
2962 return LISTBOX_Destroy( descr );
2964 case WM_ENABLE:
2965 InvalidateRect( descr->self, NULL, TRUE );
2966 return 0;
2968 case WM_SETREDRAW:
2969 LISTBOX_SetRedraw( descr, wParam != 0 );
2970 return 0;
2972 case WM_GETDLGCODE:
2973 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2975 case WM_PRINTCLIENT:
2976 case WM_PAINT:
2978 PAINTSTRUCT ps;
2979 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2980 ret = LISTBOX_Paint( descr, hdc );
2981 if( !wParam ) EndPaint( descr->self, &ps );
2983 return ret;
2984 case WM_SIZE:
2985 LISTBOX_UpdateSize( descr );
2986 return 0;
2987 case WM_GETFONT:
2988 return (LRESULT)descr->font;
2989 case WM_SETFONT:
2990 LISTBOX_SetFont( descr, (HFONT)wParam );
2991 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2992 return 0;
2993 case WM_SETFOCUS:
2994 descr->in_focus = TRUE;
2995 descr->caret_on = TRUE;
2996 if (descr->focus_item != -1)
2997 LISTBOX_DrawFocusRect( descr, TRUE );
2998 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2999 return 0;
3000 case WM_KILLFOCUS:
3001 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3002 descr->in_focus = FALSE;
3003 descr->wheel_remain = 0;
3004 if ((descr->focus_item != -1) && descr->caret_on)
3005 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3006 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3007 return 0;
3008 case WM_HSCROLL:
3009 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3010 case WM_VSCROLL:
3011 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3012 case WM_MOUSEWHEEL:
3013 if (wParam & (MK_SHIFT | MK_CONTROL))
3014 return DefWindowProcW( descr->self, msg, wParam, lParam );
3015 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3016 case WM_LBUTTONDOWN:
3017 if (lphc)
3018 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3019 (INT16)LOWORD(lParam),
3020 (INT16)HIWORD(lParam) );
3021 return LISTBOX_HandleLButtonDown( descr, wParam,
3022 (INT16)LOWORD(lParam),
3023 (INT16)HIWORD(lParam) );
3024 case WM_LBUTTONDBLCLK:
3025 if (lphc)
3026 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3027 (INT16)LOWORD(lParam),
3028 (INT16)HIWORD(lParam) );
3029 if (descr->style & LBS_NOTIFY)
3030 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3031 return 0;
3032 case WM_MOUSEMOVE:
3033 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3035 BOOL captured = descr->captured;
3036 POINT mousePos;
3037 RECT clientRect;
3039 mousePos.x = (INT16)LOWORD(lParam);
3040 mousePos.y = (INT16)HIWORD(lParam);
3043 * If we are in a dropdown combobox, we simulate that
3044 * the mouse is captured to show the tracking of the item.
3046 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3047 descr->captured = TRUE;
3049 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3051 descr->captured = captured;
3053 else if (GetCapture() == descr->self)
3055 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3056 (INT16)HIWORD(lParam) );
3058 return 0;
3059 case WM_LBUTTONUP:
3060 if (lphc)
3062 POINT mousePos;
3063 RECT clientRect;
3066 * If the mouse button "up" is not in the listbox,
3067 * we make sure there is no selection by re-selecting the
3068 * item that was selected when the listbox was made visible.
3070 mousePos.x = (INT16)LOWORD(lParam);
3071 mousePos.y = (INT16)HIWORD(lParam);
3073 GetClientRect(descr->self, &clientRect);
3076 * When the user clicks outside the combobox and the focus
3077 * is lost, the owning combobox will send a fake buttonup with
3078 * 0xFFFFFFF as the mouse location, we must also revert the
3079 * selection to the original selection.
3081 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3082 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3084 return LISTBOX_HandleLButtonUp( descr );
3085 case WM_KEYDOWN:
3086 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3088 /* for some reason Windows makes it possible to
3089 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3091 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3092 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3093 && (wParam == VK_DOWN || wParam == VK_UP)) )
3095 COMBO_FlipListbox( lphc, FALSE, FALSE );
3096 return 0;
3099 return LISTBOX_HandleKeyDown( descr, wParam );
3100 case WM_CHAR:
3102 WCHAR charW;
3103 if(unicode)
3104 charW = (WCHAR)wParam;
3105 else
3107 CHAR charA = (CHAR)wParam;
3108 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3110 return LISTBOX_HandleChar( descr, charW );
3112 case WM_SYSTIMER:
3113 return LISTBOX_HandleSystemTimer( descr );
3114 case WM_ERASEBKGND:
3115 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3117 RECT rect;
3118 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3119 wParam, (LPARAM)descr->self );
3120 TRACE("hbrush = %p\n", hbrush);
3121 if(!hbrush)
3122 hbrush = GetSysColorBrush(COLOR_WINDOW);
3123 if(hbrush)
3125 GetClientRect(descr->self, &rect);
3126 FillRect((HDC)wParam, &rect, hbrush);
3129 return 1;
3130 case WM_DROPFILES:
3131 if( lphc ) return 0;
3132 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3133 SendMessageA( descr->owner, msg, wParam, lParam );
3135 case WM_NCDESTROY:
3136 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3137 lphc->hWndLBox = 0;
3138 break;
3140 case WM_NCACTIVATE:
3141 if (lphc) return 0;
3142 break;
3144 default:
3145 if ((msg >= WM_USER) && (msg < 0xc000))
3146 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3147 hwnd, msg, wParam, lParam );
3150 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3151 DefWindowProcA( hwnd, msg, wParam, lParam );
3154 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3156 TRACE("%p\n", hwnd);
3157 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);