user32: the popup menu class (32768) does not have a cursor specified
[wine/kumbayo.git] / dlls / user32 / listbox.c
blobd819684c2cb9a31c963eef9ff7254feb31af1b89
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 * - GetListBoxInfo()
31 * - LB_GETLISTBOXINFO
32 * - LBS_NODATA
35 #include <string.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include "windef.h"
40 #include "winbase.h"
41 #include "wingdi.h"
42 #include "wine/winuser16.h"
43 #include "wine/unicode.h"
44 #include "user_private.h"
45 #include "controls.h"
46 #include "wine/debug.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
50 /* Items array granularity */
51 #define LB_ARRAY_GRANULARITY 16
53 /* Scrolling timeout in ms */
54 #define LB_SCROLL_TIMEOUT 50
56 /* Listbox system timer id */
57 #define LB_TIMER_ID 2
59 /* flag listbox changed while setredraw false - internal style */
60 #define LBS_DISPLAYCHANGED 0x80000000
62 /* Item structure */
63 typedef struct
65 LPWSTR str; /* Item text */
66 BOOL selected; /* Is item selected? */
67 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
68 ULONG_PTR data; /* User data */
69 } LB_ITEMDATA;
71 /* Listbox structure */
72 typedef struct
74 HWND self; /* Our own window handle */
75 HWND owner; /* Owner window to send notifications to */
76 UINT style; /* Window style */
77 INT width; /* Window width */
78 INT height; /* Window height */
79 LB_ITEMDATA *items; /* Array of items */
80 INT nb_items; /* Number of items */
81 INT top_item; /* Top visible item */
82 INT selected_item; /* Selected item */
83 INT focus_item; /* Item that has the focus */
84 INT anchor_item; /* Anchor item for extended selection */
85 INT item_height; /* Default item height */
86 INT page_size; /* Items per listbox page */
87 INT column_width; /* Column width for multi-column listboxes */
88 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
89 INT horz_pos; /* Horizontal position */
90 INT nb_tabs; /* Number of tabs in array */
91 INT *tabs; /* Array of tabs */
92 INT avg_char_width; /* Average width of characters */
93 BOOL caret_on; /* Is caret on? */
94 BOOL captured; /* Is mouse captured? */
95 BOOL in_focus;
96 HFONT font; /* Current font */
97 LCID locale; /* Current locale for string comparisons */
98 LPHEADCOMBO lphc; /* ComboLBox */
99 } LB_DESCR;
102 #define IS_OWNERDRAW(descr) \
103 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
105 #define HAS_STRINGS(descr) \
106 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
109 #define IS_MULTISELECT(descr) \
110 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
111 !((descr)->style & LBS_NOSEL))
113 #define SEND_NOTIFICATION(descr,code) \
114 (SendMessageW( (descr)->owner, WM_COMMAND, \
115 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
117 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
119 /* Current timer status */
120 typedef enum
122 LB_TIMER_NONE,
123 LB_TIMER_UP,
124 LB_TIMER_LEFT,
125 LB_TIMER_DOWN,
126 LB_TIMER_RIGHT
127 } TIMER_DIRECTION;
129 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
131 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
132 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
134 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
136 /*********************************************************************
137 * listbox class descriptor
139 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
140 const struct builtin_class_descr LISTBOX_builtin_class =
142 listboxW, /* name */
143 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
144 ListBoxWndProcA, /* procA */
145 ListBoxWndProcW, /* procW */
146 sizeof(LB_DESCR *), /* extra */
147 IDC_ARROW, /* cursor */
148 0 /* brush */
152 /*********************************************************************
153 * combolbox class descriptor
155 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
156 const struct builtin_class_descr COMBOLBOX_builtin_class =
158 combolboxW, /* name */
159 CS_DBLCLKS | CS_SAVEBITS, /* style */
160 ListBoxWndProcA, /* procA */
161 ListBoxWndProcW, /* procW */
162 sizeof(LB_DESCR *), /* extra */
163 IDC_ARROW, /* cursor */
164 0 /* brush */
168 /* check whether app is a Win 3.1 app */
169 static inline BOOL is_old_app( LB_DESCR *descr )
171 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
175 /***********************************************************************
176 * LISTBOX_GetCurrentPageSize
178 * Return the current page size
180 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
182 INT i, height;
183 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
184 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
186 if ((height += descr->items[i].height) > descr->height) break;
188 if (i == descr->top_item) return 1;
189 else return i - descr->top_item;
193 /***********************************************************************
194 * LISTBOX_GetMaxTopIndex
196 * Return the maximum possible index for the top of the listbox.
198 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
200 INT max, page;
202 if (descr->style & LBS_OWNERDRAWVARIABLE)
204 page = descr->height;
205 for (max = descr->nb_items - 1; max >= 0; max--)
206 if ((page -= descr->items[max].height) < 0) break;
207 if (max < descr->nb_items - 1) max++;
209 else if (descr->style & LBS_MULTICOLUMN)
211 if ((page = descr->width / descr->column_width) < 1) page = 1;
212 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
213 max = (max - page) * descr->page_size;
215 else
217 max = descr->nb_items - descr->page_size;
219 if (max < 0) max = 0;
220 return max;
224 /***********************************************************************
225 * LISTBOX_UpdateScroll
227 * Update the scrollbars. Should be called whenever the content
228 * of the listbox changes.
230 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
232 SCROLLINFO info;
234 /* Check the listbox scroll bar flags individually before we call
235 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
236 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
237 scroll bar when we do not need one.
238 if (!(descr->style & WS_VSCROLL)) return;
241 /* It is important that we check descr->style, and not wnd->dwStyle,
242 for WS_VSCROLL, as the former is exactly the one passed in
243 argument to CreateWindow.
244 In Windows (and from now on in Wine :) a listbox created
245 with such a style (no WS_SCROLL) does not update
246 the scrollbar with listbox-related data, thus letting
247 the programmer use it for his/her own purposes. */
249 if (descr->style & LBS_NOREDRAW) return;
250 info.cbSize = sizeof(info);
252 if (descr->style & LBS_MULTICOLUMN)
254 info.nMin = 0;
255 info.nMax = (descr->nb_items - 1) / descr->page_size;
256 info.nPos = descr->top_item / descr->page_size;
257 info.nPage = descr->width / descr->column_width;
258 if (info.nPage < 1) info.nPage = 1;
259 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
260 if (descr->style & LBS_DISABLENOSCROLL)
261 info.fMask |= SIF_DISABLENOSCROLL;
262 if (descr->style & WS_HSCROLL)
263 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
264 info.nMax = 0;
265 info.fMask = SIF_RANGE;
266 if (descr->style & WS_VSCROLL)
267 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
269 else
271 info.nMin = 0;
272 info.nMax = descr->nb_items - 1;
273 info.nPos = descr->top_item;
274 info.nPage = LISTBOX_GetCurrentPageSize( descr );
275 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
276 if (descr->style & LBS_DISABLENOSCROLL)
277 info.fMask |= SIF_DISABLENOSCROLL;
278 if (descr->style & WS_VSCROLL)
279 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
281 if (descr->horz_extent)
283 info.nMin = 0;
284 info.nMax = descr->horz_extent - 1;
285 info.nPos = descr->horz_pos;
286 info.nPage = descr->width;
287 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
288 if (descr->style & LBS_DISABLENOSCROLL)
289 info.fMask |= SIF_DISABLENOSCROLL;
290 if (descr->style & WS_HSCROLL)
291 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
297 /***********************************************************************
298 * LISTBOX_SetTopItem
300 * Set the top item of the listbox, scrolling up or down if necessary.
302 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
304 INT max = LISTBOX_GetMaxTopIndex( descr );
306 TRACE("setting top item %d, scroll %d\n", index, scroll);
308 if (index > max) index = max;
309 if (index < 0) index = 0;
310 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
311 if (descr->top_item == index) return LB_OKAY;
312 if (descr->style & LBS_MULTICOLUMN)
314 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
315 if (scroll && (abs(diff) < descr->width))
316 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
317 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
319 else
320 scroll = FALSE;
322 else if (scroll)
324 INT diff;
325 if (descr->style & LBS_OWNERDRAWVARIABLE)
327 INT i;
328 diff = 0;
329 if (index > descr->top_item)
331 for (i = index - 1; i >= descr->top_item; i--)
332 diff -= descr->items[i].height;
334 else
336 for (i = index; i < descr->top_item; i++)
337 diff += descr->items[i].height;
340 else
341 diff = (descr->top_item - index) * descr->item_height;
343 if (abs(diff) < descr->height)
344 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
345 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
346 else
347 scroll = FALSE;
349 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
350 descr->top_item = index;
351 LISTBOX_UpdateScroll( descr );
352 return LB_OKAY;
356 /***********************************************************************
357 * LISTBOX_UpdatePage
359 * Update the page size. Should be called when the size of
360 * the client area or the item height changes.
362 static void LISTBOX_UpdatePage( LB_DESCR *descr )
364 INT page_size;
366 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
367 page_size = 1;
368 if (page_size == descr->page_size) return;
369 descr->page_size = page_size;
370 if (descr->style & LBS_MULTICOLUMN)
371 InvalidateRect( descr->self, NULL, TRUE );
372 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
376 /***********************************************************************
377 * LISTBOX_UpdateSize
379 * Update the size of the listbox. Should be called when the size of
380 * the client area changes.
382 static void LISTBOX_UpdateSize( LB_DESCR *descr )
384 RECT rect;
386 GetClientRect( descr->self, &rect );
387 descr->width = rect.right - rect.left;
388 descr->height = rect.bottom - rect.top;
389 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
391 INT remaining;
392 RECT rect;
394 GetWindowRect( descr->self, &rect );
395 if(descr->item_height != 0)
396 remaining = descr->height % descr->item_height;
397 else
398 remaining = 0;
399 if ((descr->height > descr->item_height) && remaining)
401 if (is_old_app(descr))
402 { /* give a margin for error to 16 bits programs - if we need
403 less than the height of the nonclient area, round to the
404 *next* number of items */
405 int ncheight = rect.bottom - rect.top - descr->height;
406 if ((descr->item_height - remaining) <= ncheight)
407 remaining = remaining - descr->item_height;
409 TRACE("[%p]: changing height %d -> %d\n",
410 descr->self, descr->height, descr->height - remaining );
411 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
412 rect.bottom - rect.top - remaining,
413 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
414 return;
417 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
418 LISTBOX_UpdatePage( descr );
419 LISTBOX_UpdateScroll( descr );
421 /* Invalidate the focused item so it will be repainted correctly */
422 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
424 InvalidateRect( descr->self, &rect, FALSE );
429 /***********************************************************************
430 * LISTBOX_GetItemRect
432 * Get the rectangle enclosing an item, in listbox client coordinates.
433 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
435 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
437 /* Index <= 0 is legal even on empty listboxes */
438 if (index && (index >= descr->nb_items))
440 memset(rect, 0, sizeof(*rect));
441 SetLastError(ERROR_INVALID_INDEX);
442 return LB_ERR;
444 SetRect( rect, 0, 0, descr->width, descr->height );
445 if (descr->style & LBS_MULTICOLUMN)
447 INT col = (index / descr->page_size) -
448 (descr->top_item / descr->page_size);
449 rect->left += col * descr->column_width;
450 rect->right = rect->left + descr->column_width;
451 rect->top += (index % descr->page_size) * descr->item_height;
452 rect->bottom = rect->top + descr->item_height;
454 else if (descr->style & LBS_OWNERDRAWVARIABLE)
456 INT i;
457 rect->right += descr->horz_pos;
458 if ((index >= 0) && (index < descr->nb_items))
460 if (index < descr->top_item)
462 for (i = descr->top_item-1; i >= index; i--)
463 rect->top -= descr->items[i].height;
465 else
467 for (i = descr->top_item; i < index; i++)
468 rect->top += descr->items[i].height;
470 rect->bottom = rect->top + descr->items[index].height;
474 else
476 rect->top += (index - descr->top_item) * descr->item_height;
477 rect->bottom = rect->top + descr->item_height;
478 rect->right += descr->horz_pos;
481 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
483 return ((rect->left < descr->width) && (rect->right > 0) &&
484 (rect->top < descr->height) && (rect->bottom > 0));
488 /***********************************************************************
489 * LISTBOX_GetItemFromPoint
491 * Return the item nearest from point (x,y) (in client coordinates).
493 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
495 INT index = descr->top_item;
497 if (!descr->nb_items) return -1; /* No items */
498 if (descr->style & LBS_OWNERDRAWVARIABLE)
500 INT pos = 0;
501 if (y >= 0)
503 while (index < descr->nb_items)
505 if ((pos += descr->items[index].height) > y) break;
506 index++;
509 else
511 while (index > 0)
513 index--;
514 if ((pos -= descr->items[index].height) <= y) break;
518 else if (descr->style & LBS_MULTICOLUMN)
520 if (y >= descr->item_height * descr->page_size) return -1;
521 if (y >= 0) index += y / descr->item_height;
522 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
523 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
525 else
527 index += (y / descr->item_height);
529 if (index < 0) return 0;
530 if (index >= descr->nb_items) return -1;
531 return index;
535 /***********************************************************************
536 * LISTBOX_PaintItem
538 * Paint an item.
540 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
541 INT index, UINT action, BOOL ignoreFocus )
543 LB_ITEMDATA *item = NULL;
544 if (index < descr->nb_items) item = &descr->items[index];
546 if (IS_OWNERDRAW(descr))
548 DRAWITEMSTRUCT dis;
549 RECT r;
550 HRGN hrgn;
552 if (!item)
554 if (action == ODA_FOCUS)
555 DrawFocusRect( hdc, rect );
556 else
557 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
558 return;
561 /* some programs mess with the clipping region when
562 drawing the item, *and* restore the previous region
563 after they are done, so a region has better to exist
564 else everything ends clipped */
565 GetClientRect(descr->self, &r);
566 hrgn = CreateRectRgnIndirect(&r);
567 SelectClipRgn( hdc, hrgn);
568 DeleteObject( hrgn );
570 dis.CtlType = ODT_LISTBOX;
571 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
572 dis.hwndItem = descr->self;
573 dis.itemAction = action;
574 dis.hDC = hdc;
575 dis.itemID = index;
576 dis.itemState = 0;
577 if (item->selected) dis.itemState |= ODS_SELECTED;
578 if (!ignoreFocus && (descr->focus_item == index) &&
579 (descr->caret_on) &&
580 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
581 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
582 dis.itemData = item->data;
583 dis.rcItem = *rect;
584 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%d,%d-%d,%d\n",
585 descr->self, index, item ? debugstr_w(item->str) : "", action,
586 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
587 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
589 else
591 COLORREF oldText = 0, oldBk = 0;
593 if (action == ODA_FOCUS)
595 DrawFocusRect( hdc, rect );
596 return;
598 if (item && item->selected)
600 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
601 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
604 TRACE("[%p]: painting %d (%s) action=%02x rect=%d,%d-%d,%d\n",
605 descr->self, index, item ? debugstr_w(item->str) : "", action,
606 rect->left, rect->top, rect->right, rect->bottom );
607 if (!item)
608 ExtTextOutW( hdc, rect->left + 1, rect->top,
609 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
610 else if (!(descr->style & LBS_USETABSTOPS))
611 ExtTextOutW( hdc, rect->left + 1, rect->top,
612 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
613 strlenW(item->str), NULL );
614 else
616 /* Output empty string to paint background in the full width. */
617 ExtTextOutW( hdc, rect->left + 1, rect->top,
618 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
619 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
620 item->str, strlenW(item->str),
621 descr->nb_tabs, descr->tabs, 0);
623 if (item && item->selected)
625 SetBkColor( hdc, oldBk );
626 SetTextColor( hdc, oldText );
628 if (!ignoreFocus && (descr->focus_item == index) &&
629 (descr->caret_on) &&
630 (descr->in_focus)) DrawFocusRect( hdc, rect );
635 /***********************************************************************
636 * LISTBOX_SetRedraw
638 * Change the redraw flag.
640 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
642 if (on)
644 if (!(descr->style & LBS_NOREDRAW)) return;
645 descr->style &= ~LBS_NOREDRAW;
646 if (descr->style & LBS_DISPLAYCHANGED)
647 { /* page was changed while setredraw false, refresh automatically */
648 InvalidateRect(descr->self, NULL, TRUE);
649 if ((descr->top_item + descr->page_size) > descr->nb_items)
650 { /* reset top of page if less than number of items/page */
651 descr->top_item = descr->nb_items - descr->page_size;
652 if (descr->top_item < 0) descr->top_item = 0;
654 descr->style &= ~LBS_DISPLAYCHANGED;
656 LISTBOX_UpdateScroll( descr );
658 else descr->style |= LBS_NOREDRAW;
662 /***********************************************************************
663 * LISTBOX_RepaintItem
665 * Repaint a single item synchronously.
667 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
669 HDC hdc;
670 RECT rect;
671 HFONT oldFont = 0;
672 HBRUSH hbrush, oldBrush = 0;
674 /* Do not repaint the item if the item is not visible */
675 if (!IsWindowVisible(descr->self)) return;
676 if (descr->style & LBS_NOREDRAW)
678 descr->style |= LBS_DISPLAYCHANGED;
679 return;
681 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
682 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
683 if (descr->font) oldFont = SelectObject( hdc, descr->font );
684 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
685 (WPARAM)hdc, (LPARAM)descr->self );
686 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
687 if (!IsWindowEnabled(descr->self))
688 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
689 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
690 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
691 if (oldFont) SelectObject( hdc, oldFont );
692 if (oldBrush) SelectObject( hdc, oldBrush );
693 ReleaseDC( descr->self, hdc );
697 /***********************************************************************
698 * LISTBOX_DrawFocusRect
700 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
702 HDC hdc;
703 RECT rect;
704 HFONT oldFont = 0;
706 /* Do not repaint the item if the item is not visible */
707 if (!IsWindowVisible(descr->self)) return;
709 if (descr->focus_item == -1) return;
710 if (!descr->caret_on || !descr->in_focus) return;
712 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
713 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
714 if (descr->font) oldFont = SelectObject( hdc, descr->font );
715 if (!IsWindowEnabled(descr->self))
716 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
717 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
718 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, on ? FALSE : TRUE );
719 if (oldFont) SelectObject( hdc, oldFont );
720 ReleaseDC( descr->self, hdc );
724 /***********************************************************************
725 * LISTBOX_InitStorage
727 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
729 LB_ITEMDATA *item;
731 nb_items += LB_ARRAY_GRANULARITY - 1;
732 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
733 if (descr->items) {
734 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
735 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
736 nb_items * sizeof(LB_ITEMDATA));
738 else {
739 item = HeapAlloc( GetProcessHeap(), 0,
740 nb_items * sizeof(LB_ITEMDATA));
743 if (!item)
745 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
746 return LB_ERRSPACE;
748 descr->items = item;
749 return LB_OKAY;
753 /***********************************************************************
754 * LISTBOX_SetTabStops
756 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
758 INT i;
760 if (!(descr->style & LBS_USETABSTOPS))
762 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
763 return FALSE;
766 HeapFree( GetProcessHeap(), 0, descr->tabs );
767 if (!(descr->nb_tabs = count))
769 descr->tabs = NULL;
770 return TRUE;
772 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
773 descr->nb_tabs * sizeof(INT) )))
774 return FALSE;
775 if (short_ints)
777 INT i;
778 LPINT16 p = (LPINT16)tabs;
780 TRACE("[%p]: settabstops ", descr->self );
781 for (i = 0; i < descr->nb_tabs; i++) {
782 descr->tabs[i] = *p++<<1; /* FIXME */
783 TRACE("%hd ", descr->tabs[i]);
785 TRACE("\n");
787 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
789 /* convert into "dialog units"*/
790 for (i = 0; i < descr->nb_tabs; i++)
791 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
793 return TRUE;
797 /***********************************************************************
798 * LISTBOX_GetText
800 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
802 if ((index < 0) || (index >= descr->nb_items))
804 SetLastError(ERROR_INVALID_INDEX);
805 return LB_ERR;
807 if (HAS_STRINGS(descr))
809 if (!buffer)
811 DWORD len = strlenW(descr->items[index].str);
812 if( unicode )
813 return len;
814 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
815 NULL, 0, NULL, NULL );
818 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
820 if(unicode)
822 strcpyW( buffer, descr->items[index].str );
823 return strlenW(buffer);
825 else
827 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
829 } else {
830 if (buffer)
831 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
832 return sizeof(DWORD);
836 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
838 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
839 if (ret == CSTR_LESS_THAN)
840 return -1;
841 if (ret == CSTR_EQUAL)
842 return 0;
843 if (ret == CSTR_GREATER_THAN)
844 return 1;
845 return -1;
848 /***********************************************************************
849 * LISTBOX_FindStringPos
851 * Find the nearest string located before a given string in sort order.
852 * If 'exact' is TRUE, return an error if we don't get an exact match.
854 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
856 INT index, min, max, res = -1;
858 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
859 min = 0;
860 max = descr->nb_items;
861 while (min != max)
863 index = (min + max) / 2;
864 if (HAS_STRINGS(descr))
865 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
866 else
868 COMPAREITEMSTRUCT cis;
869 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
871 cis.CtlType = ODT_LISTBOX;
872 cis.CtlID = id;
873 cis.hwndItem = descr->self;
874 /* note that some application (MetaStock) expects the second item
875 * to be in the listbox */
876 cis.itemID1 = -1;
877 cis.itemData1 = (ULONG_PTR)str;
878 cis.itemID2 = index;
879 cis.itemData2 = descr->items[index].data;
880 cis.dwLocaleId = descr->locale;
881 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
883 if (!res) return index;
884 if (res < 0) max = index;
885 else min = index + 1;
887 return exact ? -1 : max;
891 /***********************************************************************
892 * LISTBOX_FindFileStrPos
894 * Find the nearest string located before a given string in directory
895 * sort order (i.e. first files, then directories, then drives).
897 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
899 INT min, max, res = -1;
901 if (!HAS_STRINGS(descr))
902 return LISTBOX_FindStringPos( descr, str, FALSE );
903 min = 0;
904 max = descr->nb_items;
905 while (min != max)
907 INT index = (min + max) / 2;
908 LPCWSTR p = descr->items[index].str;
909 if (*p == '[') /* drive or directory */
911 if (*str != '[') res = -1;
912 else if (p[1] == '-') /* drive */
914 if (str[1] == '-') res = str[2] - p[2];
915 else res = -1;
917 else /* directory */
919 if (str[1] == '-') res = 1;
920 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
923 else /* filename */
925 if (*str == '[') res = 1;
926 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
928 if (!res) return index;
929 if (res < 0) max = index;
930 else min = index + 1;
932 return max;
936 /***********************************************************************
937 * LISTBOX_FindString
939 * Find the item beginning with a given string.
941 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
943 INT i;
944 LB_ITEMDATA *item;
946 if (start >= descr->nb_items) start = -1;
947 item = descr->items + start + 1;
948 if (HAS_STRINGS(descr))
950 if (!str || ! str[0] ) return LB_ERR;
951 if (exact)
953 for (i = start + 1; i < descr->nb_items; i++, item++)
954 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
955 for (i = 0, item = descr->items; i <= start; i++, item++)
956 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
958 else
960 /* Special case for drives and directories: ignore prefix */
961 #define CHECK_DRIVE(item) \
962 if ((item)->str[0] == '[') \
964 if (!strncmpiW( str, (item)->str+1, len )) return i; \
965 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
966 return i; \
969 INT len = strlenW(str);
970 for (i = start + 1; i < descr->nb_items; i++, item++)
972 if (!strncmpiW( str, item->str, len )) return i;
973 CHECK_DRIVE(item);
975 for (i = 0, item = descr->items; i <= start; i++, item++)
977 if (!strncmpiW( str, item->str, len )) return i;
978 CHECK_DRIVE(item);
980 #undef CHECK_DRIVE
983 else
985 if (exact && (descr->style & LBS_SORT))
986 /* If sorted, use a WM_COMPAREITEM binary search */
987 return LISTBOX_FindStringPos( descr, str, TRUE );
989 /* Otherwise use a linear search */
990 for (i = start + 1; i < descr->nb_items; i++, item++)
991 if (item->data == (ULONG_PTR)str) return i;
992 for (i = 0, item = descr->items; i <= start; i++, item++)
993 if (item->data == (ULONG_PTR)str) return i;
995 return LB_ERR;
999 /***********************************************************************
1000 * LISTBOX_GetSelCount
1002 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1004 INT i, count;
1005 const LB_ITEMDATA *item = descr->items;
1007 if (!(descr->style & LBS_MULTIPLESEL) ||
1008 (descr->style & LBS_NOSEL))
1009 return LB_ERR;
1010 for (i = count = 0; i < descr->nb_items; i++, item++)
1011 if (item->selected) count++;
1012 return count;
1016 /***********************************************************************
1017 * LISTBOX_GetSelItems16
1019 static LRESULT LISTBOX_GetSelItems16( const LB_DESCR *descr, INT16 max, LPINT16 array )
1021 INT i, count;
1022 const LB_ITEMDATA *item = descr->items;
1024 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1025 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1026 if (item->selected) array[count++] = (INT16)i;
1027 return count;
1031 /***********************************************************************
1032 * LISTBOX_GetSelItems
1034 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1036 INT i, count;
1037 const LB_ITEMDATA *item = descr->items;
1039 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1040 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1041 if (item->selected) array[count++] = i;
1042 return count;
1046 /***********************************************************************
1047 * LISTBOX_Paint
1049 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1051 INT i, col_pos = descr->page_size - 1;
1052 RECT rect;
1053 RECT focusRect = {-1, -1, -1, -1};
1054 HFONT oldFont = 0;
1055 HBRUSH hbrush, oldBrush = 0;
1057 if (descr->style & LBS_NOREDRAW) return 0;
1059 SetRect( &rect, 0, 0, descr->width, descr->height );
1060 if (descr->style & LBS_MULTICOLUMN)
1061 rect.right = rect.left + descr->column_width;
1062 else if (descr->horz_pos)
1064 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1065 rect.right += descr->horz_pos;
1068 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1069 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1070 (WPARAM)hdc, (LPARAM)descr->self );
1071 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1072 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1074 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1075 (descr->in_focus))
1077 /* Special case for empty listbox: paint focus rect */
1078 rect.bottom = rect.top + descr->item_height;
1079 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1080 &rect, NULL, 0, NULL );
1081 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1082 rect.top = rect.bottom;
1085 /* Paint all the item, regarding the selection
1086 Focus state will be painted after */
1088 for (i = descr->top_item; i < descr->nb_items; i++)
1090 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1091 rect.bottom = rect.top + descr->item_height;
1092 else
1093 rect.bottom = rect.top + descr->items[i].height;
1095 if (i == descr->focus_item)
1097 /* keep the focus rect, to paint the focus item after */
1098 focusRect.left = rect.left;
1099 focusRect.right = rect.right;
1100 focusRect.top = rect.top;
1101 focusRect.bottom = rect.bottom;
1103 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1104 rect.top = rect.bottom;
1106 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1108 if (!IS_OWNERDRAW(descr))
1110 /* Clear the bottom of the column */
1111 if (rect.top < descr->height)
1113 rect.bottom = descr->height;
1114 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1115 &rect, NULL, 0, NULL );
1119 /* Go to the next column */
1120 rect.left += descr->column_width;
1121 rect.right += descr->column_width;
1122 rect.top = 0;
1123 col_pos = descr->page_size - 1;
1125 else
1127 col_pos--;
1128 if (rect.top >= descr->height) break;
1132 /* Paint the focus item now */
1133 if (focusRect.top != focusRect.bottom &&
1134 descr->caret_on && descr->in_focus)
1135 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1137 if (!IS_OWNERDRAW(descr))
1139 /* Clear the remainder of the client area */
1140 if (rect.top < descr->height)
1142 rect.bottom = descr->height;
1143 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1144 &rect, NULL, 0, NULL );
1146 if (rect.right < descr->width)
1148 rect.left = rect.right;
1149 rect.right = descr->width;
1150 rect.top = 0;
1151 rect.bottom = descr->height;
1152 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1153 &rect, NULL, 0, NULL );
1156 if (oldFont) SelectObject( hdc, oldFont );
1157 if (oldBrush) SelectObject( hdc, oldBrush );
1158 return 0;
1162 /***********************************************************************
1163 * LISTBOX_InvalidateItems
1165 * Invalidate all items from a given item. If the specified item is not
1166 * visible, nothing happens.
1168 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1170 RECT rect;
1172 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1174 if (descr->style & LBS_NOREDRAW)
1176 descr->style |= LBS_DISPLAYCHANGED;
1177 return;
1179 rect.bottom = descr->height;
1180 InvalidateRect( descr->self, &rect, TRUE );
1181 if (descr->style & LBS_MULTICOLUMN)
1183 /* Repaint the other columns */
1184 rect.left = rect.right;
1185 rect.right = descr->width;
1186 rect.top = 0;
1187 InvalidateRect( descr->self, &rect, TRUE );
1192 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1194 RECT rect;
1196 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1197 InvalidateRect( descr->self, &rect, TRUE );
1200 /***********************************************************************
1201 * LISTBOX_GetItemHeight
1203 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1205 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1207 if ((index < 0) || (index >= descr->nb_items))
1209 SetLastError(ERROR_INVALID_INDEX);
1210 return LB_ERR;
1212 return descr->items[index].height;
1214 else return descr->item_height;
1218 /***********************************************************************
1219 * LISTBOX_SetItemHeight
1221 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1223 if (height > MAXBYTE)
1224 return -1;
1226 if (!height) height = 1;
1228 if (descr->style & LBS_OWNERDRAWVARIABLE)
1230 if ((index < 0) || (index >= descr->nb_items))
1232 SetLastError(ERROR_INVALID_INDEX);
1233 return LB_ERR;
1235 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1236 descr->items[index].height = height;
1237 LISTBOX_UpdateScroll( descr );
1238 if (repaint)
1239 LISTBOX_InvalidateItems( descr, index );
1241 else if (height != descr->item_height)
1243 TRACE("[%p]: new height = %d\n", descr->self, height );
1244 descr->item_height = height;
1245 LISTBOX_UpdatePage( descr );
1246 LISTBOX_UpdateScroll( descr );
1247 if (repaint)
1248 InvalidateRect( descr->self, 0, TRUE );
1250 return LB_OKAY;
1254 /***********************************************************************
1255 * LISTBOX_SetHorizontalPos
1257 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1259 INT diff;
1261 if (pos > descr->horz_extent - descr->width)
1262 pos = descr->horz_extent - descr->width;
1263 if (pos < 0) pos = 0;
1264 if (!(diff = descr->horz_pos - pos)) return;
1265 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1266 descr->horz_pos = pos;
1267 LISTBOX_UpdateScroll( descr );
1268 if (abs(diff) < descr->width)
1270 RECT rect;
1271 /* Invalidate the focused item so it will be repainted correctly */
1272 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1273 InvalidateRect( descr->self, &rect, TRUE );
1274 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1275 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1277 else
1278 InvalidateRect( descr->self, NULL, TRUE );
1282 /***********************************************************************
1283 * LISTBOX_SetHorizontalExtent
1285 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1287 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1288 return LB_OKAY;
1289 if (extent <= 0) extent = 1;
1290 if (extent == descr->horz_extent) return LB_OKAY;
1291 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1292 descr->horz_extent = extent;
1293 if (descr->horz_pos > extent - descr->width)
1294 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1295 else
1296 LISTBOX_UpdateScroll( descr );
1297 return LB_OKAY;
1301 /***********************************************************************
1302 * LISTBOX_SetColumnWidth
1304 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1306 if (width == descr->column_width) return LB_OKAY;
1307 TRACE("[%p]: new column width = %d\n", descr->self, width );
1308 descr->column_width = width;
1309 LISTBOX_UpdatePage( descr );
1310 return LB_OKAY;
1314 /***********************************************************************
1315 * LISTBOX_SetFont
1317 * Returns the item height.
1319 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1321 HDC hdc;
1322 HFONT oldFont = 0;
1323 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1324 SIZE sz;
1326 descr->font = font;
1328 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1330 ERR("unable to get DC.\n" );
1331 return 16;
1333 if (font) oldFont = SelectObject( hdc, font );
1334 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1335 if (oldFont) SelectObject( hdc, oldFont );
1336 ReleaseDC( descr->self, hdc );
1338 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1339 if (!IS_OWNERDRAW(descr))
1340 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1341 return sz.cy;
1345 /***********************************************************************
1346 * LISTBOX_MakeItemVisible
1348 * Make sure that a given item is partially or fully visible.
1350 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1352 INT top;
1354 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1356 if (index <= descr->top_item) top = index;
1357 else if (descr->style & LBS_MULTICOLUMN)
1359 INT cols = descr->width;
1360 if (!fully) cols += descr->column_width - 1;
1361 if (cols >= descr->column_width) cols /= descr->column_width;
1362 else cols = 1;
1363 if (index < descr->top_item + (descr->page_size * cols)) return;
1364 top = index - descr->page_size * (cols - 1);
1366 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1368 INT height = fully ? descr->items[index].height : 1;
1369 for (top = index; top > descr->top_item; top--)
1370 if ((height += descr->items[top-1].height) > descr->height) break;
1372 else
1374 if (index < descr->top_item + descr->page_size) return;
1375 if (!fully && (index == descr->top_item + descr->page_size) &&
1376 (descr->height > (descr->page_size * descr->item_height))) return;
1377 top = index - descr->page_size + 1;
1379 LISTBOX_SetTopItem( descr, top, TRUE );
1382 /***********************************************************************
1383 * LISTBOX_SetCaretIndex
1385 * NOTES
1386 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1389 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1391 INT oldfocus = descr->focus_item;
1393 TRACE("old focus %d, index %d\n", oldfocus, index);
1395 if (descr->style & LBS_NOSEL) return LB_ERR;
1396 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1397 if (index == oldfocus) return LB_OKAY;
1399 LISTBOX_DrawFocusRect( descr, FALSE );
1400 descr->focus_item = index;
1402 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1403 LISTBOX_DrawFocusRect( descr, TRUE );
1405 return LB_OKAY;
1409 /***********************************************************************
1410 * LISTBOX_SelectItemRange
1412 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1414 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1415 INT last, BOOL on )
1417 INT i;
1419 /* A few sanity checks */
1421 if (descr->style & LBS_NOSEL) return LB_ERR;
1422 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1424 if (!descr->nb_items) return LB_OKAY;
1426 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1427 if (first < 0) first = 0;
1428 if (last < first) return LB_OKAY;
1430 if (on) /* Turn selection on */
1432 for (i = first; i <= last; i++)
1434 if (descr->items[i].selected) continue;
1435 descr->items[i].selected = TRUE;
1436 LISTBOX_InvalidateItemRect(descr, i);
1439 else /* Turn selection off */
1441 for (i = first; i <= last; i++)
1443 if (!descr->items[i].selected) continue;
1444 descr->items[i].selected = FALSE;
1445 LISTBOX_InvalidateItemRect(descr, i);
1448 return LB_OKAY;
1451 /***********************************************************************
1452 * LISTBOX_SetSelection
1454 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1455 BOOL on, BOOL send_notify )
1457 TRACE( "cur_sel=%d index=%d notify=%s\n",
1458 descr->selected_item, index, send_notify ? "YES" : "NO" );
1460 if (descr->style & LBS_NOSEL)
1462 descr->selected_item = index;
1463 return LB_ERR;
1465 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1466 if (descr->style & LBS_MULTIPLESEL)
1468 if (index == -1) /* Select all items */
1469 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1470 else /* Only one item */
1471 return LISTBOX_SelectItemRange( descr, index, index, on );
1473 else
1475 INT oldsel = descr->selected_item;
1476 if (index == oldsel) return LB_OKAY;
1477 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1478 if (index != -1) descr->items[index].selected = TRUE;
1479 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1480 descr->selected_item = index;
1481 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1482 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1483 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1484 else
1485 if( descr->lphc ) /* set selection change flag for parent combo */
1486 descr->lphc->wState |= CBF_SELCHANGE;
1488 return LB_OKAY;
1492 /***********************************************************************
1493 * LISTBOX_MoveCaret
1495 * Change the caret position and extend the selection to the new caret.
1497 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1499 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1501 if ((index < 0) || (index >= descr->nb_items))
1502 return;
1504 /* Important, repaint needs to be done in this order if
1505 you want to mimic Windows behavior:
1506 1. Remove the focus and paint the item
1507 2. Remove the selection and paint the item(s)
1508 3. Set the selection and repaint the item(s)
1509 4. Set the focus to 'index' and repaint the item */
1511 /* 1. remove the focus and repaint the item */
1512 LISTBOX_DrawFocusRect( descr, FALSE );
1514 /* 2. then turn off the previous selection */
1515 /* 3. repaint the new selected item */
1516 if (descr->style & LBS_EXTENDEDSEL)
1518 if (descr->anchor_item != -1)
1520 INT first = min( index, descr->anchor_item );
1521 INT last = max( index, descr->anchor_item );
1522 if (first > 0)
1523 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1524 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1525 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1528 else if (!(descr->style & LBS_MULTIPLESEL))
1530 /* Set selection to new caret item */
1531 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1534 /* 4. repaint the new item with the focus */
1535 descr->focus_item = index;
1536 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1537 LISTBOX_DrawFocusRect( descr, TRUE );
1541 /***********************************************************************
1542 * LISTBOX_InsertItem
1544 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1545 LPWSTR str, ULONG_PTR data )
1547 LB_ITEMDATA *item;
1548 INT max_items;
1549 INT oldfocus = descr->focus_item;
1551 if (index == -1) index = descr->nb_items;
1552 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1553 if (!descr->items) max_items = 0;
1554 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1555 if (descr->nb_items == max_items)
1557 /* We need to grow the array */
1558 max_items += LB_ARRAY_GRANULARITY;
1559 if (descr->items)
1560 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1561 max_items * sizeof(LB_ITEMDATA) );
1562 else
1563 item = HeapAlloc( GetProcessHeap(), 0,
1564 max_items * sizeof(LB_ITEMDATA) );
1565 if (!item)
1567 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1568 return LB_ERRSPACE;
1570 descr->items = item;
1573 /* Insert the item structure */
1575 item = &descr->items[index];
1576 if (index < descr->nb_items)
1577 RtlMoveMemory( item + 1, item,
1578 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1579 item->str = str;
1580 item->data = data;
1581 item->height = 0;
1582 item->selected = FALSE;
1583 descr->nb_items++;
1585 /* Get item height */
1587 if (descr->style & LBS_OWNERDRAWVARIABLE)
1589 MEASUREITEMSTRUCT mis;
1590 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1592 mis.CtlType = ODT_LISTBOX;
1593 mis.CtlID = id;
1594 mis.itemID = index;
1595 mis.itemData = descr->items[index].data;
1596 mis.itemHeight = descr->item_height;
1597 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1598 item->height = mis.itemHeight ? mis.itemHeight : 1;
1599 TRACE("[%p]: measure item %d (%s) = %d\n",
1600 descr->self, index, str ? debugstr_w(str) : "", item->height );
1603 /* Repaint the items */
1605 LISTBOX_UpdateScroll( descr );
1606 LISTBOX_InvalidateItems( descr, index );
1608 /* Move selection and focused item */
1609 /* If listbox was empty, set focus to the first item */
1610 if (descr->nb_items == 1)
1611 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1612 /* single select don't change selection index in win31 */
1613 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1615 descr->selected_item++;
1616 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1618 else
1620 if (index <= descr->selected_item)
1622 descr->selected_item++;
1623 descr->focus_item = oldfocus; /* focus not changed */
1626 return LB_OKAY;
1630 /***********************************************************************
1631 * LISTBOX_InsertString
1633 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1635 LPWSTR new_str = NULL;
1636 ULONG_PTR data = 0;
1637 LRESULT ret;
1639 if (HAS_STRINGS(descr))
1641 static const WCHAR empty_stringW[] = { 0 };
1642 if (!str) str = empty_stringW;
1643 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1645 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1646 return LB_ERRSPACE;
1648 strcpyW(new_str, str);
1650 else data = (ULONG_PTR)str;
1652 if (index == -1) index = descr->nb_items;
1653 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1655 HeapFree( GetProcessHeap(), 0, new_str );
1656 return ret;
1659 TRACE("[%p]: added item %d %s\n",
1660 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1661 return index;
1665 /***********************************************************************
1666 * LISTBOX_DeleteItem
1668 * Delete the content of an item. 'index' must be a valid index.
1670 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1672 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1673 * while Win95 sends it for all items with user data.
1674 * It's probably better to send it too often than not
1675 * often enough, so this is what we do here.
1677 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1679 DELETEITEMSTRUCT dis;
1680 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1682 dis.CtlType = ODT_LISTBOX;
1683 dis.CtlID = id;
1684 dis.itemID = index;
1685 dis.hwndItem = descr->self;
1686 dis.itemData = descr->items[index].data;
1687 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1689 if (HAS_STRINGS(descr))
1690 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1694 /***********************************************************************
1695 * LISTBOX_RemoveItem
1697 * Remove an item from the listbox and delete its content.
1699 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1701 LB_ITEMDATA *item;
1702 INT max_items;
1704 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1706 /* We need to invalidate the original rect instead of the updated one. */
1707 LISTBOX_InvalidateItems( descr, index );
1709 LISTBOX_DeleteItem( descr, index );
1711 /* Remove the item */
1713 item = &descr->items[index];
1714 if (index < descr->nb_items-1)
1715 RtlMoveMemory( item, item + 1,
1716 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1717 descr->nb_items--;
1718 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1720 /* Shrink the item array if possible */
1722 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1723 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1725 max_items -= LB_ARRAY_GRANULARITY;
1726 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1727 max_items * sizeof(LB_ITEMDATA) );
1728 if (item) descr->items = item;
1730 /* Repaint the items */
1732 LISTBOX_UpdateScroll( descr );
1733 /* if we removed the scrollbar, reset the top of the list
1734 (correct for owner-drawn ???) */
1735 if (descr->nb_items == descr->page_size)
1736 LISTBOX_SetTopItem( descr, 0, TRUE );
1738 /* Move selection and focused item */
1739 if (!IS_MULTISELECT(descr))
1741 if (index == descr->selected_item)
1742 descr->selected_item = -1;
1743 else if (index < descr->selected_item)
1745 descr->selected_item--;
1746 if (ISWIN31) /* win 31 do not change the selected item number */
1747 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1751 if (descr->focus_item >= descr->nb_items)
1753 descr->focus_item = descr->nb_items - 1;
1754 if (descr->focus_item < 0) descr->focus_item = 0;
1756 return LB_OKAY;
1760 /***********************************************************************
1761 * LISTBOX_ResetContent
1763 static void LISTBOX_ResetContent( LB_DESCR *descr )
1765 INT i;
1767 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1768 HeapFree( GetProcessHeap(), 0, descr->items );
1769 descr->nb_items = 0;
1770 descr->top_item = 0;
1771 descr->selected_item = -1;
1772 descr->focus_item = 0;
1773 descr->anchor_item = -1;
1774 descr->items = NULL;
1778 /***********************************************************************
1779 * LISTBOX_SetCount
1781 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1783 LRESULT ret;
1785 if (HAS_STRINGS(descr))
1787 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1788 return LB_ERR;
1791 /* FIXME: this is far from optimal... */
1792 if (count > descr->nb_items)
1794 while (count > descr->nb_items)
1795 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1796 return ret;
1798 else if (count < descr->nb_items)
1800 while (count < descr->nb_items)
1801 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1802 return ret;
1804 return LB_OKAY;
1808 /***********************************************************************
1809 * LISTBOX_Directory
1811 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1812 LPCWSTR filespec, BOOL long_names )
1814 HANDLE handle;
1815 LRESULT ret = LB_OKAY;
1816 WIN32_FIND_DATAW entry;
1817 int pos;
1818 LRESULT maxinsert = LB_ERR;
1820 /* don't scan directory if we just want drives exclusively */
1821 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1822 /* scan directory */
1823 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1825 int le = GetLastError();
1826 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1828 else
1832 WCHAR buffer[270];
1833 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1835 static const WCHAR bracketW[] = { ']',0 };
1836 static const WCHAR dotW[] = { '.',0 };
1837 if (!(attrib & DDL_DIRECTORY) ||
1838 !strcmpW( entry.cFileName, dotW )) continue;
1839 buffer[0] = '[';
1840 if (!long_names && entry.cAlternateFileName[0])
1841 strcpyW( buffer + 1, entry.cAlternateFileName );
1842 else
1843 strcpyW( buffer + 1, entry.cFileName );
1844 strcatW(buffer, bracketW);
1846 else /* not a directory */
1848 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1849 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1851 if ((attrib & DDL_EXCLUSIVE) &&
1852 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1853 continue;
1854 #undef ATTRIBS
1855 if (!long_names && entry.cAlternateFileName[0])
1856 strcpyW( buffer, entry.cAlternateFileName );
1857 else
1858 strcpyW( buffer, entry.cFileName );
1860 if (!long_names) CharLowerW( buffer );
1861 pos = LISTBOX_FindFileStrPos( descr, buffer );
1862 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1863 break;
1864 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1865 } while (FindNextFileW( handle, &entry ));
1866 FindClose( handle );
1869 if (ret >= 0)
1871 ret = maxinsert;
1873 /* scan drives */
1874 if (attrib & DDL_DRIVES)
1876 WCHAR buffer[] = {'[','-','a','-',']',0};
1877 WCHAR root[] = {'A',':','\\',0};
1878 int drive;
1879 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1881 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1882 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1883 break;
1887 return ret;
1891 /***********************************************************************
1892 * LISTBOX_HandleVScroll
1894 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1896 SCROLLINFO info;
1898 if (descr->style & LBS_MULTICOLUMN) return 0;
1899 switch(scrollReq)
1901 case SB_LINEUP:
1902 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1903 break;
1904 case SB_LINEDOWN:
1905 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1906 break;
1907 case SB_PAGEUP:
1908 LISTBOX_SetTopItem( descr, descr->top_item -
1909 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1910 break;
1911 case SB_PAGEDOWN:
1912 LISTBOX_SetTopItem( descr, descr->top_item +
1913 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1914 break;
1915 case SB_THUMBPOSITION:
1916 LISTBOX_SetTopItem( descr, pos, TRUE );
1917 break;
1918 case SB_THUMBTRACK:
1919 info.cbSize = sizeof(info);
1920 info.fMask = SIF_TRACKPOS;
1921 GetScrollInfo( descr->self, SB_VERT, &info );
1922 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1923 break;
1924 case SB_TOP:
1925 LISTBOX_SetTopItem( descr, 0, TRUE );
1926 break;
1927 case SB_BOTTOM:
1928 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1929 break;
1931 return 0;
1935 /***********************************************************************
1936 * LISTBOX_HandleHScroll
1938 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1940 SCROLLINFO info;
1941 INT page;
1943 if (descr->style & LBS_MULTICOLUMN)
1945 switch(scrollReq)
1947 case SB_LINELEFT:
1948 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1949 TRUE );
1950 break;
1951 case SB_LINERIGHT:
1952 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1953 TRUE );
1954 break;
1955 case SB_PAGELEFT:
1956 page = descr->width / descr->column_width;
1957 if (page < 1) page = 1;
1958 LISTBOX_SetTopItem( descr,
1959 descr->top_item - page * descr->page_size, TRUE );
1960 break;
1961 case SB_PAGERIGHT:
1962 page = descr->width / descr->column_width;
1963 if (page < 1) page = 1;
1964 LISTBOX_SetTopItem( descr,
1965 descr->top_item + page * descr->page_size, TRUE );
1966 break;
1967 case SB_THUMBPOSITION:
1968 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1969 break;
1970 case SB_THUMBTRACK:
1971 info.cbSize = sizeof(info);
1972 info.fMask = SIF_TRACKPOS;
1973 GetScrollInfo( descr->self, SB_VERT, &info );
1974 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1975 TRUE );
1976 break;
1977 case SB_LEFT:
1978 LISTBOX_SetTopItem( descr, 0, TRUE );
1979 break;
1980 case SB_RIGHT:
1981 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1982 break;
1985 else if (descr->horz_extent)
1987 switch(scrollReq)
1989 case SB_LINELEFT:
1990 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1991 break;
1992 case SB_LINERIGHT:
1993 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1994 break;
1995 case SB_PAGELEFT:
1996 LISTBOX_SetHorizontalPos( descr,
1997 descr->horz_pos - descr->width );
1998 break;
1999 case SB_PAGERIGHT:
2000 LISTBOX_SetHorizontalPos( descr,
2001 descr->horz_pos + descr->width );
2002 break;
2003 case SB_THUMBPOSITION:
2004 LISTBOX_SetHorizontalPos( descr, pos );
2005 break;
2006 case SB_THUMBTRACK:
2007 info.cbSize = sizeof(info);
2008 info.fMask = SIF_TRACKPOS;
2009 GetScrollInfo( descr->self, SB_HORZ, &info );
2010 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2011 break;
2012 case SB_LEFT:
2013 LISTBOX_SetHorizontalPos( descr, 0 );
2014 break;
2015 case SB_RIGHT:
2016 LISTBOX_SetHorizontalPos( descr,
2017 descr->horz_extent - descr->width );
2018 break;
2021 return 0;
2024 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2026 short gcWheelDelta = 0;
2027 UINT pulScrollLines = 3;
2029 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2031 gcWheelDelta -= delta;
2033 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2035 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2036 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2037 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2039 return 0;
2042 /***********************************************************************
2043 * LISTBOX_HandleLButtonDown
2045 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2047 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2049 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2050 descr->self, x, y, index, descr->focus_item);
2052 if (!descr->caret_on && (descr->in_focus)) return 0;
2054 if (!descr->in_focus)
2056 if( !descr->lphc ) SetFocus( descr->self );
2057 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2060 if (index == -1) return 0;
2062 if (!descr->lphc)
2064 if (descr->style & LBS_NOTIFY )
2065 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2066 MAKELPARAM( x, y ) );
2069 descr->captured = TRUE;
2070 SetCapture( descr->self );
2072 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2074 /* we should perhaps make sure that all items are deselected
2075 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2076 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2077 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2080 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2081 if (keys & MK_CONTROL)
2083 LISTBOX_SetCaretIndex( descr, index, FALSE );
2084 LISTBOX_SetSelection( descr, index,
2085 !descr->items[index].selected,
2086 (descr->style & LBS_NOTIFY) != 0);
2088 else
2090 LISTBOX_MoveCaret( descr, index, FALSE );
2092 if (descr->style & LBS_EXTENDEDSEL)
2094 LISTBOX_SetSelection( descr, index,
2095 descr->items[index].selected,
2096 (descr->style & LBS_NOTIFY) != 0 );
2098 else
2100 LISTBOX_SetSelection( descr, index,
2101 !descr->items[index].selected,
2102 (descr->style & LBS_NOTIFY) != 0 );
2106 else
2108 descr->anchor_item = index;
2109 LISTBOX_MoveCaret( descr, index, FALSE );
2110 LISTBOX_SetSelection( descr, index,
2111 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2114 if (!descr->lphc)
2116 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2118 POINT pt;
2120 pt.x = x;
2121 pt.y = y;
2123 if (DragDetect( descr->self, pt ))
2124 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2127 return 0;
2131 /*************************************************************************
2132 * LISTBOX_HandleLButtonDownCombo [Internal]
2134 * Process LButtonDown message for the ComboListBox
2136 * PARAMS
2137 * pWnd [I] The windows internal structure
2138 * pDescr [I] The ListBox internal structure
2139 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2140 * x [I] X Mouse Coordinate
2141 * y [I] Y Mouse Coordinate
2143 * RETURNS
2144 * 0 since we are processing the WM_LBUTTONDOWN Message
2146 * NOTES
2147 * This function is only to be used when a ListBox is a ComboListBox
2150 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2152 RECT clientRect, screenRect;
2153 POINT mousePos;
2155 mousePos.x = x;
2156 mousePos.y = y;
2158 GetClientRect(descr->self, &clientRect);
2160 if(PtInRect(&clientRect, mousePos))
2162 /* MousePos is in client, resume normal processing */
2163 if (msg == WM_LBUTTONDOWN)
2165 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2166 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2168 else if (descr->style & LBS_NOTIFY)
2169 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2171 else
2173 POINT screenMousePos;
2174 HWND hWndOldCapture;
2176 /* Check the Non-Client Area */
2177 screenMousePos = mousePos;
2178 hWndOldCapture = GetCapture();
2179 ReleaseCapture();
2180 GetWindowRect(descr->self, &screenRect);
2181 ClientToScreen(descr->self, &screenMousePos);
2183 if(!PtInRect(&screenRect, screenMousePos))
2185 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2186 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2187 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2189 else
2191 /* Check to see the NC is a scrollbar */
2192 INT nHitTestType=0;
2193 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2194 /* Check Vertical scroll bar */
2195 if (style & WS_VSCROLL)
2197 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2198 if (PtInRect( &clientRect, mousePos ))
2199 nHitTestType = HTVSCROLL;
2201 /* Check horizontal scroll bar */
2202 if (style & WS_HSCROLL)
2204 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2205 if (PtInRect( &clientRect, mousePos ))
2206 nHitTestType = HTHSCROLL;
2208 /* Windows sends this message when a scrollbar is clicked
2211 if(nHitTestType != 0)
2213 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2214 MAKELONG(screenMousePos.x, screenMousePos.y));
2216 /* Resume the Capture after scrolling is complete
2218 if(hWndOldCapture != 0)
2219 SetCapture(hWndOldCapture);
2222 return 0;
2225 /***********************************************************************
2226 * LISTBOX_HandleLButtonUp
2228 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2230 if (LISTBOX_Timer != LB_TIMER_NONE)
2231 KillSystemTimer( descr->self, LB_TIMER_ID );
2232 LISTBOX_Timer = LB_TIMER_NONE;
2233 if (descr->captured)
2235 descr->captured = FALSE;
2236 if (GetCapture() == descr->self) ReleaseCapture();
2237 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2238 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2240 return 0;
2244 /***********************************************************************
2245 * LISTBOX_HandleTimer
2247 * Handle scrolling upon a timer event.
2248 * Return TRUE if scrolling should continue.
2250 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2252 switch(dir)
2254 case LB_TIMER_UP:
2255 if (descr->top_item) index = descr->top_item - 1;
2256 else index = 0;
2257 break;
2258 case LB_TIMER_LEFT:
2259 if (descr->top_item) index -= descr->page_size;
2260 break;
2261 case LB_TIMER_DOWN:
2262 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2263 if (index == descr->focus_item) index++;
2264 if (index >= descr->nb_items) index = descr->nb_items - 1;
2265 break;
2266 case LB_TIMER_RIGHT:
2267 if (index + descr->page_size < descr->nb_items)
2268 index += descr->page_size;
2269 break;
2270 case LB_TIMER_NONE:
2271 break;
2273 if (index == descr->focus_item) return FALSE;
2274 LISTBOX_MoveCaret( descr, index, FALSE );
2275 return TRUE;
2279 /***********************************************************************
2280 * LISTBOX_HandleSystemTimer
2282 * WM_SYSTIMER handler.
2284 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2286 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2288 KillSystemTimer( descr->self, LB_TIMER_ID );
2289 LISTBOX_Timer = LB_TIMER_NONE;
2291 return 0;
2295 /***********************************************************************
2296 * LISTBOX_HandleMouseMove
2298 * WM_MOUSEMOVE handler.
2300 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2301 INT x, INT y )
2303 INT index;
2304 TIMER_DIRECTION dir = LB_TIMER_NONE;
2306 if (!descr->captured) return;
2308 if (descr->style & LBS_MULTICOLUMN)
2310 if (y < 0) y = 0;
2311 else if (y >= descr->item_height * descr->page_size)
2312 y = descr->item_height * descr->page_size - 1;
2314 if (x < 0)
2316 dir = LB_TIMER_LEFT;
2317 x = 0;
2319 else if (x >= descr->width)
2321 dir = LB_TIMER_RIGHT;
2322 x = descr->width - 1;
2325 else
2327 if (y < 0) dir = LB_TIMER_UP; /* above */
2328 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2331 index = LISTBOX_GetItemFromPoint( descr, x, y );
2332 if (index == -1) index = descr->focus_item;
2333 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2335 /* Start/stop the system timer */
2337 if (dir != LB_TIMER_NONE)
2338 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2339 else if (LISTBOX_Timer != LB_TIMER_NONE)
2340 KillSystemTimer( descr->self, LB_TIMER_ID );
2341 LISTBOX_Timer = dir;
2345 /***********************************************************************
2346 * LISTBOX_HandleKeyDown
2348 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2350 INT caret = -1;
2351 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2352 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2353 bForceSelection = FALSE; /* only for single select list */
2355 if (descr->style & LBS_WANTKEYBOARDINPUT)
2357 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2358 MAKEWPARAM(LOWORD(key), descr->focus_item),
2359 (LPARAM)descr->self );
2360 if (caret == -2) return 0;
2362 if (caret == -1) switch(key)
2364 case VK_LEFT:
2365 if (descr->style & LBS_MULTICOLUMN)
2367 bForceSelection = FALSE;
2368 if (descr->focus_item >= descr->page_size)
2369 caret = descr->focus_item - descr->page_size;
2370 break;
2372 /* fall through */
2373 case VK_UP:
2374 caret = descr->focus_item - 1;
2375 if (caret < 0) caret = 0;
2376 break;
2377 case VK_RIGHT:
2378 if (descr->style & LBS_MULTICOLUMN)
2380 bForceSelection = FALSE;
2381 if (descr->focus_item + descr->page_size < descr->nb_items)
2382 caret = descr->focus_item + descr->page_size;
2383 break;
2385 /* fall through */
2386 case VK_DOWN:
2387 caret = descr->focus_item + 1;
2388 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2389 break;
2391 case VK_PRIOR:
2392 if (descr->style & LBS_MULTICOLUMN)
2394 INT page = descr->width / descr->column_width;
2395 if (page < 1) page = 1;
2396 caret = descr->focus_item - (page * descr->page_size) + 1;
2398 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2399 if (caret < 0) caret = 0;
2400 break;
2401 case VK_NEXT:
2402 if (descr->style & LBS_MULTICOLUMN)
2404 INT page = descr->width / descr->column_width;
2405 if (page < 1) page = 1;
2406 caret = descr->focus_item + (page * descr->page_size) - 1;
2408 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2409 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2410 break;
2411 case VK_HOME:
2412 caret = 0;
2413 break;
2414 case VK_END:
2415 caret = descr->nb_items - 1;
2416 break;
2417 case VK_SPACE:
2418 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2419 else if (descr->style & LBS_MULTIPLESEL)
2421 LISTBOX_SetSelection( descr, descr->focus_item,
2422 !descr->items[descr->focus_item].selected,
2423 (descr->style & LBS_NOTIFY) != 0 );
2425 break;
2426 default:
2427 bForceSelection = FALSE;
2429 if (bForceSelection) /* focused item is used instead of key */
2430 caret = descr->focus_item;
2431 if (caret >= 0)
2433 if (((descr->style & LBS_EXTENDEDSEL) &&
2434 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2435 !IS_MULTISELECT(descr))
2436 descr->anchor_item = caret;
2437 LISTBOX_MoveCaret( descr, caret, TRUE );
2439 if (descr->style & LBS_MULTIPLESEL)
2440 descr->selected_item = caret;
2441 else
2442 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2443 if (descr->style & LBS_NOTIFY)
2445 if( descr->lphc )
2447 /* make sure that combo parent doesn't hide us */
2448 descr->lphc->wState |= CBF_NOROLLUP;
2450 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2453 return 0;
2457 /***********************************************************************
2458 * LISTBOX_HandleChar
2460 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2462 INT caret = -1;
2463 WCHAR str[2];
2465 str[0] = charW;
2466 str[1] = '\0';
2468 if (descr->style & LBS_WANTKEYBOARDINPUT)
2470 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2471 MAKEWPARAM(charW, descr->focus_item),
2472 (LPARAM)descr->self );
2473 if (caret == -2) return 0;
2475 if (caret == -1)
2476 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2477 if (caret != -1)
2479 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2480 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2481 LISTBOX_MoveCaret( descr, caret, TRUE );
2482 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2483 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2485 return 0;
2489 /***********************************************************************
2490 * LISTBOX_Create
2492 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2494 LB_DESCR *descr;
2495 MEASUREITEMSTRUCT mis;
2496 RECT rect;
2498 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2499 return FALSE;
2501 GetClientRect( hwnd, &rect );
2502 descr->self = hwnd;
2503 descr->owner = GetParent( descr->self );
2504 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2505 descr->width = rect.right - rect.left;
2506 descr->height = rect.bottom - rect.top;
2507 descr->items = NULL;
2508 descr->nb_items = 0;
2509 descr->top_item = 0;
2510 descr->selected_item = -1;
2511 descr->focus_item = 0;
2512 descr->anchor_item = -1;
2513 descr->item_height = 1;
2514 descr->page_size = 1;
2515 descr->column_width = 150;
2516 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2517 descr->horz_pos = 0;
2518 descr->nb_tabs = 0;
2519 descr->tabs = NULL;
2520 descr->caret_on = lphc ? FALSE : TRUE;
2521 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2522 descr->in_focus = FALSE;
2523 descr->captured = FALSE;
2524 descr->font = 0;
2525 descr->locale = GetUserDefaultLCID();
2526 descr->lphc = lphc;
2528 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2530 /* Win95 document "List Box Differences" from MSDN:
2531 If a list box in a version 3.x application has either the
2532 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2533 horizontal and vertical scroll bars.
2535 descr->style |= WS_VSCROLL | WS_HSCROLL;
2538 if( lphc )
2540 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2541 descr->owner = lphc->self;
2544 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2546 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2548 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2549 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2550 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2551 descr->item_height = LISTBOX_SetFont( descr, 0 );
2553 if (descr->style & LBS_OWNERDRAWFIXED)
2555 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2557 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2558 descr->item_height = lphc->fixedOwnerDrawHeight;
2560 else
2562 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2563 mis.CtlType = ODT_LISTBOX;
2564 mis.CtlID = id;
2565 mis.itemID = -1;
2566 mis.itemWidth = 0;
2567 mis.itemData = 0;
2568 mis.itemHeight = descr->item_height;
2569 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2570 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2574 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2575 return TRUE;
2579 /***********************************************************************
2580 * LISTBOX_Destroy
2582 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2584 LISTBOX_ResetContent( descr );
2585 SetWindowLongPtrW( descr->self, 0, 0 );
2586 HeapFree( GetProcessHeap(), 0, descr );
2587 return TRUE;
2591 /***********************************************************************
2592 * ListBoxWndProc_common
2594 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2595 WPARAM wParam, LPARAM lParam, BOOL unicode )
2597 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2598 LPHEADCOMBO lphc = 0;
2599 LRESULT ret;
2601 if (!descr)
2603 if (!IsWindow(hwnd)) return 0;
2605 if (msg == WM_CREATE)
2607 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2608 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2609 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2610 TRACE("creating wnd=%p descr=%x\n", hwnd, GetWindowLongPtrW( hwnd, 0 ) );
2611 return 0;
2613 /* Ignore all other messages before we get a WM_CREATE */
2614 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2615 DefWindowProcA( hwnd, msg, wParam, lParam );
2617 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2619 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2620 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2622 switch(msg)
2624 case LB_RESETCONTENT16:
2625 case LB_RESETCONTENT:
2626 LISTBOX_ResetContent( descr );
2627 LISTBOX_UpdateScroll( descr );
2628 InvalidateRect( descr->self, NULL, TRUE );
2629 return 0;
2631 case LB_ADDSTRING16:
2632 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2633 /* fall through */
2634 case LB_ADDSTRING:
2636 INT ret;
2637 LPWSTR textW;
2638 if(unicode || !HAS_STRINGS(descr))
2639 textW = (LPWSTR)lParam;
2640 else
2642 LPSTR textA = (LPSTR)lParam;
2643 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2644 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2645 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2647 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2648 ret = LISTBOX_InsertString( descr, wParam, textW );
2649 if (!unicode && HAS_STRINGS(descr))
2650 HeapFree(GetProcessHeap(), 0, textW);
2651 return ret;
2654 case LB_INSERTSTRING16:
2655 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2656 wParam = (INT)(INT16)wParam;
2657 /* fall through */
2658 case LB_INSERTSTRING:
2660 INT ret;
2661 LPWSTR textW;
2662 if(unicode || !HAS_STRINGS(descr))
2663 textW = (LPWSTR)lParam;
2664 else
2666 LPSTR textA = (LPSTR)lParam;
2667 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2668 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2669 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2671 ret = LISTBOX_InsertString( descr, wParam, textW );
2672 if(!unicode && HAS_STRINGS(descr))
2673 HeapFree(GetProcessHeap(), 0, textW);
2674 return ret;
2677 case LB_ADDFILE16:
2678 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2679 /* fall through */
2680 case LB_ADDFILE:
2682 INT ret;
2683 LPWSTR textW;
2684 if(unicode || !HAS_STRINGS(descr))
2685 textW = (LPWSTR)lParam;
2686 else
2688 LPSTR textA = (LPSTR)lParam;
2689 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2690 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2691 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2693 wParam = LISTBOX_FindFileStrPos( descr, textW );
2694 ret = LISTBOX_InsertString( descr, wParam, textW );
2695 if(!unicode && HAS_STRINGS(descr))
2696 HeapFree(GetProcessHeap(), 0, textW);
2697 return ret;
2700 case LB_DELETESTRING16:
2701 case LB_DELETESTRING:
2702 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2703 return descr->nb_items;
2704 else
2706 SetLastError(ERROR_INVALID_INDEX);
2707 return LB_ERR;
2710 case LB_GETITEMDATA16:
2711 case LB_GETITEMDATA:
2712 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2714 SetLastError(ERROR_INVALID_INDEX);
2715 return LB_ERR;
2717 return descr->items[wParam].data;
2719 case LB_SETITEMDATA16:
2720 case LB_SETITEMDATA:
2721 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2723 SetLastError(ERROR_INVALID_INDEX);
2724 return LB_ERR;
2726 descr->items[wParam].data = lParam;
2727 /* undocumented: returns TRUE, not LB_OKAY (0) */
2728 return TRUE;
2730 case LB_GETCOUNT16:
2731 case LB_GETCOUNT:
2732 return descr->nb_items;
2734 case LB_GETTEXT16:
2735 lParam = (LPARAM)MapSL(lParam);
2736 /* fall through */
2737 case LB_GETTEXT:
2738 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2740 case LB_GETTEXTLEN16:
2741 /* fall through */
2742 case LB_GETTEXTLEN:
2743 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2745 SetLastError(ERROR_INVALID_INDEX);
2746 return LB_ERR;
2748 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2749 if (unicode) return strlenW( descr->items[wParam].str );
2750 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2751 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2753 case LB_GETCURSEL16:
2754 case LB_GETCURSEL:
2755 if (descr->nb_items == 0)
2756 return LB_ERR;
2757 if (!IS_MULTISELECT(descr))
2758 return descr->selected_item;
2759 if (descr->selected_item != -1)
2760 return descr->selected_item;
2761 return descr->focus_item;
2762 /* otherwise, if the user tries to move the selection with the */
2763 /* arrow keys, we will give the application something to choke on */
2764 case LB_GETTOPINDEX16:
2765 case LB_GETTOPINDEX:
2766 return descr->top_item;
2768 case LB_GETITEMHEIGHT16:
2769 case LB_GETITEMHEIGHT:
2770 return LISTBOX_GetItemHeight( descr, wParam );
2772 case LB_SETITEMHEIGHT16:
2773 lParam = LOWORD(lParam);
2774 /* fall through */
2775 case LB_SETITEMHEIGHT:
2776 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2778 case LB_ITEMFROMPOINT:
2780 POINT pt;
2781 RECT rect;
2782 int index;
2783 BOOL hit = TRUE;
2785 /* The hiword of the return value is not a client area
2786 hittest as suggested by MSDN, but rather a hittest on
2787 the returned listbox item. */
2789 if(descr->nb_items == 0)
2790 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2792 pt.x = (short)LOWORD(lParam);
2793 pt.y = (short)HIWORD(lParam);
2795 SetRect(&rect, 0, 0, descr->width, descr->height);
2797 if(!PtInRect(&rect, pt))
2799 pt.x = min(pt.x, rect.right - 1);
2800 pt.x = max(pt.x, 0);
2801 pt.y = min(pt.y, rect.bottom - 1);
2802 pt.y = max(pt.y, 0);
2803 hit = FALSE;
2806 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2808 if(index == -1)
2810 index = descr->nb_items - 1;
2811 hit = FALSE;
2813 return MAKELONG(index, hit ? 0 : 1);
2816 case LB_SETCARETINDEX16:
2817 case LB_SETCARETINDEX:
2818 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2819 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2820 return LB_ERR;
2821 else if (ISWIN31)
2822 return wParam;
2823 else
2824 return LB_OKAY;
2826 case LB_GETCARETINDEX16:
2827 case LB_GETCARETINDEX:
2828 return descr->focus_item;
2830 case LB_SETTOPINDEX16:
2831 case LB_SETTOPINDEX:
2832 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2834 case LB_SETCOLUMNWIDTH16:
2835 case LB_SETCOLUMNWIDTH:
2836 return LISTBOX_SetColumnWidth( descr, wParam );
2838 case LB_GETITEMRECT16:
2840 RECT rect;
2841 RECT16 *r16 = MapSL(lParam);
2842 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2843 r16->left = rect.left;
2844 r16->top = rect.top;
2845 r16->right = rect.right;
2846 r16->bottom = rect.bottom;
2848 return ret;
2850 case LB_GETITEMRECT:
2851 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2853 case LB_FINDSTRING16:
2854 wParam = (INT)(INT16)wParam;
2855 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2856 /* fall through */
2857 case LB_FINDSTRING:
2859 INT ret;
2860 LPWSTR textW;
2861 if(unicode || !HAS_STRINGS(descr))
2862 textW = (LPWSTR)lParam;
2863 else
2865 LPSTR textA = (LPSTR)lParam;
2866 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2867 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2868 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2870 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2871 if(!unicode && HAS_STRINGS(descr))
2872 HeapFree(GetProcessHeap(), 0, textW);
2873 return ret;
2876 case LB_FINDSTRINGEXACT16:
2877 wParam = (INT)(INT16)wParam;
2878 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2879 /* fall through */
2880 case LB_FINDSTRINGEXACT:
2882 INT ret;
2883 LPWSTR textW;
2884 if(unicode || !HAS_STRINGS(descr))
2885 textW = (LPWSTR)lParam;
2886 else
2888 LPSTR textA = (LPSTR)lParam;
2889 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2890 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2891 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2893 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2894 if(!unicode && HAS_STRINGS(descr))
2895 HeapFree(GetProcessHeap(), 0, textW);
2896 return ret;
2899 case LB_SELECTSTRING16:
2900 wParam = (INT)(INT16)wParam;
2901 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2902 /* fall through */
2903 case LB_SELECTSTRING:
2905 INT index;
2906 LPWSTR textW;
2908 if(HAS_STRINGS(descr))
2909 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2910 debugstr_a((LPSTR)lParam));
2911 if(unicode || !HAS_STRINGS(descr))
2912 textW = (LPWSTR)lParam;
2913 else
2915 LPSTR textA = (LPSTR)lParam;
2916 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2917 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2918 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2920 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2921 if(!unicode && HAS_STRINGS(descr))
2922 HeapFree(GetProcessHeap(), 0, textW);
2923 if (index != LB_ERR)
2925 LISTBOX_MoveCaret( descr, index, TRUE );
2926 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2928 return index;
2931 case LB_GETSEL16:
2932 wParam = (INT)(INT16)wParam;
2933 /* fall through */
2934 case LB_GETSEL:
2935 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2936 return LB_ERR;
2937 return descr->items[wParam].selected;
2939 case LB_SETSEL16:
2940 lParam = (INT)(INT16)lParam;
2941 /* fall through */
2942 case LB_SETSEL:
2943 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2945 case LB_SETCURSEL16:
2946 wParam = (INT)(INT16)wParam;
2947 /* fall through */
2948 case LB_SETCURSEL:
2949 if (IS_MULTISELECT(descr)) return LB_ERR;
2950 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2951 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2952 if (ret != LB_ERR) ret = descr->selected_item;
2953 return ret;
2955 case LB_GETSELCOUNT16:
2956 case LB_GETSELCOUNT:
2957 return LISTBOX_GetSelCount( descr );
2959 case LB_GETSELITEMS16:
2960 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2962 case LB_GETSELITEMS:
2963 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2965 case LB_SELITEMRANGE16:
2966 case LB_SELITEMRANGE:
2967 if (LOWORD(lParam) <= HIWORD(lParam))
2968 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2969 HIWORD(lParam), wParam );
2970 else
2971 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2972 LOWORD(lParam), wParam );
2974 case LB_SELITEMRANGEEX16:
2975 case LB_SELITEMRANGEEX:
2976 if ((INT)lParam >= (INT)wParam)
2977 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2978 else
2979 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2981 case LB_GETHORIZONTALEXTENT16:
2982 case LB_GETHORIZONTALEXTENT:
2983 return descr->horz_extent;
2985 case LB_SETHORIZONTALEXTENT16:
2986 case LB_SETHORIZONTALEXTENT:
2987 return LISTBOX_SetHorizontalExtent( descr, wParam );
2989 case LB_GETANCHORINDEX16:
2990 case LB_GETANCHORINDEX:
2991 return descr->anchor_item;
2993 case LB_SETANCHORINDEX16:
2994 wParam = (INT)(INT16)wParam;
2995 /* fall through */
2996 case LB_SETANCHORINDEX:
2997 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2999 SetLastError(ERROR_INVALID_INDEX);
3000 return LB_ERR;
3002 descr->anchor_item = (INT)wParam;
3003 return LB_OKAY;
3005 case LB_DIR16:
3006 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
3007 * be set automatically (this is different in Win32) */
3008 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
3009 lParam = (LPARAM)MapSL(lParam);
3010 /* fall through */
3011 case LB_DIR:
3013 INT ret;
3014 LPWSTR textW;
3015 if(unicode)
3016 textW = (LPWSTR)lParam;
3017 else
3019 LPSTR textA = (LPSTR)lParam;
3020 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3021 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3022 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3024 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3025 if(!unicode)
3026 HeapFree(GetProcessHeap(), 0, textW);
3027 return ret;
3030 case LB_GETLOCALE:
3031 return descr->locale;
3033 case LB_SETLOCALE:
3035 LCID ret;
3036 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3037 return LB_ERR;
3038 ret = descr->locale;
3039 descr->locale = (LCID)wParam;
3040 return ret;
3043 case LB_INITSTORAGE:
3044 return LISTBOX_InitStorage( descr, wParam );
3046 case LB_SETCOUNT:
3047 return LISTBOX_SetCount( descr, (INT)wParam );
3049 case LB_SETTABSTOPS16:
3050 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
3052 case LB_SETTABSTOPS:
3053 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
3055 case LB_CARETON16:
3056 case LB_CARETON:
3057 if (descr->caret_on)
3058 return LB_OKAY;
3059 descr->caret_on = TRUE;
3060 if ((descr->focus_item != -1) && (descr->in_focus))
3061 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3062 return LB_OKAY;
3064 case LB_CARETOFF16:
3065 case LB_CARETOFF:
3066 if (!descr->caret_on)
3067 return LB_OKAY;
3068 descr->caret_on = FALSE;
3069 if ((descr->focus_item != -1) && (descr->in_focus))
3070 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3071 return LB_OKAY;
3073 case LB_GETLISTBOXINFO:
3074 FIXME("LB_GETLISTBOXINFO: stub!\n");
3075 return 0;
3077 case WM_DESTROY:
3078 return LISTBOX_Destroy( descr );
3080 case WM_ENABLE:
3081 InvalidateRect( descr->self, NULL, TRUE );
3082 return 0;
3084 case WM_SETREDRAW:
3085 LISTBOX_SetRedraw( descr, wParam != 0 );
3086 return 0;
3088 case WM_GETDLGCODE:
3089 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3091 case WM_PRINTCLIENT:
3092 case WM_PAINT:
3094 PAINTSTRUCT ps;
3095 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3096 ret = LISTBOX_Paint( descr, hdc );
3097 if( !wParam ) EndPaint( descr->self, &ps );
3099 return ret;
3100 case WM_SIZE:
3101 LISTBOX_UpdateSize( descr );
3102 return 0;
3103 case WM_GETFONT:
3104 return (LRESULT)descr->font;
3105 case WM_SETFONT:
3106 LISTBOX_SetFont( descr, (HFONT)wParam );
3107 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3108 return 0;
3109 case WM_SETFOCUS:
3110 descr->in_focus = TRUE;
3111 descr->caret_on = TRUE;
3112 if (descr->focus_item != -1)
3113 LISTBOX_DrawFocusRect( descr, TRUE );
3114 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3115 return 0;
3116 case WM_KILLFOCUS:
3117 descr->in_focus = FALSE;
3118 if ((descr->focus_item != -1) && descr->caret_on)
3119 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3120 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3121 return 0;
3122 case WM_HSCROLL:
3123 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3124 case WM_VSCROLL:
3125 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3126 case WM_MOUSEWHEEL:
3127 if (wParam & (MK_SHIFT | MK_CONTROL))
3128 return DefWindowProcW( descr->self, msg, wParam, lParam );
3129 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3130 case WM_LBUTTONDOWN:
3131 if (lphc)
3132 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3133 (INT16)LOWORD(lParam),
3134 (INT16)HIWORD(lParam) );
3135 return LISTBOX_HandleLButtonDown( descr, wParam,
3136 (INT16)LOWORD(lParam),
3137 (INT16)HIWORD(lParam) );
3138 case WM_LBUTTONDBLCLK:
3139 if (lphc)
3140 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3141 (INT16)LOWORD(lParam),
3142 (INT16)HIWORD(lParam) );
3143 if (descr->style & LBS_NOTIFY)
3144 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3145 return 0;
3146 case WM_MOUSEMOVE:
3147 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3149 BOOL captured = descr->captured;
3150 POINT mousePos;
3151 RECT clientRect;
3153 mousePos.x = (INT16)LOWORD(lParam);
3154 mousePos.y = (INT16)HIWORD(lParam);
3157 * If we are in a dropdown combobox, we simulate that
3158 * the mouse is captured to show the tracking of the item.
3160 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3161 descr->captured = TRUE;
3163 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3165 descr->captured = captured;
3167 else if (GetCapture() == descr->self)
3169 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3170 (INT16)HIWORD(lParam) );
3172 return 0;
3173 case WM_LBUTTONUP:
3174 if (lphc)
3176 POINT mousePos;
3177 RECT clientRect;
3180 * If the mouse button "up" is not in the listbox,
3181 * we make sure there is no selection by re-selecting the
3182 * item that was selected when the listbox was made visible.
3184 mousePos.x = (INT16)LOWORD(lParam);
3185 mousePos.y = (INT16)HIWORD(lParam);
3187 GetClientRect(descr->self, &clientRect);
3190 * When the user clicks outside the combobox and the focus
3191 * is lost, the owning combobox will send a fake buttonup with
3192 * 0xFFFFFFF as the mouse location, we must also revert the
3193 * selection to the original selection.
3195 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3196 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3198 return LISTBOX_HandleLButtonUp( descr );
3199 case WM_KEYDOWN:
3200 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3202 /* for some reason Windows makes it possible to
3203 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3205 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3206 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3207 && (wParam == VK_DOWN || wParam == VK_UP)) )
3209 COMBO_FlipListbox( lphc, FALSE, FALSE );
3210 return 0;
3213 return LISTBOX_HandleKeyDown( descr, wParam );
3214 case WM_CHAR:
3216 WCHAR charW;
3217 if(unicode)
3218 charW = (WCHAR)wParam;
3219 else
3221 CHAR charA = (CHAR)wParam;
3222 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3224 return LISTBOX_HandleChar( descr, charW );
3226 case WM_SYSTIMER:
3227 return LISTBOX_HandleSystemTimer( descr );
3228 case WM_ERASEBKGND:
3229 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3231 RECT rect;
3232 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3233 wParam, (LPARAM)descr->self );
3234 TRACE("hbrush = %p\n", hbrush);
3235 if(!hbrush)
3236 hbrush = GetSysColorBrush(COLOR_WINDOW);
3237 if(hbrush)
3239 GetClientRect(descr->self, &rect);
3240 FillRect((HDC)wParam, &rect, hbrush);
3243 return 1;
3244 case WM_DROPFILES:
3245 if( lphc ) return 0;
3246 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3247 SendMessageA( descr->owner, msg, wParam, lParam );
3249 case WM_NCDESTROY:
3250 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3251 lphc->hWndLBox = 0;
3252 break;
3254 case WM_NCACTIVATE:
3255 if (lphc) return 0;
3256 break;
3258 default:
3259 if ((msg >= WM_USER) && (msg < 0xc000))
3260 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3261 hwnd, msg, wParam, lParam );
3264 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3265 DefWindowProcA( hwnd, msg, wParam, lParam );
3268 /***********************************************************************
3269 * ListBoxWndProcA
3271 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3273 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3276 /***********************************************************************
3277 * ListBoxWndProcW
3279 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3281 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );