Replaced PTR_SEG_TO_LIN macro by exported MapSL function.
[wine.git] / controls / listbox.c
blobf6f3d417669ab3f4d2a2639310d300504f892d83
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 "winuser.h"
15 #include "winerror.h"
16 #include "heap.h"
17 #include "spy.h"
18 #include "win.h"
19 #include "controls.h"
20 #include "debugtools.h"
21 #include "tweak.h"
23 DEFAULT_DEBUG_CHANNEL(listbox);
24 DECLARE_DEBUG_CHANNEL(combo);
26 /* Unimplemented yet:
27 * - LBS_NOSEL
28 * - LBS_USETABSTOPS
29 * - Unicode
30 * - Locale handling
33 /* Items array granularity */
34 #define LB_ARRAY_GRANULARITY 16
36 /* Scrolling timeout in ms */
37 #define LB_SCROLL_TIMEOUT 50
39 /* Listbox system timer id */
40 #define LB_TIMER_ID 2
42 /* flag listbox changed while setredraw false - internal style */
43 #define LBS_DISPLAYCHANGED 0x80000000
45 /* Item structure */
46 typedef struct
48 LPSTR str; /* Item text */
49 BOOL selected; /* Is item selected? */
50 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
51 DWORD data; /* User data */
52 } LB_ITEMDATA;
54 /* Listbox structure */
55 typedef struct
57 HANDLE heap; /* Heap for this listbox */
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 (SendMessageA( (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 ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
116 /*********************************************************************
117 * listbox class descriptor
119 const struct builtin_class_descr LISTBOX_builtin_class =
121 "ListBox", /* name */
122 CS_GLOBALCLASS | CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
123 ListBoxWndProcA, /* procA */
124 NULL, /* procW (FIXME) */
125 sizeof(LB_DESCR *), /* extra */
126 IDC_ARROWA, /* cursor */
127 0 /* brush */
131 /*********************************************************************
132 * combolbox class descriptor
134 const struct builtin_class_descr COMBOLBOX_builtin_class =
136 "ComboLBox", /* name */
137 CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS, /* style */
138 ComboLBWndProcA, /* procA */
139 NULL, /* procW (FIXME) */
140 sizeof(LB_DESCR *), /* extra */
141 IDC_ARROWA, /* cursor */
142 0 /* brush */
146 /***********************************************************************
147 * LISTBOX_Dump
149 void LISTBOX_Dump( WND *wnd )
151 INT i;
152 LB_ITEMDATA *item;
153 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
155 TRACE( "Listbox:\n" );
156 TRACE( "hwnd=%04x descr=%08x heap=%08x items=%d top=%d\n",
157 wnd->hwndSelf, (UINT)descr, descr->heap, descr->nb_items,
158 descr->top_item );
159 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
161 TRACE( "%4d: %-40s %d %08lx %3d\n",
162 i, item->str, item->selected, item->data, item->height );
167 /***********************************************************************
168 * LISTBOX_GetCurrentPageSize
170 * Return the current page size
172 static INT LISTBOX_GetCurrentPageSize( WND *wnd, LB_DESCR *descr )
174 INT i, height;
175 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
176 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
178 if ((height += descr->items[i].height) > descr->height) break;
180 if (i == descr->top_item) return 1;
181 else return i - descr->top_item;
185 /***********************************************************************
186 * LISTBOX_GetMaxTopIndex
188 * Return the maximum possible index for the top of the listbox.
190 static INT LISTBOX_GetMaxTopIndex( WND *wnd, LB_DESCR *descr )
192 INT max, page;
194 if (descr->style & LBS_OWNERDRAWVARIABLE)
196 page = descr->height;
197 for (max = descr->nb_items - 1; max >= 0; max--)
198 if ((page -= descr->items[max].height) < 0) break;
199 if (max < descr->nb_items - 1) max++;
201 else if (descr->style & LBS_MULTICOLUMN)
203 if ((page = descr->width / descr->column_width) < 1) page = 1;
204 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
205 max = (max - page) * descr->page_size;
207 else
209 max = descr->nb_items - descr->page_size;
211 if (max < 0) max = 0;
212 return max;
216 /***********************************************************************
217 * LISTBOX_UpdateScroll
219 * Update the scrollbars. Should be called whenever the content
220 * of the listbox changes.
222 static void LISTBOX_UpdateScroll( WND *wnd, LB_DESCR *descr )
224 SCROLLINFO info;
226 /* Check the listbox scroll bar flags individually before we call
227 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
228 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
229 scroll bar when we do not need one.
230 if (!(descr->style & WS_VSCROLL)) return;
233 /* It is important that we check descr->style, and not wnd->dwStyle,
234 for WS_VSCROLL, as the former is exactly the one passed in
235 argument to CreateWindow.
236 In Windows (and from now on in Wine :) a listbox created
237 with such a style (no WS_SCROLL) does not update
238 the scrollbar with listbox-related data, thus letting
239 the programmer use it for his/her own purposes. */
241 if (descr->style & LBS_NOREDRAW) return;
242 info.cbSize = sizeof(info);
244 if (descr->style & LBS_MULTICOLUMN)
246 info.nMin = 0;
247 info.nMax = (descr->nb_items - 1) / descr->page_size;
248 info.nPos = descr->top_item / descr->page_size;
249 info.nPage = descr->width / descr->column_width;
250 if (info.nPage < 1) info.nPage = 1;
251 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
252 if (descr->style & LBS_DISABLENOSCROLL)
253 info.fMask |= SIF_DISABLENOSCROLL;
254 if (descr->style & WS_HSCROLL)
255 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
256 info.nMax = 0;
257 info.fMask = SIF_RANGE;
258 if (descr->style & WS_VSCROLL)
259 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
261 else
263 info.nMin = 0;
264 info.nMax = descr->nb_items - 1;
265 info.nPos = descr->top_item;
266 info.nPage = LISTBOX_GetCurrentPageSize( wnd, descr );
267 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
268 if (descr->style & LBS_DISABLENOSCROLL)
269 info.fMask |= SIF_DISABLENOSCROLL;
270 if (descr->style & WS_VSCROLL)
271 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
273 if (descr->horz_extent)
275 info.nMin = 0;
276 info.nMax = descr->horz_extent - 1;
277 info.nPos = descr->horz_pos;
278 info.nPage = descr->width;
279 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
280 if (descr->style & LBS_DISABLENOSCROLL)
281 info.fMask |= SIF_DISABLENOSCROLL;
282 if (descr->style & WS_HSCROLL)
283 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
290 /***********************************************************************
291 * LISTBOX_SetTopItem
293 * Set the top item of the listbox, scrolling up or down if necessary.
295 static LRESULT LISTBOX_SetTopItem( WND *wnd, LB_DESCR *descr, INT index,
296 BOOL scroll )
298 INT max = LISTBOX_GetMaxTopIndex( wnd, descr );
299 if (index > max) index = max;
300 if (index < 0) index = 0;
301 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
302 if (descr->top_item == index) return LB_OKAY;
303 if (descr->style & LBS_MULTICOLUMN)
305 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
306 if (scroll && (abs(diff) < descr->width))
307 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
308 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
310 else
311 scroll = FALSE;
313 else if (scroll)
315 INT diff;
316 if (descr->style & LBS_OWNERDRAWVARIABLE)
318 INT i;
319 diff = 0;
320 if (index > descr->top_item)
322 for (i = index - 1; i >= descr->top_item; i--)
323 diff -= descr->items[i].height;
325 else
327 for (i = index; i < descr->top_item; i++)
328 diff += descr->items[i].height;
331 else
332 diff = (descr->top_item - index) * descr->item_height;
334 if (abs(diff) < descr->height)
335 ScrollWindowEx( wnd->hwndSelf, 0, diff, NULL, NULL, 0, NULL,
336 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
337 else
338 scroll = FALSE;
340 if (!scroll) InvalidateRect( wnd->hwndSelf, NULL, TRUE );
341 descr->top_item = index;
342 LISTBOX_UpdateScroll( wnd, descr );
343 return LB_OKAY;
347 /***********************************************************************
348 * LISTBOX_UpdatePage
350 * Update the page size. Should be called when the size of
351 * the client area or the item height changes.
353 static void LISTBOX_UpdatePage( WND *wnd, LB_DESCR *descr )
355 INT page_size;
357 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
358 page_size = 1;
359 if (page_size == descr->page_size) return;
360 descr->page_size = page_size;
361 if (descr->style & LBS_MULTICOLUMN)
362 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
363 LISTBOX_SetTopItem( wnd, descr, descr->top_item, FALSE );
367 /***********************************************************************
368 * LISTBOX_UpdateSize
370 * Update the size of the listbox. Should be called when the size of
371 * the client area changes.
373 static void LISTBOX_UpdateSize( WND *wnd, LB_DESCR *descr )
375 RECT rect;
377 GetClientRect( wnd->hwndSelf, &rect );
378 descr->width = rect.right - rect.left;
379 descr->height = rect.bottom - rect.top;
380 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
382 UINT remaining;
384 if(descr->item_height != 0)
385 remaining = descr->height % descr->item_height;
386 else
387 remaining = 0;
388 if ((descr->height > descr->item_height) && remaining)
390 if (!(wnd->flags & WIN_ISWIN32))
391 { /* give a margin for error to 16 bits programs - if we need
392 less than the height of the nonclient area, round to the
393 *next* number of items */
394 int ncheight = wnd->rectWindow.bottom - wnd->rectWindow.top - descr->height;
395 if ((descr->item_height - remaining) <= ncheight)
396 remaining = remaining - descr->item_height;
398 TRACE("[%04x]: changing height %d -> %d\n",
399 wnd->hwndSelf, descr->height,
400 descr->height - remaining );
401 SetWindowPos( wnd->hwndSelf, 0, 0, 0,
402 wnd->rectWindow.right - wnd->rectWindow.left,
403 wnd->rectWindow.bottom - wnd->rectWindow.top - remaining,
404 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
405 return;
408 TRACE("[%04x]: new size = %d,%d\n",
409 wnd->hwndSelf, descr->width, descr->height );
410 LISTBOX_UpdatePage( wnd, descr );
411 LISTBOX_UpdateScroll( wnd, descr );
415 /***********************************************************************
416 * LISTBOX_GetItemRect
418 * Get the rectangle enclosing an item, in listbox client coordinates.
419 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
421 static LRESULT LISTBOX_GetItemRect( WND *wnd, LB_DESCR *descr, INT index,
422 RECT *rect )
424 /* Index <= 0 is legal even on empty listboxes */
425 if (index && (index >= descr->nb_items)) return -1;
426 SetRect( rect, 0, 0, descr->width, descr->height );
427 if (descr->style & LBS_MULTICOLUMN)
429 INT col = (index / descr->page_size) -
430 (descr->top_item / descr->page_size);
431 rect->left += col * descr->column_width;
432 rect->right = rect->left + descr->column_width;
433 rect->top += (index % descr->page_size) * descr->item_height;
434 rect->bottom = rect->top + descr->item_height;
436 else if (descr->style & LBS_OWNERDRAWVARIABLE)
438 INT i;
439 rect->right += descr->horz_pos;
440 if ((index >= 0) && (index < descr->nb_items))
442 if (index < descr->top_item)
444 for (i = descr->top_item-1; i >= index; i--)
445 rect->top -= descr->items[i].height;
447 else
449 for (i = descr->top_item; i < index; i++)
450 rect->top += descr->items[i].height;
452 rect->bottom = rect->top + descr->items[index].height;
456 else
458 rect->top += (index - descr->top_item) * descr->item_height;
459 rect->bottom = rect->top + descr->item_height;
460 rect->right += descr->horz_pos;
463 return ((rect->left < descr->width) && (rect->right > 0) &&
464 (rect->top < descr->height) && (rect->bottom > 0));
468 /***********************************************************************
469 * LISTBOX_GetItemFromPoint
471 * Return the item nearest from point (x,y) (in client coordinates).
473 static INT LISTBOX_GetItemFromPoint( WND *wnd, LB_DESCR *descr,
474 INT x, INT y )
476 INT index = descr->top_item;
478 if (!descr->nb_items) return -1; /* No items */
479 if (descr->style & LBS_OWNERDRAWVARIABLE)
481 INT pos = 0;
482 if (y >= 0)
484 while (index < descr->nb_items)
486 if ((pos += descr->items[index].height) > y) break;
487 index++;
490 else
492 while (index > 0)
494 index--;
495 if ((pos -= descr->items[index].height) <= y) break;
499 else if (descr->style & LBS_MULTICOLUMN)
501 if (y >= descr->item_height * descr->page_size) return -1;
502 if (y >= 0) index += y / descr->item_height;
503 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
504 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
506 else
508 index += (y / descr->item_height);
510 if (index < 0) return 0;
511 if (index >= descr->nb_items) return -1;
512 return index;
516 /***********************************************************************
517 * LISTBOX_PaintItem
519 * Paint an item.
521 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC hdc,
522 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
524 LB_ITEMDATA *item = NULL;
525 if (index < descr->nb_items) item = &descr->items[index];
527 if (IS_OWNERDRAW(descr))
529 DRAWITEMSTRUCT dis;
530 RECT r;
531 HRGN hrgn;
533 if (!item)
535 if (action == ODA_FOCUS)
536 DrawFocusRect( hdc, rect );
537 else
538 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
539 return;
542 /* some programs mess with the clipping region when
543 drawing the item, *and* restore the previous region
544 after they are done, so a region has better to exist
545 else everything ends clipped */
546 GetClientRect(wnd->hwndSelf, &r);
547 hrgn = CreateRectRgnIndirect(&r);
548 SelectClipRgn( hdc, hrgn);
549 DeleteObject( hrgn );
551 dis.CtlType = ODT_LISTBOX;
552 dis.CtlID = wnd->wIDmenu;
553 dis.hwndItem = wnd->hwndSelf;
554 dis.itemAction = action;
555 dis.hDC = hdc;
556 dis.itemID = index;
557 dis.itemState = 0;
558 if (item && item->selected) dis.itemState |= ODS_SELECTED;
559 if (!ignoreFocus && (descr->focus_item == index) &&
560 (descr->caret_on) &&
561 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
562 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
563 dis.itemData = item ? item->data : 0;
564 dis.rcItem = *rect;
565 TRACE("[%04x]: drawitem %d (%s) action=%02x "
566 "state=%02x rect=%d,%d-%d,%d\n",
567 wnd->hwndSelf, index, item ? item->str : "", action,
568 dis.itemState, rect->left, rect->top,
569 rect->right, rect->bottom );
570 SendMessageA(descr->owner, WM_DRAWITEM, wnd->wIDmenu, (LPARAM)&dis);
572 else
574 COLORREF oldText = 0, oldBk = 0;
576 if (action == ODA_FOCUS)
578 DrawFocusRect( hdc, rect );
579 return;
581 if (item && item->selected)
583 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
584 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
587 TRACE("[%04x]: painting %d (%s) action=%02x "
588 "rect=%d,%d-%d,%d\n",
589 wnd->hwndSelf, index, item ? item->str : "", action,
590 rect->left, rect->top, rect->right, rect->bottom );
591 if (!item)
592 ExtTextOutA( hdc, rect->left + 1, rect->top,
593 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
594 else if (!(descr->style & LBS_USETABSTOPS))
595 ExtTextOutA( hdc, rect->left + 1, rect->top,
596 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
597 strlen(item->str), NULL );
598 else
600 /* Output empty string to paint background in the full width. */
601 ExtTextOutA( hdc, rect->left + 1, rect->top,
602 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
603 TabbedTextOutA( hdc, rect->left + 1 , rect->top,
604 item->str, strlen(item->str),
605 descr->nb_tabs, descr->tabs, 0);
607 if (item && item->selected)
609 SetBkColor( hdc, oldBk );
610 SetTextColor( hdc, oldText );
612 if (!ignoreFocus && (descr->focus_item == index) &&
613 (descr->caret_on) &&
614 (descr->in_focus)) DrawFocusRect( hdc, rect );
619 /***********************************************************************
620 * LISTBOX_SetRedraw
622 * Change the redraw flag.
624 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL on )
626 if (on)
628 if (!(descr->style & LBS_NOREDRAW)) return;
629 descr->style &= ~LBS_NOREDRAW;
630 if (descr->style & LBS_DISPLAYCHANGED)
631 { /* page was changed while setredraw false, refresh automatically */
632 InvalidateRect(wnd->hwndSelf, NULL, TRUE);
633 if ((descr->top_item + descr->page_size) > descr->nb_items)
634 { /* reset top of page if less than number of items/page */
635 descr->top_item = descr->nb_items - descr->page_size;
636 if (descr->top_item < 0) descr->top_item = 0;
638 descr->style &= ~LBS_DISPLAYCHANGED;
640 LISTBOX_UpdateScroll( wnd, descr );
642 else descr->style |= LBS_NOREDRAW;
646 /***********************************************************************
647 * LISTBOX_RepaintItem
649 * Repaint a single item synchronously.
651 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT index,
652 UINT action )
654 HDC hdc;
655 RECT rect;
656 HFONT oldFont = 0;
657 HBRUSH hbrush, oldBrush = 0;
659 /* Do not repaint the item if the item is not visible */
660 if (!IsWindowVisible(wnd->hwndSelf)) return;
661 if (descr->style & LBS_NOREDRAW)
663 descr->style |= LBS_DISPLAYCHANGED;
664 return;
666 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) != 1) return;
667 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE ))) return;
668 if (descr->font) oldFont = SelectObject( hdc, descr->font );
669 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
670 hdc, (LPARAM)wnd->hwndSelf );
671 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
672 if (wnd->dwStyle & WS_DISABLED)
673 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
674 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
675 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action, FALSE );
676 if (oldFont) SelectObject( hdc, oldFont );
677 if (oldBrush) SelectObject( hdc, oldBrush );
678 ReleaseDC( wnd->hwndSelf, hdc );
682 /***********************************************************************
683 * LISTBOX_InitStorage
685 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT nb_items,
686 DWORD bytes )
688 LB_ITEMDATA *item;
690 nb_items += LB_ARRAY_GRANULARITY - 1;
691 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
692 if (descr->items)
693 nb_items += HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
694 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
695 nb_items * sizeof(LB_ITEMDATA) )))
697 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
698 return LB_ERRSPACE;
700 descr->items = item;
701 return LB_OKAY;
705 /***********************************************************************
706 * LISTBOX_SetTabStops
708 static BOOL LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT count,
709 LPINT tabs, BOOL short_ints )
711 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
712 if (descr->tabs) HeapFree( descr->heap, 0, descr->tabs );
713 if (!(descr->nb_tabs = count))
715 descr->tabs = NULL;
716 return TRUE;
718 /* FIXME: count = 1 */
719 if (!(descr->tabs = (INT *)HeapAlloc( descr->heap, 0,
720 descr->nb_tabs * sizeof(INT) )))
721 return FALSE;
722 if (short_ints)
724 INT i;
725 LPINT16 p = (LPINT16)tabs;
727 TRACE("[%04x]: settabstops ", wnd->hwndSelf );
728 for (i = 0; i < descr->nb_tabs; i++) {
729 descr->tabs[i] = *p++<<1; /* FIXME */
730 if (TRACE_ON(listbox)) DPRINTF("%hd ", descr->tabs[i]);
732 if (TRACE_ON(listbox)) DPRINTF("\n");
734 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
735 /* FIXME: repaint the window? */
736 return TRUE;
740 /***********************************************************************
741 * LISTBOX_GetText
743 static LRESULT LISTBOX_GetText( WND *wnd, LB_DESCR *descr, INT index,
744 LPSTR buffer )
746 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
747 if (HAS_STRINGS(descr))
749 if (!buffer)
750 return strlen(descr->items[index].str);
751 strcpy( buffer, descr->items[index].str );
752 return strlen(buffer);
753 } else {
754 if (buffer)
755 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
756 return sizeof(DWORD);
761 /***********************************************************************
762 * LISTBOX_FindStringPos
764 * Find the nearest string located before a given string in sort order.
765 * If 'exact' is TRUE, return an error if we don't get an exact match.
767 static INT LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCSTR str,
768 BOOL exact )
770 INT index, min, max, res = -1;
772 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
773 min = 0;
774 max = descr->nb_items;
775 while (min != max)
777 index = (min + max) / 2;
778 if (HAS_STRINGS(descr))
779 res = lstrcmpiA( descr->items[index].str, str );
780 else
782 COMPAREITEMSTRUCT cis;
784 cis.CtlType = ODT_LISTBOX;
785 cis.CtlID = wnd->wIDmenu;
786 cis.hwndItem = wnd->hwndSelf;
787 cis.itemID1 = index;
788 cis.itemData1 = descr->items[index].data;
789 cis.itemID2 = -1;
790 cis.itemData2 = (DWORD)str;
791 cis.dwLocaleId = descr->locale;
792 res = SendMessageA( descr->owner, WM_COMPAREITEM,
793 wnd->wIDmenu, (LPARAM)&cis );
795 if (!res) return index;
796 if (res > 0) max = index;
797 else min = index + 1;
799 return exact ? -1 : max;
803 /***********************************************************************
804 * LISTBOX_FindFileStrPos
806 * Find the nearest string located before a given string in directory
807 * sort order (i.e. first files, then directories, then drives).
809 static INT LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCSTR str )
811 INT min, max, res = -1;
813 if (!HAS_STRINGS(descr))
814 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
815 min = 0;
816 max = descr->nb_items;
817 while (min != max)
819 INT index = (min + max) / 2;
820 const char *p = descr->items[index].str;
821 if (*p == '[') /* drive or directory */
823 if (*str != '[') res = -1;
824 else if (p[1] == '-') /* drive */
826 if (str[1] == '-') res = str[2] - p[2];
827 else res = -1;
829 else /* directory */
831 if (str[1] == '-') res = 1;
832 else res = lstrcmpiA( str, p );
835 else /* filename */
837 if (*str == '[') res = 1;
838 else res = lstrcmpiA( str, p );
840 if (!res) return index;
841 if (res < 0) max = index;
842 else min = index + 1;
844 return max;
848 /***********************************************************************
849 * LISTBOX_FindString
851 * Find the item beginning with a given string.
853 static INT LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT start,
854 LPCSTR str, BOOL exact )
856 INT i;
857 LB_ITEMDATA *item;
859 if (start >= descr->nb_items) start = -1;
860 item = descr->items + start + 1;
861 if (HAS_STRINGS(descr))
863 if (!str || ! str[0] ) return LB_ERR;
864 if (exact)
866 for (i = start + 1; i < descr->nb_items; i++, item++)
867 if (!lstrcmpiA( str, item->str )) return i;
868 for (i = 0, item = descr->items; i <= start; i++, item++)
869 if (!lstrcmpiA( str, item->str )) return i;
871 else
873 /* Special case for drives and directories: ignore prefix */
874 #define CHECK_DRIVE(item) \
875 if ((item)->str[0] == '[') \
877 if (!strncasecmp( str, (item)->str+1, len )) return i; \
878 if (((item)->str[1] == '-') && !strncasecmp(str,(item)->str+2,len)) \
879 return i; \
882 INT len = strlen(str);
883 for (i = start + 1; i < descr->nb_items; i++, item++)
885 if (!strncasecmp( str, item->str, len )) return i;
886 CHECK_DRIVE(item);
888 for (i = 0, item = descr->items; i <= start; i++, item++)
890 if (!strncasecmp( str, item->str, len )) return i;
891 CHECK_DRIVE(item);
893 #undef CHECK_DRIVE
896 else
898 if (exact && (descr->style & LBS_SORT))
899 /* If sorted, use a WM_COMPAREITEM binary search */
900 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
902 /* Otherwise use a linear search */
903 for (i = start + 1; i < descr->nb_items; i++, item++)
904 if (item->data == (DWORD)str) return i;
905 for (i = 0, item = descr->items; i <= start; i++, item++)
906 if (item->data == (DWORD)str) return i;
908 return LB_ERR;
912 /***********************************************************************
913 * LISTBOX_GetSelCount
915 static LRESULT LISTBOX_GetSelCount( WND *wnd, LB_DESCR *descr )
917 INT i, count;
918 LB_ITEMDATA *item = descr->items;
920 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
921 for (i = count = 0; i < descr->nb_items; i++, item++)
922 if (item->selected) count++;
923 return count;
927 /***********************************************************************
928 * LISTBOX_GetSelItems16
930 static LRESULT LISTBOX_GetSelItems16( WND *wnd, LB_DESCR *descr, INT16 max,
931 LPINT16 array )
933 INT i, count;
934 LB_ITEMDATA *item = descr->items;
936 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
937 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
938 if (item->selected) array[count++] = (INT16)i;
939 return count;
943 /***********************************************************************
944 * LISTBOX_GetSelItems32
946 static LRESULT LISTBOX_GetSelItems( WND *wnd, LB_DESCR *descr, INT max,
947 LPINT array )
949 INT i, count;
950 LB_ITEMDATA *item = descr->items;
952 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
953 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
954 if (item->selected) array[count++] = i;
955 return count;
959 /***********************************************************************
960 * LISTBOX_Paint
962 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC hdc )
964 INT i, col_pos = descr->page_size - 1;
965 RECT rect;
966 RECT focusRect = {-1, -1, -1, -1};
967 HFONT oldFont = 0;
968 HBRUSH hbrush, oldBrush = 0;
970 if (descr->style & LBS_NOREDRAW) return 0;
972 SetRect( &rect, 0, 0, descr->width, descr->height );
973 if (descr->style & LBS_MULTICOLUMN)
974 rect.right = rect.left + descr->column_width;
975 else if (descr->horz_pos)
977 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
978 rect.right += descr->horz_pos;
981 if (descr->font) oldFont = SelectObject( hdc, descr->font );
982 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
983 hdc, (LPARAM)wnd->hwndSelf );
984 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
985 if (wnd->dwStyle & WS_DISABLED)
986 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
988 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
989 (descr->in_focus))
991 /* Special case for empty listbox: paint focus rect */
992 rect.bottom = rect.top + descr->item_height;
993 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
994 ODA_FOCUS, FALSE );
995 rect.top = rect.bottom;
998 /* Paint all the item, regarding the selection
999 Focus state will be painted after */
1001 for (i = descr->top_item; i < descr->nb_items; i++)
1003 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1004 rect.bottom = rect.top + descr->item_height;
1005 else
1006 rect.bottom = rect.top + descr->items[i].height;
1008 if (i == descr->focus_item)
1010 /* keep the focus rect, to paint the focus item after */
1011 focusRect.left = rect.left;
1012 focusRect.right = rect.right;
1013 focusRect.top = rect.top;
1014 focusRect.bottom = rect.bottom;
1016 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1017 rect.top = rect.bottom;
1019 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1021 if (!IS_OWNERDRAW(descr))
1023 /* Clear the bottom of the column */
1024 if (rect.top < descr->height)
1026 rect.bottom = descr->height;
1027 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1028 &rect, NULL, 0, NULL );
1032 /* Go to the next column */
1033 rect.left += descr->column_width;
1034 rect.right += descr->column_width;
1035 rect.top = 0;
1036 col_pos = descr->page_size - 1;
1038 else
1040 col_pos--;
1041 if (rect.top >= descr->height) break;
1045 /* Paint the focus item now */
1046 if (focusRect.top != focusRect.bottom && descr->caret_on)
1047 LISTBOX_PaintItem( wnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1049 if (!IS_OWNERDRAW(descr))
1051 /* Clear the remainder of the client area */
1052 if (rect.top < descr->height)
1054 rect.bottom = descr->height;
1055 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1056 &rect, NULL, 0, NULL );
1058 if (rect.right < descr->width)
1060 rect.left = rect.right;
1061 rect.right = descr->width;
1062 rect.top = 0;
1063 rect.bottom = descr->height;
1064 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1065 &rect, NULL, 0, NULL );
1068 if (oldFont) SelectObject( hdc, oldFont );
1069 if (oldBrush) SelectObject( hdc, oldBrush );
1070 return 0;
1074 /***********************************************************************
1075 * LISTBOX_InvalidateItems
1077 * Invalidate all items from a given item. If the specified item is not
1078 * visible, nothing happens.
1080 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT index )
1082 RECT rect;
1084 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) == 1)
1086 if (descr->style & LBS_NOREDRAW)
1088 descr->style |= LBS_DISPLAYCHANGED;
1089 return;
1091 rect.bottom = descr->height;
1092 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1093 if (descr->style & LBS_MULTICOLUMN)
1095 /* Repaint the other columns */
1096 rect.left = rect.right;
1097 rect.right = descr->width;
1098 rect.top = 0;
1099 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1105 /***********************************************************************
1106 * LISTBOX_GetItemHeight
1108 static LRESULT LISTBOX_GetItemHeight( WND *wnd, LB_DESCR *descr, INT index )
1110 if (descr->style & LBS_OWNERDRAWVARIABLE)
1112 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1113 return descr->items[index].height;
1115 else return descr->item_height;
1119 /***********************************************************************
1120 * LISTBOX_SetItemHeight
1122 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT index,
1123 UINT height )
1125 if (!height) height = 1;
1127 if (descr->style & LBS_OWNERDRAWVARIABLE)
1129 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1130 TRACE("[%04x]: item %d height = %d\n",
1131 wnd->hwndSelf, index, height );
1132 descr->items[index].height = height;
1133 LISTBOX_UpdateScroll( wnd, descr );
1134 LISTBOX_InvalidateItems( wnd, descr, index );
1136 else if (height != descr->item_height)
1138 TRACE("[%04x]: new height = %d\n",
1139 wnd->hwndSelf, height );
1140 descr->item_height = height;
1141 LISTBOX_UpdatePage( wnd, descr );
1142 LISTBOX_UpdateScroll( wnd, descr );
1143 InvalidateRect( wnd->hwndSelf, 0, TRUE );
1145 return LB_OKAY;
1149 /***********************************************************************
1150 * LISTBOX_SetHorizontalPos
1152 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT pos )
1154 INT diff;
1156 if (pos > descr->horz_extent - descr->width)
1157 pos = descr->horz_extent - descr->width;
1158 if (pos < 0) pos = 0;
1159 if (!(diff = descr->horz_pos - pos)) return;
1160 TRACE("[%04x]: new horz pos = %d\n",
1161 wnd->hwndSelf, pos );
1162 descr->horz_pos = pos;
1163 LISTBOX_UpdateScroll( wnd, descr );
1164 if (abs(diff) < descr->width)
1165 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1166 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1167 else
1168 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1172 /***********************************************************************
1173 * LISTBOX_SetHorizontalExtent
1175 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1176 UINT extent )
1178 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1179 return LB_OKAY;
1180 if (extent <= 0) extent = 1;
1181 if (extent == descr->horz_extent) return LB_OKAY;
1182 TRACE("[%04x]: new horz extent = %d\n",
1183 wnd->hwndSelf, extent );
1184 descr->horz_extent = extent;
1185 if (descr->horz_pos > extent - descr->width)
1186 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1187 else
1188 LISTBOX_UpdateScroll( wnd, descr );
1189 return LB_OKAY;
1193 /***********************************************************************
1194 * LISTBOX_SetColumnWidth
1196 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, UINT width)
1198 if (width == descr->column_width) return LB_OKAY;
1199 TRACE("[%04x]: new column width = %d\n",
1200 wnd->hwndSelf, width );
1201 descr->column_width = width;
1202 LISTBOX_UpdatePage( wnd, descr );
1203 return LB_OKAY;
1207 /***********************************************************************
1208 * LISTBOX_SetFont
1210 * Returns the item height.
1212 static INT LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT font )
1214 HDC hdc;
1215 HFONT oldFont = 0;
1216 TEXTMETRICA tm;
1218 descr->font = font;
1220 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE )))
1222 ERR("unable to get DC.\n" );
1223 return 16;
1225 if (font) oldFont = SelectObject( hdc, font );
1226 GetTextMetricsA( hdc, &tm );
1227 if (oldFont) SelectObject( hdc, oldFont );
1228 ReleaseDC( wnd->hwndSelf, hdc );
1229 if (!IS_OWNERDRAW(descr))
1230 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1231 return tm.tmHeight ;
1235 /***********************************************************************
1236 * LISTBOX_MakeItemVisible
1238 * Make sure that a given item is partially or fully visible.
1240 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT index,
1241 BOOL fully )
1243 INT top;
1245 if (index <= descr->top_item) top = index;
1246 else if (descr->style & LBS_MULTICOLUMN)
1248 INT cols = descr->width;
1249 if (!fully) cols += descr->column_width - 1;
1250 if (cols >= descr->column_width) cols /= descr->column_width;
1251 else cols = 1;
1252 if (index < descr->top_item + (descr->page_size * cols)) return;
1253 top = index - descr->page_size * (cols - 1);
1255 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1257 INT height = fully ? descr->items[index].height : 1;
1258 for (top = index; top > descr->top_item; top--)
1259 if ((height += descr->items[top-1].height) > descr->height) break;
1261 else
1263 if (index < descr->top_item + descr->page_size) return;
1264 if (!fully && (index == descr->top_item + descr->page_size) &&
1265 (descr->height > (descr->page_size * descr->item_height))) return;
1266 top = index - descr->page_size + 1;
1268 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1271 /***********************************************************************
1272 * LISTBOX_SetCaretIndex
1274 * NOTES
1275 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1278 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT index,
1279 BOOL fully_visible )
1281 INT oldfocus = descr->focus_item;
1283 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1284 if (index == oldfocus) return LB_OKAY;
1285 descr->focus_item = index;
1286 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1287 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1289 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1290 if (descr->caret_on && (descr->in_focus))
1291 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1293 return LB_OKAY;
1297 /***********************************************************************
1298 * LISTBOX_SelectItemRange
1300 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1302 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT first,
1303 INT last, BOOL on )
1305 INT i;
1307 /* A few sanity checks */
1309 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1310 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1311 if (last == -1) last = descr->nb_items - 1;
1312 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1313 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1314 /* selected_item reflects last selected/unselected item on multiple sel */
1315 descr->selected_item = last;
1317 if (on) /* Turn selection on */
1319 for (i = first; i <= last; i++)
1321 if (descr->items[i].selected) continue;
1322 descr->items[i].selected = TRUE;
1323 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1325 LISTBOX_SetCaretIndex( wnd, descr, last, TRUE );
1327 else /* Turn selection off */
1329 for (i = first; i <= last; i++)
1331 if (!descr->items[i].selected) continue;
1332 descr->items[i].selected = FALSE;
1333 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1336 return LB_OKAY;
1339 /***********************************************************************
1340 * LISTBOX_SetSelection
1342 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT index,
1343 BOOL on, BOOL send_notify )
1345 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1347 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1348 if (descr->style & LBS_MULTIPLESEL)
1350 if (index == -1) /* Select all items */
1351 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1352 else /* Only one item */
1353 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1355 else
1357 INT oldsel = descr->selected_item;
1358 if (index == oldsel) return LB_OKAY;
1359 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1360 if (index != -1) descr->items[index].selected = TRUE;
1361 descr->selected_item = index;
1362 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT );
1363 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1364 if (send_notify && descr->nb_items) SEND_NOTIFICATION( wnd, descr,
1365 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1366 else
1367 if( descr->lphc ) /* set selection change flag for parent combo */
1368 descr->lphc->wState |= CBF_SELCHANGE;
1370 return LB_OKAY;
1374 /***********************************************************************
1375 * LISTBOX_MoveCaret
1377 * Change the caret position and extend the selection to the new caret.
1379 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT index,
1380 BOOL fully_visible )
1382 INT oldfocus = descr->focus_item;
1384 if ((index < 0) || (index >= descr->nb_items))
1385 return;
1387 /* Important, repaint needs to be done in this order if
1388 you want to mimic Windows behavior:
1389 1. Remove the focus and paint the item
1390 2. Remove the selection and paint the item(s)
1391 3. Set the selection and repaint the item(s)
1392 4. Set the focus to 'index' and repaint the item */
1394 /* 1. remove the focus and repaint the item */
1395 descr->focus_item = -1;
1396 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1397 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1399 /* 2. then turn off the previous selection */
1400 /* 3. repaint the new selected item */
1401 if (descr->style & LBS_EXTENDEDSEL)
1403 if (descr->anchor_item != -1)
1405 INT first = min( index, descr->anchor_item );
1406 INT last = max( index, descr->anchor_item );
1407 if (first > 0)
1408 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1409 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1410 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1413 else if (!(descr->style & LBS_MULTIPLESEL))
1415 /* Set selection to new caret item */
1416 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1419 /* 4. repaint the new item with the focus */
1420 descr->focus_item = index;
1421 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1422 if (descr->caret_on && (descr->in_focus))
1423 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1427 /***********************************************************************
1428 * LISTBOX_InsertItem
1430 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT index,
1431 LPSTR str, DWORD data )
1433 LB_ITEMDATA *item;
1434 INT max_items;
1435 INT oldfocus = descr->focus_item;
1437 if (index == -1) index = descr->nb_items;
1438 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1439 if (!descr->items) max_items = 0;
1440 else max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
1441 if (descr->nb_items == max_items)
1443 /* We need to grow the array */
1444 max_items += LB_ARRAY_GRANULARITY;
1445 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
1446 max_items * sizeof(LB_ITEMDATA) )))
1448 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1449 return LB_ERRSPACE;
1451 descr->items = item;
1454 /* Insert the item structure */
1456 item = &descr->items[index];
1457 if (index < descr->nb_items)
1458 RtlMoveMemory( item + 1, item,
1459 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1460 item->str = str;
1461 item->data = data;
1462 item->height = 0;
1463 item->selected = FALSE;
1464 descr->nb_items++;
1466 /* Get item height */
1468 if (descr->style & LBS_OWNERDRAWVARIABLE)
1470 MEASUREITEMSTRUCT mis;
1472 mis.CtlType = ODT_LISTBOX;
1473 mis.CtlID = wnd->wIDmenu;
1474 mis.itemID = index;
1475 mis.itemData = descr->items[index].data;
1476 mis.itemHeight = descr->item_height;
1477 SendMessageA( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
1478 item->height = mis.itemHeight ? mis.itemHeight : 1;
1479 TRACE("[%04x]: measure item %d (%s) = %d\n",
1480 wnd->hwndSelf, index, str ? str : "", item->height );
1483 /* Repaint the items */
1485 LISTBOX_UpdateScroll( wnd, descr );
1486 LISTBOX_InvalidateItems( wnd, descr, index );
1488 /* Move selection and focused item */
1489 /* If listbox was empty, set focus to the first item */
1490 if (descr->nb_items == 1)
1491 LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1492 /* single select don't change selection index in win31 */
1493 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1495 descr->selected_item++;
1496 LISTBOX_SetSelection( wnd, descr, descr->selected_item-1, TRUE, FALSE );
1498 else
1500 if (index <= descr->selected_item)
1502 descr->selected_item++;
1503 descr->focus_item = oldfocus; /* focus not changed */
1506 return LB_OKAY;
1510 /***********************************************************************
1511 * LISTBOX_InsertString
1513 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT index,
1514 LPCSTR str )
1516 LPSTR new_str = NULL;
1517 DWORD data = 0;
1518 LRESULT ret;
1520 if (HAS_STRINGS(descr))
1522 if (!str) str="";
1523 if (!(new_str = HEAP_strdupA( descr->heap, 0, str )))
1525 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1526 return LB_ERRSPACE;
1529 else data = (DWORD)str;
1531 if (index == -1) index = descr->nb_items;
1532 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1534 if (new_str) HeapFree( descr->heap, 0, new_str );
1535 return ret;
1538 TRACE("[%04x]: added item %d '%s'\n",
1539 wnd->hwndSelf, index, HAS_STRINGS(descr) ? new_str : "" );
1540 return index;
1544 /***********************************************************************
1545 * LISTBOX_DeleteItem
1547 * Delete the content of an item. 'index' must be a valid index.
1549 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT index )
1551 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1552 * while Win95 sends it for all items with user data.
1553 * It's probably better to send it too often than not
1554 * often enough, so this is what we do here.
1556 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1558 DELETEITEMSTRUCT dis;
1560 dis.CtlType = ODT_LISTBOX;
1561 dis.CtlID = wnd->wIDmenu;
1562 dis.itemID = index;
1563 dis.hwndItem = wnd->hwndSelf;
1564 dis.itemData = descr->items[index].data;
1565 SendMessageA( descr->owner, WM_DELETEITEM, wnd->wIDmenu, (LPARAM)&dis );
1567 if (HAS_STRINGS(descr) && descr->items[index].str)
1568 HeapFree( descr->heap, 0, descr->items[index].str );
1572 /***********************************************************************
1573 * LISTBOX_RemoveItem
1575 * Remove an item from the listbox and delete its content.
1577 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT index )
1579 LB_ITEMDATA *item;
1580 INT max_items;
1582 if (index == -1) index = descr->nb_items - 1;
1583 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1585 /* We need to invalidate the original rect instead of the updated one. */
1586 LISTBOX_InvalidateItems( wnd, descr, index );
1588 LISTBOX_DeleteItem( wnd, descr, index );
1590 /* Remove the item */
1592 item = &descr->items[index];
1593 if (index < descr->nb_items-1)
1594 RtlMoveMemory( item, item + 1,
1595 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1596 descr->nb_items--;
1597 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1599 /* Shrink the item array if possible */
1601 max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(LB_ITEMDATA);
1602 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1604 max_items -= LB_ARRAY_GRANULARITY;
1605 item = HeapReAlloc( descr->heap, 0, descr->items,
1606 max_items * sizeof(LB_ITEMDATA) );
1607 if (item) descr->items = item;
1609 /* Repaint the items */
1611 LISTBOX_UpdateScroll( wnd, descr );
1612 /* if we removed the scrollbar, reset the top of the list
1613 (correct for owner-drawn ???) */
1614 if (descr->nb_items == descr->page_size)
1615 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1617 /* Move selection and focused item */
1618 if (!IS_MULTISELECT(descr))
1620 if (index == descr->selected_item)
1621 descr->selected_item = -1;
1622 else if (index < descr->selected_item)
1624 descr->selected_item--;
1625 if (ISWIN31) /* win 31 do not change the selected item number */
1626 LISTBOX_SetSelection( wnd, descr, descr->selected_item + 1, TRUE, FALSE);
1630 if (descr->focus_item >= descr->nb_items)
1632 descr->focus_item = descr->nb_items - 1;
1633 if (descr->focus_item < 0) descr->focus_item = 0;
1635 return LB_OKAY;
1639 /***********************************************************************
1640 * LISTBOX_ResetContent
1642 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1644 INT i;
1646 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1647 if (descr->items) HeapFree( descr->heap, 0, descr->items );
1648 descr->nb_items = 0;
1649 descr->top_item = 0;
1650 descr->selected_item = -1;
1651 descr->focus_item = 0;
1652 descr->anchor_item = -1;
1653 descr->items = NULL;
1657 /***********************************************************************
1658 * LISTBOX_SetCount
1660 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT count )
1662 LRESULT ret;
1664 if (HAS_STRINGS(descr)) return LB_ERR;
1665 /* FIXME: this is far from optimal... */
1666 if (count > descr->nb_items)
1668 while (count > descr->nb_items)
1669 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1670 return ret;
1672 else if (count < descr->nb_items)
1674 while (count < descr->nb_items)
1675 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1676 return ret;
1678 return LB_OKAY;
1682 /***********************************************************************
1683 * LISTBOX_Directory
1685 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT attrib,
1686 LPCSTR filespec, BOOL long_names )
1688 HANDLE handle;
1689 LRESULT ret = LB_OKAY;
1690 WIN32_FIND_DATAA entry;
1691 int pos;
1693 /* don't scan directory if we just want drives exclusively */
1694 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1695 /* scan directory */
1696 if ((handle = FindFirstFileA(filespec,&entry)) == INVALID_HANDLE_VALUE)
1698 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1700 else
1704 char buffer[270];
1705 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1707 if (!(attrib & DDL_DIRECTORY) ||
1708 !strcmp( entry.cAlternateFileName, "." )) continue;
1709 if (long_names) sprintf( buffer, "[%s]", entry.cFileName );
1710 else sprintf( buffer, "[%s]", entry.cAlternateFileName );
1712 else /* not a directory */
1714 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1715 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1717 if ((attrib & DDL_EXCLUSIVE) &&
1718 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1719 continue;
1720 #undef ATTRIBS
1721 if (long_names) strcpy( buffer, entry.cFileName );
1722 else strcpy( buffer, entry.cAlternateFileName );
1724 if (!long_names) CharLowerA( buffer );
1725 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1726 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1727 break;
1728 } while (FindNextFileA( handle, &entry ));
1729 FindClose( handle );
1733 /* scan drives */
1734 if ((ret >= 0) && (attrib & DDL_DRIVES))
1736 char buffer[] = "[-a-]";
1737 char root[] = "A:\\";
1738 int drive;
1739 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1741 if (GetDriveTypeA(root) <= DRIVE_NO_ROOT_DIR) continue;
1742 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1743 break;
1746 return ret;
1750 /***********************************************************************
1751 * LISTBOX_HandleVScroll
1753 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr,
1754 WPARAM wParam, LPARAM lParam )
1756 SCROLLINFO info;
1758 if (descr->style & LBS_MULTICOLUMN) return 0;
1759 switch(LOWORD(wParam))
1761 case SB_LINEUP:
1762 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1763 break;
1764 case SB_LINEDOWN:
1765 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1766 break;
1767 case SB_PAGEUP:
1768 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1769 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1770 break;
1771 case SB_PAGEDOWN:
1772 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1773 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1774 break;
1775 case SB_THUMBPOSITION:
1776 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1777 break;
1778 case SB_THUMBTRACK:
1779 info.cbSize = sizeof(info);
1780 info.fMask = SIF_TRACKPOS;
1781 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1782 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1783 break;
1784 case SB_TOP:
1785 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1786 break;
1787 case SB_BOTTOM:
1788 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1789 break;
1791 return 0;
1795 /***********************************************************************
1796 * LISTBOX_HandleHScroll
1798 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr,
1799 WPARAM wParam, LPARAM lParam )
1801 SCROLLINFO info;
1802 INT page;
1804 if (descr->style & LBS_MULTICOLUMN)
1806 switch(LOWORD(wParam))
1808 case SB_LINELEFT:
1809 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1810 TRUE );
1811 break;
1812 case SB_LINERIGHT:
1813 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1814 TRUE );
1815 break;
1816 case SB_PAGELEFT:
1817 page = descr->width / descr->column_width;
1818 if (page < 1) page = 1;
1819 LISTBOX_SetTopItem( wnd, descr,
1820 descr->top_item - page * descr->page_size, TRUE );
1821 break;
1822 case SB_PAGERIGHT:
1823 page = descr->width / descr->column_width;
1824 if (page < 1) page = 1;
1825 LISTBOX_SetTopItem( wnd, descr,
1826 descr->top_item + page * descr->page_size, TRUE );
1827 break;
1828 case SB_THUMBPOSITION:
1829 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1830 TRUE );
1831 break;
1832 case SB_THUMBTRACK:
1833 info.cbSize = sizeof(info);
1834 info.fMask = SIF_TRACKPOS;
1835 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1836 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1837 TRUE );
1838 break;
1839 case SB_LEFT:
1840 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1841 break;
1842 case SB_RIGHT:
1843 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1844 break;
1847 else if (descr->horz_extent)
1849 switch(LOWORD(wParam))
1851 case SB_LINELEFT:
1852 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1853 break;
1854 case SB_LINERIGHT:
1855 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1856 break;
1857 case SB_PAGELEFT:
1858 LISTBOX_SetHorizontalPos( wnd, descr,
1859 descr->horz_pos - descr->width );
1860 break;
1861 case SB_PAGERIGHT:
1862 LISTBOX_SetHorizontalPos( wnd, descr,
1863 descr->horz_pos + descr->width );
1864 break;
1865 case SB_THUMBPOSITION:
1866 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1867 break;
1868 case SB_THUMBTRACK:
1869 info.cbSize = sizeof(info);
1870 info.fMask = SIF_TRACKPOS;
1871 GetScrollInfo( wnd->hwndSelf, SB_HORZ, &info );
1872 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1873 break;
1874 case SB_LEFT:
1875 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1876 break;
1877 case SB_RIGHT:
1878 LISTBOX_SetHorizontalPos( wnd, descr,
1879 descr->horz_extent - descr->width );
1880 break;
1883 return 0;
1886 static LRESULT LISTBOX_HandleMouseWheel(WND *wnd, LB_DESCR *descr,WPARAM wParam, LPARAM lParam )
1888 short gcWheelDelta = 0;
1889 UINT pulScrollLines = 3;
1891 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1893 gcWheelDelta -= (short) HIWORD(wParam);
1895 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1897 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1898 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1899 LISTBOX_SetTopItem( wnd, descr, descr->top_item + cLineScroll, TRUE );
1901 return 0;
1904 /***********************************************************************
1905 * LISTBOX_HandleLButtonDown
1907 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1908 WPARAM wParam, INT x, INT y )
1910 INT index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1911 TRACE("[%04x]: lbuttondown %d,%d item %d\n",
1912 wnd->hwndSelf, x, y, index );
1913 if (!descr->caret_on && (descr->in_focus)) return 0;
1915 if (!descr->in_focus)
1917 if( !descr->lphc ) SetFocus( wnd->hwndSelf );
1918 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1919 : descr->lphc->self->hwndSelf );
1922 if (index != -1)
1924 if (descr->style & LBS_EXTENDEDSEL)
1926 /* we should perhaps make sure that all items are deselected
1927 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1928 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1929 LISTBOX_SetSelection( wnd, descr, -1, FALSE, FALSE);
1932 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1933 if (wParam & MK_CONTROL)
1935 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1936 LISTBOX_SetSelection( wnd, descr, index,
1937 !descr->items[index].selected,
1938 (descr->style & LBS_NOTIFY) != 0);
1940 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1942 else
1944 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1945 LISTBOX_SetSelection( wnd, descr, index,
1946 (!(descr->style & LBS_MULTIPLESEL) ||
1947 !descr->items[index].selected),
1948 (descr->style & LBS_NOTIFY) != 0 );
1952 descr->captured = TRUE;
1953 SetCapture( wnd->hwndSelf );
1954 if (index != -1 && !descr->lphc)
1956 if (descr->style & LBS_NOTIFY )
1957 SendMessageA( descr->owner, WM_LBTRACKPOINT, index,
1958 MAKELPARAM( x, y ) );
1959 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1961 POINT pt;
1963 pt.x = x;
1964 pt.y = y;
1966 if (DragDetect( wnd->hwndSelf, pt ))
1967 SendMessageA( descr->owner, WM_BEGINDRAG, 0, 0 );
1970 return 0;
1974 /*************************************************************************
1975 * LISTBOX_HandleLButtonDownCombo [Internal]
1977 * Process LButtonDown message for the ComboListBox
1979 * PARAMS
1980 * pWnd [I] The windows internal structure
1981 * pDescr [I] The ListBox internal structure
1982 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
1983 * x [I] X Mouse Coordinate
1984 * y [I] Y Mouse Coordinate
1986 * RETURNS
1987 * 0 since we are processing the WM_LBUTTONDOWN Message
1989 * NOTES
1990 * This function is only to be used when a ListBox is a ComboListBox
1993 static LRESULT LISTBOX_HandleLButtonDownCombo( WND *pWnd, LB_DESCR *pDescr,
1994 UINT msg, WPARAM wParam, INT x, INT y)
1996 RECT clientRect, screenRect;
1997 POINT mousePos;
1999 mousePos.x = x;
2000 mousePos.y = y;
2002 GetClientRect(pWnd->hwndSelf, &clientRect);
2004 if(PtInRect(&clientRect, mousePos))
2006 /* MousePos is in client, resume normal processing */
2007 if (msg == WM_LBUTTONDOWN)
2009 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2010 return LISTBOX_HandleLButtonDown( pWnd, pDescr, wParam, x, y);
2012 else if (pDescr->style & LBS_NOTIFY)
2013 SEND_NOTIFICATION( pWnd, pDescr, LBN_DBLCLK );
2014 return 0;
2016 else
2018 POINT screenMousePos;
2019 HWND hWndOldCapture;
2021 /* Check the Non-Client Area */
2022 screenMousePos = mousePos;
2023 hWndOldCapture = GetCapture();
2024 ReleaseCapture();
2025 GetWindowRect(pWnd->hwndSelf, &screenRect);
2026 ClientToScreen(pWnd->hwndSelf, &screenMousePos);
2028 if(!PtInRect(&screenRect, screenMousePos))
2030 LISTBOX_SetSelection( pWnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2031 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2032 return 0;
2034 else
2036 /* Check to see the NC is a scrollbar */
2037 INT nHitTestType=0;
2038 /* Check Vertical scroll bar */
2039 if (pWnd->dwStyle & WS_VSCROLL)
2041 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2042 if (PtInRect( &clientRect, mousePos ))
2044 nHitTestType = HTVSCROLL;
2047 /* Check horizontal scroll bar */
2048 if (pWnd->dwStyle & WS_HSCROLL)
2050 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2051 if (PtInRect( &clientRect, mousePos ))
2053 nHitTestType = HTHSCROLL;
2056 /* Windows sends this message when a scrollbar is clicked
2059 if(nHitTestType != 0)
2061 SendMessageA(pWnd->hwndSelf, WM_NCLBUTTONDOWN, nHitTestType,
2062 MAKELONG(screenMousePos.x, screenMousePos.y));
2064 /* Resume the Capture after scrolling is complete
2066 if(hWndOldCapture != 0)
2068 SetCapture(hWndOldCapture);
2072 return 0;
2075 /***********************************************************************
2076 * LISTBOX_HandleLButtonUp
2078 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
2080 if (LISTBOX_Timer != LB_TIMER_NONE)
2081 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2082 LISTBOX_Timer = LB_TIMER_NONE;
2083 if (descr->captured)
2085 descr->captured = FALSE;
2086 if (GetCapture() == wnd->hwndSelf) ReleaseCapture();
2087 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2088 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2090 return 0;
2094 /***********************************************************************
2095 * LISTBOX_HandleTimer
2097 * Handle scrolling upon a timer event.
2098 * Return TRUE if scrolling should continue.
2100 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
2101 INT index, TIMER_DIRECTION dir )
2103 switch(dir)
2105 case LB_TIMER_UP:
2106 if (descr->top_item) index = descr->top_item - 1;
2107 else index = 0;
2108 break;
2109 case LB_TIMER_LEFT:
2110 if (descr->top_item) index -= descr->page_size;
2111 break;
2112 case LB_TIMER_DOWN:
2113 index = descr->top_item + LISTBOX_GetCurrentPageSize( wnd, descr );
2114 if (index == descr->focus_item) index++;
2115 if (index >= descr->nb_items) index = descr->nb_items - 1;
2116 break;
2117 case LB_TIMER_RIGHT:
2118 if (index + descr->page_size < descr->nb_items)
2119 index += descr->page_size;
2120 break;
2121 case LB_TIMER_NONE:
2122 break;
2124 if (index == descr->focus_item) return FALSE;
2125 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
2126 return TRUE;
2130 /***********************************************************************
2131 * LISTBOX_HandleSystemTimer
2133 * WM_SYSTIMER handler.
2135 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
2137 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
2139 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2140 LISTBOX_Timer = LB_TIMER_NONE;
2142 return 0;
2146 /***********************************************************************
2147 * LISTBOX_HandleMouseMove
2149 * WM_MOUSEMOVE handler.
2151 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
2152 INT x, INT y )
2154 INT index;
2155 TIMER_DIRECTION dir = LB_TIMER_NONE;
2157 if (!descr->captured) return;
2159 if (descr->style & LBS_MULTICOLUMN)
2161 if (y < 0) y = 0;
2162 else if (y >= descr->item_height * descr->page_size)
2163 y = descr->item_height * descr->page_size - 1;
2165 if (x < 0)
2167 dir = LB_TIMER_LEFT;
2168 x = 0;
2170 else if (x >= descr->width)
2172 dir = LB_TIMER_RIGHT;
2173 x = descr->width - 1;
2176 else
2178 if (y < 0) dir = LB_TIMER_UP; /* above */
2179 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2182 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
2183 if (index == -1) index = descr->focus_item;
2184 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
2186 /* Start/stop the system timer */
2188 if (dir != LB_TIMER_NONE)
2189 SetSystemTimer( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2190 else if (LISTBOX_Timer != LB_TIMER_NONE)
2191 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2192 LISTBOX_Timer = dir;
2196 /***********************************************************************
2197 * LISTBOX_HandleKeyDown
2199 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM wParam )
2201 INT caret = -1;
2202 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2203 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2204 bForceSelection = FALSE; /* only for single select list */
2206 if (descr->style & LBS_WANTKEYBOARDINPUT)
2208 caret = SendMessageA( descr->owner, WM_VKEYTOITEM,
2209 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2210 wnd->hwndSelf );
2211 if (caret == -2) return 0;
2213 if (caret == -1) switch(wParam)
2215 case VK_LEFT:
2216 if (descr->style & LBS_MULTICOLUMN)
2218 bForceSelection = FALSE;
2219 if (descr->focus_item >= descr->page_size)
2220 caret = descr->focus_item - descr->page_size;
2221 break;
2223 /* fall through */
2224 case VK_UP:
2225 caret = descr->focus_item - 1;
2226 if (caret < 0) caret = 0;
2227 break;
2228 case VK_RIGHT:
2229 if (descr->style & LBS_MULTICOLUMN)
2231 bForceSelection = FALSE;
2232 if (descr->focus_item + descr->page_size < descr->nb_items)
2233 caret = descr->focus_item + descr->page_size;
2234 break;
2236 /* fall through */
2237 case VK_DOWN:
2238 caret = descr->focus_item + 1;
2239 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2240 break;
2242 case VK_PRIOR:
2243 if (descr->style & LBS_MULTICOLUMN)
2245 INT page = descr->width / descr->column_width;
2246 if (page < 1) page = 1;
2247 caret = descr->focus_item - (page * descr->page_size) + 1;
2249 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(wnd,descr)+1;
2250 if (caret < 0) caret = 0;
2251 break;
2252 case VK_NEXT:
2253 if (descr->style & LBS_MULTICOLUMN)
2255 INT page = descr->width / descr->column_width;
2256 if (page < 1) page = 1;
2257 caret = descr->focus_item + (page * descr->page_size) - 1;
2259 else caret = descr->focus_item+LISTBOX_GetCurrentPageSize(wnd,descr)-1;
2260 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2261 break;
2262 case VK_HOME:
2263 caret = 0;
2264 break;
2265 case VK_END:
2266 caret = descr->nb_items - 1;
2267 break;
2268 case VK_SPACE:
2269 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2270 else if (descr->style & LBS_MULTIPLESEL)
2272 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
2273 !descr->items[descr->focus_item].selected,
2274 (descr->style & LBS_NOTIFY) != 0 );
2276 break;
2277 default:
2278 bForceSelection = FALSE;
2280 if (bForceSelection) /* focused item is used instead of key */
2281 caret = descr->focus_item;
2282 if (caret >= 0)
2284 if ((descr->style & LBS_EXTENDEDSEL) &&
2285 !(GetKeyState( VK_SHIFT ) & 0x8000))
2286 descr->anchor_item = caret;
2287 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2288 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2289 if (descr->style & LBS_NOTIFY)
2291 if( descr->lphc && CB_GETTYPE(descr->lphc) != CBS_SIMPLE )
2293 /* make sure that combo parent doesn't hide us */
2294 descr->lphc->wState |= CBF_NOROLLUP;
2296 if (descr->nb_items) SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2299 return 0;
2303 /***********************************************************************
2304 * LISTBOX_HandleChar
2306 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr,
2307 WPARAM wParam )
2309 INT caret = -1;
2310 char str[2];
2312 str[0] = wParam & 0xff;
2313 str[1] = '\0';
2315 if (descr->style & LBS_WANTKEYBOARDINPUT)
2317 caret = SendMessageA( descr->owner, WM_CHARTOITEM,
2318 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2319 wnd->hwndSelf );
2320 if (caret == -2) return 0;
2322 if (caret == -1)
2323 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2324 if (caret != -1)
2326 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2327 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2328 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2329 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2330 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2332 return 0;
2336 /***********************************************************************
2337 * LISTBOX_Create
2339 static BOOL LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2341 LB_DESCR *descr;
2342 MEASUREITEMSTRUCT mis;
2343 RECT rect;
2345 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2346 return FALSE;
2347 if (!(descr->heap = HeapCreate( 0, 0x10000, 0 )))
2349 HeapFree( GetProcessHeap(), 0, descr );
2350 return FALSE;
2352 GetClientRect( wnd->hwndSelf, &rect );
2353 descr->owner = GetParent( wnd->hwndSelf );
2354 descr->style = wnd->dwStyle;
2355 descr->width = rect.right - rect.left;
2356 descr->height = rect.bottom - rect.top;
2357 descr->items = NULL;
2358 descr->nb_items = 0;
2359 descr->top_item = 0;
2360 descr->selected_item = -1;
2361 descr->focus_item = 0;
2362 descr->anchor_item = -1;
2363 descr->item_height = 1;
2364 descr->page_size = 1;
2365 descr->column_width = 150;
2366 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2367 descr->horz_pos = 0;
2368 descr->nb_tabs = 0;
2369 descr->tabs = NULL;
2370 descr->caret_on = lphc ? FALSE : TRUE;
2371 descr->in_focus = FALSE;
2372 descr->captured = FALSE;
2373 descr->font = 0;
2374 descr->locale = 0; /* FIXME */
2375 descr->lphc = lphc;
2377 if( ( GetExpWinVer16( wnd->hInstance ) & 0xFF00 ) == 0x0300
2378 && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2380 /* Win95 document "List Box Differences" from MSDN:
2381 If a list box in a version 3.x application has either the
2382 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2383 horizontal and vertical scroll bars.
2385 descr->style |= WS_VSCROLL | WS_HSCROLL;
2388 if( lphc )
2390 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2391 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2392 descr->owner = lphc->self->hwndSelf;
2395 *(LB_DESCR **)wnd->wExtra = descr;
2397 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2399 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2400 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2401 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2402 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2404 if (descr->style & LBS_OWNERDRAWFIXED)
2406 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2408 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2409 descr->item_height = lphc->fixedOwnerDrawHeight;
2411 else
2413 mis.CtlType = ODT_LISTBOX;
2414 mis.CtlID = wnd->wIDmenu;
2415 mis.itemID = -1;
2416 mis.itemWidth = 0;
2417 mis.itemData = 0;
2418 mis.itemHeight = descr->item_height;
2419 SendMessageA( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
2420 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2424 return TRUE;
2428 /***********************************************************************
2429 * LISTBOX_Destroy
2431 static BOOL LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2433 LISTBOX_ResetContent( wnd, descr );
2434 HeapDestroy( descr->heap );
2435 HeapFree( GetProcessHeap(), 0, descr );
2436 wnd->wExtra[0] = 0;
2437 return TRUE;
2441 /***********************************************************************
2442 * ListBoxWndProc
2444 static inline LRESULT WINAPI ListBoxWndProc_locked( WND* wnd, UINT msg,
2445 WPARAM wParam, LPARAM lParam )
2447 LRESULT ret;
2448 LB_DESCR *descr;
2449 HWND hwnd = wnd->hwndSelf;
2451 if (!wnd) return 0;
2452 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2454 switch (msg)
2456 case WM_CREATE:
2458 if (!LISTBOX_Create( wnd, NULL ))
2459 return -1;
2460 TRACE("creating wnd=%04x descr=%p\n",
2461 hwnd, *(LB_DESCR **)wnd->wExtra );
2462 return 0;
2464 case WM_NCCREATE:
2467 * When a listbox is not in a combobox and the look
2468 * is win95, the WS_BORDER style is replaced with
2469 * the WS_EX_CLIENTEDGE style.
2471 if ( (TWEAK_WineLook > WIN31_LOOK) &&
2472 (wnd->dwStyle & WS_BORDER) )
2474 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2475 wnd->dwStyle &= ~ WS_BORDER;
2480 /* Ignore all other messages before we get a WM_CREATE */
2481 return DefWindowProcA( hwnd, msg, wParam, lParam );
2484 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2485 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2486 switch(msg)
2488 case LB_RESETCONTENT16:
2489 case LB_RESETCONTENT:
2490 LISTBOX_ResetContent( wnd, descr );
2491 LISTBOX_UpdateScroll( wnd, descr );
2492 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
2493 return 0;
2495 case LB_ADDSTRING16:
2496 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2497 /* fall through */
2498 case LB_ADDSTRING:
2499 wParam = LISTBOX_FindStringPos( wnd, descr, (LPCSTR)lParam, FALSE );
2500 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2502 case LB_INSERTSTRING16:
2503 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2504 wParam = (INT)(INT16)wParam;
2505 /* fall through */
2506 case LB_INSERTSTRING:
2507 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2509 case LB_ADDFILE16:
2510 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2511 /* fall through */
2512 case LB_ADDFILE:
2513 wParam = LISTBOX_FindFileStrPos( wnd, descr, (LPCSTR)lParam );
2514 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2516 case LB_DELETESTRING16:
2517 case LB_DELETESTRING:
2518 if (LISTBOX_RemoveItem( wnd, descr, wParam) != LB_ERR)
2519 return descr->nb_items;
2520 else
2521 return LB_ERR;
2523 case LB_GETITEMDATA16:
2524 case LB_GETITEMDATA:
2525 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2526 return LB_ERR;
2527 return descr->items[wParam].data;
2529 case LB_SETITEMDATA16:
2530 case LB_SETITEMDATA:
2531 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2532 return LB_ERR;
2533 descr->items[wParam].data = (DWORD)lParam;
2534 return LB_OKAY;
2536 case LB_GETCOUNT16:
2537 case LB_GETCOUNT:
2538 return descr->nb_items;
2540 case LB_GETTEXT16:
2541 lParam = (LPARAM)MapSL(lParam);
2542 /* fall through */
2543 case LB_GETTEXT:
2544 return LISTBOX_GetText( wnd, descr, wParam, (LPSTR)lParam );
2546 case LB_GETTEXTLEN16:
2547 /* fall through */
2548 case LB_GETTEXTLEN:
2549 if (wParam >= descr->nb_items)
2550 return LB_ERR;
2551 return (HAS_STRINGS(descr) ? strlen(descr->items[wParam].str)
2552 : sizeof(DWORD));
2554 case LB_GETCURSEL16:
2555 case LB_GETCURSEL:
2556 if (descr->nb_items==0)
2557 return LB_ERR;
2558 if (!IS_MULTISELECT(descr))
2559 return descr->selected_item;
2560 /* else */
2561 if (descr->selected_item!=-1)
2562 return descr->selected_item;
2563 /* else */
2564 return descr->focus_item;
2565 /* otherwise, if the user tries to move the selection with the */
2566 /* arrow keys, we will give the application something to choke on */
2567 case LB_GETTOPINDEX16:
2568 case LB_GETTOPINDEX:
2569 return descr->top_item;
2571 case LB_GETITEMHEIGHT16:
2572 case LB_GETITEMHEIGHT:
2573 return LISTBOX_GetItemHeight( wnd, descr, wParam );
2575 case LB_SETITEMHEIGHT16:
2576 lParam = LOWORD(lParam);
2577 /* fall through */
2578 case LB_SETITEMHEIGHT:
2579 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2581 case LB_ITEMFROMPOINT:
2583 POINT pt;
2584 RECT rect;
2586 pt.x = LOWORD(lParam);
2587 pt.y = HIWORD(lParam);
2588 rect.left = 0;
2589 rect.top = 0;
2590 rect.right = descr->width;
2591 rect.bottom = descr->height;
2593 return MAKELONG( LISTBOX_GetItemFromPoint(wnd, descr, pt.x, pt.y),
2594 !PtInRect( &rect, pt ) );
2597 case LB_SETCARETINDEX16:
2598 case LB_SETCARETINDEX:
2599 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2600 if (LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam ) == LB_ERR)
2601 return LB_ERR;
2602 else if (ISWIN31)
2603 return wParam;
2604 else
2605 return LB_OKAY;
2607 case LB_GETCARETINDEX16:
2608 case LB_GETCARETINDEX:
2609 return descr->focus_item;
2611 case LB_SETTOPINDEX16:
2612 case LB_SETTOPINDEX:
2613 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2615 case LB_SETCOLUMNWIDTH16:
2616 case LB_SETCOLUMNWIDTH:
2617 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2619 case LB_GETITEMRECT16:
2621 RECT rect;
2622 ret = LISTBOX_GetItemRect( wnd, descr, (INT16)wParam, &rect );
2623 CONV_RECT32TO16( &rect, MapSL(lParam) );
2625 return ret;
2627 case LB_GETITEMRECT:
2628 return LISTBOX_GetItemRect( wnd, descr, wParam, (RECT *)lParam );
2630 case LB_FINDSTRING16:
2631 wParam = (INT)(INT16)wParam;
2632 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2633 /* fall through */
2634 case LB_FINDSTRING:
2635 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, FALSE );
2637 case LB_FINDSTRINGEXACT16:
2638 wParam = (INT)(INT16)wParam;
2639 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2640 /* fall through */
2641 case LB_FINDSTRINGEXACT:
2642 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2644 case LB_SELECTSTRING16:
2645 wParam = (INT)(INT16)wParam;
2646 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2647 /* fall through */
2648 case LB_SELECTSTRING:
2650 INT index = LISTBOX_FindString( wnd, descr, wParam,
2651 (LPCSTR)lParam, FALSE );
2652 if (index == LB_ERR)
2653 return LB_ERR;
2654 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2655 return index;
2658 case LB_GETSEL16:
2659 wParam = (INT)(INT16)wParam;
2660 /* fall through */
2661 case LB_GETSEL:
2662 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2663 return LB_ERR;
2664 return descr->items[wParam].selected;
2666 case LB_SETSEL16:
2667 lParam = (INT)(INT16)lParam;
2668 /* fall through */
2669 case LB_SETSEL:
2670 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2672 case LB_SETCURSEL16:
2673 wParam = (INT)(INT16)wParam;
2674 /* fall through */
2675 case LB_SETCURSEL:
2676 if (IS_MULTISELECT(descr)) return LB_ERR;
2677 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2678 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2680 case LB_GETSELCOUNT16:
2681 case LB_GETSELCOUNT:
2682 return LISTBOX_GetSelCount( wnd, descr );
2684 case LB_GETSELITEMS16:
2685 return LISTBOX_GetSelItems16( wnd, descr, wParam,
2686 (LPINT16)MapSL(lParam) );
2688 case LB_GETSELITEMS:
2689 return LISTBOX_GetSelItems( wnd, descr, wParam, (LPINT)lParam );
2691 case LB_SELITEMRANGE16:
2692 case LB_SELITEMRANGE:
2693 if (LOWORD(lParam) <= HIWORD(lParam))
2694 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2695 HIWORD(lParam), wParam );
2696 else
2697 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2698 LOWORD(lParam), wParam );
2700 case LB_SELITEMRANGEEX16:
2701 case LB_SELITEMRANGEEX:
2702 if ((INT)lParam >= (INT)wParam)
2703 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2704 else
2705 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2707 case LB_GETHORIZONTALEXTENT16:
2708 case LB_GETHORIZONTALEXTENT:
2709 return descr->horz_extent;
2711 case LB_SETHORIZONTALEXTENT16:
2712 case LB_SETHORIZONTALEXTENT:
2713 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2715 case LB_GETANCHORINDEX16:
2716 case LB_GETANCHORINDEX:
2717 return descr->anchor_item;
2719 case LB_SETANCHORINDEX16:
2720 wParam = (INT)(INT16)wParam;
2721 /* fall through */
2722 case LB_SETANCHORINDEX:
2723 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2724 return LB_ERR;
2725 descr->anchor_item = (INT)wParam;
2726 return LB_OKAY;
2728 case LB_DIR16:
2729 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2730 * be set automatically (this is different in Win32) */
2731 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2732 return LISTBOX_Directory( wnd, descr, wParam, MapSL(lParam), FALSE );
2734 case LB_DIR:
2735 return LISTBOX_Directory( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2737 case LB_GETLOCALE:
2738 return descr->locale;
2740 case LB_SETLOCALE:
2741 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2742 return LB_OKAY;
2744 case LB_INITSTORAGE:
2745 return LISTBOX_InitStorage( wnd, descr, wParam, (DWORD)lParam );
2747 case LB_SETCOUNT:
2748 return LISTBOX_SetCount( wnd, descr, (INT)wParam );
2750 case LB_SETTABSTOPS16:
2751 return LISTBOX_SetTabStops( wnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2753 case LB_SETTABSTOPS:
2754 return LISTBOX_SetTabStops( wnd, descr, wParam, (LPINT)lParam, FALSE );
2756 case LB_CARETON16:
2757 case LB_CARETON:
2758 if (descr->caret_on)
2759 return LB_OKAY;
2760 descr->caret_on = TRUE;
2761 if ((descr->focus_item != -1) && (descr->in_focus))
2762 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2763 return LB_OKAY;
2765 case LB_CARETOFF16:
2766 case LB_CARETOFF:
2767 if (!descr->caret_on)
2768 return LB_OKAY;
2769 descr->caret_on = FALSE;
2770 if ((descr->focus_item != -1) && (descr->in_focus))
2771 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2772 return LB_OKAY;
2774 case WM_DESTROY:
2775 return LISTBOX_Destroy( wnd, descr );
2777 case WM_ENABLE:
2778 InvalidateRect( hwnd, NULL, TRUE );
2779 return 0;
2781 case WM_SETREDRAW:
2782 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2783 return 0;
2785 case WM_GETDLGCODE:
2786 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2788 case WM_PAINT:
2790 PAINTSTRUCT ps;
2791 HDC hdc = ( wParam ) ? ((HDC)wParam)
2792 : BeginPaint( hwnd, &ps );
2793 ret = LISTBOX_Paint( wnd, descr, hdc );
2794 if( !wParam ) EndPaint( hwnd, &ps );
2796 return ret;
2797 case WM_SIZE:
2798 LISTBOX_UpdateSize( wnd, descr );
2799 return 0;
2800 case WM_GETFONT:
2801 return descr->font;
2802 case WM_SETFONT:
2803 LISTBOX_SetFont( wnd, descr, (HFONT)wParam );
2804 if (lParam) InvalidateRect( wnd->hwndSelf, 0, TRUE );
2805 return 0;
2806 case WM_SETFOCUS:
2807 descr->in_focus = TRUE;
2808 descr->caret_on = TRUE;
2809 if (descr->focus_item != -1)
2810 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2811 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2812 return 0;
2813 case WM_KILLFOCUS:
2814 descr->in_focus = FALSE;
2815 if ((descr->focus_item != -1) && descr->caret_on)
2816 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2817 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2818 return 0;
2819 case WM_HSCROLL:
2820 return LISTBOX_HandleHScroll( wnd, descr, wParam, lParam );
2821 case WM_VSCROLL:
2822 return LISTBOX_HandleVScroll( wnd, descr, wParam, lParam );
2823 case WM_MOUSEACTIVATE:
2824 return MA_NOACTIVATE;
2825 case WM_MOUSEWHEEL:
2826 if (wParam & (MK_SHIFT | MK_CONTROL))
2827 return DefWindowProcA( hwnd, msg, wParam, lParam );
2828 return LISTBOX_HandleMouseWheel( wnd, descr, wParam, lParam );
2829 case WM_LBUTTONDOWN:
2830 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2831 (INT16)LOWORD(lParam),
2832 (INT16)HIWORD(lParam) );
2833 case WM_LBUTTONDBLCLK:
2834 if (descr->style & LBS_NOTIFY)
2835 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2836 return 0;
2837 case WM_MOUSEMOVE:
2838 if (GetCapture() == hwnd)
2839 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2840 (INT16)HIWORD(lParam) );
2841 return 0;
2842 case WM_LBUTTONUP:
2843 return LISTBOX_HandleLButtonUp( wnd, descr );
2844 case WM_KEYDOWN:
2845 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2846 case WM_CHAR:
2847 return LISTBOX_HandleChar( wnd, descr, wParam );
2848 case WM_SYSTIMER:
2849 return LISTBOX_HandleSystemTimer( wnd, descr );
2850 case WM_ERASEBKGND:
2851 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2853 RECT rect;
2854 HBRUSH hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
2855 wParam, (LPARAM)wnd->hwndSelf );
2856 GetClientRect(hwnd, &rect);
2857 if (hbrush) FillRect( (HDC)wParam, &rect, hbrush );
2859 return 1;
2860 case WM_DROPFILES:
2861 if( !descr->lphc )
2862 return SendMessageA( descr->owner, msg, wParam, lParam );
2863 break;
2865 case WM_DROPOBJECT:
2866 case WM_QUERYDROPOBJECT:
2867 case WM_DRAGSELECT:
2868 case WM_DRAGMOVE:
2869 if( !descr->lphc )
2871 LPDRAGINFO16 dragInfo = MapSL( lParam );
2872 dragInfo->l = LISTBOX_GetItemFromPoint( wnd, descr, dragInfo->pt.x,
2873 dragInfo->pt.y );
2874 return SendMessageA( descr->owner, msg, wParam, lParam );
2876 break;
2878 default:
2879 if ((msg >= WM_USER) && (msg < 0xc000))
2880 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
2881 hwnd, msg, wParam, lParam );
2882 return DefWindowProcA( hwnd, msg, wParam, lParam );
2884 return 0;
2887 /***********************************************************************
2888 * ListBoxWndProcA
2890 * This is just a wrapper for the real wndproc, it only does window locking
2891 * and unlocking.
2893 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2895 WND* wndPtr = WIN_FindWndPtr( hwnd );
2896 LRESULT res = ListBoxWndProc_locked(wndPtr,msg,wParam,lParam);
2898 WIN_ReleaseWndPtr(wndPtr);
2899 return res;
2902 /***********************************************************************
2903 * COMBO_Directory
2905 LRESULT COMBO_Directory( LPHEADCOMBO lphc, UINT attrib, LPSTR dir, BOOL bLong)
2907 WND *wnd = WIN_FindWndPtr( lphc->hWndLBox );
2909 if( wnd )
2911 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2912 if( descr )
2914 LRESULT lRet = LISTBOX_Directory( wnd, descr, attrib, dir, bLong );
2916 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
2917 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
2918 WIN_ReleaseWndPtr(wnd);
2919 return lRet;
2921 WIN_ReleaseWndPtr(wnd);
2923 return CB_ERR;
2926 /***********************************************************************
2927 * ComboLBWndProc_locked
2929 * The real combo listbox wndproc, but called with locked WND struct.
2931 static inline LRESULT WINAPI ComboLBWndProc_locked( WND* wnd, UINT msg,
2932 WPARAM wParam, LPARAM lParam )
2934 LRESULT lRet = 0;
2935 HWND hwnd = wnd->hwndSelf;
2937 if (wnd)
2939 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2941 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
2942 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2944 if( descr || msg == WM_CREATE )
2946 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
2948 switch( msg )
2950 case WM_CREATE:
2951 #define lpcs ((LPCREATESTRUCTA)lParam)
2952 TRACE_(combo)("\tpassed parent handle = 0x%08x\n",
2953 (UINT)lpcs->lpCreateParams);
2955 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
2956 #undef lpcs
2957 return LISTBOX_Create( wnd, lphc );
2958 case WM_MOUSEMOVE:
2959 if ( (TWEAK_WineLook > WIN31_LOOK) &&
2960 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
2962 POINT mousePos;
2963 BOOL captured;
2964 RECT clientRect;
2966 mousePos.x = (INT16)LOWORD(lParam);
2967 mousePos.y = (INT16)HIWORD(lParam);
2970 * If we are in a dropdown combobox, we simulate that
2971 * the mouse is captured to show the tracking of the item.
2973 GetClientRect(hwnd, &clientRect);
2975 if (PtInRect( &clientRect, mousePos ))
2977 captured = descr->captured;
2978 descr->captured = TRUE;
2980 LISTBOX_HandleMouseMove( wnd, descr,
2981 mousePos.x, mousePos.y);
2983 descr->captured = captured;
2986 else
2988 LISTBOX_HandleMouseMove( wnd, descr,
2989 mousePos.x, mousePos.y);
2992 return 0;
2995 else
2998 * If we are in Win3.1 look, go with the default behavior.
3000 return ListBoxWndProcA( hwnd, msg, wParam, lParam );
3002 case WM_LBUTTONUP:
3003 if (TWEAK_WineLook > WIN31_LOOK)
3005 POINT mousePos;
3006 RECT clientRect;
3009 * If the mouse button "up" is not in the listbox,
3010 * we make sure there is no selection by re-selecting the
3011 * item that was selected when the listbox was made visible.
3013 mousePos.x = (INT16)LOWORD(lParam);
3014 mousePos.y = (INT16)HIWORD(lParam);
3016 GetClientRect(hwnd, &clientRect);
3019 * When the user clicks outside the combobox and the focus
3020 * is lost, the owning combobox will send a fake buttonup with
3021 * 0xFFFFFFF as the mouse location, we must also revert the
3022 * selection to the original selection.
3024 if ( (lParam == 0xFFFFFFFF) ||
3025 (!PtInRect( &clientRect, mousePos )) )
3027 LISTBOX_MoveCaret( wnd,
3028 descr,
3029 lphc->droppedIndex,
3030 FALSE );
3033 return LISTBOX_HandleLButtonUp( wnd, descr );
3034 case WM_LBUTTONDBLCLK:
3035 case WM_LBUTTONDOWN:
3036 return LISTBOX_HandleLButtonDownCombo(wnd, descr, msg, wParam,
3037 (INT16)LOWORD(lParam),
3038 (INT16)HIWORD(lParam) );
3039 case WM_MOUSEACTIVATE:
3040 return MA_NOACTIVATE;
3041 case WM_NCACTIVATE:
3042 return FALSE;
3043 case WM_KEYDOWN:
3044 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3046 /* for some reason(?) Windows makes it possible to
3047 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3049 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3050 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3051 && (wParam == VK_DOWN || wParam == VK_UP)) )
3053 COMBO_FlipListbox( lphc, FALSE, FALSE );
3054 return 0;
3057 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
3059 case LB_SETCURSEL16:
3060 case LB_SETCURSEL:
3061 lRet = ListBoxWndProcA( hwnd, msg, wParam, lParam );
3062 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3063 return lRet;
3064 case WM_NCDESTROY:
3065 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3066 lphc->hWndLBox = 0;
3067 /* fall through */
3069 default:
3070 return ListBoxWndProcA( hwnd, msg, wParam, lParam );
3073 lRet = DefWindowProcA( hwnd, msg, wParam, lParam );
3075 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3077 return lRet;
3080 /***********************************************************************
3081 * ComboLBWndProc
3083 * NOTE: in Windows, winproc address of the ComboLBox is the same
3084 * as that of the Listbox.
3086 * This is just a wrapper for the real wndproc, it only does window locking
3087 * and unlocking.
3089 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg,
3090 WPARAM wParam, LPARAM lParam )
3092 WND *wnd = WIN_FindWndPtr( hwnd );
3093 LRESULT res = ComboLBWndProc_locked(wnd,msg,wParam,lParam);
3095 WIN_ReleaseWndPtr(wnd);
3096 return res;