Added WC_STATIC.
[wine/hacks.git] / dlls / user / listbox.c
blobccf98239276e5fbeb14200e79c316261bbe4003e
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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/winbase16.h"
44 #include "wine/unicode.h"
45 #include "user_private.h"
46 #include "controls.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 DWORD 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( LB_DESCR *descr, INT index, RECT *rect );
137 /*********************************************************************
138 * listbox class descriptor
140 const struct builtin_class_descr LISTBOX_builtin_class =
142 "ListBox", /* name */
143 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
144 ListBoxWndProcA, /* procA */
145 ListBoxWndProcW, /* procW */
146 sizeof(LB_DESCR *), /* extra */
147 IDC_ARROW, /* cursor */
148 0 /* brush */
152 /*********************************************************************
153 * combolbox class descriptor
155 const struct builtin_class_descr COMBOLBOX_builtin_class =
157 "ComboLBox", /* name */
158 CS_DBLCLKS | CS_SAVEBITS, /* style */
159 ListBoxWndProcA, /* procA */
160 ListBoxWndProcW, /* procW */
161 sizeof(LB_DESCR *), /* extra */
162 IDC_ARROW, /* cursor */
163 0 /* brush */
167 /* check whether app is a Win 3.1 app */
168 inline static BOOL is_old_app( LB_DESCR *descr )
170 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
174 /***********************************************************************
175 * LISTBOX_GetCurrentPageSize
177 * Return the current page size
179 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
181 INT i, height;
182 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
183 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
185 if ((height += descr->items[i].height) > descr->height) break;
187 if (i == descr->top_item) return 1;
188 else return i - descr->top_item;
192 /***********************************************************************
193 * LISTBOX_GetMaxTopIndex
195 * Return the maximum possible index for the top of the listbox.
197 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
199 INT max, page;
201 if (descr->style & LBS_OWNERDRAWVARIABLE)
203 page = descr->height;
204 for (max = descr->nb_items - 1; max >= 0; max--)
205 if ((page -= descr->items[max].height) < 0) break;
206 if (max < descr->nb_items - 1) max++;
208 else if (descr->style & LBS_MULTICOLUMN)
210 if ((page = descr->width / descr->column_width) < 1) page = 1;
211 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
212 max = (max - page) * descr->page_size;
214 else
216 max = descr->nb_items - descr->page_size;
218 if (max < 0) max = 0;
219 return max;
223 /***********************************************************************
224 * LISTBOX_UpdateScroll
226 * Update the scrollbars. Should be called whenever the content
227 * of the listbox changes.
229 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
231 SCROLLINFO info;
233 /* Check the listbox scroll bar flags individually before we call
234 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
235 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
236 scroll bar when we do not need one.
237 if (!(descr->style & WS_VSCROLL)) return;
240 /* It is important that we check descr->style, and not wnd->dwStyle,
241 for WS_VSCROLL, as the former is exactly the one passed in
242 argument to CreateWindow.
243 In Windows (and from now on in Wine :) a listbox created
244 with such a style (no WS_SCROLL) does not update
245 the scrollbar with listbox-related data, thus letting
246 the programmer use it for his/her own purposes. */
248 if (descr->style & LBS_NOREDRAW) return;
249 info.cbSize = sizeof(info);
251 if (descr->style & LBS_MULTICOLUMN)
253 info.nMin = 0;
254 info.nMax = (descr->nb_items - 1) / descr->page_size;
255 info.nPos = descr->top_item / descr->page_size;
256 info.nPage = descr->width / descr->column_width;
257 if (info.nPage < 1) info.nPage = 1;
258 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
259 if (descr->style & LBS_DISABLENOSCROLL)
260 info.fMask |= SIF_DISABLENOSCROLL;
261 if (descr->style & WS_HSCROLL)
262 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
263 info.nMax = 0;
264 info.fMask = SIF_RANGE;
265 if (descr->style & WS_VSCROLL)
266 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
268 else
270 info.nMin = 0;
271 info.nMax = descr->nb_items - 1;
272 info.nPos = descr->top_item;
273 info.nPage = LISTBOX_GetCurrentPageSize( descr );
274 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
275 if (descr->style & LBS_DISABLENOSCROLL)
276 info.fMask |= SIF_DISABLENOSCROLL;
277 if (descr->style & WS_VSCROLL)
278 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
280 if (descr->horz_extent)
282 info.nMin = 0;
283 info.nMax = descr->horz_extent - 1;
284 info.nPos = descr->horz_pos;
285 info.nPage = descr->width;
286 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
287 if (descr->style & LBS_DISABLENOSCROLL)
288 info.fMask |= SIF_DISABLENOSCROLL;
289 if (descr->style & WS_HSCROLL)
290 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
296 /***********************************************************************
297 * LISTBOX_SetTopItem
299 * Set the top item of the listbox, scrolling up or down if necessary.
301 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
303 INT max = LISTBOX_GetMaxTopIndex( descr );
304 if (index > max) index = max;
305 if (index < 0) index = 0;
306 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
307 if (descr->top_item == index) return LB_OKAY;
308 if (descr->style & LBS_MULTICOLUMN)
310 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
311 if (scroll && (abs(diff) < descr->width))
312 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
313 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
315 else
316 scroll = FALSE;
318 else if (scroll)
320 INT diff;
321 if (descr->style & LBS_OWNERDRAWVARIABLE)
323 INT i;
324 diff = 0;
325 if (index > descr->top_item)
327 for (i = index - 1; i >= descr->top_item; i--)
328 diff -= descr->items[i].height;
330 else
332 for (i = index; i < descr->top_item; i++)
333 diff += descr->items[i].height;
336 else
337 diff = (descr->top_item - index) * descr->item_height;
339 if (abs(diff) < descr->height)
340 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
341 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
342 else
343 scroll = FALSE;
345 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
346 descr->top_item = index;
347 LISTBOX_UpdateScroll( descr );
348 return LB_OKAY;
352 /***********************************************************************
353 * LISTBOX_UpdatePage
355 * Update the page size. Should be called when the size of
356 * the client area or the item height changes.
358 static void LISTBOX_UpdatePage( LB_DESCR *descr )
360 INT page_size;
362 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
363 page_size = 1;
364 if (page_size == descr->page_size) return;
365 descr->page_size = page_size;
366 if (descr->style & LBS_MULTICOLUMN)
367 InvalidateRect( descr->self, NULL, TRUE );
368 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
372 /***********************************************************************
373 * LISTBOX_UpdateSize
375 * Update the size of the listbox. Should be called when the size of
376 * the client area changes.
378 static void LISTBOX_UpdateSize( LB_DESCR *descr )
380 RECT rect;
382 GetClientRect( descr->self, &rect );
383 descr->width = rect.right - rect.left;
384 descr->height = rect.bottom - rect.top;
385 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
387 INT remaining;
388 RECT rect;
390 GetWindowRect( descr->self, &rect );
391 if(descr->item_height != 0)
392 remaining = descr->height % descr->item_height;
393 else
394 remaining = 0;
395 if ((descr->height > descr->item_height) && remaining)
397 if (is_old_app(descr))
398 { /* give a margin for error to 16 bits programs - if we need
399 less than the height of the nonclient area, round to the
400 *next* number of items */
401 int ncheight = rect.bottom - rect.top - descr->height;
402 if ((descr->item_height - remaining) <= ncheight)
403 remaining = remaining - descr->item_height;
405 TRACE("[%p]: changing height %d -> %d\n",
406 descr->self, descr->height, descr->height - remaining );
407 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
408 rect.bottom - rect.top - remaining,
409 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
410 return;
413 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
414 LISTBOX_UpdatePage( descr );
415 LISTBOX_UpdateScroll( descr );
417 /* Invalidate the focused item so it will be repainted correctly */
418 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
420 InvalidateRect( descr->self, &rect, FALSE );
425 /***********************************************************************
426 * LISTBOX_GetItemRect
428 * Get the rectangle enclosing an item, in listbox client coordinates.
429 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
431 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
433 /* Index <= 0 is legal even on empty listboxes */
434 if (index && (index >= descr->nb_items)) return -1;
435 SetRect( rect, 0, 0, descr->width, descr->height );
436 if (descr->style & LBS_MULTICOLUMN)
438 INT col = (index / descr->page_size) -
439 (descr->top_item / descr->page_size);
440 rect->left += col * descr->column_width;
441 rect->right = rect->left + descr->column_width;
442 rect->top += (index % descr->page_size) * descr->item_height;
443 rect->bottom = rect->top + descr->item_height;
445 else if (descr->style & LBS_OWNERDRAWVARIABLE)
447 INT i;
448 rect->right += descr->horz_pos;
449 if ((index >= 0) && (index < descr->nb_items))
451 if (index < descr->top_item)
453 for (i = descr->top_item-1; i >= index; i--)
454 rect->top -= descr->items[i].height;
456 else
458 for (i = descr->top_item; i < index; i++)
459 rect->top += descr->items[i].height;
461 rect->bottom = rect->top + descr->items[index].height;
465 else
467 rect->top += (index - descr->top_item) * descr->item_height;
468 rect->bottom = rect->top + descr->item_height;
469 rect->right += descr->horz_pos;
472 return ((rect->left < descr->width) && (rect->right > 0) &&
473 (rect->top < descr->height) && (rect->bottom > 0));
477 /***********************************************************************
478 * LISTBOX_GetItemFromPoint
480 * Return the item nearest from point (x,y) (in client coordinates).
482 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
484 INT index = descr->top_item;
486 if (!descr->nb_items) return -1; /* No items */
487 if (descr->style & LBS_OWNERDRAWVARIABLE)
489 INT pos = 0;
490 if (y >= 0)
492 while (index < descr->nb_items)
494 if ((pos += descr->items[index].height) > y) break;
495 index++;
498 else
500 while (index > 0)
502 index--;
503 if ((pos -= descr->items[index].height) <= y) break;
507 else if (descr->style & LBS_MULTICOLUMN)
509 if (y >= descr->item_height * descr->page_size) return -1;
510 if (y >= 0) index += y / descr->item_height;
511 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
512 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
514 else
516 index += (y / descr->item_height);
518 if (index < 0) return 0;
519 if (index >= descr->nb_items) return -1;
520 return index;
524 /***********************************************************************
525 * LISTBOX_PaintItem
527 * Paint an item.
529 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
530 INT index, UINT action, BOOL ignoreFocus )
532 LB_ITEMDATA *item = NULL;
533 if (index < descr->nb_items) item = &descr->items[index];
535 if (IS_OWNERDRAW(descr))
537 DRAWITEMSTRUCT dis;
538 RECT r;
539 HRGN hrgn;
540 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
542 if (!item)
544 if (action == ODA_FOCUS)
545 DrawFocusRect( hdc, rect );
546 else
547 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
548 return;
551 /* some programs mess with the clipping region when
552 drawing the item, *and* restore the previous region
553 after they are done, so a region has better to exist
554 else everything ends clipped */
555 GetClientRect(descr->self, &r);
556 hrgn = CreateRectRgnIndirect(&r);
557 SelectClipRgn( hdc, hrgn);
558 DeleteObject( hrgn );
560 dis.CtlType = ODT_LISTBOX;
561 dis.CtlID = id;
562 dis.hwndItem = descr->self;
563 dis.itemAction = action;
564 dis.hDC = hdc;
565 dis.itemID = index;
566 dis.itemState = 0;
567 if (item && item->selected) dis.itemState |= ODS_SELECTED;
568 if (!ignoreFocus && (descr->focus_item == index) &&
569 (descr->caret_on) &&
570 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
571 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
572 dis.itemData = item ? item->data : 0;
573 dis.rcItem = *rect;
574 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
575 descr->self, index, item ? debugstr_w(item->str) : "", action,
576 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
577 SendMessageW(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
579 else
581 COLORREF oldText = 0, oldBk = 0;
583 if (action == ODA_FOCUS)
585 DrawFocusRect( hdc, rect );
586 return;
588 if (item && item->selected)
590 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
591 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
594 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
595 descr->self, index, item ? debugstr_w(item->str) : "", action,
596 rect->left, rect->top, rect->right, rect->bottom );
597 if (!item)
598 ExtTextOutW( hdc, rect->left + 1, rect->top,
599 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
600 else if (!(descr->style & LBS_USETABSTOPS))
601 ExtTextOutW( hdc, rect->left + 1, rect->top,
602 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
603 strlenW(item->str), NULL );
604 else
606 /* Output empty string to paint background in the full width. */
607 ExtTextOutW( hdc, rect->left + 1, rect->top,
608 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
609 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
610 item->str, strlenW(item->str),
611 descr->nb_tabs, descr->tabs, 0);
613 if (item && item->selected)
615 SetBkColor( hdc, oldBk );
616 SetTextColor( hdc, oldText );
618 if (!ignoreFocus && (descr->focus_item == index) &&
619 (descr->caret_on) &&
620 (descr->in_focus)) DrawFocusRect( hdc, rect );
625 /***********************************************************************
626 * LISTBOX_SetRedraw
628 * Change the redraw flag.
630 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
632 if (on)
634 if (!(descr->style & LBS_NOREDRAW)) return;
635 descr->style &= ~LBS_NOREDRAW;
636 if (descr->style & LBS_DISPLAYCHANGED)
637 { /* page was changed while setredraw false, refresh automatically */
638 InvalidateRect(descr->self, NULL, TRUE);
639 if ((descr->top_item + descr->page_size) > descr->nb_items)
640 { /* reset top of page if less than number of items/page */
641 descr->top_item = descr->nb_items - descr->page_size;
642 if (descr->top_item < 0) descr->top_item = 0;
644 descr->style &= ~LBS_DISPLAYCHANGED;
646 LISTBOX_UpdateScroll( descr );
648 else descr->style |= LBS_NOREDRAW;
652 /***********************************************************************
653 * LISTBOX_RepaintItem
655 * Repaint a single item synchronously.
657 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
659 HDC hdc;
660 RECT rect;
661 HFONT oldFont = 0;
662 HBRUSH hbrush, oldBrush = 0;
664 /* Do not repaint the item if the item is not visible */
665 if (!IsWindowVisible(descr->self)) return;
666 if (descr->style & LBS_NOREDRAW)
668 descr->style |= LBS_DISPLAYCHANGED;
669 return;
671 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
672 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
673 if (descr->font) oldFont = SelectObject( hdc, descr->font );
674 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
675 (WPARAM)hdc, (LPARAM)descr->self );
676 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
677 if (!IsWindowEnabled(descr->self))
678 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
679 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
680 LISTBOX_PaintItem( descr, hdc, &rect, index, action, FALSE );
681 if (oldFont) SelectObject( hdc, oldFont );
682 if (oldBrush) SelectObject( hdc, oldBrush );
683 ReleaseDC( descr->self, hdc );
687 /***********************************************************************
688 * LISTBOX_InitStorage
690 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
692 LB_ITEMDATA *item;
694 nb_items += LB_ARRAY_GRANULARITY - 1;
695 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
696 if (descr->items) {
697 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
698 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
699 nb_items * sizeof(LB_ITEMDATA));
701 else {
702 item = HeapAlloc( GetProcessHeap(), 0,
703 nb_items * sizeof(LB_ITEMDATA));
706 if (!item)
708 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
709 return LB_ERRSPACE;
711 descr->items = item;
712 return LB_OKAY;
716 /***********************************************************************
717 * LISTBOX_SetTabStops
719 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
721 INT i;
723 if (!(descr->style & LBS_USETABSTOPS))
725 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
726 return FALSE;
729 HeapFree( GetProcessHeap(), 0, descr->tabs );
730 if (!(descr->nb_tabs = count))
732 descr->tabs = NULL;
733 return TRUE;
735 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
736 descr->nb_tabs * sizeof(INT) )))
737 return FALSE;
738 if (short_ints)
740 INT i;
741 LPINT16 p = (LPINT16)tabs;
743 TRACE("[%p]: settabstops ", descr->self );
744 for (i = 0; i < descr->nb_tabs; i++) {
745 descr->tabs[i] = *p++<<1; /* FIXME */
746 TRACE("%hd ", descr->tabs[i]);
748 TRACE("\n");
750 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
752 /* convert into "dialog units"*/
753 for (i = 0; i < descr->nb_tabs; i++)
754 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
756 return TRUE;
760 /***********************************************************************
761 * LISTBOX_GetText
763 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
765 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
766 if (HAS_STRINGS(descr))
768 if (!buffer)
770 DWORD len = strlenW(descr->items[index].str);
771 if( unicode )
772 return len;
773 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
774 NULL, 0, NULL, NULL );
777 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
779 if(unicode)
781 strcpyW( buffer, descr->items[index].str );
782 return strlenW(buffer);
784 else
786 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
788 } else {
789 if (buffer)
790 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
791 return sizeof(DWORD);
795 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
797 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
798 if (ret == CSTR_LESS_THAN)
799 return -1;
800 if (ret == CSTR_EQUAL)
801 return 0;
802 if (ret == CSTR_GREATER_THAN)
803 return 1;
804 return -1;
807 /***********************************************************************
808 * LISTBOX_FindStringPos
810 * Find the nearest string located before a given string in sort order.
811 * If 'exact' is TRUE, return an error if we don't get an exact match.
813 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
815 INT index, min, max, res = -1;
817 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
818 min = 0;
819 max = descr->nb_items;
820 while (min != max)
822 index = (min + max) / 2;
823 if (HAS_STRINGS(descr))
824 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
825 else
827 COMPAREITEMSTRUCT cis;
828 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
830 cis.CtlType = ODT_LISTBOX;
831 cis.CtlID = id;
832 cis.hwndItem = descr->self;
833 /* note that some application (MetaStock) expects the second item
834 * to be in the listbox */
835 cis.itemID1 = -1;
836 cis.itemData1 = (DWORD)str;
837 cis.itemID2 = index;
838 cis.itemData2 = descr->items[index].data;
839 cis.dwLocaleId = descr->locale;
840 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
842 if (!res) return index;
843 if (res < 0) max = index;
844 else min = index + 1;
846 return exact ? -1 : max;
850 /***********************************************************************
851 * LISTBOX_FindFileStrPos
853 * Find the nearest string located before a given string in directory
854 * sort order (i.e. first files, then directories, then drives).
856 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
858 INT min, max, res = -1;
860 if (!HAS_STRINGS(descr))
861 return LISTBOX_FindStringPos( descr, str, FALSE );
862 min = 0;
863 max = descr->nb_items;
864 while (min != max)
866 INT index = (min + max) / 2;
867 LPCWSTR p = descr->items[index].str;
868 if (*p == '[') /* drive or directory */
870 if (*str != '[') res = -1;
871 else if (p[1] == '-') /* drive */
873 if (str[1] == '-') res = str[2] - p[2];
874 else res = -1;
876 else /* directory */
878 if (str[1] == '-') res = 1;
879 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
882 else /* filename */
884 if (*str == '[') res = 1;
885 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
887 if (!res) return index;
888 if (res < 0) max = index;
889 else min = index + 1;
891 return max;
895 /***********************************************************************
896 * LISTBOX_FindString
898 * Find the item beginning with a given string.
900 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
902 INT i;
903 LB_ITEMDATA *item;
905 if (start >= descr->nb_items) start = -1;
906 item = descr->items + start + 1;
907 if (HAS_STRINGS(descr))
909 if (!str || ! str[0] ) return LB_ERR;
910 if (exact)
912 for (i = start + 1; i < descr->nb_items; i++, item++)
913 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
914 for (i = 0, item = descr->items; i <= start; i++, item++)
915 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
917 else
919 /* Special case for drives and directories: ignore prefix */
920 #define CHECK_DRIVE(item) \
921 if ((item)->str[0] == '[') \
923 if (!strncmpiW( str, (item)->str+1, len )) return i; \
924 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
925 return i; \
928 INT len = strlenW(str);
929 for (i = start + 1; i < descr->nb_items; i++, item++)
931 if (!strncmpiW( str, item->str, len )) return i;
932 CHECK_DRIVE(item);
934 for (i = 0, item = descr->items; i <= start; i++, item++)
936 if (!strncmpiW( str, item->str, len )) return i;
937 CHECK_DRIVE(item);
939 #undef CHECK_DRIVE
942 else
944 if (exact && (descr->style & LBS_SORT))
945 /* If sorted, use a WM_COMPAREITEM binary search */
946 return LISTBOX_FindStringPos( descr, str, TRUE );
948 /* Otherwise use a linear search */
949 for (i = start + 1; i < descr->nb_items; i++, item++)
950 if (item->data == (DWORD)str) return i;
951 for (i = 0, item = descr->items; i <= start; i++, item++)
952 if (item->data == (DWORD)str) return i;
954 return LB_ERR;
958 /***********************************************************************
959 * LISTBOX_GetSelCount
961 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
963 INT i, count;
964 LB_ITEMDATA *item = descr->items;
966 if (!(descr->style & LBS_MULTIPLESEL) ||
967 (descr->style & LBS_NOSEL))
968 return LB_ERR;
969 for (i = count = 0; i < descr->nb_items; i++, item++)
970 if (item->selected) count++;
971 return count;
975 /***********************************************************************
976 * LISTBOX_GetSelItems16
978 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
980 INT i, count;
981 LB_ITEMDATA *item = descr->items;
983 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
984 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
985 if (item->selected) array[count++] = (INT16)i;
986 return count;
990 /***********************************************************************
991 * LISTBOX_GetSelItems
993 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
995 INT i, count;
996 LB_ITEMDATA *item = descr->items;
998 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
999 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1000 if (item->selected) array[count++] = i;
1001 return count;
1005 /***********************************************************************
1006 * LISTBOX_Paint
1008 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1010 INT i, col_pos = descr->page_size - 1;
1011 RECT rect;
1012 RECT focusRect = {-1, -1, -1, -1};
1013 HFONT oldFont = 0;
1014 HBRUSH hbrush, oldBrush = 0;
1016 if (descr->style & LBS_NOREDRAW) return 0;
1018 SetRect( &rect, 0, 0, descr->width, descr->height );
1019 if (descr->style & LBS_MULTICOLUMN)
1020 rect.right = rect.left + descr->column_width;
1021 else if (descr->horz_pos)
1023 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1024 rect.right += descr->horz_pos;
1027 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1028 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1029 (WPARAM)hdc, (LPARAM)descr->self );
1030 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1031 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1033 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1034 (descr->in_focus))
1036 /* Special case for empty listbox: paint focus rect */
1037 rect.bottom = rect.top + descr->item_height;
1038 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1039 &rect, NULL, 0, NULL );
1040 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1041 rect.top = rect.bottom;
1044 /* Paint all the item, regarding the selection
1045 Focus state will be painted after */
1047 for (i = descr->top_item; i < descr->nb_items; i++)
1049 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1050 rect.bottom = rect.top + descr->item_height;
1051 else
1052 rect.bottom = rect.top + descr->items[i].height;
1054 if (i == descr->focus_item)
1056 /* keep the focus rect, to paint the focus item after */
1057 focusRect.left = rect.left;
1058 focusRect.right = rect.right;
1059 focusRect.top = rect.top;
1060 focusRect.bottom = rect.bottom;
1062 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1063 rect.top = rect.bottom;
1065 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1067 if (!IS_OWNERDRAW(descr))
1069 /* Clear the bottom of the column */
1070 if (rect.top < descr->height)
1072 rect.bottom = descr->height;
1073 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1074 &rect, NULL, 0, NULL );
1078 /* Go to the next column */
1079 rect.left += descr->column_width;
1080 rect.right += descr->column_width;
1081 rect.top = 0;
1082 col_pos = descr->page_size - 1;
1084 else
1086 col_pos--;
1087 if (rect.top >= descr->height) break;
1091 /* Paint the focus item now */
1092 if (focusRect.top != focusRect.bottom &&
1093 descr->caret_on && descr->in_focus)
1094 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1096 if (!IS_OWNERDRAW(descr))
1098 /* Clear the remainder of the client area */
1099 if (rect.top < descr->height)
1101 rect.bottom = descr->height;
1102 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1103 &rect, NULL, 0, NULL );
1105 if (rect.right < descr->width)
1107 rect.left = rect.right;
1108 rect.right = descr->width;
1109 rect.top = 0;
1110 rect.bottom = descr->height;
1111 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1112 &rect, NULL, 0, NULL );
1115 if (oldFont) SelectObject( hdc, oldFont );
1116 if (oldBrush) SelectObject( hdc, oldBrush );
1117 return 0;
1121 /***********************************************************************
1122 * LISTBOX_InvalidateItems
1124 * Invalidate all items from a given item. If the specified item is not
1125 * visible, nothing happens.
1127 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1129 RECT rect;
1131 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1133 if (descr->style & LBS_NOREDRAW)
1135 descr->style |= LBS_DISPLAYCHANGED;
1136 return;
1138 rect.bottom = descr->height;
1139 InvalidateRect( descr->self, &rect, TRUE );
1140 if (descr->style & LBS_MULTICOLUMN)
1142 /* Repaint the other columns */
1143 rect.left = rect.right;
1144 rect.right = descr->width;
1145 rect.top = 0;
1146 InvalidateRect( descr->self, &rect, TRUE );
1151 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1153 RECT rect;
1155 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1156 InvalidateRect( descr->self, &rect, TRUE );
1159 /***********************************************************************
1160 * LISTBOX_GetItemHeight
1162 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1164 if (descr->style & LBS_OWNERDRAWVARIABLE)
1166 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1167 return descr->items[index].height;
1169 else return descr->item_height;
1173 /***********************************************************************
1174 * LISTBOX_SetItemHeight
1176 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1178 if (!height) height = 1;
1180 if (descr->style & LBS_OWNERDRAWVARIABLE)
1182 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1183 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1184 descr->items[index].height = height;
1185 LISTBOX_UpdateScroll( descr );
1186 if (repaint)
1187 LISTBOX_InvalidateItems( descr, index );
1189 else if (height != descr->item_height)
1191 TRACE("[%p]: new height = %d\n", descr->self, height );
1192 descr->item_height = height;
1193 LISTBOX_UpdatePage( descr );
1194 LISTBOX_UpdateScroll( descr );
1195 if (repaint)
1196 InvalidateRect( descr->self, 0, TRUE );
1198 return LB_OKAY;
1202 /***********************************************************************
1203 * LISTBOX_SetHorizontalPos
1205 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1207 INT diff;
1209 if (pos > descr->horz_extent - descr->width)
1210 pos = descr->horz_extent - descr->width;
1211 if (pos < 0) pos = 0;
1212 if (!(diff = descr->horz_pos - pos)) return;
1213 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1214 descr->horz_pos = pos;
1215 LISTBOX_UpdateScroll( descr );
1216 if (abs(diff) < descr->width)
1218 RECT rect;
1219 /* Invalidate the focused item so it will be repainted correctly */
1220 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1221 InvalidateRect( descr->self, &rect, TRUE );
1222 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1223 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1225 else
1226 InvalidateRect( descr->self, NULL, TRUE );
1230 /***********************************************************************
1231 * LISTBOX_SetHorizontalExtent
1233 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1235 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1236 return LB_OKAY;
1237 if (extent <= 0) extent = 1;
1238 if (extent == descr->horz_extent) return LB_OKAY;
1239 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1240 descr->horz_extent = extent;
1241 if (descr->horz_pos > extent - descr->width)
1242 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1243 else
1244 LISTBOX_UpdateScroll( descr );
1245 return LB_OKAY;
1249 /***********************************************************************
1250 * LISTBOX_SetColumnWidth
1252 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1254 if (width == descr->column_width) return LB_OKAY;
1255 TRACE("[%p]: new column width = %d\n", descr->self, width );
1256 descr->column_width = width;
1257 LISTBOX_UpdatePage( descr );
1258 return LB_OKAY;
1262 /***********************************************************************
1263 * LISTBOX_SetFont
1265 * Returns the item height.
1267 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1269 HDC hdc;
1270 HFONT oldFont = 0;
1271 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1272 SIZE sz;
1274 descr->font = font;
1276 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1278 ERR("unable to get DC.\n" );
1279 return 16;
1281 if (font) oldFont = SelectObject( hdc, font );
1282 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1283 if (oldFont) SelectObject( hdc, oldFont );
1284 ReleaseDC( descr->self, hdc );
1286 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1287 if (!IS_OWNERDRAW(descr))
1288 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1289 return sz.cy;
1293 /***********************************************************************
1294 * LISTBOX_MakeItemVisible
1296 * Make sure that a given item is partially or fully visible.
1298 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1300 INT top;
1302 if (index <= descr->top_item) top = index;
1303 else if (descr->style & LBS_MULTICOLUMN)
1305 INT cols = descr->width;
1306 if (!fully) cols += descr->column_width - 1;
1307 if (cols >= descr->column_width) cols /= descr->column_width;
1308 else cols = 1;
1309 if (index < descr->top_item + (descr->page_size * cols)) return;
1310 top = index - descr->page_size * (cols - 1);
1312 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1314 INT height = fully ? descr->items[index].height : 1;
1315 for (top = index; top > descr->top_item; top--)
1316 if ((height += descr->items[top-1].height) > descr->height) break;
1318 else
1320 if (index < descr->top_item + descr->page_size) return;
1321 if (!fully && (index == descr->top_item + descr->page_size) &&
1322 (descr->height > (descr->page_size * descr->item_height))) return;
1323 top = index - descr->page_size + 1;
1325 LISTBOX_SetTopItem( descr, top, TRUE );
1328 /***********************************************************************
1329 * LISTBOX_SetCaretIndex
1331 * NOTES
1332 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1335 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1337 INT oldfocus = descr->focus_item;
1339 if (descr->style & LBS_NOSEL) return LB_ERR;
1340 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1341 if (index == oldfocus) return LB_OKAY;
1342 descr->focus_item = index;
1343 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1344 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1346 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1347 if (descr->caret_on && (descr->in_focus))
1348 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1350 return LB_OKAY;
1354 /***********************************************************************
1355 * LISTBOX_SelectItemRange
1357 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1359 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1360 INT last, BOOL on )
1362 INT i;
1364 /* A few sanity checks */
1366 if (descr->style & LBS_NOSEL) return LB_ERR;
1367 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1368 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1369 if (last == -1) last = descr->nb_items - 1;
1370 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1371 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1373 if (on) /* Turn selection on */
1375 for (i = first; i <= last; i++)
1377 if (descr->items[i].selected) continue;
1378 descr->items[i].selected = TRUE;
1379 LISTBOX_InvalidateItemRect(descr, i);
1382 else /* Turn selection off */
1384 for (i = first; i <= last; i++)
1386 if (!descr->items[i].selected) continue;
1387 descr->items[i].selected = FALSE;
1388 LISTBOX_InvalidateItemRect(descr, i);
1391 return LB_OKAY;
1394 /***********************************************************************
1395 * LISTBOX_SetSelection
1397 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1398 BOOL on, BOOL send_notify )
1400 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1402 if (descr->style & LBS_NOSEL)
1404 descr->selected_item = index;
1405 return LB_ERR;
1407 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1408 if (descr->style & LBS_MULTIPLESEL)
1410 if (index == -1) /* Select all items */
1411 return LISTBOX_SelectItemRange( descr, 0, -1, on );
1412 else /* Only one item */
1413 return LISTBOX_SelectItemRange( descr, index, index, on );
1415 else
1417 INT oldsel = descr->selected_item;
1418 if (index == oldsel) return LB_OKAY;
1419 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1420 if (index != -1) descr->items[index].selected = TRUE;
1421 descr->selected_item = index;
1422 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1423 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1424 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1425 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1426 else
1427 if( descr->lphc ) /* set selection change flag for parent combo */
1428 descr->lphc->wState |= CBF_SELCHANGE;
1430 return LB_OKAY;
1434 /***********************************************************************
1435 * LISTBOX_MoveCaret
1437 * Change the caret position and extend the selection to the new caret.
1439 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1441 INT oldfocus = descr->focus_item;
1443 if ((index < 0) || (index >= descr->nb_items))
1444 return;
1446 /* Important, repaint needs to be done in this order if
1447 you want to mimic Windows behavior:
1448 1. Remove the focus and paint the item
1449 2. Remove the selection and paint the item(s)
1450 3. Set the selection and repaint the item(s)
1451 4. Set the focus to 'index' and repaint the item */
1453 /* 1. remove the focus and repaint the item */
1454 descr->focus_item = -1;
1455 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1456 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1458 /* 2. then turn off the previous selection */
1459 /* 3. repaint the new selected item */
1460 if (descr->style & LBS_EXTENDEDSEL)
1462 if (descr->anchor_item != -1)
1464 INT first = min( index, descr->anchor_item );
1465 INT last = max( index, descr->anchor_item );
1466 if (first > 0)
1467 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1468 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1469 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1472 else if (!(descr->style & LBS_MULTIPLESEL))
1474 /* Set selection to new caret item */
1475 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1478 /* 4. repaint the new item with the focus */
1479 descr->focus_item = index;
1480 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1481 if (descr->caret_on && (descr->in_focus))
1482 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1486 /***********************************************************************
1487 * LISTBOX_InsertItem
1489 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1490 LPWSTR str, DWORD data )
1492 LB_ITEMDATA *item;
1493 INT max_items;
1494 INT oldfocus = descr->focus_item;
1496 if (index == -1) index = descr->nb_items;
1497 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1498 if (!descr->items) max_items = 0;
1499 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1500 if (descr->nb_items == max_items)
1502 /* We need to grow the array */
1503 max_items += LB_ARRAY_GRANULARITY;
1504 if (descr->items)
1505 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1506 max_items * sizeof(LB_ITEMDATA) );
1507 else
1508 item = HeapAlloc( GetProcessHeap(), 0,
1509 max_items * sizeof(LB_ITEMDATA) );
1510 if (!item)
1512 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1513 return LB_ERRSPACE;
1515 descr->items = item;
1518 /* Insert the item structure */
1520 item = &descr->items[index];
1521 if (index < descr->nb_items)
1522 RtlMoveMemory( item + 1, item,
1523 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1524 item->str = str;
1525 item->data = data;
1526 item->height = 0;
1527 item->selected = FALSE;
1528 descr->nb_items++;
1530 /* Get item height */
1532 if (descr->style & LBS_OWNERDRAWVARIABLE)
1534 MEASUREITEMSTRUCT mis;
1535 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1537 mis.CtlType = ODT_LISTBOX;
1538 mis.CtlID = id;
1539 mis.itemID = index;
1540 mis.itemData = descr->items[index].data;
1541 mis.itemHeight = descr->item_height;
1542 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1543 item->height = mis.itemHeight ? mis.itemHeight : 1;
1544 TRACE("[%p]: measure item %d (%s) = %d\n",
1545 descr->self, index, str ? debugstr_w(str) : "", item->height );
1548 /* Repaint the items */
1550 LISTBOX_UpdateScroll( descr );
1551 LISTBOX_InvalidateItems( descr, index );
1553 /* Move selection and focused item */
1554 /* If listbox was empty, set focus to the first item */
1555 if (descr->nb_items == 1)
1556 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1557 /* single select don't change selection index in win31 */
1558 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1560 descr->selected_item++;
1561 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1563 else
1565 if (index <= descr->selected_item)
1567 descr->selected_item++;
1568 descr->focus_item = oldfocus; /* focus not changed */
1571 return LB_OKAY;
1575 /***********************************************************************
1576 * LISTBOX_InsertString
1578 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1580 LPWSTR new_str = NULL;
1581 DWORD data = 0;
1582 LRESULT ret;
1584 if (HAS_STRINGS(descr))
1586 static const WCHAR empty_stringW[] = { 0 };
1587 if (!str) str = empty_stringW;
1588 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1590 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1591 return LB_ERRSPACE;
1593 strcpyW(new_str, str);
1595 else data = (DWORD)str;
1597 if (index == -1) index = descr->nb_items;
1598 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1600 HeapFree( GetProcessHeap(), 0, new_str );
1601 return ret;
1604 TRACE("[%p]: added item %d %s\n",
1605 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1606 return index;
1610 /***********************************************************************
1611 * LISTBOX_DeleteItem
1613 * Delete the content of an item. 'index' must be a valid index.
1615 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1617 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1618 * while Win95 sends it for all items with user data.
1619 * It's probably better to send it too often than not
1620 * often enough, so this is what we do here.
1622 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1624 DELETEITEMSTRUCT dis;
1625 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1627 dis.CtlType = ODT_LISTBOX;
1628 dis.CtlID = id;
1629 dis.itemID = index;
1630 dis.hwndItem = descr->self;
1631 dis.itemData = descr->items[index].data;
1632 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1634 if (HAS_STRINGS(descr) && descr->items[index].str)
1635 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1639 /***********************************************************************
1640 * LISTBOX_RemoveItem
1642 * Remove an item from the listbox and delete its content.
1644 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1646 LB_ITEMDATA *item;
1647 INT max_items;
1649 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1650 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1652 /* We need to invalidate the original rect instead of the updated one. */
1653 LISTBOX_InvalidateItems( descr, index );
1655 LISTBOX_DeleteItem( descr, index );
1657 /* Remove the item */
1659 item = &descr->items[index];
1660 if (index < descr->nb_items-1)
1661 RtlMoveMemory( item, item + 1,
1662 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1663 descr->nb_items--;
1664 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1666 /* Shrink the item array if possible */
1668 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1669 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1671 max_items -= LB_ARRAY_GRANULARITY;
1672 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1673 max_items * sizeof(LB_ITEMDATA) );
1674 if (item) descr->items = item;
1676 /* Repaint the items */
1678 LISTBOX_UpdateScroll( descr );
1679 /* if we removed the scrollbar, reset the top of the list
1680 (correct for owner-drawn ???) */
1681 if (descr->nb_items == descr->page_size)
1682 LISTBOX_SetTopItem( descr, 0, TRUE );
1684 /* Move selection and focused item */
1685 if (!IS_MULTISELECT(descr))
1687 if (index == descr->selected_item)
1688 descr->selected_item = -1;
1689 else if (index < descr->selected_item)
1691 descr->selected_item--;
1692 if (ISWIN31) /* win 31 do not change the selected item number */
1693 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1697 if (descr->focus_item >= descr->nb_items)
1699 descr->focus_item = descr->nb_items - 1;
1700 if (descr->focus_item < 0) descr->focus_item = 0;
1702 return LB_OKAY;
1706 /***********************************************************************
1707 * LISTBOX_ResetContent
1709 static void LISTBOX_ResetContent( LB_DESCR *descr )
1711 INT i;
1713 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1714 HeapFree( GetProcessHeap(), 0, descr->items );
1715 descr->nb_items = 0;
1716 descr->top_item = 0;
1717 descr->selected_item = -1;
1718 descr->focus_item = 0;
1719 descr->anchor_item = -1;
1720 descr->items = NULL;
1724 /***********************************************************************
1725 * LISTBOX_SetCount
1727 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1729 LRESULT ret;
1731 if (HAS_STRINGS(descr)) return LB_ERR;
1732 /* FIXME: this is far from optimal... */
1733 if (count > descr->nb_items)
1735 while (count > descr->nb_items)
1736 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1737 return ret;
1739 else if (count < descr->nb_items)
1741 while (count < descr->nb_items)
1742 if ((ret = LISTBOX_RemoveItem( descr, -1 )) < 0)
1743 return ret;
1745 return LB_OKAY;
1749 /***********************************************************************
1750 * LISTBOX_Directory
1752 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1753 LPCWSTR filespec, BOOL long_names )
1755 HANDLE handle;
1756 LRESULT ret = LB_OKAY;
1757 WIN32_FIND_DATAW entry;
1758 int pos;
1760 /* don't scan directory if we just want drives exclusively */
1761 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1762 /* scan directory */
1763 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1765 int le = GetLastError();
1766 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1768 else
1772 WCHAR buffer[270];
1773 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1775 static const WCHAR bracketW[] = { ']',0 };
1776 static const WCHAR dotW[] = { '.',0 };
1777 if (!(attrib & DDL_DIRECTORY) ||
1778 !strcmpW( entry.cFileName, dotW )) continue;
1779 buffer[0] = '[';
1780 if (!long_names && entry.cAlternateFileName[0])
1781 strcpyW( buffer + 1, entry.cAlternateFileName );
1782 else
1783 strcpyW( buffer + 1, entry.cFileName );
1784 strcatW(buffer, bracketW);
1786 else /* not a directory */
1788 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1789 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1791 if ((attrib & DDL_EXCLUSIVE) &&
1792 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1793 continue;
1794 #undef ATTRIBS
1795 if (!long_names && entry.cAlternateFileName[0])
1796 strcpyW( buffer, entry.cAlternateFileName );
1797 else
1798 strcpyW( buffer, entry.cFileName );
1800 if (!long_names) CharLowerW( buffer );
1801 pos = LISTBOX_FindFileStrPos( descr, buffer );
1802 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1803 break;
1804 } while (FindNextFileW( handle, &entry ));
1805 FindClose( handle );
1809 /* scan drives */
1810 if ((ret >= 0) && (attrib & DDL_DRIVES))
1812 WCHAR buffer[] = {'[','-','a','-',']',0};
1813 WCHAR root[] = {'A',':','\\',0};
1814 int drive;
1815 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1817 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1818 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1819 break;
1822 return ret;
1826 /***********************************************************************
1827 * LISTBOX_HandleVScroll
1829 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1831 SCROLLINFO info;
1833 if (descr->style & LBS_MULTICOLUMN) return 0;
1834 switch(scrollReq)
1836 case SB_LINEUP:
1837 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1838 break;
1839 case SB_LINEDOWN:
1840 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1841 break;
1842 case SB_PAGEUP:
1843 LISTBOX_SetTopItem( descr, descr->top_item -
1844 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1845 break;
1846 case SB_PAGEDOWN:
1847 LISTBOX_SetTopItem( descr, descr->top_item +
1848 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1849 break;
1850 case SB_THUMBPOSITION:
1851 LISTBOX_SetTopItem( descr, pos, TRUE );
1852 break;
1853 case SB_THUMBTRACK:
1854 info.cbSize = sizeof(info);
1855 info.fMask = SIF_TRACKPOS;
1856 GetScrollInfo( descr->self, SB_VERT, &info );
1857 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1858 break;
1859 case SB_TOP:
1860 LISTBOX_SetTopItem( descr, 0, TRUE );
1861 break;
1862 case SB_BOTTOM:
1863 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1864 break;
1866 return 0;
1870 /***********************************************************************
1871 * LISTBOX_HandleHScroll
1873 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1875 SCROLLINFO info;
1876 INT page;
1878 if (descr->style & LBS_MULTICOLUMN)
1880 switch(scrollReq)
1882 case SB_LINELEFT:
1883 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1884 TRUE );
1885 break;
1886 case SB_LINERIGHT:
1887 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1888 TRUE );
1889 break;
1890 case SB_PAGELEFT:
1891 page = descr->width / descr->column_width;
1892 if (page < 1) page = 1;
1893 LISTBOX_SetTopItem( descr,
1894 descr->top_item - page * descr->page_size, TRUE );
1895 break;
1896 case SB_PAGERIGHT:
1897 page = descr->width / descr->column_width;
1898 if (page < 1) page = 1;
1899 LISTBOX_SetTopItem( descr,
1900 descr->top_item + page * descr->page_size, TRUE );
1901 break;
1902 case SB_THUMBPOSITION:
1903 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1904 break;
1905 case SB_THUMBTRACK:
1906 info.cbSize = sizeof(info);
1907 info.fMask = SIF_TRACKPOS;
1908 GetScrollInfo( descr->self, SB_VERT, &info );
1909 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1910 TRUE );
1911 break;
1912 case SB_LEFT:
1913 LISTBOX_SetTopItem( descr, 0, TRUE );
1914 break;
1915 case SB_RIGHT:
1916 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1917 break;
1920 else if (descr->horz_extent)
1922 switch(scrollReq)
1924 case SB_LINELEFT:
1925 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1926 break;
1927 case SB_LINERIGHT:
1928 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1929 break;
1930 case SB_PAGELEFT:
1931 LISTBOX_SetHorizontalPos( descr,
1932 descr->horz_pos - descr->width );
1933 break;
1934 case SB_PAGERIGHT:
1935 LISTBOX_SetHorizontalPos( descr,
1936 descr->horz_pos + descr->width );
1937 break;
1938 case SB_THUMBPOSITION:
1939 LISTBOX_SetHorizontalPos( descr, pos );
1940 break;
1941 case SB_THUMBTRACK:
1942 info.cbSize = sizeof(info);
1943 info.fMask = SIF_TRACKPOS;
1944 GetScrollInfo( descr->self, SB_HORZ, &info );
1945 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1946 break;
1947 case SB_LEFT:
1948 LISTBOX_SetHorizontalPos( descr, 0 );
1949 break;
1950 case SB_RIGHT:
1951 LISTBOX_SetHorizontalPos( descr,
1952 descr->horz_extent - descr->width );
1953 break;
1956 return 0;
1959 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1961 short gcWheelDelta = 0;
1962 UINT pulScrollLines = 3;
1964 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1966 gcWheelDelta -= delta;
1968 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1970 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1971 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1972 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
1974 return 0;
1977 /***********************************************************************
1978 * LISTBOX_HandleLButtonDown
1980 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
1982 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1983 TRACE("[%p]: lbuttondown %d,%d item %d\n", descr->self, x, y, index );
1984 if (!descr->caret_on && (descr->in_focus)) return 0;
1986 if (!descr->in_focus)
1988 if( !descr->lphc ) SetFocus( descr->self );
1989 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1992 if (index == -1) return 0;
1994 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
1996 /* we should perhaps make sure that all items are deselected
1997 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1998 if (!(keys & (MK_SHIFT|MK_CONTROL)))
1999 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2002 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2003 if (keys & MK_CONTROL)
2005 LISTBOX_SetCaretIndex( descr, index, FALSE );
2006 LISTBOX_SetSelection( descr, index,
2007 !descr->items[index].selected,
2008 (descr->style & LBS_NOTIFY) != 0);
2010 else
2012 LISTBOX_MoveCaret( descr, index, FALSE );
2014 if (descr->style & LBS_EXTENDEDSEL)
2016 LISTBOX_SetSelection( descr, index,
2017 descr->items[index].selected,
2018 (descr->style & LBS_NOTIFY) != 0 );
2020 else
2022 LISTBOX_SetSelection( descr, index,
2023 !descr->items[index].selected,
2024 (descr->style & LBS_NOTIFY) != 0 );
2028 else
2030 descr->anchor_item = index;
2031 LISTBOX_MoveCaret( descr, index, FALSE );
2032 LISTBOX_SetSelection( descr, index,
2033 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2036 descr->captured = TRUE;
2037 SetCapture( descr->self );
2039 if (!descr->lphc)
2041 if (descr->style & LBS_NOTIFY )
2042 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2043 MAKELPARAM( x, y ) );
2044 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2046 POINT pt;
2048 pt.x = x;
2049 pt.y = y;
2051 if (DragDetect( descr->self, pt ))
2052 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2055 return 0;
2059 /*************************************************************************
2060 * LISTBOX_HandleLButtonDownCombo [Internal]
2062 * Process LButtonDown message for the ComboListBox
2064 * PARAMS
2065 * pWnd [I] The windows internal structure
2066 * pDescr [I] The ListBox internal structure
2067 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2068 * x [I] X Mouse Coordinate
2069 * y [I] Y Mouse Coordinate
2071 * RETURNS
2072 * 0 since we are processing the WM_LBUTTONDOWN Message
2074 * NOTES
2075 * This function is only to be used when a ListBox is a ComboListBox
2078 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2080 RECT clientRect, screenRect;
2081 POINT mousePos;
2083 mousePos.x = x;
2084 mousePos.y = y;
2086 GetClientRect(descr->self, &clientRect);
2088 if(PtInRect(&clientRect, mousePos))
2090 /* MousePos is in client, resume normal processing */
2091 if (msg == WM_LBUTTONDOWN)
2093 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2094 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2096 else if (descr->style & LBS_NOTIFY)
2097 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2099 else
2101 POINT screenMousePos;
2102 HWND hWndOldCapture;
2104 /* Check the Non-Client Area */
2105 screenMousePos = mousePos;
2106 hWndOldCapture = GetCapture();
2107 ReleaseCapture();
2108 GetWindowRect(descr->self, &screenRect);
2109 ClientToScreen(descr->self, &screenMousePos);
2111 if(!PtInRect(&screenRect, screenMousePos))
2113 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2114 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2115 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2117 else
2119 /* Check to see the NC is a scrollbar */
2120 INT nHitTestType=0;
2121 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2122 /* Check Vertical scroll bar */
2123 if (style & WS_VSCROLL)
2125 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2126 if (PtInRect( &clientRect, mousePos ))
2127 nHitTestType = HTVSCROLL;
2129 /* Check horizontal scroll bar */
2130 if (style & WS_HSCROLL)
2132 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2133 if (PtInRect( &clientRect, mousePos ))
2134 nHitTestType = HTHSCROLL;
2136 /* Windows sends this message when a scrollbar is clicked
2139 if(nHitTestType != 0)
2141 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2142 MAKELONG(screenMousePos.x, screenMousePos.y));
2144 /* Resume the Capture after scrolling is complete
2146 if(hWndOldCapture != 0)
2147 SetCapture(hWndOldCapture);
2150 return 0;
2153 /***********************************************************************
2154 * LISTBOX_HandleLButtonUp
2156 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2158 if (LISTBOX_Timer != LB_TIMER_NONE)
2159 KillSystemTimer( descr->self, LB_TIMER_ID );
2160 LISTBOX_Timer = LB_TIMER_NONE;
2161 if (descr->captured)
2163 descr->captured = FALSE;
2164 if (GetCapture() == descr->self) ReleaseCapture();
2165 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2166 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2168 return 0;
2172 /***********************************************************************
2173 * LISTBOX_HandleTimer
2175 * Handle scrolling upon a timer event.
2176 * Return TRUE if scrolling should continue.
2178 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2180 switch(dir)
2182 case LB_TIMER_UP:
2183 if (descr->top_item) index = descr->top_item - 1;
2184 else index = 0;
2185 break;
2186 case LB_TIMER_LEFT:
2187 if (descr->top_item) index -= descr->page_size;
2188 break;
2189 case LB_TIMER_DOWN:
2190 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2191 if (index == descr->focus_item) index++;
2192 if (index >= descr->nb_items) index = descr->nb_items - 1;
2193 break;
2194 case LB_TIMER_RIGHT:
2195 if (index + descr->page_size < descr->nb_items)
2196 index += descr->page_size;
2197 break;
2198 case LB_TIMER_NONE:
2199 break;
2201 if (index == descr->focus_item) return FALSE;
2202 LISTBOX_MoveCaret( descr, index, FALSE );
2203 return TRUE;
2207 /***********************************************************************
2208 * LISTBOX_HandleSystemTimer
2210 * WM_SYSTIMER handler.
2212 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2214 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2216 KillSystemTimer( descr->self, LB_TIMER_ID );
2217 LISTBOX_Timer = LB_TIMER_NONE;
2219 return 0;
2223 /***********************************************************************
2224 * LISTBOX_HandleMouseMove
2226 * WM_MOUSEMOVE handler.
2228 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2229 INT x, INT y )
2231 INT index;
2232 TIMER_DIRECTION dir = LB_TIMER_NONE;
2234 if (!descr->captured) return;
2236 if (descr->style & LBS_MULTICOLUMN)
2238 if (y < 0) y = 0;
2239 else if (y >= descr->item_height * descr->page_size)
2240 y = descr->item_height * descr->page_size - 1;
2242 if (x < 0)
2244 dir = LB_TIMER_LEFT;
2245 x = 0;
2247 else if (x >= descr->width)
2249 dir = LB_TIMER_RIGHT;
2250 x = descr->width - 1;
2253 else
2255 if (y < 0) dir = LB_TIMER_UP; /* above */
2256 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2259 index = LISTBOX_GetItemFromPoint( descr, x, y );
2260 if (index == -1) index = descr->focus_item;
2261 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2263 /* Start/stop the system timer */
2265 if (dir != LB_TIMER_NONE)
2266 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2267 else if (LISTBOX_Timer != LB_TIMER_NONE)
2268 KillSystemTimer( descr->self, LB_TIMER_ID );
2269 LISTBOX_Timer = dir;
2273 /***********************************************************************
2274 * LISTBOX_HandleKeyDown
2276 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2278 INT caret = -1;
2279 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2280 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2281 bForceSelection = FALSE; /* only for single select list */
2283 if (descr->style & LBS_WANTKEYBOARDINPUT)
2285 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2286 MAKEWPARAM(LOWORD(key), descr->focus_item),
2287 (LPARAM)descr->self );
2288 if (caret == -2) return 0;
2290 if (caret == -1) switch(key)
2292 case VK_LEFT:
2293 if (descr->style & LBS_MULTICOLUMN)
2295 bForceSelection = FALSE;
2296 if (descr->focus_item >= descr->page_size)
2297 caret = descr->focus_item - descr->page_size;
2298 break;
2300 /* fall through */
2301 case VK_UP:
2302 caret = descr->focus_item - 1;
2303 if (caret < 0) caret = 0;
2304 break;
2305 case VK_RIGHT:
2306 if (descr->style & LBS_MULTICOLUMN)
2308 bForceSelection = FALSE;
2309 if (descr->focus_item + descr->page_size < descr->nb_items)
2310 caret = descr->focus_item + descr->page_size;
2311 break;
2313 /* fall through */
2314 case VK_DOWN:
2315 caret = descr->focus_item + 1;
2316 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2317 break;
2319 case VK_PRIOR:
2320 if (descr->style & LBS_MULTICOLUMN)
2322 INT page = descr->width / descr->column_width;
2323 if (page < 1) page = 1;
2324 caret = descr->focus_item - (page * descr->page_size) + 1;
2326 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2327 if (caret < 0) caret = 0;
2328 break;
2329 case VK_NEXT:
2330 if (descr->style & LBS_MULTICOLUMN)
2332 INT page = descr->width / descr->column_width;
2333 if (page < 1) page = 1;
2334 caret = descr->focus_item + (page * descr->page_size) - 1;
2336 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2337 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2338 break;
2339 case VK_HOME:
2340 caret = 0;
2341 break;
2342 case VK_END:
2343 caret = descr->nb_items - 1;
2344 break;
2345 case VK_SPACE:
2346 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2347 else if (descr->style & LBS_MULTIPLESEL)
2349 LISTBOX_SetSelection( descr, descr->focus_item,
2350 !descr->items[descr->focus_item].selected,
2351 (descr->style & LBS_NOTIFY) != 0 );
2353 break;
2354 default:
2355 bForceSelection = FALSE;
2357 if (bForceSelection) /* focused item is used instead of key */
2358 caret = descr->focus_item;
2359 if (caret >= 0)
2361 if (((descr->style & LBS_EXTENDEDSEL) &&
2362 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2363 !IS_MULTISELECT(descr))
2364 descr->anchor_item = caret;
2365 LISTBOX_MoveCaret( descr, caret, TRUE );
2367 if (descr->style & LBS_MULTIPLESEL)
2368 descr->selected_item = caret;
2369 else
2370 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2371 if (descr->style & LBS_NOTIFY)
2373 if( descr->lphc )
2375 /* make sure that combo parent doesn't hide us */
2376 descr->lphc->wState |= CBF_NOROLLUP;
2378 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2381 return 0;
2385 /***********************************************************************
2386 * LISTBOX_HandleChar
2388 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2390 INT caret = -1;
2391 WCHAR str[2];
2393 str[0] = charW;
2394 str[1] = '\0';
2396 if (descr->style & LBS_WANTKEYBOARDINPUT)
2398 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2399 MAKEWPARAM(charW, descr->focus_item),
2400 (LPARAM)descr->self );
2401 if (caret == -2) return 0;
2403 if (caret == -1)
2404 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2405 if (caret != -1)
2407 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2408 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2409 LISTBOX_MoveCaret( descr, caret, TRUE );
2410 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2411 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2413 return 0;
2417 /***********************************************************************
2418 * LISTBOX_Create
2420 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2422 LB_DESCR *descr;
2423 MEASUREITEMSTRUCT mis;
2424 RECT rect;
2426 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2427 return FALSE;
2429 GetClientRect( hwnd, &rect );
2430 descr->self = hwnd;
2431 descr->owner = GetParent( descr->self );
2432 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2433 descr->width = rect.right - rect.left;
2434 descr->height = rect.bottom - rect.top;
2435 descr->items = NULL;
2436 descr->nb_items = 0;
2437 descr->top_item = 0;
2438 descr->selected_item = -1;
2439 descr->focus_item = 0;
2440 descr->anchor_item = -1;
2441 descr->item_height = 1;
2442 descr->page_size = 1;
2443 descr->column_width = 150;
2444 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2445 descr->horz_pos = 0;
2446 descr->nb_tabs = 0;
2447 descr->tabs = NULL;
2448 descr->caret_on = lphc ? FALSE : TRUE;
2449 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2450 descr->in_focus = FALSE;
2451 descr->captured = FALSE;
2452 descr->font = 0;
2453 descr->locale = GetUserDefaultLCID();
2454 descr->lphc = lphc;
2456 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2458 /* Win95 document "List Box Differences" from MSDN:
2459 If a list box in a version 3.x application has either the
2460 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2461 horizontal and vertical scroll bars.
2463 descr->style |= WS_VSCROLL | WS_HSCROLL;
2466 if( lphc )
2468 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2469 descr->owner = lphc->self;
2472 SetWindowLongW( descr->self, 0, (LONG)descr );
2474 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2476 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2477 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2478 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2479 descr->item_height = LISTBOX_SetFont( descr, 0 );
2481 if (descr->style & LBS_OWNERDRAWFIXED)
2483 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2485 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2486 descr->item_height = lphc->fixedOwnerDrawHeight;
2488 else
2490 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2491 mis.CtlType = ODT_LISTBOX;
2492 mis.CtlID = id;
2493 mis.itemID = -1;
2494 mis.itemWidth = 0;
2495 mis.itemData = 0;
2496 mis.itemHeight = descr->item_height;
2497 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2498 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2502 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2503 return TRUE;
2507 /***********************************************************************
2508 * LISTBOX_Destroy
2510 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2512 LISTBOX_ResetContent( descr );
2513 SetWindowLongW( descr->self, 0, 0 );
2514 HeapFree( GetProcessHeap(), 0, descr );
2515 return TRUE;
2519 /***********************************************************************
2520 * ListBoxWndProc_common
2522 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2523 WPARAM wParam, LPARAM lParam, BOOL unicode )
2525 LB_DESCR *descr = (LB_DESCR *)GetWindowLongW( hwnd, 0 );
2526 LPHEADCOMBO lphc = 0;
2527 LRESULT ret;
2529 if (!descr)
2531 if (!IsWindow(hwnd)) return 0;
2533 if (msg == WM_CREATE)
2535 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2536 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2537 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2538 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongW( hwnd, 0 ) );
2539 return 0;
2541 /* Ignore all other messages before we get a WM_CREATE */
2542 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2543 DefWindowProcA( hwnd, msg, wParam, lParam );
2545 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2547 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2548 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2550 switch(msg)
2552 case LB_RESETCONTENT16:
2553 case LB_RESETCONTENT:
2554 LISTBOX_ResetContent( descr );
2555 LISTBOX_UpdateScroll( descr );
2556 InvalidateRect( descr->self, NULL, TRUE );
2557 return 0;
2559 case LB_ADDSTRING16:
2560 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2561 /* fall through */
2562 case LB_ADDSTRING:
2564 INT ret;
2565 LPWSTR textW;
2566 if(unicode || !HAS_STRINGS(descr))
2567 textW = (LPWSTR)lParam;
2568 else
2570 LPSTR textA = (LPSTR)lParam;
2571 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2572 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2573 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2575 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2576 ret = LISTBOX_InsertString( descr, wParam, textW );
2577 if (!unicode && HAS_STRINGS(descr))
2578 HeapFree(GetProcessHeap(), 0, textW);
2579 return ret;
2582 case LB_INSERTSTRING16:
2583 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2584 wParam = (INT)(INT16)wParam;
2585 /* fall through */
2586 case LB_INSERTSTRING:
2588 INT ret;
2589 LPWSTR textW;
2590 if(unicode || !HAS_STRINGS(descr))
2591 textW = (LPWSTR)lParam;
2592 else
2594 LPSTR textA = (LPSTR)lParam;
2595 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2596 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2597 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2599 ret = LISTBOX_InsertString( descr, wParam, textW );
2600 if(!unicode && HAS_STRINGS(descr))
2601 HeapFree(GetProcessHeap(), 0, textW);
2602 return ret;
2605 case LB_ADDFILE16:
2606 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2607 /* fall through */
2608 case LB_ADDFILE:
2610 INT ret;
2611 LPWSTR textW;
2612 if(unicode || !HAS_STRINGS(descr))
2613 textW = (LPWSTR)lParam;
2614 else
2616 LPSTR textA = (LPSTR)lParam;
2617 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2618 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2619 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2621 wParam = LISTBOX_FindFileStrPos( descr, textW );
2622 ret = LISTBOX_InsertString( descr, wParam, textW );
2623 if(!unicode && HAS_STRINGS(descr))
2624 HeapFree(GetProcessHeap(), 0, textW);
2625 return ret;
2628 case LB_DELETESTRING16:
2629 case LB_DELETESTRING:
2630 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2631 return descr->nb_items;
2632 else
2633 return LB_ERR;
2635 case LB_GETITEMDATA16:
2636 case LB_GETITEMDATA:
2637 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2638 return LB_ERR;
2639 return descr->items[wParam].data;
2641 case LB_SETITEMDATA16:
2642 case LB_SETITEMDATA:
2643 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2644 return LB_ERR;
2645 descr->items[wParam].data = (DWORD)lParam;
2646 return LB_OKAY;
2648 case LB_GETCOUNT16:
2649 case LB_GETCOUNT:
2650 return descr->nb_items;
2652 case LB_GETTEXT16:
2653 lParam = (LPARAM)MapSL(lParam);
2654 /* fall through */
2655 case LB_GETTEXT:
2656 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2658 case LB_GETTEXTLEN16:
2659 /* fall through */
2660 case LB_GETTEXTLEN:
2661 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2662 return LB_ERR;
2663 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2664 if (unicode) return strlenW( descr->items[wParam].str );
2665 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2666 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2668 case LB_GETCURSEL16:
2669 case LB_GETCURSEL:
2670 if (descr->nb_items == 0)
2671 return LB_ERR;
2672 if (!IS_MULTISELECT(descr))
2673 return descr->selected_item;
2674 if (descr->selected_item != -1)
2675 return descr->selected_item;
2676 return descr->focus_item;
2677 /* otherwise, if the user tries to move the selection with the */
2678 /* arrow keys, we will give the application something to choke on */
2679 case LB_GETTOPINDEX16:
2680 case LB_GETTOPINDEX:
2681 return descr->top_item;
2683 case LB_GETITEMHEIGHT16:
2684 case LB_GETITEMHEIGHT:
2685 return LISTBOX_GetItemHeight( descr, wParam );
2687 case LB_SETITEMHEIGHT16:
2688 lParam = LOWORD(lParam);
2689 /* fall through */
2690 case LB_SETITEMHEIGHT:
2691 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2693 case LB_ITEMFROMPOINT:
2695 POINT pt;
2696 RECT rect;
2698 pt.x = LOWORD(lParam);
2699 pt.y = HIWORD(lParam);
2700 rect.left = 0;
2701 rect.top = 0;
2702 rect.right = descr->width;
2703 rect.bottom = descr->height;
2705 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2706 !PtInRect( &rect, pt ) );
2709 case LB_SETCARETINDEX16:
2710 case LB_SETCARETINDEX:
2711 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2712 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2713 return LB_ERR;
2714 else if (ISWIN31)
2715 return wParam;
2716 else
2717 return LB_OKAY;
2719 case LB_GETCARETINDEX16:
2720 case LB_GETCARETINDEX:
2721 return descr->focus_item;
2723 case LB_SETTOPINDEX16:
2724 case LB_SETTOPINDEX:
2725 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2727 case LB_SETCOLUMNWIDTH16:
2728 case LB_SETCOLUMNWIDTH:
2729 return LISTBOX_SetColumnWidth( descr, wParam );
2731 case LB_GETITEMRECT16:
2733 RECT rect;
2734 RECT16 *r16 = MapSL(lParam);
2735 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2736 r16->left = rect.left;
2737 r16->top = rect.top;
2738 r16->right = rect.right;
2739 r16->bottom = rect.bottom;
2741 return ret;
2743 case LB_GETITEMRECT:
2744 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2746 case LB_FINDSTRING16:
2747 wParam = (INT)(INT16)wParam;
2748 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2749 /* fall through */
2750 case LB_FINDSTRING:
2752 INT ret;
2753 LPWSTR textW;
2754 if(unicode || !HAS_STRINGS(descr))
2755 textW = (LPWSTR)lParam;
2756 else
2758 LPSTR textA = (LPSTR)lParam;
2759 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2760 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2761 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2763 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2764 if(!unicode && HAS_STRINGS(descr))
2765 HeapFree(GetProcessHeap(), 0, textW);
2766 return ret;
2769 case LB_FINDSTRINGEXACT16:
2770 wParam = (INT)(INT16)wParam;
2771 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2772 /* fall through */
2773 case LB_FINDSTRINGEXACT:
2775 INT ret;
2776 LPWSTR textW;
2777 if(unicode || !HAS_STRINGS(descr))
2778 textW = (LPWSTR)lParam;
2779 else
2781 LPSTR textA = (LPSTR)lParam;
2782 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2783 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2784 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2786 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2787 if(!unicode && HAS_STRINGS(descr))
2788 HeapFree(GetProcessHeap(), 0, textW);
2789 return ret;
2792 case LB_SELECTSTRING16:
2793 wParam = (INT)(INT16)wParam;
2794 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2795 /* fall through */
2796 case LB_SELECTSTRING:
2798 INT index;
2799 LPWSTR textW;
2801 if(HAS_STRINGS(descr))
2802 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2803 debugstr_a((LPSTR)lParam));
2804 if(unicode || !HAS_STRINGS(descr))
2805 textW = (LPWSTR)lParam;
2806 else
2808 LPSTR textA = (LPSTR)lParam;
2809 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2810 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2811 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2813 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2814 if(!unicode && HAS_STRINGS(descr))
2815 HeapFree(GetProcessHeap(), 0, textW);
2816 if (index != LB_ERR)
2818 LISTBOX_MoveCaret( descr, index, TRUE );
2819 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2821 return index;
2824 case LB_GETSEL16:
2825 wParam = (INT)(INT16)wParam;
2826 /* fall through */
2827 case LB_GETSEL:
2828 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2829 return LB_ERR;
2830 return descr->items[wParam].selected;
2832 case LB_SETSEL16:
2833 lParam = (INT)(INT16)lParam;
2834 /* fall through */
2835 case LB_SETSEL:
2836 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2838 case LB_SETCURSEL16:
2839 wParam = (INT)(INT16)wParam;
2840 /* fall through */
2841 case LB_SETCURSEL:
2842 if (IS_MULTISELECT(descr)) return LB_ERR;
2843 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2844 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2845 if (lphc && ret != LB_ERR) ret = descr->selected_item;
2846 return ret;
2848 case LB_GETSELCOUNT16:
2849 case LB_GETSELCOUNT:
2850 return LISTBOX_GetSelCount( descr );
2852 case LB_GETSELITEMS16:
2853 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2855 case LB_GETSELITEMS:
2856 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2858 case LB_SELITEMRANGE16:
2859 case LB_SELITEMRANGE:
2860 if (LOWORD(lParam) <= HIWORD(lParam))
2861 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2862 HIWORD(lParam), wParam );
2863 else
2864 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2865 LOWORD(lParam), wParam );
2867 case LB_SELITEMRANGEEX16:
2868 case LB_SELITEMRANGEEX:
2869 if ((INT)lParam >= (INT)wParam)
2870 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2871 else
2872 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2874 case LB_GETHORIZONTALEXTENT16:
2875 case LB_GETHORIZONTALEXTENT:
2876 return descr->horz_extent;
2878 case LB_SETHORIZONTALEXTENT16:
2879 case LB_SETHORIZONTALEXTENT:
2880 return LISTBOX_SetHorizontalExtent( descr, wParam );
2882 case LB_GETANCHORINDEX16:
2883 case LB_GETANCHORINDEX:
2884 return descr->anchor_item;
2886 case LB_SETANCHORINDEX16:
2887 wParam = (INT)(INT16)wParam;
2888 /* fall through */
2889 case LB_SETANCHORINDEX:
2890 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2891 return LB_ERR;
2892 descr->anchor_item = (INT)wParam;
2893 return LB_OKAY;
2895 case LB_DIR16:
2896 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2897 * be set automatically (this is different in Win32) */
2898 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2899 lParam = (LPARAM)MapSL(lParam);
2900 /* fall through */
2901 case LB_DIR:
2903 INT ret;
2904 LPWSTR textW;
2905 if(unicode)
2906 textW = (LPWSTR)lParam;
2907 else
2909 LPSTR textA = (LPSTR)lParam;
2910 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2911 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2912 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2914 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2915 if(!unicode)
2916 HeapFree(GetProcessHeap(), 0, textW);
2917 return ret;
2920 case LB_GETLOCALE:
2921 return descr->locale;
2923 case LB_SETLOCALE:
2925 LCID ret;
2926 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2927 return LB_ERR;
2928 ret = descr->locale;
2929 descr->locale = (LCID)wParam;
2930 return ret;
2933 case LB_INITSTORAGE:
2934 return LISTBOX_InitStorage( descr, wParam );
2936 case LB_SETCOUNT:
2937 return LISTBOX_SetCount( descr, (INT)wParam );
2939 case LB_SETTABSTOPS16:
2940 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2942 case LB_SETTABSTOPS:
2943 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
2945 case LB_CARETON16:
2946 case LB_CARETON:
2947 if (descr->caret_on)
2948 return LB_OKAY;
2949 descr->caret_on = TRUE;
2950 if ((descr->focus_item != -1) && (descr->in_focus))
2951 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2952 return LB_OKAY;
2954 case LB_CARETOFF16:
2955 case LB_CARETOFF:
2956 if (!descr->caret_on)
2957 return LB_OKAY;
2958 descr->caret_on = FALSE;
2959 if ((descr->focus_item != -1) && (descr->in_focus))
2960 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2961 return LB_OKAY;
2963 case WM_DESTROY:
2964 return LISTBOX_Destroy( descr );
2966 case WM_ENABLE:
2967 InvalidateRect( descr->self, NULL, TRUE );
2968 return 0;
2970 case WM_SETREDRAW:
2971 LISTBOX_SetRedraw( descr, wParam != 0 );
2972 return 0;
2974 case WM_GETDLGCODE:
2975 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2977 case WM_PAINT:
2979 PAINTSTRUCT ps;
2980 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2981 ret = LISTBOX_Paint( descr, hdc );
2982 if( !wParam ) EndPaint( descr->self, &ps );
2984 return ret;
2985 case WM_SIZE:
2986 LISTBOX_UpdateSize( descr );
2987 return 0;
2988 case WM_GETFONT:
2989 return (LRESULT)descr->font;
2990 case WM_SETFONT:
2991 LISTBOX_SetFont( descr, (HFONT)wParam );
2992 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2993 return 0;
2994 case WM_SETFOCUS:
2995 descr->in_focus = TRUE;
2996 descr->caret_on = TRUE;
2997 if (descr->focus_item != -1)
2998 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2999 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3000 return 0;
3001 case WM_KILLFOCUS:
3002 descr->in_focus = FALSE;
3003 if ((descr->focus_item != -1) && descr->caret_on)
3004 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3005 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3006 return 0;
3007 case WM_HSCROLL:
3008 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3009 case WM_VSCROLL:
3010 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3011 case WM_MOUSEWHEEL:
3012 if (wParam & (MK_SHIFT | MK_CONTROL))
3013 return DefWindowProcW( descr->self, msg, wParam, lParam );
3014 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3015 case WM_LBUTTONDOWN:
3016 if (lphc)
3017 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3018 (INT16)LOWORD(lParam),
3019 (INT16)HIWORD(lParam) );
3020 return LISTBOX_HandleLButtonDown( descr, wParam,
3021 (INT16)LOWORD(lParam),
3022 (INT16)HIWORD(lParam) );
3023 case WM_LBUTTONDBLCLK:
3024 if (lphc)
3025 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3026 (INT16)LOWORD(lParam),
3027 (INT16)HIWORD(lParam) );
3028 if (descr->style & LBS_NOTIFY)
3029 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3030 return 0;
3031 case WM_MOUSEMOVE:
3032 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3034 BOOL captured = descr->captured;
3035 POINT mousePos;
3036 RECT clientRect;
3038 mousePos.x = (INT16)LOWORD(lParam);
3039 mousePos.y = (INT16)HIWORD(lParam);
3042 * If we are in a dropdown combobox, we simulate that
3043 * the mouse is captured to show the tracking of the item.
3045 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3046 descr->captured = TRUE;
3048 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3050 descr->captured = captured;
3052 else if (GetCapture() == descr->self)
3054 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3055 (INT16)HIWORD(lParam) );
3057 return 0;
3058 case WM_LBUTTONUP:
3059 if (lphc)
3061 POINT mousePos;
3062 RECT clientRect;
3065 * If the mouse button "up" is not in the listbox,
3066 * we make sure there is no selection by re-selecting the
3067 * item that was selected when the listbox was made visible.
3069 mousePos.x = (INT16)LOWORD(lParam);
3070 mousePos.y = (INT16)HIWORD(lParam);
3072 GetClientRect(descr->self, &clientRect);
3075 * When the user clicks outside the combobox and the focus
3076 * is lost, the owning combobox will send a fake buttonup with
3077 * 0xFFFFFFF as the mouse location, we must also revert the
3078 * selection to the original selection.
3080 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3081 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3083 return LISTBOX_HandleLButtonUp( descr );
3084 case WM_KEYDOWN:
3085 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3087 /* for some reason Windows makes it possible to
3088 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3090 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3091 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3092 && (wParam == VK_DOWN || wParam == VK_UP)) )
3094 COMBO_FlipListbox( lphc, FALSE, FALSE );
3095 return 0;
3098 return LISTBOX_HandleKeyDown( descr, wParam );
3099 case WM_CHAR:
3101 WCHAR charW;
3102 if(unicode)
3103 charW = (WCHAR)wParam;
3104 else
3106 CHAR charA = (CHAR)wParam;
3107 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3109 return LISTBOX_HandleChar( descr, charW );
3111 case WM_SYSTIMER:
3112 return LISTBOX_HandleSystemTimer( descr );
3113 case WM_ERASEBKGND:
3114 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3116 RECT rect;
3117 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3118 wParam, (LPARAM)descr->self );
3119 TRACE("hbrush = %p\n", hbrush);
3120 if(!hbrush)
3121 hbrush = GetSysColorBrush(COLOR_WINDOW);
3122 if(hbrush)
3124 GetClientRect(descr->self, &rect);
3125 FillRect((HDC)wParam, &rect, hbrush);
3128 return 1;
3129 case WM_DROPFILES:
3130 if( lphc ) return 0;
3131 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3132 SendMessageA( descr->owner, msg, wParam, lParam );
3134 case WM_NCDESTROY:
3135 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3136 lphc->hWndLBox = 0;
3137 break;
3139 case WM_NCACTIVATE:
3140 if (lphc) return 0;
3141 break;
3143 default:
3144 if ((msg >= WM_USER) && (msg < 0xc000))
3145 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3146 hwnd, msg, wParam, lParam );
3149 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3150 DefWindowProcA( hwnd, msg, wParam, lParam );
3153 /***********************************************************************
3154 * ListBoxWndProcA
3156 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3158 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3161 /***********************************************************************
3162 * ListBoxWndProcW
3164 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3166 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );