comctl32: Introduce ListBox control.
[wine.git] / dlls / comctl32 / listbox.c
blob7a16500bd17c7dd458bc767742c558838bcef003
1 /*
2 * Listbox controls
4 * Copyright 1996 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * TODO:
21 * - LBS_NODATA
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "commctrl.h"
33 #include "wine/unicode.h"
34 #include "wine/exception.h"
35 #include "wine/debug.h"
37 #include "comctl32.h"
39 WINE_DEFAULT_DEBUG_CHANNEL(listbox2);
41 /* Items array granularity */
42 #define LB_ARRAY_GRANULARITY 16
44 /* Scrolling timeout in ms */
45 #define LB_SCROLL_TIMEOUT 50
47 /* Listbox system timer id */
48 #define LB_TIMER_ID 2
50 /* flag listbox changed while setredraw false - internal style */
51 #define LBS_DISPLAYCHANGED 0x80000000
53 /* Item structure */
54 typedef struct
56 LPWSTR str; /* Item text */
57 BOOL selected; /* Is item selected? */
58 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
59 ULONG_PTR data; /* User data */
60 } LB_ITEMDATA;
62 /* Listbox structure */
63 typedef struct
65 HWND self; /* Our own window handle */
66 HWND owner; /* Owner window to send notifications to */
67 UINT style; /* Window style */
68 INT width; /* Window width */
69 INT height; /* Window height */
70 LB_ITEMDATA *items; /* Array of items */
71 INT nb_items; /* Number of items */
72 INT top_item; /* Top visible item */
73 INT selected_item; /* Selected item */
74 INT focus_item; /* Item that has the focus */
75 INT anchor_item; /* Anchor item for extended selection */
76 INT item_height; /* Default item height */
77 INT page_size; /* Items per listbox page */
78 INT column_width; /* Column width for multi-column listboxes */
79 INT horz_extent; /* Horizontal extent */
80 INT horz_pos; /* Horizontal position */
81 INT nb_tabs; /* Number of tabs in array */
82 INT *tabs; /* Array of tabs */
83 INT avg_char_width; /* Average width of characters */
84 INT wheel_remain; /* Left over scroll amount */
85 BOOL caret_on; /* Is caret on? */
86 BOOL captured; /* Is mouse captured? */
87 BOOL in_focus;
88 HFONT font; /* Current font */
89 LCID locale; /* Current locale for string comparisons */
90 HEADCOMBO *lphc; /* ComboLBox */
91 } LB_DESCR;
94 #define IS_OWNERDRAW(descr) \
95 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
97 #define HAS_STRINGS(descr) \
98 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
101 #define IS_MULTISELECT(descr) \
102 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
103 !((descr)->style & LBS_NOSEL))
105 #define SEND_NOTIFICATION(descr,code) \
106 (SendMessageW( (descr)->owner, WM_COMMAND, \
107 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
109 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
111 /* Current timer status */
112 typedef enum
114 LB_TIMER_NONE,
115 LB_TIMER_UP,
116 LB_TIMER_LEFT,
117 LB_TIMER_DOWN,
118 LB_TIMER_RIGHT
119 } TIMER_DIRECTION;
121 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
123 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
125 /***********************************************************************
126 * LISTBOX_GetCurrentPageSize
128 * Return the current page size
130 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
132 INT i, height;
133 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
134 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
136 if ((height += descr->items[i].height) > descr->height) break;
138 if (i == descr->top_item) return 1;
139 else return i - descr->top_item;
143 /***********************************************************************
144 * LISTBOX_GetMaxTopIndex
146 * Return the maximum possible index for the top of the listbox.
148 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
150 INT max, page;
152 if (descr->style & LBS_OWNERDRAWVARIABLE)
154 page = descr->height;
155 for (max = descr->nb_items - 1; max >= 0; max--)
156 if ((page -= descr->items[max].height) < 0) break;
157 if (max < descr->nb_items - 1) max++;
159 else if (descr->style & LBS_MULTICOLUMN)
161 if ((page = descr->width / descr->column_width) < 1) page = 1;
162 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
163 max = (max - page) * descr->page_size;
165 else
167 max = descr->nb_items - descr->page_size;
169 if (max < 0) max = 0;
170 return max;
174 /***********************************************************************
175 * LISTBOX_UpdateScroll
177 * Update the scrollbars. Should be called whenever the content
178 * of the listbox changes.
180 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
182 SCROLLINFO info;
184 /* Check the listbox scroll bar flags individually before we call
185 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
186 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
187 scroll bar when we do not need one.
188 if (!(descr->style & WS_VSCROLL)) return;
191 /* It is important that we check descr->style, and not wnd->dwStyle,
192 for WS_VSCROLL, as the former is exactly the one passed in
193 argument to CreateWindow.
194 In Windows (and from now on in Wine :) a listbox created
195 with such a style (no WS_SCROLL) does not update
196 the scrollbar with listbox-related data, thus letting
197 the programmer use it for his/her own purposes. */
199 if (descr->style & LBS_NOREDRAW) return;
200 info.cbSize = sizeof(info);
202 if (descr->style & LBS_MULTICOLUMN)
204 info.nMin = 0;
205 info.nMax = (descr->nb_items - 1) / descr->page_size;
206 info.nPos = descr->top_item / descr->page_size;
207 info.nPage = descr->width / descr->column_width;
208 if (info.nPage < 1) info.nPage = 1;
209 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
210 if (descr->style & LBS_DISABLENOSCROLL)
211 info.fMask |= SIF_DISABLENOSCROLL;
212 if (descr->style & WS_HSCROLL)
213 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
214 info.nMax = 0;
215 info.fMask = SIF_RANGE;
216 if (descr->style & WS_VSCROLL)
217 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
219 else
221 info.nMin = 0;
222 info.nMax = descr->nb_items - 1;
223 info.nPos = descr->top_item;
224 info.nPage = LISTBOX_GetCurrentPageSize( descr );
225 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
226 if (descr->style & LBS_DISABLENOSCROLL)
227 info.fMask |= SIF_DISABLENOSCROLL;
228 if (descr->style & WS_VSCROLL)
229 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
231 if ((descr->style & WS_HSCROLL) && descr->horz_extent)
233 info.nPos = descr->horz_pos;
234 info.nPage = descr->width;
235 info.fMask = SIF_POS | SIF_PAGE;
236 if (descr->style & LBS_DISABLENOSCROLL)
237 info.fMask |= SIF_DISABLENOSCROLL;
238 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
240 else
242 if (descr->style & LBS_DISABLENOSCROLL)
244 info.nMin = 0;
245 info.nMax = 0;
246 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL;
247 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
249 else
251 ShowScrollBar( descr->self, SB_HORZ, FALSE );
258 /***********************************************************************
259 * LISTBOX_SetTopItem
261 * Set the top item of the listbox, scrolling up or down if necessary.
263 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
265 INT max = LISTBOX_GetMaxTopIndex( descr );
267 TRACE("setting top item %d, scroll %d\n", index, scroll);
269 if (index > max) index = max;
270 if (index < 0) index = 0;
271 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
272 if (descr->top_item == index) return LB_OKAY;
273 if (scroll)
275 INT diff;
276 if (descr->style & LBS_MULTICOLUMN)
277 diff = (descr->top_item - index) / descr->page_size * descr->column_width;
278 else if (descr->style & LBS_OWNERDRAWVARIABLE)
280 INT i;
281 diff = 0;
282 if (index > descr->top_item)
284 for (i = index - 1; i >= descr->top_item; i--)
285 diff -= descr->items[i].height;
287 else
289 for (i = index; i < descr->top_item; i++)
290 diff += descr->items[i].height;
293 else
294 diff = (descr->top_item - index) * descr->item_height;
296 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
297 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
299 else
300 InvalidateRect( descr->self, NULL, TRUE );
301 descr->top_item = index;
302 LISTBOX_UpdateScroll( descr );
303 return LB_OKAY;
307 /***********************************************************************
308 * LISTBOX_UpdatePage
310 * Update the page size. Should be called when the size of
311 * the client area or the item height changes.
313 static void LISTBOX_UpdatePage( LB_DESCR *descr )
315 INT page_size;
317 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
318 page_size = 1;
319 if (page_size == descr->page_size) return;
320 descr->page_size = page_size;
321 if (descr->style & LBS_MULTICOLUMN)
322 InvalidateRect( descr->self, NULL, TRUE );
323 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
327 /***********************************************************************
328 * LISTBOX_UpdateSize
330 * Update the size of the listbox. Should be called when the size of
331 * the client area changes.
333 static void LISTBOX_UpdateSize( LB_DESCR *descr )
335 RECT rect;
337 GetClientRect( descr->self, &rect );
338 descr->width = rect.right - rect.left;
339 descr->height = rect.bottom - rect.top;
340 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
342 INT remaining;
343 RECT rect;
345 GetWindowRect( descr->self, &rect );
346 if(descr->item_height != 0)
347 remaining = descr->height % descr->item_height;
348 else
349 remaining = 0;
350 if ((descr->height > descr->item_height) && remaining)
352 TRACE("[%p]: changing height %d -> %d\n",
353 descr->self, descr->height, descr->height - remaining );
354 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
355 rect.bottom - rect.top - remaining,
356 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
357 return;
360 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
361 LISTBOX_UpdatePage( descr );
362 LISTBOX_UpdateScroll( descr );
364 /* Invalidate the focused item so it will be repainted correctly */
365 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
367 InvalidateRect( descr->self, &rect, FALSE );
372 /***********************************************************************
373 * LISTBOX_GetItemRect
375 * Get the rectangle enclosing an item, in listbox client coordinates.
376 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
378 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
380 /* Index <= 0 is legal even on empty listboxes */
381 if (index && (index >= descr->nb_items))
383 SetRectEmpty(rect);
384 SetLastError(ERROR_INVALID_INDEX);
385 return LB_ERR;
387 SetRect( rect, 0, 0, descr->width, descr->height );
388 if (descr->style & LBS_MULTICOLUMN)
390 INT col = (index / descr->page_size) -
391 (descr->top_item / descr->page_size);
392 rect->left += col * descr->column_width;
393 rect->right = rect->left + descr->column_width;
394 rect->top += (index % descr->page_size) * descr->item_height;
395 rect->bottom = rect->top + descr->item_height;
397 else if (descr->style & LBS_OWNERDRAWVARIABLE)
399 INT i;
400 rect->right += descr->horz_pos;
401 if ((index >= 0) && (index < descr->nb_items))
403 if (index < descr->top_item)
405 for (i = descr->top_item-1; i >= index; i--)
406 rect->top -= descr->items[i].height;
408 else
410 for (i = descr->top_item; i < index; i++)
411 rect->top += descr->items[i].height;
413 rect->bottom = rect->top + descr->items[index].height;
417 else
419 rect->top += (index - descr->top_item) * descr->item_height;
420 rect->bottom = rect->top + descr->item_height;
421 rect->right += descr->horz_pos;
424 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
426 return ((rect->left < descr->width) && (rect->right > 0) &&
427 (rect->top < descr->height) && (rect->bottom > 0));
431 /***********************************************************************
432 * LISTBOX_GetItemFromPoint
434 * Return the item nearest from point (x,y) (in client coordinates).
436 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
438 INT index = descr->top_item;
440 if (!descr->nb_items) return -1; /* No items */
441 if (descr->style & LBS_OWNERDRAWVARIABLE)
443 INT pos = 0;
444 if (y >= 0)
446 while (index < descr->nb_items)
448 if ((pos += descr->items[index].height) > y) break;
449 index++;
452 else
454 while (index > 0)
456 index--;
457 if ((pos -= descr->items[index].height) <= y) break;
461 else if (descr->style & LBS_MULTICOLUMN)
463 if (y >= descr->item_height * descr->page_size) return -1;
464 if (y >= 0) index += y / descr->item_height;
465 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
466 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
468 else
470 index += (y / descr->item_height);
472 if (index < 0) return 0;
473 if (index >= descr->nb_items) return -1;
474 return index;
478 /***********************************************************************
479 * LISTBOX_PaintItem
481 * Paint an item.
483 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
484 INT index, UINT action, BOOL ignoreFocus )
486 LB_ITEMDATA *item = NULL;
487 if (index < descr->nb_items) item = &descr->items[index];
489 if (IS_OWNERDRAW(descr))
491 DRAWITEMSTRUCT dis;
492 RECT r;
493 HRGN hrgn;
495 if (!item)
497 if (action == ODA_FOCUS)
498 DrawFocusRect( hdc, rect );
499 else
500 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
501 return;
504 /* some programs mess with the clipping region when
505 drawing the item, *and* restore the previous region
506 after they are done, so a region has better to exist
507 else everything ends clipped */
508 GetClientRect(descr->self, &r);
509 hrgn = set_control_clipping( hdc, &r );
511 dis.CtlType = ODT_LISTBOX;
512 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID );
513 dis.hwndItem = descr->self;
514 dis.itemAction = action;
515 dis.hDC = hdc;
516 dis.itemID = index;
517 dis.itemState = 0;
518 if (item->selected) dis.itemState |= ODS_SELECTED;
519 if (!ignoreFocus && (descr->focus_item == index) &&
520 (descr->caret_on) &&
521 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
522 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
523 dis.itemData = item->data;
524 dis.rcItem = *rect;
525 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
526 descr->self, index, debugstr_w(item->str), action,
527 dis.itemState, wine_dbgstr_rect(rect) );
528 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
529 SelectClipRgn( hdc, hrgn );
530 if (hrgn) DeleteObject( hrgn );
532 else
534 COLORREF oldText = 0, oldBk = 0;
536 if (action == ODA_FOCUS)
538 DrawFocusRect( hdc, rect );
539 return;
541 if (item && item->selected)
543 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
544 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
547 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
548 descr->self, index, item ? debugstr_w(item->str) : "", action,
549 wine_dbgstr_rect(rect) );
550 if (!item)
551 ExtTextOutW( hdc, rect->left + 1, rect->top,
552 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
553 else if (!(descr->style & LBS_USETABSTOPS))
554 ExtTextOutW( hdc, rect->left + 1, rect->top,
555 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
556 strlenW(item->str), NULL );
557 else
559 /* Output empty string to paint background in the full width. */
560 ExtTextOutW( hdc, rect->left + 1, rect->top,
561 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
562 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
563 item->str, strlenW(item->str),
564 descr->nb_tabs, descr->tabs, 0);
566 if (item && item->selected)
568 SetBkColor( hdc, oldBk );
569 SetTextColor( hdc, oldText );
571 if (!ignoreFocus && (descr->focus_item == index) &&
572 (descr->caret_on) &&
573 (descr->in_focus)) DrawFocusRect( hdc, rect );
578 /***********************************************************************
579 * LISTBOX_SetRedraw
581 * Change the redraw flag.
583 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
585 if (on)
587 if (!(descr->style & LBS_NOREDRAW)) return;
588 descr->style &= ~LBS_NOREDRAW;
589 if (descr->style & LBS_DISPLAYCHANGED)
590 { /* page was changed while setredraw false, refresh automatically */
591 InvalidateRect(descr->self, NULL, TRUE);
592 if ((descr->top_item + descr->page_size) > descr->nb_items)
593 { /* reset top of page if less than number of items/page */
594 descr->top_item = descr->nb_items - descr->page_size;
595 if (descr->top_item < 0) descr->top_item = 0;
597 descr->style &= ~LBS_DISPLAYCHANGED;
599 LISTBOX_UpdateScroll( descr );
601 else descr->style |= LBS_NOREDRAW;
605 /***********************************************************************
606 * LISTBOX_RepaintItem
608 * Repaint a single item synchronously.
610 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
612 HDC hdc;
613 RECT rect;
614 HFONT oldFont = 0;
615 HBRUSH hbrush, oldBrush = 0;
617 /* Do not repaint the item if the item is not visible */
618 if (!IsWindowVisible(descr->self)) return;
619 if (descr->style & LBS_NOREDRAW)
621 descr->style |= LBS_DISPLAYCHANGED;
622 return;
624 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
625 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
626 if (descr->font) oldFont = SelectObject( hdc, descr->font );
627 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
628 (WPARAM)hdc, (LPARAM)descr->self );
629 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
630 if (!IsWindowEnabled(descr->self))
631 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
632 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
633 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
634 if (oldFont) SelectObject( hdc, oldFont );
635 if (oldBrush) SelectObject( hdc, oldBrush );
636 ReleaseDC( descr->self, hdc );
640 /***********************************************************************
641 * LISTBOX_DrawFocusRect
643 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
645 HDC hdc;
646 RECT rect;
647 HFONT oldFont = 0;
649 /* Do not repaint the item if the item is not visible */
650 if (!IsWindowVisible(descr->self)) return;
652 if (descr->focus_item == -1) return;
653 if (!descr->caret_on || !descr->in_focus) return;
655 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
656 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
657 if (descr->font) oldFont = SelectObject( hdc, descr->font );
658 if (!IsWindowEnabled(descr->self))
659 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
660 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
661 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
662 if (oldFont) SelectObject( hdc, oldFont );
663 ReleaseDC( descr->self, hdc );
667 /***********************************************************************
668 * LISTBOX_InitStorage
670 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
672 LB_ITEMDATA *item;
674 nb_items += LB_ARRAY_GRANULARITY - 1;
675 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
676 if (descr->items) {
677 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
678 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
679 nb_items * sizeof(LB_ITEMDATA));
681 else {
682 item = HeapAlloc( GetProcessHeap(), 0,
683 nb_items * sizeof(LB_ITEMDATA));
686 if (!item)
688 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
689 return LB_ERRSPACE;
691 descr->items = item;
692 return LB_OKAY;
696 /***********************************************************************
697 * LISTBOX_SetTabStops
699 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
701 INT i;
703 if (!(descr->style & LBS_USETABSTOPS))
705 SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
706 return FALSE;
709 HeapFree( GetProcessHeap(), 0, descr->tabs );
710 if (!(descr->nb_tabs = count))
712 descr->tabs = NULL;
713 return TRUE;
715 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
716 descr->nb_tabs * sizeof(INT) )))
717 return FALSE;
718 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
720 /* convert into "dialog units"*/
721 for (i = 0; i < descr->nb_tabs; i++)
722 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
724 return TRUE;
728 /***********************************************************************
729 * LISTBOX_GetText
731 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
733 DWORD len;
735 if ((index < 0) || (index >= descr->nb_items))
737 SetLastError(ERROR_INVALID_INDEX);
738 return LB_ERR;
741 if (HAS_STRINGS(descr))
743 if (!buffer)
744 return strlenW(descr->items[index].str);
746 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
748 __TRY /* hide a Delphi bug that passes a read-only buffer */
750 strcpyW( buffer, descr->items[index].str );
751 len = strlenW(buffer);
753 __EXCEPT_PAGE_FAULT
755 WARN( "got an invalid buffer (Delphi bug?)\n" );
756 SetLastError( ERROR_INVALID_PARAMETER );
757 return LB_ERR;
759 __ENDTRY
760 } else
762 if (buffer)
763 *((DWORD *)buffer) = *(DWORD *)&descr->items[index].data;
764 len = sizeof(DWORD);
766 return len;
769 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
771 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
772 if (ret == CSTR_LESS_THAN)
773 return -1;
774 if (ret == CSTR_EQUAL)
775 return 0;
776 if (ret == CSTR_GREATER_THAN)
777 return 1;
778 return -1;
781 /***********************************************************************
782 * LISTBOX_FindStringPos
784 * Find the nearest string located before a given string in sort order.
785 * If 'exact' is TRUE, return an error if we don't get an exact match.
787 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
789 INT index, min, max, res;
791 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
792 min = 0;
793 max = descr->nb_items;
794 while (min != max)
796 index = (min + max) / 2;
797 if (HAS_STRINGS(descr))
798 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
799 else
801 COMPAREITEMSTRUCT cis;
802 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
804 cis.CtlType = ODT_LISTBOX;
805 cis.CtlID = id;
806 cis.hwndItem = descr->self;
807 /* note that some application (MetaStock) expects the second item
808 * to be in the listbox */
809 cis.itemID1 = -1;
810 cis.itemData1 = (ULONG_PTR)str;
811 cis.itemID2 = index;
812 cis.itemData2 = descr->items[index].data;
813 cis.dwLocaleId = descr->locale;
814 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
816 if (!res) return index;
817 if (res < 0) max = index;
818 else min = index + 1;
820 return exact ? -1 : max;
824 /***********************************************************************
825 * LISTBOX_FindFileStrPos
827 * Find the nearest string located before a given string in directory
828 * sort order (i.e. first files, then directories, then drives).
830 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
832 INT min, max, res;
834 if (!HAS_STRINGS(descr))
835 return LISTBOX_FindStringPos( descr, str, FALSE );
836 min = 0;
837 max = descr->nb_items;
838 while (min != max)
840 INT index = (min + max) / 2;
841 LPCWSTR p = descr->items[index].str;
842 if (*p == '[') /* drive or directory */
844 if (*str != '[') res = -1;
845 else if (p[1] == '-') /* drive */
847 if (str[1] == '-') res = str[2] - p[2];
848 else res = -1;
850 else /* directory */
852 if (str[1] == '-') res = 1;
853 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
856 else /* filename */
858 if (*str == '[') res = 1;
859 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
861 if (!res) return index;
862 if (res < 0) max = index;
863 else min = index + 1;
865 return max;
869 /***********************************************************************
870 * LISTBOX_FindString
872 * Find the item beginning with a given string.
874 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
876 INT i;
877 LB_ITEMDATA *item;
879 if (start >= descr->nb_items) start = -1;
880 item = descr->items + start + 1;
881 if (HAS_STRINGS(descr))
883 if (!str || ! str[0] ) return LB_ERR;
884 if (exact)
886 for (i = start + 1; i < descr->nb_items; i++, item++)
887 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
888 for (i = 0, item = descr->items; i <= start; i++, item++)
889 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
891 else
893 /* Special case for drives and directories: ignore prefix */
894 #define CHECK_DRIVE(item) \
895 if ((item)->str[0] == '[') \
897 if (!strncmpiW( str, (item)->str+1, len )) return i; \
898 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
899 return i; \
902 INT len = strlenW(str);
903 for (i = start + 1; i < descr->nb_items; i++, item++)
905 if (!strncmpiW( str, item->str, len )) return i;
906 CHECK_DRIVE(item);
908 for (i = 0, item = descr->items; i <= start; i++, item++)
910 if (!strncmpiW( str, item->str, len )) return i;
911 CHECK_DRIVE(item);
913 #undef CHECK_DRIVE
916 else
918 if (exact && (descr->style & LBS_SORT))
919 /* If sorted, use a WM_COMPAREITEM binary search */
920 return LISTBOX_FindStringPos( descr, str, TRUE );
922 /* Otherwise use a linear search */
923 for (i = start + 1; i < descr->nb_items; i++, item++)
924 if (item->data == (ULONG_PTR)str) return i;
925 for (i = 0, item = descr->items; i <= start; i++, item++)
926 if (item->data == (ULONG_PTR)str) return i;
928 return LB_ERR;
932 /***********************************************************************
933 * LISTBOX_GetSelCount
935 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
937 INT i, count;
938 const LB_ITEMDATA *item = descr->items;
940 if (!(descr->style & LBS_MULTIPLESEL) ||
941 (descr->style & LBS_NOSEL))
942 return LB_ERR;
943 for (i = count = 0; i < descr->nb_items; i++, item++)
944 if (item->selected) count++;
945 return count;
949 /***********************************************************************
950 * LISTBOX_GetSelItems
952 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
954 INT i, count;
955 const LB_ITEMDATA *item = descr->items;
957 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
958 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
959 if (item->selected) array[count++] = i;
960 return count;
964 /***********************************************************************
965 * LISTBOX_Paint
967 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
969 INT i, col_pos = descr->page_size - 1;
970 RECT rect;
971 RECT focusRect = {-1, -1, -1, -1};
972 HFONT oldFont = 0;
973 HBRUSH hbrush, oldBrush = 0;
975 if (descr->style & LBS_NOREDRAW) return 0;
977 SetRect( &rect, 0, 0, descr->width, descr->height );
978 if (descr->style & LBS_MULTICOLUMN)
979 rect.right = rect.left + descr->column_width;
980 else if (descr->horz_pos)
982 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
983 rect.right += descr->horz_pos;
986 if (descr->font) oldFont = SelectObject( hdc, descr->font );
987 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
988 (WPARAM)hdc, (LPARAM)descr->self );
989 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
990 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
992 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
993 (descr->in_focus))
995 /* Special case for empty listbox: paint focus rect */
996 rect.bottom = rect.top + descr->item_height;
997 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
998 &rect, NULL, 0, NULL );
999 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1000 rect.top = rect.bottom;
1003 /* Paint all the item, regarding the selection
1004 Focus state will be painted after */
1006 for (i = descr->top_item; i < descr->nb_items; i++)
1008 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1009 rect.bottom = rect.top + descr->item_height;
1010 else
1011 rect.bottom = rect.top + descr->items[i].height;
1013 /* keep the focus rect, to paint the focus item after */
1014 if (i == descr->focus_item)
1015 focusRect = rect;
1017 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1018 rect.top = rect.bottom;
1020 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1022 if (!IS_OWNERDRAW(descr))
1024 /* Clear the bottom of the column */
1025 if (rect.top < descr->height)
1027 rect.bottom = descr->height;
1028 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1029 &rect, NULL, 0, NULL );
1033 /* Go to the next column */
1034 rect.left += descr->column_width;
1035 rect.right += descr->column_width;
1036 rect.top = 0;
1037 col_pos = descr->page_size - 1;
1039 else
1041 col_pos--;
1042 if (rect.top >= descr->height) break;
1046 /* Paint the focus item now */
1047 if (focusRect.top != focusRect.bottom &&
1048 descr->caret_on && descr->in_focus)
1049 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1051 if (!IS_OWNERDRAW(descr))
1053 /* Clear the remainder of the client area */
1054 if (rect.top < descr->height)
1056 rect.bottom = descr->height;
1057 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1058 &rect, NULL, 0, NULL );
1060 if (rect.right < descr->width)
1062 rect.left = rect.right;
1063 rect.right = descr->width;
1064 rect.top = 0;
1065 rect.bottom = descr->height;
1066 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1067 &rect, NULL, 0, NULL );
1070 if (oldFont) SelectObject( hdc, oldFont );
1071 if (oldBrush) SelectObject( hdc, oldBrush );
1072 return 0;
1076 /***********************************************************************
1077 * LISTBOX_InvalidateItems
1079 * Invalidate all items from a given item. If the specified item is not
1080 * visible, nothing happens.
1082 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1084 RECT rect;
1086 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1088 if (descr->style & LBS_NOREDRAW)
1090 descr->style |= LBS_DISPLAYCHANGED;
1091 return;
1093 rect.bottom = descr->height;
1094 InvalidateRect( descr->self, &rect, TRUE );
1095 if (descr->style & LBS_MULTICOLUMN)
1097 /* Repaint the other columns */
1098 rect.left = rect.right;
1099 rect.right = descr->width;
1100 rect.top = 0;
1101 InvalidateRect( descr->self, &rect, TRUE );
1106 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1108 RECT rect;
1110 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1111 InvalidateRect( descr->self, &rect, TRUE );
1114 /***********************************************************************
1115 * LISTBOX_GetItemHeight
1117 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1119 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1121 if ((index < 0) || (index >= descr->nb_items))
1123 SetLastError(ERROR_INVALID_INDEX);
1124 return LB_ERR;
1126 return descr->items[index].height;
1128 else return descr->item_height;
1132 /***********************************************************************
1133 * LISTBOX_SetItemHeight
1135 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1137 if (height > MAXBYTE)
1138 return -1;
1140 if (!height) height = 1;
1142 if (descr->style & LBS_OWNERDRAWVARIABLE)
1144 if ((index < 0) || (index >= descr->nb_items))
1146 SetLastError(ERROR_INVALID_INDEX);
1147 return LB_ERR;
1149 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1150 descr->items[index].height = height;
1151 LISTBOX_UpdateScroll( descr );
1152 if (repaint)
1153 LISTBOX_InvalidateItems( descr, index );
1155 else if (height != descr->item_height)
1157 TRACE("[%p]: new height = %d\n", descr->self, height );
1158 descr->item_height = height;
1159 LISTBOX_UpdatePage( descr );
1160 LISTBOX_UpdateScroll( descr );
1161 if (repaint)
1162 InvalidateRect( descr->self, 0, TRUE );
1164 return LB_OKAY;
1168 /***********************************************************************
1169 * LISTBOX_SetHorizontalPos
1171 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1173 INT diff;
1175 if (pos > descr->horz_extent - descr->width)
1176 pos = descr->horz_extent - descr->width;
1177 if (pos < 0) pos = 0;
1178 if (!(diff = descr->horz_pos - pos)) return;
1179 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1180 descr->horz_pos = pos;
1181 LISTBOX_UpdateScroll( descr );
1182 if (abs(diff) < descr->width)
1184 RECT rect;
1185 /* Invalidate the focused item so it will be repainted correctly */
1186 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1187 InvalidateRect( descr->self, &rect, TRUE );
1188 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1189 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1191 else
1192 InvalidateRect( descr->self, NULL, TRUE );
1196 /***********************************************************************
1197 * LISTBOX_SetHorizontalExtent
1199 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1201 if (descr->style & LBS_MULTICOLUMN)
1202 return LB_OKAY;
1203 if (extent == descr->horz_extent) return LB_OKAY;
1204 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1205 descr->horz_extent = extent;
1206 if (descr->style & WS_HSCROLL) {
1207 SCROLLINFO info;
1208 info.cbSize = sizeof(info);
1209 info.nMin = 0;
1210 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0;
1211 info.fMask = SIF_RANGE;
1212 if (descr->style & LBS_DISABLENOSCROLL)
1213 info.fMask |= SIF_DISABLENOSCROLL;
1214 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
1216 if (descr->horz_pos > extent - descr->width)
1217 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1218 return LB_OKAY;
1222 /***********************************************************************
1223 * LISTBOX_SetColumnWidth
1225 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1227 if (width == descr->column_width) return LB_OKAY;
1228 TRACE("[%p]: new column width = %d\n", descr->self, width );
1229 descr->column_width = width;
1230 LISTBOX_UpdatePage( descr );
1231 return LB_OKAY;
1235 /***********************************************************************
1236 * LISTBOX_SetFont
1238 * Returns the item height.
1240 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1242 HDC hdc;
1243 HFONT oldFont = 0;
1244 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1245 SIZE sz;
1247 descr->font = font;
1249 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1251 ERR("unable to get DC.\n" );
1252 return 16;
1254 if (font) oldFont = SelectObject( hdc, font );
1255 GetTextExtentPointA( hdc, alphabet, 52, &sz);
1256 if (oldFont) SelectObject( hdc, oldFont );
1257 ReleaseDC( descr->self, hdc );
1259 descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1260 if (!IS_OWNERDRAW(descr))
1261 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1262 return sz.cy;
1266 /***********************************************************************
1267 * LISTBOX_MakeItemVisible
1269 * Make sure that a given item is partially or fully visible.
1271 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1273 INT top;
1275 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1277 if (index <= descr->top_item) top = index;
1278 else if (descr->style & LBS_MULTICOLUMN)
1280 INT cols = descr->width;
1281 if (!fully) cols += descr->column_width - 1;
1282 if (cols >= descr->column_width) cols /= descr->column_width;
1283 else cols = 1;
1284 if (index < descr->top_item + (descr->page_size * cols)) return;
1285 top = index - descr->page_size * (cols - 1);
1287 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1289 INT height = fully ? descr->items[index].height : 1;
1290 for (top = index; top > descr->top_item; top--)
1291 if ((height += descr->items[top-1].height) > descr->height) break;
1293 else
1295 if (index < descr->top_item + descr->page_size) return;
1296 if (!fully && (index == descr->top_item + descr->page_size) &&
1297 (descr->height > (descr->page_size * descr->item_height))) return;
1298 top = index - descr->page_size + 1;
1300 LISTBOX_SetTopItem( descr, top, TRUE );
1303 /***********************************************************************
1304 * LISTBOX_SetCaretIndex
1306 * NOTES
1307 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1310 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1312 INT oldfocus = descr->focus_item;
1314 TRACE("old focus %d, index %d\n", oldfocus, index);
1316 if (descr->style & LBS_NOSEL) return LB_ERR;
1317 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1318 if (index == oldfocus) return LB_OKAY;
1320 LISTBOX_DrawFocusRect( descr, FALSE );
1321 descr->focus_item = index;
1323 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1324 LISTBOX_DrawFocusRect( descr, TRUE );
1326 return LB_OKAY;
1330 /***********************************************************************
1331 * LISTBOX_SelectItemRange
1333 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1335 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1336 INT last, BOOL on )
1338 INT i;
1340 /* A few sanity checks */
1342 if (descr->style & LBS_NOSEL) return LB_ERR;
1343 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1345 if (!descr->nb_items) return LB_OKAY;
1347 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1348 if (first < 0) first = 0;
1349 if (last < first) return LB_OKAY;
1351 if (on) /* Turn selection on */
1353 for (i = first; i <= last; i++)
1355 if (descr->items[i].selected) continue;
1356 descr->items[i].selected = TRUE;
1357 LISTBOX_InvalidateItemRect(descr, i);
1360 else /* Turn selection off */
1362 for (i = first; i <= last; i++)
1364 if (!descr->items[i].selected) continue;
1365 descr->items[i].selected = FALSE;
1366 LISTBOX_InvalidateItemRect(descr, i);
1369 return LB_OKAY;
1372 /***********************************************************************
1373 * LISTBOX_SetSelection
1375 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1376 BOOL on, BOOL send_notify )
1378 TRACE( "cur_sel=%d index=%d notify=%s\n",
1379 descr->selected_item, index, send_notify ? "YES" : "NO" );
1381 if (descr->style & LBS_NOSEL)
1383 descr->selected_item = index;
1384 return LB_ERR;
1386 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1387 if (descr->style & LBS_MULTIPLESEL)
1389 if (index == -1) /* Select all items */
1390 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1391 else /* Only one item */
1392 return LISTBOX_SelectItemRange( descr, index, index, on );
1394 else
1396 INT oldsel = descr->selected_item;
1397 if (index == oldsel) return LB_OKAY;
1398 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1399 if (index != -1) descr->items[index].selected = TRUE;
1400 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1401 descr->selected_item = index;
1402 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1403 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1404 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1405 else
1406 if( descr->lphc ) /* set selection change flag for parent combo */
1407 descr->lphc->wState |= CBF_SELCHANGE;
1409 return LB_OKAY;
1413 /***********************************************************************
1414 * LISTBOX_MoveCaret
1416 * Change the caret position and extend the selection to the new caret.
1418 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1420 TRACE("old focus %d, index %d\n", descr->focus_item, index);
1422 if ((index < 0) || (index >= descr->nb_items))
1423 return;
1425 /* Important, repaint needs to be done in this order if
1426 you want to mimic Windows behavior:
1427 1. Remove the focus and paint the item
1428 2. Remove the selection and paint the item(s)
1429 3. Set the selection and repaint the item(s)
1430 4. Set the focus to 'index' and repaint the item */
1432 /* 1. remove the focus and repaint the item */
1433 LISTBOX_DrawFocusRect( descr, FALSE );
1435 /* 2. then turn off the previous selection */
1436 /* 3. repaint the new selected item */
1437 if (descr->style & LBS_EXTENDEDSEL)
1439 if (descr->anchor_item != -1)
1441 INT first = min( index, descr->anchor_item );
1442 INT last = max( index, descr->anchor_item );
1443 if (first > 0)
1444 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1445 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1446 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1449 else if (!(descr->style & LBS_MULTIPLESEL))
1451 /* Set selection to new caret item */
1452 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1455 /* 4. repaint the new item with the focus */
1456 descr->focus_item = index;
1457 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1458 LISTBOX_DrawFocusRect( descr, TRUE );
1462 /***********************************************************************
1463 * LISTBOX_InsertItem
1465 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1466 LPWSTR str, ULONG_PTR data )
1468 LB_ITEMDATA *item;
1469 INT max_items;
1470 INT oldfocus = descr->focus_item;
1472 if (index == -1) index = descr->nb_items;
1473 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1474 if (!descr->items) max_items = 0;
1475 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1476 if (descr->nb_items == max_items)
1478 /* We need to grow the array */
1479 max_items += LB_ARRAY_GRANULARITY;
1480 if (descr->items)
1481 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1482 max_items * sizeof(LB_ITEMDATA) );
1483 else
1484 item = HeapAlloc( GetProcessHeap(), 0,
1485 max_items * sizeof(LB_ITEMDATA) );
1486 if (!item)
1488 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1489 return LB_ERRSPACE;
1491 descr->items = item;
1494 /* Insert the item structure */
1496 item = &descr->items[index];
1497 if (index < descr->nb_items)
1498 RtlMoveMemory( item + 1, item,
1499 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1500 item->str = str;
1501 item->data = data;
1502 item->height = 0;
1503 item->selected = FALSE;
1504 descr->nb_items++;
1506 /* Get item height */
1508 if (descr->style & LBS_OWNERDRAWVARIABLE)
1510 MEASUREITEMSTRUCT mis;
1511 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1513 mis.CtlType = ODT_LISTBOX;
1514 mis.CtlID = id;
1515 mis.itemID = index;
1516 mis.itemData = descr->items[index].data;
1517 mis.itemHeight = descr->item_height;
1518 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1519 item->height = mis.itemHeight ? mis.itemHeight : 1;
1520 TRACE("[%p]: measure item %d (%s) = %d\n",
1521 descr->self, index, str ? debugstr_w(str) : "", item->height );
1524 /* Repaint the items */
1526 LISTBOX_UpdateScroll( descr );
1527 LISTBOX_InvalidateItems( descr, index );
1529 /* Move selection and focused item */
1530 /* If listbox was empty, set focus to the first item */
1531 if (descr->nb_items == 1)
1532 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1533 /* single select don't change selection index in win31 */
1534 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1536 descr->selected_item++;
1537 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1539 else
1541 if (index <= descr->selected_item)
1543 descr->selected_item++;
1544 descr->focus_item = oldfocus; /* focus not changed */
1547 return LB_OKAY;
1551 /***********************************************************************
1552 * LISTBOX_InsertString
1554 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1556 LPWSTR new_str = NULL;
1557 ULONG_PTR data = 0;
1558 LRESULT ret;
1560 if (HAS_STRINGS(descr))
1562 static const WCHAR empty_stringW[] = { 0 };
1563 if (!str) str = empty_stringW;
1564 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1566 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1567 return LB_ERRSPACE;
1569 strcpyW(new_str, str);
1571 else data = (ULONG_PTR)str;
1573 if (index == -1) index = descr->nb_items;
1574 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1576 HeapFree( GetProcessHeap(), 0, new_str );
1577 return ret;
1580 TRACE("[%p]: added item %d %s\n",
1581 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1582 return index;
1586 /***********************************************************************
1587 * LISTBOX_DeleteItem
1589 * Delete the content of an item. 'index' must be a valid index.
1591 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1593 /* save the item data before it gets freed by LB_RESETCONTENT */
1594 ULONG_PTR item_data = descr->items[index].data;
1595 LPWSTR item_str = descr->items[index].str;
1597 if (!descr->nb_items)
1598 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1600 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1601 * while Win95 sends it for all items with user data.
1602 * It's probably better to send it too often than not
1603 * often enough, so this is what we do here.
1605 if (IS_OWNERDRAW(descr) || item_data)
1607 DELETEITEMSTRUCT dis;
1608 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1610 dis.CtlType = ODT_LISTBOX;
1611 dis.CtlID = id;
1612 dis.itemID = index;
1613 dis.hwndItem = descr->self;
1614 dis.itemData = item_data;
1615 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1617 if (HAS_STRINGS(descr))
1618 HeapFree( GetProcessHeap(), 0, item_str );
1622 /***********************************************************************
1623 * LISTBOX_RemoveItem
1625 * Remove an item from the listbox and delete its content.
1627 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1629 LB_ITEMDATA *item;
1630 INT max_items;
1632 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1634 /* We need to invalidate the original rect instead of the updated one. */
1635 LISTBOX_InvalidateItems( descr, index );
1637 descr->nb_items--;
1638 LISTBOX_DeleteItem( descr, index );
1640 if (!descr->nb_items) return LB_OKAY;
1642 /* Remove the item */
1644 item = &descr->items[index];
1645 if (index < descr->nb_items)
1646 RtlMoveMemory( item, item + 1,
1647 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1648 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1650 /* Shrink the item array if possible */
1652 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1653 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1655 max_items -= LB_ARRAY_GRANULARITY;
1656 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1657 max_items * sizeof(LB_ITEMDATA) );
1658 if (item) descr->items = item;
1660 /* Repaint the items */
1662 LISTBOX_UpdateScroll( descr );
1663 /* if we removed the scrollbar, reset the top of the list
1664 (correct for owner-drawn ???) */
1665 if (descr->nb_items == descr->page_size)
1666 LISTBOX_SetTopItem( descr, 0, TRUE );
1668 /* Move selection and focused item */
1669 if (!IS_MULTISELECT(descr))
1671 if (index == descr->selected_item)
1672 descr->selected_item = -1;
1673 else if (index < descr->selected_item)
1675 descr->selected_item--;
1676 if (ISWIN31) /* win 31 do not change the selected item number */
1677 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1681 if (descr->focus_item >= descr->nb_items)
1683 descr->focus_item = descr->nb_items - 1;
1684 if (descr->focus_item < 0) descr->focus_item = 0;
1686 return LB_OKAY;
1690 /***********************************************************************
1691 * LISTBOX_ResetContent
1693 static void LISTBOX_ResetContent( LB_DESCR *descr )
1695 INT i;
1697 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1698 HeapFree( GetProcessHeap(), 0, descr->items );
1699 descr->nb_items = 0;
1700 descr->top_item = 0;
1701 descr->selected_item = -1;
1702 descr->focus_item = 0;
1703 descr->anchor_item = -1;
1704 descr->items = NULL;
1708 /***********************************************************************
1709 * LISTBOX_SetCount
1711 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1713 LRESULT ret;
1715 if (HAS_STRINGS(descr))
1717 SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1718 return LB_ERR;
1721 /* FIXME: this is far from optimal... */
1722 if (count > descr->nb_items)
1724 while (count > descr->nb_items)
1725 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1726 return ret;
1728 else if (count < descr->nb_items)
1730 while (count < descr->nb_items)
1731 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1732 return ret;
1735 InvalidateRect( descr->self, NULL, TRUE );
1736 return LB_OKAY;
1740 /***********************************************************************
1741 * LISTBOX_Directory
1743 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1744 LPCWSTR filespec, BOOL long_names )
1746 HANDLE handle;
1747 LRESULT ret = LB_OKAY;
1748 WIN32_FIND_DATAW entry;
1749 int pos;
1750 LRESULT maxinsert = LB_ERR;
1752 /* don't scan directory if we just want drives exclusively */
1753 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1754 /* scan directory */
1755 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1757 int le = GetLastError();
1758 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1760 else
1764 WCHAR buffer[270];
1765 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1767 static const WCHAR bracketW[] = { ']',0 };
1768 static const WCHAR dotW[] = { '.',0 };
1769 if (!(attrib & DDL_DIRECTORY) ||
1770 !strcmpW( entry.cFileName, dotW )) continue;
1771 buffer[0] = '[';
1772 if (!long_names && entry.cAlternateFileName[0])
1773 strcpyW( buffer + 1, entry.cAlternateFileName );
1774 else
1775 strcpyW( buffer + 1, entry.cFileName );
1776 strcatW(buffer, bracketW);
1778 else /* not a directory */
1780 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1781 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1783 if ((attrib & DDL_EXCLUSIVE) &&
1784 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1785 continue;
1786 #undef ATTRIBS
1787 if (!long_names && entry.cAlternateFileName[0])
1788 strcpyW( buffer, entry.cAlternateFileName );
1789 else
1790 strcpyW( buffer, entry.cFileName );
1792 if (!long_names) CharLowerW( buffer );
1793 pos = LISTBOX_FindFileStrPos( descr, buffer );
1794 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1795 break;
1796 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1797 } while (FindNextFileW( handle, &entry ));
1798 FindClose( handle );
1801 if (ret >= 0)
1803 ret = maxinsert;
1805 /* scan drives */
1806 if (attrib & DDL_DRIVES)
1808 WCHAR buffer[] = {'[','-','a','-',']',0};
1809 WCHAR root[] = {'A',':','\\',0};
1810 int drive;
1811 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1813 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1814 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1815 break;
1819 return ret;
1823 /***********************************************************************
1824 * LISTBOX_HandleVScroll
1826 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1828 SCROLLINFO info;
1830 if (descr->style & LBS_MULTICOLUMN) return 0;
1831 switch(scrollReq)
1833 case SB_LINEUP:
1834 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1835 break;
1836 case SB_LINEDOWN:
1837 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1838 break;
1839 case SB_PAGEUP:
1840 LISTBOX_SetTopItem( descr, descr->top_item -
1841 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1842 break;
1843 case SB_PAGEDOWN:
1844 LISTBOX_SetTopItem( descr, descr->top_item +
1845 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1846 break;
1847 case SB_THUMBPOSITION:
1848 LISTBOX_SetTopItem( descr, pos, TRUE );
1849 break;
1850 case SB_THUMBTRACK:
1851 info.cbSize = sizeof(info);
1852 info.fMask = SIF_TRACKPOS;
1853 GetScrollInfo( descr->self, SB_VERT, &info );
1854 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1855 break;
1856 case SB_TOP:
1857 LISTBOX_SetTopItem( descr, 0, TRUE );
1858 break;
1859 case SB_BOTTOM:
1860 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1861 break;
1863 return 0;
1867 /***********************************************************************
1868 * LISTBOX_HandleHScroll
1870 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1872 SCROLLINFO info;
1873 INT page;
1875 if (descr->style & LBS_MULTICOLUMN)
1877 switch(scrollReq)
1879 case SB_LINELEFT:
1880 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1881 TRUE );
1882 break;
1883 case SB_LINERIGHT:
1884 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1885 TRUE );
1886 break;
1887 case SB_PAGELEFT:
1888 page = descr->width / descr->column_width;
1889 if (page < 1) page = 1;
1890 LISTBOX_SetTopItem( descr,
1891 descr->top_item - page * descr->page_size, TRUE );
1892 break;
1893 case SB_PAGERIGHT:
1894 page = descr->width / descr->column_width;
1895 if (page < 1) page = 1;
1896 LISTBOX_SetTopItem( descr,
1897 descr->top_item + page * descr->page_size, TRUE );
1898 break;
1899 case SB_THUMBPOSITION:
1900 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1901 break;
1902 case SB_THUMBTRACK:
1903 info.cbSize = sizeof(info);
1904 info.fMask = SIF_TRACKPOS;
1905 GetScrollInfo( descr->self, SB_VERT, &info );
1906 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1907 TRUE );
1908 break;
1909 case SB_LEFT:
1910 LISTBOX_SetTopItem( descr, 0, TRUE );
1911 break;
1912 case SB_RIGHT:
1913 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1914 break;
1917 else if (descr->horz_extent)
1919 switch(scrollReq)
1921 case SB_LINELEFT:
1922 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1923 break;
1924 case SB_LINERIGHT:
1925 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1926 break;
1927 case SB_PAGELEFT:
1928 LISTBOX_SetHorizontalPos( descr,
1929 descr->horz_pos - descr->width );
1930 break;
1931 case SB_PAGERIGHT:
1932 LISTBOX_SetHorizontalPos( descr,
1933 descr->horz_pos + descr->width );
1934 break;
1935 case SB_THUMBPOSITION:
1936 LISTBOX_SetHorizontalPos( descr, pos );
1937 break;
1938 case SB_THUMBTRACK:
1939 info.cbSize = sizeof(info);
1940 info.fMask = SIF_TRACKPOS;
1941 GetScrollInfo( descr->self, SB_HORZ, &info );
1942 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1943 break;
1944 case SB_LEFT:
1945 LISTBOX_SetHorizontalPos( descr, 0 );
1946 break;
1947 case SB_RIGHT:
1948 LISTBOX_SetHorizontalPos( descr,
1949 descr->horz_extent - descr->width );
1950 break;
1953 return 0;
1956 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1958 UINT pulScrollLines = 3;
1960 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1962 /* if scrolling changes direction, ignore left overs */
1963 if ((delta < 0 && descr->wheel_remain < 0) ||
1964 (delta > 0 && descr->wheel_remain > 0))
1965 descr->wheel_remain += delta;
1966 else
1967 descr->wheel_remain = delta;
1969 if (descr->wheel_remain && pulScrollLines)
1971 int cLineScroll;
1972 pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
1973 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
1974 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
1975 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
1977 return 0;
1980 /***********************************************************************
1981 * LISTBOX_HandleLButtonDown
1983 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
1985 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1987 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
1988 descr->self, x, y, index, descr->focus_item);
1990 if (!descr->caret_on && (descr->in_focus)) return 0;
1992 if (!descr->in_focus)
1994 if( !descr->lphc ) SetFocus( descr->self );
1995 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1998 if (index == -1) return 0;
2000 if (!descr->lphc)
2002 if (descr->style & LBS_NOTIFY )
2003 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2004 MAKELPARAM( x, y ) );
2007 descr->captured = TRUE;
2008 SetCapture( descr->self );
2010 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2012 /* we should perhaps make sure that all items are deselected
2013 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2014 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2015 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2018 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2019 if (keys & MK_CONTROL)
2021 LISTBOX_SetCaretIndex( descr, index, FALSE );
2022 LISTBOX_SetSelection( descr, index,
2023 !descr->items[index].selected,
2024 (descr->style & LBS_NOTIFY) != 0);
2026 else
2028 LISTBOX_MoveCaret( descr, index, FALSE );
2030 if (descr->style & LBS_EXTENDEDSEL)
2032 LISTBOX_SetSelection( descr, index,
2033 descr->items[index].selected,
2034 (descr->style & LBS_NOTIFY) != 0 );
2036 else
2038 LISTBOX_SetSelection( descr, index,
2039 !descr->items[index].selected,
2040 (descr->style & LBS_NOTIFY) != 0 );
2044 else
2046 descr->anchor_item = index;
2047 LISTBOX_MoveCaret( descr, index, FALSE );
2048 LISTBOX_SetSelection( descr, index,
2049 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2052 if (!descr->lphc)
2054 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2056 POINT pt;
2058 pt.x = x;
2059 pt.y = y;
2061 if (DragDetect( descr->self, pt ))
2062 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2065 return 0;
2069 /*************************************************************************
2070 * LISTBOX_HandleLButtonDownCombo [Internal]
2072 * Process LButtonDown message for the ComboListBox
2074 * PARAMS
2075 * pWnd [I] The windows internal structure
2076 * pDescr [I] The ListBox internal structure
2077 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2078 * x [I] X Mouse Coordinate
2079 * y [I] Y Mouse Coordinate
2081 * RETURNS
2082 * 0 since we are processing the WM_LBUTTONDOWN Message
2084 * NOTES
2085 * This function is only to be used when a ListBox is a ComboListBox
2088 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2090 RECT clientRect, screenRect;
2091 POINT mousePos;
2093 mousePos.x = x;
2094 mousePos.y = y;
2096 GetClientRect(descr->self, &clientRect);
2098 if(PtInRect(&clientRect, mousePos))
2100 /* MousePos is in client, resume normal processing */
2101 if (msg == WM_LBUTTONDOWN)
2103 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2104 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2106 else if (descr->style & LBS_NOTIFY)
2107 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2109 else
2111 POINT screenMousePos;
2112 HWND hWndOldCapture;
2114 /* Check the Non-Client Area */
2115 screenMousePos = mousePos;
2116 hWndOldCapture = GetCapture();
2117 ReleaseCapture();
2118 GetWindowRect(descr->self, &screenRect);
2119 ClientToScreen(descr->self, &screenMousePos);
2121 if(!PtInRect(&screenRect, screenMousePos))
2123 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2124 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2125 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2127 else
2129 /* Check to see the NC is a scrollbar */
2130 INT nHitTestType=0;
2131 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2132 /* Check Vertical scroll bar */
2133 if (style & WS_VSCROLL)
2135 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2136 if (PtInRect( &clientRect, mousePos ))
2137 nHitTestType = HTVSCROLL;
2139 /* Check horizontal scroll bar */
2140 if (style & WS_HSCROLL)
2142 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2143 if (PtInRect( &clientRect, mousePos ))
2144 nHitTestType = HTHSCROLL;
2146 /* Windows sends this message when a scrollbar is clicked
2149 if(nHitTestType != 0)
2151 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2152 MAKELONG(screenMousePos.x, screenMousePos.y));
2154 /* Resume the Capture after scrolling is complete
2156 if(hWndOldCapture != 0)
2157 SetCapture(hWndOldCapture);
2160 return 0;
2163 /***********************************************************************
2164 * LISTBOX_HandleLButtonUp
2166 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2168 if (LISTBOX_Timer != LB_TIMER_NONE)
2169 KillSystemTimer( descr->self, LB_TIMER_ID );
2170 LISTBOX_Timer = LB_TIMER_NONE;
2171 if (descr->captured)
2173 descr->captured = FALSE;
2174 if (GetCapture() == descr->self) ReleaseCapture();
2175 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2176 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2178 return 0;
2182 /***********************************************************************
2183 * LISTBOX_HandleTimer
2185 * Handle scrolling upon a timer event.
2186 * Return TRUE if scrolling should continue.
2188 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2190 switch(dir)
2192 case LB_TIMER_UP:
2193 if (descr->top_item) index = descr->top_item - 1;
2194 else index = 0;
2195 break;
2196 case LB_TIMER_LEFT:
2197 if (descr->top_item) index -= descr->page_size;
2198 break;
2199 case LB_TIMER_DOWN:
2200 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2201 if (index == descr->focus_item) index++;
2202 if (index >= descr->nb_items) index = descr->nb_items - 1;
2203 break;
2204 case LB_TIMER_RIGHT:
2205 if (index + descr->page_size < descr->nb_items)
2206 index += descr->page_size;
2207 break;
2208 case LB_TIMER_NONE:
2209 break;
2211 if (index == descr->focus_item) return FALSE;
2212 LISTBOX_MoveCaret( descr, index, FALSE );
2213 return TRUE;
2217 /***********************************************************************
2218 * LISTBOX_HandleSystemTimer
2220 * WM_SYSTIMER handler.
2222 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2224 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2226 KillSystemTimer( descr->self, LB_TIMER_ID );
2227 LISTBOX_Timer = LB_TIMER_NONE;
2229 return 0;
2233 /***********************************************************************
2234 * LISTBOX_HandleMouseMove
2236 * WM_MOUSEMOVE handler.
2238 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2239 INT x, INT y )
2241 INT index;
2242 TIMER_DIRECTION dir = LB_TIMER_NONE;
2244 if (!descr->captured) return;
2246 if (descr->style & LBS_MULTICOLUMN)
2248 if (y < 0) y = 0;
2249 else if (y >= descr->item_height * descr->page_size)
2250 y = descr->item_height * descr->page_size - 1;
2252 if (x < 0)
2254 dir = LB_TIMER_LEFT;
2255 x = 0;
2257 else if (x >= descr->width)
2259 dir = LB_TIMER_RIGHT;
2260 x = descr->width - 1;
2263 else
2265 if (y < 0) dir = LB_TIMER_UP; /* above */
2266 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2269 index = LISTBOX_GetItemFromPoint( descr, x, y );
2270 if (index == -1) index = descr->focus_item;
2271 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2273 /* Start/stop the system timer */
2275 if (dir != LB_TIMER_NONE)
2276 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2277 else if (LISTBOX_Timer != LB_TIMER_NONE)
2278 KillSystemTimer( descr->self, LB_TIMER_ID );
2279 LISTBOX_Timer = dir;
2283 /***********************************************************************
2284 * LISTBOX_HandleKeyDown
2286 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2288 INT caret = -1;
2289 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2290 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2291 bForceSelection = FALSE; /* only for single select list */
2293 if (descr->style & LBS_WANTKEYBOARDINPUT)
2295 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2296 MAKEWPARAM(LOWORD(key), descr->focus_item),
2297 (LPARAM)descr->self );
2298 if (caret == -2) return 0;
2300 if (caret == -1) switch(key)
2302 case VK_LEFT:
2303 if (descr->style & LBS_MULTICOLUMN)
2305 bForceSelection = FALSE;
2306 if (descr->focus_item >= descr->page_size)
2307 caret = descr->focus_item - descr->page_size;
2308 break;
2310 /* fall through */
2311 case VK_UP:
2312 caret = descr->focus_item - 1;
2313 if (caret < 0) caret = 0;
2314 break;
2315 case VK_RIGHT:
2316 if (descr->style & LBS_MULTICOLUMN)
2318 bForceSelection = FALSE;
2319 if (descr->focus_item + descr->page_size < descr->nb_items)
2320 caret = descr->focus_item + descr->page_size;
2321 break;
2323 /* fall through */
2324 case VK_DOWN:
2325 caret = descr->focus_item + 1;
2326 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2327 break;
2329 case VK_PRIOR:
2330 if (descr->style & LBS_MULTICOLUMN)
2332 INT page = descr->width / descr->column_width;
2333 if (page < 1) page = 1;
2334 caret = descr->focus_item - (page * descr->page_size) + 1;
2336 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2337 if (caret < 0) caret = 0;
2338 break;
2339 case VK_NEXT:
2340 if (descr->style & LBS_MULTICOLUMN)
2342 INT page = descr->width / descr->column_width;
2343 if (page < 1) page = 1;
2344 caret = descr->focus_item + (page * descr->page_size) - 1;
2346 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2347 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2348 break;
2349 case VK_HOME:
2350 caret = 0;
2351 break;
2352 case VK_END:
2353 caret = descr->nb_items - 1;
2354 break;
2355 case VK_SPACE:
2356 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2357 else if (descr->style & LBS_MULTIPLESEL)
2359 LISTBOX_SetSelection( descr, descr->focus_item,
2360 !descr->items[descr->focus_item].selected,
2361 (descr->style & LBS_NOTIFY) != 0 );
2363 break;
2364 default:
2365 bForceSelection = FALSE;
2367 if (bForceSelection) /* focused item is used instead of key */
2368 caret = descr->focus_item;
2369 if (caret >= 0)
2371 if (((descr->style & LBS_EXTENDEDSEL) &&
2372 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2373 !IS_MULTISELECT(descr))
2374 descr->anchor_item = caret;
2375 LISTBOX_MoveCaret( descr, caret, TRUE );
2377 if (descr->style & LBS_MULTIPLESEL)
2378 descr->selected_item = caret;
2379 else
2380 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2381 if (descr->style & LBS_NOTIFY)
2383 if (descr->lphc && IsWindowVisible( descr->self ))
2385 /* make sure that combo parent doesn't hide us */
2386 descr->lphc->wState |= CBF_NOROLLUP;
2388 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2391 return 0;
2395 /***********************************************************************
2396 * LISTBOX_HandleChar
2398 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2400 INT caret = -1;
2401 WCHAR str[2];
2403 str[0] = charW;
2404 str[1] = '\0';
2406 if (descr->style & LBS_WANTKEYBOARDINPUT)
2408 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2409 MAKEWPARAM(charW, descr->focus_item),
2410 (LPARAM)descr->self );
2411 if (caret == -2) return 0;
2413 if (caret == -1)
2414 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2415 if (caret != -1)
2417 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2418 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2419 LISTBOX_MoveCaret( descr, caret, TRUE );
2420 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2421 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2423 return 0;
2427 /***********************************************************************
2428 * LISTBOX_Create
2430 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2432 LB_DESCR *descr;
2433 MEASUREITEMSTRUCT mis;
2434 RECT rect;
2436 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2437 return FALSE;
2439 GetClientRect( hwnd, &rect );
2440 descr->self = hwnd;
2441 descr->owner = GetParent( descr->self );
2442 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2443 descr->width = rect.right - rect.left;
2444 descr->height = rect.bottom - rect.top;
2445 descr->items = NULL;
2446 descr->nb_items = 0;
2447 descr->top_item = 0;
2448 descr->selected_item = -1;
2449 descr->focus_item = 0;
2450 descr->anchor_item = -1;
2451 descr->item_height = 1;
2452 descr->page_size = 1;
2453 descr->column_width = 150;
2454 descr->horz_extent = 0;
2455 descr->horz_pos = 0;
2456 descr->nb_tabs = 0;
2457 descr->tabs = NULL;
2458 descr->wheel_remain = 0;
2459 descr->caret_on = !lphc;
2460 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2461 descr->in_focus = FALSE;
2462 descr->captured = FALSE;
2463 descr->font = 0;
2464 descr->locale = GetUserDefaultLCID();
2465 descr->lphc = lphc;
2467 if( lphc )
2469 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2470 descr->owner = lphc->self;
2473 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2475 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2477 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2478 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2479 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2480 descr->item_height = LISTBOX_SetFont( descr, 0 );
2482 if (descr->style & LBS_OWNERDRAWFIXED)
2484 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2486 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2487 descr->item_height = lphc->fixedOwnerDrawHeight;
2489 else
2491 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2492 mis.CtlType = ODT_LISTBOX;
2493 mis.CtlID = id;
2494 mis.itemID = -1;
2495 mis.itemWidth = 0;
2496 mis.itemData = 0;
2497 mis.itemHeight = descr->item_height;
2498 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2499 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2503 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2504 return TRUE;
2508 /***********************************************************************
2509 * LISTBOX_Destroy
2511 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2513 LISTBOX_ResetContent( descr );
2514 SetWindowLongPtrW( descr->self, 0, 0 );
2515 HeapFree( GetProcessHeap(), 0, descr );
2516 return TRUE;
2520 /***********************************************************************
2521 * ListBoxWndProc_common
2523 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
2525 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2526 LPHEADCOMBO lphc = 0;
2527 LRESULT ret;
2529 if (!descr)
2531 if (!IsWindow(hwnd)) return 0;
2533 if (msg == WM_CREATE)
2535 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2536 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2537 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2538 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2539 return 0;
2541 /* Ignore all other messages before we get a WM_CREATE */
2542 return DefWindowProcW( hwnd, msg, wParam, lParam );
2544 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2546 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr->self, msg, wParam, lParam );
2548 switch(msg)
2550 case LB_RESETCONTENT:
2551 LISTBOX_ResetContent( descr );
2552 LISTBOX_UpdateScroll( descr );
2553 InvalidateRect( descr->self, NULL, TRUE );
2554 return 0;
2556 case LB_ADDSTRING:
2558 const WCHAR *textW = (const WCHAR *)lParam;
2559 INT index = LISTBOX_FindStringPos( descr, textW, FALSE );
2560 return LISTBOX_InsertString( descr, index, textW );
2563 case LB_INSERTSTRING:
2564 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam );
2566 case LB_ADDFILE:
2568 const WCHAR *textW = (const WCHAR *)lParam;
2569 INT index = LISTBOX_FindFileStrPos( descr, textW );
2570 return LISTBOX_InsertString( descr, index, textW );
2573 case LB_DELETESTRING:
2574 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2575 return descr->nb_items;
2576 else
2578 SetLastError(ERROR_INVALID_INDEX);
2579 return LB_ERR;
2582 case LB_GETITEMDATA:
2583 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2585 SetLastError(ERROR_INVALID_INDEX);
2586 return LB_ERR;
2588 return descr->items[wParam].data;
2590 case LB_SETITEMDATA:
2591 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2593 SetLastError(ERROR_INVALID_INDEX);
2594 return LB_ERR;
2596 descr->items[wParam].data = lParam;
2597 /* undocumented: returns TRUE, not LB_OKAY (0) */
2598 return TRUE;
2600 case LB_GETCOUNT:
2601 return descr->nb_items;
2603 case LB_GETTEXT:
2604 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE );
2606 case LB_GETTEXTLEN:
2607 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2609 SetLastError(ERROR_INVALID_INDEX);
2610 return LB_ERR;
2612 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2613 return strlenW( descr->items[wParam].str );
2615 case LB_GETCURSEL:
2616 if (descr->nb_items == 0)
2617 return LB_ERR;
2618 if (!IS_MULTISELECT(descr))
2619 return descr->selected_item;
2620 if (descr->selected_item != -1)
2621 return descr->selected_item;
2622 return descr->focus_item;
2623 /* otherwise, if the user tries to move the selection with the */
2624 /* arrow keys, we will give the application something to choke on */
2625 case LB_GETTOPINDEX:
2626 return descr->top_item;
2628 case LB_GETITEMHEIGHT:
2629 return LISTBOX_GetItemHeight( descr, wParam );
2631 case LB_SETITEMHEIGHT:
2632 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2634 case LB_ITEMFROMPOINT:
2636 POINT pt;
2637 RECT rect;
2638 int index;
2639 BOOL hit = TRUE;
2641 /* The hiword of the return value is not a client area
2642 hittest as suggested by MSDN, but rather a hittest on
2643 the returned listbox item. */
2645 if(descr->nb_items == 0)
2646 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */
2648 pt.x = (short)LOWORD(lParam);
2649 pt.y = (short)HIWORD(lParam);
2651 SetRect(&rect, 0, 0, descr->width, descr->height);
2653 if(!PtInRect(&rect, pt))
2655 pt.x = min(pt.x, rect.right - 1);
2656 pt.x = max(pt.x, 0);
2657 pt.y = min(pt.y, rect.bottom - 1);
2658 pt.y = max(pt.y, 0);
2659 hit = FALSE;
2662 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2664 if(index == -1)
2666 index = descr->nb_items - 1;
2667 hit = FALSE;
2669 return MAKELONG(index, hit ? 0 : 1);
2672 case LB_SETCARETINDEX:
2673 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2674 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2675 return LB_ERR;
2676 else if (ISWIN31)
2677 return wParam;
2678 else
2679 return LB_OKAY;
2681 case LB_GETCARETINDEX:
2682 return descr->focus_item;
2684 case LB_SETTOPINDEX:
2685 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2687 case LB_SETCOLUMNWIDTH:
2688 return LISTBOX_SetColumnWidth( descr, wParam );
2690 case LB_GETITEMRECT:
2691 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2693 case LB_FINDSTRING:
2694 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE );
2696 case LB_FINDSTRINGEXACT:
2697 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE );
2699 case LB_SELECTSTRING:
2701 const WCHAR *textW = (const WCHAR *)lParam;
2702 INT index;
2704 if (HAS_STRINGS(descr))
2705 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW));
2707 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2708 if (index != LB_ERR)
2710 LISTBOX_MoveCaret( descr, index, TRUE );
2711 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2713 return index;
2716 case LB_GETSEL:
2717 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2718 return LB_ERR;
2719 return descr->items[wParam].selected;
2721 case LB_SETSEL:
2722 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2724 case LB_SETCURSEL:
2725 if (IS_MULTISELECT(descr)) return LB_ERR;
2726 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2727 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2728 if (ret != LB_ERR) ret = descr->selected_item;
2729 return ret;
2731 case LB_GETSELCOUNT:
2732 return LISTBOX_GetSelCount( descr );
2734 case LB_GETSELITEMS:
2735 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2737 case LB_SELITEMRANGE:
2738 if (LOWORD(lParam) <= HIWORD(lParam))
2739 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2740 HIWORD(lParam), wParam );
2741 else
2742 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2743 LOWORD(lParam), wParam );
2745 case LB_SELITEMRANGEEX:
2746 if ((INT)lParam >= (INT)wParam)
2747 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2748 else
2749 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2751 case LB_GETHORIZONTALEXTENT:
2752 return descr->horz_extent;
2754 case LB_SETHORIZONTALEXTENT:
2755 return LISTBOX_SetHorizontalExtent( descr, wParam );
2757 case LB_GETANCHORINDEX:
2758 return descr->anchor_item;
2760 case LB_SETANCHORINDEX:
2761 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2763 SetLastError(ERROR_INVALID_INDEX);
2764 return LB_ERR;
2766 descr->anchor_item = (INT)wParam;
2767 return LB_OKAY;
2769 case LB_DIR:
2770 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR );
2772 case LB_GETLOCALE:
2773 return descr->locale;
2775 case LB_SETLOCALE:
2777 LCID ret;
2778 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2779 return LB_ERR;
2780 ret = descr->locale;
2781 descr->locale = (LCID)wParam;
2782 return ret;
2785 case LB_INITSTORAGE:
2786 return LISTBOX_InitStorage( descr, wParam );
2788 case LB_SETCOUNT:
2789 return LISTBOX_SetCount( descr, (INT)wParam );
2791 case LB_SETTABSTOPS:
2792 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2794 case LB_CARETON:
2795 if (descr->caret_on)
2796 return LB_OKAY;
2797 descr->caret_on = TRUE;
2798 if ((descr->focus_item != -1) && (descr->in_focus))
2799 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2800 return LB_OKAY;
2802 case LB_CARETOFF:
2803 if (!descr->caret_on)
2804 return LB_OKAY;
2805 descr->caret_on = FALSE;
2806 if ((descr->focus_item != -1) && (descr->in_focus))
2807 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2808 return LB_OKAY;
2810 case LB_GETLISTBOXINFO:
2811 return descr->page_size;
2813 case WM_DESTROY:
2814 return LISTBOX_Destroy( descr );
2816 case WM_ENABLE:
2817 InvalidateRect( descr->self, NULL, TRUE );
2818 return 0;
2820 case WM_SETREDRAW:
2821 LISTBOX_SetRedraw( descr, wParam != 0 );
2822 return 0;
2824 case WM_GETDLGCODE:
2825 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2827 case WM_PRINTCLIENT:
2828 case WM_PAINT:
2830 PAINTSTRUCT ps;
2831 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2832 ret = LISTBOX_Paint( descr, hdc );
2833 if( !wParam ) EndPaint( descr->self, &ps );
2835 return ret;
2836 case WM_SIZE:
2837 LISTBOX_UpdateSize( descr );
2838 return 0;
2839 case WM_GETFONT:
2840 return (LRESULT)descr->font;
2841 case WM_SETFONT:
2842 LISTBOX_SetFont( descr, (HFONT)wParam );
2843 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2844 return 0;
2845 case WM_SETFOCUS:
2846 descr->in_focus = TRUE;
2847 descr->caret_on = TRUE;
2848 if (descr->focus_item != -1)
2849 LISTBOX_DrawFocusRect( descr, TRUE );
2850 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2851 return 0;
2852 case WM_KILLFOCUS:
2853 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */
2854 descr->in_focus = FALSE;
2855 descr->wheel_remain = 0;
2856 if ((descr->focus_item != -1) && descr->caret_on)
2857 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2858 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
2859 return 0;
2860 case WM_HSCROLL:
2861 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2862 case WM_VSCROLL:
2863 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
2864 case WM_MOUSEWHEEL:
2865 if (wParam & (MK_SHIFT | MK_CONTROL))
2866 return DefWindowProcW( descr->self, msg, wParam, lParam );
2867 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
2868 case WM_LBUTTONDOWN:
2869 if (lphc)
2870 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2871 (INT16)LOWORD(lParam),
2872 (INT16)HIWORD(lParam) );
2873 return LISTBOX_HandleLButtonDown( descr, wParam,
2874 (INT16)LOWORD(lParam),
2875 (INT16)HIWORD(lParam) );
2876 case WM_LBUTTONDBLCLK:
2877 if (lphc)
2878 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
2879 (INT16)LOWORD(lParam),
2880 (INT16)HIWORD(lParam) );
2881 if (descr->style & LBS_NOTIFY)
2882 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2883 return 0;
2884 case WM_MOUSEMOVE:
2885 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
2887 BOOL captured = descr->captured;
2888 POINT mousePos;
2889 RECT clientRect;
2891 mousePos.x = (INT16)LOWORD(lParam);
2892 mousePos.y = (INT16)HIWORD(lParam);
2895 * If we are in a dropdown combobox, we simulate that
2896 * the mouse is captured to show the tracking of the item.
2898 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
2899 descr->captured = TRUE;
2901 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
2903 descr->captured = captured;
2905 else if (GetCapture() == descr->self)
2907 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
2908 (INT16)HIWORD(lParam) );
2910 return 0;
2911 case WM_LBUTTONUP:
2912 if (lphc)
2914 POINT mousePos;
2915 RECT clientRect;
2918 * If the mouse button "up" is not in the listbox,
2919 * we make sure there is no selection by re-selecting the
2920 * item that was selected when the listbox was made visible.
2922 mousePos.x = (INT16)LOWORD(lParam);
2923 mousePos.y = (INT16)HIWORD(lParam);
2925 GetClientRect(descr->self, &clientRect);
2928 * When the user clicks outside the combobox and the focus
2929 * is lost, the owning combobox will send a fake buttonup with
2930 * 0xFFFFFFF as the mouse location, we must also revert the
2931 * selection to the original selection.
2933 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
2934 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
2936 return LISTBOX_HandleLButtonUp( descr );
2937 case WM_KEYDOWN:
2938 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
2940 /* for some reason Windows makes it possible to
2941 * show/hide ComboLBox by sending it WM_KEYDOWNs */
2943 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
2944 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
2945 && (wParam == VK_DOWN || wParam == VK_UP)) )
2947 COMBO_FlipListbox( lphc, FALSE, FALSE );
2948 return 0;
2951 return LISTBOX_HandleKeyDown( descr, wParam );
2952 case WM_CHAR:
2953 return LISTBOX_HandleChar( descr, wParam );
2955 case WM_SYSTIMER:
2956 return LISTBOX_HandleSystemTimer( descr );
2957 case WM_ERASEBKGND:
2958 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2960 RECT rect;
2961 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
2962 wParam, (LPARAM)descr->self );
2963 TRACE("hbrush = %p\n", hbrush);
2964 if(!hbrush)
2965 hbrush = GetSysColorBrush(COLOR_WINDOW);
2966 if(hbrush)
2968 GetClientRect(descr->self, &rect);
2969 FillRect((HDC)wParam, &rect, hbrush);
2972 return 1;
2973 case WM_DROPFILES:
2974 if( lphc ) return 0;
2975 return SendMessageW( descr->owner, msg, wParam, lParam );
2977 case WM_NCDESTROY:
2978 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
2979 lphc->hWndLBox = 0;
2980 break;
2982 case WM_NCACTIVATE:
2983 if (lphc) return 0;
2984 break;
2986 default:
2987 if ((msg >= WM_USER) && (msg < 0xc000))
2988 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
2989 hwnd, msg, wParam, lParam );
2992 return DefWindowProcW( hwnd, msg, wParam, lParam );
2995 void LISTBOX_Register(void)
2997 WNDCLASSW wndClass;
2999 memset(&wndClass, 0, sizeof(wndClass));
3000 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
3001 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3002 wndClass.cbClsExtra = 0;
3003 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3004 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3005 wndClass.hbrBackground = NULL;
3006 wndClass.lpszClassName = WC_LISTBOXW;
3007 RegisterClassW(&wndClass);
3010 void COMBOLBOX_Register(void)
3012 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
3013 WNDCLASSW wndClass;
3015 memset(&wndClass, 0, sizeof(wndClass));
3016 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS;
3017 wndClass.lpfnWndProc = LISTBOX_WindowProc;
3018 wndClass.cbClsExtra = 0;
3019 wndClass.cbWndExtra = sizeof(LB_DESCR *);
3020 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3021 wndClass.hbrBackground = NULL;
3022 wndClass.lpszClassName = combolboxW;
3023 RegisterClassW(&wndClass);