- implement some more virtcopy (VCP) stuff
[wine.git] / controls / listbox.c
blob5960c4563e8d3c0d5c06c532607f39bf009f8d3a
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
5 */
7 #include <string.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include "windef.h"
11 #include "wingdi.h"
12 #include "wine/winuser16.h"
13 #include "wine/winbase16.h"
14 #include "wine/unicode.h"
15 #include "winuser.h"
16 #include "winerror.h"
17 #include "spy.h"
18 #include "win.h"
19 #include "user.h"
20 #include "controls.h"
21 #include "debugtools.h"
23 DEFAULT_DEBUG_CHANNEL(listbox);
24 DECLARE_DEBUG_CHANNEL(combo);
26 /* Unimplemented yet:
27 * - LBS_USETABSTOPS
28 * - Locale handling
30 * Probably needs improvement:
31 * - LBS_NOSEL
34 /* Items array granularity */
35 #define LB_ARRAY_GRANULARITY 16
37 /* Scrolling timeout in ms */
38 #define LB_SCROLL_TIMEOUT 50
40 /* Listbox system timer id */
41 #define LB_TIMER_ID 2
43 /* flag listbox changed while setredraw false - internal style */
44 #define LBS_DISPLAYCHANGED 0x80000000
46 /* Item structure */
47 typedef struct
49 LPWSTR str; /* Item text */
50 BOOL selected; /* Is item selected? */
51 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
52 DWORD data; /* User data */
53 } LB_ITEMDATA;
55 /* Listbox structure */
56 typedef struct
58 HWND owner; /* Owner window to send notifications to */
59 UINT style; /* Window style */
60 INT width; /* Window width */
61 INT height; /* Window height */
62 LB_ITEMDATA *items; /* Array of items */
63 INT nb_items; /* Number of items */
64 INT top_item; /* Top visible item */
65 INT selected_item; /* Selected item */
66 INT focus_item; /* Item that has the focus */
67 INT anchor_item; /* Anchor item for extended selection */
68 INT item_height; /* Default item height */
69 INT page_size; /* Items per listbox page */
70 INT column_width; /* Column width for multi-column listboxes */
71 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
72 INT horz_pos; /* Horizontal position */
73 INT nb_tabs; /* Number of tabs in array */
74 INT *tabs; /* Array of tabs */
75 BOOL caret_on; /* Is caret on? */
76 BOOL captured; /* Is mouse captured? */
77 BOOL in_focus;
78 HFONT font; /* Current font */
79 LCID locale; /* Current locale for string comparisons */
80 LPHEADCOMBO lphc; /* ComboLBox */
81 } LB_DESCR;
84 #define IS_OWNERDRAW(descr) \
85 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
87 #define HAS_STRINGS(descr) \
88 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
91 #define IS_MULTISELECT(descr) \
92 ((descr)->style & LBS_MULTIPLESEL || ((descr)->style & LBS_EXTENDEDSEL))
94 #define SEND_NOTIFICATION(wnd,descr,code) \
95 (SendMessageW( (descr)->owner, WM_COMMAND, \
96 MAKEWPARAM((wnd)->wIDmenu, (code)), (wnd)->hwndSelf ))
98 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
100 /* Current timer status */
101 typedef enum
103 LB_TIMER_NONE,
104 LB_TIMER_UP,
105 LB_TIMER_LEFT,
106 LB_TIMER_DOWN,
107 LB_TIMER_RIGHT
108 } TIMER_DIRECTION;
110 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
112 static LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
113 static LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
114 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
115 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
117 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
119 /*********************************************************************
120 * listbox class descriptor
122 const struct builtin_class_descr LISTBOX_builtin_class =
124 "ListBox", /* name */
125 CS_GLOBALCLASS | CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
126 ListBoxWndProcA, /* procA */
127 ListBoxWndProcW, /* procW */
128 sizeof(LB_DESCR *), /* extra */
129 IDC_ARROWA, /* cursor */
130 0 /* brush */
134 /*********************************************************************
135 * combolbox class descriptor
137 const struct builtin_class_descr COMBOLBOX_builtin_class =
139 "ComboLBox", /* name */
140 CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS, /* style */
141 ComboLBWndProcA, /* procA */
142 ComboLBWndProcW, /* procW */
143 sizeof(LB_DESCR *), /* extra */
144 IDC_ARROWA, /* cursor */
145 0 /* brush */
149 /***********************************************************************
150 * LISTBOX_Dump
152 void LISTBOX_Dump( WND *wnd )
154 INT i;
155 LB_ITEMDATA *item;
156 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
158 TRACE( "Listbox:\n" );
159 TRACE( "hwnd=%04x descr=%08x items=%d top=%d\n",
160 wnd->hwndSelf, (UINT)descr, descr->nb_items,
161 descr->top_item );
162 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
164 TRACE( "%4d: %-40s %d %08lx %3d\n",
165 i, debugstr_w(item->str), item->selected, item->data, item->height );
170 /***********************************************************************
171 * LISTBOX_GetCurrentPageSize
173 * Return the current page size
175 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
177 INT i, height;
178 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
179 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
181 if ((height += descr->items[i].height) > descr->height) break;
183 if (i == descr->top_item) return 1;
184 else return i - descr->top_item;
188 /***********************************************************************
189 * LISTBOX_GetMaxTopIndex
191 * Return the maximum possible index for the top of the listbox.
193 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
195 INT max, page;
197 if (descr->style & LBS_OWNERDRAWVARIABLE)
199 page = descr->height;
200 for (max = descr->nb_items - 1; max >= 0; max--)
201 if ((page -= descr->items[max].height) < 0) break;
202 if (max < descr->nb_items - 1) max++;
204 else if (descr->style & LBS_MULTICOLUMN)
206 if ((page = descr->width / descr->column_width) < 1) page = 1;
207 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
208 max = (max - page) * descr->page_size;
210 else
212 max = descr->nb_items - descr->page_size;
214 if (max < 0) max = 0;
215 return max;
219 /***********************************************************************
220 * LISTBOX_UpdateScroll
222 * Update the scrollbars. Should be called whenever the content
223 * of the listbox changes.
225 static void LISTBOX_UpdateScroll( WND *wnd, LB_DESCR *descr )
227 SCROLLINFO info;
229 /* Check the listbox scroll bar flags individually before we call
230 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
231 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
232 scroll bar when we do not need one.
233 if (!(descr->style & WS_VSCROLL)) return;
236 /* It is important that we check descr->style, and not wnd->dwStyle,
237 for WS_VSCROLL, as the former is exactly the one passed in
238 argument to CreateWindow.
239 In Windows (and from now on in Wine :) a listbox created
240 with such a style (no WS_SCROLL) does not update
241 the scrollbar with listbox-related data, thus letting
242 the programmer use it for his/her own purposes. */
244 if (descr->style & LBS_NOREDRAW) return;
245 info.cbSize = sizeof(info);
247 if (descr->style & LBS_MULTICOLUMN)
249 info.nMin = 0;
250 info.nMax = (descr->nb_items - 1) / descr->page_size;
251 info.nPos = descr->top_item / descr->page_size;
252 info.nPage = descr->width / descr->column_width;
253 if (info.nPage < 1) info.nPage = 1;
254 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
255 if (descr->style & LBS_DISABLENOSCROLL)
256 info.fMask |= SIF_DISABLENOSCROLL;
257 if (descr->style & WS_HSCROLL)
258 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
259 info.nMax = 0;
260 info.fMask = SIF_RANGE;
261 if (descr->style & WS_VSCROLL)
262 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
264 else
266 info.nMin = 0;
267 info.nMax = descr->nb_items - 1;
268 info.nPos = descr->top_item;
269 info.nPage = LISTBOX_GetCurrentPageSize( descr );
270 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
271 if (descr->style & LBS_DISABLENOSCROLL)
272 info.fMask |= SIF_DISABLENOSCROLL;
273 if (descr->style & WS_VSCROLL)
274 SetScrollInfo( wnd->hwndSelf, SB_VERT, &info, TRUE );
276 if (descr->horz_extent)
278 info.nMin = 0;
279 info.nMax = descr->horz_extent - 1;
280 info.nPos = descr->horz_pos;
281 info.nPage = descr->width;
282 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
283 if (descr->style & LBS_DISABLENOSCROLL)
284 info.fMask |= SIF_DISABLENOSCROLL;
285 if (descr->style & WS_HSCROLL)
286 SetScrollInfo( wnd->hwndSelf, SB_HORZ, &info, TRUE );
292 /***********************************************************************
293 * LISTBOX_SetTopItem
295 * Set the top item of the listbox, scrolling up or down if necessary.
297 static LRESULT LISTBOX_SetTopItem( WND *wnd, LB_DESCR *descr, INT index,
298 BOOL scroll )
300 INT max = LISTBOX_GetMaxTopIndex( descr );
301 if (index > max) index = max;
302 if (index < 0) index = 0;
303 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
304 if (descr->top_item == index) return LB_OKAY;
305 if (descr->style & LBS_MULTICOLUMN)
307 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
308 if (scroll && (abs(diff) < descr->width))
309 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
310 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
312 else
313 scroll = FALSE;
315 else if (scroll)
317 INT diff;
318 if (descr->style & LBS_OWNERDRAWVARIABLE)
320 INT i;
321 diff = 0;
322 if (index > descr->top_item)
324 for (i = index - 1; i >= descr->top_item; i--)
325 diff -= descr->items[i].height;
327 else
329 for (i = index; i < descr->top_item; i++)
330 diff += descr->items[i].height;
333 else
334 diff = (descr->top_item - index) * descr->item_height;
336 if (abs(diff) < descr->height)
337 ScrollWindowEx( wnd->hwndSelf, 0, diff, NULL, NULL, 0, NULL,
338 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
339 else
340 scroll = FALSE;
342 if (!scroll) InvalidateRect( wnd->hwndSelf, NULL, TRUE );
343 descr->top_item = index;
344 LISTBOX_UpdateScroll( wnd, descr );
345 return LB_OKAY;
349 /***********************************************************************
350 * LISTBOX_UpdatePage
352 * Update the page size. Should be called when the size of
353 * the client area or the item height changes.
355 static void LISTBOX_UpdatePage( WND *wnd, LB_DESCR *descr )
357 INT page_size;
359 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
360 page_size = 1;
361 if (page_size == descr->page_size) return;
362 descr->page_size = page_size;
363 if (descr->style & LBS_MULTICOLUMN)
364 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
365 LISTBOX_SetTopItem( wnd, descr, descr->top_item, FALSE );
369 /***********************************************************************
370 * LISTBOX_UpdateSize
372 * Update the size of the listbox. Should be called when the size of
373 * the client area changes.
375 static void LISTBOX_UpdateSize( WND *wnd, LB_DESCR *descr )
377 RECT rect;
379 GetClientRect( wnd->hwndSelf, &rect );
380 descr->width = rect.right - rect.left;
381 descr->height = rect.bottom - rect.top;
382 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
384 INT remaining;
386 if(descr->item_height != 0)
387 remaining = descr->height % descr->item_height;
388 else
389 remaining = 0;
390 if ((descr->height > descr->item_height) && remaining)
392 if (!(wnd->flags & WIN_ISWIN32))
393 { /* give a margin for error to 16 bits programs - if we need
394 less than the height of the nonclient area, round to the
395 *next* number of items */
396 int ncheight = wnd->rectWindow.bottom - wnd->rectWindow.top - descr->height;
397 if ((descr->item_height - remaining) <= ncheight)
398 remaining = remaining - descr->item_height;
400 TRACE("[%04x]: changing height %d -> %d\n",
401 wnd->hwndSelf, descr->height,
402 descr->height - remaining );
403 SetWindowPos( wnd->hwndSelf, 0, 0, 0,
404 wnd->rectWindow.right - wnd->rectWindow.left,
405 wnd->rectWindow.bottom - wnd->rectWindow.top - remaining,
406 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
407 return;
410 TRACE("[%04x]: new size = %d,%d\n",
411 wnd->hwndSelf, descr->width, descr->height );
412 LISTBOX_UpdatePage( wnd, descr );
413 LISTBOX_UpdateScroll( wnd, descr );
415 /* Invalidate the focused item so it will be repainted correctly */
416 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
418 InvalidateRect( wnd->hwndSelf, &rect, FALSE );
423 /***********************************************************************
424 * LISTBOX_GetItemRect
426 * Get the rectangle enclosing an item, in listbox client coordinates.
427 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
429 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
431 /* Index <= 0 is legal even on empty listboxes */
432 if (index && (index >= descr->nb_items)) return -1;
433 SetRect( rect, 0, 0, descr->width, descr->height );
434 if (descr->style & LBS_MULTICOLUMN)
436 INT col = (index / descr->page_size) -
437 (descr->top_item / descr->page_size);
438 rect->left += col * descr->column_width;
439 rect->right = rect->left + descr->column_width;
440 rect->top += (index % descr->page_size) * descr->item_height;
441 rect->bottom = rect->top + descr->item_height;
443 else if (descr->style & LBS_OWNERDRAWVARIABLE)
445 INT i;
446 rect->right += descr->horz_pos;
447 if ((index >= 0) && (index < descr->nb_items))
449 if (index < descr->top_item)
451 for (i = descr->top_item-1; i >= index; i--)
452 rect->top -= descr->items[i].height;
454 else
456 for (i = descr->top_item; i < index; i++)
457 rect->top += descr->items[i].height;
459 rect->bottom = rect->top + descr->items[index].height;
463 else
465 rect->top += (index - descr->top_item) * descr->item_height;
466 rect->bottom = rect->top + descr->item_height;
467 rect->right += descr->horz_pos;
470 return ((rect->left < descr->width) && (rect->right > 0) &&
471 (rect->top < descr->height) && (rect->bottom > 0));
475 /***********************************************************************
476 * LISTBOX_GetItemFromPoint
478 * Return the item nearest from point (x,y) (in client coordinates).
480 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
482 INT index = descr->top_item;
484 if (!descr->nb_items) return -1; /* No items */
485 if (descr->style & LBS_OWNERDRAWVARIABLE)
487 INT pos = 0;
488 if (y >= 0)
490 while (index < descr->nb_items)
492 if ((pos += descr->items[index].height) > y) break;
493 index++;
496 else
498 while (index > 0)
500 index--;
501 if ((pos -= descr->items[index].height) <= y) break;
505 else if (descr->style & LBS_MULTICOLUMN)
507 if (y >= descr->item_height * descr->page_size) return -1;
508 if (y >= 0) index += y / descr->item_height;
509 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
510 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
512 else
514 index += (y / descr->item_height);
516 if (index < 0) return 0;
517 if (index >= descr->nb_items) return -1;
518 return index;
522 /***********************************************************************
523 * LISTBOX_PaintItem
525 * Paint an item.
527 static void LISTBOX_PaintItem( WND *wnd, LB_DESCR *descr, HDC hdc,
528 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
530 LB_ITEMDATA *item = NULL;
531 if (index < descr->nb_items) item = &descr->items[index];
533 if (IS_OWNERDRAW(descr))
535 DRAWITEMSTRUCT dis;
536 RECT r;
537 HRGN hrgn;
539 if (!item)
541 if (action == ODA_FOCUS)
542 DrawFocusRect( hdc, rect );
543 else
544 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
545 return;
548 /* some programs mess with the clipping region when
549 drawing the item, *and* restore the previous region
550 after they are done, so a region has better to exist
551 else everything ends clipped */
552 GetClientRect(wnd->hwndSelf, &r);
553 hrgn = CreateRectRgnIndirect(&r);
554 SelectClipRgn( hdc, hrgn);
555 DeleteObject( hrgn );
557 dis.CtlType = ODT_LISTBOX;
558 dis.CtlID = wnd->wIDmenu;
559 dis.hwndItem = wnd->hwndSelf;
560 dis.itemAction = action;
561 dis.hDC = hdc;
562 dis.itemID = index;
563 dis.itemState = 0;
564 if (item && item->selected) dis.itemState |= ODS_SELECTED;
565 if (!ignoreFocus && (descr->focus_item == index) &&
566 (descr->caret_on) &&
567 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
568 if (wnd->dwStyle & WS_DISABLED) dis.itemState |= ODS_DISABLED;
569 dis.itemData = item ? item->data : 0;
570 dis.rcItem = *rect;
571 TRACE("[%04x]: drawitem %d (%s) action=%02x "
572 "state=%02x rect=%d,%d-%d,%d\n",
573 wnd->hwndSelf, index, item ? debugstr_w(item->str) : "", action,
574 dis.itemState, rect->left, rect->top,
575 rect->right, rect->bottom );
576 SendMessageW(descr->owner, WM_DRAWITEM, wnd->wIDmenu, (LPARAM)&dis);
578 else
580 COLORREF oldText = 0, oldBk = 0;
582 if (action == ODA_FOCUS)
584 DrawFocusRect( hdc, rect );
585 return;
587 if (item && item->selected)
589 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
590 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
593 TRACE("[%04x]: painting %d (%s) action=%02x "
594 "rect=%d,%d-%d,%d\n",
595 wnd->hwndSelf, index, item ? debugstr_w(item->str) : "", action,
596 rect->left, rect->top, rect->right, rect->bottom );
597 if (!item)
598 ExtTextOutW( hdc, rect->left + 1, rect->top,
599 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
600 else if (!(descr->style & LBS_USETABSTOPS))
601 ExtTextOutW( hdc, rect->left + 1, rect->top,
602 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
603 strlenW(item->str), NULL );
604 else
606 /* Output empty string to paint background in the full width. */
607 ExtTextOutW( hdc, rect->left + 1, rect->top,
608 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
609 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
610 item->str, strlenW(item->str),
611 descr->nb_tabs, descr->tabs, 0);
613 if (item && item->selected)
615 SetBkColor( hdc, oldBk );
616 SetTextColor( hdc, oldText );
618 if (!ignoreFocus && (descr->focus_item == index) &&
619 (descr->caret_on) &&
620 (descr->in_focus)) DrawFocusRect( hdc, rect );
625 /***********************************************************************
626 * LISTBOX_SetRedraw
628 * Change the redraw flag.
630 static void LISTBOX_SetRedraw( WND *wnd, LB_DESCR *descr, BOOL on )
632 if (on)
634 if (!(descr->style & LBS_NOREDRAW)) return;
635 descr->style &= ~LBS_NOREDRAW;
636 if (descr->style & LBS_DISPLAYCHANGED)
637 { /* page was changed while setredraw false, refresh automatically */
638 InvalidateRect(wnd->hwndSelf, NULL, TRUE);
639 if ((descr->top_item + descr->page_size) > descr->nb_items)
640 { /* reset top of page if less than number of items/page */
641 descr->top_item = descr->nb_items - descr->page_size;
642 if (descr->top_item < 0) descr->top_item = 0;
644 descr->style &= ~LBS_DISPLAYCHANGED;
646 LISTBOX_UpdateScroll( wnd, descr );
648 else descr->style |= LBS_NOREDRAW;
652 /***********************************************************************
653 * LISTBOX_RepaintItem
655 * Repaint a single item synchronously.
657 static void LISTBOX_RepaintItem( WND *wnd, LB_DESCR *descr, INT index,
658 UINT action )
660 HDC hdc;
661 RECT rect;
662 HFONT oldFont = 0;
663 HBRUSH hbrush, oldBrush = 0;
665 /* Do not repaint the item if the item is not visible */
666 if (!IsWindowVisible(wnd->hwndSelf)) return;
667 if (descr->style & LBS_NOREDRAW)
669 descr->style |= LBS_DISPLAYCHANGED;
670 return;
672 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
673 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE ))) return;
674 if (descr->font) oldFont = SelectObject( hdc, descr->font );
675 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
676 hdc, (LPARAM)wnd->hwndSelf );
677 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
678 if (wnd->dwStyle & WS_DISABLED)
679 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
680 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
681 LISTBOX_PaintItem( wnd, descr, hdc, &rect, index, action, FALSE );
682 if (oldFont) SelectObject( hdc, oldFont );
683 if (oldBrush) SelectObject( hdc, oldBrush );
684 ReleaseDC( wnd->hwndSelf, hdc );
688 /***********************************************************************
689 * LISTBOX_InitStorage
691 static LRESULT LISTBOX_InitStorage( WND *wnd, LB_DESCR *descr, INT nb_items )
693 LB_ITEMDATA *item;
695 nb_items += LB_ARRAY_GRANULARITY - 1;
696 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
697 if (descr->items)
698 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
699 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
700 nb_items * sizeof(LB_ITEMDATA) )))
702 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
703 return LB_ERRSPACE;
705 descr->items = item;
706 return LB_OKAY;
710 /***********************************************************************
711 * LISTBOX_SetTabStops
713 static BOOL LISTBOX_SetTabStops( WND *wnd, LB_DESCR *descr, INT count,
714 LPINT tabs, BOOL short_ints )
716 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
717 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
718 if (!(descr->nb_tabs = count))
720 descr->tabs = NULL;
721 return TRUE;
723 /* FIXME: count = 1 */
724 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
725 descr->nb_tabs * sizeof(INT) )))
726 return FALSE;
727 if (short_ints)
729 INT i;
730 LPINT16 p = (LPINT16)tabs;
732 TRACE("[%04x]: settabstops ", wnd->hwndSelf );
733 for (i = 0; i < descr->nb_tabs; i++) {
734 descr->tabs[i] = *p++<<1; /* FIXME */
735 if (TRACE_ON(listbox)) DPRINTF("%hd ", descr->tabs[i]);
737 if (TRACE_ON(listbox)) DPRINTF("\n");
739 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
740 /* FIXME: repaint the window? */
741 return TRUE;
745 /***********************************************************************
746 * LISTBOX_GetText
748 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
750 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
751 if (HAS_STRINGS(descr))
753 if (!lParam)
754 return strlenW(descr->items[index].str);
756 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
758 if(unicode)
760 LPWSTR buffer = (LPWSTR)lParam;
761 strcpyW( buffer, descr->items[index].str );
762 return strlenW(buffer);
764 else
766 LPSTR buffer = (LPSTR)lParam;
767 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
769 } else {
770 if (lParam)
771 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
772 return sizeof(DWORD);
777 /***********************************************************************
778 * LISTBOX_FindStringPos
780 * Find the nearest string located before a given string in sort order.
781 * If 'exact' is TRUE, return an error if we don't get an exact match.
783 static INT LISTBOX_FindStringPos( WND *wnd, LB_DESCR *descr, LPCWSTR str,
784 BOOL exact )
786 INT index, min, max, res = -1;
788 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
789 min = 0;
790 max = descr->nb_items;
791 while (min != max)
793 index = (min + max) / 2;
794 if (HAS_STRINGS(descr))
795 res = lstrcmpiW( descr->items[index].str, str );
796 else
798 COMPAREITEMSTRUCT cis;
800 cis.CtlType = ODT_LISTBOX;
801 cis.CtlID = wnd->wIDmenu;
802 cis.hwndItem = wnd->hwndSelf;
803 cis.itemID1 = index;
804 cis.itemData1 = descr->items[index].data;
805 cis.itemID2 = -1;
806 cis.itemData2 = (DWORD)str;
807 cis.dwLocaleId = descr->locale;
808 res = SendMessageW( descr->owner, WM_COMPAREITEM,
809 wnd->wIDmenu, (LPARAM)&cis );
811 if (!res) return index;
812 if (res > 0) max = index;
813 else min = index + 1;
815 return exact ? -1 : max;
819 /***********************************************************************
820 * LISTBOX_FindFileStrPos
822 * Find the nearest string located before a given string in directory
823 * sort order (i.e. first files, then directories, then drives).
825 static INT LISTBOX_FindFileStrPos( WND *wnd, LB_DESCR *descr, LPCWSTR str )
827 INT min, max, res = -1;
829 if (!HAS_STRINGS(descr))
830 return LISTBOX_FindStringPos( wnd, descr, str, FALSE );
831 min = 0;
832 max = descr->nb_items;
833 while (min != max)
835 INT index = (min + max) / 2;
836 LPCWSTR p = descr->items[index].str;
837 if (*p == '[') /* drive or directory */
839 if (*str != '[') res = -1;
840 else if (p[1] == '-') /* drive */
842 if (str[1] == '-') res = str[2] - p[2];
843 else res = -1;
845 else /* directory */
847 if (str[1] == '-') res = 1;
848 else res = lstrcmpiW( str, p );
851 else /* filename */
853 if (*str == '[') res = 1;
854 else res = lstrcmpiW( str, p );
856 if (!res) return index;
857 if (res < 0) max = index;
858 else min = index + 1;
860 return max;
864 /***********************************************************************
865 * LISTBOX_FindString
867 * Find the item beginning with a given string.
869 static INT LISTBOX_FindString( WND *wnd, LB_DESCR *descr, INT start,
870 LPCWSTR str, BOOL exact )
872 INT i;
873 LB_ITEMDATA *item;
875 if (start >= descr->nb_items) start = -1;
876 item = descr->items + start + 1;
877 if (HAS_STRINGS(descr))
879 if (!str || ! str[0] ) return LB_ERR;
880 if (exact)
882 for (i = start + 1; i < descr->nb_items; i++, item++)
883 if (!lstrcmpiW( str, item->str )) return i;
884 for (i = 0, item = descr->items; i <= start; i++, item++)
885 if (!lstrcmpiW( str, item->str )) return i;
887 else
889 /* Special case for drives and directories: ignore prefix */
890 #define CHECK_DRIVE(item) \
891 if ((item)->str[0] == '[') \
893 if (!strncmpiW( str, (item)->str+1, len )) return i; \
894 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
895 return i; \
898 INT len = strlenW(str);
899 for (i = start + 1; i < descr->nb_items; i++, item++)
901 if (!strncmpiW( str, item->str, len )) return i;
902 CHECK_DRIVE(item);
904 for (i = 0, item = descr->items; i <= start; i++, item++)
906 if (!strncmpiW( str, item->str, len )) return i;
907 CHECK_DRIVE(item);
909 #undef CHECK_DRIVE
912 else
914 if (exact && (descr->style & LBS_SORT))
915 /* If sorted, use a WM_COMPAREITEM binary search */
916 return LISTBOX_FindStringPos( wnd, descr, str, TRUE );
918 /* Otherwise use a linear search */
919 for (i = start + 1; i < descr->nb_items; i++, item++)
920 if (item->data == (DWORD)str) return i;
921 for (i = 0, item = descr->items; i <= start; i++, item++)
922 if (item->data == (DWORD)str) return i;
924 return LB_ERR;
928 /***********************************************************************
929 * LISTBOX_GetSelCount
931 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
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; i++, item++)
938 if (item->selected) count++;
939 return count;
943 /***********************************************************************
944 * LISTBOX_GetSelItems16
946 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
948 INT i, count;
949 LB_ITEMDATA *item = descr->items;
951 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
952 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
953 if (item->selected) array[count++] = (INT16)i;
954 return count;
958 /***********************************************************************
959 * LISTBOX_GetSelItems
961 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
963 INT i, count;
964 LB_ITEMDATA *item = descr->items;
966 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
967 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
968 if (item->selected) array[count++] = i;
969 return count;
973 /***********************************************************************
974 * LISTBOX_Paint
976 static LRESULT LISTBOX_Paint( WND *wnd, LB_DESCR *descr, HDC hdc )
978 INT i, col_pos = descr->page_size - 1;
979 RECT rect;
980 RECT focusRect = {-1, -1, -1, -1};
981 HFONT oldFont = 0;
982 HBRUSH hbrush, oldBrush = 0;
984 if (descr->style & LBS_NOREDRAW) return 0;
986 SetRect( &rect, 0, 0, descr->width, descr->height );
987 if (descr->style & LBS_MULTICOLUMN)
988 rect.right = rect.left + descr->column_width;
989 else if (descr->horz_pos)
991 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
992 rect.right += descr->horz_pos;
995 if (descr->font) oldFont = SelectObject( hdc, descr->font );
996 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
997 hdc, (LPARAM)wnd->hwndSelf );
998 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
999 if (wnd->dwStyle & WS_DISABLED)
1000 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1002 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1003 (descr->in_focus))
1005 /* Special case for empty listbox: paint focus rect */
1006 rect.bottom = rect.top + descr->item_height;
1007 LISTBOX_PaintItem( wnd, descr, hdc, &rect, descr->focus_item,
1008 ODA_FOCUS, FALSE );
1009 rect.top = rect.bottom;
1012 /* Paint all the item, regarding the selection
1013 Focus state will be painted after */
1015 for (i = descr->top_item; i < descr->nb_items; i++)
1017 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1018 rect.bottom = rect.top + descr->item_height;
1019 else
1020 rect.bottom = rect.top + descr->items[i].height;
1022 if (i == descr->focus_item)
1024 /* keep the focus rect, to paint the focus item after */
1025 focusRect.left = rect.left;
1026 focusRect.right = rect.right;
1027 focusRect.top = rect.top;
1028 focusRect.bottom = rect.bottom;
1030 LISTBOX_PaintItem( wnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1031 rect.top = rect.bottom;
1033 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1035 if (!IS_OWNERDRAW(descr))
1037 /* Clear the bottom of the column */
1038 if (rect.top < descr->height)
1040 rect.bottom = descr->height;
1041 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1042 &rect, NULL, 0, NULL );
1046 /* Go to the next column */
1047 rect.left += descr->column_width;
1048 rect.right += descr->column_width;
1049 rect.top = 0;
1050 col_pos = descr->page_size - 1;
1052 else
1054 col_pos--;
1055 if (rect.top >= descr->height) break;
1059 /* Paint the focus item now */
1060 if (focusRect.top != focusRect.bottom && descr->caret_on)
1061 LISTBOX_PaintItem( wnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1063 if (!IS_OWNERDRAW(descr))
1065 /* Clear the remainder of the client area */
1066 if (rect.top < descr->height)
1068 rect.bottom = descr->height;
1069 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1070 &rect, NULL, 0, NULL );
1072 if (rect.right < descr->width)
1074 rect.left = rect.right;
1075 rect.right = descr->width;
1076 rect.top = 0;
1077 rect.bottom = descr->height;
1078 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1079 &rect, NULL, 0, NULL );
1082 if (oldFont) SelectObject( hdc, oldFont );
1083 if (oldBrush) SelectObject( hdc, oldBrush );
1084 return 0;
1088 /***********************************************************************
1089 * LISTBOX_InvalidateItems
1091 * Invalidate all items from a given item. If the specified item is not
1092 * visible, nothing happens.
1094 static void LISTBOX_InvalidateItems( WND *wnd, LB_DESCR *descr, INT index )
1096 RECT rect;
1098 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1100 if (descr->style & LBS_NOREDRAW)
1102 descr->style |= LBS_DISPLAYCHANGED;
1103 return;
1105 rect.bottom = descr->height;
1106 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1107 if (descr->style & LBS_MULTICOLUMN)
1109 /* Repaint the other columns */
1110 rect.left = rect.right;
1111 rect.right = descr->width;
1112 rect.top = 0;
1113 InvalidateRect( wnd->hwndSelf, &rect, TRUE );
1119 /***********************************************************************
1120 * LISTBOX_GetItemHeight
1122 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1124 if (descr->style & LBS_OWNERDRAWVARIABLE)
1126 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1127 return descr->items[index].height;
1129 else return descr->item_height;
1133 /***********************************************************************
1134 * LISTBOX_SetItemHeight
1136 static LRESULT LISTBOX_SetItemHeight( WND *wnd, LB_DESCR *descr, INT index,
1137 INT height )
1139 if (!height) height = 1;
1141 if (descr->style & LBS_OWNERDRAWVARIABLE)
1143 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1144 TRACE("[%04x]: item %d height = %d\n",
1145 wnd->hwndSelf, index, height );
1146 descr->items[index].height = height;
1147 LISTBOX_UpdateScroll( wnd, descr );
1148 LISTBOX_InvalidateItems( wnd, descr, index );
1150 else if (height != descr->item_height)
1152 TRACE("[%04x]: new height = %d\n",
1153 wnd->hwndSelf, height );
1154 descr->item_height = height;
1155 LISTBOX_UpdatePage( wnd, descr );
1156 LISTBOX_UpdateScroll( wnd, descr );
1157 InvalidateRect( wnd->hwndSelf, 0, TRUE );
1159 return LB_OKAY;
1163 /***********************************************************************
1164 * LISTBOX_SetHorizontalPos
1166 static void LISTBOX_SetHorizontalPos( WND *wnd, LB_DESCR *descr, INT pos )
1168 INT diff;
1170 if (pos > descr->horz_extent - descr->width)
1171 pos = descr->horz_extent - descr->width;
1172 if (pos < 0) pos = 0;
1173 if (!(diff = descr->horz_pos - pos)) return;
1174 TRACE("[%04x]: new horz pos = %d\n",
1175 wnd->hwndSelf, pos );
1176 descr->horz_pos = pos;
1177 LISTBOX_UpdateScroll( wnd, descr );
1178 if (abs(diff) < descr->width)
1179 ScrollWindowEx( wnd->hwndSelf, diff, 0, NULL, NULL, 0, NULL,
1180 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1181 else
1182 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1186 /***********************************************************************
1187 * LISTBOX_SetHorizontalExtent
1189 static LRESULT LISTBOX_SetHorizontalExtent( WND *wnd, LB_DESCR *descr,
1190 INT extent )
1192 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1193 return LB_OKAY;
1194 if (extent <= 0) extent = 1;
1195 if (extent == descr->horz_extent) return LB_OKAY;
1196 TRACE("[%04x]: new horz extent = %d\n",
1197 wnd->hwndSelf, extent );
1198 descr->horz_extent = extent;
1199 if (descr->horz_pos > extent - descr->width)
1200 LISTBOX_SetHorizontalPos( wnd, descr, extent - descr->width );
1201 else
1202 LISTBOX_UpdateScroll( wnd, descr );
1203 return LB_OKAY;
1207 /***********************************************************************
1208 * LISTBOX_SetColumnWidth
1210 static LRESULT LISTBOX_SetColumnWidth( WND *wnd, LB_DESCR *descr, INT width)
1212 if (width == descr->column_width) return LB_OKAY;
1213 TRACE("[%04x]: new column width = %d\n",
1214 wnd->hwndSelf, width );
1215 descr->column_width = width;
1216 LISTBOX_UpdatePage( wnd, descr );
1217 return LB_OKAY;
1221 /***********************************************************************
1222 * LISTBOX_SetFont
1224 * Returns the item height.
1226 static INT LISTBOX_SetFont( WND *wnd, LB_DESCR *descr, HFONT font )
1228 HDC hdc;
1229 HFONT oldFont = 0;
1230 TEXTMETRICW tm;
1232 descr->font = font;
1234 if (!(hdc = GetDCEx( wnd->hwndSelf, 0, DCX_CACHE )))
1236 ERR("unable to get DC.\n" );
1237 return 16;
1239 if (font) oldFont = SelectObject( hdc, font );
1240 GetTextMetricsW( hdc, &tm );
1241 if (oldFont) SelectObject( hdc, oldFont );
1242 ReleaseDC( wnd->hwndSelf, hdc );
1243 if (!IS_OWNERDRAW(descr))
1244 LISTBOX_SetItemHeight( wnd, descr, 0, tm.tmHeight );
1245 return tm.tmHeight ;
1249 /***********************************************************************
1250 * LISTBOX_MakeItemVisible
1252 * Make sure that a given item is partially or fully visible.
1254 static void LISTBOX_MakeItemVisible( WND *wnd, LB_DESCR *descr, INT index,
1255 BOOL fully )
1257 INT top;
1259 if (index <= descr->top_item) top = index;
1260 else if (descr->style & LBS_MULTICOLUMN)
1262 INT cols = descr->width;
1263 if (!fully) cols += descr->column_width - 1;
1264 if (cols >= descr->column_width) cols /= descr->column_width;
1265 else cols = 1;
1266 if (index < descr->top_item + (descr->page_size * cols)) return;
1267 top = index - descr->page_size * (cols - 1);
1269 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1271 INT height = fully ? descr->items[index].height : 1;
1272 for (top = index; top > descr->top_item; top--)
1273 if ((height += descr->items[top-1].height) > descr->height) break;
1275 else
1277 if (index < descr->top_item + descr->page_size) return;
1278 if (!fully && (index == descr->top_item + descr->page_size) &&
1279 (descr->height > (descr->page_size * descr->item_height))) return;
1280 top = index - descr->page_size + 1;
1282 LISTBOX_SetTopItem( wnd, descr, top, TRUE );
1285 /***********************************************************************
1286 * LISTBOX_SetCaretIndex
1288 * NOTES
1289 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1292 static LRESULT LISTBOX_SetCaretIndex( WND *wnd, LB_DESCR *descr, INT index,
1293 BOOL fully_visible )
1295 INT oldfocus = descr->focus_item;
1297 if (descr->style & LBS_NOSEL) return LB_ERR;
1298 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1299 if (index == oldfocus) return LB_OKAY;
1300 descr->focus_item = index;
1301 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1302 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1304 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1305 if (descr->caret_on && (descr->in_focus))
1306 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1308 return LB_OKAY;
1312 /***********************************************************************
1313 * LISTBOX_SelectItemRange
1315 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1317 static LRESULT LISTBOX_SelectItemRange( WND *wnd, LB_DESCR *descr, INT first,
1318 INT last, BOOL on )
1320 INT i;
1322 /* A few sanity checks */
1324 if (descr->style & LBS_NOSEL) return LB_ERR;
1325 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1326 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1327 if (last == -1) last = descr->nb_items - 1;
1328 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1329 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1330 /* selected_item reflects last selected/unselected item on multiple sel */
1331 descr->selected_item = last;
1333 if (on) /* Turn selection on */
1335 for (i = first; i <= last; i++)
1337 if (descr->items[i].selected) continue;
1338 descr->items[i].selected = TRUE;
1339 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1341 LISTBOX_SetCaretIndex( wnd, descr, last, TRUE );
1343 else /* Turn selection off */
1345 for (i = first; i <= last; i++)
1347 if (!descr->items[i].selected) continue;
1348 descr->items[i].selected = FALSE;
1349 LISTBOX_RepaintItem( wnd, descr, i, ODA_SELECT );
1352 return LB_OKAY;
1355 /***********************************************************************
1356 * LISTBOX_SetSelection
1358 static LRESULT LISTBOX_SetSelection( WND *wnd, LB_DESCR *descr, INT index,
1359 BOOL on, BOOL send_notify )
1361 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1363 if (descr->style & LBS_NOSEL) return LB_ERR;
1364 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1365 if (descr->style & LBS_MULTIPLESEL)
1367 if (index == -1) /* Select all items */
1368 return LISTBOX_SelectItemRange( wnd, descr, 0, -1, on );
1369 else /* Only one item */
1370 return LISTBOX_SelectItemRange( wnd, descr, index, index, on );
1372 else
1374 INT oldsel = descr->selected_item;
1375 if (index == oldsel) return LB_OKAY;
1376 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1377 if (index != -1) descr->items[index].selected = TRUE;
1378 descr->selected_item = index;
1379 if (oldsel != -1) LISTBOX_RepaintItem( wnd, descr, oldsel, ODA_SELECT );
1380 if (index != -1) LISTBOX_RepaintItem( wnd, descr, index, ODA_SELECT );
1381 if (send_notify && descr->nb_items) SEND_NOTIFICATION( wnd, descr,
1382 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1383 else
1384 if( descr->lphc ) /* set selection change flag for parent combo */
1385 descr->lphc->wState |= CBF_SELCHANGE;
1387 return LB_OKAY;
1391 /***********************************************************************
1392 * LISTBOX_MoveCaret
1394 * Change the caret position and extend the selection to the new caret.
1396 static void LISTBOX_MoveCaret( WND *wnd, LB_DESCR *descr, INT index,
1397 BOOL fully_visible )
1399 INT oldfocus = descr->focus_item;
1401 if ((index < 0) || (index >= descr->nb_items))
1402 return;
1404 /* Important, repaint needs to be done in this order if
1405 you want to mimic Windows behavior:
1406 1. Remove the focus and paint the item
1407 2. Remove the selection and paint the item(s)
1408 3. Set the selection and repaint the item(s)
1409 4. Set the focus to 'index' and repaint the item */
1411 /* 1. remove the focus and repaint the item */
1412 descr->focus_item = -1;
1413 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1414 LISTBOX_RepaintItem( wnd, descr, oldfocus, ODA_FOCUS );
1416 /* 2. then turn off the previous selection */
1417 /* 3. repaint the new selected item */
1418 if (descr->style & LBS_EXTENDEDSEL)
1420 if (descr->anchor_item != -1)
1422 INT first = min( index, descr->anchor_item );
1423 INT last = max( index, descr->anchor_item );
1424 if (first > 0)
1425 LISTBOX_SelectItemRange( wnd, descr, 0, first - 1, FALSE );
1426 LISTBOX_SelectItemRange( wnd, descr, last + 1, -1, FALSE );
1427 LISTBOX_SelectItemRange( wnd, descr, first, last, TRUE );
1430 else if (!(descr->style & LBS_MULTIPLESEL))
1432 /* Set selection to new caret item */
1433 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
1436 /* 4. repaint the new item with the focus */
1437 descr->focus_item = index;
1438 LISTBOX_MakeItemVisible( wnd, descr, index, fully_visible );
1439 if (descr->caret_on && (descr->in_focus))
1440 LISTBOX_RepaintItem( wnd, descr, index, ODA_FOCUS );
1444 /***********************************************************************
1445 * LISTBOX_InsertItem
1447 static LRESULT LISTBOX_InsertItem( WND *wnd, LB_DESCR *descr, INT index,
1448 LPWSTR str, DWORD data )
1450 LB_ITEMDATA *item;
1451 INT max_items;
1452 INT oldfocus = descr->focus_item;
1454 if (index == -1) index = descr->nb_items;
1455 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1456 if (!descr->items) max_items = 0;
1457 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1458 if (descr->nb_items == max_items)
1460 /* We need to grow the array */
1461 max_items += LB_ARRAY_GRANULARITY;
1462 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1463 max_items * sizeof(LB_ITEMDATA) )))
1465 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1466 return LB_ERRSPACE;
1468 descr->items = item;
1471 /* Insert the item structure */
1473 item = &descr->items[index];
1474 if (index < descr->nb_items)
1475 RtlMoveMemory( item + 1, item,
1476 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1477 item->str = str;
1478 item->data = data;
1479 item->height = 0;
1480 item->selected = FALSE;
1481 descr->nb_items++;
1483 /* Get item height */
1485 if (descr->style & LBS_OWNERDRAWVARIABLE)
1487 MEASUREITEMSTRUCT mis;
1489 mis.CtlType = ODT_LISTBOX;
1490 mis.CtlID = wnd->wIDmenu;
1491 mis.itemID = index;
1492 mis.itemData = descr->items[index].data;
1493 mis.itemHeight = descr->item_height;
1494 SendMessageW( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
1495 item->height = mis.itemHeight ? mis.itemHeight : 1;
1496 TRACE("[%04x]: measure item %d (%s) = %d\n",
1497 wnd->hwndSelf, index, str ? debugstr_w(str) : "", item->height );
1500 /* Repaint the items */
1502 LISTBOX_UpdateScroll( wnd, descr );
1503 LISTBOX_InvalidateItems( wnd, descr, index );
1505 /* Move selection and focused item */
1506 /* If listbox was empty, set focus to the first item */
1507 if (descr->nb_items == 1)
1508 LISTBOX_SetCaretIndex( wnd, descr, 0, FALSE );
1509 /* single select don't change selection index in win31 */
1510 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1512 descr->selected_item++;
1513 LISTBOX_SetSelection( wnd, descr, descr->selected_item-1, TRUE, FALSE );
1515 else
1517 if (index <= descr->selected_item)
1519 descr->selected_item++;
1520 descr->focus_item = oldfocus; /* focus not changed */
1523 return LB_OKAY;
1527 /***********************************************************************
1528 * LISTBOX_InsertString
1530 static LRESULT LISTBOX_InsertString( WND *wnd, LB_DESCR *descr, INT index,
1531 LPCWSTR str )
1533 LPWSTR new_str = NULL;
1534 DWORD data = 0;
1535 LRESULT ret;
1537 if (HAS_STRINGS(descr))
1539 static const WCHAR empty_stringW[] = { 0 };
1540 if (!str) str = empty_stringW;
1541 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1543 SEND_NOTIFICATION( wnd, descr, LBN_ERRSPACE );
1544 return LB_ERRSPACE;
1546 strcpyW(new_str, str);
1548 else data = (DWORD)str;
1550 if (index == -1) index = descr->nb_items;
1551 if ((ret = LISTBOX_InsertItem( wnd, descr, index, new_str, data )) != 0)
1553 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1554 return ret;
1557 TRACE("[%04x]: added item %d '%s'\n",
1558 wnd->hwndSelf, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1559 return index;
1563 /***********************************************************************
1564 * LISTBOX_DeleteItem
1566 * Delete the content of an item. 'index' must be a valid index.
1568 static void LISTBOX_DeleteItem( WND *wnd, LB_DESCR *descr, INT index )
1570 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1571 * while Win95 sends it for all items with user data.
1572 * It's probably better to send it too often than not
1573 * often enough, so this is what we do here.
1575 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1577 DELETEITEMSTRUCT dis;
1579 dis.CtlType = ODT_LISTBOX;
1580 dis.CtlID = wnd->wIDmenu;
1581 dis.itemID = index;
1582 dis.hwndItem = wnd->hwndSelf;
1583 dis.itemData = descr->items[index].data;
1584 SendMessageW( descr->owner, WM_DELETEITEM, wnd->wIDmenu, (LPARAM)&dis );
1586 if (HAS_STRINGS(descr) && descr->items[index].str)
1587 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1591 /***********************************************************************
1592 * LISTBOX_RemoveItem
1594 * Remove an item from the listbox and delete its content.
1596 static LRESULT LISTBOX_RemoveItem( WND *wnd, LB_DESCR *descr, INT index )
1598 LB_ITEMDATA *item;
1599 INT max_items;
1601 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1602 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1604 /* We need to invalidate the original rect instead of the updated one. */
1605 LISTBOX_InvalidateItems( wnd, descr, index );
1607 LISTBOX_DeleteItem( wnd, descr, index );
1609 /* Remove the item */
1611 item = &descr->items[index];
1612 if (index < descr->nb_items-1)
1613 RtlMoveMemory( item, item + 1,
1614 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1615 descr->nb_items--;
1616 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1618 /* Shrink the item array if possible */
1620 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1621 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1623 max_items -= LB_ARRAY_GRANULARITY;
1624 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1625 max_items * sizeof(LB_ITEMDATA) );
1626 if (item) descr->items = item;
1628 /* Repaint the items */
1630 LISTBOX_UpdateScroll( wnd, descr );
1631 /* if we removed the scrollbar, reset the top of the list
1632 (correct for owner-drawn ???) */
1633 if (descr->nb_items == descr->page_size)
1634 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1636 /* Move selection and focused item */
1637 if (!IS_MULTISELECT(descr))
1639 if (index == descr->selected_item)
1640 descr->selected_item = -1;
1641 else if (index < descr->selected_item)
1643 descr->selected_item--;
1644 if (ISWIN31) /* win 31 do not change the selected item number */
1645 LISTBOX_SetSelection( wnd, descr, descr->selected_item + 1, TRUE, FALSE);
1649 if (descr->focus_item >= descr->nb_items)
1651 descr->focus_item = descr->nb_items - 1;
1652 if (descr->focus_item < 0) descr->focus_item = 0;
1654 return LB_OKAY;
1658 /***********************************************************************
1659 * LISTBOX_ResetContent
1661 static void LISTBOX_ResetContent( WND *wnd, LB_DESCR *descr )
1663 INT i;
1665 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( wnd, descr, i );
1666 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1667 descr->nb_items = 0;
1668 descr->top_item = 0;
1669 descr->selected_item = -1;
1670 descr->focus_item = 0;
1671 descr->anchor_item = -1;
1672 descr->items = NULL;
1676 /***********************************************************************
1677 * LISTBOX_SetCount
1679 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT count )
1681 LRESULT ret;
1683 if (HAS_STRINGS(descr)) return LB_ERR;
1684 /* FIXME: this is far from optimal... */
1685 if (count > descr->nb_items)
1687 while (count > descr->nb_items)
1688 if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1689 return ret;
1691 else if (count < descr->nb_items)
1693 while (count < descr->nb_items)
1694 if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1695 return ret;
1697 return LB_OKAY;
1701 /***********************************************************************
1702 * LISTBOX_Directory
1704 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT attrib,
1705 LPCWSTR filespec, BOOL long_names )
1707 HANDLE handle;
1708 LRESULT ret = LB_OKAY;
1709 WIN32_FIND_DATAW entry;
1710 int pos;
1712 /* don't scan directory if we just want drives exclusively */
1713 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1714 /* scan directory */
1715 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1717 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1719 else
1723 WCHAR buffer[270];
1724 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1726 static const WCHAR bracketW[] = { ']',0 };
1727 static const WCHAR dotW[] = { '.',0 };
1728 if (!(attrib & DDL_DIRECTORY) ||
1729 !strcmpW( entry.cAlternateFileName, dotW )) continue;
1730 buffer[0] = '[';
1731 if (long_names) strcpyW( buffer + 1, entry.cFileName );
1732 else strcpyW( buffer + 1, entry.cAlternateFileName );
1733 strcatW(buffer, bracketW);
1735 else /* not a directory */
1737 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1738 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1740 if ((attrib & DDL_EXCLUSIVE) &&
1741 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1742 continue;
1743 #undef ATTRIBS
1744 if (long_names) strcpyW( buffer, entry.cFileName );
1745 else strcpyW( buffer, entry.cAlternateFileName );
1747 if (!long_names) CharLowerW( buffer );
1748 pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1749 if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1750 break;
1751 } while (FindNextFileW( handle, &entry ));
1752 FindClose( handle );
1756 /* scan drives */
1757 if ((ret >= 0) && (attrib & DDL_DRIVES))
1759 WCHAR buffer[] = {'[','-','a','-',']',0};
1760 WCHAR root[] = {'A',':','\\',0};
1761 int drive;
1762 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1764 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1765 if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1766 break;
1769 return ret;
1773 /***********************************************************************
1774 * LISTBOX_HandleVScroll
1776 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr, WPARAM wParam )
1778 SCROLLINFO info;
1780 if (descr->style & LBS_MULTICOLUMN) return 0;
1781 switch(LOWORD(wParam))
1783 case SB_LINEUP:
1784 LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1785 break;
1786 case SB_LINEDOWN:
1787 LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1788 break;
1789 case SB_PAGEUP:
1790 LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1791 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1792 break;
1793 case SB_PAGEDOWN:
1794 LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1795 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1796 break;
1797 case SB_THUMBPOSITION:
1798 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1799 break;
1800 case SB_THUMBTRACK:
1801 info.cbSize = sizeof(info);
1802 info.fMask = SIF_TRACKPOS;
1803 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1804 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1805 break;
1806 case SB_TOP:
1807 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1808 break;
1809 case SB_BOTTOM:
1810 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1811 break;
1813 return 0;
1817 /***********************************************************************
1818 * LISTBOX_HandleHScroll
1820 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr, WPARAM wParam )
1822 SCROLLINFO info;
1823 INT page;
1825 if (descr->style & LBS_MULTICOLUMN)
1827 switch(LOWORD(wParam))
1829 case SB_LINELEFT:
1830 LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1831 TRUE );
1832 break;
1833 case SB_LINERIGHT:
1834 LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1835 TRUE );
1836 break;
1837 case SB_PAGELEFT:
1838 page = descr->width / descr->column_width;
1839 if (page < 1) page = 1;
1840 LISTBOX_SetTopItem( wnd, descr,
1841 descr->top_item - page * descr->page_size, TRUE );
1842 break;
1843 case SB_PAGERIGHT:
1844 page = descr->width / descr->column_width;
1845 if (page < 1) page = 1;
1846 LISTBOX_SetTopItem( wnd, descr,
1847 descr->top_item + page * descr->page_size, TRUE );
1848 break;
1849 case SB_THUMBPOSITION:
1850 LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1851 TRUE );
1852 break;
1853 case SB_THUMBTRACK:
1854 info.cbSize = sizeof(info);
1855 info.fMask = SIF_TRACKPOS;
1856 GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1857 LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1858 TRUE );
1859 break;
1860 case SB_LEFT:
1861 LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1862 break;
1863 case SB_RIGHT:
1864 LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1865 break;
1868 else if (descr->horz_extent)
1870 switch(LOWORD(wParam))
1872 case SB_LINELEFT:
1873 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1874 break;
1875 case SB_LINERIGHT:
1876 LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1877 break;
1878 case SB_PAGELEFT:
1879 LISTBOX_SetHorizontalPos( wnd, descr,
1880 descr->horz_pos - descr->width );
1881 break;
1882 case SB_PAGERIGHT:
1883 LISTBOX_SetHorizontalPos( wnd, descr,
1884 descr->horz_pos + descr->width );
1885 break;
1886 case SB_THUMBPOSITION:
1887 LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1888 break;
1889 case SB_THUMBTRACK:
1890 info.cbSize = sizeof(info);
1891 info.fMask = SIF_TRACKPOS;
1892 GetScrollInfo( wnd->hwndSelf, SB_HORZ, &info );
1893 LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1894 break;
1895 case SB_LEFT:
1896 LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1897 break;
1898 case SB_RIGHT:
1899 LISTBOX_SetHorizontalPos( wnd, descr,
1900 descr->horz_extent - descr->width );
1901 break;
1904 return 0;
1907 static LRESULT LISTBOX_HandleMouseWheel(WND *wnd, LB_DESCR *descr, WPARAM wParam )
1909 short gcWheelDelta = 0;
1910 UINT pulScrollLines = 3;
1912 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1914 gcWheelDelta -= (short) HIWORD(wParam);
1916 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1918 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1919 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1920 LISTBOX_SetTopItem( wnd, descr, descr->top_item + cLineScroll, TRUE );
1922 return 0;
1925 /***********************************************************************
1926 * LISTBOX_HandleLButtonDown
1928 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1929 WPARAM wParam, INT x, INT y )
1931 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1932 TRACE("[%04x]: lbuttondown %d,%d item %d\n",
1933 wnd->hwndSelf, x, y, index );
1934 if (!descr->caret_on && (descr->in_focus)) return 0;
1936 if (!descr->in_focus)
1938 if( !descr->lphc ) SetFocus( wnd->hwndSelf );
1939 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1940 : descr->lphc->self->hwndSelf );
1943 if (index == -1) return 0;
1945 if (descr->style & LBS_EXTENDEDSEL)
1947 /* we should perhaps make sure that all items are deselected
1948 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1949 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1950 LISTBOX_SetSelection( wnd, descr, -1, FALSE, FALSE);
1953 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1954 if (wParam & MK_CONTROL)
1956 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1957 LISTBOX_SetSelection( wnd, descr, index,
1958 !descr->items[index].selected,
1959 (descr->style & LBS_NOTIFY) != 0);
1961 else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1963 else
1965 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1966 LISTBOX_SetSelection( wnd, descr, index,
1967 (!(descr->style & LBS_MULTIPLESEL) ||
1968 !descr->items[index].selected),
1969 (descr->style & LBS_NOTIFY) != 0 );
1972 descr->captured = TRUE;
1973 SetCapture( wnd->hwndSelf );
1975 if (!descr->lphc)
1977 if (descr->style & LBS_NOTIFY )
1978 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
1979 MAKELPARAM( x, y ) );
1980 if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1982 POINT pt;
1984 pt.x = x;
1985 pt.y = y;
1987 if (DragDetect( wnd->hwndSelf, pt ))
1988 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
1991 return 0;
1995 /*************************************************************************
1996 * LISTBOX_HandleLButtonDownCombo [Internal]
1998 * Process LButtonDown message for the ComboListBox
2000 * PARAMS
2001 * pWnd [I] The windows internal structure
2002 * pDescr [I] The ListBox internal structure
2003 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2004 * x [I] X Mouse Coordinate
2005 * y [I] Y Mouse Coordinate
2007 * RETURNS
2008 * 0 since we are processing the WM_LBUTTONDOWN Message
2010 * NOTES
2011 * This function is only to be used when a ListBox is a ComboListBox
2014 static LRESULT LISTBOX_HandleLButtonDownCombo( WND *pWnd, LB_DESCR *pDescr,
2015 UINT msg, WPARAM wParam, INT x, INT y)
2017 RECT clientRect, screenRect;
2018 POINT mousePos;
2020 mousePos.x = x;
2021 mousePos.y = y;
2023 GetClientRect(pWnd->hwndSelf, &clientRect);
2025 if(PtInRect(&clientRect, mousePos))
2027 /* MousePos is in client, resume normal processing */
2028 if (msg == WM_LBUTTONDOWN)
2030 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2031 return LISTBOX_HandleLButtonDown( pWnd, pDescr, wParam, x, y);
2033 else if (pDescr->style & LBS_NOTIFY)
2034 SEND_NOTIFICATION( pWnd, pDescr, LBN_DBLCLK );
2035 return 0;
2037 else
2039 POINT screenMousePos;
2040 HWND hWndOldCapture;
2042 /* Check the Non-Client Area */
2043 screenMousePos = mousePos;
2044 hWndOldCapture = GetCapture();
2045 ReleaseCapture();
2046 GetWindowRect(pWnd->hwndSelf, &screenRect);
2047 ClientToScreen(pWnd->hwndSelf, &screenMousePos);
2049 if(!PtInRect(&screenRect, screenMousePos))
2051 LISTBOX_SetCaretIndex( pWnd, pDescr, pDescr->lphc->droppedIndex, FALSE );
2052 LISTBOX_SetSelection( pWnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2053 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2054 return 0;
2056 else
2058 /* Check to see the NC is a scrollbar */
2059 INT nHitTestType=0;
2060 /* Check Vertical scroll bar */
2061 if (pWnd->dwStyle & WS_VSCROLL)
2063 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2064 if (PtInRect( &clientRect, mousePos ))
2066 nHitTestType = HTVSCROLL;
2069 /* Check horizontal scroll bar */
2070 if (pWnd->dwStyle & WS_HSCROLL)
2072 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2073 if (PtInRect( &clientRect, mousePos ))
2075 nHitTestType = HTHSCROLL;
2078 /* Windows sends this message when a scrollbar is clicked
2081 if(nHitTestType != 0)
2083 SendMessageW(pWnd->hwndSelf, WM_NCLBUTTONDOWN, nHitTestType,
2084 MAKELONG(screenMousePos.x, screenMousePos.y));
2086 /* Resume the Capture after scrolling is complete
2088 if(hWndOldCapture != 0)
2090 SetCapture(hWndOldCapture);
2094 return 0;
2097 /***********************************************************************
2098 * LISTBOX_HandleLButtonUp
2100 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
2102 if (LISTBOX_Timer != LB_TIMER_NONE)
2103 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2104 LISTBOX_Timer = LB_TIMER_NONE;
2105 if (descr->captured)
2107 descr->captured = FALSE;
2108 if (GetCapture() == wnd->hwndSelf) ReleaseCapture();
2109 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2110 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2112 return 0;
2116 /***********************************************************************
2117 * LISTBOX_HandleTimer
2119 * Handle scrolling upon a timer event.
2120 * Return TRUE if scrolling should continue.
2122 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
2123 INT index, TIMER_DIRECTION dir )
2125 switch(dir)
2127 case LB_TIMER_UP:
2128 if (descr->top_item) index = descr->top_item - 1;
2129 else index = 0;
2130 break;
2131 case LB_TIMER_LEFT:
2132 if (descr->top_item) index -= descr->page_size;
2133 break;
2134 case LB_TIMER_DOWN:
2135 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2136 if (index == descr->focus_item) index++;
2137 if (index >= descr->nb_items) index = descr->nb_items - 1;
2138 break;
2139 case LB_TIMER_RIGHT:
2140 if (index + descr->page_size < descr->nb_items)
2141 index += descr->page_size;
2142 break;
2143 case LB_TIMER_NONE:
2144 break;
2146 if (index == descr->focus_item) return FALSE;
2147 LISTBOX_MoveCaret( wnd, descr, index, FALSE );
2148 return TRUE;
2152 /***********************************************************************
2153 * LISTBOX_HandleSystemTimer
2155 * WM_SYSTIMER handler.
2157 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
2159 if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
2161 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2162 LISTBOX_Timer = LB_TIMER_NONE;
2164 return 0;
2168 /***********************************************************************
2169 * LISTBOX_HandleMouseMove
2171 * WM_MOUSEMOVE handler.
2173 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
2174 INT x, INT y )
2176 INT index;
2177 TIMER_DIRECTION dir = LB_TIMER_NONE;
2179 if (!descr->captured) return;
2181 if (descr->style & LBS_MULTICOLUMN)
2183 if (y < 0) y = 0;
2184 else if (y >= descr->item_height * descr->page_size)
2185 y = descr->item_height * descr->page_size - 1;
2187 if (x < 0)
2189 dir = LB_TIMER_LEFT;
2190 x = 0;
2192 else if (x >= descr->width)
2194 dir = LB_TIMER_RIGHT;
2195 x = descr->width - 1;
2198 else
2200 if (y < 0) dir = LB_TIMER_UP; /* above */
2201 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2204 index = LISTBOX_GetItemFromPoint( descr, x, y );
2205 if (index == -1) index = descr->focus_item;
2206 if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
2208 /* Start/stop the system timer */
2210 if (dir != LB_TIMER_NONE)
2211 SetSystemTimer( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2212 else if (LISTBOX_Timer != LB_TIMER_NONE)
2213 KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2214 LISTBOX_Timer = dir;
2218 /***********************************************************************
2219 * LISTBOX_HandleKeyDown
2221 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM wParam )
2223 INT caret = -1;
2224 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2225 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2226 bForceSelection = FALSE; /* only for single select list */
2228 if (descr->style & LBS_WANTKEYBOARDINPUT)
2230 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2231 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2232 wnd->hwndSelf );
2233 if (caret == -2) return 0;
2235 if (caret == -1) switch(wParam)
2237 case VK_LEFT:
2238 if (descr->style & LBS_MULTICOLUMN)
2240 bForceSelection = FALSE;
2241 if (descr->focus_item >= descr->page_size)
2242 caret = descr->focus_item - descr->page_size;
2243 break;
2245 /* fall through */
2246 case VK_UP:
2247 caret = descr->focus_item - 1;
2248 if (caret < 0) caret = 0;
2249 break;
2250 case VK_RIGHT:
2251 if (descr->style & LBS_MULTICOLUMN)
2253 bForceSelection = FALSE;
2254 if (descr->focus_item + descr->page_size < descr->nb_items)
2255 caret = descr->focus_item + descr->page_size;
2256 break;
2258 /* fall through */
2259 case VK_DOWN:
2260 caret = descr->focus_item + 1;
2261 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2262 break;
2264 case VK_PRIOR:
2265 if (descr->style & LBS_MULTICOLUMN)
2267 INT page = descr->width / descr->column_width;
2268 if (page < 1) page = 1;
2269 caret = descr->focus_item - (page * descr->page_size) + 1;
2271 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2272 if (caret < 0) caret = 0;
2273 break;
2274 case VK_NEXT:
2275 if (descr->style & LBS_MULTICOLUMN)
2277 INT page = descr->width / descr->column_width;
2278 if (page < 1) page = 1;
2279 caret = descr->focus_item + (page * descr->page_size) - 1;
2281 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2282 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2283 break;
2284 case VK_HOME:
2285 caret = 0;
2286 break;
2287 case VK_END:
2288 caret = descr->nb_items - 1;
2289 break;
2290 case VK_SPACE:
2291 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2292 else if (descr->style & LBS_MULTIPLESEL)
2294 LISTBOX_SetSelection( wnd, descr, descr->focus_item,
2295 !descr->items[descr->focus_item].selected,
2296 (descr->style & LBS_NOTIFY) != 0 );
2298 break;
2299 default:
2300 bForceSelection = FALSE;
2302 if (bForceSelection) /* focused item is used instead of key */
2303 caret = descr->focus_item;
2304 if (caret >= 0)
2306 if ((descr->style & LBS_EXTENDEDSEL) &&
2307 !(GetKeyState( VK_SHIFT ) & 0x8000))
2308 descr->anchor_item = caret;
2309 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2310 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2311 if (descr->style & LBS_NOTIFY)
2313 if( descr->lphc )
2315 /* make sure that combo parent doesn't hide us */
2316 descr->lphc->wState |= CBF_NOROLLUP;
2318 if (descr->nb_items) SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2321 return 0;
2325 /***********************************************************************
2326 * LISTBOX_HandleChar
2328 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr, WCHAR charW )
2330 INT caret = -1;
2331 WCHAR str[2];
2333 str[0] = charW;
2334 str[1] = '\0';
2336 if (descr->style & LBS_WANTKEYBOARDINPUT)
2338 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2339 MAKEWPARAM(charW, descr->focus_item),
2340 wnd->hwndSelf );
2341 if (caret == -2) return 0;
2343 if (caret == -1)
2344 caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2345 if (caret != -1)
2347 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2348 LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2349 LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2350 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2351 SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2353 return 0;
2357 /***********************************************************************
2358 * LISTBOX_Create
2360 static BOOL LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2362 LB_DESCR *descr;
2363 MEASUREITEMSTRUCT mis;
2364 RECT rect;
2366 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2367 return FALSE;
2369 GetClientRect( wnd->hwndSelf, &rect );
2370 descr->owner = GetParent( wnd->hwndSelf );
2371 descr->style = wnd->dwStyle;
2372 descr->width = rect.right - rect.left;
2373 descr->height = rect.bottom - rect.top;
2374 descr->items = NULL;
2375 descr->nb_items = 0;
2376 descr->top_item = 0;
2377 descr->selected_item = -1;
2378 descr->focus_item = 0;
2379 descr->anchor_item = -1;
2380 descr->item_height = 1;
2381 descr->page_size = 1;
2382 descr->column_width = 150;
2383 descr->horz_extent = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2384 descr->horz_pos = 0;
2385 descr->nb_tabs = 0;
2386 descr->tabs = NULL;
2387 descr->caret_on = lphc ? FALSE : TRUE;
2388 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2389 descr->in_focus = FALSE;
2390 descr->captured = FALSE;
2391 descr->font = 0;
2392 descr->locale = 0; /* FIXME */
2393 descr->lphc = lphc;
2395 if( ( GetExpWinVer16( wnd->hInstance ) & 0xFF00 ) == 0x0300
2396 && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2398 /* Win95 document "List Box Differences" from MSDN:
2399 If a list box in a version 3.x application has either the
2400 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2401 horizontal and vertical scroll bars.
2403 descr->style |= WS_VSCROLL | WS_HSCROLL;
2406 if( lphc )
2408 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2409 wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2410 descr->owner = lphc->self->hwndSelf;
2413 *(LB_DESCR **)wnd->wExtra = descr;
2415 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2417 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2418 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2419 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2420 descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2422 if (descr->style & LBS_OWNERDRAWFIXED)
2424 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2426 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2427 descr->item_height = lphc->fixedOwnerDrawHeight;
2429 else
2431 mis.CtlType = ODT_LISTBOX;
2432 mis.CtlID = wnd->wIDmenu;
2433 mis.itemID = -1;
2434 mis.itemWidth = 0;
2435 mis.itemData = 0;
2436 mis.itemHeight = descr->item_height;
2437 SendMessageW( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
2438 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2442 TRACE("owner: %04x, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2443 return TRUE;
2447 /***********************************************************************
2448 * LISTBOX_Destroy
2450 static BOOL LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2452 LISTBOX_ResetContent( wnd, descr );
2453 HeapFree( GetProcessHeap(), 0, descr );
2454 wnd->wExtra[0] = 0;
2455 return TRUE;
2459 /***********************************************************************
2460 * ListBoxWndProc
2462 static LRESULT WINAPI ListBoxWndProc_locked( WND* wnd, UINT msg,
2463 WPARAM wParam, LPARAM lParam, BOOL unicode )
2465 LRESULT ret;
2466 LB_DESCR *descr;
2467 HWND hwnd = wnd->hwndSelf;
2469 if (!wnd) return 0;
2470 if (!(descr = *(LB_DESCR **)wnd->wExtra))
2472 switch (msg)
2474 case WM_CREATE:
2476 if (!LISTBOX_Create( wnd, NULL ))
2477 return -1;
2478 TRACE("creating wnd=%04x descr=%p\n",
2479 hwnd, *(LB_DESCR **)wnd->wExtra );
2480 return 0;
2482 case WM_NCCREATE:
2485 * When a listbox is not in a combobox and the look
2486 * is win95, the WS_BORDER style is replaced with
2487 * the WS_EX_CLIENTEDGE style.
2489 if ( (TWEAK_WineLook > WIN31_LOOK) &&
2490 (wnd->dwStyle & WS_BORDER) )
2492 wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2493 wnd->dwStyle &= ~ WS_BORDER;
2498 /* Ignore all other messages before we get a WM_CREATE */
2499 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2500 DefWindowProcA( hwnd, msg, wParam, lParam );
2503 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2504 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2505 switch(msg)
2507 case LB_RESETCONTENT16:
2508 case LB_RESETCONTENT:
2509 LISTBOX_ResetContent( wnd, descr );
2510 LISTBOX_UpdateScroll( wnd, descr );
2511 InvalidateRect( wnd->hwndSelf, NULL, TRUE );
2512 return 0;
2514 case LB_ADDSTRING16:
2515 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2516 /* fall through */
2517 case LB_ADDSTRING:
2519 INT ret;
2520 LPWSTR textW;
2521 if(unicode || !HAS_STRINGS(descr))
2522 textW = (LPWSTR)lParam;
2523 else
2525 LPSTR textA = (LPSTR)lParam;
2526 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2527 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2528 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2530 wParam = LISTBOX_FindStringPos( wnd, descr, textW, FALSE );
2531 ret = LISTBOX_InsertString( wnd, descr, wParam, textW );
2532 if (!unicode && HAS_STRINGS(descr))
2533 HeapFree(GetProcessHeap(), 0, textW);
2534 return ret;
2537 case LB_INSERTSTRING16:
2538 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2539 wParam = (INT)(INT16)wParam;
2540 /* fall through */
2541 case LB_INSERTSTRING:
2543 INT ret;
2544 LPWSTR textW;
2545 if(unicode || !HAS_STRINGS(descr))
2546 textW = (LPWSTR)lParam;
2547 else
2549 LPSTR textA = (LPSTR)lParam;
2550 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2551 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2552 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2554 ret = LISTBOX_InsertString( wnd, descr, wParam, textW );
2555 if(!unicode && HAS_STRINGS(descr))
2556 HeapFree(GetProcessHeap(), 0, textW);
2557 return ret;
2560 case LB_ADDFILE16:
2561 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2562 /* fall through */
2563 case LB_ADDFILE:
2565 INT ret;
2566 LPWSTR textW;
2567 if(unicode || !HAS_STRINGS(descr))
2568 textW = (LPWSTR)lParam;
2569 else
2571 LPSTR textA = (LPSTR)lParam;
2572 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2573 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2574 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2576 wParam = LISTBOX_FindFileStrPos( wnd, descr, textW );
2577 ret = LISTBOX_InsertString( wnd, descr, wParam, textW );
2578 if(!unicode && HAS_STRINGS(descr))
2579 HeapFree(GetProcessHeap(), 0, textW);
2580 return ret;
2583 case LB_DELETESTRING16:
2584 case LB_DELETESTRING:
2585 if (LISTBOX_RemoveItem( wnd, descr, wParam) != LB_ERR)
2586 return descr->nb_items;
2587 else
2588 return LB_ERR;
2590 case LB_GETITEMDATA16:
2591 case LB_GETITEMDATA:
2592 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2593 return LB_ERR;
2594 return descr->items[wParam].data;
2596 case LB_SETITEMDATA16:
2597 case LB_SETITEMDATA:
2598 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2599 return LB_ERR;
2600 descr->items[wParam].data = (DWORD)lParam;
2601 return LB_OKAY;
2603 case LB_GETCOUNT16:
2604 case LB_GETCOUNT:
2605 return descr->nb_items;
2607 case LB_GETTEXT16:
2608 lParam = (LPARAM)MapSL(lParam);
2609 /* fall through */
2610 case LB_GETTEXT:
2611 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2613 case LB_GETTEXTLEN16:
2614 /* fall through */
2615 case LB_GETTEXTLEN:
2616 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2617 return LB_ERR;
2618 return (HAS_STRINGS(descr) ? strlenW(descr->items[wParam].str)
2619 : sizeof(DWORD));
2621 case LB_GETCURSEL16:
2622 case LB_GETCURSEL:
2623 if (descr->nb_items==0)
2624 return LB_ERR;
2625 if (!IS_MULTISELECT(descr))
2626 return descr->selected_item;
2627 /* else */
2628 if (descr->selected_item!=-1)
2629 return descr->selected_item;
2630 /* else */
2631 return descr->focus_item;
2632 /* otherwise, if the user tries to move the selection with the */
2633 /* arrow keys, we will give the application something to choke on */
2634 case LB_GETTOPINDEX16:
2635 case LB_GETTOPINDEX:
2636 return descr->top_item;
2638 case LB_GETITEMHEIGHT16:
2639 case LB_GETITEMHEIGHT:
2640 return LISTBOX_GetItemHeight( descr, wParam );
2642 case LB_SETITEMHEIGHT16:
2643 lParam = LOWORD(lParam);
2644 /* fall through */
2645 case LB_SETITEMHEIGHT:
2646 return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2648 case LB_ITEMFROMPOINT:
2650 POINT pt;
2651 RECT rect;
2653 pt.x = LOWORD(lParam);
2654 pt.y = HIWORD(lParam);
2655 rect.left = 0;
2656 rect.top = 0;
2657 rect.right = descr->width;
2658 rect.bottom = descr->height;
2660 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2661 !PtInRect( &rect, pt ) );
2664 case LB_SETCARETINDEX16:
2665 case LB_SETCARETINDEX:
2666 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2667 if (LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam ) == LB_ERR)
2668 return LB_ERR;
2669 else if (ISWIN31)
2670 return wParam;
2671 else
2672 return LB_OKAY;
2674 case LB_GETCARETINDEX16:
2675 case LB_GETCARETINDEX:
2676 return descr->focus_item;
2678 case LB_SETTOPINDEX16:
2679 case LB_SETTOPINDEX:
2680 return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2682 case LB_SETCOLUMNWIDTH16:
2683 case LB_SETCOLUMNWIDTH:
2684 return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2686 case LB_GETITEMRECT16:
2688 RECT rect;
2689 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2690 CONV_RECT32TO16( &rect, MapSL(lParam) );
2692 return ret;
2694 case LB_GETITEMRECT:
2695 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2697 case LB_FINDSTRING16:
2698 wParam = (INT)(INT16)wParam;
2699 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2700 /* fall through */
2701 case LB_FINDSTRING:
2703 INT ret;
2704 LPWSTR textW;
2705 if(unicode || !HAS_STRINGS(descr))
2706 textW = (LPWSTR)lParam;
2707 else
2709 LPSTR textA = (LPSTR)lParam;
2710 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2711 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2712 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2714 ret = LISTBOX_FindString( wnd, descr, wParam, textW, FALSE );
2715 if(!unicode && HAS_STRINGS(descr))
2716 HeapFree(GetProcessHeap(), 0, textW);
2717 return ret;
2720 case LB_FINDSTRINGEXACT16:
2721 wParam = (INT)(INT16)wParam;
2722 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2723 /* fall through */
2724 case LB_FINDSTRINGEXACT:
2726 INT ret;
2727 LPWSTR textW;
2728 if(unicode || !HAS_STRINGS(descr))
2729 textW = (LPWSTR)lParam;
2730 else
2732 LPSTR textA = (LPSTR)lParam;
2733 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2734 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2735 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2737 ret = LISTBOX_FindString( wnd, descr, wParam, textW, TRUE );
2738 if(!unicode && HAS_STRINGS(descr))
2739 HeapFree(GetProcessHeap(), 0, textW);
2740 return ret;
2743 case LB_SELECTSTRING16:
2744 wParam = (INT)(INT16)wParam;
2745 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2746 /* fall through */
2747 case LB_SELECTSTRING:
2749 INT index;
2750 LPWSTR textW;
2752 if(HAS_STRINGS(descr))
2753 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2754 debugstr_a((LPSTR)lParam));
2755 if(unicode || !HAS_STRINGS(descr))
2756 textW = (LPWSTR)lParam;
2757 else
2759 LPSTR textA = (LPSTR)lParam;
2760 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2761 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2762 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2764 index = LISTBOX_FindString( wnd, descr, wParam, textW, FALSE );
2765 if(!unicode && HAS_STRINGS(descr))
2766 HeapFree(GetProcessHeap(), 0, textW);
2767 if (index != LB_ERR)
2769 LISTBOX_SetCaretIndex( wnd, descr, index, TRUE );
2770 LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2772 return index;
2775 case LB_GETSEL16:
2776 wParam = (INT)(INT16)wParam;
2777 /* fall through */
2778 case LB_GETSEL:
2779 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2780 return LB_ERR;
2781 return descr->items[wParam].selected;
2783 case LB_SETSEL16:
2784 lParam = (INT)(INT16)lParam;
2785 /* fall through */
2786 case LB_SETSEL:
2787 return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2789 case LB_SETCURSEL16:
2790 wParam = (INT)(INT16)wParam;
2791 /* fall through */
2792 case LB_SETCURSEL:
2793 if (IS_MULTISELECT(descr)) return LB_ERR;
2794 LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );
2795 return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2797 case LB_GETSELCOUNT16:
2798 case LB_GETSELCOUNT:
2799 return LISTBOX_GetSelCount( descr );
2801 case LB_GETSELITEMS16:
2802 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2804 case LB_GETSELITEMS:
2805 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2807 case LB_SELITEMRANGE16:
2808 case LB_SELITEMRANGE:
2809 if (LOWORD(lParam) <= HIWORD(lParam))
2810 return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2811 HIWORD(lParam), wParam );
2812 else
2813 return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2814 LOWORD(lParam), wParam );
2816 case LB_SELITEMRANGEEX16:
2817 case LB_SELITEMRANGEEX:
2818 if ((INT)lParam >= (INT)wParam)
2819 return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2820 else
2821 return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2823 case LB_GETHORIZONTALEXTENT16:
2824 case LB_GETHORIZONTALEXTENT:
2825 return descr->horz_extent;
2827 case LB_SETHORIZONTALEXTENT16:
2828 case LB_SETHORIZONTALEXTENT:
2829 return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2831 case LB_GETANCHORINDEX16:
2832 case LB_GETANCHORINDEX:
2833 return descr->anchor_item;
2835 case LB_SETANCHORINDEX16:
2836 wParam = (INT)(INT16)wParam;
2837 /* fall through */
2838 case LB_SETANCHORINDEX:
2839 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2840 return LB_ERR;
2841 descr->anchor_item = (INT)wParam;
2842 return LB_OKAY;
2844 case LB_DIR16:
2845 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2846 * be set automatically (this is different in Win32) */
2847 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2848 lParam = (LPARAM)MapSL(lParam);
2849 /* fall through */
2850 case LB_DIR:
2852 INT ret;
2853 LPWSTR textW;
2854 if(unicode)
2855 textW = (LPWSTR)lParam;
2856 else
2858 LPSTR textA = (LPSTR)lParam;
2859 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2860 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2861 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2863 ret = LISTBOX_Directory( wnd, descr, wParam, textW, msg == LB_DIR );
2864 if(!unicode)
2865 HeapFree(GetProcessHeap(), 0, textW);
2866 return ret;
2869 case LB_GETLOCALE:
2870 return descr->locale;
2872 case LB_SETLOCALE:
2873 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2874 return LB_OKAY;
2876 case LB_INITSTORAGE:
2877 return LISTBOX_InitStorage( wnd, descr, wParam );
2879 case LB_SETCOUNT:
2880 return LISTBOX_SetCount( wnd, descr, (INT)wParam );
2882 case LB_SETTABSTOPS16:
2883 return LISTBOX_SetTabStops( wnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2885 case LB_SETTABSTOPS:
2886 return LISTBOX_SetTabStops( wnd, descr, wParam, (LPINT)lParam, FALSE );
2888 case LB_CARETON16:
2889 case LB_CARETON:
2890 if (descr->caret_on)
2891 return LB_OKAY;
2892 descr->caret_on = TRUE;
2893 if ((descr->focus_item != -1) && (descr->in_focus))
2894 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2895 return LB_OKAY;
2897 case LB_CARETOFF16:
2898 case LB_CARETOFF:
2899 if (!descr->caret_on)
2900 return LB_OKAY;
2901 descr->caret_on = FALSE;
2902 if ((descr->focus_item != -1) && (descr->in_focus))
2903 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2904 return LB_OKAY;
2906 case WM_DESTROY:
2907 return LISTBOX_Destroy( wnd, descr );
2909 case WM_ENABLE:
2910 InvalidateRect( hwnd, NULL, TRUE );
2911 return 0;
2913 case WM_SETREDRAW:
2914 LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2915 return 0;
2917 case WM_GETDLGCODE:
2918 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2920 case WM_PAINT:
2922 PAINTSTRUCT ps;
2923 HDC hdc = ( wParam ) ? ((HDC)wParam)
2924 : BeginPaint( hwnd, &ps );
2925 ret = LISTBOX_Paint( wnd, descr, hdc );
2926 if( !wParam ) EndPaint( hwnd, &ps );
2928 return ret;
2929 case WM_SIZE:
2930 LISTBOX_UpdateSize( wnd, descr );
2931 return 0;
2932 case WM_GETFONT:
2933 return descr->font;
2934 case WM_SETFONT:
2935 LISTBOX_SetFont( wnd, descr, (HFONT)wParam );
2936 if (lParam) InvalidateRect( wnd->hwndSelf, 0, TRUE );
2937 return 0;
2938 case WM_SETFOCUS:
2939 descr->in_focus = TRUE;
2940 descr->caret_on = TRUE;
2941 if (descr->focus_item != -1)
2942 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2943 SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2944 return 0;
2945 case WM_KILLFOCUS:
2946 descr->in_focus = FALSE;
2947 if ((descr->focus_item != -1) && descr->caret_on)
2948 LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2949 SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2950 return 0;
2951 case WM_HSCROLL:
2952 return LISTBOX_HandleHScroll( wnd, descr, wParam );
2953 case WM_VSCROLL:
2954 return LISTBOX_HandleVScroll( wnd, descr, wParam );
2955 case WM_MOUSEACTIVATE:
2956 return MA_NOACTIVATE;
2957 case WM_MOUSEWHEEL:
2958 if (wParam & (MK_SHIFT | MK_CONTROL))
2959 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2960 DefWindowProcA( hwnd, msg, wParam, lParam );
2961 return LISTBOX_HandleMouseWheel( wnd, descr, wParam );
2962 case WM_LBUTTONDOWN:
2963 return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2964 (INT16)LOWORD(lParam),
2965 (INT16)HIWORD(lParam) );
2966 case WM_LBUTTONDBLCLK:
2967 if (descr->style & LBS_NOTIFY)
2968 SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2969 return 0;
2970 case WM_MOUSEMOVE:
2971 if (GetCapture() == hwnd)
2972 LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2973 (INT16)HIWORD(lParam) );
2974 return 0;
2975 case WM_LBUTTONUP:
2976 return LISTBOX_HandleLButtonUp( wnd, descr );
2977 case WM_KEYDOWN:
2978 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2979 case WM_CHAR:
2981 WCHAR charW;
2982 if(unicode)
2983 charW = (WCHAR)wParam;
2984 else
2986 CHAR charA = (CHAR)wParam;
2987 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
2989 return LISTBOX_HandleChar( wnd, descr, charW );
2991 case WM_SYSTIMER:
2992 return LISTBOX_HandleSystemTimer( wnd, descr );
2993 case WM_ERASEBKGND:
2994 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2996 RECT rect;
2997 HBRUSH hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
2998 wParam, (LPARAM)wnd->hwndSelf );
2999 TRACE("hbrush = %04x\n", hbrush);
3000 if(!hbrush)
3001 hbrush = GetSysColorBrush(COLOR_WINDOW);
3002 if(hbrush)
3004 GetClientRect(hwnd, &rect);
3005 FillRect((HDC)wParam, &rect, hbrush);
3008 return 1;
3009 case WM_DROPFILES:
3010 if( !descr->lphc )
3011 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3012 SendMessageA( descr->owner, msg, wParam, lParam );
3013 break;
3015 case WM_DROPOBJECT:
3016 case WM_QUERYDROPOBJECT:
3017 case WM_DRAGSELECT:
3018 case WM_DRAGMOVE:
3019 if( !descr->lphc )
3021 LPDRAGINFO16 dragInfo = MapSL( lParam );
3022 dragInfo->l = LISTBOX_GetItemFromPoint( descr, dragInfo->pt.x,
3023 dragInfo->pt.y );
3024 return SendMessage16( descr->owner, msg, wParam, lParam );
3026 break;
3028 default:
3029 if ((msg >= WM_USER) && (msg < 0xc000))
3030 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
3031 hwnd, msg, wParam, lParam );
3032 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3033 DefWindowProcA( hwnd, msg, wParam, lParam );
3035 return 0;
3038 /***********************************************************************
3039 * ListBoxWndProcA
3041 * This is just a wrapper for the real wndproc, it only does window locking
3042 * and unlocking.
3044 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3046 WND* wndPtr = WIN_FindWndPtr( hwnd );
3047 LRESULT res = ListBoxWndProc_locked(wndPtr, msg, wParam, lParam, FALSE);
3049 WIN_ReleaseWndPtr(wndPtr);
3050 return res;
3053 /***********************************************************************
3054 * ListBoxWndProcW
3056 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3058 WND* wndPtr = WIN_FindWndPtr( hwnd );
3059 LRESULT res = ListBoxWndProc_locked(wndPtr, msg, wParam, lParam, TRUE);
3061 WIN_ReleaseWndPtr(wndPtr);
3062 return res;
3065 /***********************************************************************
3066 * ComboLBWndProc_locked
3068 * The real combo listbox wndproc, but called with locked WND struct.
3070 static LRESULT WINAPI ComboLBWndProc_locked( WND* wnd, UINT msg,
3071 WPARAM wParam, LPARAM lParam, BOOL unicode )
3073 LRESULT lRet = 0;
3074 HWND hwnd;
3076 if (wnd)
3078 LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
3080 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
3081 wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
3083 hwnd = wnd->hwndSelf;
3085 if( descr || msg == WM_CREATE )
3087 LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
3089 switch( msg )
3091 case WM_CREATE:
3092 #define lpcs ((LPCREATESTRUCTA)lParam)
3093 TRACE_(combo)("\tpassed parent handle = 0x%08x\n",
3094 (UINT)lpcs->lpCreateParams);
3096 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3097 #undef lpcs
3098 return LISTBOX_Create( wnd, lphc );
3099 case WM_MOUSEMOVE:
3100 if ( (TWEAK_WineLook > WIN31_LOOK) &&
3101 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
3103 POINT mousePos;
3104 BOOL captured;
3105 RECT clientRect;
3107 mousePos.x = (INT16)LOWORD(lParam);
3108 mousePos.y = (INT16)HIWORD(lParam);
3111 * If we are in a dropdown combobox, we simulate that
3112 * the mouse is captured to show the tracking of the item.
3114 GetClientRect(hwnd, &clientRect);
3116 if (PtInRect( &clientRect, mousePos ))
3118 captured = descr->captured;
3119 descr->captured = TRUE;
3121 LISTBOX_HandleMouseMove( wnd, descr,
3122 mousePos.x, mousePos.y);
3124 descr->captured = captured;
3127 else
3129 LISTBOX_HandleMouseMove( wnd, descr,
3130 mousePos.x, mousePos.y);
3133 return 0;
3136 else
3139 * If we are in Win3.1 look, go with the default behavior.
3141 return unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3142 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3144 case WM_LBUTTONUP:
3145 if (TWEAK_WineLook > WIN31_LOOK)
3147 POINT mousePos;
3148 RECT clientRect;
3151 * If the mouse button "up" is not in the listbox,
3152 * we make sure there is no selection by re-selecting the
3153 * item that was selected when the listbox was made visible.
3155 mousePos.x = (INT16)LOWORD(lParam);
3156 mousePos.y = (INT16)HIWORD(lParam);
3158 GetClientRect(hwnd, &clientRect);
3161 * When the user clicks outside the combobox and the focus
3162 * is lost, the owning combobox will send a fake buttonup with
3163 * 0xFFFFFFF as the mouse location, we must also revert the
3164 * selection to the original selection.
3166 if ( (lParam == (LPARAM)-1) ||
3167 (!PtInRect( &clientRect, mousePos )) )
3169 LISTBOX_MoveCaret( wnd,
3170 descr,
3171 lphc->droppedIndex,
3172 FALSE );
3175 return LISTBOX_HandleLButtonUp( wnd, descr );
3176 case WM_LBUTTONDBLCLK:
3177 case WM_LBUTTONDOWN:
3178 return LISTBOX_HandleLButtonDownCombo(wnd, descr, msg, wParam,
3179 (INT16)LOWORD(lParam),
3180 (INT16)HIWORD(lParam) );
3181 case WM_MOUSEACTIVATE:
3182 return MA_NOACTIVATE;
3183 case WM_NCACTIVATE:
3184 return FALSE;
3185 case WM_KEYDOWN:
3186 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3188 /* for some reason(?) Windows makes it possible to
3189 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3191 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3192 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3193 && (wParam == VK_DOWN || wParam == VK_UP)) )
3195 COMBO_FlipListbox( lphc, FALSE, FALSE );
3196 return 0;
3199 return LISTBOX_HandleKeyDown( wnd, descr, wParam );
3201 case LB_SETCURSEL16:
3202 case LB_SETCURSEL:
3203 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3204 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3205 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3206 return lRet;
3207 case WM_NCDESTROY:
3208 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3209 lphc->hWndLBox = 0;
3210 /* fall through */
3212 default:
3213 return unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3214 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3217 lRet = unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3218 DefWindowProcA( hwnd, msg, wParam, lParam );
3220 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3222 return lRet;
3225 /***********************************************************************
3226 * ComboLBWndProcA
3228 * NOTE: in Windows, winproc address of the ComboLBox is the same
3229 * as that of the Listbox.
3231 * This is just a wrapper for the real wndproc, it only does window locking
3232 * and unlocking.
3234 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg,
3235 WPARAM wParam, LPARAM lParam )
3237 WND *wnd = WIN_FindWndPtr( hwnd );
3238 LRESULT res = ComboLBWndProc_locked(wnd, msg, wParam, lParam, FALSE);
3240 WIN_ReleaseWndPtr(wnd);
3241 return res;
3244 /***********************************************************************
3245 * ComboLBWndProcW
3247 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3249 WND *wnd = WIN_FindWndPtr( hwnd );
3250 LRESULT res = ComboLBWndProc_locked(wnd, msg, wParam, lParam, TRUE);
3252 WIN_ReleaseWndPtr(wnd);
3253 return res;