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
34 #include "wine/exception.h"
35 #include "wine/debug.h"
36 #include "wine/heap.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(listbox
);
42 /* Items array granularity (must be power of 2) */
43 #define LB_ARRAY_GRANULARITY 16
45 /* Scrolling timeout in ms */
46 #define LB_SCROLL_TIMEOUT 50
48 /* Listbox system timer id */
51 /* flag listbox changed while setredraw false - internal style */
52 #define LBS_DISPLAYCHANGED 0x80000000
57 LPWSTR str
; /* Item text */
58 BOOL selected
; /* Is item selected? */
59 UINT height
; /* Item height (only for OWNERDRAWVARIABLE) */
60 ULONG_PTR data
; /* User data */
63 /* Listbox structure */
66 HWND self
; /* Our own window handle */
67 HWND owner
; /* Owner window to send notifications to */
68 UINT style
; /* Window style */
69 INT width
; /* Window width */
70 INT height
; /* Window height */
73 LB_ITEMDATA
*items
; /* Array of items */
74 BYTE
*nodata_items
; /* For multi-selection LBS_NODATA */
76 INT nb_items
; /* Number of items */
77 UINT items_size
; /* Total number of allocated items in the array */
78 INT top_item
; /* Top visible item */
79 INT selected_item
; /* Selected item */
80 INT focus_item
; /* Item that has the focus */
81 INT anchor_item
; /* Anchor item for extended selection */
82 INT item_height
; /* Default item height */
83 INT page_size
; /* Items per listbox page */
84 INT column_width
; /* Column width for multi-column listboxes */
85 INT horz_extent
; /* Horizontal extent */
86 INT horz_pos
; /* Horizontal position */
87 INT nb_tabs
; /* Number of tabs in array */
88 INT
*tabs
; /* Array of tabs */
89 INT avg_char_width
; /* Average width of characters */
90 INT wheel_remain
; /* Left over scroll amount */
91 BOOL caret_on
; /* Is caret on? */
92 BOOL captured
; /* Is mouse captured? */
94 HFONT font
; /* Current font */
95 LCID locale
; /* Current locale for string comparisons */
96 HEADCOMBO
*lphc
; /* ComboLBox */
100 #define IS_OWNERDRAW(descr) \
101 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
103 #define HAS_STRINGS(descr) \
104 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
107 #define IS_MULTISELECT(descr) \
108 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
109 !((descr)->style & LBS_NOSEL))
111 #define SEND_NOTIFICATION(descr,code) \
112 (SendMessageW( (descr)->owner, WM_COMMAND, \
113 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
115 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
117 /* Current timer status */
127 static TIMER_DIRECTION LISTBOX_Timer
= LB_TIMER_NONE
;
129 static LRESULT
LISTBOX_GetItemRect( const LB_DESCR
*descr
, INT index
, RECT
*rect
);
132 For listboxes without LBS_NODATA, an array of LB_ITEMDATA is allocated
133 to store the states of each item into descr->u.items.
135 For single-selection LBS_NODATA listboxes, no storage is allocated,
136 and thus descr->u.nodata_items will always be NULL.
138 For multi-selection LBS_NODATA listboxes, one byte per item is stored
139 for the item's selection state into descr->u.nodata_items.
141 static size_t get_sizeof_item( const LB_DESCR
*descr
)
143 return (descr
->style
& LBS_NODATA
) ? sizeof(BYTE
) : sizeof(LB_ITEMDATA
);
146 static BOOL
resize_storage(LB_DESCR
*descr
, UINT items_size
)
150 if (items_size
> descr
->items_size
||
151 items_size
+ LB_ARRAY_GRANULARITY
* 2 < descr
->items_size
)
153 items_size
= (items_size
+ LB_ARRAY_GRANULARITY
- 1) & ~(LB_ARRAY_GRANULARITY
- 1);
154 if ((descr
->style
& (LBS_NODATA
| LBS_MULTIPLESEL
| LBS_EXTENDEDSEL
)) != LBS_NODATA
)
156 items
= heap_realloc(descr
->u
.items
, items_size
* get_sizeof_item(descr
));
159 SEND_NOTIFICATION(descr
, LBN_ERRSPACE
);
162 descr
->u
.items
= items
;
164 descr
->items_size
= items_size
;
167 if ((descr
->style
& LBS_NODATA
) && descr
->u
.nodata_items
&& items_size
> descr
->nb_items
)
169 memset(descr
->u
.nodata_items
+ descr
->nb_items
, 0,
170 (items_size
- descr
->nb_items
) * get_sizeof_item(descr
));
175 static ULONG_PTR
get_item_data( const LB_DESCR
*descr
, UINT index
)
177 return (descr
->style
& LBS_NODATA
) ? 0 : descr
->u
.items
[index
].data
;
180 static void set_item_data( LB_DESCR
*descr
, UINT index
, ULONG_PTR data
)
182 if (!(descr
->style
& LBS_NODATA
)) descr
->u
.items
[index
].data
= data
;
185 static WCHAR
*get_item_string( const LB_DESCR
*descr
, UINT index
)
187 return HAS_STRINGS(descr
) ? descr
->u
.items
[index
].str
: NULL
;
190 static void set_item_string( const LB_DESCR
*descr
, UINT index
, WCHAR
*string
)
192 if (!(descr
->style
& LBS_NODATA
)) descr
->u
.items
[index
].str
= string
;
195 static UINT
get_item_height( const LB_DESCR
*descr
, UINT index
)
197 return (descr
->style
& LBS_NODATA
) ? 0 : descr
->u
.items
[index
].height
;
200 static void set_item_height( LB_DESCR
*descr
, UINT index
, UINT height
)
202 if (!(descr
->style
& LBS_NODATA
)) descr
->u
.items
[index
].height
= height
;
205 static BOOL
is_item_selected( const LB_DESCR
*descr
, UINT index
)
207 if (!(descr
->style
& (LBS_MULTIPLESEL
| LBS_EXTENDEDSEL
)))
208 return index
== descr
->selected_item
;
209 if (descr
->style
& LBS_NODATA
)
210 return descr
->u
.nodata_items
[index
];
212 return descr
->u
.items
[index
].selected
;
215 static void set_item_selected_state(LB_DESCR
*descr
, UINT index
, BOOL state
)
217 if (descr
->style
& (LBS_MULTIPLESEL
| LBS_EXTENDEDSEL
))
219 if (descr
->style
& LBS_NODATA
)
220 descr
->u
.nodata_items
[index
] = state
;
222 descr
->u
.items
[index
].selected
= state
;
226 static void insert_item_data(LB_DESCR
*descr
, UINT index
)
228 size_t size
= get_sizeof_item(descr
);
229 BYTE
*p
= descr
->u
.nodata_items
+ index
* size
;
231 if (!descr
->u
.items
) return;
233 if (index
< descr
->nb_items
)
234 memmove(p
+ size
, p
, (descr
->nb_items
- index
) * size
);
237 static void remove_item_data(LB_DESCR
*descr
, UINT index
)
239 size_t size
= get_sizeof_item(descr
);
240 BYTE
*p
= descr
->u
.nodata_items
+ index
* size
;
242 if (!descr
->u
.items
) return;
244 if (index
< descr
->nb_items
)
245 memmove(p
, p
+ size
, (descr
->nb_items
- index
) * size
);
248 /***********************************************************************
249 * LISTBOX_GetCurrentPageSize
251 * Return the current page size
253 static INT
LISTBOX_GetCurrentPageSize( const LB_DESCR
*descr
)
256 if (!(descr
->style
& LBS_OWNERDRAWVARIABLE
)) return descr
->page_size
;
257 for (i
= descr
->top_item
, height
= 0; i
< descr
->nb_items
; i
++)
259 if ((height
+= get_item_height(descr
, i
)) > descr
->height
) break;
261 if (i
== descr
->top_item
) return 1;
262 else return i
- descr
->top_item
;
266 /***********************************************************************
267 * LISTBOX_GetMaxTopIndex
269 * Return the maximum possible index for the top of the listbox.
271 static INT
LISTBOX_GetMaxTopIndex( const LB_DESCR
*descr
)
275 if (descr
->style
& LBS_OWNERDRAWVARIABLE
)
277 page
= descr
->height
;
278 for (max
= descr
->nb_items
- 1; max
>= 0; max
--)
279 if ((page
-= get_item_height(descr
, max
)) < 0) break;
280 if (max
< descr
->nb_items
- 1) max
++;
282 else if (descr
->style
& LBS_MULTICOLUMN
)
284 if ((page
= descr
->width
/ descr
->column_width
) < 1) page
= 1;
285 max
= (descr
->nb_items
+ descr
->page_size
- 1) / descr
->page_size
;
286 max
= (max
- page
) * descr
->page_size
;
290 max
= descr
->nb_items
- descr
->page_size
;
292 if (max
< 0) max
= 0;
297 /***********************************************************************
298 * LISTBOX_UpdateScroll
300 * Update the scrollbars. Should be called whenever the content
301 * of the listbox changes.
303 static void LISTBOX_UpdateScroll( LB_DESCR
*descr
)
307 /* Check the listbox scroll bar flags individually before we call
308 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
309 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
310 scroll bar when we do not need one.
311 if (!(descr->style & WS_VSCROLL)) return;
314 /* It is important that we check descr->style, and not wnd->dwStyle,
315 for WS_VSCROLL, as the former is exactly the one passed in
316 argument to CreateWindow.
317 In Windows (and from now on in Wine :) a listbox created
318 with such a style (no WS_SCROLL) does not update
319 the scrollbar with listbox-related data, thus letting
320 the programmer use it for his/her own purposes. */
322 if (descr
->style
& LBS_NOREDRAW
) return;
323 info
.cbSize
= sizeof(info
);
325 if (descr
->style
& LBS_MULTICOLUMN
)
328 info
.nMax
= (descr
->nb_items
- 1) / descr
->page_size
;
329 info
.nPos
= descr
->top_item
/ descr
->page_size
;
330 info
.nPage
= descr
->width
/ descr
->column_width
;
331 if (info
.nPage
< 1) info
.nPage
= 1;
332 info
.fMask
= SIF_RANGE
| SIF_POS
| SIF_PAGE
;
333 if (descr
->style
& LBS_DISABLENOSCROLL
)
334 info
.fMask
|= SIF_DISABLENOSCROLL
;
335 if (descr
->style
& WS_HSCROLL
)
336 SetScrollInfo( descr
->self
, SB_HORZ
, &info
, TRUE
);
338 info
.fMask
= SIF_RANGE
;
339 if (descr
->style
& WS_VSCROLL
)
340 SetScrollInfo( descr
->self
, SB_VERT
, &info
, TRUE
);
345 info
.nMax
= descr
->nb_items
- 1;
346 info
.nPos
= descr
->top_item
;
347 info
.nPage
= LISTBOX_GetCurrentPageSize( descr
);
348 info
.fMask
= SIF_RANGE
| SIF_POS
| SIF_PAGE
;
349 if (descr
->style
& LBS_DISABLENOSCROLL
)
350 info
.fMask
|= SIF_DISABLENOSCROLL
;
351 if (descr
->style
& WS_VSCROLL
)
352 SetScrollInfo( descr
->self
, SB_VERT
, &info
, TRUE
);
354 if ((descr
->style
& WS_HSCROLL
) && descr
->horz_extent
)
356 info
.nPos
= descr
->horz_pos
;
357 info
.nPage
= descr
->width
;
358 info
.fMask
= SIF_POS
| SIF_PAGE
;
359 if (descr
->style
& LBS_DISABLENOSCROLL
)
360 info
.fMask
|= SIF_DISABLENOSCROLL
;
361 SetScrollInfo( descr
->self
, SB_HORZ
, &info
, TRUE
);
365 if (descr
->style
& LBS_DISABLENOSCROLL
)
369 info
.fMask
= SIF_RANGE
| SIF_DISABLENOSCROLL
;
370 SetScrollInfo( descr
->self
, SB_HORZ
, &info
, TRUE
);
374 ShowScrollBar( descr
->self
, SB_HORZ
, FALSE
);
381 /***********************************************************************
384 * Set the top item of the listbox, scrolling up or down if necessary.
386 static LRESULT
LISTBOX_SetTopItem( LB_DESCR
*descr
, INT index
, BOOL scroll
)
388 INT max
= LISTBOX_GetMaxTopIndex( descr
);
390 TRACE("setting top item %d, scroll %d\n", index
, scroll
);
392 if (index
> max
) index
= max
;
393 if (index
< 0) index
= 0;
394 if (descr
->style
& LBS_MULTICOLUMN
) index
-= index
% descr
->page_size
;
395 if (descr
->top_item
== index
) return LB_OKAY
;
399 if (descr
->style
& LBS_MULTICOLUMN
)
400 dx
= (descr
->top_item
- index
) / descr
->page_size
* descr
->column_width
;
401 else if (descr
->style
& LBS_OWNERDRAWVARIABLE
)
404 if (index
> descr
->top_item
)
406 for (i
= index
- 1; i
>= descr
->top_item
; i
--)
407 dy
-= get_item_height(descr
, i
);
411 for (i
= index
; i
< descr
->top_item
; i
++)
412 dy
+= get_item_height(descr
, i
);
416 dy
= (descr
->top_item
- index
) * descr
->item_height
;
418 ScrollWindowEx( descr
->self
, dx
, dy
, NULL
, NULL
, 0, NULL
,
419 SW_INVALIDATE
| SW_ERASE
| SW_SCROLLCHILDREN
);
422 InvalidateRect( descr
->self
, NULL
, TRUE
);
423 descr
->top_item
= index
;
424 LISTBOX_UpdateScroll( descr
);
429 /***********************************************************************
432 * Update the page size. Should be called when the size of
433 * the client area or the item height changes.
435 static void LISTBOX_UpdatePage( LB_DESCR
*descr
)
439 if ((descr
->item_height
== 0) || (page_size
= descr
->height
/ descr
->item_height
) < 1)
441 if (page_size
== descr
->page_size
) return;
442 descr
->page_size
= page_size
;
443 if (descr
->style
& LBS_MULTICOLUMN
)
444 InvalidateRect( descr
->self
, NULL
, TRUE
);
445 LISTBOX_SetTopItem( descr
, descr
->top_item
, FALSE
);
449 /***********************************************************************
452 * Update the size of the listbox. Should be called when the size of
453 * the client area changes.
455 static void LISTBOX_UpdateSize( LB_DESCR
*descr
)
459 GetClientRect( descr
->self
, &rect
);
460 descr
->width
= rect
.right
- rect
.left
;
461 descr
->height
= rect
.bottom
- rect
.top
;
462 if (!(descr
->style
& LBS_NOINTEGRALHEIGHT
) && !(descr
->style
& LBS_OWNERDRAWVARIABLE
))
467 GetWindowRect( descr
->self
, &rect
);
468 if(descr
->item_height
!= 0)
469 remaining
= descr
->height
% descr
->item_height
;
472 if ((descr
->height
> descr
->item_height
) && remaining
)
474 TRACE("[%p]: changing height %d -> %d\n",
475 descr
->self
, descr
->height
, descr
->height
- remaining
);
476 SetWindowPos( descr
->self
, 0, 0, 0, rect
.right
- rect
.left
,
477 rect
.bottom
- rect
.top
- remaining
,
478 SWP_NOZORDER
| SWP_NOACTIVATE
| SWP_NOMOVE
);
482 TRACE("[%p]: new size = %d,%d\n", descr
->self
, descr
->width
, descr
->height
);
483 LISTBOX_UpdatePage( descr
);
484 LISTBOX_UpdateScroll( descr
);
486 /* Invalidate the focused item so it will be repainted correctly */
487 if (LISTBOX_GetItemRect( descr
, descr
->focus_item
, &rect
) == 1)
489 InvalidateRect( descr
->self
, &rect
, FALSE
);
494 /***********************************************************************
495 * LISTBOX_GetItemRect
497 * Get the rectangle enclosing an item, in listbox client coordinates.
498 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
500 static LRESULT
LISTBOX_GetItemRect( const LB_DESCR
*descr
, INT index
, RECT
*rect
)
502 /* Index <= 0 is legal even on empty listboxes */
503 if (index
&& (index
>= descr
->nb_items
))
506 SetLastError(ERROR_INVALID_INDEX
);
509 SetRect( rect
, 0, 0, descr
->width
, descr
->height
);
510 if (descr
->style
& LBS_MULTICOLUMN
)
512 INT col
= (index
/ descr
->page_size
) -
513 (descr
->top_item
/ descr
->page_size
);
514 rect
->left
+= col
* descr
->column_width
;
515 rect
->right
= rect
->left
+ descr
->column_width
;
516 rect
->top
+= (index
% descr
->page_size
) * descr
->item_height
;
517 rect
->bottom
= rect
->top
+ descr
->item_height
;
519 else if (descr
->style
& LBS_OWNERDRAWVARIABLE
)
522 rect
->right
+= descr
->horz_pos
;
523 if ((index
>= 0) && (index
< descr
->nb_items
))
525 if (index
< descr
->top_item
)
527 for (i
= descr
->top_item
-1; i
>= index
; i
--)
528 rect
->top
-= get_item_height(descr
, i
);
532 for (i
= descr
->top_item
; i
< index
; i
++)
533 rect
->top
+= get_item_height(descr
, i
);
535 rect
->bottom
= rect
->top
+ get_item_height(descr
, index
);
541 rect
->top
+= (index
- descr
->top_item
) * descr
->item_height
;
542 rect
->bottom
= rect
->top
+ descr
->item_height
;
543 rect
->right
+= descr
->horz_pos
;
546 TRACE("item %d, rect %s\n", index
, wine_dbgstr_rect(rect
));
548 return ((rect
->left
< descr
->width
) && (rect
->right
> 0) &&
549 (rect
->top
< descr
->height
) && (rect
->bottom
> 0));
553 /***********************************************************************
554 * LISTBOX_GetItemFromPoint
556 * Return the item nearest from point (x,y) (in client coordinates).
558 static INT
LISTBOX_GetItemFromPoint( const LB_DESCR
*descr
, INT x
, INT y
)
560 INT index
= descr
->top_item
;
562 if (!descr
->nb_items
) return -1; /* No items */
563 if (descr
->style
& LBS_OWNERDRAWVARIABLE
)
568 while (index
< descr
->nb_items
)
570 if ((pos
+= get_item_height(descr
, index
)) > y
) break;
579 if ((pos
-= get_item_height(descr
, index
)) <= y
) break;
583 else if (descr
->style
& LBS_MULTICOLUMN
)
585 if (y
>= descr
->item_height
* descr
->page_size
) return -1;
586 if (y
>= 0) index
+= y
/ descr
->item_height
;
587 if (x
>= 0) index
+= (x
/ descr
->column_width
) * descr
->page_size
;
588 else index
-= (((x
+ 1) / descr
->column_width
) - 1) * descr
->page_size
;
592 index
+= (y
/ descr
->item_height
);
594 if (index
< 0) return 0;
595 if (index
>= descr
->nb_items
) return -1;
600 /***********************************************************************
605 static void LISTBOX_PaintItem( LB_DESCR
*descr
, HDC hdc
, const RECT
*rect
,
606 INT index
, UINT action
, BOOL ignoreFocus
)
608 BOOL selected
= FALSE
, focused
;
609 WCHAR
*item_str
= NULL
;
611 if (index
< descr
->nb_items
)
613 item_str
= get_item_string(descr
, index
);
614 selected
= is_item_selected(descr
, index
);
617 focused
= !ignoreFocus
&& descr
->focus_item
== index
&& descr
->caret_on
&& descr
->in_focus
;
619 if (IS_OWNERDRAW(descr
))
625 if (index
>= descr
->nb_items
)
627 if (action
== ODA_FOCUS
)
628 DrawFocusRect( hdc
, rect
);
630 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index
,descr
->nb_items
);
634 /* some programs mess with the clipping region when
635 drawing the item, *and* restore the previous region
636 after they are done, so a region has better to exist
637 else everything ends clipped */
638 GetClientRect(descr
->self
, &r
);
639 hrgn
= set_control_clipping( hdc
, &r
);
641 dis
.CtlType
= ODT_LISTBOX
;
642 dis
.CtlID
= GetWindowLongPtrW( descr
->self
, GWLP_ID
);
643 dis
.hwndItem
= descr
->self
;
644 dis
.itemAction
= action
;
649 dis
.itemState
|= ODS_SELECTED
;
651 dis
.itemState
|= ODS_FOCUS
;
652 if (!IsWindowEnabled(descr
->self
)) dis
.itemState
|= ODS_DISABLED
;
653 dis
.itemData
= get_item_data(descr
, index
);
655 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
656 descr
->self
, index
, debugstr_w(item_str
), action
,
657 dis
.itemState
, wine_dbgstr_rect(rect
) );
658 SendMessageW(descr
->owner
, WM_DRAWITEM
, dis
.CtlID
, (LPARAM
)&dis
);
659 SelectClipRgn( hdc
, hrgn
);
660 if (hrgn
) DeleteObject( hrgn
);
664 COLORREF oldText
= 0, oldBk
= 0;
666 if (action
== ODA_FOCUS
)
668 DrawFocusRect( hdc
, rect
);
673 oldBk
= SetBkColor( hdc
, GetSysColor( COLOR_HIGHLIGHT
) );
674 oldText
= SetTextColor( hdc
, GetSysColor(COLOR_HIGHLIGHTTEXT
));
677 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
678 descr
->self
, index
, debugstr_w(item_str
), action
,
679 wine_dbgstr_rect(rect
) );
681 ExtTextOutW( hdc
, rect
->left
+ 1, rect
->top
,
682 ETO_OPAQUE
| ETO_CLIPPED
, rect
, NULL
, 0, NULL
);
683 else if (!(descr
->style
& LBS_USETABSTOPS
))
684 ExtTextOutW( hdc
, rect
->left
+ 1, rect
->top
,
685 ETO_OPAQUE
| ETO_CLIPPED
, rect
, item_str
,
686 lstrlenW(item_str
), NULL
);
689 /* Output empty string to paint background in the full width. */
690 ExtTextOutW( hdc
, rect
->left
+ 1, rect
->top
,
691 ETO_OPAQUE
| ETO_CLIPPED
, rect
, NULL
, 0, NULL
);
692 TabbedTextOutW( hdc
, rect
->left
+ 1 , rect
->top
,
693 item_str
, lstrlenW(item_str
),
694 descr
->nb_tabs
, descr
->tabs
, 0);
698 SetBkColor( hdc
, oldBk
);
699 SetTextColor( hdc
, oldText
);
702 DrawFocusRect( hdc
, rect
);
707 /***********************************************************************
710 * Change the redraw flag.
712 static void LISTBOX_SetRedraw( LB_DESCR
*descr
, BOOL on
)
716 if (!(descr
->style
& LBS_NOREDRAW
)) return;
717 descr
->style
&= ~LBS_NOREDRAW
;
718 if (descr
->style
& LBS_DISPLAYCHANGED
)
719 { /* page was changed while setredraw false, refresh automatically */
720 InvalidateRect(descr
->self
, NULL
, TRUE
);
721 if ((descr
->top_item
+ descr
->page_size
) > descr
->nb_items
)
722 { /* reset top of page if less than number of items/page */
723 descr
->top_item
= descr
->nb_items
- descr
->page_size
;
724 if (descr
->top_item
< 0) descr
->top_item
= 0;
726 descr
->style
&= ~LBS_DISPLAYCHANGED
;
728 LISTBOX_UpdateScroll( descr
);
730 else descr
->style
|= LBS_NOREDRAW
;
734 /***********************************************************************
735 * LISTBOX_RepaintItem
737 * Repaint a single item synchronously.
739 static void LISTBOX_RepaintItem( LB_DESCR
*descr
, INT index
, UINT action
)
744 HBRUSH hbrush
, oldBrush
= 0;
746 /* Do not repaint the item if the item is not visible */
747 if (!IsWindowVisible(descr
->self
)) return;
748 if (descr
->style
& LBS_NOREDRAW
)
750 descr
->style
|= LBS_DISPLAYCHANGED
;
753 if (LISTBOX_GetItemRect( descr
, index
, &rect
) != 1) return;
754 if (!(hdc
= GetDCEx( descr
->self
, 0, DCX_CACHE
))) return;
755 if (descr
->font
) oldFont
= SelectObject( hdc
, descr
->font
);
756 hbrush
= (HBRUSH
)SendMessageW( descr
->owner
, WM_CTLCOLORLISTBOX
,
757 (WPARAM
)hdc
, (LPARAM
)descr
->self
);
758 if (hbrush
) oldBrush
= SelectObject( hdc
, hbrush
);
759 if (!IsWindowEnabled(descr
->self
))
760 SetTextColor( hdc
, GetSysColor( COLOR_GRAYTEXT
) );
761 SetWindowOrgEx( hdc
, descr
->horz_pos
, 0, NULL
);
762 LISTBOX_PaintItem( descr
, hdc
, &rect
, index
, action
, TRUE
);
763 if (oldFont
) SelectObject( hdc
, oldFont
);
764 if (oldBrush
) SelectObject( hdc
, oldBrush
);
765 ReleaseDC( descr
->self
, hdc
);
769 /***********************************************************************
770 * LISTBOX_DrawFocusRect
772 static void LISTBOX_DrawFocusRect( LB_DESCR
*descr
, BOOL on
)
778 /* Do not repaint the item if the item is not visible */
779 if (!IsWindowVisible(descr
->self
)) return;
781 if (descr
->focus_item
== -1) return;
782 if (!descr
->caret_on
|| !descr
->in_focus
) return;
784 if (LISTBOX_GetItemRect( descr
, descr
->focus_item
, &rect
) != 1) return;
785 if (!(hdc
= GetDCEx( descr
->self
, 0, DCX_CACHE
))) return;
786 if (descr
->font
) oldFont
= SelectObject( hdc
, descr
->font
);
787 if (!IsWindowEnabled(descr
->self
))
788 SetTextColor( hdc
, GetSysColor( COLOR_GRAYTEXT
) );
789 SetWindowOrgEx( hdc
, descr
->horz_pos
, 0, NULL
);
790 LISTBOX_PaintItem( descr
, hdc
, &rect
, descr
->focus_item
, ODA_FOCUS
, !on
);
791 if (oldFont
) SelectObject( hdc
, oldFont
);
792 ReleaseDC( descr
->self
, hdc
);
796 /***********************************************************************
797 * LISTBOX_InitStorage
799 static LRESULT
LISTBOX_InitStorage( LB_DESCR
*descr
, INT nb_items
)
801 UINT new_size
= descr
->nb_items
+ nb_items
;
803 if (new_size
> descr
->items_size
&& !resize_storage(descr
, new_size
))
805 return descr
->items_size
;
809 /***********************************************************************
810 * LISTBOX_SetTabStops
812 static BOOL
LISTBOX_SetTabStops( LB_DESCR
*descr
, INT count
, LPINT tabs
)
816 if (!(descr
->style
& LBS_USETABSTOPS
))
818 SetLastError(ERROR_LB_WITHOUT_TABSTOPS
);
822 HeapFree( GetProcessHeap(), 0, descr
->tabs
);
823 if (!(descr
->nb_tabs
= count
))
828 if (!(descr
->tabs
= HeapAlloc( GetProcessHeap(), 0,
829 descr
->nb_tabs
* sizeof(INT
) )))
831 memcpy( descr
->tabs
, tabs
, descr
->nb_tabs
* sizeof(INT
) );
833 /* convert into "dialog units"*/
834 for (i
= 0; i
< descr
->nb_tabs
; i
++)
835 descr
->tabs
[i
] = MulDiv(descr
->tabs
[i
], descr
->avg_char_width
, 4);
841 /***********************************************************************
844 static LRESULT
LISTBOX_GetText( LB_DESCR
*descr
, INT index
, LPWSTR buffer
, BOOL unicode
)
848 if ((index
< 0) || (index
>= descr
->nb_items
))
850 SetLastError(ERROR_INVALID_INDEX
);
854 if (HAS_STRINGS(descr
))
856 WCHAR
*str
= get_item_string(descr
, index
);
859 return lstrlenW(str
);
861 TRACE("index %d (0x%04x) %s\n", index
, index
, debugstr_w(str
));
863 __TRY
/* hide a Delphi bug that passes a read-only buffer */
865 lstrcpyW(buffer
, str
);
866 len
= lstrlenW(buffer
);
870 WARN( "got an invalid buffer (Delphi bug?)\n" );
871 SetLastError( ERROR_INVALID_PARAMETER
);
878 *((ULONG_PTR
*)buffer
) = get_item_data(descr
, index
);
879 len
= sizeof(ULONG_PTR
);
884 static inline INT
LISTBOX_lstrcmpiW( LCID lcid
, LPCWSTR str1
, LPCWSTR str2
)
886 INT ret
= CompareStringW( lcid
, NORM_IGNORECASE
, str1
, -1, str2
, -1 );
887 if (ret
== CSTR_LESS_THAN
)
889 if (ret
== CSTR_EQUAL
)
891 if (ret
== CSTR_GREATER_THAN
)
896 /***********************************************************************
897 * LISTBOX_FindStringPos
899 * Find the nearest string located before a given string in sort order.
900 * If 'exact' is TRUE, return an error if we don't get an exact match.
902 static INT
LISTBOX_FindStringPos( LB_DESCR
*descr
, LPCWSTR str
, BOOL exact
)
904 INT index
, min
, max
, res
;
906 if (!descr
->nb_items
|| !(descr
->style
& LBS_SORT
)) return -1; /* Add it at the end */
909 max
= descr
->nb_items
- 1;
912 index
= (min
+ max
) / 2;
913 if (HAS_STRINGS(descr
))
914 res
= LISTBOX_lstrcmpiW( descr
->locale
, get_item_string(descr
, index
), str
);
917 COMPAREITEMSTRUCT cis
;
918 UINT id
= (UINT
)GetWindowLongPtrW( descr
->self
, GWLP_ID
);
920 cis
.CtlType
= ODT_LISTBOX
;
922 cis
.hwndItem
= descr
->self
;
923 /* note that some application (MetaStock) expects the second item
924 * to be in the listbox */
926 cis
.itemData1
= get_item_data(descr
, index
);
928 cis
.itemData2
= (ULONG_PTR
)str
;
929 cis
.dwLocaleId
= descr
->locale
;
930 res
= SendMessageW( descr
->owner
, WM_COMPAREITEM
, id
, (LPARAM
)&cis
);
932 if (!res
) return index
;
933 if (res
> 0) max
= index
- 1;
934 else min
= index
+ 1;
936 return exact
? -1 : min
;
940 /***********************************************************************
941 * LISTBOX_FindFileStrPos
943 * Find the nearest string located before a given string in directory
944 * sort order (i.e. first files, then directories, then drives).
946 static INT
LISTBOX_FindFileStrPos( LB_DESCR
*descr
, LPCWSTR str
)
950 if (!HAS_STRINGS(descr
))
951 return LISTBOX_FindStringPos( descr
, str
, FALSE
);
953 max
= descr
->nb_items
;
956 INT index
= (min
+ max
) / 2;
957 LPCWSTR p
= get_item_string(descr
, index
);
958 if (*p
== '[') /* drive or directory */
960 if (*str
!= '[') res
= -1;
961 else if (p
[1] == '-') /* drive */
963 if (str
[1] == '-') res
= str
[2] - p
[2];
968 if (str
[1] == '-') res
= 1;
969 else res
= LISTBOX_lstrcmpiW( descr
->locale
, str
, p
);
974 if (*str
== '[') res
= 1;
975 else res
= LISTBOX_lstrcmpiW( descr
->locale
, str
, p
);
977 if (!res
) return index
;
978 if (res
< 0) max
= index
;
979 else min
= index
+ 1;
985 /***********************************************************************
988 * Find the item beginning with a given string.
990 static INT
LISTBOX_FindString( LB_DESCR
*descr
, INT start
, LPCWSTR str
, BOOL exact
)
994 if (descr
->style
& LBS_NODATA
) return LB_ERR
;
997 if (start
>= descr
->nb_items
) start
= 0;
998 if (HAS_STRINGS(descr
))
1000 if (!str
|| ! str
[0] ) return LB_ERR
;
1003 for (i
= 0, index
= start
; i
< descr
->nb_items
; i
++, index
++)
1005 if (index
== descr
->nb_items
) index
= 0;
1006 if (!LISTBOX_lstrcmpiW(descr
->locale
, str
, get_item_string(descr
, index
)))
1012 /* Special case for drives and directories: ignore prefix */
1013 INT len
= lstrlenW(str
);
1016 for (i
= 0, index
= start
; i
< descr
->nb_items
; i
++, index
++)
1018 if (index
== descr
->nb_items
) index
= 0;
1019 item_str
= get_item_string(descr
, index
);
1021 if (!wcsnicmp(str
, item_str
, len
)) return index
;
1022 if (item_str
[0] == '[')
1024 if (!wcsnicmp(str
, item_str
+ 1, len
)) return index
;
1025 if (item_str
[1] == '-' && !wcsnicmp(str
, item_str
+ 2, len
)) return index
;
1032 if (exact
&& (descr
->style
& LBS_SORT
))
1033 /* If sorted, use a WM_COMPAREITEM binary search */
1034 return LISTBOX_FindStringPos( descr
, str
, TRUE
);
1036 /* Otherwise use a linear search */
1037 for (i
= 0, index
= start
; i
< descr
->nb_items
; i
++, index
++)
1039 if (index
== descr
->nb_items
) index
= 0;
1040 if (get_item_data(descr
, index
) == (ULONG_PTR
)str
) return index
;
1047 /***********************************************************************
1048 * LISTBOX_GetSelCount
1050 static LRESULT
LISTBOX_GetSelCount( const LB_DESCR
*descr
)
1054 if (!(descr
->style
& LBS_MULTIPLESEL
) ||
1055 (descr
->style
& LBS_NOSEL
))
1057 for (i
= count
= 0; i
< descr
->nb_items
; i
++)
1058 if (is_item_selected(descr
, i
)) count
++;
1063 /***********************************************************************
1064 * LISTBOX_GetSelItems
1066 static LRESULT
LISTBOX_GetSelItems( const LB_DESCR
*descr
, INT max
, LPINT array
)
1070 if (!(descr
->style
& LBS_MULTIPLESEL
)) return LB_ERR
;
1071 for (i
= count
= 0; (i
< descr
->nb_items
) && (count
< max
); i
++)
1072 if (is_item_selected(descr
, i
)) array
[count
++] = i
;
1077 /***********************************************************************
1080 static LRESULT
LISTBOX_Paint( LB_DESCR
*descr
, HDC hdc
)
1082 INT i
, col_pos
= descr
->page_size
- 1;
1084 RECT focusRect
= {-1, -1, -1, -1};
1086 HBRUSH hbrush
, oldBrush
= 0;
1088 if (descr
->style
& LBS_NOREDRAW
) return 0;
1090 SetRect( &rect
, 0, 0, descr
->width
, descr
->height
);
1091 if (descr
->style
& LBS_MULTICOLUMN
)
1092 rect
.right
= rect
.left
+ descr
->column_width
;
1093 else if (descr
->horz_pos
)
1095 SetWindowOrgEx( hdc
, descr
->horz_pos
, 0, NULL
);
1096 rect
.right
+= descr
->horz_pos
;
1099 if (descr
->font
) oldFont
= SelectObject( hdc
, descr
->font
);
1100 hbrush
= (HBRUSH
)SendMessageW( descr
->owner
, WM_CTLCOLORLISTBOX
,
1101 (WPARAM
)hdc
, (LPARAM
)descr
->self
);
1102 if (hbrush
) oldBrush
= SelectObject( hdc
, hbrush
);
1103 if (!IsWindowEnabled(descr
->self
)) SetTextColor( hdc
, GetSysColor( COLOR_GRAYTEXT
) );
1105 if (!descr
->nb_items
&& (descr
->focus_item
!= -1) && descr
->caret_on
&&
1108 /* Special case for empty listbox: paint focus rect */
1109 rect
.bottom
= rect
.top
+ descr
->item_height
;
1110 ExtTextOutW( hdc
, 0, 0, ETO_OPAQUE
| ETO_CLIPPED
,
1111 &rect
, NULL
, 0, NULL
);
1112 LISTBOX_PaintItem( descr
, hdc
, &rect
, descr
->focus_item
, ODA_FOCUS
, FALSE
);
1113 rect
.top
= rect
.bottom
;
1116 /* Paint all the item, regarding the selection
1117 Focus state will be painted after */
1119 for (i
= descr
->top_item
; i
< descr
->nb_items
; i
++)
1121 if (!(descr
->style
& LBS_OWNERDRAWVARIABLE
))
1122 rect
.bottom
= rect
.top
+ descr
->item_height
;
1124 rect
.bottom
= rect
.top
+ get_item_height(descr
, i
);
1126 /* keep the focus rect, to paint the focus item after */
1127 if (i
== descr
->focus_item
)
1130 LISTBOX_PaintItem( descr
, hdc
, &rect
, i
, ODA_DRAWENTIRE
, TRUE
);
1131 rect
.top
= rect
.bottom
;
1133 if ((descr
->style
& LBS_MULTICOLUMN
) && !col_pos
)
1135 if (!IS_OWNERDRAW(descr
))
1137 /* Clear the bottom of the column */
1138 if (rect
.top
< descr
->height
)
1140 rect
.bottom
= descr
->height
;
1141 ExtTextOutW( hdc
, 0, 0, ETO_OPAQUE
| ETO_CLIPPED
,
1142 &rect
, NULL
, 0, NULL
);
1146 /* Go to the next column */
1147 rect
.left
+= descr
->column_width
;
1148 rect
.right
+= descr
->column_width
;
1150 col_pos
= descr
->page_size
- 1;
1151 if (rect
.left
>= descr
->width
) break;
1156 if (rect
.top
>= descr
->height
) break;
1160 /* Paint the focus item now */
1161 if (focusRect
.top
!= focusRect
.bottom
&&
1162 descr
->caret_on
&& descr
->in_focus
)
1163 LISTBOX_PaintItem( descr
, hdc
, &focusRect
, descr
->focus_item
, ODA_FOCUS
, FALSE
);
1165 if (!IS_OWNERDRAW(descr
))
1167 /* Clear the remainder of the client area */
1168 if (rect
.top
< descr
->height
)
1170 rect
.bottom
= descr
->height
;
1171 ExtTextOutW( hdc
, 0, 0, ETO_OPAQUE
| ETO_CLIPPED
,
1172 &rect
, NULL
, 0, NULL
);
1174 if (rect
.right
< descr
->width
)
1176 rect
.left
= rect
.right
;
1177 rect
.right
= descr
->width
;
1179 rect
.bottom
= descr
->height
;
1180 ExtTextOutW( hdc
, 0, 0, ETO_OPAQUE
| ETO_CLIPPED
,
1181 &rect
, NULL
, 0, NULL
);
1184 if (oldFont
) SelectObject( hdc
, oldFont
);
1185 if (oldBrush
) SelectObject( hdc
, oldBrush
);
1189 static void LISTBOX_NCPaint( LB_DESCR
*descr
, HRGN region
)
1191 DWORD exstyle
= GetWindowLongW( descr
->self
, GWL_EXSTYLE
);
1192 HTHEME theme
= GetWindowTheme( descr
->self
);
1193 HRGN cliprgn
= region
;
1198 if (!theme
|| !(exstyle
& WS_EX_CLIENTEDGE
))
1201 cxEdge
= GetSystemMetrics(SM_CXEDGE
);
1202 cyEdge
= GetSystemMetrics(SM_CYEDGE
);
1204 GetWindowRect(descr
->self
, &r
);
1206 /* New clipping region passed to default proc to exclude border */
1207 cliprgn
= CreateRectRgn(r
.left
+ cxEdge
, r
.top
+ cyEdge
,
1208 r
.right
- cxEdge
, r
.bottom
- cyEdge
);
1209 if (region
!= (HRGN
)1)
1210 CombineRgn(cliprgn
, cliprgn
, region
, RGN_AND
);
1211 OffsetRect(&r
, -r
.left
, -r
.top
);
1213 hdc
= GetDCEx(descr
->self
, region
, DCX_WINDOW
|DCX_INTERSECTRGN
);
1214 OffsetRect(&r
, -r
.left
, -r
.top
);
1216 if (IsThemeBackgroundPartiallyTransparent (theme
, 0, 0))
1217 DrawThemeParentBackground(descr
->self
, hdc
, &r
);
1218 DrawThemeBackground (theme
, hdc
, 0, 0, &r
, 0);
1219 ReleaseDC(descr
->self
, hdc
);
1222 /***********************************************************************
1223 * LISTBOX_InvalidateItems
1225 * Invalidate all items from a given item. If the specified item is not
1226 * visible, nothing happens.
1228 static void LISTBOX_InvalidateItems( LB_DESCR
*descr
, INT index
)
1232 if (LISTBOX_GetItemRect( descr
, index
, &rect
) == 1)
1234 if (descr
->style
& LBS_NOREDRAW
)
1236 descr
->style
|= LBS_DISPLAYCHANGED
;
1239 rect
.bottom
= descr
->height
;
1240 InvalidateRect( descr
->self
, &rect
, TRUE
);
1241 if (descr
->style
& LBS_MULTICOLUMN
)
1243 /* Repaint the other columns */
1244 rect
.left
= rect
.right
;
1245 rect
.right
= descr
->width
;
1247 InvalidateRect( descr
->self
, &rect
, TRUE
);
1252 static void LISTBOX_InvalidateItemRect( LB_DESCR
*descr
, INT index
)
1256 if (LISTBOX_GetItemRect( descr
, index
, &rect
) == 1)
1257 InvalidateRect( descr
->self
, &rect
, TRUE
);
1260 /***********************************************************************
1261 * LISTBOX_GetItemHeight
1263 static LRESULT
LISTBOX_GetItemHeight( const LB_DESCR
*descr
, INT index
)
1265 if (descr
->style
& LBS_OWNERDRAWVARIABLE
&& descr
->nb_items
> 0)
1267 if ((index
< 0) || (index
>= descr
->nb_items
))
1269 SetLastError(ERROR_INVALID_INDEX
);
1272 return get_item_height(descr
, index
);
1274 else return descr
->item_height
;
1278 /***********************************************************************
1279 * LISTBOX_SetItemHeight
1281 static LRESULT
LISTBOX_SetItemHeight( LB_DESCR
*descr
, INT index
, INT height
, BOOL repaint
)
1283 if (height
> MAXWORD
)
1286 if (!height
) height
= 1;
1288 if (descr
->style
& LBS_OWNERDRAWVARIABLE
)
1290 if ((index
< 0) || (index
>= descr
->nb_items
))
1292 SetLastError(ERROR_INVALID_INDEX
);
1295 TRACE("[%p]: item %d height = %d\n", descr
->self
, index
, height
);
1296 set_item_height(descr
, index
, height
);
1297 LISTBOX_UpdateScroll( descr
);
1299 LISTBOX_InvalidateItems( descr
, index
);
1301 else if (height
!= descr
->item_height
)
1303 TRACE("[%p]: new height = %d\n", descr
->self
, height
);
1304 descr
->item_height
= height
;
1305 LISTBOX_UpdatePage( descr
);
1306 LISTBOX_UpdateScroll( descr
);
1308 InvalidateRect( descr
->self
, 0, TRUE
);
1314 /***********************************************************************
1315 * LISTBOX_SetHorizontalPos
1317 static void LISTBOX_SetHorizontalPos( LB_DESCR
*descr
, INT pos
)
1321 if (pos
> descr
->horz_extent
- descr
->width
)
1322 pos
= descr
->horz_extent
- descr
->width
;
1323 if (pos
< 0) pos
= 0;
1324 if (!(diff
= descr
->horz_pos
- pos
)) return;
1325 TRACE("[%p]: new horz pos = %d\n", descr
->self
, pos
);
1326 descr
->horz_pos
= pos
;
1327 LISTBOX_UpdateScroll( descr
);
1328 if (abs(diff
) < descr
->width
)
1331 /* Invalidate the focused item so it will be repainted correctly */
1332 if (LISTBOX_GetItemRect( descr
, descr
->focus_item
, &rect
) == 1)
1333 InvalidateRect( descr
->self
, &rect
, TRUE
);
1334 ScrollWindowEx( descr
->self
, diff
, 0, NULL
, NULL
, 0, NULL
,
1335 SW_INVALIDATE
| SW_ERASE
| SW_SCROLLCHILDREN
);
1338 InvalidateRect( descr
->self
, NULL
, TRUE
);
1342 /***********************************************************************
1343 * LISTBOX_SetHorizontalExtent
1345 static LRESULT
LISTBOX_SetHorizontalExtent( LB_DESCR
*descr
, INT extent
)
1347 if (descr
->style
& LBS_MULTICOLUMN
)
1349 if (extent
== descr
->horz_extent
) return LB_OKAY
;
1350 TRACE("[%p]: new horz extent = %d\n", descr
->self
, extent
);
1351 descr
->horz_extent
= extent
;
1352 if (descr
->style
& WS_HSCROLL
) {
1354 info
.cbSize
= sizeof(info
);
1356 info
.nMax
= descr
->horz_extent
? descr
->horz_extent
- 1 : 0;
1357 info
.fMask
= SIF_RANGE
;
1358 if (descr
->style
& LBS_DISABLENOSCROLL
)
1359 info
.fMask
|= SIF_DISABLENOSCROLL
;
1360 SetScrollInfo( descr
->self
, SB_HORZ
, &info
, TRUE
);
1362 if (descr
->horz_pos
> extent
- descr
->width
)
1363 LISTBOX_SetHorizontalPos( descr
, extent
- descr
->width
);
1368 /***********************************************************************
1369 * LISTBOX_SetColumnWidth
1371 static LRESULT
LISTBOX_SetColumnWidth( LB_DESCR
*descr
, INT column_width
)
1375 TRACE("[%p]: new column width = %d\n", descr
->self
, column_width
);
1377 GetClientRect(descr
->self
, &rect
);
1378 descr
->width
= rect
.right
- rect
.left
;
1379 descr
->height
= rect
.bottom
- rect
.top
;
1380 descr
->column_width
= column_width
;
1382 LISTBOX_UpdatePage(descr
);
1383 LISTBOX_UpdateScroll(descr
);
1388 /***********************************************************************
1391 * Returns the item height.
1393 static INT
LISTBOX_SetFont( LB_DESCR
*descr
, HFONT font
)
1397 const char *alphabet
= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1402 if (!(hdc
= GetDCEx( descr
->self
, 0, DCX_CACHE
)))
1404 ERR("unable to get DC.\n" );
1407 if (font
) oldFont
= SelectObject( hdc
, font
);
1408 GetTextExtentPointA( hdc
, alphabet
, 52, &sz
);
1409 if (oldFont
) SelectObject( hdc
, oldFont
);
1410 ReleaseDC( descr
->self
, hdc
);
1412 descr
->avg_char_width
= (sz
.cx
/ 26 + 1) / 2;
1413 if (!IS_OWNERDRAW(descr
))
1414 LISTBOX_SetItemHeight( descr
, 0, sz
.cy
, FALSE
);
1419 /***********************************************************************
1420 * LISTBOX_MakeItemVisible
1422 * Make sure that a given item is partially or fully visible.
1424 static void LISTBOX_MakeItemVisible( LB_DESCR
*descr
, INT index
, BOOL fully
)
1428 TRACE("current top item %d, index %d, fully %d\n", descr
->top_item
, index
, fully
);
1430 if (index
<= descr
->top_item
) top
= index
;
1431 else if (descr
->style
& LBS_MULTICOLUMN
)
1433 INT cols
= descr
->width
;
1434 if (!fully
) cols
+= descr
->column_width
- 1;
1435 if (cols
>= descr
->column_width
) cols
/= descr
->column_width
;
1437 if (index
< descr
->top_item
+ (descr
->page_size
* cols
)) return;
1438 top
= index
- descr
->page_size
* (cols
- 1);
1440 else if (descr
->style
& LBS_OWNERDRAWVARIABLE
)
1442 INT height
= fully
? get_item_height(descr
, index
) : 1;
1443 for (top
= index
; top
> descr
->top_item
; top
--)
1444 if ((height
+= get_item_height(descr
, top
- 1)) > descr
->height
) break;
1448 if (index
< descr
->top_item
+ descr
->page_size
) return;
1449 if (!fully
&& (index
== descr
->top_item
+ descr
->page_size
) &&
1450 (descr
->height
> (descr
->page_size
* descr
->item_height
))) return;
1451 top
= index
- descr
->page_size
+ 1;
1453 LISTBOX_SetTopItem( descr
, top
, TRUE
);
1456 /***********************************************************************
1457 * LISTBOX_SetCaretIndex
1460 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1463 static LRESULT
LISTBOX_SetCaretIndex( LB_DESCR
*descr
, INT index
, BOOL fully_visible
)
1465 BOOL focus_changed
= descr
->focus_item
!= index
;
1467 TRACE("old focus %d, index %d\n", descr
->focus_item
, index
);
1469 if (descr
->style
& LBS_NOSEL
) return LB_ERR
;
1470 if ((index
< 0) || (index
>= descr
->nb_items
)) return LB_ERR
;
1474 LISTBOX_DrawFocusRect( descr
, FALSE
);
1475 descr
->focus_item
= index
;
1478 LISTBOX_MakeItemVisible( descr
, index
, fully_visible
);
1481 LISTBOX_DrawFocusRect( descr
, TRUE
);
1487 /***********************************************************************
1488 * LISTBOX_SelectItemRange
1490 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1492 static LRESULT
LISTBOX_SelectItemRange( LB_DESCR
*descr
, INT first
,
1497 /* A few sanity checks */
1499 if (descr
->style
& LBS_NOSEL
) return LB_ERR
;
1500 if (!(descr
->style
& LBS_MULTIPLESEL
)) return LB_ERR
;
1502 if (!descr
->nb_items
) return LB_OKAY
;
1504 if (last
== -1 || last
>= descr
->nb_items
) last
= descr
->nb_items
- 1;
1505 if (first
< 0) first
= 0;
1506 if (last
< first
) return LB_OKAY
;
1508 if (on
) /* Turn selection on */
1510 for (i
= first
; i
<= last
; i
++)
1512 if (is_item_selected(descr
, i
)) continue;
1513 set_item_selected_state(descr
, i
, TRUE
);
1514 LISTBOX_InvalidateItemRect(descr
, i
);
1517 else /* Turn selection off */
1519 for (i
= first
; i
<= last
; i
++)
1521 if (!is_item_selected(descr
, i
)) continue;
1522 set_item_selected_state(descr
, i
, FALSE
);
1523 LISTBOX_InvalidateItemRect(descr
, i
);
1529 /***********************************************************************
1530 * LISTBOX_SetSelection
1532 static LRESULT
LISTBOX_SetSelection( LB_DESCR
*descr
, INT index
,
1533 BOOL on
, BOOL send_notify
)
1535 TRACE( "cur_sel=%d index=%d notify=%s\n",
1536 descr
->selected_item
, index
, send_notify
? "YES" : "NO" );
1538 if (descr
->style
& LBS_NOSEL
)
1540 descr
->selected_item
= index
;
1543 if ((index
< -1) || (index
>= descr
->nb_items
)) return LB_ERR
;
1544 if (descr
->style
& LBS_MULTIPLESEL
)
1546 if (index
== -1) /* Select all items */
1547 return LISTBOX_SelectItemRange( descr
, 0, descr
->nb_items
, on
);
1548 else /* Only one item */
1549 return LISTBOX_SelectItemRange( descr
, index
, index
, on
);
1553 INT oldsel
= descr
->selected_item
;
1554 if (index
== oldsel
) return LB_OKAY
;
1555 if (oldsel
!= -1) set_item_selected_state(descr
, oldsel
, FALSE
);
1556 if (index
!= -1) set_item_selected_state(descr
, index
, TRUE
);
1557 descr
->selected_item
= index
;
1558 if (oldsel
!= -1) LISTBOX_RepaintItem( descr
, oldsel
, ODA_SELECT
);
1559 if (index
!= -1) LISTBOX_RepaintItem( descr
, index
, ODA_SELECT
);
1560 if (send_notify
&& descr
->nb_items
) SEND_NOTIFICATION( descr
,
1561 (index
!= -1) ? LBN_SELCHANGE
: LBN_SELCANCEL
);
1563 if( descr
->lphc
) /* set selection change flag for parent combo */
1564 descr
->lphc
->wState
|= CBF_SELCHANGE
;
1570 /***********************************************************************
1573 * Change the caret position and extend the selection to the new caret.
1575 static void LISTBOX_MoveCaret( LB_DESCR
*descr
, INT index
, BOOL fully_visible
)
1577 TRACE("old focus %d, index %d\n", descr
->focus_item
, index
);
1579 if ((index
< 0) || (index
>= descr
->nb_items
))
1582 /* Important, repaint needs to be done in this order if
1583 you want to mimic Windows behavior:
1584 1. Remove the focus and paint the item
1585 2. Remove the selection and paint the item(s)
1586 3. Set the selection and repaint the item(s)
1587 4. Set the focus to 'index' and repaint the item */
1589 /* 1. remove the focus and repaint the item */
1590 LISTBOX_DrawFocusRect( descr
, FALSE
);
1592 /* 2. then turn off the previous selection */
1593 /* 3. repaint the new selected item */
1594 if (descr
->style
& LBS_EXTENDEDSEL
)
1596 if (descr
->anchor_item
!= -1)
1598 INT first
= min( index
, descr
->anchor_item
);
1599 INT last
= max( index
, descr
->anchor_item
);
1601 LISTBOX_SelectItemRange( descr
, 0, first
- 1, FALSE
);
1602 LISTBOX_SelectItemRange( descr
, last
+ 1, -1, FALSE
);
1603 LISTBOX_SelectItemRange( descr
, first
, last
, TRUE
);
1606 else if (!(descr
->style
& LBS_MULTIPLESEL
))
1608 /* Set selection to new caret item */
1609 LISTBOX_SetSelection( descr
, index
, TRUE
, FALSE
);
1612 /* 4. repaint the new item with the focus */
1613 descr
->focus_item
= index
;
1614 LISTBOX_MakeItemVisible( descr
, index
, fully_visible
);
1615 LISTBOX_DrawFocusRect( descr
, TRUE
);
1619 /***********************************************************************
1620 * LISTBOX_InsertItem
1622 static LRESULT
LISTBOX_InsertItem( LB_DESCR
*descr
, INT index
,
1623 LPWSTR str
, ULONG_PTR data
)
1625 INT oldfocus
= descr
->focus_item
;
1627 if (index
== -1) index
= descr
->nb_items
;
1628 else if ((index
< 0) || (index
> descr
->nb_items
)) return LB_ERR
;
1629 if (!resize_storage(descr
, descr
->nb_items
+ 1)) return LB_ERR
;
1631 insert_item_data(descr
, index
);
1633 set_item_string(descr
, index
, str
);
1634 set_item_data(descr
, index
, HAS_STRINGS(descr
) ? 0 : data
);
1635 set_item_height(descr
, index
, 0);
1636 set_item_selected_state(descr
, index
, FALSE
);
1638 /* Get item height */
1640 if (descr
->style
& LBS_OWNERDRAWVARIABLE
)
1642 MEASUREITEMSTRUCT mis
;
1643 UINT id
= (UINT
)GetWindowLongPtrW( descr
->self
, GWLP_ID
);
1645 mis
.CtlType
= ODT_LISTBOX
;
1648 mis
.itemData
= data
;
1649 mis
.itemHeight
= descr
->item_height
;
1650 SendMessageW( descr
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&mis
);
1651 set_item_height(descr
, index
, mis
.itemHeight
? mis
.itemHeight
: 1);
1652 TRACE("[%p]: measure item %d (%s) = %d\n",
1653 descr
->self
, index
, str
? debugstr_w(str
) : "", get_item_height(descr
, index
));
1656 /* Repaint the items */
1658 LISTBOX_UpdateScroll( descr
);
1659 LISTBOX_InvalidateItems( descr
, index
);
1661 /* Move selection and focused item */
1662 /* If listbox was empty, set focus to the first item */
1663 if (descr
->nb_items
== 1)
1664 LISTBOX_SetCaretIndex( descr
, 0, FALSE
);
1665 /* single select don't change selection index in win31 */
1666 else if ((ISWIN31
) && !(IS_MULTISELECT(descr
)))
1668 descr
->selected_item
++;
1669 LISTBOX_SetSelection( descr
, descr
->selected_item
-1, TRUE
, FALSE
);
1673 if (index
<= descr
->selected_item
)
1675 descr
->selected_item
++;
1676 descr
->focus_item
= oldfocus
; /* focus not changed */
1683 /***********************************************************************
1684 * LISTBOX_InsertString
1686 static LRESULT
LISTBOX_InsertString( LB_DESCR
*descr
, INT index
, LPCWSTR str
)
1688 LPWSTR new_str
= NULL
;
1691 if (HAS_STRINGS(descr
))
1693 if (!str
) str
= L
"";
1694 if (!(new_str
= HeapAlloc( GetProcessHeap(), 0, (lstrlenW(str
) + 1) * sizeof(WCHAR
) )))
1696 SEND_NOTIFICATION( descr
, LBN_ERRSPACE
);
1699 lstrcpyW(new_str
, str
);
1702 if (index
== -1) index
= descr
->nb_items
;
1703 if ((ret
= LISTBOX_InsertItem( descr
, index
, new_str
, (ULONG_PTR
)str
)) != 0)
1705 HeapFree( GetProcessHeap(), 0, new_str
);
1709 TRACE("[%p]: added item %d %s\n",
1710 descr
->self
, index
, HAS_STRINGS(descr
) ? debugstr_w(new_str
) : "" );
1715 /***********************************************************************
1716 * LISTBOX_DeleteItem
1718 * Delete the content of an item. 'index' must be a valid index.
1720 static void LISTBOX_DeleteItem( LB_DESCR
*descr
, INT index
)
1722 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1723 * while Win95 sends it for all items with user data.
1724 * It's probably better to send it too often than not
1725 * often enough, so this is what we do here.
1727 if (IS_OWNERDRAW(descr
) || get_item_data(descr
, index
))
1729 DELETEITEMSTRUCT dis
;
1730 UINT id
= (UINT
)GetWindowLongPtrW( descr
->self
, GWLP_ID
);
1732 dis
.CtlType
= ODT_LISTBOX
;
1735 dis
.hwndItem
= descr
->self
;
1736 dis
.itemData
= get_item_data(descr
, index
);
1737 SendMessageW( descr
->owner
, WM_DELETEITEM
, id
, (LPARAM
)&dis
);
1739 HeapFree( GetProcessHeap(), 0, get_item_string(descr
, index
) );
1743 /***********************************************************************
1744 * LISTBOX_RemoveItem
1746 * Remove an item from the listbox and delete its content.
1748 static LRESULT
LISTBOX_RemoveItem( LB_DESCR
*descr
, INT index
)
1750 if ((index
< 0) || (index
>= descr
->nb_items
)) return LB_ERR
;
1752 /* We need to invalidate the original rect instead of the updated one. */
1753 LISTBOX_InvalidateItems( descr
, index
);
1755 if (descr
->nb_items
== 1)
1757 SendMessageW(descr
->self
, LB_RESETCONTENT
, 0, 0);
1761 LISTBOX_DeleteItem( descr
, index
);
1762 remove_item_data(descr
, index
);
1764 if (descr
->anchor_item
== descr
->nb_items
) descr
->anchor_item
--;
1765 resize_storage(descr
, descr
->nb_items
);
1767 /* Repaint the items */
1769 LISTBOX_UpdateScroll( descr
);
1770 /* if we removed the scrollbar, reset the top of the list
1771 (correct for owner-drawn ???) */
1772 if (descr
->nb_items
== descr
->page_size
)
1773 LISTBOX_SetTopItem( descr
, 0, TRUE
);
1775 /* Move selection and focused item */
1776 if (!IS_MULTISELECT(descr
))
1778 if (index
== descr
->selected_item
)
1779 descr
->selected_item
= -1;
1780 else if (index
< descr
->selected_item
)
1782 descr
->selected_item
--;
1783 if (ISWIN31
) /* win 31 do not change the selected item number */
1784 LISTBOX_SetSelection( descr
, descr
->selected_item
+ 1, TRUE
, FALSE
);
1788 if (descr
->focus_item
>= descr
->nb_items
)
1790 descr
->focus_item
= descr
->nb_items
- 1;
1791 if (descr
->focus_item
< 0) descr
->focus_item
= 0;
1797 /***********************************************************************
1798 * LISTBOX_ResetContent
1800 static void LISTBOX_ResetContent( LB_DESCR
*descr
)
1804 if (!(descr
->style
& LBS_NODATA
))
1805 for (i
= descr
->nb_items
- 1; i
>= 0; i
--) LISTBOX_DeleteItem(descr
, i
);
1806 HeapFree( GetProcessHeap(), 0, descr
->u
.items
);
1807 descr
->nb_items
= 0;
1808 descr
->top_item
= 0;
1809 descr
->selected_item
= -1;
1810 descr
->focus_item
= 0;
1811 descr
->anchor_item
= -1;
1812 descr
->items_size
= 0;
1813 descr
->u
.items
= NULL
;
1817 /***********************************************************************
1820 static LRESULT
LISTBOX_SetCount( LB_DESCR
*descr
, UINT count
)
1822 UINT orig_num
= descr
->nb_items
;
1824 if (!(descr
->style
& LBS_NODATA
)) return LB_ERR
;
1826 if (!resize_storage(descr
, count
))
1828 descr
->nb_items
= count
;
1832 LISTBOX_UpdateScroll(descr
);
1833 if (count
< orig_num
)
1835 descr
->anchor_item
= min(descr
->anchor_item
, count
- 1);
1836 if (descr
->selected_item
>= count
)
1837 descr
->selected_item
= -1;
1839 /* If we removed the scrollbar, reset the top of the list */
1840 if (count
<= descr
->page_size
&& orig_num
> descr
->page_size
)
1841 LISTBOX_SetTopItem(descr
, 0, TRUE
);
1843 descr
->focus_item
= min(descr
->focus_item
, count
- 1);
1846 /* If it was empty before growing, set focus to the first item */
1847 else if (orig_num
== 0) LISTBOX_SetCaretIndex(descr
, 0, FALSE
);
1849 else SendMessageW(descr
->self
, LB_RESETCONTENT
, 0, 0);
1851 InvalidateRect( descr
->self
, NULL
, TRUE
);
1856 /***********************************************************************
1859 static LRESULT
LISTBOX_Directory( LB_DESCR
*descr
, UINT attrib
,
1860 LPCWSTR filespec
, BOOL long_names
)
1863 LRESULT ret
= LB_OKAY
;
1864 WIN32_FIND_DATAW entry
;
1866 LRESULT maxinsert
= LB_ERR
;
1868 /* don't scan directory if we just want drives exclusively */
1869 if (attrib
!= (DDL_DRIVES
| DDL_EXCLUSIVE
)) {
1870 /* scan directory */
1871 if ((handle
= FindFirstFileW(filespec
, &entry
)) == INVALID_HANDLE_VALUE
)
1873 int le
= GetLastError();
1874 if ((le
!= ERROR_NO_MORE_FILES
) && (le
!= ERROR_FILE_NOT_FOUND
)) return LB_ERR
;
1881 if (entry
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
1883 if (!(attrib
& DDL_DIRECTORY
) ||
1884 !lstrcmpW( entry
.cFileName
, L
"." )) continue;
1886 if (!long_names
&& entry
.cAlternateFileName
[0])
1887 lstrcpyW( buffer
+ 1, entry
.cAlternateFileName
);
1889 lstrcpyW( buffer
+ 1, entry
.cFileName
);
1890 lstrcatW(buffer
, L
"]");
1892 else /* not a directory */
1894 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1895 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1897 if ((attrib
& DDL_EXCLUSIVE
) &&
1898 ((attrib
& ATTRIBS
) != (entry
.dwFileAttributes
& ATTRIBS
)))
1901 if (!long_names
&& entry
.cAlternateFileName
[0])
1902 lstrcpyW( buffer
, entry
.cAlternateFileName
);
1904 lstrcpyW( buffer
, entry
.cFileName
);
1906 if (!long_names
) CharLowerW( buffer
);
1907 pos
= LISTBOX_FindFileStrPos( descr
, buffer
);
1908 if ((ret
= LISTBOX_InsertString( descr
, pos
, buffer
)) < 0)
1910 if (ret
<= maxinsert
) maxinsert
++; else maxinsert
= ret
;
1911 } while (FindNextFileW( handle
, &entry
));
1912 FindClose( handle
);
1920 if (attrib
& DDL_DRIVES
)
1922 WCHAR buffer
[] = L
"[-a-]";
1923 WCHAR root
[] = L
"A:\\";
1925 for (drive
= 0; drive
< 26; drive
++, buffer
[2]++, root
[0]++)
1927 if (GetDriveTypeW(root
) <= DRIVE_NO_ROOT_DIR
) continue;
1928 if ((ret
= LISTBOX_InsertString( descr
, -1, buffer
)) < 0)
1937 /***********************************************************************
1938 * LISTBOX_HandleVScroll
1940 static LRESULT
LISTBOX_HandleVScroll( LB_DESCR
*descr
, WORD scrollReq
, WORD pos
)
1944 if (descr
->style
& LBS_MULTICOLUMN
) return 0;
1948 LISTBOX_SetTopItem( descr
, descr
->top_item
- 1, TRUE
);
1951 LISTBOX_SetTopItem( descr
, descr
->top_item
+ 1, TRUE
);
1954 LISTBOX_SetTopItem( descr
, descr
->top_item
-
1955 LISTBOX_GetCurrentPageSize( descr
), TRUE
);
1958 LISTBOX_SetTopItem( descr
, descr
->top_item
+
1959 LISTBOX_GetCurrentPageSize( descr
), TRUE
);
1961 case SB_THUMBPOSITION
:
1962 LISTBOX_SetTopItem( descr
, pos
, TRUE
);
1965 info
.cbSize
= sizeof(info
);
1966 info
.fMask
= SIF_TRACKPOS
;
1967 GetScrollInfo( descr
->self
, SB_VERT
, &info
);
1968 LISTBOX_SetTopItem( descr
, info
.nTrackPos
, TRUE
);
1971 LISTBOX_SetTopItem( descr
, 0, TRUE
);
1974 LISTBOX_SetTopItem( descr
, descr
->nb_items
, TRUE
);
1981 /***********************************************************************
1982 * LISTBOX_HandleHScroll
1984 static LRESULT
LISTBOX_HandleHScroll( LB_DESCR
*descr
, WORD scrollReq
, WORD pos
)
1989 if (descr
->style
& LBS_MULTICOLUMN
)
1994 LISTBOX_SetTopItem( descr
, descr
->top_item
-descr
->page_size
,
1998 LISTBOX_SetTopItem( descr
, descr
->top_item
+descr
->page_size
,
2002 page
= descr
->width
/ descr
->column_width
;
2003 if (page
< 1) page
= 1;
2004 LISTBOX_SetTopItem( descr
,
2005 descr
->top_item
- page
* descr
->page_size
, TRUE
);
2008 page
= descr
->width
/ descr
->column_width
;
2009 if (page
< 1) page
= 1;
2010 LISTBOX_SetTopItem( descr
,
2011 descr
->top_item
+ page
* descr
->page_size
, TRUE
);
2013 case SB_THUMBPOSITION
:
2014 LISTBOX_SetTopItem( descr
, pos
*descr
->page_size
, TRUE
);
2017 info
.cbSize
= sizeof(info
);
2018 info
.fMask
= SIF_TRACKPOS
;
2019 GetScrollInfo( descr
->self
, SB_VERT
, &info
);
2020 LISTBOX_SetTopItem( descr
, info
.nTrackPos
*descr
->page_size
,
2024 LISTBOX_SetTopItem( descr
, 0, TRUE
);
2027 LISTBOX_SetTopItem( descr
, descr
->nb_items
, TRUE
);
2031 else if (descr
->horz_extent
)
2036 LISTBOX_SetHorizontalPos( descr
, descr
->horz_pos
- 1 );
2039 LISTBOX_SetHorizontalPos( descr
, descr
->horz_pos
+ 1 );
2042 LISTBOX_SetHorizontalPos( descr
,
2043 descr
->horz_pos
- descr
->width
);
2046 LISTBOX_SetHorizontalPos( descr
,
2047 descr
->horz_pos
+ descr
->width
);
2049 case SB_THUMBPOSITION
:
2050 LISTBOX_SetHorizontalPos( descr
, pos
);
2053 info
.cbSize
= sizeof(info
);
2054 info
.fMask
= SIF_TRACKPOS
;
2055 GetScrollInfo( descr
->self
, SB_HORZ
, &info
);
2056 LISTBOX_SetHorizontalPos( descr
, info
.nTrackPos
);
2059 LISTBOX_SetHorizontalPos( descr
, 0 );
2062 LISTBOX_SetHorizontalPos( descr
,
2063 descr
->horz_extent
- descr
->width
);
2070 static LRESULT
LISTBOX_HandleMouseWheel(LB_DESCR
*descr
, SHORT delta
)
2072 INT pulScrollLines
= 3;
2074 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES
,0, &pulScrollLines
, 0);
2076 /* if scrolling changes direction, ignore left overs */
2077 if ((delta
< 0 && descr
->wheel_remain
< 0) ||
2078 (delta
> 0 && descr
->wheel_remain
> 0))
2079 descr
->wheel_remain
+= delta
;
2081 descr
->wheel_remain
= delta
;
2083 if (descr
->wheel_remain
&& pulScrollLines
)
2086 if (descr
->style
& LBS_MULTICOLUMN
)
2088 pulScrollLines
= min(descr
->width
/ descr
->column_width
, pulScrollLines
);
2089 pulScrollLines
= max(1, pulScrollLines
);
2090 cLineScroll
= pulScrollLines
* descr
->wheel_remain
/ WHEEL_DELTA
;
2091 descr
->wheel_remain
-= WHEEL_DELTA
* cLineScroll
/ pulScrollLines
;
2092 cLineScroll
*= descr
->page_size
;
2096 pulScrollLines
= min(descr
->page_size
, pulScrollLines
);
2097 cLineScroll
= pulScrollLines
* descr
->wheel_remain
/ WHEEL_DELTA
;
2098 descr
->wheel_remain
-= WHEEL_DELTA
* cLineScroll
/ pulScrollLines
;
2100 LISTBOX_SetTopItem( descr
, descr
->top_item
- cLineScroll
, TRUE
);
2105 /***********************************************************************
2106 * LISTBOX_HandleLButtonDown
2108 static LRESULT
LISTBOX_HandleLButtonDown( LB_DESCR
*descr
, DWORD keys
, INT x
, INT y
)
2110 INT index
= LISTBOX_GetItemFromPoint( descr
, x
, y
);
2112 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2113 descr
->self
, x
, y
, index
, descr
->focus_item
);
2115 if (!descr
->caret_on
&& (descr
->in_focus
)) return 0;
2117 if (!descr
->in_focus
)
2119 if( !descr
->lphc
) SetFocus( descr
->self
);
2120 else SetFocus( (descr
->lphc
->hWndEdit
) ? descr
->lphc
->hWndEdit
: descr
->lphc
->self
);
2123 if (index
== -1) return 0;
2127 if (descr
->style
& LBS_NOTIFY
)
2128 SendMessageW( descr
->owner
, WM_LBTRACKPOINT
, index
,
2129 MAKELPARAM( x
, y
) );
2132 descr
->captured
= TRUE
;
2133 SetCapture( descr
->self
);
2135 if (descr
->style
& (LBS_EXTENDEDSEL
| LBS_MULTIPLESEL
))
2137 /* we should perhaps make sure that all items are deselected
2138 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2139 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2140 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2143 if (!(keys
& MK_SHIFT
)) descr
->anchor_item
= index
;
2144 if (keys
& MK_CONTROL
)
2146 LISTBOX_SetCaretIndex( descr
, index
, FALSE
);
2147 LISTBOX_SetSelection( descr
, index
,
2148 !is_item_selected(descr
, index
),
2149 (descr
->style
& LBS_NOTIFY
) != 0);
2153 LISTBOX_MoveCaret( descr
, index
, FALSE
);
2155 if (descr
->style
& LBS_EXTENDEDSEL
)
2157 LISTBOX_SetSelection( descr
, index
,
2158 is_item_selected(descr
, index
),
2159 (descr
->style
& LBS_NOTIFY
) != 0 );
2163 LISTBOX_SetSelection( descr
, index
,
2164 !is_item_selected(descr
, index
),
2165 (descr
->style
& LBS_NOTIFY
) != 0 );
2171 descr
->anchor_item
= index
;
2172 LISTBOX_MoveCaret( descr
, index
, FALSE
);
2173 LISTBOX_SetSelection( descr
, index
,
2174 TRUE
, (descr
->style
& LBS_NOTIFY
) != 0 );
2179 if (GetWindowLongW( descr
->self
, GWL_EXSTYLE
) & WS_EX_DRAGDETECT
)
2186 if (DragDetect( descr
->self
, pt
))
2187 SendMessageW( descr
->owner
, WM_BEGINDRAG
, 0, 0 );
2194 /*************************************************************************
2195 * LISTBOX_HandleLButtonDownCombo [Internal]
2197 * Process LButtonDown message for the ComboListBox
2200 * pWnd [I] The windows internal structure
2201 * pDescr [I] The ListBox internal structure
2202 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2203 * x [I] X Mouse Coordinate
2204 * y [I] Y Mouse Coordinate
2207 * 0 since we are processing the WM_LBUTTONDOWN Message
2210 * This function is only to be used when a ListBox is a ComboListBox
2213 static LRESULT
LISTBOX_HandleLButtonDownCombo( LB_DESCR
*descr
, UINT msg
, DWORD keys
, INT x
, INT y
)
2215 RECT clientRect
, screenRect
;
2221 GetClientRect(descr
->self
, &clientRect
);
2223 if(PtInRect(&clientRect
, mousePos
))
2225 /* MousePos is in client, resume normal processing */
2226 if (msg
== WM_LBUTTONDOWN
)
2228 descr
->lphc
->droppedIndex
= descr
->nb_items
? descr
->selected_item
: -1;
2229 return LISTBOX_HandleLButtonDown( descr
, keys
, x
, y
);
2231 else if (descr
->style
& LBS_NOTIFY
)
2232 SEND_NOTIFICATION( descr
, LBN_DBLCLK
);
2236 POINT screenMousePos
;
2237 HWND hWndOldCapture
;
2239 /* Check the Non-Client Area */
2240 screenMousePos
= mousePos
;
2241 hWndOldCapture
= GetCapture();
2243 GetWindowRect(descr
->self
, &screenRect
);
2244 ClientToScreen(descr
->self
, &screenMousePos
);
2246 if(!PtInRect(&screenRect
, screenMousePos
))
2248 LISTBOX_SetCaretIndex( descr
, descr
->lphc
->droppedIndex
, FALSE
);
2249 LISTBOX_SetSelection( descr
, descr
->lphc
->droppedIndex
, FALSE
, FALSE
);
2250 COMBO_FlipListbox( descr
->lphc
, FALSE
, FALSE
);
2254 /* Check to see the NC is a scrollbar */
2256 LONG style
= GetWindowLongW( descr
->self
, GWL_STYLE
);
2257 /* Check Vertical scroll bar */
2258 if (style
& WS_VSCROLL
)
2260 clientRect
.right
+= GetSystemMetrics(SM_CXVSCROLL
);
2261 if (PtInRect( &clientRect
, mousePos
))
2262 nHitTestType
= HTVSCROLL
;
2264 /* Check horizontal scroll bar */
2265 if (style
& WS_HSCROLL
)
2267 clientRect
.bottom
+= GetSystemMetrics(SM_CYHSCROLL
);
2268 if (PtInRect( &clientRect
, mousePos
))
2269 nHitTestType
= HTHSCROLL
;
2271 /* Windows sends this message when a scrollbar is clicked
2274 if(nHitTestType
!= 0)
2276 SendMessageW(descr
->self
, WM_NCLBUTTONDOWN
, nHitTestType
,
2277 MAKELONG(screenMousePos
.x
, screenMousePos
.y
));
2279 /* Resume the Capture after scrolling is complete
2281 if(hWndOldCapture
!= 0)
2282 SetCapture(hWndOldCapture
);
2288 /***********************************************************************
2289 * LISTBOX_HandleLButtonUp
2291 static LRESULT
LISTBOX_HandleLButtonUp( LB_DESCR
*descr
)
2293 if (LISTBOX_Timer
!= LB_TIMER_NONE
)
2294 KillSystemTimer( descr
->self
, LB_TIMER_ID
);
2295 LISTBOX_Timer
= LB_TIMER_NONE
;
2296 if (descr
->captured
)
2298 descr
->captured
= FALSE
;
2299 if (GetCapture() == descr
->self
) ReleaseCapture();
2300 if ((descr
->style
& LBS_NOTIFY
) && descr
->nb_items
)
2301 SEND_NOTIFICATION( descr
, LBN_SELCHANGE
);
2307 /***********************************************************************
2308 * LISTBOX_HandleTimer
2310 * Handle scrolling upon a timer event.
2311 * Return TRUE if scrolling should continue.
2313 static LRESULT
LISTBOX_HandleTimer( LB_DESCR
*descr
, INT index
, TIMER_DIRECTION dir
)
2318 if (descr
->top_item
) index
= descr
->top_item
- 1;
2322 if (descr
->top_item
) index
-= descr
->page_size
;
2325 index
= descr
->top_item
+ LISTBOX_GetCurrentPageSize( descr
);
2326 if (index
== descr
->focus_item
) index
++;
2327 if (index
>= descr
->nb_items
) index
= descr
->nb_items
- 1;
2329 case LB_TIMER_RIGHT
:
2330 if (index
+ descr
->page_size
< descr
->nb_items
)
2331 index
+= descr
->page_size
;
2336 if (index
== descr
->focus_item
) return FALSE
;
2337 LISTBOX_MoveCaret( descr
, index
, FALSE
);
2342 /***********************************************************************
2343 * LISTBOX_HandleSystemTimer
2345 * WM_SYSTIMER handler.
2347 static LRESULT
LISTBOX_HandleSystemTimer( LB_DESCR
*descr
)
2349 if (!LISTBOX_HandleTimer( descr
, descr
->focus_item
, LISTBOX_Timer
))
2351 KillSystemTimer( descr
->self
, LB_TIMER_ID
);
2352 LISTBOX_Timer
= LB_TIMER_NONE
;
2358 /***********************************************************************
2359 * LISTBOX_HandleMouseMove
2361 * WM_MOUSEMOVE handler.
2363 static void LISTBOX_HandleMouseMove( LB_DESCR
*descr
,
2367 TIMER_DIRECTION dir
= LB_TIMER_NONE
;
2369 if (!descr
->captured
) return;
2371 if (descr
->style
& LBS_MULTICOLUMN
)
2374 else if (y
>= descr
->item_height
* descr
->page_size
)
2375 y
= descr
->item_height
* descr
->page_size
- 1;
2379 dir
= LB_TIMER_LEFT
;
2382 else if (x
>= descr
->width
)
2384 dir
= LB_TIMER_RIGHT
;
2385 x
= descr
->width
- 1;
2390 if (y
< 0) dir
= LB_TIMER_UP
; /* above */
2391 else if (y
>= descr
->height
) dir
= LB_TIMER_DOWN
; /* below */
2394 index
= LISTBOX_GetItemFromPoint( descr
, x
, y
);
2395 if (index
== -1) index
= descr
->focus_item
;
2396 if (!LISTBOX_HandleTimer( descr
, index
, dir
)) dir
= LB_TIMER_NONE
;
2398 /* Start/stop the system timer */
2400 if (dir
!= LB_TIMER_NONE
)
2401 SetSystemTimer( descr
->self
, LB_TIMER_ID
, LB_SCROLL_TIMEOUT
, NULL
);
2402 else if (LISTBOX_Timer
!= LB_TIMER_NONE
)
2403 KillSystemTimer( descr
->self
, LB_TIMER_ID
);
2404 LISTBOX_Timer
= dir
;
2408 /***********************************************************************
2409 * LISTBOX_HandleKeyDown
2411 static LRESULT
LISTBOX_HandleKeyDown( LB_DESCR
*descr
, DWORD key
)
2414 BOOL bForceSelection
= TRUE
; /* select item pointed to by focus_item */
2415 if ((IS_MULTISELECT(descr
)) || (descr
->selected_item
== descr
->focus_item
))
2416 bForceSelection
= FALSE
; /* only for single select list */
2418 if (descr
->style
& LBS_WANTKEYBOARDINPUT
)
2420 caret
= SendMessageW( descr
->owner
, WM_VKEYTOITEM
,
2421 MAKEWPARAM(LOWORD(key
), descr
->focus_item
),
2422 (LPARAM
)descr
->self
);
2423 if (caret
== -2) return 0;
2425 if (caret
== -1) switch(key
)
2428 if (descr
->style
& LBS_MULTICOLUMN
)
2430 bForceSelection
= FALSE
;
2431 if (descr
->focus_item
>= descr
->page_size
)
2432 caret
= descr
->focus_item
- descr
->page_size
;
2437 caret
= descr
->focus_item
- 1;
2438 if (caret
< 0) caret
= 0;
2441 if (descr
->style
& LBS_MULTICOLUMN
)
2443 bForceSelection
= FALSE
;
2444 caret
= min(descr
->focus_item
+ descr
->page_size
, descr
->nb_items
- 1);
2449 caret
= descr
->focus_item
+ 1;
2450 if (caret
>= descr
->nb_items
) caret
= descr
->nb_items
- 1;
2454 if (descr
->style
& LBS_MULTICOLUMN
)
2456 INT page
= descr
->width
/ descr
->column_width
;
2457 if (page
< 1) page
= 1;
2458 caret
= descr
->focus_item
- (page
* descr
->page_size
) + 1;
2460 else caret
= descr
->focus_item
-LISTBOX_GetCurrentPageSize(descr
) + 1;
2461 if (caret
< 0) caret
= 0;
2464 if (descr
->style
& LBS_MULTICOLUMN
)
2466 INT page
= descr
->width
/ descr
->column_width
;
2467 if (page
< 1) page
= 1;
2468 caret
= descr
->focus_item
+ (page
* descr
->page_size
) - 1;
2470 else caret
= descr
->focus_item
+ LISTBOX_GetCurrentPageSize(descr
) - 1;
2471 if (caret
>= descr
->nb_items
) caret
= descr
->nb_items
- 1;
2477 caret
= descr
->nb_items
- 1;
2480 if (descr
->style
& LBS_EXTENDEDSEL
) caret
= descr
->focus_item
;
2481 else if (descr
->style
& LBS_MULTIPLESEL
)
2483 LISTBOX_SetSelection( descr
, descr
->focus_item
,
2484 !is_item_selected(descr
, descr
->focus_item
),
2485 (descr
->style
& LBS_NOTIFY
) != 0 );
2489 bForceSelection
= FALSE
;
2491 if (bForceSelection
) /* focused item is used instead of key */
2492 caret
= descr
->focus_item
;
2495 if (((descr
->style
& LBS_EXTENDEDSEL
) &&
2496 !(GetKeyState( VK_SHIFT
) & 0x8000)) ||
2497 !IS_MULTISELECT(descr
))
2498 descr
->anchor_item
= caret
;
2499 LISTBOX_MoveCaret( descr
, caret
, TRUE
);
2501 if (descr
->style
& LBS_MULTIPLESEL
)
2502 descr
->selected_item
= caret
;
2504 LISTBOX_SetSelection( descr
, caret
, TRUE
, FALSE
);
2505 if (descr
->style
& LBS_NOTIFY
)
2507 if (descr
->lphc
&& IsWindowVisible( descr
->self
))
2509 /* make sure that combo parent doesn't hide us */
2510 descr
->lphc
->wState
|= CBF_NOROLLUP
;
2512 if (descr
->nb_items
) SEND_NOTIFICATION( descr
, LBN_SELCHANGE
);
2519 /***********************************************************************
2520 * LISTBOX_HandleChar
2522 static LRESULT
LISTBOX_HandleChar( LB_DESCR
*descr
, WCHAR charW
)
2530 if (descr
->style
& LBS_WANTKEYBOARDINPUT
)
2532 caret
= SendMessageW( descr
->owner
, WM_CHARTOITEM
,
2533 MAKEWPARAM(charW
, descr
->focus_item
),
2534 (LPARAM
)descr
->self
);
2535 if (caret
== -2) return 0;
2538 caret
= LISTBOX_FindString( descr
, descr
->focus_item
, str
, FALSE
);
2541 if ((!IS_MULTISELECT(descr
)) && descr
->selected_item
== -1)
2542 LISTBOX_SetSelection( descr
, caret
, TRUE
, FALSE
);
2543 LISTBOX_MoveCaret( descr
, caret
, TRUE
);
2544 if ((descr
->style
& LBS_NOTIFY
) && descr
->nb_items
)
2545 SEND_NOTIFICATION( descr
, LBN_SELCHANGE
);
2551 /***********************************************************************
2554 static BOOL
LISTBOX_Create( HWND hwnd
, LPHEADCOMBO lphc
)
2557 MEASUREITEMSTRUCT mis
;
2560 if (!(descr
= HeapAlloc( GetProcessHeap(), 0, sizeof(*descr
) )))
2563 GetClientRect( hwnd
, &rect
);
2565 descr
->owner
= GetParent( descr
->self
);
2566 descr
->style
= GetWindowLongW( descr
->self
, GWL_STYLE
);
2567 descr
->width
= rect
.right
- rect
.left
;
2568 descr
->height
= rect
.bottom
- rect
.top
;
2569 descr
->u
.items
= NULL
;
2570 descr
->items_size
= 0;
2571 descr
->nb_items
= 0;
2572 descr
->top_item
= 0;
2573 descr
->selected_item
= -1;
2574 descr
->focus_item
= 0;
2575 descr
->anchor_item
= -1;
2576 descr
->item_height
= 1;
2577 descr
->page_size
= 1;
2578 descr
->column_width
= 150;
2579 descr
->horz_extent
= 0;
2580 descr
->horz_pos
= 0;
2583 descr
->wheel_remain
= 0;
2584 descr
->caret_on
= !lphc
;
2585 if (descr
->style
& LBS_NOSEL
) descr
->caret_on
= FALSE
;
2586 descr
->in_focus
= FALSE
;
2587 descr
->captured
= FALSE
;
2589 descr
->locale
= GetUserDefaultLCID();
2594 TRACE("[%p]: resetting owner %p -> %p\n", descr
->self
, descr
->owner
, lphc
->self
);
2595 descr
->owner
= lphc
->self
;
2598 SetWindowLongPtrW( descr
->self
, 0, (LONG_PTR
)descr
);
2600 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2602 if (descr
->style
& LBS_EXTENDEDSEL
) descr
->style
|= LBS_MULTIPLESEL
;
2603 if (descr
->style
& LBS_MULTICOLUMN
) descr
->style
&= ~LBS_OWNERDRAWVARIABLE
;
2604 if (descr
->style
& LBS_OWNERDRAWVARIABLE
) descr
->style
|= LBS_NOINTEGRALHEIGHT
;
2605 if ((descr
->style
& (LBS_OWNERDRAWFIXED
| LBS_HASSTRINGS
| LBS_SORT
)) != LBS_OWNERDRAWFIXED
)
2606 descr
->style
&= ~LBS_NODATA
;
2607 descr
->item_height
= LISTBOX_SetFont( descr
, 0 );
2609 if (descr
->style
& LBS_OWNERDRAWFIXED
)
2611 descr
->style
&= ~LBS_OWNERDRAWVARIABLE
;
2613 if( descr
->lphc
&& (descr
->lphc
->dwStyle
& CBS_DROPDOWN
))
2615 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2616 descr
->item_height
= lphc
->fixedOwnerDrawHeight
;
2620 UINT id
= (UINT
)GetWindowLongPtrW( descr
->self
, GWLP_ID
);
2621 mis
.CtlType
= ODT_LISTBOX
;
2626 mis
.itemHeight
= descr
->item_height
;
2627 SendMessageW( descr
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&mis
);
2628 descr
->item_height
= mis
.itemHeight
? mis
.itemHeight
: 1;
2632 OpenThemeData( descr
->self
, WC_LISTBOXW
);
2634 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr
->owner
, descr
->style
, descr
->width
, descr
->height
);
2639 /***********************************************************************
2642 static BOOL
LISTBOX_Destroy( LB_DESCR
*descr
)
2644 HTHEME theme
= GetWindowTheme( descr
->self
);
2645 CloseThemeData( theme
);
2646 LISTBOX_ResetContent( descr
);
2647 SetWindowLongPtrW( descr
->self
, 0, 0 );
2648 HeapFree( GetProcessHeap(), 0, descr
);
2653 /***********************************************************************
2654 * ListBoxWndProc_common
2656 static LRESULT CALLBACK
LISTBOX_WindowProc( HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
2658 LB_DESCR
*descr
= (LB_DESCR
*)GetWindowLongPtrW( hwnd
, 0 );
2659 HEADCOMBO
*lphc
= NULL
;
2665 if (!IsWindow(hwnd
)) return 0;
2667 if (msg
== WM_CREATE
)
2669 CREATESTRUCTW
*lpcs
= (CREATESTRUCTW
*)lParam
;
2670 if (lpcs
->style
& LBS_COMBOBOX
) lphc
= lpcs
->lpCreateParams
;
2671 if (!LISTBOX_Create( hwnd
, lphc
)) return -1;
2672 TRACE("creating hwnd %p descr %p\n", hwnd
, (void *)GetWindowLongPtrW( hwnd
, 0 ) );
2675 /* Ignore all other messages before we get a WM_CREATE */
2676 return DefWindowProcW( hwnd
, msg
, wParam
, lParam
);
2678 if (descr
->style
& LBS_COMBOBOX
) lphc
= descr
->lphc
;
2680 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr
->self
, msg
, wParam
, lParam
);
2684 case LB_RESETCONTENT
:
2685 LISTBOX_ResetContent( descr
);
2686 LISTBOX_UpdateScroll( descr
);
2687 InvalidateRect( descr
->self
, NULL
, TRUE
);
2692 const WCHAR
*textW
= (const WCHAR
*)lParam
;
2693 INT index
= LISTBOX_FindStringPos( descr
, textW
, FALSE
);
2694 return LISTBOX_InsertString( descr
, index
, textW
);
2697 case LB_INSERTSTRING
:
2698 return LISTBOX_InsertString( descr
, wParam
, (const WCHAR
*)lParam
);
2702 const WCHAR
*textW
= (const WCHAR
*)lParam
;
2703 INT index
= LISTBOX_FindFileStrPos( descr
, textW
);
2704 return LISTBOX_InsertString( descr
, index
, textW
);
2707 case LB_DELETESTRING
:
2708 if (LISTBOX_RemoveItem( descr
, wParam
) != LB_ERR
)
2709 return descr
->nb_items
;
2712 SetLastError(ERROR_INVALID_INDEX
);
2716 case LB_GETITEMDATA
:
2717 if (((INT
)wParam
< 0) || ((INT
)wParam
>= descr
->nb_items
))
2719 SetLastError(ERROR_INVALID_INDEX
);
2722 return get_item_data(descr
, wParam
);
2724 case LB_SETITEMDATA
:
2725 if (((INT
)wParam
< 0) || ((INT
)wParam
>= descr
->nb_items
))
2727 SetLastError(ERROR_INVALID_INDEX
);
2730 set_item_data(descr
, wParam
, lParam
);
2731 /* undocumented: returns TRUE, not LB_OKAY (0) */
2735 return descr
->nb_items
;
2738 return LISTBOX_GetText( descr
, wParam
, (LPWSTR
)lParam
, TRUE
);
2741 if ((INT
)wParam
>= descr
->nb_items
|| (INT
)wParam
< 0)
2743 SetLastError(ERROR_INVALID_INDEX
);
2746 if (!HAS_STRINGS(descr
)) return sizeof(ULONG_PTR
);
2747 return lstrlenW(get_item_string(descr
, wParam
));
2750 if (descr
->nb_items
== 0)
2752 if (!IS_MULTISELECT(descr
))
2753 return descr
->selected_item
;
2754 if (descr
->selected_item
!= -1)
2755 return descr
->selected_item
;
2756 return descr
->focus_item
;
2757 /* otherwise, if the user tries to move the selection with the */
2758 /* arrow keys, we will give the application something to choke on */
2759 case LB_GETTOPINDEX
:
2760 return descr
->top_item
;
2762 case LB_GETITEMHEIGHT
:
2763 return LISTBOX_GetItemHeight( descr
, wParam
);
2765 case LB_SETITEMHEIGHT
:
2766 return LISTBOX_SetItemHeight( descr
, wParam
, lParam
, TRUE
);
2768 case LB_ITEMFROMPOINT
:
2775 /* The hiword of the return value is not a client area
2776 hittest as suggested by MSDN, but rather a hittest on
2777 the returned listbox item. */
2779 if(descr
->nb_items
== 0)
2780 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2782 pt
.x
= (short)LOWORD(lParam
);
2783 pt
.y
= (short)HIWORD(lParam
);
2785 SetRect(&rect
, 0, 0, descr
->width
, descr
->height
);
2787 if(!PtInRect(&rect
, pt
))
2789 pt
.x
= min(pt
.x
, rect
.right
- 1);
2790 pt
.x
= max(pt
.x
, 0);
2791 pt
.y
= min(pt
.y
, rect
.bottom
- 1);
2792 pt
.y
= max(pt
.y
, 0);
2796 index
= LISTBOX_GetItemFromPoint(descr
, pt
.x
, pt
.y
);
2800 index
= descr
->nb_items
- 1;
2803 return MAKELONG(index
, hit
? 0 : 1);
2806 case LB_SETCARETINDEX
:
2807 if ((!IS_MULTISELECT(descr
)) && (descr
->selected_item
!= -1)) return LB_ERR
;
2808 if (LISTBOX_SetCaretIndex( descr
, wParam
, !lParam
) == LB_ERR
)
2815 case LB_GETCARETINDEX
:
2816 return descr
->focus_item
;
2818 case LB_SETTOPINDEX
:
2819 return LISTBOX_SetTopItem( descr
, wParam
, TRUE
);
2821 case LB_SETCOLUMNWIDTH
:
2822 return LISTBOX_SetColumnWidth( descr
, wParam
);
2824 case LB_GETITEMRECT
:
2825 return LISTBOX_GetItemRect( descr
, wParam
, (RECT
*)lParam
);
2828 return LISTBOX_FindString( descr
, wParam
, (const WCHAR
*)lParam
, FALSE
);
2830 case LB_FINDSTRINGEXACT
:
2831 return LISTBOX_FindString( descr
, wParam
, (const WCHAR
*)lParam
, TRUE
);
2833 case LB_SELECTSTRING
:
2835 const WCHAR
*textW
= (const WCHAR
*)lParam
;
2838 if (HAS_STRINGS(descr
))
2839 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW
));
2841 index
= LISTBOX_FindString( descr
, wParam
, textW
, FALSE
);
2842 if (index
!= LB_ERR
)
2844 LISTBOX_MoveCaret( descr
, index
, TRUE
);
2845 LISTBOX_SetSelection( descr
, index
, TRUE
, FALSE
);
2851 if (((INT
)wParam
< 0) || ((INT
)wParam
>= descr
->nb_items
))
2853 return is_item_selected(descr
, wParam
);
2856 ret
= LISTBOX_SetSelection( descr
, lParam
, wParam
, FALSE
);
2857 if (ret
!= LB_ERR
&& wParam
)
2859 descr
->anchor_item
= lParam
;
2861 LISTBOX_SetCaretIndex( descr
, lParam
, TRUE
);
2866 if (IS_MULTISELECT(descr
)) return LB_ERR
;
2867 LISTBOX_SetCaretIndex( descr
, wParam
, TRUE
);
2868 ret
= LISTBOX_SetSelection( descr
, wParam
, TRUE
, FALSE
);
2869 if (ret
!= LB_ERR
) ret
= descr
->selected_item
;
2872 case LB_GETSELCOUNT
:
2873 return LISTBOX_GetSelCount( descr
);
2875 case LB_GETSELITEMS
:
2876 return LISTBOX_GetSelItems( descr
, wParam
, (LPINT
)lParam
);
2878 case LB_SELITEMRANGE
:
2879 if (LOWORD(lParam
) <= HIWORD(lParam
))
2880 return LISTBOX_SelectItemRange( descr
, LOWORD(lParam
),
2881 HIWORD(lParam
), wParam
);
2883 return LISTBOX_SelectItemRange( descr
, HIWORD(lParam
),
2884 LOWORD(lParam
), wParam
);
2886 case LB_SELITEMRANGEEX
:
2887 if ((INT
)lParam
>= (INT
)wParam
)
2888 return LISTBOX_SelectItemRange( descr
, wParam
, lParam
, TRUE
);
2890 return LISTBOX_SelectItemRange( descr
, lParam
, wParam
, FALSE
);
2892 case LB_GETHORIZONTALEXTENT
:
2893 return descr
->horz_extent
;
2895 case LB_SETHORIZONTALEXTENT
:
2896 return LISTBOX_SetHorizontalExtent( descr
, wParam
);
2898 case LB_GETANCHORINDEX
:
2899 return descr
->anchor_item
;
2901 case LB_SETANCHORINDEX
:
2902 if (((INT
)wParam
< -1) || ((INT
)wParam
>= descr
->nb_items
))
2904 SetLastError(ERROR_INVALID_INDEX
);
2907 descr
->anchor_item
= (INT
)wParam
;
2911 return LISTBOX_Directory( descr
, wParam
, (const WCHAR
*)lParam
, msg
== LB_DIR
);
2914 return descr
->locale
;
2919 if (!IsValidLocale((LCID
)wParam
, LCID_INSTALLED
))
2921 ret
= descr
->locale
;
2922 descr
->locale
= (LCID
)wParam
;
2926 case LB_INITSTORAGE
:
2927 return LISTBOX_InitStorage( descr
, wParam
);
2930 return LISTBOX_SetCount( descr
, (INT
)wParam
);
2932 case LB_SETTABSTOPS
:
2933 return LISTBOX_SetTabStops( descr
, wParam
, (LPINT
)lParam
);
2936 if (descr
->caret_on
)
2938 descr
->caret_on
= TRUE
;
2939 if ((descr
->focus_item
!= -1) && (descr
->in_focus
))
2940 LISTBOX_RepaintItem( descr
, descr
->focus_item
, ODA_FOCUS
);
2944 if (!descr
->caret_on
)
2946 descr
->caret_on
= FALSE
;
2947 if ((descr
->focus_item
!= -1) && (descr
->in_focus
))
2948 LISTBOX_RepaintItem( descr
, descr
->focus_item
, ODA_FOCUS
);
2951 case LB_GETLISTBOXINFO
:
2952 return descr
->page_size
;
2955 return LISTBOX_Destroy( descr
);
2958 InvalidateRect( descr
->self
, NULL
, TRUE
);
2962 LISTBOX_SetRedraw( descr
, wParam
!= 0 );
2966 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
2968 case WM_PRINTCLIENT
:
2972 HDC hdc
= ( wParam
) ? ((HDC
)wParam
) : BeginPaint( descr
->self
, &ps
);
2973 ret
= LISTBOX_Paint( descr
, hdc
);
2974 if( !wParam
) EndPaint( descr
->self
, &ps
);
2979 LISTBOX_NCPaint( descr
, (HRGN
)wParam
);
2983 LISTBOX_UpdateSize( descr
);
2986 return (LRESULT
)descr
->font
;
2988 LISTBOX_SetFont( descr
, (HFONT
)wParam
);
2989 if (lParam
) InvalidateRect( descr
->self
, 0, TRUE
);
2992 descr
->in_focus
= TRUE
;
2993 descr
->caret_on
= TRUE
;
2994 if (descr
->focus_item
!= -1)
2995 LISTBOX_DrawFocusRect( descr
, TRUE
);
2996 SEND_NOTIFICATION( descr
, LBN_SETFOCUS
);
2999 LISTBOX_HandleLButtonUp( descr
); /* Release capture if we have it */
3000 descr
->in_focus
= FALSE
;
3001 descr
->wheel_remain
= 0;
3002 if ((descr
->focus_item
!= -1) && descr
->caret_on
)
3003 LISTBOX_RepaintItem( descr
, descr
->focus_item
, ODA_FOCUS
);
3004 SEND_NOTIFICATION( descr
, LBN_KILLFOCUS
);
3007 return LISTBOX_HandleHScroll( descr
, LOWORD(wParam
), HIWORD(wParam
) );
3009 return LISTBOX_HandleVScroll( descr
, LOWORD(wParam
), HIWORD(wParam
) );
3011 if (wParam
& (MK_SHIFT
| MK_CONTROL
))
3012 return DefWindowProcW( descr
->self
, msg
, wParam
, lParam
);
3013 return LISTBOX_HandleMouseWheel( descr
, (SHORT
)HIWORD(wParam
) );
3014 case WM_LBUTTONDOWN
:
3016 return LISTBOX_HandleLButtonDownCombo(descr
, msg
, wParam
,
3017 (INT16
)LOWORD(lParam
),
3018 (INT16
)HIWORD(lParam
) );
3019 return LISTBOX_HandleLButtonDown( descr
, wParam
,
3020 (INT16
)LOWORD(lParam
),
3021 (INT16
)HIWORD(lParam
) );
3022 case WM_LBUTTONDBLCLK
:
3024 return LISTBOX_HandleLButtonDownCombo(descr
, msg
, wParam
,
3025 (INT16
)LOWORD(lParam
),
3026 (INT16
)HIWORD(lParam
) );
3027 if (descr
->style
& LBS_NOTIFY
)
3028 SEND_NOTIFICATION( descr
, LBN_DBLCLK
);
3031 if ( lphc
&& ((lphc
->dwStyle
& CBS_DROPDOWNLIST
) != CBS_SIMPLE
) )
3033 BOOL captured
= descr
->captured
;
3037 mousePos
.x
= (INT16
)LOWORD(lParam
);
3038 mousePos
.y
= (INT16
)HIWORD(lParam
);
3041 * If we are in a dropdown combobox, we simulate that
3042 * the mouse is captured to show the tracking of the item.
3044 if (GetClientRect(descr
->self
, &clientRect
) && PtInRect( &clientRect
, mousePos
))
3045 descr
->captured
= TRUE
;
3047 LISTBOX_HandleMouseMove( descr
, mousePos
.x
, mousePos
.y
);
3049 descr
->captured
= captured
;
3051 else if (GetCapture() == descr
->self
)
3053 LISTBOX_HandleMouseMove( descr
, (INT16
)LOWORD(lParam
),
3054 (INT16
)HIWORD(lParam
) );
3064 * If the mouse button "up" is not in the listbox,
3065 * we make sure there is no selection by re-selecting the
3066 * item that was selected when the listbox was made visible.
3068 mousePos
.x
= (INT16
)LOWORD(lParam
);
3069 mousePos
.y
= (INT16
)HIWORD(lParam
);
3071 GetClientRect(descr
->self
, &clientRect
);
3074 * When the user clicks outside the combobox and the focus
3075 * is lost, the owning combobox will send a fake buttonup with
3076 * 0xFFFFFFF as the mouse location, we must also revert the
3077 * selection to the original selection.
3079 if ( (lParam
== (LPARAM
)-1) || (!PtInRect( &clientRect
, mousePos
)) )
3080 LISTBOX_MoveCaret( descr
, lphc
->droppedIndex
, FALSE
);
3082 return LISTBOX_HandleLButtonUp( descr
);
3084 if( lphc
&& (lphc
->dwStyle
& CBS_DROPDOWNLIST
) != CBS_SIMPLE
)
3086 /* for some reason Windows makes it possible to
3087 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3089 if( (!(lphc
->wState
& CBF_EUI
) && wParam
== VK_F4
) ||
3090 ( (lphc
->wState
& CBF_EUI
) && !(lphc
->wState
& CBF_DROPPED
)
3091 && (wParam
== VK_DOWN
|| wParam
== VK_UP
)) )
3093 COMBO_FlipListbox( lphc
, FALSE
, FALSE
);
3097 return LISTBOX_HandleKeyDown( descr
, wParam
);
3099 return LISTBOX_HandleChar( descr
, wParam
);
3102 return LISTBOX_HandleSystemTimer( descr
);
3104 if ((IS_OWNERDRAW(descr
)) && !(descr
->style
& LBS_DISPLAYCHANGED
))
3107 HBRUSH hbrush
= (HBRUSH
)SendMessageW( descr
->owner
, WM_CTLCOLORLISTBOX
,
3108 wParam
, (LPARAM
)descr
->self
);
3109 TRACE("hbrush = %p\n", hbrush
);
3111 hbrush
= GetSysColorBrush(COLOR_WINDOW
);
3114 GetClientRect(descr
->self
, &rect
);
3115 FillRect((HDC
)wParam
, &rect
, hbrush
);
3120 if( lphc
) return 0;
3121 return SendMessageW( descr
->owner
, msg
, wParam
, lParam
);
3124 if( lphc
&& (lphc
->dwStyle
& CBS_DROPDOWNLIST
) != CBS_SIMPLE
)
3132 case WM_THEMECHANGED
:
3133 theme
= GetWindowTheme( hwnd
);
3134 CloseThemeData( theme
);
3135 OpenThemeData( hwnd
, WC_LISTBOXW
);
3139 if ((msg
>= WM_USER
) && (msg
< 0xc000))
3140 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3141 hwnd
, msg
, wParam
, lParam
);
3144 return DefWindowProcW( hwnd
, msg
, wParam
, lParam
);
3147 void LISTBOX_Register(void)
3151 memset(&wndClass
, 0, sizeof(wndClass
));
3152 wndClass
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
;
3153 wndClass
.lpfnWndProc
= LISTBOX_WindowProc
;
3154 wndClass
.cbClsExtra
= 0;
3155 wndClass
.cbWndExtra
= sizeof(LB_DESCR
*);
3156 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3157 wndClass
.hbrBackground
= NULL
;
3158 wndClass
.lpszClassName
= WC_LISTBOXW
;
3159 RegisterClassW(&wndClass
);
3162 void COMBOLBOX_Register(void)
3166 memset(&wndClass
, 0, sizeof(wndClass
));
3167 wndClass
.style
= CS_SAVEBITS
| CS_DBLCLKS
| CS_DROPSHADOW
| CS_GLOBALCLASS
;
3168 wndClass
.lpfnWndProc
= LISTBOX_WindowProc
;
3169 wndClass
.cbClsExtra
= 0;
3170 wndClass
.cbWndExtra
= sizeof(LB_DESCR
*);
3171 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
3172 wndClass
.hbrBackground
= NULL
;
3173 wndClass
.lpszClassName
= L
"ComboLBox";
3174 RegisterClassW(&wndClass
);