comctl32/listbox: Fix the listbox sorting algorithm.
[wine.git] / dlls / comctl32 / listbox.c
blobe033975f4142fcf53b50679605a85f8b87f7d9ed
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->nb_items || !(descr->style & LBS_SORT)) return -1; /* Add it at the end */
796 min = 0;
797 max = descr->nb_items - 1;
798 while (min <= max)
800 index = (min + max) / 2;
801 if (HAS_STRINGS(descr))
802 res = LISTBOX_lstrcmpiW( descr->locale, descr->items[index].str, str );
803 else
805 COMPAREITEMSTRUCT cis;
806 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
808 cis.CtlType = ODT_LISTBOX;
809 cis.CtlID = id;
810 cis.hwndItem = descr->self;
811 /* note that some application (MetaStock) expects the second item
812 * to be in the listbox */
813 cis.itemID1 = index;
814 cis.itemData1 = descr->items[index].data;
815 cis.itemID2 = -1;
816 cis.itemData2 = (ULONG_PTR)str;
817 cis.dwLocaleId = descr->locale;
818 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
820 if (!res) return index;
821 if (res > 0) max = index - 1;
822 else min = index + 1;
824 return exact ? -1 : min;
828 /***********************************************************************
829 * LISTBOX_FindFileStrPos
831 * Find the nearest string located before a given string in directory
832 * sort order (i.e. first files, then directories, then drives).
834 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
836 INT min, max, res;
838 if (!HAS_STRINGS(descr))
839 return LISTBOX_FindStringPos( descr, str, FALSE );
840 min = 0;
841 max = descr->nb_items;
842 while (min != max)
844 INT index = (min + max) / 2;
845 LPCWSTR p = descr->items[index].str;
846 if (*p == '[') /* drive or directory */
848 if (*str != '[') res = -1;
849 else if (p[1] == '-') /* drive */
851 if (str[1] == '-') res = str[2] - p[2];
852 else res = -1;
854 else /* directory */
856 if (str[1] == '-') res = 1;
857 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
860 else /* filename */
862 if (*str == '[') res = 1;
863 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
865 if (!res) return index;
866 if (res < 0) max = index;
867 else min = index + 1;
869 return max;
873 /***********************************************************************
874 * LISTBOX_FindString
876 * Find the item beginning with a given string.
878 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
880 INT i;
881 LB_ITEMDATA *item;
883 if (start >= descr->nb_items) start = -1;
884 item = descr->items + start + 1;
885 if (HAS_STRINGS(descr))
887 if (!str || ! str[0] ) return LB_ERR;
888 if (exact)
890 for (i = start + 1; i < descr->nb_items; i++, item++)
891 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
892 for (i = 0, item = descr->items; i <= start; i++, item++)
893 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
895 else
897 /* Special case for drives and directories: ignore prefix */
898 #define CHECK_DRIVE(item) \
899 if ((item)->str[0] == '[') \
901 if (!strncmpiW( str, (item)->str+1, len )) return i; \
902 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
903 return i; \
906 INT len = strlenW(str);
907 for (i = start + 1; i < descr->nb_items; i++, item++)
909 if (!strncmpiW( str, item->str, len )) return i;
910 CHECK_DRIVE(item);
912 for (i = 0, item = descr->items; i <= start; i++, item++)
914 if (!strncmpiW( str, item->str, len )) return i;
915 CHECK_DRIVE(item);
917 #undef CHECK_DRIVE
920 else
922 if (exact && (descr->style & LBS_SORT))
923 /* If sorted, use a WM_COMPAREITEM binary search */
924 return LISTBOX_FindStringPos( descr, str, TRUE );
926 /* Otherwise use a linear search */
927 for (i = start + 1; i < descr->nb_items; i++, item++)
928 if (item->data == (ULONG_PTR)str) return i;
929 for (i = 0, item = descr->items; i <= start; i++, item++)
930 if (item->data == (ULONG_PTR)str) return i;
932 return LB_ERR;
936 /***********************************************************************
937 * LISTBOX_GetSelCount
939 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
941 INT i, count;
942 const LB_ITEMDATA *item = descr->items;
944 if (!(descr->style & LBS_MULTIPLESEL) ||
945 (descr->style & LBS_NOSEL))
946 return LB_ERR;
947 for (i = count = 0; i < descr->nb_items; i++, item++)
948 if (item->selected) count++;
949 return count;
953 /***********************************************************************
954 * LISTBOX_GetSelItems
956 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
958 INT i, count;
959 const LB_ITEMDATA *item = descr->items;
961 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
962 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
963 if (item->selected) array[count++] = i;
964 return count;
968 /***********************************************************************
969 * LISTBOX_Paint
971 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
973 INT i, col_pos = descr->page_size - 1;
974 RECT rect;
975 RECT focusRect = {-1, -1, -1, -1};
976 HFONT oldFont = 0;
977 HBRUSH hbrush, oldBrush = 0;
979 if (descr->style & LBS_NOREDRAW) return 0;
981 SetRect( &rect, 0, 0, descr->width, descr->height );
982 if (descr->style & LBS_MULTICOLUMN)
983 rect.right = rect.left + descr->column_width;
984 else if (descr->horz_pos)
986 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
987 rect.right += descr->horz_pos;
990 if (descr->font) oldFont = SelectObject( hdc, descr->font );
991 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
992 (WPARAM)hdc, (LPARAM)descr->self );
993 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
994 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
996 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
997 (descr->in_focus))
999 /* Special case for empty listbox: paint focus rect */
1000 rect.bottom = rect.top + descr->item_height;
1001 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1002 &rect, NULL, 0, NULL );
1003 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1004 rect.top = rect.bottom;
1007 /* Paint all the item, regarding the selection
1008 Focus state will be painted after */
1010 for (i = descr->top_item; i < descr->nb_items; i++)
1012 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1013 rect.bottom = rect.top + descr->item_height;
1014 else
1015 rect.bottom = rect.top + descr->items[i].height;
1017 /* keep the focus rect, to paint the focus item after */
1018 if (i == descr->focus_item)
1019 focusRect = rect;
1021 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1022 rect.top = rect.bottom;
1024 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1026 if (!IS_OWNERDRAW(descr))
1028 /* Clear the bottom of the column */
1029 if (rect.top < descr->height)
1031 rect.bottom = descr->height;
1032 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1033 &rect, NULL, 0, NULL );
1037 /* Go to the next column */
1038 rect.left += descr->column_width;
1039 rect.right += descr->column_width;
1040 rect.top = 0;
1041 col_pos = descr->page_size - 1;
1043 else
1045 col_pos--;
1046 if (rect.top >= descr->height) break;
1050 /* Paint the focus item now */
1051 if (focusRect.top != focusRect.bottom &&
1052 descr->caret_on && descr->in_focus)
1053 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1055 if (!IS_OWNERDRAW(descr))
1057 /* Clear the remainder of the client area */
1058 if (rect.top < descr->height)
1060 rect.bottom = descr->height;
1061 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1062 &rect, NULL, 0, NULL );
1064 if (rect.right < descr->width)
1066 rect.left = rect.right;
1067 rect.right = descr->width;
1068 rect.top = 0;
1069 rect.bottom = descr->height;
1070 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1071 &rect, NULL, 0, NULL );
1074 if (oldFont) SelectObject( hdc, oldFont );
1075 if (oldBrush) SelectObject( hdc, oldBrush );
1076 return 0;
1079 static void LISTBOX_NCPaint( LB_DESCR *descr, HRGN region )
1081 DWORD exstyle = GetWindowLongW( descr->self, GWL_EXSTYLE);
1082 HTHEME theme = GetWindowTheme( descr->self );
1083 HRGN cliprgn = region;
1084 int cxEdge, cyEdge;
1085 HDC hdc;
1086 RECT r;
1088 if (!theme || !(exstyle & WS_EX_CLIENTEDGE))
1089 return;
1091 cxEdge = GetSystemMetrics(SM_CXEDGE),
1092 cyEdge = GetSystemMetrics(SM_CYEDGE);
1094 GetWindowRect(descr->self, &r);
1096 /* New clipping region passed to default proc to exclude border */
1097 cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge,
1098 r.right - cxEdge, r.bottom - cyEdge);
1099 if (region != (HRGN)1)
1100 CombineRgn(cliprgn, cliprgn, region, RGN_AND);
1101 OffsetRect(&r, -r.left, -r.top);
1103 hdc = GetDCEx(descr->self, region, DCX_WINDOW|DCX_INTERSECTRGN);
1104 OffsetRect(&r, -r.left, -r.top);
1106 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
1107 DrawThemeParentBackground(descr->self, hdc, &r);
1108 DrawThemeBackground (theme, hdc, 0, 0, &r, 0);
1109 ReleaseDC(descr->self, hdc);
1112 /***********************************************************************
1113 * LISTBOX_InvalidateItems
1115 * Invalidate all items from a given item. If the specified item is not
1116 * visible, nothing happens.
1118 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1120 RECT rect;
1122 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1124 if (descr->style & LBS_NOREDRAW)
1126 descr->style |= LBS_DISPLAYCHANGED;
1127 return;
1129 rect.bottom = descr->height;
1130 InvalidateRect( descr->self, &rect, TRUE );
1131 if (descr->style & LBS_MULTICOLUMN)
1133 /* Repaint the other columns */
1134 rect.left = rect.right;
1135 rect.right = descr->width;
1136 rect.top = 0;
1137 InvalidateRect( descr->self, &rect, TRUE );
1142 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1144 RECT rect;
1146 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1147 InvalidateRect( descr->self, &rect, TRUE );
1150 /***********************************************************************
1151 * LISTBOX_GetItemHeight
1153 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1155 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1157 if ((index < 0) || (index >= descr->nb_items))
1159 SetLastError(ERROR_INVALID_INDEX);
1160 return LB_ERR;
1162 return descr->items[index].height;
1164 else return descr->item_height;
1168 /***********************************************************************
1169 * LISTBOX_SetItemHeight
1171 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1173 if (height > MAXBYTE)
1174 return -1;
1176 if (!height) height = 1;
1178 if (descr->style & LBS_OWNERDRAWVARIABLE)
1180 if ((index < 0) || (index >= descr->nb_items))
1182 SetLastError(ERROR_INVALID_INDEX);
1183 return LB_ERR;
1185 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1186 descr->items[index].height = height;
1187 LISTBOX_UpdateScroll( descr );
1188 if (repaint)
1189 LISTBOX_InvalidateItems( descr, index );
1191 else if (height != descr->item_height)
1193 TRACE("[%p]: new height = %d\n", descr->self, height );
1194 descr->item_height = height;
1195 LISTBOX_UpdatePage( descr );
1196 LISTBOX_UpdateScroll( descr );
1197 if (repaint)
1198 InvalidateRect( descr->self, 0, TRUE );
1200 return LB_OKAY;
1204 /***********************************************************************
1205 * LISTBOX_SetHorizontalPos
1207 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1209 INT diff;
1211 if (pos > descr->horz_extent - descr->width)
1212 pos = descr->horz_extent - descr->width;
1213 if (pos < 0) pos = 0;
1214 if (!(diff = descr->horz_pos - pos)) return;
1215 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1216 descr->horz_pos = pos;
1217 LISTBOX_UpdateScroll( descr );
1218 if (abs(diff) < descr->width)
1220 RECT rect;
1221 /* Invalidate the focused item so it will be repainted correctly */
1222 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1223 InvalidateRect( descr->self, &rect, TRUE );
1224 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1225 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1227 else
1228 InvalidateRect( descr->self, NULL, TRUE );
1232 /***********************************************************************
1233 * LISTBOX_SetHorizontalExtent
1235 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1237 if (descr->style & LBS_MULTICOLUMN)
1238 return LB_OKAY;
1239 if (extent == descr->horz_extent) return LB_OKAY;
1240 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1241 descr->horz_extent = extent;
1242 if (descr->style & WS_HSCROLL) {
1243 SCROLLINFO info;
1244 info.cbSize = sizeof(info);
1245 info.nMin = 0;
1246 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1247 info.fMask = SIF_RANGE;
1248 if (descr->style & LBS_DISABLENOSCROLL)
1249 info.fMask |= SIF_DISABLENOSCROLL;
1250 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1252 if (descr->horz_pos > extent - descr->width)
1253 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1254 return LB_OKAY;
1258 /***********************************************************************
1259 * LISTBOX_SetColumnWidth
1261 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1263 if (width == descr->column_width) return LB_OKAY;
1264 TRACE("[%p]: new column width = %d\n", descr->self, width );
1265 descr->column_width = width;
1266 LISTBOX_UpdatePage( descr );
1267 return LB_OKAY;
1271 /***********************************************************************
1272 * LISTBOX_SetFont
1274 * Returns the item height.
1276 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1278 HDC hdc;
1279 HFONT oldFont = 0;
1280 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1281 SIZE sz;
1283 descr->font = font;
1285 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1287 ERR("unable to get DC.\n" );
1288 return 16;
1290 if (font) oldFont = SelectObject( hdc, font );
1291 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1292 if (oldFont) SelectObject( hdc, oldFont );
1293 ReleaseDC( descr->self, hdc );
1295 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1296 if (!IS_OWNERDRAW(descr))
1297 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1298 return sz.cy;
1302 /***********************************************************************
1303 * LISTBOX_MakeItemVisible
1305 * Make sure that a given item is partially or fully visible.
1307 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1309 INT top;
1311 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1313 if (index <= descr->top_item) top = index;
1314 else if (descr->style & LBS_MULTICOLUMN)
1316 INT cols = descr->width;
1317 if (!fully) cols += descr->column_width - 1;
1318 if (cols >= descr->column_width) cols /= descr->column_width;
1319 else cols = 1;
1320 if (index < descr->top_item + (descr->page_size * cols)) return;
1321 top = index - descr->page_size * (cols - 1);
1323 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1325 INT height = fully ? descr->items[index].height : 1;
1326 for (top = index; top > descr->top_item; top--)
1327 if ((height += descr->items[top-1].height) > descr->height) break;
1329 else
1331 if (index < descr->top_item + descr->page_size) return;
1332 if (!fully && (index == descr->top_item + descr->page_size) &&
1333 (descr->height > (descr->page_size * descr->item_height))) return;
1334 top = index - descr->page_size + 1;
1336 LISTBOX_SetTopItem( descr, top, TRUE );
1339 /***********************************************************************
1340 * LISTBOX_SetCaretIndex
1342 * NOTES
1343 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1346 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1348 INT oldfocus = descr->focus_item;
1350 TRACE("old focus %d, index %d\n", oldfocus, index);
1352 if (descr->style & LBS_NOSEL) return LB_ERR;
1353 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1354 if (index == oldfocus) return LB_OKAY;
1356 LISTBOX_DrawFocusRect( descr, FALSE );
1357 descr->focus_item = index;
1359 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1360 LISTBOX_DrawFocusRect( descr, TRUE );
1362 return LB_OKAY;
1366 /***********************************************************************
1367 * LISTBOX_SelectItemRange
1369 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1371 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1372 INT last, BOOL on )
1374 INT i;
1376 /* A few sanity checks */
1378 if (descr->style & LBS_NOSEL) return LB_ERR;
1379 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1381 if (!descr->nb_items) return LB_OKAY;
1383 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1384 if (first < 0) first = 0;
1385 if (last < first) return LB_OKAY;
1387 if (on) /* Turn selection on */
1389 for (i = first; i <= last; i++)
1391 if (descr->items[i].selected) continue;
1392 descr->items[i].selected = TRUE;
1393 LISTBOX_InvalidateItemRect(descr, i);
1396 else /* Turn selection off */
1398 for (i = first; i <= last; i++)
1400 if (!descr->items[i].selected) continue;
1401 descr->items[i].selected = FALSE;
1402 LISTBOX_InvalidateItemRect(descr, i);
1405 return LB_OKAY;
1408 /***********************************************************************
1409 * LISTBOX_SetSelection
1411 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1412 BOOL on, BOOL send_notify )
1414 TRACE( "cur_sel=%d index=%d notify=%s\n",
1415 descr->selected_item, index, send_notify ? "YES" : "NO" );
1417 if (descr->style & LBS_NOSEL)
1419 descr->selected_item = index;
1420 return LB_ERR;
1422 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1423 if (descr->style & LBS_MULTIPLESEL)
1425 if (index == -1) /* Select all items */
1426 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1427 else /* Only one item */
1428 return LISTBOX_SelectItemRange( descr, index, index, on );
1430 else
1432 INT oldsel = descr->selected_item;
1433 if (index == oldsel) return LB_OKAY;
1434 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1435 if (index != -1) descr->items[index].selected = TRUE;
1436 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1437 descr->selected_item = index;
1438 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1439 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1440 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1441 else
1442 if( descr->lphc ) /* set selection change flag for parent combo */
1443 descr->lphc->wState |= CBF_SELCHANGE;
1445 return LB_OKAY;
1449 /***********************************************************************
1450 * LISTBOX_MoveCaret
1452 * Change the caret position and extend the selection to the new caret.
1454 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1456 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1458 if ((index < 0) || (index >= descr->nb_items))
1459 return;
1461 /* Important, repaint needs to be done in this order if
1462 you want to mimic Windows behavior:
1463 1. Remove the focus and paint the item
1464 2. Remove the selection and paint the item(s)
1465 3. Set the selection and repaint the item(s)
1466 4. Set the focus to 'index' and repaint the item */
1468 /* 1. remove the focus and repaint the item */
1469 LISTBOX_DrawFocusRect( descr, FALSE );
1471 /* 2. then turn off the previous selection */
1472 /* 3. repaint the new selected item */
1473 if (descr->style & LBS_EXTENDEDSEL)
1475 if (descr->anchor_item != -1)
1477 INT first = min( index, descr->anchor_item );
1478 INT last = max( index, descr->anchor_item );
1479 if (first > 0)
1480 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1481 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1482 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1485 else if (!(descr->style & LBS_MULTIPLESEL))
1487 /* Set selection to new caret item */
1488 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1491 /* 4. repaint the new item with the focus */
1492 descr->focus_item = index;
1493 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1494 LISTBOX_DrawFocusRect( descr, TRUE );
1498 /***********************************************************************
1499 * LISTBOX_InsertItem
1501 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1502 LPWSTR str, ULONG_PTR data )
1504 LB_ITEMDATA *item;
1505 INT max_items;
1506 INT oldfocus = descr->focus_item;
1508 if (index == -1) index = descr->nb_items;
1509 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1510 if (!descr->items) max_items = 0;
1511 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1512 if (descr->nb_items == max_items)
1514 /* We need to grow the array */
1515 max_items += LB_ARRAY_GRANULARITY;
1516 if (descr->items)
1517 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1518 max_items * sizeof(LB_ITEMDATA) );
1519 else
1520 item = HeapAlloc( GetProcessHeap(), 0,
1521 max_items * sizeof(LB_ITEMDATA) );
1522 if (!item)
1524 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1525 return LB_ERRSPACE;
1527 descr->items = item;
1530 /* Insert the item structure */
1532 item = &descr->items[index];
1533 if (index < descr->nb_items)
1534 RtlMoveMemory( item + 1, item,
1535 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1536 item->str = str;
1537 item->data = HAS_STRINGS(descr) ? 0 : data;
1538 item->height = 0;
1539 item->selected = FALSE;
1540 descr->nb_items++;
1542 /* Get item height */
1544 if (descr->style & LBS_OWNERDRAWVARIABLE)
1546 MEASUREITEMSTRUCT mis;
1547 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1549 mis.CtlType = ODT_LISTBOX;
1550 mis.CtlID = id;
1551 mis.itemID = index;
1552 mis.itemData = data;
1553 mis.itemHeight = descr->item_height;
1554 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1555 item->height = mis.itemHeight ? mis.itemHeight : 1;
1556 TRACE("[%p]: measure item %d (%s) = %d\n",
1557 descr->self, index, str ? debugstr_w(str) : "", item->height );
1560 /* Repaint the items */
1562 LISTBOX_UpdateScroll( descr );
1563 LISTBOX_InvalidateItems( descr, index );
1565 /* Move selection and focused item */
1566 /* If listbox was empty, set focus to the first item */
1567 if (descr->nb_items == 1)
1568 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1569 /* single select don't change selection index in win31 */
1570 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1572 descr->selected_item++;
1573 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1575 else
1577 if (index <= descr->selected_item)
1579 descr->selected_item++;
1580 descr->focus_item = oldfocus; /* focus not changed */
1583 return LB_OKAY;
1587 /***********************************************************************
1588 * LISTBOX_InsertString
1590 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1592 LPWSTR new_str = NULL;
1593 LRESULT ret;
1595 if (HAS_STRINGS(descr))
1597 static const WCHAR empty_stringW[] = { 0 };
1598 if (!str) str = empty_stringW;
1599 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1601 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1602 return LB_ERRSPACE;
1604 strcpyW(new_str, str);
1607 if (index == -1) index = descr->nb_items;
1608 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0)
1610 HeapFree( GetProcessHeap(), 0, new_str );
1611 return ret;
1614 TRACE("[%p]: added item %d %s\n",
1615 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1616 return index;
1620 /***********************************************************************
1621 * LISTBOX_DeleteItem
1623 * Delete the content of an item. 'index' must be a valid index.
1625 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1627 /* save the item data before it gets freed by LB_RESETCONTENT */
1628 ULONG_PTR item_data = descr->items[index].data;
1629 LPWSTR item_str = descr->items[index].str;
1631 if (!descr->nb_items)
1632 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1634 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1635 * while Win95 sends it for all items with user data.
1636 * It's probably better to send it too often than not
1637 * often enough, so this is what we do here.
1639 if (IS_OWNERDRAW(descr) || item_data)
1641 DELETEITEMSTRUCT dis;
1642 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1644 dis.CtlType = ODT_LISTBOX;
1645 dis.CtlID = id;
1646 dis.itemID = index;
1647 dis.hwndItem = descr->self;
1648 dis.itemData = item_data;
1649 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1651 if (HAS_STRINGS(descr))
1652 HeapFree( GetProcessHeap(), 0, item_str );
1656 /***********************************************************************
1657 * LISTBOX_RemoveItem
1659 * Remove an item from the listbox and delete its content.
1661 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1663 LB_ITEMDATA *item;
1664 INT max_items;
1666 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1668 /* We need to invalidate the original rect instead of the updated one. */
1669 LISTBOX_InvalidateItems( descr, index );
1671 descr->nb_items--;
1672 LISTBOX_DeleteItem( descr, index );
1674 if (!descr->nb_items) return LB_OKAY;
1676 /* Remove the item */
1678 item = &descr->items[index];
1679 if (index < descr->nb_items)
1680 RtlMoveMemory( item, item + 1,
1681 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1682 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1684 /* Shrink the item array if possible */
1686 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1687 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1689 max_items -= LB_ARRAY_GRANULARITY;
1690 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1691 max_items * sizeof(LB_ITEMDATA) );
1692 if (item) descr->items = item;
1694 /* Repaint the items */
1696 LISTBOX_UpdateScroll( descr );
1697 /* if we removed the scrollbar, reset the top of the list
1698 (correct for owner-drawn ???) */
1699 if (descr->nb_items == descr->page_size)
1700 LISTBOX_SetTopItem( descr, 0, TRUE );
1702 /* Move selection and focused item */
1703 if (!IS_MULTISELECT(descr))
1705 if (index == descr->selected_item)
1706 descr->selected_item = -1;
1707 else if (index < descr->selected_item)
1709 descr->selected_item--;
1710 if (ISWIN31) /* win 31 do not change the selected item number */
1711 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1715 if (descr->focus_item >= descr->nb_items)
1717 descr->focus_item = descr->nb_items - 1;
1718 if (descr->focus_item < 0) descr->focus_item = 0;
1720 return LB_OKAY;
1724 /***********************************************************************
1725 * LISTBOX_ResetContent
1727 static void LISTBOX_ResetContent( LB_DESCR *descr )
1729 INT i;
1731 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1732 HeapFree( GetProcessHeap(), 0, descr->items );
1733 descr->nb_items = 0;
1734 descr->top_item = 0;
1735 descr->selected_item = -1;
1736 descr->focus_item = 0;
1737 descr->anchor_item = -1;
1738 descr->items = NULL;
1742 /***********************************************************************
1743 * LISTBOX_SetCount
1745 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1747 LRESULT ret;
1749 if (HAS_STRINGS(descr))
1751 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1752 return LB_ERR;
1755 /* FIXME: this is far from optimal... */
1756 if (count > descr->nb_items)
1758 while (count > descr->nb_items)
1759 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1760 return ret;
1762 else if (count < descr->nb_items)
1764 while (count < descr->nb_items)
1765 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1766 return ret;
1769 InvalidateRect( descr->self, NULL, TRUE );
1770 return LB_OKAY;
1774 /***********************************************************************
1775 * LISTBOX_Directory
1777 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1778 LPCWSTR filespec, BOOL long_names )
1780 HANDLE handle;
1781 LRESULT ret = LB_OKAY;
1782 WIN32_FIND_DATAW entry;
1783 int pos;
1784 LRESULT maxinsert = LB_ERR;
1786 /* don't scan directory if we just want drives exclusively */
1787 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1788 /* scan directory */
1789 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1791 int le = GetLastError();
1792 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1794 else
1798 WCHAR buffer[270];
1799 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1801 static const WCHAR bracketW[] = { ']',0 };
1802 static const WCHAR dotW[] = { '.',0 };
1803 if (!(attrib & DDL_DIRECTORY) ||
1804 !strcmpW( entry.cFileName, dotW )) continue;
1805 buffer[0] = '[';
1806 if (!long_names && entry.cAlternateFileName[0])
1807 strcpyW( buffer + 1, entry.cAlternateFileName );
1808 else
1809 strcpyW( buffer + 1, entry.cFileName );
1810 strcatW(buffer, bracketW);
1812 else /* not a directory */
1814 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1815 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1817 if ((attrib & DDL_EXCLUSIVE) &&
1818 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1819 continue;
1820 #undef ATTRIBS
1821 if (!long_names && entry.cAlternateFileName[0])
1822 strcpyW( buffer, entry.cAlternateFileName );
1823 else
1824 strcpyW( buffer, entry.cFileName );
1826 if (!long_names) CharLowerW( buffer );
1827 pos = LISTBOX_FindFileStrPos( descr, buffer );
1828 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1829 break;
1830 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1831 } while (FindNextFileW( handle, &entry ));
1832 FindClose( handle );
1835 if (ret >= 0)
1837 ret = maxinsert;
1839 /* scan drives */
1840 if (attrib & DDL_DRIVES)
1842 WCHAR buffer[] = {'[','-','a','-',']',0};
1843 WCHAR root[] = {'A',':','\\',0};
1844 int drive;
1845 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1847 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1848 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1849 break;
1853 return ret;
1857 /***********************************************************************
1858 * LISTBOX_HandleVScroll
1860 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1862 SCROLLINFO info;
1864 if (descr->style & LBS_MULTICOLUMN) return 0;
1865 switch(scrollReq)
1867 case SB_LINEUP:
1868 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1869 break;
1870 case SB_LINEDOWN:
1871 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1872 break;
1873 case SB_PAGEUP:
1874 LISTBOX_SetTopItem( descr, descr->top_item -
1875 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1876 break;
1877 case SB_PAGEDOWN:
1878 LISTBOX_SetTopItem( descr, descr->top_item +
1879 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1880 break;
1881 case SB_THUMBPOSITION:
1882 LISTBOX_SetTopItem( descr, pos, TRUE );
1883 break;
1884 case SB_THUMBTRACK:
1885 info.cbSize = sizeof(info);
1886 info.fMask = SIF_TRACKPOS;
1887 GetScrollInfo( descr->self, SB_VERT, &info );
1888 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1889 break;
1890 case SB_TOP:
1891 LISTBOX_SetTopItem( descr, 0, TRUE );
1892 break;
1893 case SB_BOTTOM:
1894 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1895 break;
1897 return 0;
1901 /***********************************************************************
1902 * LISTBOX_HandleHScroll
1904 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1906 SCROLLINFO info;
1907 INT page;
1909 if (descr->style & LBS_MULTICOLUMN)
1911 switch(scrollReq)
1913 case SB_LINELEFT:
1914 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1915 TRUE );
1916 break;
1917 case SB_LINERIGHT:
1918 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1919 TRUE );
1920 break;
1921 case SB_PAGELEFT:
1922 page = descr->width / descr->column_width;
1923 if (page < 1) page = 1;
1924 LISTBOX_SetTopItem( descr,
1925 descr->top_item - page * descr->page_size, TRUE );
1926 break;
1927 case SB_PAGERIGHT:
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_THUMBPOSITION:
1934 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1935 break;
1936 case SB_THUMBTRACK:
1937 info.cbSize = sizeof(info);
1938 info.fMask = SIF_TRACKPOS;
1939 GetScrollInfo( descr->self, SB_VERT, &info );
1940 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1941 TRUE );
1942 break;
1943 case SB_LEFT:
1944 LISTBOX_SetTopItem( descr, 0, TRUE );
1945 break;
1946 case SB_RIGHT:
1947 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1948 break;
1951 else if (descr->horz_extent)
1953 switch(scrollReq)
1955 case SB_LINELEFT:
1956 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1957 break;
1958 case SB_LINERIGHT:
1959 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1960 break;
1961 case SB_PAGELEFT:
1962 LISTBOX_SetHorizontalPos( descr,
1963 descr->horz_pos - descr->width );
1964 break;
1965 case SB_PAGERIGHT:
1966 LISTBOX_SetHorizontalPos( descr,
1967 descr->horz_pos + descr->width );
1968 break;
1969 case SB_THUMBPOSITION:
1970 LISTBOX_SetHorizontalPos( descr, pos );
1971 break;
1972 case SB_THUMBTRACK:
1973 info.cbSize = sizeof(info);
1974 info.fMask = SIF_TRACKPOS;
1975 GetScrollInfo( descr->self, SB_HORZ, &info );
1976 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1977 break;
1978 case SB_LEFT:
1979 LISTBOX_SetHorizontalPos( descr, 0 );
1980 break;
1981 case SB_RIGHT:
1982 LISTBOX_SetHorizontalPos( descr,
1983 descr->horz_extent - descr->width );
1984 break;
1987 return 0;
1990 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1992 UINT pulScrollLines = 3;
1994 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1996 /* if scrolling changes direction, ignore left overs */
1997 if ((delta < 0 && descr->wheel_remain < 0) ||
1998 (delta > 0 && descr->wheel_remain > 0))
1999 descr->wheel_remain += delta;
2000 else
2001 descr->wheel_remain = delta;
2003 if (descr->wheel_remain && pulScrollLines)
2005 int cLineScroll;
2006 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2007 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2008 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2009 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2011 return 0;
2014 /***********************************************************************
2015 * LISTBOX_HandleLButtonDown
2017 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2019 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2021 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2022 descr->self, x, y, index, descr->focus_item);
2024 if (!descr->caret_on && (descr->in_focus)) return 0;
2026 if (!descr->in_focus)
2028 if( !descr->lphc ) SetFocus( descr->self );
2029 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2032 if (index == -1) return 0;
2034 if (!descr->lphc)
2036 if (descr->style & LBS_NOTIFY )
2037 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2038 MAKELPARAM( x, y ) );
2041 descr->captured = TRUE;
2042 SetCapture( descr->self );
2044 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2046 /* we should perhaps make sure that all items are deselected
2047 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2048 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2049 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2052 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2053 if (keys & MK_CONTROL)
2055 LISTBOX_SetCaretIndex( descr, index, FALSE );
2056 LISTBOX_SetSelection( descr, index,
2057 !descr->items[index].selected,
2058 (descr->style & LBS_NOTIFY) != 0);
2060 else
2062 LISTBOX_MoveCaret( descr, index, FALSE );
2064 if (descr->style & LBS_EXTENDEDSEL)
2066 LISTBOX_SetSelection( descr, index,
2067 descr->items[index].selected,
2068 (descr->style & LBS_NOTIFY) != 0 );
2070 else
2072 LISTBOX_SetSelection( descr, index,
2073 !descr->items[index].selected,
2074 (descr->style & LBS_NOTIFY) != 0 );
2078 else
2080 descr->anchor_item = index;
2081 LISTBOX_MoveCaret( descr, index, FALSE );
2082 LISTBOX_SetSelection( descr, index,
2083 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2086 if (!descr->lphc)
2088 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2090 POINT pt;
2092 pt.x = x;
2093 pt.y = y;
2095 if (DragDetect( descr->self, pt ))
2096 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2099 return 0;
2103 /*************************************************************************
2104 * LISTBOX_HandleLButtonDownCombo [Internal]
2106 * Process LButtonDown message for the ComboListBox
2108 * PARAMS
2109 * pWnd [I] The windows internal structure
2110 * pDescr [I] The ListBox internal structure
2111 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2112 * x [I] X Mouse Coordinate
2113 * y [I] Y Mouse Coordinate
2115 * RETURNS
2116 * 0 since we are processing the WM_LBUTTONDOWN Message
2118 * NOTES
2119 * This function is only to be used when a ListBox is a ComboListBox
2122 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2124 RECT clientRect, screenRect;
2125 POINT mousePos;
2127 mousePos.x = x;
2128 mousePos.y = y;
2130 GetClientRect(descr->self, &clientRect);
2132 if(PtInRect(&clientRect, mousePos))
2134 /* MousePos is in client, resume normal processing */
2135 if (msg == WM_LBUTTONDOWN)
2137 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2138 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2140 else if (descr->style & LBS_NOTIFY)
2141 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2143 else
2145 POINT screenMousePos;
2146 HWND hWndOldCapture;
2148 /* Check the Non-Client Area */
2149 screenMousePos = mousePos;
2150 hWndOldCapture = GetCapture();
2151 ReleaseCapture();
2152 GetWindowRect(descr->self, &screenRect);
2153 ClientToScreen(descr->self, &screenMousePos);
2155 if(!PtInRect(&screenRect, screenMousePos))
2157 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2158 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2159 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2161 else
2163 /* Check to see the NC is a scrollbar */
2164 INT nHitTestType=0;
2165 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2166 /* Check Vertical scroll bar */
2167 if (style & WS_VSCROLL)
2169 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2170 if (PtInRect( &clientRect, mousePos ))
2171 nHitTestType = HTVSCROLL;
2173 /* Check horizontal scroll bar */
2174 if (style & WS_HSCROLL)
2176 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2177 if (PtInRect( &clientRect, mousePos ))
2178 nHitTestType = HTHSCROLL;
2180 /* Windows sends this message when a scrollbar is clicked
2183 if(nHitTestType != 0)
2185 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2186 MAKELONG(screenMousePos.x, screenMousePos.y));
2188 /* Resume the Capture after scrolling is complete
2190 if(hWndOldCapture != 0)
2191 SetCapture(hWndOldCapture);
2194 return 0;
2197 /***********************************************************************
2198 * LISTBOX_HandleLButtonUp
2200 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2202 if (LISTBOX_Timer != LB_TIMER_NONE)
2203 KillSystemTimer( descr->self, LB_TIMER_ID );
2204 LISTBOX_Timer = LB_TIMER_NONE;
2205 if (descr->captured)
2207 descr->captured = FALSE;
2208 if (GetCapture() == descr->self) ReleaseCapture();
2209 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2210 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2212 return 0;
2216 /***********************************************************************
2217 * LISTBOX_HandleTimer
2219 * Handle scrolling upon a timer event.
2220 * Return TRUE if scrolling should continue.
2222 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2224 switch(dir)
2226 case LB_TIMER_UP:
2227 if (descr->top_item) index = descr->top_item - 1;
2228 else index = 0;
2229 break;
2230 case LB_TIMER_LEFT:
2231 if (descr->top_item) index -= descr->page_size;
2232 break;
2233 case LB_TIMER_DOWN:
2234 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2235 if (index == descr->focus_item) index++;
2236 if (index >= descr->nb_items) index = descr->nb_items - 1;
2237 break;
2238 case LB_TIMER_RIGHT:
2239 if (index + descr->page_size < descr->nb_items)
2240 index += descr->page_size;
2241 break;
2242 case LB_TIMER_NONE:
2243 break;
2245 if (index == descr->focus_item) return FALSE;
2246 LISTBOX_MoveCaret( descr, index, FALSE );
2247 return TRUE;
2251 /***********************************************************************
2252 * LISTBOX_HandleSystemTimer
2254 * WM_SYSTIMER handler.
2256 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2258 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2260 KillSystemTimer( descr->self, LB_TIMER_ID );
2261 LISTBOX_Timer = LB_TIMER_NONE;
2263 return 0;
2267 /***********************************************************************
2268 * LISTBOX_HandleMouseMove
2270 * WM_MOUSEMOVE handler.
2272 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2273 INT x, INT y )
2275 INT index;
2276 TIMER_DIRECTION dir = LB_TIMER_NONE;
2278 if (!descr->captured) return;
2280 if (descr->style & LBS_MULTICOLUMN)
2282 if (y < 0) y = 0;
2283 else if (y >= descr->item_height * descr->page_size)
2284 y = descr->item_height * descr->page_size - 1;
2286 if (x < 0)
2288 dir = LB_TIMER_LEFT;
2289 x = 0;
2291 else if (x >= descr->width)
2293 dir = LB_TIMER_RIGHT;
2294 x = descr->width - 1;
2297 else
2299 if (y < 0) dir = LB_TIMER_UP; /* above */
2300 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2303 index = LISTBOX_GetItemFromPoint( descr, x, y );
2304 if (index == -1) index = descr->focus_item;
2305 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2307 /* Start/stop the system timer */
2309 if (dir != LB_TIMER_NONE)
2310 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2311 else if (LISTBOX_Timer != LB_TIMER_NONE)
2312 KillSystemTimer( descr->self, LB_TIMER_ID );
2313 LISTBOX_Timer = dir;
2317 /***********************************************************************
2318 * LISTBOX_HandleKeyDown
2320 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2322 INT caret = -1;
2323 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2324 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2325 bForceSelection = FALSE; /* only for single select list */
2327 if (descr->style & LBS_WANTKEYBOARDINPUT)
2329 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2330 MAKEWPARAM(LOWORD(key), descr->focus_item),
2331 (LPARAM)descr->self );
2332 if (caret == -2) return 0;
2334 if (caret == -1) switch(key)
2336 case VK_LEFT:
2337 if (descr->style & LBS_MULTICOLUMN)
2339 bForceSelection = FALSE;
2340 if (descr->focus_item >= descr->page_size)
2341 caret = descr->focus_item - descr->page_size;
2342 break;
2344 /* fall through */
2345 case VK_UP:
2346 caret = descr->focus_item - 1;
2347 if (caret < 0) caret = 0;
2348 break;
2349 case VK_RIGHT:
2350 if (descr->style & LBS_MULTICOLUMN)
2352 bForceSelection = FALSE;
2353 if (descr->focus_item + descr->page_size < descr->nb_items)
2354 caret = descr->focus_item + descr->page_size;
2355 break;
2357 /* fall through */
2358 case VK_DOWN:
2359 caret = descr->focus_item + 1;
2360 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2361 break;
2363 case VK_PRIOR:
2364 if (descr->style & LBS_MULTICOLUMN)
2366 INT page = descr->width / descr->column_width;
2367 if (page < 1) page = 1;
2368 caret = descr->focus_item - (page * descr->page_size) + 1;
2370 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2371 if (caret < 0) caret = 0;
2372 break;
2373 case VK_NEXT:
2374 if (descr->style & LBS_MULTICOLUMN)
2376 INT page = descr->width / descr->column_width;
2377 if (page < 1) page = 1;
2378 caret = descr->focus_item + (page * descr->page_size) - 1;
2380 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2381 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2382 break;
2383 case VK_HOME:
2384 caret = 0;
2385 break;
2386 case VK_END:
2387 caret = descr->nb_items - 1;
2388 break;
2389 case VK_SPACE:
2390 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2391 else if (descr->style & LBS_MULTIPLESEL)
2393 LISTBOX_SetSelection( descr, descr->focus_item,
2394 !descr->items[descr->focus_item].selected,
2395 (descr->style & LBS_NOTIFY) != 0 );
2397 break;
2398 default:
2399 bForceSelection = FALSE;
2401 if (bForceSelection) /* focused item is used instead of key */
2402 caret = descr->focus_item;
2403 if (caret >= 0)
2405 if (((descr->style & LBS_EXTENDEDSEL) &&
2406 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2407 !IS_MULTISELECT(descr))
2408 descr->anchor_item = caret;
2409 LISTBOX_MoveCaret( descr, caret, TRUE );
2411 if (descr->style & LBS_MULTIPLESEL)
2412 descr->selected_item = caret;
2413 else
2414 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2415 if (descr->style & LBS_NOTIFY)
2417 if (descr->lphc && IsWindowVisible( descr->self ))
2419 /* make sure that combo parent doesn't hide us */
2420 descr->lphc->wState |= CBF_NOROLLUP;
2422 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2425 return 0;
2429 /***********************************************************************
2430 * LISTBOX_HandleChar
2432 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2434 INT caret = -1;
2435 WCHAR str[2];
2437 str[0] = charW;
2438 str[1] = '\0';
2440 if (descr->style & LBS_WANTKEYBOARDINPUT)
2442 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2443 MAKEWPARAM(charW, descr->focus_item),
2444 (LPARAM)descr->self );
2445 if (caret == -2) return 0;
2447 if (caret == -1)
2448 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2449 if (caret != -1)
2451 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2452 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2453 LISTBOX_MoveCaret( descr, caret, TRUE );
2454 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2455 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2457 return 0;
2461 /***********************************************************************
2462 * LISTBOX_Create
2464 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2466 LB_DESCR *descr;
2467 MEASUREITEMSTRUCT mis;
2468 RECT rect;
2470 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2471 return FALSE;
2473 GetClientRect( hwnd, &rect );
2474 descr->self = hwnd;
2475 descr->owner = GetParent( descr->self );
2476 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2477 descr->width = rect.right - rect.left;
2478 descr->height = rect.bottom - rect.top;
2479 descr->items = NULL;
2480 descr->nb_items = 0;
2481 descr->top_item = 0;
2482 descr->selected_item = -1;
2483 descr->focus_item = 0;
2484 descr->anchor_item = -1;
2485 descr->item_height = 1;
2486 descr->page_size = 1;
2487 descr->column_width = 150;
2488 descr->horz_extent = 0;
2489 descr->horz_pos = 0;
2490 descr->nb_tabs = 0;
2491 descr->tabs = NULL;
2492 descr->wheel_remain = 0;
2493 descr->caret_on = !lphc;
2494 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2495 descr->in_focus = FALSE;
2496 descr->captured = FALSE;
2497 descr->font = 0;
2498 descr->locale = GetUserDefaultLCID();
2499 descr->lphc = lphc;
2501 if( lphc )
2503 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2504 descr->owner = lphc->self;
2507 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2509 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2511 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2512 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2513 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2514 descr->item_height = LISTBOX_SetFont( descr, 0 );
2516 if (descr->style & LBS_OWNERDRAWFIXED)
2518 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2520 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2521 descr->item_height = lphc->fixedOwnerDrawHeight;
2523 else
2525 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2526 mis.CtlType = ODT_LISTBOX;
2527 mis.CtlID = id;
2528 mis.itemID = -1;
2529 mis.itemWidth = 0;
2530 mis.itemData = 0;
2531 mis.itemHeight = descr->item_height;
2532 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2533 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2537 OpenThemeData( descr->self, WC_LISTBOXW );
2539 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2540 return TRUE;
2544 /***********************************************************************
2545 * LISTBOX_Destroy
2547 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2549 HTHEME theme = GetWindowTheme( descr->self );
2550 CloseThemeData( theme );
2551 LISTBOX_ResetContent( descr );
2552 SetWindowLongPtrW( descr->self, 0, 0 );
2553 HeapFree( GetProcessHeap(), 0, descr );
2554 return TRUE;
2558 /***********************************************************************
2559 * ListBoxWndProc_common
2561 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2563 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2564 HEADCOMBO *lphc = NULL;
2565 HTHEME theme;
2566 LRESULT ret;
2568 if (!descr)
2570 if (!IsWindow(hwnd)) return 0;
2572 if (msg == WM_CREATE)
2574 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2575 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2576 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2577 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2578 return 0;
2580 /* Ignore all other messages before we get a WM_CREATE */
2581 return DefWindowProcW( hwnd, msg, wParam, lParam );
2583 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2585 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr->self, msg, wParam, lParam );
2587 switch(msg)
2589 case LB_RESETCONTENT:
2590 LISTBOX_ResetContent( descr );
2591 LISTBOX_UpdateScroll( descr );
2592 InvalidateRect( descr->self, NULL, TRUE );
2593 return 0;
2595 case LB_ADDSTRING:
2597 const WCHAR *textW = (const WCHAR *)lParam;
2598 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2599 return LISTBOX_InsertString( descr, index, textW );
2602 case LB_INSERTSTRING:
2603 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2605 case LB_ADDFILE:
2607 const WCHAR *textW = (const WCHAR *)lParam;
2608 INT index = LISTBOX_FindFileStrPos( descr, textW );
2609 return LISTBOX_InsertString( descr, index, textW );
2612 case LB_DELETESTRING:
2613 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2614 return descr->nb_items;
2615 else
2617 SetLastError(ERROR_INVALID_INDEX);
2618 return LB_ERR;
2621 case LB_GETITEMDATA:
2622 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2624 SetLastError(ERROR_INVALID_INDEX);
2625 return LB_ERR;
2627 return descr->items[wParam].data;
2629 case LB_SETITEMDATA:
2630 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2632 SetLastError(ERROR_INVALID_INDEX);
2633 return LB_ERR;
2635 descr->items[wParam].data = lParam;
2636 /* undocumented: returns TRUE, not LB_OKAY (0) */
2637 return TRUE;
2639 case LB_GETCOUNT:
2640 return descr->nb_items;
2642 case LB_GETTEXT:
2643 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2645 case LB_GETTEXTLEN:
2646 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2648 SetLastError(ERROR_INVALID_INDEX);
2649 return LB_ERR;
2651 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2652 return strlenW( descr->items[wParam].str );
2654 case LB_GETCURSEL:
2655 if (descr->nb_items == 0)
2656 return LB_ERR;
2657 if (!IS_MULTISELECT(descr))
2658 return descr->selected_item;
2659 if (descr->selected_item != -1)
2660 return descr->selected_item;
2661 return descr->focus_item;
2662 /* otherwise, if the user tries to move the selection with the */
2663 /* arrow keys, we will give the application something to choke on */
2664 case LB_GETTOPINDEX:
2665 return descr->top_item;
2667 case LB_GETITEMHEIGHT:
2668 return LISTBOX_GetItemHeight( descr, wParam );
2670 case LB_SETITEMHEIGHT:
2671 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2673 case LB_ITEMFROMPOINT:
2675 POINT pt;
2676 RECT rect;
2677 int index;
2678 BOOL hit = TRUE;
2680 /* The hiword of the return value is not a client area
2681 hittest as suggested by MSDN, but rather a hittest on
2682 the returned listbox item. */
2684 if(descr->nb_items == 0)
2685 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2687 pt.x = (short)LOWORD(lParam);
2688 pt.y = (short)HIWORD(lParam);
2690 SetRect(&rect, 0, 0, descr->width, descr->height);
2692 if(!PtInRect(&rect, pt))
2694 pt.x = min(pt.x, rect.right - 1);
2695 pt.x = max(pt.x, 0);
2696 pt.y = min(pt.y, rect.bottom - 1);
2697 pt.y = max(pt.y, 0);
2698 hit = FALSE;
2701 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2703 if(index == -1)
2705 index = descr->nb_items - 1;
2706 hit = FALSE;
2708 return MAKELONG(index, hit ? 0 : 1);
2711 case LB_SETCARETINDEX:
2712 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2713 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2714 return LB_ERR;
2715 else if (ISWIN31)
2716 return wParam;
2717 else
2718 return LB_OKAY;
2720 case LB_GETCARETINDEX:
2721 return descr->focus_item;
2723 case LB_SETTOPINDEX:
2724 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2726 case LB_SETCOLUMNWIDTH:
2727 return LISTBOX_SetColumnWidth( descr, wParam );
2729 case LB_GETITEMRECT:
2730 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2732 case LB_FINDSTRING:
2733 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2735 case LB_FINDSTRINGEXACT:
2736 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2738 case LB_SELECTSTRING:
2740 const WCHAR *textW = (const WCHAR *)lParam;
2741 INT index;
2743 if (HAS_STRINGS(descr))
2744 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2746 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2747 if (index != LB_ERR)
2749 LISTBOX_MoveCaret( descr, index, TRUE );
2750 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2752 return index;
2755 case LB_GETSEL:
2756 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2757 return LB_ERR;
2758 return descr->items[wParam].selected;
2760 case LB_SETSEL:
2761 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2763 case LB_SETCURSEL:
2764 if (IS_MULTISELECT(descr)) return LB_ERR;
2765 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2766 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2767 if (ret != LB_ERR) ret = descr->selected_item;
2768 return ret;
2770 case LB_GETSELCOUNT:
2771 return LISTBOX_GetSelCount( descr );
2773 case LB_GETSELITEMS:
2774 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2776 case LB_SELITEMRANGE:
2777 if (LOWORD(lParam) <= HIWORD(lParam))
2778 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2779 HIWORD(lParam), wParam );
2780 else
2781 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2782 LOWORD(lParam), wParam );
2784 case LB_SELITEMRANGEEX:
2785 if ((INT)lParam >= (INT)wParam)
2786 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2787 else
2788 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2790 case LB_GETHORIZONTALEXTENT:
2791 return descr->horz_extent;
2793 case LB_SETHORIZONTALEXTENT:
2794 return LISTBOX_SetHorizontalExtent( descr, wParam );
2796 case LB_GETANCHORINDEX:
2797 return descr->anchor_item;
2799 case LB_SETANCHORINDEX:
2800 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2802 SetLastError(ERROR_INVALID_INDEX);
2803 return LB_ERR;
2805 descr->anchor_item = (INT)wParam;
2806 return LB_OKAY;
2808 case LB_DIR:
2809 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2811 case LB_GETLOCALE:
2812 return descr->locale;
2814 case LB_SETLOCALE:
2816 LCID ret;
2817 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2818 return LB_ERR;
2819 ret = descr->locale;
2820 descr->locale = (LCID)wParam;
2821 return ret;
2824 case LB_INITSTORAGE:
2825 return LISTBOX_InitStorage( descr, wParam );
2827 case LB_SETCOUNT:
2828 return LISTBOX_SetCount( descr, (INT)wParam );
2830 case LB_SETTABSTOPS:
2831 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2833 case LB_CARETON:
2834 if (descr->caret_on)
2835 return LB_OKAY;
2836 descr->caret_on = TRUE;
2837 if ((descr->focus_item != -1) && (descr->in_focus))
2838 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2839 return LB_OKAY;
2841 case LB_CARETOFF:
2842 if (!descr->caret_on)
2843 return LB_OKAY;
2844 descr->caret_on = FALSE;
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_GETLISTBOXINFO:
2850 return descr->page_size;
2852 case WM_DESTROY:
2853 return LISTBOX_Destroy( descr );
2855 case WM_ENABLE:
2856 InvalidateRect( descr->self, NULL, TRUE );
2857 return 0;
2859 case WM_SETREDRAW:
2860 LISTBOX_SetRedraw( descr, wParam != 0 );
2861 return 0;
2863 case WM_GETDLGCODE:
2864 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2866 case WM_PRINTCLIENT:
2867 case WM_PAINT:
2869 PAINTSTRUCT ps;
2870 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2871 ret = LISTBOX_Paint( descr, hdc );
2872 if( !wParam ) EndPaint( descr->self, &ps );
2874 return ret;
2876 case WM_NCPAINT:
2877 LISTBOX_NCPaint( descr, (HRGN)wParam );
2878 break;
2880 case WM_SIZE:
2881 LISTBOX_UpdateSize( descr );
2882 return 0;
2883 case WM_GETFONT:
2884 return (LRESULT)descr->font;
2885 case WM_SETFONT:
2886 LISTBOX_SetFont( descr, (HFONT)wParam );
2887 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2888 return 0;
2889 case WM_SETFOCUS:
2890 descr->in_focus = TRUE;
2891 descr->caret_on = TRUE;
2892 if (descr->focus_item != -1)
2893 LISTBOX_DrawFocusRect( descr, TRUE );
2894 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2895 return 0;
2896 case WM_KILLFOCUS:
2897 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
2898 descr->in_focus = FALSE;
2899 descr->wheel_remain = 0;
2900 if ((descr->focus_item != -1) && descr->caret_on)
2901 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2902 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
2903 return 0;
2904 case WM_HSCROLL:
2905 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2906 case WM_VSCROLL:
2907 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2908 case WM_MOUSEWHEEL:
2909 if (wParam & (MK_SHIFT | MK_CONTROL))
2910 return DefWindowProcW( descr->self, msg, wParam, lParam );
2911 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
2912 case WM_LBUTTONDOWN:
2913 if (lphc)
2914 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2915 (INT16)LOWORD(lParam),
2916 (INT16)HIWORD(lParam) );
2917 return LISTBOX_HandleLButtonDown( descr, wParam,
2918 (INT16)LOWORD(lParam),
2919 (INT16)HIWORD(lParam) );
2920 case WM_LBUTTONDBLCLK:
2921 if (lphc)
2922 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2923 (INT16)LOWORD(lParam),
2924 (INT16)HIWORD(lParam) );
2925 if (descr->style & LBS_NOTIFY)
2926 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2927 return 0;
2928 case WM_MOUSEMOVE:
2929 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
2931 BOOL captured = descr->captured;
2932 POINT mousePos;
2933 RECT clientRect;
2935 mousePos.x = (INT16)LOWORD(lParam);
2936 mousePos.y = (INT16)HIWORD(lParam);
2939 * If we are in a dropdown combobox, we simulate that
2940 * the mouse is captured to show the tracking of the item.
2942 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
2943 descr->captured = TRUE;
2945 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
2947 descr->captured = captured;
2949 else if (GetCapture() == descr->self)
2951 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
2952 (INT16)HIWORD(lParam) );
2954 return 0;
2955 case WM_LBUTTONUP:
2956 if (lphc)
2958 POINT mousePos;
2959 RECT clientRect;
2962 * If the mouse button "up" is not in the listbox,
2963 * we make sure there is no selection by re-selecting the
2964 * item that was selected when the listbox was made visible.
2966 mousePos.x = (INT16)LOWORD(lParam);
2967 mousePos.y = (INT16)HIWORD(lParam);
2969 GetClientRect(descr->self, &clientRect);
2972 * When the user clicks outside the combobox and the focus
2973 * is lost, the owning combobox will send a fake buttonup with
2974 * 0xFFFFFFF as the mouse location, we must also revert the
2975 * selection to the original selection.
2977 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
2978 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
2980 return LISTBOX_HandleLButtonUp( descr );
2981 case WM_KEYDOWN:
2982 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
2984 /* for some reason Windows makes it possible to
2985 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2987 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2988 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2989 && (wParam == VK_DOWN || wParam == VK_UP)) )
2991 COMBO_FlipListbox( lphc, FALSE, FALSE );
2992 return 0;
2995 return LISTBOX_HandleKeyDown( descr, wParam );
2996 case WM_CHAR:
2997 return LISTBOX_HandleChar( descr, wParam );
2999 case WM_SYSTIMER:
3000 return LISTBOX_HandleSystemTimer( descr );
3001 case WM_ERASEBKGND:
3002 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3004 RECT rect;
3005 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3006 wParam, (LPARAM)descr->self );
3007 TRACE("hbrush = %p\n", hbrush);
3008 if(!hbrush)
3009 hbrush = GetSysColorBrush(COLOR_WINDOW);
3010 if(hbrush)
3012 GetClientRect(descr->self, &rect);
3013 FillRect((HDC)wParam, &rect, hbrush);
3016 return 1;
3017 case WM_DROPFILES:
3018 if( lphc ) return 0;
3019 return SendMessageW( descr->owner, msg, wParam, lParam );
3021 case WM_NCDESTROY:
3022 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3023 lphc->hWndLBox = 0;
3024 break;
3026 case WM_NCACTIVATE:
3027 if (lphc) return 0;
3028 break;
3030 case WM_THEMECHANGED:
3031 theme = GetWindowTheme( hwnd );
3032 CloseThemeData( theme );
3033 OpenThemeData( hwnd, WC_LISTBOXW );
3034 break;
3036 default:
3037 if ((msg >= WM_USER) && (msg < 0xc000))
3038 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3039 hwnd, msg, wParam, lParam );
3042 return DefWindowProcW( hwnd, msg, wParam, lParam );
3045 void LISTBOX_Register(void)
3047 WNDCLASSW wndClass;
3049 memset(&wndClass, 0, sizeof(wndClass));
3050 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3051 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3052 wndClass.cbClsExtra = 0;
3053 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3054 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3055 wndClass.hbrBackground = NULL;
3056 wndClass.lpszClassName = WC_LISTBOXW;
3057 RegisterClassW(&wndClass);
3060 void COMBOLBOX_Register(void)
3062 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
3063 WNDCLASSW wndClass;
3065 memset(&wndClass, 0, sizeof(wndClass));
3066 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
3067 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3068 wndClass.cbClsExtra = 0;
3069 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3070 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3071 wndClass.hbrBackground = NULL;
3072 wndClass.lpszClassName = combolboxW;
3073 RegisterClassW(&wndClass);