RDW_FRAME should be RDW_NOFRAME when validating (spotted by Duane
[wine/multimedia.git] / controls / listbox.c
blob43796453f5117fcd8ee7a54add36e7ed178ef6de
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
21 #include <string.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include "windef.h"
25 #include "wingdi.h"
26 #include "wine/winuser16.h"
27 #include "wine/winbase16.h"
28 #include "wine/unicode.h"
29 #include "winuser.h"
30 #include "winerror.h"
31 #include "spy.h"
32 #include "user.h"
33 #include "controls.h"
34 #include "wine/debug.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
37 WINE_DECLARE_DEBUG_CHANNEL(combo);
39 /* Unimplemented yet:
40 * - LBS_USETABSTOPS
41 * - Locale handling
43 * Probably needs improvement:
44 * - LBS_NOSEL
47 /* Items array granularity */
48 #define LB_ARRAY_GRANULARITY 16
50 /* Scrolling timeout in ms */
51 #define LB_SCROLL_TIMEOUT 50
53 /* Listbox system timer id */
54 #define LB_TIMER_ID 2
56 /* flag listbox changed while setredraw false - internal style */
57 #define LBS_DISPLAYCHANGED 0x80000000
59 /* Item structure */
60 typedef struct
62 LPWSTR str; /* Item text */
63 BOOL selected; /* Is item selected? */
64 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
65 DWORD data; /* User data */
66 } LB_ITEMDATA;
68 /* Listbox structure */
69 typedef struct
71 HWND owner; /* Owner window to send notifications to */
72 UINT style; /* Window style */
73 INT width; /* Window width */
74 INT height; /* Window height */
75 LB_ITEMDATA *items; /* Array of items */
76 INT nb_items; /* Number of items */
77 INT top_item; /* Top visible item */
78 INT selected_item; /* Selected item */
79 INT focus_item; /* Item that has the focus */
80 INT anchor_item; /* Anchor item for extended selection */
81 INT item_height; /* Default item height */
82 INT page_size; /* Items per listbox page */
83 INT column_width; /* Column width for multi-column listboxes */
84 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
85 INT horz_pos; /* Horizontal position */
86 INT nb_tabs; /* Number of tabs in array */
87 INT *tabs; /* Array of tabs */
88 BOOL caret_on; /* Is caret on? */
89 BOOL captured; /* Is mouse captured? */
90 BOOL in_focus;
91 HFONT font; /* Current font */
92 LCID locale; /* Current locale for string comparisons */
93 LPHEADCOMBO lphc; /* ComboLBox */
94 } LB_DESCR;
97 #define IS_OWNERDRAW(descr) \
98 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
100 #define HAS_STRINGS(descr) \
101 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
104 #define IS_MULTISELECT(descr) \
105 ((descr)->style & LBS_MULTIPLESEL || ((descr)->style & LBS_EXTENDEDSEL))
107 #define SEND_NOTIFICATION(hwnd,descr,code) \
108 (SendMessageW( (descr)->owner, WM_COMMAND, \
109 MAKEWPARAM( GetWindowLongA((hwnd),GWL_ID), (code)), (hwnd) ))
111 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
113 /* Current timer status */
114 typedef enum
116 LB_TIMER_NONE,
117 LB_TIMER_UP,
118 LB_TIMER_LEFT,
119 LB_TIMER_DOWN,
120 LB_TIMER_RIGHT
121 } TIMER_DIRECTION;
123 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
125 static LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
126 static LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
127 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
128 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
130 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
132 /*********************************************************************
133 * listbox class descriptor
135 const struct builtin_class_descr LISTBOX_builtin_class =
137 "ListBox", /* name */
138 CS_GLOBALCLASS | CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
139 ListBoxWndProcA, /* procA */
140 ListBoxWndProcW, /* procW */
141 sizeof(LB_DESCR *), /* extra */
142 IDC_ARROWA, /* cursor */
143 0 /* brush */
147 /*********************************************************************
148 * combolbox class descriptor
150 const struct builtin_class_descr COMBOLBOX_builtin_class =
152 "ComboLBox", /* name */
153 CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS, /* style */
154 ComboLBWndProcA, /* procA */
155 ComboLBWndProcW, /* procW */
156 sizeof(LB_DESCR *), /* extra */
157 IDC_ARROWA, /* cursor */
158 0 /* brush */
162 /* check whether app is a Win 3.1 app */
163 inline static BOOL is_old_app( HWND hwnd )
165 return (GetExpWinVer16( GetWindowLongA(hwnd,GWL_HINSTANCE) ) & 0xFF00 ) == 0x0300;
169 /***********************************************************************
170 * LISTBOX_Dump
172 void LISTBOX_Dump( HWND hwnd )
174 INT i;
175 LB_ITEMDATA *item;
176 LB_DESCR *descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 );
178 TRACE( "Listbox:\n" );
179 TRACE( "hwnd=%04x descr=%08x items=%d top=%d\n",
180 hwnd, (UINT)descr, descr->nb_items,
181 descr->top_item );
182 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
184 TRACE( "%4d: %-40s %d %08lx %3d\n",
185 i, debugstr_w(item->str), item->selected, item->data, item->height );
190 /***********************************************************************
191 * LISTBOX_GetCurrentPageSize
193 * Return the current page size
195 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
197 INT i, height;
198 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
199 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
201 if ((height += descr->items[i].height) > descr->height) break;
203 if (i == descr->top_item) return 1;
204 else return i - descr->top_item;
208 /***********************************************************************
209 * LISTBOX_GetMaxTopIndex
211 * Return the maximum possible index for the top of the listbox.
213 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
215 INT max, page;
217 if (descr->style & LBS_OWNERDRAWVARIABLE)
219 page = descr->height;
220 for (max = descr->nb_items - 1; max >= 0; max--)
221 if ((page -= descr->items[max].height) < 0) break;
222 if (max < descr->nb_items - 1) max++;
224 else if (descr->style & LBS_MULTICOLUMN)
226 if ((page = descr->width / descr->column_width) < 1) page = 1;
227 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
228 max = (max - page) * descr->page_size;
230 else
232 max = descr->nb_items - descr->page_size;
234 if (max < 0) max = 0;
235 return max;
239 /***********************************************************************
240 * LISTBOX_UpdateScroll
242 * Update the scrollbars. Should be called whenever the content
243 * of the listbox changes.
245 static void LISTBOX_UpdateScroll( HWND hwnd, LB_DESCR *descr )
247 SCROLLINFO info;
249 /* Check the listbox scroll bar flags individually before we call
250 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
251 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
252 scroll bar when we do not need one.
253 if (!(descr->style & WS_VSCROLL)) return;
256 /* It is important that we check descr->style, and not wnd->dwStyle,
257 for WS_VSCROLL, as the former is exactly the one passed in
258 argument to CreateWindow.
259 In Windows (and from now on in Wine :) a listbox created
260 with such a style (no WS_SCROLL) does not update
261 the scrollbar with listbox-related data, thus letting
262 the programmer use it for his/her own purposes. */
264 if (descr->style & LBS_NOREDRAW) return;
265 info.cbSize = sizeof(info);
267 if (descr->style & LBS_MULTICOLUMN)
269 info.nMin = 0;
270 info.nMax = (descr->nb_items - 1) / descr->page_size;
271 info.nPos = descr->top_item / descr->page_size;
272 info.nPage = descr->width / descr->column_width;
273 if (info.nPage < 1) info.nPage = 1;
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_HSCROLL)
278 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
279 info.nMax = 0;
280 info.fMask = SIF_RANGE;
281 if (descr->style & WS_VSCROLL)
282 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
284 else
286 info.nMin = 0;
287 info.nMax = descr->nb_items - 1;
288 info.nPos = descr->top_item;
289 info.nPage = LISTBOX_GetCurrentPageSize( descr );
290 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
291 if (descr->style & LBS_DISABLENOSCROLL)
292 info.fMask |= SIF_DISABLENOSCROLL;
293 if (descr->style & WS_VSCROLL)
294 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
296 if (descr->horz_extent)
298 info.nMin = 0;
299 info.nMax = descr->horz_extent - 1;
300 info.nPos = descr->horz_pos;
301 info.nPage = descr->width;
302 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
303 if (descr->style & LBS_DISABLENOSCROLL)
304 info.fMask |= SIF_DISABLENOSCROLL;
305 if (descr->style & WS_HSCROLL)
306 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
312 /***********************************************************************
313 * LISTBOX_SetTopItem
315 * Set the top item of the listbox, scrolling up or down if necessary.
317 static LRESULT LISTBOX_SetTopItem( HWND hwnd, LB_DESCR *descr, INT index,
318 BOOL scroll )
320 INT max = LISTBOX_GetMaxTopIndex( descr );
321 if (index > max) index = max;
322 if (index < 0) index = 0;
323 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
324 if (descr->top_item == index) return LB_OKAY;
325 if (descr->style & LBS_MULTICOLUMN)
327 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
328 if (scroll && (abs(diff) < descr->width))
329 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
330 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
332 else
333 scroll = FALSE;
335 else if (scroll)
337 INT diff;
338 if (descr->style & LBS_OWNERDRAWVARIABLE)
340 INT i;
341 diff = 0;
342 if (index > descr->top_item)
344 for (i = index - 1; i >= descr->top_item; i--)
345 diff -= descr->items[i].height;
347 else
349 for (i = index; i < descr->top_item; i++)
350 diff += descr->items[i].height;
353 else
354 diff = (descr->top_item - index) * descr->item_height;
356 if (abs(diff) < descr->height)
357 ScrollWindowEx( hwnd, 0, diff, NULL, NULL, 0, NULL,
358 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
359 else
360 scroll = FALSE;
362 if (!scroll) InvalidateRect( hwnd, NULL, TRUE );
363 descr->top_item = index;
364 LISTBOX_UpdateScroll( hwnd, descr );
365 return LB_OKAY;
369 /***********************************************************************
370 * LISTBOX_UpdatePage
372 * Update the page size. Should be called when the size of
373 * the client area or the item height changes.
375 static void LISTBOX_UpdatePage( HWND hwnd, LB_DESCR *descr )
377 INT page_size;
379 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
380 page_size = 1;
381 if (page_size == descr->page_size) return;
382 descr->page_size = page_size;
383 if (descr->style & LBS_MULTICOLUMN)
384 InvalidateRect( hwnd, NULL, TRUE );
385 LISTBOX_SetTopItem( hwnd, descr, descr->top_item, FALSE );
389 /***********************************************************************
390 * LISTBOX_UpdateSize
392 * Update the size of the listbox. Should be called when the size of
393 * the client area changes.
395 static void LISTBOX_UpdateSize( HWND hwnd, LB_DESCR *descr )
397 RECT rect;
399 GetClientRect( hwnd, &rect );
400 descr->width = rect.right - rect.left;
401 descr->height = rect.bottom - rect.top;
402 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
404 INT remaining;
405 RECT rect;
407 GetWindowRect( hwnd, &rect );
408 if(descr->item_height != 0)
409 remaining = descr->height % descr->item_height;
410 else
411 remaining = 0;
412 if ((descr->height > descr->item_height) && remaining)
414 if (is_old_app(hwnd))
415 { /* give a margin for error to 16 bits programs - if we need
416 less than the height of the nonclient area, round to the
417 *next* number of items */
418 int ncheight = rect.bottom - rect.top - descr->height;
419 if ((descr->item_height - remaining) <= ncheight)
420 remaining = remaining - descr->item_height;
422 TRACE("[%04x]: changing height %d -> %d\n",
423 hwnd, descr->height, descr->height - remaining );
424 SetWindowPos( hwnd, 0, 0, 0, rect.right - rect.left,
425 rect.bottom - rect.top - remaining,
426 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
427 return;
430 TRACE("[%04x]: new size = %d,%d\n", hwnd, descr->width, descr->height );
431 LISTBOX_UpdatePage( hwnd, descr );
432 LISTBOX_UpdateScroll( hwnd, descr );
434 /* Invalidate the focused item so it will be repainted correctly */
435 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
437 InvalidateRect( hwnd, &rect, FALSE );
442 /***********************************************************************
443 * LISTBOX_GetItemRect
445 * Get the rectangle enclosing an item, in listbox client coordinates.
446 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
448 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
450 /* Index <= 0 is legal even on empty listboxes */
451 if (index && (index >= descr->nb_items)) return -1;
452 SetRect( rect, 0, 0, descr->width, descr->height );
453 if (descr->style & LBS_MULTICOLUMN)
455 INT col = (index / descr->page_size) -
456 (descr->top_item / descr->page_size);
457 rect->left += col * descr->column_width;
458 rect->right = rect->left + descr->column_width;
459 rect->top += (index % descr->page_size) * descr->item_height;
460 rect->bottom = rect->top + descr->item_height;
462 else if (descr->style & LBS_OWNERDRAWVARIABLE)
464 INT i;
465 rect->right += descr->horz_pos;
466 if ((index >= 0) && (index < descr->nb_items))
468 if (index < descr->top_item)
470 for (i = descr->top_item-1; i >= index; i--)
471 rect->top -= descr->items[i].height;
473 else
475 for (i = descr->top_item; i < index; i++)
476 rect->top += descr->items[i].height;
478 rect->bottom = rect->top + descr->items[index].height;
482 else
484 rect->top += (index - descr->top_item) * descr->item_height;
485 rect->bottom = rect->top + descr->item_height;
486 rect->right += descr->horz_pos;
489 return ((rect->left < descr->width) && (rect->right > 0) &&
490 (rect->top < descr->height) && (rect->bottom > 0));
494 /***********************************************************************
495 * LISTBOX_GetItemFromPoint
497 * Return the item nearest from point (x,y) (in client coordinates).
499 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
501 INT index = descr->top_item;
503 if (!descr->nb_items) return -1; /* No items */
504 if (descr->style & LBS_OWNERDRAWVARIABLE)
506 INT pos = 0;
507 if (y >= 0)
509 while (index < descr->nb_items)
511 if ((pos += descr->items[index].height) > y) break;
512 index++;
515 else
517 while (index > 0)
519 index--;
520 if ((pos -= descr->items[index].height) <= y) break;
524 else if (descr->style & LBS_MULTICOLUMN)
526 if (y >= descr->item_height * descr->page_size) return -1;
527 if (y >= 0) index += y / descr->item_height;
528 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
529 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
531 else
533 index += (y / descr->item_height);
535 if (index < 0) return 0;
536 if (index >= descr->nb_items) return -1;
537 return index;
541 /***********************************************************************
542 * LISTBOX_PaintItem
544 * Paint an item.
546 static void LISTBOX_PaintItem( HWND hwnd, LB_DESCR *descr, HDC hdc,
547 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
549 LB_ITEMDATA *item = NULL;
550 if (index < descr->nb_items) item = &descr->items[index];
552 if (IS_OWNERDRAW(descr))
554 DRAWITEMSTRUCT dis;
555 RECT r;
556 HRGN hrgn;
557 UINT id = GetWindowLongA( hwnd, GWL_ID );
559 if (!item)
561 if (action == ODA_FOCUS)
562 DrawFocusRect( hdc, rect );
563 else
564 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
565 return;
568 /* some programs mess with the clipping region when
569 drawing the item, *and* restore the previous region
570 after they are done, so a region has better to exist
571 else everything ends clipped */
572 GetClientRect(hwnd, &r);
573 hrgn = CreateRectRgnIndirect(&r);
574 SelectClipRgn( hdc, hrgn);
575 DeleteObject( hrgn );
577 dis.CtlType = ODT_LISTBOX;
578 dis.CtlID = id;
579 dis.hwndItem = hwnd;
580 dis.itemAction = action;
581 dis.hDC = hdc;
582 dis.itemID = index;
583 dis.itemState = 0;
584 if (item && item->selected) dis.itemState |= ODS_SELECTED;
585 if (!ignoreFocus && (descr->focus_item == index) &&
586 (descr->caret_on) &&
587 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
588 if (!IsWindowEnabled(hwnd)) dis.itemState |= ODS_DISABLED;
589 dis.itemData = item ? item->data : 0;
590 dis.rcItem = *rect;
591 TRACE("[%04x]: drawitem %d (%s) action=%02x state=%02x rect=%d,%d-%d,%d\n",
592 hwnd, index, item ? debugstr_w(item->str) : "", action,
593 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
594 SendMessageW(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
596 else
598 COLORREF oldText = 0, oldBk = 0;
600 if (action == ODA_FOCUS)
602 DrawFocusRect( hdc, rect );
603 return;
605 if (item && item->selected)
607 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
608 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
611 TRACE("[%04x]: painting %d (%s) action=%02x rect=%d,%d-%d,%d\n",
612 hwnd, index, item ? debugstr_w(item->str) : "", action,
613 rect->left, rect->top, rect->right, rect->bottom );
614 if (!item)
615 ExtTextOutW( hdc, rect->left + 1, rect->top,
616 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
617 else if (!(descr->style & LBS_USETABSTOPS))
618 ExtTextOutW( hdc, rect->left + 1, rect->top,
619 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
620 strlenW(item->str), NULL );
621 else
623 /* Output empty string to paint background in the full width. */
624 ExtTextOutW( hdc, rect->left + 1, rect->top,
625 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
626 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
627 item->str, strlenW(item->str),
628 descr->nb_tabs, descr->tabs, 0);
630 if (item && item->selected)
632 SetBkColor( hdc, oldBk );
633 SetTextColor( hdc, oldText );
635 if (!ignoreFocus && (descr->focus_item == index) &&
636 (descr->caret_on) &&
637 (descr->in_focus)) DrawFocusRect( hdc, rect );
642 /***********************************************************************
643 * LISTBOX_SetRedraw
645 * Change the redraw flag.
647 static void LISTBOX_SetRedraw( HWND hwnd, LB_DESCR *descr, BOOL on )
649 if (on)
651 if (!(descr->style & LBS_NOREDRAW)) return;
652 descr->style &= ~LBS_NOREDRAW;
653 if (descr->style & LBS_DISPLAYCHANGED)
654 { /* page was changed while setredraw false, refresh automatically */
655 InvalidateRect(hwnd, NULL, TRUE);
656 if ((descr->top_item + descr->page_size) > descr->nb_items)
657 { /* reset top of page if less than number of items/page */
658 descr->top_item = descr->nb_items - descr->page_size;
659 if (descr->top_item < 0) descr->top_item = 0;
661 descr->style &= ~LBS_DISPLAYCHANGED;
663 LISTBOX_UpdateScroll( hwnd, descr );
665 else descr->style |= LBS_NOREDRAW;
669 /***********************************************************************
670 * LISTBOX_RepaintItem
672 * Repaint a single item synchronously.
674 static void LISTBOX_RepaintItem( HWND hwnd, LB_DESCR *descr, INT index,
675 UINT action )
677 HDC hdc;
678 RECT rect;
679 HFONT oldFont = 0;
680 HBRUSH hbrush, oldBrush = 0;
682 /* Do not repaint the item if the item is not visible */
683 if (!IsWindowVisible(hwnd)) return;
684 if (descr->style & LBS_NOREDRAW)
686 descr->style |= LBS_DISPLAYCHANGED;
687 return;
689 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
690 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE ))) return;
691 if (descr->font) oldFont = SelectObject( hdc, descr->font );
692 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
693 hdc, (LPARAM)hwnd );
694 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
695 if (!IsWindowEnabled(hwnd))
696 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
697 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
698 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, index, action, FALSE );
699 if (oldFont) SelectObject( hdc, oldFont );
700 if (oldBrush) SelectObject( hdc, oldBrush );
701 ReleaseDC( hwnd, hdc );
705 /***********************************************************************
706 * LISTBOX_InitStorage
708 static LRESULT LISTBOX_InitStorage( HWND hwnd, LB_DESCR *descr, INT nb_items )
710 LB_ITEMDATA *item;
712 nb_items += LB_ARRAY_GRANULARITY - 1;
713 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
714 if (descr->items)
715 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
716 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
717 nb_items * sizeof(LB_ITEMDATA) )))
719 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
720 return LB_ERRSPACE;
722 descr->items = item;
723 return LB_OKAY;
727 /***********************************************************************
728 * LISTBOX_SetTabStops
730 static BOOL LISTBOX_SetTabStops( HWND hwnd, LB_DESCR *descr, INT count,
731 LPINT tabs, BOOL short_ints )
733 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
734 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
735 if (!(descr->nb_tabs = count))
737 descr->tabs = NULL;
738 return TRUE;
740 /* FIXME: count = 1 */
741 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
742 descr->nb_tabs * sizeof(INT) )))
743 return FALSE;
744 if (short_ints)
746 INT i;
747 LPINT16 p = (LPINT16)tabs;
749 TRACE("[%04x]: settabstops ", hwnd );
750 for (i = 0; i < descr->nb_tabs; i++) {
751 descr->tabs[i] = *p++<<1; /* FIXME */
752 if (TRACE_ON(listbox)) DPRINTF("%hd ", descr->tabs[i]);
754 if (TRACE_ON(listbox)) DPRINTF("\n");
756 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
757 /* FIXME: repaint the window? */
758 return TRUE;
762 /***********************************************************************
763 * LISTBOX_GetText
765 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
767 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
768 if (HAS_STRINGS(descr))
770 if (!lParam)
771 return strlenW(descr->items[index].str);
773 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
775 if(unicode)
777 LPWSTR buffer = (LPWSTR)lParam;
778 strcpyW( buffer, descr->items[index].str );
779 return strlenW(buffer);
781 else
783 LPSTR buffer = (LPSTR)lParam;
784 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
786 } else {
787 if (lParam)
788 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
789 return sizeof(DWORD);
794 /***********************************************************************
795 * LISTBOX_FindStringPos
797 * Find the nearest string located before a given string in sort order.
798 * If 'exact' is TRUE, return an error if we don't get an exact match.
800 static INT LISTBOX_FindStringPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str,
801 BOOL exact )
803 INT index, min, max, res = -1;
805 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
806 min = 0;
807 max = descr->nb_items;
808 while (min != max)
810 index = (min + max) / 2;
811 if (HAS_STRINGS(descr))
812 res = lstrcmpiW( descr->items[index].str, str );
813 else
815 COMPAREITEMSTRUCT cis;
816 UINT id = GetWindowLongA( hwnd, GWL_ID );
818 cis.CtlType = ODT_LISTBOX;
819 cis.CtlID = id;
820 cis.hwndItem = hwnd;
821 cis.itemID1 = index;
822 cis.itemData1 = descr->items[index].data;
823 cis.itemID2 = -1;
824 cis.itemData2 = (DWORD)str;
825 cis.dwLocaleId = descr->locale;
826 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
828 if (!res) return index;
829 if (res > 0) max = index;
830 else min = index + 1;
832 return exact ? -1 : max;
836 /***********************************************************************
837 * LISTBOX_FindFileStrPos
839 * Find the nearest string located before a given string in directory
840 * sort order (i.e. first files, then directories, then drives).
842 static INT LISTBOX_FindFileStrPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str )
844 INT min, max, res = -1;
846 if (!HAS_STRINGS(descr))
847 return LISTBOX_FindStringPos( hwnd, descr, str, FALSE );
848 min = 0;
849 max = descr->nb_items;
850 while (min != max)
852 INT index = (min + max) / 2;
853 LPCWSTR p = descr->items[index].str;
854 if (*p == '[') /* drive or directory */
856 if (*str != '[') res = -1;
857 else if (p[1] == '-') /* drive */
859 if (str[1] == '-') res = str[2] - p[2];
860 else res = -1;
862 else /* directory */
864 if (str[1] == '-') res = 1;
865 else res = lstrcmpiW( str, p );
868 else /* filename */
870 if (*str == '[') res = 1;
871 else res = lstrcmpiW( str, p );
873 if (!res) return index;
874 if (res < 0) max = index;
875 else min = index + 1;
877 return max;
881 /***********************************************************************
882 * LISTBOX_FindString
884 * Find the item beginning with a given string.
886 static INT LISTBOX_FindString( HWND hwnd, LB_DESCR *descr, INT start,
887 LPCWSTR str, BOOL exact )
889 INT i;
890 LB_ITEMDATA *item;
892 if (start >= descr->nb_items) start = -1;
893 item = descr->items + start + 1;
894 if (HAS_STRINGS(descr))
896 if (!str || ! str[0] ) return LB_ERR;
897 if (exact)
899 for (i = start + 1; i < descr->nb_items; i++, item++)
900 if (!lstrcmpiW( str, item->str )) return i;
901 for (i = 0, item = descr->items; i <= start; i++, item++)
902 if (!lstrcmpiW( str, item->str )) return i;
904 else
906 /* Special case for drives and directories: ignore prefix */
907 #define CHECK_DRIVE(item) \
908 if ((item)->str[0] == '[') \
910 if (!strncmpiW( str, (item)->str+1, len )) return i; \
911 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
912 return i; \
915 INT len = strlenW(str);
916 for (i = start + 1; i < descr->nb_items; i++, item++)
918 if (!strncmpiW( str, item->str, len )) return i;
919 CHECK_DRIVE(item);
921 for (i = 0, item = descr->items; i <= start; i++, item++)
923 if (!strncmpiW( str, item->str, len )) return i;
924 CHECK_DRIVE(item);
926 #undef CHECK_DRIVE
929 else
931 if (exact && (descr->style & LBS_SORT))
932 /* If sorted, use a WM_COMPAREITEM binary search */
933 return LISTBOX_FindStringPos( hwnd, descr, str, TRUE );
935 /* Otherwise use a linear search */
936 for (i = start + 1; i < descr->nb_items; i++, item++)
937 if (item->data == (DWORD)str) return i;
938 for (i = 0, item = descr->items; i <= start; i++, item++)
939 if (item->data == (DWORD)str) return i;
941 return LB_ERR;
945 /***********************************************************************
946 * LISTBOX_GetSelCount
948 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
950 INT i, count;
951 LB_ITEMDATA *item = descr->items;
953 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
954 for (i = count = 0; i < descr->nb_items; i++, item++)
955 if (item->selected) count++;
956 return count;
960 /***********************************************************************
961 * LISTBOX_GetSelItems16
963 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
965 INT i, count;
966 LB_ITEMDATA *item = descr->items;
968 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
969 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
970 if (item->selected) array[count++] = (INT16)i;
971 return count;
975 /***********************************************************************
976 * LISTBOX_GetSelItems
978 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT 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++] = i;
986 return count;
990 /***********************************************************************
991 * LISTBOX_Paint
993 static LRESULT LISTBOX_Paint( HWND hwnd, LB_DESCR *descr, HDC hdc )
995 INT i, col_pos = descr->page_size - 1;
996 RECT rect;
997 RECT focusRect = {-1, -1, -1, -1};
998 HFONT oldFont = 0;
999 HBRUSH hbrush, oldBrush = 0;
1001 if (descr->style & LBS_NOREDRAW) return 0;
1003 SetRect( &rect, 0, 0, descr->width, descr->height );
1004 if (descr->style & LBS_MULTICOLUMN)
1005 rect.right = rect.left + descr->column_width;
1006 else if (descr->horz_pos)
1008 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1009 rect.right += descr->horz_pos;
1012 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1013 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1014 hdc, (LPARAM)hwnd );
1015 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1016 if (!IsWindowEnabled(hwnd)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1018 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1019 (descr->in_focus))
1021 /* Special case for empty listbox: paint focus rect */
1022 rect.bottom = rect.top + descr->item_height;
1023 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, descr->focus_item,
1024 ODA_FOCUS, FALSE );
1025 rect.top = rect.bottom;
1028 /* Paint all the item, regarding the selection
1029 Focus state will be painted after */
1031 for (i = descr->top_item; i < descr->nb_items; i++)
1033 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1034 rect.bottom = rect.top + descr->item_height;
1035 else
1036 rect.bottom = rect.top + descr->items[i].height;
1038 if (i == descr->focus_item)
1040 /* keep the focus rect, to paint the focus item after */
1041 focusRect.left = rect.left;
1042 focusRect.right = rect.right;
1043 focusRect.top = rect.top;
1044 focusRect.bottom = rect.bottom;
1046 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1047 rect.top = rect.bottom;
1049 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1051 if (!IS_OWNERDRAW(descr))
1053 /* Clear the bottom of the column */
1054 if (rect.top < descr->height)
1056 rect.bottom = descr->height;
1057 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1058 &rect, NULL, 0, NULL );
1062 /* Go to the next column */
1063 rect.left += descr->column_width;
1064 rect.right += descr->column_width;
1065 rect.top = 0;
1066 col_pos = descr->page_size - 1;
1068 else
1070 col_pos--;
1071 if (rect.top >= descr->height) break;
1075 /* Paint the focus item now */
1076 if (focusRect.top != focusRect.bottom && descr->caret_on)
1077 LISTBOX_PaintItem( hwnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1079 if (!IS_OWNERDRAW(descr))
1081 /* Clear the remainder of the client area */
1082 if (rect.top < descr->height)
1084 rect.bottom = descr->height;
1085 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1086 &rect, NULL, 0, NULL );
1088 if (rect.right < descr->width)
1090 rect.left = rect.right;
1091 rect.right = descr->width;
1092 rect.top = 0;
1093 rect.bottom = descr->height;
1094 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1095 &rect, NULL, 0, NULL );
1098 if (oldFont) SelectObject( hdc, oldFont );
1099 if (oldBrush) SelectObject( hdc, oldBrush );
1100 return 0;
1104 /***********************************************************************
1105 * LISTBOX_InvalidateItems
1107 * Invalidate all items from a given item. If the specified item is not
1108 * visible, nothing happens.
1110 static void LISTBOX_InvalidateItems( HWND hwnd, LB_DESCR *descr, INT index )
1112 RECT rect;
1114 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1116 if (descr->style & LBS_NOREDRAW)
1118 descr->style |= LBS_DISPLAYCHANGED;
1119 return;
1121 rect.bottom = descr->height;
1122 InvalidateRect( hwnd, &rect, TRUE );
1123 if (descr->style & LBS_MULTICOLUMN)
1125 /* Repaint the other columns */
1126 rect.left = rect.right;
1127 rect.right = descr->width;
1128 rect.top = 0;
1129 InvalidateRect( hwnd, &rect, TRUE );
1135 /***********************************************************************
1136 * LISTBOX_GetItemHeight
1138 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1140 if (descr->style & LBS_OWNERDRAWVARIABLE)
1142 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1143 return descr->items[index].height;
1145 else return descr->item_height;
1149 /***********************************************************************
1150 * LISTBOX_SetItemHeight
1152 static LRESULT LISTBOX_SetItemHeight( HWND hwnd, LB_DESCR *descr, INT index,
1153 INT height, BOOL repaint )
1155 if (!height) height = 1;
1157 if (descr->style & LBS_OWNERDRAWVARIABLE)
1159 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1160 TRACE("[%04x]: item %d height = %d\n", hwnd, index, height );
1161 descr->items[index].height = height;
1162 LISTBOX_UpdateScroll( hwnd, descr );
1163 if (repaint)
1164 LISTBOX_InvalidateItems( hwnd, descr, index );
1166 else if (height != descr->item_height)
1168 TRACE("[%04x]: new height = %d\n", hwnd, height );
1169 descr->item_height = height;
1170 LISTBOX_UpdatePage( hwnd, descr );
1171 LISTBOX_UpdateScroll( hwnd, descr );
1172 if (repaint)
1173 InvalidateRect( hwnd, 0, TRUE );
1175 return LB_OKAY;
1179 /***********************************************************************
1180 * LISTBOX_SetHorizontalPos
1182 static void LISTBOX_SetHorizontalPos( HWND hwnd, LB_DESCR *descr, INT pos )
1184 INT diff;
1186 if (pos > descr->horz_extent - descr->width)
1187 pos = descr->horz_extent - descr->width;
1188 if (pos < 0) pos = 0;
1189 if (!(diff = descr->horz_pos - pos)) return;
1190 TRACE("[%04x]: new horz pos = %d\n", hwnd, pos );
1191 descr->horz_pos = pos;
1192 LISTBOX_UpdateScroll( hwnd, descr );
1193 if (abs(diff) < descr->width)
1194 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
1195 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1196 else
1197 InvalidateRect( hwnd, NULL, TRUE );
1201 /***********************************************************************
1202 * LISTBOX_SetHorizontalExtent
1204 static LRESULT LISTBOX_SetHorizontalExtent( HWND hwnd, LB_DESCR *descr,
1205 INT extent )
1207 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1208 return LB_OKAY;
1209 if (extent <= 0) extent = 1;
1210 if (extent == descr->horz_extent) return LB_OKAY;
1211 TRACE("[%04x]: new horz extent = %d\n", hwnd, extent );
1212 descr->horz_extent = extent;
1213 if (descr->horz_pos > extent - descr->width)
1214 LISTBOX_SetHorizontalPos( hwnd, descr, extent - descr->width );
1215 else
1216 LISTBOX_UpdateScroll( hwnd, descr );
1217 return LB_OKAY;
1221 /***********************************************************************
1222 * LISTBOX_SetColumnWidth
1224 static LRESULT LISTBOX_SetColumnWidth( HWND hwnd, LB_DESCR *descr, INT width)
1226 if (width == descr->column_width) return LB_OKAY;
1227 TRACE("[%04x]: new column width = %d\n", hwnd, width );
1228 descr->column_width = width;
1229 LISTBOX_UpdatePage( hwnd, descr );
1230 return LB_OKAY;
1234 /***********************************************************************
1235 * LISTBOX_SetFont
1237 * Returns the item height.
1239 static INT LISTBOX_SetFont( HWND hwnd, LB_DESCR *descr, HFONT font )
1241 HDC hdc;
1242 HFONT oldFont = 0;
1243 TEXTMETRICW tm;
1245 descr->font = font;
1247 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE )))
1249 ERR("unable to get DC.\n" );
1250 return 16;
1252 if (font) oldFont = SelectObject( hdc, font );
1253 GetTextMetricsW( hdc, &tm );
1254 if (oldFont) SelectObject( hdc, oldFont );
1255 ReleaseDC( hwnd, hdc );
1256 if (!IS_OWNERDRAW(descr))
1257 LISTBOX_SetItemHeight( hwnd, descr, 0, tm.tmHeight, FALSE );
1258 return tm.tmHeight ;
1262 /***********************************************************************
1263 * LISTBOX_MakeItemVisible
1265 * Make sure that a given item is partially or fully visible.
1267 static void LISTBOX_MakeItemVisible( HWND hwnd, LB_DESCR *descr, INT index,
1268 BOOL fully )
1270 INT top;
1272 if (index <= descr->top_item) top = index;
1273 else if (descr->style & LBS_MULTICOLUMN)
1275 INT cols = descr->width;
1276 if (!fully) cols += descr->column_width - 1;
1277 if (cols >= descr->column_width) cols /= descr->column_width;
1278 else cols = 1;
1279 if (index < descr->top_item + (descr->page_size * cols)) return;
1280 top = index - descr->page_size * (cols - 1);
1282 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1284 INT height = fully ? descr->items[index].height : 1;
1285 for (top = index; top > descr->top_item; top--)
1286 if ((height += descr->items[top-1].height) > descr->height) break;
1288 else
1290 if (index < descr->top_item + descr->page_size) return;
1291 if (!fully && (index == descr->top_item + descr->page_size) &&
1292 (descr->height > (descr->page_size * descr->item_height))) return;
1293 top = index - descr->page_size + 1;
1295 LISTBOX_SetTopItem( hwnd, descr, top, TRUE );
1298 /***********************************************************************
1299 * LISTBOX_SetCaretIndex
1301 * NOTES
1302 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1305 static LRESULT LISTBOX_SetCaretIndex( HWND hwnd, LB_DESCR *descr, INT index,
1306 BOOL fully_visible )
1308 INT oldfocus = descr->focus_item;
1310 if (descr->style & LBS_NOSEL) return LB_ERR;
1311 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1312 if (index == oldfocus) return LB_OKAY;
1313 descr->focus_item = index;
1314 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1315 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1317 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1318 if (descr->caret_on && (descr->in_focus))
1319 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1321 return LB_OKAY;
1325 /***********************************************************************
1326 * LISTBOX_SelectItemRange
1328 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1330 static LRESULT LISTBOX_SelectItemRange( HWND hwnd, LB_DESCR *descr, INT first,
1331 INT last, BOOL on )
1333 INT i;
1335 /* A few sanity checks */
1337 if (descr->style & LBS_NOSEL) return LB_ERR;
1338 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1339 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1340 if (last == -1) last = descr->nb_items - 1;
1341 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1342 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1343 /* selected_item reflects last selected/unselected item on multiple sel */
1344 descr->selected_item = last;
1346 if (on) /* Turn selection on */
1348 for (i = first; i <= last; i++)
1350 if (descr->items[i].selected) continue;
1351 descr->items[i].selected = TRUE;
1352 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1354 LISTBOX_SetCaretIndex( hwnd, descr, last, TRUE );
1356 else /* Turn selection off */
1358 for (i = first; i <= last; i++)
1360 if (!descr->items[i].selected) continue;
1361 descr->items[i].selected = FALSE;
1362 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1365 return LB_OKAY;
1368 /***********************************************************************
1369 * LISTBOX_SetSelection
1371 static LRESULT LISTBOX_SetSelection( HWND hwnd, LB_DESCR *descr, INT index,
1372 BOOL on, BOOL send_notify )
1374 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1376 if (descr->style & LBS_NOSEL) return LB_ERR;
1377 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1378 if (descr->style & LBS_MULTIPLESEL)
1380 if (index == -1) /* Select all items */
1381 return LISTBOX_SelectItemRange( hwnd, descr, 0, -1, on );
1382 else /* Only one item */
1383 return LISTBOX_SelectItemRange( hwnd, descr, index, index, on );
1385 else
1387 INT oldsel = descr->selected_item;
1388 if (index == oldsel) return LB_OKAY;
1389 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1390 if (index != -1) descr->items[index].selected = TRUE;
1391 descr->selected_item = index;
1392 if (oldsel != -1) LISTBOX_RepaintItem( hwnd, descr, oldsel, ODA_SELECT );
1393 if (index != -1) LISTBOX_RepaintItem( hwnd, descr, index, ODA_SELECT );
1394 if (send_notify && descr->nb_items) SEND_NOTIFICATION( hwnd, descr,
1395 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1396 else
1397 if( descr->lphc ) /* set selection change flag for parent combo */
1398 descr->lphc->wState |= CBF_SELCHANGE;
1400 return LB_OKAY;
1404 /***********************************************************************
1405 * LISTBOX_MoveCaret
1407 * Change the caret position and extend the selection to the new caret.
1409 static void LISTBOX_MoveCaret( HWND hwnd, LB_DESCR *descr, INT index,
1410 BOOL fully_visible )
1412 INT oldfocus = descr->focus_item;
1414 if ((index < 0) || (index >= descr->nb_items))
1415 return;
1417 /* Important, repaint needs to be done in this order if
1418 you want to mimic Windows behavior:
1419 1. Remove the focus and paint the item
1420 2. Remove the selection and paint the item(s)
1421 3. Set the selection and repaint the item(s)
1422 4. Set the focus to 'index' and repaint the item */
1424 /* 1. remove the focus and repaint the item */
1425 descr->focus_item = -1;
1426 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1427 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1429 /* 2. then turn off the previous selection */
1430 /* 3. repaint the new selected item */
1431 if (descr->style & LBS_EXTENDEDSEL)
1433 if (descr->anchor_item != -1)
1435 INT first = min( index, descr->anchor_item );
1436 INT last = max( index, descr->anchor_item );
1437 if (first > 0)
1438 LISTBOX_SelectItemRange( hwnd, descr, 0, first - 1, FALSE );
1439 LISTBOX_SelectItemRange( hwnd, descr, last + 1, -1, FALSE );
1440 LISTBOX_SelectItemRange( hwnd, descr, first, last, TRUE );
1443 else if (!(descr->style & LBS_MULTIPLESEL))
1445 /* Set selection to new caret item */
1446 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
1449 /* 4. repaint the new item with the focus */
1450 descr->focus_item = index;
1451 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1452 if (descr->caret_on && (descr->in_focus))
1453 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1457 /***********************************************************************
1458 * LISTBOX_InsertItem
1460 static LRESULT LISTBOX_InsertItem( HWND hwnd, LB_DESCR *descr, INT index,
1461 LPWSTR str, DWORD data )
1463 LB_ITEMDATA *item;
1464 INT max_items;
1465 INT oldfocus = descr->focus_item;
1467 if (index == -1) index = descr->nb_items;
1468 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1469 if (!descr->items) max_items = 0;
1470 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1471 if (descr->nb_items == max_items)
1473 /* We need to grow the array */
1474 max_items += LB_ARRAY_GRANULARITY;
1475 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1476 max_items * sizeof(LB_ITEMDATA) )))
1478 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1479 return LB_ERRSPACE;
1481 descr->items = item;
1484 /* Insert the item structure */
1486 item = &descr->items[index];
1487 if (index < descr->nb_items)
1488 RtlMoveMemory( item + 1, item,
1489 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1490 item->str = str;
1491 item->data = data;
1492 item->height = 0;
1493 item->selected = FALSE;
1494 descr->nb_items++;
1496 /* Get item height */
1498 if (descr->style & LBS_OWNERDRAWVARIABLE)
1500 MEASUREITEMSTRUCT mis;
1501 UINT id = GetWindowLongA( hwnd, GWL_ID );
1503 mis.CtlType = ODT_LISTBOX;
1504 mis.CtlID = id;
1505 mis.itemID = index;
1506 mis.itemData = descr->items[index].data;
1507 mis.itemHeight = descr->item_height;
1508 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1509 item->height = mis.itemHeight ? mis.itemHeight : 1;
1510 TRACE("[%04x]: measure item %d (%s) = %d\n",
1511 hwnd, index, str ? debugstr_w(str) : "", item->height );
1514 /* Repaint the items */
1516 LISTBOX_UpdateScroll( hwnd, descr );
1517 LISTBOX_InvalidateItems( hwnd, descr, index );
1519 /* Move selection and focused item */
1520 /* If listbox was empty, set focus to the first item */
1521 if (descr->nb_items == 1)
1522 LISTBOX_SetCaretIndex( hwnd, descr, 0, FALSE );
1523 /* single select don't change selection index in win31 */
1524 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1526 descr->selected_item++;
1527 LISTBOX_SetSelection( hwnd, descr, descr->selected_item-1, TRUE, FALSE );
1529 else
1531 if (index <= descr->selected_item)
1533 descr->selected_item++;
1534 descr->focus_item = oldfocus; /* focus not changed */
1537 return LB_OKAY;
1541 /***********************************************************************
1542 * LISTBOX_InsertString
1544 static LRESULT LISTBOX_InsertString( HWND hwnd, LB_DESCR *descr, INT index,
1545 LPCWSTR str )
1547 LPWSTR new_str = NULL;
1548 DWORD data = 0;
1549 LRESULT ret;
1551 if (HAS_STRINGS(descr))
1553 static const WCHAR empty_stringW[] = { 0 };
1554 if (!str) str = empty_stringW;
1555 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1557 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1558 return LB_ERRSPACE;
1560 strcpyW(new_str, str);
1562 else data = (DWORD)str;
1564 if (index == -1) index = descr->nb_items;
1565 if ((ret = LISTBOX_InsertItem( hwnd, descr, index, new_str, data )) != 0)
1567 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1568 return ret;
1571 TRACE("[%04x]: added item %d %s\n",
1572 hwnd, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1573 return index;
1577 /***********************************************************************
1578 * LISTBOX_DeleteItem
1580 * Delete the content of an item. 'index' must be a valid index.
1582 static void LISTBOX_DeleteItem( HWND hwnd, LB_DESCR *descr, INT index )
1584 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1585 * while Win95 sends it for all items with user data.
1586 * It's probably better to send it too often than not
1587 * often enough, so this is what we do here.
1589 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1591 DELETEITEMSTRUCT dis;
1592 UINT id = GetWindowLongA( hwnd, GWL_ID );
1594 dis.CtlType = ODT_LISTBOX;
1595 dis.CtlID = id;
1596 dis.itemID = index;
1597 dis.hwndItem = hwnd;
1598 dis.itemData = descr->items[index].data;
1599 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1601 if (HAS_STRINGS(descr) && descr->items[index].str)
1602 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1606 /***********************************************************************
1607 * LISTBOX_RemoveItem
1609 * Remove an item from the listbox and delete its content.
1611 static LRESULT LISTBOX_RemoveItem( HWND hwnd, LB_DESCR *descr, INT index )
1613 LB_ITEMDATA *item;
1614 INT max_items;
1616 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1617 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1619 /* We need to invalidate the original rect instead of the updated one. */
1620 LISTBOX_InvalidateItems( hwnd, descr, index );
1622 LISTBOX_DeleteItem( hwnd, descr, index );
1624 /* Remove the item */
1626 item = &descr->items[index];
1627 if (index < descr->nb_items-1)
1628 RtlMoveMemory( item, item + 1,
1629 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1630 descr->nb_items--;
1631 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1633 /* Shrink the item array if possible */
1635 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1636 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1638 max_items -= LB_ARRAY_GRANULARITY;
1639 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1640 max_items * sizeof(LB_ITEMDATA) );
1641 if (item) descr->items = item;
1643 /* Repaint the items */
1645 LISTBOX_UpdateScroll( hwnd, descr );
1646 /* if we removed the scrollbar, reset the top of the list
1647 (correct for owner-drawn ???) */
1648 if (descr->nb_items == descr->page_size)
1649 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1651 /* Move selection and focused item */
1652 if (!IS_MULTISELECT(descr))
1654 if (index == descr->selected_item)
1655 descr->selected_item = -1;
1656 else if (index < descr->selected_item)
1658 descr->selected_item--;
1659 if (ISWIN31) /* win 31 do not change the selected item number */
1660 LISTBOX_SetSelection( hwnd, descr, descr->selected_item + 1, TRUE, FALSE);
1664 if (descr->focus_item >= descr->nb_items)
1666 descr->focus_item = descr->nb_items - 1;
1667 if (descr->focus_item < 0) descr->focus_item = 0;
1669 return LB_OKAY;
1673 /***********************************************************************
1674 * LISTBOX_ResetContent
1676 static void LISTBOX_ResetContent( HWND hwnd, LB_DESCR *descr )
1678 INT i;
1680 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( hwnd, descr, i );
1681 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1682 descr->nb_items = 0;
1683 descr->top_item = 0;
1684 descr->selected_item = -1;
1685 descr->focus_item = 0;
1686 descr->anchor_item = -1;
1687 descr->items = NULL;
1691 /***********************************************************************
1692 * LISTBOX_SetCount
1694 static LRESULT LISTBOX_SetCount( HWND hwnd, LB_DESCR *descr, INT count )
1696 LRESULT ret;
1698 if (HAS_STRINGS(descr)) return LB_ERR;
1699 /* FIXME: this is far from optimal... */
1700 if (count > descr->nb_items)
1702 while (count > descr->nb_items)
1703 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, 0 )) < 0)
1704 return ret;
1706 else if (count < descr->nb_items)
1708 while (count < descr->nb_items)
1709 if ((ret = LISTBOX_RemoveItem( hwnd, descr, -1 )) < 0)
1710 return ret;
1712 return LB_OKAY;
1716 /***********************************************************************
1717 * LISTBOX_Directory
1719 static LRESULT LISTBOX_Directory( HWND hwnd, LB_DESCR *descr, UINT attrib,
1720 LPCWSTR filespec, BOOL long_names )
1722 HANDLE handle;
1723 LRESULT ret = LB_OKAY;
1724 WIN32_FIND_DATAW entry;
1725 int pos;
1727 /* don't scan directory if we just want drives exclusively */
1728 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1729 /* scan directory */
1730 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1732 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1734 else
1738 WCHAR buffer[270];
1739 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1741 static const WCHAR bracketW[] = { ']',0 };
1742 static const WCHAR dotW[] = { '.',0 };
1743 if (!(attrib & DDL_DIRECTORY) ||
1744 !strcmpW( entry.cAlternateFileName, dotW )) continue;
1745 buffer[0] = '[';
1746 if (long_names) strcpyW( buffer + 1, entry.cFileName );
1747 else strcpyW( buffer + 1, entry.cAlternateFileName );
1748 strcatW(buffer, bracketW);
1750 else /* not a directory */
1752 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1753 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1755 if ((attrib & DDL_EXCLUSIVE) &&
1756 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1757 continue;
1758 #undef ATTRIBS
1759 if (long_names) strcpyW( buffer, entry.cFileName );
1760 else strcpyW( buffer, entry.cAlternateFileName );
1762 if (!long_names) CharLowerW( buffer );
1763 pos = LISTBOX_FindFileStrPos( hwnd, descr, buffer );
1764 if ((ret = LISTBOX_InsertString( hwnd, descr, pos, buffer )) < 0)
1765 break;
1766 } while (FindNextFileW( handle, &entry ));
1767 FindClose( handle );
1771 /* scan drives */
1772 if ((ret >= 0) && (attrib & DDL_DRIVES))
1774 WCHAR buffer[] = {'[','-','a','-',']',0};
1775 WCHAR root[] = {'A',':','\\',0};
1776 int drive;
1777 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1779 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1780 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, buffer )) < 0)
1781 break;
1784 return ret;
1788 /***********************************************************************
1789 * LISTBOX_HandleVScroll
1791 static LRESULT LISTBOX_HandleVScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1793 SCROLLINFO info;
1795 if (descr->style & LBS_MULTICOLUMN) return 0;
1796 switch(LOWORD(wParam))
1798 case SB_LINEUP:
1799 LISTBOX_SetTopItem( hwnd, descr, descr->top_item - 1, TRUE );
1800 break;
1801 case SB_LINEDOWN:
1802 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + 1, TRUE );
1803 break;
1804 case SB_PAGEUP:
1805 LISTBOX_SetTopItem( hwnd, descr, descr->top_item -
1806 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1807 break;
1808 case SB_PAGEDOWN:
1809 LISTBOX_SetTopItem( hwnd, descr, descr->top_item +
1810 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1811 break;
1812 case SB_THUMBPOSITION:
1813 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam), TRUE );
1814 break;
1815 case SB_THUMBTRACK:
1816 info.cbSize = sizeof(info);
1817 info.fMask = SIF_TRACKPOS;
1818 GetScrollInfo( hwnd, SB_VERT, &info );
1819 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos, TRUE );
1820 break;
1821 case SB_TOP:
1822 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1823 break;
1824 case SB_BOTTOM:
1825 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1826 break;
1828 return 0;
1832 /***********************************************************************
1833 * LISTBOX_HandleHScroll
1835 static LRESULT LISTBOX_HandleHScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1837 SCROLLINFO info;
1838 INT page;
1840 if (descr->style & LBS_MULTICOLUMN)
1842 switch(LOWORD(wParam))
1844 case SB_LINELEFT:
1845 LISTBOX_SetTopItem( hwnd, descr, descr->top_item-descr->page_size,
1846 TRUE );
1847 break;
1848 case SB_LINERIGHT:
1849 LISTBOX_SetTopItem( hwnd, descr, descr->top_item+descr->page_size,
1850 TRUE );
1851 break;
1852 case SB_PAGELEFT:
1853 page = descr->width / descr->column_width;
1854 if (page < 1) page = 1;
1855 LISTBOX_SetTopItem( hwnd, descr,
1856 descr->top_item - page * descr->page_size, TRUE );
1857 break;
1858 case SB_PAGERIGHT:
1859 page = descr->width / descr->column_width;
1860 if (page < 1) page = 1;
1861 LISTBOX_SetTopItem( hwnd, descr,
1862 descr->top_item + page * descr->page_size, TRUE );
1863 break;
1864 case SB_THUMBPOSITION:
1865 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam)*descr->page_size,
1866 TRUE );
1867 break;
1868 case SB_THUMBTRACK:
1869 info.cbSize = sizeof(info);
1870 info.fMask = SIF_TRACKPOS;
1871 GetScrollInfo( hwnd, SB_VERT, &info );
1872 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos*descr->page_size,
1873 TRUE );
1874 break;
1875 case SB_LEFT:
1876 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1877 break;
1878 case SB_RIGHT:
1879 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1880 break;
1883 else if (descr->horz_extent)
1885 switch(LOWORD(wParam))
1887 case SB_LINELEFT:
1888 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos - 1 );
1889 break;
1890 case SB_LINERIGHT:
1891 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos + 1 );
1892 break;
1893 case SB_PAGELEFT:
1894 LISTBOX_SetHorizontalPos( hwnd, descr,
1895 descr->horz_pos - descr->width );
1896 break;
1897 case SB_PAGERIGHT:
1898 LISTBOX_SetHorizontalPos( hwnd, descr,
1899 descr->horz_pos + descr->width );
1900 break;
1901 case SB_THUMBPOSITION:
1902 LISTBOX_SetHorizontalPos( hwnd, descr, HIWORD(wParam) );
1903 break;
1904 case SB_THUMBTRACK:
1905 info.cbSize = sizeof(info);
1906 info.fMask = SIF_TRACKPOS;
1907 GetScrollInfo( hwnd, SB_HORZ, &info );
1908 LISTBOX_SetHorizontalPos( hwnd, descr, info.nTrackPos );
1909 break;
1910 case SB_LEFT:
1911 LISTBOX_SetHorizontalPos( hwnd, descr, 0 );
1912 break;
1913 case SB_RIGHT:
1914 LISTBOX_SetHorizontalPos( hwnd, descr,
1915 descr->horz_extent - descr->width );
1916 break;
1919 return 0;
1922 static LRESULT LISTBOX_HandleMouseWheel(HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1924 short gcWheelDelta = 0;
1925 UINT pulScrollLines = 3;
1927 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1929 gcWheelDelta -= (short) HIWORD(wParam);
1931 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1933 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1934 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1935 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + cLineScroll, TRUE );
1937 return 0;
1940 /***********************************************************************
1941 * LISTBOX_HandleLButtonDown
1943 static LRESULT LISTBOX_HandleLButtonDown( HWND hwnd, LB_DESCR *descr,
1944 WPARAM wParam, INT x, INT y )
1946 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1947 TRACE("[%04x]: lbuttondown %d,%d item %d\n", hwnd, x, y, index );
1948 if (!descr->caret_on && (descr->in_focus)) return 0;
1950 if (!descr->in_focus)
1952 if( !descr->lphc ) SetFocus( hwnd );
1953 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1956 if (index == -1) return 0;
1958 if (descr->style & LBS_EXTENDEDSEL)
1960 /* we should perhaps make sure that all items are deselected
1961 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1962 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1963 LISTBOX_SetSelection( hwnd, descr, -1, FALSE, FALSE);
1966 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1967 if (wParam & MK_CONTROL)
1969 LISTBOX_SetCaretIndex( hwnd, descr, index, FALSE );
1970 LISTBOX_SetSelection( hwnd, descr, index,
1971 !descr->items[index].selected,
1972 (descr->style & LBS_NOTIFY) != 0);
1974 else LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1976 else
1978 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1979 LISTBOX_SetSelection( hwnd, descr, index,
1980 (!(descr->style & LBS_MULTIPLESEL) ||
1981 !descr->items[index].selected),
1982 (descr->style & LBS_NOTIFY) != 0 );
1985 descr->captured = TRUE;
1986 SetCapture( hwnd );
1988 if (!descr->lphc)
1990 if (descr->style & LBS_NOTIFY )
1991 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
1992 MAKELPARAM( x, y ) );
1993 if (GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
1995 POINT pt;
1997 pt.x = x;
1998 pt.y = y;
2000 if (DragDetect( hwnd, pt ))
2001 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2004 return 0;
2008 /*************************************************************************
2009 * LISTBOX_HandleLButtonDownCombo [Internal]
2011 * Process LButtonDown message for the ComboListBox
2013 nn * PARAMS
2014 * pWnd [I] The windows internal structure
2015 * pDescr [I] The ListBox internal structure
2016 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2017 * x [I] X Mouse Coordinate
2018 * y [I] Y Mouse Coordinate
2020 * RETURNS
2021 * 0 since we are processing the WM_LBUTTONDOWN Message
2023 * NOTES
2024 * This function is only to be used when a ListBox is a ComboListBox
2027 static LRESULT LISTBOX_HandleLButtonDownCombo( HWND hwnd, LB_DESCR *pDescr,
2028 UINT msg, WPARAM wParam, INT x, INT y)
2030 RECT clientRect, screenRect;
2031 POINT mousePos;
2033 mousePos.x = x;
2034 mousePos.y = y;
2036 GetClientRect(hwnd, &clientRect);
2038 if(PtInRect(&clientRect, mousePos))
2040 /* MousePos is in client, resume normal processing */
2041 if (msg == WM_LBUTTONDOWN)
2043 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2044 return LISTBOX_HandleLButtonDown( hwnd, pDescr, wParam, x, y);
2046 else if (pDescr->style & LBS_NOTIFY)
2047 SEND_NOTIFICATION( hwnd, pDescr, LBN_DBLCLK );
2048 return 0;
2050 else
2052 POINT screenMousePos;
2053 HWND hWndOldCapture;
2055 /* Check the Non-Client Area */
2056 screenMousePos = mousePos;
2057 hWndOldCapture = GetCapture();
2058 ReleaseCapture();
2059 GetWindowRect(hwnd, &screenRect);
2060 ClientToScreen(hwnd, &screenMousePos);
2062 if(!PtInRect(&screenRect, screenMousePos))
2064 LISTBOX_SetCaretIndex( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE );
2065 LISTBOX_SetSelection( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2066 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2067 return 0;
2069 else
2071 /* Check to see the NC is a scrollbar */
2072 INT nHitTestType=0;
2073 LONG style = GetWindowLongA( hwnd, GWL_STYLE );
2074 /* Check Vertical scroll bar */
2075 if (style & WS_VSCROLL)
2077 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2078 if (PtInRect( &clientRect, mousePos ))
2080 nHitTestType = HTVSCROLL;
2083 /* Check horizontal scroll bar */
2084 if (style & WS_HSCROLL)
2086 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2087 if (PtInRect( &clientRect, mousePos ))
2089 nHitTestType = HTHSCROLL;
2092 /* Windows sends this message when a scrollbar is clicked
2095 if(nHitTestType != 0)
2097 SendMessageW(hwnd, WM_NCLBUTTONDOWN, nHitTestType,
2098 MAKELONG(screenMousePos.x, screenMousePos.y));
2100 /* Resume the Capture after scrolling is complete
2102 if(hWndOldCapture != 0)
2104 SetCapture(hWndOldCapture);
2108 return 0;
2111 /***********************************************************************
2112 * LISTBOX_HandleLButtonUp
2114 static LRESULT LISTBOX_HandleLButtonUp( HWND hwnd, LB_DESCR *descr )
2116 if (LISTBOX_Timer != LB_TIMER_NONE)
2117 KillSystemTimer( hwnd, LB_TIMER_ID );
2118 LISTBOX_Timer = LB_TIMER_NONE;
2119 if (descr->captured)
2121 descr->captured = FALSE;
2122 if (GetCapture() == hwnd) ReleaseCapture();
2123 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2124 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2126 return 0;
2130 /***********************************************************************
2131 * LISTBOX_HandleTimer
2133 * Handle scrolling upon a timer event.
2134 * Return TRUE if scrolling should continue.
2136 static LRESULT LISTBOX_HandleTimer( HWND hwnd, LB_DESCR *descr,
2137 INT index, TIMER_DIRECTION dir )
2139 switch(dir)
2141 case LB_TIMER_UP:
2142 if (descr->top_item) index = descr->top_item - 1;
2143 else index = 0;
2144 break;
2145 case LB_TIMER_LEFT:
2146 if (descr->top_item) index -= descr->page_size;
2147 break;
2148 case LB_TIMER_DOWN:
2149 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2150 if (index == descr->focus_item) index++;
2151 if (index >= descr->nb_items) index = descr->nb_items - 1;
2152 break;
2153 case LB_TIMER_RIGHT:
2154 if (index + descr->page_size < descr->nb_items)
2155 index += descr->page_size;
2156 break;
2157 case LB_TIMER_NONE:
2158 break;
2160 if (index == descr->focus_item) return FALSE;
2161 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2162 return TRUE;
2166 /***********************************************************************
2167 * LISTBOX_HandleSystemTimer
2169 * WM_SYSTIMER handler.
2171 static LRESULT LISTBOX_HandleSystemTimer( HWND hwnd, LB_DESCR *descr )
2173 if (!LISTBOX_HandleTimer( hwnd, descr, descr->focus_item, LISTBOX_Timer ))
2175 KillSystemTimer( hwnd, LB_TIMER_ID );
2176 LISTBOX_Timer = LB_TIMER_NONE;
2178 return 0;
2182 /***********************************************************************
2183 * LISTBOX_HandleMouseMove
2185 * WM_MOUSEMOVE handler.
2187 static void LISTBOX_HandleMouseMove( HWND hwnd, LB_DESCR *descr,
2188 INT x, INT y )
2190 INT index;
2191 TIMER_DIRECTION dir = LB_TIMER_NONE;
2193 if (!descr->captured) return;
2195 if (descr->style & LBS_MULTICOLUMN)
2197 if (y < 0) y = 0;
2198 else if (y >= descr->item_height * descr->page_size)
2199 y = descr->item_height * descr->page_size - 1;
2201 if (x < 0)
2203 dir = LB_TIMER_LEFT;
2204 x = 0;
2206 else if (x >= descr->width)
2208 dir = LB_TIMER_RIGHT;
2209 x = descr->width - 1;
2212 else
2214 if (y < 0) dir = LB_TIMER_UP; /* above */
2215 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2218 index = LISTBOX_GetItemFromPoint( descr, x, y );
2219 if (index == -1) index = descr->focus_item;
2220 if (!LISTBOX_HandleTimer( hwnd, descr, index, dir )) dir = LB_TIMER_NONE;
2222 /* Start/stop the system timer */
2224 if (dir != LB_TIMER_NONE)
2225 SetSystemTimer( hwnd, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2226 else if (LISTBOX_Timer != LB_TIMER_NONE)
2227 KillSystemTimer( hwnd, LB_TIMER_ID );
2228 LISTBOX_Timer = dir;
2232 /***********************************************************************
2233 * LISTBOX_HandleKeyDown
2235 static LRESULT LISTBOX_HandleKeyDown( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
2237 INT caret = -1;
2238 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2239 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2240 bForceSelection = FALSE; /* only for single select list */
2242 if (descr->style & LBS_WANTKEYBOARDINPUT)
2244 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2245 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2246 hwnd );
2247 if (caret == -2) return 0;
2249 if (caret == -1) switch(wParam)
2251 case VK_LEFT:
2252 if (descr->style & LBS_MULTICOLUMN)
2254 bForceSelection = FALSE;
2255 if (descr->focus_item >= descr->page_size)
2256 caret = descr->focus_item - descr->page_size;
2257 break;
2259 /* fall through */
2260 case VK_UP:
2261 caret = descr->focus_item - 1;
2262 if (caret < 0) caret = 0;
2263 break;
2264 case VK_RIGHT:
2265 if (descr->style & LBS_MULTICOLUMN)
2267 bForceSelection = FALSE;
2268 if (descr->focus_item + descr->page_size < descr->nb_items)
2269 caret = descr->focus_item + descr->page_size;
2270 break;
2272 /* fall through */
2273 case VK_DOWN:
2274 caret = descr->focus_item + 1;
2275 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2276 break;
2278 case VK_PRIOR:
2279 if (descr->style & LBS_MULTICOLUMN)
2281 INT page = descr->width / descr->column_width;
2282 if (page < 1) page = 1;
2283 caret = descr->focus_item - (page * descr->page_size) + 1;
2285 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2286 if (caret < 0) caret = 0;
2287 break;
2288 case VK_NEXT:
2289 if (descr->style & LBS_MULTICOLUMN)
2291 INT page = descr->width / descr->column_width;
2292 if (page < 1) page = 1;
2293 caret = descr->focus_item + (page * descr->page_size) - 1;
2295 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2296 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2297 break;
2298 case VK_HOME:
2299 caret = 0;
2300 break;
2301 case VK_END:
2302 caret = descr->nb_items - 1;
2303 break;
2304 case VK_SPACE:
2305 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2306 else if (descr->style & LBS_MULTIPLESEL)
2308 LISTBOX_SetSelection( hwnd, descr, descr->focus_item,
2309 !descr->items[descr->focus_item].selected,
2310 (descr->style & LBS_NOTIFY) != 0 );
2312 break;
2313 default:
2314 bForceSelection = FALSE;
2316 if (bForceSelection) /* focused item is used instead of key */
2317 caret = descr->focus_item;
2318 if (caret >= 0)
2320 if ((descr->style & LBS_EXTENDEDSEL) &&
2321 !(GetKeyState( VK_SHIFT ) & 0x8000))
2322 descr->anchor_item = caret;
2323 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2324 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2325 if (descr->style & LBS_NOTIFY)
2327 if( descr->lphc )
2329 /* make sure that combo parent doesn't hide us */
2330 descr->lphc->wState |= CBF_NOROLLUP;
2332 if (descr->nb_items) SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2335 return 0;
2339 /***********************************************************************
2340 * LISTBOX_HandleChar
2342 static LRESULT LISTBOX_HandleChar( HWND hwnd, LB_DESCR *descr, WCHAR charW )
2344 INT caret = -1;
2345 WCHAR str[2];
2347 str[0] = charW;
2348 str[1] = '\0';
2350 if (descr->style & LBS_WANTKEYBOARDINPUT)
2352 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2353 MAKEWPARAM(charW, descr->focus_item),
2354 hwnd );
2355 if (caret == -2) return 0;
2357 if (caret == -1)
2358 caret = LISTBOX_FindString( hwnd, descr, descr->focus_item, str, FALSE);
2359 if (caret != -1)
2361 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2362 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2363 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2364 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2365 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2367 return 0;
2371 /***********************************************************************
2372 * LISTBOX_Create
2374 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2376 LB_DESCR *descr;
2377 MEASUREITEMSTRUCT mis;
2378 RECT rect;
2380 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2381 return FALSE;
2383 GetClientRect( hwnd, &rect );
2384 descr->owner = GetParent( hwnd );
2385 descr->style = GetWindowLongA( hwnd, GWL_STYLE );
2386 descr->width = rect.right - rect.left;
2387 descr->height = rect.bottom - rect.top;
2388 descr->items = NULL;
2389 descr->nb_items = 0;
2390 descr->top_item = 0;
2391 descr->selected_item = -1;
2392 descr->focus_item = 0;
2393 descr->anchor_item = -1;
2394 descr->item_height = 1;
2395 descr->page_size = 1;
2396 descr->column_width = 150;
2397 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2398 descr->horz_pos = 0;
2399 descr->nb_tabs = 0;
2400 descr->tabs = NULL;
2401 descr->caret_on = lphc ? FALSE : TRUE;
2402 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2403 descr->in_focus = FALSE;
2404 descr->captured = FALSE;
2405 descr->font = 0;
2406 descr->locale = 0; /* FIXME */
2407 descr->lphc = lphc;
2409 if (is_old_app(hwnd) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2411 /* Win95 document "List Box Differences" from MSDN:
2412 If a list box in a version 3.x application has either the
2413 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2414 horizontal and vertical scroll bars.
2416 descr->style |= WS_VSCROLL | WS_HSCROLL;
2419 if( lphc )
2421 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2422 hwnd, descr->owner, lphc->self );
2423 descr->owner = lphc->self;
2426 SetWindowLongA( hwnd, 0, (LONG)descr );
2428 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2430 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2431 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2432 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2433 descr->item_height = LISTBOX_SetFont( hwnd, descr, 0 );
2435 if (descr->style & LBS_OWNERDRAWFIXED)
2437 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2439 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2440 descr->item_height = lphc->fixedOwnerDrawHeight;
2442 else
2444 UINT id = GetWindowLongA( hwnd, GWL_ID );
2445 mis.CtlType = ODT_LISTBOX;
2446 mis.CtlID = id;
2447 mis.itemID = -1;
2448 mis.itemWidth = 0;
2449 mis.itemData = 0;
2450 mis.itemHeight = descr->item_height;
2451 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2452 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2456 TRACE("owner: %04x, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2457 return TRUE;
2461 /***********************************************************************
2462 * LISTBOX_Destroy
2464 static BOOL LISTBOX_Destroy( HWND hwnd, LB_DESCR *descr )
2466 LISTBOX_ResetContent( hwnd, descr );
2467 SetWindowLongA( hwnd, 0, 0 );
2468 HeapFree( GetProcessHeap(), 0, descr );
2469 return TRUE;
2473 /***********************************************************************
2474 * ListBoxWndProc_common
2476 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2477 WPARAM wParam, LPARAM lParam, BOOL unicode )
2479 LRESULT ret;
2480 LB_DESCR *descr;
2482 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
2484 if (msg == WM_CREATE)
2486 if (!LISTBOX_Create( hwnd, NULL ))
2487 return -1;
2488 TRACE("creating wnd=%04x descr=%lx\n", hwnd, GetWindowLongA( hwnd, 0 ) );
2489 return 0;
2491 /* Ignore all other messages before we get a WM_CREATE */
2492 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2493 DefWindowProcA( hwnd, msg, wParam, lParam );
2496 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2497 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
2498 switch(msg)
2500 case LB_RESETCONTENT16:
2501 case LB_RESETCONTENT:
2502 LISTBOX_ResetContent( hwnd, descr );
2503 LISTBOX_UpdateScroll( hwnd, descr );
2504 InvalidateRect( hwnd, NULL, TRUE );
2505 return 0;
2507 case LB_ADDSTRING16:
2508 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2509 /* fall through */
2510 case LB_ADDSTRING:
2512 INT ret;
2513 LPWSTR textW;
2514 if(unicode || !HAS_STRINGS(descr))
2515 textW = (LPWSTR)lParam;
2516 else
2518 LPSTR textA = (LPSTR)lParam;
2519 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2520 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2521 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2523 wParam = LISTBOX_FindStringPos( hwnd, descr, textW, FALSE );
2524 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2525 if (!unicode && HAS_STRINGS(descr))
2526 HeapFree(GetProcessHeap(), 0, textW);
2527 return ret;
2530 case LB_INSERTSTRING16:
2531 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2532 wParam = (INT)(INT16)wParam;
2533 /* fall through */
2534 case LB_INSERTSTRING:
2536 INT ret;
2537 LPWSTR textW;
2538 if(unicode || !HAS_STRINGS(descr))
2539 textW = (LPWSTR)lParam;
2540 else
2542 LPSTR textA = (LPSTR)lParam;
2543 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2544 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2545 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2547 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2548 if(!unicode && HAS_STRINGS(descr))
2549 HeapFree(GetProcessHeap(), 0, textW);
2550 return ret;
2553 case LB_ADDFILE16:
2554 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2555 /* fall through */
2556 case LB_ADDFILE:
2558 INT ret;
2559 LPWSTR textW;
2560 if(unicode || !HAS_STRINGS(descr))
2561 textW = (LPWSTR)lParam;
2562 else
2564 LPSTR textA = (LPSTR)lParam;
2565 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2566 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2567 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2569 wParam = LISTBOX_FindFileStrPos( hwnd, descr, textW );
2570 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2571 if(!unicode && HAS_STRINGS(descr))
2572 HeapFree(GetProcessHeap(), 0, textW);
2573 return ret;
2576 case LB_DELETESTRING16:
2577 case LB_DELETESTRING:
2578 if (LISTBOX_RemoveItem( hwnd, descr, wParam) != LB_ERR)
2579 return descr->nb_items;
2580 else
2581 return LB_ERR;
2583 case LB_GETITEMDATA16:
2584 case LB_GETITEMDATA:
2585 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2586 return LB_ERR;
2587 return descr->items[wParam].data;
2589 case LB_SETITEMDATA16:
2590 case LB_SETITEMDATA:
2591 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2592 return LB_ERR;
2593 descr->items[wParam].data = (DWORD)lParam;
2594 return LB_OKAY;
2596 case LB_GETCOUNT16:
2597 case LB_GETCOUNT:
2598 return descr->nb_items;
2600 case LB_GETTEXT16:
2601 lParam = (LPARAM)MapSL(lParam);
2602 /* fall through */
2603 case LB_GETTEXT:
2604 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2606 case LB_GETTEXTLEN16:
2607 /* fall through */
2608 case LB_GETTEXTLEN:
2609 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2610 return LB_ERR;
2611 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2612 if (unicode) return strlenW( descr->items[wParam].str );
2613 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2614 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2616 case LB_GETCURSEL16:
2617 case LB_GETCURSEL:
2618 if (descr->nb_items==0)
2619 return LB_ERR;
2620 if (!IS_MULTISELECT(descr))
2621 return descr->selected_item;
2622 /* else */
2623 if (descr->selected_item!=-1)
2624 return descr->selected_item;
2625 /* else */
2626 return descr->focus_item;
2627 /* otherwise, if the user tries to move the selection with the */
2628 /* arrow keys, we will give the application something to choke on */
2629 case LB_GETTOPINDEX16:
2630 case LB_GETTOPINDEX:
2631 return descr->top_item;
2633 case LB_GETITEMHEIGHT16:
2634 case LB_GETITEMHEIGHT:
2635 return LISTBOX_GetItemHeight( descr, wParam );
2637 case LB_SETITEMHEIGHT16:
2638 lParam = LOWORD(lParam);
2639 /* fall through */
2640 case LB_SETITEMHEIGHT:
2641 return LISTBOX_SetItemHeight( hwnd, descr, wParam, lParam, TRUE );
2643 case LB_ITEMFROMPOINT:
2645 POINT pt;
2646 RECT rect;
2648 pt.x = LOWORD(lParam);
2649 pt.y = HIWORD(lParam);
2650 rect.left = 0;
2651 rect.top = 0;
2652 rect.right = descr->width;
2653 rect.bottom = descr->height;
2655 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2656 !PtInRect( &rect, pt ) );
2659 case LB_SETCARETINDEX16:
2660 case LB_SETCARETINDEX:
2661 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2662 if (LISTBOX_SetCaretIndex( hwnd, descr, wParam, !lParam ) == LB_ERR)
2663 return LB_ERR;
2664 else if (ISWIN31)
2665 return wParam;
2666 else
2667 return LB_OKAY;
2669 case LB_GETCARETINDEX16:
2670 case LB_GETCARETINDEX:
2671 return descr->focus_item;
2673 case LB_SETTOPINDEX16:
2674 case LB_SETTOPINDEX:
2675 return LISTBOX_SetTopItem( hwnd, descr, wParam, TRUE );
2677 case LB_SETCOLUMNWIDTH16:
2678 case LB_SETCOLUMNWIDTH:
2679 return LISTBOX_SetColumnWidth( hwnd, descr, wParam );
2681 case LB_GETITEMRECT16:
2683 RECT rect;
2684 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2685 CONV_RECT32TO16( &rect, MapSL(lParam) );
2687 return ret;
2689 case LB_GETITEMRECT:
2690 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2692 case LB_FINDSTRING16:
2693 wParam = (INT)(INT16)wParam;
2694 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2695 /* fall through */
2696 case LB_FINDSTRING:
2698 INT ret;
2699 LPWSTR textW;
2700 if(unicode || !HAS_STRINGS(descr))
2701 textW = (LPWSTR)lParam;
2702 else
2704 LPSTR textA = (LPSTR)lParam;
2705 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2706 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2707 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2709 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2710 if(!unicode && HAS_STRINGS(descr))
2711 HeapFree(GetProcessHeap(), 0, textW);
2712 return ret;
2715 case LB_FINDSTRINGEXACT16:
2716 wParam = (INT)(INT16)wParam;
2717 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2718 /* fall through */
2719 case LB_FINDSTRINGEXACT:
2721 INT ret;
2722 LPWSTR textW;
2723 if(unicode || !HAS_STRINGS(descr))
2724 textW = (LPWSTR)lParam;
2725 else
2727 LPSTR textA = (LPSTR)lParam;
2728 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2729 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2730 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2732 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, TRUE );
2733 if(!unicode && HAS_STRINGS(descr))
2734 HeapFree(GetProcessHeap(), 0, textW);
2735 return ret;
2738 case LB_SELECTSTRING16:
2739 wParam = (INT)(INT16)wParam;
2740 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2741 /* fall through */
2742 case LB_SELECTSTRING:
2744 INT index;
2745 LPWSTR textW;
2747 if(HAS_STRINGS(descr))
2748 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2749 debugstr_a((LPSTR)lParam));
2750 if(unicode || !HAS_STRINGS(descr))
2751 textW = (LPWSTR)lParam;
2752 else
2754 LPSTR textA = (LPSTR)lParam;
2755 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2756 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2757 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2759 index = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2760 if(!unicode && HAS_STRINGS(descr))
2761 HeapFree(GetProcessHeap(), 0, textW);
2762 if (index != LB_ERR)
2764 LISTBOX_SetCaretIndex( hwnd, descr, index, TRUE );
2765 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
2767 return index;
2770 case LB_GETSEL16:
2771 wParam = (INT)(INT16)wParam;
2772 /* fall through */
2773 case LB_GETSEL:
2774 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2775 return LB_ERR;
2776 return descr->items[wParam].selected;
2778 case LB_SETSEL16:
2779 lParam = (INT)(INT16)lParam;
2780 /* fall through */
2781 case LB_SETSEL:
2782 return LISTBOX_SetSelection( hwnd, descr, lParam, wParam, FALSE );
2784 case LB_SETCURSEL16:
2785 wParam = (INT)(INT16)wParam;
2786 /* fall through */
2787 case LB_SETCURSEL:
2788 if (IS_MULTISELECT(descr)) return LB_ERR;
2789 LISTBOX_SetCaretIndex( hwnd, descr, wParam, TRUE );
2790 return LISTBOX_SetSelection( hwnd, descr, wParam, TRUE, FALSE );
2792 case LB_GETSELCOUNT16:
2793 case LB_GETSELCOUNT:
2794 return LISTBOX_GetSelCount( descr );
2796 case LB_GETSELITEMS16:
2797 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2799 case LB_GETSELITEMS:
2800 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2802 case LB_SELITEMRANGE16:
2803 case LB_SELITEMRANGE:
2804 if (LOWORD(lParam) <= HIWORD(lParam))
2805 return LISTBOX_SelectItemRange( hwnd, descr, LOWORD(lParam),
2806 HIWORD(lParam), wParam );
2807 else
2808 return LISTBOX_SelectItemRange( hwnd, descr, HIWORD(lParam),
2809 LOWORD(lParam), wParam );
2811 case LB_SELITEMRANGEEX16:
2812 case LB_SELITEMRANGEEX:
2813 if ((INT)lParam >= (INT)wParam)
2814 return LISTBOX_SelectItemRange( hwnd, descr, wParam, lParam, TRUE );
2815 else
2816 return LISTBOX_SelectItemRange( hwnd, descr, lParam, wParam, FALSE);
2818 case LB_GETHORIZONTALEXTENT16:
2819 case LB_GETHORIZONTALEXTENT:
2820 return descr->horz_extent;
2822 case LB_SETHORIZONTALEXTENT16:
2823 case LB_SETHORIZONTALEXTENT:
2824 return LISTBOX_SetHorizontalExtent( hwnd, descr, wParam );
2826 case LB_GETANCHORINDEX16:
2827 case LB_GETANCHORINDEX:
2828 return descr->anchor_item;
2830 case LB_SETANCHORINDEX16:
2831 wParam = (INT)(INT16)wParam;
2832 /* fall through */
2833 case LB_SETANCHORINDEX:
2834 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2835 return LB_ERR;
2836 descr->anchor_item = (INT)wParam;
2837 return LB_OKAY;
2839 case LB_DIR16:
2840 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2841 * be set automatically (this is different in Win32) */
2842 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2843 lParam = (LPARAM)MapSL(lParam);
2844 /* fall through */
2845 case LB_DIR:
2847 INT ret;
2848 LPWSTR textW;
2849 if(unicode)
2850 textW = (LPWSTR)lParam;
2851 else
2853 LPSTR textA = (LPSTR)lParam;
2854 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2855 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2856 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2858 ret = LISTBOX_Directory( hwnd, descr, wParam, textW, msg == LB_DIR );
2859 if(!unicode)
2860 HeapFree(GetProcessHeap(), 0, textW);
2861 return ret;
2864 case LB_GETLOCALE:
2865 return descr->locale;
2867 case LB_SETLOCALE:
2868 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2869 return LB_OKAY;
2871 case LB_INITSTORAGE:
2872 return LISTBOX_InitStorage( hwnd, descr, wParam );
2874 case LB_SETCOUNT:
2875 return LISTBOX_SetCount( hwnd, descr, (INT)wParam );
2877 case LB_SETTABSTOPS16:
2878 return LISTBOX_SetTabStops( hwnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2880 case LB_SETTABSTOPS:
2881 return LISTBOX_SetTabStops( hwnd, descr, wParam, (LPINT)lParam, FALSE );
2883 case LB_CARETON16:
2884 case LB_CARETON:
2885 if (descr->caret_on)
2886 return LB_OKAY;
2887 descr->caret_on = TRUE;
2888 if ((descr->focus_item != -1) && (descr->in_focus))
2889 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2890 return LB_OKAY;
2892 case LB_CARETOFF16:
2893 case LB_CARETOFF:
2894 if (!descr->caret_on)
2895 return LB_OKAY;
2896 descr->caret_on = FALSE;
2897 if ((descr->focus_item != -1) && (descr->in_focus))
2898 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2899 return LB_OKAY;
2901 case WM_DESTROY:
2902 return LISTBOX_Destroy( hwnd, descr );
2904 case WM_ENABLE:
2905 InvalidateRect( hwnd, NULL, TRUE );
2906 return 0;
2908 case WM_SETREDRAW:
2909 LISTBOX_SetRedraw( hwnd, descr, wParam != 0 );
2910 return 0;
2912 case WM_GETDLGCODE:
2913 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2915 case WM_PAINT:
2917 PAINTSTRUCT ps;
2918 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( hwnd, &ps );
2919 ret = LISTBOX_Paint( hwnd, descr, hdc );
2920 if( !wParam ) EndPaint( hwnd, &ps );
2922 return ret;
2923 case WM_SIZE:
2924 LISTBOX_UpdateSize( hwnd, descr );
2925 return 0;
2926 case WM_GETFONT:
2927 return descr->font;
2928 case WM_SETFONT:
2929 LISTBOX_SetFont( hwnd, descr, (HFONT)wParam );
2930 if (lParam) InvalidateRect( hwnd, 0, TRUE );
2931 return 0;
2932 case WM_SETFOCUS:
2933 descr->in_focus = TRUE;
2934 descr->caret_on = TRUE;
2935 if (descr->focus_item != -1)
2936 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2937 SEND_NOTIFICATION( hwnd, descr, LBN_SETFOCUS );
2938 return 0;
2939 case WM_KILLFOCUS:
2940 descr->in_focus = FALSE;
2941 if ((descr->focus_item != -1) && descr->caret_on)
2942 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2943 SEND_NOTIFICATION( hwnd, descr, LBN_KILLFOCUS );
2944 return 0;
2945 case WM_HSCROLL:
2946 return LISTBOX_HandleHScroll( hwnd, descr, wParam );
2947 case WM_VSCROLL:
2948 return LISTBOX_HandleVScroll( hwnd, descr, wParam );
2949 case WM_MOUSEWHEEL:
2950 if (wParam & (MK_SHIFT | MK_CONTROL))
2951 return DefWindowProcW( hwnd, msg, wParam, lParam );
2952 return LISTBOX_HandleMouseWheel( hwnd, descr, wParam );
2953 case WM_LBUTTONDOWN:
2954 return LISTBOX_HandleLButtonDown( hwnd, descr, wParam,
2955 (INT16)LOWORD(lParam),
2956 (INT16)HIWORD(lParam) );
2957 case WM_LBUTTONDBLCLK:
2958 if (descr->style & LBS_NOTIFY)
2959 SEND_NOTIFICATION( hwnd, descr, LBN_DBLCLK );
2960 return 0;
2961 case WM_MOUSEMOVE:
2962 if (GetCapture() == hwnd)
2963 LISTBOX_HandleMouseMove( hwnd, descr, (INT16)LOWORD(lParam),
2964 (INT16)HIWORD(lParam) );
2965 return 0;
2966 case WM_LBUTTONUP:
2967 return LISTBOX_HandleLButtonUp( hwnd, descr );
2968 case WM_KEYDOWN:
2969 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
2970 case WM_CHAR:
2972 WCHAR charW;
2973 if(unicode)
2974 charW = (WCHAR)wParam;
2975 else
2977 CHAR charA = (CHAR)wParam;
2978 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
2980 return LISTBOX_HandleChar( hwnd, descr, charW );
2982 case WM_SYSTIMER:
2983 return LISTBOX_HandleSystemTimer( hwnd, descr );
2984 case WM_ERASEBKGND:
2985 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2987 RECT rect;
2988 HBRUSH hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
2989 wParam, (LPARAM)hwnd );
2990 TRACE("hbrush = %04x\n", hbrush);
2991 if(!hbrush)
2992 hbrush = GetSysColorBrush(COLOR_WINDOW);
2993 if(hbrush)
2995 GetClientRect(hwnd, &rect);
2996 FillRect((HDC)wParam, &rect, hbrush);
2999 return 1;
3000 case WM_DROPFILES:
3001 if( !descr->lphc )
3002 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3003 SendMessageA( descr->owner, msg, wParam, lParam );
3004 break;
3006 case WM_DROPOBJECT:
3007 case WM_QUERYDROPOBJECT:
3008 case WM_DRAGSELECT:
3009 case WM_DRAGMOVE:
3010 if( !descr->lphc )
3012 LPDRAGINFO16 dragInfo = MapSL( lParam );
3013 dragInfo->l = LISTBOX_GetItemFromPoint( descr, dragInfo->pt.x,
3014 dragInfo->pt.y );
3015 return SendMessage16( descr->owner, msg, wParam, lParam );
3017 break;
3019 default:
3020 if ((msg >= WM_USER) && (msg < 0xc000))
3021 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
3022 hwnd, msg, wParam, lParam );
3023 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3024 DefWindowProcA( hwnd, msg, wParam, lParam );
3026 return 0;
3029 /***********************************************************************
3030 * ListBoxWndProcA
3032 * This is just a wrapper for the real wndproc, it only does window locking
3033 * and unlocking.
3035 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3037 if (!IsWindow(hwnd)) return 0;
3038 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3041 /***********************************************************************
3042 * ListBoxWndProcW
3044 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3046 if (!IsWindow(hwnd)) return 0;
3047 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );
3050 /***********************************************************************
3051 * ComboLBWndProc_common
3053 * The real combo listbox wndproc
3055 static LRESULT WINAPI ComboLBWndProc_common( HWND hwnd, UINT msg,
3056 WPARAM wParam, LPARAM lParam, BOOL unicode )
3058 LRESULT lRet = 0;
3059 LB_DESCR *descr;
3060 LPHEADCOMBO lphc;
3062 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
3064 if (msg == WM_CREATE)
3066 CREATESTRUCTA *lpcs = (CREATESTRUCTA *)lParam;
3067 TRACE_(combo)("\tpassed parent handle = %p\n",lpcs->lpCreateParams);
3068 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3069 return LISTBOX_Create( hwnd, lphc );
3071 /* Ignore all other messages before we get a WM_CREATE */
3072 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3073 DefWindowProcA( hwnd, msg, wParam, lParam );
3076 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
3077 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
3079 if ((lphc = descr->lphc) != NULL)
3081 switch( msg )
3083 case WM_MOUSEMOVE:
3084 if ( (TWEAK_WineLook > WIN31_LOOK) &&
3085 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
3087 POINT mousePos;
3088 BOOL captured;
3089 RECT clientRect;
3091 mousePos.x = (INT16)LOWORD(lParam);
3092 mousePos.y = (INT16)HIWORD(lParam);
3095 * If we are in a dropdown combobox, we simulate that
3096 * the mouse is captured to show the tracking of the item.
3098 GetClientRect(hwnd, &clientRect);
3100 if (PtInRect( &clientRect, mousePos ))
3102 captured = descr->captured;
3103 descr->captured = TRUE;
3105 LISTBOX_HandleMouseMove( hwnd, descr,
3106 mousePos.x, mousePos.y);
3108 descr->captured = captured;
3111 else
3113 LISTBOX_HandleMouseMove( hwnd, descr,
3114 mousePos.x, mousePos.y);
3117 return 0;
3120 /* else we are in Win3.1 look, go with the default behavior. */
3121 break;
3123 case WM_LBUTTONUP:
3124 if (TWEAK_WineLook > WIN31_LOOK)
3126 POINT mousePos;
3127 RECT clientRect;
3130 * If the mouse button "up" is not in the listbox,
3131 * we make sure there is no selection by re-selecting the
3132 * item that was selected when the listbox was made visible.
3134 mousePos.x = (INT16)LOWORD(lParam);
3135 mousePos.y = (INT16)HIWORD(lParam);
3137 GetClientRect(hwnd, &clientRect);
3140 * When the user clicks outside the combobox and the focus
3141 * is lost, the owning combobox will send a fake buttonup with
3142 * 0xFFFFFFF as the mouse location, we must also revert the
3143 * selection to the original selection.
3145 if ( (lParam == (LPARAM)-1) ||
3146 (!PtInRect( &clientRect, mousePos )) )
3148 LISTBOX_MoveCaret( hwnd, descr, lphc->droppedIndex, FALSE );
3151 return LISTBOX_HandleLButtonUp( hwnd, descr );
3152 case WM_LBUTTONDBLCLK:
3153 case WM_LBUTTONDOWN:
3154 return LISTBOX_HandleLButtonDownCombo(hwnd, descr, msg, wParam,
3155 (INT16)LOWORD(lParam),
3156 (INT16)HIWORD(lParam) );
3157 case WM_NCACTIVATE:
3158 return FALSE;
3159 case WM_KEYDOWN:
3160 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3162 /* for some reason(?) Windows makes it possible to
3163 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3165 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3166 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3167 && (wParam == VK_DOWN || wParam == VK_UP)) )
3169 COMBO_FlipListbox( lphc, FALSE, FALSE );
3170 return 0;
3173 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3175 case LB_SETCURSEL16:
3176 case LB_SETCURSEL:
3177 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3178 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3179 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3180 return lRet;
3181 case WM_NCDESTROY:
3182 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3183 lphc->hWndLBox = 0;
3184 break;
3188 /* default handling: call listbox wnd proc */
3189 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3190 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3192 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3194 return lRet;
3197 /***********************************************************************
3198 * ComboLBWndProcA
3200 * NOTE: in Windows, winproc address of the ComboLBox is the same
3201 * as that of the Listbox.
3203 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3205 if (!IsWindow(hwnd)) return 0;
3206 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3209 /***********************************************************************
3210 * ComboLBWndProcW
3212 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3214 if (!IsWindow(hwnd)) return 0;
3215 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, TRUE );