Removed the A/W constants for builtin cursors, icons and resource
[wine/multimedia.git] / controls / listbox.c
blobb6ddb38a08f82133a09ae0dd2c69535c7bbeedff
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 <stdarg.h>
24 #include <stdio.h>
25 #include "windef.h"
26 #include "winbase.h"
27 #include "wingdi.h"
28 #include "wine/winuser16.h"
29 #include "wine/winbase16.h"
30 #include "wownt32.h"
31 #include "wine/unicode.h"
32 #include "winuser.h"
33 #include "winerror.h"
34 #include "message.h"
35 #include "user.h"
36 #include "controls.h"
37 #include "wine/debug.h"
38 #include "win.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
41 WINE_DECLARE_DEBUG_CHANNEL(combo);
43 /* Unimplemented yet:
44 * - LBS_USETABSTOPS
45 * - Locale handling
47 * Probably needs improvement:
48 * - LBS_NOSEL
51 /* Items array granularity */
52 #define LB_ARRAY_GRANULARITY 16
54 /* Scrolling timeout in ms */
55 #define LB_SCROLL_TIMEOUT 50
57 /* Listbox system timer id */
58 #define LB_TIMER_ID 2
60 /* flag listbox changed while setredraw false - internal style */
61 #define LBS_DISPLAYCHANGED 0x80000000
63 /* Item structure */
64 typedef struct
66 LPWSTR str; /* Item text */
67 BOOL selected; /* Is item selected? */
68 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
69 DWORD data; /* User data */
70 } LB_ITEMDATA;
72 /* Listbox structure */
73 typedef struct
75 HWND owner; /* Owner window to send notifications to */
76 UINT style; /* Window style */
77 INT width; /* Window width */
78 INT height; /* Window height */
79 LB_ITEMDATA *items; /* Array of items */
80 INT nb_items; /* Number of items */
81 INT top_item; /* Top visible item */
82 INT selected_item; /* Selected item */
83 INT focus_item; /* Item that has the focus */
84 INT anchor_item; /* Anchor item for extended selection */
85 INT item_height; /* Default item height */
86 INT page_size; /* Items per listbox page */
87 INT column_width; /* Column width for multi-column listboxes */
88 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
89 INT horz_pos; /* Horizontal position */
90 INT nb_tabs; /* Number of tabs in array */
91 INT *tabs; /* Array of tabs */
92 BOOL caret_on; /* Is caret on? */
93 BOOL captured; /* Is mouse captured? */
94 BOOL in_focus;
95 HFONT font; /* Current font */
96 LCID locale; /* Current locale for string comparisons */
97 LPHEADCOMBO lphc; /* ComboLBox */
98 } LB_DESCR;
101 #define IS_OWNERDRAW(descr) \
102 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
104 #define HAS_STRINGS(descr) \
105 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108 #define IS_MULTISELECT(descr) \
109 ((descr)->style & LBS_MULTIPLESEL || ((descr)->style & LBS_EXTENDEDSEL))
111 #define SEND_NOTIFICATION(hwnd,descr,code) \
112 (SendMessageW( (descr)->owner, WM_COMMAND, \
113 MAKEWPARAM( GetWindowLongA((hwnd),GWL_ID), (code)), (LPARAM)(hwnd) ))
115 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
117 /* Current timer status */
118 typedef enum
120 LB_TIMER_NONE,
121 LB_TIMER_UP,
122 LB_TIMER_LEFT,
123 LB_TIMER_DOWN,
124 LB_TIMER_RIGHT
125 } TIMER_DIRECTION;
127 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
129 static LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
130 static LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
131 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
132 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
134 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
136 /*********************************************************************
137 * listbox class descriptor
139 const struct builtin_class_descr LISTBOX_builtin_class =
141 "ListBox", /* name */
142 CS_GLOBALCLASS | CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
143 ListBoxWndProcA, /* procA */
144 ListBoxWndProcW, /* procW */
145 sizeof(LB_DESCR *), /* extra */
146 IDC_ARROW, /* cursor */
147 0 /* brush */
151 /*********************************************************************
152 * combolbox class descriptor
154 const struct builtin_class_descr COMBOLBOX_builtin_class =
156 "ComboLBox", /* name */
157 CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS, /* style */
158 ComboLBWndProcA, /* procA */
159 ComboLBWndProcW, /* procW */
160 sizeof(LB_DESCR *), /* extra */
161 IDC_ARROW, /* cursor */
162 0 /* brush */
166 /* check whether app is a Win 3.1 app */
167 inline static BOOL is_old_app( HWND hwnd )
169 return (GetExpWinVer16( GetWindowLongA(hwnd,GWL_HINSTANCE) ) & 0xFF00 ) == 0x0300;
173 /***********************************************************************
174 * LISTBOX_Dump
176 void LISTBOX_Dump( HWND hwnd )
178 INT i;
179 LB_ITEMDATA *item;
180 LB_DESCR *descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 );
182 TRACE( "Listbox:\n" );
183 TRACE( "hwnd=%p descr=%08x items=%d top=%d\n",
184 hwnd, (UINT)descr, descr->nb_items, descr->top_item );
185 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
187 TRACE( "%4d: %-40s %d %08lx %3d\n",
188 i, debugstr_w(item->str), item->selected, item->data, item->height );
193 /***********************************************************************
194 * LISTBOX_GetCurrentPageSize
196 * Return the current page size
198 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
200 INT i, height;
201 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
202 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
204 if ((height += descr->items[i].height) > descr->height) break;
206 if (i == descr->top_item) return 1;
207 else return i - descr->top_item;
211 /***********************************************************************
212 * LISTBOX_GetMaxTopIndex
214 * Return the maximum possible index for the top of the listbox.
216 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
218 INT max, page;
220 if (descr->style & LBS_OWNERDRAWVARIABLE)
222 page = descr->height;
223 for (max = descr->nb_items - 1; max >= 0; max--)
224 if ((page -= descr->items[max].height) < 0) break;
225 if (max < descr->nb_items - 1) max++;
227 else if (descr->style & LBS_MULTICOLUMN)
229 if ((page = descr->width / descr->column_width) < 1) page = 1;
230 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
231 max = (max - page) * descr->page_size;
233 else
235 max = descr->nb_items - descr->page_size;
237 if (max < 0) max = 0;
238 return max;
242 /***********************************************************************
243 * LISTBOX_UpdateScroll
245 * Update the scrollbars. Should be called whenever the content
246 * of the listbox changes.
248 static void LISTBOX_UpdateScroll( HWND hwnd, LB_DESCR *descr )
250 SCROLLINFO info;
252 /* Check the listbox scroll bar flags individually before we call
253 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
254 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
255 scroll bar when we do not need one.
256 if (!(descr->style & WS_VSCROLL)) return;
259 /* It is important that we check descr->style, and not wnd->dwStyle,
260 for WS_VSCROLL, as the former is exactly the one passed in
261 argument to CreateWindow.
262 In Windows (and from now on in Wine :) a listbox created
263 with such a style (no WS_SCROLL) does not update
264 the scrollbar with listbox-related data, thus letting
265 the programmer use it for his/her own purposes. */
267 if (descr->style & LBS_NOREDRAW) return;
268 info.cbSize = sizeof(info);
270 if (descr->style & LBS_MULTICOLUMN)
272 info.nMin = 0;
273 info.nMax = (descr->nb_items - 1) / descr->page_size;
274 info.nPos = descr->top_item / descr->page_size;
275 info.nPage = descr->width / descr->column_width;
276 if (info.nPage < 1) info.nPage = 1;
277 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
278 if (descr->style & LBS_DISABLENOSCROLL)
279 info.fMask |= SIF_DISABLENOSCROLL;
280 if (descr->style & WS_HSCROLL)
281 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
282 info.nMax = 0;
283 info.fMask = SIF_RANGE;
284 if (descr->style & WS_VSCROLL)
285 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
287 else
289 info.nMin = 0;
290 info.nMax = descr->nb_items - 1;
291 info.nPos = descr->top_item;
292 info.nPage = LISTBOX_GetCurrentPageSize( descr );
293 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
294 if (descr->style & LBS_DISABLENOSCROLL)
295 info.fMask |= SIF_DISABLENOSCROLL;
296 if (descr->style & WS_VSCROLL)
297 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
299 if (descr->horz_extent)
301 info.nMin = 0;
302 info.nMax = descr->horz_extent - 1;
303 info.nPos = descr->horz_pos;
304 info.nPage = descr->width;
305 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
306 if (descr->style & LBS_DISABLENOSCROLL)
307 info.fMask |= SIF_DISABLENOSCROLL;
308 if (descr->style & WS_HSCROLL)
309 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
315 /***********************************************************************
316 * LISTBOX_SetTopItem
318 * Set the top item of the listbox, scrolling up or down if necessary.
320 static LRESULT LISTBOX_SetTopItem( HWND hwnd, LB_DESCR *descr, INT index,
321 BOOL scroll )
323 INT max = LISTBOX_GetMaxTopIndex( descr );
324 if (index > max) index = max;
325 if (index < 0) index = 0;
326 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
327 if (descr->top_item == index) return LB_OKAY;
328 if (descr->style & LBS_MULTICOLUMN)
330 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
331 if (scroll && (abs(diff) < descr->width))
332 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
333 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
335 else
336 scroll = FALSE;
338 else if (scroll)
340 INT diff;
341 if (descr->style & LBS_OWNERDRAWVARIABLE)
343 INT i;
344 diff = 0;
345 if (index > descr->top_item)
347 for (i = index - 1; i >= descr->top_item; i--)
348 diff -= descr->items[i].height;
350 else
352 for (i = index; i < descr->top_item; i++)
353 diff += descr->items[i].height;
356 else
357 diff = (descr->top_item - index) * descr->item_height;
359 if (abs(diff) < descr->height)
360 ScrollWindowEx( hwnd, 0, diff, NULL, NULL, 0, NULL,
361 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
362 else
363 scroll = FALSE;
365 if (!scroll) InvalidateRect( hwnd, NULL, TRUE );
366 descr->top_item = index;
367 LISTBOX_UpdateScroll( hwnd, descr );
368 return LB_OKAY;
372 /***********************************************************************
373 * LISTBOX_UpdatePage
375 * Update the page size. Should be called when the size of
376 * the client area or the item height changes.
378 static void LISTBOX_UpdatePage( HWND hwnd, LB_DESCR *descr )
380 INT page_size;
382 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
383 page_size = 1;
384 if (page_size == descr->page_size) return;
385 descr->page_size = page_size;
386 if (descr->style & LBS_MULTICOLUMN)
387 InvalidateRect( hwnd, NULL, TRUE );
388 LISTBOX_SetTopItem( hwnd, descr, descr->top_item, FALSE );
392 /***********************************************************************
393 * LISTBOX_UpdateSize
395 * Update the size of the listbox. Should be called when the size of
396 * the client area changes.
398 static void LISTBOX_UpdateSize( HWND hwnd, LB_DESCR *descr )
400 RECT rect;
402 GetClientRect( hwnd, &rect );
403 descr->width = rect.right - rect.left;
404 descr->height = rect.bottom - rect.top;
405 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
407 INT remaining;
408 RECT rect;
410 GetWindowRect( hwnd, &rect );
411 if(descr->item_height != 0)
412 remaining = descr->height % descr->item_height;
413 else
414 remaining = 0;
415 if ((descr->height > descr->item_height) && remaining)
417 if (is_old_app(hwnd))
418 { /* give a margin for error to 16 bits programs - if we need
419 less than the height of the nonclient area, round to the
420 *next* number of items */
421 int ncheight = rect.bottom - rect.top - descr->height;
422 if ((descr->item_height - remaining) <= ncheight)
423 remaining = remaining - descr->item_height;
425 TRACE("[%p]: changing height %d -> %d\n",
426 hwnd, descr->height, descr->height - remaining );
427 SetWindowPos( hwnd, 0, 0, 0, rect.right - rect.left,
428 rect.bottom - rect.top - remaining,
429 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
430 return;
433 TRACE("[%p]: new size = %d,%d\n", hwnd, descr->width, descr->height );
434 LISTBOX_UpdatePage( hwnd, descr );
435 LISTBOX_UpdateScroll( hwnd, descr );
437 /* Invalidate the focused item so it will be repainted correctly */
438 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
440 InvalidateRect( hwnd, &rect, FALSE );
445 /***********************************************************************
446 * LISTBOX_GetItemRect
448 * Get the rectangle enclosing an item, in listbox client coordinates.
449 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
451 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
453 /* Index <= 0 is legal even on empty listboxes */
454 if (index && (index >= descr->nb_items)) return -1;
455 SetRect( rect, 0, 0, descr->width, descr->height );
456 if (descr->style & LBS_MULTICOLUMN)
458 INT col = (index / descr->page_size) -
459 (descr->top_item / descr->page_size);
460 rect->left += col * descr->column_width;
461 rect->right = rect->left + descr->column_width;
462 rect->top += (index % descr->page_size) * descr->item_height;
463 rect->bottom = rect->top + descr->item_height;
465 else if (descr->style & LBS_OWNERDRAWVARIABLE)
467 INT i;
468 rect->right += descr->horz_pos;
469 if ((index >= 0) && (index < descr->nb_items))
471 if (index < descr->top_item)
473 for (i = descr->top_item-1; i >= index; i--)
474 rect->top -= descr->items[i].height;
476 else
478 for (i = descr->top_item; i < index; i++)
479 rect->top += descr->items[i].height;
481 rect->bottom = rect->top + descr->items[index].height;
485 else
487 rect->top += (index - descr->top_item) * descr->item_height;
488 rect->bottom = rect->top + descr->item_height;
489 rect->right += descr->horz_pos;
492 return ((rect->left < descr->width) && (rect->right > 0) &&
493 (rect->top < descr->height) && (rect->bottom > 0));
497 /***********************************************************************
498 * LISTBOX_GetItemFromPoint
500 * Return the item nearest from point (x,y) (in client coordinates).
502 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
504 INT index = descr->top_item;
506 if (!descr->nb_items) return -1; /* No items */
507 if (descr->style & LBS_OWNERDRAWVARIABLE)
509 INT pos = 0;
510 if (y >= 0)
512 while (index < descr->nb_items)
514 if ((pos += descr->items[index].height) > y) break;
515 index++;
518 else
520 while (index > 0)
522 index--;
523 if ((pos -= descr->items[index].height) <= y) break;
527 else if (descr->style & LBS_MULTICOLUMN)
529 if (y >= descr->item_height * descr->page_size) return -1;
530 if (y >= 0) index += y / descr->item_height;
531 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
532 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
534 else
536 index += (y / descr->item_height);
538 if (index < 0) return 0;
539 if (index >= descr->nb_items) return -1;
540 return index;
544 /***********************************************************************
545 * LISTBOX_PaintItem
547 * Paint an item.
549 static void LISTBOX_PaintItem( HWND hwnd, LB_DESCR *descr, HDC hdc,
550 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
552 LB_ITEMDATA *item = NULL;
553 if (index < descr->nb_items) item = &descr->items[index];
555 if (IS_OWNERDRAW(descr))
557 DRAWITEMSTRUCT dis;
558 RECT r;
559 HRGN hrgn;
560 UINT id = GetWindowLongA( hwnd, GWL_ID );
562 if (!item)
564 if (action == ODA_FOCUS)
565 DrawFocusRect( hdc, rect );
566 else
567 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
568 return;
571 /* some programs mess with the clipping region when
572 drawing the item, *and* restore the previous region
573 after they are done, so a region has better to exist
574 else everything ends clipped */
575 GetClientRect(hwnd, &r);
576 hrgn = CreateRectRgnIndirect(&r);
577 SelectClipRgn( hdc, hrgn);
578 DeleteObject( hrgn );
580 dis.CtlType = ODT_LISTBOX;
581 dis.CtlID = id;
582 dis.hwndItem = hwnd;
583 dis.itemAction = action;
584 dis.hDC = hdc;
585 dis.itemID = index;
586 dis.itemState = 0;
587 if (item && item->selected) dis.itemState |= ODS_SELECTED;
588 if (!ignoreFocus && (descr->focus_item == index) &&
589 (descr->caret_on) &&
590 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
591 if (!IsWindowEnabled(hwnd)) dis.itemState |= ODS_DISABLED;
592 dis.itemData = item ? item->data : 0;
593 dis.rcItem = *rect;
594 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
595 hwnd, index, item ? debugstr_w(item->str) : "", action,
596 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
597 SendMessageW(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
599 else
601 COLORREF oldText = 0, oldBk = 0;
603 if (action == ODA_FOCUS)
605 DrawFocusRect( hdc, rect );
606 return;
608 if (item && item->selected)
610 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
611 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
614 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
615 hwnd, index, item ? debugstr_w(item->str) : "", action,
616 rect->left, rect->top, rect->right, rect->bottom );
617 if (!item)
618 ExtTextOutW( hdc, rect->left + 1, rect->top,
619 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
620 else if (!(descr->style & LBS_USETABSTOPS))
621 ExtTextOutW( hdc, rect->left + 1, rect->top,
622 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
623 strlenW(item->str), NULL );
624 else
626 /* Output empty string to paint background in the full width. */
627 ExtTextOutW( hdc, rect->left + 1, rect->top,
628 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
629 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
630 item->str, strlenW(item->str),
631 descr->nb_tabs, descr->tabs, 0);
633 if (item && item->selected)
635 SetBkColor( hdc, oldBk );
636 SetTextColor( hdc, oldText );
638 if (!ignoreFocus && (descr->focus_item == index) &&
639 (descr->caret_on) &&
640 (descr->in_focus)) DrawFocusRect( hdc, rect );
645 /***********************************************************************
646 * LISTBOX_SetRedraw
648 * Change the redraw flag.
650 static void LISTBOX_SetRedraw( HWND hwnd, LB_DESCR *descr, BOOL on )
652 if (on)
654 if (!(descr->style & LBS_NOREDRAW)) return;
655 descr->style &= ~LBS_NOREDRAW;
656 if (descr->style & LBS_DISPLAYCHANGED)
657 { /* page was changed while setredraw false, refresh automatically */
658 InvalidateRect(hwnd, NULL, TRUE);
659 if ((descr->top_item + descr->page_size) > descr->nb_items)
660 { /* reset top of page if less than number of items/page */
661 descr->top_item = descr->nb_items - descr->page_size;
662 if (descr->top_item < 0) descr->top_item = 0;
664 descr->style &= ~LBS_DISPLAYCHANGED;
666 LISTBOX_UpdateScroll( hwnd, descr );
668 else descr->style |= LBS_NOREDRAW;
672 /***********************************************************************
673 * LISTBOX_RepaintItem
675 * Repaint a single item synchronously.
677 static void LISTBOX_RepaintItem( HWND hwnd, LB_DESCR *descr, INT index,
678 UINT action )
680 HDC hdc;
681 RECT rect;
682 HFONT oldFont = 0;
683 HBRUSH hbrush, oldBrush = 0;
685 /* Do not repaint the item if the item is not visible */
686 if (!IsWindowVisible(hwnd)) return;
687 if (descr->style & LBS_NOREDRAW)
689 descr->style |= LBS_DISPLAYCHANGED;
690 return;
692 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
693 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE ))) return;
694 if (descr->font) oldFont = SelectObject( hdc, descr->font );
695 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
696 (WPARAM)hdc, (LPARAM)hwnd );
697 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
698 if (!IsWindowEnabled(hwnd))
699 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
700 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
701 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, index, action, FALSE );
702 if (oldFont) SelectObject( hdc, oldFont );
703 if (oldBrush) SelectObject( hdc, oldBrush );
704 ReleaseDC( hwnd, hdc );
708 /***********************************************************************
709 * LISTBOX_InitStorage
711 static LRESULT LISTBOX_InitStorage( HWND hwnd, LB_DESCR *descr, INT nb_items )
713 LB_ITEMDATA *item;
715 nb_items += LB_ARRAY_GRANULARITY - 1;
716 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
717 if (descr->items)
718 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
719 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
720 nb_items * sizeof(LB_ITEMDATA) )))
722 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
723 return LB_ERRSPACE;
725 descr->items = item;
726 return LB_OKAY;
730 /***********************************************************************
731 * LISTBOX_SetTabStops
733 static BOOL LISTBOX_SetTabStops( HWND hwnd, LB_DESCR *descr, INT count,
734 LPINT tabs, BOOL short_ints )
736 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
737 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
738 if (!(descr->nb_tabs = count))
740 descr->tabs = NULL;
741 return TRUE;
743 /* FIXME: count = 1 */
744 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
745 descr->nb_tabs * sizeof(INT) )))
746 return FALSE;
747 if (short_ints)
749 INT i;
750 LPINT16 p = (LPINT16)tabs;
752 TRACE("[%p]: settabstops ", hwnd );
753 for (i = 0; i < descr->nb_tabs; i++) {
754 descr->tabs[i] = *p++<<1; /* FIXME */
755 if (TRACE_ON(listbox)) TRACE("%hd ", descr->tabs[i]);
757 if (TRACE_ON(listbox)) TRACE("\n");
759 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
760 /* FIXME: repaint the window? */
761 return TRUE;
765 /***********************************************************************
766 * LISTBOX_GetText
768 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
770 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
771 if (HAS_STRINGS(descr))
773 if (!lParam)
774 return strlenW(descr->items[index].str);
776 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
778 if(unicode)
780 LPWSTR buffer = (LPWSTR)lParam;
781 strcpyW( buffer, descr->items[index].str );
782 return strlenW(buffer);
784 else
786 LPSTR buffer = (LPSTR)lParam;
787 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
789 } else {
790 if (lParam)
791 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
792 return sizeof(DWORD);
797 /***********************************************************************
798 * LISTBOX_FindStringPos
800 * Find the nearest string located before a given string in sort order.
801 * If 'exact' is TRUE, return an error if we don't get an exact match.
803 static INT LISTBOX_FindStringPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str,
804 BOOL exact )
806 INT index, min, max, res = -1;
808 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
809 min = 0;
810 max = descr->nb_items;
811 while (min != max)
813 index = (min + max) / 2;
814 if (HAS_STRINGS(descr))
815 res = lstrcmpiW( str, descr->items[index].str);
816 else
818 COMPAREITEMSTRUCT cis;
819 UINT id = GetWindowLongA( hwnd, GWL_ID );
821 cis.CtlType = ODT_LISTBOX;
822 cis.CtlID = id;
823 cis.hwndItem = hwnd;
824 /* note that some application (MetaStock) expects the second item
825 * to be in the listbox */
826 cis.itemID1 = -1;
827 cis.itemData1 = (DWORD)str;
828 cis.itemID2 = index;
829 cis.itemData2 = descr->items[index].data;
830 cis.dwLocaleId = descr->locale;
831 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
833 if (!res) return index;
834 if (res < 0) max = index;
835 else min = index + 1;
837 return exact ? -1 : max;
841 /***********************************************************************
842 * LISTBOX_FindFileStrPos
844 * Find the nearest string located before a given string in directory
845 * sort order (i.e. first files, then directories, then drives).
847 static INT LISTBOX_FindFileStrPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str )
849 INT min, max, res = -1;
851 if (!HAS_STRINGS(descr))
852 return LISTBOX_FindStringPos( hwnd, descr, str, FALSE );
853 min = 0;
854 max = descr->nb_items;
855 while (min != max)
857 INT index = (min + max) / 2;
858 LPCWSTR p = descr->items[index].str;
859 if (*p == '[') /* drive or directory */
861 if (*str != '[') res = -1;
862 else if (p[1] == '-') /* drive */
864 if (str[1] == '-') res = str[2] - p[2];
865 else res = -1;
867 else /* directory */
869 if (str[1] == '-') res = 1;
870 else res = lstrcmpiW( str, p );
873 else /* filename */
875 if (*str == '[') res = 1;
876 else res = lstrcmpiW( str, p );
878 if (!res) return index;
879 if (res < 0) max = index;
880 else min = index + 1;
882 return max;
886 /***********************************************************************
887 * LISTBOX_FindString
889 * Find the item beginning with a given string.
891 static INT LISTBOX_FindString( HWND hwnd, LB_DESCR *descr, INT start,
892 LPCWSTR str, BOOL exact )
894 INT i;
895 LB_ITEMDATA *item;
897 if (start >= descr->nb_items) start = -1;
898 item = descr->items + start + 1;
899 if (HAS_STRINGS(descr))
901 if (!str || ! str[0] ) return LB_ERR;
902 if (exact)
904 for (i = start + 1; i < descr->nb_items; i++, item++)
905 if (!lstrcmpiW( str, item->str )) return i;
906 for (i = 0, item = descr->items; i <= start; i++, item++)
907 if (!lstrcmpiW( str, item->str )) return i;
909 else
911 /* Special case for drives and directories: ignore prefix */
912 #define CHECK_DRIVE(item) \
913 if ((item)->str[0] == '[') \
915 if (!strncmpiW( str, (item)->str+1, len )) return i; \
916 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
917 return i; \
920 INT len = strlenW(str);
921 for (i = start + 1; i < descr->nb_items; i++, item++)
923 if (!strncmpiW( str, item->str, len )) return i;
924 CHECK_DRIVE(item);
926 for (i = 0, item = descr->items; i <= start; i++, item++)
928 if (!strncmpiW( str, item->str, len )) return i;
929 CHECK_DRIVE(item);
931 #undef CHECK_DRIVE
934 else
936 if (exact && (descr->style & LBS_SORT))
937 /* If sorted, use a WM_COMPAREITEM binary search */
938 return LISTBOX_FindStringPos( hwnd, descr, str, TRUE );
940 /* Otherwise use a linear search */
941 for (i = start + 1; i < descr->nb_items; i++, item++)
942 if (item->data == (DWORD)str) return i;
943 for (i = 0, item = descr->items; i <= start; i++, item++)
944 if (item->data == (DWORD)str) return i;
946 return LB_ERR;
950 /***********************************************************************
951 * LISTBOX_GetSelCount
953 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
955 INT i, count;
956 LB_ITEMDATA *item = descr->items;
958 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
959 for (i = count = 0; i < descr->nb_items; i++, item++)
960 if (item->selected) count++;
961 return count;
965 /***********************************************************************
966 * LISTBOX_GetSelItems16
968 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
970 INT i, count;
971 LB_ITEMDATA *item = descr->items;
973 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
974 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
975 if (item->selected) array[count++] = (INT16)i;
976 return count;
980 /***********************************************************************
981 * LISTBOX_GetSelItems
983 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
985 INT i, count;
986 LB_ITEMDATA *item = descr->items;
988 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
989 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
990 if (item->selected) array[count++] = i;
991 return count;
995 /***********************************************************************
996 * LISTBOX_Paint
998 static LRESULT LISTBOX_Paint( HWND hwnd, LB_DESCR *descr, HDC hdc )
1000 INT i, col_pos = descr->page_size - 1;
1001 RECT rect;
1002 RECT focusRect = {-1, -1, -1, -1};
1003 HFONT oldFont = 0;
1004 HBRUSH hbrush, oldBrush = 0;
1006 if (descr->style & LBS_NOREDRAW) return 0;
1008 SetRect( &rect, 0, 0, descr->width, descr->height );
1009 if (descr->style & LBS_MULTICOLUMN)
1010 rect.right = rect.left + descr->column_width;
1011 else if (descr->horz_pos)
1013 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1014 rect.right += descr->horz_pos;
1017 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1018 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1019 (WPARAM)hdc, (LPARAM)hwnd );
1020 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1021 if (!IsWindowEnabled(hwnd)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1023 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1024 (descr->in_focus))
1026 /* Special case for empty listbox: paint focus rect */
1027 rect.bottom = rect.top + descr->item_height;
1028 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, descr->focus_item,
1029 ODA_FOCUS, FALSE );
1030 rect.top = rect.bottom;
1033 /* Paint all the item, regarding the selection
1034 Focus state will be painted after */
1036 for (i = descr->top_item; i < descr->nb_items; i++)
1038 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1039 rect.bottom = rect.top + descr->item_height;
1040 else
1041 rect.bottom = rect.top + descr->items[i].height;
1043 if (i == descr->focus_item)
1045 /* keep the focus rect, to paint the focus item after */
1046 focusRect.left = rect.left;
1047 focusRect.right = rect.right;
1048 focusRect.top = rect.top;
1049 focusRect.bottom = rect.bottom;
1051 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1052 rect.top = rect.bottom;
1054 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1056 if (!IS_OWNERDRAW(descr))
1058 /* Clear the bottom of the column */
1059 if (rect.top < descr->height)
1061 rect.bottom = descr->height;
1062 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1063 &rect, NULL, 0, NULL );
1067 /* Go to the next column */
1068 rect.left += descr->column_width;
1069 rect.right += descr->column_width;
1070 rect.top = 0;
1071 col_pos = descr->page_size - 1;
1073 else
1075 col_pos--;
1076 if (rect.top >= descr->height) break;
1080 /* Paint the focus item now */
1081 if (focusRect.top != focusRect.bottom && descr->caret_on)
1082 LISTBOX_PaintItem( hwnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1084 if (!IS_OWNERDRAW(descr))
1086 /* Clear the remainder of the client area */
1087 if (rect.top < descr->height)
1089 rect.bottom = descr->height;
1090 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1091 &rect, NULL, 0, NULL );
1093 if (rect.right < descr->width)
1095 rect.left = rect.right;
1096 rect.right = descr->width;
1097 rect.top = 0;
1098 rect.bottom = descr->height;
1099 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1100 &rect, NULL, 0, NULL );
1103 if (oldFont) SelectObject( hdc, oldFont );
1104 if (oldBrush) SelectObject( hdc, oldBrush );
1105 return 0;
1109 /***********************************************************************
1110 * LISTBOX_InvalidateItems
1112 * Invalidate all items from a given item. If the specified item is not
1113 * visible, nothing happens.
1115 static void LISTBOX_InvalidateItems( HWND hwnd, LB_DESCR *descr, INT index )
1117 RECT rect;
1119 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1121 if (descr->style & LBS_NOREDRAW)
1123 descr->style |= LBS_DISPLAYCHANGED;
1124 return;
1126 rect.bottom = descr->height;
1127 InvalidateRect( hwnd, &rect, TRUE );
1128 if (descr->style & LBS_MULTICOLUMN)
1130 /* Repaint the other columns */
1131 rect.left = rect.right;
1132 rect.right = descr->width;
1133 rect.top = 0;
1134 InvalidateRect( hwnd, &rect, TRUE );
1140 /***********************************************************************
1141 * LISTBOX_GetItemHeight
1143 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1145 if (descr->style & LBS_OWNERDRAWVARIABLE)
1147 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1148 return descr->items[index].height;
1150 else return descr->item_height;
1154 /***********************************************************************
1155 * LISTBOX_SetItemHeight
1157 static LRESULT LISTBOX_SetItemHeight( HWND hwnd, LB_DESCR *descr, INT index,
1158 INT height, BOOL repaint )
1160 if (!height) height = 1;
1162 if (descr->style & LBS_OWNERDRAWVARIABLE)
1164 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1165 TRACE("[%p]: item %d height = %d\n", hwnd, index, height );
1166 descr->items[index].height = height;
1167 LISTBOX_UpdateScroll( hwnd, descr );
1168 if (repaint)
1169 LISTBOX_InvalidateItems( hwnd, descr, index );
1171 else if (height != descr->item_height)
1173 TRACE("[%p]: new height = %d\n", hwnd, height );
1174 descr->item_height = height;
1175 LISTBOX_UpdatePage( hwnd, descr );
1176 LISTBOX_UpdateScroll( hwnd, descr );
1177 if (repaint)
1178 InvalidateRect( hwnd, 0, TRUE );
1180 return LB_OKAY;
1184 /***********************************************************************
1185 * LISTBOX_SetHorizontalPos
1187 static void LISTBOX_SetHorizontalPos( HWND hwnd, LB_DESCR *descr, INT pos )
1189 INT diff;
1191 if (pos > descr->horz_extent - descr->width)
1192 pos = descr->horz_extent - descr->width;
1193 if (pos < 0) pos = 0;
1194 if (!(diff = descr->horz_pos - pos)) return;
1195 TRACE("[%p]: new horz pos = %d\n", hwnd, pos );
1196 descr->horz_pos = pos;
1197 LISTBOX_UpdateScroll( hwnd, descr );
1198 if (abs(diff) < descr->width)
1199 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
1200 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1201 else
1202 InvalidateRect( hwnd, NULL, TRUE );
1206 /***********************************************************************
1207 * LISTBOX_SetHorizontalExtent
1209 static LRESULT LISTBOX_SetHorizontalExtent( HWND hwnd, LB_DESCR *descr,
1210 INT extent )
1212 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1213 return LB_OKAY;
1214 if (extent <= 0) extent = 1;
1215 if (extent == descr->horz_extent) return LB_OKAY;
1216 TRACE("[%p]: new horz extent = %d\n", hwnd, extent );
1217 descr->horz_extent = extent;
1218 if (descr->horz_pos > extent - descr->width)
1219 LISTBOX_SetHorizontalPos( hwnd, descr, extent - descr->width );
1220 else
1221 LISTBOX_UpdateScroll( hwnd, descr );
1222 return LB_OKAY;
1226 /***********************************************************************
1227 * LISTBOX_SetColumnWidth
1229 static LRESULT LISTBOX_SetColumnWidth( HWND hwnd, LB_DESCR *descr, INT width)
1231 if (width == descr->column_width) return LB_OKAY;
1232 TRACE("[%p]: new column width = %d\n", hwnd, width );
1233 descr->column_width = width;
1234 LISTBOX_UpdatePage( hwnd, descr );
1235 return LB_OKAY;
1239 /***********************************************************************
1240 * LISTBOX_SetFont
1242 * Returns the item height.
1244 static INT LISTBOX_SetFont( HWND hwnd, LB_DESCR *descr, HFONT font )
1246 HDC hdc;
1247 HFONT oldFont = 0;
1248 TEXTMETRICW tm;
1250 descr->font = font;
1252 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE )))
1254 ERR("unable to get DC.\n" );
1255 return 16;
1257 if (font) oldFont = SelectObject( hdc, font );
1258 GetTextMetricsW( hdc, &tm );
1259 if (oldFont) SelectObject( hdc, oldFont );
1260 ReleaseDC( hwnd, hdc );
1261 if (!IS_OWNERDRAW(descr))
1262 LISTBOX_SetItemHeight( hwnd, descr, 0, tm.tmHeight, FALSE );
1263 return tm.tmHeight ;
1267 /***********************************************************************
1268 * LISTBOX_MakeItemVisible
1270 * Make sure that a given item is partially or fully visible.
1272 static void LISTBOX_MakeItemVisible( HWND hwnd, LB_DESCR *descr, INT index,
1273 BOOL fully )
1275 INT top;
1277 if (index <= descr->top_item) top = index;
1278 else if (descr->style & LBS_MULTICOLUMN)
1280 INT cols = descr->width;
1281 if (!fully) cols += descr->column_width - 1;
1282 if (cols >= descr->column_width) cols /= descr->column_width;
1283 else cols = 1;
1284 if (index < descr->top_item + (descr->page_size * cols)) return;
1285 top = index - descr->page_size * (cols - 1);
1287 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1289 INT height = fully ? descr->items[index].height : 1;
1290 for (top = index; top > descr->top_item; top--)
1291 if ((height += descr->items[top-1].height) > descr->height) break;
1293 else
1295 if (index < descr->top_item + descr->page_size) return;
1296 if (!fully && (index == descr->top_item + descr->page_size) &&
1297 (descr->height > (descr->page_size * descr->item_height))) return;
1298 top = index - descr->page_size + 1;
1300 LISTBOX_SetTopItem( hwnd, descr, top, TRUE );
1303 /***********************************************************************
1304 * LISTBOX_SetCaretIndex
1306 * NOTES
1307 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1310 static LRESULT LISTBOX_SetCaretIndex( HWND hwnd, LB_DESCR *descr, INT index,
1311 BOOL fully_visible )
1313 INT oldfocus = descr->focus_item;
1315 if (descr->style & LBS_NOSEL) return LB_ERR;
1316 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1317 if (index == oldfocus) return LB_OKAY;
1318 descr->focus_item = index;
1319 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1320 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1322 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1323 if (descr->caret_on && (descr->in_focus))
1324 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1326 return LB_OKAY;
1330 /***********************************************************************
1331 * LISTBOX_SelectItemRange
1333 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1335 static LRESULT LISTBOX_SelectItemRange( HWND hwnd, LB_DESCR *descr, INT first,
1336 INT last, BOOL on )
1338 INT i;
1340 /* A few sanity checks */
1342 if (descr->style & LBS_NOSEL) return LB_ERR;
1343 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1344 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1345 if (last == -1) last = descr->nb_items - 1;
1346 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1347 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1348 /* selected_item reflects last selected/unselected item on multiple sel */
1349 descr->selected_item = last;
1351 if (on) /* Turn selection on */
1353 for (i = first; i <= last; i++)
1355 if (descr->items[i].selected) continue;
1356 descr->items[i].selected = TRUE;
1357 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1359 LISTBOX_SetCaretIndex( hwnd, descr, last, TRUE );
1361 else /* Turn selection off */
1363 for (i = first; i <= last; i++)
1365 if (!descr->items[i].selected) continue;
1366 descr->items[i].selected = FALSE;
1367 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1370 return LB_OKAY;
1373 /***********************************************************************
1374 * LISTBOX_SetSelection
1376 static LRESULT LISTBOX_SetSelection( HWND hwnd, LB_DESCR *descr, INT index,
1377 BOOL on, BOOL send_notify )
1379 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1381 if (descr->style & LBS_NOSEL) return LB_ERR;
1382 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1383 if (descr->style & LBS_MULTIPLESEL)
1385 if (index == -1) /* Select all items */
1386 return LISTBOX_SelectItemRange( hwnd, descr, 0, -1, on );
1387 else /* Only one item */
1388 return LISTBOX_SelectItemRange( hwnd, descr, index, index, on );
1390 else
1392 INT oldsel = descr->selected_item;
1393 if (index == oldsel) return LB_OKAY;
1394 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1395 if (index != -1) descr->items[index].selected = TRUE;
1396 descr->selected_item = index;
1397 if (oldsel != -1) LISTBOX_RepaintItem( hwnd, descr, oldsel, ODA_SELECT );
1398 if (index != -1) LISTBOX_RepaintItem( hwnd, descr, index, ODA_SELECT );
1399 if (send_notify && descr->nb_items) SEND_NOTIFICATION( hwnd, descr,
1400 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1401 else
1402 if( descr->lphc ) /* set selection change flag for parent combo */
1403 descr->lphc->wState |= CBF_SELCHANGE;
1405 return LB_OKAY;
1409 /***********************************************************************
1410 * LISTBOX_MoveCaret
1412 * Change the caret position and extend the selection to the new caret.
1414 static void LISTBOX_MoveCaret( HWND hwnd, LB_DESCR *descr, INT index,
1415 BOOL fully_visible )
1417 INT oldfocus = descr->focus_item;
1419 if ((index < 0) || (index >= descr->nb_items))
1420 return;
1422 /* Important, repaint needs to be done in this order if
1423 you want to mimic Windows behavior:
1424 1. Remove the focus and paint the item
1425 2. Remove the selection and paint the item(s)
1426 3. Set the selection and repaint the item(s)
1427 4. Set the focus to 'index' and repaint the item */
1429 /* 1. remove the focus and repaint the item */
1430 descr->focus_item = -1;
1431 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1432 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1434 /* 2. then turn off the previous selection */
1435 /* 3. repaint the new selected item */
1436 if (descr->style & LBS_EXTENDEDSEL)
1438 if (descr->anchor_item != -1)
1440 INT first = min( index, descr->anchor_item );
1441 INT last = max( index, descr->anchor_item );
1442 if (first > 0)
1443 LISTBOX_SelectItemRange( hwnd, descr, 0, first - 1, FALSE );
1444 LISTBOX_SelectItemRange( hwnd, descr, last + 1, -1, FALSE );
1445 LISTBOX_SelectItemRange( hwnd, descr, first, last, TRUE );
1448 else if (!(descr->style & LBS_MULTIPLESEL))
1450 /* Set selection to new caret item */
1451 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
1454 /* 4. repaint the new item with the focus */
1455 descr->focus_item = index;
1456 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1457 if (descr->caret_on && (descr->in_focus))
1458 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1462 /***********************************************************************
1463 * LISTBOX_InsertItem
1465 static LRESULT LISTBOX_InsertItem( HWND hwnd, LB_DESCR *descr, INT index,
1466 LPWSTR str, DWORD data )
1468 LB_ITEMDATA *item;
1469 INT max_items;
1470 INT oldfocus = descr->focus_item;
1472 if (index == -1) index = descr->nb_items;
1473 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1474 if (!descr->items) max_items = 0;
1475 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1476 if (descr->nb_items == max_items)
1478 /* We need to grow the array */
1479 max_items += LB_ARRAY_GRANULARITY;
1480 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1481 max_items * sizeof(LB_ITEMDATA) )))
1483 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1484 return LB_ERRSPACE;
1486 descr->items = item;
1489 /* Insert the item structure */
1491 item = &descr->items[index];
1492 if (index < descr->nb_items)
1493 RtlMoveMemory( item + 1, item,
1494 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1495 item->str = str;
1496 item->data = data;
1497 item->height = 0;
1498 item->selected = FALSE;
1499 descr->nb_items++;
1501 /* Get item height */
1503 if (descr->style & LBS_OWNERDRAWVARIABLE)
1505 MEASUREITEMSTRUCT mis;
1506 UINT id = GetWindowLongA( hwnd, GWL_ID );
1508 mis.CtlType = ODT_LISTBOX;
1509 mis.CtlID = id;
1510 mis.itemID = index;
1511 mis.itemData = descr->items[index].data;
1512 mis.itemHeight = descr->item_height;
1513 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1514 item->height = mis.itemHeight ? mis.itemHeight : 1;
1515 TRACE("[%p]: measure item %d (%s) = %d\n",
1516 hwnd, index, str ? debugstr_w(str) : "", item->height );
1519 /* Repaint the items */
1521 LISTBOX_UpdateScroll( hwnd, descr );
1522 LISTBOX_InvalidateItems( hwnd, descr, index );
1524 /* Move selection and focused item */
1525 /* If listbox was empty, set focus to the first item */
1526 if (descr->nb_items == 1)
1527 LISTBOX_SetCaretIndex( hwnd, descr, 0, FALSE );
1528 /* single select don't change selection index in win31 */
1529 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1531 descr->selected_item++;
1532 LISTBOX_SetSelection( hwnd, descr, descr->selected_item-1, TRUE, FALSE );
1534 else
1536 if (index <= descr->selected_item)
1538 descr->selected_item++;
1539 descr->focus_item = oldfocus; /* focus not changed */
1542 return LB_OKAY;
1546 /***********************************************************************
1547 * LISTBOX_InsertString
1549 static LRESULT LISTBOX_InsertString( HWND hwnd, LB_DESCR *descr, INT index,
1550 LPCWSTR str )
1552 LPWSTR new_str = NULL;
1553 DWORD data = 0;
1554 LRESULT ret;
1556 if (HAS_STRINGS(descr))
1558 static const WCHAR empty_stringW[] = { 0 };
1559 if (!str) str = empty_stringW;
1560 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1562 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1563 return LB_ERRSPACE;
1565 strcpyW(new_str, str);
1567 else data = (DWORD)str;
1569 if (index == -1) index = descr->nb_items;
1570 if ((ret = LISTBOX_InsertItem( hwnd, descr, index, new_str, data )) != 0)
1572 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1573 return ret;
1576 TRACE("[%p]: added item %d %s\n",
1577 hwnd, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1578 return index;
1582 /***********************************************************************
1583 * LISTBOX_DeleteItem
1585 * Delete the content of an item. 'index' must be a valid index.
1587 static void LISTBOX_DeleteItem( HWND hwnd, LB_DESCR *descr, INT index )
1589 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1590 * while Win95 sends it for all items with user data.
1591 * It's probably better to send it too often than not
1592 * often enough, so this is what we do here.
1594 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1596 DELETEITEMSTRUCT dis;
1597 UINT id = GetWindowLongA( hwnd, GWL_ID );
1599 dis.CtlType = ODT_LISTBOX;
1600 dis.CtlID = id;
1601 dis.itemID = index;
1602 dis.hwndItem = hwnd;
1603 dis.itemData = descr->items[index].data;
1604 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1606 if (HAS_STRINGS(descr) && descr->items[index].str)
1607 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1611 /***********************************************************************
1612 * LISTBOX_RemoveItem
1614 * Remove an item from the listbox and delete its content.
1616 static LRESULT LISTBOX_RemoveItem( HWND hwnd, LB_DESCR *descr, INT index )
1618 LB_ITEMDATA *item;
1619 INT max_items;
1621 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1622 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1624 /* We need to invalidate the original rect instead of the updated one. */
1625 LISTBOX_InvalidateItems( hwnd, descr, index );
1627 LISTBOX_DeleteItem( hwnd, descr, index );
1629 /* Remove the item */
1631 item = &descr->items[index];
1632 if (index < descr->nb_items-1)
1633 RtlMoveMemory( item, item + 1,
1634 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1635 descr->nb_items--;
1636 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1638 /* Shrink the item array if possible */
1640 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1641 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1643 max_items -= LB_ARRAY_GRANULARITY;
1644 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1645 max_items * sizeof(LB_ITEMDATA) );
1646 if (item) descr->items = item;
1648 /* Repaint the items */
1650 LISTBOX_UpdateScroll( hwnd, descr );
1651 /* if we removed the scrollbar, reset the top of the list
1652 (correct for owner-drawn ???) */
1653 if (descr->nb_items == descr->page_size)
1654 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1656 /* Move selection and focused item */
1657 if (!IS_MULTISELECT(descr))
1659 if (index == descr->selected_item)
1660 descr->selected_item = -1;
1661 else if (index < descr->selected_item)
1663 descr->selected_item--;
1664 if (ISWIN31) /* win 31 do not change the selected item number */
1665 LISTBOX_SetSelection( hwnd, descr, descr->selected_item + 1, TRUE, FALSE);
1669 if (descr->focus_item >= descr->nb_items)
1671 descr->focus_item = descr->nb_items - 1;
1672 if (descr->focus_item < 0) descr->focus_item = 0;
1674 return LB_OKAY;
1678 /***********************************************************************
1679 * LISTBOX_ResetContent
1681 static void LISTBOX_ResetContent( HWND hwnd, LB_DESCR *descr )
1683 INT i;
1685 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( hwnd, descr, i );
1686 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1687 descr->nb_items = 0;
1688 descr->top_item = 0;
1689 descr->selected_item = -1;
1690 descr->focus_item = 0;
1691 descr->anchor_item = -1;
1692 descr->items = NULL;
1696 /***********************************************************************
1697 * LISTBOX_SetCount
1699 static LRESULT LISTBOX_SetCount( HWND hwnd, LB_DESCR *descr, INT count )
1701 LRESULT ret;
1703 if (HAS_STRINGS(descr)) return LB_ERR;
1704 /* FIXME: this is far from optimal... */
1705 if (count > descr->nb_items)
1707 while (count > descr->nb_items)
1708 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, 0 )) < 0)
1709 return ret;
1711 else if (count < descr->nb_items)
1713 while (count < descr->nb_items)
1714 if ((ret = LISTBOX_RemoveItem( hwnd, descr, -1 )) < 0)
1715 return ret;
1717 return LB_OKAY;
1721 /***********************************************************************
1722 * LISTBOX_Directory
1724 static LRESULT LISTBOX_Directory( HWND hwnd, LB_DESCR *descr, UINT attrib,
1725 LPCWSTR filespec, BOOL long_names )
1727 HANDLE handle;
1728 LRESULT ret = LB_OKAY;
1729 WIN32_FIND_DATAW entry;
1730 int pos;
1732 /* don't scan directory if we just want drives exclusively */
1733 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1734 /* scan directory */
1735 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1737 int le = GetLastError();
1738 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1740 else
1744 WCHAR buffer[270];
1745 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1747 static const WCHAR bracketW[] = { ']',0 };
1748 static const WCHAR dotW[] = { '.',0 };
1749 if (!(attrib & DDL_DIRECTORY) ||
1750 !strcmpW( entry.cAlternateFileName, dotW )) continue;
1751 buffer[0] = '[';
1752 if (long_names) strcpyW( buffer + 1, entry.cFileName );
1753 else strcpyW( buffer + 1, entry.cAlternateFileName );
1754 strcatW(buffer, bracketW);
1756 else /* not a directory */
1758 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1759 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1761 if ((attrib & DDL_EXCLUSIVE) &&
1762 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1763 continue;
1764 #undef ATTRIBS
1765 if (long_names) strcpyW( buffer, entry.cFileName );
1766 else strcpyW( buffer, entry.cAlternateFileName );
1768 if (!long_names) CharLowerW( buffer );
1769 pos = LISTBOX_FindFileStrPos( hwnd, descr, buffer );
1770 if ((ret = LISTBOX_InsertString( hwnd, descr, pos, buffer )) < 0)
1771 break;
1772 } while (FindNextFileW( handle, &entry ));
1773 FindClose( handle );
1777 /* scan drives */
1778 if ((ret >= 0) && (attrib & DDL_DRIVES))
1780 WCHAR buffer[] = {'[','-','a','-',']',0};
1781 WCHAR root[] = {'A',':','\\',0};
1782 int drive;
1783 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1785 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1786 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, buffer )) < 0)
1787 break;
1790 return ret;
1794 /***********************************************************************
1795 * LISTBOX_HandleVScroll
1797 static LRESULT LISTBOX_HandleVScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1799 SCROLLINFO info;
1801 if (descr->style & LBS_MULTICOLUMN) return 0;
1802 switch(LOWORD(wParam))
1804 case SB_LINEUP:
1805 LISTBOX_SetTopItem( hwnd, descr, descr->top_item - 1, TRUE );
1806 break;
1807 case SB_LINEDOWN:
1808 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + 1, TRUE );
1809 break;
1810 case SB_PAGEUP:
1811 LISTBOX_SetTopItem( hwnd, descr, descr->top_item -
1812 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1813 break;
1814 case SB_PAGEDOWN:
1815 LISTBOX_SetTopItem( hwnd, descr, descr->top_item +
1816 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1817 break;
1818 case SB_THUMBPOSITION:
1819 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam), TRUE );
1820 break;
1821 case SB_THUMBTRACK:
1822 info.cbSize = sizeof(info);
1823 info.fMask = SIF_TRACKPOS;
1824 GetScrollInfo( hwnd, SB_VERT, &info );
1825 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos, TRUE );
1826 break;
1827 case SB_TOP:
1828 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1829 break;
1830 case SB_BOTTOM:
1831 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1832 break;
1834 return 0;
1838 /***********************************************************************
1839 * LISTBOX_HandleHScroll
1841 static LRESULT LISTBOX_HandleHScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1843 SCROLLINFO info;
1844 INT page;
1846 if (descr->style & LBS_MULTICOLUMN)
1848 switch(LOWORD(wParam))
1850 case SB_LINELEFT:
1851 LISTBOX_SetTopItem( hwnd, descr, descr->top_item-descr->page_size,
1852 TRUE );
1853 break;
1854 case SB_LINERIGHT:
1855 LISTBOX_SetTopItem( hwnd, descr, descr->top_item+descr->page_size,
1856 TRUE );
1857 break;
1858 case SB_PAGELEFT:
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_PAGERIGHT:
1865 page = descr->width / descr->column_width;
1866 if (page < 1) page = 1;
1867 LISTBOX_SetTopItem( hwnd, descr,
1868 descr->top_item + page * descr->page_size, TRUE );
1869 break;
1870 case SB_THUMBPOSITION:
1871 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam)*descr->page_size,
1872 TRUE );
1873 break;
1874 case SB_THUMBTRACK:
1875 info.cbSize = sizeof(info);
1876 info.fMask = SIF_TRACKPOS;
1877 GetScrollInfo( hwnd, SB_VERT, &info );
1878 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos*descr->page_size,
1879 TRUE );
1880 break;
1881 case SB_LEFT:
1882 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1883 break;
1884 case SB_RIGHT:
1885 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1886 break;
1889 else if (descr->horz_extent)
1891 switch(LOWORD(wParam))
1893 case SB_LINELEFT:
1894 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos - 1 );
1895 break;
1896 case SB_LINERIGHT:
1897 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos + 1 );
1898 break;
1899 case SB_PAGELEFT:
1900 LISTBOX_SetHorizontalPos( hwnd, descr,
1901 descr->horz_pos - descr->width );
1902 break;
1903 case SB_PAGERIGHT:
1904 LISTBOX_SetHorizontalPos( hwnd, descr,
1905 descr->horz_pos + descr->width );
1906 break;
1907 case SB_THUMBPOSITION:
1908 LISTBOX_SetHorizontalPos( hwnd, descr, HIWORD(wParam) );
1909 break;
1910 case SB_THUMBTRACK:
1911 info.cbSize = sizeof(info);
1912 info.fMask = SIF_TRACKPOS;
1913 GetScrollInfo( hwnd, SB_HORZ, &info );
1914 LISTBOX_SetHorizontalPos( hwnd, descr, info.nTrackPos );
1915 break;
1916 case SB_LEFT:
1917 LISTBOX_SetHorizontalPos( hwnd, descr, 0 );
1918 break;
1919 case SB_RIGHT:
1920 LISTBOX_SetHorizontalPos( hwnd, descr,
1921 descr->horz_extent - descr->width );
1922 break;
1925 return 0;
1928 static LRESULT LISTBOX_HandleMouseWheel(HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1930 short gcWheelDelta = 0;
1931 UINT pulScrollLines = 3;
1933 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1935 gcWheelDelta -= (short) HIWORD(wParam);
1937 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1939 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1940 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1941 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + cLineScroll, TRUE );
1943 return 0;
1946 /***********************************************************************
1947 * LISTBOX_HandleLButtonDown
1949 static LRESULT LISTBOX_HandleLButtonDown( HWND hwnd, LB_DESCR *descr,
1950 WPARAM wParam, INT x, INT y )
1952 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1953 TRACE("[%p]: lbuttondown %d,%d item %d\n", hwnd, x, y, index );
1954 if (!descr->caret_on && (descr->in_focus)) return 0;
1956 if (!descr->in_focus)
1958 if( !descr->lphc ) SetFocus( hwnd );
1959 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1962 if (index == -1) return 0;
1964 if (descr->style & LBS_EXTENDEDSEL)
1966 /* we should perhaps make sure that all items are deselected
1967 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1968 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1969 LISTBOX_SetSelection( hwnd, descr, -1, FALSE, FALSE);
1972 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1973 if (wParam & MK_CONTROL)
1975 LISTBOX_SetCaretIndex( hwnd, descr, index, FALSE );
1976 LISTBOX_SetSelection( hwnd, descr, index,
1977 !descr->items[index].selected,
1978 (descr->style & LBS_NOTIFY) != 0);
1980 else LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1982 else
1984 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1985 LISTBOX_SetSelection( hwnd, descr, index,
1986 (!(descr->style & LBS_MULTIPLESEL) ||
1987 !descr->items[index].selected),
1988 (descr->style & LBS_NOTIFY) != 0 );
1991 descr->captured = TRUE;
1992 SetCapture( hwnd );
1994 if (!descr->lphc)
1996 if (descr->style & LBS_NOTIFY )
1997 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
1998 MAKELPARAM( x, y ) );
1999 if (GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2001 POINT pt;
2003 pt.x = x;
2004 pt.y = y;
2006 if (DragDetect( hwnd, pt ))
2007 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2010 return 0;
2014 /*************************************************************************
2015 * LISTBOX_HandleLButtonDownCombo [Internal]
2017 * Process LButtonDown message for the ComboListBox
2019 nn * PARAMS
2020 * pWnd [I] The windows internal structure
2021 * pDescr [I] The ListBox internal structure
2022 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2023 * x [I] X Mouse Coordinate
2024 * y [I] Y Mouse Coordinate
2026 * RETURNS
2027 * 0 since we are processing the WM_LBUTTONDOWN Message
2029 * NOTES
2030 * This function is only to be used when a ListBox is a ComboListBox
2033 static LRESULT LISTBOX_HandleLButtonDownCombo( HWND hwnd, LB_DESCR *pDescr,
2034 UINT msg, WPARAM wParam, INT x, INT y)
2036 RECT clientRect, screenRect;
2037 POINT mousePos;
2039 mousePos.x = x;
2040 mousePos.y = y;
2042 GetClientRect(hwnd, &clientRect);
2044 if(PtInRect(&clientRect, mousePos))
2046 /* MousePos is in client, resume normal processing */
2047 if (msg == WM_LBUTTONDOWN)
2049 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2050 return LISTBOX_HandleLButtonDown( hwnd, pDescr, wParam, x, y);
2052 else if (pDescr->style & LBS_NOTIFY)
2053 SEND_NOTIFICATION( hwnd, pDescr, LBN_DBLCLK );
2054 return 0;
2056 else
2058 POINT screenMousePos;
2059 HWND hWndOldCapture;
2061 /* Check the Non-Client Area */
2062 screenMousePos = mousePos;
2063 hWndOldCapture = GetCapture();
2064 ReleaseCapture();
2065 GetWindowRect(hwnd, &screenRect);
2066 ClientToScreen(hwnd, &screenMousePos);
2068 if(!PtInRect(&screenRect, screenMousePos))
2070 LISTBOX_SetCaretIndex( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE );
2071 LISTBOX_SetSelection( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2072 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2073 return 0;
2075 else
2077 /* Check to see the NC is a scrollbar */
2078 INT nHitTestType=0;
2079 LONG style = GetWindowLongA( hwnd, GWL_STYLE );
2080 /* Check Vertical scroll bar */
2081 if (style & WS_VSCROLL)
2083 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2084 if (PtInRect( &clientRect, mousePos ))
2086 nHitTestType = HTVSCROLL;
2089 /* Check horizontal scroll bar */
2090 if (style & WS_HSCROLL)
2092 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2093 if (PtInRect( &clientRect, mousePos ))
2095 nHitTestType = HTHSCROLL;
2098 /* Windows sends this message when a scrollbar is clicked
2101 if(nHitTestType != 0)
2103 SendMessageW(hwnd, WM_NCLBUTTONDOWN, nHitTestType,
2104 MAKELONG(screenMousePos.x, screenMousePos.y));
2106 /* Resume the Capture after scrolling is complete
2108 if(hWndOldCapture != 0)
2110 SetCapture(hWndOldCapture);
2114 return 0;
2117 /***********************************************************************
2118 * LISTBOX_HandleLButtonUp
2120 static LRESULT LISTBOX_HandleLButtonUp( HWND hwnd, LB_DESCR *descr )
2122 if (LISTBOX_Timer != LB_TIMER_NONE)
2123 KillSystemTimer( hwnd, LB_TIMER_ID );
2124 LISTBOX_Timer = LB_TIMER_NONE;
2125 if (descr->captured)
2127 descr->captured = FALSE;
2128 if (GetCapture() == hwnd) ReleaseCapture();
2129 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2130 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2132 return 0;
2136 /***********************************************************************
2137 * LISTBOX_HandleTimer
2139 * Handle scrolling upon a timer event.
2140 * Return TRUE if scrolling should continue.
2142 static LRESULT LISTBOX_HandleTimer( HWND hwnd, LB_DESCR *descr,
2143 INT index, TIMER_DIRECTION dir )
2145 switch(dir)
2147 case LB_TIMER_UP:
2148 if (descr->top_item) index = descr->top_item - 1;
2149 else index = 0;
2150 break;
2151 case LB_TIMER_LEFT:
2152 if (descr->top_item) index -= descr->page_size;
2153 break;
2154 case LB_TIMER_DOWN:
2155 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2156 if (index == descr->focus_item) index++;
2157 if (index >= descr->nb_items) index = descr->nb_items - 1;
2158 break;
2159 case LB_TIMER_RIGHT:
2160 if (index + descr->page_size < descr->nb_items)
2161 index += descr->page_size;
2162 break;
2163 case LB_TIMER_NONE:
2164 break;
2166 if (index == descr->focus_item) return FALSE;
2167 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2168 return TRUE;
2172 /***********************************************************************
2173 * LISTBOX_HandleSystemTimer
2175 * WM_SYSTIMER handler.
2177 static LRESULT LISTBOX_HandleSystemTimer( HWND hwnd, LB_DESCR *descr )
2179 if (!LISTBOX_HandleTimer( hwnd, descr, descr->focus_item, LISTBOX_Timer ))
2181 KillSystemTimer( hwnd, LB_TIMER_ID );
2182 LISTBOX_Timer = LB_TIMER_NONE;
2184 return 0;
2188 /***********************************************************************
2189 * LISTBOX_HandleMouseMove
2191 * WM_MOUSEMOVE handler.
2193 static void LISTBOX_HandleMouseMove( HWND hwnd, LB_DESCR *descr,
2194 INT x, INT y )
2196 INT index;
2197 TIMER_DIRECTION dir = LB_TIMER_NONE;
2199 if (!descr->captured) return;
2201 if (descr->style & LBS_MULTICOLUMN)
2203 if (y < 0) y = 0;
2204 else if (y >= descr->item_height * descr->page_size)
2205 y = descr->item_height * descr->page_size - 1;
2207 if (x < 0)
2209 dir = LB_TIMER_LEFT;
2210 x = 0;
2212 else if (x >= descr->width)
2214 dir = LB_TIMER_RIGHT;
2215 x = descr->width - 1;
2218 else
2220 if (y < 0) dir = LB_TIMER_UP; /* above */
2221 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2224 index = LISTBOX_GetItemFromPoint( descr, x, y );
2225 if (index == -1) index = descr->focus_item;
2226 if (!LISTBOX_HandleTimer( hwnd, descr, index, dir )) dir = LB_TIMER_NONE;
2228 /* Start/stop the system timer */
2230 if (dir != LB_TIMER_NONE)
2231 SetSystemTimer( hwnd, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2232 else if (LISTBOX_Timer != LB_TIMER_NONE)
2233 KillSystemTimer( hwnd, LB_TIMER_ID );
2234 LISTBOX_Timer = dir;
2238 /***********************************************************************
2239 * LISTBOX_HandleKeyDown
2241 static LRESULT LISTBOX_HandleKeyDown( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
2243 INT caret = -1;
2244 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2245 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2246 bForceSelection = FALSE; /* only for single select list */
2248 if (descr->style & LBS_WANTKEYBOARDINPUT)
2250 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2251 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2252 (LPARAM)hwnd );
2253 if (caret == -2) return 0;
2255 if (caret == -1) switch(wParam)
2257 case VK_LEFT:
2258 if (descr->style & LBS_MULTICOLUMN)
2260 bForceSelection = FALSE;
2261 if (descr->focus_item >= descr->page_size)
2262 caret = descr->focus_item - descr->page_size;
2263 break;
2265 /* fall through */
2266 case VK_UP:
2267 caret = descr->focus_item - 1;
2268 if (caret < 0) caret = 0;
2269 break;
2270 case VK_RIGHT:
2271 if (descr->style & LBS_MULTICOLUMN)
2273 bForceSelection = FALSE;
2274 if (descr->focus_item + descr->page_size < descr->nb_items)
2275 caret = descr->focus_item + descr->page_size;
2276 break;
2278 /* fall through */
2279 case VK_DOWN:
2280 caret = descr->focus_item + 1;
2281 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2282 break;
2284 case VK_PRIOR:
2285 if (descr->style & LBS_MULTICOLUMN)
2287 INT page = descr->width / descr->column_width;
2288 if (page < 1) page = 1;
2289 caret = descr->focus_item - (page * descr->page_size) + 1;
2291 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2292 if (caret < 0) caret = 0;
2293 break;
2294 case VK_NEXT:
2295 if (descr->style & LBS_MULTICOLUMN)
2297 INT page = descr->width / descr->column_width;
2298 if (page < 1) page = 1;
2299 caret = descr->focus_item + (page * descr->page_size) - 1;
2301 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2302 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2303 break;
2304 case VK_HOME:
2305 caret = 0;
2306 break;
2307 case VK_END:
2308 caret = descr->nb_items - 1;
2309 break;
2310 case VK_SPACE:
2311 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2312 else if (descr->style & LBS_MULTIPLESEL)
2314 LISTBOX_SetSelection( hwnd, descr, descr->focus_item,
2315 !descr->items[descr->focus_item].selected,
2316 (descr->style & LBS_NOTIFY) != 0 );
2318 break;
2319 default:
2320 bForceSelection = FALSE;
2322 if (bForceSelection) /* focused item is used instead of key */
2323 caret = descr->focus_item;
2324 if (caret >= 0)
2326 if ((descr->style & LBS_EXTENDEDSEL) &&
2327 !(GetKeyState( VK_SHIFT ) & 0x8000))
2328 descr->anchor_item = caret;
2329 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2330 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2331 if (descr->style & LBS_NOTIFY)
2333 if( descr->lphc )
2335 /* make sure that combo parent doesn't hide us */
2336 descr->lphc->wState |= CBF_NOROLLUP;
2338 if (descr->nb_items) SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2341 return 0;
2345 /***********************************************************************
2346 * LISTBOX_HandleChar
2348 static LRESULT LISTBOX_HandleChar( HWND hwnd, LB_DESCR *descr, WCHAR charW )
2350 INT caret = -1;
2351 WCHAR str[2];
2353 str[0] = charW;
2354 str[1] = '\0';
2356 if (descr->style & LBS_WANTKEYBOARDINPUT)
2358 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2359 MAKEWPARAM(charW, descr->focus_item),
2360 (LPARAM)hwnd );
2361 if (caret == -2) return 0;
2363 if (caret == -1)
2364 caret = LISTBOX_FindString( hwnd, descr, descr->focus_item, str, FALSE);
2365 if (caret != -1)
2367 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2368 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2369 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2370 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2371 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2373 return 0;
2377 /***********************************************************************
2378 * LISTBOX_Create
2380 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2382 LB_DESCR *descr;
2383 MEASUREITEMSTRUCT mis;
2384 RECT rect;
2386 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2387 return FALSE;
2389 GetClientRect( hwnd, &rect );
2390 descr->owner = GetParent( hwnd );
2391 descr->style = GetWindowLongA( hwnd, GWL_STYLE );
2392 descr->width = rect.right - rect.left;
2393 descr->height = rect.bottom - rect.top;
2394 descr->items = NULL;
2395 descr->nb_items = 0;
2396 descr->top_item = 0;
2397 descr->selected_item = -1;
2398 descr->focus_item = 0;
2399 descr->anchor_item = -1;
2400 descr->item_height = 1;
2401 descr->page_size = 1;
2402 descr->column_width = 150;
2403 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2404 descr->horz_pos = 0;
2405 descr->nb_tabs = 0;
2406 descr->tabs = NULL;
2407 descr->caret_on = lphc ? FALSE : TRUE;
2408 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2409 descr->in_focus = FALSE;
2410 descr->captured = FALSE;
2411 descr->font = 0;
2412 descr->locale = 0; /* FIXME */
2413 descr->lphc = lphc;
2415 if (is_old_app(hwnd) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2417 /* Win95 document "List Box Differences" from MSDN:
2418 If a list box in a version 3.x application has either the
2419 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2420 horizontal and vertical scroll bars.
2422 descr->style |= WS_VSCROLL | WS_HSCROLL;
2425 if( lphc )
2427 TRACE_(combo)("[%p]: resetting owner %p -> %p\n", hwnd, descr->owner, lphc->self );
2428 descr->owner = lphc->self;
2431 SetWindowLongA( hwnd, 0, (LONG)descr );
2433 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2435 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2436 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2437 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2438 descr->item_height = LISTBOX_SetFont( hwnd, descr, 0 );
2440 if (descr->style & LBS_OWNERDRAWFIXED)
2442 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2444 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2445 descr->item_height = lphc->fixedOwnerDrawHeight;
2447 else
2449 UINT id = GetWindowLongA( hwnd, GWL_ID );
2450 mis.CtlType = ODT_LISTBOX;
2451 mis.CtlID = id;
2452 mis.itemID = -1;
2453 mis.itemWidth = 0;
2454 mis.itemData = 0;
2455 mis.itemHeight = descr->item_height;
2456 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2457 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2461 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2462 return TRUE;
2466 /***********************************************************************
2467 * LISTBOX_Destroy
2469 static BOOL LISTBOX_Destroy( HWND hwnd, LB_DESCR *descr )
2471 LISTBOX_ResetContent( hwnd, descr );
2472 SetWindowLongA( hwnd, 0, 0 );
2473 HeapFree( GetProcessHeap(), 0, descr );
2474 return TRUE;
2478 /***********************************************************************
2479 * ListBoxWndProc_common
2481 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2482 WPARAM wParam, LPARAM lParam, BOOL unicode )
2484 LRESULT ret;
2485 LB_DESCR *descr;
2487 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
2489 if (msg == WM_CREATE)
2491 if (!LISTBOX_Create( hwnd, NULL ))
2492 return -1;
2493 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongA( hwnd, 0 ) );
2494 return 0;
2496 /* Ignore all other messages before we get a WM_CREATE */
2497 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2498 DefWindowProcA( hwnd, msg, wParam, lParam );
2501 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2502 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
2503 switch(msg)
2505 case LB_RESETCONTENT16:
2506 case LB_RESETCONTENT:
2507 LISTBOX_ResetContent( hwnd, descr );
2508 LISTBOX_UpdateScroll( hwnd, descr );
2509 InvalidateRect( hwnd, NULL, TRUE );
2510 return 0;
2512 case LB_ADDSTRING16:
2513 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2514 /* fall through */
2515 case LB_ADDSTRING:
2517 INT ret;
2518 LPWSTR textW;
2519 if(unicode || !HAS_STRINGS(descr))
2520 textW = (LPWSTR)lParam;
2521 else
2523 LPSTR textA = (LPSTR)lParam;
2524 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2525 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2526 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2528 wParam = LISTBOX_FindStringPos( hwnd, descr, textW, FALSE );
2529 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2530 if (!unicode && HAS_STRINGS(descr))
2531 HeapFree(GetProcessHeap(), 0, textW);
2532 return ret;
2535 case LB_INSERTSTRING16:
2536 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2537 wParam = (INT)(INT16)wParam;
2538 /* fall through */
2539 case LB_INSERTSTRING:
2541 INT ret;
2542 LPWSTR textW;
2543 if(unicode || !HAS_STRINGS(descr))
2544 textW = (LPWSTR)lParam;
2545 else
2547 LPSTR textA = (LPSTR)lParam;
2548 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2549 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2550 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2552 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2553 if(!unicode && HAS_STRINGS(descr))
2554 HeapFree(GetProcessHeap(), 0, textW);
2555 return ret;
2558 case LB_ADDFILE16:
2559 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2560 /* fall through */
2561 case LB_ADDFILE:
2563 INT ret;
2564 LPWSTR textW;
2565 if(unicode || !HAS_STRINGS(descr))
2566 textW = (LPWSTR)lParam;
2567 else
2569 LPSTR textA = (LPSTR)lParam;
2570 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2571 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2572 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2574 wParam = LISTBOX_FindFileStrPos( hwnd, descr, textW );
2575 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2576 if(!unicode && HAS_STRINGS(descr))
2577 HeapFree(GetProcessHeap(), 0, textW);
2578 return ret;
2581 case LB_DELETESTRING16:
2582 case LB_DELETESTRING:
2583 if (LISTBOX_RemoveItem( hwnd, descr, wParam) != LB_ERR)
2584 return descr->nb_items;
2585 else
2586 return LB_ERR;
2588 case LB_GETITEMDATA16:
2589 case LB_GETITEMDATA:
2590 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2591 return LB_ERR;
2592 return descr->items[wParam].data;
2594 case LB_SETITEMDATA16:
2595 case LB_SETITEMDATA:
2596 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2597 return LB_ERR;
2598 descr->items[wParam].data = (DWORD)lParam;
2599 return LB_OKAY;
2601 case LB_GETCOUNT16:
2602 case LB_GETCOUNT:
2603 return descr->nb_items;
2605 case LB_GETTEXT16:
2606 lParam = (LPARAM)MapSL(lParam);
2607 /* fall through */
2608 case LB_GETTEXT:
2609 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2611 case LB_GETTEXTLEN16:
2612 /* fall through */
2613 case LB_GETTEXTLEN:
2614 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2615 return LB_ERR;
2616 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2617 if (unicode) return strlenW( descr->items[wParam].str );
2618 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2619 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2621 case LB_GETCURSEL16:
2622 case LB_GETCURSEL:
2623 if (descr->nb_items==0)
2624 return LB_ERR;
2625 if (!IS_MULTISELECT(descr))
2626 return descr->selected_item;
2627 /* else */
2628 if (descr->selected_item!=-1)
2629 return descr->selected_item;
2630 /* else */
2631 return descr->focus_item;
2632 /* otherwise, if the user tries to move the selection with the */
2633 /* arrow keys, we will give the application something to choke on */
2634 case LB_GETTOPINDEX16:
2635 case LB_GETTOPINDEX:
2636 return descr->top_item;
2638 case LB_GETITEMHEIGHT16:
2639 case LB_GETITEMHEIGHT:
2640 return LISTBOX_GetItemHeight( descr, wParam );
2642 case LB_SETITEMHEIGHT16:
2643 lParam = LOWORD(lParam);
2644 /* fall through */
2645 case LB_SETITEMHEIGHT:
2646 return LISTBOX_SetItemHeight( hwnd, descr, wParam, lParam, TRUE );
2648 case LB_ITEMFROMPOINT:
2650 POINT pt;
2651 RECT rect;
2653 pt.x = LOWORD(lParam);
2654 pt.y = HIWORD(lParam);
2655 rect.left = 0;
2656 rect.top = 0;
2657 rect.right = descr->width;
2658 rect.bottom = descr->height;
2660 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2661 !PtInRect( &rect, pt ) );
2664 case LB_SETCARETINDEX16:
2665 case LB_SETCARETINDEX:
2666 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2667 if (LISTBOX_SetCaretIndex( hwnd, descr, wParam, !lParam ) == LB_ERR)
2668 return LB_ERR;
2669 else if (ISWIN31)
2670 return wParam;
2671 else
2672 return LB_OKAY;
2674 case LB_GETCARETINDEX16:
2675 case LB_GETCARETINDEX:
2676 return descr->focus_item;
2678 case LB_SETTOPINDEX16:
2679 case LB_SETTOPINDEX:
2680 return LISTBOX_SetTopItem( hwnd, descr, wParam, TRUE );
2682 case LB_SETCOLUMNWIDTH16:
2683 case LB_SETCOLUMNWIDTH:
2684 return LISTBOX_SetColumnWidth( hwnd, descr, wParam );
2686 case LB_GETITEMRECT16:
2688 RECT rect;
2689 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2690 CONV_RECT32TO16( &rect, MapSL(lParam) );
2692 return ret;
2694 case LB_GETITEMRECT:
2695 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2697 case LB_FINDSTRING16:
2698 wParam = (INT)(INT16)wParam;
2699 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2700 /* fall through */
2701 case LB_FINDSTRING:
2703 INT ret;
2704 LPWSTR textW;
2705 if(unicode || !HAS_STRINGS(descr))
2706 textW = (LPWSTR)lParam;
2707 else
2709 LPSTR textA = (LPSTR)lParam;
2710 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2711 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2712 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2714 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2715 if(!unicode && HAS_STRINGS(descr))
2716 HeapFree(GetProcessHeap(), 0, textW);
2717 return ret;
2720 case LB_FINDSTRINGEXACT16:
2721 wParam = (INT)(INT16)wParam;
2722 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2723 /* fall through */
2724 case LB_FINDSTRINGEXACT:
2726 INT ret;
2727 LPWSTR textW;
2728 if(unicode || !HAS_STRINGS(descr))
2729 textW = (LPWSTR)lParam;
2730 else
2732 LPSTR textA = (LPSTR)lParam;
2733 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2734 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2735 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2737 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, TRUE );
2738 if(!unicode && HAS_STRINGS(descr))
2739 HeapFree(GetProcessHeap(), 0, textW);
2740 return ret;
2743 case LB_SELECTSTRING16:
2744 wParam = (INT)(INT16)wParam;
2745 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2746 /* fall through */
2747 case LB_SELECTSTRING:
2749 INT index;
2750 LPWSTR textW;
2752 if(HAS_STRINGS(descr))
2753 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2754 debugstr_a((LPSTR)lParam));
2755 if(unicode || !HAS_STRINGS(descr))
2756 textW = (LPWSTR)lParam;
2757 else
2759 LPSTR textA = (LPSTR)lParam;
2760 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2761 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2762 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2764 index = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2765 if(!unicode && HAS_STRINGS(descr))
2766 HeapFree(GetProcessHeap(), 0, textW);
2767 if (index != LB_ERR)
2769 LISTBOX_SetCaretIndex( hwnd, descr, index, TRUE );
2770 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
2772 return index;
2775 case LB_GETSEL16:
2776 wParam = (INT)(INT16)wParam;
2777 /* fall through */
2778 case LB_GETSEL:
2779 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2780 return LB_ERR;
2781 return descr->items[wParam].selected;
2783 case LB_SETSEL16:
2784 lParam = (INT)(INT16)lParam;
2785 /* fall through */
2786 case LB_SETSEL:
2787 return LISTBOX_SetSelection( hwnd, descr, lParam, wParam, FALSE );
2789 case LB_SETCURSEL16:
2790 wParam = (INT)(INT16)wParam;
2791 /* fall through */
2792 case LB_SETCURSEL:
2793 if (IS_MULTISELECT(descr)) return LB_ERR;
2794 LISTBOX_SetCaretIndex( hwnd, descr, wParam, TRUE );
2795 return LISTBOX_SetSelection( hwnd, descr, wParam, TRUE, FALSE );
2797 case LB_GETSELCOUNT16:
2798 case LB_GETSELCOUNT:
2799 return LISTBOX_GetSelCount( descr );
2801 case LB_GETSELITEMS16:
2802 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2804 case LB_GETSELITEMS:
2805 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2807 case LB_SELITEMRANGE16:
2808 case LB_SELITEMRANGE:
2809 if (LOWORD(lParam) <= HIWORD(lParam))
2810 return LISTBOX_SelectItemRange( hwnd, descr, LOWORD(lParam),
2811 HIWORD(lParam), wParam );
2812 else
2813 return LISTBOX_SelectItemRange( hwnd, descr, HIWORD(lParam),
2814 LOWORD(lParam), wParam );
2816 case LB_SELITEMRANGEEX16:
2817 case LB_SELITEMRANGEEX:
2818 if ((INT)lParam >= (INT)wParam)
2819 return LISTBOX_SelectItemRange( hwnd, descr, wParam, lParam, TRUE );
2820 else
2821 return LISTBOX_SelectItemRange( hwnd, descr, lParam, wParam, FALSE);
2823 case LB_GETHORIZONTALEXTENT16:
2824 case LB_GETHORIZONTALEXTENT:
2825 return descr->horz_extent;
2827 case LB_SETHORIZONTALEXTENT16:
2828 case LB_SETHORIZONTALEXTENT:
2829 return LISTBOX_SetHorizontalExtent( hwnd, descr, wParam );
2831 case LB_GETANCHORINDEX16:
2832 case LB_GETANCHORINDEX:
2833 return descr->anchor_item;
2835 case LB_SETANCHORINDEX16:
2836 wParam = (INT)(INT16)wParam;
2837 /* fall through */
2838 case LB_SETANCHORINDEX:
2839 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2840 return LB_ERR;
2841 descr->anchor_item = (INT)wParam;
2842 return LB_OKAY;
2844 case LB_DIR16:
2845 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2846 * be set automatically (this is different in Win32) */
2847 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2848 lParam = (LPARAM)MapSL(lParam);
2849 /* fall through */
2850 case LB_DIR:
2852 INT ret;
2853 LPWSTR textW;
2854 if(unicode)
2855 textW = (LPWSTR)lParam;
2856 else
2858 LPSTR textA = (LPSTR)lParam;
2859 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2860 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2861 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2863 ret = LISTBOX_Directory( hwnd, descr, wParam, textW, msg == LB_DIR );
2864 if(!unicode)
2865 HeapFree(GetProcessHeap(), 0, textW);
2866 return ret;
2869 case LB_GETLOCALE:
2870 return descr->locale;
2872 case LB_SETLOCALE:
2873 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2874 return LB_OKAY;
2876 case LB_INITSTORAGE:
2877 return LISTBOX_InitStorage( hwnd, descr, wParam );
2879 case LB_SETCOUNT:
2880 return LISTBOX_SetCount( hwnd, descr, (INT)wParam );
2882 case LB_SETTABSTOPS16:
2883 return LISTBOX_SetTabStops( hwnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2885 case LB_SETTABSTOPS:
2886 return LISTBOX_SetTabStops( hwnd, descr, wParam, (LPINT)lParam, FALSE );
2888 case LB_CARETON16:
2889 case LB_CARETON:
2890 if (descr->caret_on)
2891 return LB_OKAY;
2892 descr->caret_on = TRUE;
2893 if ((descr->focus_item != -1) && (descr->in_focus))
2894 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2895 return LB_OKAY;
2897 case LB_CARETOFF16:
2898 case LB_CARETOFF:
2899 if (!descr->caret_on)
2900 return LB_OKAY;
2901 descr->caret_on = FALSE;
2902 if ((descr->focus_item != -1) && (descr->in_focus))
2903 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2904 return LB_OKAY;
2906 case WM_DESTROY:
2907 return LISTBOX_Destroy( hwnd, descr );
2909 case WM_ENABLE:
2910 InvalidateRect( hwnd, NULL, TRUE );
2911 return 0;
2913 case WM_SETREDRAW:
2914 LISTBOX_SetRedraw( hwnd, descr, wParam != 0 );
2915 return 0;
2917 case WM_GETDLGCODE:
2918 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2920 case WM_PAINT:
2922 PAINTSTRUCT ps;
2923 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( hwnd, &ps );
2924 ret = LISTBOX_Paint( hwnd, descr, hdc );
2925 if( !wParam ) EndPaint( hwnd, &ps );
2927 return ret;
2928 case WM_SIZE:
2929 LISTBOX_UpdateSize( hwnd, descr );
2930 return 0;
2931 case WM_GETFONT:
2932 return (LRESULT)descr->font;
2933 case WM_SETFONT:
2934 LISTBOX_SetFont( hwnd, descr, (HFONT)wParam );
2935 if (lParam) InvalidateRect( hwnd, 0, TRUE );
2936 return 0;
2937 case WM_SETFOCUS:
2938 descr->in_focus = TRUE;
2939 descr->caret_on = TRUE;
2940 if (descr->focus_item != -1)
2941 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2942 SEND_NOTIFICATION( hwnd, descr, LBN_SETFOCUS );
2943 return 0;
2944 case WM_KILLFOCUS:
2945 descr->in_focus = FALSE;
2946 if ((descr->focus_item != -1) && descr->caret_on)
2947 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2948 SEND_NOTIFICATION( hwnd, descr, LBN_KILLFOCUS );
2949 return 0;
2950 case WM_HSCROLL:
2951 return LISTBOX_HandleHScroll( hwnd, descr, wParam );
2952 case WM_VSCROLL:
2953 return LISTBOX_HandleVScroll( hwnd, descr, wParam );
2954 case WM_MOUSEWHEEL:
2955 if (wParam & (MK_SHIFT | MK_CONTROL))
2956 return DefWindowProcW( hwnd, msg, wParam, lParam );
2957 return LISTBOX_HandleMouseWheel( hwnd, descr, wParam );
2958 case WM_LBUTTONDOWN:
2959 return LISTBOX_HandleLButtonDown( hwnd, descr, wParam,
2960 (INT16)LOWORD(lParam),
2961 (INT16)HIWORD(lParam) );
2962 case WM_LBUTTONDBLCLK:
2963 if (descr->style & LBS_NOTIFY)
2964 SEND_NOTIFICATION( hwnd, descr, LBN_DBLCLK );
2965 return 0;
2966 case WM_MOUSEMOVE:
2967 if (GetCapture() == hwnd)
2968 LISTBOX_HandleMouseMove( hwnd, descr, (INT16)LOWORD(lParam),
2969 (INT16)HIWORD(lParam) );
2970 return 0;
2971 case WM_LBUTTONUP:
2972 return LISTBOX_HandleLButtonUp( hwnd, descr );
2973 case WM_KEYDOWN:
2974 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
2975 case WM_CHAR:
2977 WCHAR charW;
2978 if(unicode)
2979 charW = (WCHAR)wParam;
2980 else
2982 CHAR charA = (CHAR)wParam;
2983 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
2985 return LISTBOX_HandleChar( hwnd, descr, charW );
2987 case WM_SYSTIMER:
2988 return LISTBOX_HandleSystemTimer( hwnd, descr );
2989 case WM_ERASEBKGND:
2990 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2992 RECT rect;
2993 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
2994 wParam, (LPARAM)hwnd );
2995 TRACE("hbrush = %p\n", hbrush);
2996 if(!hbrush)
2997 hbrush = GetSysColorBrush(COLOR_WINDOW);
2998 if(hbrush)
3000 GetClientRect(hwnd, &rect);
3001 FillRect((HDC)wParam, &rect, hbrush);
3004 return 1;
3005 case WM_DROPFILES:
3006 if( !descr->lphc )
3007 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3008 SendMessageA( descr->owner, msg, wParam, lParam );
3009 break;
3011 default:
3012 if ((msg >= WM_USER) && (msg < 0xc000))
3013 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3014 hwnd, msg, wParam, lParam );
3015 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3016 DefWindowProcA( hwnd, msg, wParam, lParam );
3018 return 0;
3021 /***********************************************************************
3022 * ListBoxWndProcA
3024 * This is just a wrapper for the real wndproc, it only does window locking
3025 * and unlocking.
3027 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3029 if (!IsWindow(hwnd)) return 0;
3030 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3033 /***********************************************************************
3034 * ListBoxWndProcW
3036 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3038 if (!IsWindow(hwnd)) return 0;
3039 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );
3042 /***********************************************************************
3043 * ComboLBWndProc_common
3045 * The real combo listbox wndproc
3047 static LRESULT WINAPI ComboLBWndProc_common( HWND hwnd, UINT msg,
3048 WPARAM wParam, LPARAM lParam, BOOL unicode )
3050 LRESULT lRet = 0;
3051 LB_DESCR *descr;
3052 LPHEADCOMBO lphc;
3054 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
3056 if (msg == WM_CREATE)
3058 CREATESTRUCTA *lpcs = (CREATESTRUCTA *)lParam;
3059 TRACE_(combo)("\tpassed parent handle = %p\n",lpcs->lpCreateParams);
3060 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3061 return LISTBOX_Create( hwnd, lphc );
3063 /* Ignore all other messages before we get a WM_CREATE */
3064 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3065 DefWindowProcA( hwnd, msg, wParam, lParam );
3068 TRACE_(combo)("[%p]: msg %s wp %08x lp %08lx\n",
3069 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
3071 if ((lphc = descr->lphc) != NULL)
3073 switch( msg )
3075 case WM_MOUSEMOVE:
3076 if ( (TWEAK_WineLook > WIN31_LOOK) &&
3077 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
3079 POINT mousePos;
3080 BOOL captured;
3081 RECT clientRect;
3083 mousePos.x = (INT16)LOWORD(lParam);
3084 mousePos.y = (INT16)HIWORD(lParam);
3087 * If we are in a dropdown combobox, we simulate that
3088 * the mouse is captured to show the tracking of the item.
3090 GetClientRect(hwnd, &clientRect);
3092 if (PtInRect( &clientRect, mousePos ))
3094 captured = descr->captured;
3095 descr->captured = TRUE;
3097 LISTBOX_HandleMouseMove( hwnd, descr,
3098 mousePos.x, mousePos.y);
3100 descr->captured = captured;
3103 else
3105 LISTBOX_HandleMouseMove( hwnd, descr,
3106 mousePos.x, mousePos.y);
3109 return 0;
3112 /* else we are in Win3.1 look, go with the default behavior. */
3113 break;
3115 case WM_LBUTTONUP:
3116 if (TWEAK_WineLook > WIN31_LOOK)
3118 POINT mousePos;
3119 RECT clientRect;
3122 * If the mouse button "up" is not in the listbox,
3123 * we make sure there is no selection by re-selecting the
3124 * item that was selected when the listbox was made visible.
3126 mousePos.x = (INT16)LOWORD(lParam);
3127 mousePos.y = (INT16)HIWORD(lParam);
3129 GetClientRect(hwnd, &clientRect);
3132 * When the user clicks outside the combobox and the focus
3133 * is lost, the owning combobox will send a fake buttonup with
3134 * 0xFFFFFFF as the mouse location, we must also revert the
3135 * selection to the original selection.
3137 if ( (lParam == (LPARAM)-1) ||
3138 (!PtInRect( &clientRect, mousePos )) )
3140 LISTBOX_MoveCaret( hwnd, descr, lphc->droppedIndex, FALSE );
3143 return LISTBOX_HandleLButtonUp( hwnd, descr );
3144 case WM_LBUTTONDBLCLK:
3145 case WM_LBUTTONDOWN:
3146 return LISTBOX_HandleLButtonDownCombo(hwnd, descr, msg, wParam,
3147 (INT16)LOWORD(lParam),
3148 (INT16)HIWORD(lParam) );
3149 case WM_NCACTIVATE:
3150 return FALSE;
3151 case WM_KEYDOWN:
3152 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3154 /* for some reason(?) Windows makes it possible to
3155 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3157 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3158 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3159 && (wParam == VK_DOWN || wParam == VK_UP)) )
3161 COMBO_FlipListbox( lphc, FALSE, FALSE );
3162 return 0;
3165 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3167 case LB_SETCURSEL16:
3168 case LB_SETCURSEL:
3169 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3170 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3171 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3172 return lRet;
3173 case WM_NCDESTROY:
3174 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3175 lphc->hWndLBox = 0;
3176 break;
3180 /* default handling: call listbox wnd proc */
3181 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3182 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3184 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3186 return lRet;
3189 /***********************************************************************
3190 * ComboLBWndProcA
3192 * NOTE: in Windows, winproc address of the ComboLBox is the same
3193 * as that of the Listbox.
3195 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3197 if (!IsWindow(hwnd)) return 0;
3198 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3201 /***********************************************************************
3202 * ComboLBWndProcW
3204 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3206 if (!IsWindow(hwnd)) return 0;
3207 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, TRUE );