user32/listbox: Use a helper to retrieve item data by index.
[wine.git] / dlls / user32 / listbox.c
blob70b55e1c8e50f4c238b86c80ed07eb1d6ddcd2aa
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 * TODO:
21 * - LBS_NODATA
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "wine/unicode.h"
32 #include "user_private.h"
33 #include "controls.h"
34 #include "wine/exception.h"
35 #include "wine/debug.h"
36 #include "wine/heap.h"
38 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
40 /* Items array granularity (must be power of 2) */
41 #define LB_ARRAY_GRANULARITY 16
43 /* Scrolling timeout in ms */
44 #define LB_SCROLL_TIMEOUT 50
46 /* Listbox system timer id */
47 #define LB_TIMER_ID 2
49 /* flag listbox changed while setredraw false - internal style */
50 #define LBS_DISPLAYCHANGED 0x80000000
52 /* Item structure */
53 typedef struct
55 LPWSTR str; /* Item text */
56 BOOL selected; /* Is item selected? */
57 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
58 ULONG_PTR data; /* User data */
59 } LB_ITEMDATA;
61 /* Listbox structure */
62 typedef struct
64 HWND self; /* Our own window handle */
65 HWND owner; /* Owner window to send notifications to */
66 UINT style; /* Window style */
67 INT width; /* Window width */
68 INT height; /* Window height */
69 LB_ITEMDATA *items; /* Array of items */
70 INT nb_items; /* Number of items */
71 UINT items_size; /* Total number of allocated items in the array */
72 INT top_item; /* Top visible item */
73 INT selected_item; /* Selected item */
74 INT focus_item; /* Item that has the focus */
75 INT anchor_item; /* Anchor item for extended selection */
76 INT item_height; /* Default item height */
77 INT page_size; /* Items per listbox page */
78 INT column_width; /* Column width for multi-column listboxes */
79 INT horz_extent; /* Horizontal extent */
80 INT horz_pos; /* Horizontal position */
81 INT nb_tabs; /* Number of tabs in array */
82 INT *tabs; /* Array of tabs */
83 INT avg_char_width; /* Average width of characters */
84 INT wheel_remain; /* Left over scroll amount */
85 BOOL caret_on; /* Is caret on? */
86 BOOL captured; /* Is mouse captured? */
87 BOOL in_focus;
88 HFONT font; /* Current font */
89 LCID locale; /* Current locale for string comparisons */
90 LPHEADCOMBO lphc; /* ComboLBox */
91 } LB_DESCR;
94 #define IS_OWNERDRAW(descr) \
95 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
97 #define HAS_STRINGS(descr) \
98 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
101 #define IS_MULTISELECT(descr) \
102 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
103 !((descr)->style & LBS_NOSEL))
105 #define SEND_NOTIFICATION(descr,code) \
106 (SendMessageW( (descr)->owner, WM_COMMAND, \
107 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
109 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
111 /* Current timer status */
112 typedef enum
114 LB_TIMER_NONE,
115 LB_TIMER_UP,
116 LB_TIMER_LEFT,
117 LB_TIMER_DOWN,
118 LB_TIMER_RIGHT
119 } TIMER_DIRECTION;
121 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
123 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
125 static BOOL resize_storage(LB_DESCR *descr, UINT items_size)
127 LB_ITEMDATA *items;
129 if (items_size > descr->items_size ||
130 items_size + LB_ARRAY_GRANULARITY * 2 < descr->items_size)
132 items_size = (items_size + LB_ARRAY_GRANULARITY - 1) & ~(LB_ARRAY_GRANULARITY - 1);
133 items = heap_realloc(descr->items, items_size * sizeof(LB_ITEMDATA));
134 if (!items)
136 SEND_NOTIFICATION(descr, LBN_ERRSPACE);
137 return FALSE;
139 descr->items_size = items_size;
140 descr->items = items;
143 if ((descr->style & LBS_NODATA) && items_size > descr->nb_items)
145 memset(&descr->items[descr->nb_items], 0,
146 (items_size - descr->nb_items) * sizeof(LB_ITEMDATA));
148 return TRUE;
151 static ULONG_PTR get_item_data( const LB_DESCR *descr, UINT index )
153 return (descr->style & LBS_NODATA) ? 0 : descr->items[index].data;
156 static BOOL is_item_selected( const LB_DESCR *descr, UINT index )
158 if (!(descr->style & (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)))
159 return index == descr->selected_item;
160 return descr->items[index].selected;
163 /*********************************************************************
164 * listbox class descriptor
166 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
167 const struct builtin_class_descr LISTBOX_builtin_class =
169 listboxW, /* name */
170 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
171 WINPROC_LISTBOX, /* proc */
172 sizeof(LB_DESCR *), /* extra */
173 IDC_ARROW, /* cursor */
174 0 /* brush */
178 /*********************************************************************
179 * combolbox class descriptor
181 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
182 const struct builtin_class_descr COMBOLBOX_builtin_class =
184 combolboxW, /* name */
185 CS_DBLCLKS | CS_SAVEBITS, /* style */
186 WINPROC_LISTBOX, /* proc */
187 sizeof(LB_DESCR *), /* extra */
188 IDC_ARROW, /* cursor */
189 0 /* brush */
193 /***********************************************************************
194 * LISTBOX_GetCurrentPageSize
196 * Return the current page size
198 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
200 INT i, height;
201 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
202 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
204 if ((height += descr->items[i].height) > descr->height) break;
206 if (i == descr->top_item) return 1;
207 else return i - descr->top_item;
211 /***********************************************************************
212 * LISTBOX_GetMaxTopIndex
214 * Return the maximum possible index for the top of the listbox.
216 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
218 INT max, page;
220 if (descr->style & LBS_OWNERDRAWVARIABLE)
222 page = descr->height;
223 for (max = descr->nb_items - 1; max >= 0; max--)
224 if ((page -= descr->items[max].height) < 0) break;
225 if (max < descr->nb_items - 1) max++;
227 else if (descr->style & LBS_MULTICOLUMN)
229 if ((page = descr->width / descr->column_width) < 1) page = 1;
230 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
231 max = (max - page) * descr->page_size;
233 else
235 max = descr->nb_items - descr->page_size;
237 if (max < 0) max = 0;
238 return max;
242 /***********************************************************************
243 * LISTBOX_UpdateScroll
245 * Update the scrollbars. Should be called whenever the content
246 * of the listbox changes.
248 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
250 SCROLLINFO info;
252 /* Check the listbox scroll bar flags individually before we call
253 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
254 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
255 scroll bar when we do not need one.
256 if (!(descr->style & WS_VSCROLL)) return;
259 /* It is important that we check descr->style, and not wnd->dwStyle,
260 for WS_VSCROLL, as the former is exactly the one passed in
261 argument to CreateWindow.
262 In Windows (and from now on in Wine :) a listbox created
263 with such a style (no WS_SCROLL) does not update
264 the scrollbar with listbox-related data, thus letting
265 the programmer use it for his/her own purposes. */
267 if (descr->style & LBS_NOREDRAW) return;
268 info.cbSize = sizeof(info);
270 if (descr->style & LBS_MULTICOLUMN)
272 info.nMin = 0;
273 info.nMax = (descr->nb_items - 1) / descr->page_size;
274 info.nPos = descr->top_item / descr->page_size;
275 info.nPage = descr->width / descr->column_width;
276 if (info.nPage < 1) info.nPage = 1;
277 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
278 if (descr->style & LBS_DISABLENOSCROLL)
279 info.fMask |= SIF_DISABLENOSCROLL;
280 if (descr->style & WS_HSCROLL)
281 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
282 info.nMax = 0;
283 info.fMask = SIF_RANGE;
284 if (descr->style & WS_VSCROLL)
285 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
287 else
289 info.nMin = 0;
290 info.nMax = descr->nb_items - 1;
291 info.nPos = descr->top_item;
292 info.nPage = LISTBOX_GetCurrentPageSize( descr );
293 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
294 if (descr->style & LBS_DISABLENOSCROLL)
295 info.fMask |= SIF_DISABLENOSCROLL;
296 if (descr->style & WS_VSCROLL)
297 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
299 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
301 info.nPos = descr->horz_pos;
302 info.nPage = descr->width;
303 info.fMask = SIF_POS | SIF_PAGE;
304 if (descr->style & LBS_DISABLENOSCROLL)
305 info.fMask |= SIF_DISABLENOSCROLL;
306 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
308 else
310 if (descr->style & LBS_DISABLENOSCROLL)
312 info.nMin = 0;
313 info.nMax = 0;
314 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
315 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
317 else
319 ShowScrollBar( descr->self, SB_HORZ, FALSE );
326 /***********************************************************************
327 * LISTBOX_SetTopItem
329 * Set the top item of the listbox, scrolling up or down if necessary.
331 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
333 INT max = LISTBOX_GetMaxTopIndex( descr );
335 TRACE("setting top item %d, scroll %d\n", index, scroll);
337 if (index > max) index = max;
338 if (index < 0) index = 0;
339 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
340 if (descr->top_item == index) return LB_OKAY;
341 if (scroll)
343 INT dx = 0, dy = 0;
344 if (descr->style & LBS_MULTICOLUMN)
345 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
346 else if (descr->style & LBS_OWNERDRAWVARIABLE)
348 INT i;
349 if (index > descr->top_item)
351 for (i = index - 1; i >= descr->top_item; i--)
352 dy -= descr->items[i].height;
354 else
356 for (i = index; i < descr->top_item; i++)
357 dy += descr->items[i].height;
360 else
361 dy = (descr->top_item - index) * descr->item_height;
363 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
364 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
366 else
367 InvalidateRect( descr->self, NULL, TRUE );
368 descr->top_item = index;
369 LISTBOX_UpdateScroll( descr );
370 return LB_OKAY;
374 /***********************************************************************
375 * LISTBOX_UpdatePage
377 * Update the page size. Should be called when the size of
378 * the client area or the item height changes.
380 static void LISTBOX_UpdatePage( LB_DESCR *descr )
382 INT page_size;
384 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
385 page_size = 1;
386 if (page_size == descr->page_size) return;
387 descr->page_size = page_size;
388 if (descr->style & LBS_MULTICOLUMN)
389 InvalidateRect( descr->self, NULL, TRUE );
390 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
394 /***********************************************************************
395 * LISTBOX_UpdateSize
397 * Update the size of the listbox. Should be called when the size of
398 * the client area changes.
400 static void LISTBOX_UpdateSize( LB_DESCR *descr )
402 RECT rect;
404 GetClientRect( descr->self, &rect );
405 descr->width = rect.right - rect.left;
406 descr->height = rect.bottom - rect.top;
407 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
409 INT remaining;
410 RECT rect;
412 GetWindowRect( descr->self, &rect );
413 if(descr->item_height != 0)
414 remaining = descr->height % descr->item_height;
415 else
416 remaining = 0;
417 if ((descr->height > descr->item_height) && remaining)
419 TRACE("[%p]: changing height %d -> %d\n",
420 descr->self, descr->height, descr->height - remaining );
421 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
422 rect.bottom - rect.top - remaining,
423 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
424 return;
427 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
428 LISTBOX_UpdatePage( descr );
429 LISTBOX_UpdateScroll( descr );
431 /* Invalidate the focused item so it will be repainted correctly */
432 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
434 InvalidateRect( descr->self, &rect, FALSE );
439 /***********************************************************************
440 * LISTBOX_GetItemRect
442 * Get the rectangle enclosing an item, in listbox client coordinates.
443 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
445 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
447 /* Index <= 0 is legal even on empty listboxes */
448 if (index && (index >= descr->nb_items))
450 SetRectEmpty(rect);
451 SetLastError(ERROR_INVALID_INDEX);
452 return LB_ERR;
454 SetRect( rect, 0, 0, descr->width, descr->height );
455 if (descr->style & LBS_MULTICOLUMN)
457 INT col = (index / descr->page_size) -
458 (descr->top_item / descr->page_size);
459 rect->left += col * descr->column_width;
460 rect->right = rect->left + descr->column_width;
461 rect->top += (index % descr->page_size) * descr->item_height;
462 rect->bottom = rect->top + descr->item_height;
464 else if (descr->style & LBS_OWNERDRAWVARIABLE)
466 INT i;
467 rect->right += descr->horz_pos;
468 if ((index >= 0) && (index < descr->nb_items))
470 if (index < descr->top_item)
472 for (i = descr->top_item-1; i >= index; i--)
473 rect->top -= descr->items[i].height;
475 else
477 for (i = descr->top_item; i < index; i++)
478 rect->top += descr->items[i].height;
480 rect->bottom = rect->top + descr->items[index].height;
484 else
486 rect->top += (index - descr->top_item) * descr->item_height;
487 rect->bottom = rect->top + descr->item_height;
488 rect->right += descr->horz_pos;
491 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
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( const 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( LB_DESCR *descr, HDC hdc, const RECT *rect,
551 INT index, UINT action, BOOL ignoreFocus )
553 BOOL selected = FALSE, focused;
554 LB_ITEMDATA *item = NULL;
556 if (index < descr->nb_items)
558 item = &descr->items[index];
559 selected = is_item_selected(descr, index);
562 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
564 if (IS_OWNERDRAW(descr))
566 DRAWITEMSTRUCT dis;
567 RECT r;
568 HRGN hrgn;
570 if (index >= descr->nb_items)
572 if (action == ODA_FOCUS)
573 DrawFocusRect( hdc, rect );
574 else
575 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
576 return;
579 /* some programs mess with the clipping region when
580 drawing the item, *and* restore the previous region
581 after they are done, so a region has better to exist
582 else everything ends clipped */
583 GetClientRect(descr->self, &r);
584 hrgn = set_control_clipping( hdc, &r );
586 dis.CtlType = ODT_LISTBOX;
587 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
588 dis.hwndItem = descr->self;
589 dis.itemAction = action;
590 dis.hDC = hdc;
591 dis.itemID = index;
592 dis.itemState = 0;
593 if (selected)
594 dis.itemState |= ODS_SELECTED;
595 if (focused)
596 dis.itemState |= ODS_FOCUS;
597 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
598 dis.itemData = get_item_data(descr, index);
599 dis.rcItem = *rect;
600 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
601 descr->self, index, item ? debugstr_w(item->str) : "", action,
602 dis.itemState, wine_dbgstr_rect(rect) );
603 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
604 SelectClipRgn( hdc, hrgn );
605 if (hrgn) DeleteObject( hrgn );
607 else
609 COLORREF oldText = 0, oldBk = 0;
611 if (action == ODA_FOCUS)
613 DrawFocusRect( hdc, rect );
614 return;
616 if (selected)
618 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
619 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
622 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
623 descr->self, index, item ? debugstr_w(item->str) : "", action,
624 wine_dbgstr_rect(rect) );
625 if (!item)
626 ExtTextOutW( hdc, rect->left + 1, rect->top,
627 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
628 else if (!(descr->style & LBS_USETABSTOPS))
629 ExtTextOutW( hdc, rect->left + 1, rect->top,
630 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
631 strlenW(item->str), NULL );
632 else
634 /* Output empty string to paint background in the full width. */
635 ExtTextOutW( hdc, rect->left + 1, rect->top,
636 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
637 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
638 item->str, strlenW(item->str),
639 descr->nb_tabs, descr->tabs, 0);
641 if (selected)
643 SetBkColor( hdc, oldBk );
644 SetTextColor( hdc, oldText );
646 if (focused)
647 DrawFocusRect( hdc, rect );
652 /***********************************************************************
653 * LISTBOX_SetRedraw
655 * Change the redraw flag.
657 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
659 if (on)
661 if (!(descr->style & LBS_NOREDRAW)) return;
662 descr->style &= ~LBS_NOREDRAW;
663 if (descr->style & LBS_DISPLAYCHANGED)
664 { /* page was changed while setredraw false, refresh automatically */
665 InvalidateRect(descr->self, NULL, TRUE);
666 if ((descr->top_item + descr->page_size) > descr->nb_items)
667 { /* reset top of page if less than number of items/page */
668 descr->top_item = descr->nb_items - descr->page_size;
669 if (descr->top_item < 0) descr->top_item = 0;
671 descr->style &= ~LBS_DISPLAYCHANGED;
673 LISTBOX_UpdateScroll( descr );
675 else descr->style |= LBS_NOREDRAW;
679 /***********************************************************************
680 * LISTBOX_RepaintItem
682 * Repaint a single item synchronously.
684 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
686 HDC hdc;
687 RECT rect;
688 HFONT oldFont = 0;
689 HBRUSH hbrush, oldBrush = 0;
691 /* Do not repaint the item if the item is not visible */
692 if (!IsWindowVisible(descr->self)) return;
693 if (descr->style & LBS_NOREDRAW)
695 descr->style |= LBS_DISPLAYCHANGED;
696 return;
698 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
699 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
700 if (descr->font) oldFont = SelectObject( hdc, descr->font );
701 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
702 (WPARAM)hdc, (LPARAM)descr->self );
703 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
704 if (!IsWindowEnabled(descr->self))
705 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
706 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
707 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
708 if (oldFont) SelectObject( hdc, oldFont );
709 if (oldBrush) SelectObject( hdc, oldBrush );
710 ReleaseDC( descr->self, hdc );
714 /***********************************************************************
715 * LISTBOX_DrawFocusRect
717 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
719 HDC hdc;
720 RECT rect;
721 HFONT oldFont = 0;
723 /* Do not repaint the item if the item is not visible */
724 if (!IsWindowVisible(descr->self)) return;
726 if (descr->focus_item == -1) return;
727 if (!descr->caret_on || !descr->in_focus) return;
729 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
730 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
731 if (descr->font) oldFont = SelectObject( hdc, descr->font );
732 if (!IsWindowEnabled(descr->self))
733 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
734 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
735 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
736 if (oldFont) SelectObject( hdc, oldFont );
737 ReleaseDC( descr->self, hdc );
741 /***********************************************************************
742 * LISTBOX_InitStorage
744 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
746 UINT new_size = descr->nb_items + nb_items;
748 if (new_size > descr->items_size && !resize_storage(descr, new_size))
749 return LB_ERRSPACE;
750 return descr->items_size;
754 /***********************************************************************
755 * LISTBOX_SetTabStops
757 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
759 INT i;
761 if (!(descr->style & LBS_USETABSTOPS))
763 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
764 return FALSE;
767 HeapFree( GetProcessHeap(), 0, descr->tabs );
768 if (!(descr->nb_tabs = count))
770 descr->tabs = NULL;
771 return TRUE;
773 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
774 descr->nb_tabs * sizeof(INT) )))
775 return FALSE;
776 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
778 /* convert into "dialog units"*/
779 for (i = 0; i < descr->nb_tabs; i++)
780 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
782 return TRUE;
786 /***********************************************************************
787 * LISTBOX_GetText
789 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
791 DWORD len;
793 if ((index < 0) || (index >= descr->nb_items))
795 SetLastError(ERROR_INVALID_INDEX);
796 return LB_ERR;
798 if (HAS_STRINGS(descr))
800 if (!buffer)
802 len = strlenW(descr->items[index].str);
803 if( unicode )
804 return len;
805 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
806 NULL, 0, NULL, NULL );
809 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
811 __TRY /* hide a Delphi bug that passes a read-only buffer */
813 if(unicode)
815 strcpyW( buffer, descr->items[index].str );
816 len = strlenW(buffer);
818 else
820 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
821 (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
824 __EXCEPT_PAGE_FAULT
826 WARN( "got an invalid buffer (Delphi bug?)\n" );
827 SetLastError( ERROR_INVALID_PARAMETER );
828 return LB_ERR;
830 __ENDTRY
831 } else {
832 if (buffer)
833 *((ULONG_PTR *)buffer) = get_item_data(descr, index);
834 len = sizeof(ULONG_PTR);
836 return len;
839 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
841 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
842 if (ret == CSTR_LESS_THAN)
843 return -1;
844 if (ret == CSTR_EQUAL)
845 return 0;
846 if (ret == CSTR_GREATER_THAN)
847 return 1;
848 return -1;
851 /***********************************************************************
852 * LISTBOX_FindStringPos
854 * Find the nearest string located before a given string in sort order.
855 * If 'exact' is TRUE, return an error if we don't get an exact match.
857 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
859 INT index, min, max, res;
861 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
863 min = 0;
864 max = descr->nb_items - 1;
865 while (min <= max)
867 index = (min + max) / 2;
868 if (HAS_STRINGS(descr))
869 res = LISTBOX_lstrcmpiW( descr->locale, descr->items[index].str, str );
870 else
872 COMPAREITEMSTRUCT cis;
873 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
875 cis.CtlType = ODT_LISTBOX;
876 cis.CtlID = id;
877 cis.hwndItem = descr->self;
878 /* note that some application (MetaStock) expects the second item
879 * to be in the listbox */
880 cis.itemID1 = index;
881 cis.itemData1 = get_item_data(descr, index);
882 cis.itemID2 = -1;
883 cis.itemData2 = (ULONG_PTR)str;
884 cis.dwLocaleId = descr->locale;
885 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
887 if (!res) return index;
888 if (res > 0) max = index - 1;
889 else min = index + 1;
891 return exact ? -1 : min;
895 /***********************************************************************
896 * LISTBOX_FindFileStrPos
898 * Find the nearest string located before a given string in directory
899 * sort order (i.e. first files, then directories, then drives).
901 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
903 INT min, max, res;
905 if (!HAS_STRINGS(descr))
906 return LISTBOX_FindStringPos( descr, str, FALSE );
907 min = 0;
908 max = descr->nb_items;
909 while (min != max)
911 INT index = (min + max) / 2;
912 LPCWSTR p = descr->items[index].str;
913 if (*p == '[') /* drive or directory */
915 if (*str != '[') res = -1;
916 else if (p[1] == '-') /* drive */
918 if (str[1] == '-') res = str[2] - p[2];
919 else res = -1;
921 else /* directory */
923 if (str[1] == '-') res = 1;
924 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
927 else /* filename */
929 if (*str == '[') res = 1;
930 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
932 if (!res) return index;
933 if (res < 0) max = index;
934 else min = index + 1;
936 return max;
940 /***********************************************************************
941 * LISTBOX_FindString
943 * Find the item beginning with a given string.
945 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
947 INT i;
948 LB_ITEMDATA *item;
950 if (descr->style & LBS_NODATA)
952 SetLastError(ERROR_INVALID_PARAMETER);
953 return LB_ERR;
956 if (start >= descr->nb_items) start = -1;
957 item = descr->items + start + 1;
958 if (HAS_STRINGS(descr))
960 if (!str || ! str[0] ) return LB_ERR;
961 if (exact)
963 for (i = start + 1; i < descr->nb_items; i++, item++)
964 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
965 for (i = 0, item = descr->items; i <= start; i++, item++)
966 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
968 else
970 /* Special case for drives and directories: ignore prefix */
971 #define CHECK_DRIVE(item) \
972 if ((item)->str[0] == '[') \
974 if (!strncmpiW( str, (item)->str+1, len )) return i; \
975 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
976 return i; \
979 INT len = strlenW(str);
980 for (i = start + 1; i < descr->nb_items; i++, item++)
982 if (!strncmpiW( str, item->str, len )) return i;
983 CHECK_DRIVE(item);
985 for (i = 0, item = descr->items; i <= start; i++, item++)
987 if (!strncmpiW( str, item->str, len )) return i;
988 CHECK_DRIVE(item);
990 #undef CHECK_DRIVE
993 else
995 if (exact && (descr->style & LBS_SORT))
996 /* If sorted, use a WM_COMPAREITEM binary search */
997 return LISTBOX_FindStringPos( descr, str, TRUE );
999 /* Otherwise use a linear search */
1000 for (i = start + 1; i < descr->nb_items; i++, item++)
1001 if (item->data == (ULONG_PTR)str) return i;
1002 for (i = 0, item = descr->items; i <= start; i++, item++)
1003 if (item->data == (ULONG_PTR)str) return i;
1005 return LB_ERR;
1009 /***********************************************************************
1010 * LISTBOX_GetSelCount
1012 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
1014 INT i, count;
1015 const LB_ITEMDATA *item = descr->items;
1017 if (!(descr->style & LBS_MULTIPLESEL) ||
1018 (descr->style & LBS_NOSEL))
1019 return LB_ERR;
1020 for (i = count = 0; i < descr->nb_items; i++, item++)
1021 if (item->selected) count++;
1022 return count;
1026 /***********************************************************************
1027 * LISTBOX_GetSelItems
1029 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
1031 INT i, count;
1032 const LB_ITEMDATA *item = descr->items;
1034 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1035 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1036 if (item->selected) array[count++] = i;
1037 return count;
1041 /***********************************************************************
1042 * LISTBOX_Paint
1044 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1046 INT i, col_pos = descr->page_size - 1;
1047 RECT rect;
1048 RECT focusRect = {-1, -1, -1, -1};
1049 HFONT oldFont = 0;
1050 HBRUSH hbrush, oldBrush = 0;
1052 if (descr->style & LBS_NOREDRAW) return 0;
1054 SetRect( &rect, 0, 0, descr->width, descr->height );
1055 if (descr->style & LBS_MULTICOLUMN)
1056 rect.right = rect.left + descr->column_width;
1057 else if (descr->horz_pos)
1059 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1060 rect.right += descr->horz_pos;
1063 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1064 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1065 (WPARAM)hdc, (LPARAM)descr->self );
1066 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1067 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1069 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1070 (descr->in_focus))
1072 /* Special case for empty listbox: paint focus rect */
1073 rect.bottom = rect.top + descr->item_height;
1074 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1075 &rect, NULL, 0, NULL );
1076 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1077 rect.top = rect.bottom;
1080 /* Paint all the item, regarding the selection
1081 Focus state will be painted after */
1083 for (i = descr->top_item; i < descr->nb_items; i++)
1085 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1086 rect.bottom = rect.top + descr->item_height;
1087 else
1088 rect.bottom = rect.top + descr->items[i].height;
1090 /* keep the focus rect, to paint the focus item after */
1091 if (i == descr->focus_item)
1092 focusRect = rect;
1094 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1095 rect.top = rect.bottom;
1097 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1099 if (!IS_OWNERDRAW(descr))
1101 /* Clear the bottom of the column */
1102 if (rect.top < descr->height)
1104 rect.bottom = descr->height;
1105 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1106 &rect, NULL, 0, NULL );
1110 /* Go to the next column */
1111 rect.left += descr->column_width;
1112 rect.right += descr->column_width;
1113 rect.top = 0;
1114 col_pos = descr->page_size - 1;
1116 else
1118 col_pos--;
1119 if (rect.top >= descr->height) break;
1123 /* Paint the focus item now */
1124 if (focusRect.top != focusRect.bottom &&
1125 descr->caret_on && descr->in_focus)
1126 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1128 if (!IS_OWNERDRAW(descr))
1130 /* Clear the remainder of the client area */
1131 if (rect.top < descr->height)
1133 rect.bottom = descr->height;
1134 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1135 &rect, NULL, 0, NULL );
1137 if (rect.right < descr->width)
1139 rect.left = rect.right;
1140 rect.right = descr->width;
1141 rect.top = 0;
1142 rect.bottom = descr->height;
1143 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1144 &rect, NULL, 0, NULL );
1147 if (oldFont) SelectObject( hdc, oldFont );
1148 if (oldBrush) SelectObject( hdc, oldBrush );
1149 return 0;
1153 /***********************************************************************
1154 * LISTBOX_InvalidateItems
1156 * Invalidate all items from a given item. If the specified item is not
1157 * visible, nothing happens.
1159 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1161 RECT rect;
1163 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1165 if (descr->style & LBS_NOREDRAW)
1167 descr->style |= LBS_DISPLAYCHANGED;
1168 return;
1170 rect.bottom = descr->height;
1171 InvalidateRect( descr->self, &rect, TRUE );
1172 if (descr->style & LBS_MULTICOLUMN)
1174 /* Repaint the other columns */
1175 rect.left = rect.right;
1176 rect.right = descr->width;
1177 rect.top = 0;
1178 InvalidateRect( descr->self, &rect, TRUE );
1183 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1185 RECT rect;
1187 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1188 InvalidateRect( descr->self, &rect, TRUE );
1191 /***********************************************************************
1192 * LISTBOX_GetItemHeight
1194 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1196 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1198 if ((index < 0) || (index >= descr->nb_items))
1200 SetLastError(ERROR_INVALID_INDEX);
1201 return LB_ERR;
1203 return descr->items[index].height;
1205 else return descr->item_height;
1209 /***********************************************************************
1210 * LISTBOX_SetItemHeight
1212 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1214 if (height > MAXBYTE)
1215 return -1;
1217 if (!height) height = 1;
1219 if (descr->style & LBS_OWNERDRAWVARIABLE)
1221 if ((index < 0) || (index >= descr->nb_items))
1223 SetLastError(ERROR_INVALID_INDEX);
1224 return LB_ERR;
1226 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1227 descr->items[index].height = height;
1228 LISTBOX_UpdateScroll( descr );
1229 if (repaint)
1230 LISTBOX_InvalidateItems( descr, index );
1232 else if (height != descr->item_height)
1234 TRACE("[%p]: new height = %d\n", descr->self, height );
1235 descr->item_height = height;
1236 LISTBOX_UpdatePage( descr );
1237 LISTBOX_UpdateScroll( descr );
1238 if (repaint)
1239 InvalidateRect( descr->self, 0, TRUE );
1241 return LB_OKAY;
1245 /***********************************************************************
1246 * LISTBOX_SetHorizontalPos
1248 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1250 INT diff;
1252 if (pos > descr->horz_extent - descr->width)
1253 pos = descr->horz_extent - descr->width;
1254 if (pos < 0) pos = 0;
1255 if (!(diff = descr->horz_pos - pos)) return;
1256 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1257 descr->horz_pos = pos;
1258 LISTBOX_UpdateScroll( descr );
1259 if (abs(diff) < descr->width)
1261 RECT rect;
1262 /* Invalidate the focused item so it will be repainted correctly */
1263 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1264 InvalidateRect( descr->self, &rect, TRUE );
1265 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1266 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1268 else
1269 InvalidateRect( descr->self, NULL, TRUE );
1273 /***********************************************************************
1274 * LISTBOX_SetHorizontalExtent
1276 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1278 if (descr->style & LBS_MULTICOLUMN)
1279 return LB_OKAY;
1280 if (extent == descr->horz_extent) return LB_OKAY;
1281 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1282 descr->horz_extent = extent;
1283 if (descr->style & WS_HSCROLL) {
1284 SCROLLINFO info;
1285 info.cbSize = sizeof(info);
1286 info.nMin = 0;
1287 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1288 info.fMask = SIF_RANGE;
1289 if (descr->style & LBS_DISABLENOSCROLL)
1290 info.fMask |= SIF_DISABLENOSCROLL;
1291 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1293 if (descr->horz_pos > extent - descr->width)
1294 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1295 return LB_OKAY;
1299 /***********************************************************************
1300 * LISTBOX_SetColumnWidth
1302 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1304 RECT rect;
1306 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1308 GetClientRect(descr->self, &rect);
1309 descr->width = rect.right - rect.left;
1310 descr->height = rect.bottom - rect.top;
1311 descr->column_width = column_width;
1313 LISTBOX_UpdatePage(descr);
1314 LISTBOX_UpdateScroll(descr);
1315 return LB_OKAY;
1319 /***********************************************************************
1320 * LISTBOX_SetFont
1322 * Returns the item height.
1324 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1326 HDC hdc;
1327 HFONT oldFont = 0;
1328 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1329 SIZE sz;
1331 descr->font = font;
1333 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1335 ERR("unable to get DC.\n" );
1336 return 16;
1338 if (font) oldFont = SelectObject( hdc, font );
1339 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1340 if (oldFont) SelectObject( hdc, oldFont );
1341 ReleaseDC( descr->self, hdc );
1343 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1344 if (!IS_OWNERDRAW(descr))
1345 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1346 return sz.cy;
1350 /***********************************************************************
1351 * LISTBOX_MakeItemVisible
1353 * Make sure that a given item is partially or fully visible.
1355 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1357 INT top;
1359 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1361 if (index <= descr->top_item) top = index;
1362 else if (descr->style & LBS_MULTICOLUMN)
1364 INT cols = descr->width;
1365 if (!fully) cols += descr->column_width - 1;
1366 if (cols >= descr->column_width) cols /= descr->column_width;
1367 else cols = 1;
1368 if (index < descr->top_item + (descr->page_size * cols)) return;
1369 top = index - descr->page_size * (cols - 1);
1371 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1373 INT height = fully ? descr->items[index].height : 1;
1374 for (top = index; top > descr->top_item; top--)
1375 if ((height += descr->items[top-1].height) > descr->height) break;
1377 else
1379 if (index < descr->top_item + descr->page_size) return;
1380 if (!fully && (index == descr->top_item + descr->page_size) &&
1381 (descr->height > (descr->page_size * descr->item_height))) return;
1382 top = index - descr->page_size + 1;
1384 LISTBOX_SetTopItem( descr, top, TRUE );
1387 /***********************************************************************
1388 * LISTBOX_SetCaretIndex
1390 * NOTES
1391 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1394 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1396 INT oldfocus = descr->focus_item;
1398 TRACE("old focus %d, index %d\n", oldfocus, index);
1400 if (descr->style & LBS_NOSEL) return LB_ERR;
1401 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1402 if (index == oldfocus) return LB_OKAY;
1404 LISTBOX_DrawFocusRect( descr, FALSE );
1405 descr->focus_item = index;
1407 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1408 LISTBOX_DrawFocusRect( descr, TRUE );
1410 return LB_OKAY;
1414 /***********************************************************************
1415 * LISTBOX_SelectItemRange
1417 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1419 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1420 INT last, BOOL on )
1422 INT i;
1424 /* A few sanity checks */
1426 if (descr->style & LBS_NOSEL) return LB_ERR;
1427 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1429 if (!descr->nb_items) return LB_OKAY;
1431 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1432 if (first < 0) first = 0;
1433 if (last < first) return LB_OKAY;
1435 if (on) /* Turn selection on */
1437 for (i = first; i <= last; i++)
1439 if (descr->items[i].selected) continue;
1440 descr->items[i].selected = TRUE;
1441 LISTBOX_InvalidateItemRect(descr, i);
1444 else /* Turn selection off */
1446 for (i = first; i <= last; i++)
1448 if (!descr->items[i].selected) continue;
1449 descr->items[i].selected = FALSE;
1450 LISTBOX_InvalidateItemRect(descr, i);
1453 return LB_OKAY;
1456 /***********************************************************************
1457 * LISTBOX_SetSelection
1459 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1460 BOOL on, BOOL send_notify )
1462 TRACE( "cur_sel=%d index=%d notify=%s\n",
1463 descr->selected_item, index, send_notify ? "YES" : "NO" );
1465 if (descr->style & LBS_NOSEL)
1467 descr->selected_item = index;
1468 return LB_ERR;
1470 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1471 if (descr->style & LBS_MULTIPLESEL)
1473 if (index == -1) /* Select all items */
1474 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1475 else /* Only one item */
1476 return LISTBOX_SelectItemRange( descr, index, index, on );
1478 else
1480 INT oldsel = descr->selected_item;
1481 if (index == oldsel) return LB_OKAY;
1482 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1483 if (index != -1) descr->items[index].selected = TRUE;
1484 descr->selected_item = index;
1485 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1486 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1487 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1488 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1489 else
1490 if( descr->lphc ) /* set selection change flag for parent combo */
1491 descr->lphc->wState |= CBF_SELCHANGE;
1493 return LB_OKAY;
1497 /***********************************************************************
1498 * LISTBOX_MoveCaret
1500 * Change the caret position and extend the selection to the new caret.
1502 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1504 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1506 if ((index < 0) || (index >= descr->nb_items))
1507 return;
1509 /* Important, repaint needs to be done in this order if
1510 you want to mimic Windows behavior:
1511 1. Remove the focus and paint the item
1512 2. Remove the selection and paint the item(s)
1513 3. Set the selection and repaint the item(s)
1514 4. Set the focus to 'index' and repaint the item */
1516 /* 1. remove the focus and repaint the item */
1517 LISTBOX_DrawFocusRect( descr, FALSE );
1519 /* 2. then turn off the previous selection */
1520 /* 3. repaint the new selected item */
1521 if (descr->style & LBS_EXTENDEDSEL)
1523 if (descr->anchor_item != -1)
1525 INT first = min( index, descr->anchor_item );
1526 INT last = max( index, descr->anchor_item );
1527 if (first > 0)
1528 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1529 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1530 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1533 else if (!(descr->style & LBS_MULTIPLESEL))
1535 /* Set selection to new caret item */
1536 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1539 /* 4. repaint the new item with the focus */
1540 descr->focus_item = index;
1541 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1542 LISTBOX_DrawFocusRect( descr, TRUE );
1546 /***********************************************************************
1547 * LISTBOX_InsertItem
1549 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1550 LPWSTR str, ULONG_PTR data )
1552 LB_ITEMDATA *item;
1553 INT oldfocus = descr->focus_item;
1555 if (index == -1) index = descr->nb_items;
1556 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1557 if (!resize_storage(descr, descr->nb_items + 1)) return LB_ERR;
1559 /* Insert the item structure */
1561 item = &descr->items[index];
1562 if (index < descr->nb_items)
1563 RtlMoveMemory( item + 1, item,
1564 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1565 item->str = str;
1566 item->data = HAS_STRINGS(descr) ? 0 : data;
1567 item->height = 0;
1568 item->selected = FALSE;
1569 descr->nb_items++;
1571 /* Get item height */
1573 if (descr->style & LBS_OWNERDRAWVARIABLE)
1575 MEASUREITEMSTRUCT mis;
1576 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1578 mis.CtlType = ODT_LISTBOX;
1579 mis.CtlID = id;
1580 mis.itemID = index;
1581 mis.itemData = str ? (ULONG_PTR)str : data;
1582 mis.itemHeight = descr->item_height;
1583 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1584 item->height = mis.itemHeight ? mis.itemHeight : 1;
1585 TRACE("[%p]: measure item %d (%s) = %d\n",
1586 descr->self, index, str ? debugstr_w(str) : "", item->height );
1589 /* Repaint the items */
1591 LISTBOX_UpdateScroll( descr );
1592 LISTBOX_InvalidateItems( descr, index );
1594 /* Move selection and focused item */
1595 /* If listbox was empty, set focus to the first item */
1596 if (descr->nb_items == 1)
1597 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1598 /* single select don't change selection index in win31 */
1599 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1601 descr->selected_item++;
1602 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1604 else
1606 if (index <= descr->selected_item)
1608 descr->selected_item++;
1609 descr->focus_item = oldfocus; /* focus not changed */
1612 return LB_OKAY;
1616 /***********************************************************************
1617 * LISTBOX_InsertString
1619 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1621 LPWSTR new_str = NULL;
1622 LRESULT ret;
1624 if (HAS_STRINGS(descr))
1626 static const WCHAR empty_stringW[] = { 0 };
1627 if (!str) str = empty_stringW;
1628 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1630 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1631 return LB_ERRSPACE;
1633 strcpyW(new_str, str);
1636 if (index == -1) index = descr->nb_items;
1637 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1639 HeapFree( GetProcessHeap(), 0, new_str );
1640 return ret;
1643 TRACE("[%p]: added item %d %s\n",
1644 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1645 return index;
1649 /***********************************************************************
1650 * LISTBOX_DeleteItem
1652 * Delete the content of an item. 'index' must be a valid index.
1654 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1656 /* save the item data before it gets freed by LB_RESETCONTENT */
1657 ULONG_PTR item_data = get_item_data(descr, index);
1658 LPWSTR item_str = descr->items[index].str;
1660 if (!descr->nb_items)
1661 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1663 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1664 * while Win95 sends it for all items with user data.
1665 * It's probably better to send it too often than not
1666 * often enough, so this is what we do here.
1668 if (IS_OWNERDRAW(descr) || item_data)
1670 DELETEITEMSTRUCT dis;
1671 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1673 dis.CtlType = ODT_LISTBOX;
1674 dis.CtlID = id;
1675 dis.itemID = index;
1676 dis.hwndItem = descr->self;
1677 dis.itemData = item_data;
1678 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1680 if (HAS_STRINGS(descr))
1681 HeapFree( GetProcessHeap(), 0, item_str );
1685 /***********************************************************************
1686 * LISTBOX_RemoveItem
1688 * Remove an item from the listbox and delete its content.
1690 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1692 LB_ITEMDATA *item;
1694 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1696 /* We need to invalidate the original rect instead of the updated one. */
1697 LISTBOX_InvalidateItems( descr, index );
1699 descr->nb_items--;
1700 LISTBOX_DeleteItem( descr, index );
1702 if (!descr->nb_items) return LB_OKAY;
1704 /* Remove the item */
1706 item = &descr->items[index];
1707 if (index < descr->nb_items)
1708 RtlMoveMemory( item, item + 1,
1709 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1710 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1712 resize_storage(descr, descr->nb_items);
1714 /* Repaint the items */
1716 LISTBOX_UpdateScroll( descr );
1717 /* if we removed the scrollbar, reset the top of the list
1718 (correct for owner-drawn ???) */
1719 if (descr->nb_items == descr->page_size)
1720 LISTBOX_SetTopItem( descr, 0, TRUE );
1722 /* Move selection and focused item */
1723 if (!IS_MULTISELECT(descr))
1725 if (index == descr->selected_item)
1726 descr->selected_item = -1;
1727 else if (index < descr->selected_item)
1729 descr->selected_item--;
1730 if (ISWIN31) /* win 31 do not change the selected item number */
1731 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1735 if (descr->focus_item >= descr->nb_items)
1737 descr->focus_item = descr->nb_items - 1;
1738 if (descr->focus_item < 0) descr->focus_item = 0;
1740 return LB_OKAY;
1744 /***********************************************************************
1745 * LISTBOX_ResetContent
1747 static void LISTBOX_ResetContent( LB_DESCR *descr )
1749 INT i;
1751 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1752 HeapFree( GetProcessHeap(), 0, descr->items );
1753 descr->nb_items = 0;
1754 descr->top_item = 0;
1755 descr->selected_item = -1;
1756 descr->focus_item = 0;
1757 descr->anchor_item = -1;
1758 descr->items_size = 0;
1759 descr->items = NULL;
1763 /***********************************************************************
1764 * LISTBOX_SetCount
1766 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, UINT count )
1768 UINT orig_num = descr->nb_items;
1770 if (!(descr->style & LBS_NODATA))
1772 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1773 return LB_ERR;
1776 if (!resize_storage(descr, count))
1777 return LB_ERRSPACE;
1778 descr->nb_items = count;
1780 if (count)
1782 LISTBOX_UpdateScroll(descr);
1783 if (count < orig_num)
1785 descr->anchor_item = min(descr->anchor_item, count - 1);
1786 if (descr->selected_item >= count)
1787 descr->selected_item = -1;
1789 /* If we removed the scrollbar, reset the top of the list */
1790 if (count <= descr->page_size && orig_num > descr->page_size)
1791 LISTBOX_SetTopItem(descr, 0, TRUE);
1793 descr->focus_item = min(descr->focus_item, count - 1);
1796 /* If it was empty before growing, set focus to the first item */
1797 else if (orig_num == 0) LISTBOX_SetCaretIndex(descr, 0, FALSE);
1799 else SendMessageW(descr->self, LB_RESETCONTENT, 0, 0);
1801 InvalidateRect( descr->self, NULL, TRUE );
1802 return LB_OKAY;
1806 /***********************************************************************
1807 * LISTBOX_Directory
1809 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1810 LPCWSTR filespec, BOOL long_names )
1812 HANDLE handle;
1813 LRESULT ret = LB_OKAY;
1814 WIN32_FIND_DATAW entry;
1815 int pos;
1816 LRESULT maxinsert = LB_ERR;
1818 /* don't scan directory if we just want drives exclusively */
1819 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1820 /* scan directory */
1821 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1823 int le = GetLastError();
1824 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1826 else
1830 WCHAR buffer[270];
1831 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1833 static const WCHAR bracketW[] = { ']',0 };
1834 static const WCHAR dotW[] = { '.',0 };
1835 if (!(attrib & DDL_DIRECTORY) ||
1836 !strcmpW( entry.cFileName, dotW )) continue;
1837 buffer[0] = '[';
1838 if (!long_names && entry.cAlternateFileName[0])
1839 strcpyW( buffer + 1, entry.cAlternateFileName );
1840 else
1841 strcpyW( buffer + 1, entry.cFileName );
1842 strcatW(buffer, bracketW);
1844 else /* not a directory */
1846 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1847 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1849 if ((attrib & DDL_EXCLUSIVE) &&
1850 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1851 continue;
1852 #undef ATTRIBS
1853 if (!long_names && entry.cAlternateFileName[0])
1854 strcpyW( buffer, entry.cAlternateFileName );
1855 else
1856 strcpyW( buffer, entry.cFileName );
1858 if (!long_names) CharLowerW( buffer );
1859 pos = LISTBOX_FindFileStrPos( descr, buffer );
1860 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1861 break;
1862 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1863 } while (FindNextFileW( handle, &entry ));
1864 FindClose( handle );
1867 if (ret >= 0)
1869 ret = maxinsert;
1871 /* scan drives */
1872 if (attrib & DDL_DRIVES)
1874 WCHAR buffer[] = {'[','-','a','-',']',0};
1875 WCHAR root[] = {'A',':','\\',0};
1876 int drive;
1877 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1879 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1880 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1881 break;
1885 return ret;
1889 /***********************************************************************
1890 * LISTBOX_HandleVScroll
1892 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1894 SCROLLINFO info;
1896 if (descr->style & LBS_MULTICOLUMN) return 0;
1897 switch(scrollReq)
1899 case SB_LINEUP:
1900 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1901 break;
1902 case SB_LINEDOWN:
1903 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1904 break;
1905 case SB_PAGEUP:
1906 LISTBOX_SetTopItem( descr, descr->top_item -
1907 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1908 break;
1909 case SB_PAGEDOWN:
1910 LISTBOX_SetTopItem( descr, descr->top_item +
1911 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1912 break;
1913 case SB_THUMBPOSITION:
1914 LISTBOX_SetTopItem( descr, pos, TRUE );
1915 break;
1916 case SB_THUMBTRACK:
1917 info.cbSize = sizeof(info);
1918 info.fMask = SIF_TRACKPOS;
1919 GetScrollInfo( descr->self, SB_VERT, &info );
1920 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1921 break;
1922 case SB_TOP:
1923 LISTBOX_SetTopItem( descr, 0, TRUE );
1924 break;
1925 case SB_BOTTOM:
1926 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1927 break;
1929 return 0;
1933 /***********************************************************************
1934 * LISTBOX_HandleHScroll
1936 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1938 SCROLLINFO info;
1939 INT page;
1941 if (descr->style & LBS_MULTICOLUMN)
1943 switch(scrollReq)
1945 case SB_LINELEFT:
1946 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1947 TRUE );
1948 break;
1949 case SB_LINERIGHT:
1950 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1951 TRUE );
1952 break;
1953 case SB_PAGELEFT:
1954 page = descr->width / descr->column_width;
1955 if (page < 1) page = 1;
1956 LISTBOX_SetTopItem( descr,
1957 descr->top_item - page * descr->page_size, TRUE );
1958 break;
1959 case SB_PAGERIGHT:
1960 page = descr->width / descr->column_width;
1961 if (page < 1) page = 1;
1962 LISTBOX_SetTopItem( descr,
1963 descr->top_item + page * descr->page_size, TRUE );
1964 break;
1965 case SB_THUMBPOSITION:
1966 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1967 break;
1968 case SB_THUMBTRACK:
1969 info.cbSize = sizeof(info);
1970 info.fMask = SIF_TRACKPOS;
1971 GetScrollInfo( descr->self, SB_VERT, &info );
1972 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1973 TRUE );
1974 break;
1975 case SB_LEFT:
1976 LISTBOX_SetTopItem( descr, 0, TRUE );
1977 break;
1978 case SB_RIGHT:
1979 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1980 break;
1983 else if (descr->horz_extent)
1985 switch(scrollReq)
1987 case SB_LINELEFT:
1988 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1989 break;
1990 case SB_LINERIGHT:
1991 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1992 break;
1993 case SB_PAGELEFT:
1994 LISTBOX_SetHorizontalPos( descr,
1995 descr->horz_pos - descr->width );
1996 break;
1997 case SB_PAGERIGHT:
1998 LISTBOX_SetHorizontalPos( descr,
1999 descr->horz_pos + descr->width );
2000 break;
2001 case SB_THUMBPOSITION:
2002 LISTBOX_SetHorizontalPos( descr, pos );
2003 break;
2004 case SB_THUMBTRACK:
2005 info.cbSize = sizeof(info);
2006 info.fMask = SIF_TRACKPOS;
2007 GetScrollInfo( descr->self, SB_HORZ, &info );
2008 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2009 break;
2010 case SB_LEFT:
2011 LISTBOX_SetHorizontalPos( descr, 0 );
2012 break;
2013 case SB_RIGHT:
2014 LISTBOX_SetHorizontalPos( descr,
2015 descr->horz_extent - descr->width );
2016 break;
2019 return 0;
2022 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2024 UINT pulScrollLines = 3;
2026 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2028 /* if scrolling changes direction, ignore left overs */
2029 if ((delta < 0 && descr->wheel_remain < 0) ||
2030 (delta > 0 && descr->wheel_remain > 0))
2031 descr->wheel_remain += delta;
2032 else
2033 descr->wheel_remain = delta;
2035 if (descr->wheel_remain && pulScrollLines)
2037 int cLineScroll;
2038 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2039 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2040 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2041 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2043 return 0;
2046 /***********************************************************************
2047 * LISTBOX_HandleLButtonDown
2049 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2051 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2053 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2054 descr->self, x, y, index, descr->focus_item);
2056 if (!descr->caret_on && (descr->in_focus)) return 0;
2058 if (!descr->in_focus)
2060 if( !descr->lphc ) SetFocus( descr->self );
2061 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2064 if (index == -1) return 0;
2066 if (!descr->lphc)
2068 if (descr->style & LBS_NOTIFY )
2069 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2070 MAKELPARAM( x, y ) );
2073 descr->captured = TRUE;
2074 SetCapture( descr->self );
2076 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2078 /* we should perhaps make sure that all items are deselected
2079 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2080 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2081 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2084 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2085 if (keys & MK_CONTROL)
2087 LISTBOX_SetCaretIndex( descr, index, FALSE );
2088 LISTBOX_SetSelection( descr, index,
2089 !descr->items[index].selected,
2090 (descr->style & LBS_NOTIFY) != 0);
2092 else
2094 LISTBOX_MoveCaret( descr, index, FALSE );
2096 if (descr->style & LBS_EXTENDEDSEL)
2098 LISTBOX_SetSelection( descr, index,
2099 descr->items[index].selected,
2100 (descr->style & LBS_NOTIFY) != 0 );
2102 else
2104 LISTBOX_SetSelection( descr, index,
2105 !descr->items[index].selected,
2106 (descr->style & LBS_NOTIFY) != 0 );
2110 else
2112 descr->anchor_item = index;
2113 LISTBOX_MoveCaret( descr, index, FALSE );
2114 LISTBOX_SetSelection( descr, index,
2115 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2118 if (!descr->lphc)
2120 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2122 POINT pt;
2124 pt.x = x;
2125 pt.y = y;
2127 if (DragDetect( descr->self, pt ))
2128 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2131 return 0;
2135 /*************************************************************************
2136 * LISTBOX_HandleLButtonDownCombo [Internal]
2138 * Process LButtonDown message for the ComboListBox
2140 * PARAMS
2141 * pWnd [I] The windows internal structure
2142 * pDescr [I] The ListBox internal structure
2143 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2144 * x [I] X Mouse Coordinate
2145 * y [I] Y Mouse Coordinate
2147 * RETURNS
2148 * 0 since we are processing the WM_LBUTTONDOWN Message
2150 * NOTES
2151 * This function is only to be used when a ListBox is a ComboListBox
2154 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2156 RECT clientRect, screenRect;
2157 POINT mousePos;
2159 mousePos.x = x;
2160 mousePos.y = y;
2162 GetClientRect(descr->self, &clientRect);
2164 if(PtInRect(&clientRect, mousePos))
2166 /* MousePos is in client, resume normal processing */
2167 if (msg == WM_LBUTTONDOWN)
2169 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2170 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2172 else if (descr->style & LBS_NOTIFY)
2173 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2175 else
2177 POINT screenMousePos;
2178 HWND hWndOldCapture;
2180 /* Check the Non-Client Area */
2181 screenMousePos = mousePos;
2182 hWndOldCapture = GetCapture();
2183 ReleaseCapture();
2184 GetWindowRect(descr->self, &screenRect);
2185 ClientToScreen(descr->self, &screenMousePos);
2187 if(!PtInRect(&screenRect, screenMousePos))
2189 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2190 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2191 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2193 else
2195 /* Check to see the NC is a scrollbar */
2196 INT nHitTestType=0;
2197 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2198 /* Check Vertical scroll bar */
2199 if (style & WS_VSCROLL)
2201 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2202 if (PtInRect( &clientRect, mousePos ))
2203 nHitTestType = HTVSCROLL;
2205 /* Check horizontal scroll bar */
2206 if (style & WS_HSCROLL)
2208 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2209 if (PtInRect( &clientRect, mousePos ))
2210 nHitTestType = HTHSCROLL;
2212 /* Windows sends this message when a scrollbar is clicked
2215 if(nHitTestType != 0)
2217 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2218 MAKELONG(screenMousePos.x, screenMousePos.y));
2220 /* Resume the Capture after scrolling is complete
2222 if(hWndOldCapture != 0)
2223 SetCapture(hWndOldCapture);
2226 return 0;
2229 /***********************************************************************
2230 * LISTBOX_HandleLButtonUp
2232 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2234 if (LISTBOX_Timer != LB_TIMER_NONE)
2235 KillSystemTimer( descr->self, LB_TIMER_ID );
2236 LISTBOX_Timer = LB_TIMER_NONE;
2237 if (descr->captured)
2239 descr->captured = FALSE;
2240 if (GetCapture() == descr->self) ReleaseCapture();
2241 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2242 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2244 return 0;
2248 /***********************************************************************
2249 * LISTBOX_HandleTimer
2251 * Handle scrolling upon a timer event.
2252 * Return TRUE if scrolling should continue.
2254 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2256 switch(dir)
2258 case LB_TIMER_UP:
2259 if (descr->top_item) index = descr->top_item - 1;
2260 else index = 0;
2261 break;
2262 case LB_TIMER_LEFT:
2263 if (descr->top_item) index -= descr->page_size;
2264 break;
2265 case LB_TIMER_DOWN:
2266 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2267 if (index == descr->focus_item) index++;
2268 if (index >= descr->nb_items) index = descr->nb_items - 1;
2269 break;
2270 case LB_TIMER_RIGHT:
2271 if (index + descr->page_size < descr->nb_items)
2272 index += descr->page_size;
2273 break;
2274 case LB_TIMER_NONE:
2275 break;
2277 if (index == descr->focus_item) return FALSE;
2278 LISTBOX_MoveCaret( descr, index, FALSE );
2279 return TRUE;
2283 /***********************************************************************
2284 * LISTBOX_HandleSystemTimer
2286 * WM_SYSTIMER handler.
2288 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2290 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2292 KillSystemTimer( descr->self, LB_TIMER_ID );
2293 LISTBOX_Timer = LB_TIMER_NONE;
2295 return 0;
2299 /***********************************************************************
2300 * LISTBOX_HandleMouseMove
2302 * WM_MOUSEMOVE handler.
2304 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2305 INT x, INT y )
2307 INT index;
2308 TIMER_DIRECTION dir = LB_TIMER_NONE;
2310 if (!descr->captured) return;
2312 if (descr->style & LBS_MULTICOLUMN)
2314 if (y < 0) y = 0;
2315 else if (y >= descr->item_height * descr->page_size)
2316 y = descr->item_height * descr->page_size - 1;
2318 if (x < 0)
2320 dir = LB_TIMER_LEFT;
2321 x = 0;
2323 else if (x >= descr->width)
2325 dir = LB_TIMER_RIGHT;
2326 x = descr->width - 1;
2329 else
2331 if (y < 0) dir = LB_TIMER_UP; /* above */
2332 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2335 index = LISTBOX_GetItemFromPoint( descr, x, y );
2336 if (index == -1) index = descr->focus_item;
2337 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2339 /* Start/stop the system timer */
2341 if (dir != LB_TIMER_NONE)
2342 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2343 else if (LISTBOX_Timer != LB_TIMER_NONE)
2344 KillSystemTimer( descr->self, LB_TIMER_ID );
2345 LISTBOX_Timer = dir;
2349 /***********************************************************************
2350 * LISTBOX_HandleKeyDown
2352 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2354 INT caret = -1;
2355 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2356 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2357 bForceSelection = FALSE; /* only for single select list */
2359 if (descr->style & LBS_WANTKEYBOARDINPUT)
2361 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2362 MAKEWPARAM(LOWORD(key), descr->focus_item),
2363 (LPARAM)descr->self );
2364 if (caret == -2) return 0;
2366 if (caret == -1) switch(key)
2368 case VK_LEFT:
2369 if (descr->style & LBS_MULTICOLUMN)
2371 bForceSelection = FALSE;
2372 if (descr->focus_item >= descr->page_size)
2373 caret = descr->focus_item - descr->page_size;
2374 break;
2376 /* fall through */
2377 case VK_UP:
2378 caret = descr->focus_item - 1;
2379 if (caret < 0) caret = 0;
2380 break;
2381 case VK_RIGHT:
2382 if (descr->style & LBS_MULTICOLUMN)
2384 bForceSelection = FALSE;
2385 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2386 break;
2388 /* fall through */
2389 case VK_DOWN:
2390 caret = descr->focus_item + 1;
2391 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2392 break;
2394 case VK_PRIOR:
2395 if (descr->style & LBS_MULTICOLUMN)
2397 INT page = descr->width / descr->column_width;
2398 if (page < 1) page = 1;
2399 caret = descr->focus_item - (page * descr->page_size) + 1;
2401 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2402 if (caret < 0) caret = 0;
2403 break;
2404 case VK_NEXT:
2405 if (descr->style & LBS_MULTICOLUMN)
2407 INT page = descr->width / descr->column_width;
2408 if (page < 1) page = 1;
2409 caret = descr->focus_item + (page * descr->page_size) - 1;
2411 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2412 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2413 break;
2414 case VK_HOME:
2415 caret = 0;
2416 break;
2417 case VK_END:
2418 caret = descr->nb_items - 1;
2419 break;
2420 case VK_SPACE:
2421 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2422 else if (descr->style & LBS_MULTIPLESEL)
2424 LISTBOX_SetSelection( descr, descr->focus_item,
2425 !descr->items[descr->focus_item].selected,
2426 (descr->style & LBS_NOTIFY) != 0 );
2428 break;
2429 default:
2430 bForceSelection = FALSE;
2432 if (bForceSelection) /* focused item is used instead of key */
2433 caret = descr->focus_item;
2434 if (caret >= 0)
2436 if (((descr->style & LBS_EXTENDEDSEL) &&
2437 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2438 !IS_MULTISELECT(descr))
2439 descr->anchor_item = caret;
2440 LISTBOX_MoveCaret( descr, caret, TRUE );
2442 if (descr->style & LBS_MULTIPLESEL)
2443 descr->selected_item = caret;
2444 else
2445 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2446 if (descr->style & LBS_NOTIFY)
2448 if (descr->lphc && IsWindowVisible( descr->self ))
2450 /* make sure that combo parent doesn't hide us */
2451 descr->lphc->wState |= CBF_NOROLLUP;
2453 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2456 return 0;
2460 /***********************************************************************
2461 * LISTBOX_HandleChar
2463 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2465 INT caret = -1;
2466 WCHAR str[2];
2468 str[0] = charW;
2469 str[1] = '\0';
2471 if (descr->style & LBS_WANTKEYBOARDINPUT)
2473 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2474 MAKEWPARAM(charW, descr->focus_item),
2475 (LPARAM)descr->self );
2476 if (caret == -2) return 0;
2478 if (caret == -1 && !(descr->style & LBS_NODATA))
2479 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2480 if (caret != -1)
2482 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2483 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2484 LISTBOX_MoveCaret( descr, caret, TRUE );
2485 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2486 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2488 return 0;
2492 /***********************************************************************
2493 * LISTBOX_Create
2495 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2497 LB_DESCR *descr;
2498 MEASUREITEMSTRUCT mis;
2499 RECT rect;
2501 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2502 return FALSE;
2504 GetClientRect( hwnd, &rect );
2505 descr->self = hwnd;
2506 descr->owner = GetParent( descr->self );
2507 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2508 descr->width = rect.right - rect.left;
2509 descr->height = rect.bottom - rect.top;
2510 descr->items = NULL;
2511 descr->items_size = 0;
2512 descr->nb_items = 0;
2513 descr->top_item = 0;
2514 descr->selected_item = -1;
2515 descr->focus_item = 0;
2516 descr->anchor_item = -1;
2517 descr->item_height = 1;
2518 descr->page_size = 1;
2519 descr->column_width = 150;
2520 descr->horz_extent = 0;
2521 descr->horz_pos = 0;
2522 descr->nb_tabs = 0;
2523 descr->tabs = NULL;
2524 descr->wheel_remain = 0;
2525 descr->caret_on = !lphc;
2526 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2527 descr->in_focus = FALSE;
2528 descr->captured = FALSE;
2529 descr->font = 0;
2530 descr->locale = GetUserDefaultLCID();
2531 descr->lphc = lphc;
2533 if( lphc )
2535 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2536 descr->owner = lphc->self;
2539 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2541 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2543 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2544 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2545 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2546 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2547 descr->style &= ~LBS_NODATA;
2548 descr->item_height = LISTBOX_SetFont( descr, 0 );
2550 if (descr->style & LBS_OWNERDRAWFIXED)
2552 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2554 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2556 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2557 descr->item_height = lphc->fixedOwnerDrawHeight;
2559 else
2561 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2562 mis.CtlType = ODT_LISTBOX;
2563 mis.CtlID = id;
2564 mis.itemID = -1;
2565 mis.itemWidth = 0;
2566 mis.itemData = 0;
2567 mis.itemHeight = descr->item_height;
2568 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2569 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2573 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2574 return TRUE;
2578 /***********************************************************************
2579 * LISTBOX_Destroy
2581 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2583 LISTBOX_ResetContent( descr );
2584 SetWindowLongPtrW( descr->self, 0, 0 );
2585 HeapFree( GetProcessHeap(), 0, descr );
2586 return TRUE;
2590 /***********************************************************************
2591 * ListBoxWndProc_common
2593 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2595 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2596 LPHEADCOMBO lphc = 0;
2597 LRESULT ret;
2599 if (!descr)
2601 if (!IsWindow(hwnd)) return 0;
2603 if (msg == WM_CREATE)
2605 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2606 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2607 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2608 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2609 return 0;
2611 /* Ignore all other messages before we get a WM_CREATE */
2612 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2613 DefWindowProcA( hwnd, msg, wParam, lParam );
2615 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2617 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2618 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2620 switch(msg)
2622 case LB_RESETCONTENT:
2623 LISTBOX_ResetContent( descr );
2624 LISTBOX_UpdateScroll( descr );
2625 InvalidateRect( descr->self, NULL, TRUE );
2626 return 0;
2628 case LB_ADDSTRING:
2630 INT ret;
2631 LPWSTR textW;
2632 if(unicode || !HAS_STRINGS(descr))
2633 textW = (LPWSTR)lParam;
2634 else
2636 LPSTR textA = (LPSTR)lParam;
2637 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2638 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2639 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2640 else
2641 return LB_ERRSPACE;
2643 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2644 ret = LISTBOX_InsertString( descr, wParam, textW );
2645 if (!unicode && HAS_STRINGS(descr))
2646 HeapFree(GetProcessHeap(), 0, textW);
2647 return ret;
2650 case LB_INSERTSTRING:
2652 INT ret;
2653 LPWSTR textW;
2654 if(unicode || !HAS_STRINGS(descr))
2655 textW = (LPWSTR)lParam;
2656 else
2658 LPSTR textA = (LPSTR)lParam;
2659 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2660 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2661 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2662 else
2663 return LB_ERRSPACE;
2665 ret = LISTBOX_InsertString( descr, wParam, textW );
2666 if(!unicode && HAS_STRINGS(descr))
2667 HeapFree(GetProcessHeap(), 0, textW);
2668 return ret;
2671 case LB_ADDFILE:
2673 INT ret;
2674 LPWSTR textW;
2675 if(unicode || !HAS_STRINGS(descr))
2676 textW = (LPWSTR)lParam;
2677 else
2679 LPSTR textA = (LPSTR)lParam;
2680 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2681 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2682 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2683 else
2684 return LB_ERRSPACE;
2686 wParam = LISTBOX_FindFileStrPos( descr, textW );
2687 ret = LISTBOX_InsertString( descr, wParam, textW );
2688 if(!unicode && HAS_STRINGS(descr))
2689 HeapFree(GetProcessHeap(), 0, textW);
2690 return ret;
2693 case LB_DELETESTRING:
2694 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2695 return descr->nb_items;
2696 else
2698 SetLastError(ERROR_INVALID_INDEX);
2699 return LB_ERR;
2702 case LB_GETITEMDATA:
2703 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2705 SetLastError(ERROR_INVALID_INDEX);
2706 return LB_ERR;
2708 return get_item_data(descr, wParam);
2710 case LB_SETITEMDATA:
2711 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2713 SetLastError(ERROR_INVALID_INDEX);
2714 return LB_ERR;
2716 if (!(descr->style & LBS_NODATA)) descr->items[wParam].data = lParam;
2717 /* undocumented: returns TRUE, not LB_OKAY (0) */
2718 return TRUE;
2720 case LB_GETCOUNT:
2721 return descr->nb_items;
2723 case LB_GETTEXT:
2724 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2726 case LB_GETTEXTLEN:
2727 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2729 SetLastError(ERROR_INVALID_INDEX);
2730 return LB_ERR;
2732 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2733 if (unicode) return strlenW( descr->items[wParam].str );
2734 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2735 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2737 case LB_GETCURSEL:
2738 if (descr->nb_items == 0)
2739 return LB_ERR;
2740 if (!IS_MULTISELECT(descr))
2741 return descr->selected_item;
2742 if (descr->selected_item != -1)
2743 return descr->selected_item;
2744 return descr->focus_item;
2745 /* otherwise, if the user tries to move the selection with the */
2746 /* arrow keys, we will give the application something to choke on */
2747 case LB_GETTOPINDEX:
2748 return descr->top_item;
2750 case LB_GETITEMHEIGHT:
2751 return LISTBOX_GetItemHeight( descr, wParam );
2753 case LB_SETITEMHEIGHT:
2754 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2756 case LB_ITEMFROMPOINT:
2758 POINT pt;
2759 RECT rect;
2760 int index;
2761 BOOL hit = TRUE;
2763 /* The hiword of the return value is not a client area
2764 hittest as suggested by MSDN, but rather a hittest on
2765 the returned listbox item. */
2767 if(descr->nb_items == 0)
2768 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2770 pt.x = (short)LOWORD(lParam);
2771 pt.y = (short)HIWORD(lParam);
2773 SetRect(&rect, 0, 0, descr->width, descr->height);
2775 if(!PtInRect(&rect, pt))
2777 pt.x = min(pt.x, rect.right - 1);
2778 pt.x = max(pt.x, 0);
2779 pt.y = min(pt.y, rect.bottom - 1);
2780 pt.y = max(pt.y, 0);
2781 hit = FALSE;
2784 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2786 if(index == -1)
2788 index = descr->nb_items - 1;
2789 hit = FALSE;
2791 return MAKELONG(index, hit ? 0 : 1);
2794 case LB_SETCARETINDEX:
2795 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2796 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2797 return LB_ERR;
2798 else if (ISWIN31)
2799 return wParam;
2800 else
2801 return LB_OKAY;
2803 case LB_GETCARETINDEX:
2804 return descr->focus_item;
2806 case LB_SETTOPINDEX:
2807 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2809 case LB_SETCOLUMNWIDTH:
2810 return LISTBOX_SetColumnWidth( descr, wParam );
2812 case LB_GETITEMRECT:
2813 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2815 case LB_FINDSTRING:
2817 INT ret;
2818 LPWSTR textW;
2819 if(unicode || !HAS_STRINGS(descr))
2820 textW = (LPWSTR)lParam;
2821 else
2823 LPSTR textA = (LPSTR)lParam;
2824 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2825 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2826 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2828 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2829 if(!unicode && HAS_STRINGS(descr))
2830 HeapFree(GetProcessHeap(), 0, textW);
2831 return ret;
2834 case LB_FINDSTRINGEXACT:
2836 INT ret;
2837 LPWSTR textW;
2838 if(unicode || !HAS_STRINGS(descr))
2839 textW = (LPWSTR)lParam;
2840 else
2842 LPSTR textA = (LPSTR)lParam;
2843 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2844 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2845 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2847 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2848 if(!unicode && HAS_STRINGS(descr))
2849 HeapFree(GetProcessHeap(), 0, textW);
2850 return ret;
2853 case LB_SELECTSTRING:
2855 INT index;
2856 LPWSTR textW;
2858 if(HAS_STRINGS(descr))
2859 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2860 debugstr_a((LPSTR)lParam));
2861 if(unicode || !HAS_STRINGS(descr))
2862 textW = (LPWSTR)lParam;
2863 else
2865 LPSTR textA = (LPSTR)lParam;
2866 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2867 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2868 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2870 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2871 if(!unicode && HAS_STRINGS(descr))
2872 HeapFree(GetProcessHeap(), 0, textW);
2873 if (index != LB_ERR)
2875 LISTBOX_MoveCaret( descr, index, TRUE );
2876 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2878 return index;
2881 case LB_GETSEL:
2882 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2883 return LB_ERR;
2884 return is_item_selected(descr, wParam);
2886 case LB_SETSEL:
2887 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2888 if (ret != LB_ERR && wParam)
2889 descr->anchor_item = lParam;
2890 return ret;
2892 case LB_SETCURSEL:
2893 if (IS_MULTISELECT(descr)) return LB_ERR;
2894 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2895 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2896 if (ret != LB_ERR) ret = descr->selected_item;
2897 return ret;
2899 case LB_GETSELCOUNT:
2900 return LISTBOX_GetSelCount( descr );
2902 case LB_GETSELITEMS:
2903 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2905 case LB_SELITEMRANGE:
2906 if (LOWORD(lParam) <= HIWORD(lParam))
2907 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2908 HIWORD(lParam), wParam );
2909 else
2910 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2911 LOWORD(lParam), wParam );
2913 case LB_SELITEMRANGEEX:
2914 if ((INT)lParam >= (INT)wParam)
2915 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2916 else
2917 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2919 case LB_GETHORIZONTALEXTENT:
2920 return descr->horz_extent;
2922 case LB_SETHORIZONTALEXTENT:
2923 return LISTBOX_SetHorizontalExtent( descr, wParam );
2925 case LB_GETANCHORINDEX:
2926 return descr->anchor_item;
2928 case LB_SETANCHORINDEX:
2929 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2931 SetLastError(ERROR_INVALID_INDEX);
2932 return LB_ERR;
2934 descr->anchor_item = (INT)wParam;
2935 return LB_OKAY;
2937 case LB_DIR:
2939 INT ret;
2940 LPWSTR textW;
2941 if(unicode)
2942 textW = (LPWSTR)lParam;
2943 else
2945 LPSTR textA = (LPSTR)lParam;
2946 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2947 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2948 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2950 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2951 if(!unicode)
2952 HeapFree(GetProcessHeap(), 0, textW);
2953 return ret;
2956 case LB_GETLOCALE:
2957 return descr->locale;
2959 case LB_SETLOCALE:
2961 LCID ret;
2962 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2963 return LB_ERR;
2964 ret = descr->locale;
2965 descr->locale = (LCID)wParam;
2966 return ret;
2969 case LB_INITSTORAGE:
2970 return LISTBOX_InitStorage( descr, wParam );
2972 case LB_SETCOUNT:
2973 return LISTBOX_SetCount( descr, (INT)wParam );
2975 case LB_SETTABSTOPS:
2976 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2978 case LB_CARETON:
2979 if (descr->caret_on)
2980 return LB_OKAY;
2981 descr->caret_on = TRUE;
2982 if ((descr->focus_item != -1) && (descr->in_focus))
2983 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2984 return LB_OKAY;
2986 case LB_CARETOFF:
2987 if (!descr->caret_on)
2988 return LB_OKAY;
2989 descr->caret_on = FALSE;
2990 if ((descr->focus_item != -1) && (descr->in_focus))
2991 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2992 return LB_OKAY;
2994 case LB_GETLISTBOXINFO:
2995 return descr->page_size;
2997 case WM_DESTROY:
2998 return LISTBOX_Destroy( descr );
3000 case WM_ENABLE:
3001 InvalidateRect( descr->self, NULL, TRUE );
3002 return 0;
3004 case WM_SETREDRAW:
3005 LISTBOX_SetRedraw( descr, wParam != 0 );
3006 return 0;
3008 case WM_GETDLGCODE:
3009 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3011 case WM_PRINTCLIENT:
3012 case WM_PAINT:
3014 PAINTSTRUCT ps;
3015 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
3016 ret = LISTBOX_Paint( descr, hdc );
3017 if( !wParam ) EndPaint( descr->self, &ps );
3019 return ret;
3020 case WM_SIZE:
3021 LISTBOX_UpdateSize( descr );
3022 return 0;
3023 case WM_GETFONT:
3024 return (LRESULT)descr->font;
3025 case WM_SETFONT:
3026 LISTBOX_SetFont( descr, (HFONT)wParam );
3027 if (lParam) InvalidateRect( descr->self, 0, TRUE );
3028 return 0;
3029 case WM_SETFOCUS:
3030 descr->in_focus = TRUE;
3031 descr->caret_on = TRUE;
3032 if (descr->focus_item != -1)
3033 LISTBOX_DrawFocusRect( descr, TRUE );
3034 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
3035 return 0;
3036 case WM_KILLFOCUS:
3037 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
3038 descr->in_focus = FALSE;
3039 descr->wheel_remain = 0;
3040 if ((descr->focus_item != -1) && descr->caret_on)
3041 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3042 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3043 return 0;
3044 case WM_HSCROLL:
3045 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3046 case WM_VSCROLL:
3047 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3048 case WM_MOUSEWHEEL:
3049 if (wParam & (MK_SHIFT | MK_CONTROL))
3050 return DefWindowProcW( descr->self, msg, wParam, lParam );
3051 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3052 case WM_LBUTTONDOWN:
3053 if (lphc)
3054 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3055 (INT16)LOWORD(lParam),
3056 (INT16)HIWORD(lParam) );
3057 return LISTBOX_HandleLButtonDown( descr, wParam,
3058 (INT16)LOWORD(lParam),
3059 (INT16)HIWORD(lParam) );
3060 case WM_LBUTTONDBLCLK:
3061 if (lphc)
3062 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3063 (INT16)LOWORD(lParam),
3064 (INT16)HIWORD(lParam) );
3065 if (descr->style & LBS_NOTIFY)
3066 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3067 return 0;
3068 case WM_MOUSEMOVE:
3069 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3071 BOOL captured = descr->captured;
3072 POINT mousePos;
3073 RECT clientRect;
3075 mousePos.x = (INT16)LOWORD(lParam);
3076 mousePos.y = (INT16)HIWORD(lParam);
3079 * If we are in a dropdown combobox, we simulate that
3080 * the mouse is captured to show the tracking of the item.
3082 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3083 descr->captured = TRUE;
3085 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3087 descr->captured = captured;
3089 else if (GetCapture() == descr->self)
3091 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3092 (INT16)HIWORD(lParam) );
3094 return 0;
3095 case WM_LBUTTONUP:
3096 if (lphc)
3098 POINT mousePos;
3099 RECT clientRect;
3102 * If the mouse button "up" is not in the listbox,
3103 * we make sure there is no selection by re-selecting the
3104 * item that was selected when the listbox was made visible.
3106 mousePos.x = (INT16)LOWORD(lParam);
3107 mousePos.y = (INT16)HIWORD(lParam);
3109 GetClientRect(descr->self, &clientRect);
3112 * When the user clicks outside the combobox and the focus
3113 * is lost, the owning combobox will send a fake buttonup with
3114 * 0xFFFFFFF as the mouse location, we must also revert the
3115 * selection to the original selection.
3117 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3118 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3120 return LISTBOX_HandleLButtonUp( descr );
3121 case WM_KEYDOWN:
3122 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3124 /* for some reason Windows makes it possible to
3125 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3127 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3128 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3129 && (wParam == VK_DOWN || wParam == VK_UP)) )
3131 COMBO_FlipListbox( lphc, FALSE, FALSE );
3132 return 0;
3135 return LISTBOX_HandleKeyDown( descr, wParam );
3136 case WM_CHAR:
3138 WCHAR charW;
3139 if(unicode)
3140 charW = (WCHAR)wParam;
3141 else
3143 CHAR charA = (CHAR)wParam;
3144 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3146 return LISTBOX_HandleChar( descr, charW );
3148 case WM_SYSTIMER:
3149 return LISTBOX_HandleSystemTimer( descr );
3150 case WM_ERASEBKGND:
3151 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3153 RECT rect;
3154 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3155 wParam, (LPARAM)descr->self );
3156 TRACE("hbrush = %p\n", hbrush);
3157 if(!hbrush)
3158 hbrush = GetSysColorBrush(COLOR_WINDOW);
3159 if(hbrush)
3161 GetClientRect(descr->self, &rect);
3162 FillRect((HDC)wParam, &rect, hbrush);
3165 return 1;
3166 case WM_DROPFILES:
3167 if( lphc ) return 0;
3168 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3169 SendMessageA( descr->owner, msg, wParam, lParam );
3171 case WM_NCDESTROY:
3172 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3173 lphc->hWndLBox = 0;
3174 break;
3176 case WM_NCACTIVATE:
3177 if (lphc) return 0;
3178 break;
3180 default:
3181 if ((msg >= WM_USER) && (msg < 0xc000))
3182 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3183 hwnd, msg, wParam, lParam );
3186 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3187 DefWindowProcA( hwnd, msg, wParam, lParam );
3190 DWORD WINAPI GetListBoxInfo(HWND hwnd)
3192 TRACE("%p\n", hwnd);
3193 return SendMessageW(hwnd, LB_GETLISTBOXINFO, 0, 0);