ninput: Implement SetPropertyInteractionContext().
[wine.git] / dlls / comctl32 / listbox.c
blob94712fd1c804303d8925589290f49b7f1de26cc6
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(listbox2);
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 INT top_item; /* Top visible item */
76 INT selected_item; /* Selected item */
77 INT focus_item; /* Item that has the focus */
78 INT anchor_item; /* Anchor item for extended selection */
79 INT item_height; /* Default item height */
80 INT page_size; /* Items per listbox page */
81 INT column_width; /* Column width for multi-column listboxes */
82 INT horz_extent; /* Horizontal extent */
83 INT horz_pos; /* Horizontal position */
84 INT nb_tabs; /* Number of tabs in array */
85 INT *tabs; /* Array of tabs */
86 INT avg_char_width; /* Average width of characters */
87 INT wheel_remain; /* Left over scroll amount */
88 BOOL caret_on; /* Is caret on? */
89 BOOL captured; /* Is mouse captured? */
90 BOOL in_focus;
91 HFONT font; /* Current font */
92 LCID locale; /* Current locale for string comparisons */
93 HEADCOMBO *lphc; /* ComboLBox */
94 } LB_DESCR;
97 #define IS_OWNERDRAW(descr) \
98 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
100 #define HAS_STRINGS(descr) \
101 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
104 #define IS_MULTISELECT(descr) \
105 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
106 !((descr)->style & LBS_NOSEL))
108 #define SEND_NOTIFICATION(descr,code) \
109 (SendMessageW( (descr)->owner, WM_COMMAND, \
110 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
112 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
114 /* Current timer status */
115 typedef enum
117 LB_TIMER_NONE,
118 LB_TIMER_UP,
119 LB_TIMER_LEFT,
120 LB_TIMER_DOWN,
121 LB_TIMER_RIGHT
122 } TIMER_DIRECTION;
124 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
126 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
128 /***********************************************************************
129 * LISTBOX_GetCurrentPageSize
131 * Return the current page size
133 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
135 INT i, height;
136 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
137 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
139 if ((height += descr->items[i].height) > descr->height) break;
141 if (i == descr->top_item) return 1;
142 else return i - descr->top_item;
146 /***********************************************************************
147 * LISTBOX_GetMaxTopIndex
149 * Return the maximum possible index for the top of the listbox.
151 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
153 INT max, page;
155 if (descr->style & LBS_OWNERDRAWVARIABLE)
157 page = descr->height;
158 for (max = descr->nb_items - 1; max >= 0; max--)
159 if ((page -= descr->items[max].height) < 0) break;
160 if (max < descr->nb_items - 1) max++;
162 else if (descr->style & LBS_MULTICOLUMN)
164 if ((page = descr->width / descr->column_width) < 1) page = 1;
165 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
166 max = (max - page) * descr->page_size;
168 else
170 max = descr->nb_items - descr->page_size;
172 if (max < 0) max = 0;
173 return max;
177 /***********************************************************************
178 * LISTBOX_UpdateScroll
180 * Update the scrollbars. Should be called whenever the content
181 * of the listbox changes.
183 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
185 SCROLLINFO info;
187 /* Check the listbox scroll bar flags individually before we call
188 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
189 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
190 scroll bar when we do not need one.
191 if (!(descr->style & WS_VSCROLL)) return;
194 /* It is important that we check descr->style, and not wnd->dwStyle,
195 for WS_VSCROLL, as the former is exactly the one passed in
196 argument to CreateWindow.
197 In Windows (and from now on in Wine :) a listbox created
198 with such a style (no WS_SCROLL) does not update
199 the scrollbar with listbox-related data, thus letting
200 the programmer use it for his/her own purposes. */
202 if (descr->style & LBS_NOREDRAW) return;
203 info.cbSize = sizeof(info);
205 if (descr->style & LBS_MULTICOLUMN)
207 info.nMin = 0;
208 info.nMax = (descr->nb_items - 1) / descr->page_size;
209 info.nPos = descr->top_item / descr->page_size;
210 info.nPage = descr->width / descr->column_width;
211 if (info.nPage < 1) info.nPage = 1;
212 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
213 if (descr->style & LBS_DISABLENOSCROLL)
214 info.fMask |= SIF_DISABLENOSCROLL;
215 if (descr->style & WS_HSCROLL)
216 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
217 info.nMax = 0;
218 info.fMask = SIF_RANGE;
219 if (descr->style & WS_VSCROLL)
220 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
222 else
224 info.nMin = 0;
225 info.nMax = descr->nb_items - 1;
226 info.nPos = descr->top_item;
227 info.nPage = LISTBOX_GetCurrentPageSize( descr );
228 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
229 if (descr->style & LBS_DISABLENOSCROLL)
230 info.fMask |= SIF_DISABLENOSCROLL;
231 if (descr->style & WS_VSCROLL)
232 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
234 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
236 info.nPos = descr->horz_pos;
237 info.nPage = descr->width;
238 info.fMask = SIF_POS | SIF_PAGE;
239 if (descr->style & LBS_DISABLENOSCROLL)
240 info.fMask |= SIF_DISABLENOSCROLL;
241 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
243 else
245 if (descr->style & LBS_DISABLENOSCROLL)
247 info.nMin = 0;
248 info.nMax = 0;
249 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
250 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
252 else
254 ShowScrollBar( descr->self, SB_HORZ, FALSE );
261 /***********************************************************************
262 * LISTBOX_SetTopItem
264 * Set the top item of the listbox, scrolling up or down if necessary.
266 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
268 INT max = LISTBOX_GetMaxTopIndex( descr );
270 TRACE("setting top item %d, scroll %d\n", index, scroll);
272 if (index > max) index = max;
273 if (index < 0) index = 0;
274 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
275 if (descr->top_item == index) return LB_OKAY;
276 if (scroll)
278 INT diff;
279 if (descr->style & LBS_MULTICOLUMN)
280 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
281 else if (descr->style & LBS_OWNERDRAWVARIABLE)
283 INT i;
284 diff = 0;
285 if (index > descr->top_item)
287 for (i = index - 1; i >= descr->top_item; i--)
288 diff -= descr->items[i].height;
290 else
292 for (i = index; i < descr->top_item; i++)
293 diff += descr->items[i].height;
296 else
297 diff = (descr->top_item - index) * descr->item_height;
299 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
300 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
302 else
303 InvalidateRect( descr->self, NULL, TRUE );
304 descr->top_item = index;
305 LISTBOX_UpdateScroll( descr );
306 return LB_OKAY;
310 /***********************************************************************
311 * LISTBOX_UpdatePage
313 * Update the page size. Should be called when the size of
314 * the client area or the item height changes.
316 static void LISTBOX_UpdatePage( LB_DESCR *descr )
318 INT page_size;
320 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
321 page_size = 1;
322 if (page_size == descr->page_size) return;
323 descr->page_size = page_size;
324 if (descr->style & LBS_MULTICOLUMN)
325 InvalidateRect( descr->self, NULL, TRUE );
326 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
330 /***********************************************************************
331 * LISTBOX_UpdateSize
333 * Update the size of the listbox. Should be called when the size of
334 * the client area changes.
336 static void LISTBOX_UpdateSize( LB_DESCR *descr )
338 RECT rect;
340 GetClientRect( descr->self, &rect );
341 descr->width = rect.right - rect.left;
342 descr->height = rect.bottom - rect.top;
343 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
345 INT remaining;
346 RECT rect;
348 GetWindowRect( descr->self, &rect );
349 if(descr->item_height != 0)
350 remaining = descr->height % descr->item_height;
351 else
352 remaining = 0;
353 if ((descr->height > descr->item_height) && remaining)
355 TRACE("[%p]: changing height %d -> %d\n",
356 descr->self, descr->height, descr->height - remaining );
357 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
358 rect.bottom - rect.top - remaining,
359 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
360 return;
363 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
364 LISTBOX_UpdatePage( descr );
365 LISTBOX_UpdateScroll( descr );
367 /* Invalidate the focused item so it will be repainted correctly */
368 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
370 InvalidateRect( descr->self, &rect, FALSE );
375 /***********************************************************************
376 * LISTBOX_GetItemRect
378 * Get the rectangle enclosing an item, in listbox client coordinates.
379 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
381 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
383 /* Index <= 0 is legal even on empty listboxes */
384 if (index && (index >= descr->nb_items))
386 SetRectEmpty(rect);
387 SetLastError(ERROR_INVALID_INDEX);
388 return LB_ERR;
390 SetRect( rect, 0, 0, descr->width, descr->height );
391 if (descr->style & LBS_MULTICOLUMN)
393 INT col = (index / descr->page_size) -
394 (descr->top_item / descr->page_size);
395 rect->left += col * descr->column_width;
396 rect->right = rect->left + descr->column_width;
397 rect->top += (index % descr->page_size) * descr->item_height;
398 rect->bottom = rect->top + descr->item_height;
400 else if (descr->style & LBS_OWNERDRAWVARIABLE)
402 INT i;
403 rect->right += descr->horz_pos;
404 if ((index >= 0) && (index < descr->nb_items))
406 if (index < descr->top_item)
408 for (i = descr->top_item-1; i >= index; i--)
409 rect->top -= descr->items[i].height;
411 else
413 for (i = descr->top_item; i < index; i++)
414 rect->top += descr->items[i].height;
416 rect->bottom = rect->top + descr->items[index].height;
420 else
422 rect->top += (index - descr->top_item) * descr->item_height;
423 rect->bottom = rect->top + descr->item_height;
424 rect->right += descr->horz_pos;
427 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
429 return ((rect->left < descr->width) && (rect->right > 0) &&
430 (rect->top < descr->height) && (rect->bottom > 0));
434 /***********************************************************************
435 * LISTBOX_GetItemFromPoint
437 * Return the item nearest from point (x,y) (in client coordinates).
439 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
441 INT index = descr->top_item;
443 if (!descr->nb_items) return -1; /* No items */
444 if (descr->style & LBS_OWNERDRAWVARIABLE)
446 INT pos = 0;
447 if (y >= 0)
449 while (index < descr->nb_items)
451 if ((pos += descr->items[index].height) > y) break;
452 index++;
455 else
457 while (index > 0)
459 index--;
460 if ((pos -= descr->items[index].height) <= y) break;
464 else if (descr->style & LBS_MULTICOLUMN)
466 if (y >= descr->item_height * descr->page_size) return -1;
467 if (y >= 0) index += y / descr->item_height;
468 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
469 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
471 else
473 index += (y / descr->item_height);
475 if (index < 0) return 0;
476 if (index >= descr->nb_items) return -1;
477 return index;
481 /***********************************************************************
482 * LISTBOX_PaintItem
484 * Paint an item.
486 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
487 INT index, UINT action, BOOL ignoreFocus )
489 LB_ITEMDATA *item = NULL;
490 if (index < descr->nb_items) item = &descr->items[index];
492 if (IS_OWNERDRAW(descr))
494 DRAWITEMSTRUCT dis;
495 RECT r;
496 HRGN hrgn;
498 if (!item)
500 if (action == ODA_FOCUS)
501 DrawFocusRect( hdc, rect );
502 else
503 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
504 return;
507 /* some programs mess with the clipping region when
508 drawing the item, *and* restore the previous region
509 after they are done, so a region has better to exist
510 else everything ends clipped */
511 GetClientRect(descr->self, &r);
512 hrgn = set_control_clipping( hdc, &r );
514 dis.CtlType = ODT_LISTBOX;
515 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
516 dis.hwndItem = descr->self;
517 dis.itemAction = action;
518 dis.hDC = hdc;
519 dis.itemID = index;
520 dis.itemState = 0;
521 if (item->selected) dis.itemState |= ODS_SELECTED;
522 if (!ignoreFocus && (descr->focus_item == index) &&
523 (descr->caret_on) &&
524 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
525 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
526 dis.itemData = item->data;
527 dis.rcItem = *rect;
528 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
529 descr->self, index, debugstr_w(item->str), action,
530 dis.itemState, wine_dbgstr_rect(rect) );
531 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
532 SelectClipRgn( hdc, hrgn );
533 if (hrgn) DeleteObject( hrgn );
535 else
537 COLORREF oldText = 0, oldBk = 0;
539 if (action == ODA_FOCUS)
541 DrawFocusRect( hdc, rect );
542 return;
544 if (item && item->selected)
546 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
547 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
550 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
551 descr->self, index, item ? debugstr_w(item->str) : "", action,
552 wine_dbgstr_rect(rect) );
553 if (!item)
554 ExtTextOutW( hdc, rect->left + 1, rect->top,
555 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
556 else if (!(descr->style & LBS_USETABSTOPS))
557 ExtTextOutW( hdc, rect->left + 1, rect->top,
558 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
559 strlenW(item->str), NULL );
560 else
562 /* Output empty string to paint background in the full width. */
563 ExtTextOutW( hdc, rect->left + 1, rect->top,
564 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
565 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
566 item->str, strlenW(item->str),
567 descr->nb_tabs, descr->tabs, 0);
569 if (item && item->selected)
571 SetBkColor( hdc, oldBk );
572 SetTextColor( hdc, oldText );
574 if (!ignoreFocus && (descr->focus_item == index) &&
575 (descr->caret_on) &&
576 (descr->in_focus)) DrawFocusRect( hdc, rect );
581 /***********************************************************************
582 * LISTBOX_SetRedraw
584 * Change the redraw flag.
586 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
588 if (on)
590 if (!(descr->style & LBS_NOREDRAW)) return;
591 descr->style &= ~LBS_NOREDRAW;
592 if (descr->style & LBS_DISPLAYCHANGED)
593 { /* page was changed while setredraw false, refresh automatically */
594 InvalidateRect(descr->self, NULL, TRUE);
595 if ((descr->top_item + descr->page_size) > descr->nb_items)
596 { /* reset top of page if less than number of items/page */
597 descr->top_item = descr->nb_items - descr->page_size;
598 if (descr->top_item < 0) descr->top_item = 0;
600 descr->style &= ~LBS_DISPLAYCHANGED;
602 LISTBOX_UpdateScroll( descr );
604 else descr->style |= LBS_NOREDRAW;
608 /***********************************************************************
609 * LISTBOX_RepaintItem
611 * Repaint a single item synchronously.
613 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
615 HDC hdc;
616 RECT rect;
617 HFONT oldFont = 0;
618 HBRUSH hbrush, oldBrush = 0;
620 /* Do not repaint the item if the item is not visible */
621 if (!IsWindowVisible(descr->self)) return;
622 if (descr->style & LBS_NOREDRAW)
624 descr->style |= LBS_DISPLAYCHANGED;
625 return;
627 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
628 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
629 if (descr->font) oldFont = SelectObject( hdc, descr->font );
630 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
631 (WPARAM)hdc, (LPARAM)descr->self );
632 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
633 if (!IsWindowEnabled(descr->self))
634 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
635 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
636 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
637 if (oldFont) SelectObject( hdc, oldFont );
638 if (oldBrush) SelectObject( hdc, oldBrush );
639 ReleaseDC( descr->self, hdc );
643 /***********************************************************************
644 * LISTBOX_DrawFocusRect
646 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
648 HDC hdc;
649 RECT rect;
650 HFONT oldFont = 0;
652 /* Do not repaint the item if the item is not visible */
653 if (!IsWindowVisible(descr->self)) return;
655 if (descr->focus_item == -1) return;
656 if (!descr->caret_on || !descr->in_focus) return;
658 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
659 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
660 if (descr->font) oldFont = SelectObject( hdc, descr->font );
661 if (!IsWindowEnabled(descr->self))
662 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
663 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
664 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
665 if (oldFont) SelectObject( hdc, oldFont );
666 ReleaseDC( descr->self, hdc );
670 /***********************************************************************
671 * LISTBOX_InitStorage
673 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
675 LB_ITEMDATA *item;
677 nb_items += LB_ARRAY_GRANULARITY - 1;
678 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
679 if (descr->items) {
680 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
681 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
682 nb_items * sizeof(LB_ITEMDATA));
684 else {
685 item = HeapAlloc( GetProcessHeap(), 0,
686 nb_items * sizeof(LB_ITEMDATA));
689 if (!item)
691 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
692 return LB_ERRSPACE;
694 descr->items = item;
695 return LB_OKAY;
699 /***********************************************************************
700 * LISTBOX_SetTabStops
702 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
704 INT i;
706 if (!(descr->style & LBS_USETABSTOPS))
708 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
709 return FALSE;
712 HeapFree( GetProcessHeap(), 0, descr->tabs );
713 if (!(descr->nb_tabs = count))
715 descr->tabs = NULL;
716 return TRUE;
718 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
719 descr->nb_tabs * sizeof(INT) )))
720 return FALSE;
721 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
723 /* convert into "dialog units"*/
724 for (i = 0; i < descr->nb_tabs; i++)
725 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
727 return TRUE;
731 /***********************************************************************
732 * LISTBOX_GetText
734 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
736 DWORD len;
738 if ((index < 0) || (index >= descr->nb_items))
740 SetLastError(ERROR_INVALID_INDEX);
741 return LB_ERR;
744 if (HAS_STRINGS(descr))
746 if (!buffer)
747 return strlenW(descr->items[index].str);
749 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
751 __TRY /* hide a Delphi bug that passes a read-only buffer */
753 strcpyW( buffer, descr->items[index].str );
754 len = strlenW(buffer);
756 __EXCEPT_PAGE_FAULT
758 WARN( "got an invalid buffer (Delphi bug?)\n" );
759 SetLastError( ERROR_INVALID_PARAMETER );
760 return LB_ERR;
762 __ENDTRY
763 } else
765 if (buffer)
766 *((DWORD *)buffer) = *(DWORD *)&descr->items[index].data;
767 len = sizeof(DWORD);
769 return len;
772 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
774 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
775 if (ret == CSTR_LESS_THAN)
776 return -1;
777 if (ret == CSTR_EQUAL)
778 return 0;
779 if (ret == CSTR_GREATER_THAN)
780 return 1;
781 return -1;
784 /***********************************************************************
785 * LISTBOX_FindStringPos
787 * Find the nearest string located before a given string in sort order.
788 * If 'exact' is TRUE, return an error if we don't get an exact match.
790 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
792 INT index, min, max, res;
794 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
795 min = 0;
796 max = descr->nb_items;
797 while (min != max)
799 index = (min + max) / 2;
800 if (HAS_STRINGS(descr))
801 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
802 else
804 COMPAREITEMSTRUCT cis;
805 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
807 cis.CtlType = ODT_LISTBOX;
808 cis.CtlID = id;
809 cis.hwndItem = descr->self;
810 /* note that some application (MetaStock) expects the second item
811 * to be in the listbox */
812 cis.itemID1 = -1;
813 cis.itemData1 = (ULONG_PTR)str;
814 cis.itemID2 = index;
815 cis.itemData2 = descr->items[index].data;
816 cis.dwLocaleId = descr->locale;
817 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
819 if (!res) return index;
820 if (res < 0) max = index;
821 else min = index + 1;
823 return exact ? -1 : max;
827 /***********************************************************************
828 * LISTBOX_FindFileStrPos
830 * Find the nearest string located before a given string in directory
831 * sort order (i.e. first files, then directories, then drives).
833 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
835 INT min, max, res;
837 if (!HAS_STRINGS(descr))
838 return LISTBOX_FindStringPos( descr, str, FALSE );
839 min = 0;
840 max = descr->nb_items;
841 while (min != max)
843 INT index = (min + max) / 2;
844 LPCWSTR p = descr->items[index].str;
845 if (*p == '[') /* drive or directory */
847 if (*str != '[') res = -1;
848 else if (p[1] == '-') /* drive */
850 if (str[1] == '-') res = str[2] - p[2];
851 else res = -1;
853 else /* directory */
855 if (str[1] == '-') res = 1;
856 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
859 else /* filename */
861 if (*str == '[') res = 1;
862 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
864 if (!res) return index;
865 if (res < 0) max = index;
866 else min = index + 1;
868 return max;
872 /***********************************************************************
873 * LISTBOX_FindString
875 * Find the item beginning with a given string.
877 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
879 INT i;
880 LB_ITEMDATA *item;
882 if (start >= descr->nb_items) start = -1;
883 item = descr->items + start + 1;
884 if (HAS_STRINGS(descr))
886 if (!str || ! str[0] ) return LB_ERR;
887 if (exact)
889 for (i = start + 1; i < descr->nb_items; i++, item++)
890 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
891 for (i = 0, item = descr->items; i <= start; i++, item++)
892 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
894 else
896 /* Special case for drives and directories: ignore prefix */
897 #define CHECK_DRIVE(item) \
898 if ((item)->str[0] == '[') \
900 if (!strncmpiW( str, (item)->str+1, len )) return i; \
901 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
902 return i; \
905 INT len = strlenW(str);
906 for (i = start + 1; i < descr->nb_items; i++, item++)
908 if (!strncmpiW( str, item->str, len )) return i;
909 CHECK_DRIVE(item);
911 for (i = 0, item = descr->items; i <= start; i++, item++)
913 if (!strncmpiW( str, item->str, len )) return i;
914 CHECK_DRIVE(item);
916 #undef CHECK_DRIVE
919 else
921 if (exact && (descr->style & LBS_SORT))
922 /* If sorted, use a WM_COMPAREITEM binary search */
923 return LISTBOX_FindStringPos( descr, str, TRUE );
925 /* Otherwise use a linear search */
926 for (i = start + 1; i < descr->nb_items; i++, item++)
927 if (item->data == (ULONG_PTR)str) return i;
928 for (i = 0, item = descr->items; i <= start; i++, item++)
929 if (item->data == (ULONG_PTR)str) return i;
931 return LB_ERR;
935 /***********************************************************************
936 * LISTBOX_GetSelCount
938 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
940 INT i, count;
941 const LB_ITEMDATA *item = descr->items;
943 if (!(descr->style & LBS_MULTIPLESEL) ||
944 (descr->style & LBS_NOSEL))
945 return LB_ERR;
946 for (i = count = 0; i < descr->nb_items; i++, item++)
947 if (item->selected) count++;
948 return count;
952 /***********************************************************************
953 * LISTBOX_GetSelItems
955 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
957 INT i, count;
958 const LB_ITEMDATA *item = descr->items;
960 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
961 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
962 if (item->selected) array[count++] = i;
963 return count;
967 /***********************************************************************
968 * LISTBOX_Paint
970 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
972 INT i, col_pos = descr->page_size - 1;
973 RECT rect;
974 RECT focusRect = {-1, -1, -1, -1};
975 HFONT oldFont = 0;
976 HBRUSH hbrush, oldBrush = 0;
978 if (descr->style & LBS_NOREDRAW) return 0;
980 SetRect( &rect, 0, 0, descr->width, descr->height );
981 if (descr->style & LBS_MULTICOLUMN)
982 rect.right = rect.left + descr->column_width;
983 else if (descr->horz_pos)
985 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
986 rect.right += descr->horz_pos;
989 if (descr->font) oldFont = SelectObject( hdc, descr->font );
990 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
991 (WPARAM)hdc, (LPARAM)descr->self );
992 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
993 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
995 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
996 (descr->in_focus))
998 /* Special case for empty listbox: paint focus rect */
999 rect.bottom = rect.top + descr->item_height;
1000 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1001 &rect, NULL, 0, NULL );
1002 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1003 rect.top = rect.bottom;
1006 /* Paint all the item, regarding the selection
1007 Focus state will be painted after */
1009 for (i = descr->top_item; i < descr->nb_items; i++)
1011 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1012 rect.bottom = rect.top + descr->item_height;
1013 else
1014 rect.bottom = rect.top + descr->items[i].height;
1016 /* keep the focus rect, to paint the focus item after */
1017 if (i == descr->focus_item)
1018 focusRect = rect;
1020 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1021 rect.top = rect.bottom;
1023 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1025 if (!IS_OWNERDRAW(descr))
1027 /* Clear the bottom of the column */
1028 if (rect.top < descr->height)
1030 rect.bottom = descr->height;
1031 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1032 &rect, NULL, 0, NULL );
1036 /* Go to the next column */
1037 rect.left += descr->column_width;
1038 rect.right += descr->column_width;
1039 rect.top = 0;
1040 col_pos = descr->page_size - 1;
1042 else
1044 col_pos--;
1045 if (rect.top >= descr->height) break;
1049 /* Paint the focus item now */
1050 if (focusRect.top != focusRect.bottom &&
1051 descr->caret_on && descr->in_focus)
1052 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1054 if (!IS_OWNERDRAW(descr))
1056 /* Clear the remainder of the client area */
1057 if (rect.top < descr->height)
1059 rect.bottom = descr->height;
1060 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1061 &rect, NULL, 0, NULL );
1063 if (rect.right < descr->width)
1065 rect.left = rect.right;
1066 rect.right = descr->width;
1067 rect.top = 0;
1068 rect.bottom = descr->height;
1069 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1070 &rect, NULL, 0, NULL );
1073 if (oldFont) SelectObject( hdc, oldFont );
1074 if (oldBrush) SelectObject( hdc, oldBrush );
1075 return 0;
1078 static void LISTBOX_NCPaint( LB_DESCR *descr, HRGN region )
1080 DWORD exstyle = GetWindowLongW( descr->self, GWL_EXSTYLE);
1081 HTHEME theme = GetWindowTheme( descr->self );
1082 HRGN cliprgn = region;
1083 int cxEdge, cyEdge;
1084 HDC hdc;
1085 RECT r;
1087 if (!theme || !(exstyle & WS_EX_CLIENTEDGE))
1088 return;
1090 cxEdge = GetSystemMetrics(SM_CXEDGE),
1091 cyEdge = GetSystemMetrics(SM_CYEDGE);
1093 GetWindowRect(descr->self, &r);
1095 /* New clipping region passed to default proc to exclude border */
1096 cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge,
1097 r.right - cxEdge, r.bottom - cyEdge);
1098 if (region != (HRGN)1)
1099 CombineRgn(cliprgn, cliprgn, region, RGN_AND);
1100 OffsetRect(&r, -r.left, -r.top);
1102 hdc = GetDCEx(descr->self, region, DCX_WINDOW|DCX_INTERSECTRGN);
1103 OffsetRect(&r, -r.left, -r.top);
1105 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
1106 DrawThemeParentBackground(descr->self, hdc, &r);
1107 DrawThemeBackground (theme, hdc, 0, 0, &r, 0);
1108 ReleaseDC(descr->self, hdc);
1111 /***********************************************************************
1112 * LISTBOX_InvalidateItems
1114 * Invalidate all items from a given item. If the specified item is not
1115 * visible, nothing happens.
1117 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1119 RECT rect;
1121 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1123 if (descr->style & LBS_NOREDRAW)
1125 descr->style |= LBS_DISPLAYCHANGED;
1126 return;
1128 rect.bottom = descr->height;
1129 InvalidateRect( descr->self, &rect, TRUE );
1130 if (descr->style & LBS_MULTICOLUMN)
1132 /* Repaint the other columns */
1133 rect.left = rect.right;
1134 rect.right = descr->width;
1135 rect.top = 0;
1136 InvalidateRect( descr->self, &rect, TRUE );
1141 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1143 RECT rect;
1145 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1146 InvalidateRect( descr->self, &rect, TRUE );
1149 /***********************************************************************
1150 * LISTBOX_GetItemHeight
1152 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1154 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1156 if ((index < 0) || (index >= descr->nb_items))
1158 SetLastError(ERROR_INVALID_INDEX);
1159 return LB_ERR;
1161 return descr->items[index].height;
1163 else return descr->item_height;
1167 /***********************************************************************
1168 * LISTBOX_SetItemHeight
1170 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1172 if (height > MAXBYTE)
1173 return -1;
1175 if (!height) height = 1;
1177 if (descr->style & LBS_OWNERDRAWVARIABLE)
1179 if ((index < 0) || (index >= descr->nb_items))
1181 SetLastError(ERROR_INVALID_INDEX);
1182 return LB_ERR;
1184 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1185 descr->items[index].height = height;
1186 LISTBOX_UpdateScroll( descr );
1187 if (repaint)
1188 LISTBOX_InvalidateItems( descr, index );
1190 else if (height != descr->item_height)
1192 TRACE("[%p]: new height = %d\n", descr->self, height );
1193 descr->item_height = height;
1194 LISTBOX_UpdatePage( descr );
1195 LISTBOX_UpdateScroll( descr );
1196 if (repaint)
1197 InvalidateRect( descr->self, 0, TRUE );
1199 return LB_OKAY;
1203 /***********************************************************************
1204 * LISTBOX_SetHorizontalPos
1206 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1208 INT diff;
1210 if (pos > descr->horz_extent - descr->width)
1211 pos = descr->horz_extent - descr->width;
1212 if (pos < 0) pos = 0;
1213 if (!(diff = descr->horz_pos - pos)) return;
1214 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1215 descr->horz_pos = pos;
1216 LISTBOX_UpdateScroll( descr );
1217 if (abs(diff) < descr->width)
1219 RECT rect;
1220 /* Invalidate the focused item so it will be repainted correctly */
1221 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1222 InvalidateRect( descr->self, &rect, TRUE );
1223 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1224 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1226 else
1227 InvalidateRect( descr->self, NULL, TRUE );
1231 /***********************************************************************
1232 * LISTBOX_SetHorizontalExtent
1234 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1236 if (descr->style & LBS_MULTICOLUMN)
1237 return LB_OKAY;
1238 if (extent == descr->horz_extent) return LB_OKAY;
1239 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1240 descr->horz_extent = extent;
1241 if (descr->style & WS_HSCROLL) {
1242 SCROLLINFO info;
1243 info.cbSize = sizeof(info);
1244 info.nMin = 0;
1245 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1246 info.fMask = SIF_RANGE;
1247 if (descr->style & LBS_DISABLENOSCROLL)
1248 info.fMask |= SIF_DISABLENOSCROLL;
1249 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1251 if (descr->horz_pos > extent - descr->width)
1252 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1253 return LB_OKAY;
1257 /***********************************************************************
1258 * LISTBOX_SetColumnWidth
1260 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1262 if (width == descr->column_width) return LB_OKAY;
1263 TRACE("[%p]: new column width = %d\n", descr->self, width );
1264 descr->column_width = width;
1265 LISTBOX_UpdatePage( descr );
1266 return LB_OKAY;
1270 /***********************************************************************
1271 * LISTBOX_SetFont
1273 * Returns the item height.
1275 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1277 HDC hdc;
1278 HFONT oldFont = 0;
1279 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1280 SIZE sz;
1282 descr->font = font;
1284 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1286 ERR("unable to get DC.\n" );
1287 return 16;
1289 if (font) oldFont = SelectObject( hdc, font );
1290 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1291 if (oldFont) SelectObject( hdc, oldFont );
1292 ReleaseDC( descr->self, hdc );
1294 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1295 if (!IS_OWNERDRAW(descr))
1296 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1297 return sz.cy;
1301 /***********************************************************************
1302 * LISTBOX_MakeItemVisible
1304 * Make sure that a given item is partially or fully visible.
1306 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1308 INT top;
1310 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1312 if (index <= descr->top_item) top = index;
1313 else if (descr->style & LBS_MULTICOLUMN)
1315 INT cols = descr->width;
1316 if (!fully) cols += descr->column_width - 1;
1317 if (cols >= descr->column_width) cols /= descr->column_width;
1318 else cols = 1;
1319 if (index < descr->top_item + (descr->page_size * cols)) return;
1320 top = index - descr->page_size * (cols - 1);
1322 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1324 INT height = fully ? descr->items[index].height : 1;
1325 for (top = index; top > descr->top_item; top--)
1326 if ((height += descr->items[top-1].height) > descr->height) break;
1328 else
1330 if (index < descr->top_item + descr->page_size) return;
1331 if (!fully && (index == descr->top_item + descr->page_size) &&
1332 (descr->height > (descr->page_size * descr->item_height))) return;
1333 top = index - descr->page_size + 1;
1335 LISTBOX_SetTopItem( descr, top, TRUE );
1338 /***********************************************************************
1339 * LISTBOX_SetCaretIndex
1341 * NOTES
1342 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1345 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1347 INT oldfocus = descr->focus_item;
1349 TRACE("old focus %d, index %d\n", oldfocus, index);
1351 if (descr->style & LBS_NOSEL) return LB_ERR;
1352 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1353 if (index == oldfocus) return LB_OKAY;
1355 LISTBOX_DrawFocusRect( descr, FALSE );
1356 descr->focus_item = index;
1358 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1359 LISTBOX_DrawFocusRect( descr, TRUE );
1361 return LB_OKAY;
1365 /***********************************************************************
1366 * LISTBOX_SelectItemRange
1368 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1370 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1371 INT last, BOOL on )
1373 INT i;
1375 /* A few sanity checks */
1377 if (descr->style & LBS_NOSEL) return LB_ERR;
1378 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1380 if (!descr->nb_items) return LB_OKAY;
1382 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1383 if (first < 0) first = 0;
1384 if (last < first) return LB_OKAY;
1386 if (on) /* Turn selection on */
1388 for (i = first; i <= last; i++)
1390 if (descr->items[i].selected) continue;
1391 descr->items[i].selected = TRUE;
1392 LISTBOX_InvalidateItemRect(descr, i);
1395 else /* Turn selection off */
1397 for (i = first; i <= last; i++)
1399 if (!descr->items[i].selected) continue;
1400 descr->items[i].selected = FALSE;
1401 LISTBOX_InvalidateItemRect(descr, i);
1404 return LB_OKAY;
1407 /***********************************************************************
1408 * LISTBOX_SetSelection
1410 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1411 BOOL on, BOOL send_notify )
1413 TRACE( "cur_sel=%d index=%d notify=%s\n",
1414 descr->selected_item, index, send_notify ? "YES" : "NO" );
1416 if (descr->style & LBS_NOSEL)
1418 descr->selected_item = index;
1419 return LB_ERR;
1421 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1422 if (descr->style & LBS_MULTIPLESEL)
1424 if (index == -1) /* Select all items */
1425 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1426 else /* Only one item */
1427 return LISTBOX_SelectItemRange( descr, index, index, on );
1429 else
1431 INT oldsel = descr->selected_item;
1432 if (index == oldsel) return LB_OKAY;
1433 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1434 if (index != -1) descr->items[index].selected = TRUE;
1435 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1436 descr->selected_item = index;
1437 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1438 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1439 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1440 else
1441 if( descr->lphc ) /* set selection change flag for parent combo */
1442 descr->lphc->wState |= CBF_SELCHANGE;
1444 return LB_OKAY;
1448 /***********************************************************************
1449 * LISTBOX_MoveCaret
1451 * Change the caret position and extend the selection to the new caret.
1453 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1455 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1457 if ((index < 0) || (index >= descr->nb_items))
1458 return;
1460 /* Important, repaint needs to be done in this order if
1461 you want to mimic Windows behavior:
1462 1. Remove the focus and paint the item
1463 2. Remove the selection and paint the item(s)
1464 3. Set the selection and repaint the item(s)
1465 4. Set the focus to 'index' and repaint the item */
1467 /* 1. remove the focus and repaint the item */
1468 LISTBOX_DrawFocusRect( descr, FALSE );
1470 /* 2. then turn off the previous selection */
1471 /* 3. repaint the new selected item */
1472 if (descr->style & LBS_EXTENDEDSEL)
1474 if (descr->anchor_item != -1)
1476 INT first = min( index, descr->anchor_item );
1477 INT last = max( index, descr->anchor_item );
1478 if (first > 0)
1479 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1480 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1481 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1484 else if (!(descr->style & LBS_MULTIPLESEL))
1486 /* Set selection to new caret item */
1487 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1490 /* 4. repaint the new item with the focus */
1491 descr->focus_item = index;
1492 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1493 LISTBOX_DrawFocusRect( descr, TRUE );
1497 /***********************************************************************
1498 * LISTBOX_InsertItem
1500 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1501 LPWSTR str, ULONG_PTR data )
1503 LB_ITEMDATA *item;
1504 INT max_items;
1505 INT oldfocus = descr->focus_item;
1507 if (index == -1) index = descr->nb_items;
1508 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1509 if (!descr->items) max_items = 0;
1510 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1511 if (descr->nb_items == max_items)
1513 /* We need to grow the array */
1514 max_items += LB_ARRAY_GRANULARITY;
1515 if (descr->items)
1516 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1517 max_items * sizeof(LB_ITEMDATA) );
1518 else
1519 item = HeapAlloc( GetProcessHeap(), 0,
1520 max_items * sizeof(LB_ITEMDATA) );
1521 if (!item)
1523 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1524 return LB_ERRSPACE;
1526 descr->items = item;
1529 /* Insert the item structure */
1531 item = &descr->items[index];
1532 if (index < descr->nb_items)
1533 RtlMoveMemory( item + 1, item,
1534 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1535 item->str = str;
1536 item->data = HAS_STRINGS(descr) ? 0 : data;
1537 item->height = 0;
1538 item->selected = FALSE;
1539 descr->nb_items++;
1541 /* Get item height */
1543 if (descr->style & LBS_OWNERDRAWVARIABLE)
1545 MEASUREITEMSTRUCT mis;
1546 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1548 mis.CtlType = ODT_LISTBOX;
1549 mis.CtlID = id;
1550 mis.itemID = index;
1551 mis.itemData = data;
1552 mis.itemHeight = descr->item_height;
1553 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1554 item->height = mis.itemHeight ? mis.itemHeight : 1;
1555 TRACE("[%p]: measure item %d (%s) = %d\n",
1556 descr->self, index, str ? debugstr_w(str) : "", item->height );
1559 /* Repaint the items */
1561 LISTBOX_UpdateScroll( descr );
1562 LISTBOX_InvalidateItems( descr, index );
1564 /* Move selection and focused item */
1565 /* If listbox was empty, set focus to the first item */
1566 if (descr->nb_items == 1)
1567 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1568 /* single select don't change selection index in win31 */
1569 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1571 descr->selected_item++;
1572 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1574 else
1576 if (index <= descr->selected_item)
1578 descr->selected_item++;
1579 descr->focus_item = oldfocus; /* focus not changed */
1582 return LB_OKAY;
1586 /***********************************************************************
1587 * LISTBOX_InsertString
1589 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1591 LPWSTR new_str = NULL;
1592 LRESULT ret;
1594 if (HAS_STRINGS(descr))
1596 static const WCHAR empty_stringW[] = { 0 };
1597 if (!str) str = empty_stringW;
1598 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1600 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1601 return LB_ERRSPACE;
1603 strcpyW(new_str, str);
1606 if (index == -1) index = descr->nb_items;
1607 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1609 HeapFree( GetProcessHeap(), 0, new_str );
1610 return ret;
1613 TRACE("[%p]: added item %d %s\n",
1614 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1615 return index;
1619 /***********************************************************************
1620 * LISTBOX_DeleteItem
1622 * Delete the content of an item. 'index' must be a valid index.
1624 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1626 /* save the item data before it gets freed by LB_RESETCONTENT */
1627 ULONG_PTR item_data = descr->items[index].data;
1628 LPWSTR item_str = descr->items[index].str;
1630 if (!descr->nb_items)
1631 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1633 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1634 * while Win95 sends it for all items with user data.
1635 * It's probably better to send it too often than not
1636 * often enough, so this is what we do here.
1638 if (IS_OWNERDRAW(descr) || item_data)
1640 DELETEITEMSTRUCT dis;
1641 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1643 dis.CtlType = ODT_LISTBOX;
1644 dis.CtlID = id;
1645 dis.itemID = index;
1646 dis.hwndItem = descr->self;
1647 dis.itemData = item_data;
1648 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1650 if (HAS_STRINGS(descr))
1651 HeapFree( GetProcessHeap(), 0, item_str );
1655 /***********************************************************************
1656 * LISTBOX_RemoveItem
1658 * Remove an item from the listbox and delete its content.
1660 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1662 LB_ITEMDATA *item;
1663 INT max_items;
1665 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1667 /* We need to invalidate the original rect instead of the updated one. */
1668 LISTBOX_InvalidateItems( descr, index );
1670 descr->nb_items--;
1671 LISTBOX_DeleteItem( descr, index );
1673 if (!descr->nb_items) return LB_OKAY;
1675 /* Remove the item */
1677 item = &descr->items[index];
1678 if (index < descr->nb_items)
1679 RtlMoveMemory( item, item + 1,
1680 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1681 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1683 /* Shrink the item array if possible */
1685 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1686 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1688 max_items -= LB_ARRAY_GRANULARITY;
1689 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1690 max_items * sizeof(LB_ITEMDATA) );
1691 if (item) descr->items = item;
1693 /* Repaint the items */
1695 LISTBOX_UpdateScroll( descr );
1696 /* if we removed the scrollbar, reset the top of the list
1697 (correct for owner-drawn ???) */
1698 if (descr->nb_items == descr->page_size)
1699 LISTBOX_SetTopItem( descr, 0, TRUE );
1701 /* Move selection and focused item */
1702 if (!IS_MULTISELECT(descr))
1704 if (index == descr->selected_item)
1705 descr->selected_item = -1;
1706 else if (index < descr->selected_item)
1708 descr->selected_item--;
1709 if (ISWIN31) /* win 31 do not change the selected item number */
1710 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1714 if (descr->focus_item >= descr->nb_items)
1716 descr->focus_item = descr->nb_items - 1;
1717 if (descr->focus_item < 0) descr->focus_item = 0;
1719 return LB_OKAY;
1723 /***********************************************************************
1724 * LISTBOX_ResetContent
1726 static void LISTBOX_ResetContent( LB_DESCR *descr )
1728 INT i;
1730 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1731 HeapFree( GetProcessHeap(), 0, descr->items );
1732 descr->nb_items = 0;
1733 descr->top_item = 0;
1734 descr->selected_item = -1;
1735 descr->focus_item = 0;
1736 descr->anchor_item = -1;
1737 descr->items = NULL;
1741 /***********************************************************************
1742 * LISTBOX_SetCount
1744 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1746 LRESULT ret;
1748 if (HAS_STRINGS(descr))
1750 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1751 return LB_ERR;
1754 /* FIXME: this is far from optimal... */
1755 if (count > descr->nb_items)
1757 while (count > descr->nb_items)
1758 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1759 return ret;
1761 else if (count < descr->nb_items)
1763 while (count < descr->nb_items)
1764 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1765 return ret;
1768 InvalidateRect( descr->self, NULL, TRUE );
1769 return LB_OKAY;
1773 /***********************************************************************
1774 * LISTBOX_Directory
1776 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1777 LPCWSTR filespec, BOOL long_names )
1779 HANDLE handle;
1780 LRESULT ret = LB_OKAY;
1781 WIN32_FIND_DATAW entry;
1782 int pos;
1783 LRESULT maxinsert = LB_ERR;
1785 /* don't scan directory if we just want drives exclusively */
1786 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1787 /* scan directory */
1788 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1790 int le = GetLastError();
1791 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1793 else
1797 WCHAR buffer[270];
1798 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1800 static const WCHAR bracketW[] = { ']',0 };
1801 static const WCHAR dotW[] = { '.',0 };
1802 if (!(attrib & DDL_DIRECTORY) ||
1803 !strcmpW( entry.cFileName, dotW )) continue;
1804 buffer[0] = '[';
1805 if (!long_names && entry.cAlternateFileName[0])
1806 strcpyW( buffer + 1, entry.cAlternateFileName );
1807 else
1808 strcpyW( buffer + 1, entry.cFileName );
1809 strcatW(buffer, bracketW);
1811 else /* not a directory */
1813 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1814 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1816 if ((attrib & DDL_EXCLUSIVE) &&
1817 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1818 continue;
1819 #undef ATTRIBS
1820 if (!long_names && entry.cAlternateFileName[0])
1821 strcpyW( buffer, entry.cAlternateFileName );
1822 else
1823 strcpyW( buffer, entry.cFileName );
1825 if (!long_names) CharLowerW( buffer );
1826 pos = LISTBOX_FindFileStrPos( descr, buffer );
1827 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1828 break;
1829 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1830 } while (FindNextFileW( handle, &entry ));
1831 FindClose( handle );
1834 if (ret >= 0)
1836 ret = maxinsert;
1838 /* scan drives */
1839 if (attrib & DDL_DRIVES)
1841 WCHAR buffer[] = {'[','-','a','-',']',0};
1842 WCHAR root[] = {'A',':','\\',0};
1843 int drive;
1844 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1846 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1847 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1848 break;
1852 return ret;
1856 /***********************************************************************
1857 * LISTBOX_HandleVScroll
1859 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1861 SCROLLINFO info;
1863 if (descr->style & LBS_MULTICOLUMN) return 0;
1864 switch(scrollReq)
1866 case SB_LINEUP:
1867 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1868 break;
1869 case SB_LINEDOWN:
1870 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1871 break;
1872 case SB_PAGEUP:
1873 LISTBOX_SetTopItem( descr, descr->top_item -
1874 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1875 break;
1876 case SB_PAGEDOWN:
1877 LISTBOX_SetTopItem( descr, descr->top_item +
1878 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1879 break;
1880 case SB_THUMBPOSITION:
1881 LISTBOX_SetTopItem( descr, pos, TRUE );
1882 break;
1883 case SB_THUMBTRACK:
1884 info.cbSize = sizeof(info);
1885 info.fMask = SIF_TRACKPOS;
1886 GetScrollInfo( descr->self, SB_VERT, &info );
1887 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1888 break;
1889 case SB_TOP:
1890 LISTBOX_SetTopItem( descr, 0, TRUE );
1891 break;
1892 case SB_BOTTOM:
1893 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1894 break;
1896 return 0;
1900 /***********************************************************************
1901 * LISTBOX_HandleHScroll
1903 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1905 SCROLLINFO info;
1906 INT page;
1908 if (descr->style & LBS_MULTICOLUMN)
1910 switch(scrollReq)
1912 case SB_LINELEFT:
1913 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1914 TRUE );
1915 break;
1916 case SB_LINERIGHT:
1917 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1918 TRUE );
1919 break;
1920 case SB_PAGELEFT:
1921 page = descr->width / descr->column_width;
1922 if (page < 1) page = 1;
1923 LISTBOX_SetTopItem( descr,
1924 descr->top_item - page * descr->page_size, TRUE );
1925 break;
1926 case SB_PAGERIGHT:
1927 page = descr->width / descr->column_width;
1928 if (page < 1) page = 1;
1929 LISTBOX_SetTopItem( descr,
1930 descr->top_item + page * descr->page_size, TRUE );
1931 break;
1932 case SB_THUMBPOSITION:
1933 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1934 break;
1935 case SB_THUMBTRACK:
1936 info.cbSize = sizeof(info);
1937 info.fMask = SIF_TRACKPOS;
1938 GetScrollInfo( descr->self, SB_VERT, &info );
1939 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1940 TRUE );
1941 break;
1942 case SB_LEFT:
1943 LISTBOX_SetTopItem( descr, 0, TRUE );
1944 break;
1945 case SB_RIGHT:
1946 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1947 break;
1950 else if (descr->horz_extent)
1952 switch(scrollReq)
1954 case SB_LINELEFT:
1955 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1956 break;
1957 case SB_LINERIGHT:
1958 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1959 break;
1960 case SB_PAGELEFT:
1961 LISTBOX_SetHorizontalPos( descr,
1962 descr->horz_pos - descr->width );
1963 break;
1964 case SB_PAGERIGHT:
1965 LISTBOX_SetHorizontalPos( descr,
1966 descr->horz_pos + descr->width );
1967 break;
1968 case SB_THUMBPOSITION:
1969 LISTBOX_SetHorizontalPos( descr, pos );
1970 break;
1971 case SB_THUMBTRACK:
1972 info.cbSize = sizeof(info);
1973 info.fMask = SIF_TRACKPOS;
1974 GetScrollInfo( descr->self, SB_HORZ, &info );
1975 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1976 break;
1977 case SB_LEFT:
1978 LISTBOX_SetHorizontalPos( descr, 0 );
1979 break;
1980 case SB_RIGHT:
1981 LISTBOX_SetHorizontalPos( descr,
1982 descr->horz_extent - descr->width );
1983 break;
1986 return 0;
1989 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1991 UINT pulScrollLines = 3;
1993 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1995 /* if scrolling changes direction, ignore left overs */
1996 if ((delta < 0 && descr->wheel_remain < 0) ||
1997 (delta > 0 && descr->wheel_remain > 0))
1998 descr->wheel_remain += delta;
1999 else
2000 descr->wheel_remain = delta;
2002 if (descr->wheel_remain && pulScrollLines)
2004 int cLineScroll;
2005 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2006 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2007 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2008 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2010 return 0;
2013 /***********************************************************************
2014 * LISTBOX_HandleLButtonDown
2016 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2018 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2020 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2021 descr->self, x, y, index, descr->focus_item);
2023 if (!descr->caret_on && (descr->in_focus)) return 0;
2025 if (!descr->in_focus)
2027 if( !descr->lphc ) SetFocus( descr->self );
2028 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2031 if (index == -1) return 0;
2033 if (!descr->lphc)
2035 if (descr->style & LBS_NOTIFY )
2036 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2037 MAKELPARAM( x, y ) );
2040 descr->captured = TRUE;
2041 SetCapture( descr->self );
2043 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2045 /* we should perhaps make sure that all items are deselected
2046 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2047 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2048 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2051 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2052 if (keys & MK_CONTROL)
2054 LISTBOX_SetCaretIndex( descr, index, FALSE );
2055 LISTBOX_SetSelection( descr, index,
2056 !descr->items[index].selected,
2057 (descr->style & LBS_NOTIFY) != 0);
2059 else
2061 LISTBOX_MoveCaret( descr, index, FALSE );
2063 if (descr->style & LBS_EXTENDEDSEL)
2065 LISTBOX_SetSelection( descr, index,
2066 descr->items[index].selected,
2067 (descr->style & LBS_NOTIFY) != 0 );
2069 else
2071 LISTBOX_SetSelection( descr, index,
2072 !descr->items[index].selected,
2073 (descr->style & LBS_NOTIFY) != 0 );
2077 else
2079 descr->anchor_item = index;
2080 LISTBOX_MoveCaret( descr, index, FALSE );
2081 LISTBOX_SetSelection( descr, index,
2082 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2085 if (!descr->lphc)
2087 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2089 POINT pt;
2091 pt.x = x;
2092 pt.y = y;
2094 if (DragDetect( descr->self, pt ))
2095 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2098 return 0;
2102 /*************************************************************************
2103 * LISTBOX_HandleLButtonDownCombo [Internal]
2105 * Process LButtonDown message for the ComboListBox
2107 * PARAMS
2108 * pWnd [I] The windows internal structure
2109 * pDescr [I] The ListBox internal structure
2110 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2111 * x [I] X Mouse Coordinate
2112 * y [I] Y Mouse Coordinate
2114 * RETURNS
2115 * 0 since we are processing the WM_LBUTTONDOWN Message
2117 * NOTES
2118 * This function is only to be used when a ListBox is a ComboListBox
2121 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2123 RECT clientRect, screenRect;
2124 POINT mousePos;
2126 mousePos.x = x;
2127 mousePos.y = y;
2129 GetClientRect(descr->self, &clientRect);
2131 if(PtInRect(&clientRect, mousePos))
2133 /* MousePos is in client, resume normal processing */
2134 if (msg == WM_LBUTTONDOWN)
2136 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2137 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2139 else if (descr->style & LBS_NOTIFY)
2140 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2142 else
2144 POINT screenMousePos;
2145 HWND hWndOldCapture;
2147 /* Check the Non-Client Area */
2148 screenMousePos = mousePos;
2149 hWndOldCapture = GetCapture();
2150 ReleaseCapture();
2151 GetWindowRect(descr->self, &screenRect);
2152 ClientToScreen(descr->self, &screenMousePos);
2154 if(!PtInRect(&screenRect, screenMousePos))
2156 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2157 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2158 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2160 else
2162 /* Check to see the NC is a scrollbar */
2163 INT nHitTestType=0;
2164 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2165 /* Check Vertical scroll bar */
2166 if (style & WS_VSCROLL)
2168 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2169 if (PtInRect( &clientRect, mousePos ))
2170 nHitTestType = HTVSCROLL;
2172 /* Check horizontal scroll bar */
2173 if (style & WS_HSCROLL)
2175 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2176 if (PtInRect( &clientRect, mousePos ))
2177 nHitTestType = HTHSCROLL;
2179 /* Windows sends this message when a scrollbar is clicked
2182 if(nHitTestType != 0)
2184 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2185 MAKELONG(screenMousePos.x, screenMousePos.y));
2187 /* Resume the Capture after scrolling is complete
2189 if(hWndOldCapture != 0)
2190 SetCapture(hWndOldCapture);
2193 return 0;
2196 /***********************************************************************
2197 * LISTBOX_HandleLButtonUp
2199 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2201 if (LISTBOX_Timer != LB_TIMER_NONE)
2202 KillSystemTimer( descr->self, LB_TIMER_ID );
2203 LISTBOX_Timer = LB_TIMER_NONE;
2204 if (descr->captured)
2206 descr->captured = FALSE;
2207 if (GetCapture() == descr->self) ReleaseCapture();
2208 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2209 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2211 return 0;
2215 /***********************************************************************
2216 * LISTBOX_HandleTimer
2218 * Handle scrolling upon a timer event.
2219 * Return TRUE if scrolling should continue.
2221 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2223 switch(dir)
2225 case LB_TIMER_UP:
2226 if (descr->top_item) index = descr->top_item - 1;
2227 else index = 0;
2228 break;
2229 case LB_TIMER_LEFT:
2230 if (descr->top_item) index -= descr->page_size;
2231 break;
2232 case LB_TIMER_DOWN:
2233 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2234 if (index == descr->focus_item) index++;
2235 if (index >= descr->nb_items) index = descr->nb_items - 1;
2236 break;
2237 case LB_TIMER_RIGHT:
2238 if (index + descr->page_size < descr->nb_items)
2239 index += descr->page_size;
2240 break;
2241 case LB_TIMER_NONE:
2242 break;
2244 if (index == descr->focus_item) return FALSE;
2245 LISTBOX_MoveCaret( descr, index, FALSE );
2246 return TRUE;
2250 /***********************************************************************
2251 * LISTBOX_HandleSystemTimer
2253 * WM_SYSTIMER handler.
2255 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2257 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2259 KillSystemTimer( descr->self, LB_TIMER_ID );
2260 LISTBOX_Timer = LB_TIMER_NONE;
2262 return 0;
2266 /***********************************************************************
2267 * LISTBOX_HandleMouseMove
2269 * WM_MOUSEMOVE handler.
2271 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2272 INT x, INT y )
2274 INT index;
2275 TIMER_DIRECTION dir = LB_TIMER_NONE;
2277 if (!descr->captured) return;
2279 if (descr->style & LBS_MULTICOLUMN)
2281 if (y < 0) y = 0;
2282 else if (y >= descr->item_height * descr->page_size)
2283 y = descr->item_height * descr->page_size - 1;
2285 if (x < 0)
2287 dir = LB_TIMER_LEFT;
2288 x = 0;
2290 else if (x >= descr->width)
2292 dir = LB_TIMER_RIGHT;
2293 x = descr->width - 1;
2296 else
2298 if (y < 0) dir = LB_TIMER_UP; /* above */
2299 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2302 index = LISTBOX_GetItemFromPoint( descr, x, y );
2303 if (index == -1) index = descr->focus_item;
2304 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2306 /* Start/stop the system timer */
2308 if (dir != LB_TIMER_NONE)
2309 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2310 else if (LISTBOX_Timer != LB_TIMER_NONE)
2311 KillSystemTimer( descr->self, LB_TIMER_ID );
2312 LISTBOX_Timer = dir;
2316 /***********************************************************************
2317 * LISTBOX_HandleKeyDown
2319 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2321 INT caret = -1;
2322 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2323 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2324 bForceSelection = FALSE; /* only for single select list */
2326 if (descr->style & LBS_WANTKEYBOARDINPUT)
2328 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2329 MAKEWPARAM(LOWORD(key), descr->focus_item),
2330 (LPARAM)descr->self );
2331 if (caret == -2) return 0;
2333 if (caret == -1) switch(key)
2335 case VK_LEFT:
2336 if (descr->style & LBS_MULTICOLUMN)
2338 bForceSelection = FALSE;
2339 if (descr->focus_item >= descr->page_size)
2340 caret = descr->focus_item - descr->page_size;
2341 break;
2343 /* fall through */
2344 case VK_UP:
2345 caret = descr->focus_item - 1;
2346 if (caret < 0) caret = 0;
2347 break;
2348 case VK_RIGHT:
2349 if (descr->style & LBS_MULTICOLUMN)
2351 bForceSelection = FALSE;
2352 if (descr->focus_item + descr->page_size < descr->nb_items)
2353 caret = descr->focus_item + descr->page_size;
2354 break;
2356 /* fall through */
2357 case VK_DOWN:
2358 caret = descr->focus_item + 1;
2359 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2360 break;
2362 case VK_PRIOR:
2363 if (descr->style & LBS_MULTICOLUMN)
2365 INT page = descr->width / descr->column_width;
2366 if (page < 1) page = 1;
2367 caret = descr->focus_item - (page * descr->page_size) + 1;
2369 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2370 if (caret < 0) caret = 0;
2371 break;
2372 case VK_NEXT:
2373 if (descr->style & LBS_MULTICOLUMN)
2375 INT page = descr->width / descr->column_width;
2376 if (page < 1) page = 1;
2377 caret = descr->focus_item + (page * descr->page_size) - 1;
2379 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2380 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2381 break;
2382 case VK_HOME:
2383 caret = 0;
2384 break;
2385 case VK_END:
2386 caret = descr->nb_items - 1;
2387 break;
2388 case VK_SPACE:
2389 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2390 else if (descr->style & LBS_MULTIPLESEL)
2392 LISTBOX_SetSelection( descr, descr->focus_item,
2393 !descr->items[descr->focus_item].selected,
2394 (descr->style & LBS_NOTIFY) != 0 );
2396 break;
2397 default:
2398 bForceSelection = FALSE;
2400 if (bForceSelection) /* focused item is used instead of key */
2401 caret = descr->focus_item;
2402 if (caret >= 0)
2404 if (((descr->style & LBS_EXTENDEDSEL) &&
2405 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2406 !IS_MULTISELECT(descr))
2407 descr->anchor_item = caret;
2408 LISTBOX_MoveCaret( descr, caret, TRUE );
2410 if (descr->style & LBS_MULTIPLESEL)
2411 descr->selected_item = caret;
2412 else
2413 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2414 if (descr->style & LBS_NOTIFY)
2416 if (descr->lphc && IsWindowVisible( descr->self ))
2418 /* make sure that combo parent doesn't hide us */
2419 descr->lphc->wState |= CBF_NOROLLUP;
2421 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2424 return 0;
2428 /***********************************************************************
2429 * LISTBOX_HandleChar
2431 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2433 INT caret = -1;
2434 WCHAR str[2];
2436 str[0] = charW;
2437 str[1] = '\0';
2439 if (descr->style & LBS_WANTKEYBOARDINPUT)
2441 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2442 MAKEWPARAM(charW, descr->focus_item),
2443 (LPARAM)descr->self );
2444 if (caret == -2) return 0;
2446 if (caret == -1)
2447 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2448 if (caret != -1)
2450 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2451 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2452 LISTBOX_MoveCaret( descr, caret, TRUE );
2453 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2454 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2456 return 0;
2460 /***********************************************************************
2461 * LISTBOX_Create
2463 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2465 LB_DESCR *descr;
2466 MEASUREITEMSTRUCT mis;
2467 RECT rect;
2469 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2470 return FALSE;
2472 GetClientRect( hwnd, &rect );
2473 descr->self = hwnd;
2474 descr->owner = GetParent( descr->self );
2475 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2476 descr->width = rect.right - rect.left;
2477 descr->height = rect.bottom - rect.top;
2478 descr->items = NULL;
2479 descr->nb_items = 0;
2480 descr->top_item = 0;
2481 descr->selected_item = -1;
2482 descr->focus_item = 0;
2483 descr->anchor_item = -1;
2484 descr->item_height = 1;
2485 descr->page_size = 1;
2486 descr->column_width = 150;
2487 descr->horz_extent = 0;
2488 descr->horz_pos = 0;
2489 descr->nb_tabs = 0;
2490 descr->tabs = NULL;
2491 descr->wheel_remain = 0;
2492 descr->caret_on = !lphc;
2493 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2494 descr->in_focus = FALSE;
2495 descr->captured = FALSE;
2496 descr->font = 0;
2497 descr->locale = GetUserDefaultLCID();
2498 descr->lphc = lphc;
2500 if( lphc )
2502 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2503 descr->owner = lphc->self;
2506 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2508 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2510 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2511 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2512 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2513 descr->item_height = LISTBOX_SetFont( descr, 0 );
2515 if (descr->style & LBS_OWNERDRAWFIXED)
2517 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2519 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2520 descr->item_height = lphc->fixedOwnerDrawHeight;
2522 else
2524 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2525 mis.CtlType = ODT_LISTBOX;
2526 mis.CtlID = id;
2527 mis.itemID = -1;
2528 mis.itemWidth = 0;
2529 mis.itemData = 0;
2530 mis.itemHeight = descr->item_height;
2531 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2532 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2536 OpenThemeData( descr->self, WC_LISTBOXW );
2538 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2539 return TRUE;
2543 /***********************************************************************
2544 * LISTBOX_Destroy
2546 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2548 HTHEME theme = GetWindowTheme( descr->self );
2549 CloseThemeData( theme );
2550 LISTBOX_ResetContent( descr );
2551 SetWindowLongPtrW( descr->self, 0, 0 );
2552 HeapFree( GetProcessHeap(), 0, descr );
2553 return TRUE;
2557 /***********************************************************************
2558 * ListBoxWndProc_common
2560 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2562 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2563 HEADCOMBO *lphc = NULL;
2564 HTHEME theme;
2565 LRESULT ret;
2567 if (!descr)
2569 if (!IsWindow(hwnd)) return 0;
2571 if (msg == WM_CREATE)
2573 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2574 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2575 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2576 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2577 return 0;
2579 /* Ignore all other messages before we get a WM_CREATE */
2580 return DefWindowProcW( hwnd, msg, wParam, lParam );
2582 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2584 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr->self, msg, wParam, lParam );
2586 switch(msg)
2588 case LB_RESETCONTENT:
2589 LISTBOX_ResetContent( descr );
2590 LISTBOX_UpdateScroll( descr );
2591 InvalidateRect( descr->self, NULL, TRUE );
2592 return 0;
2594 case LB_ADDSTRING:
2596 const WCHAR *textW = (const WCHAR *)lParam;
2597 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2598 return LISTBOX_InsertString( descr, index, textW );
2601 case LB_INSERTSTRING:
2602 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2604 case LB_ADDFILE:
2606 const WCHAR *textW = (const WCHAR *)lParam;
2607 INT index = LISTBOX_FindFileStrPos( descr, textW );
2608 return LISTBOX_InsertString( descr, index, textW );
2611 case LB_DELETESTRING:
2612 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2613 return descr->nb_items;
2614 else
2616 SetLastError(ERROR_INVALID_INDEX);
2617 return LB_ERR;
2620 case LB_GETITEMDATA:
2621 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2623 SetLastError(ERROR_INVALID_INDEX);
2624 return LB_ERR;
2626 return descr->items[wParam].data;
2628 case LB_SETITEMDATA:
2629 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2631 SetLastError(ERROR_INVALID_INDEX);
2632 return LB_ERR;
2634 descr->items[wParam].data = lParam;
2635 /* undocumented: returns TRUE, not LB_OKAY (0) */
2636 return TRUE;
2638 case LB_GETCOUNT:
2639 return descr->nb_items;
2641 case LB_GETTEXT:
2642 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2644 case LB_GETTEXTLEN:
2645 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2647 SetLastError(ERROR_INVALID_INDEX);
2648 return LB_ERR;
2650 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2651 return strlenW( descr->items[wParam].str );
2653 case LB_GETCURSEL:
2654 if (descr->nb_items == 0)
2655 return LB_ERR;
2656 if (!IS_MULTISELECT(descr))
2657 return descr->selected_item;
2658 if (descr->selected_item != -1)
2659 return descr->selected_item;
2660 return descr->focus_item;
2661 /* otherwise, if the user tries to move the selection with the */
2662 /* arrow keys, we will give the application something to choke on */
2663 case LB_GETTOPINDEX:
2664 return descr->top_item;
2666 case LB_GETITEMHEIGHT:
2667 return LISTBOX_GetItemHeight( descr, wParam );
2669 case LB_SETITEMHEIGHT:
2670 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2672 case LB_ITEMFROMPOINT:
2674 POINT pt;
2675 RECT rect;
2676 int index;
2677 BOOL hit = TRUE;
2679 /* The hiword of the return value is not a client area
2680 hittest as suggested by MSDN, but rather a hittest on
2681 the returned listbox item. */
2683 if(descr->nb_items == 0)
2684 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2686 pt.x = (short)LOWORD(lParam);
2687 pt.y = (short)HIWORD(lParam);
2689 SetRect(&rect, 0, 0, descr->width, descr->height);
2691 if(!PtInRect(&rect, pt))
2693 pt.x = min(pt.x, rect.right - 1);
2694 pt.x = max(pt.x, 0);
2695 pt.y = min(pt.y, rect.bottom - 1);
2696 pt.y = max(pt.y, 0);
2697 hit = FALSE;
2700 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2702 if(index == -1)
2704 index = descr->nb_items - 1;
2705 hit = FALSE;
2707 return MAKELONG(index, hit ? 0 : 1);
2710 case LB_SETCARETINDEX:
2711 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2712 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2713 return LB_ERR;
2714 else if (ISWIN31)
2715 return wParam;
2716 else
2717 return LB_OKAY;
2719 case LB_GETCARETINDEX:
2720 return descr->focus_item;
2722 case LB_SETTOPINDEX:
2723 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2725 case LB_SETCOLUMNWIDTH:
2726 return LISTBOX_SetColumnWidth( descr, wParam );
2728 case LB_GETITEMRECT:
2729 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2731 case LB_FINDSTRING:
2732 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2734 case LB_FINDSTRINGEXACT:
2735 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2737 case LB_SELECTSTRING:
2739 const WCHAR *textW = (const WCHAR *)lParam;
2740 INT index;
2742 if (HAS_STRINGS(descr))
2743 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2745 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2746 if (index != LB_ERR)
2748 LISTBOX_MoveCaret( descr, index, TRUE );
2749 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2751 return index;
2754 case LB_GETSEL:
2755 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2756 return LB_ERR;
2757 return descr->items[wParam].selected;
2759 case LB_SETSEL:
2760 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2762 case LB_SETCURSEL:
2763 if (IS_MULTISELECT(descr)) return LB_ERR;
2764 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2765 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2766 if (ret != LB_ERR) ret = descr->selected_item;
2767 return ret;
2769 case LB_GETSELCOUNT:
2770 return LISTBOX_GetSelCount( descr );
2772 case LB_GETSELITEMS:
2773 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2775 case LB_SELITEMRANGE:
2776 if (LOWORD(lParam) <= HIWORD(lParam))
2777 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2778 HIWORD(lParam), wParam );
2779 else
2780 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2781 LOWORD(lParam), wParam );
2783 case LB_SELITEMRANGEEX:
2784 if ((INT)lParam >= (INT)wParam)
2785 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2786 else
2787 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2789 case LB_GETHORIZONTALEXTENT:
2790 return descr->horz_extent;
2792 case LB_SETHORIZONTALEXTENT:
2793 return LISTBOX_SetHorizontalExtent( descr, wParam );
2795 case LB_GETANCHORINDEX:
2796 return descr->anchor_item;
2798 case LB_SETANCHORINDEX:
2799 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2801 SetLastError(ERROR_INVALID_INDEX);
2802 return LB_ERR;
2804 descr->anchor_item = (INT)wParam;
2805 return LB_OKAY;
2807 case LB_DIR:
2808 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2810 case LB_GETLOCALE:
2811 return descr->locale;
2813 case LB_SETLOCALE:
2815 LCID ret;
2816 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2817 return LB_ERR;
2818 ret = descr->locale;
2819 descr->locale = (LCID)wParam;
2820 return ret;
2823 case LB_INITSTORAGE:
2824 return LISTBOX_InitStorage( descr, wParam );
2826 case LB_SETCOUNT:
2827 return LISTBOX_SetCount( descr, (INT)wParam );
2829 case LB_SETTABSTOPS:
2830 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2832 case LB_CARETON:
2833 if (descr->caret_on)
2834 return LB_OKAY;
2835 descr->caret_on = TRUE;
2836 if ((descr->focus_item != -1) && (descr->in_focus))
2837 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2838 return LB_OKAY;
2840 case LB_CARETOFF:
2841 if (!descr->caret_on)
2842 return LB_OKAY;
2843 descr->caret_on = FALSE;
2844 if ((descr->focus_item != -1) && (descr->in_focus))
2845 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2846 return LB_OKAY;
2848 case LB_GETLISTBOXINFO:
2849 return descr->page_size;
2851 case WM_DESTROY:
2852 return LISTBOX_Destroy( descr );
2854 case WM_ENABLE:
2855 InvalidateRect( descr->self, NULL, TRUE );
2856 return 0;
2858 case WM_SETREDRAW:
2859 LISTBOX_SetRedraw( descr, wParam != 0 );
2860 return 0;
2862 case WM_GETDLGCODE:
2863 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2865 case WM_PRINTCLIENT:
2866 case WM_PAINT:
2868 PAINTSTRUCT ps;
2869 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2870 ret = LISTBOX_Paint( descr, hdc );
2871 if( !wParam ) EndPaint( descr->self, &ps );
2873 return ret;
2875 case WM_NCPAINT:
2876 LISTBOX_NCPaint( descr, (HRGN)wParam );
2877 break;
2879 case WM_SIZE:
2880 LISTBOX_UpdateSize( descr );
2881 return 0;
2882 case WM_GETFONT:
2883 return (LRESULT)descr->font;
2884 case WM_SETFONT:
2885 LISTBOX_SetFont( descr, (HFONT)wParam );
2886 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2887 return 0;
2888 case WM_SETFOCUS:
2889 descr->in_focus = TRUE;
2890 descr->caret_on = TRUE;
2891 if (descr->focus_item != -1)
2892 LISTBOX_DrawFocusRect( descr, TRUE );
2893 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2894 return 0;
2895 case WM_KILLFOCUS:
2896 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
2897 descr->in_focus = FALSE;
2898 descr->wheel_remain = 0;
2899 if ((descr->focus_item != -1) && descr->caret_on)
2900 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2901 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
2902 return 0;
2903 case WM_HSCROLL:
2904 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2905 case WM_VSCROLL:
2906 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2907 case WM_MOUSEWHEEL:
2908 if (wParam & (MK_SHIFT | MK_CONTROL))
2909 return DefWindowProcW( descr->self, msg, wParam, lParam );
2910 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
2911 case WM_LBUTTONDOWN:
2912 if (lphc)
2913 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2914 (INT16)LOWORD(lParam),
2915 (INT16)HIWORD(lParam) );
2916 return LISTBOX_HandleLButtonDown( descr, wParam,
2917 (INT16)LOWORD(lParam),
2918 (INT16)HIWORD(lParam) );
2919 case WM_LBUTTONDBLCLK:
2920 if (lphc)
2921 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2922 (INT16)LOWORD(lParam),
2923 (INT16)HIWORD(lParam) );
2924 if (descr->style & LBS_NOTIFY)
2925 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2926 return 0;
2927 case WM_MOUSEMOVE:
2928 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
2930 BOOL captured = descr->captured;
2931 POINT mousePos;
2932 RECT clientRect;
2934 mousePos.x = (INT16)LOWORD(lParam);
2935 mousePos.y = (INT16)HIWORD(lParam);
2938 * If we are in a dropdown combobox, we simulate that
2939 * the mouse is captured to show the tracking of the item.
2941 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
2942 descr->captured = TRUE;
2944 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
2946 descr->captured = captured;
2948 else if (GetCapture() == descr->self)
2950 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
2951 (INT16)HIWORD(lParam) );
2953 return 0;
2954 case WM_LBUTTONUP:
2955 if (lphc)
2957 POINT mousePos;
2958 RECT clientRect;
2961 * If the mouse button "up" is not in the listbox,
2962 * we make sure there is no selection by re-selecting the
2963 * item that was selected when the listbox was made visible.
2965 mousePos.x = (INT16)LOWORD(lParam);
2966 mousePos.y = (INT16)HIWORD(lParam);
2968 GetClientRect(descr->self, &clientRect);
2971 * When the user clicks outside the combobox and the focus
2972 * is lost, the owning combobox will send a fake buttonup with
2973 * 0xFFFFFFF as the mouse location, we must also revert the
2974 * selection to the original selection.
2976 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
2977 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
2979 return LISTBOX_HandleLButtonUp( descr );
2980 case WM_KEYDOWN:
2981 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
2983 /* for some reason Windows makes it possible to
2984 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2986 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2987 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2988 && (wParam == VK_DOWN || wParam == VK_UP)) )
2990 COMBO_FlipListbox( lphc, FALSE, FALSE );
2991 return 0;
2994 return LISTBOX_HandleKeyDown( descr, wParam );
2995 case WM_CHAR:
2996 return LISTBOX_HandleChar( descr, wParam );
2998 case WM_SYSTIMER:
2999 return LISTBOX_HandleSystemTimer( descr );
3000 case WM_ERASEBKGND:
3001 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3003 RECT rect;
3004 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3005 wParam, (LPARAM)descr->self );
3006 TRACE("hbrush = %p\n", hbrush);
3007 if(!hbrush)
3008 hbrush = GetSysColorBrush(COLOR_WINDOW);
3009 if(hbrush)
3011 GetClientRect(descr->self, &rect);
3012 FillRect((HDC)wParam, &rect, hbrush);
3015 return 1;
3016 case WM_DROPFILES:
3017 if( lphc ) return 0;
3018 return SendMessageW( descr->owner, msg, wParam, lParam );
3020 case WM_NCDESTROY:
3021 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3022 lphc->hWndLBox = 0;
3023 break;
3025 case WM_NCACTIVATE:
3026 if (lphc) return 0;
3027 break;
3029 case WM_THEMECHANGED:
3030 theme = GetWindowTheme( hwnd );
3031 CloseThemeData( theme );
3032 OpenThemeData( hwnd, WC_LISTBOXW );
3033 break;
3035 default:
3036 if ((msg >= WM_USER) && (msg < 0xc000))
3037 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3038 hwnd, msg, wParam, lParam );
3041 return DefWindowProcW( hwnd, msg, wParam, lParam );
3044 void LISTBOX_Register(void)
3046 WNDCLASSW wndClass;
3048 memset(&wndClass, 0, sizeof(wndClass));
3049 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3050 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3051 wndClass.cbClsExtra = 0;
3052 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3053 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3054 wndClass.hbrBackground = NULL;
3055 wndClass.lpszClassName = WC_LISTBOXW;
3056 RegisterClassW(&wndClass);
3059 void COMBOLBOX_Register(void)
3061 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
3062 WNDCLASSW wndClass;
3064 memset(&wndClass, 0, sizeof(wndClass));
3065 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
3066 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3067 wndClass.cbClsExtra = 0;
3068 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3069 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3070 wndClass.hbrBackground = NULL;
3071 wndClass.lpszClassName = combolboxW;
3072 RegisterClassW(&wndClass);