Use vertex arrays when possible.
[wine.git] / controls / listbox.c
blobbd14a895e9a9ed2f8c0dc076bb79ed82157b2de3
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 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
720 nb_items * sizeof(LB_ITEMDATA));
722 else {
723 item = HeapAlloc( GetProcessHeap(), 0,
724 nb_items * sizeof(LB_ITEMDATA));
727 if (!item)
729 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
730 return LB_ERRSPACE;
732 descr->items = item;
733 return LB_OKAY;
737 /***********************************************************************
738 * LISTBOX_SetTabStops
740 static BOOL LISTBOX_SetTabStops( HWND hwnd, LB_DESCR *descr, INT count,
741 LPINT tabs, BOOL short_ints )
743 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
744 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
745 if (!(descr->nb_tabs = count))
747 descr->tabs = NULL;
748 return TRUE;
750 /* FIXME: count = 1 */
751 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
752 descr->nb_tabs * sizeof(INT) )))
753 return FALSE;
754 if (short_ints)
756 INT i;
757 LPINT16 p = (LPINT16)tabs;
759 TRACE("[%p]: settabstops ", hwnd );
760 for (i = 0; i < descr->nb_tabs; i++) {
761 descr->tabs[i] = *p++<<1; /* FIXME */
762 if (TRACE_ON(listbox)) TRACE("%hd ", descr->tabs[i]);
764 if (TRACE_ON(listbox)) TRACE("\n");
766 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
767 /* FIXME: repaint the window? */
768 return TRUE;
772 /***********************************************************************
773 * LISTBOX_GetText
775 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
777 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
778 if (HAS_STRINGS(descr))
780 if (!lParam)
781 return strlenW(descr->items[index].str);
783 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
785 if(unicode)
787 LPWSTR buffer = (LPWSTR)lParam;
788 strcpyW( buffer, descr->items[index].str );
789 return strlenW(buffer);
791 else
793 LPSTR buffer = (LPSTR)lParam;
794 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
796 } else {
797 if (lParam)
798 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
799 return sizeof(DWORD);
804 /***********************************************************************
805 * LISTBOX_FindStringPos
807 * Find the nearest string located before a given string in sort order.
808 * If 'exact' is TRUE, return an error if we don't get an exact match.
810 static INT LISTBOX_FindStringPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str,
811 BOOL exact )
813 INT index, min, max, res = -1;
815 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
816 min = 0;
817 max = descr->nb_items;
818 while (min != max)
820 index = (min + max) / 2;
821 if (HAS_STRINGS(descr))
822 res = lstrcmpiW( str, descr->items[index].str);
823 else
825 COMPAREITEMSTRUCT cis;
826 UINT id = GetWindowLongA( hwnd, GWL_ID );
828 cis.CtlType = ODT_LISTBOX;
829 cis.CtlID = id;
830 cis.hwndItem = hwnd;
831 /* note that some application (MetaStock) expects the second item
832 * to be in the listbox */
833 cis.itemID1 = -1;
834 cis.itemData1 = (DWORD)str;
835 cis.itemID2 = index;
836 cis.itemData2 = descr->items[index].data;
837 cis.dwLocaleId = descr->locale;
838 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
840 if (!res) return index;
841 if (res < 0) max = index;
842 else min = index + 1;
844 return exact ? -1 : max;
848 /***********************************************************************
849 * LISTBOX_FindFileStrPos
851 * Find the nearest string located before a given string in directory
852 * sort order (i.e. first files, then directories, then drives).
854 static INT LISTBOX_FindFileStrPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str )
856 INT min, max, res = -1;
858 if (!HAS_STRINGS(descr))
859 return LISTBOX_FindStringPos( hwnd, descr, str, FALSE );
860 min = 0;
861 max = descr->nb_items;
862 while (min != max)
864 INT index = (min + max) / 2;
865 LPCWSTR p = descr->items[index].str;
866 if (*p == '[') /* drive or directory */
868 if (*str != '[') res = -1;
869 else if (p[1] == '-') /* drive */
871 if (str[1] == '-') res = str[2] - p[2];
872 else res = -1;
874 else /* directory */
876 if (str[1] == '-') res = 1;
877 else res = lstrcmpiW( str, p );
880 else /* filename */
882 if (*str == '[') res = 1;
883 else res = lstrcmpiW( str, p );
885 if (!res) return index;
886 if (res < 0) max = index;
887 else min = index + 1;
889 return max;
893 /***********************************************************************
894 * LISTBOX_FindString
896 * Find the item beginning with a given string.
898 static INT LISTBOX_FindString( HWND hwnd, LB_DESCR *descr, INT start,
899 LPCWSTR str, BOOL exact )
901 INT i;
902 LB_ITEMDATA *item;
904 if (start >= descr->nb_items) start = -1;
905 item = descr->items + start + 1;
906 if (HAS_STRINGS(descr))
908 if (!str || ! str[0] ) return LB_ERR;
909 if (exact)
911 for (i = start + 1; i < descr->nb_items; i++, item++)
912 if (!lstrcmpiW( str, item->str )) return i;
913 for (i = 0, item = descr->items; i <= start; i++, item++)
914 if (!lstrcmpiW( str, item->str )) return i;
916 else
918 /* Special case for drives and directories: ignore prefix */
919 #define CHECK_DRIVE(item) \
920 if ((item)->str[0] == '[') \
922 if (!strncmpiW( str, (item)->str+1, len )) return i; \
923 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
924 return i; \
927 INT len = strlenW(str);
928 for (i = start + 1; i < descr->nb_items; i++, item++)
930 if (!strncmpiW( str, item->str, len )) return i;
931 CHECK_DRIVE(item);
933 for (i = 0, item = descr->items; i <= start; i++, item++)
935 if (!strncmpiW( str, item->str, len )) return i;
936 CHECK_DRIVE(item);
938 #undef CHECK_DRIVE
941 else
943 if (exact && (descr->style & LBS_SORT))
944 /* If sorted, use a WM_COMPAREITEM binary search */
945 return LISTBOX_FindStringPos( hwnd, descr, str, TRUE );
947 /* Otherwise use a linear search */
948 for (i = start + 1; i < descr->nb_items; i++, item++)
949 if (item->data == (DWORD)str) return i;
950 for (i = 0, item = descr->items; i <= start; i++, item++)
951 if (item->data == (DWORD)str) return i;
953 return LB_ERR;
957 /***********************************************************************
958 * LISTBOX_GetSelCount
960 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
962 INT i, count;
963 LB_ITEMDATA *item = descr->items;
965 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
966 for (i = count = 0; i < descr->nb_items; i++, item++)
967 if (item->selected) count++;
968 return count;
972 /***********************************************************************
973 * LISTBOX_GetSelItems16
975 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
977 INT i, count;
978 LB_ITEMDATA *item = descr->items;
980 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
981 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
982 if (item->selected) array[count++] = (INT16)i;
983 return count;
987 /***********************************************************************
988 * LISTBOX_GetSelItems
990 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
992 INT i, count;
993 LB_ITEMDATA *item = descr->items;
995 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
996 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
997 if (item->selected) array[count++] = i;
998 return count;
1002 /***********************************************************************
1003 * LISTBOX_Paint
1005 static LRESULT LISTBOX_Paint( HWND hwnd, LB_DESCR *descr, HDC hdc )
1007 INT i, col_pos = descr->page_size - 1;
1008 RECT rect;
1009 RECT focusRect = {-1, -1, -1, -1};
1010 HFONT oldFont = 0;
1011 HBRUSH hbrush, oldBrush = 0;
1013 if (descr->style & LBS_NOREDRAW) return 0;
1015 SetRect( &rect, 0, 0, descr->width, descr->height );
1016 if (descr->style & LBS_MULTICOLUMN)
1017 rect.right = rect.left + descr->column_width;
1018 else if (descr->horz_pos)
1020 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1021 rect.right += descr->horz_pos;
1024 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1025 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1026 (WPARAM)hdc, (LPARAM)hwnd );
1027 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1028 if (!IsWindowEnabled(hwnd)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1030 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1031 (descr->in_focus))
1033 /* Special case for empty listbox: paint focus rect */
1034 rect.bottom = rect.top + descr->item_height;
1035 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, descr->focus_item,
1036 ODA_FOCUS, FALSE );
1037 rect.top = rect.bottom;
1040 /* Paint all the item, regarding the selection
1041 Focus state will be painted after */
1043 for (i = descr->top_item; i < descr->nb_items; i++)
1045 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1046 rect.bottom = rect.top + descr->item_height;
1047 else
1048 rect.bottom = rect.top + descr->items[i].height;
1050 if (i == descr->focus_item)
1052 /* keep the focus rect, to paint the focus item after */
1053 focusRect.left = rect.left;
1054 focusRect.right = rect.right;
1055 focusRect.top = rect.top;
1056 focusRect.bottom = rect.bottom;
1058 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1059 rect.top = rect.bottom;
1061 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1063 if (!IS_OWNERDRAW(descr))
1065 /* Clear the bottom of the column */
1066 if (rect.top < descr->height)
1068 rect.bottom = descr->height;
1069 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1070 &rect, NULL, 0, NULL );
1074 /* Go to the next column */
1075 rect.left += descr->column_width;
1076 rect.right += descr->column_width;
1077 rect.top = 0;
1078 col_pos = descr->page_size - 1;
1080 else
1082 col_pos--;
1083 if (rect.top >= descr->height) break;
1087 /* Paint the focus item now */
1088 if (focusRect.top != focusRect.bottom && descr->caret_on)
1089 LISTBOX_PaintItem( hwnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1091 if (!IS_OWNERDRAW(descr))
1093 /* Clear the remainder of the client area */
1094 if (rect.top < descr->height)
1096 rect.bottom = descr->height;
1097 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1098 &rect, NULL, 0, NULL );
1100 if (rect.right < descr->width)
1102 rect.left = rect.right;
1103 rect.right = descr->width;
1104 rect.top = 0;
1105 rect.bottom = descr->height;
1106 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1107 &rect, NULL, 0, NULL );
1110 if (oldFont) SelectObject( hdc, oldFont );
1111 if (oldBrush) SelectObject( hdc, oldBrush );
1112 return 0;
1116 /***********************************************************************
1117 * LISTBOX_InvalidateItems
1119 * Invalidate all items from a given item. If the specified item is not
1120 * visible, nothing happens.
1122 static void LISTBOX_InvalidateItems( HWND hwnd, LB_DESCR *descr, INT index )
1124 RECT rect;
1126 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1128 if (descr->style & LBS_NOREDRAW)
1130 descr->style |= LBS_DISPLAYCHANGED;
1131 return;
1133 rect.bottom = descr->height;
1134 InvalidateRect( hwnd, &rect, TRUE );
1135 if (descr->style & LBS_MULTICOLUMN)
1137 /* Repaint the other columns */
1138 rect.left = rect.right;
1139 rect.right = descr->width;
1140 rect.top = 0;
1141 InvalidateRect( hwnd, &rect, TRUE );
1147 /***********************************************************************
1148 * LISTBOX_GetItemHeight
1150 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1152 if (descr->style & LBS_OWNERDRAWVARIABLE)
1154 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1155 return descr->items[index].height;
1157 else return descr->item_height;
1161 /***********************************************************************
1162 * LISTBOX_SetItemHeight
1164 static LRESULT LISTBOX_SetItemHeight( HWND hwnd, LB_DESCR *descr, INT index,
1165 INT height, BOOL repaint )
1167 if (!height) height = 1;
1169 if (descr->style & LBS_OWNERDRAWVARIABLE)
1171 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1172 TRACE("[%p]: item %d height = %d\n", hwnd, index, height );
1173 descr->items[index].height = height;
1174 LISTBOX_UpdateScroll( hwnd, descr );
1175 if (repaint)
1176 LISTBOX_InvalidateItems( hwnd, descr, index );
1178 else if (height != descr->item_height)
1180 TRACE("[%p]: new height = %d\n", hwnd, height );
1181 descr->item_height = height;
1182 LISTBOX_UpdatePage( hwnd, descr );
1183 LISTBOX_UpdateScroll( hwnd, descr );
1184 if (repaint)
1185 InvalidateRect( hwnd, 0, TRUE );
1187 return LB_OKAY;
1191 /***********************************************************************
1192 * LISTBOX_SetHorizontalPos
1194 static void LISTBOX_SetHorizontalPos( HWND hwnd, LB_DESCR *descr, INT pos )
1196 INT diff;
1198 if (pos > descr->horz_extent - descr->width)
1199 pos = descr->horz_extent - descr->width;
1200 if (pos < 0) pos = 0;
1201 if (!(diff = descr->horz_pos - pos)) return;
1202 TRACE("[%p]: new horz pos = %d\n", hwnd, pos );
1203 descr->horz_pos = pos;
1204 LISTBOX_UpdateScroll( hwnd, descr );
1205 if (abs(diff) < descr->width)
1206 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
1207 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1208 else
1209 InvalidateRect( hwnd, NULL, TRUE );
1213 /***********************************************************************
1214 * LISTBOX_SetHorizontalExtent
1216 static LRESULT LISTBOX_SetHorizontalExtent( HWND hwnd, LB_DESCR *descr,
1217 INT extent )
1219 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1220 return LB_OKAY;
1221 if (extent <= 0) extent = 1;
1222 if (extent == descr->horz_extent) return LB_OKAY;
1223 TRACE("[%p]: new horz extent = %d\n", hwnd, extent );
1224 descr->horz_extent = extent;
1225 if (descr->horz_pos > extent - descr->width)
1226 LISTBOX_SetHorizontalPos( hwnd, descr, extent - descr->width );
1227 else
1228 LISTBOX_UpdateScroll( hwnd, descr );
1229 return LB_OKAY;
1233 /***********************************************************************
1234 * LISTBOX_SetColumnWidth
1236 static LRESULT LISTBOX_SetColumnWidth( HWND hwnd, LB_DESCR *descr, INT width)
1238 if (width == descr->column_width) return LB_OKAY;
1239 TRACE("[%p]: new column width = %d\n", hwnd, width );
1240 descr->column_width = width;
1241 LISTBOX_UpdatePage( hwnd, descr );
1242 return LB_OKAY;
1246 /***********************************************************************
1247 * LISTBOX_SetFont
1249 * Returns the item height.
1251 static INT LISTBOX_SetFont( HWND hwnd, LB_DESCR *descr, HFONT font )
1253 HDC hdc;
1254 HFONT oldFont = 0;
1255 TEXTMETRICW tm;
1257 descr->font = font;
1259 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE )))
1261 ERR("unable to get DC.\n" );
1262 return 16;
1264 if (font) oldFont = SelectObject( hdc, font );
1265 GetTextMetricsW( hdc, &tm );
1266 if (oldFont) SelectObject( hdc, oldFont );
1267 ReleaseDC( hwnd, hdc );
1268 if (!IS_OWNERDRAW(descr))
1269 LISTBOX_SetItemHeight( hwnd, descr, 0, tm.tmHeight, FALSE );
1270 return tm.tmHeight ;
1274 /***********************************************************************
1275 * LISTBOX_MakeItemVisible
1277 * Make sure that a given item is partially or fully visible.
1279 static void LISTBOX_MakeItemVisible( HWND hwnd, LB_DESCR *descr, INT index,
1280 BOOL fully )
1282 INT top;
1284 if (index <= descr->top_item) top = index;
1285 else if (descr->style & LBS_MULTICOLUMN)
1287 INT cols = descr->width;
1288 if (!fully) cols += descr->column_width - 1;
1289 if (cols >= descr->column_width) cols /= descr->column_width;
1290 else cols = 1;
1291 if (index < descr->top_item + (descr->page_size * cols)) return;
1292 top = index - descr->page_size * (cols - 1);
1294 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1296 INT height = fully ? descr->items[index].height : 1;
1297 for (top = index; top > descr->top_item; top--)
1298 if ((height += descr->items[top-1].height) > descr->height) break;
1300 else
1302 if (index < descr->top_item + descr->page_size) return;
1303 if (!fully && (index == descr->top_item + descr->page_size) &&
1304 (descr->height > (descr->page_size * descr->item_height))) return;
1305 top = index - descr->page_size + 1;
1307 LISTBOX_SetTopItem( hwnd, descr, top, TRUE );
1310 /***********************************************************************
1311 * LISTBOX_SetCaretIndex
1313 * NOTES
1314 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1317 static LRESULT LISTBOX_SetCaretIndex( HWND hwnd, LB_DESCR *descr, INT index,
1318 BOOL fully_visible )
1320 INT oldfocus = descr->focus_item;
1322 if (descr->style & LBS_NOSEL) return LB_ERR;
1323 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1324 if (index == oldfocus) return LB_OKAY;
1325 descr->focus_item = index;
1326 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1327 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1329 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1330 if (descr->caret_on && (descr->in_focus))
1331 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1333 return LB_OKAY;
1337 /***********************************************************************
1338 * LISTBOX_SelectItemRange
1340 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1342 static LRESULT LISTBOX_SelectItemRange( HWND hwnd, LB_DESCR *descr, INT first,
1343 INT last, BOOL on )
1345 INT i;
1347 /* A few sanity checks */
1349 if (descr->style & LBS_NOSEL) return LB_ERR;
1350 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1351 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1352 if (last == -1) last = descr->nb_items - 1;
1353 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1354 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1355 /* selected_item reflects last selected/unselected item on multiple sel */
1356 descr->selected_item = last;
1358 if (on) /* Turn selection on */
1360 for (i = first; i <= last; i++)
1362 if (descr->items[i].selected) continue;
1363 descr->items[i].selected = TRUE;
1364 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1366 LISTBOX_SetCaretIndex( hwnd, descr, last, TRUE );
1368 else /* Turn selection off */
1370 for (i = first; i <= last; i++)
1372 if (!descr->items[i].selected) continue;
1373 descr->items[i].selected = FALSE;
1374 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1377 return LB_OKAY;
1380 /***********************************************************************
1381 * LISTBOX_SetSelection
1383 static LRESULT LISTBOX_SetSelection( HWND hwnd, LB_DESCR *descr, INT index,
1384 BOOL on, BOOL send_notify )
1386 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1388 if (descr->style & LBS_NOSEL) return LB_ERR;
1389 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1390 if (descr->style & LBS_MULTIPLESEL)
1392 if (index == -1) /* Select all items */
1393 return LISTBOX_SelectItemRange( hwnd, descr, 0, -1, on );
1394 else /* Only one item */
1395 return LISTBOX_SelectItemRange( hwnd, descr, index, index, on );
1397 else
1399 INT oldsel = descr->selected_item;
1400 if (index == oldsel) return LB_OKAY;
1401 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1402 if (index != -1) descr->items[index].selected = TRUE;
1403 descr->selected_item = index;
1404 if (oldsel != -1) LISTBOX_RepaintItem( hwnd, descr, oldsel, ODA_SELECT );
1405 if (index != -1) LISTBOX_RepaintItem( hwnd, descr, index, ODA_SELECT );
1406 if (send_notify && descr->nb_items) SEND_NOTIFICATION( hwnd, descr,
1407 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1408 else
1409 if( descr->lphc ) /* set selection change flag for parent combo */
1410 descr->lphc->wState |= CBF_SELCHANGE;
1412 return LB_OKAY;
1416 /***********************************************************************
1417 * LISTBOX_MoveCaret
1419 * Change the caret position and extend the selection to the new caret.
1421 static void LISTBOX_MoveCaret( HWND hwnd, LB_DESCR *descr, INT index,
1422 BOOL fully_visible )
1424 INT oldfocus = descr->focus_item;
1426 if ((index < 0) || (index >= descr->nb_items))
1427 return;
1429 /* Important, repaint needs to be done in this order if
1430 you want to mimic Windows behavior:
1431 1. Remove the focus and paint the item
1432 2. Remove the selection and paint the item(s)
1433 3. Set the selection and repaint the item(s)
1434 4. Set the focus to 'index' and repaint the item */
1436 /* 1. remove the focus and repaint the item */
1437 descr->focus_item = -1;
1438 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1439 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1441 /* 2. then turn off the previous selection */
1442 /* 3. repaint the new selected item */
1443 if (descr->style & LBS_EXTENDEDSEL)
1445 if (descr->anchor_item != -1)
1447 INT first = min( index, descr->anchor_item );
1448 INT last = max( index, descr->anchor_item );
1449 if (first > 0)
1450 LISTBOX_SelectItemRange( hwnd, descr, 0, first - 1, FALSE );
1451 LISTBOX_SelectItemRange( hwnd, descr, last + 1, -1, FALSE );
1452 LISTBOX_SelectItemRange( hwnd, descr, first, last, TRUE );
1455 else if (!(descr->style & LBS_MULTIPLESEL))
1457 /* Set selection to new caret item */
1458 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
1461 /* 4. repaint the new item with the focus */
1462 descr->focus_item = index;
1463 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1464 if (descr->caret_on && (descr->in_focus))
1465 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1469 /***********************************************************************
1470 * LISTBOX_InsertItem
1472 static LRESULT LISTBOX_InsertItem( HWND hwnd, LB_DESCR *descr, INT index,
1473 LPWSTR str, DWORD data )
1475 LB_ITEMDATA *item;
1476 INT max_items;
1477 INT oldfocus = descr->focus_item;
1479 if (index == -1) index = descr->nb_items;
1480 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1481 if (!descr->items) max_items = 0;
1482 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1483 if (descr->nb_items == max_items)
1485 /* We need to grow the array */
1486 max_items += LB_ARRAY_GRANULARITY;
1487 if (descr->items)
1488 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1489 max_items * sizeof(LB_ITEMDATA) );
1490 else
1491 item = HeapAlloc( GetProcessHeap(), 0,
1492 max_items * sizeof(LB_ITEMDATA) );
1493 if (!item)
1495 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1496 return LB_ERRSPACE;
1498 descr->items = item;
1501 /* Insert the item structure */
1503 item = &descr->items[index];
1504 if (index < descr->nb_items)
1505 RtlMoveMemory( item + 1, item,
1506 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1507 item->str = str;
1508 item->data = data;
1509 item->height = 0;
1510 item->selected = FALSE;
1511 descr->nb_items++;
1513 /* Get item height */
1515 if (descr->style & LBS_OWNERDRAWVARIABLE)
1517 MEASUREITEMSTRUCT mis;
1518 UINT id = GetWindowLongA( hwnd, GWL_ID );
1520 mis.CtlType = ODT_LISTBOX;
1521 mis.CtlID = id;
1522 mis.itemID = index;
1523 mis.itemData = descr->items[index].data;
1524 mis.itemHeight = descr->item_height;
1525 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1526 item->height = mis.itemHeight ? mis.itemHeight : 1;
1527 TRACE("[%p]: measure item %d (%s) = %d\n",
1528 hwnd, index, str ? debugstr_w(str) : "", item->height );
1531 /* Repaint the items */
1533 LISTBOX_UpdateScroll( hwnd, descr );
1534 LISTBOX_InvalidateItems( hwnd, descr, index );
1536 /* Move selection and focused item */
1537 /* If listbox was empty, set focus to the first item */
1538 if (descr->nb_items == 1)
1539 LISTBOX_SetCaretIndex( hwnd, descr, 0, FALSE );
1540 /* single select don't change selection index in win31 */
1541 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1543 descr->selected_item++;
1544 LISTBOX_SetSelection( hwnd, descr, descr->selected_item-1, TRUE, FALSE );
1546 else
1548 if (index <= descr->selected_item)
1550 descr->selected_item++;
1551 descr->focus_item = oldfocus; /* focus not changed */
1554 return LB_OKAY;
1558 /***********************************************************************
1559 * LISTBOX_InsertString
1561 static LRESULT LISTBOX_InsertString( HWND hwnd, LB_DESCR *descr, INT index,
1562 LPCWSTR str )
1564 LPWSTR new_str = NULL;
1565 DWORD data = 0;
1566 LRESULT ret;
1568 if (HAS_STRINGS(descr))
1570 static const WCHAR empty_stringW[] = { 0 };
1571 if (!str) str = empty_stringW;
1572 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1574 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1575 return LB_ERRSPACE;
1577 strcpyW(new_str, str);
1579 else data = (DWORD)str;
1581 if (index == -1) index = descr->nb_items;
1582 if ((ret = LISTBOX_InsertItem( hwnd, descr, index, new_str, data )) != 0)
1584 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1585 return ret;
1588 TRACE("[%p]: added item %d %s\n",
1589 hwnd, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1590 return index;
1594 /***********************************************************************
1595 * LISTBOX_DeleteItem
1597 * Delete the content of an item. 'index' must be a valid index.
1599 static void LISTBOX_DeleteItem( HWND hwnd, LB_DESCR *descr, INT index )
1601 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1602 * while Win95 sends it for all items with user data.
1603 * It's probably better to send it too often than not
1604 * often enough, so this is what we do here.
1606 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1608 DELETEITEMSTRUCT dis;
1609 UINT id = GetWindowLongA( hwnd, GWL_ID );
1611 dis.CtlType = ODT_LISTBOX;
1612 dis.CtlID = id;
1613 dis.itemID = index;
1614 dis.hwndItem = hwnd;
1615 dis.itemData = descr->items[index].data;
1616 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1618 if (HAS_STRINGS(descr) && descr->items[index].str)
1619 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1623 /***********************************************************************
1624 * LISTBOX_RemoveItem
1626 * Remove an item from the listbox and delete its content.
1628 static LRESULT LISTBOX_RemoveItem( HWND hwnd, LB_DESCR *descr, INT index )
1630 LB_ITEMDATA *item;
1631 INT max_items;
1633 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1634 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1636 /* We need to invalidate the original rect instead of the updated one. */
1637 LISTBOX_InvalidateItems( hwnd, descr, index );
1639 LISTBOX_DeleteItem( hwnd, descr, index );
1641 /* Remove the item */
1643 item = &descr->items[index];
1644 if (index < descr->nb_items-1)
1645 RtlMoveMemory( item, item + 1,
1646 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1647 descr->nb_items--;
1648 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1650 /* Shrink the item array if possible */
1652 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1653 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1655 max_items -= LB_ARRAY_GRANULARITY;
1656 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1657 max_items * sizeof(LB_ITEMDATA) );
1658 if (item) descr->items = item;
1660 /* Repaint the items */
1662 LISTBOX_UpdateScroll( hwnd, descr );
1663 /* if we removed the scrollbar, reset the top of the list
1664 (correct for owner-drawn ???) */
1665 if (descr->nb_items == descr->page_size)
1666 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1668 /* Move selection and focused item */
1669 if (!IS_MULTISELECT(descr))
1671 if (index == descr->selected_item)
1672 descr->selected_item = -1;
1673 else if (index < descr->selected_item)
1675 descr->selected_item--;
1676 if (ISWIN31) /* win 31 do not change the selected item number */
1677 LISTBOX_SetSelection( hwnd, descr, descr->selected_item + 1, TRUE, FALSE);
1681 if (descr->focus_item >= descr->nb_items)
1683 descr->focus_item = descr->nb_items - 1;
1684 if (descr->focus_item < 0) descr->focus_item = 0;
1686 return LB_OKAY;
1690 /***********************************************************************
1691 * LISTBOX_ResetContent
1693 static void LISTBOX_ResetContent( HWND hwnd, LB_DESCR *descr )
1695 INT i;
1697 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( hwnd, descr, i );
1698 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1699 descr->nb_items = 0;
1700 descr->top_item = 0;
1701 descr->selected_item = -1;
1702 descr->focus_item = 0;
1703 descr->anchor_item = -1;
1704 descr->items = NULL;
1708 /***********************************************************************
1709 * LISTBOX_SetCount
1711 static LRESULT LISTBOX_SetCount( HWND hwnd, LB_DESCR *descr, INT count )
1713 LRESULT ret;
1715 if (HAS_STRINGS(descr)) return LB_ERR;
1716 /* FIXME: this is far from optimal... */
1717 if (count > descr->nb_items)
1719 while (count > descr->nb_items)
1720 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, 0 )) < 0)
1721 return ret;
1723 else if (count < descr->nb_items)
1725 while (count < descr->nb_items)
1726 if ((ret = LISTBOX_RemoveItem( hwnd, descr, -1 )) < 0)
1727 return ret;
1729 return LB_OKAY;
1733 /***********************************************************************
1734 * LISTBOX_Directory
1736 static LRESULT LISTBOX_Directory( HWND hwnd, LB_DESCR *descr, UINT attrib,
1737 LPCWSTR filespec, BOOL long_names )
1739 HANDLE handle;
1740 LRESULT ret = LB_OKAY;
1741 WIN32_FIND_DATAW entry;
1742 int pos;
1744 /* don't scan directory if we just want drives exclusively */
1745 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1746 /* scan directory */
1747 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1749 int le = GetLastError();
1750 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1752 else
1756 WCHAR buffer[270];
1757 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1759 static const WCHAR bracketW[] = { ']',0 };
1760 static const WCHAR dotW[] = { '.',0 };
1761 if (!(attrib & DDL_DIRECTORY) ||
1762 !strcmpW( entry.cAlternateFileName, dotW )) continue;
1763 buffer[0] = '[';
1764 if (long_names) strcpyW( buffer + 1, entry.cFileName );
1765 else strcpyW( buffer + 1, entry.cAlternateFileName );
1766 strcatW(buffer, bracketW);
1768 else /* not a directory */
1770 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1771 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1773 if ((attrib & DDL_EXCLUSIVE) &&
1774 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1775 continue;
1776 #undef ATTRIBS
1777 if (long_names) strcpyW( buffer, entry.cFileName );
1778 else strcpyW( buffer, entry.cAlternateFileName );
1780 if (!long_names) CharLowerW( buffer );
1781 pos = LISTBOX_FindFileStrPos( hwnd, descr, buffer );
1782 if ((ret = LISTBOX_InsertString( hwnd, descr, pos, buffer )) < 0)
1783 break;
1784 } while (FindNextFileW( handle, &entry ));
1785 FindClose( handle );
1789 /* scan drives */
1790 if ((ret >= 0) && (attrib & DDL_DRIVES))
1792 WCHAR buffer[] = {'[','-','a','-',']',0};
1793 WCHAR root[] = {'A',':','\\',0};
1794 int drive;
1795 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1797 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1798 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, buffer )) < 0)
1799 break;
1802 return ret;
1806 /***********************************************************************
1807 * LISTBOX_HandleVScroll
1809 static LRESULT LISTBOX_HandleVScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1811 SCROLLINFO info;
1813 if (descr->style & LBS_MULTICOLUMN) return 0;
1814 switch(LOWORD(wParam))
1816 case SB_LINEUP:
1817 LISTBOX_SetTopItem( hwnd, descr, descr->top_item - 1, TRUE );
1818 break;
1819 case SB_LINEDOWN:
1820 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + 1, TRUE );
1821 break;
1822 case SB_PAGEUP:
1823 LISTBOX_SetTopItem( hwnd, descr, descr->top_item -
1824 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1825 break;
1826 case SB_PAGEDOWN:
1827 LISTBOX_SetTopItem( hwnd, descr, descr->top_item +
1828 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1829 break;
1830 case SB_THUMBPOSITION:
1831 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam), TRUE );
1832 break;
1833 case SB_THUMBTRACK:
1834 info.cbSize = sizeof(info);
1835 info.fMask = SIF_TRACKPOS;
1836 GetScrollInfo( hwnd, SB_VERT, &info );
1837 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos, TRUE );
1838 break;
1839 case SB_TOP:
1840 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1841 break;
1842 case SB_BOTTOM:
1843 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1844 break;
1846 return 0;
1850 /***********************************************************************
1851 * LISTBOX_HandleHScroll
1853 static LRESULT LISTBOX_HandleHScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1855 SCROLLINFO info;
1856 INT page;
1858 if (descr->style & LBS_MULTICOLUMN)
1860 switch(LOWORD(wParam))
1862 case SB_LINELEFT:
1863 LISTBOX_SetTopItem( hwnd, descr, descr->top_item-descr->page_size,
1864 TRUE );
1865 break;
1866 case SB_LINERIGHT:
1867 LISTBOX_SetTopItem( hwnd, descr, descr->top_item+descr->page_size,
1868 TRUE );
1869 break;
1870 case SB_PAGELEFT:
1871 page = descr->width / descr->column_width;
1872 if (page < 1) page = 1;
1873 LISTBOX_SetTopItem( hwnd, descr,
1874 descr->top_item - page * descr->page_size, TRUE );
1875 break;
1876 case SB_PAGERIGHT:
1877 page = descr->width / descr->column_width;
1878 if (page < 1) page = 1;
1879 LISTBOX_SetTopItem( hwnd, descr,
1880 descr->top_item + page * descr->page_size, TRUE );
1881 break;
1882 case SB_THUMBPOSITION:
1883 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam)*descr->page_size,
1884 TRUE );
1885 break;
1886 case SB_THUMBTRACK:
1887 info.cbSize = sizeof(info);
1888 info.fMask = SIF_TRACKPOS;
1889 GetScrollInfo( hwnd, SB_VERT, &info );
1890 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos*descr->page_size,
1891 TRUE );
1892 break;
1893 case SB_LEFT:
1894 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1895 break;
1896 case SB_RIGHT:
1897 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1898 break;
1901 else if (descr->horz_extent)
1903 switch(LOWORD(wParam))
1905 case SB_LINELEFT:
1906 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos - 1 );
1907 break;
1908 case SB_LINERIGHT:
1909 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos + 1 );
1910 break;
1911 case SB_PAGELEFT:
1912 LISTBOX_SetHorizontalPos( hwnd, descr,
1913 descr->horz_pos - descr->width );
1914 break;
1915 case SB_PAGERIGHT:
1916 LISTBOX_SetHorizontalPos( hwnd, descr,
1917 descr->horz_pos + descr->width );
1918 break;
1919 case SB_THUMBPOSITION:
1920 LISTBOX_SetHorizontalPos( hwnd, descr, HIWORD(wParam) );
1921 break;
1922 case SB_THUMBTRACK:
1923 info.cbSize = sizeof(info);
1924 info.fMask = SIF_TRACKPOS;
1925 GetScrollInfo( hwnd, SB_HORZ, &info );
1926 LISTBOX_SetHorizontalPos( hwnd, descr, info.nTrackPos );
1927 break;
1928 case SB_LEFT:
1929 LISTBOX_SetHorizontalPos( hwnd, descr, 0 );
1930 break;
1931 case SB_RIGHT:
1932 LISTBOX_SetHorizontalPos( hwnd, descr,
1933 descr->horz_extent - descr->width );
1934 break;
1937 return 0;
1940 static LRESULT LISTBOX_HandleMouseWheel(HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1942 short gcWheelDelta = 0;
1943 UINT pulScrollLines = 3;
1945 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1947 gcWheelDelta -= (short) HIWORD(wParam);
1949 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1951 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1952 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1953 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + cLineScroll, TRUE );
1955 return 0;
1958 /***********************************************************************
1959 * LISTBOX_HandleLButtonDown
1961 static LRESULT LISTBOX_HandleLButtonDown( HWND hwnd, LB_DESCR *descr,
1962 WPARAM wParam, INT x, INT y )
1964 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1965 TRACE("[%p]: lbuttondown %d,%d item %d\n", hwnd, x, y, index );
1966 if (!descr->caret_on && (descr->in_focus)) return 0;
1968 if (!descr->in_focus)
1970 if( !descr->lphc ) SetFocus( hwnd );
1971 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1974 if (index == -1) return 0;
1976 if (descr->style & LBS_EXTENDEDSEL)
1978 /* we should perhaps make sure that all items are deselected
1979 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1980 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1981 LISTBOX_SetSelection( hwnd, descr, -1, FALSE, FALSE);
1984 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1985 if (wParam & MK_CONTROL)
1987 LISTBOX_SetCaretIndex( hwnd, descr, index, FALSE );
1988 LISTBOX_SetSelection( hwnd, descr, index,
1989 !descr->items[index].selected,
1990 (descr->style & LBS_NOTIFY) != 0);
1992 else LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1994 else
1996 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1997 LISTBOX_SetSelection( hwnd, descr, index,
1998 (!(descr->style & LBS_MULTIPLESEL) ||
1999 !descr->items[index].selected),
2000 (descr->style & LBS_NOTIFY) != 0 );
2003 descr->captured = TRUE;
2004 SetCapture( hwnd );
2006 if (!descr->lphc)
2008 if (descr->style & LBS_NOTIFY )
2009 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2010 MAKELPARAM( x, y ) );
2011 if (GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2013 POINT pt;
2015 pt.x = x;
2016 pt.y = y;
2018 if (DragDetect( hwnd, pt ))
2019 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2022 return 0;
2026 /*************************************************************************
2027 * LISTBOX_HandleLButtonDownCombo [Internal]
2029 * Process LButtonDown message for the ComboListBox
2031 nn * PARAMS
2032 * pWnd [I] The windows internal structure
2033 * pDescr [I] The ListBox internal structure
2034 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2035 * x [I] X Mouse Coordinate
2036 * y [I] Y Mouse Coordinate
2038 * RETURNS
2039 * 0 since we are processing the WM_LBUTTONDOWN Message
2041 * NOTES
2042 * This function is only to be used when a ListBox is a ComboListBox
2045 static LRESULT LISTBOX_HandleLButtonDownCombo( HWND hwnd, LB_DESCR *pDescr,
2046 UINT msg, WPARAM wParam, INT x, INT y)
2048 RECT clientRect, screenRect;
2049 POINT mousePos;
2051 mousePos.x = x;
2052 mousePos.y = y;
2054 GetClientRect(hwnd, &clientRect);
2056 if(PtInRect(&clientRect, mousePos))
2058 /* MousePos is in client, resume normal processing */
2059 if (msg == WM_LBUTTONDOWN)
2061 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2062 return LISTBOX_HandleLButtonDown( hwnd, pDescr, wParam, x, y);
2064 else if (pDescr->style & LBS_NOTIFY)
2065 SEND_NOTIFICATION( hwnd, pDescr, LBN_DBLCLK );
2066 return 0;
2068 else
2070 POINT screenMousePos;
2071 HWND hWndOldCapture;
2073 /* Check the Non-Client Area */
2074 screenMousePos = mousePos;
2075 hWndOldCapture = GetCapture();
2076 ReleaseCapture();
2077 GetWindowRect(hwnd, &screenRect);
2078 ClientToScreen(hwnd, &screenMousePos);
2080 if(!PtInRect(&screenRect, screenMousePos))
2082 LISTBOX_SetCaretIndex( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE );
2083 LISTBOX_SetSelection( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2084 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2085 return 0;
2087 else
2089 /* Check to see the NC is a scrollbar */
2090 INT nHitTestType=0;
2091 LONG style = GetWindowLongA( hwnd, GWL_STYLE );
2092 /* Check Vertical scroll bar */
2093 if (style & WS_VSCROLL)
2095 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2096 if (PtInRect( &clientRect, mousePos ))
2098 nHitTestType = HTVSCROLL;
2101 /* Check horizontal scroll bar */
2102 if (style & WS_HSCROLL)
2104 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2105 if (PtInRect( &clientRect, mousePos ))
2107 nHitTestType = HTHSCROLL;
2110 /* Windows sends this message when a scrollbar is clicked
2113 if(nHitTestType != 0)
2115 SendMessageW(hwnd, WM_NCLBUTTONDOWN, nHitTestType,
2116 MAKELONG(screenMousePos.x, screenMousePos.y));
2118 /* Resume the Capture after scrolling is complete
2120 if(hWndOldCapture != 0)
2122 SetCapture(hWndOldCapture);
2126 return 0;
2129 /***********************************************************************
2130 * LISTBOX_HandleLButtonUp
2132 static LRESULT LISTBOX_HandleLButtonUp( HWND hwnd, LB_DESCR *descr )
2134 if (LISTBOX_Timer != LB_TIMER_NONE)
2135 KillSystemTimer( hwnd, LB_TIMER_ID );
2136 LISTBOX_Timer = LB_TIMER_NONE;
2137 if (descr->captured)
2139 descr->captured = FALSE;
2140 if (GetCapture() == hwnd) ReleaseCapture();
2141 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2142 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2144 return 0;
2148 /***********************************************************************
2149 * LISTBOX_HandleTimer
2151 * Handle scrolling upon a timer event.
2152 * Return TRUE if scrolling should continue.
2154 static LRESULT LISTBOX_HandleTimer( HWND hwnd, LB_DESCR *descr,
2155 INT index, TIMER_DIRECTION dir )
2157 switch(dir)
2159 case LB_TIMER_UP:
2160 if (descr->top_item) index = descr->top_item - 1;
2161 else index = 0;
2162 break;
2163 case LB_TIMER_LEFT:
2164 if (descr->top_item) index -= descr->page_size;
2165 break;
2166 case LB_TIMER_DOWN:
2167 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2168 if (index == descr->focus_item) index++;
2169 if (index >= descr->nb_items) index = descr->nb_items - 1;
2170 break;
2171 case LB_TIMER_RIGHT:
2172 if (index + descr->page_size < descr->nb_items)
2173 index += descr->page_size;
2174 break;
2175 case LB_TIMER_NONE:
2176 break;
2178 if (index == descr->focus_item) return FALSE;
2179 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2180 return TRUE;
2184 /***********************************************************************
2185 * LISTBOX_HandleSystemTimer
2187 * WM_SYSTIMER handler.
2189 static LRESULT LISTBOX_HandleSystemTimer( HWND hwnd, LB_DESCR *descr )
2191 if (!LISTBOX_HandleTimer( hwnd, descr, descr->focus_item, LISTBOX_Timer ))
2193 KillSystemTimer( hwnd, LB_TIMER_ID );
2194 LISTBOX_Timer = LB_TIMER_NONE;
2196 return 0;
2200 /***********************************************************************
2201 * LISTBOX_HandleMouseMove
2203 * WM_MOUSEMOVE handler.
2205 static void LISTBOX_HandleMouseMove( HWND hwnd, LB_DESCR *descr,
2206 INT x, INT y )
2208 INT index;
2209 TIMER_DIRECTION dir = LB_TIMER_NONE;
2211 if (!descr->captured) return;
2213 if (descr->style & LBS_MULTICOLUMN)
2215 if (y < 0) y = 0;
2216 else if (y >= descr->item_height * descr->page_size)
2217 y = descr->item_height * descr->page_size - 1;
2219 if (x < 0)
2221 dir = LB_TIMER_LEFT;
2222 x = 0;
2224 else if (x >= descr->width)
2226 dir = LB_TIMER_RIGHT;
2227 x = descr->width - 1;
2230 else
2232 if (y < 0) dir = LB_TIMER_UP; /* above */
2233 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2236 index = LISTBOX_GetItemFromPoint( descr, x, y );
2237 if (index == -1) index = descr->focus_item;
2238 if (!LISTBOX_HandleTimer( hwnd, descr, index, dir )) dir = LB_TIMER_NONE;
2240 /* Start/stop the system timer */
2242 if (dir != LB_TIMER_NONE)
2243 SetSystemTimer( hwnd, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2244 else if (LISTBOX_Timer != LB_TIMER_NONE)
2245 KillSystemTimer( hwnd, LB_TIMER_ID );
2246 LISTBOX_Timer = dir;
2250 /***********************************************************************
2251 * LISTBOX_HandleKeyDown
2253 static LRESULT LISTBOX_HandleKeyDown( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
2255 INT caret = -1;
2256 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2257 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2258 bForceSelection = FALSE; /* only for single select list */
2260 if (descr->style & LBS_WANTKEYBOARDINPUT)
2262 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2263 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2264 (LPARAM)hwnd );
2265 if (caret == -2) return 0;
2267 if (caret == -1) switch(wParam)
2269 case VK_LEFT:
2270 if (descr->style & LBS_MULTICOLUMN)
2272 bForceSelection = FALSE;
2273 if (descr->focus_item >= descr->page_size)
2274 caret = descr->focus_item - descr->page_size;
2275 break;
2277 /* fall through */
2278 case VK_UP:
2279 caret = descr->focus_item - 1;
2280 if (caret < 0) caret = 0;
2281 break;
2282 case VK_RIGHT:
2283 if (descr->style & LBS_MULTICOLUMN)
2285 bForceSelection = FALSE;
2286 if (descr->focus_item + descr->page_size < descr->nb_items)
2287 caret = descr->focus_item + descr->page_size;
2288 break;
2290 /* fall through */
2291 case VK_DOWN:
2292 caret = descr->focus_item + 1;
2293 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2294 break;
2296 case VK_PRIOR:
2297 if (descr->style & LBS_MULTICOLUMN)
2299 INT page = descr->width / descr->column_width;
2300 if (page < 1) page = 1;
2301 caret = descr->focus_item - (page * descr->page_size) + 1;
2303 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2304 if (caret < 0) caret = 0;
2305 break;
2306 case VK_NEXT:
2307 if (descr->style & LBS_MULTICOLUMN)
2309 INT page = descr->width / descr->column_width;
2310 if (page < 1) page = 1;
2311 caret = descr->focus_item + (page * descr->page_size) - 1;
2313 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2314 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2315 break;
2316 case VK_HOME:
2317 caret = 0;
2318 break;
2319 case VK_END:
2320 caret = descr->nb_items - 1;
2321 break;
2322 case VK_SPACE:
2323 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2324 else if (descr->style & LBS_MULTIPLESEL)
2326 LISTBOX_SetSelection( hwnd, descr, descr->focus_item,
2327 !descr->items[descr->focus_item].selected,
2328 (descr->style & LBS_NOTIFY) != 0 );
2330 break;
2331 default:
2332 bForceSelection = FALSE;
2334 if (bForceSelection) /* focused item is used instead of key */
2335 caret = descr->focus_item;
2336 if (caret >= 0)
2338 if ((descr->style & LBS_EXTENDEDSEL) &&
2339 !(GetKeyState( VK_SHIFT ) & 0x8000))
2340 descr->anchor_item = caret;
2341 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2342 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2343 if (descr->style & LBS_NOTIFY)
2345 if( descr->lphc )
2347 /* make sure that combo parent doesn't hide us */
2348 descr->lphc->wState |= CBF_NOROLLUP;
2350 if (descr->nb_items) SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2353 return 0;
2357 /***********************************************************************
2358 * LISTBOX_HandleChar
2360 static LRESULT LISTBOX_HandleChar( HWND hwnd, LB_DESCR *descr, WCHAR charW )
2362 INT caret = -1;
2363 WCHAR str[2];
2365 str[0] = charW;
2366 str[1] = '\0';
2368 if (descr->style & LBS_WANTKEYBOARDINPUT)
2370 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2371 MAKEWPARAM(charW, descr->focus_item),
2372 (LPARAM)hwnd );
2373 if (caret == -2) return 0;
2375 if (caret == -1)
2376 caret = LISTBOX_FindString( hwnd, descr, descr->focus_item, str, FALSE);
2377 if (caret != -1)
2379 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2380 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2381 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2382 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2383 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2385 return 0;
2389 /***********************************************************************
2390 * LISTBOX_Create
2392 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2394 LB_DESCR *descr;
2395 MEASUREITEMSTRUCT mis;
2396 RECT rect;
2398 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2399 return FALSE;
2401 GetClientRect( hwnd, &rect );
2402 descr->owner = GetParent( hwnd );
2403 descr->style = GetWindowLongA( hwnd, GWL_STYLE );
2404 descr->width = rect.right - rect.left;
2405 descr->height = rect.bottom - rect.top;
2406 descr->items = NULL;
2407 descr->nb_items = 0;
2408 descr->top_item = 0;
2409 descr->selected_item = -1;
2410 descr->focus_item = 0;
2411 descr->anchor_item = -1;
2412 descr->item_height = 1;
2413 descr->page_size = 1;
2414 descr->column_width = 150;
2415 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2416 descr->horz_pos = 0;
2417 descr->nb_tabs = 0;
2418 descr->tabs = NULL;
2419 descr->caret_on = lphc ? FALSE : TRUE;
2420 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2421 descr->in_focus = FALSE;
2422 descr->captured = FALSE;
2423 descr->font = 0;
2424 descr->locale = 0; /* FIXME */
2425 descr->lphc = lphc;
2427 if (is_old_app(hwnd) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2429 /* Win95 document "List Box Differences" from MSDN:
2430 If a list box in a version 3.x application has either the
2431 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2432 horizontal and vertical scroll bars.
2434 descr->style |= WS_VSCROLL | WS_HSCROLL;
2437 if( lphc )
2439 TRACE_(combo)("[%p]: resetting owner %p -> %p\n", hwnd, descr->owner, lphc->self );
2440 descr->owner = lphc->self;
2443 SetWindowLongA( hwnd, 0, (LONG)descr );
2445 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2447 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2448 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2449 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2450 descr->item_height = LISTBOX_SetFont( hwnd, descr, 0 );
2452 if (descr->style & LBS_OWNERDRAWFIXED)
2454 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2456 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2457 descr->item_height = lphc->fixedOwnerDrawHeight;
2459 else
2461 UINT id = GetWindowLongA( hwnd, GWL_ID );
2462 mis.CtlType = ODT_LISTBOX;
2463 mis.CtlID = id;
2464 mis.itemID = -1;
2465 mis.itemWidth = 0;
2466 mis.itemData = 0;
2467 mis.itemHeight = descr->item_height;
2468 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2469 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2473 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2474 return TRUE;
2478 /***********************************************************************
2479 * LISTBOX_Destroy
2481 static BOOL LISTBOX_Destroy( HWND hwnd, LB_DESCR *descr )
2483 LISTBOX_ResetContent( hwnd, descr );
2484 SetWindowLongA( hwnd, 0, 0 );
2485 HeapFree( GetProcessHeap(), 0, descr );
2486 return TRUE;
2490 /***********************************************************************
2491 * ListBoxWndProc_common
2493 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2494 WPARAM wParam, LPARAM lParam, BOOL unicode )
2496 LRESULT ret;
2497 LB_DESCR *descr;
2499 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
2501 if (msg == WM_CREATE)
2503 if (!LISTBOX_Create( hwnd, NULL ))
2504 return -1;
2505 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongA( hwnd, 0 ) );
2506 return 0;
2508 /* Ignore all other messages before we get a WM_CREATE */
2509 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2510 DefWindowProcA( hwnd, msg, wParam, lParam );
2513 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2514 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
2515 switch(msg)
2517 case LB_RESETCONTENT16:
2518 case LB_RESETCONTENT:
2519 LISTBOX_ResetContent( hwnd, descr );
2520 LISTBOX_UpdateScroll( hwnd, descr );
2521 InvalidateRect( hwnd, NULL, TRUE );
2522 return 0;
2524 case LB_ADDSTRING16:
2525 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2526 /* fall through */
2527 case LB_ADDSTRING:
2529 INT ret;
2530 LPWSTR textW;
2531 if(unicode || !HAS_STRINGS(descr))
2532 textW = (LPWSTR)lParam;
2533 else
2535 LPSTR textA = (LPSTR)lParam;
2536 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2537 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2538 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2540 wParam = LISTBOX_FindStringPos( hwnd, descr, textW, FALSE );
2541 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2542 if (!unicode && HAS_STRINGS(descr))
2543 HeapFree(GetProcessHeap(), 0, textW);
2544 return ret;
2547 case LB_INSERTSTRING16:
2548 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2549 wParam = (INT)(INT16)wParam;
2550 /* fall through */
2551 case LB_INSERTSTRING:
2553 INT ret;
2554 LPWSTR textW;
2555 if(unicode || !HAS_STRINGS(descr))
2556 textW = (LPWSTR)lParam;
2557 else
2559 LPSTR textA = (LPSTR)lParam;
2560 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2561 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2562 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2564 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2565 if(!unicode && HAS_STRINGS(descr))
2566 HeapFree(GetProcessHeap(), 0, textW);
2567 return ret;
2570 case LB_ADDFILE16:
2571 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2572 /* fall through */
2573 case LB_ADDFILE:
2575 INT ret;
2576 LPWSTR textW;
2577 if(unicode || !HAS_STRINGS(descr))
2578 textW = (LPWSTR)lParam;
2579 else
2581 LPSTR textA = (LPSTR)lParam;
2582 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2583 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2584 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2586 wParam = LISTBOX_FindFileStrPos( hwnd, descr, textW );
2587 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2588 if(!unicode && HAS_STRINGS(descr))
2589 HeapFree(GetProcessHeap(), 0, textW);
2590 return ret;
2593 case LB_DELETESTRING16:
2594 case LB_DELETESTRING:
2595 if (LISTBOX_RemoveItem( hwnd, descr, wParam) != LB_ERR)
2596 return descr->nb_items;
2597 else
2598 return LB_ERR;
2600 case LB_GETITEMDATA16:
2601 case LB_GETITEMDATA:
2602 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2603 return LB_ERR;
2604 return descr->items[wParam].data;
2606 case LB_SETITEMDATA16:
2607 case LB_SETITEMDATA:
2608 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2609 return LB_ERR;
2610 descr->items[wParam].data = (DWORD)lParam;
2611 return LB_OKAY;
2613 case LB_GETCOUNT16:
2614 case LB_GETCOUNT:
2615 return descr->nb_items;
2617 case LB_GETTEXT16:
2618 lParam = (LPARAM)MapSL(lParam);
2619 /* fall through */
2620 case LB_GETTEXT:
2621 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2623 case LB_GETTEXTLEN16:
2624 /* fall through */
2625 case LB_GETTEXTLEN:
2626 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2627 return LB_ERR;
2628 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2629 if (unicode) return strlenW( descr->items[wParam].str );
2630 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2631 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2633 case LB_GETCURSEL16:
2634 case LB_GETCURSEL:
2635 if (descr->nb_items==0)
2636 return LB_ERR;
2637 if (!IS_MULTISELECT(descr))
2638 return descr->selected_item;
2639 /* else */
2640 if (descr->selected_item!=-1)
2641 return descr->selected_item;
2642 /* else */
2643 return descr->focus_item;
2644 /* otherwise, if the user tries to move the selection with the */
2645 /* arrow keys, we will give the application something to choke on */
2646 case LB_GETTOPINDEX16:
2647 case LB_GETTOPINDEX:
2648 return descr->top_item;
2650 case LB_GETITEMHEIGHT16:
2651 case LB_GETITEMHEIGHT:
2652 return LISTBOX_GetItemHeight( descr, wParam );
2654 case LB_SETITEMHEIGHT16:
2655 lParam = LOWORD(lParam);
2656 /* fall through */
2657 case LB_SETITEMHEIGHT:
2658 return LISTBOX_SetItemHeight( hwnd, descr, wParam, lParam, TRUE );
2660 case LB_ITEMFROMPOINT:
2662 POINT pt;
2663 RECT rect;
2665 pt.x = LOWORD(lParam);
2666 pt.y = HIWORD(lParam);
2667 rect.left = 0;
2668 rect.top = 0;
2669 rect.right = descr->width;
2670 rect.bottom = descr->height;
2672 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2673 !PtInRect( &rect, pt ) );
2676 case LB_SETCARETINDEX16:
2677 case LB_SETCARETINDEX:
2678 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2679 if (LISTBOX_SetCaretIndex( hwnd, descr, wParam, !lParam ) == LB_ERR)
2680 return LB_ERR;
2681 else if (ISWIN31)
2682 return wParam;
2683 else
2684 return LB_OKAY;
2686 case LB_GETCARETINDEX16:
2687 case LB_GETCARETINDEX:
2688 return descr->focus_item;
2690 case LB_SETTOPINDEX16:
2691 case LB_SETTOPINDEX:
2692 return LISTBOX_SetTopItem( hwnd, descr, wParam, TRUE );
2694 case LB_SETCOLUMNWIDTH16:
2695 case LB_SETCOLUMNWIDTH:
2696 return LISTBOX_SetColumnWidth( hwnd, descr, wParam );
2698 case LB_GETITEMRECT16:
2700 RECT rect;
2701 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2702 CONV_RECT32TO16( &rect, MapSL(lParam) );
2704 return ret;
2706 case LB_GETITEMRECT:
2707 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2709 case LB_FINDSTRING16:
2710 wParam = (INT)(INT16)wParam;
2711 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2712 /* fall through */
2713 case LB_FINDSTRING:
2715 INT ret;
2716 LPWSTR textW;
2717 if(unicode || !HAS_STRINGS(descr))
2718 textW = (LPWSTR)lParam;
2719 else
2721 LPSTR textA = (LPSTR)lParam;
2722 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2723 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2724 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2726 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2727 if(!unicode && HAS_STRINGS(descr))
2728 HeapFree(GetProcessHeap(), 0, textW);
2729 return ret;
2732 case LB_FINDSTRINGEXACT16:
2733 wParam = (INT)(INT16)wParam;
2734 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2735 /* fall through */
2736 case LB_FINDSTRINGEXACT:
2738 INT ret;
2739 LPWSTR textW;
2740 if(unicode || !HAS_STRINGS(descr))
2741 textW = (LPWSTR)lParam;
2742 else
2744 LPSTR textA = (LPSTR)lParam;
2745 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2746 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2747 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2749 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, TRUE );
2750 if(!unicode && HAS_STRINGS(descr))
2751 HeapFree(GetProcessHeap(), 0, textW);
2752 return ret;
2755 case LB_SELECTSTRING16:
2756 wParam = (INT)(INT16)wParam;
2757 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2758 /* fall through */
2759 case LB_SELECTSTRING:
2761 INT index;
2762 LPWSTR textW;
2764 if(HAS_STRINGS(descr))
2765 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2766 debugstr_a((LPSTR)lParam));
2767 if(unicode || !HAS_STRINGS(descr))
2768 textW = (LPWSTR)lParam;
2769 else
2771 LPSTR textA = (LPSTR)lParam;
2772 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2773 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2774 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2776 index = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2777 if(!unicode && HAS_STRINGS(descr))
2778 HeapFree(GetProcessHeap(), 0, textW);
2779 if (index != LB_ERR)
2781 LISTBOX_SetCaretIndex( hwnd, descr, index, TRUE );
2782 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
2784 return index;
2787 case LB_GETSEL16:
2788 wParam = (INT)(INT16)wParam;
2789 /* fall through */
2790 case LB_GETSEL:
2791 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2792 return LB_ERR;
2793 return descr->items[wParam].selected;
2795 case LB_SETSEL16:
2796 lParam = (INT)(INT16)lParam;
2797 /* fall through */
2798 case LB_SETSEL:
2799 return LISTBOX_SetSelection( hwnd, descr, lParam, wParam, FALSE );
2801 case LB_SETCURSEL16:
2802 wParam = (INT)(INT16)wParam;
2803 /* fall through */
2804 case LB_SETCURSEL:
2805 if (IS_MULTISELECT(descr)) return LB_ERR;
2806 LISTBOX_SetCaretIndex( hwnd, descr, wParam, TRUE );
2807 return LISTBOX_SetSelection( hwnd, descr, wParam, TRUE, FALSE );
2809 case LB_GETSELCOUNT16:
2810 case LB_GETSELCOUNT:
2811 return LISTBOX_GetSelCount( descr );
2813 case LB_GETSELITEMS16:
2814 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2816 case LB_GETSELITEMS:
2817 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2819 case LB_SELITEMRANGE16:
2820 case LB_SELITEMRANGE:
2821 if (LOWORD(lParam) <= HIWORD(lParam))
2822 return LISTBOX_SelectItemRange( hwnd, descr, LOWORD(lParam),
2823 HIWORD(lParam), wParam );
2824 else
2825 return LISTBOX_SelectItemRange( hwnd, descr, HIWORD(lParam),
2826 LOWORD(lParam), wParam );
2828 case LB_SELITEMRANGEEX16:
2829 case LB_SELITEMRANGEEX:
2830 if ((INT)lParam >= (INT)wParam)
2831 return LISTBOX_SelectItemRange( hwnd, descr, wParam, lParam, TRUE );
2832 else
2833 return LISTBOX_SelectItemRange( hwnd, descr, lParam, wParam, FALSE);
2835 case LB_GETHORIZONTALEXTENT16:
2836 case LB_GETHORIZONTALEXTENT:
2837 return descr->horz_extent;
2839 case LB_SETHORIZONTALEXTENT16:
2840 case LB_SETHORIZONTALEXTENT:
2841 return LISTBOX_SetHorizontalExtent( hwnd, descr, wParam );
2843 case LB_GETANCHORINDEX16:
2844 case LB_GETANCHORINDEX:
2845 return descr->anchor_item;
2847 case LB_SETANCHORINDEX16:
2848 wParam = (INT)(INT16)wParam;
2849 /* fall through */
2850 case LB_SETANCHORINDEX:
2851 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2852 return LB_ERR;
2853 descr->anchor_item = (INT)wParam;
2854 return LB_OKAY;
2856 case LB_DIR16:
2857 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2858 * be set automatically (this is different in Win32) */
2859 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2860 lParam = (LPARAM)MapSL(lParam);
2861 /* fall through */
2862 case LB_DIR:
2864 INT ret;
2865 LPWSTR textW;
2866 if(unicode)
2867 textW = (LPWSTR)lParam;
2868 else
2870 LPSTR textA = (LPSTR)lParam;
2871 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2872 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2873 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2875 ret = LISTBOX_Directory( hwnd, descr, wParam, textW, msg == LB_DIR );
2876 if(!unicode)
2877 HeapFree(GetProcessHeap(), 0, textW);
2878 return ret;
2881 case LB_GETLOCALE:
2882 return descr->locale;
2884 case LB_SETLOCALE:
2885 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2886 return LB_OKAY;
2888 case LB_INITSTORAGE:
2889 return LISTBOX_InitStorage( hwnd, descr, wParam );
2891 case LB_SETCOUNT:
2892 return LISTBOX_SetCount( hwnd, descr, (INT)wParam );
2894 case LB_SETTABSTOPS16:
2895 return LISTBOX_SetTabStops( hwnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2897 case LB_SETTABSTOPS:
2898 return LISTBOX_SetTabStops( hwnd, descr, wParam, (LPINT)lParam, FALSE );
2900 case LB_CARETON16:
2901 case LB_CARETON:
2902 if (descr->caret_on)
2903 return LB_OKAY;
2904 descr->caret_on = TRUE;
2905 if ((descr->focus_item != -1) && (descr->in_focus))
2906 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2907 return LB_OKAY;
2909 case LB_CARETOFF16:
2910 case LB_CARETOFF:
2911 if (!descr->caret_on)
2912 return LB_OKAY;
2913 descr->caret_on = FALSE;
2914 if ((descr->focus_item != -1) && (descr->in_focus))
2915 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2916 return LB_OKAY;
2918 case WM_DESTROY:
2919 return LISTBOX_Destroy( hwnd, descr );
2921 case WM_ENABLE:
2922 InvalidateRect( hwnd, NULL, TRUE );
2923 return 0;
2925 case WM_SETREDRAW:
2926 LISTBOX_SetRedraw( hwnd, descr, wParam != 0 );
2927 return 0;
2929 case WM_GETDLGCODE:
2930 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2932 case WM_PAINT:
2934 PAINTSTRUCT ps;
2935 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( hwnd, &ps );
2936 ret = LISTBOX_Paint( hwnd, descr, hdc );
2937 if( !wParam ) EndPaint( hwnd, &ps );
2939 return ret;
2940 case WM_SIZE:
2941 LISTBOX_UpdateSize( hwnd, descr );
2942 return 0;
2943 case WM_GETFONT:
2944 return (LRESULT)descr->font;
2945 case WM_SETFONT:
2946 LISTBOX_SetFont( hwnd, descr, (HFONT)wParam );
2947 if (lParam) InvalidateRect( hwnd, 0, TRUE );
2948 return 0;
2949 case WM_SETFOCUS:
2950 descr->in_focus = TRUE;
2951 descr->caret_on = TRUE;
2952 if (descr->focus_item != -1)
2953 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2954 SEND_NOTIFICATION( hwnd, descr, LBN_SETFOCUS );
2955 return 0;
2956 case WM_KILLFOCUS:
2957 descr->in_focus = FALSE;
2958 if ((descr->focus_item != -1) && descr->caret_on)
2959 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2960 SEND_NOTIFICATION( hwnd, descr, LBN_KILLFOCUS );
2961 return 0;
2962 case WM_HSCROLL:
2963 return LISTBOX_HandleHScroll( hwnd, descr, wParam );
2964 case WM_VSCROLL:
2965 return LISTBOX_HandleVScroll( hwnd, descr, wParam );
2966 case WM_MOUSEWHEEL:
2967 if (wParam & (MK_SHIFT | MK_CONTROL))
2968 return DefWindowProcW( hwnd, msg, wParam, lParam );
2969 return LISTBOX_HandleMouseWheel( hwnd, descr, wParam );
2970 case WM_LBUTTONDOWN:
2971 return LISTBOX_HandleLButtonDown( hwnd, descr, wParam,
2972 (INT16)LOWORD(lParam),
2973 (INT16)HIWORD(lParam) );
2974 case WM_LBUTTONDBLCLK:
2975 if (descr->style & LBS_NOTIFY)
2976 SEND_NOTIFICATION( hwnd, descr, LBN_DBLCLK );
2977 return 0;
2978 case WM_MOUSEMOVE:
2979 if (GetCapture() == hwnd)
2980 LISTBOX_HandleMouseMove( hwnd, descr, (INT16)LOWORD(lParam),
2981 (INT16)HIWORD(lParam) );
2982 return 0;
2983 case WM_LBUTTONUP:
2984 return LISTBOX_HandleLButtonUp( hwnd, descr );
2985 case WM_KEYDOWN:
2986 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
2987 case WM_CHAR:
2989 WCHAR charW;
2990 if(unicode)
2991 charW = (WCHAR)wParam;
2992 else
2994 CHAR charA = (CHAR)wParam;
2995 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
2997 return LISTBOX_HandleChar( hwnd, descr, charW );
2999 case WM_SYSTIMER:
3000 return LISTBOX_HandleSystemTimer( hwnd, descr );
3001 case WM_ERASEBKGND:
3002 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3004 RECT rect;
3005 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3006 wParam, (LPARAM)hwnd );
3007 TRACE("hbrush = %p\n", hbrush);
3008 if(!hbrush)
3009 hbrush = GetSysColorBrush(COLOR_WINDOW);
3010 if(hbrush)
3012 GetClientRect(hwnd, &rect);
3013 FillRect((HDC)wParam, &rect, hbrush);
3016 return 1;
3017 case WM_DROPFILES:
3018 if( !descr->lphc )
3019 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3020 SendMessageA( descr->owner, msg, wParam, lParam );
3021 break;
3023 default:
3024 if ((msg >= WM_USER) && (msg < 0xc000))
3025 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3026 hwnd, msg, wParam, lParam );
3027 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3028 DefWindowProcA( hwnd, msg, wParam, lParam );
3030 return 0;
3033 /***********************************************************************
3034 * ListBoxWndProcA
3036 * This is just a wrapper for the real wndproc, it only does window locking
3037 * and unlocking.
3039 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3041 if (!IsWindow(hwnd)) return 0;
3042 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3045 /***********************************************************************
3046 * ListBoxWndProcW
3048 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3050 if (!IsWindow(hwnd)) return 0;
3051 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );
3054 /***********************************************************************
3055 * ComboLBWndProc_common
3057 * The real combo listbox wndproc
3059 static LRESULT WINAPI ComboLBWndProc_common( HWND hwnd, UINT msg,
3060 WPARAM wParam, LPARAM lParam, BOOL unicode )
3062 LRESULT lRet = 0;
3063 LB_DESCR *descr;
3064 LPHEADCOMBO lphc;
3066 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
3068 if (msg == WM_CREATE)
3070 CREATESTRUCTA *lpcs = (CREATESTRUCTA *)lParam;
3071 TRACE_(combo)("\tpassed parent handle = %p\n",lpcs->lpCreateParams);
3072 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3073 return LISTBOX_Create( hwnd, lphc );
3075 /* Ignore all other messages before we get a WM_CREATE */
3076 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3077 DefWindowProcA( hwnd, msg, wParam, lParam );
3080 TRACE_(combo)("[%p]: msg %s wp %08x lp %08lx\n",
3081 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
3083 if ((lphc = descr->lphc) != NULL)
3085 switch( msg )
3087 case WM_MOUSEMOVE:
3088 if ( (TWEAK_WineLook > WIN31_LOOK) &&
3089 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
3091 POINT mousePos;
3092 BOOL captured;
3093 RECT clientRect;
3095 mousePos.x = (INT16)LOWORD(lParam);
3096 mousePos.y = (INT16)HIWORD(lParam);
3099 * If we are in a dropdown combobox, we simulate that
3100 * the mouse is captured to show the tracking of the item.
3102 GetClientRect(hwnd, &clientRect);
3104 if (PtInRect( &clientRect, mousePos ))
3106 captured = descr->captured;
3107 descr->captured = TRUE;
3109 LISTBOX_HandleMouseMove( hwnd, descr,
3110 mousePos.x, mousePos.y);
3112 descr->captured = captured;
3115 else
3117 LISTBOX_HandleMouseMove( hwnd, descr,
3118 mousePos.x, mousePos.y);
3121 return 0;
3124 /* else we are in Win3.1 look, go with the default behavior. */
3125 break;
3127 case WM_LBUTTONUP:
3128 if (TWEAK_WineLook > WIN31_LOOK)
3130 POINT mousePos;
3131 RECT clientRect;
3134 * If the mouse button "up" is not in the listbox,
3135 * we make sure there is no selection by re-selecting the
3136 * item that was selected when the listbox was made visible.
3138 mousePos.x = (INT16)LOWORD(lParam);
3139 mousePos.y = (INT16)HIWORD(lParam);
3141 GetClientRect(hwnd, &clientRect);
3144 * When the user clicks outside the combobox and the focus
3145 * is lost, the owning combobox will send a fake buttonup with
3146 * 0xFFFFFFF as the mouse location, we must also revert the
3147 * selection to the original selection.
3149 if ( (lParam == (LPARAM)-1) ||
3150 (!PtInRect( &clientRect, mousePos )) )
3152 LISTBOX_MoveCaret( hwnd, descr, lphc->droppedIndex, FALSE );
3155 return LISTBOX_HandleLButtonUp( hwnd, descr );
3156 case WM_LBUTTONDBLCLK:
3157 case WM_LBUTTONDOWN:
3158 return LISTBOX_HandleLButtonDownCombo(hwnd, descr, msg, wParam,
3159 (INT16)LOWORD(lParam),
3160 (INT16)HIWORD(lParam) );
3161 case WM_NCACTIVATE:
3162 return FALSE;
3163 case WM_KEYDOWN:
3164 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3166 /* for some reason(?) Windows makes it possible to
3167 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3169 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3170 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3171 && (wParam == VK_DOWN || wParam == VK_UP)) )
3173 COMBO_FlipListbox( lphc, FALSE, FALSE );
3174 return 0;
3177 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3179 case LB_SETCURSEL16:
3180 case LB_SETCURSEL:
3181 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3182 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3183 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3184 return lRet;
3185 case WM_NCDESTROY:
3186 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3187 lphc->hWndLBox = 0;
3188 break;
3192 /* default handling: call listbox wnd proc */
3193 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3194 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3196 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3198 return lRet;
3201 /***********************************************************************
3202 * ComboLBWndProcA
3204 * NOTE: in Windows, winproc address of the ComboLBox is the same
3205 * as that of the Listbox.
3207 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3209 if (!IsWindow(hwnd)) return 0;
3210 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3213 /***********************************************************************
3214 * ComboLBWndProcW
3216 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3218 if (!IsWindow(hwnd)) return 0;
3219 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, TRUE );