Draw background of the empty selected item in empty lists.
[wine/wine-kai.git] / dlls / user / listbox.c
blobe87a32eda202559b3103550dc5ae88fdaa41d7cc
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 #include <string.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include "windef.h"
26 #include "winbase.h"
27 #include "wingdi.h"
28 #include "wine/winuser16.h"
29 #include "wine/winbase16.h"
30 #include "wownt32.h"
31 #include "wine/unicode.h"
32 #include "winuser.h"
33 #include "winerror.h"
34 #include "message.h"
35 #include "user.h"
36 #include "controls.h"
37 #include "wine/debug.h"
38 #include "win.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
41 WINE_DECLARE_DEBUG_CHANNEL(combo);
43 /* Unimplemented yet:
44 * - LBS_USETABSTOPS
45 * - Locale handling
47 * Probably needs improvement:
48 * - LBS_NOSEL
51 /* Items array granularity */
52 #define LB_ARRAY_GRANULARITY 16
54 /* Scrolling timeout in ms */
55 #define LB_SCROLL_TIMEOUT 50
57 /* Listbox system timer id */
58 #define LB_TIMER_ID 2
60 /* flag listbox changed while setredraw false - internal style */
61 #define LBS_DISPLAYCHANGED 0x80000000
63 /* Item structure */
64 typedef struct
66 LPWSTR str; /* Item text */
67 BOOL selected; /* Is item selected? */
68 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
69 DWORD data; /* User data */
70 } LB_ITEMDATA;
72 /* Listbox structure */
73 typedef struct
75 HWND owner; /* Owner window to send notifications to */
76 UINT style; /* Window style */
77 INT width; /* Window width */
78 INT height; /* Window height */
79 LB_ITEMDATA *items; /* Array of items */
80 INT nb_items; /* Number of items */
81 INT top_item; /* Top visible item */
82 INT selected_item; /* Selected item */
83 INT focus_item; /* Item that has the focus */
84 INT anchor_item; /* Anchor item for extended selection */
85 INT item_height; /* Default item height */
86 INT page_size; /* Items per listbox page */
87 INT column_width; /* Column width for multi-column listboxes */
88 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
89 INT horz_pos; /* Horizontal position */
90 INT nb_tabs; /* Number of tabs in array */
91 INT *tabs; /* Array of tabs */
92 BOOL caret_on; /* Is caret on? */
93 BOOL captured; /* Is mouse captured? */
94 BOOL in_focus;
95 HFONT font; /* Current font */
96 LCID locale; /* Current locale for string comparisons */
97 LPHEADCOMBO lphc; /* ComboLBox */
98 } LB_DESCR;
101 #define IS_OWNERDRAW(descr) \
102 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
104 #define HAS_STRINGS(descr) \
105 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108 #define IS_MULTISELECT(descr) \
109 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
110 !((descr)->style & LBS_NOSEL))
112 #define SEND_NOTIFICATION(hwnd,descr,code) \
113 (SendMessageW( (descr)->owner, WM_COMMAND, \
114 MAKEWPARAM( GetWindowLongPtrW((hwnd),GWLP_ID), (code)), (LPARAM)(hwnd) ))
116 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
118 /* Current timer status */
119 typedef enum
121 LB_TIMER_NONE,
122 LB_TIMER_UP,
123 LB_TIMER_LEFT,
124 LB_TIMER_DOWN,
125 LB_TIMER_RIGHT
126 } TIMER_DIRECTION;
128 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
130 static LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
131 static LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
132 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
133 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
135 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
137 /*********************************************************************
138 * listbox class descriptor
140 const struct builtin_class_descr LISTBOX_builtin_class =
142 "ListBox", /* name */
143 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
144 ListBoxWndProcA, /* procA */
145 ListBoxWndProcW, /* procW */
146 sizeof(LB_DESCR *), /* extra */
147 IDC_ARROW, /* cursor */
148 0 /* brush */
152 /*********************************************************************
153 * combolbox class descriptor
155 const struct builtin_class_descr COMBOLBOX_builtin_class =
157 "ComboLBox", /* name */
158 CS_DBLCLKS | CS_SAVEBITS, /* style */
159 ComboLBWndProcA, /* procA */
160 ComboLBWndProcW, /* procW */
161 sizeof(LB_DESCR *), /* extra */
162 IDC_ARROW, /* cursor */
163 0 /* brush */
167 /* check whether app is a Win 3.1 app */
168 inline static BOOL is_old_app( HWND hwnd )
170 return (GetExpWinVer16( GetWindowLongPtrW(hwnd,GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
174 /***********************************************************************
175 * LISTBOX_Dump
177 void LISTBOX_Dump( HWND hwnd )
179 INT i;
180 LB_ITEMDATA *item;
181 LB_DESCR *descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 );
183 TRACE( "Listbox:\n" );
184 TRACE( "hwnd=%p descr=%08x items=%d top=%d\n",
185 hwnd, (UINT)descr, descr->nb_items, descr->top_item );
186 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
188 TRACE( "%4d: %-40s %d %08lx %3d\n",
189 i, debugstr_w(item->str), item->selected, item->data, item->height );
194 /***********************************************************************
195 * LISTBOX_GetCurrentPageSize
197 * Return the current page size
199 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
201 INT i, height;
202 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
203 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
205 if ((height += descr->items[i].height) > descr->height) break;
207 if (i == descr->top_item) return 1;
208 else return i - descr->top_item;
212 /***********************************************************************
213 * LISTBOX_GetMaxTopIndex
215 * Return the maximum possible index for the top of the listbox.
217 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
219 INT max, page;
221 if (descr->style & LBS_OWNERDRAWVARIABLE)
223 page = descr->height;
224 for (max = descr->nb_items - 1; max >= 0; max--)
225 if ((page -= descr->items[max].height) < 0) break;
226 if (max < descr->nb_items - 1) max++;
228 else if (descr->style & LBS_MULTICOLUMN)
230 if ((page = descr->width / descr->column_width) < 1) page = 1;
231 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
232 max = (max - page) * descr->page_size;
234 else
236 max = descr->nb_items - descr->page_size;
238 if (max < 0) max = 0;
239 return max;
243 /***********************************************************************
244 * LISTBOX_UpdateScroll
246 * Update the scrollbars. Should be called whenever the content
247 * of the listbox changes.
249 static void LISTBOX_UpdateScroll( HWND hwnd, LB_DESCR *descr )
251 SCROLLINFO info;
253 /* Check the listbox scroll bar flags individually before we call
254 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
255 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
256 scroll bar when we do not need one.
257 if (!(descr->style & WS_VSCROLL)) return;
260 /* It is important that we check descr->style, and not wnd->dwStyle,
261 for WS_VSCROLL, as the former is exactly the one passed in
262 argument to CreateWindow.
263 In Windows (and from now on in Wine :) a listbox created
264 with such a style (no WS_SCROLL) does not update
265 the scrollbar with listbox-related data, thus letting
266 the programmer use it for his/her own purposes. */
268 if (descr->style & LBS_NOREDRAW) return;
269 info.cbSize = sizeof(info);
271 if (descr->style & LBS_MULTICOLUMN)
273 info.nMin = 0;
274 info.nMax = (descr->nb_items - 1) / descr->page_size;
275 info.nPos = descr->top_item / descr->page_size;
276 info.nPage = descr->width / descr->column_width;
277 if (info.nPage < 1) info.nPage = 1;
278 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
279 if (descr->style & LBS_DISABLENOSCROLL)
280 info.fMask |= SIF_DISABLENOSCROLL;
281 if (descr->style & WS_HSCROLL)
282 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
283 info.nMax = 0;
284 info.fMask = SIF_RANGE;
285 if (descr->style & WS_VSCROLL)
286 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
288 else
290 info.nMin = 0;
291 info.nMax = descr->nb_items - 1;
292 info.nPos = descr->top_item;
293 info.nPage = LISTBOX_GetCurrentPageSize( descr );
294 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
295 if (descr->style & LBS_DISABLENOSCROLL)
296 info.fMask |= SIF_DISABLENOSCROLL;
297 if (descr->style & WS_VSCROLL)
298 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
300 if (descr->horz_extent)
302 info.nMin = 0;
303 info.nMax = descr->horz_extent - 1;
304 info.nPos = descr->horz_pos;
305 info.nPage = descr->width;
306 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
307 if (descr->style & LBS_DISABLENOSCROLL)
308 info.fMask |= SIF_DISABLENOSCROLL;
309 if (descr->style & WS_HSCROLL)
310 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
316 /***********************************************************************
317 * LISTBOX_SetTopItem
319 * Set the top item of the listbox, scrolling up or down if necessary.
321 static LRESULT LISTBOX_SetTopItem( HWND hwnd, LB_DESCR *descr, INT index,
322 BOOL scroll )
324 INT max = LISTBOX_GetMaxTopIndex( descr );
325 if (index > max) index = max;
326 if (index < 0) index = 0;
327 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
328 if (descr->top_item == index) return LB_OKAY;
329 if (descr->style & LBS_MULTICOLUMN)
331 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
332 if (scroll && (abs(diff) < descr->width))
333 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
334 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
336 else
337 scroll = FALSE;
339 else if (scroll)
341 INT diff;
342 if (descr->style & LBS_OWNERDRAWVARIABLE)
344 INT i;
345 diff = 0;
346 if (index > descr->top_item)
348 for (i = index - 1; i >= descr->top_item; i--)
349 diff -= descr->items[i].height;
351 else
353 for (i = index; i < descr->top_item; i++)
354 diff += descr->items[i].height;
357 else
358 diff = (descr->top_item - index) * descr->item_height;
360 if (abs(diff) < descr->height)
361 ScrollWindowEx( hwnd, 0, diff, NULL, NULL, 0, NULL,
362 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
363 else
364 scroll = FALSE;
366 if (!scroll) InvalidateRect( hwnd, NULL, TRUE );
367 descr->top_item = index;
368 LISTBOX_UpdateScroll( hwnd, descr );
369 return LB_OKAY;
373 /***********************************************************************
374 * LISTBOX_UpdatePage
376 * Update the page size. Should be called when the size of
377 * the client area or the item height changes.
379 static void LISTBOX_UpdatePage( HWND hwnd, LB_DESCR *descr )
381 INT page_size;
383 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
384 page_size = 1;
385 if (page_size == descr->page_size) return;
386 descr->page_size = page_size;
387 if (descr->style & LBS_MULTICOLUMN)
388 InvalidateRect( hwnd, NULL, TRUE );
389 LISTBOX_SetTopItem( hwnd, descr, descr->top_item, FALSE );
393 /***********************************************************************
394 * LISTBOX_UpdateSize
396 * Update the size of the listbox. Should be called when the size of
397 * the client area changes.
399 static void LISTBOX_UpdateSize( HWND hwnd, LB_DESCR *descr )
401 RECT rect;
403 GetClientRect( hwnd, &rect );
404 descr->width = rect.right - rect.left;
405 descr->height = rect.bottom - rect.top;
406 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
408 INT remaining;
409 RECT rect;
411 GetWindowRect( hwnd, &rect );
412 if(descr->item_height != 0)
413 remaining = descr->height % descr->item_height;
414 else
415 remaining = 0;
416 if ((descr->height > descr->item_height) && remaining)
418 if (is_old_app(hwnd))
419 { /* give a margin for error to 16 bits programs - if we need
420 less than the height of the nonclient area, round to the
421 *next* number of items */
422 int ncheight = rect.bottom - rect.top - descr->height;
423 if ((descr->item_height - remaining) <= ncheight)
424 remaining = remaining - descr->item_height;
426 TRACE("[%p]: changing height %d -> %d\n",
427 hwnd, descr->height, descr->height - remaining );
428 SetWindowPos( hwnd, 0, 0, 0, rect.right - rect.left,
429 rect.bottom - rect.top - remaining,
430 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
431 return;
434 TRACE("[%p]: new size = %d,%d\n", hwnd, descr->width, descr->height );
435 LISTBOX_UpdatePage( hwnd, descr );
436 LISTBOX_UpdateScroll( hwnd, descr );
438 /* Invalidate the focused item so it will be repainted correctly */
439 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
441 InvalidateRect( hwnd, &rect, FALSE );
446 /***********************************************************************
447 * LISTBOX_GetItemRect
449 * Get the rectangle enclosing an item, in listbox client coordinates.
450 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
452 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
454 /* Index <= 0 is legal even on empty listboxes */
455 if (index && (index >= descr->nb_items)) return -1;
456 SetRect( rect, 0, 0, descr->width, descr->height );
457 if (descr->style & LBS_MULTICOLUMN)
459 INT col = (index / descr->page_size) -
460 (descr->top_item / descr->page_size);
461 rect->left += col * descr->column_width;
462 rect->right = rect->left + descr->column_width;
463 rect->top += (index % descr->page_size) * descr->item_height;
464 rect->bottom = rect->top + descr->item_height;
466 else if (descr->style & LBS_OWNERDRAWVARIABLE)
468 INT i;
469 rect->right += descr->horz_pos;
470 if ((index >= 0) && (index < descr->nb_items))
472 if (index < descr->top_item)
474 for (i = descr->top_item-1; i >= index; i--)
475 rect->top -= descr->items[i].height;
477 else
479 for (i = descr->top_item; i < index; i++)
480 rect->top += descr->items[i].height;
482 rect->bottom = rect->top + descr->items[index].height;
486 else
488 rect->top += (index - descr->top_item) * descr->item_height;
489 rect->bottom = rect->top + descr->item_height;
490 rect->right += descr->horz_pos;
493 return ((rect->left < descr->width) && (rect->right > 0) &&
494 (rect->top < descr->height) && (rect->bottom > 0));
498 /***********************************************************************
499 * LISTBOX_GetItemFromPoint
501 * Return the item nearest from point (x,y) (in client coordinates).
503 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
505 INT index = descr->top_item;
507 if (!descr->nb_items) return -1; /* No items */
508 if (descr->style & LBS_OWNERDRAWVARIABLE)
510 INT pos = 0;
511 if (y >= 0)
513 while (index < descr->nb_items)
515 if ((pos += descr->items[index].height) > y) break;
516 index++;
519 else
521 while (index > 0)
523 index--;
524 if ((pos -= descr->items[index].height) <= y) break;
528 else if (descr->style & LBS_MULTICOLUMN)
530 if (y >= descr->item_height * descr->page_size) return -1;
531 if (y >= 0) index += y / descr->item_height;
532 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
533 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
535 else
537 index += (y / descr->item_height);
539 if (index < 0) return 0;
540 if (index >= descr->nb_items) return -1;
541 return index;
545 /***********************************************************************
546 * LISTBOX_PaintItem
548 * Paint an item.
550 static void LISTBOX_PaintItem( HWND hwnd, LB_DESCR *descr, HDC hdc,
551 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
553 LB_ITEMDATA *item = NULL;
554 if (index < descr->nb_items) item = &descr->items[index];
556 if (IS_OWNERDRAW(descr))
558 DRAWITEMSTRUCT dis;
559 RECT r;
560 HRGN hrgn;
561 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
563 if (!item)
565 if (action == ODA_FOCUS)
566 DrawFocusRect( hdc, rect );
567 else
568 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
569 return;
572 /* some programs mess with the clipping region when
573 drawing the item, *and* restore the previous region
574 after they are done, so a region has better to exist
575 else everything ends clipped */
576 GetClientRect(hwnd, &r);
577 hrgn = CreateRectRgnIndirect(&r);
578 SelectClipRgn( hdc, hrgn);
579 DeleteObject( hrgn );
581 dis.CtlType = ODT_LISTBOX;
582 dis.CtlID = id;
583 dis.hwndItem = hwnd;
584 dis.itemAction = action;
585 dis.hDC = hdc;
586 dis.itemID = index;
587 dis.itemState = 0;
588 if (item && item->selected) dis.itemState |= ODS_SELECTED;
589 if (!ignoreFocus && (descr->focus_item == index) &&
590 (descr->caret_on) &&
591 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
592 if (!IsWindowEnabled(hwnd)) dis.itemState |= ODS_DISABLED;
593 dis.itemData = item ? item->data : 0;
594 dis.rcItem = *rect;
595 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
596 hwnd, index, item ? debugstr_w(item->str) : "", action,
597 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
598 SendMessageW(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
600 else
602 COLORREF oldText = 0, oldBk = 0;
604 if (action == ODA_FOCUS)
606 DrawFocusRect( hdc, rect );
607 return;
609 if (item && item->selected)
611 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
612 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
615 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
616 hwnd, index, item ? debugstr_w(item->str) : "", action,
617 rect->left, rect->top, rect->right, rect->bottom );
618 if (!item)
619 ExtTextOutW( hdc, rect->left + 1, rect->top,
620 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
621 else if (!(descr->style & LBS_USETABSTOPS))
622 ExtTextOutW( hdc, rect->left + 1, rect->top,
623 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
624 strlenW(item->str), NULL );
625 else
627 /* Output empty string to paint background in the full width. */
628 ExtTextOutW( hdc, rect->left + 1, rect->top,
629 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
630 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
631 item->str, strlenW(item->str),
632 descr->nb_tabs, descr->tabs, 0);
634 if (item && item->selected)
636 SetBkColor( hdc, oldBk );
637 SetTextColor( hdc, oldText );
639 if (!ignoreFocus && (descr->focus_item == index) &&
640 (descr->caret_on) &&
641 (descr->in_focus)) DrawFocusRect( hdc, rect );
646 /***********************************************************************
647 * LISTBOX_SetRedraw
649 * Change the redraw flag.
651 static void LISTBOX_SetRedraw( HWND hwnd, LB_DESCR *descr, BOOL on )
653 if (on)
655 if (!(descr->style & LBS_NOREDRAW)) return;
656 descr->style &= ~LBS_NOREDRAW;
657 if (descr->style & LBS_DISPLAYCHANGED)
658 { /* page was changed while setredraw false, refresh automatically */
659 InvalidateRect(hwnd, NULL, TRUE);
660 if ((descr->top_item + descr->page_size) > descr->nb_items)
661 { /* reset top of page if less than number of items/page */
662 descr->top_item = descr->nb_items - descr->page_size;
663 if (descr->top_item < 0) descr->top_item = 0;
665 descr->style &= ~LBS_DISPLAYCHANGED;
667 LISTBOX_UpdateScroll( hwnd, descr );
669 else descr->style |= LBS_NOREDRAW;
673 /***********************************************************************
674 * LISTBOX_RepaintItem
676 * Repaint a single item synchronously.
678 static void LISTBOX_RepaintItem( HWND hwnd, LB_DESCR *descr, INT index,
679 UINT action )
681 HDC hdc;
682 RECT rect;
683 HFONT oldFont = 0;
684 HBRUSH hbrush, oldBrush = 0;
686 /* Do not repaint the item if the item is not visible */
687 if (!IsWindowVisible(hwnd)) return;
688 if (descr->style & LBS_NOREDRAW)
690 descr->style |= LBS_DISPLAYCHANGED;
691 return;
693 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
694 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE ))) return;
695 if (descr->font) oldFont = SelectObject( hdc, descr->font );
696 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
697 (WPARAM)hdc, (LPARAM)hwnd );
698 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
699 if (!IsWindowEnabled(hwnd))
700 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
701 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
702 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, index, action, FALSE );
703 if (oldFont) SelectObject( hdc, oldFont );
704 if (oldBrush) SelectObject( hdc, oldBrush );
705 ReleaseDC( hwnd, hdc );
709 /***********************************************************************
710 * LISTBOX_InitStorage
712 static LRESULT LISTBOX_InitStorage( HWND hwnd, LB_DESCR *descr, INT nb_items )
714 LB_ITEMDATA *item;
716 nb_items += LB_ARRAY_GRANULARITY - 1;
717 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
718 if (descr->items) {
719 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
720 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
721 nb_items * sizeof(LB_ITEMDATA));
723 else {
724 item = HeapAlloc( GetProcessHeap(), 0,
725 nb_items * sizeof(LB_ITEMDATA));
728 if (!item)
730 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
731 return LB_ERRSPACE;
733 descr->items = item;
734 return LB_OKAY;
738 /***********************************************************************
739 * LISTBOX_SetTabStops
741 static BOOL LISTBOX_SetTabStops( HWND hwnd, LB_DESCR *descr, INT count,
742 LPINT tabs, BOOL short_ints )
744 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
745 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
746 if (!(descr->nb_tabs = count))
748 descr->tabs = NULL;
749 return TRUE;
751 /* FIXME: count = 1 */
752 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
753 descr->nb_tabs * sizeof(INT) )))
754 return FALSE;
755 if (short_ints)
757 INT i;
758 LPINT16 p = (LPINT16)tabs;
760 TRACE("[%p]: settabstops ", hwnd );
761 for (i = 0; i < descr->nb_tabs; i++) {
762 descr->tabs[i] = *p++<<1; /* FIXME */
763 if (TRACE_ON(listbox)) TRACE("%hd ", descr->tabs[i]);
765 if (TRACE_ON(listbox)) TRACE("\n");
767 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
768 /* FIXME: repaint the window? */
769 return TRUE;
773 /***********************************************************************
774 * LISTBOX_GetText
776 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
778 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
779 if (HAS_STRINGS(descr))
781 if (!lParam)
783 DWORD len = strlenW(descr->items[index].str);
784 if( unicode )
785 return len;
786 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
787 NULL, 0, NULL, NULL );
790 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
792 if(unicode)
794 LPWSTR buffer = (LPWSTR)lParam;
795 strcpyW( buffer, descr->items[index].str );
796 return strlenW(buffer);
798 else
800 LPSTR buffer = (LPSTR)lParam;
801 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
803 } else {
804 if (lParam)
805 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
806 return sizeof(DWORD);
811 /***********************************************************************
812 * LISTBOX_FindStringPos
814 * Find the nearest string located before a given string in sort order.
815 * If 'exact' is TRUE, return an error if we don't get an exact match.
817 static INT LISTBOX_FindStringPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str,
818 BOOL exact )
820 INT index, min, max, res = -1;
822 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
823 min = 0;
824 max = descr->nb_items;
825 while (min != max)
827 index = (min + max) / 2;
828 if (HAS_STRINGS(descr))
829 res = lstrcmpiW( str, descr->items[index].str);
830 else
832 COMPAREITEMSTRUCT cis;
833 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
835 cis.CtlType = ODT_LISTBOX;
836 cis.CtlID = id;
837 cis.hwndItem = hwnd;
838 /* note that some application (MetaStock) expects the second item
839 * to be in the listbox */
840 cis.itemID1 = -1;
841 cis.itemData1 = (DWORD)str;
842 cis.itemID2 = index;
843 cis.itemData2 = descr->items[index].data;
844 cis.dwLocaleId = descr->locale;
845 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
847 if (!res) return index;
848 if (res < 0) max = index;
849 else min = index + 1;
851 return exact ? -1 : max;
855 /***********************************************************************
856 * LISTBOX_FindFileStrPos
858 * Find the nearest string located before a given string in directory
859 * sort order (i.e. first files, then directories, then drives).
861 static INT LISTBOX_FindFileStrPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str )
863 INT min, max, res = -1;
865 if (!HAS_STRINGS(descr))
866 return LISTBOX_FindStringPos( hwnd, descr, str, FALSE );
867 min = 0;
868 max = descr->nb_items;
869 while (min != max)
871 INT index = (min + max) / 2;
872 LPCWSTR p = descr->items[index].str;
873 if (*p == '[') /* drive or directory */
875 if (*str != '[') res = -1;
876 else if (p[1] == '-') /* drive */
878 if (str[1] == '-') res = str[2] - p[2];
879 else res = -1;
881 else /* directory */
883 if (str[1] == '-') res = 1;
884 else res = lstrcmpiW( str, p );
887 else /* filename */
889 if (*str == '[') res = 1;
890 else res = lstrcmpiW( str, p );
892 if (!res) return index;
893 if (res < 0) max = index;
894 else min = index + 1;
896 return max;
900 /***********************************************************************
901 * LISTBOX_FindString
903 * Find the item beginning with a given string.
905 static INT LISTBOX_FindString( HWND hwnd, LB_DESCR *descr, INT start,
906 LPCWSTR str, BOOL exact )
908 INT i;
909 LB_ITEMDATA *item;
911 if (start >= descr->nb_items) start = -1;
912 item = descr->items + start + 1;
913 if (HAS_STRINGS(descr))
915 if (!str || ! str[0] ) return LB_ERR;
916 if (exact)
918 for (i = start + 1; i < descr->nb_items; i++, item++)
919 if (!lstrcmpiW( str, item->str )) return i;
920 for (i = 0, item = descr->items; i <= start; i++, item++)
921 if (!lstrcmpiW( str, item->str )) return i;
923 else
925 /* Special case for drives and directories: ignore prefix */
926 #define CHECK_DRIVE(item) \
927 if ((item)->str[0] == '[') \
929 if (!strncmpiW( str, (item)->str+1, len )) return i; \
930 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
931 return i; \
934 INT len = strlenW(str);
935 for (i = start + 1; i < descr->nb_items; i++, item++)
937 if (!strncmpiW( str, item->str, len )) return i;
938 CHECK_DRIVE(item);
940 for (i = 0, item = descr->items; i <= start; i++, item++)
942 if (!strncmpiW( str, item->str, len )) return i;
943 CHECK_DRIVE(item);
945 #undef CHECK_DRIVE
948 else
950 if (exact && (descr->style & LBS_SORT))
951 /* If sorted, use a WM_COMPAREITEM binary search */
952 return LISTBOX_FindStringPos( hwnd, descr, str, TRUE );
954 /* Otherwise use a linear search */
955 for (i = start + 1; i < descr->nb_items; i++, item++)
956 if (item->data == (DWORD)str) return i;
957 for (i = 0, item = descr->items; i <= start; i++, item++)
958 if (item->data == (DWORD)str) return i;
960 return LB_ERR;
964 /***********************************************************************
965 * LISTBOX_GetSelCount
967 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
969 INT i, count;
970 LB_ITEMDATA *item = descr->items;
972 if (!(descr->style & LBS_MULTIPLESEL) ||
973 (descr->style & LBS_NOSEL))
974 return LB_ERR;
975 for (i = count = 0; i < descr->nb_items; i++, item++)
976 if (item->selected) count++;
977 return count;
981 /***********************************************************************
982 * LISTBOX_GetSelItems16
984 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
986 INT i, count;
987 LB_ITEMDATA *item = descr->items;
989 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
990 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
991 if (item->selected) array[count++] = (INT16)i;
992 return count;
996 /***********************************************************************
997 * LISTBOX_GetSelItems
999 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
1001 INT i, count;
1002 LB_ITEMDATA *item = descr->items;
1004 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1005 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1006 if (item->selected) array[count++] = i;
1007 return count;
1011 /***********************************************************************
1012 * LISTBOX_Paint
1014 static LRESULT LISTBOX_Paint( HWND hwnd, LB_DESCR *descr, HDC hdc )
1016 INT i, col_pos = descr->page_size - 1;
1017 RECT rect;
1018 RECT focusRect = {-1, -1, -1, -1};
1019 HFONT oldFont = 0;
1020 HBRUSH hbrush, oldBrush = 0;
1022 if (descr->style & LBS_NOREDRAW) return 0;
1024 SetRect( &rect, 0, 0, descr->width, descr->height );
1025 if (descr->style & LBS_MULTICOLUMN)
1026 rect.right = rect.left + descr->column_width;
1027 else if (descr->horz_pos)
1029 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1030 rect.right += descr->horz_pos;
1033 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1034 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1035 (WPARAM)hdc, (LPARAM)hwnd );
1036 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1037 if (!IsWindowEnabled(hwnd)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1039 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1040 (descr->in_focus))
1042 /* Special case for empty listbox: paint focus rect */
1043 rect.bottom = rect.top + descr->item_height;
1044 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1045 &rect, NULL, 0, NULL );
1046 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, descr->focus_item,
1047 ODA_FOCUS, FALSE );
1048 rect.top = rect.bottom;
1051 /* Paint all the item, regarding the selection
1052 Focus state will be painted after */
1054 for (i = descr->top_item; i < descr->nb_items; i++)
1056 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1057 rect.bottom = rect.top + descr->item_height;
1058 else
1059 rect.bottom = rect.top + descr->items[i].height;
1061 if (i == descr->focus_item)
1063 /* keep the focus rect, to paint the focus item after */
1064 focusRect.left = rect.left;
1065 focusRect.right = rect.right;
1066 focusRect.top = rect.top;
1067 focusRect.bottom = rect.bottom;
1069 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1070 rect.top = rect.bottom;
1072 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1074 if (!IS_OWNERDRAW(descr))
1076 /* Clear the bottom of the column */
1077 if (rect.top < descr->height)
1079 rect.bottom = descr->height;
1080 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1081 &rect, NULL, 0, NULL );
1085 /* Go to the next column */
1086 rect.left += descr->column_width;
1087 rect.right += descr->column_width;
1088 rect.top = 0;
1089 col_pos = descr->page_size - 1;
1091 else
1093 col_pos--;
1094 if (rect.top >= descr->height) break;
1098 /* Paint the focus item now */
1099 if (focusRect.top != focusRect.bottom &&
1100 descr->caret_on && descr->in_focus)
1101 LISTBOX_PaintItem( hwnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1103 if (!IS_OWNERDRAW(descr))
1105 /* Clear the remainder of the client area */
1106 if (rect.top < descr->height)
1108 rect.bottom = descr->height;
1109 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1110 &rect, NULL, 0, NULL );
1112 if (rect.right < descr->width)
1114 rect.left = rect.right;
1115 rect.right = descr->width;
1116 rect.top = 0;
1117 rect.bottom = descr->height;
1118 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1119 &rect, NULL, 0, NULL );
1122 if (oldFont) SelectObject( hdc, oldFont );
1123 if (oldBrush) SelectObject( hdc, oldBrush );
1124 return 0;
1128 /***********************************************************************
1129 * LISTBOX_InvalidateItems
1131 * Invalidate all items from a given item. If the specified item is not
1132 * visible, nothing happens.
1134 static void LISTBOX_InvalidateItems( HWND hwnd, LB_DESCR *descr, INT index )
1136 RECT rect;
1138 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1140 if (descr->style & LBS_NOREDRAW)
1142 descr->style |= LBS_DISPLAYCHANGED;
1143 return;
1145 rect.bottom = descr->height;
1146 InvalidateRect( hwnd, &rect, TRUE );
1147 if (descr->style & LBS_MULTICOLUMN)
1149 /* Repaint the other columns */
1150 rect.left = rect.right;
1151 rect.right = descr->width;
1152 rect.top = 0;
1153 InvalidateRect( hwnd, &rect, TRUE );
1158 static void LISTBOX_InvalidateItemRect( HWND hwnd, LB_DESCR *descr, INT index )
1160 RECT rect;
1162 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1163 InvalidateRect( hwnd, &rect, TRUE );
1166 /***********************************************************************
1167 * LISTBOX_GetItemHeight
1169 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1171 if (descr->style & LBS_OWNERDRAWVARIABLE)
1173 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1174 return descr->items[index].height;
1176 else return descr->item_height;
1180 /***********************************************************************
1181 * LISTBOX_SetItemHeight
1183 static LRESULT LISTBOX_SetItemHeight( HWND hwnd, LB_DESCR *descr, INT index,
1184 INT height, BOOL repaint )
1186 if (!height) height = 1;
1188 if (descr->style & LBS_OWNERDRAWVARIABLE)
1190 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1191 TRACE("[%p]: item %d height = %d\n", hwnd, index, height );
1192 descr->items[index].height = height;
1193 LISTBOX_UpdateScroll( hwnd, descr );
1194 if (repaint)
1195 LISTBOX_InvalidateItems( hwnd, descr, index );
1197 else if (height != descr->item_height)
1199 TRACE("[%p]: new height = %d\n", hwnd, height );
1200 descr->item_height = height;
1201 LISTBOX_UpdatePage( hwnd, descr );
1202 LISTBOX_UpdateScroll( hwnd, descr );
1203 if (repaint)
1204 InvalidateRect( hwnd, 0, TRUE );
1206 return LB_OKAY;
1210 /***********************************************************************
1211 * LISTBOX_SetHorizontalPos
1213 static void LISTBOX_SetHorizontalPos( HWND hwnd, LB_DESCR *descr, INT pos )
1215 INT diff;
1217 if (pos > descr->horz_extent - descr->width)
1218 pos = descr->horz_extent - descr->width;
1219 if (pos < 0) pos = 0;
1220 if (!(diff = descr->horz_pos - pos)) return;
1221 TRACE("[%p]: new horz pos = %d\n", hwnd, pos );
1222 descr->horz_pos = pos;
1223 LISTBOX_UpdateScroll( hwnd, descr );
1224 if (abs(diff) < descr->width)
1226 RECT rect;
1227 /* Invalidate the focused item so it will be repainted correctly */
1228 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1229 InvalidateRect( hwnd, &rect, TRUE );
1230 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
1231 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1233 else
1234 InvalidateRect( hwnd, NULL, TRUE );
1238 /***********************************************************************
1239 * LISTBOX_SetHorizontalExtent
1241 static LRESULT LISTBOX_SetHorizontalExtent( HWND hwnd, LB_DESCR *descr,
1242 INT extent )
1244 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1245 return LB_OKAY;
1246 if (extent <= 0) extent = 1;
1247 if (extent == descr->horz_extent) return LB_OKAY;
1248 TRACE("[%p]: new horz extent = %d\n", hwnd, extent );
1249 descr->horz_extent = extent;
1250 if (descr->horz_pos > extent - descr->width)
1251 LISTBOX_SetHorizontalPos( hwnd, descr, extent - descr->width );
1252 else
1253 LISTBOX_UpdateScroll( hwnd, descr );
1254 return LB_OKAY;
1258 /***********************************************************************
1259 * LISTBOX_SetColumnWidth
1261 static LRESULT LISTBOX_SetColumnWidth( HWND hwnd, LB_DESCR *descr, INT width)
1263 if (width == descr->column_width) return LB_OKAY;
1264 TRACE("[%p]: new column width = %d\n", hwnd, width );
1265 descr->column_width = width;
1266 LISTBOX_UpdatePage( hwnd, descr );
1267 return LB_OKAY;
1271 /***********************************************************************
1272 * LISTBOX_SetFont
1274 * Returns the item height.
1276 static INT LISTBOX_SetFont( HWND hwnd, LB_DESCR *descr, HFONT font )
1278 HDC hdc;
1279 HFONT oldFont = 0;
1280 TEXTMETRICW tm;
1282 descr->font = font;
1284 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE )))
1286 ERR("unable to get DC.\n" );
1287 return 16;
1289 if (font) oldFont = SelectObject( hdc, font );
1290 GetTextMetricsW( hdc, &tm );
1291 if (oldFont) SelectObject( hdc, oldFont );
1292 ReleaseDC( hwnd, hdc );
1293 if (!IS_OWNERDRAW(descr))
1294 LISTBOX_SetItemHeight( hwnd, descr, 0, tm.tmHeight, FALSE );
1295 return tm.tmHeight ;
1299 /***********************************************************************
1300 * LISTBOX_MakeItemVisible
1302 * Make sure that a given item is partially or fully visible.
1304 static void LISTBOX_MakeItemVisible( HWND hwnd, LB_DESCR *descr, INT index,
1305 BOOL fully )
1307 INT top;
1309 if (index <= descr->top_item) top = index;
1310 else if (descr->style & LBS_MULTICOLUMN)
1312 INT cols = descr->width;
1313 if (!fully) cols += descr->column_width - 1;
1314 if (cols >= descr->column_width) cols /= descr->column_width;
1315 else cols = 1;
1316 if (index < descr->top_item + (descr->page_size * cols)) return;
1317 top = index - descr->page_size * (cols - 1);
1319 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1321 INT height = fully ? descr->items[index].height : 1;
1322 for (top = index; top > descr->top_item; top--)
1323 if ((height += descr->items[top-1].height) > descr->height) break;
1325 else
1327 if (index < descr->top_item + descr->page_size) return;
1328 if (!fully && (index == descr->top_item + descr->page_size) &&
1329 (descr->height > (descr->page_size * descr->item_height))) return;
1330 top = index - descr->page_size + 1;
1332 LISTBOX_SetTopItem( hwnd, descr, top, TRUE );
1335 /***********************************************************************
1336 * LISTBOX_SetCaretIndex
1338 * NOTES
1339 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1342 static LRESULT LISTBOX_SetCaretIndex( HWND hwnd, LB_DESCR *descr, INT index,
1343 BOOL fully_visible )
1345 INT oldfocus = descr->focus_item;
1347 if (descr->style & LBS_NOSEL) return LB_ERR;
1348 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1349 if (index == oldfocus) return LB_OKAY;
1350 descr->focus_item = index;
1351 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1352 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1354 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1355 if (descr->caret_on && (descr->in_focus))
1356 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1358 return LB_OKAY;
1362 /***********************************************************************
1363 * LISTBOX_SelectItemRange
1365 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1367 static LRESULT LISTBOX_SelectItemRange( HWND hwnd, LB_DESCR *descr, INT first,
1368 INT last, BOOL on )
1370 INT i;
1372 /* A few sanity checks */
1374 if (descr->style & LBS_NOSEL) return LB_ERR;
1375 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1376 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1377 if (last == -1) last = descr->nb_items - 1;
1378 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1379 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1380 /* selected_item reflects last selected/unselected item on multiple sel */
1381 descr->selected_item = last;
1383 if (on) /* Turn selection on */
1385 for (i = first; i <= last; i++)
1387 if (descr->items[i].selected) continue;
1388 descr->items[i].selected = TRUE;
1389 LISTBOX_InvalidateItemRect(hwnd, descr, i);
1391 LISTBOX_SetCaretIndex( hwnd, descr, last, TRUE );
1393 else /* Turn selection off */
1395 for (i = first; i <= last; i++)
1397 if (!descr->items[i].selected) continue;
1398 descr->items[i].selected = FALSE;
1399 LISTBOX_InvalidateItemRect(hwnd, descr, i);
1402 return LB_OKAY;
1405 /***********************************************************************
1406 * LISTBOX_SetSelection
1408 static LRESULT LISTBOX_SetSelection( HWND hwnd, LB_DESCR *descr, INT index,
1409 BOOL on, BOOL send_notify )
1411 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1413 if (descr->style & LBS_NOSEL)
1415 descr->selected_item = index;
1416 return LB_ERR;
1418 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1419 if (descr->style & LBS_MULTIPLESEL)
1421 if (index == -1) /* Select all items */
1422 return LISTBOX_SelectItemRange( hwnd, descr, 0, -1, on );
1423 else /* Only one item */
1424 return LISTBOX_SelectItemRange( hwnd, descr, index, index, on );
1426 else
1428 INT oldsel = descr->selected_item;
1429 if (index == oldsel) return LB_OKAY;
1430 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1431 if (index != -1) descr->items[index].selected = TRUE;
1432 descr->selected_item = index;
1433 if (oldsel != -1) LISTBOX_RepaintItem( hwnd, descr, oldsel, ODA_SELECT );
1434 if (index != -1) LISTBOX_RepaintItem( hwnd, descr, index, ODA_SELECT );
1435 if (send_notify && descr->nb_items) SEND_NOTIFICATION( hwnd, descr,
1436 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1437 else
1438 if( descr->lphc ) /* set selection change flag for parent combo */
1439 descr->lphc->wState |= CBF_SELCHANGE;
1441 return LB_OKAY;
1445 /***********************************************************************
1446 * LISTBOX_MoveCaret
1448 * Change the caret position and extend the selection to the new caret.
1450 static void LISTBOX_MoveCaret( HWND hwnd, LB_DESCR *descr, INT index,
1451 BOOL fully_visible )
1453 INT oldfocus = descr->focus_item;
1455 if ((index < 0) || (index >= descr->nb_items))
1456 return;
1458 /* Important, repaint needs to be done in this order if
1459 you want to mimic Windows behavior:
1460 1. Remove the focus and paint the item
1461 2. Remove the selection and paint the item(s)
1462 3. Set the selection and repaint the item(s)
1463 4. Set the focus to 'index' and repaint the item */
1465 /* 1. remove the focus and repaint the item */
1466 descr->focus_item = -1;
1467 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1468 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1470 /* 2. then turn off the previous selection */
1471 /* 3. repaint the new selected item */
1472 if (descr->style & LBS_EXTENDEDSEL)
1474 if (descr->anchor_item != -1)
1476 INT first = min( index, descr->anchor_item );
1477 INT last = max( index, descr->anchor_item );
1478 if (first > 0)
1479 LISTBOX_SelectItemRange( hwnd, descr, 0, first - 1, FALSE );
1480 LISTBOX_SelectItemRange( hwnd, descr, last + 1, -1, FALSE );
1481 LISTBOX_SelectItemRange( hwnd, descr, first, last, TRUE );
1484 else if (!(descr->style & LBS_MULTIPLESEL))
1486 /* Set selection to new caret item */
1487 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
1490 /* 4. repaint the new item with the focus */
1491 descr->focus_item = index;
1492 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1493 if (descr->caret_on && (descr->in_focus))
1494 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1498 /***********************************************************************
1499 * LISTBOX_InsertItem
1501 static LRESULT LISTBOX_InsertItem( HWND hwnd, LB_DESCR *descr, INT index,
1502 LPWSTR str, DWORD data )
1504 LB_ITEMDATA *item;
1505 INT max_items;
1506 INT oldfocus = descr->focus_item;
1508 if (index == -1) index = descr->nb_items;
1509 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1510 if (!descr->items) max_items = 0;
1511 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1512 if (descr->nb_items == max_items)
1514 /* We need to grow the array */
1515 max_items += LB_ARRAY_GRANULARITY;
1516 if (descr->items)
1517 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1518 max_items * sizeof(LB_ITEMDATA) );
1519 else
1520 item = HeapAlloc( GetProcessHeap(), 0,
1521 max_items * sizeof(LB_ITEMDATA) );
1522 if (!item)
1524 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1525 return LB_ERRSPACE;
1527 descr->items = item;
1530 /* Insert the item structure */
1532 item = &descr->items[index];
1533 if (index < descr->nb_items)
1534 RtlMoveMemory( item + 1, item,
1535 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1536 item->str = str;
1537 item->data = data;
1538 item->height = 0;
1539 item->selected = FALSE;
1540 descr->nb_items++;
1542 /* Get item height */
1544 if (descr->style & LBS_OWNERDRAWVARIABLE)
1546 MEASUREITEMSTRUCT mis;
1547 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
1549 mis.CtlType = ODT_LISTBOX;
1550 mis.CtlID = id;
1551 mis.itemID = index;
1552 mis.itemData = descr->items[index].data;
1553 mis.itemHeight = descr->item_height;
1554 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1555 item->height = mis.itemHeight ? mis.itemHeight : 1;
1556 TRACE("[%p]: measure item %d (%s) = %d\n",
1557 hwnd, index, str ? debugstr_w(str) : "", item->height );
1560 /* Repaint the items */
1562 LISTBOX_UpdateScroll( hwnd, descr );
1563 LISTBOX_InvalidateItems( hwnd, descr, index );
1565 /* Move selection and focused item */
1566 /* If listbox was empty, set focus to the first item */
1567 if (descr->nb_items == 1)
1568 LISTBOX_SetCaretIndex( hwnd, descr, 0, FALSE );
1569 /* single select don't change selection index in win31 */
1570 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1572 descr->selected_item++;
1573 LISTBOX_SetSelection( hwnd, descr, descr->selected_item-1, TRUE, FALSE );
1575 else
1577 if (index <= descr->selected_item)
1579 descr->selected_item++;
1580 descr->focus_item = oldfocus; /* focus not changed */
1583 return LB_OKAY;
1587 /***********************************************************************
1588 * LISTBOX_InsertString
1590 static LRESULT LISTBOX_InsertString( HWND hwnd, LB_DESCR *descr, INT index,
1591 LPCWSTR str )
1593 LPWSTR new_str = NULL;
1594 DWORD data = 0;
1595 LRESULT ret;
1597 if (HAS_STRINGS(descr))
1599 static const WCHAR empty_stringW[] = { 0 };
1600 if (!str) str = empty_stringW;
1601 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1603 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1604 return LB_ERRSPACE;
1606 strcpyW(new_str, str);
1608 else data = (DWORD)str;
1610 if (index == -1) index = descr->nb_items;
1611 if ((ret = LISTBOX_InsertItem( hwnd, descr, index, new_str, data )) != 0)
1613 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1614 return ret;
1617 TRACE("[%p]: added item %d %s\n",
1618 hwnd, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1619 return index;
1623 /***********************************************************************
1624 * LISTBOX_DeleteItem
1626 * Delete the content of an item. 'index' must be a valid index.
1628 static void LISTBOX_DeleteItem( HWND hwnd, LB_DESCR *descr, INT index )
1630 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1631 * while Win95 sends it for all items with user data.
1632 * It's probably better to send it too often than not
1633 * often enough, so this is what we do here.
1635 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1637 DELETEITEMSTRUCT dis;
1638 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
1640 dis.CtlType = ODT_LISTBOX;
1641 dis.CtlID = id;
1642 dis.itemID = index;
1643 dis.hwndItem = hwnd;
1644 dis.itemData = descr->items[index].data;
1645 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1647 if (HAS_STRINGS(descr) && descr->items[index].str)
1648 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1652 /***********************************************************************
1653 * LISTBOX_RemoveItem
1655 * Remove an item from the listbox and delete its content.
1657 static LRESULT LISTBOX_RemoveItem( HWND hwnd, LB_DESCR *descr, INT index )
1659 LB_ITEMDATA *item;
1660 INT max_items;
1662 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1663 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1665 /* We need to invalidate the original rect instead of the updated one. */
1666 LISTBOX_InvalidateItems( hwnd, descr, index );
1668 LISTBOX_DeleteItem( hwnd, descr, index );
1670 /* Remove the item */
1672 item = &descr->items[index];
1673 if (index < descr->nb_items-1)
1674 RtlMoveMemory( item, item + 1,
1675 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1676 descr->nb_items--;
1677 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1679 /* Shrink the item array if possible */
1681 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1682 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1684 max_items -= LB_ARRAY_GRANULARITY;
1685 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1686 max_items * sizeof(LB_ITEMDATA) );
1687 if (item) descr->items = item;
1689 /* Repaint the items */
1691 LISTBOX_UpdateScroll( hwnd, descr );
1692 /* if we removed the scrollbar, reset the top of the list
1693 (correct for owner-drawn ???) */
1694 if (descr->nb_items == descr->page_size)
1695 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1697 /* Move selection and focused item */
1698 if (!IS_MULTISELECT(descr))
1700 if (index == descr->selected_item)
1701 descr->selected_item = -1;
1702 else if (index < descr->selected_item)
1704 descr->selected_item--;
1705 if (ISWIN31) /* win 31 do not change the selected item number */
1706 LISTBOX_SetSelection( hwnd, descr, descr->selected_item + 1, TRUE, FALSE);
1710 if (descr->focus_item >= descr->nb_items)
1712 descr->focus_item = descr->nb_items - 1;
1713 if (descr->focus_item < 0) descr->focus_item = 0;
1715 return LB_OKAY;
1719 /***********************************************************************
1720 * LISTBOX_ResetContent
1722 static void LISTBOX_ResetContent( HWND hwnd, LB_DESCR *descr )
1724 INT i;
1726 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( hwnd, descr, i );
1727 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1728 descr->nb_items = 0;
1729 descr->top_item = 0;
1730 descr->selected_item = -1;
1731 descr->focus_item = 0;
1732 descr->anchor_item = -1;
1733 descr->items = NULL;
1737 /***********************************************************************
1738 * LISTBOX_SetCount
1740 static LRESULT LISTBOX_SetCount( HWND hwnd, LB_DESCR *descr, INT count )
1742 LRESULT ret;
1744 if (HAS_STRINGS(descr)) return LB_ERR;
1745 /* FIXME: this is far from optimal... */
1746 if (count > descr->nb_items)
1748 while (count > descr->nb_items)
1749 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, 0 )) < 0)
1750 return ret;
1752 else if (count < descr->nb_items)
1754 while (count < descr->nb_items)
1755 if ((ret = LISTBOX_RemoveItem( hwnd, descr, -1 )) < 0)
1756 return ret;
1758 return LB_OKAY;
1762 /***********************************************************************
1763 * LISTBOX_Directory
1765 static LRESULT LISTBOX_Directory( HWND hwnd, LB_DESCR *descr, UINT attrib,
1766 LPCWSTR filespec, BOOL long_names )
1768 HANDLE handle;
1769 LRESULT ret = LB_OKAY;
1770 WIN32_FIND_DATAW entry;
1771 int pos;
1773 /* don't scan directory if we just want drives exclusively */
1774 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1775 /* scan directory */
1776 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1778 int le = GetLastError();
1779 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1781 else
1785 WCHAR buffer[270];
1786 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1788 static const WCHAR bracketW[] = { ']',0 };
1789 static const WCHAR dotW[] = { '.',0 };
1790 if (!(attrib & DDL_DIRECTORY) ||
1791 !strcmpW( entry.cFileName, dotW )) continue;
1792 buffer[0] = '[';
1793 if (!long_names && entry.cAlternateFileName[0])
1794 strcpyW( buffer + 1, entry.cAlternateFileName );
1795 else
1796 strcpyW( buffer + 1, entry.cFileName );
1797 strcatW(buffer, bracketW);
1799 else /* not a directory */
1801 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1802 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1804 if ((attrib & DDL_EXCLUSIVE) &&
1805 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1806 continue;
1807 #undef ATTRIBS
1808 if (!long_names && entry.cAlternateFileName[0])
1809 strcpyW( buffer, entry.cAlternateFileName );
1810 else
1811 strcpyW( buffer, entry.cFileName );
1813 if (!long_names) CharLowerW( buffer );
1814 pos = LISTBOX_FindFileStrPos( hwnd, descr, buffer );
1815 if ((ret = LISTBOX_InsertString( hwnd, descr, pos, buffer )) < 0)
1816 break;
1817 } while (FindNextFileW( handle, &entry ));
1818 FindClose( handle );
1822 /* scan drives */
1823 if ((ret >= 0) && (attrib & DDL_DRIVES))
1825 WCHAR buffer[] = {'[','-','a','-',']',0};
1826 WCHAR root[] = {'A',':','\\',0};
1827 int drive;
1828 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1830 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1831 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, buffer )) < 0)
1832 break;
1835 return ret;
1839 /***********************************************************************
1840 * LISTBOX_HandleVScroll
1842 static LRESULT LISTBOX_HandleVScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1844 SCROLLINFO info;
1846 if (descr->style & LBS_MULTICOLUMN) return 0;
1847 switch(LOWORD(wParam))
1849 case SB_LINEUP:
1850 LISTBOX_SetTopItem( hwnd, descr, descr->top_item - 1, TRUE );
1851 break;
1852 case SB_LINEDOWN:
1853 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + 1, TRUE );
1854 break;
1855 case SB_PAGEUP:
1856 LISTBOX_SetTopItem( hwnd, descr, descr->top_item -
1857 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1858 break;
1859 case SB_PAGEDOWN:
1860 LISTBOX_SetTopItem( hwnd, descr, descr->top_item +
1861 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1862 break;
1863 case SB_THUMBPOSITION:
1864 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam), TRUE );
1865 break;
1866 case SB_THUMBTRACK:
1867 info.cbSize = sizeof(info);
1868 info.fMask = SIF_TRACKPOS;
1869 GetScrollInfo( hwnd, SB_VERT, &info );
1870 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos, TRUE );
1871 break;
1872 case SB_TOP:
1873 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1874 break;
1875 case SB_BOTTOM:
1876 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1877 break;
1879 return 0;
1883 /***********************************************************************
1884 * LISTBOX_HandleHScroll
1886 static LRESULT LISTBOX_HandleHScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1888 SCROLLINFO info;
1889 INT page;
1891 if (descr->style & LBS_MULTICOLUMN)
1893 switch(LOWORD(wParam))
1895 case SB_LINELEFT:
1896 LISTBOX_SetTopItem( hwnd, descr, descr->top_item-descr->page_size,
1897 TRUE );
1898 break;
1899 case SB_LINERIGHT:
1900 LISTBOX_SetTopItem( hwnd, descr, descr->top_item+descr->page_size,
1901 TRUE );
1902 break;
1903 case SB_PAGELEFT:
1904 page = descr->width / descr->column_width;
1905 if (page < 1) page = 1;
1906 LISTBOX_SetTopItem( hwnd, descr,
1907 descr->top_item - page * descr->page_size, TRUE );
1908 break;
1909 case SB_PAGERIGHT:
1910 page = descr->width / descr->column_width;
1911 if (page < 1) page = 1;
1912 LISTBOX_SetTopItem( hwnd, descr,
1913 descr->top_item + page * descr->page_size, TRUE );
1914 break;
1915 case SB_THUMBPOSITION:
1916 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam)*descr->page_size,
1917 TRUE );
1918 break;
1919 case SB_THUMBTRACK:
1920 info.cbSize = sizeof(info);
1921 info.fMask = SIF_TRACKPOS;
1922 GetScrollInfo( hwnd, SB_VERT, &info );
1923 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos*descr->page_size,
1924 TRUE );
1925 break;
1926 case SB_LEFT:
1927 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1928 break;
1929 case SB_RIGHT:
1930 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1931 break;
1934 else if (descr->horz_extent)
1936 switch(LOWORD(wParam))
1938 case SB_LINELEFT:
1939 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos - 1 );
1940 break;
1941 case SB_LINERIGHT:
1942 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos + 1 );
1943 break;
1944 case SB_PAGELEFT:
1945 LISTBOX_SetHorizontalPos( hwnd, descr,
1946 descr->horz_pos - descr->width );
1947 break;
1948 case SB_PAGERIGHT:
1949 LISTBOX_SetHorizontalPos( hwnd, descr,
1950 descr->horz_pos + descr->width );
1951 break;
1952 case SB_THUMBPOSITION:
1953 LISTBOX_SetHorizontalPos( hwnd, descr, HIWORD(wParam) );
1954 break;
1955 case SB_THUMBTRACK:
1956 info.cbSize = sizeof(info);
1957 info.fMask = SIF_TRACKPOS;
1958 GetScrollInfo( hwnd, SB_HORZ, &info );
1959 LISTBOX_SetHorizontalPos( hwnd, descr, info.nTrackPos );
1960 break;
1961 case SB_LEFT:
1962 LISTBOX_SetHorizontalPos( hwnd, descr, 0 );
1963 break;
1964 case SB_RIGHT:
1965 LISTBOX_SetHorizontalPos( hwnd, descr,
1966 descr->horz_extent - descr->width );
1967 break;
1970 return 0;
1973 static LRESULT LISTBOX_HandleMouseWheel(HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1975 short gcWheelDelta = 0;
1976 UINT pulScrollLines = 3;
1978 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1980 gcWheelDelta -= (short) HIWORD(wParam);
1982 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1984 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1985 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1986 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + cLineScroll, TRUE );
1988 return 0;
1991 /***********************************************************************
1992 * LISTBOX_HandleLButtonDown
1994 static LRESULT LISTBOX_HandleLButtonDown( HWND hwnd, LB_DESCR *descr,
1995 WPARAM wParam, INT x, INT y )
1997 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1998 TRACE("[%p]: lbuttondown %d,%d item %d\n", hwnd, x, y, index );
1999 if (!descr->caret_on && (descr->in_focus)) return 0;
2001 if (!descr->in_focus)
2003 if( !descr->lphc ) SetFocus( hwnd );
2004 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2007 if (index == -1) return 0;
2009 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2011 /* we should perhaps make sure that all items are deselected
2012 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2013 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
2014 LISTBOX_SetSelection( hwnd, descr, -1, FALSE, FALSE);
2017 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
2018 if (wParam & MK_CONTROL)
2020 LISTBOX_SetCaretIndex( hwnd, descr, index, FALSE );
2021 LISTBOX_SetSelection( hwnd, descr, index,
2022 !descr->items[index].selected,
2023 (descr->style & LBS_NOTIFY) != 0);
2025 else
2027 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2029 if (descr->style & LBS_MULTIPLESEL)
2031 LISTBOX_SetSelection( hwnd, descr, index,
2032 (!(descr->style & LBS_MULTIPLESEL) ||
2033 !descr->items[index].selected),
2034 (descr->style & LBS_NOTIFY) != 0 );
2038 else
2040 descr->anchor_item = index;
2041 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2042 LISTBOX_SetSelection( hwnd, descr, index,
2043 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2046 descr->captured = TRUE;
2047 SetCapture( hwnd );
2049 if (!descr->lphc)
2051 if (descr->style & LBS_NOTIFY )
2052 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2053 MAKELPARAM( x, y ) );
2054 if (GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2056 POINT pt;
2058 pt.x = x;
2059 pt.y = y;
2061 if (DragDetect( hwnd, pt ))
2062 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2065 return 0;
2069 /*************************************************************************
2070 * LISTBOX_HandleLButtonDownCombo [Internal]
2072 * Process LButtonDown message for the ComboListBox
2074 nn * PARAMS
2075 * pWnd [I] The windows internal structure
2076 * pDescr [I] The ListBox internal structure
2077 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2078 * x [I] X Mouse Coordinate
2079 * y [I] Y Mouse Coordinate
2081 * RETURNS
2082 * 0 since we are processing the WM_LBUTTONDOWN Message
2084 * NOTES
2085 * This function is only to be used when a ListBox is a ComboListBox
2088 static LRESULT LISTBOX_HandleLButtonDownCombo( HWND hwnd, LB_DESCR *pDescr,
2089 UINT msg, WPARAM wParam, INT x, INT y)
2091 RECT clientRect, screenRect;
2092 POINT mousePos;
2094 mousePos.x = x;
2095 mousePos.y = y;
2097 GetClientRect(hwnd, &clientRect);
2099 if(PtInRect(&clientRect, mousePos))
2101 /* MousePos is in client, resume normal processing */
2102 if (msg == WM_LBUTTONDOWN)
2104 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2105 return LISTBOX_HandleLButtonDown( hwnd, pDescr, wParam, x, y);
2107 else if (pDescr->style & LBS_NOTIFY)
2108 SEND_NOTIFICATION( hwnd, pDescr, LBN_DBLCLK );
2109 return 0;
2111 else
2113 POINT screenMousePos;
2114 HWND hWndOldCapture;
2116 /* Check the Non-Client Area */
2117 screenMousePos = mousePos;
2118 hWndOldCapture = GetCapture();
2119 ReleaseCapture();
2120 GetWindowRect(hwnd, &screenRect);
2121 ClientToScreen(hwnd, &screenMousePos);
2123 if(!PtInRect(&screenRect, screenMousePos))
2125 LISTBOX_SetCaretIndex( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE );
2126 LISTBOX_SetSelection( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2127 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2128 return 0;
2130 else
2132 /* Check to see the NC is a scrollbar */
2133 INT nHitTestType=0;
2134 LONG style = GetWindowLongA( hwnd, GWL_STYLE );
2135 /* Check Vertical scroll bar */
2136 if (style & WS_VSCROLL)
2138 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2139 if (PtInRect( &clientRect, mousePos ))
2141 nHitTestType = HTVSCROLL;
2144 /* Check horizontal scroll bar */
2145 if (style & WS_HSCROLL)
2147 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2148 if (PtInRect( &clientRect, mousePos ))
2150 nHitTestType = HTHSCROLL;
2153 /* Windows sends this message when a scrollbar is clicked
2156 if(nHitTestType != 0)
2158 SendMessageW(hwnd, WM_NCLBUTTONDOWN, nHitTestType,
2159 MAKELONG(screenMousePos.x, screenMousePos.y));
2161 /* Resume the Capture after scrolling is complete
2163 if(hWndOldCapture != 0)
2165 SetCapture(hWndOldCapture);
2169 return 0;
2172 /***********************************************************************
2173 * LISTBOX_HandleLButtonUp
2175 static LRESULT LISTBOX_HandleLButtonUp( HWND hwnd, LB_DESCR *descr )
2177 if (LISTBOX_Timer != LB_TIMER_NONE)
2178 KillSystemTimer( hwnd, LB_TIMER_ID );
2179 LISTBOX_Timer = LB_TIMER_NONE;
2180 if (descr->captured)
2182 descr->captured = FALSE;
2183 if (GetCapture() == hwnd) ReleaseCapture();
2184 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2185 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2187 return 0;
2191 /***********************************************************************
2192 * LISTBOX_HandleTimer
2194 * Handle scrolling upon a timer event.
2195 * Return TRUE if scrolling should continue.
2197 static LRESULT LISTBOX_HandleTimer( HWND hwnd, LB_DESCR *descr,
2198 INT index, TIMER_DIRECTION dir )
2200 switch(dir)
2202 case LB_TIMER_UP:
2203 if (descr->top_item) index = descr->top_item - 1;
2204 else index = 0;
2205 break;
2206 case LB_TIMER_LEFT:
2207 if (descr->top_item) index -= descr->page_size;
2208 break;
2209 case LB_TIMER_DOWN:
2210 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2211 if (index == descr->focus_item) index++;
2212 if (index >= descr->nb_items) index = descr->nb_items - 1;
2213 break;
2214 case LB_TIMER_RIGHT:
2215 if (index + descr->page_size < descr->nb_items)
2216 index += descr->page_size;
2217 break;
2218 case LB_TIMER_NONE:
2219 break;
2221 if (index == descr->focus_item) return FALSE;
2222 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2223 return TRUE;
2227 /***********************************************************************
2228 * LISTBOX_HandleSystemTimer
2230 * WM_SYSTIMER handler.
2232 static LRESULT LISTBOX_HandleSystemTimer( HWND hwnd, LB_DESCR *descr )
2234 if (!LISTBOX_HandleTimer( hwnd, descr, descr->focus_item, LISTBOX_Timer ))
2236 KillSystemTimer( hwnd, LB_TIMER_ID );
2237 LISTBOX_Timer = LB_TIMER_NONE;
2239 return 0;
2243 /***********************************************************************
2244 * LISTBOX_HandleMouseMove
2246 * WM_MOUSEMOVE handler.
2248 static void LISTBOX_HandleMouseMove( HWND hwnd, LB_DESCR *descr,
2249 INT x, INT y )
2251 INT index;
2252 TIMER_DIRECTION dir = LB_TIMER_NONE;
2254 if (!descr->captured) return;
2256 if (descr->style & LBS_MULTICOLUMN)
2258 if (y < 0) y = 0;
2259 else if (y >= descr->item_height * descr->page_size)
2260 y = descr->item_height * descr->page_size - 1;
2262 if (x < 0)
2264 dir = LB_TIMER_LEFT;
2265 x = 0;
2267 else if (x >= descr->width)
2269 dir = LB_TIMER_RIGHT;
2270 x = descr->width - 1;
2273 else
2275 if (y < 0) dir = LB_TIMER_UP; /* above */
2276 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2279 index = LISTBOX_GetItemFromPoint( descr, x, y );
2280 if (index == -1) index = descr->focus_item;
2281 if (!LISTBOX_HandleTimer( hwnd, descr, index, dir )) dir = LB_TIMER_NONE;
2283 /* Start/stop the system timer */
2285 if (dir != LB_TIMER_NONE)
2286 SetSystemTimer( hwnd, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2287 else if (LISTBOX_Timer != LB_TIMER_NONE)
2288 KillSystemTimer( hwnd, LB_TIMER_ID );
2289 LISTBOX_Timer = dir;
2293 /***********************************************************************
2294 * LISTBOX_HandleKeyDown
2296 static LRESULT LISTBOX_HandleKeyDown( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
2298 INT caret = -1;
2299 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2300 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2301 bForceSelection = FALSE; /* only for single select list */
2303 if (descr->style & LBS_WANTKEYBOARDINPUT)
2305 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2306 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2307 (LPARAM)hwnd );
2308 if (caret == -2) return 0;
2310 if (caret == -1) switch(wParam)
2312 case VK_LEFT:
2313 if (descr->style & LBS_MULTICOLUMN)
2315 bForceSelection = FALSE;
2316 if (descr->focus_item >= descr->page_size)
2317 caret = descr->focus_item - descr->page_size;
2318 break;
2320 /* fall through */
2321 case VK_UP:
2322 caret = descr->focus_item - 1;
2323 if (caret < 0) caret = 0;
2324 break;
2325 case VK_RIGHT:
2326 if (descr->style & LBS_MULTICOLUMN)
2328 bForceSelection = FALSE;
2329 if (descr->focus_item + descr->page_size < descr->nb_items)
2330 caret = descr->focus_item + descr->page_size;
2331 break;
2333 /* fall through */
2334 case VK_DOWN:
2335 caret = descr->focus_item + 1;
2336 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2337 break;
2339 case VK_PRIOR:
2340 if (descr->style & LBS_MULTICOLUMN)
2342 INT page = descr->width / descr->column_width;
2343 if (page < 1) page = 1;
2344 caret = descr->focus_item - (page * descr->page_size) + 1;
2346 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2347 if (caret < 0) caret = 0;
2348 break;
2349 case VK_NEXT:
2350 if (descr->style & LBS_MULTICOLUMN)
2352 INT page = descr->width / descr->column_width;
2353 if (page < 1) page = 1;
2354 caret = descr->focus_item + (page * descr->page_size) - 1;
2356 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2357 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2358 break;
2359 case VK_HOME:
2360 caret = 0;
2361 break;
2362 case VK_END:
2363 caret = descr->nb_items - 1;
2364 break;
2365 case VK_SPACE:
2366 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2367 else if (descr->style & LBS_MULTIPLESEL)
2369 LISTBOX_SetSelection( hwnd, descr, descr->focus_item,
2370 !descr->items[descr->focus_item].selected,
2371 (descr->style & LBS_NOTIFY) != 0 );
2373 break;
2374 default:
2375 bForceSelection = FALSE;
2377 if (bForceSelection) /* focused item is used instead of key */
2378 caret = descr->focus_item;
2379 if (caret >= 0)
2381 if (((descr->style & LBS_EXTENDEDSEL) &&
2382 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2383 !IS_MULTISELECT(descr))
2384 descr->anchor_item = caret;
2385 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2387 if (descr->style & LBS_MULTIPLESEL)
2388 descr->selected_item = caret;
2389 else
2390 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2391 if (descr->style & LBS_NOTIFY)
2393 if( descr->lphc )
2395 /* make sure that combo parent doesn't hide us */
2396 descr->lphc->wState |= CBF_NOROLLUP;
2398 if (descr->nb_items) SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2401 return 0;
2405 /***********************************************************************
2406 * LISTBOX_HandleChar
2408 static LRESULT LISTBOX_HandleChar( HWND hwnd, LB_DESCR *descr, WCHAR charW )
2410 INT caret = -1;
2411 WCHAR str[2];
2413 str[0] = charW;
2414 str[1] = '\0';
2416 if (descr->style & LBS_WANTKEYBOARDINPUT)
2418 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2419 MAKEWPARAM(charW, descr->focus_item),
2420 (LPARAM)hwnd );
2421 if (caret == -2) return 0;
2423 if (caret == -1)
2424 caret = LISTBOX_FindString( hwnd, descr, descr->focus_item, str, FALSE);
2425 if (caret != -1)
2427 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2428 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2429 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2430 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2431 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2433 return 0;
2437 /***********************************************************************
2438 * LISTBOX_Create
2440 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2442 LB_DESCR *descr;
2443 MEASUREITEMSTRUCT mis;
2444 RECT rect;
2446 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2447 return FALSE;
2449 GetClientRect( hwnd, &rect );
2450 descr->owner = GetParent( hwnd );
2451 descr->style = GetWindowLongA( hwnd, GWL_STYLE );
2452 descr->width = rect.right - rect.left;
2453 descr->height = rect.bottom - rect.top;
2454 descr->items = NULL;
2455 descr->nb_items = 0;
2456 descr->top_item = 0;
2457 descr->selected_item = -1;
2458 descr->focus_item = 0;
2459 descr->anchor_item = -1;
2460 descr->item_height = 1;
2461 descr->page_size = 1;
2462 descr->column_width = 150;
2463 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2464 descr->horz_pos = 0;
2465 descr->nb_tabs = 0;
2466 descr->tabs = NULL;
2467 descr->caret_on = lphc ? FALSE : TRUE;
2468 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2469 descr->in_focus = FALSE;
2470 descr->captured = FALSE;
2471 descr->font = 0;
2472 descr->locale = 0; /* FIXME */
2473 descr->lphc = lphc;
2475 if (is_old_app(hwnd) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2477 /* Win95 document "List Box Differences" from MSDN:
2478 If a list box in a version 3.x application has either the
2479 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2480 horizontal and vertical scroll bars.
2482 descr->style |= WS_VSCROLL | WS_HSCROLL;
2485 if( lphc )
2487 TRACE_(combo)("[%p]: resetting owner %p -> %p\n", hwnd, descr->owner, lphc->self );
2488 descr->owner = lphc->self;
2491 SetWindowLongA( hwnd, 0, (LONG)descr );
2493 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2495 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2496 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2497 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2498 descr->item_height = LISTBOX_SetFont( hwnd, descr, 0 );
2500 if (descr->style & LBS_OWNERDRAWFIXED)
2502 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2504 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2505 descr->item_height = lphc->fixedOwnerDrawHeight;
2507 else
2509 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
2510 mis.CtlType = ODT_LISTBOX;
2511 mis.CtlID = id;
2512 mis.itemID = -1;
2513 mis.itemWidth = 0;
2514 mis.itemData = 0;
2515 mis.itemHeight = descr->item_height;
2516 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2517 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2521 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2522 return TRUE;
2526 /***********************************************************************
2527 * LISTBOX_Destroy
2529 static BOOL LISTBOX_Destroy( HWND hwnd, LB_DESCR *descr )
2531 LISTBOX_ResetContent( hwnd, descr );
2532 SetWindowLongA( hwnd, 0, 0 );
2533 HeapFree( GetProcessHeap(), 0, descr );
2534 return TRUE;
2538 /***********************************************************************
2539 * ListBoxWndProc_common
2541 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2542 WPARAM wParam, LPARAM lParam, BOOL unicode )
2544 LRESULT ret;
2545 LB_DESCR *descr;
2547 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
2549 if (msg == WM_CREATE)
2551 if (!LISTBOX_Create( hwnd, NULL ))
2552 return -1;
2553 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongA( hwnd, 0 ) );
2554 return 0;
2556 /* Ignore all other messages before we get a WM_CREATE */
2557 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2558 DefWindowProcA( hwnd, msg, wParam, lParam );
2561 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2562 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
2563 switch(msg)
2565 case LB_RESETCONTENT16:
2566 case LB_RESETCONTENT:
2567 LISTBOX_ResetContent( hwnd, descr );
2568 LISTBOX_UpdateScroll( hwnd, descr );
2569 InvalidateRect( hwnd, NULL, TRUE );
2570 return 0;
2572 case LB_ADDSTRING16:
2573 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2574 /* fall through */
2575 case LB_ADDSTRING:
2577 INT ret;
2578 LPWSTR textW;
2579 if(unicode || !HAS_STRINGS(descr))
2580 textW = (LPWSTR)lParam;
2581 else
2583 LPSTR textA = (LPSTR)lParam;
2584 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2585 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2586 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2588 wParam = LISTBOX_FindStringPos( hwnd, descr, textW, FALSE );
2589 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2590 if (!unicode && HAS_STRINGS(descr))
2591 HeapFree(GetProcessHeap(), 0, textW);
2592 return ret;
2595 case LB_INSERTSTRING16:
2596 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2597 wParam = (INT)(INT16)wParam;
2598 /* fall through */
2599 case LB_INSERTSTRING:
2601 INT ret;
2602 LPWSTR textW;
2603 if(unicode || !HAS_STRINGS(descr))
2604 textW = (LPWSTR)lParam;
2605 else
2607 LPSTR textA = (LPSTR)lParam;
2608 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2609 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2610 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2612 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2613 if(!unicode && HAS_STRINGS(descr))
2614 HeapFree(GetProcessHeap(), 0, textW);
2615 return ret;
2618 case LB_ADDFILE16:
2619 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2620 /* fall through */
2621 case LB_ADDFILE:
2623 INT ret;
2624 LPWSTR textW;
2625 if(unicode || !HAS_STRINGS(descr))
2626 textW = (LPWSTR)lParam;
2627 else
2629 LPSTR textA = (LPSTR)lParam;
2630 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2631 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2632 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2634 wParam = LISTBOX_FindFileStrPos( hwnd, descr, textW );
2635 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2636 if(!unicode && HAS_STRINGS(descr))
2637 HeapFree(GetProcessHeap(), 0, textW);
2638 return ret;
2641 case LB_DELETESTRING16:
2642 case LB_DELETESTRING:
2643 if (LISTBOX_RemoveItem( hwnd, descr, wParam) != LB_ERR)
2644 return descr->nb_items;
2645 else
2646 return LB_ERR;
2648 case LB_GETITEMDATA16:
2649 case LB_GETITEMDATA:
2650 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2651 return LB_ERR;
2652 return descr->items[wParam].data;
2654 case LB_SETITEMDATA16:
2655 case LB_SETITEMDATA:
2656 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2657 return LB_ERR;
2658 descr->items[wParam].data = (DWORD)lParam;
2659 return LB_OKAY;
2661 case LB_GETCOUNT16:
2662 case LB_GETCOUNT:
2663 return descr->nb_items;
2665 case LB_GETTEXT16:
2666 lParam = (LPARAM)MapSL(lParam);
2667 /* fall through */
2668 case LB_GETTEXT:
2669 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2671 case LB_GETTEXTLEN16:
2672 /* fall through */
2673 case LB_GETTEXTLEN:
2674 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2675 return LB_ERR;
2676 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2677 if (unicode) return strlenW( descr->items[wParam].str );
2678 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2679 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2681 case LB_GETCURSEL16:
2682 case LB_GETCURSEL:
2683 if (descr->nb_items == 0)
2684 return LB_ERR;
2685 if (!IS_MULTISELECT(descr))
2686 return descr->selected_item;
2687 if (descr->selected_item != -1)
2688 return descr->selected_item;
2689 return descr->focus_item;
2690 /* otherwise, if the user tries to move the selection with the */
2691 /* arrow keys, we will give the application something to choke on */
2692 case LB_GETTOPINDEX16:
2693 case LB_GETTOPINDEX:
2694 return descr->top_item;
2696 case LB_GETITEMHEIGHT16:
2697 case LB_GETITEMHEIGHT:
2698 return LISTBOX_GetItemHeight( descr, wParam );
2700 case LB_SETITEMHEIGHT16:
2701 lParam = LOWORD(lParam);
2702 /* fall through */
2703 case LB_SETITEMHEIGHT:
2704 return LISTBOX_SetItemHeight( hwnd, descr, wParam, lParam, TRUE );
2706 case LB_ITEMFROMPOINT:
2708 POINT pt;
2709 RECT rect;
2711 pt.x = LOWORD(lParam);
2712 pt.y = HIWORD(lParam);
2713 rect.left = 0;
2714 rect.top = 0;
2715 rect.right = descr->width;
2716 rect.bottom = descr->height;
2718 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2719 !PtInRect( &rect, pt ) );
2722 case LB_SETCARETINDEX16:
2723 case LB_SETCARETINDEX:
2724 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2725 if (LISTBOX_SetCaretIndex( hwnd, descr, wParam, !lParam ) == LB_ERR)
2726 return LB_ERR;
2727 else if (ISWIN31)
2728 return wParam;
2729 else
2730 return LB_OKAY;
2732 case LB_GETCARETINDEX16:
2733 case LB_GETCARETINDEX:
2734 return descr->focus_item;
2736 case LB_SETTOPINDEX16:
2737 case LB_SETTOPINDEX:
2738 return LISTBOX_SetTopItem( hwnd, descr, wParam, TRUE );
2740 case LB_SETCOLUMNWIDTH16:
2741 case LB_SETCOLUMNWIDTH:
2742 return LISTBOX_SetColumnWidth( hwnd, descr, wParam );
2744 case LB_GETITEMRECT16:
2746 RECT rect;
2747 RECT16 *r16 = MapSL(lParam);
2748 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2749 r16->left = rect.left;
2750 r16->top = rect.top;
2751 r16->right = rect.right;
2752 r16->bottom = rect.bottom;
2754 return ret;
2756 case LB_GETITEMRECT:
2757 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2759 case LB_FINDSTRING16:
2760 wParam = (INT)(INT16)wParam;
2761 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2762 /* fall through */
2763 case LB_FINDSTRING:
2765 INT ret;
2766 LPWSTR textW;
2767 if(unicode || !HAS_STRINGS(descr))
2768 textW = (LPWSTR)lParam;
2769 else
2771 LPSTR textA = (LPSTR)lParam;
2772 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2773 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2774 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2776 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2777 if(!unicode && HAS_STRINGS(descr))
2778 HeapFree(GetProcessHeap(), 0, textW);
2779 return ret;
2782 case LB_FINDSTRINGEXACT16:
2783 wParam = (INT)(INT16)wParam;
2784 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2785 /* fall through */
2786 case LB_FINDSTRINGEXACT:
2788 INT ret;
2789 LPWSTR textW;
2790 if(unicode || !HAS_STRINGS(descr))
2791 textW = (LPWSTR)lParam;
2792 else
2794 LPSTR textA = (LPSTR)lParam;
2795 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2796 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2797 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2799 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, TRUE );
2800 if(!unicode && HAS_STRINGS(descr))
2801 HeapFree(GetProcessHeap(), 0, textW);
2802 return ret;
2805 case LB_SELECTSTRING16:
2806 wParam = (INT)(INT16)wParam;
2807 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2808 /* fall through */
2809 case LB_SELECTSTRING:
2811 INT index;
2812 LPWSTR textW;
2814 if(HAS_STRINGS(descr))
2815 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2816 debugstr_a((LPSTR)lParam));
2817 if(unicode || !HAS_STRINGS(descr))
2818 textW = (LPWSTR)lParam;
2819 else
2821 LPSTR textA = (LPSTR)lParam;
2822 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2823 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2824 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2826 index = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2827 if(!unicode && HAS_STRINGS(descr))
2828 HeapFree(GetProcessHeap(), 0, textW);
2829 if (index != LB_ERR)
2831 LISTBOX_MoveCaret( hwnd, descr, index, TRUE );
2832 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
2834 return index;
2837 case LB_GETSEL16:
2838 wParam = (INT)(INT16)wParam;
2839 /* fall through */
2840 case LB_GETSEL:
2841 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2842 return LB_ERR;
2843 return descr->items[wParam].selected;
2845 case LB_SETSEL16:
2846 lParam = (INT)(INT16)lParam;
2847 /* fall through */
2848 case LB_SETSEL:
2849 return LISTBOX_SetSelection( hwnd, descr, lParam, wParam, FALSE );
2851 case LB_SETCURSEL16:
2852 wParam = (INT)(INT16)wParam;
2853 /* fall through */
2854 case LB_SETCURSEL:
2855 if (IS_MULTISELECT(descr)) return LB_ERR;
2856 LISTBOX_SetCaretIndex( hwnd, descr, wParam, TRUE );
2857 return LISTBOX_SetSelection( hwnd, descr, wParam, TRUE, FALSE );
2859 case LB_GETSELCOUNT16:
2860 case LB_GETSELCOUNT:
2861 return LISTBOX_GetSelCount( descr );
2863 case LB_GETSELITEMS16:
2864 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2866 case LB_GETSELITEMS:
2867 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2869 case LB_SELITEMRANGE16:
2870 case LB_SELITEMRANGE:
2871 if (LOWORD(lParam) <= HIWORD(lParam))
2872 return LISTBOX_SelectItemRange( hwnd, descr, LOWORD(lParam),
2873 HIWORD(lParam), wParam );
2874 else
2875 return LISTBOX_SelectItemRange( hwnd, descr, HIWORD(lParam),
2876 LOWORD(lParam), wParam );
2878 case LB_SELITEMRANGEEX16:
2879 case LB_SELITEMRANGEEX:
2880 if ((INT)lParam >= (INT)wParam)
2881 return LISTBOX_SelectItemRange( hwnd, descr, wParam, lParam, TRUE );
2882 else
2883 return LISTBOX_SelectItemRange( hwnd, descr, lParam, wParam, FALSE);
2885 case LB_GETHORIZONTALEXTENT16:
2886 case LB_GETHORIZONTALEXTENT:
2887 return descr->horz_extent;
2889 case LB_SETHORIZONTALEXTENT16:
2890 case LB_SETHORIZONTALEXTENT:
2891 return LISTBOX_SetHorizontalExtent( hwnd, descr, wParam );
2893 case LB_GETANCHORINDEX16:
2894 case LB_GETANCHORINDEX:
2895 return descr->anchor_item;
2897 case LB_SETANCHORINDEX16:
2898 wParam = (INT)(INT16)wParam;
2899 /* fall through */
2900 case LB_SETANCHORINDEX:
2901 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2902 return LB_ERR;
2903 descr->anchor_item = (INT)wParam;
2904 return LB_OKAY;
2906 case LB_DIR16:
2907 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2908 * be set automatically (this is different in Win32) */
2909 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2910 lParam = (LPARAM)MapSL(lParam);
2911 /* fall through */
2912 case LB_DIR:
2914 INT ret;
2915 LPWSTR textW;
2916 if(unicode)
2917 textW = (LPWSTR)lParam;
2918 else
2920 LPSTR textA = (LPSTR)lParam;
2921 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2922 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2923 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2925 ret = LISTBOX_Directory( hwnd, descr, wParam, textW, msg == LB_DIR );
2926 if(!unicode)
2927 HeapFree(GetProcessHeap(), 0, textW);
2928 return ret;
2931 case LB_GETLOCALE:
2932 return descr->locale;
2934 case LB_SETLOCALE:
2935 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2936 return LB_OKAY;
2938 case LB_INITSTORAGE:
2939 return LISTBOX_InitStorage( hwnd, descr, wParam );
2941 case LB_SETCOUNT:
2942 return LISTBOX_SetCount( hwnd, descr, (INT)wParam );
2944 case LB_SETTABSTOPS16:
2945 return LISTBOX_SetTabStops( hwnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2947 case LB_SETTABSTOPS:
2948 return LISTBOX_SetTabStops( hwnd, descr, wParam, (LPINT)lParam, FALSE );
2950 case LB_CARETON16:
2951 case LB_CARETON:
2952 if (descr->caret_on)
2953 return LB_OKAY;
2954 descr->caret_on = TRUE;
2955 if ((descr->focus_item != -1) && (descr->in_focus))
2956 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2957 return LB_OKAY;
2959 case LB_CARETOFF16:
2960 case LB_CARETOFF:
2961 if (!descr->caret_on)
2962 return LB_OKAY;
2963 descr->caret_on = FALSE;
2964 if ((descr->focus_item != -1) && (descr->in_focus))
2965 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2966 return LB_OKAY;
2968 case WM_DESTROY:
2969 return LISTBOX_Destroy( hwnd, descr );
2971 case WM_ENABLE:
2972 InvalidateRect( hwnd, NULL, TRUE );
2973 return 0;
2975 case WM_SETREDRAW:
2976 LISTBOX_SetRedraw( hwnd, descr, wParam != 0 );
2977 return 0;
2979 case WM_GETDLGCODE:
2980 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2982 case WM_PAINT:
2984 PAINTSTRUCT ps;
2985 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( hwnd, &ps );
2986 ret = LISTBOX_Paint( hwnd, descr, hdc );
2987 if( !wParam ) EndPaint( hwnd, &ps );
2989 return ret;
2990 case WM_SIZE:
2991 LISTBOX_UpdateSize( hwnd, descr );
2992 return 0;
2993 case WM_GETFONT:
2994 return (LRESULT)descr->font;
2995 case WM_SETFONT:
2996 LISTBOX_SetFont( hwnd, descr, (HFONT)wParam );
2997 if (lParam) InvalidateRect( hwnd, 0, TRUE );
2998 return 0;
2999 case WM_SETFOCUS:
3000 descr->in_focus = TRUE;
3001 descr->caret_on = TRUE;
3002 if (descr->focus_item != -1)
3003 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
3004 SEND_NOTIFICATION( hwnd, descr, LBN_SETFOCUS );
3005 return 0;
3006 case WM_KILLFOCUS:
3007 descr->in_focus = FALSE;
3008 if ((descr->focus_item != -1) && descr->caret_on)
3009 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
3010 SEND_NOTIFICATION( hwnd, descr, LBN_KILLFOCUS );
3011 return 0;
3012 case WM_HSCROLL:
3013 return LISTBOX_HandleHScroll( hwnd, descr, wParam );
3014 case WM_VSCROLL:
3015 return LISTBOX_HandleVScroll( hwnd, descr, wParam );
3016 case WM_MOUSEWHEEL:
3017 if (wParam & (MK_SHIFT | MK_CONTROL))
3018 return DefWindowProcW( hwnd, msg, wParam, lParam );
3019 return LISTBOX_HandleMouseWheel( hwnd, descr, wParam );
3020 case WM_LBUTTONDOWN:
3021 return LISTBOX_HandleLButtonDown( hwnd, descr, wParam,
3022 (INT16)LOWORD(lParam),
3023 (INT16)HIWORD(lParam) );
3024 case WM_LBUTTONDBLCLK:
3025 if (descr->style & LBS_NOTIFY)
3026 SEND_NOTIFICATION( hwnd, descr, LBN_DBLCLK );
3027 return 0;
3028 case WM_MOUSEMOVE:
3029 if (GetCapture() == hwnd)
3030 LISTBOX_HandleMouseMove( hwnd, descr, (INT16)LOWORD(lParam),
3031 (INT16)HIWORD(lParam) );
3032 return 0;
3033 case WM_LBUTTONUP:
3034 return LISTBOX_HandleLButtonUp( hwnd, descr );
3035 case WM_KEYDOWN:
3036 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3037 case WM_CHAR:
3039 WCHAR charW;
3040 if(unicode)
3041 charW = (WCHAR)wParam;
3042 else
3044 CHAR charA = (CHAR)wParam;
3045 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3047 return LISTBOX_HandleChar( hwnd, descr, charW );
3049 case WM_SYSTIMER:
3050 return LISTBOX_HandleSystemTimer( hwnd, descr );
3051 case WM_ERASEBKGND:
3052 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3054 RECT rect;
3055 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3056 wParam, (LPARAM)hwnd );
3057 TRACE("hbrush = %p\n", hbrush);
3058 if(!hbrush)
3059 hbrush = GetSysColorBrush(COLOR_WINDOW);
3060 if(hbrush)
3062 GetClientRect(hwnd, &rect);
3063 FillRect((HDC)wParam, &rect, hbrush);
3066 return 1;
3067 case WM_DROPFILES:
3068 if( !descr->lphc )
3069 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3070 SendMessageA( descr->owner, msg, wParam, lParam );
3071 break;
3073 default:
3074 if ((msg >= WM_USER) && (msg < 0xc000))
3075 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3076 hwnd, msg, wParam, lParam );
3077 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3078 DefWindowProcA( hwnd, msg, wParam, lParam );
3080 return 0;
3083 /***********************************************************************
3084 * ListBoxWndProcA
3086 * This is just a wrapper for the real wndproc, it only does window locking
3087 * and unlocking.
3089 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3091 if (!IsWindow(hwnd)) return 0;
3092 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3095 /***********************************************************************
3096 * ListBoxWndProcW
3098 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3100 if (!IsWindow(hwnd)) return 0;
3101 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );
3104 /***********************************************************************
3105 * ComboLBWndProc_common
3107 * The real combo listbox wndproc
3109 static LRESULT WINAPI ComboLBWndProc_common( HWND hwnd, UINT msg,
3110 WPARAM wParam, LPARAM lParam, BOOL unicode )
3112 LRESULT lRet = 0;
3113 LB_DESCR *descr;
3114 LPHEADCOMBO lphc;
3116 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
3118 if (msg == WM_CREATE)
3120 CREATESTRUCTA *lpcs = (CREATESTRUCTA *)lParam;
3121 TRACE_(combo)("\tpassed parent handle = %p\n",lpcs->lpCreateParams);
3122 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3123 return LISTBOX_Create( hwnd, lphc );
3125 /* Ignore all other messages before we get a WM_CREATE */
3126 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3127 DefWindowProcA( hwnd, msg, wParam, lParam );
3130 TRACE_(combo)("[%p]: msg %s wp %08x lp %08lx\n",
3131 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
3133 if ((lphc = descr->lphc) != NULL)
3135 switch( msg )
3137 case WM_MOUSEMOVE:
3138 if ( (CB_GETTYPE(lphc) != CBS_SIMPLE) )
3140 POINT mousePos;
3141 BOOL captured;
3142 RECT clientRect;
3144 mousePos.x = (INT16)LOWORD(lParam);
3145 mousePos.y = (INT16)HIWORD(lParam);
3148 * If we are in a dropdown combobox, we simulate that
3149 * the mouse is captured to show the tracking of the item.
3151 GetClientRect(hwnd, &clientRect);
3153 if (PtInRect( &clientRect, mousePos ))
3155 captured = descr->captured;
3156 descr->captured = TRUE;
3158 LISTBOX_HandleMouseMove( hwnd, descr,
3159 mousePos.x, mousePos.y);
3161 descr->captured = captured;
3164 else
3166 LISTBOX_HandleMouseMove( hwnd, descr,
3167 mousePos.x, mousePos.y);
3170 return 0;
3173 break;
3175 case WM_LBUTTONUP:
3177 POINT mousePos;
3178 RECT clientRect;
3181 * If the mouse button "up" is not in the listbox,
3182 * we make sure there is no selection by re-selecting the
3183 * item that was selected when the listbox was made visible.
3185 mousePos.x = (INT16)LOWORD(lParam);
3186 mousePos.y = (INT16)HIWORD(lParam);
3188 GetClientRect(hwnd, &clientRect);
3191 * When the user clicks outside the combobox and the focus
3192 * is lost, the owning combobox will send a fake buttonup with
3193 * 0xFFFFFFF as the mouse location, we must also revert the
3194 * selection to the original selection.
3196 if ( (lParam == (LPARAM)-1) ||
3197 (!PtInRect( &clientRect, mousePos )) )
3199 LISTBOX_MoveCaret( hwnd, descr, lphc->droppedIndex, FALSE );
3202 return LISTBOX_HandleLButtonUp( hwnd, descr );
3203 case WM_LBUTTONDBLCLK:
3204 case WM_LBUTTONDOWN:
3205 return LISTBOX_HandleLButtonDownCombo(hwnd, descr, msg, wParam,
3206 (INT16)LOWORD(lParam),
3207 (INT16)HIWORD(lParam) );
3208 case WM_NCACTIVATE:
3209 return FALSE;
3210 case WM_KEYDOWN:
3211 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3213 /* for some reason(?) Windows makes it possible to
3214 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3216 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3217 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3218 && (wParam == VK_DOWN || wParam == VK_UP)) )
3220 COMBO_FlipListbox( lphc, FALSE, FALSE );
3221 return 0;
3224 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3226 case LB_SETCURSEL16:
3227 case LB_SETCURSEL:
3228 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3229 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3230 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3231 return lRet;
3232 case WM_NCDESTROY:
3233 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3234 lphc->hWndLBox = 0;
3235 break;
3239 /* default handling: call listbox wnd proc */
3240 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3241 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3243 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3245 return lRet;
3248 /***********************************************************************
3249 * ComboLBWndProcA
3251 * NOTE: in Windows, winproc address of the ComboLBox is the same
3252 * as that of the Listbox.
3254 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3256 if (!IsWindow(hwnd)) return 0;
3257 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3260 /***********************************************************************
3261 * ComboLBWndProcW
3263 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3265 if (!IsWindow(hwnd)) return 0;
3266 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, TRUE );