push 9ed7f32abbb93ea3a813fd6b1650e5bcfc506606
[wine/hacks.git] / dlls / user32 / listbox.c
blob0a39582b6fecab918839b5a1e7b4318475b95f61
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( LB_DESCR *descr, INT index, RECT *rect );
136 /*********************************************************************
137 * listbox class descriptor
139 const struct builtin_class_descr LISTBOX_builtin_class =
141 "ListBox", /* name */
142 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
143 ListBoxWndProcA, /* procA */
144 ListBoxWndProcW, /* procW */
145 sizeof(LB_DESCR *), /* extra */
146 IDC_ARROW, /* cursor */
147 0 /* brush */
151 /*********************************************************************
152 * combolbox class descriptor
154 const struct builtin_class_descr COMBOLBOX_builtin_class =
156 "ComboLBox", /* name */
157 CS_DBLCLKS | CS_SAVEBITS, /* style */
158 ListBoxWndProcA, /* procA */
159 ListBoxWndProcW, /* procW */
160 sizeof(LB_DESCR *), /* extra */
161 IDC_ARROW, /* cursor */
162 0 /* brush */
166 /* check whether app is a Win 3.1 app */
167 static inline BOOL is_old_app( LB_DESCR *descr )
169 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
173 /***********************************************************************
174 * LISTBOX_GetCurrentPageSize
176 * Return the current page size
178 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
180 INT i, height;
181 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
182 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
184 if ((height += descr->items[i].height) > descr->height) break;
186 if (i == descr->top_item) return 1;
187 else return i - descr->top_item;
191 /***********************************************************************
192 * LISTBOX_GetMaxTopIndex
194 * Return the maximum possible index for the top of the listbox.
196 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
198 INT max, page;
200 if (descr->style & LBS_OWNERDRAWVARIABLE)
202 page = descr->height;
203 for (max = descr->nb_items - 1; max >= 0; max--)
204 if ((page -= descr->items[max].height) < 0) break;
205 if (max < descr->nb_items - 1) max++;
207 else if (descr->style & LBS_MULTICOLUMN)
209 if ((page = descr->width / descr->column_width) < 1) page = 1;
210 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
211 max = (max - page) * descr->page_size;
213 else
215 max = descr->nb_items - descr->page_size;
217 if (max < 0) max = 0;
218 return max;
222 /***********************************************************************
223 * LISTBOX_UpdateScroll
225 * Update the scrollbars. Should be called whenever the content
226 * of the listbox changes.
228 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
230 SCROLLINFO info;
232 /* Check the listbox scroll bar flags individually before we call
233 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
234 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
235 scroll bar when we do not need one.
236 if (!(descr->style & WS_VSCROLL)) return;
239 /* It is important that we check descr->style, and not wnd->dwStyle,
240 for WS_VSCROLL, as the former is exactly the one passed in
241 argument to CreateWindow.
242 In Windows (and from now on in Wine :) a listbox created
243 with such a style (no WS_SCROLL) does not update
244 the scrollbar with listbox-related data, thus letting
245 the programmer use it for his/her own purposes. */
247 if (descr->style & LBS_NOREDRAW) return;
248 info.cbSize = sizeof(info);
250 if (descr->style & LBS_MULTICOLUMN)
252 info.nMin = 0;
253 info.nMax = (descr->nb_items - 1) / descr->page_size;
254 info.nPos = descr->top_item / descr->page_size;
255 info.nPage = descr->width / descr->column_width;
256 if (info.nPage < 1) info.nPage = 1;
257 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
258 if (descr->style & LBS_DISABLENOSCROLL)
259 info.fMask |= SIF_DISABLENOSCROLL;
260 if (descr->style & WS_HSCROLL)
261 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
262 info.nMax = 0;
263 info.fMask = SIF_RANGE;
264 if (descr->style & WS_VSCROLL)
265 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
267 else
269 info.nMin = 0;
270 info.nMax = descr->nb_items - 1;
271 info.nPos = descr->top_item;
272 info.nPage = LISTBOX_GetCurrentPageSize( descr );
273 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
274 if (descr->style & LBS_DISABLENOSCROLL)
275 info.fMask |= SIF_DISABLENOSCROLL;
276 if (descr->style & WS_VSCROLL)
277 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
279 if (descr->horz_extent)
281 info.nMin = 0;
282 info.nMax = descr->horz_extent - 1;
283 info.nPos = descr->horz_pos;
284 info.nPage = descr->width;
285 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
286 if (descr->style & LBS_DISABLENOSCROLL)
287 info.fMask |= SIF_DISABLENOSCROLL;
288 if (descr->style & WS_HSCROLL)
289 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
295 /***********************************************************************
296 * LISTBOX_SetTopItem
298 * Set the top item of the listbox, scrolling up or down if necessary.
300 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
302 INT max = LISTBOX_GetMaxTopIndex( descr );
304 TRACE("setting top item %d, scroll %d\n", index, scroll);
306 if (index > max) index = max;
307 if (index < 0) index = 0;
308 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
309 if (descr->top_item == index) return LB_OKAY;
310 if (descr->style & LBS_MULTICOLUMN)
312 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
313 if (scroll && (abs(diff) < descr->width))
314 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
315 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
317 else
318 scroll = FALSE;
320 else if (scroll)
322 INT diff;
323 if (descr->style & LBS_OWNERDRAWVARIABLE)
325 INT i;
326 diff = 0;
327 if (index > descr->top_item)
329 for (i = index - 1; i >= descr->top_item; i--)
330 diff -= descr->items[i].height;
332 else
334 for (i = index; i < descr->top_item; i++)
335 diff += descr->items[i].height;
338 else
339 diff = (descr->top_item - index) * descr->item_height;
341 if (abs(diff) < descr->height)
342 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
343 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
344 else
345 scroll = FALSE;
347 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
348 descr->top_item = index;
349 LISTBOX_UpdateScroll( descr );
350 return LB_OKAY;
354 /***********************************************************************
355 * LISTBOX_UpdatePage
357 * Update the page size. Should be called when the size of
358 * the client area or the item height changes.
360 static void LISTBOX_UpdatePage( LB_DESCR *descr )
362 INT page_size;
364 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
365 page_size = 1;
366 if (page_size == descr->page_size) return;
367 descr->page_size = page_size;
368 if (descr->style & LBS_MULTICOLUMN)
369 InvalidateRect( descr->self, NULL, TRUE );
370 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
374 /***********************************************************************
375 * LISTBOX_UpdateSize
377 * Update the size of the listbox. Should be called when the size of
378 * the client area changes.
380 static void LISTBOX_UpdateSize( LB_DESCR *descr )
382 RECT rect;
384 GetClientRect( descr->self, &rect );
385 descr->width = rect.right - rect.left;
386 descr->height = rect.bottom - rect.top;
387 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
389 INT remaining;
390 RECT rect;
392 GetWindowRect( descr->self, &rect );
393 if(descr->item_height != 0)
394 remaining = descr->height % descr->item_height;
395 else
396 remaining = 0;
397 if ((descr->height > descr->item_height) && remaining)
399 if (is_old_app(descr))
400 { /* give a margin for error to 16 bits programs - if we need
401 less than the height of the nonclient area, round to the
402 *next* number of items */
403 int ncheight = rect.bottom - rect.top - descr->height;
404 if ((descr->item_height - remaining) <= ncheight)
405 remaining = remaining - descr->item_height;
407 TRACE("[%p]: changing height %d -> %d\n",
408 descr->self, descr->height, descr->height - remaining );
409 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
410 rect.bottom - rect.top - remaining,
411 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
412 return;
415 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
416 LISTBOX_UpdatePage( descr );
417 LISTBOX_UpdateScroll( descr );
419 /* Invalidate the focused item so it will be repainted correctly */
420 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
422 InvalidateRect( descr->self, &rect, FALSE );
427 /***********************************************************************
428 * LISTBOX_GetItemRect
430 * Get the rectangle enclosing an item, in listbox client coordinates.
431 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
433 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
435 /* Index <= 0 is legal even on empty listboxes */
436 if (index && (index >= descr->nb_items))
438 memset(rect, 0, sizeof(*rect));
439 SetLastError(ERROR_INVALID_INDEX);
440 return LB_ERR;
442 SetRect( rect, 0, 0, descr->width, descr->height );
443 if (descr->style & LBS_MULTICOLUMN)
445 INT col = (index / descr->page_size) -
446 (descr->top_item / descr->page_size);
447 rect->left += col * descr->column_width;
448 rect->right = rect->left + descr->column_width;
449 rect->top += (index % descr->page_size) * descr->item_height;
450 rect->bottom = rect->top + descr->item_height;
452 else if (descr->style & LBS_OWNERDRAWVARIABLE)
454 INT i;
455 rect->right += descr->horz_pos;
456 if ((index >= 0) && (index < descr->nb_items))
458 if (index < descr->top_item)
460 for (i = descr->top_item-1; i >= index; i--)
461 rect->top -= descr->items[i].height;
463 else
465 for (i = descr->top_item; i < index; i++)
466 rect->top += descr->items[i].height;
468 rect->bottom = rect->top + descr->items[index].height;
472 else
474 rect->top += (index - descr->top_item) * descr->item_height;
475 rect->bottom = rect->top + descr->item_height;
476 rect->right += descr->horz_pos;
479 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
481 return ((rect->left < descr->width) && (rect->right > 0) &&
482 (rect->top < descr->height) && (rect->bottom > 0));
486 /***********************************************************************
487 * LISTBOX_GetItemFromPoint
489 * Return the item nearest from point (x,y) (in client coordinates).
491 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
493 INT index = descr->top_item;
495 if (!descr->nb_items) return -1; /* No items */
496 if (descr->style & LBS_OWNERDRAWVARIABLE)
498 INT pos = 0;
499 if (y >= 0)
501 while (index < descr->nb_items)
503 if ((pos += descr->items[index].height) > y) break;
504 index++;
507 else
509 while (index > 0)
511 index--;
512 if ((pos -= descr->items[index].height) <= y) break;
516 else if (descr->style & LBS_MULTICOLUMN)
518 if (y >= descr->item_height * descr->page_size) return -1;
519 if (y >= 0) index += y / descr->item_height;
520 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
521 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
523 else
525 index += (y / descr->item_height);
527 if (index < 0) return 0;
528 if (index >= descr->nb_items) return -1;
529 return index;
533 /***********************************************************************
534 * LISTBOX_PaintItem
536 * Paint an item.
538 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
539 INT index, UINT action, BOOL ignoreFocus )
541 LB_ITEMDATA *item = NULL;
542 if (index < descr->nb_items) item = &descr->items[index];
544 if (IS_OWNERDRAW(descr))
546 DRAWITEMSTRUCT dis;
547 RECT r;
548 HRGN hrgn;
550 if (!item)
552 if (action == ODA_FOCUS)
553 DrawFocusRect( hdc, rect );
554 else
555 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
556 return;
559 /* some programs mess with the clipping region when
560 drawing the item, *and* restore the previous region
561 after they are done, so a region has better to exist
562 else everything ends clipped */
563 GetClientRect(descr->self, &r);
564 hrgn = CreateRectRgnIndirect(&r);
565 SelectClipRgn( hdc, hrgn);
566 DeleteObject( hrgn );
568 dis.CtlType = ODT_LISTBOX;
569 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
570 dis.hwndItem = descr->self;
571 dis.itemAction = action;
572 dis.hDC = hdc;
573 dis.itemID = index;
574 dis.itemState = 0;
575 if (item->selected) dis.itemState |= ODS_SELECTED;
576 if (!ignoreFocus && (descr->focus_item == index) &&
577 (descr->caret_on) &&
578 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
579 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
580 dis.itemData = item->data;
581 dis.rcItem = *rect;
582 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%d,%d-%d,%d\n",
583 descr->self, index, item ? debugstr_w(item->str) : "", action,
584 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
585 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
587 else
589 COLORREF oldText = 0, oldBk = 0;
591 if (action == ODA_FOCUS)
593 DrawFocusRect( hdc, rect );
594 return;
596 if (item && item->selected)
598 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
599 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
602 TRACE("[%p]: painting %d (%s) action=%02x rect=%d,%d-%d,%d\n",
603 descr->self, index, item ? debugstr_w(item->str) : "", action,
604 rect->left, rect->top, rect->right, rect->bottom );
605 if (!item)
606 ExtTextOutW( hdc, rect->left + 1, rect->top,
607 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
608 else if (!(descr->style & LBS_USETABSTOPS))
609 ExtTextOutW( hdc, rect->left + 1, rect->top,
610 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
611 strlenW(item->str), NULL );
612 else
614 /* Output empty string to paint background in the full width. */
615 ExtTextOutW( hdc, rect->left + 1, rect->top,
616 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
617 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
618 item->str, strlenW(item->str),
619 descr->nb_tabs, descr->tabs, 0);
621 if (item && item->selected)
623 SetBkColor( hdc, oldBk );
624 SetTextColor( hdc, oldText );
626 if (!ignoreFocus && (descr->focus_item == index) &&
627 (descr->caret_on) &&
628 (descr->in_focus)) DrawFocusRect( hdc, rect );
633 /***********************************************************************
634 * LISTBOX_SetRedraw
636 * Change the redraw flag.
638 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
640 if (on)
642 if (!(descr->style & LBS_NOREDRAW)) return;
643 descr->style &= ~LBS_NOREDRAW;
644 if (descr->style & LBS_DISPLAYCHANGED)
645 { /* page was changed while setredraw false, refresh automatically */
646 InvalidateRect(descr->self, NULL, TRUE);
647 if ((descr->top_item + descr->page_size) > descr->nb_items)
648 { /* reset top of page if less than number of items/page */
649 descr->top_item = descr->nb_items - descr->page_size;
650 if (descr->top_item < 0) descr->top_item = 0;
652 descr->style &= ~LBS_DISPLAYCHANGED;
654 LISTBOX_UpdateScroll( descr );
656 else descr->style |= LBS_NOREDRAW;
660 /***********************************************************************
661 * LISTBOX_RepaintItem
663 * Repaint a single item synchronously.
665 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
667 HDC hdc;
668 RECT rect;
669 HFONT oldFont = 0;
670 HBRUSH hbrush, oldBrush = 0;
672 /* Do not repaint the item if the item is not visible */
673 if (!IsWindowVisible(descr->self)) return;
674 if (descr->style & LBS_NOREDRAW)
676 descr->style |= LBS_DISPLAYCHANGED;
677 return;
679 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
680 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
681 if (descr->font) oldFont = SelectObject( hdc, descr->font );
682 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
683 (WPARAM)hdc, (LPARAM)descr->self );
684 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
685 if (!IsWindowEnabled(descr->self))
686 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
687 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
688 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
689 if (oldFont) SelectObject( hdc, oldFont );
690 if (oldBrush) SelectObject( hdc, oldBrush );
691 ReleaseDC( descr->self, hdc );
695 /***********************************************************************
696 * LISTBOX_DrawFocusRect
698 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
700 HDC hdc;
701 RECT rect;
702 HFONT oldFont = 0;
704 /* Do not repaint the item if the item is not visible */
705 if (!IsWindowVisible(descr->self)) return;
707 if (descr->focus_item == -1) return;
708 if (!descr->caret_on || !descr->in_focus) return;
710 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
711 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
712 if (descr->font) oldFont = SelectObject( hdc, descr->font );
713 if (!IsWindowEnabled(descr->self))
714 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
715 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
716 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, on ? FALSE : TRUE );
717 if (oldFont) SelectObject( hdc, oldFont );
718 ReleaseDC( descr->self, hdc );
722 /***********************************************************************
723 * LISTBOX_InitStorage
725 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
727 LB_ITEMDATA *item;
729 nb_items += LB_ARRAY_GRANULARITY - 1;
730 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
731 if (descr->items) {
732 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
733 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
734 nb_items * sizeof(LB_ITEMDATA));
736 else {
737 item = HeapAlloc( GetProcessHeap(), 0,
738 nb_items * sizeof(LB_ITEMDATA));
741 if (!item)
743 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
744 return LB_ERRSPACE;
746 descr->items = item;
747 return LB_OKAY;
751 /***********************************************************************
752 * LISTBOX_SetTabStops
754 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
756 INT i;
758 if (!(descr->style & LBS_USETABSTOPS))
760 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
761 return FALSE;
764 HeapFree( GetProcessHeap(), 0, descr->tabs );
765 if (!(descr->nb_tabs = count))
767 descr->tabs = NULL;
768 return TRUE;
770 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
771 descr->nb_tabs * sizeof(INT) )))
772 return FALSE;
773 if (short_ints)
775 INT i;
776 LPINT16 p = (LPINT16)tabs;
778 TRACE("[%p]: settabstops ", descr->self );
779 for (i = 0; i < descr->nb_tabs; i++) {
780 descr->tabs[i] = *p++<<1; /* FIXME */
781 TRACE("%hd ", descr->tabs[i]);
783 TRACE("\n");
785 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
787 /* convert into "dialog units"*/
788 for (i = 0; i < descr->nb_tabs; i++)
789 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
791 return TRUE;
795 /***********************************************************************
796 * LISTBOX_GetText
798 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
800 if ((index < 0) || (index >= descr->nb_items))
802 SetLastError(ERROR_INVALID_INDEX);
803 return LB_ERR;
805 if (HAS_STRINGS(descr))
807 if (!buffer)
809 DWORD len = strlenW(descr->items[index].str);
810 if( unicode )
811 return len;
812 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
813 NULL, 0, NULL, NULL );
816 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
818 if(unicode)
820 strcpyW( buffer, descr->items[index].str );
821 return strlenW(buffer);
823 else
825 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
827 } else {
828 if (buffer)
829 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
830 return sizeof(DWORD);
834 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
836 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
837 if (ret == CSTR_LESS_THAN)
838 return -1;
839 if (ret == CSTR_EQUAL)
840 return 0;
841 if (ret == CSTR_GREATER_THAN)
842 return 1;
843 return -1;
846 /***********************************************************************
847 * LISTBOX_FindStringPos
849 * Find the nearest string located before a given string in sort order.
850 * If 'exact' is TRUE, return an error if we don't get an exact match.
852 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
854 INT index, min, max, res = -1;
856 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
857 min = 0;
858 max = descr->nb_items;
859 while (min != max)
861 index = (min + max) / 2;
862 if (HAS_STRINGS(descr))
863 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
864 else
866 COMPAREITEMSTRUCT cis;
867 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
869 cis.CtlType = ODT_LISTBOX;
870 cis.CtlID = id;
871 cis.hwndItem = descr->self;
872 /* note that some application (MetaStock) expects the second item
873 * to be in the listbox */
874 cis.itemID1 = -1;
875 cis.itemData1 = (ULONG_PTR)str;
876 cis.itemID2 = index;
877 cis.itemData2 = descr->items[index].data;
878 cis.dwLocaleId = descr->locale;
879 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
881 if (!res) return index;
882 if (res < 0) max = index;
883 else min = index + 1;
885 return exact ? -1 : max;
889 /***********************************************************************
890 * LISTBOX_FindFileStrPos
892 * Find the nearest string located before a given string in directory
893 * sort order (i.e. first files, then directories, then drives).
895 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
897 INT min, max, res = -1;
899 if (!HAS_STRINGS(descr))
900 return LISTBOX_FindStringPos( descr, str, FALSE );
901 min = 0;
902 max = descr->nb_items;
903 while (min != max)
905 INT index = (min + max) / 2;
906 LPCWSTR p = descr->items[index].str;
907 if (*p == '[') /* drive or directory */
909 if (*str != '[') res = -1;
910 else if (p[1] == '-') /* drive */
912 if (str[1] == '-') res = str[2] - p[2];
913 else res = -1;
915 else /* directory */
917 if (str[1] == '-') res = 1;
918 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
921 else /* filename */
923 if (*str == '[') res = 1;
924 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
926 if (!res) return index;
927 if (res < 0) max = index;
928 else min = index + 1;
930 return max;
934 /***********************************************************************
935 * LISTBOX_FindString
937 * Find the item beginning with a given string.
939 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
941 INT i;
942 LB_ITEMDATA *item;
944 if (start >= descr->nb_items) start = -1;
945 item = descr->items + start + 1;
946 if (HAS_STRINGS(descr))
948 if (!str || ! str[0] ) return LB_ERR;
949 if (exact)
951 for (i = start + 1; i < descr->nb_items; i++, item++)
952 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
953 for (i = 0, item = descr->items; i <= start; i++, item++)
954 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
956 else
958 /* Special case for drives and directories: ignore prefix */
959 #define CHECK_DRIVE(item) \
960 if ((item)->str[0] == '[') \
962 if (!strncmpiW( str, (item)->str+1, len )) return i; \
963 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
964 return i; \
967 INT len = strlenW(str);
968 for (i = start + 1; i < descr->nb_items; i++, item++)
970 if (!strncmpiW( str, item->str, len )) return i;
971 CHECK_DRIVE(item);
973 for (i = 0, item = descr->items; i <= start; i++, item++)
975 if (!strncmpiW( str, item->str, len )) return i;
976 CHECK_DRIVE(item);
978 #undef CHECK_DRIVE
981 else
983 if (exact && (descr->style & LBS_SORT))
984 /* If sorted, use a WM_COMPAREITEM binary search */
985 return LISTBOX_FindStringPos( descr, str, TRUE );
987 /* Otherwise use a linear search */
988 for (i = start + 1; i < descr->nb_items; i++, item++)
989 if (item->data == (ULONG_PTR)str) return i;
990 for (i = 0, item = descr->items; i <= start; i++, item++)
991 if (item->data == (ULONG_PTR)str) return i;
993 return LB_ERR;
997 /***********************************************************************
998 * LISTBOX_GetSelCount
1000 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
1002 INT i, count;
1003 LB_ITEMDATA *item = descr->items;
1005 if (!(descr->style & LBS_MULTIPLESEL) ||
1006 (descr->style & LBS_NOSEL))
1007 return LB_ERR;
1008 for (i = count = 0; i < descr->nb_items; i++, item++)
1009 if (item->selected) count++;
1010 return count;
1014 /***********************************************************************
1015 * LISTBOX_GetSelItems16
1017 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
1019 INT i, count;
1020 LB_ITEMDATA *item = descr->items;
1022 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1023 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1024 if (item->selected) array[count++] = (INT16)i;
1025 return count;
1029 /***********************************************************************
1030 * LISTBOX_GetSelItems
1032 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
1034 INT i, count;
1035 LB_ITEMDATA *item = descr->items;
1037 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1038 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1039 if (item->selected) array[count++] = i;
1040 return count;
1044 /***********************************************************************
1045 * LISTBOX_Paint
1047 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1049 INT i, col_pos = descr->page_size - 1;
1050 RECT rect;
1051 RECT focusRect = {-1, -1, -1, -1};
1052 HFONT oldFont = 0;
1053 HBRUSH hbrush, oldBrush = 0;
1055 if (descr->style & LBS_NOREDRAW) return 0;
1057 SetRect( &rect, 0, 0, descr->width, descr->height );
1058 if (descr->style & LBS_MULTICOLUMN)
1059 rect.right = rect.left + descr->column_width;
1060 else if (descr->horz_pos)
1062 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1063 rect.right += descr->horz_pos;
1066 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1067 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1068 (WPARAM)hdc, (LPARAM)descr->self );
1069 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1070 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1072 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1073 (descr->in_focus))
1075 /* Special case for empty listbox: paint focus rect */
1076 rect.bottom = rect.top + descr->item_height;
1077 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1078 &rect, NULL, 0, NULL );
1079 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1080 rect.top = rect.bottom;
1083 /* Paint all the item, regarding the selection
1084 Focus state will be painted after */
1086 for (i = descr->top_item; i < descr->nb_items; i++)
1088 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1089 rect.bottom = rect.top + descr->item_height;
1090 else
1091 rect.bottom = rect.top + descr->items[i].height;
1093 if (i == descr->focus_item)
1095 /* keep the focus rect, to paint the focus item after */
1096 focusRect.left = rect.left;
1097 focusRect.right = rect.right;
1098 focusRect.top = rect.top;
1099 focusRect.bottom = rect.bottom;
1101 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1102 rect.top = rect.bottom;
1104 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1106 if (!IS_OWNERDRAW(descr))
1108 /* Clear the bottom of the column */
1109 if (rect.top < descr->height)
1111 rect.bottom = descr->height;
1112 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1113 &rect, NULL, 0, NULL );
1117 /* Go to the next column */
1118 rect.left += descr->column_width;
1119 rect.right += descr->column_width;
1120 rect.top = 0;
1121 col_pos = descr->page_size - 1;
1123 else
1125 col_pos--;
1126 if (rect.top >= descr->height) break;
1130 /* Paint the focus item now */
1131 if (focusRect.top != focusRect.bottom &&
1132 descr->caret_on && descr->in_focus)
1133 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1135 if (!IS_OWNERDRAW(descr))
1137 /* Clear the remainder of the client area */
1138 if (rect.top < descr->height)
1140 rect.bottom = descr->height;
1141 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1142 &rect, NULL, 0, NULL );
1144 if (rect.right < descr->width)
1146 rect.left = rect.right;
1147 rect.right = descr->width;
1148 rect.top = 0;
1149 rect.bottom = descr->height;
1150 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1151 &rect, NULL, 0, NULL );
1154 if (oldFont) SelectObject( hdc, oldFont );
1155 if (oldBrush) SelectObject( hdc, oldBrush );
1156 return 0;
1160 /***********************************************************************
1161 * LISTBOX_InvalidateItems
1163 * Invalidate all items from a given item. If the specified item is not
1164 * visible, nothing happens.
1166 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1168 RECT rect;
1170 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1172 if (descr->style & LBS_NOREDRAW)
1174 descr->style |= LBS_DISPLAYCHANGED;
1175 return;
1177 rect.bottom = descr->height;
1178 InvalidateRect( descr->self, &rect, TRUE );
1179 if (descr->style & LBS_MULTICOLUMN)
1181 /* Repaint the other columns */
1182 rect.left = rect.right;
1183 rect.right = descr->width;
1184 rect.top = 0;
1185 InvalidateRect( descr->self, &rect, TRUE );
1190 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1192 RECT rect;
1194 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1195 InvalidateRect( descr->self, &rect, TRUE );
1198 /***********************************************************************
1199 * LISTBOX_GetItemHeight
1201 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1203 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1205 if ((index < 0) || (index >= descr->nb_items))
1207 SetLastError(ERROR_INVALID_INDEX);
1208 return LB_ERR;
1210 return descr->items[index].height;
1212 else return descr->item_height;
1216 /***********************************************************************
1217 * LISTBOX_SetItemHeight
1219 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1221 if (height > MAXBYTE)
1222 return -1;
1224 if (!height) height = 1;
1226 if (descr->style & LBS_OWNERDRAWVARIABLE)
1228 if ((index < 0) || (index >= descr->nb_items))
1230 SetLastError(ERROR_INVALID_INDEX);
1231 return LB_ERR;
1233 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1234 descr->items[index].height = height;
1235 LISTBOX_UpdateScroll( descr );
1236 if (repaint)
1237 LISTBOX_InvalidateItems( descr, index );
1239 else if (height != descr->item_height)
1241 TRACE("[%p]: new height = %d\n", descr->self, height );
1242 descr->item_height = height;
1243 LISTBOX_UpdatePage( descr );
1244 LISTBOX_UpdateScroll( descr );
1245 if (repaint)
1246 InvalidateRect( descr->self, 0, TRUE );
1248 return LB_OKAY;
1252 /***********************************************************************
1253 * LISTBOX_SetHorizontalPos
1255 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1257 INT diff;
1259 if (pos > descr->horz_extent - descr->width)
1260 pos = descr->horz_extent - descr->width;
1261 if (pos < 0) pos = 0;
1262 if (!(diff = descr->horz_pos - pos)) return;
1263 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1264 descr->horz_pos = pos;
1265 LISTBOX_UpdateScroll( descr );
1266 if (abs(diff) < descr->width)
1268 RECT rect;
1269 /* Invalidate the focused item so it will be repainted correctly */
1270 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1271 InvalidateRect( descr->self, &rect, TRUE );
1272 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1273 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1275 else
1276 InvalidateRect( descr->self, NULL, TRUE );
1280 /***********************************************************************
1281 * LISTBOX_SetHorizontalExtent
1283 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1285 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1286 return LB_OKAY;
1287 if (extent <= 0) extent = 1;
1288 if (extent == descr->horz_extent) return LB_OKAY;
1289 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1290 descr->horz_extent = extent;
1291 if (descr->horz_pos > extent - descr->width)
1292 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1293 else
1294 LISTBOX_UpdateScroll( descr );
1295 return LB_OKAY;
1299 /***********************************************************************
1300 * LISTBOX_SetColumnWidth
1302 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1304 if (width == descr->column_width) return LB_OKAY;
1305 TRACE("[%p]: new column width = %d\n", descr->self, width );
1306 descr->column_width = width;
1307 LISTBOX_UpdatePage( descr );
1308 return LB_OKAY;
1312 /***********************************************************************
1313 * LISTBOX_SetFont
1315 * Returns the item height.
1317 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1319 HDC hdc;
1320 HFONT oldFont = 0;
1321 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1322 SIZE sz;
1324 descr->font = font;
1326 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1328 ERR("unable to get DC.\n" );
1329 return 16;
1331 if (font) oldFont = SelectObject( hdc, font );
1332 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1333 if (oldFont) SelectObject( hdc, oldFont );
1334 ReleaseDC( descr->self, hdc );
1336 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1337 if (!IS_OWNERDRAW(descr))
1338 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1339 return sz.cy;
1343 /***********************************************************************
1344 * LISTBOX_MakeItemVisible
1346 * Make sure that a given item is partially or fully visible.
1348 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1350 INT top;
1352 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1354 if (index <= descr->top_item) top = index;
1355 else if (descr->style & LBS_MULTICOLUMN)
1357 INT cols = descr->width;
1358 if (!fully) cols += descr->column_width - 1;
1359 if (cols >= descr->column_width) cols /= descr->column_width;
1360 else cols = 1;
1361 if (index < descr->top_item + (descr->page_size * cols)) return;
1362 top = index - descr->page_size * (cols - 1);
1364 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1366 INT height = fully ? descr->items[index].height : 1;
1367 for (top = index; top > descr->top_item; top--)
1368 if ((height += descr->items[top-1].height) > descr->height) break;
1370 else
1372 if (index < descr->top_item + descr->page_size) return;
1373 if (!fully && (index == descr->top_item + descr->page_size) &&
1374 (descr->height > (descr->page_size * descr->item_height))) return;
1375 top = index - descr->page_size + 1;
1377 LISTBOX_SetTopItem( descr, top, TRUE );
1380 /***********************************************************************
1381 * LISTBOX_SetCaretIndex
1383 * NOTES
1384 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1387 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1389 INT oldfocus = descr->focus_item;
1391 TRACE("old focus %d, index %d\n", oldfocus, index);
1393 if (descr->style & LBS_NOSEL) return LB_ERR;
1394 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1395 if (index == oldfocus) return LB_OKAY;
1397 LISTBOX_DrawFocusRect( descr, FALSE );
1398 descr->focus_item = index;
1400 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1401 LISTBOX_DrawFocusRect( descr, TRUE );
1403 return LB_OKAY;
1407 /***********************************************************************
1408 * LISTBOX_SelectItemRange
1410 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1412 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1413 INT last, BOOL on )
1415 INT i;
1417 /* A few sanity checks */
1419 if (descr->style & LBS_NOSEL) return LB_ERR;
1420 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1422 if (!descr->nb_items) return LB_OKAY;
1424 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1425 if (first < 0) first = 0;
1426 if (last < first) return LB_OKAY;
1428 if (on) /* Turn selection on */
1430 for (i = first; i <= last; i++)
1432 if (descr->items[i].selected) continue;
1433 descr->items[i].selected = TRUE;
1434 LISTBOX_InvalidateItemRect(descr, i);
1437 else /* Turn selection off */
1439 for (i = first; i <= last; i++)
1441 if (!descr->items[i].selected) continue;
1442 descr->items[i].selected = FALSE;
1443 LISTBOX_InvalidateItemRect(descr, i);
1446 return LB_OKAY;
1449 /***********************************************************************
1450 * LISTBOX_SetSelection
1452 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1453 BOOL on, BOOL send_notify )
1455 TRACE( "cur_sel=%d index=%d notify=%s\n",
1456 descr->selected_item, index, send_notify ? "YES" : "NO" );
1458 if (descr->style & LBS_NOSEL)
1460 descr->selected_item = index;
1461 return LB_ERR;
1463 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1464 if (descr->style & LBS_MULTIPLESEL)
1466 if (index == -1) /* Select all items */
1467 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1468 else /* Only one item */
1469 return LISTBOX_SelectItemRange( descr, index, index, on );
1471 else
1473 INT oldsel = descr->selected_item;
1474 if (index == oldsel) return LB_OKAY;
1475 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1476 if (index != -1) descr->items[index].selected = TRUE;
1477 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1478 descr->selected_item = index;
1479 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1480 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1481 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1482 else
1483 if( descr->lphc ) /* set selection change flag for parent combo */
1484 descr->lphc->wState |= CBF_SELCHANGE;
1486 return LB_OKAY;
1490 /***********************************************************************
1491 * LISTBOX_MoveCaret
1493 * Change the caret position and extend the selection to the new caret.
1495 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1497 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1499 if ((index < 0) || (index >= descr->nb_items))
1500 return;
1502 /* Important, repaint needs to be done in this order if
1503 you want to mimic Windows behavior:
1504 1. Remove the focus and paint the item
1505 2. Remove the selection and paint the item(s)
1506 3. Set the selection and repaint the item(s)
1507 4. Set the focus to 'index' and repaint the item */
1509 /* 1. remove the focus and repaint the item */
1510 LISTBOX_DrawFocusRect( descr, FALSE );
1512 /* 2. then turn off the previous selection */
1513 /* 3. repaint the new selected item */
1514 if (descr->style & LBS_EXTENDEDSEL)
1516 if (descr->anchor_item != -1)
1518 INT first = min( index, descr->anchor_item );
1519 INT last = max( index, descr->anchor_item );
1520 if (first > 0)
1521 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1522 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1523 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1526 else if (!(descr->style & LBS_MULTIPLESEL))
1528 /* Set selection to new caret item */
1529 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1532 /* 4. repaint the new item with the focus */
1533 descr->focus_item = index;
1534 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1535 LISTBOX_DrawFocusRect( descr, TRUE );
1539 /***********************************************************************
1540 * LISTBOX_InsertItem
1542 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1543 LPWSTR str, ULONG_PTR data )
1545 LB_ITEMDATA *item;
1546 INT max_items;
1547 INT oldfocus = descr->focus_item;
1549 if (index == -1) index = descr->nb_items;
1550 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1551 if (!descr->items) max_items = 0;
1552 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1553 if (descr->nb_items == max_items)
1555 /* We need to grow the array */
1556 max_items += LB_ARRAY_GRANULARITY;
1557 if (descr->items)
1558 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1559 max_items * sizeof(LB_ITEMDATA) );
1560 else
1561 item = HeapAlloc( GetProcessHeap(), 0,
1562 max_items * sizeof(LB_ITEMDATA) );
1563 if (!item)
1565 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1566 return LB_ERRSPACE;
1568 descr->items = item;
1571 /* Insert the item structure */
1573 item = &descr->items[index];
1574 if (index < descr->nb_items)
1575 RtlMoveMemory( item + 1, item,
1576 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1577 item->str = str;
1578 item->data = data;
1579 item->height = 0;
1580 item->selected = FALSE;
1581 descr->nb_items++;
1583 /* Get item height */
1585 if (descr->style & LBS_OWNERDRAWVARIABLE)
1587 MEASUREITEMSTRUCT mis;
1588 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1590 mis.CtlType = ODT_LISTBOX;
1591 mis.CtlID = id;
1592 mis.itemID = index;
1593 mis.itemData = descr->items[index].data;
1594 mis.itemHeight = descr->item_height;
1595 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1596 item->height = mis.itemHeight ? mis.itemHeight : 1;
1597 TRACE("[%p]: measure item %d (%s) = %d\n",
1598 descr->self, index, str ? debugstr_w(str) : "", item->height );
1601 /* Repaint the items */
1603 LISTBOX_UpdateScroll( descr );
1604 LISTBOX_InvalidateItems( descr, index );
1606 /* Move selection and focused item */
1607 /* If listbox was empty, set focus to the first item */
1608 if (descr->nb_items == 1)
1609 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1610 /* single select don't change selection index in win31 */
1611 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1613 descr->selected_item++;
1614 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1616 else
1618 if (index <= descr->selected_item)
1620 descr->selected_item++;
1621 descr->focus_item = oldfocus; /* focus not changed */
1624 return LB_OKAY;
1628 /***********************************************************************
1629 * LISTBOX_InsertString
1631 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1633 LPWSTR new_str = NULL;
1634 ULONG_PTR data = 0;
1635 LRESULT ret;
1637 if (HAS_STRINGS(descr))
1639 static const WCHAR empty_stringW[] = { 0 };
1640 if (!str) str = empty_stringW;
1641 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1643 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1644 return LB_ERRSPACE;
1646 strcpyW(new_str, str);
1648 else data = (ULONG_PTR)str;
1650 if (index == -1) index = descr->nb_items;
1651 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1653 HeapFree( GetProcessHeap(), 0, new_str );
1654 return ret;
1657 TRACE("[%p]: added item %d %s\n",
1658 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1659 return index;
1663 /***********************************************************************
1664 * LISTBOX_DeleteItem
1666 * Delete the content of an item. 'index' must be a valid index.
1668 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1670 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1671 * while Win95 sends it for all items with user data.
1672 * It's probably better to send it too often than not
1673 * often enough, so this is what we do here.
1675 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1677 DELETEITEMSTRUCT dis;
1678 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1680 dis.CtlType = ODT_LISTBOX;
1681 dis.CtlID = id;
1682 dis.itemID = index;
1683 dis.hwndItem = descr->self;
1684 dis.itemData = descr->items[index].data;
1685 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1687 if (HAS_STRINGS(descr))
1688 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1692 /***********************************************************************
1693 * LISTBOX_RemoveItem
1695 * Remove an item from the listbox and delete its content.
1697 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1699 LB_ITEMDATA *item;
1700 INT max_items;
1702 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1704 /* We need to invalidate the original rect instead of the updated one. */
1705 LISTBOX_InvalidateItems( descr, index );
1707 LISTBOX_DeleteItem( descr, index );
1709 /* Remove the item */
1711 item = &descr->items[index];
1712 if (index < descr->nb_items-1)
1713 RtlMoveMemory( item, item + 1,
1714 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1715 descr->nb_items--;
1716 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1718 /* Shrink the item array if possible */
1720 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1721 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1723 max_items -= LB_ARRAY_GRANULARITY;
1724 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1725 max_items * sizeof(LB_ITEMDATA) );
1726 if (item) descr->items = item;
1728 /* Repaint the items */
1730 LISTBOX_UpdateScroll( descr );
1731 /* if we removed the scrollbar, reset the top of the list
1732 (correct for owner-drawn ???) */
1733 if (descr->nb_items == descr->page_size)
1734 LISTBOX_SetTopItem( descr, 0, TRUE );
1736 /* Move selection and focused item */
1737 if (!IS_MULTISELECT(descr))
1739 if (index == descr->selected_item)
1740 descr->selected_item = -1;
1741 else if (index < descr->selected_item)
1743 descr->selected_item--;
1744 if (ISWIN31) /* win 31 do not change the selected item number */
1745 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1749 if (descr->focus_item >= descr->nb_items)
1751 descr->focus_item = descr->nb_items - 1;
1752 if (descr->focus_item < 0) descr->focus_item = 0;
1754 return LB_OKAY;
1758 /***********************************************************************
1759 * LISTBOX_ResetContent
1761 static void LISTBOX_ResetContent( LB_DESCR *descr )
1763 INT i;
1765 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1766 HeapFree( GetProcessHeap(), 0, descr->items );
1767 descr->nb_items = 0;
1768 descr->top_item = 0;
1769 descr->selected_item = -1;
1770 descr->focus_item = 0;
1771 descr->anchor_item = -1;
1772 descr->items = NULL;
1776 /***********************************************************************
1777 * LISTBOX_SetCount
1779 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1781 LRESULT ret;
1783 if (HAS_STRINGS(descr))
1785 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1786 return LB_ERR;
1789 /* FIXME: this is far from optimal... */
1790 if (count > descr->nb_items)
1792 while (count > descr->nb_items)
1793 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1794 return ret;
1796 else if (count < descr->nb_items)
1798 while (count < descr->nb_items)
1799 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1800 return ret;
1802 return LB_OKAY;
1806 /***********************************************************************
1807 * LISTBOX_Directory
1809 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1810 LPCWSTR filespec, BOOL long_names )
1812 HANDLE handle;
1813 LRESULT ret = LB_OKAY;
1814 WIN32_FIND_DATAW entry;
1815 int pos;
1817 /* don't scan directory if we just want drives exclusively */
1818 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1819 /* scan directory */
1820 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1822 int le = GetLastError();
1823 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1825 else
1829 WCHAR buffer[270];
1830 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1832 static const WCHAR bracketW[] = { ']',0 };
1833 static const WCHAR dotW[] = { '.',0 };
1834 if (!(attrib & DDL_DIRECTORY) ||
1835 !strcmpW( entry.cFileName, dotW )) continue;
1836 buffer[0] = '[';
1837 if (!long_names && entry.cAlternateFileName[0])
1838 strcpyW( buffer + 1, entry.cAlternateFileName );
1839 else
1840 strcpyW( buffer + 1, entry.cFileName );
1841 strcatW(buffer, bracketW);
1843 else /* not a directory */
1845 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1846 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1848 if ((attrib & DDL_EXCLUSIVE) &&
1849 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1850 continue;
1851 #undef ATTRIBS
1852 if (!long_names && entry.cAlternateFileName[0])
1853 strcpyW( buffer, entry.cAlternateFileName );
1854 else
1855 strcpyW( buffer, entry.cFileName );
1857 if (!long_names) CharLowerW( buffer );
1858 pos = LISTBOX_FindFileStrPos( descr, buffer );
1859 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1860 break;
1861 } while (FindNextFileW( handle, &entry ));
1862 FindClose( handle );
1866 /* scan drives */
1867 if ((ret >= 0) && (attrib & DDL_DRIVES))
1869 WCHAR buffer[] = {'[','-','a','-',']',0};
1870 WCHAR root[] = {'A',':','\\',0};
1871 int drive;
1872 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1874 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1875 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1876 break;
1879 return ret;
1883 /***********************************************************************
1884 * LISTBOX_HandleVScroll
1886 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1888 SCROLLINFO info;
1890 if (descr->style & LBS_MULTICOLUMN) return 0;
1891 switch(scrollReq)
1893 case SB_LINEUP:
1894 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1895 break;
1896 case SB_LINEDOWN:
1897 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1898 break;
1899 case SB_PAGEUP:
1900 LISTBOX_SetTopItem( descr, descr->top_item -
1901 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1902 break;
1903 case SB_PAGEDOWN:
1904 LISTBOX_SetTopItem( descr, descr->top_item +
1905 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1906 break;
1907 case SB_THUMBPOSITION:
1908 LISTBOX_SetTopItem( descr, pos, TRUE );
1909 break;
1910 case SB_THUMBTRACK:
1911 info.cbSize = sizeof(info);
1912 info.fMask = SIF_TRACKPOS;
1913 GetScrollInfo( descr->self, SB_VERT, &info );
1914 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1915 break;
1916 case SB_TOP:
1917 LISTBOX_SetTopItem( descr, 0, TRUE );
1918 break;
1919 case SB_BOTTOM:
1920 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1921 break;
1923 return 0;
1927 /***********************************************************************
1928 * LISTBOX_HandleHScroll
1930 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1932 SCROLLINFO info;
1933 INT page;
1935 if (descr->style & LBS_MULTICOLUMN)
1937 switch(scrollReq)
1939 case SB_LINELEFT:
1940 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1941 TRUE );
1942 break;
1943 case SB_LINERIGHT:
1944 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1945 TRUE );
1946 break;
1947 case SB_PAGELEFT:
1948 page = descr->width / descr->column_width;
1949 if (page < 1) page = 1;
1950 LISTBOX_SetTopItem( descr,
1951 descr->top_item - page * descr->page_size, TRUE );
1952 break;
1953 case SB_PAGERIGHT:
1954 page = descr->width / descr->column_width;
1955 if (page < 1) page = 1;
1956 LISTBOX_SetTopItem( descr,
1957 descr->top_item + page * descr->page_size, TRUE );
1958 break;
1959 case SB_THUMBPOSITION:
1960 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1961 break;
1962 case SB_THUMBTRACK:
1963 info.cbSize = sizeof(info);
1964 info.fMask = SIF_TRACKPOS;
1965 GetScrollInfo( descr->self, SB_VERT, &info );
1966 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1967 TRUE );
1968 break;
1969 case SB_LEFT:
1970 LISTBOX_SetTopItem( descr, 0, TRUE );
1971 break;
1972 case SB_RIGHT:
1973 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1974 break;
1977 else if (descr->horz_extent)
1979 switch(scrollReq)
1981 case SB_LINELEFT:
1982 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1983 break;
1984 case SB_LINERIGHT:
1985 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1986 break;
1987 case SB_PAGELEFT:
1988 LISTBOX_SetHorizontalPos( descr,
1989 descr->horz_pos - descr->width );
1990 break;
1991 case SB_PAGERIGHT:
1992 LISTBOX_SetHorizontalPos( descr,
1993 descr->horz_pos + descr->width );
1994 break;
1995 case SB_THUMBPOSITION:
1996 LISTBOX_SetHorizontalPos( descr, pos );
1997 break;
1998 case SB_THUMBTRACK:
1999 info.cbSize = sizeof(info);
2000 info.fMask = SIF_TRACKPOS;
2001 GetScrollInfo( descr->self, SB_HORZ, &info );
2002 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2003 break;
2004 case SB_LEFT:
2005 LISTBOX_SetHorizontalPos( descr, 0 );
2006 break;
2007 case SB_RIGHT:
2008 LISTBOX_SetHorizontalPos( descr,
2009 descr->horz_extent - descr->width );
2010 break;
2013 return 0;
2016 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2018 short gcWheelDelta = 0;
2019 UINT pulScrollLines = 3;
2021 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2023 gcWheelDelta -= delta;
2025 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2027 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2028 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2029 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2031 return 0;
2034 /***********************************************************************
2035 * LISTBOX_HandleLButtonDown
2037 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2039 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2041 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2042 descr->self, x, y, index, descr->focus_item);
2044 if (!descr->caret_on && (descr->in_focus)) return 0;
2046 if (!descr->in_focus)
2048 if( !descr->lphc ) SetFocus( descr->self );
2049 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2052 if (index == -1) return 0;
2054 if (!descr->lphc)
2056 if (descr->style & LBS_NOTIFY )
2057 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2058 MAKELPARAM( x, y ) );
2061 descr->captured = TRUE;
2062 SetCapture( descr->self );
2064 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2066 /* we should perhaps make sure that all items are deselected
2067 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2068 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2069 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2072 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2073 if (keys & MK_CONTROL)
2075 LISTBOX_SetCaretIndex( descr, index, FALSE );
2076 LISTBOX_SetSelection( descr, index,
2077 !descr->items[index].selected,
2078 (descr->style & LBS_NOTIFY) != 0);
2080 else
2082 LISTBOX_MoveCaret( descr, index, FALSE );
2084 if (descr->style & LBS_EXTENDEDSEL)
2086 LISTBOX_SetSelection( descr, index,
2087 descr->items[index].selected,
2088 (descr->style & LBS_NOTIFY) != 0 );
2090 else
2092 LISTBOX_SetSelection( descr, index,
2093 !descr->items[index].selected,
2094 (descr->style & LBS_NOTIFY) != 0 );
2098 else
2100 descr->anchor_item = index;
2101 LISTBOX_MoveCaret( descr, index, FALSE );
2102 LISTBOX_SetSelection( descr, index,
2103 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2106 if (!descr->lphc)
2108 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2110 POINT pt;
2112 pt.x = x;
2113 pt.y = y;
2115 if (DragDetect( descr->self, pt ))
2116 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2119 return 0;
2123 /*************************************************************************
2124 * LISTBOX_HandleLButtonDownCombo [Internal]
2126 * Process LButtonDown message for the ComboListBox
2128 * PARAMS
2129 * pWnd [I] The windows internal structure
2130 * pDescr [I] The ListBox internal structure
2131 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2132 * x [I] X Mouse Coordinate
2133 * y [I] Y Mouse Coordinate
2135 * RETURNS
2136 * 0 since we are processing the WM_LBUTTONDOWN Message
2138 * NOTES
2139 * This function is only to be used when a ListBox is a ComboListBox
2142 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2144 RECT clientRect, screenRect;
2145 POINT mousePos;
2147 mousePos.x = x;
2148 mousePos.y = y;
2150 GetClientRect(descr->self, &clientRect);
2152 if(PtInRect(&clientRect, mousePos))
2154 /* MousePos is in client, resume normal processing */
2155 if (msg == WM_LBUTTONDOWN)
2157 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2158 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2160 else if (descr->style & LBS_NOTIFY)
2161 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2163 else
2165 POINT screenMousePos;
2166 HWND hWndOldCapture;
2168 /* Check the Non-Client Area */
2169 screenMousePos = mousePos;
2170 hWndOldCapture = GetCapture();
2171 ReleaseCapture();
2172 GetWindowRect(descr->self, &screenRect);
2173 ClientToScreen(descr->self, &screenMousePos);
2175 if(!PtInRect(&screenRect, screenMousePos))
2177 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2178 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2179 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2181 else
2183 /* Check to see the NC is a scrollbar */
2184 INT nHitTestType=0;
2185 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2186 /* Check Vertical scroll bar */
2187 if (style & WS_VSCROLL)
2189 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2190 if (PtInRect( &clientRect, mousePos ))
2191 nHitTestType = HTVSCROLL;
2193 /* Check horizontal scroll bar */
2194 if (style & WS_HSCROLL)
2196 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2197 if (PtInRect( &clientRect, mousePos ))
2198 nHitTestType = HTHSCROLL;
2200 /* Windows sends this message when a scrollbar is clicked
2203 if(nHitTestType != 0)
2205 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2206 MAKELONG(screenMousePos.x, screenMousePos.y));
2208 /* Resume the Capture after scrolling is complete
2210 if(hWndOldCapture != 0)
2211 SetCapture(hWndOldCapture);
2214 return 0;
2217 /***********************************************************************
2218 * LISTBOX_HandleLButtonUp
2220 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2222 if (LISTBOX_Timer != LB_TIMER_NONE)
2223 KillSystemTimer( descr->self, LB_TIMER_ID );
2224 LISTBOX_Timer = LB_TIMER_NONE;
2225 if (descr->captured)
2227 descr->captured = FALSE;
2228 if (GetCapture() == descr->self) ReleaseCapture();
2229 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2230 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2232 return 0;
2236 /***********************************************************************
2237 * LISTBOX_HandleTimer
2239 * Handle scrolling upon a timer event.
2240 * Return TRUE if scrolling should continue.
2242 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2244 switch(dir)
2246 case LB_TIMER_UP:
2247 if (descr->top_item) index = descr->top_item - 1;
2248 else index = 0;
2249 break;
2250 case LB_TIMER_LEFT:
2251 if (descr->top_item) index -= descr->page_size;
2252 break;
2253 case LB_TIMER_DOWN:
2254 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2255 if (index == descr->focus_item) index++;
2256 if (index >= descr->nb_items) index = descr->nb_items - 1;
2257 break;
2258 case LB_TIMER_RIGHT:
2259 if (index + descr->page_size < descr->nb_items)
2260 index += descr->page_size;
2261 break;
2262 case LB_TIMER_NONE:
2263 break;
2265 if (index == descr->focus_item) return FALSE;
2266 LISTBOX_MoveCaret( descr, index, FALSE );
2267 return TRUE;
2271 /***********************************************************************
2272 * LISTBOX_HandleSystemTimer
2274 * WM_SYSTIMER handler.
2276 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2278 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2280 KillSystemTimer( descr->self, LB_TIMER_ID );
2281 LISTBOX_Timer = LB_TIMER_NONE;
2283 return 0;
2287 /***********************************************************************
2288 * LISTBOX_HandleMouseMove
2290 * WM_MOUSEMOVE handler.
2292 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2293 INT x, INT y )
2295 INT index;
2296 TIMER_DIRECTION dir = LB_TIMER_NONE;
2298 if (!descr->captured) return;
2300 if (descr->style & LBS_MULTICOLUMN)
2302 if (y < 0) y = 0;
2303 else if (y >= descr->item_height * descr->page_size)
2304 y = descr->item_height * descr->page_size - 1;
2306 if (x < 0)
2308 dir = LB_TIMER_LEFT;
2309 x = 0;
2311 else if (x >= descr->width)
2313 dir = LB_TIMER_RIGHT;
2314 x = descr->width - 1;
2317 else
2319 if (y < 0) dir = LB_TIMER_UP; /* above */
2320 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2323 index = LISTBOX_GetItemFromPoint( descr, x, y );
2324 if (index == -1) index = descr->focus_item;
2325 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2327 /* Start/stop the system timer */
2329 if (dir != LB_TIMER_NONE)
2330 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2331 else if (LISTBOX_Timer != LB_TIMER_NONE)
2332 KillSystemTimer( descr->self, LB_TIMER_ID );
2333 LISTBOX_Timer = dir;
2337 /***********************************************************************
2338 * LISTBOX_HandleKeyDown
2340 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2342 INT caret = -1;
2343 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2344 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2345 bForceSelection = FALSE; /* only for single select list */
2347 if (descr->style & LBS_WANTKEYBOARDINPUT)
2349 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2350 MAKEWPARAM(LOWORD(key), descr->focus_item),
2351 (LPARAM)descr->self );
2352 if (caret == -2) return 0;
2354 if (caret == -1) switch(key)
2356 case VK_LEFT:
2357 if (descr->style & LBS_MULTICOLUMN)
2359 bForceSelection = FALSE;
2360 if (descr->focus_item >= descr->page_size)
2361 caret = descr->focus_item - descr->page_size;
2362 break;
2364 /* fall through */
2365 case VK_UP:
2366 caret = descr->focus_item - 1;
2367 if (caret < 0) caret = 0;
2368 break;
2369 case VK_RIGHT:
2370 if (descr->style & LBS_MULTICOLUMN)
2372 bForceSelection = FALSE;
2373 if (descr->focus_item + descr->page_size < descr->nb_items)
2374 caret = descr->focus_item + descr->page_size;
2375 break;
2377 /* fall through */
2378 case VK_DOWN:
2379 caret = descr->focus_item + 1;
2380 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2381 break;
2383 case VK_PRIOR:
2384 if (descr->style & LBS_MULTICOLUMN)
2386 INT page = descr->width / descr->column_width;
2387 if (page < 1) page = 1;
2388 caret = descr->focus_item - (page * descr->page_size) + 1;
2390 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2391 if (caret < 0) caret = 0;
2392 break;
2393 case VK_NEXT:
2394 if (descr->style & LBS_MULTICOLUMN)
2396 INT page = descr->width / descr->column_width;
2397 if (page < 1) page = 1;
2398 caret = descr->focus_item + (page * descr->page_size) - 1;
2400 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2401 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2402 break;
2403 case VK_HOME:
2404 caret = 0;
2405 break;
2406 case VK_END:
2407 caret = descr->nb_items - 1;
2408 break;
2409 case VK_SPACE:
2410 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2411 else if (descr->style & LBS_MULTIPLESEL)
2413 LISTBOX_SetSelection( descr, descr->focus_item,
2414 !descr->items[descr->focus_item].selected,
2415 (descr->style & LBS_NOTIFY) != 0 );
2417 break;
2418 default:
2419 bForceSelection = FALSE;
2421 if (bForceSelection) /* focused item is used instead of key */
2422 caret = descr->focus_item;
2423 if (caret >= 0)
2425 if (((descr->style & LBS_EXTENDEDSEL) &&
2426 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2427 !IS_MULTISELECT(descr))
2428 descr->anchor_item = caret;
2429 LISTBOX_MoveCaret( descr, caret, TRUE );
2431 if (descr->style & LBS_MULTIPLESEL)
2432 descr->selected_item = caret;
2433 else
2434 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2435 if (descr->style & LBS_NOTIFY)
2437 if( descr->lphc )
2439 /* make sure that combo parent doesn't hide us */
2440 descr->lphc->wState |= CBF_NOROLLUP;
2442 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2445 return 0;
2449 /***********************************************************************
2450 * LISTBOX_HandleChar
2452 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2454 INT caret = -1;
2455 WCHAR str[2];
2457 str[0] = charW;
2458 str[1] = '\0';
2460 if (descr->style & LBS_WANTKEYBOARDINPUT)
2462 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2463 MAKEWPARAM(charW, descr->focus_item),
2464 (LPARAM)descr->self );
2465 if (caret == -2) return 0;
2467 if (caret == -1)
2468 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2469 if (caret != -1)
2471 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2472 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2473 LISTBOX_MoveCaret( descr, caret, TRUE );
2474 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2475 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2477 return 0;
2481 /***********************************************************************
2482 * LISTBOX_Create
2484 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2486 LB_DESCR *descr;
2487 MEASUREITEMSTRUCT mis;
2488 RECT rect;
2490 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2491 return FALSE;
2493 GetClientRect( hwnd, &rect );
2494 descr->self = hwnd;
2495 descr->owner = GetParent( descr->self );
2496 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2497 descr->width = rect.right - rect.left;
2498 descr->height = rect.bottom - rect.top;
2499 descr->items = NULL;
2500 descr->nb_items = 0;
2501 descr->top_item = 0;
2502 descr->selected_item = -1;
2503 descr->focus_item = 0;
2504 descr->anchor_item = -1;
2505 descr->item_height = 1;
2506 descr->page_size = 1;
2507 descr->column_width = 150;
2508 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2509 descr->horz_pos = 0;
2510 descr->nb_tabs = 0;
2511 descr->tabs = NULL;
2512 descr->caret_on = lphc ? FALSE : TRUE;
2513 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2514 descr->in_focus = FALSE;
2515 descr->captured = FALSE;
2516 descr->font = 0;
2517 descr->locale = GetUserDefaultLCID();
2518 descr->lphc = lphc;
2520 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2522 /* Win95 document "List Box Differences" from MSDN:
2523 If a list box in a version 3.x application has either the
2524 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2525 horizontal and vertical scroll bars.
2527 descr->style |= WS_VSCROLL | WS_HSCROLL;
2530 if( lphc )
2532 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2533 descr->owner = lphc->self;
2536 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2538 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2540 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2541 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2542 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2543 descr->item_height = LISTBOX_SetFont( descr, 0 );
2545 if (descr->style & LBS_OWNERDRAWFIXED)
2547 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2549 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2550 descr->item_height = lphc->fixedOwnerDrawHeight;
2552 else
2554 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2555 mis.CtlType = ODT_LISTBOX;
2556 mis.CtlID = id;
2557 mis.itemID = -1;
2558 mis.itemWidth = 0;
2559 mis.itemData = 0;
2560 mis.itemHeight = descr->item_height;
2561 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2562 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2566 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2567 return TRUE;
2571 /***********************************************************************
2572 * LISTBOX_Destroy
2574 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2576 LISTBOX_ResetContent( descr );
2577 SetWindowLongPtrW( descr->self, 0, 0 );
2578 HeapFree( GetProcessHeap(), 0, descr );
2579 return TRUE;
2583 /***********************************************************************
2584 * ListBoxWndProc_common
2586 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2587 WPARAM wParam, LPARAM lParam, BOOL unicode )
2589 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2590 LPHEADCOMBO lphc = 0;
2591 LRESULT ret;
2593 if (!descr)
2595 if (!IsWindow(hwnd)) return 0;
2597 if (msg == WM_CREATE)
2599 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2600 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2601 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2602 TRACE("creating wnd=%p descr=%x\n", hwnd, GetWindowLongPtrW( hwnd, 0 ) );
2603 return 0;
2605 /* Ignore all other messages before we get a WM_CREATE */
2606 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2607 DefWindowProcA( hwnd, msg, wParam, lParam );
2609 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2611 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2612 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2614 switch(msg)
2616 case LB_RESETCONTENT16:
2617 case LB_RESETCONTENT:
2618 LISTBOX_ResetContent( descr );
2619 LISTBOX_UpdateScroll( descr );
2620 InvalidateRect( descr->self, NULL, TRUE );
2621 return 0;
2623 case LB_ADDSTRING16:
2624 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2625 /* fall through */
2626 case LB_ADDSTRING:
2628 INT ret;
2629 LPWSTR textW;
2630 if(unicode || !HAS_STRINGS(descr))
2631 textW = (LPWSTR)lParam;
2632 else
2634 LPSTR textA = (LPSTR)lParam;
2635 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2636 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2637 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2639 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2640 ret = LISTBOX_InsertString( descr, wParam, textW );
2641 if (!unicode && HAS_STRINGS(descr))
2642 HeapFree(GetProcessHeap(), 0, textW);
2643 return ret;
2646 case LB_INSERTSTRING16:
2647 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2648 wParam = (INT)(INT16)wParam;
2649 /* fall through */
2650 case LB_INSERTSTRING:
2652 INT ret;
2653 LPWSTR textW;
2654 if(unicode || !HAS_STRINGS(descr))
2655 textW = (LPWSTR)lParam;
2656 else
2658 LPSTR textA = (LPSTR)lParam;
2659 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2660 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2661 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2663 ret = LISTBOX_InsertString( descr, wParam, textW );
2664 if(!unicode && HAS_STRINGS(descr))
2665 HeapFree(GetProcessHeap(), 0, textW);
2666 return ret;
2669 case LB_ADDFILE16:
2670 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2671 /* fall through */
2672 case LB_ADDFILE:
2674 INT ret;
2675 LPWSTR textW;
2676 if(unicode || !HAS_STRINGS(descr))
2677 textW = (LPWSTR)lParam;
2678 else
2680 LPSTR textA = (LPSTR)lParam;
2681 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2682 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2683 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2685 wParam = LISTBOX_FindFileStrPos( descr, textW );
2686 ret = LISTBOX_InsertString( descr, wParam, textW );
2687 if(!unicode && HAS_STRINGS(descr))
2688 HeapFree(GetProcessHeap(), 0, textW);
2689 return ret;
2692 case LB_DELETESTRING16:
2693 case LB_DELETESTRING:
2694 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2695 return descr->nb_items;
2696 else
2698 SetLastError(ERROR_INVALID_INDEX);
2699 return LB_ERR;
2702 case LB_GETITEMDATA16:
2703 case LB_GETITEMDATA:
2704 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2706 SetLastError(ERROR_INVALID_INDEX);
2707 return LB_ERR;
2709 return descr->items[wParam].data;
2711 case LB_SETITEMDATA16:
2712 case LB_SETITEMDATA:
2713 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2715 SetLastError(ERROR_INVALID_INDEX);
2716 return LB_ERR;
2718 descr->items[wParam].data = lParam;
2719 /* undocumented: returns TRUE, not LB_OKAY (0) */
2720 return TRUE;
2722 case LB_GETCOUNT16:
2723 case LB_GETCOUNT:
2724 return descr->nb_items;
2726 case LB_GETTEXT16:
2727 lParam = (LPARAM)MapSL(lParam);
2728 /* fall through */
2729 case LB_GETTEXT:
2730 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2732 case LB_GETTEXTLEN16:
2733 /* fall through */
2734 case LB_GETTEXTLEN:
2735 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2737 SetLastError(ERROR_INVALID_INDEX);
2738 return LB_ERR;
2740 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2741 if (unicode) return strlenW( descr->items[wParam].str );
2742 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2743 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2745 case LB_GETCURSEL16:
2746 case LB_GETCURSEL:
2747 if (descr->nb_items == 0)
2748 return LB_ERR;
2749 if (!IS_MULTISELECT(descr))
2750 return descr->selected_item;
2751 if (descr->selected_item != -1)
2752 return descr->selected_item;
2753 return descr->focus_item;
2754 /* otherwise, if the user tries to move the selection with the */
2755 /* arrow keys, we will give the application something to choke on */
2756 case LB_GETTOPINDEX16:
2757 case LB_GETTOPINDEX:
2758 return descr->top_item;
2760 case LB_GETITEMHEIGHT16:
2761 case LB_GETITEMHEIGHT:
2762 return LISTBOX_GetItemHeight( descr, wParam );
2764 case LB_SETITEMHEIGHT16:
2765 lParam = LOWORD(lParam);
2766 /* fall through */
2767 case LB_SETITEMHEIGHT:
2768 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2770 case LB_ITEMFROMPOINT:
2772 POINT pt;
2773 RECT rect;
2774 int index;
2775 BOOL hit = TRUE;
2777 /* The hiword of the return value is not a client area
2778 hittest as suggested by MSDN, but rather a hittest on
2779 the returned listbox item. */
2781 if(descr->nb_items == 0)
2782 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2784 pt.x = (short)LOWORD(lParam);
2785 pt.y = (short)HIWORD(lParam);
2787 SetRect(&rect, 0, 0, descr->width, descr->height);
2789 if(!PtInRect(&rect, pt))
2791 pt.x = min(pt.x, rect.right - 1);
2792 pt.x = max(pt.x, 0);
2793 pt.y = min(pt.y, rect.bottom - 1);
2794 pt.y = max(pt.y, 0);
2795 hit = FALSE;
2798 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2800 if(index == -1)
2802 index = descr->nb_items - 1;
2803 hit = FALSE;
2805 return MAKELONG(index, hit ? 0 : 1);
2808 case LB_SETCARETINDEX16:
2809 case LB_SETCARETINDEX:
2810 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2811 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2812 return LB_ERR;
2813 else if (ISWIN31)
2814 return wParam;
2815 else
2816 return LB_OKAY;
2818 case LB_GETCARETINDEX16:
2819 case LB_GETCARETINDEX:
2820 return descr->focus_item;
2822 case LB_SETTOPINDEX16:
2823 case LB_SETTOPINDEX:
2824 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2826 case LB_SETCOLUMNWIDTH16:
2827 case LB_SETCOLUMNWIDTH:
2828 return LISTBOX_SetColumnWidth( descr, wParam );
2830 case LB_GETITEMRECT16:
2832 RECT rect;
2833 RECT16 *r16 = MapSL(lParam);
2834 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2835 r16->left = rect.left;
2836 r16->top = rect.top;
2837 r16->right = rect.right;
2838 r16->bottom = rect.bottom;
2840 return ret;
2842 case LB_GETITEMRECT:
2843 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2845 case LB_FINDSTRING16:
2846 wParam = (INT)(INT16)wParam;
2847 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2848 /* fall through */
2849 case LB_FINDSTRING:
2851 INT ret;
2852 LPWSTR textW;
2853 if(unicode || !HAS_STRINGS(descr))
2854 textW = (LPWSTR)lParam;
2855 else
2857 LPSTR textA = (LPSTR)lParam;
2858 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2859 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2860 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2862 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2863 if(!unicode && HAS_STRINGS(descr))
2864 HeapFree(GetProcessHeap(), 0, textW);
2865 return ret;
2868 case LB_FINDSTRINGEXACT16:
2869 wParam = (INT)(INT16)wParam;
2870 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2871 /* fall through */
2872 case LB_FINDSTRINGEXACT:
2874 INT ret;
2875 LPWSTR textW;
2876 if(unicode || !HAS_STRINGS(descr))
2877 textW = (LPWSTR)lParam;
2878 else
2880 LPSTR textA = (LPSTR)lParam;
2881 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2882 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2883 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2885 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2886 if(!unicode && HAS_STRINGS(descr))
2887 HeapFree(GetProcessHeap(), 0, textW);
2888 return ret;
2891 case LB_SELECTSTRING16:
2892 wParam = (INT)(INT16)wParam;
2893 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2894 /* fall through */
2895 case LB_SELECTSTRING:
2897 INT index;
2898 LPWSTR textW;
2900 if(HAS_STRINGS(descr))
2901 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2902 debugstr_a((LPSTR)lParam));
2903 if(unicode || !HAS_STRINGS(descr))
2904 textW = (LPWSTR)lParam;
2905 else
2907 LPSTR textA = (LPSTR)lParam;
2908 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2909 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2910 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2912 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2913 if(!unicode && HAS_STRINGS(descr))
2914 HeapFree(GetProcessHeap(), 0, textW);
2915 if (index != LB_ERR)
2917 LISTBOX_MoveCaret( descr, index, TRUE );
2918 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2920 return index;
2923 case LB_GETSEL16:
2924 wParam = (INT)(INT16)wParam;
2925 /* fall through */
2926 case LB_GETSEL:
2927 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2928 return LB_ERR;
2929 return descr->items[wParam].selected;
2931 case LB_SETSEL16:
2932 lParam = (INT)(INT16)lParam;
2933 /* fall through */
2934 case LB_SETSEL:
2935 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2937 case LB_SETCURSEL16:
2938 wParam = (INT)(INT16)wParam;
2939 /* fall through */
2940 case LB_SETCURSEL:
2941 if (IS_MULTISELECT(descr)) return LB_ERR;
2942 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2943 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2944 if (ret != LB_ERR) ret = descr->selected_item;
2945 return ret;
2947 case LB_GETSELCOUNT16:
2948 case LB_GETSELCOUNT:
2949 return LISTBOX_GetSelCount( descr );
2951 case LB_GETSELITEMS16:
2952 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2954 case LB_GETSELITEMS:
2955 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2957 case LB_SELITEMRANGE16:
2958 case LB_SELITEMRANGE:
2959 if (LOWORD(lParam) <= HIWORD(lParam))
2960 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2961 HIWORD(lParam), wParam );
2962 else
2963 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2964 LOWORD(lParam), wParam );
2966 case LB_SELITEMRANGEEX16:
2967 case LB_SELITEMRANGEEX:
2968 if ((INT)lParam >= (INT)wParam)
2969 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2970 else
2971 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2973 case LB_GETHORIZONTALEXTENT16:
2974 case LB_GETHORIZONTALEXTENT:
2975 return descr->horz_extent;
2977 case LB_SETHORIZONTALEXTENT16:
2978 case LB_SETHORIZONTALEXTENT:
2979 return LISTBOX_SetHorizontalExtent( descr, wParam );
2981 case LB_GETANCHORINDEX16:
2982 case LB_GETANCHORINDEX:
2983 return descr->anchor_item;
2985 case LB_SETANCHORINDEX16:
2986 wParam = (INT)(INT16)wParam;
2987 /* fall through */
2988 case LB_SETANCHORINDEX:
2989 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2991 SetLastError(ERROR_INVALID_INDEX);
2992 return LB_ERR;
2994 descr->anchor_item = (INT)wParam;
2995 return LB_OKAY;
2997 case LB_DIR16:
2998 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2999 * be set automatically (this is different in Win32) */
3000 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
3001 lParam = (LPARAM)MapSL(lParam);
3002 /* fall through */
3003 case LB_DIR:
3005 INT ret;
3006 LPWSTR textW;
3007 if(unicode)
3008 textW = (LPWSTR)lParam;
3009 else
3011 LPSTR textA = (LPSTR)lParam;
3012 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3013 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3014 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3016 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3017 if(!unicode)
3018 HeapFree(GetProcessHeap(), 0, textW);
3019 return ret;
3022 case LB_GETLOCALE:
3023 return descr->locale;
3025 case LB_SETLOCALE:
3027 LCID ret;
3028 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3029 return LB_ERR;
3030 ret = descr->locale;
3031 descr->locale = (LCID)wParam;
3032 return ret;
3035 case LB_INITSTORAGE:
3036 return LISTBOX_InitStorage( descr, wParam );
3038 case LB_SETCOUNT:
3039 return LISTBOX_SetCount( descr, (INT)wParam );
3041 case LB_SETTABSTOPS16:
3042 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
3044 case LB_SETTABSTOPS:
3045 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
3047 case LB_CARETON16:
3048 case LB_CARETON:
3049 if (descr->caret_on)
3050 return LB_OKAY;
3051 descr->caret_on = TRUE;
3052 if ((descr->focus_item != -1) && (descr->in_focus))
3053 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3054 return LB_OKAY;
3056 case LB_CARETOFF16:
3057 case LB_CARETOFF:
3058 if (!descr->caret_on)
3059 return LB_OKAY;
3060 descr->caret_on = FALSE;
3061 if ((descr->focus_item != -1) && (descr->in_focus))
3062 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3063 return LB_OKAY;
3065 case LB_GETLISTBOXINFO:
3066 FIXME("LB_GETLISTBOXINFO: stub!\n");
3067 return 0;
3069 case WM_DESTROY:
3070 return LISTBOX_Destroy( descr );
3072 case WM_ENABLE:
3073 InvalidateRect( descr->self, NULL, TRUE );
3074 return 0;
3076 case WM_SETREDRAW:
3077 LISTBOX_SetRedraw( descr, wParam != 0 );
3078 return 0;
3080 case WM_GETDLGCODE:
3081 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3083 case WM_PRINTCLIENT:
3084 case WM_PAINT:
3086 PAINTSTRUCT ps;
3087 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3088 ret = LISTBOX_Paint( descr, hdc );
3089 if( !wParam ) EndPaint( descr->self, &ps );
3091 return ret;
3092 case WM_SIZE:
3093 LISTBOX_UpdateSize( descr );
3094 return 0;
3095 case WM_GETFONT:
3096 return (LRESULT)descr->font;
3097 case WM_SETFONT:
3098 LISTBOX_SetFont( descr, (HFONT)wParam );
3099 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3100 return 0;
3101 case WM_SETFOCUS:
3102 descr->in_focus = TRUE;
3103 descr->caret_on = TRUE;
3104 if (descr->focus_item != -1)
3105 LISTBOX_DrawFocusRect( descr, TRUE );
3106 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3107 return 0;
3108 case WM_KILLFOCUS:
3109 descr->in_focus = FALSE;
3110 if ((descr->focus_item != -1) && descr->caret_on)
3111 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3112 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3113 return 0;
3114 case WM_HSCROLL:
3115 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3116 case WM_VSCROLL:
3117 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3118 case WM_MOUSEWHEEL:
3119 if (wParam & (MK_SHIFT | MK_CONTROL))
3120 return DefWindowProcW( descr->self, msg, wParam, lParam );
3121 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3122 case WM_LBUTTONDOWN:
3123 if (lphc)
3124 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3125 (INT16)LOWORD(lParam),
3126 (INT16)HIWORD(lParam) );
3127 return LISTBOX_HandleLButtonDown( descr, wParam,
3128 (INT16)LOWORD(lParam),
3129 (INT16)HIWORD(lParam) );
3130 case WM_LBUTTONDBLCLK:
3131 if (lphc)
3132 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3133 (INT16)LOWORD(lParam),
3134 (INT16)HIWORD(lParam) );
3135 if (descr->style & LBS_NOTIFY)
3136 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3137 return 0;
3138 case WM_MOUSEMOVE:
3139 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3141 BOOL captured = descr->captured;
3142 POINT mousePos;
3143 RECT clientRect;
3145 mousePos.x = (INT16)LOWORD(lParam);
3146 mousePos.y = (INT16)HIWORD(lParam);
3149 * If we are in a dropdown combobox, we simulate that
3150 * the mouse is captured to show the tracking of the item.
3152 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3153 descr->captured = TRUE;
3155 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3157 descr->captured = captured;
3159 else if (GetCapture() == descr->self)
3161 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3162 (INT16)HIWORD(lParam) );
3164 return 0;
3165 case WM_LBUTTONUP:
3166 if (lphc)
3168 POINT mousePos;
3169 RECT clientRect;
3172 * If the mouse button "up" is not in the listbox,
3173 * we make sure there is no selection by re-selecting the
3174 * item that was selected when the listbox was made visible.
3176 mousePos.x = (INT16)LOWORD(lParam);
3177 mousePos.y = (INT16)HIWORD(lParam);
3179 GetClientRect(descr->self, &clientRect);
3182 * When the user clicks outside the combobox and the focus
3183 * is lost, the owning combobox will send a fake buttonup with
3184 * 0xFFFFFFF as the mouse location, we must also revert the
3185 * selection to the original selection.
3187 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3188 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3190 return LISTBOX_HandleLButtonUp( descr );
3191 case WM_KEYDOWN:
3192 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3194 /* for some reason Windows makes it possible to
3195 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3197 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3198 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3199 && (wParam == VK_DOWN || wParam == VK_UP)) )
3201 COMBO_FlipListbox( lphc, FALSE, FALSE );
3202 return 0;
3205 return LISTBOX_HandleKeyDown( descr, wParam );
3206 case WM_CHAR:
3208 WCHAR charW;
3209 if(unicode)
3210 charW = (WCHAR)wParam;
3211 else
3213 CHAR charA = (CHAR)wParam;
3214 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3216 return LISTBOX_HandleChar( descr, charW );
3218 case WM_SYSTIMER:
3219 return LISTBOX_HandleSystemTimer( descr );
3220 case WM_ERASEBKGND:
3221 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3223 RECT rect;
3224 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3225 wParam, (LPARAM)descr->self );
3226 TRACE("hbrush = %p\n", hbrush);
3227 if(!hbrush)
3228 hbrush = GetSysColorBrush(COLOR_WINDOW);
3229 if(hbrush)
3231 GetClientRect(descr->self, &rect);
3232 FillRect((HDC)wParam, &rect, hbrush);
3235 return 1;
3236 case WM_DROPFILES:
3237 if( lphc ) return 0;
3238 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3239 SendMessageA( descr->owner, msg, wParam, lParam );
3241 case WM_NCDESTROY:
3242 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3243 lphc->hWndLBox = 0;
3244 break;
3246 case WM_NCACTIVATE:
3247 if (lphc) return 0;
3248 break;
3250 default:
3251 if ((msg >= WM_USER) && (msg < 0xc000))
3252 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3253 hwnd, msg, wParam, lParam );
3256 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3257 DefWindowProcA( hwnd, msg, wParam, lParam );
3260 /***********************************************************************
3261 * ListBoxWndProcA
3263 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3265 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3268 /***********************************************************************
3269 * ListBoxWndProcW
3271 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3273 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );