push e9c4c6cdd0babd7b2cb4288f191bb331b756eaf2
[wine/hacks.git] / dlls / user32 / listbox.c
blob15aadcb80f56a53b084e39d825a08396b7616e5e
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 /* save the item data before it gets freed by LB_RESETCONTENT */
1688 ULONG_PTR item_data = descr->items[index].data;
1689 LPWSTR item_str = descr->items[index].str;
1691 if (!descr->nb_items)
1692 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1694 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1695 * while Win95 sends it for all items with user data.
1696 * It's probably better to send it too often than not
1697 * often enough, so this is what we do here.
1699 if (IS_OWNERDRAW(descr) || item_data)
1701 DELETEITEMSTRUCT dis;
1702 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1704 dis.CtlType = ODT_LISTBOX;
1705 dis.CtlID = id;
1706 dis.itemID = index;
1707 dis.hwndItem = descr->self;
1708 dis.itemData = item_data;
1709 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1711 if (HAS_STRINGS(descr))
1712 HeapFree( GetProcessHeap(), 0, item_str );
1716 /***********************************************************************
1717 * LISTBOX_RemoveItem
1719 * Remove an item from the listbox and delete its content.
1721 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1723 LB_ITEMDATA *item;
1724 INT max_items;
1726 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1728 /* We need to invalidate the original rect instead of the updated one. */
1729 LISTBOX_InvalidateItems( descr, index );
1731 descr->nb_items--;
1732 LISTBOX_DeleteItem( descr, index );
1734 if (!descr->nb_items) return LB_OKAY;
1736 /* Remove the item */
1738 item = &descr->items[index];
1739 if (index < descr->nb_items)
1740 RtlMoveMemory( item, item + 1,
1741 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1742 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1744 /* Shrink the item array if possible */
1746 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1747 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1749 max_items -= LB_ARRAY_GRANULARITY;
1750 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1751 max_items * sizeof(LB_ITEMDATA) );
1752 if (item) descr->items = item;
1754 /* Repaint the items */
1756 LISTBOX_UpdateScroll( descr );
1757 /* if we removed the scrollbar, reset the top of the list
1758 (correct for owner-drawn ???) */
1759 if (descr->nb_items == descr->page_size)
1760 LISTBOX_SetTopItem( descr, 0, TRUE );
1762 /* Move selection and focused item */
1763 if (!IS_MULTISELECT(descr))
1765 if (index == descr->selected_item)
1766 descr->selected_item = -1;
1767 else if (index < descr->selected_item)
1769 descr->selected_item--;
1770 if (ISWIN31) /* win 31 do not change the selected item number */
1771 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1775 if (descr->focus_item >= descr->nb_items)
1777 descr->focus_item = descr->nb_items - 1;
1778 if (descr->focus_item < 0) descr->focus_item = 0;
1780 return LB_OKAY;
1784 /***********************************************************************
1785 * LISTBOX_ResetContent
1787 static void LISTBOX_ResetContent( LB_DESCR *descr )
1789 INT i;
1791 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1792 HeapFree( GetProcessHeap(), 0, descr->items );
1793 descr->nb_items = 0;
1794 descr->top_item = 0;
1795 descr->selected_item = -1;
1796 descr->focus_item = 0;
1797 descr->anchor_item = -1;
1798 descr->items = NULL;
1802 /***********************************************************************
1803 * LISTBOX_SetCount
1805 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1807 LRESULT ret;
1809 if (HAS_STRINGS(descr))
1811 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1812 return LB_ERR;
1815 /* FIXME: this is far from optimal... */
1816 if (count > descr->nb_items)
1818 while (count > descr->nb_items)
1819 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1820 return ret;
1822 else if (count < descr->nb_items)
1824 while (count < descr->nb_items)
1825 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1826 return ret;
1828 return LB_OKAY;
1832 /***********************************************************************
1833 * LISTBOX_Directory
1835 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1836 LPCWSTR filespec, BOOL long_names )
1838 HANDLE handle;
1839 LRESULT ret = LB_OKAY;
1840 WIN32_FIND_DATAW entry;
1841 int pos;
1842 LRESULT maxinsert = LB_ERR;
1844 /* don't scan directory if we just want drives exclusively */
1845 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1846 /* scan directory */
1847 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1849 int le = GetLastError();
1850 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1852 else
1856 WCHAR buffer[270];
1857 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1859 static const WCHAR bracketW[] = { ']',0 };
1860 static const WCHAR dotW[] = { '.',0 };
1861 if (!(attrib & DDL_DIRECTORY) ||
1862 !strcmpW( entry.cFileName, dotW )) continue;
1863 buffer[0] = '[';
1864 if (!long_names && entry.cAlternateFileName[0])
1865 strcpyW( buffer + 1, entry.cAlternateFileName );
1866 else
1867 strcpyW( buffer + 1, entry.cFileName );
1868 strcatW(buffer, bracketW);
1870 else /* not a directory */
1872 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1873 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1875 if ((attrib & DDL_EXCLUSIVE) &&
1876 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1877 continue;
1878 #undef ATTRIBS
1879 if (!long_names && entry.cAlternateFileName[0])
1880 strcpyW( buffer, entry.cAlternateFileName );
1881 else
1882 strcpyW( buffer, entry.cFileName );
1884 if (!long_names) CharLowerW( buffer );
1885 pos = LISTBOX_FindFileStrPos( descr, buffer );
1886 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1887 break;
1888 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1889 } while (FindNextFileW( handle, &entry ));
1890 FindClose( handle );
1893 if (ret >= 0)
1895 ret = maxinsert;
1897 /* scan drives */
1898 if (attrib & DDL_DRIVES)
1900 WCHAR buffer[] = {'[','-','a','-',']',0};
1901 WCHAR root[] = {'A',':','\\',0};
1902 int drive;
1903 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1905 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1906 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1907 break;
1911 return ret;
1915 /***********************************************************************
1916 * LISTBOX_HandleVScroll
1918 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1920 SCROLLINFO info;
1922 if (descr->style & LBS_MULTICOLUMN) return 0;
1923 switch(scrollReq)
1925 case SB_LINEUP:
1926 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1927 break;
1928 case SB_LINEDOWN:
1929 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1930 break;
1931 case SB_PAGEUP:
1932 LISTBOX_SetTopItem( descr, descr->top_item -
1933 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1934 break;
1935 case SB_PAGEDOWN:
1936 LISTBOX_SetTopItem( descr, descr->top_item +
1937 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1938 break;
1939 case SB_THUMBPOSITION:
1940 LISTBOX_SetTopItem( descr, pos, TRUE );
1941 break;
1942 case SB_THUMBTRACK:
1943 info.cbSize = sizeof(info);
1944 info.fMask = SIF_TRACKPOS;
1945 GetScrollInfo( descr->self, SB_VERT, &info );
1946 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1947 break;
1948 case SB_TOP:
1949 LISTBOX_SetTopItem( descr, 0, TRUE );
1950 break;
1951 case SB_BOTTOM:
1952 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1953 break;
1955 return 0;
1959 /***********************************************************************
1960 * LISTBOX_HandleHScroll
1962 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1964 SCROLLINFO info;
1965 INT page;
1967 if (descr->style & LBS_MULTICOLUMN)
1969 switch(scrollReq)
1971 case SB_LINELEFT:
1972 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1973 TRUE );
1974 break;
1975 case SB_LINERIGHT:
1976 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1977 TRUE );
1978 break;
1979 case SB_PAGELEFT:
1980 page = descr->width / descr->column_width;
1981 if (page < 1) page = 1;
1982 LISTBOX_SetTopItem( descr,
1983 descr->top_item - page * descr->page_size, TRUE );
1984 break;
1985 case SB_PAGERIGHT:
1986 page = descr->width / descr->column_width;
1987 if (page < 1) page = 1;
1988 LISTBOX_SetTopItem( descr,
1989 descr->top_item + page * descr->page_size, TRUE );
1990 break;
1991 case SB_THUMBPOSITION:
1992 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1993 break;
1994 case SB_THUMBTRACK:
1995 info.cbSize = sizeof(info);
1996 info.fMask = SIF_TRACKPOS;
1997 GetScrollInfo( descr->self, SB_VERT, &info );
1998 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1999 TRUE );
2000 break;
2001 case SB_LEFT:
2002 LISTBOX_SetTopItem( descr, 0, TRUE );
2003 break;
2004 case SB_RIGHT:
2005 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
2006 break;
2009 else if (descr->horz_extent)
2011 switch(scrollReq)
2013 case SB_LINELEFT:
2014 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
2015 break;
2016 case SB_LINERIGHT:
2017 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
2018 break;
2019 case SB_PAGELEFT:
2020 LISTBOX_SetHorizontalPos( descr,
2021 descr->horz_pos - descr->width );
2022 break;
2023 case SB_PAGERIGHT:
2024 LISTBOX_SetHorizontalPos( descr,
2025 descr->horz_pos + descr->width );
2026 break;
2027 case SB_THUMBPOSITION:
2028 LISTBOX_SetHorizontalPos( descr, pos );
2029 break;
2030 case SB_THUMBTRACK:
2031 info.cbSize = sizeof(info);
2032 info.fMask = SIF_TRACKPOS;
2033 GetScrollInfo( descr->self, SB_HORZ, &info );
2034 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2035 break;
2036 case SB_LEFT:
2037 LISTBOX_SetHorizontalPos( descr, 0 );
2038 break;
2039 case SB_RIGHT:
2040 LISTBOX_SetHorizontalPos( descr,
2041 descr->horz_extent - descr->width );
2042 break;
2045 return 0;
2048 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2050 short gcWheelDelta = 0;
2051 UINT pulScrollLines = 3;
2053 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2055 gcWheelDelta -= delta;
2057 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2059 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2060 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2061 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2063 return 0;
2066 /***********************************************************************
2067 * LISTBOX_HandleLButtonDown
2069 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2071 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2073 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2074 descr->self, x, y, index, descr->focus_item);
2076 if (!descr->caret_on && (descr->in_focus)) return 0;
2078 if (!descr->in_focus)
2080 if( !descr->lphc ) SetFocus( descr->self );
2081 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2084 if (index == -1) return 0;
2086 if (!descr->lphc)
2088 if (descr->style & LBS_NOTIFY )
2089 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2090 MAKELPARAM( x, y ) );
2093 descr->captured = TRUE;
2094 SetCapture( descr->self );
2096 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2098 /* we should perhaps make sure that all items are deselected
2099 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2100 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2101 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2104 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2105 if (keys & MK_CONTROL)
2107 LISTBOX_SetCaretIndex( descr, index, FALSE );
2108 LISTBOX_SetSelection( descr, index,
2109 !descr->items[index].selected,
2110 (descr->style & LBS_NOTIFY) != 0);
2112 else
2114 LISTBOX_MoveCaret( descr, index, FALSE );
2116 if (descr->style & LBS_EXTENDEDSEL)
2118 LISTBOX_SetSelection( descr, index,
2119 descr->items[index].selected,
2120 (descr->style & LBS_NOTIFY) != 0 );
2122 else
2124 LISTBOX_SetSelection( descr, index,
2125 !descr->items[index].selected,
2126 (descr->style & LBS_NOTIFY) != 0 );
2130 else
2132 descr->anchor_item = index;
2133 LISTBOX_MoveCaret( descr, index, FALSE );
2134 LISTBOX_SetSelection( descr, index,
2135 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2138 if (!descr->lphc)
2140 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2142 POINT pt;
2144 pt.x = x;
2145 pt.y = y;
2147 if (DragDetect( descr->self, pt ))
2148 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2151 return 0;
2155 /*************************************************************************
2156 * LISTBOX_HandleLButtonDownCombo [Internal]
2158 * Process LButtonDown message for the ComboListBox
2160 * PARAMS
2161 * pWnd [I] The windows internal structure
2162 * pDescr [I] The ListBox internal structure
2163 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2164 * x [I] X Mouse Coordinate
2165 * y [I] Y Mouse Coordinate
2167 * RETURNS
2168 * 0 since we are processing the WM_LBUTTONDOWN Message
2170 * NOTES
2171 * This function is only to be used when a ListBox is a ComboListBox
2174 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2176 RECT clientRect, screenRect;
2177 POINT mousePos;
2179 mousePos.x = x;
2180 mousePos.y = y;
2182 GetClientRect(descr->self, &clientRect);
2184 if(PtInRect(&clientRect, mousePos))
2186 /* MousePos is in client, resume normal processing */
2187 if (msg == WM_LBUTTONDOWN)
2189 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2190 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2192 else if (descr->style & LBS_NOTIFY)
2193 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2195 else
2197 POINT screenMousePos;
2198 HWND hWndOldCapture;
2200 /* Check the Non-Client Area */
2201 screenMousePos = mousePos;
2202 hWndOldCapture = GetCapture();
2203 ReleaseCapture();
2204 GetWindowRect(descr->self, &screenRect);
2205 ClientToScreen(descr->self, &screenMousePos);
2207 if(!PtInRect(&screenRect, screenMousePos))
2209 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2210 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2211 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2213 else
2215 /* Check to see the NC is a scrollbar */
2216 INT nHitTestType=0;
2217 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2218 /* Check Vertical scroll bar */
2219 if (style & WS_VSCROLL)
2221 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2222 if (PtInRect( &clientRect, mousePos ))
2223 nHitTestType = HTVSCROLL;
2225 /* Check horizontal scroll bar */
2226 if (style & WS_HSCROLL)
2228 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2229 if (PtInRect( &clientRect, mousePos ))
2230 nHitTestType = HTHSCROLL;
2232 /* Windows sends this message when a scrollbar is clicked
2235 if(nHitTestType != 0)
2237 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2238 MAKELONG(screenMousePos.x, screenMousePos.y));
2240 /* Resume the Capture after scrolling is complete
2242 if(hWndOldCapture != 0)
2243 SetCapture(hWndOldCapture);
2246 return 0;
2249 /***********************************************************************
2250 * LISTBOX_HandleLButtonUp
2252 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2254 if (LISTBOX_Timer != LB_TIMER_NONE)
2255 KillSystemTimer( descr->self, LB_TIMER_ID );
2256 LISTBOX_Timer = LB_TIMER_NONE;
2257 if (descr->captured)
2259 descr->captured = FALSE;
2260 if (GetCapture() == descr->self) ReleaseCapture();
2261 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2262 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2264 return 0;
2268 /***********************************************************************
2269 * LISTBOX_HandleTimer
2271 * Handle scrolling upon a timer event.
2272 * Return TRUE if scrolling should continue.
2274 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2276 switch(dir)
2278 case LB_TIMER_UP:
2279 if (descr->top_item) index = descr->top_item - 1;
2280 else index = 0;
2281 break;
2282 case LB_TIMER_LEFT:
2283 if (descr->top_item) index -= descr->page_size;
2284 break;
2285 case LB_TIMER_DOWN:
2286 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2287 if (index == descr->focus_item) index++;
2288 if (index >= descr->nb_items) index = descr->nb_items - 1;
2289 break;
2290 case LB_TIMER_RIGHT:
2291 if (index + descr->page_size < descr->nb_items)
2292 index += descr->page_size;
2293 break;
2294 case LB_TIMER_NONE:
2295 break;
2297 if (index == descr->focus_item) return FALSE;
2298 LISTBOX_MoveCaret( descr, index, FALSE );
2299 return TRUE;
2303 /***********************************************************************
2304 * LISTBOX_HandleSystemTimer
2306 * WM_SYSTIMER handler.
2308 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2310 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2312 KillSystemTimer( descr->self, LB_TIMER_ID );
2313 LISTBOX_Timer = LB_TIMER_NONE;
2315 return 0;
2319 /***********************************************************************
2320 * LISTBOX_HandleMouseMove
2322 * WM_MOUSEMOVE handler.
2324 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2325 INT x, INT y )
2327 INT index;
2328 TIMER_DIRECTION dir = LB_TIMER_NONE;
2330 if (!descr->captured) return;
2332 if (descr->style & LBS_MULTICOLUMN)
2334 if (y < 0) y = 0;
2335 else if (y >= descr->item_height * descr->page_size)
2336 y = descr->item_height * descr->page_size - 1;
2338 if (x < 0)
2340 dir = LB_TIMER_LEFT;
2341 x = 0;
2343 else if (x >= descr->width)
2345 dir = LB_TIMER_RIGHT;
2346 x = descr->width - 1;
2349 else
2351 if (y < 0) dir = LB_TIMER_UP; /* above */
2352 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2355 index = LISTBOX_GetItemFromPoint( descr, x, y );
2356 if (index == -1) index = descr->focus_item;
2357 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2359 /* Start/stop the system timer */
2361 if (dir != LB_TIMER_NONE)
2362 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2363 else if (LISTBOX_Timer != LB_TIMER_NONE)
2364 KillSystemTimer( descr->self, LB_TIMER_ID );
2365 LISTBOX_Timer = dir;
2369 /***********************************************************************
2370 * LISTBOX_HandleKeyDown
2372 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2374 INT caret = -1;
2375 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2376 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2377 bForceSelection = FALSE; /* only for single select list */
2379 if (descr->style & LBS_WANTKEYBOARDINPUT)
2381 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2382 MAKEWPARAM(LOWORD(key), descr->focus_item),
2383 (LPARAM)descr->self );
2384 if (caret == -2) return 0;
2386 if (caret == -1) switch(key)
2388 case VK_LEFT:
2389 if (descr->style & LBS_MULTICOLUMN)
2391 bForceSelection = FALSE;
2392 if (descr->focus_item >= descr->page_size)
2393 caret = descr->focus_item - descr->page_size;
2394 break;
2396 /* fall through */
2397 case VK_UP:
2398 caret = descr->focus_item - 1;
2399 if (caret < 0) caret = 0;
2400 break;
2401 case VK_RIGHT:
2402 if (descr->style & LBS_MULTICOLUMN)
2404 bForceSelection = FALSE;
2405 if (descr->focus_item + descr->page_size < descr->nb_items)
2406 caret = descr->focus_item + descr->page_size;
2407 break;
2409 /* fall through */
2410 case VK_DOWN:
2411 caret = descr->focus_item + 1;
2412 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2413 break;
2415 case VK_PRIOR:
2416 if (descr->style & LBS_MULTICOLUMN)
2418 INT page = descr->width / descr->column_width;
2419 if (page < 1) page = 1;
2420 caret = descr->focus_item - (page * descr->page_size) + 1;
2422 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2423 if (caret < 0) caret = 0;
2424 break;
2425 case VK_NEXT:
2426 if (descr->style & LBS_MULTICOLUMN)
2428 INT page = descr->width / descr->column_width;
2429 if (page < 1) page = 1;
2430 caret = descr->focus_item + (page * descr->page_size) - 1;
2432 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2433 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2434 break;
2435 case VK_HOME:
2436 caret = 0;
2437 break;
2438 case VK_END:
2439 caret = descr->nb_items - 1;
2440 break;
2441 case VK_SPACE:
2442 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2443 else if (descr->style & LBS_MULTIPLESEL)
2445 LISTBOX_SetSelection( descr, descr->focus_item,
2446 !descr->items[descr->focus_item].selected,
2447 (descr->style & LBS_NOTIFY) != 0 );
2449 break;
2450 default:
2451 bForceSelection = FALSE;
2453 if (bForceSelection) /* focused item is used instead of key */
2454 caret = descr->focus_item;
2455 if (caret >= 0)
2457 if (((descr->style & LBS_EXTENDEDSEL) &&
2458 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2459 !IS_MULTISELECT(descr))
2460 descr->anchor_item = caret;
2461 LISTBOX_MoveCaret( descr, caret, TRUE );
2463 if (descr->style & LBS_MULTIPLESEL)
2464 descr->selected_item = caret;
2465 else
2466 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2467 if (descr->style & LBS_NOTIFY)
2469 if (descr->lphc && IsWindowVisible( descr->self ))
2471 /* make sure that combo parent doesn't hide us */
2472 descr->lphc->wState |= CBF_NOROLLUP;
2474 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2477 return 0;
2481 /***********************************************************************
2482 * LISTBOX_HandleChar
2484 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2486 INT caret = -1;
2487 WCHAR str[2];
2489 str[0] = charW;
2490 str[1] = '\0';
2492 if (descr->style & LBS_WANTKEYBOARDINPUT)
2494 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2495 MAKEWPARAM(charW, descr->focus_item),
2496 (LPARAM)descr->self );
2497 if (caret == -2) return 0;
2499 if (caret == -1)
2500 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2501 if (caret != -1)
2503 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2504 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2505 LISTBOX_MoveCaret( descr, caret, TRUE );
2506 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2507 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2509 return 0;
2513 /***********************************************************************
2514 * LISTBOX_Create
2516 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2518 LB_DESCR *descr;
2519 MEASUREITEMSTRUCT mis;
2520 RECT rect;
2522 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2523 return FALSE;
2525 GetClientRect( hwnd, &rect );
2526 descr->self = hwnd;
2527 descr->owner = GetParent( descr->self );
2528 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2529 descr->width = rect.right - rect.left;
2530 descr->height = rect.bottom - rect.top;
2531 descr->items = NULL;
2532 descr->nb_items = 0;
2533 descr->top_item = 0;
2534 descr->selected_item = -1;
2535 descr->focus_item = 0;
2536 descr->anchor_item = -1;
2537 descr->item_height = 1;
2538 descr->page_size = 1;
2539 descr->column_width = 150;
2540 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2541 descr->horz_pos = 0;
2542 descr->nb_tabs = 0;
2543 descr->tabs = NULL;
2544 descr->caret_on = lphc ? FALSE : TRUE;
2545 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2546 descr->in_focus = FALSE;
2547 descr->captured = FALSE;
2548 descr->font = 0;
2549 descr->locale = GetUserDefaultLCID();
2550 descr->lphc = lphc;
2552 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2554 /* Win95 document "List Box Differences" from MSDN:
2555 If a list box in a version 3.x application has either the
2556 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2557 horizontal and vertical scroll bars.
2559 descr->style |= WS_VSCROLL | WS_HSCROLL;
2562 if( lphc )
2564 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2565 descr->owner = lphc->self;
2568 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2570 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2572 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2573 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2574 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2575 descr->item_height = LISTBOX_SetFont( descr, 0 );
2577 if (descr->style & LBS_OWNERDRAWFIXED)
2579 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2581 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2582 descr->item_height = lphc->fixedOwnerDrawHeight;
2584 else
2586 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2587 mis.CtlType = ODT_LISTBOX;
2588 mis.CtlID = id;
2589 mis.itemID = -1;
2590 mis.itemWidth = 0;
2591 mis.itemData = 0;
2592 mis.itemHeight = descr->item_height;
2593 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2594 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2598 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2599 return TRUE;
2603 /***********************************************************************
2604 * LISTBOX_Destroy
2606 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2608 LISTBOX_ResetContent( descr );
2609 SetWindowLongPtrW( descr->self, 0, 0 );
2610 HeapFree( GetProcessHeap(), 0, descr );
2611 return TRUE;
2615 /***********************************************************************
2616 * ListBoxWndProc_common
2618 static LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg,
2619 WPARAM wParam, LPARAM lParam, BOOL unicode )
2621 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2622 LPHEADCOMBO lphc = 0;
2623 LRESULT ret;
2625 if (!descr)
2627 if (!IsWindow(hwnd)) return 0;
2629 if (msg == WM_CREATE)
2631 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2632 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2633 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2634 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2635 return 0;
2637 /* Ignore all other messages before we get a WM_CREATE */
2638 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2639 DefWindowProcA( hwnd, msg, wParam, lParam );
2641 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2643 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2644 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2646 switch(msg)
2648 case LB_RESETCONTENT16:
2649 case LB_RESETCONTENT:
2650 LISTBOX_ResetContent( descr );
2651 LISTBOX_UpdateScroll( descr );
2652 InvalidateRect( descr->self, NULL, TRUE );
2653 return 0;
2655 case LB_ADDSTRING16:
2656 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2657 /* fall through */
2658 case LB_ADDSTRING:
2660 INT ret;
2661 LPWSTR textW;
2662 if(unicode || !HAS_STRINGS(descr))
2663 textW = (LPWSTR)lParam;
2664 else
2666 LPSTR textA = (LPSTR)lParam;
2667 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2668 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2669 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2670 else
2671 return LB_ERRSPACE;
2673 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2674 ret = LISTBOX_InsertString( descr, wParam, textW );
2675 if (!unicode && HAS_STRINGS(descr))
2676 HeapFree(GetProcessHeap(), 0, textW);
2677 return ret;
2680 case LB_INSERTSTRING16:
2681 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2682 wParam = (INT)(INT16)wParam;
2683 /* fall through */
2684 case LB_INSERTSTRING:
2686 INT ret;
2687 LPWSTR textW;
2688 if(unicode || !HAS_STRINGS(descr))
2689 textW = (LPWSTR)lParam;
2690 else
2692 LPSTR textA = (LPSTR)lParam;
2693 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2694 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2695 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2696 else
2697 return LB_ERRSPACE;
2699 ret = LISTBOX_InsertString( descr, wParam, textW );
2700 if(!unicode && HAS_STRINGS(descr))
2701 HeapFree(GetProcessHeap(), 0, textW);
2702 return ret;
2705 case LB_ADDFILE16:
2706 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2707 /* fall through */
2708 case LB_ADDFILE:
2710 INT ret;
2711 LPWSTR textW;
2712 if(unicode || !HAS_STRINGS(descr))
2713 textW = (LPWSTR)lParam;
2714 else
2716 LPSTR textA = (LPSTR)lParam;
2717 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2718 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2719 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2720 else
2721 return LB_ERRSPACE;
2723 wParam = LISTBOX_FindFileStrPos( descr, textW );
2724 ret = LISTBOX_InsertString( descr, wParam, textW );
2725 if(!unicode && HAS_STRINGS(descr))
2726 HeapFree(GetProcessHeap(), 0, textW);
2727 return ret;
2730 case LB_DELETESTRING16:
2731 case LB_DELETESTRING:
2732 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2733 return descr->nb_items;
2734 else
2736 SetLastError(ERROR_INVALID_INDEX);
2737 return LB_ERR;
2740 case LB_GETITEMDATA16:
2741 case LB_GETITEMDATA:
2742 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2744 SetLastError(ERROR_INVALID_INDEX);
2745 return LB_ERR;
2747 return descr->items[wParam].data;
2749 case LB_SETITEMDATA16:
2750 case LB_SETITEMDATA:
2751 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2753 SetLastError(ERROR_INVALID_INDEX);
2754 return LB_ERR;
2756 descr->items[wParam].data = lParam;
2757 /* undocumented: returns TRUE, not LB_OKAY (0) */
2758 return TRUE;
2760 case LB_GETCOUNT16:
2761 case LB_GETCOUNT:
2762 return descr->nb_items;
2764 case LB_GETTEXT16:
2765 lParam = (LPARAM)MapSL(lParam);
2766 /* fall through */
2767 case LB_GETTEXT:
2768 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2770 case LB_GETTEXTLEN16:
2771 /* fall through */
2772 case LB_GETTEXTLEN:
2773 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2775 SetLastError(ERROR_INVALID_INDEX);
2776 return LB_ERR;
2778 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2779 if (unicode) return strlenW( descr->items[wParam].str );
2780 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2781 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2783 case LB_GETCURSEL16:
2784 case LB_GETCURSEL:
2785 if (descr->nb_items == 0)
2786 return LB_ERR;
2787 if (!IS_MULTISELECT(descr))
2788 return descr->selected_item;
2789 if (descr->selected_item != -1)
2790 return descr->selected_item;
2791 return descr->focus_item;
2792 /* otherwise, if the user tries to move the selection with the */
2793 /* arrow keys, we will give the application something to choke on */
2794 case LB_GETTOPINDEX16:
2795 case LB_GETTOPINDEX:
2796 return descr->top_item;
2798 case LB_GETITEMHEIGHT16:
2799 case LB_GETITEMHEIGHT:
2800 return LISTBOX_GetItemHeight( descr, wParam );
2802 case LB_SETITEMHEIGHT16:
2803 lParam = LOWORD(lParam);
2804 /* fall through */
2805 case LB_SETITEMHEIGHT:
2806 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2808 case LB_ITEMFROMPOINT:
2810 POINT pt;
2811 RECT rect;
2812 int index;
2813 BOOL hit = TRUE;
2815 /* The hiword of the return value is not a client area
2816 hittest as suggested by MSDN, but rather a hittest on
2817 the returned listbox item. */
2819 if(descr->nb_items == 0)
2820 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2822 pt.x = (short)LOWORD(lParam);
2823 pt.y = (short)HIWORD(lParam);
2825 SetRect(&rect, 0, 0, descr->width, descr->height);
2827 if(!PtInRect(&rect, pt))
2829 pt.x = min(pt.x, rect.right - 1);
2830 pt.x = max(pt.x, 0);
2831 pt.y = min(pt.y, rect.bottom - 1);
2832 pt.y = max(pt.y, 0);
2833 hit = FALSE;
2836 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2838 if(index == -1)
2840 index = descr->nb_items - 1;
2841 hit = FALSE;
2843 return MAKELONG(index, hit ? 0 : 1);
2846 case LB_SETCARETINDEX16:
2847 case LB_SETCARETINDEX:
2848 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2849 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2850 return LB_ERR;
2851 else if (ISWIN31)
2852 return wParam;
2853 else
2854 return LB_OKAY;
2856 case LB_GETCARETINDEX16:
2857 case LB_GETCARETINDEX:
2858 return descr->focus_item;
2860 case LB_SETTOPINDEX16:
2861 case LB_SETTOPINDEX:
2862 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2864 case LB_SETCOLUMNWIDTH16:
2865 case LB_SETCOLUMNWIDTH:
2866 return LISTBOX_SetColumnWidth( descr, wParam );
2868 case LB_GETITEMRECT16:
2870 RECT rect;
2871 RECT16 *r16 = MapSL(lParam);
2872 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2873 r16->left = rect.left;
2874 r16->top = rect.top;
2875 r16->right = rect.right;
2876 r16->bottom = rect.bottom;
2878 return ret;
2880 case LB_GETITEMRECT:
2881 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2883 case LB_FINDSTRING16:
2884 wParam = (INT)(INT16)wParam;
2885 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2886 /* fall through */
2887 case LB_FINDSTRING:
2889 INT ret;
2890 LPWSTR textW;
2891 if(unicode || !HAS_STRINGS(descr))
2892 textW = (LPWSTR)lParam;
2893 else
2895 LPSTR textA = (LPSTR)lParam;
2896 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2897 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2898 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2900 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2901 if(!unicode && HAS_STRINGS(descr))
2902 HeapFree(GetProcessHeap(), 0, textW);
2903 return ret;
2906 case LB_FINDSTRINGEXACT16:
2907 wParam = (INT)(INT16)wParam;
2908 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2909 /* fall through */
2910 case LB_FINDSTRINGEXACT:
2912 INT ret;
2913 LPWSTR textW;
2914 if(unicode || !HAS_STRINGS(descr))
2915 textW = (LPWSTR)lParam;
2916 else
2918 LPSTR textA = (LPSTR)lParam;
2919 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2920 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2921 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2923 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2924 if(!unicode && HAS_STRINGS(descr))
2925 HeapFree(GetProcessHeap(), 0, textW);
2926 return ret;
2929 case LB_SELECTSTRING16:
2930 wParam = (INT)(INT16)wParam;
2931 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2932 /* fall through */
2933 case LB_SELECTSTRING:
2935 INT index;
2936 LPWSTR textW;
2938 if(HAS_STRINGS(descr))
2939 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2940 debugstr_a((LPSTR)lParam));
2941 if(unicode || !HAS_STRINGS(descr))
2942 textW = (LPWSTR)lParam;
2943 else
2945 LPSTR textA = (LPSTR)lParam;
2946 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2947 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2948 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2950 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2951 if(!unicode && HAS_STRINGS(descr))
2952 HeapFree(GetProcessHeap(), 0, textW);
2953 if (index != LB_ERR)
2955 LISTBOX_MoveCaret( descr, index, TRUE );
2956 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2958 return index;
2961 case LB_GETSEL16:
2962 wParam = (INT)(INT16)wParam;
2963 /* fall through */
2964 case LB_GETSEL:
2965 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2966 return LB_ERR;
2967 return descr->items[wParam].selected;
2969 case LB_SETSEL16:
2970 lParam = (INT)(INT16)lParam;
2971 /* fall through */
2972 case LB_SETSEL:
2973 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2975 case LB_SETCURSEL16:
2976 wParam = (INT)(INT16)wParam;
2977 /* fall through */
2978 case LB_SETCURSEL:
2979 if (IS_MULTISELECT(descr)) return LB_ERR;
2980 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2981 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2982 if (ret != LB_ERR) ret = descr->selected_item;
2983 return ret;
2985 case LB_GETSELCOUNT16:
2986 case LB_GETSELCOUNT:
2987 return LISTBOX_GetSelCount( descr );
2989 case LB_GETSELITEMS16:
2990 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2992 case LB_GETSELITEMS:
2993 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2995 case LB_SELITEMRANGE16:
2996 case LB_SELITEMRANGE:
2997 if (LOWORD(lParam) <= HIWORD(lParam))
2998 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2999 HIWORD(lParam), wParam );
3000 else
3001 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
3002 LOWORD(lParam), wParam );
3004 case LB_SELITEMRANGEEX16:
3005 case LB_SELITEMRANGEEX:
3006 if ((INT)lParam >= (INT)wParam)
3007 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
3008 else
3009 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
3011 case LB_GETHORIZONTALEXTENT16:
3012 case LB_GETHORIZONTALEXTENT:
3013 return descr->horz_extent;
3015 case LB_SETHORIZONTALEXTENT16:
3016 case LB_SETHORIZONTALEXTENT:
3017 return LISTBOX_SetHorizontalExtent( descr, wParam );
3019 case LB_GETANCHORINDEX16:
3020 case LB_GETANCHORINDEX:
3021 return descr->anchor_item;
3023 case LB_SETANCHORINDEX16:
3024 wParam = (INT)(INT16)wParam;
3025 /* fall through */
3026 case LB_SETANCHORINDEX:
3027 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
3029 SetLastError(ERROR_INVALID_INDEX);
3030 return LB_ERR;
3032 descr->anchor_item = (INT)wParam;
3033 return LB_OKAY;
3035 case LB_DIR16:
3036 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
3037 * be set automatically (this is different in Win32) */
3038 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
3039 lParam = (LPARAM)MapSL(lParam);
3040 /* fall through */
3041 case LB_DIR:
3043 INT ret;
3044 LPWSTR textW;
3045 if(unicode)
3046 textW = (LPWSTR)lParam;
3047 else
3049 LPSTR textA = (LPSTR)lParam;
3050 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
3051 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
3052 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
3054 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
3055 if(!unicode)
3056 HeapFree(GetProcessHeap(), 0, textW);
3057 return ret;
3060 case LB_GETLOCALE:
3061 return descr->locale;
3063 case LB_SETLOCALE:
3065 LCID ret;
3066 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
3067 return LB_ERR;
3068 ret = descr->locale;
3069 descr->locale = (LCID)wParam;
3070 return ret;
3073 case LB_INITSTORAGE:
3074 return LISTBOX_InitStorage( descr, wParam );
3076 case LB_SETCOUNT:
3077 return LISTBOX_SetCount( descr, (INT)wParam );
3079 case LB_SETTABSTOPS16:
3080 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
3082 case LB_SETTABSTOPS:
3083 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
3085 case LB_CARETON16:
3086 case LB_CARETON:
3087 if (descr->caret_on)
3088 return LB_OKAY;
3089 descr->caret_on = TRUE;
3090 if ((descr->focus_item != -1) && (descr->in_focus))
3091 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3092 return LB_OKAY;
3094 case LB_CARETOFF16:
3095 case LB_CARETOFF:
3096 if (!descr->caret_on)
3097 return LB_OKAY;
3098 descr->caret_on = FALSE;
3099 if ((descr->focus_item != -1) && (descr->in_focus))
3100 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3101 return LB_OKAY;
3103 case LB_GETLISTBOXINFO:
3104 FIXME("LB_GETLISTBOXINFO: stub!\n");
3105 return 0;
3107 case WM_DESTROY:
3108 return LISTBOX_Destroy( descr );
3110 case WM_ENABLE:
3111 InvalidateRect( descr->self, NULL, TRUE );
3112 return 0;
3114 case WM_SETREDRAW:
3115 LISTBOX_SetRedraw( descr, wParam != 0 );
3116 return 0;
3118 case WM_GETDLGCODE:
3119 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3121 case WM_PRINTCLIENT:
3122 case WM_PAINT:
3124 PAINTSTRUCT ps;
3125 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3126 ret = LISTBOX_Paint( descr, hdc );
3127 if( !wParam ) EndPaint( descr->self, &ps );
3129 return ret;
3130 case WM_SIZE:
3131 LISTBOX_UpdateSize( descr );
3132 return 0;
3133 case WM_GETFONT:
3134 return (LRESULT)descr->font;
3135 case WM_SETFONT:
3136 LISTBOX_SetFont( descr, (HFONT)wParam );
3137 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3138 return 0;
3139 case WM_SETFOCUS:
3140 descr->in_focus = TRUE;
3141 descr->caret_on = TRUE;
3142 if (descr->focus_item != -1)
3143 LISTBOX_DrawFocusRect( descr, TRUE );
3144 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3145 return 0;
3146 case WM_KILLFOCUS:
3147 descr->in_focus = FALSE;
3148 if ((descr->focus_item != -1) && descr->caret_on)
3149 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3150 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3151 return 0;
3152 case WM_HSCROLL:
3153 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3154 case WM_VSCROLL:
3155 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3156 case WM_MOUSEWHEEL:
3157 if (wParam & (MK_SHIFT | MK_CONTROL))
3158 return DefWindowProcW( descr->self, msg, wParam, lParam );
3159 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3160 case WM_LBUTTONDOWN:
3161 if (lphc)
3162 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3163 (INT16)LOWORD(lParam),
3164 (INT16)HIWORD(lParam) );
3165 return LISTBOX_HandleLButtonDown( descr, wParam,
3166 (INT16)LOWORD(lParam),
3167 (INT16)HIWORD(lParam) );
3168 case WM_LBUTTONDBLCLK:
3169 if (lphc)
3170 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3171 (INT16)LOWORD(lParam),
3172 (INT16)HIWORD(lParam) );
3173 if (descr->style & LBS_NOTIFY)
3174 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3175 return 0;
3176 case WM_MOUSEMOVE:
3177 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3179 BOOL captured = descr->captured;
3180 POINT mousePos;
3181 RECT clientRect;
3183 mousePos.x = (INT16)LOWORD(lParam);
3184 mousePos.y = (INT16)HIWORD(lParam);
3187 * If we are in a dropdown combobox, we simulate that
3188 * the mouse is captured to show the tracking of the item.
3190 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3191 descr->captured = TRUE;
3193 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3195 descr->captured = captured;
3197 else if (GetCapture() == descr->self)
3199 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3200 (INT16)HIWORD(lParam) );
3202 return 0;
3203 case WM_LBUTTONUP:
3204 if (lphc)
3206 POINT mousePos;
3207 RECT clientRect;
3210 * If the mouse button "up" is not in the listbox,
3211 * we make sure there is no selection by re-selecting the
3212 * item that was selected when the listbox was made visible.
3214 mousePos.x = (INT16)LOWORD(lParam);
3215 mousePos.y = (INT16)HIWORD(lParam);
3217 GetClientRect(descr->self, &clientRect);
3220 * When the user clicks outside the combobox and the focus
3221 * is lost, the owning combobox will send a fake buttonup with
3222 * 0xFFFFFFF as the mouse location, we must also revert the
3223 * selection to the original selection.
3225 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3226 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3228 return LISTBOX_HandleLButtonUp( descr );
3229 case WM_KEYDOWN:
3230 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3232 /* for some reason Windows makes it possible to
3233 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3235 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3236 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3237 && (wParam == VK_DOWN || wParam == VK_UP)) )
3239 COMBO_FlipListbox( lphc, FALSE, FALSE );
3240 return 0;
3243 return LISTBOX_HandleKeyDown( descr, wParam );
3244 case WM_CHAR:
3246 WCHAR charW;
3247 if(unicode)
3248 charW = (WCHAR)wParam;
3249 else
3251 CHAR charA = (CHAR)wParam;
3252 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3254 return LISTBOX_HandleChar( descr, charW );
3256 case WM_SYSTIMER:
3257 return LISTBOX_HandleSystemTimer( descr );
3258 case WM_ERASEBKGND:
3259 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3261 RECT rect;
3262 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3263 wParam, (LPARAM)descr->self );
3264 TRACE("hbrush = %p\n", hbrush);
3265 if(!hbrush)
3266 hbrush = GetSysColorBrush(COLOR_WINDOW);
3267 if(hbrush)
3269 GetClientRect(descr->self, &rect);
3270 FillRect((HDC)wParam, &rect, hbrush);
3273 return 1;
3274 case WM_DROPFILES:
3275 if( lphc ) return 0;
3276 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3277 SendMessageA( descr->owner, msg, wParam, lParam );
3279 case WM_NCDESTROY:
3280 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3281 lphc->hWndLBox = 0;
3282 break;
3284 case WM_NCACTIVATE:
3285 if (lphc) return 0;
3286 break;
3288 default:
3289 if ((msg >= WM_USER) && (msg < 0xc000))
3290 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3291 hwnd, msg, wParam, lParam );
3294 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3295 DefWindowProcA( hwnd, msg, wParam, lParam );
3298 /***********************************************************************
3299 * ListBoxWndProcA
3301 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3303 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3306 /***********************************************************************
3307 * ListBoxWndProcW
3309 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3311 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );