d2d1: Implement D2D1MakeSkewMatrix().
[wine.git] / dlls / comctl32 / listbox.c
blob2137ef86e09dfaf66c8fc3b27b58379ab8af6f8c
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
5 * Copyright 2005 Frank Richter
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * TODO:
22 * - LBS_NODATA
25 #include <string.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include "windef.h"
30 #include "winbase.h"
31 #include "wingdi.h"
32 #include "winuser.h"
33 #include "commctrl.h"
34 #include "uxtheme.h"
35 #include "vssym32.h"
36 #include "wine/unicode.h"
37 #include "wine/exception.h"
38 #include "wine/debug.h"
40 #include "comctl32.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
44 /* Items array granularity */
45 #define LB_ARRAY_GRANULARITY 16
47 /* Scrolling timeout in ms */
48 #define LB_SCROLL_TIMEOUT 50
50 /* Listbox system timer id */
51 #define LB_TIMER_ID 2
53 /* flag listbox changed while setredraw false - internal style */
54 #define LBS_DISPLAYCHANGED 0x80000000
56 /* Item structure */
57 typedef struct
59 LPWSTR str; /* Item text */
60 BOOL selected; /* Is item selected? */
61 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
62 ULONG_PTR data; /* User data */
63 } LB_ITEMDATA;
65 /* Listbox structure */
66 typedef struct
68 HWND self; /* Our own window handle */
69 HWND owner; /* Owner window to send notifications to */
70 UINT style; /* Window style */
71 INT width; /* Window width */
72 INT height; /* Window height */
73 LB_ITEMDATA *items; /* Array of items */
74 INT nb_items; /* Number of items */
75 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 dx = 0, dy = 0;
279 if (descr->style & LBS_MULTICOLUMN)
280 dx = (descr->top_item - index) / descr->page_size * descr->column_width;
281 else if (descr->style & LBS_OWNERDRAWVARIABLE)
283 INT i;
284 if (index > descr->top_item)
286 for (i = index - 1; i >= descr->top_item; i--)
287 dy -= descr->items[i].height;
289 else
291 for (i = index; i < descr->top_item; i++)
292 dy += descr->items[i].height;
295 else
296 dy = (descr->top_item - index) * descr->item_height;
298 ScrollWindowEx( descr->self, dx, dy, NULL, NULL, 0, NULL,
299 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
301 else
302 InvalidateRect( descr->self, NULL, TRUE );
303 descr->top_item = index;
304 LISTBOX_UpdateScroll( descr );
305 return LB_OKAY;
309 /***********************************************************************
310 * LISTBOX_UpdatePage
312 * Update the page size. Should be called when the size of
313 * the client area or the item height changes.
315 static void LISTBOX_UpdatePage( LB_DESCR *descr )
317 INT page_size;
319 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
320 page_size = 1;
321 if (page_size == descr->page_size) return;
322 descr->page_size = page_size;
323 if (descr->style & LBS_MULTICOLUMN)
324 InvalidateRect( descr->self, NULL, TRUE );
325 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
329 /***********************************************************************
330 * LISTBOX_UpdateSize
332 * Update the size of the listbox. Should be called when the size of
333 * the client area changes.
335 static void LISTBOX_UpdateSize( LB_DESCR *descr )
337 RECT rect;
339 GetClientRect( descr->self, &rect );
340 descr->width = rect.right - rect.left;
341 descr->height = rect.bottom - rect.top;
342 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
344 INT remaining;
345 RECT rect;
347 GetWindowRect( descr->self, &rect );
348 if(descr->item_height != 0)
349 remaining = descr->height % descr->item_height;
350 else
351 remaining = 0;
352 if ((descr->height > descr->item_height) && remaining)
354 TRACE("[%p]: changing height %d -> %d\n",
355 descr->self, descr->height, descr->height - remaining );
356 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
357 rect.bottom - rect.top - remaining,
358 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
359 return;
362 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
363 LISTBOX_UpdatePage( descr );
364 LISTBOX_UpdateScroll( descr );
366 /* Invalidate the focused item so it will be repainted correctly */
367 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
369 InvalidateRect( descr->self, &rect, FALSE );
374 /***********************************************************************
375 * LISTBOX_GetItemRect
377 * Get the rectangle enclosing an item, in listbox client coordinates.
378 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
380 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
382 /* Index <= 0 is legal even on empty listboxes */
383 if (index && (index >= descr->nb_items))
385 SetRectEmpty(rect);
386 SetLastError(ERROR_INVALID_INDEX);
387 return LB_ERR;
389 SetRect( rect, 0, 0, descr->width, descr->height );
390 if (descr->style & LBS_MULTICOLUMN)
392 INT col = (index / descr->page_size) -
393 (descr->top_item / descr->page_size);
394 rect->left += col * descr->column_width;
395 rect->right = rect->left + descr->column_width;
396 rect->top += (index % descr->page_size) * descr->item_height;
397 rect->bottom = rect->top + descr->item_height;
399 else if (descr->style & LBS_OWNERDRAWVARIABLE)
401 INT i;
402 rect->right += descr->horz_pos;
403 if ((index >= 0) && (index < descr->nb_items))
405 if (index < descr->top_item)
407 for (i = descr->top_item-1; i >= index; i--)
408 rect->top -= descr->items[i].height;
410 else
412 for (i = descr->top_item; i < index; i++)
413 rect->top += descr->items[i].height;
415 rect->bottom = rect->top + descr->items[index].height;
419 else
421 rect->top += (index - descr->top_item) * descr->item_height;
422 rect->bottom = rect->top + descr->item_height;
423 rect->right += descr->horz_pos;
426 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
428 return ((rect->left < descr->width) && (rect->right > 0) &&
429 (rect->top < descr->height) && (rect->bottom > 0));
433 /***********************************************************************
434 * LISTBOX_GetItemFromPoint
436 * Return the item nearest from point (x,y) (in client coordinates).
438 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
440 INT index = descr->top_item;
442 if (!descr->nb_items) return -1; /* No items */
443 if (descr->style & LBS_OWNERDRAWVARIABLE)
445 INT pos = 0;
446 if (y >= 0)
448 while (index < descr->nb_items)
450 if ((pos += descr->items[index].height) > y) break;
451 index++;
454 else
456 while (index > 0)
458 index--;
459 if ((pos -= descr->items[index].height) <= y) break;
463 else if (descr->style & LBS_MULTICOLUMN)
465 if (y >= descr->item_height * descr->page_size) return -1;
466 if (y >= 0) index += y / descr->item_height;
467 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
468 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
470 else
472 index += (y / descr->item_height);
474 if (index < 0) return 0;
475 if (index >= descr->nb_items) return -1;
476 return index;
480 /***********************************************************************
481 * LISTBOX_PaintItem
483 * Paint an item.
485 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
486 INT index, UINT action, BOOL ignoreFocus )
488 LB_ITEMDATA *item = NULL;
489 if (index < descr->nb_items) item = &descr->items[index];
491 if (IS_OWNERDRAW(descr))
493 DRAWITEMSTRUCT dis;
494 RECT r;
495 HRGN hrgn;
497 if (!item)
499 if (action == ODA_FOCUS)
500 DrawFocusRect( hdc, rect );
501 else
502 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
503 return;
506 /* some programs mess with the clipping region when
507 drawing the item, *and* restore the previous region
508 after they are done, so a region has better to exist
509 else everything ends clipped */
510 GetClientRect(descr->self, &r);
511 hrgn = set_control_clipping( hdc, &r );
513 dis.CtlType = ODT_LISTBOX;
514 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
515 dis.hwndItem = descr->self;
516 dis.itemAction = action;
517 dis.hDC = hdc;
518 dis.itemID = index;
519 dis.itemState = 0;
520 if (item->selected) dis.itemState |= ODS_SELECTED;
521 if (!ignoreFocus && (descr->focus_item == index) &&
522 (descr->caret_on) &&
523 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
524 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
525 dis.itemData = item->data;
526 dis.rcItem = *rect;
527 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
528 descr->self, index, debugstr_w(item->str), action,
529 dis.itemState, wine_dbgstr_rect(rect) );
530 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
531 SelectClipRgn( hdc, hrgn );
532 if (hrgn) DeleteObject( hrgn );
534 else
536 COLORREF oldText = 0, oldBk = 0;
538 if (action == ODA_FOCUS)
540 DrawFocusRect( hdc, rect );
541 return;
543 if (item && item->selected)
545 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
546 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
549 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
550 descr->self, index, item ? debugstr_w(item->str) : "", action,
551 wine_dbgstr_rect(rect) );
552 if (!item)
553 ExtTextOutW( hdc, rect->left + 1, rect->top,
554 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
555 else if (!(descr->style & LBS_USETABSTOPS))
556 ExtTextOutW( hdc, rect->left + 1, rect->top,
557 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
558 strlenW(item->str), NULL );
559 else
561 /* Output empty string to paint background in the full width. */
562 ExtTextOutW( hdc, rect->left + 1, rect->top,
563 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
564 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
565 item->str, strlenW(item->str),
566 descr->nb_tabs, descr->tabs, 0);
568 if (item && item->selected)
570 SetBkColor( hdc, oldBk );
571 SetTextColor( hdc, oldText );
573 if (!ignoreFocus && (descr->focus_item == index) &&
574 (descr->caret_on) &&
575 (descr->in_focus)) DrawFocusRect( hdc, rect );
580 /***********************************************************************
581 * LISTBOX_SetRedraw
583 * Change the redraw flag.
585 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
587 if (on)
589 if (!(descr->style & LBS_NOREDRAW)) return;
590 descr->style &= ~LBS_NOREDRAW;
591 if (descr->style & LBS_DISPLAYCHANGED)
592 { /* page was changed while setredraw false, refresh automatically */
593 InvalidateRect(descr->self, NULL, TRUE);
594 if ((descr->top_item + descr->page_size) > descr->nb_items)
595 { /* reset top of page if less than number of items/page */
596 descr->top_item = descr->nb_items - descr->page_size;
597 if (descr->top_item < 0) descr->top_item = 0;
599 descr->style &= ~LBS_DISPLAYCHANGED;
601 LISTBOX_UpdateScroll( descr );
603 else descr->style |= LBS_NOREDRAW;
607 /***********************************************************************
608 * LISTBOX_RepaintItem
610 * Repaint a single item synchronously.
612 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
614 HDC hdc;
615 RECT rect;
616 HFONT oldFont = 0;
617 HBRUSH hbrush, oldBrush = 0;
619 /* Do not repaint the item if the item is not visible */
620 if (!IsWindowVisible(descr->self)) return;
621 if (descr->style & LBS_NOREDRAW)
623 descr->style |= LBS_DISPLAYCHANGED;
624 return;
626 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
627 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
628 if (descr->font) oldFont = SelectObject( hdc, descr->font );
629 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
630 (WPARAM)hdc, (LPARAM)descr->self );
631 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
632 if (!IsWindowEnabled(descr->self))
633 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
634 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
635 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
636 if (oldFont) SelectObject( hdc, oldFont );
637 if (oldBrush) SelectObject( hdc, oldBrush );
638 ReleaseDC( descr->self, hdc );
642 /***********************************************************************
643 * LISTBOX_DrawFocusRect
645 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
647 HDC hdc;
648 RECT rect;
649 HFONT oldFont = 0;
651 /* Do not repaint the item if the item is not visible */
652 if (!IsWindowVisible(descr->self)) return;
654 if (descr->focus_item == -1) return;
655 if (!descr->caret_on || !descr->in_focus) return;
657 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
658 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
659 if (descr->font) oldFont = SelectObject( hdc, descr->font );
660 if (!IsWindowEnabled(descr->self))
661 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
662 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
663 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
664 if (oldFont) SelectObject( hdc, oldFont );
665 ReleaseDC( descr->self, hdc );
669 /***********************************************************************
670 * LISTBOX_InitStorage
672 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
674 LB_ITEMDATA *item;
676 nb_items += LB_ARRAY_GRANULARITY - 1;
677 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
678 if (descr->items) {
679 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
680 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
681 nb_items * sizeof(LB_ITEMDATA));
683 else {
684 item = HeapAlloc( GetProcessHeap(), 0,
685 nb_items * sizeof(LB_ITEMDATA));
688 if (!item)
690 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
691 return LB_ERRSPACE;
693 descr->items = item;
694 return LB_OKAY;
698 /***********************************************************************
699 * LISTBOX_SetTabStops
701 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
703 INT i;
705 if (!(descr->style & LBS_USETABSTOPS))
707 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
708 return FALSE;
711 HeapFree( GetProcessHeap(), 0, descr->tabs );
712 if (!(descr->nb_tabs = count))
714 descr->tabs = NULL;
715 return TRUE;
717 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
718 descr->nb_tabs * sizeof(INT) )))
719 return FALSE;
720 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
722 /* convert into "dialog units"*/
723 for (i = 0; i < descr->nb_tabs; i++)
724 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
726 return TRUE;
730 /***********************************************************************
731 * LISTBOX_GetText
733 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
735 DWORD len;
737 if ((index < 0) || (index >= descr->nb_items))
739 SetLastError(ERROR_INVALID_INDEX);
740 return LB_ERR;
743 if (HAS_STRINGS(descr))
745 if (!buffer)
746 return strlenW(descr->items[index].str);
748 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
750 __TRY /* hide a Delphi bug that passes a read-only buffer */
752 strcpyW( buffer, descr->items[index].str );
753 len = strlenW(buffer);
755 __EXCEPT_PAGE_FAULT
757 WARN( "got an invalid buffer (Delphi bug?)\n" );
758 SetLastError( ERROR_INVALID_PARAMETER );
759 return LB_ERR;
761 __ENDTRY
762 } else
764 if (buffer)
765 *((DWORD *)buffer) = *(DWORD *)&descr->items[index].data;
766 len = sizeof(DWORD);
768 return len;
771 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
773 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
774 if (ret == CSTR_LESS_THAN)
775 return -1;
776 if (ret == CSTR_EQUAL)
777 return 0;
778 if (ret == CSTR_GREATER_THAN)
779 return 1;
780 return -1;
783 /***********************************************************************
784 * LISTBOX_FindStringPos
786 * Find the nearest string located before a given string in sort order.
787 * If 'exact' is TRUE, return an error if we don't get an exact match.
789 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
791 INT index, min, max, res;
793 if (!descr->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
795 min = 0;
796 max = descr->nb_items - 1;
797 while (min <= max)
799 index = (min + max) / 2;
800 if (HAS_STRINGS(descr))
801 res = LISTBOX_lstrcmpiW( descr->locale, descr->items[index].str, 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 = index;
813 cis.itemData1 = descr->items[index].data;
814 cis.itemID2 = -1;
815 cis.itemData2 = (ULONG_PTR)str;
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 - 1;
821 else min = index + 1;
823 return exact ? -1 : min;
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 column_width)
1262 RECT rect;
1264 TRACE("[%p]: new column width = %d\n", descr->self, column_width);
1266 GetClientRect(descr->self, &rect);
1267 descr->width = rect.right - rect.left;
1268 descr->height = rect.bottom - rect.top;
1269 descr->column_width = column_width;
1271 LISTBOX_UpdatePage(descr);
1272 LISTBOX_UpdateScroll(descr);
1273 return LB_OKAY;
1277 /***********************************************************************
1278 * LISTBOX_SetFont
1280 * Returns the item height.
1282 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1284 HDC hdc;
1285 HFONT oldFont = 0;
1286 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1287 SIZE sz;
1289 descr->font = font;
1291 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1293 ERR("unable to get DC.\n" );
1294 return 16;
1296 if (font) oldFont = SelectObject( hdc, font );
1297 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1298 if (oldFont) SelectObject( hdc, oldFont );
1299 ReleaseDC( descr->self, hdc );
1301 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1302 if (!IS_OWNERDRAW(descr))
1303 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1304 return sz.cy;
1308 /***********************************************************************
1309 * LISTBOX_MakeItemVisible
1311 * Make sure that a given item is partially or fully visible.
1313 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1315 INT top;
1317 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1319 if (index <= descr->top_item) top = index;
1320 else if (descr->style & LBS_MULTICOLUMN)
1322 INT cols = descr->width;
1323 if (!fully) cols += descr->column_width - 1;
1324 if (cols >= descr->column_width) cols /= descr->column_width;
1325 else cols = 1;
1326 if (index < descr->top_item + (descr->page_size * cols)) return;
1327 top = index - descr->page_size * (cols - 1);
1329 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1331 INT height = fully ? descr->items[index].height : 1;
1332 for (top = index; top > descr->top_item; top--)
1333 if ((height += descr->items[top-1].height) > descr->height) break;
1335 else
1337 if (index < descr->top_item + descr->page_size) return;
1338 if (!fully && (index == descr->top_item + descr->page_size) &&
1339 (descr->height > (descr->page_size * descr->item_height))) return;
1340 top = index - descr->page_size + 1;
1342 LISTBOX_SetTopItem( descr, top, TRUE );
1345 /***********************************************************************
1346 * LISTBOX_SetCaretIndex
1348 * NOTES
1349 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1352 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1354 INT oldfocus = descr->focus_item;
1356 TRACE("old focus %d, index %d\n", oldfocus, index);
1358 if (descr->style & LBS_NOSEL) return LB_ERR;
1359 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1360 if (index == oldfocus) return LB_OKAY;
1362 LISTBOX_DrawFocusRect( descr, FALSE );
1363 descr->focus_item = index;
1365 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1366 LISTBOX_DrawFocusRect( descr, TRUE );
1368 return LB_OKAY;
1372 /***********************************************************************
1373 * LISTBOX_SelectItemRange
1375 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1377 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1378 INT last, BOOL on )
1380 INT i;
1382 /* A few sanity checks */
1384 if (descr->style & LBS_NOSEL) return LB_ERR;
1385 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1387 if (!descr->nb_items) return LB_OKAY;
1389 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1390 if (first < 0) first = 0;
1391 if (last < first) return LB_OKAY;
1393 if (on) /* Turn selection on */
1395 for (i = first; i <= last; i++)
1397 if (descr->items[i].selected) continue;
1398 descr->items[i].selected = TRUE;
1399 LISTBOX_InvalidateItemRect(descr, i);
1402 else /* Turn selection off */
1404 for (i = first; i <= last; i++)
1406 if (!descr->items[i].selected) continue;
1407 descr->items[i].selected = FALSE;
1408 LISTBOX_InvalidateItemRect(descr, i);
1411 return LB_OKAY;
1414 /***********************************************************************
1415 * LISTBOX_SetSelection
1417 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1418 BOOL on, BOOL send_notify )
1420 TRACE( "cur_sel=%d index=%d notify=%s\n",
1421 descr->selected_item, index, send_notify ? "YES" : "NO" );
1423 if (descr->style & LBS_NOSEL)
1425 descr->selected_item = index;
1426 return LB_ERR;
1428 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1429 if (descr->style & LBS_MULTIPLESEL)
1431 if (index == -1) /* Select all items */
1432 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1433 else /* Only one item */
1434 return LISTBOX_SelectItemRange( descr, index, index, on );
1436 else
1438 INT oldsel = descr->selected_item;
1439 if (index == oldsel) return LB_OKAY;
1440 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1441 if (index != -1) descr->items[index].selected = TRUE;
1442 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1443 descr->selected_item = index;
1444 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1445 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1446 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1447 else
1448 if( descr->lphc ) /* set selection change flag for parent combo */
1449 descr->lphc->wState |= CBF_SELCHANGE;
1451 return LB_OKAY;
1455 /***********************************************************************
1456 * LISTBOX_MoveCaret
1458 * Change the caret position and extend the selection to the new caret.
1460 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1462 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1464 if ((index < 0) || (index >= descr->nb_items))
1465 return;
1467 /* Important, repaint needs to be done in this order if
1468 you want to mimic Windows behavior:
1469 1. Remove the focus and paint the item
1470 2. Remove the selection and paint the item(s)
1471 3. Set the selection and repaint the item(s)
1472 4. Set the focus to 'index' and repaint the item */
1474 /* 1. remove the focus and repaint the item */
1475 LISTBOX_DrawFocusRect( descr, FALSE );
1477 /* 2. then turn off the previous selection */
1478 /* 3. repaint the new selected item */
1479 if (descr->style & LBS_EXTENDEDSEL)
1481 if (descr->anchor_item != -1)
1483 INT first = min( index, descr->anchor_item );
1484 INT last = max( index, descr->anchor_item );
1485 if (first > 0)
1486 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1487 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1488 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1491 else if (!(descr->style & LBS_MULTIPLESEL))
1493 /* Set selection to new caret item */
1494 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1497 /* 4. repaint the new item with the focus */
1498 descr->focus_item = index;
1499 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1500 LISTBOX_DrawFocusRect( descr, TRUE );
1504 /***********************************************************************
1505 * LISTBOX_InsertItem
1507 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1508 LPWSTR str, ULONG_PTR data )
1510 LB_ITEMDATA *item;
1511 INT max_items;
1512 INT oldfocus = descr->focus_item;
1514 if (index == -1) index = descr->nb_items;
1515 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1516 if (!descr->items) max_items = 0;
1517 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1518 if (descr->nb_items == max_items)
1520 /* We need to grow the array */
1521 max_items += LB_ARRAY_GRANULARITY;
1522 if (descr->items)
1523 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1524 max_items * sizeof(LB_ITEMDATA) );
1525 else
1526 item = HeapAlloc( GetProcessHeap(), 0,
1527 max_items * sizeof(LB_ITEMDATA) );
1528 if (!item)
1530 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1531 return LB_ERRSPACE;
1533 descr->items = item;
1536 /* Insert the item structure */
1538 item = &descr->items[index];
1539 if (index < descr->nb_items)
1540 RtlMoveMemory( item + 1, item,
1541 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1542 item->str = str;
1543 item->data = HAS_STRINGS(descr) ? 0 : data;
1544 item->height = 0;
1545 item->selected = FALSE;
1546 descr->nb_items++;
1548 /* Get item height */
1550 if (descr->style & LBS_OWNERDRAWVARIABLE)
1552 MEASUREITEMSTRUCT mis;
1553 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1555 mis.CtlType = ODT_LISTBOX;
1556 mis.CtlID = id;
1557 mis.itemID = index;
1558 mis.itemData = data;
1559 mis.itemHeight = descr->item_height;
1560 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1561 item->height = mis.itemHeight ? mis.itemHeight : 1;
1562 TRACE("[%p]: measure item %d (%s) = %d\n",
1563 descr->self, index, str ? debugstr_w(str) : "", item->height );
1566 /* Repaint the items */
1568 LISTBOX_UpdateScroll( descr );
1569 LISTBOX_InvalidateItems( descr, index );
1571 /* Move selection and focused item */
1572 /* If listbox was empty, set focus to the first item */
1573 if (descr->nb_items == 1)
1574 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1575 /* single select don't change selection index in win31 */
1576 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1578 descr->selected_item++;
1579 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1581 else
1583 if (index <= descr->selected_item)
1585 descr->selected_item++;
1586 descr->focus_item = oldfocus; /* focus not changed */
1589 return LB_OKAY;
1593 /***********************************************************************
1594 * LISTBOX_InsertString
1596 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1598 LPWSTR new_str = NULL;
1599 LRESULT ret;
1601 if (HAS_STRINGS(descr))
1603 static const WCHAR empty_stringW[] = { 0 };
1604 if (!str) str = empty_stringW;
1605 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1607 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1608 return LB_ERRSPACE;
1610 strcpyW(new_str, str);
1613 if (index == -1) index = descr->nb_items;
1614 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1616 HeapFree( GetProcessHeap(), 0, new_str );
1617 return ret;
1620 TRACE("[%p]: added item %d %s\n",
1621 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1622 return index;
1626 /***********************************************************************
1627 * LISTBOX_DeleteItem
1629 * Delete the content of an item. 'index' must be a valid index.
1631 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1633 /* save the item data before it gets freed by LB_RESETCONTENT */
1634 ULONG_PTR item_data = descr->items[index].data;
1635 LPWSTR item_str = descr->items[index].str;
1637 if (!descr->nb_items)
1638 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1640 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1641 * while Win95 sends it for all items with user data.
1642 * It's probably better to send it too often than not
1643 * often enough, so this is what we do here.
1645 if (IS_OWNERDRAW(descr) || item_data)
1647 DELETEITEMSTRUCT dis;
1648 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1650 dis.CtlType = ODT_LISTBOX;
1651 dis.CtlID = id;
1652 dis.itemID = index;
1653 dis.hwndItem = descr->self;
1654 dis.itemData = item_data;
1655 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1657 if (HAS_STRINGS(descr))
1658 HeapFree( GetProcessHeap(), 0, item_str );
1662 /***********************************************************************
1663 * LISTBOX_RemoveItem
1665 * Remove an item from the listbox and delete its content.
1667 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1669 LB_ITEMDATA *item;
1670 INT max_items;
1672 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1674 /* We need to invalidate the original rect instead of the updated one. */
1675 LISTBOX_InvalidateItems( descr, index );
1677 descr->nb_items--;
1678 LISTBOX_DeleteItem( descr, index );
1680 if (!descr->nb_items) return LB_OKAY;
1682 /* Remove the item */
1684 item = &descr->items[index];
1685 if (index < descr->nb_items)
1686 RtlMoveMemory( item, item + 1,
1687 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1688 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1690 /* Shrink the item array if possible */
1692 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1693 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1695 max_items -= LB_ARRAY_GRANULARITY;
1696 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1697 max_items * sizeof(LB_ITEMDATA) );
1698 if (item) descr->items = item;
1700 /* Repaint the items */
1702 LISTBOX_UpdateScroll( descr );
1703 /* if we removed the scrollbar, reset the top of the list
1704 (correct for owner-drawn ???) */
1705 if (descr->nb_items == descr->page_size)
1706 LISTBOX_SetTopItem( descr, 0, TRUE );
1708 /* Move selection and focused item */
1709 if (!IS_MULTISELECT(descr))
1711 if (index == descr->selected_item)
1712 descr->selected_item = -1;
1713 else if (index < descr->selected_item)
1715 descr->selected_item--;
1716 if (ISWIN31) /* win 31 do not change the selected item number */
1717 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1721 if (descr->focus_item >= descr->nb_items)
1723 descr->focus_item = descr->nb_items - 1;
1724 if (descr->focus_item < 0) descr->focus_item = 0;
1726 return LB_OKAY;
1730 /***********************************************************************
1731 * LISTBOX_ResetContent
1733 static void LISTBOX_ResetContent( LB_DESCR *descr )
1735 INT i;
1737 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1738 HeapFree( GetProcessHeap(), 0, descr->items );
1739 descr->nb_items = 0;
1740 descr->top_item = 0;
1741 descr->selected_item = -1;
1742 descr->focus_item = 0;
1743 descr->anchor_item = -1;
1744 descr->items = NULL;
1748 /***********************************************************************
1749 * LISTBOX_SetCount
1751 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1753 LRESULT ret;
1755 if (HAS_STRINGS(descr))
1757 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1758 return LB_ERR;
1761 /* FIXME: this is far from optimal... */
1762 if (count > descr->nb_items)
1764 while (count > descr->nb_items)
1765 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1766 return ret;
1768 else if (count < descr->nb_items)
1770 while (count < descr->nb_items)
1771 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1772 return ret;
1775 InvalidateRect( descr->self, NULL, TRUE );
1776 return LB_OKAY;
1780 /***********************************************************************
1781 * LISTBOX_Directory
1783 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1784 LPCWSTR filespec, BOOL long_names )
1786 HANDLE handle;
1787 LRESULT ret = LB_OKAY;
1788 WIN32_FIND_DATAW entry;
1789 int pos;
1790 LRESULT maxinsert = LB_ERR;
1792 /* don't scan directory if we just want drives exclusively */
1793 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1794 /* scan directory */
1795 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1797 int le = GetLastError();
1798 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1800 else
1804 WCHAR buffer[270];
1805 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1807 static const WCHAR bracketW[] = { ']',0 };
1808 static const WCHAR dotW[] = { '.',0 };
1809 if (!(attrib & DDL_DIRECTORY) ||
1810 !strcmpW( entry.cFileName, dotW )) continue;
1811 buffer[0] = '[';
1812 if (!long_names && entry.cAlternateFileName[0])
1813 strcpyW( buffer + 1, entry.cAlternateFileName );
1814 else
1815 strcpyW( buffer + 1, entry.cFileName );
1816 strcatW(buffer, bracketW);
1818 else /* not a directory */
1820 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1821 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1823 if ((attrib & DDL_EXCLUSIVE) &&
1824 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1825 continue;
1826 #undef ATTRIBS
1827 if (!long_names && entry.cAlternateFileName[0])
1828 strcpyW( buffer, entry.cAlternateFileName );
1829 else
1830 strcpyW( buffer, entry.cFileName );
1832 if (!long_names) CharLowerW( buffer );
1833 pos = LISTBOX_FindFileStrPos( descr, buffer );
1834 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1835 break;
1836 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1837 } while (FindNextFileW( handle, &entry ));
1838 FindClose( handle );
1841 if (ret >= 0)
1843 ret = maxinsert;
1845 /* scan drives */
1846 if (attrib & DDL_DRIVES)
1848 WCHAR buffer[] = {'[','-','a','-',']',0};
1849 WCHAR root[] = {'A',':','\\',0};
1850 int drive;
1851 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1853 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1854 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1855 break;
1859 return ret;
1863 /***********************************************************************
1864 * LISTBOX_HandleVScroll
1866 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1868 SCROLLINFO info;
1870 if (descr->style & LBS_MULTICOLUMN) return 0;
1871 switch(scrollReq)
1873 case SB_LINEUP:
1874 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1875 break;
1876 case SB_LINEDOWN:
1877 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1878 break;
1879 case SB_PAGEUP:
1880 LISTBOX_SetTopItem( descr, descr->top_item -
1881 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1882 break;
1883 case SB_PAGEDOWN:
1884 LISTBOX_SetTopItem( descr, descr->top_item +
1885 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1886 break;
1887 case SB_THUMBPOSITION:
1888 LISTBOX_SetTopItem( descr, pos, TRUE );
1889 break;
1890 case SB_THUMBTRACK:
1891 info.cbSize = sizeof(info);
1892 info.fMask = SIF_TRACKPOS;
1893 GetScrollInfo( descr->self, SB_VERT, &info );
1894 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1895 break;
1896 case SB_TOP:
1897 LISTBOX_SetTopItem( descr, 0, TRUE );
1898 break;
1899 case SB_BOTTOM:
1900 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1901 break;
1903 return 0;
1907 /***********************************************************************
1908 * LISTBOX_HandleHScroll
1910 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1912 SCROLLINFO info;
1913 INT page;
1915 if (descr->style & LBS_MULTICOLUMN)
1917 switch(scrollReq)
1919 case SB_LINELEFT:
1920 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1921 TRUE );
1922 break;
1923 case SB_LINERIGHT:
1924 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1925 TRUE );
1926 break;
1927 case SB_PAGELEFT:
1928 page = descr->width / descr->column_width;
1929 if (page < 1) page = 1;
1930 LISTBOX_SetTopItem( descr,
1931 descr->top_item - page * descr->page_size, TRUE );
1932 break;
1933 case SB_PAGERIGHT:
1934 page = descr->width / descr->column_width;
1935 if (page < 1) page = 1;
1936 LISTBOX_SetTopItem( descr,
1937 descr->top_item + page * descr->page_size, TRUE );
1938 break;
1939 case SB_THUMBPOSITION:
1940 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1941 break;
1942 case SB_THUMBTRACK:
1943 info.cbSize = sizeof(info);
1944 info.fMask = SIF_TRACKPOS;
1945 GetScrollInfo( descr->self, SB_VERT, &info );
1946 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1947 TRUE );
1948 break;
1949 case SB_LEFT:
1950 LISTBOX_SetTopItem( descr, 0, TRUE );
1951 break;
1952 case SB_RIGHT:
1953 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1954 break;
1957 else if (descr->horz_extent)
1959 switch(scrollReq)
1961 case SB_LINELEFT:
1962 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1963 break;
1964 case SB_LINERIGHT:
1965 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1966 break;
1967 case SB_PAGELEFT:
1968 LISTBOX_SetHorizontalPos( descr,
1969 descr->horz_pos - descr->width );
1970 break;
1971 case SB_PAGERIGHT:
1972 LISTBOX_SetHorizontalPos( descr,
1973 descr->horz_pos + descr->width );
1974 break;
1975 case SB_THUMBPOSITION:
1976 LISTBOX_SetHorizontalPos( descr, pos );
1977 break;
1978 case SB_THUMBTRACK:
1979 info.cbSize = sizeof(info);
1980 info.fMask = SIF_TRACKPOS;
1981 GetScrollInfo( descr->self, SB_HORZ, &info );
1982 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1983 break;
1984 case SB_LEFT:
1985 LISTBOX_SetHorizontalPos( descr, 0 );
1986 break;
1987 case SB_RIGHT:
1988 LISTBOX_SetHorizontalPos( descr,
1989 descr->horz_extent - descr->width );
1990 break;
1993 return 0;
1996 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1998 UINT pulScrollLines = 3;
2000 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
2002 /* if scrolling changes direction, ignore left overs */
2003 if ((delta < 0 && descr->wheel_remain < 0) ||
2004 (delta > 0 && descr->wheel_remain > 0))
2005 descr->wheel_remain += delta;
2006 else
2007 descr->wheel_remain = delta;
2009 if (descr->wheel_remain && pulScrollLines)
2011 int cLineScroll;
2012 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2013 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2014 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2015 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2017 return 0;
2020 /***********************************************************************
2021 * LISTBOX_HandleLButtonDown
2023 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2025 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2027 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2028 descr->self, x, y, index, descr->focus_item);
2030 if (!descr->caret_on && (descr->in_focus)) return 0;
2032 if (!descr->in_focus)
2034 if( !descr->lphc ) SetFocus( descr->self );
2035 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2038 if (index == -1) return 0;
2040 if (!descr->lphc)
2042 if (descr->style & LBS_NOTIFY )
2043 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2044 MAKELPARAM( x, y ) );
2047 descr->captured = TRUE;
2048 SetCapture( descr->self );
2050 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2052 /* we should perhaps make sure that all items are deselected
2053 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2054 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2055 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2058 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2059 if (keys & MK_CONTROL)
2061 LISTBOX_SetCaretIndex( descr, index, FALSE );
2062 LISTBOX_SetSelection( descr, index,
2063 !descr->items[index].selected,
2064 (descr->style & LBS_NOTIFY) != 0);
2066 else
2068 LISTBOX_MoveCaret( descr, index, FALSE );
2070 if (descr->style & LBS_EXTENDEDSEL)
2072 LISTBOX_SetSelection( descr, index,
2073 descr->items[index].selected,
2074 (descr->style & LBS_NOTIFY) != 0 );
2076 else
2078 LISTBOX_SetSelection( descr, index,
2079 !descr->items[index].selected,
2080 (descr->style & LBS_NOTIFY) != 0 );
2084 else
2086 descr->anchor_item = index;
2087 LISTBOX_MoveCaret( descr, index, FALSE );
2088 LISTBOX_SetSelection( descr, index,
2089 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2092 if (!descr->lphc)
2094 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2096 POINT pt;
2098 pt.x = x;
2099 pt.y = y;
2101 if (DragDetect( descr->self, pt ))
2102 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2105 return 0;
2109 /*************************************************************************
2110 * LISTBOX_HandleLButtonDownCombo [Internal]
2112 * Process LButtonDown message for the ComboListBox
2114 * PARAMS
2115 * pWnd [I] The windows internal structure
2116 * pDescr [I] The ListBox internal structure
2117 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2118 * x [I] X Mouse Coordinate
2119 * y [I] Y Mouse Coordinate
2121 * RETURNS
2122 * 0 since we are processing the WM_LBUTTONDOWN Message
2124 * NOTES
2125 * This function is only to be used when a ListBox is a ComboListBox
2128 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2130 RECT clientRect, screenRect;
2131 POINT mousePos;
2133 mousePos.x = x;
2134 mousePos.y = y;
2136 GetClientRect(descr->self, &clientRect);
2138 if(PtInRect(&clientRect, mousePos))
2140 /* MousePos is in client, resume normal processing */
2141 if (msg == WM_LBUTTONDOWN)
2143 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2144 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2146 else if (descr->style & LBS_NOTIFY)
2147 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2149 else
2151 POINT screenMousePos;
2152 HWND hWndOldCapture;
2154 /* Check the Non-Client Area */
2155 screenMousePos = mousePos;
2156 hWndOldCapture = GetCapture();
2157 ReleaseCapture();
2158 GetWindowRect(descr->self, &screenRect);
2159 ClientToScreen(descr->self, &screenMousePos);
2161 if(!PtInRect(&screenRect, screenMousePos))
2163 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2164 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2165 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2167 else
2169 /* Check to see the NC is a scrollbar */
2170 INT nHitTestType=0;
2171 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2172 /* Check Vertical scroll bar */
2173 if (style & WS_VSCROLL)
2175 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2176 if (PtInRect( &clientRect, mousePos ))
2177 nHitTestType = HTVSCROLL;
2179 /* Check horizontal scroll bar */
2180 if (style & WS_HSCROLL)
2182 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2183 if (PtInRect( &clientRect, mousePos ))
2184 nHitTestType = HTHSCROLL;
2186 /* Windows sends this message when a scrollbar is clicked
2189 if(nHitTestType != 0)
2191 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2192 MAKELONG(screenMousePos.x, screenMousePos.y));
2194 /* Resume the Capture after scrolling is complete
2196 if(hWndOldCapture != 0)
2197 SetCapture(hWndOldCapture);
2200 return 0;
2203 /***********************************************************************
2204 * LISTBOX_HandleLButtonUp
2206 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2208 if (LISTBOX_Timer != LB_TIMER_NONE)
2209 KillSystemTimer( descr->self, LB_TIMER_ID );
2210 LISTBOX_Timer = LB_TIMER_NONE;
2211 if (descr->captured)
2213 descr->captured = FALSE;
2214 if (GetCapture() == descr->self) ReleaseCapture();
2215 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2216 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2218 return 0;
2222 /***********************************************************************
2223 * LISTBOX_HandleTimer
2225 * Handle scrolling upon a timer event.
2226 * Return TRUE if scrolling should continue.
2228 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2230 switch(dir)
2232 case LB_TIMER_UP:
2233 if (descr->top_item) index = descr->top_item - 1;
2234 else index = 0;
2235 break;
2236 case LB_TIMER_LEFT:
2237 if (descr->top_item) index -= descr->page_size;
2238 break;
2239 case LB_TIMER_DOWN:
2240 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2241 if (index == descr->focus_item) index++;
2242 if (index >= descr->nb_items) index = descr->nb_items - 1;
2243 break;
2244 case LB_TIMER_RIGHT:
2245 if (index + descr->page_size < descr->nb_items)
2246 index += descr->page_size;
2247 break;
2248 case LB_TIMER_NONE:
2249 break;
2251 if (index == descr->focus_item) return FALSE;
2252 LISTBOX_MoveCaret( descr, index, FALSE );
2253 return TRUE;
2257 /***********************************************************************
2258 * LISTBOX_HandleSystemTimer
2260 * WM_SYSTIMER handler.
2262 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2264 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2266 KillSystemTimer( descr->self, LB_TIMER_ID );
2267 LISTBOX_Timer = LB_TIMER_NONE;
2269 return 0;
2273 /***********************************************************************
2274 * LISTBOX_HandleMouseMove
2276 * WM_MOUSEMOVE handler.
2278 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2279 INT x, INT y )
2281 INT index;
2282 TIMER_DIRECTION dir = LB_TIMER_NONE;
2284 if (!descr->captured) return;
2286 if (descr->style & LBS_MULTICOLUMN)
2288 if (y < 0) y = 0;
2289 else if (y >= descr->item_height * descr->page_size)
2290 y = descr->item_height * descr->page_size - 1;
2292 if (x < 0)
2294 dir = LB_TIMER_LEFT;
2295 x = 0;
2297 else if (x >= descr->width)
2299 dir = LB_TIMER_RIGHT;
2300 x = descr->width - 1;
2303 else
2305 if (y < 0) dir = LB_TIMER_UP; /* above */
2306 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2309 index = LISTBOX_GetItemFromPoint( descr, x, y );
2310 if (index == -1) index = descr->focus_item;
2311 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2313 /* Start/stop the system timer */
2315 if (dir != LB_TIMER_NONE)
2316 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2317 else if (LISTBOX_Timer != LB_TIMER_NONE)
2318 KillSystemTimer( descr->self, LB_TIMER_ID );
2319 LISTBOX_Timer = dir;
2323 /***********************************************************************
2324 * LISTBOX_HandleKeyDown
2326 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2328 INT caret = -1;
2329 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2330 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2331 bForceSelection = FALSE; /* only for single select list */
2333 if (descr->style & LBS_WANTKEYBOARDINPUT)
2335 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2336 MAKEWPARAM(LOWORD(key), descr->focus_item),
2337 (LPARAM)descr->self );
2338 if (caret == -2) return 0;
2340 if (caret == -1) switch(key)
2342 case VK_LEFT:
2343 if (descr->style & LBS_MULTICOLUMN)
2345 bForceSelection = FALSE;
2346 if (descr->focus_item >= descr->page_size)
2347 caret = descr->focus_item - descr->page_size;
2348 break;
2350 /* fall through */
2351 case VK_UP:
2352 caret = descr->focus_item - 1;
2353 if (caret < 0) caret = 0;
2354 break;
2355 case VK_RIGHT:
2356 if (descr->style & LBS_MULTICOLUMN)
2358 bForceSelection = FALSE;
2359 caret = min(descr->focus_item + descr->page_size, descr->nb_items - 1);
2360 break;
2362 /* fall through */
2363 case VK_DOWN:
2364 caret = descr->focus_item + 1;
2365 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2366 break;
2368 case VK_PRIOR:
2369 if (descr->style & LBS_MULTICOLUMN)
2371 INT page = descr->width / descr->column_width;
2372 if (page < 1) page = 1;
2373 caret = descr->focus_item - (page * descr->page_size) + 1;
2375 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2376 if (caret < 0) caret = 0;
2377 break;
2378 case VK_NEXT:
2379 if (descr->style & LBS_MULTICOLUMN)
2381 INT page = descr->width / descr->column_width;
2382 if (page < 1) page = 1;
2383 caret = descr->focus_item + (page * descr->page_size) - 1;
2385 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2386 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2387 break;
2388 case VK_HOME:
2389 caret = 0;
2390 break;
2391 case VK_END:
2392 caret = descr->nb_items - 1;
2393 break;
2394 case VK_SPACE:
2395 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2396 else if (descr->style & LBS_MULTIPLESEL)
2398 LISTBOX_SetSelection( descr, descr->focus_item,
2399 !descr->items[descr->focus_item].selected,
2400 (descr->style & LBS_NOTIFY) != 0 );
2402 break;
2403 default:
2404 bForceSelection = FALSE;
2406 if (bForceSelection) /* focused item is used instead of key */
2407 caret = descr->focus_item;
2408 if (caret >= 0)
2410 if (((descr->style & LBS_EXTENDEDSEL) &&
2411 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2412 !IS_MULTISELECT(descr))
2413 descr->anchor_item = caret;
2414 LISTBOX_MoveCaret( descr, caret, TRUE );
2416 if (descr->style & LBS_MULTIPLESEL)
2417 descr->selected_item = caret;
2418 else
2419 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2420 if (descr->style & LBS_NOTIFY)
2422 if (descr->lphc && IsWindowVisible( descr->self ))
2424 /* make sure that combo parent doesn't hide us */
2425 descr->lphc->wState |= CBF_NOROLLUP;
2427 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2430 return 0;
2434 /***********************************************************************
2435 * LISTBOX_HandleChar
2437 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2439 INT caret = -1;
2440 WCHAR str[2];
2442 str[0] = charW;
2443 str[1] = '\0';
2445 if (descr->style & LBS_WANTKEYBOARDINPUT)
2447 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2448 MAKEWPARAM(charW, descr->focus_item),
2449 (LPARAM)descr->self );
2450 if (caret == -2) return 0;
2452 if (caret == -1)
2453 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2454 if (caret != -1)
2456 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2457 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2458 LISTBOX_MoveCaret( descr, caret, TRUE );
2459 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2460 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2462 return 0;
2466 /***********************************************************************
2467 * LISTBOX_Create
2469 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2471 LB_DESCR *descr;
2472 MEASUREITEMSTRUCT mis;
2473 RECT rect;
2475 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2476 return FALSE;
2478 GetClientRect( hwnd, &rect );
2479 descr->self = hwnd;
2480 descr->owner = GetParent( descr->self );
2481 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2482 descr->width = rect.right - rect.left;
2483 descr->height = rect.bottom - rect.top;
2484 descr->items = NULL;
2485 descr->nb_items = 0;
2486 descr->top_item = 0;
2487 descr->selected_item = -1;
2488 descr->focus_item = 0;
2489 descr->anchor_item = -1;
2490 descr->item_height = 1;
2491 descr->page_size = 1;
2492 descr->column_width = 150;
2493 descr->horz_extent = 0;
2494 descr->horz_pos = 0;
2495 descr->nb_tabs = 0;
2496 descr->tabs = NULL;
2497 descr->wheel_remain = 0;
2498 descr->caret_on = !lphc;
2499 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2500 descr->in_focus = FALSE;
2501 descr->captured = FALSE;
2502 descr->font = 0;
2503 descr->locale = GetUserDefaultLCID();
2504 descr->lphc = lphc;
2506 if( lphc )
2508 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2509 descr->owner = lphc->self;
2512 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2514 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2516 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2517 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2518 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2519 descr->item_height = LISTBOX_SetFont( descr, 0 );
2521 if (descr->style & LBS_OWNERDRAWFIXED)
2523 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2525 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2526 descr->item_height = lphc->fixedOwnerDrawHeight;
2528 else
2530 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2531 mis.CtlType = ODT_LISTBOX;
2532 mis.CtlID = id;
2533 mis.itemID = -1;
2534 mis.itemWidth = 0;
2535 mis.itemData = 0;
2536 mis.itemHeight = descr->item_height;
2537 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2538 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2542 OpenThemeData( descr->self, WC_LISTBOXW );
2544 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2545 return TRUE;
2549 /***********************************************************************
2550 * LISTBOX_Destroy
2552 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2554 HTHEME theme = GetWindowTheme( descr->self );
2555 CloseThemeData( theme );
2556 LISTBOX_ResetContent( descr );
2557 SetWindowLongPtrW( descr->self, 0, 0 );
2558 HeapFree( GetProcessHeap(), 0, descr );
2559 return TRUE;
2563 /***********************************************************************
2564 * ListBoxWndProc_common
2566 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2568 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2569 HEADCOMBO *lphc = NULL;
2570 HTHEME theme;
2571 LRESULT ret;
2573 if (!descr)
2575 if (!IsWindow(hwnd)) return 0;
2577 if (msg == WM_CREATE)
2579 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2580 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2581 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2582 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2583 return 0;
2585 /* Ignore all other messages before we get a WM_CREATE */
2586 return DefWindowProcW( hwnd, msg, wParam, lParam );
2588 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2590 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr->self, msg, wParam, lParam );
2592 switch(msg)
2594 case LB_RESETCONTENT:
2595 LISTBOX_ResetContent( descr );
2596 LISTBOX_UpdateScroll( descr );
2597 InvalidateRect( descr->self, NULL, TRUE );
2598 return 0;
2600 case LB_ADDSTRING:
2602 const WCHAR *textW = (const WCHAR *)lParam;
2603 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2604 return LISTBOX_InsertString( descr, index, textW );
2607 case LB_INSERTSTRING:
2608 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2610 case LB_ADDFILE:
2612 const WCHAR *textW = (const WCHAR *)lParam;
2613 INT index = LISTBOX_FindFileStrPos( descr, textW );
2614 return LISTBOX_InsertString( descr, index, textW );
2617 case LB_DELETESTRING:
2618 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2619 return descr->nb_items;
2620 else
2622 SetLastError(ERROR_INVALID_INDEX);
2623 return LB_ERR;
2626 case LB_GETITEMDATA:
2627 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2629 SetLastError(ERROR_INVALID_INDEX);
2630 return LB_ERR;
2632 return descr->items[wParam].data;
2634 case LB_SETITEMDATA:
2635 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2637 SetLastError(ERROR_INVALID_INDEX);
2638 return LB_ERR;
2640 descr->items[wParam].data = lParam;
2641 /* undocumented: returns TRUE, not LB_OKAY (0) */
2642 return TRUE;
2644 case LB_GETCOUNT:
2645 return descr->nb_items;
2647 case LB_GETTEXT:
2648 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2650 case LB_GETTEXTLEN:
2651 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2653 SetLastError(ERROR_INVALID_INDEX);
2654 return LB_ERR;
2656 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2657 return strlenW( descr->items[wParam].str );
2659 case LB_GETCURSEL:
2660 if (descr->nb_items == 0)
2661 return LB_ERR;
2662 if (!IS_MULTISELECT(descr))
2663 return descr->selected_item;
2664 if (descr->selected_item != -1)
2665 return descr->selected_item;
2666 return descr->focus_item;
2667 /* otherwise, if the user tries to move the selection with the */
2668 /* arrow keys, we will give the application something to choke on */
2669 case LB_GETTOPINDEX:
2670 return descr->top_item;
2672 case LB_GETITEMHEIGHT:
2673 return LISTBOX_GetItemHeight( descr, wParam );
2675 case LB_SETITEMHEIGHT:
2676 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2678 case LB_ITEMFROMPOINT:
2680 POINT pt;
2681 RECT rect;
2682 int index;
2683 BOOL hit = TRUE;
2685 /* The hiword of the return value is not a client area
2686 hittest as suggested by MSDN, but rather a hittest on
2687 the returned listbox item. */
2689 if(descr->nb_items == 0)
2690 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2692 pt.x = (short)LOWORD(lParam);
2693 pt.y = (short)HIWORD(lParam);
2695 SetRect(&rect, 0, 0, descr->width, descr->height);
2697 if(!PtInRect(&rect, pt))
2699 pt.x = min(pt.x, rect.right - 1);
2700 pt.x = max(pt.x, 0);
2701 pt.y = min(pt.y, rect.bottom - 1);
2702 pt.y = max(pt.y, 0);
2703 hit = FALSE;
2706 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2708 if(index == -1)
2710 index = descr->nb_items - 1;
2711 hit = FALSE;
2713 return MAKELONG(index, hit ? 0 : 1);
2716 case LB_SETCARETINDEX:
2717 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2718 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2719 return LB_ERR;
2720 else if (ISWIN31)
2721 return wParam;
2722 else
2723 return LB_OKAY;
2725 case LB_GETCARETINDEX:
2726 return descr->focus_item;
2728 case LB_SETTOPINDEX:
2729 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2731 case LB_SETCOLUMNWIDTH:
2732 return LISTBOX_SetColumnWidth( descr, wParam );
2734 case LB_GETITEMRECT:
2735 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2737 case LB_FINDSTRING:
2738 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2740 case LB_FINDSTRINGEXACT:
2741 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2743 case LB_SELECTSTRING:
2745 const WCHAR *textW = (const WCHAR *)lParam;
2746 INT index;
2748 if (HAS_STRINGS(descr))
2749 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2751 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2752 if (index != LB_ERR)
2754 LISTBOX_MoveCaret( descr, index, TRUE );
2755 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2757 return index;
2760 case LB_GETSEL:
2761 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2762 return LB_ERR;
2763 return descr->items[wParam].selected;
2765 case LB_SETSEL:
2766 ret = LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2767 if (ret != LB_ERR && wParam)
2768 descr->anchor_item = lParam;
2769 return ret;
2771 case LB_SETCURSEL:
2772 if (IS_MULTISELECT(descr)) return LB_ERR;
2773 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2774 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2775 if (ret != LB_ERR) ret = descr->selected_item;
2776 return ret;
2778 case LB_GETSELCOUNT:
2779 return LISTBOX_GetSelCount( descr );
2781 case LB_GETSELITEMS:
2782 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2784 case LB_SELITEMRANGE:
2785 if (LOWORD(lParam) <= HIWORD(lParam))
2786 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2787 HIWORD(lParam), wParam );
2788 else
2789 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2790 LOWORD(lParam), wParam );
2792 case LB_SELITEMRANGEEX:
2793 if ((INT)lParam >= (INT)wParam)
2794 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2795 else
2796 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2798 case LB_GETHORIZONTALEXTENT:
2799 return descr->horz_extent;
2801 case LB_SETHORIZONTALEXTENT:
2802 return LISTBOX_SetHorizontalExtent( descr, wParam );
2804 case LB_GETANCHORINDEX:
2805 return descr->anchor_item;
2807 case LB_SETANCHORINDEX:
2808 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2810 SetLastError(ERROR_INVALID_INDEX);
2811 return LB_ERR;
2813 descr->anchor_item = (INT)wParam;
2814 return LB_OKAY;
2816 case LB_DIR:
2817 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2819 case LB_GETLOCALE:
2820 return descr->locale;
2822 case LB_SETLOCALE:
2824 LCID ret;
2825 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2826 return LB_ERR;
2827 ret = descr->locale;
2828 descr->locale = (LCID)wParam;
2829 return ret;
2832 case LB_INITSTORAGE:
2833 return LISTBOX_InitStorage( descr, wParam );
2835 case LB_SETCOUNT:
2836 return LISTBOX_SetCount( descr, (INT)wParam );
2838 case LB_SETTABSTOPS:
2839 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2841 case LB_CARETON:
2842 if (descr->caret_on)
2843 return LB_OKAY;
2844 descr->caret_on = TRUE;
2845 if ((descr->focus_item != -1) && (descr->in_focus))
2846 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2847 return LB_OKAY;
2849 case LB_CARETOFF:
2850 if (!descr->caret_on)
2851 return LB_OKAY;
2852 descr->caret_on = FALSE;
2853 if ((descr->focus_item != -1) && (descr->in_focus))
2854 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2855 return LB_OKAY;
2857 case LB_GETLISTBOXINFO:
2858 return descr->page_size;
2860 case WM_DESTROY:
2861 return LISTBOX_Destroy( descr );
2863 case WM_ENABLE:
2864 InvalidateRect( descr->self, NULL, TRUE );
2865 return 0;
2867 case WM_SETREDRAW:
2868 LISTBOX_SetRedraw( descr, wParam != 0 );
2869 return 0;
2871 case WM_GETDLGCODE:
2872 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2874 case WM_PRINTCLIENT:
2875 case WM_PAINT:
2877 PAINTSTRUCT ps;
2878 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2879 ret = LISTBOX_Paint( descr, hdc );
2880 if( !wParam ) EndPaint( descr->self, &ps );
2882 return ret;
2884 case WM_NCPAINT:
2885 LISTBOX_NCPaint( descr, (HRGN)wParam );
2886 break;
2888 case WM_SIZE:
2889 LISTBOX_UpdateSize( descr );
2890 return 0;
2891 case WM_GETFONT:
2892 return (LRESULT)descr->font;
2893 case WM_SETFONT:
2894 LISTBOX_SetFont( descr, (HFONT)wParam );
2895 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2896 return 0;
2897 case WM_SETFOCUS:
2898 descr->in_focus = TRUE;
2899 descr->caret_on = TRUE;
2900 if (descr->focus_item != -1)
2901 LISTBOX_DrawFocusRect( descr, TRUE );
2902 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2903 return 0;
2904 case WM_KILLFOCUS:
2905 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
2906 descr->in_focus = FALSE;
2907 descr->wheel_remain = 0;
2908 if ((descr->focus_item != -1) && descr->caret_on)
2909 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2910 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
2911 return 0;
2912 case WM_HSCROLL:
2913 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2914 case WM_VSCROLL:
2915 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2916 case WM_MOUSEWHEEL:
2917 if (wParam & (MK_SHIFT | MK_CONTROL))
2918 return DefWindowProcW( descr->self, msg, wParam, lParam );
2919 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
2920 case WM_LBUTTONDOWN:
2921 if (lphc)
2922 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2923 (INT16)LOWORD(lParam),
2924 (INT16)HIWORD(lParam) );
2925 return LISTBOX_HandleLButtonDown( descr, wParam,
2926 (INT16)LOWORD(lParam),
2927 (INT16)HIWORD(lParam) );
2928 case WM_LBUTTONDBLCLK:
2929 if (lphc)
2930 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2931 (INT16)LOWORD(lParam),
2932 (INT16)HIWORD(lParam) );
2933 if (descr->style & LBS_NOTIFY)
2934 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2935 return 0;
2936 case WM_MOUSEMOVE:
2937 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
2939 BOOL captured = descr->captured;
2940 POINT mousePos;
2941 RECT clientRect;
2943 mousePos.x = (INT16)LOWORD(lParam);
2944 mousePos.y = (INT16)HIWORD(lParam);
2947 * If we are in a dropdown combobox, we simulate that
2948 * the mouse is captured to show the tracking of the item.
2950 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
2951 descr->captured = TRUE;
2953 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
2955 descr->captured = captured;
2957 else if (GetCapture() == descr->self)
2959 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
2960 (INT16)HIWORD(lParam) );
2962 return 0;
2963 case WM_LBUTTONUP:
2964 if (lphc)
2966 POINT mousePos;
2967 RECT clientRect;
2970 * If the mouse button "up" is not in the listbox,
2971 * we make sure there is no selection by re-selecting the
2972 * item that was selected when the listbox was made visible.
2974 mousePos.x = (INT16)LOWORD(lParam);
2975 mousePos.y = (INT16)HIWORD(lParam);
2977 GetClientRect(descr->self, &clientRect);
2980 * When the user clicks outside the combobox and the focus
2981 * is lost, the owning combobox will send a fake buttonup with
2982 * 0xFFFFFFF as the mouse location, we must also revert the
2983 * selection to the original selection.
2985 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
2986 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
2988 return LISTBOX_HandleLButtonUp( descr );
2989 case WM_KEYDOWN:
2990 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
2992 /* for some reason Windows makes it possible to
2993 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2995 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2996 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2997 && (wParam == VK_DOWN || wParam == VK_UP)) )
2999 COMBO_FlipListbox( lphc, FALSE, FALSE );
3000 return 0;
3003 return LISTBOX_HandleKeyDown( descr, wParam );
3004 case WM_CHAR:
3005 return LISTBOX_HandleChar( descr, wParam );
3007 case WM_SYSTIMER:
3008 return LISTBOX_HandleSystemTimer( descr );
3009 case WM_ERASEBKGND:
3010 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3012 RECT rect;
3013 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3014 wParam, (LPARAM)descr->self );
3015 TRACE("hbrush = %p\n", hbrush);
3016 if(!hbrush)
3017 hbrush = GetSysColorBrush(COLOR_WINDOW);
3018 if(hbrush)
3020 GetClientRect(descr->self, &rect);
3021 FillRect((HDC)wParam, &rect, hbrush);
3024 return 1;
3025 case WM_DROPFILES:
3026 if( lphc ) return 0;
3027 return SendMessageW( descr->owner, msg, wParam, lParam );
3029 case WM_NCDESTROY:
3030 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3031 lphc->hWndLBox = 0;
3032 break;
3034 case WM_NCACTIVATE:
3035 if (lphc) return 0;
3036 break;
3038 case WM_THEMECHANGED:
3039 theme = GetWindowTheme( hwnd );
3040 CloseThemeData( theme );
3041 OpenThemeData( hwnd, WC_LISTBOXW );
3042 break;
3044 default:
3045 if ((msg >= WM_USER) && (msg < 0xc000))
3046 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3047 hwnd, msg, wParam, lParam );
3050 return DefWindowProcW( hwnd, msg, wParam, lParam );
3053 void LISTBOX_Register(void)
3055 WNDCLASSW wndClass;
3057 memset(&wndClass, 0, sizeof(wndClass));
3058 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3059 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3060 wndClass.cbClsExtra = 0;
3061 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3062 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3063 wndClass.hbrBackground = NULL;
3064 wndClass.lpszClassName = WC_LISTBOXW;
3065 RegisterClassW(&wndClass);
3068 void COMBOLBOX_Register(void)
3070 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
3071 WNDCLASSW wndClass;
3073 memset(&wndClass, 0, sizeof(wndClass));
3074 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
3075 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3076 wndClass.cbClsExtra = 0;
3077 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3078 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3079 wndClass.hbrBackground = NULL;
3080 wndClass.lpszClassName = combolboxW;
3081 RegisterClassW(&wndClass);