cmd/tests: Add MOVE tests.
[wine/multimedia.git] / dlls / user32 / listbox.c
blob6bf24a736e491cd9fcf0eeaec7dbad7e7fd02e00
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * NOTES
22 * This code was audited for completeness against the documented features
23 * of Comctl32.dll version 6.0 on Oct. 9, 2004, by Dimitrie O. Paun.
25 * Unless otherwise noted, we believe this code to be complete, as per
26 * the specification mentioned above.
27 * If you discover missing features, or bugs, please note them below.
29 * TODO:
30 * - GetListBoxInfo()
31 * - LB_GETLISTBOXINFO
32 * - LBS_NODATA
35 #include <string.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include "windef.h"
40 #include "winbase.h"
41 #include "wingdi.h"
42 #include "wine/unicode.h"
43 #include "user_private.h"
44 #include "controls.h"
45 #include "wine/exception.h"
46 #include "wine/debug.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
50 /* Items array granularity */
51 #define LB_ARRAY_GRANULARITY 16
53 /* Scrolling timeout in ms */
54 #define LB_SCROLL_TIMEOUT 50
56 /* Listbox system timer id */
57 #define LB_TIMER_ID 2
59 /* flag listbox changed while setredraw false - internal style */
60 #define LBS_DISPLAYCHANGED 0x80000000
62 /* Item structure */
63 typedef struct
65 LPWSTR str; /* Item text */
66 BOOL selected; /* Is item selected? */
67 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
68 ULONG_PTR data; /* User data */
69 } LB_ITEMDATA;
71 /* Listbox structure */
72 typedef struct
74 HWND self; /* Our own window handle */
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 INT avg_char_width; /* Average width of characters */
93 BOOL caret_on; /* Is caret on? */
94 BOOL captured; /* Is mouse captured? */
95 BOOL in_focus;
96 HFONT font; /* Current font */
97 LCID locale; /* Current locale for string comparisons */
98 LPHEADCOMBO lphc; /* ComboLBox */
99 } LB_DESCR;
102 #define IS_OWNERDRAW(descr) \
103 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
105 #define HAS_STRINGS(descr) \
106 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
109 #define IS_MULTISELECT(descr) \
110 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
111 !((descr)->style & LBS_NOSEL))
113 #define SEND_NOTIFICATION(descr,code) \
114 (SendMessageW( (descr)->owner, WM_COMMAND, \
115 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
117 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
119 /* Current timer status */
120 typedef enum
122 LB_TIMER_NONE,
123 LB_TIMER_UP,
124 LB_TIMER_LEFT,
125 LB_TIMER_DOWN,
126 LB_TIMER_RIGHT
127 } TIMER_DIRECTION;
129 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
131 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
133 /*********************************************************************
134 * listbox class descriptor
136 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
137 const struct builtin_class_descr LISTBOX_builtin_class =
139 listboxW, /* name */
140 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
141 WINPROC_LISTBOX, /* proc */
142 sizeof(LB_DESCR *), /* extra */
143 IDC_ARROW, /* cursor */
144 0 /* brush */
148 /*********************************************************************
149 * combolbox class descriptor
151 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
152 const struct builtin_class_descr COMBOLBOX_builtin_class =
154 combolboxW, /* name */
155 CS_DBLCLKS | CS_SAVEBITS, /* style */
156 WINPROC_LISTBOX, /* proc */
157 sizeof(LB_DESCR *), /* extra */
158 IDC_ARROW, /* cursor */
159 0 /* brush */
163 /***********************************************************************
164 * LISTBOX_GetCurrentPageSize
166 * Return the current page size
168 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
170 INT i, height;
171 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
172 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
174 if ((height += descr->items[i].height) > descr->height) break;
176 if (i == descr->top_item) return 1;
177 else return i - descr->top_item;
181 /***********************************************************************
182 * LISTBOX_GetMaxTopIndex
184 * Return the maximum possible index for the top of the listbox.
186 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
188 INT max, page;
190 if (descr->style & LBS_OWNERDRAWVARIABLE)
192 page = descr->height;
193 for (max = descr->nb_items - 1; max >= 0; max--)
194 if ((page -= descr->items[max].height) < 0) break;
195 if (max < descr->nb_items - 1) max++;
197 else if (descr->style & LBS_MULTICOLUMN)
199 if ((page = descr->width / descr->column_width) < 1) page = 1;
200 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
201 max = (max - page) * descr->page_size;
203 else
205 max = descr->nb_items - descr->page_size;
207 if (max < 0) max = 0;
208 return max;
212 /***********************************************************************
213 * LISTBOX_UpdateScroll
215 * Update the scrollbars. Should be called whenever the content
216 * of the listbox changes.
218 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
220 SCROLLINFO info;
222 /* Check the listbox scroll bar flags individually before we call
223 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
224 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
225 scroll bar when we do not need one.
226 if (!(descr->style & WS_VSCROLL)) return;
229 /* It is important that we check descr->style, and not wnd->dwStyle,
230 for WS_VSCROLL, as the former is exactly the one passed in
231 argument to CreateWindow.
232 In Windows (and from now on in Wine :) a listbox created
233 with such a style (no WS_SCROLL) does not update
234 the scrollbar with listbox-related data, thus letting
235 the programmer use it for his/her own purposes. */
237 if (descr->style & LBS_NOREDRAW) return;
238 info.cbSize = sizeof(info);
240 if (descr->style & LBS_MULTICOLUMN)
242 info.nMin = 0;
243 info.nMax = (descr->nb_items - 1) / descr->page_size;
244 info.nPos = descr->top_item / descr->page_size;
245 info.nPage = descr->width / descr->column_width;
246 if (info.nPage < 1) info.nPage = 1;
247 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
248 if (descr->style & LBS_DISABLENOSCROLL)
249 info.fMask |= SIF_DISABLENOSCROLL;
250 if (descr->style & WS_HSCROLL)
251 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
252 info.nMax = 0;
253 info.fMask = SIF_RANGE;
254 if (descr->style & WS_VSCROLL)
255 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
257 else
259 info.nMin = 0;
260 info.nMax = descr->nb_items - 1;
261 info.nPos = descr->top_item;
262 info.nPage = LISTBOX_GetCurrentPageSize( descr );
263 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
264 if (descr->style & LBS_DISABLENOSCROLL)
265 info.fMask |= SIF_DISABLENOSCROLL;
266 if (descr->style & WS_VSCROLL)
267 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
269 if (descr->horz_extent)
271 info.nMin = 0;
272 info.nMax = descr->horz_extent - 1;
273 info.nPos = descr->horz_pos;
274 info.nPage = descr->width;
275 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
276 if (descr->style & LBS_DISABLENOSCROLL)
277 info.fMask |= SIF_DISABLENOSCROLL;
278 if (descr->style & WS_HSCROLL)
279 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
285 /***********************************************************************
286 * LISTBOX_SetTopItem
288 * Set the top item of the listbox, scrolling up or down if necessary.
290 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
292 INT max = LISTBOX_GetMaxTopIndex( descr );
294 TRACE("setting top item %d, scroll %d\n", index, scroll);
296 if (index > max) index = max;
297 if (index < 0) index = 0;
298 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
299 if (descr->top_item == index) return LB_OKAY;
300 if (descr->style & LBS_MULTICOLUMN)
302 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
303 if (scroll && (abs(diff) < descr->width))
304 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
305 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
307 else
308 scroll = FALSE;
310 else if (scroll)
312 INT diff;
313 if (descr->style & LBS_OWNERDRAWVARIABLE)
315 INT i;
316 diff = 0;
317 if (index > descr->top_item)
319 for (i = index - 1; i >= descr->top_item; i--)
320 diff -= descr->items[i].height;
322 else
324 for (i = index; i < descr->top_item; i++)
325 diff += descr->items[i].height;
328 else
329 diff = (descr->top_item - index) * descr->item_height;
331 if (abs(diff) < descr->height)
332 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
333 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
334 else
335 scroll = FALSE;
337 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
338 descr->top_item = index;
339 LISTBOX_UpdateScroll( descr );
340 return LB_OKAY;
344 /***********************************************************************
345 * LISTBOX_UpdatePage
347 * Update the page size. Should be called when the size of
348 * the client area or the item height changes.
350 static void LISTBOX_UpdatePage( LB_DESCR *descr )
352 INT page_size;
354 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
355 page_size = 1;
356 if (page_size == descr->page_size) return;
357 descr->page_size = page_size;
358 if (descr->style & LBS_MULTICOLUMN)
359 InvalidateRect( descr->self, NULL, TRUE );
360 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
364 /***********************************************************************
365 * LISTBOX_UpdateSize
367 * Update the size of the listbox. Should be called when the size of
368 * the client area changes.
370 static void LISTBOX_UpdateSize( LB_DESCR *descr )
372 RECT rect;
374 GetClientRect( descr->self, &rect );
375 descr->width = rect.right - rect.left;
376 descr->height = rect.bottom - rect.top;
377 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
379 INT remaining;
380 RECT rect;
382 GetWindowRect( descr->self, &rect );
383 if(descr->item_height != 0)
384 remaining = descr->height % descr->item_height;
385 else
386 remaining = 0;
387 if ((descr->height > descr->item_height) && remaining)
389 TRACE("[%p]: changing height %d -> %d\n",
390 descr->self, descr->height, descr->height - remaining );
391 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
392 rect.bottom - rect.top - remaining,
393 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
394 return;
397 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
398 LISTBOX_UpdatePage( descr );
399 LISTBOX_UpdateScroll( descr );
401 /* Invalidate the focused item so it will be repainted correctly */
402 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
404 InvalidateRect( descr->self, &rect, FALSE );
409 /***********************************************************************
410 * LISTBOX_GetItemRect
412 * Get the rectangle enclosing an item, in listbox client coordinates.
413 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
415 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
417 /* Index <= 0 is legal even on empty listboxes */
418 if (index && (index >= descr->nb_items))
420 memset(rect, 0, sizeof(*rect));
421 SetLastError(ERROR_INVALID_INDEX);
422 return LB_ERR;
424 SetRect( rect, 0, 0, descr->width, descr->height );
425 if (descr->style & LBS_MULTICOLUMN)
427 INT col = (index / descr->page_size) -
428 (descr->top_item / descr->page_size);
429 rect->left += col * descr->column_width;
430 rect->right = rect->left + descr->column_width;
431 rect->top += (index % descr->page_size) * descr->item_height;
432 rect->bottom = rect->top + descr->item_height;
434 else if (descr->style & LBS_OWNERDRAWVARIABLE)
436 INT i;
437 rect->right += descr->horz_pos;
438 if ((index >= 0) && (index < descr->nb_items))
440 if (index < descr->top_item)
442 for (i = descr->top_item-1; i >= index; i--)
443 rect->top -= descr->items[i].height;
445 else
447 for (i = descr->top_item; i < index; i++)
448 rect->top += descr->items[i].height;
450 rect->bottom = rect->top + descr->items[index].height;
454 else
456 rect->top += (index - descr->top_item) * descr->item_height;
457 rect->bottom = rect->top + descr->item_height;
458 rect->right += descr->horz_pos;
461 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
463 return ((rect->left < descr->width) && (rect->right > 0) &&
464 (rect->top < descr->height) && (rect->bottom > 0));
468 /***********************************************************************
469 * LISTBOX_GetItemFromPoint
471 * Return the item nearest from point (x,y) (in client coordinates).
473 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
475 INT index = descr->top_item;
477 if (!descr->nb_items) return -1; /* No items */
478 if (descr->style & LBS_OWNERDRAWVARIABLE)
480 INT pos = 0;
481 if (y >= 0)
483 while (index < descr->nb_items)
485 if ((pos += descr->items[index].height) > y) break;
486 index++;
489 else
491 while (index > 0)
493 index--;
494 if ((pos -= descr->items[index].height) <= y) break;
498 else if (descr->style & LBS_MULTICOLUMN)
500 if (y >= descr->item_height * descr->page_size) return -1;
501 if (y >= 0) index += y / descr->item_height;
502 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
503 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
505 else
507 index += (y / descr->item_height);
509 if (index < 0) return 0;
510 if (index >= descr->nb_items) return -1;
511 return index;
515 /***********************************************************************
516 * LISTBOX_PaintItem
518 * Paint an item.
520 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
521 INT index, UINT action, BOOL ignoreFocus )
523 LB_ITEMDATA *item = NULL;
524 if (index < descr->nb_items) item = &descr->items[index];
526 if (IS_OWNERDRAW(descr))
528 DRAWITEMSTRUCT dis;
529 RECT r;
530 HRGN hrgn;
532 if (!item)
534 if (action == ODA_FOCUS)
535 DrawFocusRect( hdc, rect );
536 else
537 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
538 return;
541 /* some programs mess with the clipping region when
542 drawing the item, *and* restore the previous region
543 after they are done, so a region has better to exist
544 else everything ends clipped */
545 GetClientRect(descr->self, &r);
546 hrgn = set_control_clipping( hdc, &r );
548 dis.CtlType = ODT_LISTBOX;
549 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
550 dis.hwndItem = descr->self;
551 dis.itemAction = action;
552 dis.hDC = hdc;
553 dis.itemID = index;
554 dis.itemState = 0;
555 if (item->selected) dis.itemState |= ODS_SELECTED;
556 if (!ignoreFocus && (descr->focus_item == index) &&
557 (descr->caret_on) &&
558 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
559 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
560 dis.itemData = item->data;
561 dis.rcItem = *rect;
562 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
563 descr->self, index, debugstr_w(item->str), action,
564 dis.itemState, wine_dbgstr_rect(rect) );
565 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
566 SelectClipRgn( hdc, hrgn );
567 if (hrgn) DeleteObject( hrgn );
569 else
571 COLORREF oldText = 0, oldBk = 0;
573 if (action == ODA_FOCUS)
575 DrawFocusRect( hdc, rect );
576 return;
578 if (item && item->selected)
580 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
581 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
584 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
585 descr->self, index, item ? debugstr_w(item->str) : "", action,
586 wine_dbgstr_rect(rect) );
587 if (!item)
588 ExtTextOutW( hdc, rect->left + 1, rect->top,
589 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
590 else if (!(descr->style & LBS_USETABSTOPS))
591 ExtTextOutW( hdc, rect->left + 1, rect->top,
592 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
593 strlenW(item->str), NULL );
594 else
596 /* Output empty string to paint background in the full width. */
597 ExtTextOutW( hdc, rect->left + 1, rect->top,
598 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
599 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
600 item->str, strlenW(item->str),
601 descr->nb_tabs, descr->tabs, 0);
603 if (item && item->selected)
605 SetBkColor( hdc, oldBk );
606 SetTextColor( hdc, oldText );
608 if (!ignoreFocus && (descr->focus_item == index) &&
609 (descr->caret_on) &&
610 (descr->in_focus)) DrawFocusRect( hdc, rect );
615 /***********************************************************************
616 * LISTBOX_SetRedraw
618 * Change the redraw flag.
620 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
622 if (on)
624 if (!(descr->style & LBS_NOREDRAW)) return;
625 descr->style &= ~LBS_NOREDRAW;
626 if (descr->style & LBS_DISPLAYCHANGED)
627 { /* page was changed while setredraw false, refresh automatically */
628 InvalidateRect(descr->self, NULL, TRUE);
629 if ((descr->top_item + descr->page_size) > descr->nb_items)
630 { /* reset top of page if less than number of items/page */
631 descr->top_item = descr->nb_items - descr->page_size;
632 if (descr->top_item < 0) descr->top_item = 0;
634 descr->style &= ~LBS_DISPLAYCHANGED;
636 LISTBOX_UpdateScroll( descr );
638 else descr->style |= LBS_NOREDRAW;
642 /***********************************************************************
643 * LISTBOX_RepaintItem
645 * Repaint a single item synchronously.
647 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
649 HDC hdc;
650 RECT rect;
651 HFONT oldFont = 0;
652 HBRUSH hbrush, oldBrush = 0;
654 /* Do not repaint the item if the item is not visible */
655 if (!IsWindowVisible(descr->self)) return;
656 if (descr->style & LBS_NOREDRAW)
658 descr->style |= LBS_DISPLAYCHANGED;
659 return;
661 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
662 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
663 if (descr->font) oldFont = SelectObject( hdc, descr->font );
664 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
665 (WPARAM)hdc, (LPARAM)descr->self );
666 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
667 if (!IsWindowEnabled(descr->self))
668 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
669 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
670 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
671 if (oldFont) SelectObject( hdc, oldFont );
672 if (oldBrush) SelectObject( hdc, oldBrush );
673 ReleaseDC( descr->self, hdc );
677 /***********************************************************************
678 * LISTBOX_DrawFocusRect
680 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
682 HDC hdc;
683 RECT rect;
684 HFONT oldFont = 0;
686 /* Do not repaint the item if the item is not visible */
687 if (!IsWindowVisible(descr->self)) return;
689 if (descr->focus_item == -1) return;
690 if (!descr->caret_on || !descr->in_focus) return;
692 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
693 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
694 if (descr->font) oldFont = SelectObject( hdc, descr->font );
695 if (!IsWindowEnabled(descr->self))
696 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
697 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
698 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, on ? FALSE : TRUE );
699 if (oldFont) SelectObject( hdc, oldFont );
700 ReleaseDC( descr->self, hdc );
704 /***********************************************************************
705 * LISTBOX_InitStorage
707 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
709 LB_ITEMDATA *item;
711 nb_items += LB_ARRAY_GRANULARITY - 1;
712 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
713 if (descr->items) {
714 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
715 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
716 nb_items * sizeof(LB_ITEMDATA));
718 else {
719 item = HeapAlloc( GetProcessHeap(), 0,
720 nb_items * sizeof(LB_ITEMDATA));
723 if (!item)
725 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
726 return LB_ERRSPACE;
728 descr->items = item;
729 return LB_OKAY;
733 /***********************************************************************
734 * LISTBOX_SetTabStops
736 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
738 INT i;
740 if (!(descr->style & LBS_USETABSTOPS))
742 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
743 return FALSE;
746 HeapFree( GetProcessHeap(), 0, descr->tabs );
747 if (!(descr->nb_tabs = count))
749 descr->tabs = NULL;
750 return TRUE;
752 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
753 descr->nb_tabs * sizeof(INT) )))
754 return FALSE;
755 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
757 /* convert into "dialog units"*/
758 for (i = 0; i < descr->nb_tabs; i++)
759 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
761 return TRUE;
765 /***********************************************************************
766 * LISTBOX_GetText
768 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
770 DWORD len;
772 if ((index < 0) || (index >= descr->nb_items))
774 SetLastError(ERROR_INVALID_INDEX);
775 return LB_ERR;
777 if (HAS_STRINGS(descr))
779 if (!buffer)
781 len = strlenW(descr->items[index].str);
782 if( unicode )
783 return len;
784 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
785 NULL, 0, NULL, NULL );
788 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
790 __TRY /* hide a Delphi bug that passes a read-only buffer */
792 if(unicode)
794 strcpyW( buffer, descr->items[index].str );
795 len = strlenW(buffer);
797 else
799 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
800 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
803 __EXCEPT_PAGE_FAULT
805 WARN( "got an invalid buffer (Delphi bug?)\n" );
806 SetLastError( ERROR_INVALID_PARAMETER );
807 return LB_ERR;
809 __ENDTRY
810 } else {
811 if (buffer)
812 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
813 len = sizeof(DWORD);
815 return len;
818 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
820 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
821 if (ret == CSTR_LESS_THAN)
822 return -1;
823 if (ret == CSTR_EQUAL)
824 return 0;
825 if (ret == CSTR_GREATER_THAN)
826 return 1;
827 return -1;
830 /***********************************************************************
831 * LISTBOX_FindStringPos
833 * Find the nearest string located before a given string in sort order.
834 * If 'exact' is TRUE, return an error if we don't get an exact match.
836 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
838 INT index, min, max, res = -1;
840 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
841 min = 0;
842 max = descr->nb_items;
843 while (min != max)
845 index = (min + max) / 2;
846 if (HAS_STRINGS(descr))
847 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
848 else
850 COMPAREITEMSTRUCT cis;
851 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
853 cis.CtlType = ODT_LISTBOX;
854 cis.CtlID = id;
855 cis.hwndItem = descr->self;
856 /* note that some application (MetaStock) expects the second item
857 * to be in the listbox */
858 cis.itemID1 = -1;
859 cis.itemData1 = (ULONG_PTR)str;
860 cis.itemID2 = index;
861 cis.itemData2 = descr->items[index].data;
862 cis.dwLocaleId = descr->locale;
863 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
865 if (!res) return index;
866 if (res < 0) max = index;
867 else min = index + 1;
869 return exact ? -1 : max;
873 /***********************************************************************
874 * LISTBOX_FindFileStrPos
876 * Find the nearest string located before a given string in directory
877 * sort order (i.e. first files, then directories, then drives).
879 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
881 INT min, max, res = -1;
883 if (!HAS_STRINGS(descr))
884 return LISTBOX_FindStringPos( descr, str, FALSE );
885 min = 0;
886 max = descr->nb_items;
887 while (min != max)
889 INT index = (min + max) / 2;
890 LPCWSTR p = descr->items[index].str;
891 if (*p == '[') /* drive or directory */
893 if (*str != '[') res = -1;
894 else if (p[1] == '-') /* drive */
896 if (str[1] == '-') res = str[2] - p[2];
897 else res = -1;
899 else /* directory */
901 if (str[1] == '-') res = 1;
902 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
905 else /* filename */
907 if (*str == '[') res = 1;
908 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
910 if (!res) return index;
911 if (res < 0) max = index;
912 else min = index + 1;
914 return max;
918 /***********************************************************************
919 * LISTBOX_FindString
921 * Find the item beginning with a given string.
923 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
925 INT i;
926 LB_ITEMDATA *item;
928 if (start >= descr->nb_items) start = -1;
929 item = descr->items + start + 1;
930 if (HAS_STRINGS(descr))
932 if (!str || ! str[0] ) return LB_ERR;
933 if (exact)
935 for (i = start + 1; i < descr->nb_items; i++, item++)
936 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
937 for (i = 0, item = descr->items; i <= start; i++, item++)
938 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
940 else
942 /* Special case for drives and directories: ignore prefix */
943 #define CHECK_DRIVE(item) \
944 if ((item)->str[0] == '[') \
946 if (!strncmpiW( str, (item)->str+1, len )) return i; \
947 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
948 return i; \
951 INT len = strlenW(str);
952 for (i = start + 1; i < descr->nb_items; i++, item++)
954 if (!strncmpiW( str, item->str, len )) return i;
955 CHECK_DRIVE(item);
957 for (i = 0, item = descr->items; i <= start; i++, item++)
959 if (!strncmpiW( str, item->str, len )) return i;
960 CHECK_DRIVE(item);
962 #undef CHECK_DRIVE
965 else
967 if (exact && (descr->style & LBS_SORT))
968 /* If sorted, use a WM_COMPAREITEM binary search */
969 return LISTBOX_FindStringPos( descr, str, TRUE );
971 /* Otherwise use a linear search */
972 for (i = start + 1; i < descr->nb_items; i++, item++)
973 if (item->data == (ULONG_PTR)str) return i;
974 for (i = 0, item = descr->items; i <= start; i++, item++)
975 if (item->data == (ULONG_PTR)str) return i;
977 return LB_ERR;
981 /***********************************************************************
982 * LISTBOX_GetSelCount
984 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
986 INT i, count;
987 const LB_ITEMDATA *item = descr->items;
989 if (!(descr->style & LBS_MULTIPLESEL) ||
990 (descr->style & LBS_NOSEL))
991 return LB_ERR;
992 for (i = count = 0; i < descr->nb_items; i++, item++)
993 if (item->selected) count++;
994 return count;
998 /***********************************************************************
999 * LISTBOX_GetSelItems
1001 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1003 INT i, count;
1004 const LB_ITEMDATA *item = descr->items;
1006 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1007 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1008 if (item->selected) array[count++] = i;
1009 return count;
1013 /***********************************************************************
1014 * LISTBOX_Paint
1016 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1018 INT i, col_pos = descr->page_size - 1;
1019 RECT rect;
1020 RECT focusRect = {-1, -1, -1, -1};
1021 HFONT oldFont = 0;
1022 HBRUSH hbrush, oldBrush = 0;
1024 if (descr->style & LBS_NOREDRAW) return 0;
1026 SetRect( &rect, 0, 0, descr->width, descr->height );
1027 if (descr->style & LBS_MULTICOLUMN)
1028 rect.right = rect.left + descr->column_width;
1029 else if (descr->horz_pos)
1031 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1032 rect.right += descr->horz_pos;
1035 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1036 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1037 (WPARAM)hdc, (LPARAM)descr->self );
1038 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1039 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1041 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1042 (descr->in_focus))
1044 /* Special case for empty listbox: paint focus rect */
1045 rect.bottom = rect.top + descr->item_height;
1046 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1047 &rect, NULL, 0, NULL );
1048 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1049 rect.top = rect.bottom;
1052 /* Paint all the item, regarding the selection
1053 Focus state will be painted after */
1055 for (i = descr->top_item; i < descr->nb_items; i++)
1057 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1058 rect.bottom = rect.top + descr->item_height;
1059 else
1060 rect.bottom = rect.top + descr->items[i].height;
1062 if (i == descr->focus_item)
1064 /* keep the focus rect, to paint the focus item after */
1065 focusRect.left = rect.left;
1066 focusRect.right = rect.right;
1067 focusRect.top = rect.top;
1068 focusRect.bottom = rect.bottom;
1070 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1071 rect.top = rect.bottom;
1073 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1075 if (!IS_OWNERDRAW(descr))
1077 /* Clear the bottom of the column */
1078 if (rect.top < descr->height)
1080 rect.bottom = descr->height;
1081 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1082 &rect, NULL, 0, NULL );
1086 /* Go to the next column */
1087 rect.left += descr->column_width;
1088 rect.right += descr->column_width;
1089 rect.top = 0;
1090 col_pos = descr->page_size - 1;
1092 else
1094 col_pos--;
1095 if (rect.top >= descr->height) break;
1099 /* Paint the focus item now */
1100 if (focusRect.top != focusRect.bottom &&
1101 descr->caret_on && descr->in_focus)
1102 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1104 if (!IS_OWNERDRAW(descr))
1106 /* Clear the remainder of the client area */
1107 if (rect.top < descr->height)
1109 rect.bottom = descr->height;
1110 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1111 &rect, NULL, 0, NULL );
1113 if (rect.right < descr->width)
1115 rect.left = rect.right;
1116 rect.right = descr->width;
1117 rect.top = 0;
1118 rect.bottom = descr->height;
1119 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1120 &rect, NULL, 0, NULL );
1123 if (oldFont) SelectObject( hdc, oldFont );
1124 if (oldBrush) SelectObject( hdc, oldBrush );
1125 return 0;
1129 /***********************************************************************
1130 * LISTBOX_InvalidateItems
1132 * Invalidate all items from a given item. If the specified item is not
1133 * visible, nothing happens.
1135 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1137 RECT rect;
1139 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1141 if (descr->style & LBS_NOREDRAW)
1143 descr->style |= LBS_DISPLAYCHANGED;
1144 return;
1146 rect.bottom = descr->height;
1147 InvalidateRect( descr->self, &rect, TRUE );
1148 if (descr->style & LBS_MULTICOLUMN)
1150 /* Repaint the other columns */
1151 rect.left = rect.right;
1152 rect.right = descr->width;
1153 rect.top = 0;
1154 InvalidateRect( descr->self, &rect, TRUE );
1159 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1161 RECT rect;
1163 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1164 InvalidateRect( descr->self, &rect, TRUE );
1167 /***********************************************************************
1168 * LISTBOX_GetItemHeight
1170 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1172 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1174 if ((index < 0) || (index >= descr->nb_items))
1176 SetLastError(ERROR_INVALID_INDEX);
1177 return LB_ERR;
1179 return descr->items[index].height;
1181 else return descr->item_height;
1185 /***********************************************************************
1186 * LISTBOX_SetItemHeight
1188 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1190 if (height > MAXBYTE)
1191 return -1;
1193 if (!height) height = 1;
1195 if (descr->style & LBS_OWNERDRAWVARIABLE)
1197 if ((index < 0) || (index >= descr->nb_items))
1199 SetLastError(ERROR_INVALID_INDEX);
1200 return LB_ERR;
1202 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1203 descr->items[index].height = height;
1204 LISTBOX_UpdateScroll( descr );
1205 if (repaint)
1206 LISTBOX_InvalidateItems( descr, index );
1208 else if (height != descr->item_height)
1210 TRACE("[%p]: new height = %d\n", descr->self, height );
1211 descr->item_height = height;
1212 LISTBOX_UpdatePage( descr );
1213 LISTBOX_UpdateScroll( descr );
1214 if (repaint)
1215 InvalidateRect( descr->self, 0, TRUE );
1217 return LB_OKAY;
1221 /***********************************************************************
1222 * LISTBOX_SetHorizontalPos
1224 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1226 INT diff;
1228 if (pos > descr->horz_extent - descr->width)
1229 pos = descr->horz_extent - descr->width;
1230 if (pos < 0) pos = 0;
1231 if (!(diff = descr->horz_pos - pos)) return;
1232 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1233 descr->horz_pos = pos;
1234 LISTBOX_UpdateScroll( descr );
1235 if (abs(diff) < descr->width)
1237 RECT rect;
1238 /* Invalidate the focused item so it will be repainted correctly */
1239 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1240 InvalidateRect( descr->self, &rect, TRUE );
1241 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1242 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1244 else
1245 InvalidateRect( descr->self, NULL, TRUE );
1249 /***********************************************************************
1250 * LISTBOX_SetHorizontalExtent
1252 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1254 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1255 return LB_OKAY;
1256 if (extent <= 0) extent = 1;
1257 if (extent == descr->horz_extent) return LB_OKAY;
1258 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1259 descr->horz_extent = extent;
1260 if (descr->horz_pos > extent - descr->width)
1261 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1262 else
1263 LISTBOX_UpdateScroll( descr );
1264 return LB_OKAY;
1268 /***********************************************************************
1269 * LISTBOX_SetColumnWidth
1271 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1273 if (width == descr->column_width) return LB_OKAY;
1274 TRACE("[%p]: new column width = %d\n", descr->self, width );
1275 descr->column_width = width;
1276 LISTBOX_UpdatePage( descr );
1277 return LB_OKAY;
1281 /***********************************************************************
1282 * LISTBOX_SetFont
1284 * Returns the item height.
1286 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1288 HDC hdc;
1289 HFONT oldFont = 0;
1290 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1291 SIZE sz;
1293 descr->font = font;
1295 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1297 ERR("unable to get DC.\n" );
1298 return 16;
1300 if (font) oldFont = SelectObject( hdc, font );
1301 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1302 if (oldFont) SelectObject( hdc, oldFont );
1303 ReleaseDC( descr->self, hdc );
1305 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1306 if (!IS_OWNERDRAW(descr))
1307 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1308 return sz.cy;
1312 /***********************************************************************
1313 * LISTBOX_MakeItemVisible
1315 * Make sure that a given item is partially or fully visible.
1317 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1319 INT top;
1321 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1323 if (index <= descr->top_item) top = index;
1324 else if (descr->style & LBS_MULTICOLUMN)
1326 INT cols = descr->width;
1327 if (!fully) cols += descr->column_width - 1;
1328 if (cols >= descr->column_width) cols /= descr->column_width;
1329 else cols = 1;
1330 if (index < descr->top_item + (descr->page_size * cols)) return;
1331 top = index - descr->page_size * (cols - 1);
1333 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1335 INT height = fully ? descr->items[index].height : 1;
1336 for (top = index; top > descr->top_item; top--)
1337 if ((height += descr->items[top-1].height) > descr->height) break;
1339 else
1341 if (index < descr->top_item + descr->page_size) return;
1342 if (!fully && (index == descr->top_item + descr->page_size) &&
1343 (descr->height > (descr->page_size * descr->item_height))) return;
1344 top = index - descr->page_size + 1;
1346 LISTBOX_SetTopItem( descr, top, TRUE );
1349 /***********************************************************************
1350 * LISTBOX_SetCaretIndex
1352 * NOTES
1353 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1356 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1358 INT oldfocus = descr->focus_item;
1360 TRACE("old focus %d, index %d\n", oldfocus, index);
1362 if (descr->style & LBS_NOSEL) return LB_ERR;
1363 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1364 if (index == oldfocus) return LB_OKAY;
1366 LISTBOX_DrawFocusRect( descr, FALSE );
1367 descr->focus_item = index;
1369 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1370 LISTBOX_DrawFocusRect( descr, TRUE );
1372 return LB_OKAY;
1376 /***********************************************************************
1377 * LISTBOX_SelectItemRange
1379 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1381 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1382 INT last, BOOL on )
1384 INT i;
1386 /* A few sanity checks */
1388 if (descr->style & LBS_NOSEL) return LB_ERR;
1389 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1391 if (!descr->nb_items) return LB_OKAY;
1393 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1394 if (first < 0) first = 0;
1395 if (last < first) return LB_OKAY;
1397 if (on) /* Turn selection on */
1399 for (i = first; i <= last; i++)
1401 if (descr->items[i].selected) continue;
1402 descr->items[i].selected = TRUE;
1403 LISTBOX_InvalidateItemRect(descr, i);
1406 else /* Turn selection off */
1408 for (i = first; i <= last; i++)
1410 if (!descr->items[i].selected) continue;
1411 descr->items[i].selected = FALSE;
1412 LISTBOX_InvalidateItemRect(descr, i);
1415 return LB_OKAY;
1418 /***********************************************************************
1419 * LISTBOX_SetSelection
1421 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1422 BOOL on, BOOL send_notify )
1424 TRACE( "cur_sel=%d index=%d notify=%s\n",
1425 descr->selected_item, index, send_notify ? "YES" : "NO" );
1427 if (descr->style & LBS_NOSEL)
1429 descr->selected_item = index;
1430 return LB_ERR;
1432 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1433 if (descr->style & LBS_MULTIPLESEL)
1435 if (index == -1) /* Select all items */
1436 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1437 else /* Only one item */
1438 return LISTBOX_SelectItemRange( descr, index, index, on );
1440 else
1442 INT oldsel = descr->selected_item;
1443 if (index == oldsel) return LB_OKAY;
1444 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1445 if (index != -1) descr->items[index].selected = TRUE;
1446 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1447 descr->selected_item = index;
1448 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1449 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1450 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1451 else
1452 if( descr->lphc ) /* set selection change flag for parent combo */
1453 descr->lphc->wState |= CBF_SELCHANGE;
1455 return LB_OKAY;
1459 /***********************************************************************
1460 * LISTBOX_MoveCaret
1462 * Change the caret position and extend the selection to the new caret.
1464 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1466 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1468 if ((index < 0) || (index >= descr->nb_items))
1469 return;
1471 /* Important, repaint needs to be done in this order if
1472 you want to mimic Windows behavior:
1473 1. Remove the focus and paint the item
1474 2. Remove the selection and paint the item(s)
1475 3. Set the selection and repaint the item(s)
1476 4. Set the focus to 'index' and repaint the item */
1478 /* 1. remove the focus and repaint the item */
1479 LISTBOX_DrawFocusRect( descr, FALSE );
1481 /* 2. then turn off the previous selection */
1482 /* 3. repaint the new selected item */
1483 if (descr->style & LBS_EXTENDEDSEL)
1485 if (descr->anchor_item != -1)
1487 INT first = min( index, descr->anchor_item );
1488 INT last = max( index, descr->anchor_item );
1489 if (first > 0)
1490 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1491 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1492 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1495 else if (!(descr->style & LBS_MULTIPLESEL))
1497 /* Set selection to new caret item */
1498 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1501 /* 4. repaint the new item with the focus */
1502 descr->focus_item = index;
1503 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1504 LISTBOX_DrawFocusRect( descr, TRUE );
1508 /***********************************************************************
1509 * LISTBOX_InsertItem
1511 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1512 LPWSTR str, ULONG_PTR data )
1514 LB_ITEMDATA *item;
1515 INT max_items;
1516 INT oldfocus = descr->focus_item;
1518 if (index == -1) index = descr->nb_items;
1519 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1520 if (!descr->items) max_items = 0;
1521 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1522 if (descr->nb_items == max_items)
1524 /* We need to grow the array */
1525 max_items += LB_ARRAY_GRANULARITY;
1526 if (descr->items)
1527 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1528 max_items * sizeof(LB_ITEMDATA) );
1529 else
1530 item = HeapAlloc( GetProcessHeap(), 0,
1531 max_items * sizeof(LB_ITEMDATA) );
1532 if (!item)
1534 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1535 return LB_ERRSPACE;
1537 descr->items = item;
1540 /* Insert the item structure */
1542 item = &descr->items[index];
1543 if (index < descr->nb_items)
1544 RtlMoveMemory( item + 1, item,
1545 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1546 item->str = str;
1547 item->data = data;
1548 item->height = 0;
1549 item->selected = FALSE;
1550 descr->nb_items++;
1552 /* Get item height */
1554 if (descr->style & LBS_OWNERDRAWVARIABLE)
1556 MEASUREITEMSTRUCT mis;
1557 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1559 mis.CtlType = ODT_LISTBOX;
1560 mis.CtlID = id;
1561 mis.itemID = index;
1562 mis.itemData = descr->items[index].data;
1563 mis.itemHeight = descr->item_height;
1564 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1565 item->height = mis.itemHeight ? mis.itemHeight : 1;
1566 TRACE("[%p]: measure item %d (%s) = %d\n",
1567 descr->self, index, str ? debugstr_w(str) : "", item->height );
1570 /* Repaint the items */
1572 LISTBOX_UpdateScroll( descr );
1573 LISTBOX_InvalidateItems( descr, index );
1575 /* Move selection and focused item */
1576 /* If listbox was empty, set focus to the first item */
1577 if (descr->nb_items == 1)
1578 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1579 /* single select don't change selection index in win31 */
1580 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1582 descr->selected_item++;
1583 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1585 else
1587 if (index <= descr->selected_item)
1589 descr->selected_item++;
1590 descr->focus_item = oldfocus; /* focus not changed */
1593 return LB_OKAY;
1597 /***********************************************************************
1598 * LISTBOX_InsertString
1600 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1602 LPWSTR new_str = NULL;
1603 ULONG_PTR data = 0;
1604 LRESULT ret;
1606 if (HAS_STRINGS(descr))
1608 static const WCHAR empty_stringW[] = { 0 };
1609 if (!str) str = empty_stringW;
1610 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1612 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1613 return LB_ERRSPACE;
1615 strcpyW(new_str, str);
1617 else data = (ULONG_PTR)str;
1619 if (index == -1) index = descr->nb_items;
1620 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1622 HeapFree( GetProcessHeap(), 0, new_str );
1623 return ret;
1626 TRACE("[%p]: added item %d %s\n",
1627 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1628 return index;
1632 /***********************************************************************
1633 * LISTBOX_DeleteItem
1635 * Delete the content of an item. 'index' must be a valid index.
1637 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1639 /* save the item data before it gets freed by LB_RESETCONTENT */
1640 ULONG_PTR item_data = descr->items[index].data;
1641 LPWSTR item_str = descr->items[index].str;
1643 if (!descr->nb_items)
1644 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1646 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1647 * while Win95 sends it for all items with user data.
1648 * It's probably better to send it too often than not
1649 * often enough, so this is what we do here.
1651 if (IS_OWNERDRAW(descr) || item_data)
1653 DELETEITEMSTRUCT dis;
1654 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1656 dis.CtlType = ODT_LISTBOX;
1657 dis.CtlID = id;
1658 dis.itemID = index;
1659 dis.hwndItem = descr->self;
1660 dis.itemData = item_data;
1661 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1663 if (HAS_STRINGS(descr))
1664 HeapFree( GetProcessHeap(), 0, item_str );
1668 /***********************************************************************
1669 * LISTBOX_RemoveItem
1671 * Remove an item from the listbox and delete its content.
1673 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1675 LB_ITEMDATA *item;
1676 INT max_items;
1678 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1680 /* We need to invalidate the original rect instead of the updated one. */
1681 LISTBOX_InvalidateItems( descr, index );
1683 descr->nb_items--;
1684 LISTBOX_DeleteItem( descr, index );
1686 if (!descr->nb_items) return LB_OKAY;
1688 /* Remove the item */
1690 item = &descr->items[index];
1691 if (index < descr->nb_items)
1692 RtlMoveMemory( item, item + 1,
1693 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1694 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1696 /* Shrink the item array if possible */
1698 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1699 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1701 max_items -= LB_ARRAY_GRANULARITY;
1702 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1703 max_items * sizeof(LB_ITEMDATA) );
1704 if (item) descr->items = item;
1706 /* Repaint the items */
1708 LISTBOX_UpdateScroll( descr );
1709 /* if we removed the scrollbar, reset the top of the list
1710 (correct for owner-drawn ???) */
1711 if (descr->nb_items == descr->page_size)
1712 LISTBOX_SetTopItem( descr, 0, TRUE );
1714 /* Move selection and focused item */
1715 if (!IS_MULTISELECT(descr))
1717 if (index == descr->selected_item)
1718 descr->selected_item = -1;
1719 else if (index < descr->selected_item)
1721 descr->selected_item--;
1722 if (ISWIN31) /* win 31 do not change the selected item number */
1723 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1727 if (descr->focus_item >= descr->nb_items)
1729 descr->focus_item = descr->nb_items - 1;
1730 if (descr->focus_item < 0) descr->focus_item = 0;
1732 return LB_OKAY;
1736 /***********************************************************************
1737 * LISTBOX_ResetContent
1739 static void LISTBOX_ResetContent( LB_DESCR *descr )
1741 INT i;
1743 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1744 HeapFree( GetProcessHeap(), 0, descr->items );
1745 descr->nb_items = 0;
1746 descr->top_item = 0;
1747 descr->selected_item = -1;
1748 descr->focus_item = 0;
1749 descr->anchor_item = -1;
1750 descr->items = NULL;
1754 /***********************************************************************
1755 * LISTBOX_SetCount
1757 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1759 LRESULT ret;
1761 if (HAS_STRINGS(descr))
1763 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1764 return LB_ERR;
1767 /* FIXME: this is far from optimal... */
1768 if (count > descr->nb_items)
1770 while (count > descr->nb_items)
1771 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1772 return ret;
1774 else if (count < descr->nb_items)
1776 while (count < descr->nb_items)
1777 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1778 return ret;
1780 return LB_OKAY;
1784 /***********************************************************************
1785 * LISTBOX_Directory
1787 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1788 LPCWSTR filespec, BOOL long_names )
1790 HANDLE handle;
1791 LRESULT ret = LB_OKAY;
1792 WIN32_FIND_DATAW entry;
1793 int pos;
1794 LRESULT maxinsert = LB_ERR;
1796 /* don't scan directory if we just want drives exclusively */
1797 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1798 /* scan directory */
1799 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1801 int le = GetLastError();
1802 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1804 else
1808 WCHAR buffer[270];
1809 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1811 static const WCHAR bracketW[] = { ']',0 };
1812 static const WCHAR dotW[] = { '.',0 };
1813 if (!(attrib & DDL_DIRECTORY) ||
1814 !strcmpW( entry.cFileName, dotW )) continue;
1815 buffer[0] = '[';
1816 if (!long_names && entry.cAlternateFileName[0])
1817 strcpyW( buffer + 1, entry.cAlternateFileName );
1818 else
1819 strcpyW( buffer + 1, entry.cFileName );
1820 strcatW(buffer, bracketW);
1822 else /* not a directory */
1824 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1825 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1827 if ((attrib & DDL_EXCLUSIVE) &&
1828 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1829 continue;
1830 #undef ATTRIBS
1831 if (!long_names && entry.cAlternateFileName[0])
1832 strcpyW( buffer, entry.cAlternateFileName );
1833 else
1834 strcpyW( buffer, entry.cFileName );
1836 if (!long_names) CharLowerW( buffer );
1837 pos = LISTBOX_FindFileStrPos( descr, buffer );
1838 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1839 break;
1840 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1841 } while (FindNextFileW( handle, &entry ));
1842 FindClose( handle );
1845 if (ret >= 0)
1847 ret = maxinsert;
1849 /* scan drives */
1850 if (attrib & DDL_DRIVES)
1852 WCHAR buffer[] = {'[','-','a','-',']',0};
1853 WCHAR root[] = {'A',':','\\',0};
1854 int drive;
1855 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1857 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1858 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1859 break;
1863 return ret;
1867 /***********************************************************************
1868 * LISTBOX_HandleVScroll
1870 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1872 SCROLLINFO info;
1874 if (descr->style & LBS_MULTICOLUMN) return 0;
1875 switch(scrollReq)
1877 case SB_LINEUP:
1878 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1879 break;
1880 case SB_LINEDOWN:
1881 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1882 break;
1883 case SB_PAGEUP:
1884 LISTBOX_SetTopItem( descr, descr->top_item -
1885 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1886 break;
1887 case SB_PAGEDOWN:
1888 LISTBOX_SetTopItem( descr, descr->top_item +
1889 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1890 break;
1891 case SB_THUMBPOSITION:
1892 LISTBOX_SetTopItem( descr, pos, TRUE );
1893 break;
1894 case SB_THUMBTRACK:
1895 info.cbSize = sizeof(info);
1896 info.fMask = SIF_TRACKPOS;
1897 GetScrollInfo( descr->self, SB_VERT, &info );
1898 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1899 break;
1900 case SB_TOP:
1901 LISTBOX_SetTopItem( descr, 0, TRUE );
1902 break;
1903 case SB_BOTTOM:
1904 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1905 break;
1907 return 0;
1911 /***********************************************************************
1912 * LISTBOX_HandleHScroll
1914 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1916 SCROLLINFO info;
1917 INT page;
1919 if (descr->style & LBS_MULTICOLUMN)
1921 switch(scrollReq)
1923 case SB_LINELEFT:
1924 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1925 TRUE );
1926 break;
1927 case SB_LINERIGHT:
1928 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1929 TRUE );
1930 break;
1931 case SB_PAGELEFT:
1932 page = descr->width / descr->column_width;
1933 if (page < 1) page = 1;
1934 LISTBOX_SetTopItem( descr,
1935 descr->top_item - page * descr->page_size, TRUE );
1936 break;
1937 case SB_PAGERIGHT:
1938 page = descr->width / descr->column_width;
1939 if (page < 1) page = 1;
1940 LISTBOX_SetTopItem( descr,
1941 descr->top_item + page * descr->page_size, TRUE );
1942 break;
1943 case SB_THUMBPOSITION:
1944 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1945 break;
1946 case SB_THUMBTRACK:
1947 info.cbSize = sizeof(info);
1948 info.fMask = SIF_TRACKPOS;
1949 GetScrollInfo( descr->self, SB_VERT, &info );
1950 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1951 TRUE );
1952 break;
1953 case SB_LEFT:
1954 LISTBOX_SetTopItem( descr, 0, TRUE );
1955 break;
1956 case SB_RIGHT:
1957 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1958 break;
1961 else if (descr->horz_extent)
1963 switch(scrollReq)
1965 case SB_LINELEFT:
1966 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1967 break;
1968 case SB_LINERIGHT:
1969 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1970 break;
1971 case SB_PAGELEFT:
1972 LISTBOX_SetHorizontalPos( descr,
1973 descr->horz_pos - descr->width );
1974 break;
1975 case SB_PAGERIGHT:
1976 LISTBOX_SetHorizontalPos( descr,
1977 descr->horz_pos + descr->width );
1978 break;
1979 case SB_THUMBPOSITION:
1980 LISTBOX_SetHorizontalPos( descr, pos );
1981 break;
1982 case SB_THUMBTRACK:
1983 info.cbSize = sizeof(info);
1984 info.fMask = SIF_TRACKPOS;
1985 GetScrollInfo( descr->self, SB_HORZ, &info );
1986 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1987 break;
1988 case SB_LEFT:
1989 LISTBOX_SetHorizontalPos( descr, 0 );
1990 break;
1991 case SB_RIGHT:
1992 LISTBOX_SetHorizontalPos( descr,
1993 descr->horz_extent - descr->width );
1994 break;
1997 return 0;
2000 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2002 short gcWheelDelta = 0;
2003 UINT pulScrollLines = 3;
2005 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2007 gcWheelDelta -= delta;
2009 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
2011 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
2012 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
2013 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
2015 return 0;
2018 /***********************************************************************
2019 * LISTBOX_HandleLButtonDown
2021 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2023 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2025 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2026 descr->self, x, y, index, descr->focus_item);
2028 if (!descr->caret_on && (descr->in_focus)) return 0;
2030 if (!descr->in_focus)
2032 if( !descr->lphc ) SetFocus( descr->self );
2033 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2036 if (index == -1) return 0;
2038 if (!descr->lphc)
2040 if (descr->style & LBS_NOTIFY )
2041 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2042 MAKELPARAM( x, y ) );
2045 descr->captured = TRUE;
2046 SetCapture( descr->self );
2048 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2050 /* we should perhaps make sure that all items are deselected
2051 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2052 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2053 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2056 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2057 if (keys & MK_CONTROL)
2059 LISTBOX_SetCaretIndex( descr, index, FALSE );
2060 LISTBOX_SetSelection( descr, index,
2061 !descr->items[index].selected,
2062 (descr->style & LBS_NOTIFY) != 0);
2064 else
2066 LISTBOX_MoveCaret( descr, index, FALSE );
2068 if (descr->style & LBS_EXTENDEDSEL)
2070 LISTBOX_SetSelection( descr, index,
2071 descr->items[index].selected,
2072 (descr->style & LBS_NOTIFY) != 0 );
2074 else
2076 LISTBOX_SetSelection( descr, index,
2077 !descr->items[index].selected,
2078 (descr->style & LBS_NOTIFY) != 0 );
2082 else
2084 descr->anchor_item = index;
2085 LISTBOX_MoveCaret( descr, index, FALSE );
2086 LISTBOX_SetSelection( descr, index,
2087 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2090 if (!descr->lphc)
2092 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2094 POINT pt;
2096 pt.x = x;
2097 pt.y = y;
2099 if (DragDetect( descr->self, pt ))
2100 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2103 return 0;
2107 /*************************************************************************
2108 * LISTBOX_HandleLButtonDownCombo [Internal]
2110 * Process LButtonDown message for the ComboListBox
2112 * PARAMS
2113 * pWnd [I] The windows internal structure
2114 * pDescr [I] The ListBox internal structure
2115 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2116 * x [I] X Mouse Coordinate
2117 * y [I] Y Mouse Coordinate
2119 * RETURNS
2120 * 0 since we are processing the WM_LBUTTONDOWN Message
2122 * NOTES
2123 * This function is only to be used when a ListBox is a ComboListBox
2126 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2128 RECT clientRect, screenRect;
2129 POINT mousePos;
2131 mousePos.x = x;
2132 mousePos.y = y;
2134 GetClientRect(descr->self, &clientRect);
2136 if(PtInRect(&clientRect, mousePos))
2138 /* MousePos is in client, resume normal processing */
2139 if (msg == WM_LBUTTONDOWN)
2141 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2142 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2144 else if (descr->style & LBS_NOTIFY)
2145 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2147 else
2149 POINT screenMousePos;
2150 HWND hWndOldCapture;
2152 /* Check the Non-Client Area */
2153 screenMousePos = mousePos;
2154 hWndOldCapture = GetCapture();
2155 ReleaseCapture();
2156 GetWindowRect(descr->self, &screenRect);
2157 ClientToScreen(descr->self, &screenMousePos);
2159 if(!PtInRect(&screenRect, screenMousePos))
2161 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2162 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2163 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2165 else
2167 /* Check to see the NC is a scrollbar */
2168 INT nHitTestType=0;
2169 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2170 /* Check Vertical scroll bar */
2171 if (style & WS_VSCROLL)
2173 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2174 if (PtInRect( &clientRect, mousePos ))
2175 nHitTestType = HTVSCROLL;
2177 /* Check horizontal scroll bar */
2178 if (style & WS_HSCROLL)
2180 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2181 if (PtInRect( &clientRect, mousePos ))
2182 nHitTestType = HTHSCROLL;
2184 /* Windows sends this message when a scrollbar is clicked
2187 if(nHitTestType != 0)
2189 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2190 MAKELONG(screenMousePos.x, screenMousePos.y));
2192 /* Resume the Capture after scrolling is complete
2194 if(hWndOldCapture != 0)
2195 SetCapture(hWndOldCapture);
2198 return 0;
2201 /***********************************************************************
2202 * LISTBOX_HandleLButtonUp
2204 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2206 if (LISTBOX_Timer != LB_TIMER_NONE)
2207 KillSystemTimer( descr->self, LB_TIMER_ID );
2208 LISTBOX_Timer = LB_TIMER_NONE;
2209 if (descr->captured)
2211 descr->captured = FALSE;
2212 if (GetCapture() == descr->self) ReleaseCapture();
2213 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2214 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2216 return 0;
2220 /***********************************************************************
2221 * LISTBOX_HandleTimer
2223 * Handle scrolling upon a timer event.
2224 * Return TRUE if scrolling should continue.
2226 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2228 switch(dir)
2230 case LB_TIMER_UP:
2231 if (descr->top_item) index = descr->top_item - 1;
2232 else index = 0;
2233 break;
2234 case LB_TIMER_LEFT:
2235 if (descr->top_item) index -= descr->page_size;
2236 break;
2237 case LB_TIMER_DOWN:
2238 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2239 if (index == descr->focus_item) index++;
2240 if (index >= descr->nb_items) index = descr->nb_items - 1;
2241 break;
2242 case LB_TIMER_RIGHT:
2243 if (index + descr->page_size < descr->nb_items)
2244 index += descr->page_size;
2245 break;
2246 case LB_TIMER_NONE:
2247 break;
2249 if (index == descr->focus_item) return FALSE;
2250 LISTBOX_MoveCaret( descr, index, FALSE );
2251 return TRUE;
2255 /***********************************************************************
2256 * LISTBOX_HandleSystemTimer
2258 * WM_SYSTIMER handler.
2260 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2262 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2264 KillSystemTimer( descr->self, LB_TIMER_ID );
2265 LISTBOX_Timer = LB_TIMER_NONE;
2267 return 0;
2271 /***********************************************************************
2272 * LISTBOX_HandleMouseMove
2274 * WM_MOUSEMOVE handler.
2276 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2277 INT x, INT y )
2279 INT index;
2280 TIMER_DIRECTION dir = LB_TIMER_NONE;
2282 if (!descr->captured) return;
2284 if (descr->style & LBS_MULTICOLUMN)
2286 if (y < 0) y = 0;
2287 else if (y >= descr->item_height * descr->page_size)
2288 y = descr->item_height * descr->page_size - 1;
2290 if (x < 0)
2292 dir = LB_TIMER_LEFT;
2293 x = 0;
2295 else if (x >= descr->width)
2297 dir = LB_TIMER_RIGHT;
2298 x = descr->width - 1;
2301 else
2303 if (y < 0) dir = LB_TIMER_UP; /* above */
2304 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2307 index = LISTBOX_GetItemFromPoint( descr, x, y );
2308 if (index == -1) index = descr->focus_item;
2309 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2311 /* Start/stop the system timer */
2313 if (dir != LB_TIMER_NONE)
2314 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2315 else if (LISTBOX_Timer != LB_TIMER_NONE)
2316 KillSystemTimer( descr->self, LB_TIMER_ID );
2317 LISTBOX_Timer = dir;
2321 /***********************************************************************
2322 * LISTBOX_HandleKeyDown
2324 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2326 INT caret = -1;
2327 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2328 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2329 bForceSelection = FALSE; /* only for single select list */
2331 if (descr->style & LBS_WANTKEYBOARDINPUT)
2333 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2334 MAKEWPARAM(LOWORD(key), descr->focus_item),
2335 (LPARAM)descr->self );
2336 if (caret == -2) return 0;
2338 if (caret == -1) switch(key)
2340 case VK_LEFT:
2341 if (descr->style & LBS_MULTICOLUMN)
2343 bForceSelection = FALSE;
2344 if (descr->focus_item >= descr->page_size)
2345 caret = descr->focus_item - descr->page_size;
2346 break;
2348 /* fall through */
2349 case VK_UP:
2350 caret = descr->focus_item - 1;
2351 if (caret < 0) caret = 0;
2352 break;
2353 case VK_RIGHT:
2354 if (descr->style & LBS_MULTICOLUMN)
2356 bForceSelection = FALSE;
2357 if (descr->focus_item + descr->page_size < descr->nb_items)
2358 caret = descr->focus_item + descr->page_size;
2359 break;
2361 /* fall through */
2362 case VK_DOWN:
2363 caret = descr->focus_item + 1;
2364 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2365 break;
2367 case VK_PRIOR:
2368 if (descr->style & LBS_MULTICOLUMN)
2370 INT page = descr->width / descr->column_width;
2371 if (page < 1) page = 1;
2372 caret = descr->focus_item - (page * descr->page_size) + 1;
2374 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2375 if (caret < 0) caret = 0;
2376 break;
2377 case VK_NEXT:
2378 if (descr->style & LBS_MULTICOLUMN)
2380 INT page = descr->width / descr->column_width;
2381 if (page < 1) page = 1;
2382 caret = descr->focus_item + (page * descr->page_size) - 1;
2384 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2385 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2386 break;
2387 case VK_HOME:
2388 caret = 0;
2389 break;
2390 case VK_END:
2391 caret = descr->nb_items - 1;
2392 break;
2393 case VK_SPACE:
2394 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2395 else if (descr->style & LBS_MULTIPLESEL)
2397 LISTBOX_SetSelection( descr, descr->focus_item,
2398 !descr->items[descr->focus_item].selected,
2399 (descr->style & LBS_NOTIFY) != 0 );
2401 break;
2402 default:
2403 bForceSelection = FALSE;
2405 if (bForceSelection) /* focused item is used instead of key */
2406 caret = descr->focus_item;
2407 if (caret >= 0)
2409 if (((descr->style & LBS_EXTENDEDSEL) &&
2410 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2411 !IS_MULTISELECT(descr))
2412 descr->anchor_item = caret;
2413 LISTBOX_MoveCaret( descr, caret, TRUE );
2415 if (descr->style & LBS_MULTIPLESEL)
2416 descr->selected_item = caret;
2417 else
2418 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2419 if (descr->style & LBS_NOTIFY)
2421 if (descr->lphc && IsWindowVisible( descr->self ))
2423 /* make sure that combo parent doesn't hide us */
2424 descr->lphc->wState |= CBF_NOROLLUP;
2426 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2429 return 0;
2433 /***********************************************************************
2434 * LISTBOX_HandleChar
2436 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2438 INT caret = -1;
2439 WCHAR str[2];
2441 str[0] = charW;
2442 str[1] = '\0';
2444 if (descr->style & LBS_WANTKEYBOARDINPUT)
2446 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2447 MAKEWPARAM(charW, descr->focus_item),
2448 (LPARAM)descr->self );
2449 if (caret == -2) return 0;
2451 if (caret == -1)
2452 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2453 if (caret != -1)
2455 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2456 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2457 LISTBOX_MoveCaret( descr, caret, TRUE );
2458 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2459 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2461 return 0;
2465 /***********************************************************************
2466 * LISTBOX_Create
2468 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2470 LB_DESCR *descr;
2471 MEASUREITEMSTRUCT mis;
2472 RECT rect;
2474 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2475 return FALSE;
2477 GetClientRect( hwnd, &rect );
2478 descr->self = hwnd;
2479 descr->owner = GetParent( descr->self );
2480 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2481 descr->width = rect.right - rect.left;
2482 descr->height = rect.bottom - rect.top;
2483 descr->items = NULL;
2484 descr->nb_items = 0;
2485 descr->top_item = 0;
2486 descr->selected_item = -1;
2487 descr->focus_item = 0;
2488 descr->anchor_item = -1;
2489 descr->item_height = 1;
2490 descr->page_size = 1;
2491 descr->column_width = 150;
2492 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2493 descr->horz_pos = 0;
2494 descr->nb_tabs = 0;
2495 descr->tabs = NULL;
2496 descr->caret_on = lphc ? FALSE : TRUE;
2497 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2498 descr->in_focus = FALSE;
2499 descr->captured = FALSE;
2500 descr->font = 0;
2501 descr->locale = GetUserDefaultLCID();
2502 descr->lphc = lphc;
2504 if( lphc )
2506 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2507 descr->owner = lphc->self;
2510 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2512 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2514 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2515 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2516 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2517 descr->item_height = LISTBOX_SetFont( descr, 0 );
2519 if (descr->style & LBS_OWNERDRAWFIXED)
2521 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2523 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2524 descr->item_height = lphc->fixedOwnerDrawHeight;
2526 else
2528 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2529 mis.CtlType = ODT_LISTBOX;
2530 mis.CtlID = id;
2531 mis.itemID = -1;
2532 mis.itemWidth = 0;
2533 mis.itemData = 0;
2534 mis.itemHeight = descr->item_height;
2535 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2536 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2540 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2541 return TRUE;
2545 /***********************************************************************
2546 * LISTBOX_Destroy
2548 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2550 LISTBOX_ResetContent( descr );
2551 SetWindowLongPtrW( descr->self, 0, 0 );
2552 HeapFree( GetProcessHeap(), 0, descr );
2553 return TRUE;
2557 /***********************************************************************
2558 * ListBoxWndProc_common
2560 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2562 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2563 LPHEADCOMBO lphc = 0;
2564 LRESULT ret;
2566 if (!descr)
2568 if (!IsWindow(hwnd)) return 0;
2570 if (msg == WM_CREATE)
2572 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2573 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2574 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2575 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2576 return 0;
2578 /* Ignore all other messages before we get a WM_CREATE */
2579 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2580 DefWindowProcA( hwnd, msg, wParam, lParam );
2582 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2584 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2585 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2587 switch(msg)
2589 case LB_RESETCONTENT:
2590 LISTBOX_ResetContent( descr );
2591 LISTBOX_UpdateScroll( descr );
2592 InvalidateRect( descr->self, NULL, TRUE );
2593 return 0;
2595 case LB_ADDSTRING:
2597 INT ret;
2598 LPWSTR textW;
2599 if(unicode || !HAS_STRINGS(descr))
2600 textW = (LPWSTR)lParam;
2601 else
2603 LPSTR textA = (LPSTR)lParam;
2604 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2605 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2606 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2607 else
2608 return LB_ERRSPACE;
2610 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2611 ret = LISTBOX_InsertString( descr, wParam, textW );
2612 if (!unicode && HAS_STRINGS(descr))
2613 HeapFree(GetProcessHeap(), 0, textW);
2614 return ret;
2617 case LB_INSERTSTRING:
2619 INT ret;
2620 LPWSTR textW;
2621 if(unicode || !HAS_STRINGS(descr))
2622 textW = (LPWSTR)lParam;
2623 else
2625 LPSTR textA = (LPSTR)lParam;
2626 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2627 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2628 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2629 else
2630 return LB_ERRSPACE;
2632 ret = LISTBOX_InsertString( descr, wParam, textW );
2633 if(!unicode && HAS_STRINGS(descr))
2634 HeapFree(GetProcessHeap(), 0, textW);
2635 return ret;
2638 case LB_ADDFILE:
2640 INT ret;
2641 LPWSTR textW;
2642 if(unicode || !HAS_STRINGS(descr))
2643 textW = (LPWSTR)lParam;
2644 else
2646 LPSTR textA = (LPSTR)lParam;
2647 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2648 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2649 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2650 else
2651 return LB_ERRSPACE;
2653 wParam = LISTBOX_FindFileStrPos( descr, textW );
2654 ret = LISTBOX_InsertString( descr, wParam, textW );
2655 if(!unicode && HAS_STRINGS(descr))
2656 HeapFree(GetProcessHeap(), 0, textW);
2657 return ret;
2660 case LB_DELETESTRING:
2661 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2662 return descr->nb_items;
2663 else
2665 SetLastError(ERROR_INVALID_INDEX);
2666 return LB_ERR;
2669 case LB_GETITEMDATA:
2670 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2672 SetLastError(ERROR_INVALID_INDEX);
2673 return LB_ERR;
2675 return descr->items[wParam].data;
2677 case LB_SETITEMDATA:
2678 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2680 SetLastError(ERROR_INVALID_INDEX);
2681 return LB_ERR;
2683 descr->items[wParam].data = lParam;
2684 /* undocumented: returns TRUE, not LB_OKAY (0) */
2685 return TRUE;
2687 case LB_GETCOUNT:
2688 return descr->nb_items;
2690 case LB_GETTEXT:
2691 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2693 case LB_GETTEXTLEN:
2694 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2696 SetLastError(ERROR_INVALID_INDEX);
2697 return LB_ERR;
2699 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2700 if (unicode) return strlenW( descr->items[wParam].str );
2701 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2702 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2704 case LB_GETCURSEL:
2705 if (descr->nb_items == 0)
2706 return LB_ERR;
2707 if (!IS_MULTISELECT(descr))
2708 return descr->selected_item;
2709 if (descr->selected_item != -1)
2710 return descr->selected_item;
2711 return descr->focus_item;
2712 /* otherwise, if the user tries to move the selection with the */
2713 /* arrow keys, we will give the application something to choke on */
2714 case LB_GETTOPINDEX:
2715 return descr->top_item;
2717 case LB_GETITEMHEIGHT:
2718 return LISTBOX_GetItemHeight( descr, wParam );
2720 case LB_SETITEMHEIGHT:
2721 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2723 case LB_ITEMFROMPOINT:
2725 POINT pt;
2726 RECT rect;
2727 int index;
2728 BOOL hit = TRUE;
2730 /* The hiword of the return value is not a client area
2731 hittest as suggested by MSDN, but rather a hittest on
2732 the returned listbox item. */
2734 if(descr->nb_items == 0)
2735 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2737 pt.x = (short)LOWORD(lParam);
2738 pt.y = (short)HIWORD(lParam);
2740 SetRect(&rect, 0, 0, descr->width, descr->height);
2742 if(!PtInRect(&rect, pt))
2744 pt.x = min(pt.x, rect.right - 1);
2745 pt.x = max(pt.x, 0);
2746 pt.y = min(pt.y, rect.bottom - 1);
2747 pt.y = max(pt.y, 0);
2748 hit = FALSE;
2751 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2753 if(index == -1)
2755 index = descr->nb_items - 1;
2756 hit = FALSE;
2758 return MAKELONG(index, hit ? 0 : 1);
2761 case LB_SETCARETINDEX:
2762 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2763 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2764 return LB_ERR;
2765 else if (ISWIN31)
2766 return wParam;
2767 else
2768 return LB_OKAY;
2770 case LB_GETCARETINDEX:
2771 return descr->focus_item;
2773 case LB_SETTOPINDEX:
2774 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2776 case LB_SETCOLUMNWIDTH:
2777 return LISTBOX_SetColumnWidth( descr, wParam );
2779 case LB_GETITEMRECT:
2780 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2782 case LB_FINDSTRING:
2784 INT ret;
2785 LPWSTR textW;
2786 if(unicode || !HAS_STRINGS(descr))
2787 textW = (LPWSTR)lParam;
2788 else
2790 LPSTR textA = (LPSTR)lParam;
2791 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2792 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2793 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2795 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2796 if(!unicode && HAS_STRINGS(descr))
2797 HeapFree(GetProcessHeap(), 0, textW);
2798 return ret;
2801 case LB_FINDSTRINGEXACT:
2803 INT ret;
2804 LPWSTR textW;
2805 if(unicode || !HAS_STRINGS(descr))
2806 textW = (LPWSTR)lParam;
2807 else
2809 LPSTR textA = (LPSTR)lParam;
2810 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2811 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2812 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2814 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2815 if(!unicode && HAS_STRINGS(descr))
2816 HeapFree(GetProcessHeap(), 0, textW);
2817 return ret;
2820 case LB_SELECTSTRING:
2822 INT index;
2823 LPWSTR textW;
2825 if(HAS_STRINGS(descr))
2826 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2827 debugstr_a((LPSTR)lParam));
2828 if(unicode || !HAS_STRINGS(descr))
2829 textW = (LPWSTR)lParam;
2830 else
2832 LPSTR textA = (LPSTR)lParam;
2833 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2834 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2835 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2837 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2838 if(!unicode && HAS_STRINGS(descr))
2839 HeapFree(GetProcessHeap(), 0, textW);
2840 if (index != LB_ERR)
2842 LISTBOX_MoveCaret( descr, index, TRUE );
2843 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2845 return index;
2848 case LB_GETSEL:
2849 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2850 return LB_ERR;
2851 return descr->items[wParam].selected;
2853 case LB_SETSEL:
2854 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2856 case LB_SETCURSEL:
2857 if (IS_MULTISELECT(descr)) return LB_ERR;
2858 LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2859 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2860 if (ret != LB_ERR) ret = descr->selected_item;
2861 return ret;
2863 case LB_GETSELCOUNT:
2864 return LISTBOX_GetSelCount( descr );
2866 case LB_GETSELITEMS:
2867 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2869 case LB_SELITEMRANGE:
2870 if (LOWORD(lParam) <= HIWORD(lParam))
2871 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2872 HIWORD(lParam), wParam );
2873 else
2874 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2875 LOWORD(lParam), wParam );
2877 case LB_SELITEMRANGEEX:
2878 if ((INT)lParam >= (INT)wParam)
2879 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2880 else
2881 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2883 case LB_GETHORIZONTALEXTENT:
2884 return descr->horz_extent;
2886 case LB_SETHORIZONTALEXTENT:
2887 return LISTBOX_SetHorizontalExtent( descr, wParam );
2889 case LB_GETANCHORINDEX:
2890 return descr->anchor_item;
2892 case LB_SETANCHORINDEX:
2893 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2895 SetLastError(ERROR_INVALID_INDEX);
2896 return LB_ERR;
2898 descr->anchor_item = (INT)wParam;
2899 return LB_OKAY;
2901 case LB_DIR:
2903 INT ret;
2904 LPWSTR textW;
2905 if(unicode)
2906 textW = (LPWSTR)lParam;
2907 else
2909 LPSTR textA = (LPSTR)lParam;
2910 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2911 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2912 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2914 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2915 if(!unicode)
2916 HeapFree(GetProcessHeap(), 0, textW);
2917 return ret;
2920 case LB_GETLOCALE:
2921 return descr->locale;
2923 case LB_SETLOCALE:
2925 LCID ret;
2926 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2927 return LB_ERR;
2928 ret = descr->locale;
2929 descr->locale = (LCID)wParam;
2930 return ret;
2933 case LB_INITSTORAGE:
2934 return LISTBOX_InitStorage( descr, wParam );
2936 case LB_SETCOUNT:
2937 return LISTBOX_SetCount( descr, (INT)wParam );
2939 case LB_SETTABSTOPS:
2940 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2942 case LB_CARETON:
2943 if (descr->caret_on)
2944 return LB_OKAY;
2945 descr->caret_on = TRUE;
2946 if ((descr->focus_item != -1) && (descr->in_focus))
2947 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2948 return LB_OKAY;
2950 case LB_CARETOFF:
2951 if (!descr->caret_on)
2952 return LB_OKAY;
2953 descr->caret_on = FALSE;
2954 if ((descr->focus_item != -1) && (descr->in_focus))
2955 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2956 return LB_OKAY;
2958 case LB_GETLISTBOXINFO:
2959 FIXME("LB_GETLISTBOXINFO: stub!\n");
2960 return 0;
2962 case WM_DESTROY:
2963 return LISTBOX_Destroy( descr );
2965 case WM_ENABLE:
2966 InvalidateRect( descr->self, NULL, TRUE );
2967 return 0;
2969 case WM_SETREDRAW:
2970 LISTBOX_SetRedraw( descr, wParam != 0 );
2971 return 0;
2973 case WM_GETDLGCODE:
2974 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2976 case WM_PRINTCLIENT:
2977 case WM_PAINT:
2979 PAINTSTRUCT ps;
2980 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2981 ret = LISTBOX_Paint( descr, hdc );
2982 if( !wParam ) EndPaint( descr->self, &ps );
2984 return ret;
2985 case WM_SIZE:
2986 LISTBOX_UpdateSize( descr );
2987 return 0;
2988 case WM_GETFONT:
2989 return (LRESULT)descr->font;
2990 case WM_SETFONT:
2991 LISTBOX_SetFont( descr, (HFONT)wParam );
2992 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2993 return 0;
2994 case WM_SETFOCUS:
2995 descr->in_focus = TRUE;
2996 descr->caret_on = TRUE;
2997 if (descr->focus_item != -1)
2998 LISTBOX_DrawFocusRect( descr, TRUE );
2999 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3000 return 0;
3001 case WM_KILLFOCUS:
3002 descr->in_focus = FALSE;
3003 if ((descr->focus_item != -1) && descr->caret_on)
3004 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3005 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3006 return 0;
3007 case WM_HSCROLL:
3008 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3009 case WM_VSCROLL:
3010 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3011 case WM_MOUSEWHEEL:
3012 if (wParam & (MK_SHIFT | MK_CONTROL))
3013 return DefWindowProcW( descr->self, msg, wParam, lParam );
3014 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3015 case WM_LBUTTONDOWN:
3016 if (lphc)
3017 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3018 (INT16)LOWORD(lParam),
3019 (INT16)HIWORD(lParam) );
3020 return LISTBOX_HandleLButtonDown( descr, wParam,
3021 (INT16)LOWORD(lParam),
3022 (INT16)HIWORD(lParam) );
3023 case WM_LBUTTONDBLCLK:
3024 if (lphc)
3025 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3026 (INT16)LOWORD(lParam),
3027 (INT16)HIWORD(lParam) );
3028 if (descr->style & LBS_NOTIFY)
3029 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3030 return 0;
3031 case WM_MOUSEMOVE:
3032 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3034 BOOL captured = descr->captured;
3035 POINT mousePos;
3036 RECT clientRect;
3038 mousePos.x = (INT16)LOWORD(lParam);
3039 mousePos.y = (INT16)HIWORD(lParam);
3042 * If we are in a dropdown combobox, we simulate that
3043 * the mouse is captured to show the tracking of the item.
3045 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3046 descr->captured = TRUE;
3048 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3050 descr->captured = captured;
3052 else if (GetCapture() == descr->self)
3054 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3055 (INT16)HIWORD(lParam) );
3057 return 0;
3058 case WM_LBUTTONUP:
3059 if (lphc)
3061 POINT mousePos;
3062 RECT clientRect;
3065 * If the mouse button "up" is not in the listbox,
3066 * we make sure there is no selection by re-selecting the
3067 * item that was selected when the listbox was made visible.
3069 mousePos.x = (INT16)LOWORD(lParam);
3070 mousePos.y = (INT16)HIWORD(lParam);
3072 GetClientRect(descr->self, &clientRect);
3075 * When the user clicks outside the combobox and the focus
3076 * is lost, the owning combobox will send a fake buttonup with
3077 * 0xFFFFFFF as the mouse location, we must also revert the
3078 * selection to the original selection.
3080 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3081 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3083 return LISTBOX_HandleLButtonUp( descr );
3084 case WM_KEYDOWN:
3085 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3087 /* for some reason Windows makes it possible to
3088 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3090 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3091 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3092 && (wParam == VK_DOWN || wParam == VK_UP)) )
3094 COMBO_FlipListbox( lphc, FALSE, FALSE );
3095 return 0;
3098 return LISTBOX_HandleKeyDown( descr, wParam );
3099 case WM_CHAR:
3101 WCHAR charW;
3102 if(unicode)
3103 charW = (WCHAR)wParam;
3104 else
3106 CHAR charA = (CHAR)wParam;
3107 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3109 return LISTBOX_HandleChar( descr, charW );
3111 case WM_SYSTIMER:
3112 return LISTBOX_HandleSystemTimer( descr );
3113 case WM_ERASEBKGND:
3114 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3116 RECT rect;
3117 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3118 wParam, (LPARAM)descr->self );
3119 TRACE("hbrush = %p\n", hbrush);
3120 if(!hbrush)
3121 hbrush = GetSysColorBrush(COLOR_WINDOW);
3122 if(hbrush)
3124 GetClientRect(descr->self, &rect);
3125 FillRect((HDC)wParam, &rect, hbrush);
3128 return 1;
3129 case WM_DROPFILES:
3130 if( lphc ) return 0;
3131 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3132 SendMessageA( descr->owner, msg, wParam, lParam );
3134 case WM_NCDESTROY:
3135 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3136 lphc->hWndLBox = 0;
3137 break;
3139 case WM_NCACTIVATE:
3140 if (lphc) return 0;
3141 break;
3143 default:
3144 if ((msg >= WM_USER) && (msg < 0xc000))
3145 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3146 hwnd, msg, wParam, lParam );
3149 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3150 DefWindowProcA( hwnd, msg, wParam, lParam );