msi: Add stub actions for CCPSearch and RMCCPSearch.
[wine/gsoc_dplay.git] / dlls / user / listbox.c
blobe40cc80038f6e0ab0d8fae87666f6df5baed031a
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 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( 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))
436 memset(rect, 0, sizeof(*rect));
437 SetLastError(ERROR_INVALID_INDEX);
438 return LB_ERR;
440 SetRect( rect, 0, 0, descr->width, descr->height );
441 if (descr->style & LBS_MULTICOLUMN)
443 INT col = (index / descr->page_size) -
444 (descr->top_item / descr->page_size);
445 rect->left += col * descr->column_width;
446 rect->right = rect->left + descr->column_width;
447 rect->top += (index % descr->page_size) * descr->item_height;
448 rect->bottom = rect->top + descr->item_height;
450 else if (descr->style & LBS_OWNERDRAWVARIABLE)
452 INT i;
453 rect->right += descr->horz_pos;
454 if ((index >= 0) && (index < descr->nb_items))
456 if (index < descr->top_item)
458 for (i = descr->top_item-1; i >= index; i--)
459 rect->top -= descr->items[i].height;
461 else
463 for (i = descr->top_item; i < index; i++)
464 rect->top += descr->items[i].height;
466 rect->bottom = rect->top + descr->items[index].height;
470 else
472 rect->top += (index - descr->top_item) * descr->item_height;
473 rect->bottom = rect->top + descr->item_height;
474 rect->right += descr->horz_pos;
477 return ((rect->left < descr->width) && (rect->right > 0) &&
478 (rect->top < descr->height) && (rect->bottom > 0));
482 /***********************************************************************
483 * LISTBOX_GetItemFromPoint
485 * Return the item nearest from point (x,y) (in client coordinates).
487 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
489 INT index = descr->top_item;
491 if (!descr->nb_items) return -1; /* No items */
492 if (descr->style & LBS_OWNERDRAWVARIABLE)
494 INT pos = 0;
495 if (y >= 0)
497 while (index < descr->nb_items)
499 if ((pos += descr->items[index].height) > y) break;
500 index++;
503 else
505 while (index > 0)
507 index--;
508 if ((pos -= descr->items[index].height) <= y) break;
512 else if (descr->style & LBS_MULTICOLUMN)
514 if (y >= descr->item_height * descr->page_size) return -1;
515 if (y >= 0) index += y / descr->item_height;
516 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
517 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
519 else
521 index += (y / descr->item_height);
523 if (index < 0) return 0;
524 if (index >= descr->nb_items) return -1;
525 return index;
529 /***********************************************************************
530 * LISTBOX_PaintItem
532 * Paint an item.
534 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
535 INT index, UINT action, BOOL ignoreFocus )
537 LB_ITEMDATA *item = NULL;
538 if (index < descr->nb_items) item = &descr->items[index];
540 if (IS_OWNERDRAW(descr))
542 DRAWITEMSTRUCT dis;
543 RECT r;
544 HRGN hrgn;
546 if (!item)
548 if (action == ODA_FOCUS)
549 DrawFocusRect( hdc, rect );
550 else
551 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
552 return;
555 /* some programs mess with the clipping region when
556 drawing the item, *and* restore the previous region
557 after they are done, so a region has better to exist
558 else everything ends clipped */
559 GetClientRect(descr->self, &r);
560 hrgn = CreateRectRgnIndirect(&r);
561 SelectClipRgn( hdc, hrgn);
562 DeleteObject( hrgn );
564 dis.CtlType = ODT_LISTBOX;
565 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
566 dis.hwndItem = descr->self;
567 dis.itemAction = action;
568 dis.hDC = hdc;
569 dis.itemID = index;
570 dis.itemState = 0;
571 if (item && item->selected) dis.itemState |= ODS_SELECTED;
572 if (!ignoreFocus && (descr->focus_item == index) &&
573 (descr->caret_on) &&
574 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
575 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
576 dis.itemData = item ? item->data : 0;
577 dis.rcItem = *rect;
578 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
579 descr->self, index, item ? debugstr_w(item->str) : "", action,
580 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
581 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
583 else
585 COLORREF oldText = 0, oldBk = 0;
587 if (action == ODA_FOCUS)
589 DrawFocusRect( hdc, rect );
590 return;
592 if (item && item->selected)
594 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
595 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
598 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
599 descr->self, index, item ? debugstr_w(item->str) : "", action,
600 rect->left, rect->top, rect->right, rect->bottom );
601 if (!item)
602 ExtTextOutW( hdc, rect->left + 1, rect->top,
603 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
604 else if (!(descr->style & LBS_USETABSTOPS))
605 ExtTextOutW( hdc, rect->left + 1, rect->top,
606 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
607 strlenW(item->str), NULL );
608 else
610 /* Output empty string to paint background in the full width. */
611 ExtTextOutW( hdc, rect->left + 1, rect->top,
612 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
613 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
614 item->str, strlenW(item->str),
615 descr->nb_tabs, descr->tabs, 0);
617 if (item && item->selected)
619 SetBkColor( hdc, oldBk );
620 SetTextColor( hdc, oldText );
622 if (!ignoreFocus && (descr->focus_item == index) &&
623 (descr->caret_on) &&
624 (descr->in_focus)) DrawFocusRect( hdc, rect );
629 /***********************************************************************
630 * LISTBOX_SetRedraw
632 * Change the redraw flag.
634 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
636 if (on)
638 if (!(descr->style & LBS_NOREDRAW)) return;
639 descr->style &= ~LBS_NOREDRAW;
640 if (descr->style & LBS_DISPLAYCHANGED)
641 { /* page was changed while setredraw false, refresh automatically */
642 InvalidateRect(descr->self, NULL, TRUE);
643 if ((descr->top_item + descr->page_size) > descr->nb_items)
644 { /* reset top of page if less than number of items/page */
645 descr->top_item = descr->nb_items - descr->page_size;
646 if (descr->top_item < 0) descr->top_item = 0;
648 descr->style &= ~LBS_DISPLAYCHANGED;
650 LISTBOX_UpdateScroll( descr );
652 else descr->style |= LBS_NOREDRAW;
656 /***********************************************************************
657 * LISTBOX_RepaintItem
659 * Repaint a single item synchronously.
661 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
663 HDC hdc;
664 RECT rect;
665 HFONT oldFont = 0;
666 HBRUSH hbrush, oldBrush = 0;
668 /* Do not repaint the item if the item is not visible */
669 if (!IsWindowVisible(descr->self)) return;
670 if (descr->style & LBS_NOREDRAW)
672 descr->style |= LBS_DISPLAYCHANGED;
673 return;
675 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
676 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
677 if (descr->font) oldFont = SelectObject( hdc, descr->font );
678 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
679 (WPARAM)hdc, (LPARAM)descr->self );
680 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
681 if (!IsWindowEnabled(descr->self))
682 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
683 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
684 LISTBOX_PaintItem( descr, hdc, &rect, index, action, FALSE );
685 if (oldFont) SelectObject( hdc, oldFont );
686 if (oldBrush) SelectObject( hdc, oldBrush );
687 ReleaseDC( descr->self, hdc );
691 /***********************************************************************
692 * LISTBOX_InitStorage
694 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
696 LB_ITEMDATA *item;
698 nb_items += LB_ARRAY_GRANULARITY - 1;
699 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
700 if (descr->items) {
701 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
702 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
703 nb_items * sizeof(LB_ITEMDATA));
705 else {
706 item = HeapAlloc( GetProcessHeap(), 0,
707 nb_items * sizeof(LB_ITEMDATA));
710 if (!item)
712 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
713 return LB_ERRSPACE;
715 descr->items = item;
716 return LB_OKAY;
720 /***********************************************************************
721 * LISTBOX_SetTabStops
723 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
725 INT i;
727 if (!(descr->style & LBS_USETABSTOPS))
729 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
730 return FALSE;
733 HeapFree( GetProcessHeap(), 0, descr->tabs );
734 if (!(descr->nb_tabs = count))
736 descr->tabs = NULL;
737 return TRUE;
739 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
740 descr->nb_tabs * sizeof(INT) )))
741 return FALSE;
742 if (short_ints)
744 INT i;
745 LPINT16 p = (LPINT16)tabs;
747 TRACE("[%p]: settabstops ", descr->self );
748 for (i = 0; i < descr->nb_tabs; i++) {
749 descr->tabs[i] = *p++<<1; /* FIXME */
750 TRACE("%hd ", descr->tabs[i]);
752 TRACE("\n");
754 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
756 /* convert into "dialog units"*/
757 for (i = 0; i < descr->nb_tabs; i++)
758 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
760 return TRUE;
764 /***********************************************************************
765 * LISTBOX_GetText
767 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
769 if ((index < 0) || (index >= descr->nb_items))
771 SetLastError(ERROR_INVALID_INDEX);
772 return LB_ERR;
774 if (HAS_STRINGS(descr))
776 if (!buffer)
778 DWORD len = strlenW(descr->items[index].str);
779 if( unicode )
780 return len;
781 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
782 NULL, 0, NULL, NULL );
785 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
787 if(unicode)
789 strcpyW( buffer, descr->items[index].str );
790 return strlenW(buffer);
792 else
794 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
796 } else {
797 if (buffer)
798 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
799 return sizeof(DWORD);
803 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
805 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
806 if (ret == CSTR_LESS_THAN)
807 return -1;
808 if (ret == CSTR_EQUAL)
809 return 0;
810 if (ret == CSTR_GREATER_THAN)
811 return 1;
812 return -1;
815 /***********************************************************************
816 * LISTBOX_FindStringPos
818 * Find the nearest string located before a given string in sort order.
819 * If 'exact' is TRUE, return an error if we don't get an exact match.
821 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
823 INT index, min, max, res = -1;
825 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
826 min = 0;
827 max = descr->nb_items;
828 while (min != max)
830 index = (min + max) / 2;
831 if (HAS_STRINGS(descr))
832 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
833 else
835 COMPAREITEMSTRUCT cis;
836 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
838 cis.CtlType = ODT_LISTBOX;
839 cis.CtlID = id;
840 cis.hwndItem = descr->self;
841 /* note that some application (MetaStock) expects the second item
842 * to be in the listbox */
843 cis.itemID1 = -1;
844 cis.itemData1 = (ULONG_PTR)str;
845 cis.itemID2 = index;
846 cis.itemData2 = descr->items[index].data;
847 cis.dwLocaleId = descr->locale;
848 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
850 if (!res) return index;
851 if (res < 0) max = index;
852 else min = index + 1;
854 return exact ? -1 : max;
858 /***********************************************************************
859 * LISTBOX_FindFileStrPos
861 * Find the nearest string located before a given string in directory
862 * sort order (i.e. first files, then directories, then drives).
864 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
866 INT min, max, res = -1;
868 if (!HAS_STRINGS(descr))
869 return LISTBOX_FindStringPos( descr, str, FALSE );
870 min = 0;
871 max = descr->nb_items;
872 while (min != max)
874 INT index = (min + max) / 2;
875 LPCWSTR p = descr->items[index].str;
876 if (*p == '[') /* drive or directory */
878 if (*str != '[') res = -1;
879 else if (p[1] == '-') /* drive */
881 if (str[1] == '-') res = str[2] - p[2];
882 else res = -1;
884 else /* directory */
886 if (str[1] == '-') res = 1;
887 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
890 else /* filename */
892 if (*str == '[') res = 1;
893 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
895 if (!res) return index;
896 if (res < 0) max = index;
897 else min = index + 1;
899 return max;
903 /***********************************************************************
904 * LISTBOX_FindString
906 * Find the item beginning with a given string.
908 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
910 INT i;
911 LB_ITEMDATA *item;
913 if (start >= descr->nb_items) start = -1;
914 item = descr->items + start + 1;
915 if (HAS_STRINGS(descr))
917 if (!str || ! str[0] ) return LB_ERR;
918 if (exact)
920 for (i = start + 1; i < descr->nb_items; i++, item++)
921 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
922 for (i = 0, item = descr->items; i <= start; i++, item++)
923 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
925 else
927 /* Special case for drives and directories: ignore prefix */
928 #define CHECK_DRIVE(item) \
929 if ((item)->str[0] == '[') \
931 if (!strncmpiW( str, (item)->str+1, len )) return i; \
932 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
933 return i; \
936 INT len = strlenW(str);
937 for (i = start + 1; i < descr->nb_items; i++, item++)
939 if (!strncmpiW( str, item->str, len )) return i;
940 CHECK_DRIVE(item);
942 for (i = 0, item = descr->items; i <= start; i++, item++)
944 if (!strncmpiW( str, item->str, len )) return i;
945 CHECK_DRIVE(item);
947 #undef CHECK_DRIVE
950 else
952 if (exact && (descr->style & LBS_SORT))
953 /* If sorted, use a WM_COMPAREITEM binary search */
954 return LISTBOX_FindStringPos( descr, str, TRUE );
956 /* Otherwise use a linear search */
957 for (i = start + 1; i < descr->nb_items; i++, item++)
958 if (item->data == (ULONG_PTR)str) return i;
959 for (i = 0, item = descr->items; i <= start; i++, item++)
960 if (item->data == (ULONG_PTR)str) return i;
962 return LB_ERR;
966 /***********************************************************************
967 * LISTBOX_GetSelCount
969 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
971 INT i, count;
972 LB_ITEMDATA *item = descr->items;
974 if (!(descr->style & LBS_MULTIPLESEL) ||
975 (descr->style & LBS_NOSEL))
976 return LB_ERR;
977 for (i = count = 0; i < descr->nb_items; i++, item++)
978 if (item->selected) count++;
979 return count;
983 /***********************************************************************
984 * LISTBOX_GetSelItems16
986 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
988 INT i, count;
989 LB_ITEMDATA *item = descr->items;
991 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
992 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
993 if (item->selected) array[count++] = (INT16)i;
994 return count;
998 /***********************************************************************
999 * LISTBOX_GetSelItems
1001 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
1003 INT i, count;
1004 LB_ITEMDATA *item = descr->items;
1006 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1007 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1008 if (item->selected) array[count++] = i;
1009 return count;
1013 /***********************************************************************
1014 * LISTBOX_Paint
1016 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1018 INT i, col_pos = descr->page_size - 1;
1019 RECT rect;
1020 RECT focusRect = {-1, -1, -1, -1};
1021 HFONT oldFont = 0;
1022 HBRUSH hbrush, oldBrush = 0;
1024 if (descr->style & LBS_NOREDRAW) return 0;
1026 SetRect( &rect, 0, 0, descr->width, descr->height );
1027 if (descr->style & LBS_MULTICOLUMN)
1028 rect.right = rect.left + descr->column_width;
1029 else if (descr->horz_pos)
1031 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1032 rect.right += descr->horz_pos;
1035 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1036 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1037 (WPARAM)hdc, (LPARAM)descr->self );
1038 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1039 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1041 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1042 (descr->in_focus))
1044 /* Special case for empty listbox: paint focus rect */
1045 rect.bottom = rect.top + descr->item_height;
1046 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1047 &rect, NULL, 0, NULL );
1048 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1049 rect.top = rect.bottom;
1052 /* Paint all the item, regarding the selection
1053 Focus state will be painted after */
1055 for (i = descr->top_item; i < descr->nb_items; i++)
1057 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1058 rect.bottom = rect.top + descr->item_height;
1059 else
1060 rect.bottom = rect.top + descr->items[i].height;
1062 if (i == descr->focus_item)
1064 /* keep the focus rect, to paint the focus item after */
1065 focusRect.left = rect.left;
1066 focusRect.right = rect.right;
1067 focusRect.top = rect.top;
1068 focusRect.bottom = rect.bottom;
1070 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1071 rect.top = rect.bottom;
1073 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1075 if (!IS_OWNERDRAW(descr))
1077 /* Clear the bottom of the column */
1078 if (rect.top < descr->height)
1080 rect.bottom = descr->height;
1081 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1082 &rect, NULL, 0, NULL );
1086 /* Go to the next column */
1087 rect.left += descr->column_width;
1088 rect.right += descr->column_width;
1089 rect.top = 0;
1090 col_pos = descr->page_size - 1;
1092 else
1094 col_pos--;
1095 if (rect.top >= descr->height) break;
1099 /* Paint the focus item now */
1100 if (focusRect.top != focusRect.bottom &&
1101 descr->caret_on && descr->in_focus)
1102 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1104 if (!IS_OWNERDRAW(descr))
1106 /* Clear the remainder of the client area */
1107 if (rect.top < descr->height)
1109 rect.bottom = descr->height;
1110 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1111 &rect, NULL, 0, NULL );
1113 if (rect.right < descr->width)
1115 rect.left = rect.right;
1116 rect.right = descr->width;
1117 rect.top = 0;
1118 rect.bottom = descr->height;
1119 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1120 &rect, NULL, 0, NULL );
1123 if (oldFont) SelectObject( hdc, oldFont );
1124 if (oldBrush) SelectObject( hdc, oldBrush );
1125 return 0;
1129 /***********************************************************************
1130 * LISTBOX_InvalidateItems
1132 * Invalidate all items from a given item. If the specified item is not
1133 * visible, nothing happens.
1135 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1137 RECT rect;
1139 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1141 if (descr->style & LBS_NOREDRAW)
1143 descr->style |= LBS_DISPLAYCHANGED;
1144 return;
1146 rect.bottom = descr->height;
1147 InvalidateRect( descr->self, &rect, TRUE );
1148 if (descr->style & LBS_MULTICOLUMN)
1150 /* Repaint the other columns */
1151 rect.left = rect.right;
1152 rect.right = descr->width;
1153 rect.top = 0;
1154 InvalidateRect( descr->self, &rect, TRUE );
1159 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1161 RECT rect;
1163 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1164 InvalidateRect( descr->self, &rect, TRUE );
1167 /***********************************************************************
1168 * LISTBOX_GetItemHeight
1170 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1172 if (descr->style & LBS_OWNERDRAWVARIABLE)
1174 if ((index < 0) || (index >= descr->nb_items))
1176 SetLastError(ERROR_INVALID_INDEX);
1177 return LB_ERR;
1179 return descr->items[index].height;
1181 else return descr->item_height;
1185 /***********************************************************************
1186 * LISTBOX_SetItemHeight
1188 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1190 if (!height) height = 1;
1192 if (descr->style & LBS_OWNERDRAWVARIABLE)
1194 if ((index < 0) || (index >= descr->nb_items))
1196 SetLastError(ERROR_INVALID_INDEX);
1197 return LB_ERR;
1199 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1200 descr->items[index].height = height;
1201 LISTBOX_UpdateScroll( descr );
1202 if (repaint)
1203 LISTBOX_InvalidateItems( descr, index );
1205 else if (height != descr->item_height)
1207 TRACE("[%p]: new height = %d\n", descr->self, height );
1208 descr->item_height = height;
1209 LISTBOX_UpdatePage( descr );
1210 LISTBOX_UpdateScroll( descr );
1211 if (repaint)
1212 InvalidateRect( descr->self, 0, TRUE );
1214 return LB_OKAY;
1218 /***********************************************************************
1219 * LISTBOX_SetHorizontalPos
1221 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1223 INT diff;
1225 if (pos > descr->horz_extent - descr->width)
1226 pos = descr->horz_extent - descr->width;
1227 if (pos < 0) pos = 0;
1228 if (!(diff = descr->horz_pos - pos)) return;
1229 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1230 descr->horz_pos = pos;
1231 LISTBOX_UpdateScroll( descr );
1232 if (abs(diff) < descr->width)
1234 RECT rect;
1235 /* Invalidate the focused item so it will be repainted correctly */
1236 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1237 InvalidateRect( descr->self, &rect, TRUE );
1238 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1239 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1241 else
1242 InvalidateRect( descr->self, NULL, TRUE );
1246 /***********************************************************************
1247 * LISTBOX_SetHorizontalExtent
1249 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1251 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1252 return LB_OKAY;
1253 if (extent <= 0) extent = 1;
1254 if (extent == descr->horz_extent) return LB_OKAY;
1255 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1256 descr->horz_extent = extent;
1257 if (descr->horz_pos > extent - descr->width)
1258 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1259 else
1260 LISTBOX_UpdateScroll( descr );
1261 return LB_OKAY;
1265 /***********************************************************************
1266 * LISTBOX_SetColumnWidth
1268 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1270 if (width == descr->column_width) return LB_OKAY;
1271 TRACE("[%p]: new column width = %d\n", descr->self, width );
1272 descr->column_width = width;
1273 LISTBOX_UpdatePage( descr );
1274 return LB_OKAY;
1278 /***********************************************************************
1279 * LISTBOX_SetFont
1281 * Returns the item height.
1283 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1285 HDC hdc;
1286 HFONT oldFont = 0;
1287 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1288 SIZE sz;
1290 descr->font = font;
1292 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1294 ERR("unable to get DC.\n" );
1295 return 16;
1297 if (font) oldFont = SelectObject( hdc, font );
1298 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1299 if (oldFont) SelectObject( hdc, oldFont );
1300 ReleaseDC( descr->self, hdc );
1302 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1303 if (!IS_OWNERDRAW(descr))
1304 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1305 return sz.cy;
1309 /***********************************************************************
1310 * LISTBOX_MakeItemVisible
1312 * Make sure that a given item is partially or fully visible.
1314 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1316 INT top;
1318 if (index <= descr->top_item) top = index;
1319 else if (descr->style & LBS_MULTICOLUMN)
1321 INT cols = descr->width;
1322 if (!fully) cols += descr->column_width - 1;
1323 if (cols >= descr->column_width) cols /= descr->column_width;
1324 else cols = 1;
1325 if (index < descr->top_item + (descr->page_size * cols)) return;
1326 top = index - descr->page_size * (cols - 1);
1328 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1330 INT height = fully ? descr->items[index].height : 1;
1331 for (top = index; top > descr->top_item; top--)
1332 if ((height += descr->items[top-1].height) > descr->height) break;
1334 else
1336 if (index < descr->top_item + descr->page_size) return;
1337 if (!fully && (index == descr->top_item + descr->page_size) &&
1338 (descr->height > (descr->page_size * descr->item_height))) return;
1339 top = index - descr->page_size + 1;
1341 LISTBOX_SetTopItem( descr, top, TRUE );
1344 /***********************************************************************
1345 * LISTBOX_SetCaretIndex
1347 * NOTES
1348 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1351 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1353 INT oldfocus = descr->focus_item;
1355 if (descr->style & LBS_NOSEL) return LB_ERR;
1356 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1357 if (index == oldfocus) return LB_OKAY;
1358 descr->focus_item = index;
1359 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1360 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1362 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1363 if (descr->caret_on && (descr->in_focus))
1364 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1366 return LB_OKAY;
1370 /***********************************************************************
1371 * LISTBOX_SelectItemRange
1373 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1375 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1376 INT last, BOOL on )
1378 INT i;
1380 /* A few sanity checks */
1382 if (descr->style & LBS_NOSEL) return LB_ERR;
1383 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1385 if (!descr->nb_items) return LB_OKAY;
1387 if (last >= descr->nb_items) last = descr->nb_items - 1;
1388 if (first < 0) first = 0;
1389 if (last < first) return LB_OKAY;
1391 if (on) /* Turn selection on */
1393 for (i = first; i <= last; i++)
1395 if (descr->items[i].selected) continue;
1396 descr->items[i].selected = TRUE;
1397 LISTBOX_InvalidateItemRect(descr, i);
1400 else /* Turn selection off */
1402 for (i = first; i <= last; i++)
1404 if (!descr->items[i].selected) continue;
1405 descr->items[i].selected = FALSE;
1406 LISTBOX_InvalidateItemRect(descr, i);
1409 return LB_OKAY;
1412 /***********************************************************************
1413 * LISTBOX_SetSelection
1415 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1416 BOOL on, BOOL send_notify )
1418 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1420 if (descr->style & LBS_NOSEL)
1422 descr->selected_item = index;
1423 return LB_ERR;
1425 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1426 if (descr->style & LBS_MULTIPLESEL)
1428 if (index == -1) /* Select all items */
1429 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1430 else /* Only one item */
1431 return LISTBOX_SelectItemRange( descr, index, index, on );
1433 else
1435 INT oldsel = descr->selected_item;
1436 if (index == oldsel) return LB_OKAY;
1437 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1438 if (index != -1) descr->items[index].selected = TRUE;
1439 descr->selected_item = index;
1440 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1441 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1442 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1443 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1444 else
1445 if( descr->lphc ) /* set selection change flag for parent combo */
1446 descr->lphc->wState |= CBF_SELCHANGE;
1448 return LB_OKAY;
1452 /***********************************************************************
1453 * LISTBOX_MoveCaret
1455 * Change the caret position and extend the selection to the new caret.
1457 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1459 INT oldfocus = descr->focus_item;
1461 if ((index < 0) || (index >= descr->nb_items))
1462 return;
1464 /* Important, repaint needs to be done in this order if
1465 you want to mimic Windows behavior:
1466 1. Remove the focus and paint the item
1467 2. Remove the selection and paint the item(s)
1468 3. Set the selection and repaint the item(s)
1469 4. Set the focus to 'index' and repaint the item */
1471 /* 1. remove the focus and repaint the item */
1472 descr->focus_item = -1;
1473 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1474 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1476 /* 2. then turn off the previous selection */
1477 /* 3. repaint the new selected item */
1478 if (descr->style & LBS_EXTENDEDSEL)
1480 if (descr->anchor_item != -1)
1482 INT first = min( index, descr->anchor_item );
1483 INT last = max( index, descr->anchor_item );
1484 if (first > 0)
1485 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1486 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1487 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1490 else if (!(descr->style & LBS_MULTIPLESEL))
1492 /* Set selection to new caret item */
1493 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1496 /* 4. repaint the new item with the focus */
1497 descr->focus_item = index;
1498 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1499 if (descr->caret_on && (descr->in_focus))
1500 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1504 /***********************************************************************
1505 * LISTBOX_InsertItem
1507 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1508 LPWSTR str, ULONG_PTR data )
1510 LB_ITEMDATA *item;
1511 INT max_items;
1512 INT oldfocus = descr->focus_item;
1514 if (index == -1) index = descr->nb_items;
1515 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1516 if (!descr->items) max_items = 0;
1517 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1518 if (descr->nb_items == max_items)
1520 /* We need to grow the array */
1521 max_items += LB_ARRAY_GRANULARITY;
1522 if (descr->items)
1523 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1524 max_items * sizeof(LB_ITEMDATA) );
1525 else
1526 item = HeapAlloc( GetProcessHeap(), 0,
1527 max_items * sizeof(LB_ITEMDATA) );
1528 if (!item)
1530 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1531 return LB_ERRSPACE;
1533 descr->items = item;
1536 /* Insert the item structure */
1538 item = &descr->items[index];
1539 if (index < descr->nb_items)
1540 RtlMoveMemory( item + 1, item,
1541 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1542 item->str = str;
1543 item->data = data;
1544 item->height = 0;
1545 item->selected = FALSE;
1546 descr->nb_items++;
1548 /* Get item height */
1550 if (descr->style & LBS_OWNERDRAWVARIABLE)
1552 MEASUREITEMSTRUCT mis;
1553 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1555 mis.CtlType = ODT_LISTBOX;
1556 mis.CtlID = id;
1557 mis.itemID = index;
1558 mis.itemData = descr->items[index].data;
1559 mis.itemHeight = descr->item_height;
1560 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1561 item->height = mis.itemHeight ? mis.itemHeight : 1;
1562 TRACE("[%p]: measure item %d (%s) = %d\n",
1563 descr->self, index, str ? debugstr_w(str) : "", item->height );
1566 /* Repaint the items */
1568 LISTBOX_UpdateScroll( descr );
1569 LISTBOX_InvalidateItems( descr, index );
1571 /* Move selection and focused item */
1572 /* If listbox was empty, set focus to the first item */
1573 if (descr->nb_items == 1)
1574 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1575 /* single select don't change selection index in win31 */
1576 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1578 descr->selected_item++;
1579 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1581 else
1583 if (index <= descr->selected_item)
1585 descr->selected_item++;
1586 descr->focus_item = oldfocus; /* focus not changed */
1589 return LB_OKAY;
1593 /***********************************************************************
1594 * LISTBOX_InsertString
1596 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1598 LPWSTR new_str = NULL;
1599 ULONG_PTR data = 0;
1600 LRESULT ret;
1602 if (HAS_STRINGS(descr))
1604 static const WCHAR empty_stringW[] = { 0 };
1605 if (!str) str = empty_stringW;
1606 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1608 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1609 return LB_ERRSPACE;
1611 strcpyW(new_str, str);
1613 else data = (ULONG_PTR)str;
1615 if (index == -1) index = descr->nb_items;
1616 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1618 HeapFree( GetProcessHeap(), 0, new_str );
1619 return ret;
1622 TRACE("[%p]: added item %d %s\n",
1623 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1624 return index;
1628 /***********************************************************************
1629 * LISTBOX_DeleteItem
1631 * Delete the content of an item. 'index' must be a valid index.
1633 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1635 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1636 * while Win95 sends it for all items with user data.
1637 * It's probably better to send it too often than not
1638 * often enough, so this is what we do here.
1640 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1642 DELETEITEMSTRUCT dis;
1643 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1645 dis.CtlType = ODT_LISTBOX;
1646 dis.CtlID = id;
1647 dis.itemID = index;
1648 dis.hwndItem = descr->self;
1649 dis.itemData = descr->items[index].data;
1650 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1652 if (HAS_STRINGS(descr) && descr->items[index].str)
1653 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1657 /***********************************************************************
1658 * LISTBOX_RemoveItem
1660 * Remove an item from the listbox and delete its content.
1662 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1664 LB_ITEMDATA *item;
1665 INT max_items;
1667 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1669 /* We need to invalidate the original rect instead of the updated one. */
1670 LISTBOX_InvalidateItems( descr, index );
1672 LISTBOX_DeleteItem( descr, index );
1674 /* Remove the item */
1676 item = &descr->items[index];
1677 if (index < descr->nb_items-1)
1678 RtlMoveMemory( item, item + 1,
1679 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1680 descr->nb_items--;
1681 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1683 /* Shrink the item array if possible */
1685 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1686 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1688 max_items -= LB_ARRAY_GRANULARITY;
1689 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1690 max_items * sizeof(LB_ITEMDATA) );
1691 if (item) descr->items = item;
1693 /* Repaint the items */
1695 LISTBOX_UpdateScroll( descr );
1696 /* if we removed the scrollbar, reset the top of the list
1697 (correct for owner-drawn ???) */
1698 if (descr->nb_items == descr->page_size)
1699 LISTBOX_SetTopItem( descr, 0, TRUE );
1701 /* Move selection and focused item */
1702 if (!IS_MULTISELECT(descr))
1704 if (index == descr->selected_item)
1705 descr->selected_item = -1;
1706 else if (index < descr->selected_item)
1708 descr->selected_item--;
1709 if (ISWIN31) /* win 31 do not change the selected item number */
1710 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1714 if (descr->focus_item >= descr->nb_items)
1716 descr->focus_item = descr->nb_items - 1;
1717 if (descr->focus_item < 0) descr->focus_item = 0;
1719 return LB_OKAY;
1723 /***********************************************************************
1724 * LISTBOX_ResetContent
1726 static void LISTBOX_ResetContent( LB_DESCR *descr )
1728 INT i;
1730 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1731 HeapFree( GetProcessHeap(), 0, descr->items );
1732 descr->nb_items = 0;
1733 descr->top_item = 0;
1734 descr->selected_item = -1;
1735 descr->focus_item = 0;
1736 descr->anchor_item = -1;
1737 descr->items = NULL;
1741 /***********************************************************************
1742 * LISTBOX_SetCount
1744 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1746 LRESULT ret;
1748 if (HAS_STRINGS(descr))
1750 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1751 return LB_ERR;
1754 /* FIXME: this is far from optimal... */
1755 if (count > descr->nb_items)
1757 while (count > descr->nb_items)
1758 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1759 return ret;
1761 else if (count < descr->nb_items)
1763 while (count < descr->nb_items)
1764 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1765 return ret;
1767 return LB_OKAY;
1771 /***********************************************************************
1772 * LISTBOX_Directory
1774 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1775 LPCWSTR filespec, BOOL long_names )
1777 HANDLE handle;
1778 LRESULT ret = LB_OKAY;
1779 WIN32_FIND_DATAW entry;
1780 int pos;
1782 /* don't scan directory if we just want drives exclusively */
1783 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1784 /* scan directory */
1785 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1787 int le = GetLastError();
1788 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1790 else
1794 WCHAR buffer[270];
1795 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1797 static const WCHAR bracketW[] = { ']',0 };
1798 static const WCHAR dotW[] = { '.',0 };
1799 if (!(attrib & DDL_DIRECTORY) ||
1800 !strcmpW( entry.cFileName, dotW )) continue;
1801 buffer[0] = '[';
1802 if (!long_names && entry.cAlternateFileName[0])
1803 strcpyW( buffer + 1, entry.cAlternateFileName );
1804 else
1805 strcpyW( buffer + 1, entry.cFileName );
1806 strcatW(buffer, bracketW);
1808 else /* not a directory */
1810 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1811 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1813 if ((attrib & DDL_EXCLUSIVE) &&
1814 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1815 continue;
1816 #undef ATTRIBS
1817 if (!long_names && entry.cAlternateFileName[0])
1818 strcpyW( buffer, entry.cAlternateFileName );
1819 else
1820 strcpyW( buffer, entry.cFileName );
1822 if (!long_names) CharLowerW( buffer );
1823 pos = LISTBOX_FindFileStrPos( descr, buffer );
1824 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1825 break;
1826 } while (FindNextFileW( handle, &entry ));
1827 FindClose( handle );
1831 /* scan drives */
1832 if ((ret >= 0) && (attrib & DDL_DRIVES))
1834 WCHAR buffer[] = {'[','-','a','-',']',0};
1835 WCHAR root[] = {'A',':','\\',0};
1836 int drive;
1837 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1839 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1840 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1841 break;
1844 return ret;
1848 /***********************************************************************
1849 * LISTBOX_HandleVScroll
1851 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1853 SCROLLINFO info;
1855 if (descr->style & LBS_MULTICOLUMN) return 0;
1856 switch(scrollReq)
1858 case SB_LINEUP:
1859 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1860 break;
1861 case SB_LINEDOWN:
1862 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1863 break;
1864 case SB_PAGEUP:
1865 LISTBOX_SetTopItem( descr, descr->top_item -
1866 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1867 break;
1868 case SB_PAGEDOWN:
1869 LISTBOX_SetTopItem( descr, descr->top_item +
1870 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1871 break;
1872 case SB_THUMBPOSITION:
1873 LISTBOX_SetTopItem( descr, pos, TRUE );
1874 break;
1875 case SB_THUMBTRACK:
1876 info.cbSize = sizeof(info);
1877 info.fMask = SIF_TRACKPOS;
1878 GetScrollInfo( descr->self, SB_VERT, &info );
1879 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1880 break;
1881 case SB_TOP:
1882 LISTBOX_SetTopItem( descr, 0, TRUE );
1883 break;
1884 case SB_BOTTOM:
1885 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1886 break;
1888 return 0;
1892 /***********************************************************************
1893 * LISTBOX_HandleHScroll
1895 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1897 SCROLLINFO info;
1898 INT page;
1900 if (descr->style & LBS_MULTICOLUMN)
1902 switch(scrollReq)
1904 case SB_LINELEFT:
1905 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1906 TRUE );
1907 break;
1908 case SB_LINERIGHT:
1909 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1910 TRUE );
1911 break;
1912 case SB_PAGELEFT:
1913 page = descr->width / descr->column_width;
1914 if (page < 1) page = 1;
1915 LISTBOX_SetTopItem( descr,
1916 descr->top_item - page * descr->page_size, TRUE );
1917 break;
1918 case SB_PAGERIGHT:
1919 page = descr->width / descr->column_width;
1920 if (page < 1) page = 1;
1921 LISTBOX_SetTopItem( descr,
1922 descr->top_item + page * descr->page_size, TRUE );
1923 break;
1924 case SB_THUMBPOSITION:
1925 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1926 break;
1927 case SB_THUMBTRACK:
1928 info.cbSize = sizeof(info);
1929 info.fMask = SIF_TRACKPOS;
1930 GetScrollInfo( descr->self, SB_VERT, &info );
1931 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1932 TRUE );
1933 break;
1934 case SB_LEFT:
1935 LISTBOX_SetTopItem( descr, 0, TRUE );
1936 break;
1937 case SB_RIGHT:
1938 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1939 break;
1942 else if (descr->horz_extent)
1944 switch(scrollReq)
1946 case SB_LINELEFT:
1947 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1948 break;
1949 case SB_LINERIGHT:
1950 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1951 break;
1952 case SB_PAGELEFT:
1953 LISTBOX_SetHorizontalPos( descr,
1954 descr->horz_pos - descr->width );
1955 break;
1956 case SB_PAGERIGHT:
1957 LISTBOX_SetHorizontalPos( descr,
1958 descr->horz_pos + descr->width );
1959 break;
1960 case SB_THUMBPOSITION:
1961 LISTBOX_SetHorizontalPos( descr, pos );
1962 break;
1963 case SB_THUMBTRACK:
1964 info.cbSize = sizeof(info);
1965 info.fMask = SIF_TRACKPOS;
1966 GetScrollInfo( descr->self, SB_HORZ, &info );
1967 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1968 break;
1969 case SB_LEFT:
1970 LISTBOX_SetHorizontalPos( descr, 0 );
1971 break;
1972 case SB_RIGHT:
1973 LISTBOX_SetHorizontalPos( descr,
1974 descr->horz_extent - descr->width );
1975 break;
1978 return 0;
1981 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1983 short gcWheelDelta = 0;
1984 UINT pulScrollLines = 3;
1986 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1988 gcWheelDelta -= delta;
1990 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1992 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1993 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1994 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
1996 return 0;
1999 /***********************************************************************
2000 * LISTBOX_HandleLButtonDown
2002 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2004 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2005 TRACE("[%p]: lbuttondown %d,%d item %d\n", descr->self, x, y, index );
2006 if (!descr->caret_on && (descr->in_focus)) return 0;
2008 if (!descr->in_focus)
2010 if( !descr->lphc ) SetFocus( descr->self );
2011 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2014 if (index == -1) return 0;
2016 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2018 /* we should perhaps make sure that all items are deselected
2019 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2020 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2021 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2024 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2025 if (keys & MK_CONTROL)
2027 LISTBOX_SetCaretIndex( descr, index, FALSE );
2028 LISTBOX_SetSelection( descr, index,
2029 !descr->items[index].selected,
2030 (descr->style & LBS_NOTIFY) != 0);
2032 else
2034 LISTBOX_MoveCaret( descr, index, FALSE );
2036 if (descr->style & LBS_EXTENDEDSEL)
2038 LISTBOX_SetSelection( descr, index,
2039 descr->items[index].selected,
2040 (descr->style & LBS_NOTIFY) != 0 );
2042 else
2044 LISTBOX_SetSelection( descr, index,
2045 !descr->items[index].selected,
2046 (descr->style & LBS_NOTIFY) != 0 );
2050 else
2052 descr->anchor_item = index;
2053 LISTBOX_MoveCaret( descr, index, FALSE );
2054 LISTBOX_SetSelection( descr, index,
2055 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2058 descr->captured = TRUE;
2059 SetCapture( descr->self );
2061 if (!descr->lphc)
2063 if (descr->style & LBS_NOTIFY )
2064 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2065 MAKELPARAM( x, y ) );
2066 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2068 POINT pt;
2070 pt.x = x;
2071 pt.y = y;
2073 if (DragDetect( descr->self, pt ))
2074 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2077 return 0;
2081 /*************************************************************************
2082 * LISTBOX_HandleLButtonDownCombo [Internal]
2084 * Process LButtonDown message for the ComboListBox
2086 * PARAMS
2087 * pWnd [I] The windows internal structure
2088 * pDescr [I] The ListBox internal structure
2089 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2090 * x [I] X Mouse Coordinate
2091 * y [I] Y Mouse Coordinate
2093 * RETURNS
2094 * 0 since we are processing the WM_LBUTTONDOWN Message
2096 * NOTES
2097 * This function is only to be used when a ListBox is a ComboListBox
2100 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2102 RECT clientRect, screenRect;
2103 POINT mousePos;
2105 mousePos.x = x;
2106 mousePos.y = y;
2108 GetClientRect(descr->self, &clientRect);
2110 if(PtInRect(&clientRect, mousePos))
2112 /* MousePos is in client, resume normal processing */
2113 if (msg == WM_LBUTTONDOWN)
2115 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2116 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2118 else if (descr->style & LBS_NOTIFY)
2119 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2121 else
2123 POINT screenMousePos;
2124 HWND hWndOldCapture;
2126 /* Check the Non-Client Area */
2127 screenMousePos = mousePos;
2128 hWndOldCapture = GetCapture();
2129 ReleaseCapture();
2130 GetWindowRect(descr->self, &screenRect);
2131 ClientToScreen(descr->self, &screenMousePos);
2133 if(!PtInRect(&screenRect, screenMousePos))
2135 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2136 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2137 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2139 else
2141 /* Check to see the NC is a scrollbar */
2142 INT nHitTestType=0;
2143 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2144 /* Check Vertical scroll bar */
2145 if (style & WS_VSCROLL)
2147 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2148 if (PtInRect( &clientRect, mousePos ))
2149 nHitTestType = HTVSCROLL;
2151 /* Check horizontal scroll bar */
2152 if (style & WS_HSCROLL)
2154 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2155 if (PtInRect( &clientRect, mousePos ))
2156 nHitTestType = HTHSCROLL;
2158 /* Windows sends this message when a scrollbar is clicked
2161 if(nHitTestType != 0)
2163 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2164 MAKELONG(screenMousePos.x, screenMousePos.y));
2166 /* Resume the Capture after scrolling is complete
2168 if(hWndOldCapture != 0)
2169 SetCapture(hWndOldCapture);
2172 return 0;
2175 /***********************************************************************
2176 * LISTBOX_HandleLButtonUp
2178 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2180 if (LISTBOX_Timer != LB_TIMER_NONE)
2181 KillSystemTimer( descr->self, LB_TIMER_ID );
2182 LISTBOX_Timer = LB_TIMER_NONE;
2183 if (descr->captured)
2185 descr->captured = FALSE;
2186 if (GetCapture() == descr->self) ReleaseCapture();
2187 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2188 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2190 return 0;
2194 /***********************************************************************
2195 * LISTBOX_HandleTimer
2197 * Handle scrolling upon a timer event.
2198 * Return TRUE if scrolling should continue.
2200 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2202 switch(dir)
2204 case LB_TIMER_UP:
2205 if (descr->top_item) index = descr->top_item - 1;
2206 else index = 0;
2207 break;
2208 case LB_TIMER_LEFT:
2209 if (descr->top_item) index -= descr->page_size;
2210 break;
2211 case LB_TIMER_DOWN:
2212 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2213 if (index == descr->focus_item) index++;
2214 if (index >= descr->nb_items) index = descr->nb_items - 1;
2215 break;
2216 case LB_TIMER_RIGHT:
2217 if (index + descr->page_size < descr->nb_items)
2218 index += descr->page_size;
2219 break;
2220 case LB_TIMER_NONE:
2221 break;
2223 if (index == descr->focus_item) return FALSE;
2224 LISTBOX_MoveCaret( descr, index, FALSE );
2225 return TRUE;
2229 /***********************************************************************
2230 * LISTBOX_HandleSystemTimer
2232 * WM_SYSTIMER handler.
2234 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2236 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2238 KillSystemTimer( descr->self, LB_TIMER_ID );
2239 LISTBOX_Timer = LB_TIMER_NONE;
2241 return 0;
2245 /***********************************************************************
2246 * LISTBOX_HandleMouseMove
2248 * WM_MOUSEMOVE handler.
2250 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2251 INT x, INT y )
2253 INT index;
2254 TIMER_DIRECTION dir = LB_TIMER_NONE;
2256 if (!descr->captured) return;
2258 if (descr->style & LBS_MULTICOLUMN)
2260 if (y < 0) y = 0;
2261 else if (y >= descr->item_height * descr->page_size)
2262 y = descr->item_height * descr->page_size - 1;
2264 if (x < 0)
2266 dir = LB_TIMER_LEFT;
2267 x = 0;
2269 else if (x >= descr->width)
2271 dir = LB_TIMER_RIGHT;
2272 x = descr->width - 1;
2275 else
2277 if (y < 0) dir = LB_TIMER_UP; /* above */
2278 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2281 index = LISTBOX_GetItemFromPoint( descr, x, y );
2282 if (index == -1) index = descr->focus_item;
2283 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2285 /* Start/stop the system timer */
2287 if (dir != LB_TIMER_NONE)
2288 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2289 else if (LISTBOX_Timer != LB_TIMER_NONE)
2290 KillSystemTimer( descr->self, LB_TIMER_ID );
2291 LISTBOX_Timer = dir;
2295 /***********************************************************************
2296 * LISTBOX_HandleKeyDown
2298 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2300 INT caret = -1;
2301 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2302 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2303 bForceSelection = FALSE; /* only for single select list */
2305 if (descr->style & LBS_WANTKEYBOARDINPUT)
2307 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2308 MAKEWPARAM(LOWORD(key), descr->focus_item),
2309 (LPARAM)descr->self );
2310 if (caret == -2) return 0;
2312 if (caret == -1) switch(key)
2314 case VK_LEFT:
2315 if (descr->style & LBS_MULTICOLUMN)
2317 bForceSelection = FALSE;
2318 if (descr->focus_item >= descr->page_size)
2319 caret = descr->focus_item - descr->page_size;
2320 break;
2322 /* fall through */
2323 case VK_UP:
2324 caret = descr->focus_item - 1;
2325 if (caret < 0) caret = 0;
2326 break;
2327 case VK_RIGHT:
2328 if (descr->style & LBS_MULTICOLUMN)
2330 bForceSelection = FALSE;
2331 if (descr->focus_item + descr->page_size < descr->nb_items)
2332 caret = descr->focus_item + descr->page_size;
2333 break;
2335 /* fall through */
2336 case VK_DOWN:
2337 caret = descr->focus_item + 1;
2338 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2339 break;
2341 case VK_PRIOR:
2342 if (descr->style & LBS_MULTICOLUMN)
2344 INT page = descr->width / descr->column_width;
2345 if (page < 1) page = 1;
2346 caret = descr->focus_item - (page * descr->page_size) + 1;
2348 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2349 if (caret < 0) caret = 0;
2350 break;
2351 case VK_NEXT:
2352 if (descr->style & LBS_MULTICOLUMN)
2354 INT page = descr->width / descr->column_width;
2355 if (page < 1) page = 1;
2356 caret = descr->focus_item + (page * descr->page_size) - 1;
2358 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2359 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2360 break;
2361 case VK_HOME:
2362 caret = 0;
2363 break;
2364 case VK_END:
2365 caret = descr->nb_items - 1;
2366 break;
2367 case VK_SPACE:
2368 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2369 else if (descr->style & LBS_MULTIPLESEL)
2371 LISTBOX_SetSelection( descr, descr->focus_item,
2372 !descr->items[descr->focus_item].selected,
2373 (descr->style & LBS_NOTIFY) != 0 );
2375 break;
2376 default:
2377 bForceSelection = FALSE;
2379 if (bForceSelection) /* focused item is used instead of key */
2380 caret = descr->focus_item;
2381 if (caret >= 0)
2383 if (((descr->style & LBS_EXTENDEDSEL) &&
2384 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2385 !IS_MULTISELECT(descr))
2386 descr->anchor_item = caret;
2387 LISTBOX_MoveCaret( descr, caret, TRUE );
2389 if (descr->style & LBS_MULTIPLESEL)
2390 descr->selected_item = caret;
2391 else
2392 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2393 if (descr->style & LBS_NOTIFY)
2395 if( descr->lphc )
2397 /* make sure that combo parent doesn't hide us */
2398 descr->lphc->wState |= CBF_NOROLLUP;
2400 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2403 return 0;
2407 /***********************************************************************
2408 * LISTBOX_HandleChar
2410 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2412 INT caret = -1;
2413 WCHAR str[2];
2415 str[0] = charW;
2416 str[1] = '\0';
2418 if (descr->style & LBS_WANTKEYBOARDINPUT)
2420 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2421 MAKEWPARAM(charW, descr->focus_item),
2422 (LPARAM)descr->self );
2423 if (caret == -2) return 0;
2425 if (caret == -1)
2426 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2427 if (caret != -1)
2429 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2430 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2431 LISTBOX_MoveCaret( descr, caret, TRUE );
2432 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2433 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2435 return 0;
2439 /***********************************************************************
2440 * LISTBOX_Create
2442 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2444 LB_DESCR *descr;
2445 MEASUREITEMSTRUCT mis;
2446 RECT rect;
2448 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2449 return FALSE;
2451 GetClientRect( hwnd, &rect );
2452 descr->self = hwnd;
2453 descr->owner = GetParent( descr->self );
2454 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2455 descr->width = rect.right - rect.left;
2456 descr->height = rect.bottom - rect.top;
2457 descr->items = NULL;
2458 descr->nb_items = 0;
2459 descr->top_item = 0;
2460 descr->selected_item = -1;
2461 descr->focus_item = 0;
2462 descr->anchor_item = -1;
2463 descr->item_height = 1;
2464 descr->page_size = 1;
2465 descr->column_width = 150;
2466 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2467 descr->horz_pos = 0;
2468 descr->nb_tabs = 0;
2469 descr->tabs = NULL;
2470 descr->caret_on = lphc ? FALSE : TRUE;
2471 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2472 descr->in_focus = FALSE;
2473 descr->captured = FALSE;
2474 descr->font = 0;
2475 descr->locale = GetUserDefaultLCID();
2476 descr->lphc = lphc;
2478 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2480 /* Win95 document "List Box Differences" from MSDN:
2481 If a list box in a version 3.x application has either the
2482 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2483 horizontal and vertical scroll bars.
2485 descr->style |= WS_VSCROLL | WS_HSCROLL;
2488 if( lphc )
2490 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2491 descr->owner = lphc->self;
2494 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2496 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2498 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2499 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2500 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2501 descr->item_height = LISTBOX_SetFont( descr, 0 );
2503 if (descr->style & LBS_OWNERDRAWFIXED)
2505 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2507 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2508 descr->item_height = lphc->fixedOwnerDrawHeight;
2510 else
2512 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2513 mis.CtlType = ODT_LISTBOX;
2514 mis.CtlID = id;
2515 mis.itemID = -1;
2516 mis.itemWidth = 0;
2517 mis.itemData = 0;
2518 mis.itemHeight = descr->item_height;
2519 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2520 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2524 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2525 return TRUE;
2529 /***********************************************************************
2530 * LISTBOX_Destroy
2532 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2534 LISTBOX_ResetContent( descr );
2535 SetWindowLongPtrW( descr->self, 0, 0 );
2536 HeapFree( GetProcessHeap(), 0, descr );
2537 return TRUE;
2541 /***********************************************************************
2542 * ListBoxWndProc_common
2544 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2545 WPARAM wParam, LPARAM lParam, BOOL unicode )
2547 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2548 LPHEADCOMBO lphc = 0;
2549 LRESULT ret;
2551 if (!descr)
2553 if (!IsWindow(hwnd)) return 0;
2555 if (msg == WM_CREATE)
2557 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2558 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2559 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2560 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongPtrW( hwnd, 0 ) );
2561 return 0;
2563 /* Ignore all other messages before we get a WM_CREATE */
2564 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2565 DefWindowProcA( hwnd, msg, wParam, lParam );
2567 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2569 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2570 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2572 switch(msg)
2574 case LB_RESETCONTENT16:
2575 case LB_RESETCONTENT:
2576 LISTBOX_ResetContent( descr );
2577 LISTBOX_UpdateScroll( descr );
2578 InvalidateRect( descr->self, NULL, TRUE );
2579 return 0;
2581 case LB_ADDSTRING16:
2582 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2583 /* fall through */
2584 case LB_ADDSTRING:
2586 INT ret;
2587 LPWSTR textW;
2588 if(unicode || !HAS_STRINGS(descr))
2589 textW = (LPWSTR)lParam;
2590 else
2592 LPSTR textA = (LPSTR)lParam;
2593 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2594 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2595 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2597 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2598 ret = LISTBOX_InsertString( descr, wParam, textW );
2599 if (!unicode && HAS_STRINGS(descr))
2600 HeapFree(GetProcessHeap(), 0, textW);
2601 return ret;
2604 case LB_INSERTSTRING16:
2605 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2606 wParam = (INT)(INT16)wParam;
2607 /* fall through */
2608 case LB_INSERTSTRING:
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 ret = LISTBOX_InsertString( descr, wParam, textW );
2622 if(!unicode && HAS_STRINGS(descr))
2623 HeapFree(GetProcessHeap(), 0, textW);
2624 return ret;
2627 case LB_ADDFILE16:
2628 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2629 /* fall through */
2630 case LB_ADDFILE:
2632 INT ret;
2633 LPWSTR textW;
2634 if(unicode || !HAS_STRINGS(descr))
2635 textW = (LPWSTR)lParam;
2636 else
2638 LPSTR textA = (LPSTR)lParam;
2639 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2640 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2641 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2643 wParam = LISTBOX_FindFileStrPos( descr, textW );
2644 ret = LISTBOX_InsertString( descr, wParam, textW );
2645 if(!unicode && HAS_STRINGS(descr))
2646 HeapFree(GetProcessHeap(), 0, textW);
2647 return ret;
2650 case LB_DELETESTRING16:
2651 case LB_DELETESTRING:
2652 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2653 return descr->nb_items;
2654 else
2656 SetLastError(ERROR_INVALID_INDEX);
2657 return LB_ERR;
2660 case LB_GETITEMDATA16:
2661 case LB_GETITEMDATA:
2662 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2664 SetLastError(ERROR_INVALID_INDEX);
2665 return LB_ERR;
2667 return descr->items[wParam].data;
2669 case LB_SETITEMDATA16:
2670 case LB_SETITEMDATA:
2671 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2673 SetLastError(ERROR_INVALID_INDEX);
2674 return LB_ERR;
2676 descr->items[wParam].data = lParam;
2677 return LB_OKAY;
2679 case LB_GETCOUNT16:
2680 case LB_GETCOUNT:
2681 return descr->nb_items;
2683 case LB_GETTEXT16:
2684 lParam = (LPARAM)MapSL(lParam);
2685 /* fall through */
2686 case LB_GETTEXT:
2687 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2689 case LB_GETTEXTLEN16:
2690 /* fall through */
2691 case LB_GETTEXTLEN:
2692 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2694 SetLastError(ERROR_INVALID_INDEX);
2695 return LB_ERR;
2697 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2698 if (unicode) return strlenW( descr->items[wParam].str );
2699 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2700 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2702 case LB_GETCURSEL16:
2703 case LB_GETCURSEL:
2704 if (descr->nb_items == 0)
2705 return LB_ERR;
2706 if (!IS_MULTISELECT(descr))
2707 return descr->selected_item;
2708 if (descr->selected_item != -1)
2709 return descr->selected_item;
2710 return descr->focus_item;
2711 /* otherwise, if the user tries to move the selection with the */
2712 /* arrow keys, we will give the application something to choke on */
2713 case LB_GETTOPINDEX16:
2714 case LB_GETTOPINDEX:
2715 return descr->top_item;
2717 case LB_GETITEMHEIGHT16:
2718 case LB_GETITEMHEIGHT:
2719 return LISTBOX_GetItemHeight( descr, wParam );
2721 case LB_SETITEMHEIGHT16:
2722 lParam = LOWORD(lParam);
2723 /* fall through */
2724 case LB_SETITEMHEIGHT:
2725 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2727 case LB_ITEMFROMPOINT:
2729 POINT pt;
2730 RECT rect;
2732 pt.x = LOWORD(lParam);
2733 pt.y = HIWORD(lParam);
2734 rect.left = 0;
2735 rect.top = 0;
2736 rect.right = descr->width;
2737 rect.bottom = descr->height;
2739 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2740 !PtInRect( &rect, pt ) );
2743 case LB_SETCARETINDEX16:
2744 case LB_SETCARETINDEX:
2745 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2746 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2747 return LB_ERR;
2748 else if (ISWIN31)
2749 return wParam;
2750 else
2751 return LB_OKAY;
2753 case LB_GETCARETINDEX16:
2754 case LB_GETCARETINDEX:
2755 return descr->focus_item;
2757 case LB_SETTOPINDEX16:
2758 case LB_SETTOPINDEX:
2759 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2761 case LB_SETCOLUMNWIDTH16:
2762 case LB_SETCOLUMNWIDTH:
2763 return LISTBOX_SetColumnWidth( descr, wParam );
2765 case LB_GETITEMRECT16:
2767 RECT rect;
2768 RECT16 *r16 = MapSL(lParam);
2769 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2770 r16->left = rect.left;
2771 r16->top = rect.top;
2772 r16->right = rect.right;
2773 r16->bottom = rect.bottom;
2775 return ret;
2777 case LB_GETITEMRECT:
2778 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2780 case LB_FINDSTRING16:
2781 wParam = (INT)(INT16)wParam;
2782 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2783 /* fall through */
2784 case LB_FINDSTRING:
2786 INT ret;
2787 LPWSTR textW;
2788 if(unicode || !HAS_STRINGS(descr))
2789 textW = (LPWSTR)lParam;
2790 else
2792 LPSTR textA = (LPSTR)lParam;
2793 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2794 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2795 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2797 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2798 if(!unicode && HAS_STRINGS(descr))
2799 HeapFree(GetProcessHeap(), 0, textW);
2800 return ret;
2803 case LB_FINDSTRINGEXACT16:
2804 wParam = (INT)(INT16)wParam;
2805 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2806 /* fall through */
2807 case LB_FINDSTRINGEXACT:
2809 INT ret;
2810 LPWSTR textW;
2811 if(unicode || !HAS_STRINGS(descr))
2812 textW = (LPWSTR)lParam;
2813 else
2815 LPSTR textA = (LPSTR)lParam;
2816 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2817 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2818 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2820 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2821 if(!unicode && HAS_STRINGS(descr))
2822 HeapFree(GetProcessHeap(), 0, textW);
2823 return ret;
2826 case LB_SELECTSTRING16:
2827 wParam = (INT)(INT16)wParam;
2828 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2829 /* fall through */
2830 case LB_SELECTSTRING:
2832 INT index;
2833 LPWSTR textW;
2835 if(HAS_STRINGS(descr))
2836 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2837 debugstr_a((LPSTR)lParam));
2838 if(unicode || !HAS_STRINGS(descr))
2839 textW = (LPWSTR)lParam;
2840 else
2842 LPSTR textA = (LPSTR)lParam;
2843 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2844 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2845 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2847 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2848 if(!unicode && HAS_STRINGS(descr))
2849 HeapFree(GetProcessHeap(), 0, textW);
2850 if (index != LB_ERR)
2852 LISTBOX_MoveCaret( descr, index, TRUE );
2853 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2855 return index;
2858 case LB_GETSEL16:
2859 wParam = (INT)(INT16)wParam;
2860 /* fall through */
2861 case LB_GETSEL:
2862 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2863 return LB_ERR;
2864 return descr->items[wParam].selected;
2866 case LB_SETSEL16:
2867 lParam = (INT)(INT16)lParam;
2868 /* fall through */
2869 case LB_SETSEL:
2870 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2872 case LB_SETCURSEL16:
2873 wParam = (INT)(INT16)wParam;
2874 /* fall through */
2875 case LB_SETCURSEL:
2876 if (IS_MULTISELECT(descr)) return LB_ERR;
2877 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2878 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2879 if (lphc && ret != LB_ERR) ret = descr->selected_item;
2880 return ret;
2882 case LB_GETSELCOUNT16:
2883 case LB_GETSELCOUNT:
2884 return LISTBOX_GetSelCount( descr );
2886 case LB_GETSELITEMS16:
2887 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2889 case LB_GETSELITEMS:
2890 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2892 case LB_SELITEMRANGE16:
2893 case LB_SELITEMRANGE:
2894 if (LOWORD(lParam) <= HIWORD(lParam))
2895 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2896 HIWORD(lParam), wParam );
2897 else
2898 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2899 LOWORD(lParam), wParam );
2901 case LB_SELITEMRANGEEX16:
2902 case LB_SELITEMRANGEEX:
2903 if ((INT)lParam >= (INT)wParam)
2904 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2905 else
2906 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2908 case LB_GETHORIZONTALEXTENT16:
2909 case LB_GETHORIZONTALEXTENT:
2910 return descr->horz_extent;
2912 case LB_SETHORIZONTALEXTENT16:
2913 case LB_SETHORIZONTALEXTENT:
2914 return LISTBOX_SetHorizontalExtent( descr, wParam );
2916 case LB_GETANCHORINDEX16:
2917 case LB_GETANCHORINDEX:
2918 return descr->anchor_item;
2920 case LB_SETANCHORINDEX16:
2921 wParam = (INT)(INT16)wParam;
2922 /* fall through */
2923 case LB_SETANCHORINDEX:
2924 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2926 SetLastError(ERROR_INVALID_INDEX);
2927 return LB_ERR;
2929 descr->anchor_item = (INT)wParam;
2930 return LB_OKAY;
2932 case LB_DIR16:
2933 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2934 * be set automatically (this is different in Win32) */
2935 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2936 lParam = (LPARAM)MapSL(lParam);
2937 /* fall through */
2938 case LB_DIR:
2940 INT ret;
2941 LPWSTR textW;
2942 if(unicode)
2943 textW = (LPWSTR)lParam;
2944 else
2946 LPSTR textA = (LPSTR)lParam;
2947 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2948 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2949 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2951 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2952 if(!unicode)
2953 HeapFree(GetProcessHeap(), 0, textW);
2954 return ret;
2957 case LB_GETLOCALE:
2958 return descr->locale;
2960 case LB_SETLOCALE:
2962 LCID ret;
2963 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2964 return LB_ERR;
2965 ret = descr->locale;
2966 descr->locale = (LCID)wParam;
2967 return ret;
2970 case LB_INITSTORAGE:
2971 return LISTBOX_InitStorage( descr, wParam );
2973 case LB_SETCOUNT:
2974 return LISTBOX_SetCount( descr, (INT)wParam );
2976 case LB_SETTABSTOPS16:
2977 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2979 case LB_SETTABSTOPS:
2980 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
2982 case LB_CARETON16:
2983 case LB_CARETON:
2984 if (descr->caret_on)
2985 return LB_OKAY;
2986 descr->caret_on = TRUE;
2987 if ((descr->focus_item != -1) && (descr->in_focus))
2988 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2989 return LB_OKAY;
2991 case LB_CARETOFF16:
2992 case LB_CARETOFF:
2993 if (!descr->caret_on)
2994 return LB_OKAY;
2995 descr->caret_on = FALSE;
2996 if ((descr->focus_item != -1) && (descr->in_focus))
2997 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2998 return LB_OKAY;
3000 case LB_GETLISTBOXINFO:
3001 FIXME("LB_GETLISTBOXINFO: stub!\n");
3002 return 0;
3004 case WM_DESTROY:
3005 return LISTBOX_Destroy( descr );
3007 case WM_ENABLE:
3008 InvalidateRect( descr->self, NULL, TRUE );
3009 return 0;
3011 case WM_SETREDRAW:
3012 LISTBOX_SetRedraw( descr, wParam != 0 );
3013 return 0;
3015 case WM_GETDLGCODE:
3016 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3018 case WM_PRINTCLIENT:
3019 case WM_PAINT:
3021 PAINTSTRUCT ps;
3022 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3023 ret = LISTBOX_Paint( descr, hdc );
3024 if( !wParam ) EndPaint( descr->self, &ps );
3026 return ret;
3027 case WM_SIZE:
3028 LISTBOX_UpdateSize( descr );
3029 return 0;
3030 case WM_GETFONT:
3031 return (LRESULT)descr->font;
3032 case WM_SETFONT:
3033 LISTBOX_SetFont( descr, (HFONT)wParam );
3034 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3035 return 0;
3036 case WM_SETFOCUS:
3037 descr->in_focus = TRUE;
3038 descr->caret_on = TRUE;
3039 if (descr->focus_item != -1)
3040 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3041 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3042 return 0;
3043 case WM_KILLFOCUS:
3044 descr->in_focus = FALSE;
3045 if ((descr->focus_item != -1) && descr->caret_on)
3046 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3047 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3048 return 0;
3049 case WM_HSCROLL:
3050 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3051 case WM_VSCROLL:
3052 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3053 case WM_MOUSEWHEEL:
3054 if (wParam & (MK_SHIFT | MK_CONTROL))
3055 return DefWindowProcW( descr->self, msg, wParam, lParam );
3056 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3057 case WM_LBUTTONDOWN:
3058 if (lphc)
3059 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3060 (INT16)LOWORD(lParam),
3061 (INT16)HIWORD(lParam) );
3062 return LISTBOX_HandleLButtonDown( descr, wParam,
3063 (INT16)LOWORD(lParam),
3064 (INT16)HIWORD(lParam) );
3065 case WM_LBUTTONDBLCLK:
3066 if (lphc)
3067 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3068 (INT16)LOWORD(lParam),
3069 (INT16)HIWORD(lParam) );
3070 if (descr->style & LBS_NOTIFY)
3071 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3072 return 0;
3073 case WM_MOUSEMOVE:
3074 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3076 BOOL captured = descr->captured;
3077 POINT mousePos;
3078 RECT clientRect;
3080 mousePos.x = (INT16)LOWORD(lParam);
3081 mousePos.y = (INT16)HIWORD(lParam);
3084 * If we are in a dropdown combobox, we simulate that
3085 * the mouse is captured to show the tracking of the item.
3087 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3088 descr->captured = TRUE;
3090 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3092 descr->captured = captured;
3094 else if (GetCapture() == descr->self)
3096 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3097 (INT16)HIWORD(lParam) );
3099 return 0;
3100 case WM_LBUTTONUP:
3101 if (lphc)
3103 POINT mousePos;
3104 RECT clientRect;
3107 * If the mouse button "up" is not in the listbox,
3108 * we make sure there is no selection by re-selecting the
3109 * item that was selected when the listbox was made visible.
3111 mousePos.x = (INT16)LOWORD(lParam);
3112 mousePos.y = (INT16)HIWORD(lParam);
3114 GetClientRect(descr->self, &clientRect);
3117 * When the user clicks outside the combobox and the focus
3118 * is lost, the owning combobox will send a fake buttonup with
3119 * 0xFFFFFFF as the mouse location, we must also revert the
3120 * selection to the original selection.
3122 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3123 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3125 return LISTBOX_HandleLButtonUp( descr );
3126 case WM_KEYDOWN:
3127 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3129 /* for some reason Windows makes it possible to
3130 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3132 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3133 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3134 && (wParam == VK_DOWN || wParam == VK_UP)) )
3136 COMBO_FlipListbox( lphc, FALSE, FALSE );
3137 return 0;
3140 return LISTBOX_HandleKeyDown( descr, wParam );
3141 case WM_CHAR:
3143 WCHAR charW;
3144 if(unicode)
3145 charW = (WCHAR)wParam;
3146 else
3148 CHAR charA = (CHAR)wParam;
3149 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3151 return LISTBOX_HandleChar( descr, charW );
3153 case WM_SYSTIMER:
3154 return LISTBOX_HandleSystemTimer( descr );
3155 case WM_ERASEBKGND:
3156 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3158 RECT rect;
3159 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3160 wParam, (LPARAM)descr->self );
3161 TRACE("hbrush = %p\n", hbrush);
3162 if(!hbrush)
3163 hbrush = GetSysColorBrush(COLOR_WINDOW);
3164 if(hbrush)
3166 GetClientRect(descr->self, &rect);
3167 FillRect((HDC)wParam, &rect, hbrush);
3170 return 1;
3171 case WM_DROPFILES:
3172 if( lphc ) return 0;
3173 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3174 SendMessageA( descr->owner, msg, wParam, lParam );
3176 case WM_NCDESTROY:
3177 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3178 lphc->hWndLBox = 0;
3179 break;
3181 case WM_NCACTIVATE:
3182 if (lphc) return 0;
3183 break;
3185 default:
3186 if ((msg >= WM_USER) && (msg < 0xc000))
3187 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3188 hwnd, msg, wParam, lParam );
3191 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3192 DefWindowProcA( hwnd, msg, wParam, lParam );
3195 /***********************************************************************
3196 * ListBoxWndProcA
3198 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3200 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3203 /***********************************************************************
3204 * ListBoxWndProcW
3206 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3208 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );