Do not free the not owned error string.
[wine.git] / controls / listbox.c
blob06faf1359950a45dfd97a1101aee2258eefb1f5a
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 );
115 static LRESULT LISTBOX_GetItemRect( WND *wnd, LB_DESCR *descr, INT index,
116 RECT *rect );
118 /*********************************************************************
119 * listbox class descriptor
121 const struct builtin_class_descr LISTBOX_builtin_class =
123 "ListBox", /* name */
124 CS_GLOBALCLASS | CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
125 ListBoxWndProcA, /* procA */
126 NULL, /* procW (FIXME) */
127 sizeof(LB_DESCR *), /* extra */
128 IDC_ARROWA, /* cursor */
129 0 /* brush */
133 /*********************************************************************
134 * combolbox class descriptor
136 const struct builtin_class_descr COMBOLBOX_builtin_class =
138 "ComboLBox", /* name */
139 CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS, /* style */
140 ComboLBWndProcA, /* procA */
141 NULL, /* procW (FIXME) */
142 sizeof(LB_DESCR *), /* extra */
143 IDC_ARROWA, /* cursor */
144 0 /* brush */
148 /***********************************************************************
149 * LISTBOX_Dump
151 void LISTBOX_Dump( WND *wnd )
153 INT i;
154 LB_ITEMDATA *item;
155 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
157 TRACE( "Listbox:\n" );
158 TRACE( "hwnd=%04x descr=%08x heap=%08x items=%d top=%d\n",
159 wnd->hwndSelf, (UINT)descr, descr->heap, descr->nb_items,
160 descr->top_item );
161 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
163 TRACE( "%4d: %-40s %d %08lx %3d\n",
164 i, item->str, item->selected, item->data, item->height );
169 /***********************************************************************
170 * LISTBOX_GetCurrentPageSize
172 * Return the current page size
174 static INT LISTBOX_GetCurrentPageSize( WND *wnd, LB_DESCR *descr )
176 INT i, height;
177 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
178 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
180 if ((height += descr->items[i].height) > descr->height) break;
182 if (i == descr->top_item) return 1;
183 else return i - descr->top_item;
187 /***********************************************************************
188 * LISTBOX_GetMaxTopIndex
190 * Return the maximum possible index for the top of the listbox.
192 static INT LISTBOX_GetMaxTopIndex( WND *wnd, LB_DESCR *descr )
194 INT max, page;
196 if (descr->style & LBS_OWNERDRAWVARIABLE)
198 page = descr->height;
199 for (max = descr->nb_items - 1; max >= 0; max--)
200 if ((page -= descr->items[max].height) < 0) break;
201 if (max < descr->nb_items - 1) max++;
203 else if (descr->style & LBS_MULTICOLUMN)
205 if ((page = descr->width / descr->column_width) < 1) page = 1;
206 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
207 max = (max - page) * descr->page_size;
209 else
211 max = descr->nb_items - descr->page_size;
213 if (max < 0) max = 0;
214 return max;
218 /***********************************************************************
219 * LISTBOX_UpdateScroll
221 * Update the scrollbars. Should be called whenever the content
222 * of the listbox changes.
224 static void LISTBOX_UpdateScroll( WND *wnd, LB_DESCR *descr )
226 SCROLLINFO info;
228 /* Check the listbox scroll bar flags individually before we call
229 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
230 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
231 scroll bar when we do not need one.
232 if (!(descr->style & WS_VSCROLL)) return;
235 /* It is important that we check descr->style, and not wnd->dwStyle,
236 for WS_VSCROLL, as the former is exactly the one passed in
237 argument to CreateWindow.
238 In Windows (and from now on in Wine :) a listbox created
239 with such a style (no WS_SCROLL) does not update
240 the scrollbar with listbox-related data, thus letting
241 the programmer use it for his/her own purposes. */
243 if (descr->style & LBS_NOREDRAW) return;
244 info.cbSize = sizeof(info);
246 if (descr->style & LBS_MULTICOLUMN)
248 info.nMin = 0;
249 info.nMax = (descr->nb_items - 1) / descr->page_size;
250 info.nPos = descr->top_item / descr->page_size;
251 info.nPage = descr->width / descr->column_width;
252 if (info.nPage < 1) info.nPage = 1;
253 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
254 if (descr->style & LBS_DISABLENOSCROLL)
255 info.fMask |= SIF_DISABLENOSCROLL;
256 if (descr->style & WS_HSCROLL)
257 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
258 info.nMax = 0;
259 info.fMask = SIF_RANGE;
260 if (descr->style & WS_VSCROLL)
261 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
263 else
265 info.nMin = 0;
266 info.nMax = descr->nb_items - 1;
267 info.nPos = descr->top_item;
268 info.nPage = LISTBOX_GetCurrentPageSize( wnd, descr );
269 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
270 if (descr->style & LBS_DISABLENOSCROLL)
271 info.fMask |= SIF_DISABLENOSCROLL;
272 if (descr->style & WS_VSCROLL)
273 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
275 if (descr->horz_extent)
277 info.nMin = 0;
278 info.nMax = descr->horz_extent - 1;
279 info.nPos = descr->horz_pos;
280 info.nPage = descr->width;
281 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
282 if (descr->style & LBS_DISABLENOSCROLL)
283 info.fMask |= SIF_DISABLENOSCROLL;
284 if (descr->style & WS_HSCROLL)
285 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( wnd, 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 UINT 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 (1==LISTBOX_GetItemRect( wnd, descr, descr->focus_item, &rect ))
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( WND *wnd, LB_DESCR *descr, INT index,
430 RECT *rect )
432 /* Index <= 0 is legal even on empty listboxes */
433 if (index && (index >= descr->nb_items)) return -1;
434 SetRect( rect, 0, 0, descr->width, descr->height );
435 if (descr->style & LBS_MULTICOLUMN)
437 INT col = (index / descr->page_size) -
438 (descr->top_item / descr->page_size);
439 rect->left += col * descr->column_width;
440 rect->right = rect->left + descr->column_width;
441 rect->top += (index % descr->page_size) * descr->item_height;
442 rect->bottom = rect->top + descr->item_height;
444 else if (descr->style & LBS_OWNERDRAWVARIABLE)
446 INT i;
447 rect->right += descr->horz_pos;
448 if ((index >= 0) && (index < descr->nb_items))
450 if (index < descr->top_item)
452 for (i = descr->top_item-1; i >= index; i--)
453 rect->top -= descr->items[i].height;
455 else
457 for (i = descr->top_item; i < index; i++)
458 rect->top += descr->items[i].height;
460 rect->bottom = rect->top + descr->items[index].height;
464 else
466 rect->top += (index - descr->top_item) * descr->item_height;
467 rect->bottom = rect->top + descr->item_height;
468 rect->right += descr->horz_pos;
471 return ((rect->left < descr->width) && (rect->right > 0) &&
472 (rect->top < descr->height) && (rect->bottom > 0));
476 /***********************************************************************
477 * LISTBOX_GetItemFromPoint
479 * Return the item nearest from point (x,y) (in client coordinates).
481 static INT LISTBOX_GetItemFromPoint( WND *wnd, LB_DESCR *descr,
482 INT x, INT y )
484 INT index = descr->top_item;
486 if (!descr->nb_items) return -1; /* No items */
487 if (descr->style & LBS_OWNERDRAWVARIABLE)
489 INT pos = 0;
490 if (y >= 0)
492 while (index < descr->nb_items)
494 if ((pos += descr->items[index].height) > y) break;
495 index++;
498 else
500 while (index > 0)
502 index--;
503 if ((pos -= descr->items[index].height) <= y) break;
507 else if (descr->style & LBS_MULTICOLUMN)
509 if (y >= descr->item_height * descr->page_size) return -1;
510 if (y >= 0) index += y / descr->item_height;
511 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
512 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
514 else
516 index += (y / descr->item_height);
518 if (index < 0) return 0;
519 if (index >= descr->nb_items) return -1;
520 return index;
524 /***********************************************************************
525 * LISTBOX_PaintItem
527 * Paint an item.
529 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC hdc,
530 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
532 LB_ITEMDATA *item = NULL;
533 if (index < descr->nb_items) item = &descr->items[index];
535 if (IS_OWNERDRAW(descr))
537 DRAWITEMSTRUCT dis;
538 RECT r;
539 HRGN hrgn;
541 if (!item)
543 if (action == ODA_FOCUS)
544 DrawFocusRect( hdc, rect );
545 else
546 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
547 return;
550 /* some programs mess with the clipping region when
551 drawing the item, *and* restore the previous region
552 after they are done, so a region has better to exist
553 else everything ends clipped */
554 GetClientRect(wnd->hwndSelf, &r);
555 hrgn = CreateRectRgnIndirect(&r);
556 SelectClipRgn( hdc, hrgn);
557 DeleteObject( hrgn );
559 dis.CtlType = ODT_LISTBOX;
560 dis.CtlID = wnd->wIDmenu;
561 dis.hwndItem = wnd->hwndSelf;
562 dis.itemAction = action;
563 dis.hDC = hdc;
564 dis.itemID = index;
565 dis.itemState = 0;
566 if (item && item->selected) dis.itemState |= ODS_SELECTED;
567 if (!ignoreFocus && (descr->focus_item == index) &&
568 (descr->caret_on) &&
569 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
570 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
571 dis.itemData = item ? item->data : 0;
572 dis.rcItem = *rect;
573 TRACE("[%04x]: drawitem %d (%s) action=%02x "
574 "state=%02x rect=%d,%d-%d,%d\n",
575 wnd->hwndSelf, index, item ? item->str : "", action,
576 dis.itemState, rect->left, rect->top,
577 rect->right, rect->bottom );
578 SendMessageA(descr->owner, WM_DRAWITEM, wnd->wIDmenu, (LPARAM)&dis);
580 else
582 COLORREF oldText = 0, oldBk = 0;
584 if (action == ODA_FOCUS)
586 DrawFocusRect( hdc, rect );
587 return;
589 if (item && item->selected)
591 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
592 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
595 TRACE("[%04x]: painting %d (%s) action=%02x "
596 "rect=%d,%d-%d,%d\n",
597 wnd->hwndSelf, index, item ? item->str : "", action,
598 rect->left, rect->top, rect->right, rect->bottom );
599 if (!item)
600 ExtTextOutA( hdc, rect->left + 1, rect->top,
601 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
602 else if (!(descr->style & LBS_USETABSTOPS))
603 ExtTextOutA( hdc, rect->left + 1, rect->top,
604 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
605 strlen(item->str), NULL );
606 else
608 /* Output empty string to paint background in the full width. */
609 ExtTextOutA( hdc, rect->left + 1, rect->top,
610 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
611 TabbedTextOutA( hdc, rect->left + 1 , rect->top,
612 item->str, strlen(item->str),
613 descr->nb_tabs, descr->tabs, 0);
615 if (item && item->selected)
617 SetBkColor( hdc, oldBk );
618 SetTextColor( hdc, oldText );
620 if (!ignoreFocus && (descr->focus_item == index) &&
621 (descr->caret_on) &&
622 (descr->in_focus)) DrawFocusRect( hdc, rect );
627 /***********************************************************************
628 * LISTBOX_SetRedraw
630 * Change the redraw flag.
632 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL on )
634 if (on)
636 if (!(descr->style & LBS_NOREDRAW)) return;
637 descr->style &= ~LBS_NOREDRAW;
638 if (descr->style & LBS_DISPLAYCHANGED)
639 { /* page was changed while setredraw false, refresh automatically */
640 InvalidateRect(wnd->hwndSelf, NULL, TRUE);
641 if ((descr->top_item + descr->page_size) > descr->nb_items)
642 { /* reset top of page if less than number of items/page */
643 descr->top_item = descr->nb_items - descr->page_size;
644 if (descr->top_item < 0) descr->top_item = 0;
646 descr->style &= ~LBS_DISPLAYCHANGED;
648 LISTBOX_UpdateScroll( wnd, descr );
650 else descr->style |= LBS_NOREDRAW;
654 /***********************************************************************
655 * LISTBOX_RepaintItem
657 * Repaint a single item synchronously.
659 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT index,
660 UINT action )
662 HDC hdc;
663 RECT rect;
664 HFONT oldFont = 0;
665 HBRUSH hbrush, oldBrush = 0;
667 /* Do not repaint the item if the item is not visible */
668 if (!IsWindowVisible(wnd->hwndSelf)) return;
669 if (descr->style & LBS_NOREDRAW)
671 descr->style |= LBS_DISPLAYCHANGED;
672 return;
674 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) != 1) return;
675 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE ))) return;
676 if (descr->font) oldFont = SelectObject( hdc, descr->font );
677 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
678 hdc, (LPARAM)wnd->hwndSelf );
679 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
680 if (wnd->dwStyle & WS_DISABLED)
681 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
682 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
683 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action, FALSE );
684 if (oldFont) SelectObject( hdc, oldFont );
685 if (oldBrush) SelectObject( hdc, oldBrush );
686 ReleaseDC( wnd->hwndSelf, hdc );
690 /***********************************************************************
691 * LISTBOX_InitStorage
693 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT nb_items,
694 DWORD bytes )
696 LB_ITEMDATA *item;
698 nb_items += LB_ARRAY_GRANULARITY - 1;
699 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
700 if (descr->items)
701 nb_items += HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
702 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
703 nb_items * sizeof(LB_ITEMDATA) )))
705 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
706 return LB_ERRSPACE;
708 descr->items = item;
709 return LB_OKAY;
713 /***********************************************************************
714 * LISTBOX_SetTabStops
716 static BOOL LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT count,
717 LPINT tabs, BOOL short_ints )
719 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
720 if (descr->tabs) HeapFree( descr->heap, 0, descr->tabs );
721 if (!(descr->nb_tabs = count))
723 descr->tabs = NULL;
724 return TRUE;
726 /* FIXME: count = 1 */
727 if (!(descr->tabs = (INT *)HeapAlloc( descr->heap, 0,
728 descr->nb_tabs * sizeof(INT) )))
729 return FALSE;
730 if (short_ints)
732 INT i;
733 LPINT16 p = (LPINT16)tabs;
735 TRACE("[%04x]: settabstops ", wnd->hwndSelf );
736 for (i = 0; i < descr->nb_tabs; i++) {
737 descr->tabs[i] = *p++<<1; /* FIXME */
738 if (TRACE_ON(listbox)) DPRINTF("%hd ", descr->tabs[i]);
740 if (TRACE_ON(listbox)) DPRINTF("\n");
742 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
743 /* FIXME: repaint the window? */
744 return TRUE;
748 /***********************************************************************
749 * LISTBOX_GetText
751 static LRESULT LISTBOX_GetText( WND *wnd, LB_DESCR *descr, INT index,
752 LPSTR buffer )
754 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
755 if (HAS_STRINGS(descr))
757 if (!buffer)
758 return strlen(descr->items[index].str);
759 strcpy( buffer, descr->items[index].str );
760 return strlen(buffer);
761 } else {
762 if (buffer)
763 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
764 return sizeof(DWORD);
769 /***********************************************************************
770 * LISTBOX_FindStringPos
772 * Find the nearest string located before a given string in sort order.
773 * If 'exact' is TRUE, return an error if we don't get an exact match.
775 static INT LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCSTR str,
776 BOOL exact )
778 INT index, min, max, res = -1;
780 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
781 min = 0;
782 max = descr->nb_items;
783 while (min != max)
785 index = (min + max) / 2;
786 if (HAS_STRINGS(descr))
787 res = lstrcmpiA( descr->items[index].str, str );
788 else
790 COMPAREITEMSTRUCT cis;
792 cis.CtlType = ODT_LISTBOX;
793 cis.CtlID = wnd->wIDmenu;
794 cis.hwndItem = wnd->hwndSelf;
795 cis.itemID1 = index;
796 cis.itemData1 = descr->items[index].data;
797 cis.itemID2 = -1;
798 cis.itemData2 = (DWORD)str;
799 cis.dwLocaleId = descr->locale;
800 res = SendMessageA( descr->owner, WM_COMPAREITEM,
801 wnd->wIDmenu, (LPARAM)&cis );
803 if (!res) return index;
804 if (res > 0) max = index;
805 else min = index + 1;
807 return exact ? -1 : max;
811 /***********************************************************************
812 * LISTBOX_FindFileStrPos
814 * Find the nearest string located before a given string in directory
815 * sort order (i.e. first files, then directories, then drives).
817 static INT LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCSTR str )
819 INT min, max, res = -1;
821 if (!HAS_STRINGS(descr))
822 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
823 min = 0;
824 max = descr->nb_items;
825 while (min != max)
827 INT index = (min + max) / 2;
828 const char *p = descr->items[index].str;
829 if (*p == '[') /* drive or directory */
831 if (*str != '[') res = -1;
832 else if (p[1] == '-') /* drive */
834 if (str[1] == '-') res = str[2] - p[2];
835 else res = -1;
837 else /* directory */
839 if (str[1] == '-') res = 1;
840 else res = lstrcmpiA( str, p );
843 else /* filename */
845 if (*str == '[') res = 1;
846 else res = lstrcmpiA( str, p );
848 if (!res) return index;
849 if (res < 0) max = index;
850 else min = index + 1;
852 return max;
856 /***********************************************************************
857 * LISTBOX_FindString
859 * Find the item beginning with a given string.
861 static INT LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT start,
862 LPCSTR str, BOOL exact )
864 INT i;
865 LB_ITEMDATA *item;
867 if (start >= descr->nb_items) start = -1;
868 item = descr->items + start + 1;
869 if (HAS_STRINGS(descr))
871 if (!str || ! str[0] ) return LB_ERR;
872 if (exact)
874 for (i = start + 1; i < descr->nb_items; i++, item++)
875 if (!lstrcmpiA( str, item->str )) return i;
876 for (i = 0, item = descr->items; i <= start; i++, item++)
877 if (!lstrcmpiA( str, item->str )) return i;
879 else
881 /* Special case for drives and directories: ignore prefix */
882 #define CHECK_DRIVE(item) \
883 if ((item)->str[0] == '[') \
885 if (!strncasecmp( str, (item)->str+1, len )) return i; \
886 if (((item)->str[1] == '-') && !strncasecmp(str,(item)->str+2,len)) \
887 return i; \
890 INT len = strlen(str);
891 for (i = start + 1; i < descr->nb_items; i++, item++)
893 if (!strncasecmp( str, item->str, len )) return i;
894 CHECK_DRIVE(item);
896 for (i = 0, item = descr->items; i <= start; i++, item++)
898 if (!strncasecmp( str, item->str, len )) return i;
899 CHECK_DRIVE(item);
901 #undef CHECK_DRIVE
904 else
906 if (exact && (descr->style & LBS_SORT))
907 /* If sorted, use a WM_COMPAREITEM binary search */
908 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
910 /* Otherwise use a linear search */
911 for (i = start + 1; i < descr->nb_items; i++, item++)
912 if (item->data == (DWORD)str) return i;
913 for (i = 0, item = descr->items; i <= start; i++, item++)
914 if (item->data == (DWORD)str) return i;
916 return LB_ERR;
920 /***********************************************************************
921 * LISTBOX_GetSelCount
923 static LRESULT LISTBOX_GetSelCount( WND *wnd, LB_DESCR *descr )
925 INT i, count;
926 LB_ITEMDATA *item = descr->items;
928 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
929 for (i = count = 0; i < descr->nb_items; i++, item++)
930 if (item->selected) count++;
931 return count;
935 /***********************************************************************
936 * LISTBOX_GetSelItems16
938 static LRESULT LISTBOX_GetSelItems16( WND *wnd, LB_DESCR *descr, INT16 max,
939 LPINT16 array )
941 INT i, count;
942 LB_ITEMDATA *item = descr->items;
944 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
945 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
946 if (item->selected) array[count++] = (INT16)i;
947 return count;
951 /***********************************************************************
952 * LISTBOX_GetSelItems32
954 static LRESULT LISTBOX_GetSelItems( WND *wnd, LB_DESCR *descr, INT max,
955 LPINT array )
957 INT i, count;
958 LB_ITEMDATA *item = descr->items;
960 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
961 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
962 if (item->selected) array[count++] = i;
963 return count;
967 /***********************************************************************
968 * LISTBOX_Paint
970 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC hdc )
972 INT i, col_pos = descr->page_size - 1;
973 RECT rect;
974 RECT focusRect = {-1, -1, -1, -1};
975 HFONT oldFont = 0;
976 HBRUSH hbrush, oldBrush = 0;
978 if (descr->style & LBS_NOREDRAW) return 0;
980 SetRect( &rect, 0, 0, descr->width, descr->height );
981 if (descr->style & LBS_MULTICOLUMN)
982 rect.right = rect.left + descr->column_width;
983 else if (descr->horz_pos)
985 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
986 rect.right += descr->horz_pos;
989 if (descr->font) oldFont = SelectObject( hdc, descr->font );
990 hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
991 hdc, (LPARAM)wnd->hwndSelf );
992 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
993 if (wnd->dwStyle & WS_DISABLED)
994 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
996 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
997 (descr->in_focus))
999 /* Special case for empty listbox: paint focus rect */
1000 rect.bottom = rect.top + descr->item_height;
1001 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
1002 ODA_FOCUS, FALSE );
1003 rect.top = rect.bottom;
1006 /* Paint all the item, regarding the selection
1007 Focus state will be painted after */
1009 for (i = descr->top_item; i < descr->nb_items; i++)
1011 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1012 rect.bottom = rect.top + descr->item_height;
1013 else
1014 rect.bottom = rect.top + descr->items[i].height;
1016 if (i == descr->focus_item)
1018 /* keep the focus rect, to paint the focus item after */
1019 focusRect.left = rect.left;
1020 focusRect.right = rect.right;
1021 focusRect.top = rect.top;
1022 focusRect.bottom = rect.bottom;
1024 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1025 rect.top = rect.bottom;
1027 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1029 if (!IS_OWNERDRAW(descr))
1031 /* Clear the bottom of the column */
1032 if (rect.top < descr->height)
1034 rect.bottom = descr->height;
1035 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1036 &rect, NULL, 0, NULL );
1040 /* Go to the next column */
1041 rect.left += descr->column_width;
1042 rect.right += descr->column_width;
1043 rect.top = 0;
1044 col_pos = descr->page_size - 1;
1046 else
1048 col_pos--;
1049 if (rect.top >= descr->height) break;
1053 /* Paint the focus item now */
1054 if (focusRect.top != focusRect.bottom && descr->caret_on)
1055 LISTBOX_PaintItem( wnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1057 if (!IS_OWNERDRAW(descr))
1059 /* Clear the remainder of the client area */
1060 if (rect.top < descr->height)
1062 rect.bottom = descr->height;
1063 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1064 &rect, NULL, 0, NULL );
1066 if (rect.right < descr->width)
1068 rect.left = rect.right;
1069 rect.right = descr->width;
1070 rect.top = 0;
1071 rect.bottom = descr->height;
1072 ExtTextOutA( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1073 &rect, NULL, 0, NULL );
1076 if (oldFont) SelectObject( hdc, oldFont );
1077 if (oldBrush) SelectObject( hdc, oldBrush );
1078 return 0;
1082 /***********************************************************************
1083 * LISTBOX_InvalidateItems
1085 * Invalidate all items from a given item. If the specified item is not
1086 * visible, nothing happens.
1088 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT index )
1090 RECT rect;
1092 if (LISTBOX_GetItemRect( wnd, descr, index, &rect ) == 1)
1094 if (descr->style & LBS_NOREDRAW)
1096 descr->style |= LBS_DISPLAYCHANGED;
1097 return;
1099 rect.bottom = descr->height;
1100 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1101 if (descr->style & LBS_MULTICOLUMN)
1103 /* Repaint the other columns */
1104 rect.left = rect.right;
1105 rect.right = descr->width;
1106 rect.top = 0;
1107 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1113 /***********************************************************************
1114 * LISTBOX_GetItemHeight
1116 static LRESULT LISTBOX_GetItemHeight( WND *wnd, LB_DESCR *descr, INT index )
1118 if (descr->style & LBS_OWNERDRAWVARIABLE)
1120 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1121 return descr->items[index].height;
1123 else return descr->item_height;
1127 /***********************************************************************
1128 * LISTBOX_SetItemHeight
1130 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT index,
1131 UINT height )
1133 if (!height) height = 1;
1135 if (descr->style & LBS_OWNERDRAWVARIABLE)
1137 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1138 TRACE("[%04x]: item %d height = %d\n",
1139 wnd->hwndSelf, index, height );
1140 descr->items[index].height = height;
1141 LISTBOX_UpdateScroll( wnd, descr );
1142 LISTBOX_InvalidateItems( wnd, descr, index );
1144 else if (height != descr->item_height)
1146 TRACE("[%04x]: new height = %d\n",
1147 wnd->hwndSelf, height );
1148 descr->item_height = height;
1149 LISTBOX_UpdatePage( wnd, descr );
1150 LISTBOX_UpdateScroll( wnd, descr );
1151 InvalidateRect( wnd->hwndSelf, 0, TRUE );
1153 return LB_OKAY;
1157 /***********************************************************************
1158 * LISTBOX_SetHorizontalPos
1160 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT pos )
1162 INT diff;
1164 if (pos > descr->horz_extent - descr->width)
1165 pos = descr->horz_extent - descr->width;
1166 if (pos < 0) pos = 0;
1167 if (!(diff = descr->horz_pos - pos)) return;
1168 TRACE("[%04x]: new horz pos = %d\n",
1169 wnd->hwndSelf, pos );
1170 descr->horz_pos = pos;
1171 LISTBOX_UpdateScroll( wnd, descr );
1172 if (abs(diff) < descr->width)
1173 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1174 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1175 else
1176 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1180 /***********************************************************************
1181 * LISTBOX_SetHorizontalExtent
1183 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1184 UINT extent )
1186 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1187 return LB_OKAY;
1188 if (extent <= 0) extent = 1;
1189 if (extent == descr->horz_extent) return LB_OKAY;
1190 TRACE("[%04x]: new horz extent = %d\n",
1191 wnd->hwndSelf, extent );
1192 descr->horz_extent = extent;
1193 if (descr->horz_pos > extent - descr->width)
1194 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1195 else
1196 LISTBOX_UpdateScroll( wnd, descr );
1197 return LB_OKAY;
1201 /***********************************************************************
1202 * LISTBOX_SetColumnWidth
1204 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, UINT width)
1206 if (width == descr->column_width) return LB_OKAY;
1207 TRACE("[%04x]: new column width = %d\n",
1208 wnd->hwndSelf, width );
1209 descr->column_width = width;
1210 LISTBOX_UpdatePage( wnd, descr );
1211 return LB_OKAY;
1215 /***********************************************************************
1216 * LISTBOX_SetFont
1218 * Returns the item height.
1220 static INT LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT font )
1222 HDC hdc;
1223 HFONT oldFont = 0;
1224 TEXTMETRICA tm;
1226 descr->font = font;
1228 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE )))
1230 ERR("unable to get DC.\n" );
1231 return 16;
1233 if (font) oldFont = SelectObject( hdc, font );
1234 GetTextMetricsA( hdc, &tm );
1235 if (oldFont) SelectObject( hdc, oldFont );
1236 ReleaseDC( wnd->hwndSelf, hdc );
1237 if (!IS_OWNERDRAW(descr))
1238 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1239 return tm.tmHeight ;
1243 /***********************************************************************
1244 * LISTBOX_MakeItemVisible
1246 * Make sure that a given item is partially or fully visible.
1248 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT index,
1249 BOOL fully )
1251 INT top;
1253 if (index <= descr->top_item) top = index;
1254 else if (descr->style & LBS_MULTICOLUMN)
1256 INT cols = descr->width;
1257 if (!fully) cols += descr->column_width - 1;
1258 if (cols >= descr->column_width) cols /= descr->column_width;
1259 else cols = 1;
1260 if (index < descr->top_item + (descr->page_size * cols)) return;
1261 top = index - descr->page_size * (cols - 1);
1263 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1265 INT height = fully ? descr->items[index].height : 1;
1266 for (top = index; top > descr->top_item; top--)
1267 if ((height += descr->items[top-1].height) > descr->height) break;
1269 else
1271 if (index < descr->top_item + descr->page_size) return;
1272 if (!fully && (index == descr->top_item + descr->page_size) &&
1273 (descr->height > (descr->page_size * descr->item_height))) return;
1274 top = index - descr->page_size + 1;
1276 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1279 /***********************************************************************
1280 * LISTBOX_SetCaretIndex
1282 * NOTES
1283 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1286 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT index,
1287 BOOL fully_visible )
1289 INT oldfocus = descr->focus_item;
1291 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1292 if (index == oldfocus) return LB_OKAY;
1293 descr->focus_item = index;
1294 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1295 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1297 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1298 if (descr->caret_on && (descr->in_focus))
1299 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1301 return LB_OKAY;
1305 /***********************************************************************
1306 * LISTBOX_SelectItemRange
1308 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1310 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT first,
1311 INT last, BOOL on )
1313 INT i;
1315 /* A few sanity checks */
1317 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1318 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1319 if (last == -1) last = descr->nb_items - 1;
1320 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1321 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1322 /* selected_item reflects last selected/unselected item on multiple sel */
1323 descr->selected_item = last;
1325 if (on) /* Turn selection on */
1327 for (i = first; i <= last; i++)
1329 if (descr->items[i].selected) continue;
1330 descr->items[i].selected = TRUE;
1331 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1333 LISTBOX_SetCaretIndex( wnd, descr, last, TRUE );
1335 else /* Turn selection off */
1337 for (i = first; i <= last; i++)
1339 if (!descr->items[i].selected) continue;
1340 descr->items[i].selected = FALSE;
1341 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1344 return LB_OKAY;
1347 /***********************************************************************
1348 * LISTBOX_SetSelection
1350 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT index,
1351 BOOL on, BOOL send_notify )
1353 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1355 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1356 if (descr->style & LBS_MULTIPLESEL)
1358 if (index == -1) /* Select all items */
1359 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1360 else /* Only one item */
1361 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1363 else
1365 INT oldsel = descr->selected_item;
1366 if (index == oldsel) return LB_OKAY;
1367 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1368 if (index != -1) descr->items[index].selected = TRUE;
1369 descr->selected_item = index;
1370 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT );
1371 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1372 if (send_notify && descr->nb_items) SEND_NOTIFICATION( wnd, descr,
1373 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1374 else
1375 if( descr->lphc ) /* set selection change flag for parent combo */
1376 descr->lphc->wState |= CBF_SELCHANGE;
1378 return LB_OKAY;
1382 /***********************************************************************
1383 * LISTBOX_MoveCaret
1385 * Change the caret position and extend the selection to the new caret.
1387 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT index,
1388 BOOL fully_visible )
1390 INT oldfocus = descr->focus_item;
1392 if ((index < 0) || (index >= descr->nb_items))
1393 return;
1395 /* Important, repaint needs to be done in this order if
1396 you want to mimic Windows behavior:
1397 1. Remove the focus and paint the item
1398 2. Remove the selection and paint the item(s)
1399 3. Set the selection and repaint the item(s)
1400 4. Set the focus to 'index' and repaint the item */
1402 /* 1. remove the focus and repaint the item */
1403 descr->focus_item = -1;
1404 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1405 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1407 /* 2. then turn off the previous selection */
1408 /* 3. repaint the new selected item */
1409 if (descr->style & LBS_EXTENDEDSEL)
1411 if (descr->anchor_item != -1)
1413 INT first = min( index, descr->anchor_item );
1414 INT last = max( index, descr->anchor_item );
1415 if (first > 0)
1416 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1417 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1418 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1421 else if (!(descr->style & LBS_MULTIPLESEL))
1423 /* Set selection to new caret item */
1424 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1427 /* 4. repaint the new item with the focus */
1428 descr->focus_item = index;
1429 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1430 if (descr->caret_on && (descr->in_focus))
1431 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1435 /***********************************************************************
1436 * LISTBOX_InsertItem
1438 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT index,
1439 LPSTR str, DWORD data )
1441 LB_ITEMDATA *item;
1442 INT max_items;
1443 INT oldfocus = descr->focus_item;
1445 if (index == -1) index = descr->nb_items;
1446 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1447 if (!descr->items) max_items = 0;
1448 else max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(*item);
1449 if (descr->nb_items == max_items)
1451 /* We need to grow the array */
1452 max_items += LB_ARRAY_GRANULARITY;
1453 if (!(item = HeapReAlloc( descr->heap, 0, descr->items,
1454 max_items * sizeof(LB_ITEMDATA) )))
1456 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1457 return LB_ERRSPACE;
1459 descr->items = item;
1462 /* Insert the item structure */
1464 item = &descr->items[index];
1465 if (index < descr->nb_items)
1466 RtlMoveMemory( item + 1, item,
1467 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1468 item->str = str;
1469 item->data = data;
1470 item->height = 0;
1471 item->selected = FALSE;
1472 descr->nb_items++;
1474 /* Get item height */
1476 if (descr->style & LBS_OWNERDRAWVARIABLE)
1478 MEASUREITEMSTRUCT mis;
1480 mis.CtlType = ODT_LISTBOX;
1481 mis.CtlID = wnd->wIDmenu;
1482 mis.itemID = index;
1483 mis.itemData = descr->items[index].data;
1484 mis.itemHeight = descr->item_height;
1485 SendMessageA( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
1486 item->height = mis.itemHeight ? mis.itemHeight : 1;
1487 TRACE("[%04x]: measure item %d (%s) = %d\n",
1488 wnd->hwndSelf, index, str ? str : "", item->height );
1491 /* Repaint the items */
1493 LISTBOX_UpdateScroll( wnd, descr );
1494 LISTBOX_InvalidateItems( wnd, descr, index );
1496 /* Move selection and focused item */
1497 /* If listbox was empty, set focus to the first item */
1498 if (descr->nb_items == 1)
1499 LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1500 /* single select don't change selection index in win31 */
1501 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1503 descr->selected_item++;
1504 LISTBOX_SetSelection( wnd, descr, descr->selected_item-1, TRUE, FALSE );
1506 else
1508 if (index <= descr->selected_item)
1510 descr->selected_item++;
1511 descr->focus_item = oldfocus; /* focus not changed */
1514 return LB_OKAY;
1518 /***********************************************************************
1519 * LISTBOX_InsertString
1521 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT index,
1522 LPCSTR str )
1524 LPSTR new_str = NULL;
1525 DWORD data = 0;
1526 LRESULT ret;
1528 if (HAS_STRINGS(descr))
1530 if (!str) str="";
1531 if (!(new_str = HEAP_strdupA( descr->heap, 0, str )))
1533 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1534 return LB_ERRSPACE;
1537 else data = (DWORD)str;
1539 if (index == -1) index = descr->nb_items;
1540 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1542 if (new_str) HeapFree( descr->heap, 0, new_str );
1543 return ret;
1546 TRACE("[%04x]: added item %d '%s'\n",
1547 wnd->hwndSelf, index, HAS_STRINGS(descr) ? new_str : "" );
1548 return index;
1552 /***********************************************************************
1553 * LISTBOX_DeleteItem
1555 * Delete the content of an item. 'index' must be a valid index.
1557 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT index )
1559 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1560 * while Win95 sends it for all items with user data.
1561 * It's probably better to send it too often than not
1562 * often enough, so this is what we do here.
1564 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1566 DELETEITEMSTRUCT dis;
1568 dis.CtlType = ODT_LISTBOX;
1569 dis.CtlID = wnd->wIDmenu;
1570 dis.itemID = index;
1571 dis.hwndItem = wnd->hwndSelf;
1572 dis.itemData = descr->items[index].data;
1573 SendMessageA( descr->owner, WM_DELETEITEM, wnd->wIDmenu, (LPARAM)&dis );
1575 if (HAS_STRINGS(descr) && descr->items[index].str)
1576 HeapFree( descr->heap, 0, descr->items[index].str );
1580 /***********************************************************************
1581 * LISTBOX_RemoveItem
1583 * Remove an item from the listbox and delete its content.
1585 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT index )
1587 LB_ITEMDATA *item;
1588 INT max_items;
1590 if (index == -1) index = descr->nb_items - 1;
1591 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1593 /* We need to invalidate the original rect instead of the updated one. */
1594 LISTBOX_InvalidateItems( wnd, descr, index );
1596 LISTBOX_DeleteItem( wnd, descr, index );
1598 /* Remove the item */
1600 item = &descr->items[index];
1601 if (index < descr->nb_items-1)
1602 RtlMoveMemory( item, item + 1,
1603 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1604 descr->nb_items--;
1605 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1607 /* Shrink the item array if possible */
1609 max_items = HeapSize( descr->heap, 0, descr->items ) / sizeof(LB_ITEMDATA);
1610 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1612 max_items -= LB_ARRAY_GRANULARITY;
1613 item = HeapReAlloc( descr->heap, 0, descr->items,
1614 max_items * sizeof(LB_ITEMDATA) );
1615 if (item) descr->items = item;
1617 /* Repaint the items */
1619 LISTBOX_UpdateScroll( wnd, descr );
1620 /* if we removed the scrollbar, reset the top of the list
1621 (correct for owner-drawn ???) */
1622 if (descr->nb_items == descr->page_size)
1623 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1625 /* Move selection and focused item */
1626 if (!IS_MULTISELECT(descr))
1628 if (index == descr->selected_item)
1629 descr->selected_item = -1;
1630 else if (index < descr->selected_item)
1632 descr->selected_item--;
1633 if (ISWIN31) /* win 31 do not change the selected item number */
1634 LISTBOX_SetSelection( wnd, descr, descr->selected_item + 1, TRUE, FALSE);
1638 if (descr->focus_item >= descr->nb_items)
1640 descr->focus_item = descr->nb_items - 1;
1641 if (descr->focus_item < 0) descr->focus_item = 0;
1643 return LB_OKAY;
1647 /***********************************************************************
1648 * LISTBOX_ResetContent
1650 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1652 INT i;
1654 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1655 if (descr->items) HeapFree( descr->heap, 0, descr->items );
1656 descr->nb_items = 0;
1657 descr->top_item = 0;
1658 descr->selected_item = -1;
1659 descr->focus_item = 0;
1660 descr->anchor_item = -1;
1661 descr->items = NULL;
1665 /***********************************************************************
1666 * LISTBOX_SetCount
1668 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT count )
1670 LRESULT ret;
1672 if (HAS_STRINGS(descr)) return LB_ERR;
1673 /* FIXME: this is far from optimal... */
1674 if (count > descr->nb_items)
1676 while (count > descr->nb_items)
1677 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1678 return ret;
1680 else if (count < descr->nb_items)
1682 while (count < descr->nb_items)
1683 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1684 return ret;
1686 return LB_OKAY;
1690 /***********************************************************************
1691 * LISTBOX_Directory
1693 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT attrib,
1694 LPCSTR filespec, BOOL long_names )
1696 HANDLE handle;
1697 LRESULT ret = LB_OKAY;
1698 WIN32_FIND_DATAA entry;
1699 int pos;
1701 /* don't scan directory if we just want drives exclusively */
1702 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1703 /* scan directory */
1704 if ((handle = FindFirstFileA(filespec,&entry)) == INVALID_HANDLE_VALUE)
1706 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1708 else
1712 char buffer[270];
1713 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1715 if (!(attrib & DDL_DIRECTORY) ||
1716 !strcmp( entry.cAlternateFileName, "." )) continue;
1717 if (long_names) sprintf( buffer, "[%s]", entry.cFileName );
1718 else sprintf( buffer, "[%s]", entry.cAlternateFileName );
1720 else /* not a directory */
1722 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1723 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1725 if ((attrib & DDL_EXCLUSIVE) &&
1726 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1727 continue;
1728 #undef ATTRIBS
1729 if (long_names) strcpy( buffer, entry.cFileName );
1730 else strcpy( buffer, entry.cAlternateFileName );
1732 if (!long_names) CharLowerA( buffer );
1733 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1734 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1735 break;
1736 } while (FindNextFileA( handle, &entry ));
1737 FindClose( handle );
1741 /* scan drives */
1742 if ((ret >= 0) && (attrib & DDL_DRIVES))
1744 char buffer[] = "[-a-]";
1745 char root[] = "A:\\";
1746 int drive;
1747 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1749 if (GetDriveTypeA(root) <= DRIVE_NO_ROOT_DIR) continue;
1750 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1751 break;
1754 return ret;
1758 /***********************************************************************
1759 * LISTBOX_HandleVScroll
1761 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr,
1762 WPARAM wParam, LPARAM lParam )
1764 SCROLLINFO info;
1766 if (descr->style & LBS_MULTICOLUMN) return 0;
1767 switch(LOWORD(wParam))
1769 case SB_LINEUP:
1770 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1771 break;
1772 case SB_LINEDOWN:
1773 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1774 break;
1775 case SB_PAGEUP:
1776 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1777 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1778 break;
1779 case SB_PAGEDOWN:
1780 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1781 LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1782 break;
1783 case SB_THUMBPOSITION:
1784 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1785 break;
1786 case SB_THUMBTRACK:
1787 info.cbSize = sizeof(info);
1788 info.fMask = SIF_TRACKPOS;
1789 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1790 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1791 break;
1792 case SB_TOP:
1793 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1794 break;
1795 case SB_BOTTOM:
1796 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1797 break;
1799 return 0;
1803 /***********************************************************************
1804 * LISTBOX_HandleHScroll
1806 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr,
1807 WPARAM wParam, LPARAM lParam )
1809 SCROLLINFO info;
1810 INT page;
1812 if (descr->style & LBS_MULTICOLUMN)
1814 switch(LOWORD(wParam))
1816 case SB_LINELEFT:
1817 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1818 TRUE );
1819 break;
1820 case SB_LINERIGHT:
1821 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1822 TRUE );
1823 break;
1824 case SB_PAGELEFT:
1825 page = descr->width / descr->column_width;
1826 if (page < 1) page = 1;
1827 LISTBOX_SetTopItem( wnd, descr,
1828 descr->top_item - page * descr->page_size, TRUE );
1829 break;
1830 case SB_PAGERIGHT:
1831 page = descr->width / descr->column_width;
1832 if (page < 1) page = 1;
1833 LISTBOX_SetTopItem( wnd, descr,
1834 descr->top_item + page * descr->page_size, TRUE );
1835 break;
1836 case SB_THUMBPOSITION:
1837 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1838 TRUE );
1839 break;
1840 case SB_THUMBTRACK:
1841 info.cbSize = sizeof(info);
1842 info.fMask = SIF_TRACKPOS;
1843 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1844 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1845 TRUE );
1846 break;
1847 case SB_LEFT:
1848 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1849 break;
1850 case SB_RIGHT:
1851 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1852 break;
1855 else if (descr->horz_extent)
1857 switch(LOWORD(wParam))
1859 case SB_LINELEFT:
1860 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1861 break;
1862 case SB_LINERIGHT:
1863 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1864 break;
1865 case SB_PAGELEFT:
1866 LISTBOX_SetHorizontalPos( wnd, descr,
1867 descr->horz_pos - descr->width );
1868 break;
1869 case SB_PAGERIGHT:
1870 LISTBOX_SetHorizontalPos( wnd, descr,
1871 descr->horz_pos + descr->width );
1872 break;
1873 case SB_THUMBPOSITION:
1874 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1875 break;
1876 case SB_THUMBTRACK:
1877 info.cbSize = sizeof(info);
1878 info.fMask = SIF_TRACKPOS;
1879 GetScrollInfo( wnd->hwndSelf, SB_HORZ, &info );
1880 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1881 break;
1882 case SB_LEFT:
1883 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1884 break;
1885 case SB_RIGHT:
1886 LISTBOX_SetHorizontalPos( wnd, descr,
1887 descr->horz_extent - descr->width );
1888 break;
1891 return 0;
1894 static LRESULT LISTBOX_HandleMouseWheel(WND *wnd, LB_DESCR *descr,WPARAM wParam, LPARAM lParam )
1896 short gcWheelDelta = 0;
1897 UINT pulScrollLines = 3;
1899 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1901 gcWheelDelta -= (short) HIWORD(wParam);
1903 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1905 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1906 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1907 LISTBOX_SetTopItem( wnd, descr, descr->top_item + cLineScroll, TRUE );
1909 return 0;
1912 /***********************************************************************
1913 * LISTBOX_HandleLButtonDown
1915 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1916 WPARAM wParam, INT x, INT y )
1918 INT index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1919 TRACE("[%04x]: lbuttondown %d,%d item %d\n",
1920 wnd->hwndSelf, x, y, index );
1921 if (!descr->caret_on && (descr->in_focus)) return 0;
1923 if (!descr->in_focus)
1925 if( !descr->lphc ) SetFocus( wnd->hwndSelf );
1926 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1927 : descr->lphc->self->hwndSelf );
1930 if (index == -1) return 0;
1932 if (descr->style & LBS_EXTENDEDSEL)
1934 /* we should perhaps make sure that all items are deselected
1935 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1936 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1937 LISTBOX_SetSelection( wnd, descr, -1, FALSE, FALSE);
1940 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1941 if (wParam & MK_CONTROL)
1943 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1944 LISTBOX_SetSelection( wnd, descr, index,
1945 !descr->items[index].selected,
1946 (descr->style & LBS_NOTIFY) != 0);
1948 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1950 else
1952 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1953 LISTBOX_SetSelection( wnd, descr, index,
1954 (!(descr->style & LBS_MULTIPLESEL) ||
1955 !descr->items[index].selected),
1956 (descr->style & LBS_NOTIFY) != 0 );
1959 descr->captured = TRUE;
1960 SetCapture( wnd->hwndSelf );
1962 if (!descr->lphc)
1964 if (descr->style & LBS_NOTIFY )
1965 SendMessageA( descr->owner, WM_LBTRACKPOINT, index,
1966 MAKELPARAM( x, y ) );
1967 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1969 POINT pt;
1971 pt.x = x;
1972 pt.y = y;
1974 if (DragDetect( wnd->hwndSelf, pt ))
1975 SendMessageA( descr->owner, WM_BEGINDRAG, 0, 0 );
1978 return 0;
1982 /*************************************************************************
1983 * LISTBOX_HandleLButtonDownCombo [Internal]
1985 * Process LButtonDown message for the ComboListBox
1987 * PARAMS
1988 * pWnd [I] The windows internal structure
1989 * pDescr [I] The ListBox internal structure
1990 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
1991 * x [I] X Mouse Coordinate
1992 * y [I] Y Mouse Coordinate
1994 * RETURNS
1995 * 0 since we are processing the WM_LBUTTONDOWN Message
1997 * NOTES
1998 * This function is only to be used when a ListBox is a ComboListBox
2001 static LRESULT LISTBOX_HandleLButtonDownCombo( WND *pWnd, LB_DESCR *pDescr,
2002 UINT msg, WPARAM wParam, INT x, INT y)
2004 RECT clientRect, screenRect;
2005 POINT mousePos;
2007 mousePos.x = x;
2008 mousePos.y = y;
2010 GetClientRect(pWnd->hwndSelf, &clientRect);
2012 if(PtInRect(&clientRect, mousePos))
2014 /* MousePos is in client, resume normal processing */
2015 if (msg == WM_LBUTTONDOWN)
2017 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2018 return LISTBOX_HandleLButtonDown( pWnd, pDescr, wParam, x, y);
2020 else if (pDescr->style & LBS_NOTIFY)
2021 SEND_NOTIFICATION( pWnd, pDescr, LBN_DBLCLK );
2022 return 0;
2024 else
2026 POINT screenMousePos;
2027 HWND hWndOldCapture;
2029 /* Check the Non-Client Area */
2030 screenMousePos = mousePos;
2031 hWndOldCapture = GetCapture();
2032 ReleaseCapture();
2033 GetWindowRect(pWnd->hwndSelf, &screenRect);
2034 ClientToScreen(pWnd->hwndSelf, &screenMousePos);
2036 if(!PtInRect(&screenRect, screenMousePos))
2038 LISTBOX_SetSelection( pWnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2039 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2040 return 0;
2042 else
2044 /* Check to see the NC is a scrollbar */
2045 INT nHitTestType=0;
2046 /* Check Vertical scroll bar */
2047 if (pWnd->dwStyle & WS_VSCROLL)
2049 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2050 if (PtInRect( &clientRect, mousePos ))
2052 nHitTestType = HTVSCROLL;
2055 /* Check horizontal scroll bar */
2056 if (pWnd->dwStyle & WS_HSCROLL)
2058 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2059 if (PtInRect( &clientRect, mousePos ))
2061 nHitTestType = HTHSCROLL;
2064 /* Windows sends this message when a scrollbar is clicked
2067 if(nHitTestType != 0)
2069 SendMessageA(pWnd->hwndSelf, WM_NCLBUTTONDOWN, nHitTestType,
2070 MAKELONG(screenMousePos.x, screenMousePos.y));
2072 /* Resume the Capture after scrolling is complete
2074 if(hWndOldCapture != 0)
2076 SetCapture(hWndOldCapture);
2080 return 0;
2083 /***********************************************************************
2084 * LISTBOX_HandleLButtonUp
2086 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
2088 if (LISTBOX_Timer != LB_TIMER_NONE)
2089 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2090 LISTBOX_Timer = LB_TIMER_NONE;
2091 if (descr->captured)
2093 descr->captured = FALSE;
2094 if (GetCapture() == wnd->hwndSelf) ReleaseCapture();
2095 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2096 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2098 return 0;
2102 /***********************************************************************
2103 * LISTBOX_HandleTimer
2105 * Handle scrolling upon a timer event.
2106 * Return TRUE if scrolling should continue.
2108 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
2109 INT index, TIMER_DIRECTION dir )
2111 switch(dir)
2113 case LB_TIMER_UP:
2114 if (descr->top_item) index = descr->top_item - 1;
2115 else index = 0;
2116 break;
2117 case LB_TIMER_LEFT:
2118 if (descr->top_item) index -= descr->page_size;
2119 break;
2120 case LB_TIMER_DOWN:
2121 index = descr->top_item + LISTBOX_GetCurrentPageSize( wnd, descr );
2122 if (index == descr->focus_item) index++;
2123 if (index >= descr->nb_items) index = descr->nb_items - 1;
2124 break;
2125 case LB_TIMER_RIGHT:
2126 if (index + descr->page_size < descr->nb_items)
2127 index += descr->page_size;
2128 break;
2129 case LB_TIMER_NONE:
2130 break;
2132 if (index == descr->focus_item) return FALSE;
2133 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
2134 return TRUE;
2138 /***********************************************************************
2139 * LISTBOX_HandleSystemTimer
2141 * WM_SYSTIMER handler.
2143 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
2145 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
2147 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2148 LISTBOX_Timer = LB_TIMER_NONE;
2150 return 0;
2154 /***********************************************************************
2155 * LISTBOX_HandleMouseMove
2157 * WM_MOUSEMOVE handler.
2159 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
2160 INT x, INT y )
2162 INT index;
2163 TIMER_DIRECTION dir = LB_TIMER_NONE;
2165 if (!descr->captured) return;
2167 if (descr->style & LBS_MULTICOLUMN)
2169 if (y < 0) y = 0;
2170 else if (y >= descr->item_height * descr->page_size)
2171 y = descr->item_height * descr->page_size - 1;
2173 if (x < 0)
2175 dir = LB_TIMER_LEFT;
2176 x = 0;
2178 else if (x >= descr->width)
2180 dir = LB_TIMER_RIGHT;
2181 x = descr->width - 1;
2184 else
2186 if (y < 0) dir = LB_TIMER_UP; /* above */
2187 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2190 index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
2191 if (index == -1) index = descr->focus_item;
2192 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
2194 /* Start/stop the system timer */
2196 if (dir != LB_TIMER_NONE)
2197 SetSystemTimer( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2198 else if (LISTBOX_Timer != LB_TIMER_NONE)
2199 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2200 LISTBOX_Timer = dir;
2204 /***********************************************************************
2205 * LISTBOX_HandleKeyDown
2207 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM wParam )
2209 INT caret = -1;
2210 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2211 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2212 bForceSelection = FALSE; /* only for single select list */
2214 if (descr->style & LBS_WANTKEYBOARDINPUT)
2216 caret = SendMessageA( descr->owner, WM_VKEYTOITEM,
2217 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2218 wnd->hwndSelf );
2219 if (caret == -2) return 0;
2221 if (caret == -1) switch(wParam)
2223 case VK_LEFT:
2224 if (descr->style & LBS_MULTICOLUMN)
2226 bForceSelection = FALSE;
2227 if (descr->focus_item >= descr->page_size)
2228 caret = descr->focus_item - descr->page_size;
2229 break;
2231 /* fall through */
2232 case VK_UP:
2233 caret = descr->focus_item - 1;
2234 if (caret < 0) caret = 0;
2235 break;
2236 case VK_RIGHT:
2237 if (descr->style & LBS_MULTICOLUMN)
2239 bForceSelection = FALSE;
2240 if (descr->focus_item + descr->page_size < descr->nb_items)
2241 caret = descr->focus_item + descr->page_size;
2242 break;
2244 /* fall through */
2245 case VK_DOWN:
2246 caret = descr->focus_item + 1;
2247 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2248 break;
2250 case VK_PRIOR:
2251 if (descr->style & LBS_MULTICOLUMN)
2253 INT page = descr->width / descr->column_width;
2254 if (page < 1) page = 1;
2255 caret = descr->focus_item - (page * descr->page_size) + 1;
2257 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(wnd,descr)+1;
2258 if (caret < 0) caret = 0;
2259 break;
2260 case VK_NEXT:
2261 if (descr->style & LBS_MULTICOLUMN)
2263 INT page = descr->width / descr->column_width;
2264 if (page < 1) page = 1;
2265 caret = descr->focus_item + (page * descr->page_size) - 1;
2267 else caret = descr->focus_item+LISTBOX_GetCurrentPageSize(wnd,descr)-1;
2268 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2269 break;
2270 case VK_HOME:
2271 caret = 0;
2272 break;
2273 case VK_END:
2274 caret = descr->nb_items - 1;
2275 break;
2276 case VK_SPACE:
2277 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2278 else if (descr->style & LBS_MULTIPLESEL)
2280 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
2281 !descr->items[descr->focus_item].selected,
2282 (descr->style & LBS_NOTIFY) != 0 );
2284 break;
2285 default:
2286 bForceSelection = FALSE;
2288 if (bForceSelection) /* focused item is used instead of key */
2289 caret = descr->focus_item;
2290 if (caret >= 0)
2292 if ((descr->style & LBS_EXTENDEDSEL) &&
2293 !(GetKeyState( VK_SHIFT ) & 0x8000))
2294 descr->anchor_item = caret;
2295 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2296 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2297 if (descr->style & LBS_NOTIFY)
2299 if( descr->lphc && CB_GETTYPE(descr->lphc) != CBS_SIMPLE )
2301 /* make sure that combo parent doesn't hide us */
2302 descr->lphc->wState |= CBF_NOROLLUP;
2304 if (descr->nb_items) SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2307 return 0;
2311 /***********************************************************************
2312 * LISTBOX_HandleChar
2314 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr,
2315 WPARAM wParam )
2317 INT caret = -1;
2318 char str[2];
2320 str[0] = wParam & 0xff;
2321 str[1] = '\0';
2323 if (descr->style & LBS_WANTKEYBOARDINPUT)
2325 caret = SendMessageA( descr->owner, WM_CHARTOITEM,
2326 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2327 wnd->hwndSelf );
2328 if (caret == -2) return 0;
2330 if (caret == -1)
2331 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2332 if (caret != -1)
2334 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2335 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2336 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2337 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2338 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2340 return 0;
2344 /***********************************************************************
2345 * LISTBOX_Create
2347 static BOOL LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2349 LB_DESCR *descr;
2350 MEASUREITEMSTRUCT mis;
2351 RECT rect;
2353 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2354 return FALSE;
2355 if (!(descr->heap = HeapCreate( 0, 0x10000, 0 )))
2357 HeapFree( GetProcessHeap(), 0, descr );
2358 return FALSE;
2360 GetClientRect( wnd->hwndSelf, &rect );
2361 descr->owner = GetParent( wnd->hwndSelf );
2362 descr->style = wnd->dwStyle;
2363 descr->width = rect.right - rect.left;
2364 descr->height = rect.bottom - rect.top;
2365 descr->items = NULL;
2366 descr->nb_items = 0;
2367 descr->top_item = 0;
2368 descr->selected_item = -1;
2369 descr->focus_item = 0;
2370 descr->anchor_item = -1;
2371 descr->item_height = 1;
2372 descr->page_size = 1;
2373 descr->column_width = 150;
2374 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2375 descr->horz_pos = 0;
2376 descr->nb_tabs = 0;
2377 descr->tabs = NULL;
2378 descr->caret_on = lphc ? FALSE : TRUE;
2379 descr->in_focus = FALSE;
2380 descr->captured = FALSE;
2381 descr->font = 0;
2382 descr->locale = 0; /* FIXME */
2383 descr->lphc = lphc;
2385 if( ( GetExpWinVer16( wnd->hInstance ) & 0xFF00 ) == 0x0300
2386 && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2388 /* Win95 document "List Box Differences" from MSDN:
2389 If a list box in a version 3.x application has either the
2390 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2391 horizontal and vertical scroll bars.
2393 descr->style |= WS_VSCROLL | WS_HSCROLL;
2396 if( lphc )
2398 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2399 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2400 descr->owner = lphc->self->hwndSelf;
2403 *(LB_DESCR **)wnd->wExtra = descr;
2405 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2407 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2408 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2409 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2410 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2412 if (descr->style & LBS_OWNERDRAWFIXED)
2414 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2416 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2417 descr->item_height = lphc->fixedOwnerDrawHeight;
2419 else
2421 mis.CtlType = ODT_LISTBOX;
2422 mis.CtlID = wnd->wIDmenu;
2423 mis.itemID = -1;
2424 mis.itemWidth = 0;
2425 mis.itemData = 0;
2426 mis.itemHeight = descr->item_height;
2427 SendMessageA( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
2428 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2432 return TRUE;
2436 /***********************************************************************
2437 * LISTBOX_Destroy
2439 static BOOL LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2441 LISTBOX_ResetContent( wnd, descr );
2442 HeapDestroy( descr->heap );
2443 HeapFree( GetProcessHeap(), 0, descr );
2444 wnd->wExtra[0] = 0;
2445 return TRUE;
2449 /***********************************************************************
2450 * ListBoxWndProc
2452 static inline LRESULT WINAPI ListBoxWndProc_locked( WND* wnd, UINT msg,
2453 WPARAM wParam, LPARAM lParam )
2455 LRESULT ret;
2456 LB_DESCR *descr;
2457 HWND hwnd = wnd->hwndSelf;
2459 if (!wnd) return 0;
2460 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2462 switch (msg)
2464 case WM_CREATE:
2466 if (!LISTBOX_Create( wnd, NULL ))
2467 return -1;
2468 TRACE("creating wnd=%04x descr=%p\n",
2469 hwnd, *(LB_DESCR **)wnd->wExtra );
2470 return 0;
2472 case WM_NCCREATE:
2475 * When a listbox is not in a combobox and the look
2476 * is win95, the WS_BORDER style is replaced with
2477 * the WS_EX_CLIENTEDGE style.
2479 if ( (TWEAK_WineLook > WIN31_LOOK) &&
2480 (wnd->dwStyle & WS_BORDER) )
2482 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2483 wnd->dwStyle &= ~ WS_BORDER;
2488 /* Ignore all other messages before we get a WM_CREATE */
2489 return DefWindowProcA( hwnd, msg, wParam, lParam );
2492 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2493 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2494 switch(msg)
2496 case LB_RESETCONTENT16:
2497 case LB_RESETCONTENT:
2498 LISTBOX_ResetContent( wnd, descr );
2499 LISTBOX_UpdateScroll( wnd, descr );
2500 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
2501 return 0;
2503 case LB_ADDSTRING16:
2504 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2505 /* fall through */
2506 case LB_ADDSTRING:
2507 wParam = LISTBOX_FindStringPos( wnd, descr, (LPCSTR)lParam, FALSE );
2508 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2510 case LB_INSERTSTRING16:
2511 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2512 wParam = (INT)(INT16)wParam;
2513 /* fall through */
2514 case LB_INSERTSTRING:
2515 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2517 case LB_ADDFILE16:
2518 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2519 /* fall through */
2520 case LB_ADDFILE:
2521 wParam = LISTBOX_FindFileStrPos( wnd, descr, (LPCSTR)lParam );
2522 return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2524 case LB_DELETESTRING16:
2525 case LB_DELETESTRING:
2526 if (LISTBOX_RemoveItem( wnd, descr, wParam) != LB_ERR)
2527 return descr->nb_items;
2528 else
2529 return LB_ERR;
2531 case LB_GETITEMDATA16:
2532 case LB_GETITEMDATA:
2533 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2534 return LB_ERR;
2535 return descr->items[wParam].data;
2537 case LB_SETITEMDATA16:
2538 case LB_SETITEMDATA:
2539 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2540 return LB_ERR;
2541 descr->items[wParam].data = (DWORD)lParam;
2542 return LB_OKAY;
2544 case LB_GETCOUNT16:
2545 case LB_GETCOUNT:
2546 return descr->nb_items;
2548 case LB_GETTEXT16:
2549 lParam = (LPARAM)MapSL(lParam);
2550 /* fall through */
2551 case LB_GETTEXT:
2552 return LISTBOX_GetText( wnd, descr, wParam, (LPSTR)lParam );
2554 case LB_GETTEXTLEN16:
2555 /* fall through */
2556 case LB_GETTEXTLEN:
2557 if (wParam >= descr->nb_items)
2558 return LB_ERR;
2559 return (HAS_STRINGS(descr) ? strlen(descr->items[wParam].str)
2560 : sizeof(DWORD));
2562 case LB_GETCURSEL16:
2563 case LB_GETCURSEL:
2564 if (descr->nb_items==0)
2565 return LB_ERR;
2566 if (!IS_MULTISELECT(descr))
2567 return descr->selected_item;
2568 /* else */
2569 if (descr->selected_item!=-1)
2570 return descr->selected_item;
2571 /* else */
2572 return descr->focus_item;
2573 /* otherwise, if the user tries to move the selection with the */
2574 /* arrow keys, we will give the application something to choke on */
2575 case LB_GETTOPINDEX16:
2576 case LB_GETTOPINDEX:
2577 return descr->top_item;
2579 case LB_GETITEMHEIGHT16:
2580 case LB_GETITEMHEIGHT:
2581 return LISTBOX_GetItemHeight( wnd, descr, wParam );
2583 case LB_SETITEMHEIGHT16:
2584 lParam = LOWORD(lParam);
2585 /* fall through */
2586 case LB_SETITEMHEIGHT:
2587 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2589 case LB_ITEMFROMPOINT:
2591 POINT pt;
2592 RECT rect;
2594 pt.x = LOWORD(lParam);
2595 pt.y = HIWORD(lParam);
2596 rect.left = 0;
2597 rect.top = 0;
2598 rect.right = descr->width;
2599 rect.bottom = descr->height;
2601 return MAKELONG( LISTBOX_GetItemFromPoint(wnd, descr, pt.x, pt.y),
2602 !PtInRect( &rect, pt ) );
2605 case LB_SETCARETINDEX16:
2606 case LB_SETCARETINDEX:
2607 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2608 if (LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam ) == LB_ERR)
2609 return LB_ERR;
2610 else if (ISWIN31)
2611 return wParam;
2612 else
2613 return LB_OKAY;
2615 case LB_GETCARETINDEX16:
2616 case LB_GETCARETINDEX:
2617 return descr->focus_item;
2619 case LB_SETTOPINDEX16:
2620 case LB_SETTOPINDEX:
2621 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2623 case LB_SETCOLUMNWIDTH16:
2624 case LB_SETCOLUMNWIDTH:
2625 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2627 case LB_GETITEMRECT16:
2629 RECT rect;
2630 ret = LISTBOX_GetItemRect( wnd, descr, (INT16)wParam, &rect );
2631 CONV_RECT32TO16( &rect, MapSL(lParam) );
2633 return ret;
2635 case LB_GETITEMRECT:
2636 return LISTBOX_GetItemRect( wnd, descr, wParam, (RECT *)lParam );
2638 case LB_FINDSTRING16:
2639 wParam = (INT)(INT16)wParam;
2640 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2641 /* fall through */
2642 case LB_FINDSTRING:
2643 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, FALSE );
2645 case LB_FINDSTRINGEXACT16:
2646 wParam = (INT)(INT16)wParam;
2647 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2648 /* fall through */
2649 case LB_FINDSTRINGEXACT:
2650 return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2652 case LB_SELECTSTRING16:
2653 wParam = (INT)(INT16)wParam;
2654 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2655 /* fall through */
2656 case LB_SELECTSTRING:
2658 INT index = LISTBOX_FindString( wnd, descr, wParam,
2659 (LPCSTR)lParam, FALSE );
2660 if (index == LB_ERR)
2661 return LB_ERR;
2662 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2663 return index;
2666 case LB_GETSEL16:
2667 wParam = (INT)(INT16)wParam;
2668 /* fall through */
2669 case LB_GETSEL:
2670 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2671 return LB_ERR;
2672 return descr->items[wParam].selected;
2674 case LB_SETSEL16:
2675 lParam = (INT)(INT16)lParam;
2676 /* fall through */
2677 case LB_SETSEL:
2678 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2680 case LB_SETCURSEL16:
2681 wParam = (INT)(INT16)wParam;
2682 /* fall through */
2683 case LB_SETCURSEL:
2684 if (IS_MULTISELECT(descr)) return LB_ERR;
2685 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2686 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2688 case LB_GETSELCOUNT16:
2689 case LB_GETSELCOUNT:
2690 return LISTBOX_GetSelCount( wnd, descr );
2692 case LB_GETSELITEMS16:
2693 return LISTBOX_GetSelItems16( wnd, descr, wParam,
2694 (LPINT16)MapSL(lParam) );
2696 case LB_GETSELITEMS:
2697 return LISTBOX_GetSelItems( wnd, descr, wParam, (LPINT)lParam );
2699 case LB_SELITEMRANGE16:
2700 case LB_SELITEMRANGE:
2701 if (LOWORD(lParam) <= HIWORD(lParam))
2702 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2703 HIWORD(lParam), wParam );
2704 else
2705 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2706 LOWORD(lParam), wParam );
2708 case LB_SELITEMRANGEEX16:
2709 case LB_SELITEMRANGEEX:
2710 if ((INT)lParam >= (INT)wParam)
2711 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2712 else
2713 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2715 case LB_GETHORIZONTALEXTENT16:
2716 case LB_GETHORIZONTALEXTENT:
2717 return descr->horz_extent;
2719 case LB_SETHORIZONTALEXTENT16:
2720 case LB_SETHORIZONTALEXTENT:
2721 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2723 case LB_GETANCHORINDEX16:
2724 case LB_GETANCHORINDEX:
2725 return descr->anchor_item;
2727 case LB_SETANCHORINDEX16:
2728 wParam = (INT)(INT16)wParam;
2729 /* fall through */
2730 case LB_SETANCHORINDEX:
2731 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2732 return LB_ERR;
2733 descr->anchor_item = (INT)wParam;
2734 return LB_OKAY;
2736 case LB_DIR16:
2737 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2738 * be set automatically (this is different in Win32) */
2739 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2740 return LISTBOX_Directory( wnd, descr, wParam, MapSL(lParam), FALSE );
2742 case LB_DIR:
2743 return LISTBOX_Directory( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2745 case LB_GETLOCALE:
2746 return descr->locale;
2748 case LB_SETLOCALE:
2749 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2750 return LB_OKAY;
2752 case LB_INITSTORAGE:
2753 return LISTBOX_InitStorage( wnd, descr, wParam, (DWORD)lParam );
2755 case LB_SETCOUNT:
2756 return LISTBOX_SetCount( wnd, descr, (INT)wParam );
2758 case LB_SETTABSTOPS16:
2759 return LISTBOX_SetTabStops( wnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2761 case LB_SETTABSTOPS:
2762 return LISTBOX_SetTabStops( wnd, descr, wParam, (LPINT)lParam, FALSE );
2764 case LB_CARETON16:
2765 case LB_CARETON:
2766 if (descr->caret_on)
2767 return LB_OKAY;
2768 descr->caret_on = TRUE;
2769 if ((descr->focus_item != -1) && (descr->in_focus))
2770 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2771 return LB_OKAY;
2773 case LB_CARETOFF16:
2774 case LB_CARETOFF:
2775 if (!descr->caret_on)
2776 return LB_OKAY;
2777 descr->caret_on = FALSE;
2778 if ((descr->focus_item != -1) && (descr->in_focus))
2779 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2780 return LB_OKAY;
2782 case WM_DESTROY:
2783 return LISTBOX_Destroy( wnd, descr );
2785 case WM_ENABLE:
2786 InvalidateRect( hwnd, NULL, TRUE );
2787 return 0;
2789 case WM_SETREDRAW:
2790 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2791 return 0;
2793 case WM_GETDLGCODE:
2794 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2796 case WM_PAINT:
2798 PAINTSTRUCT ps;
2799 HDC hdc = ( wParam ) ? ((HDC)wParam)
2800 : BeginPaint( hwnd, &ps );
2801 ret = LISTBOX_Paint( wnd, descr, hdc );
2802 if( !wParam ) EndPaint( hwnd, &ps );
2804 return ret;
2805 case WM_SIZE:
2806 LISTBOX_UpdateSize( wnd, descr );
2807 return 0;
2808 case WM_GETFONT:
2809 return descr->font;
2810 case WM_SETFONT:
2811 LISTBOX_SetFont( wnd, descr, (HFONT)wParam );
2812 if (lParam) InvalidateRect( wnd->hwndSelf, 0, TRUE );
2813 return 0;
2814 case WM_SETFOCUS:
2815 descr->in_focus = TRUE;
2816 descr->caret_on = TRUE;
2817 if (descr->focus_item != -1)
2818 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2819 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2820 return 0;
2821 case WM_KILLFOCUS:
2822 descr->in_focus = FALSE;
2823 if ((descr->focus_item != -1) && descr->caret_on)
2824 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2825 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2826 return 0;
2827 case WM_HSCROLL:
2828 return LISTBOX_HandleHScroll( wnd, descr, wParam, lParam );
2829 case WM_VSCROLL:
2830 return LISTBOX_HandleVScroll( wnd, descr, wParam, lParam );
2831 case WM_MOUSEACTIVATE:
2832 return MA_NOACTIVATE;
2833 case WM_MOUSEWHEEL:
2834 if (wParam & (MK_SHIFT | MK_CONTROL))
2835 return DefWindowProcA( hwnd, msg, wParam, lParam );
2836 return LISTBOX_HandleMouseWheel( wnd, descr, wParam, lParam );
2837 case WM_LBUTTONDOWN:
2838 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2839 (INT16)LOWORD(lParam),
2840 (INT16)HIWORD(lParam) );
2841 case WM_LBUTTONDBLCLK:
2842 if (descr->style & LBS_NOTIFY)
2843 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2844 return 0;
2845 case WM_MOUSEMOVE:
2846 if (GetCapture() == hwnd)
2847 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2848 (INT16)HIWORD(lParam) );
2849 return 0;
2850 case WM_LBUTTONUP:
2851 return LISTBOX_HandleLButtonUp( wnd, descr );
2852 case WM_KEYDOWN:
2853 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2854 case WM_CHAR:
2855 return LISTBOX_HandleChar( wnd, descr, wParam );
2856 case WM_SYSTIMER:
2857 return LISTBOX_HandleSystemTimer( wnd, descr );
2858 case WM_ERASEBKGND:
2859 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2861 RECT rect;
2862 HBRUSH hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
2863 wParam, (LPARAM)wnd->hwndSelf );
2864 GetClientRect(hwnd, &rect);
2865 if (hbrush) FillRect( (HDC)wParam, &rect, hbrush );
2867 return 1;
2868 case WM_DROPFILES:
2869 if( !descr->lphc )
2870 return SendMessageA( descr->owner, msg, wParam, lParam );
2871 break;
2873 case WM_DROPOBJECT:
2874 case WM_QUERYDROPOBJECT:
2875 case WM_DRAGSELECT:
2876 case WM_DRAGMOVE:
2877 if( !descr->lphc )
2879 LPDRAGINFO16 dragInfo = MapSL( lParam );
2880 dragInfo->l = LISTBOX_GetItemFromPoint( wnd, descr, dragInfo->pt.x,
2881 dragInfo->pt.y );
2882 return SendMessageA( descr->owner, msg, wParam, lParam );
2884 break;
2886 default:
2887 if ((msg >= WM_USER) && (msg < 0xc000))
2888 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
2889 hwnd, msg, wParam, lParam );
2890 return DefWindowProcA( hwnd, msg, wParam, lParam );
2892 return 0;
2895 /***********************************************************************
2896 * ListBoxWndProcA
2898 * This is just a wrapper for the real wndproc, it only does window locking
2899 * and unlocking.
2901 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2903 WND* wndPtr = WIN_FindWndPtr( hwnd );
2904 LRESULT res = ListBoxWndProc_locked(wndPtr,msg,wParam,lParam);
2906 WIN_ReleaseWndPtr(wndPtr);
2907 return res;
2910 /***********************************************************************
2911 * COMBO_Directory
2913 LRESULT COMBO_Directory( LPHEADCOMBO lphc, UINT attrib, LPSTR dir, BOOL bLong)
2915 WND *wnd = WIN_FindWndPtr( lphc->hWndLBox );
2917 if( wnd )
2919 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2920 if( descr )
2922 LRESULT lRet = LISTBOX_Directory( wnd, descr, attrib, dir, bLong );
2924 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
2925 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
2926 WIN_ReleaseWndPtr(wnd);
2927 return lRet;
2929 WIN_ReleaseWndPtr(wnd);
2931 return CB_ERR;
2934 /***********************************************************************
2935 * ComboLBWndProc_locked
2937 * The real combo listbox wndproc, but called with locked WND struct.
2939 static inline LRESULT WINAPI ComboLBWndProc_locked( WND* wnd, UINT msg,
2940 WPARAM wParam, LPARAM lParam )
2942 LRESULT lRet = 0;
2943 HWND hwnd = wnd->hwndSelf;
2945 if (wnd)
2947 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2949 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
2950 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2952 if( descr || msg == WM_CREATE )
2954 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
2956 switch( msg )
2958 case WM_CREATE:
2959 #define lpcs ((LPCREATESTRUCTA)lParam)
2960 TRACE_(combo)("\tpassed parent handle = 0x%08x\n",
2961 (UINT)lpcs->lpCreateParams);
2963 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
2964 #undef lpcs
2965 return LISTBOX_Create( wnd, lphc );
2966 case WM_MOUSEMOVE:
2967 if ( (TWEAK_WineLook > WIN31_LOOK) &&
2968 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
2970 POINT mousePos;
2971 BOOL captured;
2972 RECT clientRect;
2974 mousePos.x = (INT16)LOWORD(lParam);
2975 mousePos.y = (INT16)HIWORD(lParam);
2978 * If we are in a dropdown combobox, we simulate that
2979 * the mouse is captured to show the tracking of the item.
2981 GetClientRect(hwnd, &clientRect);
2983 if (PtInRect( &clientRect, mousePos ))
2985 captured = descr->captured;
2986 descr->captured = TRUE;
2988 LISTBOX_HandleMouseMove( wnd, descr,
2989 mousePos.x, mousePos.y);
2991 descr->captured = captured;
2994 else
2996 LISTBOX_HandleMouseMove( wnd, descr,
2997 mousePos.x, mousePos.y);
3000 return 0;
3003 else
3006 * If we are in Win3.1 look, go with the default behavior.
3008 return ListBoxWndProcA( hwnd, msg, wParam, lParam );
3010 case WM_LBUTTONUP:
3011 if (TWEAK_WineLook > WIN31_LOOK)
3013 POINT mousePos;
3014 RECT clientRect;
3017 * If the mouse button "up" is not in the listbox,
3018 * we make sure there is no selection by re-selecting the
3019 * item that was selected when the listbox was made visible.
3021 mousePos.x = (INT16)LOWORD(lParam);
3022 mousePos.y = (INT16)HIWORD(lParam);
3024 GetClientRect(hwnd, &clientRect);
3027 * When the user clicks outside the combobox and the focus
3028 * is lost, the owning combobox will send a fake buttonup with
3029 * 0xFFFFFFF as the mouse location, we must also revert the
3030 * selection to the original selection.
3032 if ( (lParam == 0xFFFFFFFF) ||
3033 (!PtInRect( &clientRect, mousePos )) )
3035 LISTBOX_MoveCaret( wnd,
3036 descr,
3037 lphc->droppedIndex,
3038 FALSE );
3041 return LISTBOX_HandleLButtonUp( wnd, descr );
3042 case WM_LBUTTONDBLCLK:
3043 case WM_LBUTTONDOWN:
3044 return LISTBOX_HandleLButtonDownCombo(wnd, descr, msg, wParam,
3045 (INT16)LOWORD(lParam),
3046 (INT16)HIWORD(lParam) );
3047 case WM_MOUSEACTIVATE:
3048 return MA_NOACTIVATE;
3049 case WM_NCACTIVATE:
3050 return FALSE;
3051 case WM_KEYDOWN:
3052 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3054 /* for some reason(?) Windows makes it possible to
3055 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3057 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3058 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3059 && (wParam == VK_DOWN || wParam == VK_UP)) )
3061 COMBO_FlipListbox( lphc, FALSE, FALSE );
3062 return 0;
3065 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
3067 case LB_SETCURSEL16:
3068 case LB_SETCURSEL:
3069 lRet = ListBoxWndProcA( hwnd, msg, wParam, lParam );
3070 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3071 return lRet;
3072 case WM_NCDESTROY:
3073 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3074 lphc->hWndLBox = 0;
3075 /* fall through */
3077 default:
3078 return ListBoxWndProcA( hwnd, msg, wParam, lParam );
3081 lRet = DefWindowProcA( hwnd, msg, wParam, lParam );
3083 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3085 return lRet;
3088 /***********************************************************************
3089 * ComboLBWndProc
3091 * NOTE: in Windows, winproc address of the ComboLBox is the same
3092 * as that of the Listbox.
3094 * This is just a wrapper for the real wndproc, it only does window locking
3095 * and unlocking.
3097 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg,
3098 WPARAM wParam, LPARAM lParam )
3100 WND *wnd = WIN_FindWndPtr( hwnd );
3101 LRESULT res = ComboLBWndProc_locked(wnd,msg,wParam,lParam);
3103 WIN_ReleaseWndPtr(wnd);
3104 return res;