wininet: Make another test pass on IE6.
[wine.git] / dlls / user32 / listbox.c
blobee326c695285e6c0153a2ae234635e038852e870
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/exception.h"
47 #include "wine/debug.h"
49 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
51 /* Items array granularity */
52 #define LB_ARRAY_GRANULARITY 16
54 /* Scrolling timeout in ms */
55 #define LB_SCROLL_TIMEOUT 50
57 /* Listbox system timer id */
58 #define LB_TIMER_ID 2
60 /* flag listbox changed while setredraw false - internal style */
61 #define LBS_DISPLAYCHANGED 0x80000000
63 /* Item structure */
64 typedef struct
66 LPWSTR str; /* Item text */
67 BOOL selected; /* Is item selected? */
68 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
69 ULONG_PTR data; /* User data */
70 } LB_ITEMDATA;
72 /* Listbox structure */
73 typedef struct
75 HWND self; /* Our own window handle */
76 HWND owner; /* Owner window to send notifications to */
77 UINT style; /* Window style */
78 INT width; /* Window width */
79 INT height; /* Window height */
80 LB_ITEMDATA *items; /* Array of items */
81 INT nb_items; /* Number of items */
82 INT top_item; /* Top visible item */
83 INT selected_item; /* Selected item */
84 INT focus_item; /* Item that has the focus */
85 INT anchor_item; /* Anchor item for extended selection */
86 INT item_height; /* Default item height */
87 INT page_size; /* Items per listbox page */
88 INT column_width; /* Column width for multi-column listboxes */
89 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
90 INT horz_pos; /* Horizontal position */
91 INT nb_tabs; /* Number of tabs in array */
92 INT *tabs; /* Array of tabs */
93 INT avg_char_width; /* Average width of characters */
94 BOOL caret_on; /* Is caret on? */
95 BOOL captured; /* Is mouse captured? */
96 BOOL in_focus;
97 HFONT font; /* Current font */
98 LCID locale; /* Current locale for string comparisons */
99 LPHEADCOMBO lphc; /* ComboLBox */
100 } LB_DESCR;
103 #define IS_OWNERDRAW(descr) \
104 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
106 #define HAS_STRINGS(descr) \
107 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
110 #define IS_MULTISELECT(descr) \
111 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
112 !((descr)->style & LBS_NOSEL))
114 #define SEND_NOTIFICATION(descr,code) \
115 (SendMessageW( (descr)->owner, WM_COMMAND, \
116 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
118 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
120 /* Current timer status */
121 typedef enum
123 LB_TIMER_NONE,
124 LB_TIMER_UP,
125 LB_TIMER_LEFT,
126 LB_TIMER_DOWN,
127 LB_TIMER_RIGHT
128 } TIMER_DIRECTION;
130 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
132 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
133 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
135 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
137 /*********************************************************************
138 * listbox class descriptor
140 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
141 const struct builtin_class_descr LISTBOX_builtin_class =
143 listboxW, /* name */
144 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
145 ListBoxWndProcA, /* procA */
146 ListBoxWndProcW, /* procW */
147 sizeof(LB_DESCR *), /* extra */
148 IDC_ARROW, /* cursor */
149 0 /* brush */
153 /*********************************************************************
154 * combolbox class descriptor
156 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
157 const struct builtin_class_descr COMBOLBOX_builtin_class =
159 combolboxW, /* name */
160 CS_DBLCLKS | CS_SAVEBITS, /* style */
161 ListBoxWndProcA, /* procA */
162 ListBoxWndProcW, /* procW */
163 sizeof(LB_DESCR *), /* extra */
164 IDC_ARROW, /* cursor */
165 0 /* brush */
169 /* check whether app is a Win 3.1 app */
170 static inline BOOL is_old_app( LB_DESCR *descr )
172 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
176 /***********************************************************************
177 * LISTBOX_GetCurrentPageSize
179 * Return the current page size
181 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
183 INT i, height;
184 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
185 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
187 if ((height += descr->items[i].height) > descr->height) break;
189 if (i == descr->top_item) return 1;
190 else return i - descr->top_item;
194 /***********************************************************************
195 * LISTBOX_GetMaxTopIndex
197 * Return the maximum possible index for the top of the listbox.
199 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
201 INT max, page;
203 if (descr->style & LBS_OWNERDRAWVARIABLE)
205 page = descr->height;
206 for (max = descr->nb_items - 1; max >= 0; max--)
207 if ((page -= descr->items[max].height) < 0) break;
208 if (max < descr->nb_items - 1) max++;
210 else if (descr->style & LBS_MULTICOLUMN)
212 if ((page = descr->width / descr->column_width) < 1) page = 1;
213 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
214 max = (max - page) * descr->page_size;
216 else
218 max = descr->nb_items - descr->page_size;
220 if (max < 0) max = 0;
221 return max;
225 /***********************************************************************
226 * LISTBOX_UpdateScroll
228 * Update the scrollbars. Should be called whenever the content
229 * of the listbox changes.
231 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
233 SCROLLINFO info;
235 /* Check the listbox scroll bar flags individually before we call
236 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
237 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
238 scroll bar when we do not need one.
239 if (!(descr->style & WS_VSCROLL)) return;
242 /* It is important that we check descr->style, and not wnd->dwStyle,
243 for WS_VSCROLL, as the former is exactly the one passed in
244 argument to CreateWindow.
245 In Windows (and from now on in Wine :) a listbox created
246 with such a style (no WS_SCROLL) does not update
247 the scrollbar with listbox-related data, thus letting
248 the programmer use it for his/her own purposes. */
250 if (descr->style & LBS_NOREDRAW) return;
251 info.cbSize = sizeof(info);
253 if (descr->style & LBS_MULTICOLUMN)
255 info.nMin = 0;
256 info.nMax = (descr->nb_items - 1) / descr->page_size;
257 info.nPos = descr->top_item / descr->page_size;
258 info.nPage = descr->width / descr->column_width;
259 if (info.nPage < 1) info.nPage = 1;
260 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
261 if (descr->style & LBS_DISABLENOSCROLL)
262 info.fMask |= SIF_DISABLENOSCROLL;
263 if (descr->style & WS_HSCROLL)
264 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
265 info.nMax = 0;
266 info.fMask = SIF_RANGE;
267 if (descr->style & WS_VSCROLL)
268 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
270 else
272 info.nMin = 0;
273 info.nMax = descr->nb_items - 1;
274 info.nPos = descr->top_item;
275 info.nPage = LISTBOX_GetCurrentPageSize( descr );
276 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
277 if (descr->style & LBS_DISABLENOSCROLL)
278 info.fMask |= SIF_DISABLENOSCROLL;
279 if (descr->style & WS_VSCROLL)
280 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
282 if (descr->horz_extent)
284 info.nMin = 0;
285 info.nMax = descr->horz_extent - 1;
286 info.nPos = descr->horz_pos;
287 info.nPage = descr->width;
288 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
289 if (descr->style & LBS_DISABLENOSCROLL)
290 info.fMask |= SIF_DISABLENOSCROLL;
291 if (descr->style & WS_HSCROLL)
292 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
298 /***********************************************************************
299 * LISTBOX_SetTopItem
301 * Set the top item of the listbox, scrolling up or down if necessary.
303 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
305 INT max = LISTBOX_GetMaxTopIndex( descr );
307 TRACE("setting top item %d, scroll %d\n", index, scroll);
309 if (index > max) index = max;
310 if (index < 0) index = 0;
311 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
312 if (descr->top_item == index) return LB_OKAY;
313 if (descr->style & LBS_MULTICOLUMN)
315 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
316 if (scroll && (abs(diff) < descr->width))
317 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
318 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
320 else
321 scroll = FALSE;
323 else if (scroll)
325 INT diff;
326 if (descr->style & LBS_OWNERDRAWVARIABLE)
328 INT i;
329 diff = 0;
330 if (index > descr->top_item)
332 for (i = index - 1; i >= descr->top_item; i--)
333 diff -= descr->items[i].height;
335 else
337 for (i = index; i < descr->top_item; i++)
338 diff += descr->items[i].height;
341 else
342 diff = (descr->top_item - index) * descr->item_height;
344 if (abs(diff) < descr->height)
345 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
346 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
347 else
348 scroll = FALSE;
350 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
351 descr->top_item = index;
352 LISTBOX_UpdateScroll( descr );
353 return LB_OKAY;
357 /***********************************************************************
358 * LISTBOX_UpdatePage
360 * Update the page size. Should be called when the size of
361 * the client area or the item height changes.
363 static void LISTBOX_UpdatePage( LB_DESCR *descr )
365 INT page_size;
367 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
368 page_size = 1;
369 if (page_size == descr->page_size) return;
370 descr->page_size = page_size;
371 if (descr->style & LBS_MULTICOLUMN)
372 InvalidateRect( descr->self, NULL, TRUE );
373 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
377 /***********************************************************************
378 * LISTBOX_UpdateSize
380 * Update the size of the listbox. Should be called when the size of
381 * the client area changes.
383 static void LISTBOX_UpdateSize( LB_DESCR *descr )
385 RECT rect;
387 GetClientRect( descr->self, &rect );
388 descr->width = rect.right - rect.left;
389 descr->height = rect.bottom - rect.top;
390 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
392 INT remaining;
393 RECT rect;
395 GetWindowRect( descr->self, &rect );
396 if(descr->item_height != 0)
397 remaining = descr->height % descr->item_height;
398 else
399 remaining = 0;
400 if ((descr->height > descr->item_height) && remaining)
402 if (is_old_app(descr))
403 { /* give a margin for error to 16 bits programs - if we need
404 less than the height of the nonclient area, round to the
405 *next* number of items */
406 int ncheight = rect.bottom - rect.top - descr->height;
407 if ((descr->item_height - remaining) <= ncheight)
408 remaining = remaining - descr->item_height;
410 TRACE("[%p]: changing height %d -> %d\n",
411 descr->self, descr->height, descr->height - remaining );
412 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
413 rect.bottom - rect.top - remaining,
414 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
415 return;
418 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
419 LISTBOX_UpdatePage( descr );
420 LISTBOX_UpdateScroll( descr );
422 /* Invalidate the focused item so it will be repainted correctly */
423 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
425 InvalidateRect( descr->self, &rect, FALSE );
430 /***********************************************************************
431 * LISTBOX_GetItemRect
433 * Get the rectangle enclosing an item, in listbox client coordinates.
434 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
436 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
438 /* Index <= 0 is legal even on empty listboxes */
439 if (index && (index >= descr->nb_items))
441 memset(rect, 0, sizeof(*rect));
442 SetLastError(ERROR_INVALID_INDEX);
443 return LB_ERR;
445 SetRect( rect, 0, 0, descr->width, descr->height );
446 if (descr->style & LBS_MULTICOLUMN)
448 INT col = (index / descr->page_size) -
449 (descr->top_item / descr->page_size);
450 rect->left += col * descr->column_width;
451 rect->right = rect->left + descr->column_width;
452 rect->top += (index % descr->page_size) * descr->item_height;
453 rect->bottom = rect->top + descr->item_height;
455 else if (descr->style & LBS_OWNERDRAWVARIABLE)
457 INT i;
458 rect->right += descr->horz_pos;
459 if ((index >= 0) && (index < descr->nb_items))
461 if (index < descr->top_item)
463 for (i = descr->top_item-1; i >= index; i--)
464 rect->top -= descr->items[i].height;
466 else
468 for (i = descr->top_item; i < index; i++)
469 rect->top += descr->items[i].height;
471 rect->bottom = rect->top + descr->items[index].height;
475 else
477 rect->top += (index - descr->top_item) * descr->item_height;
478 rect->bottom = rect->top + descr->item_height;
479 rect->right += descr->horz_pos;
482 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
484 return ((rect->left < descr->width) && (rect->right > 0) &&
485 (rect->top < descr->height) && (rect->bottom > 0));
489 /***********************************************************************
490 * LISTBOX_GetItemFromPoint
492 * Return the item nearest from point (x,y) (in client coordinates).
494 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
496 INT index = descr->top_item;
498 if (!descr->nb_items) return -1; /* No items */
499 if (descr->style & LBS_OWNERDRAWVARIABLE)
501 INT pos = 0;
502 if (y >= 0)
504 while (index < descr->nb_items)
506 if ((pos += descr->items[index].height) > y) break;
507 index++;
510 else
512 while (index > 0)
514 index--;
515 if ((pos -= descr->items[index].height) <= y) break;
519 else if (descr->style & LBS_MULTICOLUMN)
521 if (y >= descr->item_height * descr->page_size) return -1;
522 if (y >= 0) index += y / descr->item_height;
523 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
524 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
526 else
528 index += (y / descr->item_height);
530 if (index < 0) return 0;
531 if (index >= descr->nb_items) return -1;
532 return index;
536 /***********************************************************************
537 * LISTBOX_PaintItem
539 * Paint an item.
541 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
542 INT index, UINT action, BOOL ignoreFocus )
544 LB_ITEMDATA *item = NULL;
545 if (index < descr->nb_items) item = &descr->items[index];
547 if (IS_OWNERDRAW(descr))
549 DRAWITEMSTRUCT dis;
550 RECT r;
551 HRGN hrgn;
553 if (!item)
555 if (action == ODA_FOCUS)
556 DrawFocusRect( hdc, rect );
557 else
558 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
559 return;
562 /* some programs mess with the clipping region when
563 drawing the item, *and* restore the previous region
564 after they are done, so a region has better to exist
565 else everything ends clipped */
566 GetClientRect(descr->self, &r);
567 hrgn = CreateRectRgnIndirect(&r);
568 SelectClipRgn( hdc, hrgn);
569 DeleteObject( hrgn );
571 dis.CtlType = ODT_LISTBOX;
572 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
573 dis.hwndItem = descr->self;
574 dis.itemAction = action;
575 dis.hDC = hdc;
576 dis.itemID = index;
577 dis.itemState = 0;
578 if (item->selected) dis.itemState |= ODS_SELECTED;
579 if (!ignoreFocus && (descr->focus_item == index) &&
580 (descr->caret_on) &&
581 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
582 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
583 dis.itemData = item->data;
584 dis.rcItem = *rect;
585 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
586 descr->self, index, item ? debugstr_w(item->str) : "", action,
587 dis.itemState, wine_dbgstr_rect(rect) );
588 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
590 else
592 COLORREF oldText = 0, oldBk = 0;
594 if (action == ODA_FOCUS)
596 DrawFocusRect( hdc, rect );
597 return;
599 if (item && item->selected)
601 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
602 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
605 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
606 descr->self, index, item ? debugstr_w(item->str) : "", action,
607 wine_dbgstr_rect(rect) );
608 if (!item)
609 ExtTextOutW( hdc, rect->left + 1, rect->top,
610 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
611 else if (!(descr->style & LBS_USETABSTOPS))
612 ExtTextOutW( hdc, rect->left + 1, rect->top,
613 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
614 strlenW(item->str), NULL );
615 else
617 /* Output empty string to paint background in the full width. */
618 ExtTextOutW( hdc, rect->left + 1, rect->top,
619 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
620 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
621 item->str, strlenW(item->str),
622 descr->nb_tabs, descr->tabs, 0);
624 if (item && item->selected)
626 SetBkColor( hdc, oldBk );
627 SetTextColor( hdc, oldText );
629 if (!ignoreFocus && (descr->focus_item == index) &&
630 (descr->caret_on) &&
631 (descr->in_focus)) DrawFocusRect( hdc, rect );
636 /***********************************************************************
637 * LISTBOX_SetRedraw
639 * Change the redraw flag.
641 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
643 if (on)
645 if (!(descr->style & LBS_NOREDRAW)) return;
646 descr->style &= ~LBS_NOREDRAW;
647 if (descr->style & LBS_DISPLAYCHANGED)
648 { /* page was changed while setredraw false, refresh automatically */
649 InvalidateRect(descr->self, NULL, TRUE);
650 if ((descr->top_item + descr->page_size) > descr->nb_items)
651 { /* reset top of page if less than number of items/page */
652 descr->top_item = descr->nb_items - descr->page_size;
653 if (descr->top_item < 0) descr->top_item = 0;
655 descr->style &= ~LBS_DISPLAYCHANGED;
657 LISTBOX_UpdateScroll( descr );
659 else descr->style |= LBS_NOREDRAW;
663 /***********************************************************************
664 * LISTBOX_RepaintItem
666 * Repaint a single item synchronously.
668 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
670 HDC hdc;
671 RECT rect;
672 HFONT oldFont = 0;
673 HBRUSH hbrush, oldBrush = 0;
675 /* Do not repaint the item if the item is not visible */
676 if (!IsWindowVisible(descr->self)) return;
677 if (descr->style & LBS_NOREDRAW)
679 descr->style |= LBS_DISPLAYCHANGED;
680 return;
682 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
683 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
684 if (descr->font) oldFont = SelectObject( hdc, descr->font );
685 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
686 (WPARAM)hdc, (LPARAM)descr->self );
687 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
688 if (!IsWindowEnabled(descr->self))
689 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
690 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
691 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
692 if (oldFont) SelectObject( hdc, oldFont );
693 if (oldBrush) SelectObject( hdc, oldBrush );
694 ReleaseDC( descr->self, hdc );
698 /***********************************************************************
699 * LISTBOX_DrawFocusRect
701 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
703 HDC hdc;
704 RECT rect;
705 HFONT oldFont = 0;
707 /* Do not repaint the item if the item is not visible */
708 if (!IsWindowVisible(descr->self)) return;
710 if (descr->focus_item == -1) return;
711 if (!descr->caret_on || !descr->in_focus) return;
713 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
714 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
715 if (descr->font) oldFont = SelectObject( hdc, descr->font );
716 if (!IsWindowEnabled(descr->self))
717 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
718 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
719 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, on ? FALSE : TRUE );
720 if (oldFont) SelectObject( hdc, oldFont );
721 ReleaseDC( descr->self, hdc );
725 /***********************************************************************
726 * LISTBOX_InitStorage
728 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
730 LB_ITEMDATA *item;
732 nb_items += LB_ARRAY_GRANULARITY - 1;
733 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
734 if (descr->items) {
735 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
736 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
737 nb_items * sizeof(LB_ITEMDATA));
739 else {
740 item = HeapAlloc( GetProcessHeap(), 0,
741 nb_items * sizeof(LB_ITEMDATA));
744 if (!item)
746 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
747 return LB_ERRSPACE;
749 descr->items = item;
750 return LB_OKAY;
754 /***********************************************************************
755 * LISTBOX_SetTabStops
757 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
759 INT i;
761 if (!(descr->style & LBS_USETABSTOPS))
763 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
764 return FALSE;
767 HeapFree( GetProcessHeap(), 0, descr->tabs );
768 if (!(descr->nb_tabs = count))
770 descr->tabs = NULL;
771 return TRUE;
773 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
774 descr->nb_tabs * sizeof(INT) )))
775 return FALSE;
776 if (short_ints)
778 INT i;
779 LPINT16 p = (LPINT16)tabs;
781 TRACE("[%p]: settabstops ", descr->self );
782 for (i = 0; i < descr->nb_tabs; i++) {
783 descr->tabs[i] = *p++<<1; /* FIXME */
784 TRACE("%hd ", descr->tabs[i]);
786 TRACE("\n");
788 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
790 /* convert into "dialog units"*/
791 for (i = 0; i < descr->nb_tabs; i++)
792 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
794 return TRUE;
798 /***********************************************************************
799 * LISTBOX_GetText
801 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
803 DWORD len;
805 if ((index < 0) || (index >= descr->nb_items))
807 SetLastError(ERROR_INVALID_INDEX);
808 return LB_ERR;
810 if (HAS_STRINGS(descr))
812 if (!buffer)
814 len = strlenW(descr->items[index].str);
815 if( unicode )
816 return len;
817 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
818 NULL, 0, NULL, NULL );
821 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
823 __TRY /* hide a Delphi bug that passes a read-only buffer */
825 if(unicode)
827 strcpyW( buffer, descr->items[index].str );
828 len = strlenW(buffer);
830 else
832 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
833 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
836 __EXCEPT_PAGE_FAULT
838 WARN( "got an invalid buffer (Delphi bug?)\n" );
839 SetLastError( ERROR_INVALID_PARAMETER );
840 return LB_ERR;
842 __ENDTRY
843 } else {
844 if (buffer)
845 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
846 len = sizeof(DWORD);
848 return len;
851 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
853 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
854 if (ret == CSTR_LESS_THAN)
855 return -1;
856 if (ret == CSTR_EQUAL)
857 return 0;
858 if (ret == CSTR_GREATER_THAN)
859 return 1;
860 return -1;
863 /***********************************************************************
864 * LISTBOX_FindStringPos
866 * Find the nearest string located before a given string in sort order.
867 * If 'exact' is TRUE, return an error if we don't get an exact match.
869 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
871 INT index, min, max, res = -1;
873 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
874 min = 0;
875 max = descr->nb_items;
876 while (min != max)
878 index = (min + max) / 2;
879 if (HAS_STRINGS(descr))
880 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
881 else
883 COMPAREITEMSTRUCT cis;
884 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
886 cis.CtlType = ODT_LISTBOX;
887 cis.CtlID = id;
888 cis.hwndItem = descr->self;
889 /* note that some application (MetaStock) expects the second item
890 * to be in the listbox */
891 cis.itemID1 = -1;
892 cis.itemData1 = (ULONG_PTR)str;
893 cis.itemID2 = index;
894 cis.itemData2 = descr->items[index].data;
895 cis.dwLocaleId = descr->locale;
896 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
898 if (!res) return index;
899 if (res < 0) max = index;
900 else min = index + 1;
902 return exact ? -1 : max;
906 /***********************************************************************
907 * LISTBOX_FindFileStrPos
909 * Find the nearest string located before a given string in directory
910 * sort order (i.e. first files, then directories, then drives).
912 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
914 INT min, max, res = -1;
916 if (!HAS_STRINGS(descr))
917 return LISTBOX_FindStringPos( descr, str, FALSE );
918 min = 0;
919 max = descr->nb_items;
920 while (min != max)
922 INT index = (min + max) / 2;
923 LPCWSTR p = descr->items[index].str;
924 if (*p == '[') /* drive or directory */
926 if (*str != '[') res = -1;
927 else if (p[1] == '-') /* drive */
929 if (str[1] == '-') res = str[2] - p[2];
930 else res = -1;
932 else /* directory */
934 if (str[1] == '-') res = 1;
935 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
938 else /* filename */
940 if (*str == '[') res = 1;
941 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
943 if (!res) return index;
944 if (res < 0) max = index;
945 else min = index + 1;
947 return max;
951 /***********************************************************************
952 * LISTBOX_FindString
954 * Find the item beginning with a given string.
956 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
958 INT i;
959 LB_ITEMDATA *item;
961 if (start >= descr->nb_items) start = -1;
962 item = descr->items + start + 1;
963 if (HAS_STRINGS(descr))
965 if (!str || ! str[0] ) return LB_ERR;
966 if (exact)
968 for (i = start + 1; i < descr->nb_items; i++, item++)
969 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
970 for (i = 0, item = descr->items; i <= start; i++, item++)
971 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
973 else
975 /* Special case for drives and directories: ignore prefix */
976 #define CHECK_DRIVE(item) \
977 if ((item)->str[0] == '[') \
979 if (!strncmpiW( str, (item)->str+1, len )) return i; \
980 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
981 return i; \
984 INT len = strlenW(str);
985 for (i = start + 1; i < descr->nb_items; i++, item++)
987 if (!strncmpiW( str, item->str, len )) return i;
988 CHECK_DRIVE(item);
990 for (i = 0, item = descr->items; i <= start; i++, item++)
992 if (!strncmpiW( str, item->str, len )) return i;
993 CHECK_DRIVE(item);
995 #undef CHECK_DRIVE
998 else
1000 if (exact && (descr->style & LBS_SORT))
1001 /* If sorted, use a WM_COMPAREITEM binary search */
1002 return LISTBOX_FindStringPos( descr, str, TRUE );
1004 /* Otherwise use a linear search */
1005 for (i = start + 1; i < descr->nb_items; i++, item++)
1006 if (item->data == (ULONG_PTR)str) return i;
1007 for (i = 0, item = descr->items; i <= start; i++, item++)
1008 if (item->data == (ULONG_PTR)str) return i;
1010 return LB_ERR;
1014 /***********************************************************************
1015 * LISTBOX_GetSelCount
1017 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1019 INT i, count;
1020 const LB_ITEMDATA *item = descr->items;
1022 if (!(descr->style & LBS_MULTIPLESEL) ||
1023 (descr->style & LBS_NOSEL))
1024 return LB_ERR;
1025 for (i = count = 0; i < descr->nb_items; i++, item++)
1026 if (item->selected) count++;
1027 return count;
1031 /***********************************************************************
1032 * LISTBOX_GetSelItems16
1034 static LRESULT LISTBOX_GetSelItems16( const LB_DESCR *descr, INT16 max, LPINT16 array )
1036 INT i, count;
1037 const LB_ITEMDATA *item = descr->items;
1039 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1040 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1041 if (item->selected) array[count++] = (INT16)i;
1042 return count;
1046 /***********************************************************************
1047 * LISTBOX_GetSelItems
1049 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1051 INT i, count;
1052 const LB_ITEMDATA *item = descr->items;
1054 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1055 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1056 if (item->selected) array[count++] = i;
1057 return count;
1061 /***********************************************************************
1062 * LISTBOX_Paint
1064 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1066 INT i, col_pos = descr->page_size - 1;
1067 RECT rect;
1068 RECT focusRect = {-1, -1, -1, -1};
1069 HFONT oldFont = 0;
1070 HBRUSH hbrush, oldBrush = 0;
1072 if (descr->style & LBS_NOREDRAW) return 0;
1074 SetRect( &rect, 0, 0, descr->width, descr->height );
1075 if (descr->style & LBS_MULTICOLUMN)
1076 rect.right = rect.left + descr->column_width;
1077 else if (descr->horz_pos)
1079 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1080 rect.right += descr->horz_pos;
1083 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1084 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1085 (WPARAM)hdc, (LPARAM)descr->self );
1086 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1087 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1089 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1090 (descr->in_focus))
1092 /* Special case for empty listbox: paint focus rect */
1093 rect.bottom = rect.top + descr->item_height;
1094 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1095 &rect, NULL, 0, NULL );
1096 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1097 rect.top = rect.bottom;
1100 /* Paint all the item, regarding the selection
1101 Focus state will be painted after */
1103 for (i = descr->top_item; i < descr->nb_items; i++)
1105 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1106 rect.bottom = rect.top + descr->item_height;
1107 else
1108 rect.bottom = rect.top + descr->items[i].height;
1110 if (i == descr->focus_item)
1112 /* keep the focus rect, to paint the focus item after */
1113 focusRect.left = rect.left;
1114 focusRect.right = rect.right;
1115 focusRect.top = rect.top;
1116 focusRect.bottom = rect.bottom;
1118 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1119 rect.top = rect.bottom;
1121 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1123 if (!IS_OWNERDRAW(descr))
1125 /* Clear the bottom of the column */
1126 if (rect.top < descr->height)
1128 rect.bottom = descr->height;
1129 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1130 &rect, NULL, 0, NULL );
1134 /* Go to the next column */
1135 rect.left += descr->column_width;
1136 rect.right += descr->column_width;
1137 rect.top = 0;
1138 col_pos = descr->page_size - 1;
1140 else
1142 col_pos--;
1143 if (rect.top >= descr->height) break;
1147 /* Paint the focus item now */
1148 if (focusRect.top != focusRect.bottom &&
1149 descr->caret_on && descr->in_focus)
1150 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1152 if (!IS_OWNERDRAW(descr))
1154 /* Clear the remainder of the client area */
1155 if (rect.top < descr->height)
1157 rect.bottom = descr->height;
1158 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1159 &rect, NULL, 0, NULL );
1161 if (rect.right < descr->width)
1163 rect.left = rect.right;
1164 rect.right = descr->width;
1165 rect.top = 0;
1166 rect.bottom = descr->height;
1167 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1168 &rect, NULL, 0, NULL );
1171 if (oldFont) SelectObject( hdc, oldFont );
1172 if (oldBrush) SelectObject( hdc, oldBrush );
1173 return 0;
1177 /***********************************************************************
1178 * LISTBOX_InvalidateItems
1180 * Invalidate all items from a given item. If the specified item is not
1181 * visible, nothing happens.
1183 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1185 RECT rect;
1187 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1189 if (descr->style & LBS_NOREDRAW)
1191 descr->style |= LBS_DISPLAYCHANGED;
1192 return;
1194 rect.bottom = descr->height;
1195 InvalidateRect( descr->self, &rect, TRUE );
1196 if (descr->style & LBS_MULTICOLUMN)
1198 /* Repaint the other columns */
1199 rect.left = rect.right;
1200 rect.right = descr->width;
1201 rect.top = 0;
1202 InvalidateRect( descr->self, &rect, TRUE );
1207 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1209 RECT rect;
1211 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1212 InvalidateRect( descr->self, &rect, TRUE );
1215 /***********************************************************************
1216 * LISTBOX_GetItemHeight
1218 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1220 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1222 if ((index < 0) || (index >= descr->nb_items))
1224 SetLastError(ERROR_INVALID_INDEX);
1225 return LB_ERR;
1227 return descr->items[index].height;
1229 else return descr->item_height;
1233 /***********************************************************************
1234 * LISTBOX_SetItemHeight
1236 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1238 if (height > MAXBYTE)
1239 return -1;
1241 if (!height) height = 1;
1243 if (descr->style & LBS_OWNERDRAWVARIABLE)
1245 if ((index < 0) || (index >= descr->nb_items))
1247 SetLastError(ERROR_INVALID_INDEX);
1248 return LB_ERR;
1250 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1251 descr->items[index].height = height;
1252 LISTBOX_UpdateScroll( descr );
1253 if (repaint)
1254 LISTBOX_InvalidateItems( descr, index );
1256 else if (height != descr->item_height)
1258 TRACE("[%p]: new height = %d\n", descr->self, height );
1259 descr->item_height = height;
1260 LISTBOX_UpdatePage( descr );
1261 LISTBOX_UpdateScroll( descr );
1262 if (repaint)
1263 InvalidateRect( descr->self, 0, TRUE );
1265 return LB_OKAY;
1269 /***********************************************************************
1270 * LISTBOX_SetHorizontalPos
1272 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1274 INT diff;
1276 if (pos > descr->horz_extent - descr->width)
1277 pos = descr->horz_extent - descr->width;
1278 if (pos < 0) pos = 0;
1279 if (!(diff = descr->horz_pos - pos)) return;
1280 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1281 descr->horz_pos = pos;
1282 LISTBOX_UpdateScroll( descr );
1283 if (abs(diff) < descr->width)
1285 RECT rect;
1286 /* Invalidate the focused item so it will be repainted correctly */
1287 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1288 InvalidateRect( descr->self, &rect, TRUE );
1289 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1290 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1292 else
1293 InvalidateRect( descr->self, NULL, TRUE );
1297 /***********************************************************************
1298 * LISTBOX_SetHorizontalExtent
1300 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1302 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1303 return LB_OKAY;
1304 if (extent <= 0) extent = 1;
1305 if (extent == descr->horz_extent) return LB_OKAY;
1306 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1307 descr->horz_extent = extent;
1308 if (descr->horz_pos > extent - descr->width)
1309 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1310 else
1311 LISTBOX_UpdateScroll( descr );
1312 return LB_OKAY;
1316 /***********************************************************************
1317 * LISTBOX_SetColumnWidth
1319 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1321 if (width == descr->column_width) return LB_OKAY;
1322 TRACE("[%p]: new column width = %d\n", descr->self, width );
1323 descr->column_width = width;
1324 LISTBOX_UpdatePage( descr );
1325 return LB_OKAY;
1329 /***********************************************************************
1330 * LISTBOX_SetFont
1332 * Returns the item height.
1334 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1336 HDC hdc;
1337 HFONT oldFont = 0;
1338 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1339 SIZE sz;
1341 descr->font = font;
1343 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1345 ERR("unable to get DC.\n" );
1346 return 16;
1348 if (font) oldFont = SelectObject( hdc, font );
1349 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1350 if (oldFont) SelectObject( hdc, oldFont );
1351 ReleaseDC( descr->self, hdc );
1353 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1354 if (!IS_OWNERDRAW(descr))
1355 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1356 return sz.cy;
1360 /***********************************************************************
1361 * LISTBOX_MakeItemVisible
1363 * Make sure that a given item is partially or fully visible.
1365 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1367 INT top;
1369 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1371 if (index <= descr->top_item) top = index;
1372 else if (descr->style & LBS_MULTICOLUMN)
1374 INT cols = descr->width;
1375 if (!fully) cols += descr->column_width - 1;
1376 if (cols >= descr->column_width) cols /= descr->column_width;
1377 else cols = 1;
1378 if (index < descr->top_item + (descr->page_size * cols)) return;
1379 top = index - descr->page_size * (cols - 1);
1381 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1383 INT height = fully ? descr->items[index].height : 1;
1384 for (top = index; top > descr->top_item; top--)
1385 if ((height += descr->items[top-1].height) > descr->height) break;
1387 else
1389 if (index < descr->top_item + descr->page_size) return;
1390 if (!fully && (index == descr->top_item + descr->page_size) &&
1391 (descr->height > (descr->page_size * descr->item_height))) return;
1392 top = index - descr->page_size + 1;
1394 LISTBOX_SetTopItem( descr, top, TRUE );
1397 /***********************************************************************
1398 * LISTBOX_SetCaretIndex
1400 * NOTES
1401 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1404 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1406 INT oldfocus = descr->focus_item;
1408 TRACE("old focus %d, index %d\n", oldfocus, index);
1410 if (descr->style & LBS_NOSEL) return LB_ERR;
1411 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1412 if (index == oldfocus) return LB_OKAY;
1414 LISTBOX_DrawFocusRect( descr, FALSE );
1415 descr->focus_item = index;
1417 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1418 LISTBOX_DrawFocusRect( descr, TRUE );
1420 return LB_OKAY;
1424 /***********************************************************************
1425 * LISTBOX_SelectItemRange
1427 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1429 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1430 INT last, BOOL on )
1432 INT i;
1434 /* A few sanity checks */
1436 if (descr->style & LBS_NOSEL) return LB_ERR;
1437 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1439 if (!descr->nb_items) return LB_OKAY;
1441 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1442 if (first < 0) first = 0;
1443 if (last < first) return LB_OKAY;
1445 if (on) /* Turn selection on */
1447 for (i = first; i <= last; i++)
1449 if (descr->items[i].selected) continue;
1450 descr->items[i].selected = TRUE;
1451 LISTBOX_InvalidateItemRect(descr, i);
1454 else /* Turn selection off */
1456 for (i = first; i <= last; i++)
1458 if (!descr->items[i].selected) continue;
1459 descr->items[i].selected = FALSE;
1460 LISTBOX_InvalidateItemRect(descr, i);
1463 return LB_OKAY;
1466 /***********************************************************************
1467 * LISTBOX_SetSelection
1469 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1470 BOOL on, BOOL send_notify )
1472 TRACE( "cur_sel=%d index=%d notify=%s\n",
1473 descr->selected_item, index, send_notify ? "YES" : "NO" );
1475 if (descr->style & LBS_NOSEL)
1477 descr->selected_item = index;
1478 return LB_ERR;
1480 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1481 if (descr->style & LBS_MULTIPLESEL)
1483 if (index == -1) /* Select all items */
1484 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1485 else /* Only one item */
1486 return LISTBOX_SelectItemRange( descr, index, index, on );
1488 else
1490 INT oldsel = descr->selected_item;
1491 if (index == oldsel) return LB_OKAY;
1492 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1493 if (index != -1) descr->items[index].selected = TRUE;
1494 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1495 descr->selected_item = index;
1496 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1497 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1498 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1499 else
1500 if( descr->lphc ) /* set selection change flag for parent combo */
1501 descr->lphc->wState |= CBF_SELCHANGE;
1503 return LB_OKAY;
1507 /***********************************************************************
1508 * LISTBOX_MoveCaret
1510 * Change the caret position and extend the selection to the new caret.
1512 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1514 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1516 if ((index < 0) || (index >= descr->nb_items))
1517 return;
1519 /* Important, repaint needs to be done in this order if
1520 you want to mimic Windows behavior:
1521 1. Remove the focus and paint the item
1522 2. Remove the selection and paint the item(s)
1523 3. Set the selection and repaint the item(s)
1524 4. Set the focus to 'index' and repaint the item */
1526 /* 1. remove the focus and repaint the item */
1527 LISTBOX_DrawFocusRect( descr, FALSE );
1529 /* 2. then turn off the previous selection */
1530 /* 3. repaint the new selected item */
1531 if (descr->style & LBS_EXTENDEDSEL)
1533 if (descr->anchor_item != -1)
1535 INT first = min( index, descr->anchor_item );
1536 INT last = max( index, descr->anchor_item );
1537 if (first > 0)
1538 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1539 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1540 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1543 else if (!(descr->style & LBS_MULTIPLESEL))
1545 /* Set selection to new caret item */
1546 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1549 /* 4. repaint the new item with the focus */
1550 descr->focus_item = index;
1551 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1552 LISTBOX_DrawFocusRect( descr, TRUE );
1556 /***********************************************************************
1557 * LISTBOX_InsertItem
1559 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1560 LPWSTR str, ULONG_PTR data )
1562 LB_ITEMDATA *item;
1563 INT max_items;
1564 INT oldfocus = descr->focus_item;
1566 if (index == -1) index = descr->nb_items;
1567 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1568 if (!descr->items) max_items = 0;
1569 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1570 if (descr->nb_items == max_items)
1572 /* We need to grow the array */
1573 max_items += LB_ARRAY_GRANULARITY;
1574 if (descr->items)
1575 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1576 max_items * sizeof(LB_ITEMDATA) );
1577 else
1578 item = HeapAlloc( GetProcessHeap(), 0,
1579 max_items * sizeof(LB_ITEMDATA) );
1580 if (!item)
1582 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1583 return LB_ERRSPACE;
1585 descr->items = item;
1588 /* Insert the item structure */
1590 item = &descr->items[index];
1591 if (index < descr->nb_items)
1592 RtlMoveMemory( item + 1, item,
1593 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1594 item->str = str;
1595 item->data = data;
1596 item->height = 0;
1597 item->selected = FALSE;
1598 descr->nb_items++;
1600 /* Get item height */
1602 if (descr->style & LBS_OWNERDRAWVARIABLE)
1604 MEASUREITEMSTRUCT mis;
1605 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1607 mis.CtlType = ODT_LISTBOX;
1608 mis.CtlID = id;
1609 mis.itemID = index;
1610 mis.itemData = descr->items[index].data;
1611 mis.itemHeight = descr->item_height;
1612 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1613 item->height = mis.itemHeight ? mis.itemHeight : 1;
1614 TRACE("[%p]: measure item %d (%s) = %d\n",
1615 descr->self, index, str ? debugstr_w(str) : "", item->height );
1618 /* Repaint the items */
1620 LISTBOX_UpdateScroll( descr );
1621 LISTBOX_InvalidateItems( descr, index );
1623 /* Move selection and focused item */
1624 /* If listbox was empty, set focus to the first item */
1625 if (descr->nb_items == 1)
1626 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1627 /* single select don't change selection index in win31 */
1628 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1630 descr->selected_item++;
1631 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1633 else
1635 if (index <= descr->selected_item)
1637 descr->selected_item++;
1638 descr->focus_item = oldfocus; /* focus not changed */
1641 return LB_OKAY;
1645 /***********************************************************************
1646 * LISTBOX_InsertString
1648 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1650 LPWSTR new_str = NULL;
1651 ULONG_PTR data = 0;
1652 LRESULT ret;
1654 if (HAS_STRINGS(descr))
1656 static const WCHAR empty_stringW[] = { 0 };
1657 if (!str) str = empty_stringW;
1658 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1660 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1661 return LB_ERRSPACE;
1663 strcpyW(new_str, str);
1665 else data = (ULONG_PTR)str;
1667 if (index == -1) index = descr->nb_items;
1668 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1670 HeapFree( GetProcessHeap(), 0, new_str );
1671 return ret;
1674 TRACE("[%p]: added item %d %s\n",
1675 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1676 return index;
1680 /***********************************************************************
1681 * LISTBOX_DeleteItem
1683 * Delete the content of an item. 'index' must be a valid index.
1685 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1687 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1688 * while Win95 sends it for all items with user data.
1689 * It's probably better to send it too often than not
1690 * often enough, so this is what we do here.
1692 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1694 DELETEITEMSTRUCT dis;
1695 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1697 dis.CtlType = ODT_LISTBOX;
1698 dis.CtlID = id;
1699 dis.itemID = index;
1700 dis.hwndItem = descr->self;
1701 dis.itemData = descr->items[index].data;
1702 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1704 if (HAS_STRINGS(descr))
1705 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1709 /***********************************************************************
1710 * LISTBOX_RemoveItem
1712 * Remove an item from the listbox and delete its content.
1714 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1716 LB_ITEMDATA *item;
1717 INT max_items;
1719 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1721 /* We need to invalidate the original rect instead of the updated one. */
1722 LISTBOX_InvalidateItems( descr, index );
1724 LISTBOX_DeleteItem( descr, index );
1726 /* Remove the item */
1728 item = &descr->items[index];
1729 if (index < descr->nb_items-1)
1730 RtlMoveMemory( item, item + 1,
1731 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1732 descr->nb_items--;
1733 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1735 /* Shrink the item array if possible */
1737 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1738 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1740 max_items -= LB_ARRAY_GRANULARITY;
1741 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1742 max_items * sizeof(LB_ITEMDATA) );
1743 if (item) descr->items = item;
1745 /* Repaint the items */
1747 LISTBOX_UpdateScroll( descr );
1748 /* if we removed the scrollbar, reset the top of the list
1749 (correct for owner-drawn ???) */
1750 if (descr->nb_items == descr->page_size)
1751 LISTBOX_SetTopItem( descr, 0, TRUE );
1753 /* Move selection and focused item */
1754 if (!IS_MULTISELECT(descr))
1756 if (index == descr->selected_item)
1757 descr->selected_item = -1;
1758 else if (index < descr->selected_item)
1760 descr->selected_item--;
1761 if (ISWIN31) /* win 31 do not change the selected item number */
1762 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1766 if (descr->focus_item >= descr->nb_items)
1768 descr->focus_item = descr->nb_items - 1;
1769 if (descr->focus_item < 0) descr->focus_item = 0;
1771 return LB_OKAY;
1775 /***********************************************************************
1776 * LISTBOX_ResetContent
1778 static void LISTBOX_ResetContent( LB_DESCR *descr )
1780 INT i;
1782 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1783 HeapFree( GetProcessHeap(), 0, descr->items );
1784 descr->nb_items = 0;
1785 descr->top_item = 0;
1786 descr->selected_item = -1;
1787 descr->focus_item = 0;
1788 descr->anchor_item = -1;
1789 descr->items = NULL;
1793 /***********************************************************************
1794 * LISTBOX_SetCount
1796 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1798 LRESULT ret;
1800 if (HAS_STRINGS(descr))
1802 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1803 return LB_ERR;
1806 /* FIXME: this is far from optimal... */
1807 if (count > descr->nb_items)
1809 while (count > descr->nb_items)
1810 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1811 return ret;
1813 else if (count < descr->nb_items)
1815 while (count < descr->nb_items)
1816 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1817 return ret;
1819 return LB_OKAY;
1823 /***********************************************************************
1824 * LISTBOX_Directory
1826 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1827 LPCWSTR filespec, BOOL long_names )
1829 HANDLE handle;
1830 LRESULT ret = LB_OKAY;
1831 WIN32_FIND_DATAW entry;
1832 int pos;
1833 LRESULT maxinsert = LB_ERR;
1835 /* don't scan directory if we just want drives exclusively */
1836 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1837 /* scan directory */
1838 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1840 int le = GetLastError();
1841 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1843 else
1847 WCHAR buffer[270];
1848 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1850 static const WCHAR bracketW[] = { ']',0 };
1851 static const WCHAR dotW[] = { '.',0 };
1852 if (!(attrib & DDL_DIRECTORY) ||
1853 !strcmpW( entry.cFileName, dotW )) continue;
1854 buffer[0] = '[';
1855 if (!long_names && entry.cAlternateFileName[0])
1856 strcpyW( buffer + 1, entry.cAlternateFileName );
1857 else
1858 strcpyW( buffer + 1, entry.cFileName );
1859 strcatW(buffer, bracketW);
1861 else /* not a directory */
1863 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1864 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1866 if ((attrib & DDL_EXCLUSIVE) &&
1867 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1868 continue;
1869 #undef ATTRIBS
1870 if (!long_names && entry.cAlternateFileName[0])
1871 strcpyW( buffer, entry.cAlternateFileName );
1872 else
1873 strcpyW( buffer, entry.cFileName );
1875 if (!long_names) CharLowerW( buffer );
1876 pos = LISTBOX_FindFileStrPos( descr, buffer );
1877 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1878 break;
1879 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1880 } while (FindNextFileW( handle, &entry ));
1881 FindClose( handle );
1884 if (ret >= 0)
1886 ret = maxinsert;
1888 /* scan drives */
1889 if (attrib & DDL_DRIVES)
1891 WCHAR buffer[] = {'[','-','a','-',']',0};
1892 WCHAR root[] = {'A',':','\\',0};
1893 int drive;
1894 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1896 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1897 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1898 break;
1902 return ret;
1906 /***********************************************************************
1907 * LISTBOX_HandleVScroll
1909 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1911 SCROLLINFO info;
1913 if (descr->style & LBS_MULTICOLUMN) return 0;
1914 switch(scrollReq)
1916 case SB_LINEUP:
1917 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1918 break;
1919 case SB_LINEDOWN:
1920 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1921 break;
1922 case SB_PAGEUP:
1923 LISTBOX_SetTopItem( descr, descr->top_item -
1924 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1925 break;
1926 case SB_PAGEDOWN:
1927 LISTBOX_SetTopItem( descr, descr->top_item +
1928 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1929 break;
1930 case SB_THUMBPOSITION:
1931 LISTBOX_SetTopItem( descr, pos, TRUE );
1932 break;
1933 case SB_THUMBTRACK:
1934 info.cbSize = sizeof(info);
1935 info.fMask = SIF_TRACKPOS;
1936 GetScrollInfo( descr->self, SB_VERT, &info );
1937 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1938 break;
1939 case SB_TOP:
1940 LISTBOX_SetTopItem( descr, 0, TRUE );
1941 break;
1942 case SB_BOTTOM:
1943 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1944 break;
1946 return 0;
1950 /***********************************************************************
1951 * LISTBOX_HandleHScroll
1953 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1955 SCROLLINFO info;
1956 INT page;
1958 if (descr->style & LBS_MULTICOLUMN)
1960 switch(scrollReq)
1962 case SB_LINELEFT:
1963 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1964 TRUE );
1965 break;
1966 case SB_LINERIGHT:
1967 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1968 TRUE );
1969 break;
1970 case SB_PAGELEFT:
1971 page = descr->width / descr->column_width;
1972 if (page < 1) page = 1;
1973 LISTBOX_SetTopItem( descr,
1974 descr->top_item - page * descr->page_size, TRUE );
1975 break;
1976 case SB_PAGERIGHT:
1977 page = descr->width / descr->column_width;
1978 if (page < 1) page = 1;
1979 LISTBOX_SetTopItem( descr,
1980 descr->top_item + page * descr->page_size, TRUE );
1981 break;
1982 case SB_THUMBPOSITION:
1983 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1984 break;
1985 case SB_THUMBTRACK:
1986 info.cbSize = sizeof(info);
1987 info.fMask = SIF_TRACKPOS;
1988 GetScrollInfo( descr->self, SB_VERT, &info );
1989 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1990 TRUE );
1991 break;
1992 case SB_LEFT:
1993 LISTBOX_SetTopItem( descr, 0, TRUE );
1994 break;
1995 case SB_RIGHT:
1996 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1997 break;
2000 else if (descr->horz_extent)
2002 switch(scrollReq)
2004 case SB_LINELEFT:
2005 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2006 break;
2007 case SB_LINERIGHT:
2008 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2009 break;
2010 case SB_PAGELEFT:
2011 LISTBOX_SetHorizontalPos( descr,
2012 descr->horz_pos - descr->width );
2013 break;
2014 case SB_PAGERIGHT:
2015 LISTBOX_SetHorizontalPos( descr,
2016 descr->horz_pos + descr->width );
2017 break;
2018 case SB_THUMBPOSITION:
2019 LISTBOX_SetHorizontalPos( descr, pos );
2020 break;
2021 case SB_THUMBTRACK:
2022 info.cbSize = sizeof(info);
2023 info.fMask = SIF_TRACKPOS;
2024 GetScrollInfo( descr->self, SB_HORZ, &info );
2025 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2026 break;
2027 case SB_LEFT:
2028 LISTBOX_SetHorizontalPos( descr, 0 );
2029 break;
2030 case SB_RIGHT:
2031 LISTBOX_SetHorizontalPos( descr,
2032 descr->horz_extent - descr->width );
2033 break;
2036 return 0;
2039 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2041 short gcWheelDelta = 0;
2042 UINT pulScrollLines = 3;
2044 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2046 gcWheelDelta -= delta;
2048 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2050 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2051 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2052 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2054 return 0;
2057 /***********************************************************************
2058 * LISTBOX_HandleLButtonDown
2060 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2062 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2064 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2065 descr->self, x, y, index, descr->focus_item);
2067 if (!descr->caret_on && (descr->in_focus)) return 0;
2069 if (!descr->in_focus)
2071 if( !descr->lphc ) SetFocus( descr->self );
2072 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2075 if (index == -1) return 0;
2077 if (!descr->lphc)
2079 if (descr->style & LBS_NOTIFY )
2080 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2081 MAKELPARAM( x, y ) );
2084 descr->captured = TRUE;
2085 SetCapture( descr->self );
2087 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2089 /* we should perhaps make sure that all items are deselected
2090 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2091 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2092 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2095 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2096 if (keys & MK_CONTROL)
2098 LISTBOX_SetCaretIndex( descr, index, FALSE );
2099 LISTBOX_SetSelection( descr, index,
2100 !descr->items[index].selected,
2101 (descr->style & LBS_NOTIFY) != 0);
2103 else
2105 LISTBOX_MoveCaret( descr, index, FALSE );
2107 if (descr->style & LBS_EXTENDEDSEL)
2109 LISTBOX_SetSelection( descr, index,
2110 descr->items[index].selected,
2111 (descr->style & LBS_NOTIFY) != 0 );
2113 else
2115 LISTBOX_SetSelection( descr, index,
2116 !descr->items[index].selected,
2117 (descr->style & LBS_NOTIFY) != 0 );
2121 else
2123 descr->anchor_item = index;
2124 LISTBOX_MoveCaret( descr, index, FALSE );
2125 LISTBOX_SetSelection( descr, index,
2126 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2129 if (!descr->lphc)
2131 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2133 POINT pt;
2135 pt.x = x;
2136 pt.y = y;
2138 if (DragDetect( descr->self, pt ))
2139 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2142 return 0;
2146 /*************************************************************************
2147 * LISTBOX_HandleLButtonDownCombo [Internal]
2149 * Process LButtonDown message for the ComboListBox
2151 * PARAMS
2152 * pWnd [I] The windows internal structure
2153 * pDescr [I] The ListBox internal structure
2154 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2155 * x [I] X Mouse Coordinate
2156 * y [I] Y Mouse Coordinate
2158 * RETURNS
2159 * 0 since we are processing the WM_LBUTTONDOWN Message
2161 * NOTES
2162 * This function is only to be used when a ListBox is a ComboListBox
2165 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2167 RECT clientRect, screenRect;
2168 POINT mousePos;
2170 mousePos.x = x;
2171 mousePos.y = y;
2173 GetClientRect(descr->self, &clientRect);
2175 if(PtInRect(&clientRect, mousePos))
2177 /* MousePos is in client, resume normal processing */
2178 if (msg == WM_LBUTTONDOWN)
2180 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2181 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2183 else if (descr->style & LBS_NOTIFY)
2184 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2186 else
2188 POINT screenMousePos;
2189 HWND hWndOldCapture;
2191 /* Check the Non-Client Area */
2192 screenMousePos = mousePos;
2193 hWndOldCapture = GetCapture();
2194 ReleaseCapture();
2195 GetWindowRect(descr->self, &screenRect);
2196 ClientToScreen(descr->self, &screenMousePos);
2198 if(!PtInRect(&screenRect, screenMousePos))
2200 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2201 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2202 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2204 else
2206 /* Check to see the NC is a scrollbar */
2207 INT nHitTestType=0;
2208 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2209 /* Check Vertical scroll bar */
2210 if (style & WS_VSCROLL)
2212 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2213 if (PtInRect( &clientRect, mousePos ))
2214 nHitTestType = HTVSCROLL;
2216 /* Check horizontal scroll bar */
2217 if (style & WS_HSCROLL)
2219 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2220 if (PtInRect( &clientRect, mousePos ))
2221 nHitTestType = HTHSCROLL;
2223 /* Windows sends this message when a scrollbar is clicked
2226 if(nHitTestType != 0)
2228 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2229 MAKELONG(screenMousePos.x, screenMousePos.y));
2231 /* Resume the Capture after scrolling is complete
2233 if(hWndOldCapture != 0)
2234 SetCapture(hWndOldCapture);
2237 return 0;
2240 /***********************************************************************
2241 * LISTBOX_HandleLButtonUp
2243 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2245 if (LISTBOX_Timer != LB_TIMER_NONE)
2246 KillSystemTimer( descr->self, LB_TIMER_ID );
2247 LISTBOX_Timer = LB_TIMER_NONE;
2248 if (descr->captured)
2250 descr->captured = FALSE;
2251 if (GetCapture() == descr->self) ReleaseCapture();
2252 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2253 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2255 return 0;
2259 /***********************************************************************
2260 * LISTBOX_HandleTimer
2262 * Handle scrolling upon a timer event.
2263 * Return TRUE if scrolling should continue.
2265 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2267 switch(dir)
2269 case LB_TIMER_UP:
2270 if (descr->top_item) index = descr->top_item - 1;
2271 else index = 0;
2272 break;
2273 case LB_TIMER_LEFT:
2274 if (descr->top_item) index -= descr->page_size;
2275 break;
2276 case LB_TIMER_DOWN:
2277 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2278 if (index == descr->focus_item) index++;
2279 if (index >= descr->nb_items) index = descr->nb_items - 1;
2280 break;
2281 case LB_TIMER_RIGHT:
2282 if (index + descr->page_size < descr->nb_items)
2283 index += descr->page_size;
2284 break;
2285 case LB_TIMER_NONE:
2286 break;
2288 if (index == descr->focus_item) return FALSE;
2289 LISTBOX_MoveCaret( descr, index, FALSE );
2290 return TRUE;
2294 /***********************************************************************
2295 * LISTBOX_HandleSystemTimer
2297 * WM_SYSTIMER handler.
2299 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2301 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2303 KillSystemTimer( descr->self, LB_TIMER_ID );
2304 LISTBOX_Timer = LB_TIMER_NONE;
2306 return 0;
2310 /***********************************************************************
2311 * LISTBOX_HandleMouseMove
2313 * WM_MOUSEMOVE handler.
2315 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2316 INT x, INT y )
2318 INT index;
2319 TIMER_DIRECTION dir = LB_TIMER_NONE;
2321 if (!descr->captured) return;
2323 if (descr->style & LBS_MULTICOLUMN)
2325 if (y < 0) y = 0;
2326 else if (y >= descr->item_height * descr->page_size)
2327 y = descr->item_height * descr->page_size - 1;
2329 if (x < 0)
2331 dir = LB_TIMER_LEFT;
2332 x = 0;
2334 else if (x >= descr->width)
2336 dir = LB_TIMER_RIGHT;
2337 x = descr->width - 1;
2340 else
2342 if (y < 0) dir = LB_TIMER_UP; /* above */
2343 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2346 index = LISTBOX_GetItemFromPoint( descr, x, y );
2347 if (index == -1) index = descr->focus_item;
2348 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2350 /* Start/stop the system timer */
2352 if (dir != LB_TIMER_NONE)
2353 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2354 else if (LISTBOX_Timer != LB_TIMER_NONE)
2355 KillSystemTimer( descr->self, LB_TIMER_ID );
2356 LISTBOX_Timer = dir;
2360 /***********************************************************************
2361 * LISTBOX_HandleKeyDown
2363 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2365 INT caret = -1;
2366 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2367 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2368 bForceSelection = FALSE; /* only for single select list */
2370 if (descr->style & LBS_WANTKEYBOARDINPUT)
2372 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2373 MAKEWPARAM(LOWORD(key), descr->focus_item),
2374 (LPARAM)descr->self );
2375 if (caret == -2) return 0;
2377 if (caret == -1) switch(key)
2379 case VK_LEFT:
2380 if (descr->style & LBS_MULTICOLUMN)
2382 bForceSelection = FALSE;
2383 if (descr->focus_item >= descr->page_size)
2384 caret = descr->focus_item - descr->page_size;
2385 break;
2387 /* fall through */
2388 case VK_UP:
2389 caret = descr->focus_item - 1;
2390 if (caret < 0) caret = 0;
2391 break;
2392 case VK_RIGHT:
2393 if (descr->style & LBS_MULTICOLUMN)
2395 bForceSelection = FALSE;
2396 if (descr->focus_item + descr->page_size < descr->nb_items)
2397 caret = descr->focus_item + descr->page_size;
2398 break;
2400 /* fall through */
2401 case VK_DOWN:
2402 caret = descr->focus_item + 1;
2403 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2404 break;
2406 case VK_PRIOR:
2407 if (descr->style & LBS_MULTICOLUMN)
2409 INT page = descr->width / descr->column_width;
2410 if (page < 1) page = 1;
2411 caret = descr->focus_item - (page * descr->page_size) + 1;
2413 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2414 if (caret < 0) caret = 0;
2415 break;
2416 case VK_NEXT:
2417 if (descr->style & LBS_MULTICOLUMN)
2419 INT page = descr->width / descr->column_width;
2420 if (page < 1) page = 1;
2421 caret = descr->focus_item + (page * descr->page_size) - 1;
2423 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2424 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2425 break;
2426 case VK_HOME:
2427 caret = 0;
2428 break;
2429 case VK_END:
2430 caret = descr->nb_items - 1;
2431 break;
2432 case VK_SPACE:
2433 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2434 else if (descr->style & LBS_MULTIPLESEL)
2436 LISTBOX_SetSelection( descr, descr->focus_item,
2437 !descr->items[descr->focus_item].selected,
2438 (descr->style & LBS_NOTIFY) != 0 );
2440 break;
2441 default:
2442 bForceSelection = FALSE;
2444 if (bForceSelection) /* focused item is used instead of key */
2445 caret = descr->focus_item;
2446 if (caret >= 0)
2448 if (((descr->style & LBS_EXTENDEDSEL) &&
2449 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2450 !IS_MULTISELECT(descr))
2451 descr->anchor_item = caret;
2452 LISTBOX_MoveCaret( descr, caret, TRUE );
2454 if (descr->style & LBS_MULTIPLESEL)
2455 descr->selected_item = caret;
2456 else
2457 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2458 if (descr->style & LBS_NOTIFY)
2460 if (descr->lphc && IsWindowVisible( descr->self ))
2462 /* make sure that combo parent doesn't hide us */
2463 descr->lphc->wState |= CBF_NOROLLUP;
2465 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2468 return 0;
2472 /***********************************************************************
2473 * LISTBOX_HandleChar
2475 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2477 INT caret = -1;
2478 WCHAR str[2];
2480 str[0] = charW;
2481 str[1] = '\0';
2483 if (descr->style & LBS_WANTKEYBOARDINPUT)
2485 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2486 MAKEWPARAM(charW, descr->focus_item),
2487 (LPARAM)descr->self );
2488 if (caret == -2) return 0;
2490 if (caret == -1)
2491 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2492 if (caret != -1)
2494 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2495 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2496 LISTBOX_MoveCaret( descr, caret, TRUE );
2497 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2498 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2500 return 0;
2504 /***********************************************************************
2505 * LISTBOX_Create
2507 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2509 LB_DESCR *descr;
2510 MEASUREITEMSTRUCT mis;
2511 RECT rect;
2513 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2514 return FALSE;
2516 GetClientRect( hwnd, &rect );
2517 descr->self = hwnd;
2518 descr->owner = GetParent( descr->self );
2519 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2520 descr->width = rect.right - rect.left;
2521 descr->height = rect.bottom - rect.top;
2522 descr->items = NULL;
2523 descr->nb_items = 0;
2524 descr->top_item = 0;
2525 descr->selected_item = -1;
2526 descr->focus_item = 0;
2527 descr->anchor_item = -1;
2528 descr->item_height = 1;
2529 descr->page_size = 1;
2530 descr->column_width = 150;
2531 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2532 descr->horz_pos = 0;
2533 descr->nb_tabs = 0;
2534 descr->tabs = NULL;
2535 descr->caret_on = lphc ? FALSE : TRUE;
2536 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2537 descr->in_focus = FALSE;
2538 descr->captured = FALSE;
2539 descr->font = 0;
2540 descr->locale = GetUserDefaultLCID();
2541 descr->lphc = lphc;
2543 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2545 /* Win95 document "List Box Differences" from MSDN:
2546 If a list box in a version 3.x application has either the
2547 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2548 horizontal and vertical scroll bars.
2550 descr->style |= WS_VSCROLL | WS_HSCROLL;
2553 if( lphc )
2555 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2556 descr->owner = lphc->self;
2559 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2561 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2563 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2564 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2565 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2566 descr->item_height = LISTBOX_SetFont( descr, 0 );
2568 if (descr->style & LBS_OWNERDRAWFIXED)
2570 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2572 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2573 descr->item_height = lphc->fixedOwnerDrawHeight;
2575 else
2577 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2578 mis.CtlType = ODT_LISTBOX;
2579 mis.CtlID = id;
2580 mis.itemID = -1;
2581 mis.itemWidth = 0;
2582 mis.itemData = 0;
2583 mis.itemHeight = descr->item_height;
2584 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2585 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2589 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2590 return TRUE;
2594 /***********************************************************************
2595 * LISTBOX_Destroy
2597 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2599 LISTBOX_ResetContent( descr );
2600 SetWindowLongPtrW( descr->self, 0, 0 );
2601 HeapFree( GetProcessHeap(), 0, descr );
2602 return TRUE;
2606 /***********************************************************************
2607 * ListBoxWndProc_common
2609 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2610 WPARAM wParam, LPARAM lParam, BOOL unicode )
2612 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2613 LPHEADCOMBO lphc = 0;
2614 LRESULT ret;
2616 if (!descr)
2618 if (!IsWindow(hwnd)) return 0;
2620 if (msg == WM_CREATE)
2622 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2623 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2624 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2625 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2626 return 0;
2628 /* Ignore all other messages before we get a WM_CREATE */
2629 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2630 DefWindowProcA( hwnd, msg, wParam, lParam );
2632 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2634 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2635 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2637 switch(msg)
2639 case LB_RESETCONTENT16:
2640 case LB_RESETCONTENT:
2641 LISTBOX_ResetContent( descr );
2642 LISTBOX_UpdateScroll( descr );
2643 InvalidateRect( descr->self, NULL, TRUE );
2644 return 0;
2646 case LB_ADDSTRING16:
2647 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2648 /* fall through */
2649 case LB_ADDSTRING:
2651 INT ret;
2652 LPWSTR textW;
2653 if(unicode || !HAS_STRINGS(descr))
2654 textW = (LPWSTR)lParam;
2655 else
2657 LPSTR textA = (LPSTR)lParam;
2658 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2659 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2660 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2662 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2663 ret = LISTBOX_InsertString( descr, wParam, textW );
2664 if (!unicode && HAS_STRINGS(descr))
2665 HeapFree(GetProcessHeap(), 0, textW);
2666 return ret;
2669 case LB_INSERTSTRING16:
2670 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2671 wParam = (INT)(INT16)wParam;
2672 /* fall through */
2673 case LB_INSERTSTRING:
2675 INT ret;
2676 LPWSTR textW;
2677 if(unicode || !HAS_STRINGS(descr))
2678 textW = (LPWSTR)lParam;
2679 else
2681 LPSTR textA = (LPSTR)lParam;
2682 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2683 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2684 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2686 ret = LISTBOX_InsertString( descr, wParam, textW );
2687 if(!unicode && HAS_STRINGS(descr))
2688 HeapFree(GetProcessHeap(), 0, textW);
2689 return ret;
2692 case LB_ADDFILE16:
2693 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2694 /* fall through */
2695 case LB_ADDFILE:
2697 INT ret;
2698 LPWSTR textW;
2699 if(unicode || !HAS_STRINGS(descr))
2700 textW = (LPWSTR)lParam;
2701 else
2703 LPSTR textA = (LPSTR)lParam;
2704 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2705 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2706 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2708 wParam = LISTBOX_FindFileStrPos( descr, textW );
2709 ret = LISTBOX_InsertString( descr, wParam, textW );
2710 if(!unicode && HAS_STRINGS(descr))
2711 HeapFree(GetProcessHeap(), 0, textW);
2712 return ret;
2715 case LB_DELETESTRING16:
2716 case LB_DELETESTRING:
2717 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2718 return descr->nb_items;
2719 else
2721 SetLastError(ERROR_INVALID_INDEX);
2722 return LB_ERR;
2725 case LB_GETITEMDATA16:
2726 case LB_GETITEMDATA:
2727 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2729 SetLastError(ERROR_INVALID_INDEX);
2730 return LB_ERR;
2732 return descr->items[wParam].data;
2734 case LB_SETITEMDATA16:
2735 case LB_SETITEMDATA:
2736 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2738 SetLastError(ERROR_INVALID_INDEX);
2739 return LB_ERR;
2741 descr->items[wParam].data = lParam;
2742 /* undocumented: returns TRUE, not LB_OKAY (0) */
2743 return TRUE;
2745 case LB_GETCOUNT16:
2746 case LB_GETCOUNT:
2747 return descr->nb_items;
2749 case LB_GETTEXT16:
2750 lParam = (LPARAM)MapSL(lParam);
2751 /* fall through */
2752 case LB_GETTEXT:
2753 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2755 case LB_GETTEXTLEN16:
2756 /* fall through */
2757 case LB_GETTEXTLEN:
2758 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2760 SetLastError(ERROR_INVALID_INDEX);
2761 return LB_ERR;
2763 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2764 if (unicode) return strlenW( descr->items[wParam].str );
2765 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2766 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2768 case LB_GETCURSEL16:
2769 case LB_GETCURSEL:
2770 if (descr->nb_items == 0)
2771 return LB_ERR;
2772 if (!IS_MULTISELECT(descr))
2773 return descr->selected_item;
2774 if (descr->selected_item != -1)
2775 return descr->selected_item;
2776 return descr->focus_item;
2777 /* otherwise, if the user tries to move the selection with the */
2778 /* arrow keys, we will give the application something to choke on */
2779 case LB_GETTOPINDEX16:
2780 case LB_GETTOPINDEX:
2781 return descr->top_item;
2783 case LB_GETITEMHEIGHT16:
2784 case LB_GETITEMHEIGHT:
2785 return LISTBOX_GetItemHeight( descr, wParam );
2787 case LB_SETITEMHEIGHT16:
2788 lParam = LOWORD(lParam);
2789 /* fall through */
2790 case LB_SETITEMHEIGHT:
2791 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2793 case LB_ITEMFROMPOINT:
2795 POINT pt;
2796 RECT rect;
2797 int index;
2798 BOOL hit = TRUE;
2800 /* The hiword of the return value is not a client area
2801 hittest as suggested by MSDN, but rather a hittest on
2802 the returned listbox item. */
2804 if(descr->nb_items == 0)
2805 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2807 pt.x = (short)LOWORD(lParam);
2808 pt.y = (short)HIWORD(lParam);
2810 SetRect(&rect, 0, 0, descr->width, descr->height);
2812 if(!PtInRect(&rect, pt))
2814 pt.x = min(pt.x, rect.right - 1);
2815 pt.x = max(pt.x, 0);
2816 pt.y = min(pt.y, rect.bottom - 1);
2817 pt.y = max(pt.y, 0);
2818 hit = FALSE;
2821 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2823 if(index == -1)
2825 index = descr->nb_items - 1;
2826 hit = FALSE;
2828 return MAKELONG(index, hit ? 0 : 1);
2831 case LB_SETCARETINDEX16:
2832 case LB_SETCARETINDEX:
2833 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2834 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2835 return LB_ERR;
2836 else if (ISWIN31)
2837 return wParam;
2838 else
2839 return LB_OKAY;
2841 case LB_GETCARETINDEX16:
2842 case LB_GETCARETINDEX:
2843 return descr->focus_item;
2845 case LB_SETTOPINDEX16:
2846 case LB_SETTOPINDEX:
2847 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2849 case LB_SETCOLUMNWIDTH16:
2850 case LB_SETCOLUMNWIDTH:
2851 return LISTBOX_SetColumnWidth( descr, wParam );
2853 case LB_GETITEMRECT16:
2855 RECT rect;
2856 RECT16 *r16 = MapSL(lParam);
2857 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2858 r16->left = rect.left;
2859 r16->top = rect.top;
2860 r16->right = rect.right;
2861 r16->bottom = rect.bottom;
2863 return ret;
2865 case LB_GETITEMRECT:
2866 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2868 case LB_FINDSTRING16:
2869 wParam = (INT)(INT16)wParam;
2870 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2871 /* fall through */
2872 case LB_FINDSTRING:
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, FALSE );
2886 if(!unicode && HAS_STRINGS(descr))
2887 HeapFree(GetProcessHeap(), 0, textW);
2888 return ret;
2891 case LB_FINDSTRINGEXACT16:
2892 wParam = (INT)(INT16)wParam;
2893 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2894 /* fall through */
2895 case LB_FINDSTRINGEXACT:
2897 INT ret;
2898 LPWSTR textW;
2899 if(unicode || !HAS_STRINGS(descr))
2900 textW = (LPWSTR)lParam;
2901 else
2903 LPSTR textA = (LPSTR)lParam;
2904 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2905 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2906 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2908 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2909 if(!unicode && HAS_STRINGS(descr))
2910 HeapFree(GetProcessHeap(), 0, textW);
2911 return ret;
2914 case LB_SELECTSTRING16:
2915 wParam = (INT)(INT16)wParam;
2916 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2917 /* fall through */
2918 case LB_SELECTSTRING:
2920 INT index;
2921 LPWSTR textW;
2923 if(HAS_STRINGS(descr))
2924 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2925 debugstr_a((LPSTR)lParam));
2926 if(unicode || !HAS_STRINGS(descr))
2927 textW = (LPWSTR)lParam;
2928 else
2930 LPSTR textA = (LPSTR)lParam;
2931 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2932 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2933 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2935 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2936 if(!unicode && HAS_STRINGS(descr))
2937 HeapFree(GetProcessHeap(), 0, textW);
2938 if (index != LB_ERR)
2940 LISTBOX_MoveCaret( descr, index, TRUE );
2941 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2943 return index;
2946 case LB_GETSEL16:
2947 wParam = (INT)(INT16)wParam;
2948 /* fall through */
2949 case LB_GETSEL:
2950 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2951 return LB_ERR;
2952 return descr->items[wParam].selected;
2954 case LB_SETSEL16:
2955 lParam = (INT)(INT16)lParam;
2956 /* fall through */
2957 case LB_SETSEL:
2958 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2960 case LB_SETCURSEL16:
2961 wParam = (INT)(INT16)wParam;
2962 /* fall through */
2963 case LB_SETCURSEL:
2964 if (IS_MULTISELECT(descr)) return LB_ERR;
2965 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2966 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2967 if (ret != LB_ERR) ret = descr->selected_item;
2968 return ret;
2970 case LB_GETSELCOUNT16:
2971 case LB_GETSELCOUNT:
2972 return LISTBOX_GetSelCount( descr );
2974 case LB_GETSELITEMS16:
2975 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2977 case LB_GETSELITEMS:
2978 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2980 case LB_SELITEMRANGE16:
2981 case LB_SELITEMRANGE:
2982 if (LOWORD(lParam) <= HIWORD(lParam))
2983 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2984 HIWORD(lParam), wParam );
2985 else
2986 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2987 LOWORD(lParam), wParam );
2989 case LB_SELITEMRANGEEX16:
2990 case LB_SELITEMRANGEEX:
2991 if ((INT)lParam >= (INT)wParam)
2992 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2993 else
2994 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2996 case LB_GETHORIZONTALEXTENT16:
2997 case LB_GETHORIZONTALEXTENT:
2998 return descr->horz_extent;
3000 case LB_SETHORIZONTALEXTENT16:
3001 case LB_SETHORIZONTALEXTENT:
3002 return LISTBOX_SetHorizontalExtent( descr, wParam );
3004 case LB_GETANCHORINDEX16:
3005 case LB_GETANCHORINDEX:
3006 return descr->anchor_item;
3008 case LB_SETANCHORINDEX16:
3009 wParam = (INT)(INT16)wParam;
3010 /* fall through */
3011 case LB_SETANCHORINDEX:
3012 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
3014 SetLastError(ERROR_INVALID_INDEX);
3015 return LB_ERR;
3017 descr->anchor_item = (INT)wParam;
3018 return LB_OKAY;
3020 case LB_DIR16:
3021 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
3022 * be set automatically (this is different in Win32) */
3023 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
3024 lParam = (LPARAM)MapSL(lParam);
3025 /* fall through */
3026 case LB_DIR:
3028 INT ret;
3029 LPWSTR textW;
3030 if(unicode)
3031 textW = (LPWSTR)lParam;
3032 else
3034 LPSTR textA = (LPSTR)lParam;
3035 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3036 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3037 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3039 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3040 if(!unicode)
3041 HeapFree(GetProcessHeap(), 0, textW);
3042 return ret;
3045 case LB_GETLOCALE:
3046 return descr->locale;
3048 case LB_SETLOCALE:
3050 LCID ret;
3051 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3052 return LB_ERR;
3053 ret = descr->locale;
3054 descr->locale = (LCID)wParam;
3055 return ret;
3058 case LB_INITSTORAGE:
3059 return LISTBOX_InitStorage( descr, wParam );
3061 case LB_SETCOUNT:
3062 return LISTBOX_SetCount( descr, (INT)wParam );
3064 case LB_SETTABSTOPS16:
3065 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
3067 case LB_SETTABSTOPS:
3068 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
3070 case LB_CARETON16:
3071 case LB_CARETON:
3072 if (descr->caret_on)
3073 return LB_OKAY;
3074 descr->caret_on = TRUE;
3075 if ((descr->focus_item != -1) && (descr->in_focus))
3076 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3077 return LB_OKAY;
3079 case LB_CARETOFF16:
3080 case LB_CARETOFF:
3081 if (!descr->caret_on)
3082 return LB_OKAY;
3083 descr->caret_on = FALSE;
3084 if ((descr->focus_item != -1) && (descr->in_focus))
3085 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3086 return LB_OKAY;
3088 case LB_GETLISTBOXINFO:
3089 FIXME("LB_GETLISTBOXINFO: stub!\n");
3090 return 0;
3092 case WM_DESTROY:
3093 return LISTBOX_Destroy( descr );
3095 case WM_ENABLE:
3096 InvalidateRect( descr->self, NULL, TRUE );
3097 return 0;
3099 case WM_SETREDRAW:
3100 LISTBOX_SetRedraw( descr, wParam != 0 );
3101 return 0;
3103 case WM_GETDLGCODE:
3104 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3106 case WM_PRINTCLIENT:
3107 case WM_PAINT:
3109 PAINTSTRUCT ps;
3110 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3111 ret = LISTBOX_Paint( descr, hdc );
3112 if( !wParam ) EndPaint( descr->self, &ps );
3114 return ret;
3115 case WM_SIZE:
3116 LISTBOX_UpdateSize( descr );
3117 return 0;
3118 case WM_GETFONT:
3119 return (LRESULT)descr->font;
3120 case WM_SETFONT:
3121 LISTBOX_SetFont( descr, (HFONT)wParam );
3122 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3123 return 0;
3124 case WM_SETFOCUS:
3125 descr->in_focus = TRUE;
3126 descr->caret_on = TRUE;
3127 if (descr->focus_item != -1)
3128 LISTBOX_DrawFocusRect( descr, TRUE );
3129 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3130 return 0;
3131 case WM_KILLFOCUS:
3132 descr->in_focus = FALSE;
3133 if ((descr->focus_item != -1) && descr->caret_on)
3134 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3135 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3136 return 0;
3137 case WM_HSCROLL:
3138 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3139 case WM_VSCROLL:
3140 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3141 case WM_MOUSEWHEEL:
3142 if (wParam & (MK_SHIFT | MK_CONTROL))
3143 return DefWindowProcW( descr->self, msg, wParam, lParam );
3144 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3145 case WM_LBUTTONDOWN:
3146 if (lphc)
3147 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3148 (INT16)LOWORD(lParam),
3149 (INT16)HIWORD(lParam) );
3150 return LISTBOX_HandleLButtonDown( descr, wParam,
3151 (INT16)LOWORD(lParam),
3152 (INT16)HIWORD(lParam) );
3153 case WM_LBUTTONDBLCLK:
3154 if (lphc)
3155 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3156 (INT16)LOWORD(lParam),
3157 (INT16)HIWORD(lParam) );
3158 if (descr->style & LBS_NOTIFY)
3159 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3160 return 0;
3161 case WM_MOUSEMOVE:
3162 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3164 BOOL captured = descr->captured;
3165 POINT mousePos;
3166 RECT clientRect;
3168 mousePos.x = (INT16)LOWORD(lParam);
3169 mousePos.y = (INT16)HIWORD(lParam);
3172 * If we are in a dropdown combobox, we simulate that
3173 * the mouse is captured to show the tracking of the item.
3175 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3176 descr->captured = TRUE;
3178 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3180 descr->captured = captured;
3182 else if (GetCapture() == descr->self)
3184 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3185 (INT16)HIWORD(lParam) );
3187 return 0;
3188 case WM_LBUTTONUP:
3189 if (lphc)
3191 POINT mousePos;
3192 RECT clientRect;
3195 * If the mouse button "up" is not in the listbox,
3196 * we make sure there is no selection by re-selecting the
3197 * item that was selected when the listbox was made visible.
3199 mousePos.x = (INT16)LOWORD(lParam);
3200 mousePos.y = (INT16)HIWORD(lParam);
3202 GetClientRect(descr->self, &clientRect);
3205 * When the user clicks outside the combobox and the focus
3206 * is lost, the owning combobox will send a fake buttonup with
3207 * 0xFFFFFFF as the mouse location, we must also revert the
3208 * selection to the original selection.
3210 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3211 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3213 return LISTBOX_HandleLButtonUp( descr );
3214 case WM_KEYDOWN:
3215 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3217 /* for some reason Windows makes it possible to
3218 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3220 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3221 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3222 && (wParam == VK_DOWN || wParam == VK_UP)) )
3224 COMBO_FlipListbox( lphc, FALSE, FALSE );
3225 return 0;
3228 return LISTBOX_HandleKeyDown( descr, wParam );
3229 case WM_CHAR:
3231 WCHAR charW;
3232 if(unicode)
3233 charW = (WCHAR)wParam;
3234 else
3236 CHAR charA = (CHAR)wParam;
3237 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3239 return LISTBOX_HandleChar( descr, charW );
3241 case WM_SYSTIMER:
3242 return LISTBOX_HandleSystemTimer( descr );
3243 case WM_ERASEBKGND:
3244 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3246 RECT rect;
3247 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3248 wParam, (LPARAM)descr->self );
3249 TRACE("hbrush = %p\n", hbrush);
3250 if(!hbrush)
3251 hbrush = GetSysColorBrush(COLOR_WINDOW);
3252 if(hbrush)
3254 GetClientRect(descr->self, &rect);
3255 FillRect((HDC)wParam, &rect, hbrush);
3258 return 1;
3259 case WM_DROPFILES:
3260 if( lphc ) return 0;
3261 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3262 SendMessageA( descr->owner, msg, wParam, lParam );
3264 case WM_NCDESTROY:
3265 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3266 lphc->hWndLBox = 0;
3267 break;
3269 case WM_NCACTIVATE:
3270 if (lphc) return 0;
3271 break;
3273 default:
3274 if ((msg >= WM_USER) && (msg < 0xc000))
3275 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3276 hwnd, msg, wParam, lParam );
3279 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3280 DefWindowProcA( hwnd, msg, wParam, lParam );
3283 /***********************************************************************
3284 * ListBoxWndProcA
3286 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3288 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3291 /***********************************************************************
3292 * ListBoxWndProcW
3294 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3296 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );