Add MS Shell Dlg font to the set of default sans serif fonts.
[wine/hacks.git] / controls / listbox.c
blob252cff7e587e30f04fea68843f8507d6ce9b5d1f
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
5 */
7 #include <string.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include "windef.h"
11 #include "wingdi.h"
12 #include "wine/winuser16.h"
13 #include "wine/winbase16.h"
14 #include "wine/unicode.h"
15 #include "winuser.h"
16 #include "winerror.h"
17 #include "spy.h"
18 #include "win.h"
19 #include "user.h"
20 #include "controls.h"
21 #include "debugtools.h"
23 DEFAULT_DEBUG_CHANNEL(listbox);
24 DECLARE_DEBUG_CHANNEL(combo);
26 /* Unimplemented yet:
27 * - LBS_USETABSTOPS
28 * - Locale handling
30 * Probably needs improvement:
31 * - LBS_NOSEL
34 /* Items array granularity */
35 #define LB_ARRAY_GRANULARITY 16
37 /* Scrolling timeout in ms */
38 #define LB_SCROLL_TIMEOUT 50
40 /* Listbox system timer id */
41 #define LB_TIMER_ID 2
43 /* flag listbox changed while setredraw false - internal style */
44 #define LBS_DISPLAYCHANGED 0x80000000
46 /* Item structure */
47 typedef struct
49 LPWSTR str; /* Item text */
50 BOOL selected; /* Is item selected? */
51 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
52 DWORD data; /* User data */
53 } LB_ITEMDATA;
55 /* Listbox structure */
56 typedef struct
58 HWND owner; /* Owner window to send notifications to */
59 UINT style; /* Window style */
60 INT width; /* Window width */
61 INT height; /* Window height */
62 LB_ITEMDATA *items; /* Array of items */
63 INT nb_items; /* Number of items */
64 INT top_item; /* Top visible item */
65 INT selected_item; /* Selected item */
66 INT focus_item; /* Item that has the focus */
67 INT anchor_item; /* Anchor item for extended selection */
68 INT item_height; /* Default item height */
69 INT page_size; /* Items per listbox page */
70 INT column_width; /* Column width for multi-column listboxes */
71 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
72 INT horz_pos; /* Horizontal position */
73 INT nb_tabs; /* Number of tabs in array */
74 INT *tabs; /* Array of tabs */
75 BOOL caret_on; /* Is caret on? */
76 BOOL captured; /* Is mouse captured? */
77 BOOL in_focus;
78 HFONT font; /* Current font */
79 LCID locale; /* Current locale for string comparisons */
80 LPHEADCOMBO lphc; /* ComboLBox */
81 } LB_DESCR;
84 #define IS_OWNERDRAW(descr) \
85 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
87 #define HAS_STRINGS(descr) \
88 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
91 #define IS_MULTISELECT(descr) \
92 ((descr)->style & LBS_MULTIPLESEL || ((descr)->style & LBS_EXTENDEDSEL))
94 #define SEND_NOTIFICATION(wnd,descr,code) \
95 (SendMessageW( (descr)->owner, WM_COMMAND, \
96 MAKEWPARAM((wnd)->wIDmenu, (code)), (wnd)->hwndSelf ))
98 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
100 /* Current timer status */
101 typedef enum
103 LB_TIMER_NONE,
104 LB_TIMER_UP,
105 LB_TIMER_LEFT,
106 LB_TIMER_DOWN,
107 LB_TIMER_RIGHT
108 } TIMER_DIRECTION;
110 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
112 static LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
113 static LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
114 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
115 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
117 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
119 /*********************************************************************
120 * listbox class descriptor
122 const struct builtin_class_descr LISTBOX_builtin_class =
124 "ListBox", /* name */
125 CS_GLOBALCLASS | CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
126 ListBoxWndProcA, /* procA */
127 ListBoxWndProcW, /* procW */
128 sizeof(LB_DESCR *), /* extra */
129 IDC_ARROWA, /* cursor */
130 0 /* brush */
134 /*********************************************************************
135 * combolbox class descriptor
137 const struct builtin_class_descr COMBOLBOX_builtin_class =
139 "ComboLBox", /* name */
140 CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS, /* style */
141 ComboLBWndProcA, /* procA */
142 ComboLBWndProcW, /* procW */
143 sizeof(LB_DESCR *), /* extra */
144 IDC_ARROWA, /* cursor */
145 0 /* brush */
149 /***********************************************************************
150 * LISTBOX_Dump
152 void LISTBOX_Dump( WND *wnd )
154 INT i;
155 LB_ITEMDATA *item;
156 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
158 TRACE( "Listbox:\n" );
159 TRACE( "hwnd=%04x descr=%08x items=%d top=%d\n",
160 wnd->hwndSelf, (UINT)descr, descr->nb_items,
161 descr->top_item );
162 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
164 TRACE( "%4d: %-40s %d %08lx %3d\n",
165 i, debugstr_w(item->str), item->selected, item->data, item->height );
170 /***********************************************************************
171 * LISTBOX_GetCurrentPageSize
173 * Return the current page size
175 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
177 INT i, height;
178 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
179 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
181 if ((height += descr->items[i].height) > descr->height) break;
183 if (i == descr->top_item) return 1;
184 else return i - descr->top_item;
188 /***********************************************************************
189 * LISTBOX_GetMaxTopIndex
191 * Return the maximum possible index for the top of the listbox.
193 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
195 INT max, page;
197 if (descr->style & LBS_OWNERDRAWVARIABLE)
199 page = descr->height;
200 for (max = descr->nb_items - 1; max >= 0; max--)
201 if ((page -= descr->items[max].height) < 0) break;
202 if (max < descr->nb_items - 1) max++;
204 else if (descr->style & LBS_MULTICOLUMN)
206 if ((page = descr->width / descr->column_width) < 1) page = 1;
207 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
208 max = (max - page) * descr->page_size;
210 else
212 max = descr->nb_items - descr->page_size;
214 if (max < 0) max = 0;
215 return max;
219 /***********************************************************************
220 * LISTBOX_UpdateScroll
222 * Update the scrollbars. Should be called whenever the content
223 * of the listbox changes.
225 static void LISTBOX_UpdateScroll( WND *wnd, LB_DESCR *descr )
227 SCROLLINFO info;
229 /* Check the listbox scroll bar flags individually before we call
230 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
231 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
232 scroll bar when we do not need one.
233 if (!(descr->style & WS_VSCROLL)) return;
236 /* It is important that we check descr->style, and not wnd->dwStyle,
237 for WS_VSCROLL, as the former is exactly the one passed in
238 argument to CreateWindow.
239 In Windows (and from now on in Wine :) a listbox created
240 with such a style (no WS_SCROLL) does not update
241 the scrollbar with listbox-related data, thus letting
242 the programmer use it for his/her own purposes. */
244 if (descr->style & LBS_NOREDRAW) return;
245 info.cbSize = sizeof(info);
247 if (descr->style & LBS_MULTICOLUMN)
249 info.nMin = 0;
250 info.nMax = (descr->nb_items - 1) / descr->page_size;
251 info.nPos = descr->top_item / descr->page_size;
252 info.nPage = descr->width / descr->column_width;
253 if (info.nPage < 1) info.nPage = 1;
254 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
255 if (descr->style & LBS_DISABLENOSCROLL)
256 info.fMask |= SIF_DISABLENOSCROLL;
257 if (descr->style & WS_HSCROLL)
258 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
259 info.nMax = 0;
260 info.fMask = SIF_RANGE;
261 if (descr->style & WS_VSCROLL)
262 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
264 else
266 info.nMin = 0;
267 info.nMax = descr->nb_items - 1;
268 info.nPos = descr->top_item;
269 info.nPage = LISTBOX_GetCurrentPageSize( descr );
270 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
271 if (descr->style & LBS_DISABLENOSCROLL)
272 info.fMask |= SIF_DISABLENOSCROLL;
273 if (descr->style & WS_VSCROLL)
274 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
276 if (descr->horz_extent)
278 info.nMin = 0;
279 info.nMax = descr->horz_extent - 1;
280 info.nPos = descr->horz_pos;
281 info.nPage = descr->width;
282 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
283 if (descr->style & LBS_DISABLENOSCROLL)
284 info.fMask |= SIF_DISABLENOSCROLL;
285 if (descr->style & WS_HSCROLL)
286 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
292 /***********************************************************************
293 * LISTBOX_SetTopItem
295 * Set the top item of the listbox, scrolling up or down if necessary.
297 static LRESULT LISTBOX_SetTopItem( WND *wnd, LB_DESCR *descr, INT index,
298 BOOL scroll )
300 INT max = LISTBOX_GetMaxTopIndex( descr );
301 if (index > max) index = max;
302 if (index < 0) index = 0;
303 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
304 if (descr->top_item == index) return LB_OKAY;
305 if (descr->style & LBS_MULTICOLUMN)
307 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
308 if (scroll && (abs(diff) < descr->width))
309 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
310 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
312 else
313 scroll = FALSE;
315 else if (scroll)
317 INT diff;
318 if (descr->style & LBS_OWNERDRAWVARIABLE)
320 INT i;
321 diff = 0;
322 if (index > descr->top_item)
324 for (i = index - 1; i >= descr->top_item; i--)
325 diff -= descr->items[i].height;
327 else
329 for (i = index; i < descr->top_item; i++)
330 diff += descr->items[i].height;
333 else
334 diff = (descr->top_item - index) * descr->item_height;
336 if (abs(diff) < descr->height)
337 ScrollWindowEx( wnd->hwndSelf, 0, diff, NULL, NULL, 0, NULL,
338 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
339 else
340 scroll = FALSE;
342 if (!scroll) InvalidateRect( wnd->hwndSelf, NULL, TRUE );
343 descr->top_item = index;
344 LISTBOX_UpdateScroll( wnd, descr );
345 return LB_OKAY;
349 /***********************************************************************
350 * LISTBOX_UpdatePage
352 * Update the page size. Should be called when the size of
353 * the client area or the item height changes.
355 static void LISTBOX_UpdatePage( WND *wnd, LB_DESCR *descr )
357 INT page_size;
359 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
360 page_size = 1;
361 if (page_size == descr->page_size) return;
362 descr->page_size = page_size;
363 if (descr->style & LBS_MULTICOLUMN)
364 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
365 LISTBOX_SetTopItem( wnd, descr, descr->top_item, FALSE );
369 /***********************************************************************
370 * LISTBOX_UpdateSize
372 * Update the size of the listbox. Should be called when the size of
373 * the client area changes.
375 static void LISTBOX_UpdateSize( WND *wnd, LB_DESCR *descr )
377 RECT rect;
379 GetClientRect( wnd->hwndSelf, &rect );
380 descr->width = rect.right - rect.left;
381 descr->height = rect.bottom - rect.top;
382 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
384 INT remaining;
386 if(descr->item_height != 0)
387 remaining = descr->height % descr->item_height;
388 else
389 remaining = 0;
390 if ((descr->height > descr->item_height) && remaining)
392 if (!(wnd->flags & WIN_ISWIN32))
393 { /* give a margin for error to 16 bits programs - if we need
394 less than the height of the nonclient area, round to the
395 *next* number of items */
396 int ncheight = wnd->rectWindow.bottom - wnd->rectWindow.top - descr->height;
397 if ((descr->item_height - remaining) <= ncheight)
398 remaining = remaining - descr->item_height;
400 TRACE("[%04x]: changing height %d -> %d\n",
401 wnd->hwndSelf, descr->height,
402 descr->height - remaining );
403 SetWindowPos( wnd->hwndSelf, 0, 0, 0,
404 wnd->rectWindow.right - wnd->rectWindow.left,
405 wnd->rectWindow.bottom - wnd->rectWindow.top - remaining,
406 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
407 return;
410 TRACE("[%04x]: new size = %d,%d\n",
411 wnd->hwndSelf, descr->width, descr->height );
412 LISTBOX_UpdatePage( wnd, descr );
413 LISTBOX_UpdateScroll( wnd, descr );
415 /* Invalidate the focused item so it will be repainted correctly */
416 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
418 InvalidateRect( wnd->hwndSelf, &rect, FALSE );
423 /***********************************************************************
424 * LISTBOX_GetItemRect
426 * Get the rectangle enclosing an item, in listbox client coordinates.
427 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
429 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
431 /* Index <= 0 is legal even on empty listboxes */
432 if (index && (index >= descr->nb_items)) return -1;
433 SetRect( rect, 0, 0, descr->width, descr->height );
434 if (descr->style & LBS_MULTICOLUMN)
436 INT col = (index / descr->page_size) -
437 (descr->top_item / descr->page_size);
438 rect->left += col * descr->column_width;
439 rect->right = rect->left + descr->column_width;
440 rect->top += (index % descr->page_size) * descr->item_height;
441 rect->bottom = rect->top + descr->item_height;
443 else if (descr->style & LBS_OWNERDRAWVARIABLE)
445 INT i;
446 rect->right += descr->horz_pos;
447 if ((index >= 0) && (index < descr->nb_items))
449 if (index < descr->top_item)
451 for (i = descr->top_item-1; i >= index; i--)
452 rect->top -= descr->items[i].height;
454 else
456 for (i = descr->top_item; i < index; i++)
457 rect->top += descr->items[i].height;
459 rect->bottom = rect->top + descr->items[index].height;
463 else
465 rect->top += (index - descr->top_item) * descr->item_height;
466 rect->bottom = rect->top + descr->item_height;
467 rect->right += descr->horz_pos;
470 return ((rect->left < descr->width) && (rect->right > 0) &&
471 (rect->top < descr->height) && (rect->bottom > 0));
475 /***********************************************************************
476 * LISTBOX_GetItemFromPoint
478 * Return the item nearest from point (x,y) (in client coordinates).
480 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
482 INT index = descr->top_item;
484 if (!descr->nb_items) return -1; /* No items */
485 if (descr->style & LBS_OWNERDRAWVARIABLE)
487 INT pos = 0;
488 if (y >= 0)
490 while (index < descr->nb_items)
492 if ((pos += descr->items[index].height) > y) break;
493 index++;
496 else
498 while (index > 0)
500 index--;
501 if ((pos -= descr->items[index].height) <= y) break;
505 else if (descr->style & LBS_MULTICOLUMN)
507 if (y >= descr->item_height * descr->page_size) return -1;
508 if (y >= 0) index += y / descr->item_height;
509 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
510 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
512 else
514 index += (y / descr->item_height);
516 if (index < 0) return 0;
517 if (index >= descr->nb_items) return -1;
518 return index;
522 /***********************************************************************
523 * LISTBOX_PaintItem
525 * Paint an item.
527 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC hdc,
528 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
530 LB_ITEMDATA *item = NULL;
531 if (index < descr->nb_items) item = &descr->items[index];
533 if (IS_OWNERDRAW(descr))
535 DRAWITEMSTRUCT dis;
536 RECT r;
537 HRGN hrgn;
539 if (!item)
541 if (action == ODA_FOCUS)
542 DrawFocusRect( hdc, rect );
543 else
544 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
545 return;
548 /* some programs mess with the clipping region when
549 drawing the item, *and* restore the previous region
550 after they are done, so a region has better to exist
551 else everything ends clipped */
552 GetClientRect(wnd->hwndSelf, &r);
553 hrgn = CreateRectRgnIndirect(&r);
554 SelectClipRgn( hdc, hrgn);
555 DeleteObject( hrgn );
557 dis.CtlType = ODT_LISTBOX;
558 dis.CtlID = wnd->wIDmenu;
559 dis.hwndItem = wnd->hwndSelf;
560 dis.itemAction = action;
561 dis.hDC = hdc;
562 dis.itemID = index;
563 dis.itemState = 0;
564 if (item && item->selected) dis.itemState |= ODS_SELECTED;
565 if (!ignoreFocus && (descr->focus_item == index) &&
566 (descr->caret_on) &&
567 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
568 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
569 dis.itemData = item ? item->data : 0;
570 dis.rcItem = *rect;
571 TRACE("[%04x]: drawitem %d (%s) action=%02x "
572 "state=%02x rect=%d,%d-%d,%d\n",
573 wnd->hwndSelf, index, item ? debugstr_w(item->str) : "", action,
574 dis.itemState, rect->left, rect->top,
575 rect->right, rect->bottom );
576 SendMessageW(descr->owner, WM_DRAWITEM, wnd->wIDmenu, (LPARAM)&dis);
578 else
580 COLORREF oldText = 0, oldBk = 0;
582 if (action == ODA_FOCUS)
584 DrawFocusRect( hdc, rect );
585 return;
587 if (item && item->selected)
589 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
590 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
593 TRACE("[%04x]: painting %d (%s) action=%02x "
594 "rect=%d,%d-%d,%d\n",
595 wnd->hwndSelf, index, item ? debugstr_w(item->str) : "", action,
596 rect->left, rect->top, rect->right, rect->bottom );
597 if (!item)
598 ExtTextOutW( hdc, rect->left + 1, rect->top,
599 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
600 else if (!(descr->style & LBS_USETABSTOPS))
601 ExtTextOutW( hdc, rect->left + 1, rect->top,
602 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
603 strlenW(item->str), NULL );
604 else
606 /* Output empty string to paint background in the full width. */
607 ExtTextOutW( hdc, rect->left + 1, rect->top,
608 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
609 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
610 item->str, strlenW(item->str),
611 descr->nb_tabs, descr->tabs, 0);
613 if (item && item->selected)
615 SetBkColor( hdc, oldBk );
616 SetTextColor( hdc, oldText );
618 if (!ignoreFocus && (descr->focus_item == index) &&
619 (descr->caret_on) &&
620 (descr->in_focus)) DrawFocusRect( hdc, rect );
625 /***********************************************************************
626 * LISTBOX_SetRedraw
628 * Change the redraw flag.
630 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL on )
632 if (on)
634 if (!(descr->style & LBS_NOREDRAW)) return;
635 descr->style &= ~LBS_NOREDRAW;
636 if (descr->style & LBS_DISPLAYCHANGED)
637 { /* page was changed while setredraw false, refresh automatically */
638 InvalidateRect(wnd->hwndSelf, NULL, TRUE);
639 if ((descr->top_item + descr->page_size) > descr->nb_items)
640 { /* reset top of page if less than number of items/page */
641 descr->top_item = descr->nb_items - descr->page_size;
642 if (descr->top_item < 0) descr->top_item = 0;
644 descr->style &= ~LBS_DISPLAYCHANGED;
646 LISTBOX_UpdateScroll( wnd, descr );
648 else descr->style |= LBS_NOREDRAW;
652 /***********************************************************************
653 * LISTBOX_RepaintItem
655 * Repaint a single item synchronously.
657 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT index,
658 UINT action )
660 HDC hdc;
661 RECT rect;
662 HFONT oldFont = 0;
663 HBRUSH hbrush, oldBrush = 0;
665 /* Do not repaint the item if the item is not visible */
666 if (!IsWindowVisible(wnd->hwndSelf)) return;
667 if (descr->style & LBS_NOREDRAW)
669 descr->style |= LBS_DISPLAYCHANGED;
670 return;
672 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
673 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE ))) return;
674 if (descr->font) oldFont = SelectObject( hdc, descr->font );
675 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
676 hdc, (LPARAM)wnd->hwndSelf );
677 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
678 if (wnd->dwStyle & WS_DISABLED)
679 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
680 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
681 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action, FALSE );
682 if (oldFont) SelectObject( hdc, oldFont );
683 if (oldBrush) SelectObject( hdc, oldBrush );
684 ReleaseDC( wnd->hwndSelf, hdc );
688 /***********************************************************************
689 * LISTBOX_InitStorage
691 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT nb_items )
693 LB_ITEMDATA *item;
695 nb_items += LB_ARRAY_GRANULARITY - 1;
696 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
697 if (descr->items)
698 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
699 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
700 nb_items * sizeof(LB_ITEMDATA) )))
702 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
703 return LB_ERRSPACE;
705 descr->items = item;
706 return LB_OKAY;
710 /***********************************************************************
711 * LISTBOX_SetTabStops
713 static BOOL LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT count,
714 LPINT tabs, BOOL short_ints )
716 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
717 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
718 if (!(descr->nb_tabs = count))
720 descr->tabs = NULL;
721 return TRUE;
723 /* FIXME: count = 1 */
724 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
725 descr->nb_tabs * sizeof(INT) )))
726 return FALSE;
727 if (short_ints)
729 INT i;
730 LPINT16 p = (LPINT16)tabs;
732 TRACE("[%04x]: settabstops ", wnd->hwndSelf );
733 for (i = 0; i < descr->nb_tabs; i++) {
734 descr->tabs[i] = *p++<<1; /* FIXME */
735 if (TRACE_ON(listbox)) DPRINTF("%hd ", descr->tabs[i]);
737 if (TRACE_ON(listbox)) DPRINTF("\n");
739 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
740 /* FIXME: repaint the window? */
741 return TRUE;
745 /***********************************************************************
746 * LISTBOX_GetText
748 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
750 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
751 if (HAS_STRINGS(descr))
753 if (!lParam)
754 return strlenW(descr->items[index].str);
756 if(unicode)
758 LPWSTR buffer = (LPWSTR)lParam;
759 strcpyW( buffer, descr->items[index].str );
760 return strlenW(buffer);
762 else
764 LPSTR buffer = (LPSTR)lParam;
765 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
767 } else {
768 if (lParam)
769 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
770 return sizeof(DWORD);
775 /***********************************************************************
776 * LISTBOX_FindStringPos
778 * Find the nearest string located before a given string in sort order.
779 * If 'exact' is TRUE, return an error if we don't get an exact match.
781 static INT LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCWSTR str,
782 BOOL exact )
784 INT index, min, max, res = -1;
786 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
787 min = 0;
788 max = descr->nb_items;
789 while (min != max)
791 index = (min + max) / 2;
792 if (HAS_STRINGS(descr))
793 res = lstrcmpiW( descr->items[index].str, str );
794 else
796 COMPAREITEMSTRUCT cis;
798 cis.CtlType = ODT_LISTBOX;
799 cis.CtlID = wnd->wIDmenu;
800 cis.hwndItem = wnd->hwndSelf;
801 cis.itemID1 = index;
802 cis.itemData1 = descr->items[index].data;
803 cis.itemID2 = -1;
804 cis.itemData2 = (DWORD)str;
805 cis.dwLocaleId = descr->locale;
806 res = SendMessageW( descr->owner, WM_COMPAREITEM,
807 wnd->wIDmenu, (LPARAM)&cis );
809 if (!res) return index;
810 if (res > 0) max = index;
811 else min = index + 1;
813 return exact ? -1 : max;
817 /***********************************************************************
818 * LISTBOX_FindFileStrPos
820 * Find the nearest string located before a given string in directory
821 * sort order (i.e. first files, then directories, then drives).
823 static INT LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCWSTR str )
825 INT min, max, res = -1;
827 if (!HAS_STRINGS(descr))
828 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
829 min = 0;
830 max = descr->nb_items;
831 while (min != max)
833 INT index = (min + max) / 2;
834 LPCWSTR p = descr->items[index].str;
835 if (*p == '[') /* drive or directory */
837 if (*str != '[') res = -1;
838 else if (p[1] == '-') /* drive */
840 if (str[1] == '-') res = str[2] - p[2];
841 else res = -1;
843 else /* directory */
845 if (str[1] == '-') res = 1;
846 else res = lstrcmpiW( str, p );
849 else /* filename */
851 if (*str == '[') res = 1;
852 else res = lstrcmpiW( str, p );
854 if (!res) return index;
855 if (res < 0) max = index;
856 else min = index + 1;
858 return max;
862 /***********************************************************************
863 * LISTBOX_FindString
865 * Find the item beginning with a given string.
867 static INT LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT start,
868 LPCWSTR str, BOOL exact )
870 INT i;
871 LB_ITEMDATA *item;
873 if (start >= descr->nb_items) start = -1;
874 item = descr->items + start + 1;
875 if (HAS_STRINGS(descr))
877 if (!str || ! str[0] ) return LB_ERR;
878 if (exact)
880 for (i = start + 1; i < descr->nb_items; i++, item++)
881 if (!lstrcmpiW( str, item->str )) return i;
882 for (i = 0, item = descr->items; i <= start; i++, item++)
883 if (!lstrcmpiW( str, item->str )) return i;
885 else
887 /* Special case for drives and directories: ignore prefix */
888 #define CHECK_DRIVE(item) \
889 if ((item)->str[0] == '[') \
891 if (!strncmpiW( str, (item)->str+1, len )) return i; \
892 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
893 return i; \
896 INT len = strlenW(str);
897 for (i = start + 1; i < descr->nb_items; i++, item++)
899 if (!strncmpiW( str, item->str, len )) return i;
900 CHECK_DRIVE(item);
902 for (i = 0, item = descr->items; i <= start; i++, item++)
904 if (!strncmpiW( str, item->str, len )) return i;
905 CHECK_DRIVE(item);
907 #undef CHECK_DRIVE
910 else
912 if (exact && (descr->style & LBS_SORT))
913 /* If sorted, use a WM_COMPAREITEM binary search */
914 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
916 /* Otherwise use a linear search */
917 for (i = start + 1; i < descr->nb_items; i++, item++)
918 if (item->data == (DWORD)str) return i;
919 for (i = 0, item = descr->items; i <= start; i++, item++)
920 if (item->data == (DWORD)str) return i;
922 return LB_ERR;
926 /***********************************************************************
927 * LISTBOX_GetSelCount
929 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
931 INT i, count;
932 LB_ITEMDATA *item = descr->items;
934 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
935 for (i = count = 0; i < descr->nb_items; i++, item++)
936 if (item->selected) count++;
937 return count;
941 /***********************************************************************
942 * LISTBOX_GetSelItems16
944 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
946 INT i, count;
947 LB_ITEMDATA *item = descr->items;
949 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
950 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
951 if (item->selected) array[count++] = (INT16)i;
952 return count;
956 /***********************************************************************
957 * LISTBOX_GetSelItems
959 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
961 INT i, count;
962 LB_ITEMDATA *item = descr->items;
964 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
965 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
966 if (item->selected) array[count++] = i;
967 return count;
971 /***********************************************************************
972 * LISTBOX_Paint
974 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC hdc )
976 INT i, col_pos = descr->page_size - 1;
977 RECT rect;
978 RECT focusRect = {-1, -1, -1, -1};
979 HFONT oldFont = 0;
980 HBRUSH hbrush, oldBrush = 0;
982 if (descr->style & LBS_NOREDRAW) return 0;
984 SetRect( &rect, 0, 0, descr->width, descr->height );
985 if (descr->style & LBS_MULTICOLUMN)
986 rect.right = rect.left + descr->column_width;
987 else if (descr->horz_pos)
989 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
990 rect.right += descr->horz_pos;
993 if (descr->font) oldFont = SelectObject( hdc, descr->font );
994 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
995 hdc, (LPARAM)wnd->hwndSelf );
996 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
997 if (wnd->dwStyle & WS_DISABLED)
998 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1000 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1001 (descr->in_focus))
1003 /* Special case for empty listbox: paint focus rect */
1004 rect.bottom = rect.top + descr->item_height;
1005 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
1006 ODA_FOCUS, FALSE );
1007 rect.top = rect.bottom;
1010 /* Paint all the item, regarding the selection
1011 Focus state will be painted after */
1013 for (i = descr->top_item; i < descr->nb_items; i++)
1015 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1016 rect.bottom = rect.top + descr->item_height;
1017 else
1018 rect.bottom = rect.top + descr->items[i].height;
1020 if (i == descr->focus_item)
1022 /* keep the focus rect, to paint the focus item after */
1023 focusRect.left = rect.left;
1024 focusRect.right = rect.right;
1025 focusRect.top = rect.top;
1026 focusRect.bottom = rect.bottom;
1028 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1029 rect.top = rect.bottom;
1031 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1033 if (!IS_OWNERDRAW(descr))
1035 /* Clear the bottom of the column */
1036 if (rect.top < descr->height)
1038 rect.bottom = descr->height;
1039 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1040 &rect, NULL, 0, NULL );
1044 /* Go to the next column */
1045 rect.left += descr->column_width;
1046 rect.right += descr->column_width;
1047 rect.top = 0;
1048 col_pos = descr->page_size - 1;
1050 else
1052 col_pos--;
1053 if (rect.top >= descr->height) break;
1057 /* Paint the focus item now */
1058 if (focusRect.top != focusRect.bottom && descr->caret_on)
1059 LISTBOX_PaintItem( wnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1061 if (!IS_OWNERDRAW(descr))
1063 /* Clear the remainder of the client area */
1064 if (rect.top < descr->height)
1066 rect.bottom = descr->height;
1067 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1068 &rect, NULL, 0, NULL );
1070 if (rect.right < descr->width)
1072 rect.left = rect.right;
1073 rect.right = descr->width;
1074 rect.top = 0;
1075 rect.bottom = descr->height;
1076 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1077 &rect, NULL, 0, NULL );
1080 if (oldFont) SelectObject( hdc, oldFont );
1081 if (oldBrush) SelectObject( hdc, oldBrush );
1082 return 0;
1086 /***********************************************************************
1087 * LISTBOX_InvalidateItems
1089 * Invalidate all items from a given item. If the specified item is not
1090 * visible, nothing happens.
1092 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT index )
1094 RECT rect;
1096 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1098 if (descr->style & LBS_NOREDRAW)
1100 descr->style |= LBS_DISPLAYCHANGED;
1101 return;
1103 rect.bottom = descr->height;
1104 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1105 if (descr->style & LBS_MULTICOLUMN)
1107 /* Repaint the other columns */
1108 rect.left = rect.right;
1109 rect.right = descr->width;
1110 rect.top = 0;
1111 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1117 /***********************************************************************
1118 * LISTBOX_GetItemHeight
1120 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1122 if (descr->style & LBS_OWNERDRAWVARIABLE)
1124 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1125 return descr->items[index].height;
1127 else return descr->item_height;
1131 /***********************************************************************
1132 * LISTBOX_SetItemHeight
1134 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT index,
1135 INT height )
1137 if (!height) height = 1;
1139 if (descr->style & LBS_OWNERDRAWVARIABLE)
1141 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1142 TRACE("[%04x]: item %d height = %d\n",
1143 wnd->hwndSelf, index, height );
1144 descr->items[index].height = height;
1145 LISTBOX_UpdateScroll( wnd, descr );
1146 LISTBOX_InvalidateItems( wnd, descr, index );
1148 else if (height != descr->item_height)
1150 TRACE("[%04x]: new height = %d\n",
1151 wnd->hwndSelf, height );
1152 descr->item_height = height;
1153 LISTBOX_UpdatePage( wnd, descr );
1154 LISTBOX_UpdateScroll( wnd, descr );
1155 InvalidateRect( wnd->hwndSelf, 0, TRUE );
1157 return LB_OKAY;
1161 /***********************************************************************
1162 * LISTBOX_SetHorizontalPos
1164 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT pos )
1166 INT diff;
1168 if (pos > descr->horz_extent - descr->width)
1169 pos = descr->horz_extent - descr->width;
1170 if (pos < 0) pos = 0;
1171 if (!(diff = descr->horz_pos - pos)) return;
1172 TRACE("[%04x]: new horz pos = %d\n",
1173 wnd->hwndSelf, pos );
1174 descr->horz_pos = pos;
1175 LISTBOX_UpdateScroll( wnd, descr );
1176 if (abs(diff) < descr->width)
1177 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1178 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1179 else
1180 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1184 /***********************************************************************
1185 * LISTBOX_SetHorizontalExtent
1187 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1188 INT extent )
1190 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1191 return LB_OKAY;
1192 if (extent <= 0) extent = 1;
1193 if (extent == descr->horz_extent) return LB_OKAY;
1194 TRACE("[%04x]: new horz extent = %d\n",
1195 wnd->hwndSelf, extent );
1196 descr->horz_extent = extent;
1197 if (descr->horz_pos > extent - descr->width)
1198 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1199 else
1200 LISTBOX_UpdateScroll( wnd, descr );
1201 return LB_OKAY;
1205 /***********************************************************************
1206 * LISTBOX_SetColumnWidth
1208 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, INT width)
1210 if (width == descr->column_width) return LB_OKAY;
1211 TRACE("[%04x]: new column width = %d\n",
1212 wnd->hwndSelf, width );
1213 descr->column_width = width;
1214 LISTBOX_UpdatePage( wnd, descr );
1215 return LB_OKAY;
1219 /***********************************************************************
1220 * LISTBOX_SetFont
1222 * Returns the item height.
1224 static INT LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT font )
1226 HDC hdc;
1227 HFONT oldFont = 0;
1228 TEXTMETRICW tm;
1230 descr->font = font;
1232 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE )))
1234 ERR("unable to get DC.\n" );
1235 return 16;
1237 if (font) oldFont = SelectObject( hdc, font );
1238 GetTextMetricsW( hdc, &tm );
1239 if (oldFont) SelectObject( hdc, oldFont );
1240 ReleaseDC( wnd->hwndSelf, hdc );
1241 if (!IS_OWNERDRAW(descr))
1242 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1243 return tm.tmHeight ;
1247 /***********************************************************************
1248 * LISTBOX_MakeItemVisible
1250 * Make sure that a given item is partially or fully visible.
1252 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT index,
1253 BOOL fully )
1255 INT top;
1257 if (index <= descr->top_item) top = index;
1258 else if (descr->style & LBS_MULTICOLUMN)
1260 INT cols = descr->width;
1261 if (!fully) cols += descr->column_width - 1;
1262 if (cols >= descr->column_width) cols /= descr->column_width;
1263 else cols = 1;
1264 if (index < descr->top_item + (descr->page_size * cols)) return;
1265 top = index - descr->page_size * (cols - 1);
1267 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1269 INT height = fully ? descr->items[index].height : 1;
1270 for (top = index; top > descr->top_item; top--)
1271 if ((height += descr->items[top-1].height) > descr->height) break;
1273 else
1275 if (index < descr->top_item + descr->page_size) return;
1276 if (!fully && (index == descr->top_item + descr->page_size) &&
1277 (descr->height > (descr->page_size * descr->item_height))) return;
1278 top = index - descr->page_size + 1;
1280 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1283 /***********************************************************************
1284 * LISTBOX_SetCaretIndex
1286 * NOTES
1287 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1290 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT index,
1291 BOOL fully_visible )
1293 INT oldfocus = descr->focus_item;
1295 if (descr->style & LBS_NOSEL) return LB_ERR;
1296 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1297 if (index == oldfocus) return LB_OKAY;
1298 descr->focus_item = index;
1299 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1300 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1302 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1303 if (descr->caret_on && (descr->in_focus))
1304 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1306 return LB_OKAY;
1310 /***********************************************************************
1311 * LISTBOX_SelectItemRange
1313 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1315 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT first,
1316 INT last, BOOL on )
1318 INT i;
1320 /* A few sanity checks */
1322 if (descr->style & LBS_NOSEL) return LB_ERR;
1323 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1324 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1325 if (last == -1) last = descr->nb_items - 1;
1326 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1327 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1328 /* selected_item reflects last selected/unselected item on multiple sel */
1329 descr->selected_item = last;
1331 if (on) /* Turn selection on */
1333 for (i = first; i <= last; i++)
1335 if (descr->items[i].selected) continue;
1336 descr->items[i].selected = TRUE;
1337 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1339 LISTBOX_SetCaretIndex( wnd, descr, last, TRUE );
1341 else /* Turn selection off */
1343 for (i = first; i <= last; i++)
1345 if (!descr->items[i].selected) continue;
1346 descr->items[i].selected = FALSE;
1347 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1350 return LB_OKAY;
1353 /***********************************************************************
1354 * LISTBOX_SetSelection
1356 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT index,
1357 BOOL on, BOOL send_notify )
1359 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1361 if (descr->style & LBS_NOSEL) return LB_ERR;
1362 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1363 if (descr->style & LBS_MULTIPLESEL)
1365 if (index == -1) /* Select all items */
1366 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1367 else /* Only one item */
1368 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1370 else
1372 INT oldsel = descr->selected_item;
1373 if (index == oldsel) return LB_OKAY;
1374 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1375 if (index != -1) descr->items[index].selected = TRUE;
1376 descr->selected_item = index;
1377 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT );
1378 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1379 if (send_notify && descr->nb_items) SEND_NOTIFICATION( wnd, descr,
1380 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1381 else
1382 if( descr->lphc ) /* set selection change flag for parent combo */
1383 descr->lphc->wState |= CBF_SELCHANGE;
1385 return LB_OKAY;
1389 /***********************************************************************
1390 * LISTBOX_MoveCaret
1392 * Change the caret position and extend the selection to the new caret.
1394 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT index,
1395 BOOL fully_visible )
1397 INT oldfocus = descr->focus_item;
1399 if ((index < 0) || (index >= descr->nb_items))
1400 return;
1402 /* Important, repaint needs to be done in this order if
1403 you want to mimic Windows behavior:
1404 1. Remove the focus and paint the item
1405 2. Remove the selection and paint the item(s)
1406 3. Set the selection and repaint the item(s)
1407 4. Set the focus to 'index' and repaint the item */
1409 /* 1. remove the focus and repaint the item */
1410 descr->focus_item = -1;
1411 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1412 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1414 /* 2. then turn off the previous selection */
1415 /* 3. repaint the new selected item */
1416 if (descr->style & LBS_EXTENDEDSEL)
1418 if (descr->anchor_item != -1)
1420 INT first = min( index, descr->anchor_item );
1421 INT last = max( index, descr->anchor_item );
1422 if (first > 0)
1423 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1424 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1425 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1428 else if (!(descr->style & LBS_MULTIPLESEL))
1430 /* Set selection to new caret item */
1431 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1434 /* 4. repaint the new item with the focus */
1435 descr->focus_item = index;
1436 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1437 if (descr->caret_on && (descr->in_focus))
1438 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1442 /***********************************************************************
1443 * LISTBOX_InsertItem
1445 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT index,
1446 LPWSTR str, DWORD data )
1448 LB_ITEMDATA *item;
1449 INT max_items;
1450 INT oldfocus = descr->focus_item;
1452 if (index == -1) index = descr->nb_items;
1453 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1454 if (!descr->items) max_items = 0;
1455 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1456 if (descr->nb_items == max_items)
1458 /* We need to grow the array */
1459 max_items += LB_ARRAY_GRANULARITY;
1460 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1461 max_items * sizeof(LB_ITEMDATA) )))
1463 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1464 return LB_ERRSPACE;
1466 descr->items = item;
1469 /* Insert the item structure */
1471 item = &descr->items[index];
1472 if (index < descr->nb_items)
1473 RtlMoveMemory( item + 1, item,
1474 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1475 item->str = str;
1476 item->data = data;
1477 item->height = 0;
1478 item->selected = FALSE;
1479 descr->nb_items++;
1481 /* Get item height */
1483 if (descr->style & LBS_OWNERDRAWVARIABLE)
1485 MEASUREITEMSTRUCT mis;
1487 mis.CtlType = ODT_LISTBOX;
1488 mis.CtlID = wnd->wIDmenu;
1489 mis.itemID = index;
1490 mis.itemData = descr->items[index].data;
1491 mis.itemHeight = descr->item_height;
1492 SendMessageW( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
1493 item->height = mis.itemHeight ? mis.itemHeight : 1;
1494 TRACE("[%04x]: measure item %d (%s) = %d\n",
1495 wnd->hwndSelf, index, str ? debugstr_w(str) : "", item->height );
1498 /* Repaint the items */
1500 LISTBOX_UpdateScroll( wnd, descr );
1501 LISTBOX_InvalidateItems( wnd, descr, index );
1503 /* Move selection and focused item */
1504 /* If listbox was empty, set focus to the first item */
1505 if (descr->nb_items == 1)
1506 LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1507 /* single select don't change selection index in win31 */
1508 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1510 descr->selected_item++;
1511 LISTBOX_SetSelection( wnd, descr, descr->selected_item-1, TRUE, FALSE );
1513 else
1515 if (index <= descr->selected_item)
1517 descr->selected_item++;
1518 descr->focus_item = oldfocus; /* focus not changed */
1521 return LB_OKAY;
1525 /***********************************************************************
1526 * LISTBOX_InsertString
1528 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT index,
1529 LPCWSTR str )
1531 LPWSTR new_str = NULL;
1532 DWORD data = 0;
1533 LRESULT ret;
1535 if (HAS_STRINGS(descr))
1537 static const WCHAR empty_stringW[] = { 0 };
1538 if (!str) str = empty_stringW;
1539 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1541 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1542 return LB_ERRSPACE;
1544 strcpyW(new_str, str);
1546 else data = (DWORD)str;
1548 if (index == -1) index = descr->nb_items;
1549 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1551 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1552 return ret;
1555 TRACE("[%04x]: added item %d '%s'\n",
1556 wnd->hwndSelf, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1557 return index;
1561 /***********************************************************************
1562 * LISTBOX_DeleteItem
1564 * Delete the content of an item. 'index' must be a valid index.
1566 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT index )
1568 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1569 * while Win95 sends it for all items with user data.
1570 * It's probably better to send it too often than not
1571 * often enough, so this is what we do here.
1573 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1575 DELETEITEMSTRUCT dis;
1577 dis.CtlType = ODT_LISTBOX;
1578 dis.CtlID = wnd->wIDmenu;
1579 dis.itemID = index;
1580 dis.hwndItem = wnd->hwndSelf;
1581 dis.itemData = descr->items[index].data;
1582 SendMessageW( descr->owner, WM_DELETEITEM, wnd->wIDmenu, (LPARAM)&dis );
1584 if (HAS_STRINGS(descr) && descr->items[index].str)
1585 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1589 /***********************************************************************
1590 * LISTBOX_RemoveItem
1592 * Remove an item from the listbox and delete its content.
1594 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT index )
1596 LB_ITEMDATA *item;
1597 INT max_items;
1599 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1600 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1602 /* We need to invalidate the original rect instead of the updated one. */
1603 LISTBOX_InvalidateItems( wnd, descr, index );
1605 LISTBOX_DeleteItem( wnd, descr, index );
1607 /* Remove the item */
1609 item = &descr->items[index];
1610 if (index < descr->nb_items-1)
1611 RtlMoveMemory( item, item + 1,
1612 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1613 descr->nb_items--;
1614 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1616 /* Shrink the item array if possible */
1618 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1619 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1621 max_items -= LB_ARRAY_GRANULARITY;
1622 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1623 max_items * sizeof(LB_ITEMDATA) );
1624 if (item) descr->items = item;
1626 /* Repaint the items */
1628 LISTBOX_UpdateScroll( wnd, descr );
1629 /* if we removed the scrollbar, reset the top of the list
1630 (correct for owner-drawn ???) */
1631 if (descr->nb_items == descr->page_size)
1632 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1634 /* Move selection and focused item */
1635 if (!IS_MULTISELECT(descr))
1637 if (index == descr->selected_item)
1638 descr->selected_item = -1;
1639 else if (index < descr->selected_item)
1641 descr->selected_item--;
1642 if (ISWIN31) /* win 31 do not change the selected item number */
1643 LISTBOX_SetSelection( wnd, descr, descr->selected_item + 1, TRUE, FALSE);
1647 if (descr->focus_item >= descr->nb_items)
1649 descr->focus_item = descr->nb_items - 1;
1650 if (descr->focus_item < 0) descr->focus_item = 0;
1652 return LB_OKAY;
1656 /***********************************************************************
1657 * LISTBOX_ResetContent
1659 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1661 INT i;
1663 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1664 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1665 descr->nb_items = 0;
1666 descr->top_item = 0;
1667 descr->selected_item = -1;
1668 descr->focus_item = 0;
1669 descr->anchor_item = -1;
1670 descr->items = NULL;
1674 /***********************************************************************
1675 * LISTBOX_SetCount
1677 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT count )
1679 LRESULT ret;
1681 if (HAS_STRINGS(descr)) return LB_ERR;
1682 /* FIXME: this is far from optimal... */
1683 if (count > descr->nb_items)
1685 while (count > descr->nb_items)
1686 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1687 return ret;
1689 else if (count < descr->nb_items)
1691 while (count < descr->nb_items)
1692 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1693 return ret;
1695 return LB_OKAY;
1699 /***********************************************************************
1700 * LISTBOX_Directory
1702 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT attrib,
1703 LPCWSTR filespec, BOOL long_names )
1705 HANDLE handle;
1706 LRESULT ret = LB_OKAY;
1707 WIN32_FIND_DATAW entry;
1708 int pos;
1710 /* don't scan directory if we just want drives exclusively */
1711 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1712 /* scan directory */
1713 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1715 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1717 else
1721 WCHAR buffer[270];
1722 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1724 static const WCHAR bracketW[] = { ']',0 };
1725 static const WCHAR dotW[] = { '.',0 };
1726 if (!(attrib & DDL_DIRECTORY) ||
1727 !strcmpW( entry.cAlternateFileName, dotW )) continue;
1728 buffer[0] = '[';
1729 if (long_names) strcpyW( buffer + 1, entry.cFileName );
1730 else strcpyW( buffer + 1, entry.cAlternateFileName );
1731 strcatW(buffer, bracketW);
1733 else /* not a directory */
1735 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1736 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1738 if ((attrib & DDL_EXCLUSIVE) &&
1739 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1740 continue;
1741 #undef ATTRIBS
1742 if (long_names) strcpyW( buffer, entry.cFileName );
1743 else strcpyW( buffer, entry.cAlternateFileName );
1745 if (!long_names) CharLowerW( buffer );
1746 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1747 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1748 break;
1749 } while (FindNextFileW( handle, &entry ));
1750 FindClose( handle );
1754 /* scan drives */
1755 if ((ret >= 0) && (attrib & DDL_DRIVES))
1757 WCHAR buffer[] = {'[','-','a','-',']',0};
1758 WCHAR root[] = {'A',':','\\',0};
1759 int drive;
1760 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1762 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1763 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1764 break;
1767 return ret;
1771 /***********************************************************************
1772 * LISTBOX_HandleVScroll
1774 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr, WPARAM wParam )
1776 SCROLLINFO info;
1778 if (descr->style & LBS_MULTICOLUMN) return 0;
1779 switch(LOWORD(wParam))
1781 case SB_LINEUP:
1782 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1783 break;
1784 case SB_LINEDOWN:
1785 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1786 break;
1787 case SB_PAGEUP:
1788 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1789 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1790 break;
1791 case SB_PAGEDOWN:
1792 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1793 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1794 break;
1795 case SB_THUMBPOSITION:
1796 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1797 break;
1798 case SB_THUMBTRACK:
1799 info.cbSize = sizeof(info);
1800 info.fMask = SIF_TRACKPOS;
1801 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1802 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1803 break;
1804 case SB_TOP:
1805 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1806 break;
1807 case SB_BOTTOM:
1808 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1809 break;
1811 return 0;
1815 /***********************************************************************
1816 * LISTBOX_HandleHScroll
1818 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr, WPARAM wParam )
1820 SCROLLINFO info;
1821 INT page;
1823 if (descr->style & LBS_MULTICOLUMN)
1825 switch(LOWORD(wParam))
1827 case SB_LINELEFT:
1828 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1829 TRUE );
1830 break;
1831 case SB_LINERIGHT:
1832 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1833 TRUE );
1834 break;
1835 case SB_PAGELEFT:
1836 page = descr->width / descr->column_width;
1837 if (page < 1) page = 1;
1838 LISTBOX_SetTopItem( wnd, descr,
1839 descr->top_item - page * descr->page_size, TRUE );
1840 break;
1841 case SB_PAGERIGHT:
1842 page = descr->width / descr->column_width;
1843 if (page < 1) page = 1;
1844 LISTBOX_SetTopItem( wnd, descr,
1845 descr->top_item + page * descr->page_size, TRUE );
1846 break;
1847 case SB_THUMBPOSITION:
1848 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1849 TRUE );
1850 break;
1851 case SB_THUMBTRACK:
1852 info.cbSize = sizeof(info);
1853 info.fMask = SIF_TRACKPOS;
1854 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1855 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1856 TRUE );
1857 break;
1858 case SB_LEFT:
1859 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1860 break;
1861 case SB_RIGHT:
1862 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1863 break;
1866 else if (descr->horz_extent)
1868 switch(LOWORD(wParam))
1870 case SB_LINELEFT:
1871 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1872 break;
1873 case SB_LINERIGHT:
1874 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1875 break;
1876 case SB_PAGELEFT:
1877 LISTBOX_SetHorizontalPos( wnd, descr,
1878 descr->horz_pos - descr->width );
1879 break;
1880 case SB_PAGERIGHT:
1881 LISTBOX_SetHorizontalPos( wnd, descr,
1882 descr->horz_pos + descr->width );
1883 break;
1884 case SB_THUMBPOSITION:
1885 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1886 break;
1887 case SB_THUMBTRACK:
1888 info.cbSize = sizeof(info);
1889 info.fMask = SIF_TRACKPOS;
1890 GetScrollInfo( wnd->hwndSelf, SB_HORZ, &info );
1891 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1892 break;
1893 case SB_LEFT:
1894 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1895 break;
1896 case SB_RIGHT:
1897 LISTBOX_SetHorizontalPos( wnd, descr,
1898 descr->horz_extent - descr->width );
1899 break;
1902 return 0;
1905 static LRESULT LISTBOX_HandleMouseWheel(WND *wnd, LB_DESCR *descr, WPARAM wParam )
1907 short gcWheelDelta = 0;
1908 UINT pulScrollLines = 3;
1910 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1912 gcWheelDelta -= (short) HIWORD(wParam);
1914 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1916 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1917 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1918 LISTBOX_SetTopItem( wnd, descr, descr->top_item + cLineScroll, TRUE );
1920 return 0;
1923 /***********************************************************************
1924 * LISTBOX_HandleLButtonDown
1926 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1927 WPARAM wParam, INT x, INT y )
1929 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1930 TRACE("[%04x]: lbuttondown %d,%d item %d\n",
1931 wnd->hwndSelf, x, y, index );
1932 if (!descr->caret_on && (descr->in_focus)) return 0;
1934 if (!descr->in_focus)
1936 if( !descr->lphc ) SetFocus( wnd->hwndSelf );
1937 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1938 : descr->lphc->self->hwndSelf );
1941 if (index == -1) return 0;
1943 if (descr->style & LBS_EXTENDEDSEL)
1945 /* we should perhaps make sure that all items are deselected
1946 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1947 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1948 LISTBOX_SetSelection( wnd, descr, -1, FALSE, FALSE);
1951 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1952 if (wParam & MK_CONTROL)
1954 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1955 LISTBOX_SetSelection( wnd, descr, index,
1956 !descr->items[index].selected,
1957 (descr->style & LBS_NOTIFY) != 0);
1959 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1961 else
1963 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1964 LISTBOX_SetSelection( wnd, descr, index,
1965 (!(descr->style & LBS_MULTIPLESEL) ||
1966 !descr->items[index].selected),
1967 (descr->style & LBS_NOTIFY) != 0 );
1970 descr->captured = TRUE;
1971 SetCapture( wnd->hwndSelf );
1973 if (!descr->lphc)
1975 if (descr->style & LBS_NOTIFY )
1976 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
1977 MAKELPARAM( x, y ) );
1978 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1980 POINT pt;
1982 pt.x = x;
1983 pt.y = y;
1985 if (DragDetect( wnd->hwndSelf, pt ))
1986 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
1989 return 0;
1993 /*************************************************************************
1994 * LISTBOX_HandleLButtonDownCombo [Internal]
1996 * Process LButtonDown message for the ComboListBox
1998 * PARAMS
1999 * pWnd [I] The windows internal structure
2000 * pDescr [I] The ListBox internal structure
2001 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2002 * x [I] X Mouse Coordinate
2003 * y [I] Y Mouse Coordinate
2005 * RETURNS
2006 * 0 since we are processing the WM_LBUTTONDOWN Message
2008 * NOTES
2009 * This function is only to be used when a ListBox is a ComboListBox
2012 static LRESULT LISTBOX_HandleLButtonDownCombo( WND *pWnd, LB_DESCR *pDescr,
2013 UINT msg, WPARAM wParam, INT x, INT y)
2015 RECT clientRect, screenRect;
2016 POINT mousePos;
2018 mousePos.x = x;
2019 mousePos.y = y;
2021 GetClientRect(pWnd->hwndSelf, &clientRect);
2023 if(PtInRect(&clientRect, mousePos))
2025 /* MousePos is in client, resume normal processing */
2026 if (msg == WM_LBUTTONDOWN)
2028 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2029 return LISTBOX_HandleLButtonDown( pWnd, pDescr, wParam, x, y);
2031 else if (pDescr->style & LBS_NOTIFY)
2032 SEND_NOTIFICATION( pWnd, pDescr, LBN_DBLCLK );
2033 return 0;
2035 else
2037 POINT screenMousePos;
2038 HWND hWndOldCapture;
2040 /* Check the Non-Client Area */
2041 screenMousePos = mousePos;
2042 hWndOldCapture = GetCapture();
2043 ReleaseCapture();
2044 GetWindowRect(pWnd->hwndSelf, &screenRect);
2045 ClientToScreen(pWnd->hwndSelf, &screenMousePos);
2047 if(!PtInRect(&screenRect, screenMousePos))
2049 LISTBOX_SetSelection( pWnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2050 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2051 return 0;
2053 else
2055 /* Check to see the NC is a scrollbar */
2056 INT nHitTestType=0;
2057 /* Check Vertical scroll bar */
2058 if (pWnd->dwStyle & WS_VSCROLL)
2060 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2061 if (PtInRect( &clientRect, mousePos ))
2063 nHitTestType = HTVSCROLL;
2066 /* Check horizontal scroll bar */
2067 if (pWnd->dwStyle & WS_HSCROLL)
2069 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2070 if (PtInRect( &clientRect, mousePos ))
2072 nHitTestType = HTHSCROLL;
2075 /* Windows sends this message when a scrollbar is clicked
2078 if(nHitTestType != 0)
2080 SendMessageW(pWnd->hwndSelf, WM_NCLBUTTONDOWN, nHitTestType,
2081 MAKELONG(screenMousePos.x, screenMousePos.y));
2083 /* Resume the Capture after scrolling is complete
2085 if(hWndOldCapture != 0)
2087 SetCapture(hWndOldCapture);
2091 return 0;
2094 /***********************************************************************
2095 * LISTBOX_HandleLButtonUp
2097 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
2099 if (LISTBOX_Timer != LB_TIMER_NONE)
2100 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2101 LISTBOX_Timer = LB_TIMER_NONE;
2102 if (descr->captured)
2104 descr->captured = FALSE;
2105 if (GetCapture() == wnd->hwndSelf) ReleaseCapture();
2106 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2107 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2109 return 0;
2113 /***********************************************************************
2114 * LISTBOX_HandleTimer
2116 * Handle scrolling upon a timer event.
2117 * Return TRUE if scrolling should continue.
2119 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
2120 INT index, TIMER_DIRECTION dir )
2122 switch(dir)
2124 case LB_TIMER_UP:
2125 if (descr->top_item) index = descr->top_item - 1;
2126 else index = 0;
2127 break;
2128 case LB_TIMER_LEFT:
2129 if (descr->top_item) index -= descr->page_size;
2130 break;
2131 case LB_TIMER_DOWN:
2132 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2133 if (index == descr->focus_item) index++;
2134 if (index >= descr->nb_items) index = descr->nb_items - 1;
2135 break;
2136 case LB_TIMER_RIGHT:
2137 if (index + descr->page_size < descr->nb_items)
2138 index += descr->page_size;
2139 break;
2140 case LB_TIMER_NONE:
2141 break;
2143 if (index == descr->focus_item) return FALSE;
2144 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
2145 return TRUE;
2149 /***********************************************************************
2150 * LISTBOX_HandleSystemTimer
2152 * WM_SYSTIMER handler.
2154 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
2156 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
2158 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2159 LISTBOX_Timer = LB_TIMER_NONE;
2161 return 0;
2165 /***********************************************************************
2166 * LISTBOX_HandleMouseMove
2168 * WM_MOUSEMOVE handler.
2170 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
2171 INT x, INT y )
2173 INT index;
2174 TIMER_DIRECTION dir = LB_TIMER_NONE;
2176 if (!descr->captured) return;
2178 if (descr->style & LBS_MULTICOLUMN)
2180 if (y < 0) y = 0;
2181 else if (y >= descr->item_height * descr->page_size)
2182 y = descr->item_height * descr->page_size - 1;
2184 if (x < 0)
2186 dir = LB_TIMER_LEFT;
2187 x = 0;
2189 else if (x >= descr->width)
2191 dir = LB_TIMER_RIGHT;
2192 x = descr->width - 1;
2195 else
2197 if (y < 0) dir = LB_TIMER_UP; /* above */
2198 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2201 index = LISTBOX_GetItemFromPoint( descr, x, y );
2202 if (index == -1) index = descr->focus_item;
2203 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
2205 /* Start/stop the system timer */
2207 if (dir != LB_TIMER_NONE)
2208 SetSystemTimer( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2209 else if (LISTBOX_Timer != LB_TIMER_NONE)
2210 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2211 LISTBOX_Timer = dir;
2215 /***********************************************************************
2216 * LISTBOX_HandleKeyDown
2218 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM wParam )
2220 INT caret = -1;
2221 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2222 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2223 bForceSelection = FALSE; /* only for single select list */
2225 if (descr->style & LBS_WANTKEYBOARDINPUT)
2227 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2228 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2229 wnd->hwndSelf );
2230 if (caret == -2) return 0;
2232 if (caret == -1) switch(wParam)
2234 case VK_LEFT:
2235 if (descr->style & LBS_MULTICOLUMN)
2237 bForceSelection = FALSE;
2238 if (descr->focus_item >= descr->page_size)
2239 caret = descr->focus_item - descr->page_size;
2240 break;
2242 /* fall through */
2243 case VK_UP:
2244 caret = descr->focus_item - 1;
2245 if (caret < 0) caret = 0;
2246 break;
2247 case VK_RIGHT:
2248 if (descr->style & LBS_MULTICOLUMN)
2250 bForceSelection = FALSE;
2251 if (descr->focus_item + descr->page_size < descr->nb_items)
2252 caret = descr->focus_item + descr->page_size;
2253 break;
2255 /* fall through */
2256 case VK_DOWN:
2257 caret = descr->focus_item + 1;
2258 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2259 break;
2261 case VK_PRIOR:
2262 if (descr->style & LBS_MULTICOLUMN)
2264 INT page = descr->width / descr->column_width;
2265 if (page < 1) page = 1;
2266 caret = descr->focus_item - (page * descr->page_size) + 1;
2268 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2269 if (caret < 0) caret = 0;
2270 break;
2271 case VK_NEXT:
2272 if (descr->style & LBS_MULTICOLUMN)
2274 INT page = descr->width / descr->column_width;
2275 if (page < 1) page = 1;
2276 caret = descr->focus_item + (page * descr->page_size) - 1;
2278 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2279 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2280 break;
2281 case VK_HOME:
2282 caret = 0;
2283 break;
2284 case VK_END:
2285 caret = descr->nb_items - 1;
2286 break;
2287 case VK_SPACE:
2288 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2289 else if (descr->style & LBS_MULTIPLESEL)
2291 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
2292 !descr->items[descr->focus_item].selected,
2293 (descr->style & LBS_NOTIFY) != 0 );
2295 break;
2296 default:
2297 bForceSelection = FALSE;
2299 if (bForceSelection) /* focused item is used instead of key */
2300 caret = descr->focus_item;
2301 if (caret >= 0)
2303 if ((descr->style & LBS_EXTENDEDSEL) &&
2304 !(GetKeyState( VK_SHIFT ) & 0x8000))
2305 descr->anchor_item = caret;
2306 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2307 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2308 if (descr->style & LBS_NOTIFY)
2310 if( descr->lphc )
2312 /* make sure that combo parent doesn't hide us */
2313 descr->lphc->wState |= CBF_NOROLLUP;
2315 if (descr->nb_items) SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2318 return 0;
2322 /***********************************************************************
2323 * LISTBOX_HandleChar
2325 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr, WCHAR charW )
2327 INT caret = -1;
2328 WCHAR str[2];
2330 str[0] = charW;
2331 str[1] = '\0';
2333 if (descr->style & LBS_WANTKEYBOARDINPUT)
2335 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2336 MAKEWPARAM(charW, descr->focus_item),
2337 wnd->hwndSelf );
2338 if (caret == -2) return 0;
2340 if (caret == -1)
2341 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2342 if (caret != -1)
2344 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2345 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2346 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2347 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2348 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2350 return 0;
2354 /***********************************************************************
2355 * LISTBOX_Create
2357 static BOOL LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2359 LB_DESCR *descr;
2360 MEASUREITEMSTRUCT mis;
2361 RECT rect;
2363 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2364 return FALSE;
2366 GetClientRect( wnd->hwndSelf, &rect );
2367 descr->owner = GetParent( wnd->hwndSelf );
2368 descr->style = wnd->dwStyle;
2369 descr->width = rect.right - rect.left;
2370 descr->height = rect.bottom - rect.top;
2371 descr->items = NULL;
2372 descr->nb_items = 0;
2373 descr->top_item = 0;
2374 descr->selected_item = -1;
2375 descr->focus_item = 0;
2376 descr->anchor_item = -1;
2377 descr->item_height = 1;
2378 descr->page_size = 1;
2379 descr->column_width = 150;
2380 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2381 descr->horz_pos = 0;
2382 descr->nb_tabs = 0;
2383 descr->tabs = NULL;
2384 descr->caret_on = lphc ? FALSE : TRUE;
2385 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2386 descr->in_focus = FALSE;
2387 descr->captured = FALSE;
2388 descr->font = 0;
2389 descr->locale = 0; /* FIXME */
2390 descr->lphc = lphc;
2392 if( ( GetExpWinVer16( wnd->hInstance ) & 0xFF00 ) == 0x0300
2393 && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2395 /* Win95 document "List Box Differences" from MSDN:
2396 If a list box in a version 3.x application has either the
2397 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2398 horizontal and vertical scroll bars.
2400 descr->style |= WS_VSCROLL | WS_HSCROLL;
2403 if( lphc )
2405 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2406 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2407 descr->owner = lphc->self->hwndSelf;
2410 *(LB_DESCR **)wnd->wExtra = descr;
2412 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2414 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2415 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2416 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2417 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2419 if (descr->style & LBS_OWNERDRAWFIXED)
2421 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2423 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2424 descr->item_height = lphc->fixedOwnerDrawHeight;
2426 else
2428 mis.CtlType = ODT_LISTBOX;
2429 mis.CtlID = wnd->wIDmenu;
2430 mis.itemID = -1;
2431 mis.itemWidth = 0;
2432 mis.itemData = 0;
2433 mis.itemHeight = descr->item_height;
2434 SendMessageW( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
2435 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2439 TRACE("owner: %04x, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2440 return TRUE;
2444 /***********************************************************************
2445 * LISTBOX_Destroy
2447 static BOOL LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2449 LISTBOX_ResetContent( wnd, descr );
2450 HeapFree( GetProcessHeap(), 0, descr );
2451 wnd->wExtra[0] = 0;
2452 return TRUE;
2456 /***********************************************************************
2457 * ListBoxWndProc
2459 static LRESULT WINAPI ListBoxWndProc_locked( WND* wnd, UINT msg,
2460 WPARAM wParam, LPARAM lParam, BOOL unicode )
2462 LRESULT ret;
2463 LB_DESCR *descr;
2464 HWND hwnd = wnd->hwndSelf;
2466 if (!wnd) return 0;
2467 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2469 switch (msg)
2471 case WM_CREATE:
2473 if (!LISTBOX_Create( wnd, NULL ))
2474 return -1;
2475 TRACE("creating wnd=%04x descr=%p\n",
2476 hwnd, *(LB_DESCR **)wnd->wExtra );
2477 return 0;
2479 case WM_NCCREATE:
2482 * When a listbox is not in a combobox and the look
2483 * is win95, the WS_BORDER style is replaced with
2484 * the WS_EX_CLIENTEDGE style.
2486 if ( (TWEAK_WineLook > WIN31_LOOK) &&
2487 (wnd->dwStyle & WS_BORDER) )
2489 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2490 wnd->dwStyle &= ~ WS_BORDER;
2495 /* Ignore all other messages before we get a WM_CREATE */
2496 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2497 DefWindowProcA( hwnd, msg, wParam, lParam );
2500 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2501 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2502 switch(msg)
2504 case LB_RESETCONTENT16:
2505 case LB_RESETCONTENT:
2506 LISTBOX_ResetContent( wnd, descr );
2507 LISTBOX_UpdateScroll( wnd, descr );
2508 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
2509 return 0;
2511 case LB_ADDSTRING16:
2512 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2513 /* fall through */
2514 case LB_ADDSTRING:
2516 INT ret;
2517 LPWSTR textW;
2518 if(unicode || !HAS_STRINGS(descr))
2519 textW = (LPWSTR)lParam;
2520 else
2522 LPSTR textA = (LPSTR)lParam;
2523 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2524 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2525 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2527 wParam = LISTBOX_FindStringPos( wnd, descr, textW, FALSE );
2528 ret = LISTBOX_InsertString( wnd, descr, wParam, textW );
2529 if (!unicode && HAS_STRINGS(descr))
2530 HeapFree(GetProcessHeap(), 0, textW);
2531 return ret;
2534 case LB_INSERTSTRING16:
2535 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2536 wParam = (INT)(INT16)wParam;
2537 /* fall through */
2538 case LB_INSERTSTRING:
2540 INT ret;
2541 LPWSTR textW;
2542 if(unicode || !HAS_STRINGS(descr))
2543 textW = (LPWSTR)lParam;
2544 else
2546 LPSTR textA = (LPSTR)lParam;
2547 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2548 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2549 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2551 ret = LISTBOX_InsertString( wnd, descr, wParam, textW );
2552 if(!unicode && HAS_STRINGS(descr))
2553 HeapFree(GetProcessHeap(), 0, textW);
2554 return ret;
2557 case LB_ADDFILE16:
2558 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2559 /* fall through */
2560 case LB_ADDFILE:
2562 INT ret;
2563 LPWSTR textW;
2564 if(unicode || !HAS_STRINGS(descr))
2565 textW = (LPWSTR)lParam;
2566 else
2568 LPSTR textA = (LPSTR)lParam;
2569 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2570 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2571 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2573 wParam = LISTBOX_FindFileStrPos( wnd, descr, textW );
2574 ret = LISTBOX_InsertString( wnd, descr, wParam, textW );
2575 if(!unicode && HAS_STRINGS(descr))
2576 HeapFree(GetProcessHeap(), 0, textW);
2577 return ret;
2580 case LB_DELETESTRING16:
2581 case LB_DELETESTRING:
2582 if (LISTBOX_RemoveItem( wnd, descr, wParam) != LB_ERR)
2583 return descr->nb_items;
2584 else
2585 return LB_ERR;
2587 case LB_GETITEMDATA16:
2588 case LB_GETITEMDATA:
2589 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2590 return LB_ERR;
2591 return descr->items[wParam].data;
2593 case LB_SETITEMDATA16:
2594 case LB_SETITEMDATA:
2595 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2596 return LB_ERR;
2597 descr->items[wParam].data = (DWORD)lParam;
2598 return LB_OKAY;
2600 case LB_GETCOUNT16:
2601 case LB_GETCOUNT:
2602 return descr->nb_items;
2604 case LB_GETTEXT16:
2605 lParam = (LPARAM)MapSL(lParam);
2606 /* fall through */
2607 case LB_GETTEXT:
2608 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2610 case LB_GETTEXTLEN16:
2611 /* fall through */
2612 case LB_GETTEXTLEN:
2613 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2614 return LB_ERR;
2615 return (HAS_STRINGS(descr) ? strlenW(descr->items[wParam].str)
2616 : sizeof(DWORD));
2618 case LB_GETCURSEL16:
2619 case LB_GETCURSEL:
2620 if (descr->nb_items==0)
2621 return LB_ERR;
2622 if (!IS_MULTISELECT(descr))
2623 return descr->selected_item;
2624 /* else */
2625 if (descr->selected_item!=-1)
2626 return descr->selected_item;
2627 /* else */
2628 return descr->focus_item;
2629 /* otherwise, if the user tries to move the selection with the */
2630 /* arrow keys, we will give the application something to choke on */
2631 case LB_GETTOPINDEX16:
2632 case LB_GETTOPINDEX:
2633 return descr->top_item;
2635 case LB_GETITEMHEIGHT16:
2636 case LB_GETITEMHEIGHT:
2637 return LISTBOX_GetItemHeight( descr, wParam );
2639 case LB_SETITEMHEIGHT16:
2640 lParam = LOWORD(lParam);
2641 /* fall through */
2642 case LB_SETITEMHEIGHT:
2643 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2645 case LB_ITEMFROMPOINT:
2647 POINT pt;
2648 RECT rect;
2650 pt.x = LOWORD(lParam);
2651 pt.y = HIWORD(lParam);
2652 rect.left = 0;
2653 rect.top = 0;
2654 rect.right = descr->width;
2655 rect.bottom = descr->height;
2657 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2658 !PtInRect( &rect, pt ) );
2661 case LB_SETCARETINDEX16:
2662 case LB_SETCARETINDEX:
2663 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2664 if (LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam ) == LB_ERR)
2665 return LB_ERR;
2666 else if (ISWIN31)
2667 return wParam;
2668 else
2669 return LB_OKAY;
2671 case LB_GETCARETINDEX16:
2672 case LB_GETCARETINDEX:
2673 return descr->focus_item;
2675 case LB_SETTOPINDEX16:
2676 case LB_SETTOPINDEX:
2677 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2679 case LB_SETCOLUMNWIDTH16:
2680 case LB_SETCOLUMNWIDTH:
2681 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2683 case LB_GETITEMRECT16:
2685 RECT rect;
2686 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2687 CONV_RECT32TO16( &rect, MapSL(lParam) );
2689 return ret;
2691 case LB_GETITEMRECT:
2692 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2694 case LB_FINDSTRING16:
2695 wParam = (INT)(INT16)wParam;
2696 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2697 /* fall through */
2698 case LB_FINDSTRING:
2700 INT ret;
2701 LPWSTR textW;
2702 if(unicode || !HAS_STRINGS(descr))
2703 textW = (LPWSTR)lParam;
2704 else
2706 LPSTR textA = (LPSTR)lParam;
2707 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2708 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2709 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2711 ret = LISTBOX_FindString( wnd, descr, wParam, textW, FALSE );
2712 if(!unicode && HAS_STRINGS(descr))
2713 HeapFree(GetProcessHeap(), 0, textW);
2714 return ret;
2717 case LB_FINDSTRINGEXACT16:
2718 wParam = (INT)(INT16)wParam;
2719 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2720 /* fall through */
2721 case LB_FINDSTRINGEXACT:
2723 INT ret;
2724 LPWSTR textW;
2725 if(unicode || !HAS_STRINGS(descr))
2726 textW = (LPWSTR)lParam;
2727 else
2729 LPSTR textA = (LPSTR)lParam;
2730 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2731 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2732 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2734 ret = LISTBOX_FindString( wnd, descr, wParam, textW, TRUE );
2735 if(!unicode && HAS_STRINGS(descr))
2736 HeapFree(GetProcessHeap(), 0, textW);
2737 return ret;
2740 case LB_SELECTSTRING16:
2741 wParam = (INT)(INT16)wParam;
2742 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2743 /* fall through */
2744 case LB_SELECTSTRING:
2746 INT index;
2747 LPWSTR textW;
2748 if(unicode || !HAS_STRINGS(descr))
2749 textW = (LPWSTR)lParam;
2750 else
2752 LPSTR textA = (LPSTR)lParam;
2753 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2754 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2755 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2757 index = LISTBOX_FindString( wnd, descr, wParam, textW, FALSE );
2758 if(!unicode && HAS_STRINGS(descr))
2759 HeapFree(GetProcessHeap(), 0, textW);
2760 if (index != LB_ERR)
2761 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2762 return index;
2765 case LB_GETSEL16:
2766 wParam = (INT)(INT16)wParam;
2767 /* fall through */
2768 case LB_GETSEL:
2769 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2770 return LB_ERR;
2771 return descr->items[wParam].selected;
2773 case LB_SETSEL16:
2774 lParam = (INT)(INT16)lParam;
2775 /* fall through */
2776 case LB_SETSEL:
2777 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2779 case LB_SETCURSEL16:
2780 wParam = (INT)(INT16)wParam;
2781 /* fall through */
2782 case LB_SETCURSEL:
2783 if (IS_MULTISELECT(descr)) return LB_ERR;
2784 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2785 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2787 case LB_GETSELCOUNT16:
2788 case LB_GETSELCOUNT:
2789 return LISTBOX_GetSelCount( descr );
2791 case LB_GETSELITEMS16:
2792 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2794 case LB_GETSELITEMS:
2795 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2797 case LB_SELITEMRANGE16:
2798 case LB_SELITEMRANGE:
2799 if (LOWORD(lParam) <= HIWORD(lParam))
2800 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2801 HIWORD(lParam), wParam );
2802 else
2803 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2804 LOWORD(lParam), wParam );
2806 case LB_SELITEMRANGEEX16:
2807 case LB_SELITEMRANGEEX:
2808 if ((INT)lParam >= (INT)wParam)
2809 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2810 else
2811 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2813 case LB_GETHORIZONTALEXTENT16:
2814 case LB_GETHORIZONTALEXTENT:
2815 return descr->horz_extent;
2817 case LB_SETHORIZONTALEXTENT16:
2818 case LB_SETHORIZONTALEXTENT:
2819 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2821 case LB_GETANCHORINDEX16:
2822 case LB_GETANCHORINDEX:
2823 return descr->anchor_item;
2825 case LB_SETANCHORINDEX16:
2826 wParam = (INT)(INT16)wParam;
2827 /* fall through */
2828 case LB_SETANCHORINDEX:
2829 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2830 return LB_ERR;
2831 descr->anchor_item = (INT)wParam;
2832 return LB_OKAY;
2834 case LB_DIR16:
2835 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2836 * be set automatically (this is different in Win32) */
2837 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2838 lParam = (LPARAM)MapSL(lParam);
2839 /* fall through */
2840 case LB_DIR:
2842 INT ret;
2843 LPWSTR textW;
2844 if(unicode)
2845 textW = (LPWSTR)lParam;
2846 else
2848 LPSTR textA = (LPSTR)lParam;
2849 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2850 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2851 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2853 ret = LISTBOX_Directory( wnd, descr, wParam, textW, msg == LB_DIR );
2854 if(!unicode)
2855 HeapFree(GetProcessHeap(), 0, textW);
2856 return ret;
2859 case LB_GETLOCALE:
2860 return descr->locale;
2862 case LB_SETLOCALE:
2863 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2864 return LB_OKAY;
2866 case LB_INITSTORAGE:
2867 return LISTBOX_InitStorage( wnd, descr, wParam );
2869 case LB_SETCOUNT:
2870 return LISTBOX_SetCount( wnd, descr, (INT)wParam );
2872 case LB_SETTABSTOPS16:
2873 return LISTBOX_SetTabStops( wnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2875 case LB_SETTABSTOPS:
2876 return LISTBOX_SetTabStops( wnd, descr, wParam, (LPINT)lParam, FALSE );
2878 case LB_CARETON16:
2879 case LB_CARETON:
2880 if (descr->caret_on)
2881 return LB_OKAY;
2882 descr->caret_on = TRUE;
2883 if ((descr->focus_item != -1) && (descr->in_focus))
2884 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2885 return LB_OKAY;
2887 case LB_CARETOFF16:
2888 case LB_CARETOFF:
2889 if (!descr->caret_on)
2890 return LB_OKAY;
2891 descr->caret_on = FALSE;
2892 if ((descr->focus_item != -1) && (descr->in_focus))
2893 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2894 return LB_OKAY;
2896 case WM_DESTROY:
2897 return LISTBOX_Destroy( wnd, descr );
2899 case WM_ENABLE:
2900 InvalidateRect( hwnd, NULL, TRUE );
2901 return 0;
2903 case WM_SETREDRAW:
2904 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2905 return 0;
2907 case WM_GETDLGCODE:
2908 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2910 case WM_PAINT:
2912 PAINTSTRUCT ps;
2913 HDC hdc = ( wParam ) ? ((HDC)wParam)
2914 : BeginPaint( hwnd, &ps );
2915 ret = LISTBOX_Paint( wnd, descr, hdc );
2916 if( !wParam ) EndPaint( hwnd, &ps );
2918 return ret;
2919 case WM_SIZE:
2920 LISTBOX_UpdateSize( wnd, descr );
2921 return 0;
2922 case WM_GETFONT:
2923 return descr->font;
2924 case WM_SETFONT:
2925 LISTBOX_SetFont( wnd, descr, (HFONT)wParam );
2926 if (lParam) InvalidateRect( wnd->hwndSelf, 0, TRUE );
2927 return 0;
2928 case WM_SETFOCUS:
2929 descr->in_focus = TRUE;
2930 descr->caret_on = TRUE;
2931 if (descr->focus_item != -1)
2932 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2933 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2934 return 0;
2935 case WM_KILLFOCUS:
2936 descr->in_focus = FALSE;
2937 if ((descr->focus_item != -1) && descr->caret_on)
2938 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2939 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2940 return 0;
2941 case WM_HSCROLL:
2942 return LISTBOX_HandleHScroll( wnd, descr, wParam );
2943 case WM_VSCROLL:
2944 return LISTBOX_HandleVScroll( wnd, descr, wParam );
2945 case WM_MOUSEACTIVATE:
2946 return MA_NOACTIVATE;
2947 case WM_MOUSEWHEEL:
2948 if (wParam & (MK_SHIFT | MK_CONTROL))
2949 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2950 DefWindowProcA( hwnd, msg, wParam, lParam );
2951 return LISTBOX_HandleMouseWheel( wnd, descr, wParam );
2952 case WM_LBUTTONDOWN:
2953 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2954 (INT16)LOWORD(lParam),
2955 (INT16)HIWORD(lParam) );
2956 case WM_LBUTTONDBLCLK:
2957 if (descr->style & LBS_NOTIFY)
2958 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2959 return 0;
2960 case WM_MOUSEMOVE:
2961 if (GetCapture() == hwnd)
2962 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2963 (INT16)HIWORD(lParam) );
2964 return 0;
2965 case WM_LBUTTONUP:
2966 return LISTBOX_HandleLButtonUp( wnd, descr );
2967 case WM_KEYDOWN:
2968 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2969 case WM_CHAR:
2971 WCHAR charW;
2972 if(unicode)
2973 charW = (WCHAR)wParam;
2974 else
2976 CHAR charA = (CHAR)wParam;
2977 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
2979 return LISTBOX_HandleChar( wnd, descr, charW );
2981 case WM_SYSTIMER:
2982 return LISTBOX_HandleSystemTimer( wnd, descr );
2983 case WM_ERASEBKGND:
2984 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2986 RECT rect;
2987 HBRUSH hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
2988 wParam, (LPARAM)wnd->hwndSelf );
2989 TRACE("hbrush = %04x\n", hbrush);
2990 if(!hbrush)
2991 hbrush = GetSysColorBrush(COLOR_WINDOW);
2992 if(hbrush)
2994 GetClientRect(hwnd, &rect);
2995 FillRect((HDC)wParam, &rect, hbrush);
2998 return 1;
2999 case WM_DROPFILES:
3000 if( !descr->lphc )
3001 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3002 SendMessageA( descr->owner, msg, wParam, lParam );
3003 break;
3005 case WM_DROPOBJECT:
3006 case WM_QUERYDROPOBJECT:
3007 case WM_DRAGSELECT:
3008 case WM_DRAGMOVE:
3009 if( !descr->lphc )
3011 LPDRAGINFO16 dragInfo = MapSL( lParam );
3012 dragInfo->l = LISTBOX_GetItemFromPoint( descr, dragInfo->pt.x,
3013 dragInfo->pt.y );
3014 return SendMessage16( descr->owner, msg, wParam, lParam );
3016 break;
3018 default:
3019 if ((msg >= WM_USER) && (msg < 0xc000))
3020 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
3021 hwnd, msg, wParam, lParam );
3022 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3023 DefWindowProcA( hwnd, msg, wParam, lParam );
3025 return 0;
3028 /***********************************************************************
3029 * ListBoxWndProcA
3031 * This is just a wrapper for the real wndproc, it only does window locking
3032 * and unlocking.
3034 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3036 WND* wndPtr = WIN_FindWndPtr( hwnd );
3037 LRESULT res = ListBoxWndProc_locked(wndPtr, msg, wParam, lParam, FALSE);
3039 WIN_ReleaseWndPtr(wndPtr);
3040 return res;
3043 /***********************************************************************
3044 * ListBoxWndProcW
3046 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3048 WND* wndPtr = WIN_FindWndPtr( hwnd );
3049 LRESULT res = ListBoxWndProc_locked(wndPtr, msg, wParam, lParam, TRUE);
3051 WIN_ReleaseWndPtr(wndPtr);
3052 return res;
3055 /***********************************************************************
3056 * ComboLBWndProc_locked
3058 * The real combo listbox wndproc, but called with locked WND struct.
3060 static LRESULT WINAPI ComboLBWndProc_locked( WND* wnd, UINT msg,
3061 WPARAM wParam, LPARAM lParam, BOOL unicode )
3063 LRESULT lRet = 0;
3064 HWND hwnd = wnd->hwndSelf;
3066 if (wnd)
3068 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
3070 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
3071 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
3073 if( descr || msg == WM_CREATE )
3075 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
3077 switch( msg )
3079 case WM_CREATE:
3080 #define lpcs ((LPCREATESTRUCTA)lParam)
3081 TRACE_(combo)("\tpassed parent handle = 0x%08x\n",
3082 (UINT)lpcs->lpCreateParams);
3084 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3085 #undef lpcs
3086 return LISTBOX_Create( wnd, lphc );
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( wnd, descr,
3110 mousePos.x, mousePos.y);
3112 descr->captured = captured;
3115 else
3117 LISTBOX_HandleMouseMove( wnd, descr,
3118 mousePos.x, mousePos.y);
3121 return 0;
3124 else
3127 * If we are in Win3.1 look, go with the default behavior.
3129 return unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3130 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3132 case WM_LBUTTONUP:
3133 if (TWEAK_WineLook > WIN31_LOOK)
3135 POINT mousePos;
3136 RECT clientRect;
3139 * If the mouse button "up" is not in the listbox,
3140 * we make sure there is no selection by re-selecting the
3141 * item that was selected when the listbox was made visible.
3143 mousePos.x = (INT16)LOWORD(lParam);
3144 mousePos.y = (INT16)HIWORD(lParam);
3146 GetClientRect(hwnd, &clientRect);
3149 * When the user clicks outside the combobox and the focus
3150 * is lost, the owning combobox will send a fake buttonup with
3151 * 0xFFFFFFF as the mouse location, we must also revert the
3152 * selection to the original selection.
3154 if ( (lParam == (LPARAM)-1) ||
3155 (!PtInRect( &clientRect, mousePos )) )
3157 LISTBOX_MoveCaret( wnd,
3158 descr,
3159 lphc->droppedIndex,
3160 FALSE );
3163 return LISTBOX_HandleLButtonUp( wnd, descr );
3164 case WM_LBUTTONDBLCLK:
3165 case WM_LBUTTONDOWN:
3166 return LISTBOX_HandleLButtonDownCombo(wnd, descr, msg, wParam,
3167 (INT16)LOWORD(lParam),
3168 (INT16)HIWORD(lParam) );
3169 case WM_MOUSEACTIVATE:
3170 return MA_NOACTIVATE;
3171 case WM_NCACTIVATE:
3172 return FALSE;
3173 case WM_KEYDOWN:
3174 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3176 /* for some reason(?) Windows makes it possible to
3177 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3179 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3180 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3181 && (wParam == VK_DOWN || wParam == VK_UP)) )
3183 COMBO_FlipListbox( lphc, FALSE, FALSE );
3184 return 0;
3187 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
3189 case LB_SETCURSEL16:
3190 case LB_SETCURSEL:
3191 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3192 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3193 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3194 return lRet;
3195 case WM_NCDESTROY:
3196 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3197 lphc->hWndLBox = 0;
3198 /* fall through */
3200 default:
3201 return unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3202 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3205 lRet = unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3206 DefWindowProcA( hwnd, msg, wParam, lParam );
3208 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3210 return lRet;
3213 /***********************************************************************
3214 * ComboLBWndProcA
3216 * NOTE: in Windows, winproc address of the ComboLBox is the same
3217 * as that of the Listbox.
3219 * This is just a wrapper for the real wndproc, it only does window locking
3220 * and unlocking.
3222 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg,
3223 WPARAM wParam, LPARAM lParam )
3225 WND *wnd = WIN_FindWndPtr( hwnd );
3226 LRESULT res = ComboLBWndProc_locked(wnd, msg, wParam, lParam, FALSE);
3228 WIN_ReleaseWndPtr(wnd);
3229 return res;
3232 /***********************************************************************
3233 * ComboLBWndProcW
3235 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3237 WND *wnd = WIN_FindWndPtr( hwnd );
3238 LRESULT res = ComboLBWndProc_locked(wnd, msg, wParam, lParam, TRUE);
3240 WIN_ReleaseWndPtr(wnd);
3241 return res;