comctl32/listbox: Store the items array size instead of using HeapSize().
[wine.git] / dlls / comctl32 / listbox.c
blobcb645b446724109552631ac40605028b120a0b6a
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
5 * Copyright 2005 Frank Richter
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * TODO:
22 * - LBS_NODATA
25 #include <string.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include "windef.h"
30 #include "winbase.h"
31 #include "wingdi.h"
32 #include "winuser.h"
33 #include "commctrl.h"
34 #include "uxtheme.h"
35 #include "vssym32.h"
36 #include "wine/unicode.h"
37 #include "wine/exception.h"
38 #include "wine/debug.h"
40 #include "comctl32.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
44 /* Items array granularity */
45 #define LB_ARRAY_GRANULARITY 16
47 /* Scrolling timeout in ms */
48 #define LB_SCROLL_TIMEOUT 50
50 /* Listbox system timer id */
51 #define LB_TIMER_ID 2
53 /* flag listbox changed while setredraw false - internal style */
54 #define LBS_DISPLAYCHANGED 0x80000000
56 /* Item structure */
57 typedef struct
59 LPWSTR str; /* Item text */
60 BOOL selected; /* Is item selected? */
61 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
62 ULONG_PTR data; /* User data */
63 } LB_ITEMDATA;
65 /* Listbox structure */
66 typedef struct
68 HWND self; /* Our own window handle */
69 HWND owner; /* Owner window to send notifications to */
70 UINT style; /* Window style */
71 INT width; /* Window width */
72 INT height; /* Window height */
73 LB_ITEMDATA *items; /* Array of items */
74 INT nb_items; /* Number of items */
75 UINT items_size; /* Total number of allocated items in the array */
76 INT top_item; /* Top visible item */
77 INT selected_item; /* Selected item */
78 INT focus_item; /* Item that has the focus */
79 INT anchor_item; /* Anchor item for extended selection */
80 INT item_height; /* Default item height */
81 INT page_size; /* Items per listbox page */
82 INT column_width; /* Column width for multi-column listboxes */
83 INT horz_extent; /* Horizontal extent */
84 INT horz_pos; /* Horizontal position */
85 INT nb_tabs; /* Number of tabs in array */
86 INT *tabs; /* Array of tabs */
87 INT avg_char_width; /* Average width of characters */
88 INT wheel_remain; /* Left over scroll amount */
89 BOOL caret_on; /* Is caret on? */
90 BOOL captured; /* Is mouse captured? */
91 BOOL in_focus;
92 HFONT font; /* Current font */
93 LCID locale; /* Current locale for string comparisons */
94 HEADCOMBO *lphc; /* ComboLBox */
95 } LB_DESCR;
98 #define IS_OWNERDRAW(descr) \
99 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
101 #define HAS_STRINGS(descr) \
102 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
105 #define IS_MULTISELECT(descr) \
106 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
107 !((descr)->style & LBS_NOSEL))
109 #define SEND_NOTIFICATION(descr,code) \
110 (SendMessageW( (descr)->owner, WM_COMMAND, \
111 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
113 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
115 /* Current timer status */
116 typedef enum
118 LB_TIMER_NONE,
119 LB_TIMER_UP,
120 LB_TIMER_LEFT,
121 LB_TIMER_DOWN,
122 LB_TIMER_RIGHT
123 } TIMER_DIRECTION;
125 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
127 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
129 static BOOL is_item_selected( const LB_DESCR *descr, UINT index )
131 return descr->items[index].selected;
134 /***********************************************************************
135 * LISTBOX_GetCurrentPageSize
137 * Return the current page size
139 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
141 INT i, height;
142 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
143 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
145 if ((height += descr->items[i].height) > descr->height) break;
147 if (i == descr->top_item) return 1;
148 else return i - descr->top_item;
152 /***********************************************************************
153 * LISTBOX_GetMaxTopIndex
155 * Return the maximum possible index for the top of the listbox.
157 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
159 INT max, page;
161 if (descr->style & LBS_OWNERDRAWVARIABLE)
163 page = descr->height;
164 for (max = descr->nb_items - 1; max >= 0; max--)
165 if ((page -= descr->items[max].height) < 0) break;
166 if (max < descr->nb_items - 1) max++;
168 else if (descr->style & LBS_MULTICOLUMN)
170 if ((page = descr->width / descr->column_width) < 1) page = 1;
171 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
172 max = (max - page) * descr->page_size;
174 else
176 max = descr->nb_items - descr->page_size;
178 if (max < 0) max = 0;
179 return max;
183 /***********************************************************************
184 * LISTBOX_UpdateScroll
186 * Update the scrollbars. Should be called whenever the content
187 * of the listbox changes.
189 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
191 SCROLLINFO info;
193 /* Check the listbox scroll bar flags individually before we call
194 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
195 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
196 scroll bar when we do not need one.
197 if (!(descr->style & WS_VSCROLL)) return;
200 /* It is important that we check descr->style, and not wnd->dwStyle,
201 for WS_VSCROLL, as the former is exactly the one passed in
202 argument to CreateWindow.
203 In Windows (and from now on in Wine :) a listbox created
204 with such a style (no WS_SCROLL) does not update
205 the scrollbar with listbox-related data, thus letting
206 the programmer use it for his/her own purposes. */
208 if (descr->style & LBS_NOREDRAW) return;
209 info.cbSize = sizeof(info);
211 if (descr->style & LBS_MULTICOLUMN)
213 info.nMin = 0;
214 info.nMax = (descr->nb_items - 1) / descr->page_size;
215 info.nPos = descr->top_item / descr->page_size;
216 info.nPage = descr->width / descr->column_width;
217 if (info.nPage < 1) info.nPage = 1;
218 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
219 if (descr->style & LBS_DISABLENOSCROLL)
220 info.fMask |= SIF_DISABLENOSCROLL;
221 if (descr->style & WS_HSCROLL)
222 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
223 info.nMax = 0;
224 info.fMask = SIF_RANGE;
225 if (descr->style & WS_VSCROLL)
226 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
228 else
230 info.nMin = 0;
231 info.nMax = descr->nb_items - 1;
232 info.nPos = descr->top_item;
233 info.nPage = LISTBOX_GetCurrentPageSize( descr );
234 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
235 if (descr->style & LBS_DISABLENOSCROLL)
236 info.fMask |= SIF_DISABLENOSCROLL;
237 if (descr->style & WS_VSCROLL)
238 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
240 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
242 info.nPos = descr->horz_pos;
243 info.nPage = descr->width;
244 info.fMask = SIF_POS | SIF_PAGE;
245 if (descr->style & LBS_DISABLENOSCROLL)
246 info.fMask |= SIF_DISABLENOSCROLL;
247 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
249 else
251 if (descr->style & LBS_DISABLENOSCROLL)
253 info.nMin = 0;
254 info.nMax = 0;
255 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
256 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
258 else
260 ShowScrollBar( descr->self, SB_HORZ, FALSE );
267 /***********************************************************************
268 * LISTBOX_SetTopItem
270 * Set the top item of the listbox, scrolling up or down if necessary.
272 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
274 INT max = LISTBOX_GetMaxTopIndex( descr );
276 TRACE("setting top item %d, scroll %d\n", index, scroll);
278 if (index > max) index = max;
279 if (index < 0) index = 0;
280 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
281 if (descr->top_item == index) return LB_OKAY;
282 if (scroll)
284 INT dx = 0, dy = 0;
285 if (descr->style & LBS_MULTICOLUMN)
286 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
287 else if (descr->style & LBS_OWNERDRAWVARIABLE)
289 INT i;
290 if (index > descr->top_item)
292 for (i = index - 1; i >= descr->top_item; i--)
293 dy -= descr->items[i].height;
295 else
297 for (i = index; i < descr->top_item; i++)
298 dy += descr->items[i].height;
301 else
302 dy = (descr->top_item - index) * descr->item_height;
304 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
305 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
307 else
308 InvalidateRect( descr->self, NULL, TRUE );
309 descr->top_item = index;
310 LISTBOX_UpdateScroll( descr );
311 return LB_OKAY;
315 /***********************************************************************
316 * LISTBOX_UpdatePage
318 * Update the page size. Should be called when the size of
319 * the client area or the item height changes.
321 static void LISTBOX_UpdatePage( LB_DESCR *descr )
323 INT page_size;
325 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
326 page_size = 1;
327 if (page_size == descr->page_size) return;
328 descr->page_size = page_size;
329 if (descr->style & LBS_MULTICOLUMN)
330 InvalidateRect( descr->self, NULL, TRUE );
331 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
335 /***********************************************************************
336 * LISTBOX_UpdateSize
338 * Update the size of the listbox. Should be called when the size of
339 * the client area changes.
341 static void LISTBOX_UpdateSize( LB_DESCR *descr )
343 RECT rect;
345 GetClientRect( descr->self, &rect );
346 descr->width = rect.right - rect.left;
347 descr->height = rect.bottom - rect.top;
348 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
350 INT remaining;
351 RECT rect;
353 GetWindowRect( descr->self, &rect );
354 if(descr->item_height != 0)
355 remaining = descr->height % descr->item_height;
356 else
357 remaining = 0;
358 if ((descr->height > descr->item_height) && remaining)
360 TRACE("[%p]: changing height %d -> %d\n",
361 descr->self, descr->height, descr->height - remaining );
362 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
363 rect.bottom - rect.top - remaining,
364 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
365 return;
368 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
369 LISTBOX_UpdatePage( descr );
370 LISTBOX_UpdateScroll( descr );
372 /* Invalidate the focused item so it will be repainted correctly */
373 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
375 InvalidateRect( descr->self, &rect, FALSE );
380 /***********************************************************************
381 * LISTBOX_GetItemRect
383 * Get the rectangle enclosing an item, in listbox client coordinates.
384 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
386 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
388 /* Index <= 0 is legal even on empty listboxes */
389 if (index && (index >= descr->nb_items))
391 SetRectEmpty(rect);
392 SetLastError(ERROR_INVALID_INDEX);
393 return LB_ERR;
395 SetRect( rect, 0, 0, descr->width, descr->height );
396 if (descr->style & LBS_MULTICOLUMN)
398 INT col = (index / descr->page_size) -
399 (descr->top_item / descr->page_size);
400 rect->left += col * descr->column_width;
401 rect->right = rect->left + descr->column_width;
402 rect->top += (index % descr->page_size) * descr->item_height;
403 rect->bottom = rect->top + descr->item_height;
405 else if (descr->style & LBS_OWNERDRAWVARIABLE)
407 INT i;
408 rect->right += descr->horz_pos;
409 if ((index >= 0) && (index < descr->nb_items))
411 if (index < descr->top_item)
413 for (i = descr->top_item-1; i >= index; i--)
414 rect->top -= descr->items[i].height;
416 else
418 for (i = descr->top_item; i < index; i++)
419 rect->top += descr->items[i].height;
421 rect->bottom = rect->top + descr->items[index].height;
425 else
427 rect->top += (index - descr->top_item) * descr->item_height;
428 rect->bottom = rect->top + descr->item_height;
429 rect->right += descr->horz_pos;
432 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
434 return ((rect->left < descr->width) && (rect->right > 0) &&
435 (rect->top < descr->height) && (rect->bottom > 0));
439 /***********************************************************************
440 * LISTBOX_GetItemFromPoint
442 * Return the item nearest from point (x,y) (in client coordinates).
444 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
446 INT index = descr->top_item;
448 if (!descr->nb_items) return -1; /* No items */
449 if (descr->style & LBS_OWNERDRAWVARIABLE)
451 INT pos = 0;
452 if (y >= 0)
454 while (index < descr->nb_items)
456 if ((pos += descr->items[index].height) > y) break;
457 index++;
460 else
462 while (index > 0)
464 index--;
465 if ((pos -= descr->items[index].height) <= y) break;
469 else if (descr->style & LBS_MULTICOLUMN)
471 if (y >= descr->item_height * descr->page_size) return -1;
472 if (y >= 0) index += y / descr->item_height;
473 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
474 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
476 else
478 index += (y / descr->item_height);
480 if (index < 0) return 0;
481 if (index >= descr->nb_items) return -1;
482 return index;
486 /***********************************************************************
487 * LISTBOX_PaintItem
489 * Paint an item.
491 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
492 INT index, UINT action, BOOL ignoreFocus )
494 BOOL selected = FALSE, focused;
495 LB_ITEMDATA *item = NULL;
497 if (index < descr->nb_items)
499 item = &descr->items[index];
500 selected = is_item_selected(descr, index);
503 focused = !ignoreFocus && descr->focus_item == index && descr->caret_on && descr->in_focus;
505 if (IS_OWNERDRAW(descr))
507 DRAWITEMSTRUCT dis;
508 RECT r;
509 HRGN hrgn;
511 if (!item)
513 if (action == ODA_FOCUS)
514 DrawFocusRect( hdc, rect );
515 else
516 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
517 return;
520 /* some programs mess with the clipping region when
521 drawing the item, *and* restore the previous region
522 after they are done, so a region has better to exist
523 else everything ends clipped */
524 GetClientRect(descr->self, &r);
525 hrgn = set_control_clipping( hdc, &r );
527 dis.CtlType = ODT_LISTBOX;
528 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
529 dis.hwndItem = descr->self;
530 dis.itemAction = action;
531 dis.hDC = hdc;
532 dis.itemID = index;
533 dis.itemState = 0;
534 if (selected)
535 dis.itemState |= ODS_SELECTED;
536 if (focused)
537 dis.itemState |= ODS_FOCUS;
538 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
539 dis.itemData = item ? item->data : 0;
540 dis.rcItem = *rect;
541 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
542 descr->self, index, item ? debugstr_w(item->str) : "", action,
543 dis.itemState, wine_dbgstr_rect(rect) );
544 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
545 SelectClipRgn( hdc, hrgn );
546 if (hrgn) DeleteObject( hrgn );
548 else
550 COLORREF oldText = 0, oldBk = 0;
552 if (action == ODA_FOCUS)
554 DrawFocusRect( hdc, rect );
555 return;
557 if (selected)
559 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
560 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
563 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
564 descr->self, index, item ? debugstr_w(item->str) : "", action,
565 wine_dbgstr_rect(rect) );
566 if (!item)
567 ExtTextOutW( hdc, rect->left + 1, rect->top,
568 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
569 else if (!(descr->style & LBS_USETABSTOPS))
570 ExtTextOutW( hdc, rect->left + 1, rect->top,
571 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
572 strlenW(item->str), NULL );
573 else
575 /* Output empty string to paint background in the full width. */
576 ExtTextOutW( hdc, rect->left + 1, rect->top,
577 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
578 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
579 item->str, strlenW(item->str),
580 descr->nb_tabs, descr->tabs, 0);
582 if (selected)
584 SetBkColor( hdc, oldBk );
585 SetTextColor( hdc, oldText );
587 if (focused)
588 DrawFocusRect( hdc, rect );
593 /***********************************************************************
594 * LISTBOX_SetRedraw
596 * Change the redraw flag.
598 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
600 if (on)
602 if (!(descr->style & LBS_NOREDRAW)) return;
603 descr->style &= ~LBS_NOREDRAW;
604 if (descr->style & LBS_DISPLAYCHANGED)
605 { /* page was changed while setredraw false, refresh automatically */
606 InvalidateRect(descr->self, NULL, TRUE);
607 if ((descr->top_item + descr->page_size) > descr->nb_items)
608 { /* reset top of page if less than number of items/page */
609 descr->top_item = descr->nb_items - descr->page_size;
610 if (descr->top_item < 0) descr->top_item = 0;
612 descr->style &= ~LBS_DISPLAYCHANGED;
614 LISTBOX_UpdateScroll( descr );
616 else descr->style |= LBS_NOREDRAW;
620 /***********************************************************************
621 * LISTBOX_RepaintItem
623 * Repaint a single item synchronously.
625 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
627 HDC hdc;
628 RECT rect;
629 HFONT oldFont = 0;
630 HBRUSH hbrush, oldBrush = 0;
632 /* Do not repaint the item if the item is not visible */
633 if (!IsWindowVisible(descr->self)) return;
634 if (descr->style & LBS_NOREDRAW)
636 descr->style |= LBS_DISPLAYCHANGED;
637 return;
639 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
640 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
641 if (descr->font) oldFont = SelectObject( hdc, descr->font );
642 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
643 (WPARAM)hdc, (LPARAM)descr->self );
644 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
645 if (!IsWindowEnabled(descr->self))
646 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
647 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
648 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
649 if (oldFont) SelectObject( hdc, oldFont );
650 if (oldBrush) SelectObject( hdc, oldBrush );
651 ReleaseDC( descr->self, hdc );
655 /***********************************************************************
656 * LISTBOX_DrawFocusRect
658 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
660 HDC hdc;
661 RECT rect;
662 HFONT oldFont = 0;
664 /* Do not repaint the item if the item is not visible */
665 if (!IsWindowVisible(descr->self)) return;
667 if (descr->focus_item == -1) return;
668 if (!descr->caret_on || !descr->in_focus) return;
670 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
671 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
672 if (descr->font) oldFont = SelectObject( hdc, descr->font );
673 if (!IsWindowEnabled(descr->self))
674 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
675 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
676 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
677 if (oldFont) SelectObject( hdc, oldFont );
678 ReleaseDC( descr->self, hdc );
682 /***********************************************************************
683 * LISTBOX_InitStorage
685 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
687 LB_ITEMDATA *item;
689 nb_items += LB_ARRAY_GRANULARITY - 1;
690 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
691 if (descr->items) {
692 nb_items += descr->items_size;
693 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
694 nb_items * sizeof(LB_ITEMDATA));
696 else {
697 item = HeapAlloc( GetProcessHeap(), 0,
698 nb_items * sizeof(LB_ITEMDATA));
701 if (!item)
703 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
704 return LB_ERRSPACE;
706 descr->items_size = nb_items;
707 descr->items = item;
708 return LB_OKAY;
712 /***********************************************************************
713 * LISTBOX_SetTabStops
715 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
717 INT i;
719 if (!(descr->style & LBS_USETABSTOPS))
721 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
722 return FALSE;
725 HeapFree( GetProcessHeap(), 0, descr->tabs );
726 if (!(descr->nb_tabs = count))
728 descr->tabs = NULL;
729 return TRUE;
731 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
732 descr->nb_tabs * sizeof(INT) )))
733 return FALSE;
734 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
736 /* convert into "dialog units"*/
737 for (i = 0; i < descr->nb_tabs; i++)
738 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
740 return TRUE;
744 /***********************************************************************
745 * LISTBOX_GetText
747 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
749 DWORD len;
751 if ((index < 0) || (index >= descr->nb_items))
753 SetLastError(ERROR_INVALID_INDEX);
754 return LB_ERR;
757 if (HAS_STRINGS(descr))
759 if (!buffer)
760 return strlenW(descr->items[index].str);
762 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
764 __TRY /* hide a Delphi bug that passes a read-only buffer */
766 strcpyW( buffer, descr->items[index].str );
767 len = strlenW(buffer);
769 __EXCEPT_PAGE_FAULT
771 WARN( "got an invalid buffer (Delphi bug?)\n" );
772 SetLastError( ERROR_INVALID_PARAMETER );
773 return LB_ERR;
775 __ENDTRY
776 } else
778 if (buffer)
779 *((ULONG_PTR *)buffer) = (descr->style & LBS_NODATA)
780 ? 0 : descr->items[index].data;
781 len = sizeof(ULONG_PTR);
783 return len;
786 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
788 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
789 if (ret == CSTR_LESS_THAN)
790 return -1;
791 if (ret == CSTR_EQUAL)
792 return 0;
793 if (ret == CSTR_GREATER_THAN)
794 return 1;
795 return -1;
798 /***********************************************************************
799 * LISTBOX_FindStringPos
801 * Find the nearest string located before a given string in sort order.
802 * If 'exact' is TRUE, return an error if we don't get an exact match.
804 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
806 INT index, min, max, res;
808 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
810 min = 0;
811 max = descr->nb_items - 1;
812 while (min <= max)
814 index = (min + max) / 2;
815 if (HAS_STRINGS(descr))
816 res = LISTBOX_lstrcmpiW( descr->locale, descr->items[index].str, str );
817 else
819 COMPAREITEMSTRUCT cis;
820 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
822 cis.CtlType = ODT_LISTBOX;
823 cis.CtlID = id;
824 cis.hwndItem = descr->self;
825 /* note that some application (MetaStock) expects the second item
826 * to be in the listbox */
827 cis.itemID1 = index;
828 cis.itemData1 = descr->items[index].data;
829 cis.itemID2 = -1;
830 cis.itemData2 = (ULONG_PTR)str;
831 cis.dwLocaleId = descr->locale;
832 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
834 if (!res) return index;
835 if (res > 0) max = index - 1;
836 else min = index + 1;
838 return exact ? -1 : min;
842 /***********************************************************************
843 * LISTBOX_FindFileStrPos
845 * Find the nearest string located before a given string in directory
846 * sort order (i.e. first files, then directories, then drives).
848 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
850 INT min, max, res;
852 if (!HAS_STRINGS(descr))
853 return LISTBOX_FindStringPos( descr, str, FALSE );
854 min = 0;
855 max = descr->nb_items;
856 while (min != max)
858 INT index = (min + max) / 2;
859 LPCWSTR p = descr->items[index].str;
860 if (*p == '[') /* drive or directory */
862 if (*str != '[') res = -1;
863 else if (p[1] == '-') /* drive */
865 if (str[1] == '-') res = str[2] - p[2];
866 else res = -1;
868 else /* directory */
870 if (str[1] == '-') res = 1;
871 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
874 else /* filename */
876 if (*str == '[') res = 1;
877 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
879 if (!res) return index;
880 if (res < 0) max = index;
881 else min = index + 1;
883 return max;
887 /***********************************************************************
888 * LISTBOX_FindString
890 * Find the item beginning with a given string.
892 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
894 INT i;
895 LB_ITEMDATA *item;
897 if (descr->style & LBS_NODATA) return LB_ERR;
899 if (start >= descr->nb_items) start = -1;
900 item = descr->items + start + 1;
901 if (HAS_STRINGS(descr))
903 if (!str || ! str[0] ) return LB_ERR;
904 if (exact)
906 for (i = start + 1; i < descr->nb_items; i++, item++)
907 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
908 for (i = 0, item = descr->items; i <= start; i++, item++)
909 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
911 else
913 /* Special case for drives and directories: ignore prefix */
914 #define CHECK_DRIVE(item) \
915 if ((item)->str[0] == '[') \
917 if (!strncmpiW( str, (item)->str+1, len )) return i; \
918 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
919 return i; \
922 INT len = strlenW(str);
923 for (i = start + 1; i < descr->nb_items; i++, item++)
925 if (!strncmpiW( str, item->str, len )) return i;
926 CHECK_DRIVE(item);
928 for (i = 0, item = descr->items; i <= start; i++, item++)
930 if (!strncmpiW( str, item->str, len )) return i;
931 CHECK_DRIVE(item);
933 #undef CHECK_DRIVE
936 else
938 if (exact && (descr->style & LBS_SORT))
939 /* If sorted, use a WM_COMPAREITEM binary search */
940 return LISTBOX_FindStringPos( descr, str, TRUE );
942 /* Otherwise use a linear search */
943 for (i = start + 1; i < descr->nb_items; i++, item++)
944 if (item->data == (ULONG_PTR)str) return i;
945 for (i = 0, item = descr->items; i <= start; i++, item++)
946 if (item->data == (ULONG_PTR)str) return i;
948 return LB_ERR;
952 /***********************************************************************
953 * LISTBOX_GetSelCount
955 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
957 INT i, count;
958 const LB_ITEMDATA *item = descr->items;
960 if (!(descr->style & LBS_MULTIPLESEL) ||
961 (descr->style & LBS_NOSEL))
962 return LB_ERR;
963 for (i = count = 0; i < descr->nb_items; i++, item++)
964 if (item->selected) count++;
965 return count;
969 /***********************************************************************
970 * LISTBOX_GetSelItems
972 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
974 INT i, count;
975 const LB_ITEMDATA *item = descr->items;
977 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
978 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
979 if (item->selected) array[count++] = i;
980 return count;
984 /***********************************************************************
985 * LISTBOX_Paint
987 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
989 INT i, col_pos = descr->page_size - 1;
990 RECT rect;
991 RECT focusRect = {-1, -1, -1, -1};
992 HFONT oldFont = 0;
993 HBRUSH hbrush, oldBrush = 0;
995 if (descr->style & LBS_NOREDRAW) return 0;
997 SetRect( &rect, 0, 0, descr->width, descr->height );
998 if (descr->style & LBS_MULTICOLUMN)
999 rect.right = rect.left + descr->column_width;
1000 else if (descr->horz_pos)
1002 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1003 rect.right += descr->horz_pos;
1006 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1007 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1008 (WPARAM)hdc, (LPARAM)descr->self );
1009 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1010 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1012 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1013 (descr->in_focus))
1015 /* Special case for empty listbox: paint focus rect */
1016 rect.bottom = rect.top + descr->item_height;
1017 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1018 &rect, NULL, 0, NULL );
1019 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1020 rect.top = rect.bottom;
1023 /* Paint all the item, regarding the selection
1024 Focus state will be painted after */
1026 for (i = descr->top_item; i < descr->nb_items; i++)
1028 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1029 rect.bottom = rect.top + descr->item_height;
1030 else
1031 rect.bottom = rect.top + descr->items[i].height;
1033 /* keep the focus rect, to paint the focus item after */
1034 if (i == descr->focus_item)
1035 focusRect = rect;
1037 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1038 rect.top = rect.bottom;
1040 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1042 if (!IS_OWNERDRAW(descr))
1044 /* Clear the bottom of the column */
1045 if (rect.top < descr->height)
1047 rect.bottom = descr->height;
1048 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1049 &rect, NULL, 0, NULL );
1053 /* Go to the next column */
1054 rect.left += descr->column_width;
1055 rect.right += descr->column_width;
1056 rect.top = 0;
1057 col_pos = descr->page_size - 1;
1059 else
1061 col_pos--;
1062 if (rect.top >= descr->height) break;
1066 /* Paint the focus item now */
1067 if (focusRect.top != focusRect.bottom &&
1068 descr->caret_on && descr->in_focus)
1069 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1071 if (!IS_OWNERDRAW(descr))
1073 /* Clear the remainder of the client area */
1074 if (rect.top < descr->height)
1076 rect.bottom = descr->height;
1077 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1078 &rect, NULL, 0, NULL );
1080 if (rect.right < descr->width)
1082 rect.left = rect.right;
1083 rect.right = descr->width;
1084 rect.top = 0;
1085 rect.bottom = descr->height;
1086 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1087 &rect, NULL, 0, NULL );
1090 if (oldFont) SelectObject( hdc, oldFont );
1091 if (oldBrush) SelectObject( hdc, oldBrush );
1092 return 0;
1095 static void LISTBOX_NCPaint( LB_DESCR *descr, HRGN region )
1097 DWORD exstyle = GetWindowLongW( descr->self, GWL_EXSTYLE);
1098 HTHEME theme = GetWindowTheme( descr->self );
1099 HRGN cliprgn = region;
1100 int cxEdge, cyEdge;
1101 HDC hdc;
1102 RECT r;
1104 if (!theme || !(exstyle & WS_EX_CLIENTEDGE))
1105 return;
1107 cxEdge = GetSystemMetrics(SM_CXEDGE),
1108 cyEdge = GetSystemMetrics(SM_CYEDGE);
1110 GetWindowRect(descr->self, &r);
1112 /* New clipping region passed to default proc to exclude border */
1113 cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge,
1114 r.right - cxEdge, r.bottom - cyEdge);
1115 if (region != (HRGN)1)
1116 CombineRgn(cliprgn, cliprgn, region, RGN_AND);
1117 OffsetRect(&r, -r.left, -r.top);
1119 hdc = GetDCEx(descr->self, region, DCX_WINDOW|DCX_INTERSECTRGN);
1120 OffsetRect(&r, -r.left, -r.top);
1122 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
1123 DrawThemeParentBackground(descr->self, hdc, &r);
1124 DrawThemeBackground (theme, hdc, 0, 0, &r, 0);
1125 ReleaseDC(descr->self, hdc);
1128 /***********************************************************************
1129 * LISTBOX_InvalidateItems
1131 * Invalidate all items from a given item. If the specified item is not
1132 * visible, nothing happens.
1134 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1136 RECT rect;
1138 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1140 if (descr->style & LBS_NOREDRAW)
1142 descr->style |= LBS_DISPLAYCHANGED;
1143 return;
1145 rect.bottom = descr->height;
1146 InvalidateRect( descr->self, &rect, TRUE );
1147 if (descr->style & LBS_MULTICOLUMN)
1149 /* Repaint the other columns */
1150 rect.left = rect.right;
1151 rect.right = descr->width;
1152 rect.top = 0;
1153 InvalidateRect( descr->self, &rect, TRUE );
1158 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1160 RECT rect;
1162 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1163 InvalidateRect( descr->self, &rect, TRUE );
1166 /***********************************************************************
1167 * LISTBOX_GetItemHeight
1169 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1171 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1173 if ((index < 0) || (index >= descr->nb_items))
1175 SetLastError(ERROR_INVALID_INDEX);
1176 return LB_ERR;
1178 return descr->items[index].height;
1180 else return descr->item_height;
1184 /***********************************************************************
1185 * LISTBOX_SetItemHeight
1187 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1189 if (height > MAXBYTE)
1190 return -1;
1192 if (!height) height = 1;
1194 if (descr->style & LBS_OWNERDRAWVARIABLE)
1196 if ((index < 0) || (index >= descr->nb_items))
1198 SetLastError(ERROR_INVALID_INDEX);
1199 return LB_ERR;
1201 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1202 descr->items[index].height = height;
1203 LISTBOX_UpdateScroll( descr );
1204 if (repaint)
1205 LISTBOX_InvalidateItems( descr, index );
1207 else if (height != descr->item_height)
1209 TRACE("[%p]: new height = %d\n", descr->self, height );
1210 descr->item_height = height;
1211 LISTBOX_UpdatePage( descr );
1212 LISTBOX_UpdateScroll( descr );
1213 if (repaint)
1214 InvalidateRect( descr->self, 0, TRUE );
1216 return LB_OKAY;
1220 /***********************************************************************
1221 * LISTBOX_SetHorizontalPos
1223 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1225 INT diff;
1227 if (pos > descr->horz_extent - descr->width)
1228 pos = descr->horz_extent - descr->width;
1229 if (pos < 0) pos = 0;
1230 if (!(diff = descr->horz_pos - pos)) return;
1231 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1232 descr->horz_pos = pos;
1233 LISTBOX_UpdateScroll( descr );
1234 if (abs(diff) < descr->width)
1236 RECT rect;
1237 /* Invalidate the focused item so it will be repainted correctly */
1238 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1239 InvalidateRect( descr->self, &rect, TRUE );
1240 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1241 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1243 else
1244 InvalidateRect( descr->self, NULL, TRUE );
1248 /***********************************************************************
1249 * LISTBOX_SetHorizontalExtent
1251 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1253 if (descr->style & LBS_MULTICOLUMN)
1254 return LB_OKAY;
1255 if (extent == descr->horz_extent) return LB_OKAY;
1256 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1257 descr->horz_extent = extent;
1258 if (descr->style & WS_HSCROLL) {
1259 SCROLLINFO info;
1260 info.cbSize = sizeof(info);
1261 info.nMin = 0;
1262 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1263 info.fMask = SIF_RANGE;
1264 if (descr->style & LBS_DISABLENOSCROLL)
1265 info.fMask |= SIF_DISABLENOSCROLL;
1266 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1268 if (descr->horz_pos > extent - descr->width)
1269 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1270 return LB_OKAY;
1274 /***********************************************************************
1275 * LISTBOX_SetColumnWidth
1277 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT column_width)
1279 RECT rect;
1281 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1283 GetClientRect(descr->self, &rect);
1284 descr->width = rect.right - rect.left;
1285 descr->height = rect.bottom - rect.top;
1286 descr->column_width = column_width;
1288 LISTBOX_UpdatePage(descr);
1289 LISTBOX_UpdateScroll(descr);
1290 return LB_OKAY;
1294 /***********************************************************************
1295 * LISTBOX_SetFont
1297 * Returns the item height.
1299 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1301 HDC hdc;
1302 HFONT oldFont = 0;
1303 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1304 SIZE sz;
1306 descr->font = font;
1308 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1310 ERR("unable to get DC.\n" );
1311 return 16;
1313 if (font) oldFont = SelectObject( hdc, font );
1314 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1315 if (oldFont) SelectObject( hdc, oldFont );
1316 ReleaseDC( descr->self, hdc );
1318 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1319 if (!IS_OWNERDRAW(descr))
1320 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1321 return sz.cy;
1325 /***********************************************************************
1326 * LISTBOX_MakeItemVisible
1328 * Make sure that a given item is partially or fully visible.
1330 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1332 INT top;
1334 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1336 if (index <= descr->top_item) top = index;
1337 else if (descr->style & LBS_MULTICOLUMN)
1339 INT cols = descr->width;
1340 if (!fully) cols += descr->column_width - 1;
1341 if (cols >= descr->column_width) cols /= descr->column_width;
1342 else cols = 1;
1343 if (index < descr->top_item + (descr->page_size * cols)) return;
1344 top = index - descr->page_size * (cols - 1);
1346 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1348 INT height = fully ? descr->items[index].height : 1;
1349 for (top = index; top > descr->top_item; top--)
1350 if ((height += descr->items[top-1].height) > descr->height) break;
1352 else
1354 if (index < descr->top_item + descr->page_size) return;
1355 if (!fully && (index == descr->top_item + descr->page_size) &&
1356 (descr->height > (descr->page_size * descr->item_height))) return;
1357 top = index - descr->page_size + 1;
1359 LISTBOX_SetTopItem( descr, top, TRUE );
1362 /***********************************************************************
1363 * LISTBOX_SetCaretIndex
1365 * NOTES
1366 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1369 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1371 INT oldfocus = descr->focus_item;
1373 TRACE("old focus %d, index %d\n", oldfocus, index);
1375 if (descr->style & LBS_NOSEL) return LB_ERR;
1376 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1377 if (index == oldfocus) return LB_OKAY;
1379 LISTBOX_DrawFocusRect( descr, FALSE );
1380 descr->focus_item = index;
1382 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1383 LISTBOX_DrawFocusRect( descr, TRUE );
1385 return LB_OKAY;
1389 /***********************************************************************
1390 * LISTBOX_SelectItemRange
1392 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1394 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1395 INT last, BOOL on )
1397 INT i;
1399 /* A few sanity checks */
1401 if (descr->style & LBS_NOSEL) return LB_ERR;
1402 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1404 if (!descr->nb_items) return LB_OKAY;
1406 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1407 if (first < 0) first = 0;
1408 if (last < first) return LB_OKAY;
1410 if (on) /* Turn selection on */
1412 for (i = first; i <= last; i++)
1414 if (descr->items[i].selected) continue;
1415 descr->items[i].selected = TRUE;
1416 LISTBOX_InvalidateItemRect(descr, i);
1419 else /* Turn selection off */
1421 for (i = first; i <= last; i++)
1423 if (!descr->items[i].selected) continue;
1424 descr->items[i].selected = FALSE;
1425 LISTBOX_InvalidateItemRect(descr, i);
1428 return LB_OKAY;
1431 /***********************************************************************
1432 * LISTBOX_SetSelection
1434 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1435 BOOL on, BOOL send_notify )
1437 TRACE( "cur_sel=%d index=%d notify=%s\n",
1438 descr->selected_item, index, send_notify ? "YES" : "NO" );
1440 if (descr->style & LBS_NOSEL)
1442 descr->selected_item = index;
1443 return LB_ERR;
1445 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1446 if (descr->style & LBS_MULTIPLESEL)
1448 if (index == -1) /* Select all items */
1449 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1450 else /* Only one item */
1451 return LISTBOX_SelectItemRange( descr, index, index, on );
1453 else
1455 INT oldsel = descr->selected_item;
1456 if (index == oldsel) return LB_OKAY;
1457 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1458 if (index != -1) descr->items[index].selected = TRUE;
1459 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1460 descr->selected_item = index;
1461 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1462 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1463 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1464 else
1465 if( descr->lphc ) /* set selection change flag for parent combo */
1466 descr->lphc->wState |= CBF_SELCHANGE;
1468 return LB_OKAY;
1472 /***********************************************************************
1473 * LISTBOX_MoveCaret
1475 * Change the caret position and extend the selection to the new caret.
1477 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1479 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1481 if ((index < 0) || (index >= descr->nb_items))
1482 return;
1484 /* Important, repaint needs to be done in this order if
1485 you want to mimic Windows behavior:
1486 1. Remove the focus and paint the item
1487 2. Remove the selection and paint the item(s)
1488 3. Set the selection and repaint the item(s)
1489 4. Set the focus to 'index' and repaint the item */
1491 /* 1. remove the focus and repaint the item */
1492 LISTBOX_DrawFocusRect( descr, FALSE );
1494 /* 2. then turn off the previous selection */
1495 /* 3. repaint the new selected item */
1496 if (descr->style & LBS_EXTENDEDSEL)
1498 if (descr->anchor_item != -1)
1500 INT first = min( index, descr->anchor_item );
1501 INT last = max( index, descr->anchor_item );
1502 if (first > 0)
1503 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1504 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1505 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1508 else if (!(descr->style & LBS_MULTIPLESEL))
1510 /* Set selection to new caret item */
1511 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1514 /* 4. repaint the new item with the focus */
1515 descr->focus_item = index;
1516 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1517 LISTBOX_DrawFocusRect( descr, TRUE );
1521 /***********************************************************************
1522 * LISTBOX_InsertItem
1524 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1525 LPWSTR str, ULONG_PTR data )
1527 LB_ITEMDATA *item;
1528 INT max_items;
1529 INT oldfocus = descr->focus_item;
1531 if (index == -1) index = descr->nb_items;
1532 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1533 if (descr->nb_items == descr->items_size)
1535 /* We need to grow the array */
1536 max_items = descr->items_size + LB_ARRAY_GRANULARITY;
1537 if (descr->items)
1538 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1539 max_items * sizeof(LB_ITEMDATA) );
1540 else
1541 item = HeapAlloc( GetProcessHeap(), 0,
1542 max_items * sizeof(LB_ITEMDATA) );
1543 if (!item)
1545 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1546 return LB_ERRSPACE;
1548 descr->items_size = max_items;
1549 descr->items = item;
1552 /* Insert the item structure */
1554 item = &descr->items[index];
1555 if (index < descr->nb_items)
1556 RtlMoveMemory( item + 1, item,
1557 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1558 item->str = str;
1559 item->data = HAS_STRINGS(descr) ? 0 : data;
1560 item->height = 0;
1561 item->selected = FALSE;
1562 descr->nb_items++;
1564 /* Get item height */
1566 if (descr->style & LBS_OWNERDRAWVARIABLE)
1568 MEASUREITEMSTRUCT mis;
1569 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1571 mis.CtlType = ODT_LISTBOX;
1572 mis.CtlID = id;
1573 mis.itemID = index;
1574 mis.itemData = data;
1575 mis.itemHeight = descr->item_height;
1576 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1577 item->height = mis.itemHeight ? mis.itemHeight : 1;
1578 TRACE("[%p]: measure item %d (%s) = %d\n",
1579 descr->self, index, str ? debugstr_w(str) : "", item->height );
1582 /* Repaint the items */
1584 LISTBOX_UpdateScroll( descr );
1585 LISTBOX_InvalidateItems( descr, index );
1587 /* Move selection and focused item */
1588 /* If listbox was empty, set focus to the first item */
1589 if (descr->nb_items == 1)
1590 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1591 /* single select don't change selection index in win31 */
1592 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1594 descr->selected_item++;
1595 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1597 else
1599 if (index <= descr->selected_item)
1601 descr->selected_item++;
1602 descr->focus_item = oldfocus; /* focus not changed */
1605 return LB_OKAY;
1609 /***********************************************************************
1610 * LISTBOX_InsertString
1612 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1614 LPWSTR new_str = NULL;
1615 LRESULT ret;
1617 if (HAS_STRINGS(descr))
1619 static const WCHAR empty_stringW[] = { 0 };
1620 if (!str) str = empty_stringW;
1621 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1623 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1624 return LB_ERRSPACE;
1626 strcpyW(new_str, str);
1629 if (index == -1) index = descr->nb_items;
1630 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1632 HeapFree( GetProcessHeap(), 0, new_str );
1633 return ret;
1636 TRACE("[%p]: added item %d %s\n",
1637 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1638 return index;
1642 /***********************************************************************
1643 * LISTBOX_DeleteItem
1645 * Delete the content of an item. 'index' must be a valid index.
1647 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1649 /* save the item data before it gets freed by LB_RESETCONTENT */
1650 ULONG_PTR item_data = descr->items[index].data;
1651 LPWSTR item_str = descr->items[index].str;
1653 if (!descr->nb_items)
1654 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1656 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1657 * while Win95 sends it for all items with user data.
1658 * It's probably better to send it too often than not
1659 * often enough, so this is what we do here.
1661 if (IS_OWNERDRAW(descr) || item_data)
1663 DELETEITEMSTRUCT dis;
1664 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1666 dis.CtlType = ODT_LISTBOX;
1667 dis.CtlID = id;
1668 dis.itemID = index;
1669 dis.hwndItem = descr->self;
1670 dis.itemData = item_data;
1671 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1673 if (HAS_STRINGS(descr))
1674 HeapFree( GetProcessHeap(), 0, item_str );
1678 /***********************************************************************
1679 * LISTBOX_RemoveItem
1681 * Remove an item from the listbox and delete its content.
1683 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1685 LB_ITEMDATA *item;
1686 INT max_items;
1688 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1690 /* We need to invalidate the original rect instead of the updated one. */
1691 LISTBOX_InvalidateItems( descr, index );
1693 descr->nb_items--;
1694 LISTBOX_DeleteItem( descr, index );
1696 if (!descr->nb_items) return LB_OKAY;
1698 /* Remove the item */
1700 item = &descr->items[index];
1701 if (index < descr->nb_items)
1702 RtlMoveMemory( item, item + 1,
1703 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1704 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1706 /* Shrink the item array if possible */
1708 max_items = descr->items_size;
1709 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1711 max_items -= LB_ARRAY_GRANULARITY;
1712 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1713 max_items * sizeof(LB_ITEMDATA) );
1714 if (item)
1716 descr->items_size = max_items;
1717 descr->items = item;
1720 /* Repaint the items */
1722 LISTBOX_UpdateScroll( descr );
1723 /* if we removed the scrollbar, reset the top of the list
1724 (correct for owner-drawn ???) */
1725 if (descr->nb_items == descr->page_size)
1726 LISTBOX_SetTopItem( descr, 0, TRUE );
1728 /* Move selection and focused item */
1729 if (!IS_MULTISELECT(descr))
1731 if (index == descr->selected_item)
1732 descr->selected_item = -1;
1733 else if (index < descr->selected_item)
1735 descr->selected_item--;
1736 if (ISWIN31) /* win 31 do not change the selected item number */
1737 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1741 if (descr->focus_item >= descr->nb_items)
1743 descr->focus_item = descr->nb_items - 1;
1744 if (descr->focus_item < 0) descr->focus_item = 0;
1746 return LB_OKAY;
1750 /***********************************************************************
1751 * LISTBOX_ResetContent
1753 static void LISTBOX_ResetContent( LB_DESCR *descr )
1755 INT i;
1757 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1758 HeapFree( GetProcessHeap(), 0, descr->items );
1759 descr->nb_items = 0;
1760 descr->top_item = 0;
1761 descr->selected_item = -1;
1762 descr->focus_item = 0;
1763 descr->anchor_item = -1;
1764 descr->items_size = 0;
1765 descr->items = NULL;
1769 /***********************************************************************
1770 * LISTBOX_SetCount
1772 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1774 LRESULT ret;
1776 if (!(descr->style & LBS_NODATA)) return LB_ERR;
1778 /* FIXME: this is far from optimal... */
1779 if (count > descr->nb_items)
1781 while (count > descr->nb_items)
1782 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1783 return ret;
1785 else if (count < descr->nb_items)
1787 while (count < descr->nb_items)
1788 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1789 return ret;
1792 InvalidateRect( descr->self, NULL, TRUE );
1793 return LB_OKAY;
1797 /***********************************************************************
1798 * LISTBOX_Directory
1800 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1801 LPCWSTR filespec, BOOL long_names )
1803 HANDLE handle;
1804 LRESULT ret = LB_OKAY;
1805 WIN32_FIND_DATAW entry;
1806 int pos;
1807 LRESULT maxinsert = LB_ERR;
1809 /* don't scan directory if we just want drives exclusively */
1810 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1811 /* scan directory */
1812 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1814 int le = GetLastError();
1815 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1817 else
1821 WCHAR buffer[270];
1822 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1824 static const WCHAR bracketW[] = { ']',0 };
1825 static const WCHAR dotW[] = { '.',0 };
1826 if (!(attrib & DDL_DIRECTORY) ||
1827 !strcmpW( entry.cFileName, dotW )) continue;
1828 buffer[0] = '[';
1829 if (!long_names && entry.cAlternateFileName[0])
1830 strcpyW( buffer + 1, entry.cAlternateFileName );
1831 else
1832 strcpyW( buffer + 1, entry.cFileName );
1833 strcatW(buffer, bracketW);
1835 else /* not a directory */
1837 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1838 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1840 if ((attrib & DDL_EXCLUSIVE) &&
1841 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1842 continue;
1843 #undef ATTRIBS
1844 if (!long_names && entry.cAlternateFileName[0])
1845 strcpyW( buffer, entry.cAlternateFileName );
1846 else
1847 strcpyW( buffer, entry.cFileName );
1849 if (!long_names) CharLowerW( buffer );
1850 pos = LISTBOX_FindFileStrPos( descr, buffer );
1851 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1852 break;
1853 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1854 } while (FindNextFileW( handle, &entry ));
1855 FindClose( handle );
1858 if (ret >= 0)
1860 ret = maxinsert;
1862 /* scan drives */
1863 if (attrib & DDL_DRIVES)
1865 WCHAR buffer[] = {'[','-','a','-',']',0};
1866 WCHAR root[] = {'A',':','\\',0};
1867 int drive;
1868 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1870 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1871 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1872 break;
1876 return ret;
1880 /***********************************************************************
1881 * LISTBOX_HandleVScroll
1883 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1885 SCROLLINFO info;
1887 if (descr->style & LBS_MULTICOLUMN) return 0;
1888 switch(scrollReq)
1890 case SB_LINEUP:
1891 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1892 break;
1893 case SB_LINEDOWN:
1894 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1895 break;
1896 case SB_PAGEUP:
1897 LISTBOX_SetTopItem( descr, descr->top_item -
1898 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1899 break;
1900 case SB_PAGEDOWN:
1901 LISTBOX_SetTopItem( descr, descr->top_item +
1902 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1903 break;
1904 case SB_THUMBPOSITION:
1905 LISTBOX_SetTopItem( descr, pos, TRUE );
1906 break;
1907 case SB_THUMBTRACK:
1908 info.cbSize = sizeof(info);
1909 info.fMask = SIF_TRACKPOS;
1910 GetScrollInfo( descr->self, SB_VERT, &info );
1911 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1912 break;
1913 case SB_TOP:
1914 LISTBOX_SetTopItem( descr, 0, TRUE );
1915 break;
1916 case SB_BOTTOM:
1917 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1918 break;
1920 return 0;
1924 /***********************************************************************
1925 * LISTBOX_HandleHScroll
1927 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1929 SCROLLINFO info;
1930 INT page;
1932 if (descr->style & LBS_MULTICOLUMN)
1934 switch(scrollReq)
1936 case SB_LINELEFT:
1937 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1938 TRUE );
1939 break;
1940 case SB_LINERIGHT:
1941 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1942 TRUE );
1943 break;
1944 case SB_PAGELEFT:
1945 page = descr->width / descr->column_width;
1946 if (page < 1) page = 1;
1947 LISTBOX_SetTopItem( descr,
1948 descr->top_item - page * descr->page_size, TRUE );
1949 break;
1950 case SB_PAGERIGHT:
1951 page = descr->width / descr->column_width;
1952 if (page < 1) page = 1;
1953 LISTBOX_SetTopItem( descr,
1954 descr->top_item + page * descr->page_size, TRUE );
1955 break;
1956 case SB_THUMBPOSITION:
1957 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1958 break;
1959 case SB_THUMBTRACK:
1960 info.cbSize = sizeof(info);
1961 info.fMask = SIF_TRACKPOS;
1962 GetScrollInfo( descr->self, SB_VERT, &info );
1963 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1964 TRUE );
1965 break;
1966 case SB_LEFT:
1967 LISTBOX_SetTopItem( descr, 0, TRUE );
1968 break;
1969 case SB_RIGHT:
1970 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1971 break;
1974 else if (descr->horz_extent)
1976 switch(scrollReq)
1978 case SB_LINELEFT:
1979 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1980 break;
1981 case SB_LINERIGHT:
1982 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1983 break;
1984 case SB_PAGELEFT:
1985 LISTBOX_SetHorizontalPos( descr,
1986 descr->horz_pos - descr->width );
1987 break;
1988 case SB_PAGERIGHT:
1989 LISTBOX_SetHorizontalPos( descr,
1990 descr->horz_pos + descr->width );
1991 break;
1992 case SB_THUMBPOSITION:
1993 LISTBOX_SetHorizontalPos( descr, pos );
1994 break;
1995 case SB_THUMBTRACK:
1996 info.cbSize = sizeof(info);
1997 info.fMask = SIF_TRACKPOS;
1998 GetScrollInfo( descr->self, SB_HORZ, &info );
1999 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
2000 break;
2001 case SB_LEFT:
2002 LISTBOX_SetHorizontalPos( descr, 0 );
2003 break;
2004 case SB_RIGHT:
2005 LISTBOX_SetHorizontalPos( descr,
2006 descr->horz_extent - descr->width );
2007 break;
2010 return 0;
2013 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
2015 UINT pulScrollLines = 3;
2017 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2019 /* if scrolling changes direction, ignore left overs */
2020 if ((delta < 0 && descr->wheel_remain < 0) ||
2021 (delta > 0 && descr->wheel_remain > 0))
2022 descr->wheel_remain += delta;
2023 else
2024 descr->wheel_remain = delta;
2026 if (descr->wheel_remain && pulScrollLines)
2028 int cLineScroll;
2029 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2030 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2031 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2032 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2034 return 0;
2037 /***********************************************************************
2038 * LISTBOX_HandleLButtonDown
2040 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2042 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2044 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2045 descr->self, x, y, index, descr->focus_item);
2047 if (!descr->caret_on && (descr->in_focus)) return 0;
2049 if (!descr->in_focus)
2051 if( !descr->lphc ) SetFocus( descr->self );
2052 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2055 if (index == -1) return 0;
2057 if (!descr->lphc)
2059 if (descr->style & LBS_NOTIFY )
2060 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2061 MAKELPARAM( x, y ) );
2064 descr->captured = TRUE;
2065 SetCapture( descr->self );
2067 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2069 /* we should perhaps make sure that all items are deselected
2070 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2071 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2072 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2075 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2076 if (keys & MK_CONTROL)
2078 LISTBOX_SetCaretIndex( descr, index, FALSE );
2079 LISTBOX_SetSelection( descr, index,
2080 !descr->items[index].selected,
2081 (descr->style & LBS_NOTIFY) != 0);
2083 else
2085 LISTBOX_MoveCaret( descr, index, FALSE );
2087 if (descr->style & LBS_EXTENDEDSEL)
2089 LISTBOX_SetSelection( descr, index,
2090 descr->items[index].selected,
2091 (descr->style & LBS_NOTIFY) != 0 );
2093 else
2095 LISTBOX_SetSelection( descr, index,
2096 !descr->items[index].selected,
2097 (descr->style & LBS_NOTIFY) != 0 );
2101 else
2103 descr->anchor_item = index;
2104 LISTBOX_MoveCaret( descr, index, FALSE );
2105 LISTBOX_SetSelection( descr, index,
2106 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2109 if (!descr->lphc)
2111 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2113 POINT pt;
2115 pt.x = x;
2116 pt.y = y;
2118 if (DragDetect( descr->self, pt ))
2119 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2122 return 0;
2126 /*************************************************************************
2127 * LISTBOX_HandleLButtonDownCombo [Internal]
2129 * Process LButtonDown message for the ComboListBox
2131 * PARAMS
2132 * pWnd [I] The windows internal structure
2133 * pDescr [I] The ListBox internal structure
2134 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2135 * x [I] X Mouse Coordinate
2136 * y [I] Y Mouse Coordinate
2138 * RETURNS
2139 * 0 since we are processing the WM_LBUTTONDOWN Message
2141 * NOTES
2142 * This function is only to be used when a ListBox is a ComboListBox
2145 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2147 RECT clientRect, screenRect;
2148 POINT mousePos;
2150 mousePos.x = x;
2151 mousePos.y = y;
2153 GetClientRect(descr->self, &clientRect);
2155 if(PtInRect(&clientRect, mousePos))
2157 /* MousePos is in client, resume normal processing */
2158 if (msg == WM_LBUTTONDOWN)
2160 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2161 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2163 else if (descr->style & LBS_NOTIFY)
2164 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2166 else
2168 POINT screenMousePos;
2169 HWND hWndOldCapture;
2171 /* Check the Non-Client Area */
2172 screenMousePos = mousePos;
2173 hWndOldCapture = GetCapture();
2174 ReleaseCapture();
2175 GetWindowRect(descr->self, &screenRect);
2176 ClientToScreen(descr->self, &screenMousePos);
2178 if(!PtInRect(&screenRect, screenMousePos))
2180 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2181 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2182 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2184 else
2186 /* Check to see the NC is a scrollbar */
2187 INT nHitTestType=0;
2188 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2189 /* Check Vertical scroll bar */
2190 if (style & WS_VSCROLL)
2192 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2193 if (PtInRect( &clientRect, mousePos ))
2194 nHitTestType = HTVSCROLL;
2196 /* Check horizontal scroll bar */
2197 if (style & WS_HSCROLL)
2199 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2200 if (PtInRect( &clientRect, mousePos ))
2201 nHitTestType = HTHSCROLL;
2203 /* Windows sends this message when a scrollbar is clicked
2206 if(nHitTestType != 0)
2208 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2209 MAKELONG(screenMousePos.x, screenMousePos.y));
2211 /* Resume the Capture after scrolling is complete
2213 if(hWndOldCapture != 0)
2214 SetCapture(hWndOldCapture);
2217 return 0;
2220 /***********************************************************************
2221 * LISTBOX_HandleLButtonUp
2223 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2225 if (LISTBOX_Timer != LB_TIMER_NONE)
2226 KillSystemTimer( descr->self, LB_TIMER_ID );
2227 LISTBOX_Timer = LB_TIMER_NONE;
2228 if (descr->captured)
2230 descr->captured = FALSE;
2231 if (GetCapture() == descr->self) ReleaseCapture();
2232 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2233 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2235 return 0;
2239 /***********************************************************************
2240 * LISTBOX_HandleTimer
2242 * Handle scrolling upon a timer event.
2243 * Return TRUE if scrolling should continue.
2245 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2247 switch(dir)
2249 case LB_TIMER_UP:
2250 if (descr->top_item) index = descr->top_item - 1;
2251 else index = 0;
2252 break;
2253 case LB_TIMER_LEFT:
2254 if (descr->top_item) index -= descr->page_size;
2255 break;
2256 case LB_TIMER_DOWN:
2257 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2258 if (index == descr->focus_item) index++;
2259 if (index >= descr->nb_items) index = descr->nb_items - 1;
2260 break;
2261 case LB_TIMER_RIGHT:
2262 if (index + descr->page_size < descr->nb_items)
2263 index += descr->page_size;
2264 break;
2265 case LB_TIMER_NONE:
2266 break;
2268 if (index == descr->focus_item) return FALSE;
2269 LISTBOX_MoveCaret( descr, index, FALSE );
2270 return TRUE;
2274 /***********************************************************************
2275 * LISTBOX_HandleSystemTimer
2277 * WM_SYSTIMER handler.
2279 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2281 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2283 KillSystemTimer( descr->self, LB_TIMER_ID );
2284 LISTBOX_Timer = LB_TIMER_NONE;
2286 return 0;
2290 /***********************************************************************
2291 * LISTBOX_HandleMouseMove
2293 * WM_MOUSEMOVE handler.
2295 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2296 INT x, INT y )
2298 INT index;
2299 TIMER_DIRECTION dir = LB_TIMER_NONE;
2301 if (!descr->captured) return;
2303 if (descr->style & LBS_MULTICOLUMN)
2305 if (y < 0) y = 0;
2306 else if (y >= descr->item_height * descr->page_size)
2307 y = descr->item_height * descr->page_size - 1;
2309 if (x < 0)
2311 dir = LB_TIMER_LEFT;
2312 x = 0;
2314 else if (x >= descr->width)
2316 dir = LB_TIMER_RIGHT;
2317 x = descr->width - 1;
2320 else
2322 if (y < 0) dir = LB_TIMER_UP; /* above */
2323 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2326 index = LISTBOX_GetItemFromPoint( descr, x, y );
2327 if (index == -1) index = descr->focus_item;
2328 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2330 /* Start/stop the system timer */
2332 if (dir != LB_TIMER_NONE)
2333 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2334 else if (LISTBOX_Timer != LB_TIMER_NONE)
2335 KillSystemTimer( descr->self, LB_TIMER_ID );
2336 LISTBOX_Timer = dir;
2340 /***********************************************************************
2341 * LISTBOX_HandleKeyDown
2343 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2345 INT caret = -1;
2346 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2347 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2348 bForceSelection = FALSE; /* only for single select list */
2350 if (descr->style & LBS_WANTKEYBOARDINPUT)
2352 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2353 MAKEWPARAM(LOWORD(key), descr->focus_item),
2354 (LPARAM)descr->self );
2355 if (caret == -2) return 0;
2357 if (caret == -1) switch(key)
2359 case VK_LEFT:
2360 if (descr->style & LBS_MULTICOLUMN)
2362 bForceSelection = FALSE;
2363 if (descr->focus_item >= descr->page_size)
2364 caret = descr->focus_item - descr->page_size;
2365 break;
2367 /* fall through */
2368 case VK_UP:
2369 caret = descr->focus_item - 1;
2370 if (caret < 0) caret = 0;
2371 break;
2372 case VK_RIGHT:
2373 if (descr->style & LBS_MULTICOLUMN)
2375 bForceSelection = FALSE;
2376 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2377 break;
2379 /* fall through */
2380 case VK_DOWN:
2381 caret = descr->focus_item + 1;
2382 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2383 break;
2385 case VK_PRIOR:
2386 if (descr->style & LBS_MULTICOLUMN)
2388 INT page = descr->width / descr->column_width;
2389 if (page < 1) page = 1;
2390 caret = descr->focus_item - (page * descr->page_size) + 1;
2392 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2393 if (caret < 0) caret = 0;
2394 break;
2395 case VK_NEXT:
2396 if (descr->style & LBS_MULTICOLUMN)
2398 INT page = descr->width / descr->column_width;
2399 if (page < 1) page = 1;
2400 caret = descr->focus_item + (page * descr->page_size) - 1;
2402 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2403 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2404 break;
2405 case VK_HOME:
2406 caret = 0;
2407 break;
2408 case VK_END:
2409 caret = descr->nb_items - 1;
2410 break;
2411 case VK_SPACE:
2412 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2413 else if (descr->style & LBS_MULTIPLESEL)
2415 LISTBOX_SetSelection( descr, descr->focus_item,
2416 !descr->items[descr->focus_item].selected,
2417 (descr->style & LBS_NOTIFY) != 0 );
2419 break;
2420 default:
2421 bForceSelection = FALSE;
2423 if (bForceSelection) /* focused item is used instead of key */
2424 caret = descr->focus_item;
2425 if (caret >= 0)
2427 if (((descr->style & LBS_EXTENDEDSEL) &&
2428 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2429 !IS_MULTISELECT(descr))
2430 descr->anchor_item = caret;
2431 LISTBOX_MoveCaret( descr, caret, TRUE );
2433 if (descr->style & LBS_MULTIPLESEL)
2434 descr->selected_item = caret;
2435 else
2436 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2437 if (descr->style & LBS_NOTIFY)
2439 if (descr->lphc && IsWindowVisible( descr->self ))
2441 /* make sure that combo parent doesn't hide us */
2442 descr->lphc->wState |= CBF_NOROLLUP;
2444 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2447 return 0;
2451 /***********************************************************************
2452 * LISTBOX_HandleChar
2454 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2456 INT caret = -1;
2457 WCHAR str[2];
2459 str[0] = charW;
2460 str[1] = '\0';
2462 if (descr->style & LBS_WANTKEYBOARDINPUT)
2464 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2465 MAKEWPARAM(charW, descr->focus_item),
2466 (LPARAM)descr->self );
2467 if (caret == -2) return 0;
2469 if (caret == -1)
2470 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2471 if (caret != -1)
2473 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2474 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2475 LISTBOX_MoveCaret( descr, caret, TRUE );
2476 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2477 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2479 return 0;
2483 /***********************************************************************
2484 * LISTBOX_Create
2486 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2488 LB_DESCR *descr;
2489 MEASUREITEMSTRUCT mis;
2490 RECT rect;
2492 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2493 return FALSE;
2495 GetClientRect( hwnd, &rect );
2496 descr->self = hwnd;
2497 descr->owner = GetParent( descr->self );
2498 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2499 descr->width = rect.right - rect.left;
2500 descr->height = rect.bottom - rect.top;
2501 descr->items = NULL;
2502 descr->items_size = 0;
2503 descr->nb_items = 0;
2504 descr->top_item = 0;
2505 descr->selected_item = -1;
2506 descr->focus_item = 0;
2507 descr->anchor_item = -1;
2508 descr->item_height = 1;
2509 descr->page_size = 1;
2510 descr->column_width = 150;
2511 descr->horz_extent = 0;
2512 descr->horz_pos = 0;
2513 descr->nb_tabs = 0;
2514 descr->tabs = NULL;
2515 descr->wheel_remain = 0;
2516 descr->caret_on = !lphc;
2517 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2518 descr->in_focus = FALSE;
2519 descr->captured = FALSE;
2520 descr->font = 0;
2521 descr->locale = GetUserDefaultLCID();
2522 descr->lphc = lphc;
2524 if( lphc )
2526 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2527 descr->owner = lphc->self;
2530 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2532 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2534 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2535 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2536 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2537 if ((descr->style & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_SORT)) != LBS_OWNERDRAWFIXED)
2538 descr->style &= ~LBS_NODATA;
2539 descr->item_height = LISTBOX_SetFont( descr, 0 );
2541 if (descr->style & LBS_OWNERDRAWFIXED)
2543 descr->style &= ~LBS_OWNERDRAWVARIABLE;
2545 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2547 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2548 descr->item_height = lphc->fixedOwnerDrawHeight;
2550 else
2552 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2553 mis.CtlType = ODT_LISTBOX;
2554 mis.CtlID = id;
2555 mis.itemID = -1;
2556 mis.itemWidth = 0;
2557 mis.itemData = 0;
2558 mis.itemHeight = descr->item_height;
2559 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2560 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2564 OpenThemeData( descr->self, WC_LISTBOXW );
2566 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2567 return TRUE;
2571 /***********************************************************************
2572 * LISTBOX_Destroy
2574 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2576 HTHEME theme = GetWindowTheme( descr->self );
2577 CloseThemeData( theme );
2578 LISTBOX_ResetContent( descr );
2579 SetWindowLongPtrW( descr->self, 0, 0 );
2580 HeapFree( GetProcessHeap(), 0, descr );
2581 return TRUE;
2585 /***********************************************************************
2586 * ListBoxWndProc_common
2588 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2590 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2591 HEADCOMBO *lphc = NULL;
2592 HTHEME theme;
2593 LRESULT ret;
2595 if (!descr)
2597 if (!IsWindow(hwnd)) return 0;
2599 if (msg == WM_CREATE)
2601 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2602 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2603 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2604 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2605 return 0;
2607 /* Ignore all other messages before we get a WM_CREATE */
2608 return DefWindowProcW( hwnd, msg, wParam, lParam );
2610 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2612 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr->self, msg, wParam, lParam );
2614 switch(msg)
2616 case LB_RESETCONTENT:
2617 LISTBOX_ResetContent( descr );
2618 LISTBOX_UpdateScroll( descr );
2619 InvalidateRect( descr->self, NULL, TRUE );
2620 return 0;
2622 case LB_ADDSTRING:
2624 const WCHAR *textW = (const WCHAR *)lParam;
2625 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2626 return LISTBOX_InsertString( descr, index, textW );
2629 case LB_INSERTSTRING:
2630 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2632 case LB_ADDFILE:
2634 const WCHAR *textW = (const WCHAR *)lParam;
2635 INT index = LISTBOX_FindFileStrPos( descr, textW );
2636 return LISTBOX_InsertString( descr, index, textW );
2639 case LB_DELETESTRING:
2640 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2641 return descr->nb_items;
2642 else
2644 SetLastError(ERROR_INVALID_INDEX);
2645 return LB_ERR;
2648 case LB_GETITEMDATA:
2649 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2651 SetLastError(ERROR_INVALID_INDEX);
2652 return LB_ERR;
2654 return (descr->style & LBS_NODATA) ? 0 : descr->items[wParam].data;
2656 case LB_SETITEMDATA:
2657 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2659 SetLastError(ERROR_INVALID_INDEX);
2660 return LB_ERR;
2662 if (!(descr->style & LBS_NODATA)) descr->items[wParam].data = lParam;
2663 /* undocumented: returns TRUE, not LB_OKAY (0) */
2664 return TRUE;
2666 case LB_GETCOUNT:
2667 return descr->nb_items;
2669 case LB_GETTEXT:
2670 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2672 case LB_GETTEXTLEN:
2673 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2675 SetLastError(ERROR_INVALID_INDEX);
2676 return LB_ERR;
2678 if (!HAS_STRINGS(descr)) return sizeof(ULONG_PTR);
2679 return strlenW( descr->items[wParam].str );
2681 case LB_GETCURSEL:
2682 if (descr->nb_items == 0)
2683 return LB_ERR;
2684 if (!IS_MULTISELECT(descr))
2685 return descr->selected_item;
2686 if (descr->selected_item != -1)
2687 return descr->selected_item;
2688 return descr->focus_item;
2689 /* otherwise, if the user tries to move the selection with the */
2690 /* arrow keys, we will give the application something to choke on */
2691 case LB_GETTOPINDEX:
2692 return descr->top_item;
2694 case LB_GETITEMHEIGHT:
2695 return LISTBOX_GetItemHeight( descr, wParam );
2697 case LB_SETITEMHEIGHT:
2698 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2700 case LB_ITEMFROMPOINT:
2702 POINT pt;
2703 RECT rect;
2704 int index;
2705 BOOL hit = TRUE;
2707 /* The hiword of the return value is not a client area
2708 hittest as suggested by MSDN, but rather a hittest on
2709 the returned listbox item. */
2711 if(descr->nb_items == 0)
2712 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2714 pt.x = (short)LOWORD(lParam);
2715 pt.y = (short)HIWORD(lParam);
2717 SetRect(&rect, 0, 0, descr->width, descr->height);
2719 if(!PtInRect(&rect, pt))
2721 pt.x = min(pt.x, rect.right - 1);
2722 pt.x = max(pt.x, 0);
2723 pt.y = min(pt.y, rect.bottom - 1);
2724 pt.y = max(pt.y, 0);
2725 hit = FALSE;
2728 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2730 if(index == -1)
2732 index = descr->nb_items - 1;
2733 hit = FALSE;
2735 return MAKELONG(index, hit ? 0 : 1);
2738 case LB_SETCARETINDEX:
2739 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2740 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2741 return LB_ERR;
2742 else if (ISWIN31)
2743 return wParam;
2744 else
2745 return LB_OKAY;
2747 case LB_GETCARETINDEX:
2748 return descr->focus_item;
2750 case LB_SETTOPINDEX:
2751 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2753 case LB_SETCOLUMNWIDTH:
2754 return LISTBOX_SetColumnWidth( descr, wParam );
2756 case LB_GETITEMRECT:
2757 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2759 case LB_FINDSTRING:
2760 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2762 case LB_FINDSTRINGEXACT:
2763 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2765 case LB_SELECTSTRING:
2767 const WCHAR *textW = (const WCHAR *)lParam;
2768 INT index;
2770 if (HAS_STRINGS(descr))
2771 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2773 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2774 if (index != LB_ERR)
2776 LISTBOX_MoveCaret( descr, index, TRUE );
2777 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2779 return index;
2782 case LB_GETSEL:
2783 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2784 return LB_ERR;
2785 return is_item_selected(descr, wParam);
2787 case LB_SETSEL:
2788 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2789 if (ret != LB_ERR && wParam)
2790 descr->anchor_item = lParam;
2791 return ret;
2793 case LB_SETCURSEL:
2794 if (IS_MULTISELECT(descr)) return LB_ERR;
2795 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2796 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2797 if (ret != LB_ERR) ret = descr->selected_item;
2798 return ret;
2800 case LB_GETSELCOUNT:
2801 return LISTBOX_GetSelCount( descr );
2803 case LB_GETSELITEMS:
2804 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2806 case LB_SELITEMRANGE:
2807 if (LOWORD(lParam) <= HIWORD(lParam))
2808 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2809 HIWORD(lParam), wParam );
2810 else
2811 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2812 LOWORD(lParam), wParam );
2814 case LB_SELITEMRANGEEX:
2815 if ((INT)lParam >= (INT)wParam)
2816 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2817 else
2818 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2820 case LB_GETHORIZONTALEXTENT:
2821 return descr->horz_extent;
2823 case LB_SETHORIZONTALEXTENT:
2824 return LISTBOX_SetHorizontalExtent( descr, wParam );
2826 case LB_GETANCHORINDEX:
2827 return descr->anchor_item;
2829 case LB_SETANCHORINDEX:
2830 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2832 SetLastError(ERROR_INVALID_INDEX);
2833 return LB_ERR;
2835 descr->anchor_item = (INT)wParam;
2836 return LB_OKAY;
2838 case LB_DIR:
2839 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2841 case LB_GETLOCALE:
2842 return descr->locale;
2844 case LB_SETLOCALE:
2846 LCID ret;
2847 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2848 return LB_ERR;
2849 ret = descr->locale;
2850 descr->locale = (LCID)wParam;
2851 return ret;
2854 case LB_INITSTORAGE:
2855 return LISTBOX_InitStorage( descr, wParam );
2857 case LB_SETCOUNT:
2858 return LISTBOX_SetCount( descr, (INT)wParam );
2860 case LB_SETTABSTOPS:
2861 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2863 case LB_CARETON:
2864 if (descr->caret_on)
2865 return LB_OKAY;
2866 descr->caret_on = TRUE;
2867 if ((descr->focus_item != -1) && (descr->in_focus))
2868 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2869 return LB_OKAY;
2871 case LB_CARETOFF:
2872 if (!descr->caret_on)
2873 return LB_OKAY;
2874 descr->caret_on = FALSE;
2875 if ((descr->focus_item != -1) && (descr->in_focus))
2876 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2877 return LB_OKAY;
2879 case LB_GETLISTBOXINFO:
2880 return descr->page_size;
2882 case WM_DESTROY:
2883 return LISTBOX_Destroy( descr );
2885 case WM_ENABLE:
2886 InvalidateRect( descr->self, NULL, TRUE );
2887 return 0;
2889 case WM_SETREDRAW:
2890 LISTBOX_SetRedraw( descr, wParam != 0 );
2891 return 0;
2893 case WM_GETDLGCODE:
2894 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2896 case WM_PRINTCLIENT:
2897 case WM_PAINT:
2899 PAINTSTRUCT ps;
2900 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2901 ret = LISTBOX_Paint( descr, hdc );
2902 if( !wParam ) EndPaint( descr->self, &ps );
2904 return ret;
2906 case WM_NCPAINT:
2907 LISTBOX_NCPaint( descr, (HRGN)wParam );
2908 break;
2910 case WM_SIZE:
2911 LISTBOX_UpdateSize( descr );
2912 return 0;
2913 case WM_GETFONT:
2914 return (LRESULT)descr->font;
2915 case WM_SETFONT:
2916 LISTBOX_SetFont( descr, (HFONT)wParam );
2917 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2918 return 0;
2919 case WM_SETFOCUS:
2920 descr->in_focus = TRUE;
2921 descr->caret_on = TRUE;
2922 if (descr->focus_item != -1)
2923 LISTBOX_DrawFocusRect( descr, TRUE );
2924 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2925 return 0;
2926 case WM_KILLFOCUS:
2927 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
2928 descr->in_focus = FALSE;
2929 descr->wheel_remain = 0;
2930 if ((descr->focus_item != -1) && descr->caret_on)
2931 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2932 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
2933 return 0;
2934 case WM_HSCROLL:
2935 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2936 case WM_VSCROLL:
2937 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2938 case WM_MOUSEWHEEL:
2939 if (wParam & (MK_SHIFT | MK_CONTROL))
2940 return DefWindowProcW( descr->self, msg, wParam, lParam );
2941 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
2942 case WM_LBUTTONDOWN:
2943 if (lphc)
2944 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2945 (INT16)LOWORD(lParam),
2946 (INT16)HIWORD(lParam) );
2947 return LISTBOX_HandleLButtonDown( descr, wParam,
2948 (INT16)LOWORD(lParam),
2949 (INT16)HIWORD(lParam) );
2950 case WM_LBUTTONDBLCLK:
2951 if (lphc)
2952 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2953 (INT16)LOWORD(lParam),
2954 (INT16)HIWORD(lParam) );
2955 if (descr->style & LBS_NOTIFY)
2956 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2957 return 0;
2958 case WM_MOUSEMOVE:
2959 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
2961 BOOL captured = descr->captured;
2962 POINT mousePos;
2963 RECT clientRect;
2965 mousePos.x = (INT16)LOWORD(lParam);
2966 mousePos.y = (INT16)HIWORD(lParam);
2969 * If we are in a dropdown combobox, we simulate that
2970 * the mouse is captured to show the tracking of the item.
2972 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
2973 descr->captured = TRUE;
2975 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
2977 descr->captured = captured;
2979 else if (GetCapture() == descr->self)
2981 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
2982 (INT16)HIWORD(lParam) );
2984 return 0;
2985 case WM_LBUTTONUP:
2986 if (lphc)
2988 POINT mousePos;
2989 RECT clientRect;
2992 * If the mouse button "up" is not in the listbox,
2993 * we make sure there is no selection by re-selecting the
2994 * item that was selected when the listbox was made visible.
2996 mousePos.x = (INT16)LOWORD(lParam);
2997 mousePos.y = (INT16)HIWORD(lParam);
2999 GetClientRect(descr->self, &clientRect);
3002 * When the user clicks outside the combobox and the focus
3003 * is lost, the owning combobox will send a fake buttonup with
3004 * 0xFFFFFFF as the mouse location, we must also revert the
3005 * selection to the original selection.
3007 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3008 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3010 return LISTBOX_HandleLButtonUp( descr );
3011 case WM_KEYDOWN:
3012 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3014 /* for some reason Windows makes it possible to
3015 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3017 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3018 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3019 && (wParam == VK_DOWN || wParam == VK_UP)) )
3021 COMBO_FlipListbox( lphc, FALSE, FALSE );
3022 return 0;
3025 return LISTBOX_HandleKeyDown( descr, wParam );
3026 case WM_CHAR:
3027 return LISTBOX_HandleChar( descr, wParam );
3029 case WM_SYSTIMER:
3030 return LISTBOX_HandleSystemTimer( descr );
3031 case WM_ERASEBKGND:
3032 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3034 RECT rect;
3035 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3036 wParam, (LPARAM)descr->self );
3037 TRACE("hbrush = %p\n", hbrush);
3038 if(!hbrush)
3039 hbrush = GetSysColorBrush(COLOR_WINDOW);
3040 if(hbrush)
3042 GetClientRect(descr->self, &rect);
3043 FillRect((HDC)wParam, &rect, hbrush);
3046 return 1;
3047 case WM_DROPFILES:
3048 if( lphc ) return 0;
3049 return SendMessageW( descr->owner, msg, wParam, lParam );
3051 case WM_NCDESTROY:
3052 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3053 lphc->hWndLBox = 0;
3054 break;
3056 case WM_NCACTIVATE:
3057 if (lphc) return 0;
3058 break;
3060 case WM_THEMECHANGED:
3061 theme = GetWindowTheme( hwnd );
3062 CloseThemeData( theme );
3063 OpenThemeData( hwnd, WC_LISTBOXW );
3064 break;
3066 default:
3067 if ((msg >= WM_USER) && (msg < 0xc000))
3068 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3069 hwnd, msg, wParam, lParam );
3072 return DefWindowProcW( hwnd, msg, wParam, lParam );
3075 void LISTBOX_Register(void)
3077 WNDCLASSW wndClass;
3079 memset(&wndClass, 0, sizeof(wndClass));
3080 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3081 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3082 wndClass.cbClsExtra = 0;
3083 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3084 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3085 wndClass.hbrBackground = NULL;
3086 wndClass.lpszClassName = WC_LISTBOXW;
3087 RegisterClassW(&wndClass);
3090 void COMBOLBOX_Register(void)
3092 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
3093 WNDCLASSW wndClass;
3095 memset(&wndClass, 0, sizeof(wndClass));
3096 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
3097 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3098 wndClass.cbClsExtra = 0;
3099 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3100 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3101 wndClass.hbrBackground = NULL;
3102 wndClass.lpszClassName = combolboxW;
3103 RegisterClassW(&wndClass);