User controls: GWL_ -> GWLP_.
[wine/multimedia.git] / controls / listbox.c
blobc00d889ee243ee3d377bdb7beba0ef1114394104
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 #include <string.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include "windef.h"
26 #include "winbase.h"
27 #include "wingdi.h"
28 #include "wine/winuser16.h"
29 #include "wine/winbase16.h"
30 #include "wownt32.h"
31 #include "wine/unicode.h"
32 #include "winuser.h"
33 #include "winerror.h"
34 #include "message.h"
35 #include "user.h"
36 #include "controls.h"
37 #include "wine/debug.h"
38 #include "win.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
41 WINE_DECLARE_DEBUG_CHANNEL(combo);
43 /* Unimplemented yet:
44 * - LBS_USETABSTOPS
45 * - Locale handling
47 * Probably needs improvement:
48 * - LBS_NOSEL
51 /* Items array granularity */
52 #define LB_ARRAY_GRANULARITY 16
54 /* Scrolling timeout in ms */
55 #define LB_SCROLL_TIMEOUT 50
57 /* Listbox system timer id */
58 #define LB_TIMER_ID 2
60 /* flag listbox changed while setredraw false - internal style */
61 #define LBS_DISPLAYCHANGED 0x80000000
63 /* Item structure */
64 typedef struct
66 LPWSTR str; /* Item text */
67 BOOL selected; /* Is item selected? */
68 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
69 DWORD data; /* User data */
70 } LB_ITEMDATA;
72 /* Listbox structure */
73 typedef struct
75 HWND owner; /* Owner window to send notifications to */
76 UINT style; /* Window style */
77 INT width; /* Window width */
78 INT height; /* Window height */
79 LB_ITEMDATA *items; /* Array of items */
80 INT nb_items; /* Number of items */
81 INT top_item; /* Top visible item */
82 INT selected_item; /* Selected item */
83 INT focus_item; /* Item that has the focus */
84 INT anchor_item; /* Anchor item for extended selection */
85 INT item_height; /* Default item height */
86 INT page_size; /* Items per listbox page */
87 INT column_width; /* Column width for multi-column listboxes */
88 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
89 INT horz_pos; /* Horizontal position */
90 INT nb_tabs; /* Number of tabs in array */
91 INT *tabs; /* Array of tabs */
92 BOOL caret_on; /* Is caret on? */
93 BOOL captured; /* Is mouse captured? */
94 BOOL in_focus;
95 HFONT font; /* Current font */
96 LCID locale; /* Current locale for string comparisons */
97 LPHEADCOMBO lphc; /* ComboLBox */
98 } LB_DESCR;
101 #define IS_OWNERDRAW(descr) \
102 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
104 #define HAS_STRINGS(descr) \
105 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108 #define IS_MULTISELECT(descr) \
109 ((descr)->style & LBS_MULTIPLESEL || ((descr)->style & LBS_EXTENDEDSEL))
111 #define SEND_NOTIFICATION(hwnd,descr,code) \
112 (SendMessageW( (descr)->owner, WM_COMMAND, \
113 MAKEWPARAM( GetWindowLongPtrW((hwnd),GWLP_ID), (code)), (LPARAM)(hwnd) ))
115 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
117 /* Current timer status */
118 typedef enum
120 LB_TIMER_NONE,
121 LB_TIMER_UP,
122 LB_TIMER_LEFT,
123 LB_TIMER_DOWN,
124 LB_TIMER_RIGHT
125 } TIMER_DIRECTION;
127 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
129 static LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
130 static LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
131 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
132 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
134 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
136 /*********************************************************************
137 * listbox class descriptor
139 const struct builtin_class_descr LISTBOX_builtin_class =
141 "ListBox", /* name */
142 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
143 ListBoxWndProcA, /* procA */
144 ListBoxWndProcW, /* procW */
145 sizeof(LB_DESCR *), /* extra */
146 IDC_ARROW, /* cursor */
147 0 /* brush */
151 /*********************************************************************
152 * combolbox class descriptor
154 const struct builtin_class_descr COMBOLBOX_builtin_class =
156 "ComboLBox", /* name */
157 CS_DBLCLKS | CS_SAVEBITS, /* style */
158 ComboLBWndProcA, /* procA */
159 ComboLBWndProcW, /* procW */
160 sizeof(LB_DESCR *), /* extra */
161 IDC_ARROW, /* cursor */
162 0 /* brush */
166 /* check whether app is a Win 3.1 app */
167 inline static BOOL is_old_app( HWND hwnd )
169 return (GetExpWinVer16( GetWindowLongPtrW(hwnd,GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
173 /***********************************************************************
174 * LISTBOX_Dump
176 void LISTBOX_Dump( HWND hwnd )
178 INT i;
179 LB_ITEMDATA *item;
180 LB_DESCR *descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 );
182 TRACE( "Listbox:\n" );
183 TRACE( "hwnd=%p descr=%08x items=%d top=%d\n",
184 hwnd, (UINT)descr, descr->nb_items, descr->top_item );
185 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
187 TRACE( "%4d: %-40s %d %08lx %3d\n",
188 i, debugstr_w(item->str), item->selected, item->data, item->height );
193 /***********************************************************************
194 * LISTBOX_GetCurrentPageSize
196 * Return the current page size
198 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
200 INT i, height;
201 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
202 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
204 if ((height += descr->items[i].height) > descr->height) break;
206 if (i == descr->top_item) return 1;
207 else return i - descr->top_item;
211 /***********************************************************************
212 * LISTBOX_GetMaxTopIndex
214 * Return the maximum possible index for the top of the listbox.
216 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
218 INT max, page;
220 if (descr->style & LBS_OWNERDRAWVARIABLE)
222 page = descr->height;
223 for (max = descr->nb_items - 1; max >= 0; max--)
224 if ((page -= descr->items[max].height) < 0) break;
225 if (max < descr->nb_items - 1) max++;
227 else if (descr->style & LBS_MULTICOLUMN)
229 if ((page = descr->width / descr->column_width) < 1) page = 1;
230 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
231 max = (max - page) * descr->page_size;
233 else
235 max = descr->nb_items - descr->page_size;
237 if (max < 0) max = 0;
238 return max;
242 /***********************************************************************
243 * LISTBOX_UpdateScroll
245 * Update the scrollbars. Should be called whenever the content
246 * of the listbox changes.
248 static void LISTBOX_UpdateScroll( HWND hwnd, LB_DESCR *descr )
250 SCROLLINFO info;
252 /* Check the listbox scroll bar flags individually before we call
253 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
254 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
255 scroll bar when we do not need one.
256 if (!(descr->style & WS_VSCROLL)) return;
259 /* It is important that we check descr->style, and not wnd->dwStyle,
260 for WS_VSCROLL, as the former is exactly the one passed in
261 argument to CreateWindow.
262 In Windows (and from now on in Wine :) a listbox created
263 with such a style (no WS_SCROLL) does not update
264 the scrollbar with listbox-related data, thus letting
265 the programmer use it for his/her own purposes. */
267 if (descr->style & LBS_NOREDRAW) return;
268 info.cbSize = sizeof(info);
270 if (descr->style & LBS_MULTICOLUMN)
272 info.nMin = 0;
273 info.nMax = (descr->nb_items - 1) / descr->page_size;
274 info.nPos = descr->top_item / descr->page_size;
275 info.nPage = descr->width / descr->column_width;
276 if (info.nPage < 1) info.nPage = 1;
277 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
278 if (descr->style & LBS_DISABLENOSCROLL)
279 info.fMask |= SIF_DISABLENOSCROLL;
280 if (descr->style & WS_HSCROLL)
281 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
282 info.nMax = 0;
283 info.fMask = SIF_RANGE;
284 if (descr->style & WS_VSCROLL)
285 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
287 else
289 info.nMin = 0;
290 info.nMax = descr->nb_items - 1;
291 info.nPos = descr->top_item;
292 info.nPage = LISTBOX_GetCurrentPageSize( descr );
293 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
294 if (descr->style & LBS_DISABLENOSCROLL)
295 info.fMask |= SIF_DISABLENOSCROLL;
296 if (descr->style & WS_VSCROLL)
297 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
299 if (descr->horz_extent)
301 info.nMin = 0;
302 info.nMax = descr->horz_extent - 1;
303 info.nPos = descr->horz_pos;
304 info.nPage = descr->width;
305 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
306 if (descr->style & LBS_DISABLENOSCROLL)
307 info.fMask |= SIF_DISABLENOSCROLL;
308 if (descr->style & WS_HSCROLL)
309 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
315 /***********************************************************************
316 * LISTBOX_SetTopItem
318 * Set the top item of the listbox, scrolling up or down if necessary.
320 static LRESULT LISTBOX_SetTopItem( HWND hwnd, LB_DESCR *descr, INT index,
321 BOOL scroll )
323 INT max = LISTBOX_GetMaxTopIndex( descr );
324 if (index > max) index = max;
325 if (index < 0) index = 0;
326 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
327 if (descr->top_item == index) return LB_OKAY;
328 if (descr->style & LBS_MULTICOLUMN)
330 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
331 if (scroll && (abs(diff) < descr->width))
332 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
333 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
335 else
336 scroll = FALSE;
338 else if (scroll)
340 INT diff;
341 if (descr->style & LBS_OWNERDRAWVARIABLE)
343 INT i;
344 diff = 0;
345 if (index > descr->top_item)
347 for (i = index - 1; i >= descr->top_item; i--)
348 diff -= descr->items[i].height;
350 else
352 for (i = index; i < descr->top_item; i++)
353 diff += descr->items[i].height;
356 else
357 diff = (descr->top_item - index) * descr->item_height;
359 if (abs(diff) < descr->height)
360 ScrollWindowEx( hwnd, 0, diff, NULL, NULL, 0, NULL,
361 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
362 else
363 scroll = FALSE;
365 if (!scroll) InvalidateRect( hwnd, NULL, TRUE );
366 descr->top_item = index;
367 LISTBOX_UpdateScroll( hwnd, descr );
368 return LB_OKAY;
372 /***********************************************************************
373 * LISTBOX_UpdatePage
375 * Update the page size. Should be called when the size of
376 * the client area or the item height changes.
378 static void LISTBOX_UpdatePage( HWND hwnd, LB_DESCR *descr )
380 INT page_size;
382 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
383 page_size = 1;
384 if (page_size == descr->page_size) return;
385 descr->page_size = page_size;
386 if (descr->style & LBS_MULTICOLUMN)
387 InvalidateRect( hwnd, NULL, TRUE );
388 LISTBOX_SetTopItem( hwnd, descr, descr->top_item, FALSE );
392 /***********************************************************************
393 * LISTBOX_UpdateSize
395 * Update the size of the listbox. Should be called when the size of
396 * the client area changes.
398 static void LISTBOX_UpdateSize( HWND hwnd, LB_DESCR *descr )
400 RECT rect;
402 GetClientRect( hwnd, &rect );
403 descr->width = rect.right - rect.left;
404 descr->height = rect.bottom - rect.top;
405 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
407 INT remaining;
408 RECT rect;
410 GetWindowRect( hwnd, &rect );
411 if(descr->item_height != 0)
412 remaining = descr->height % descr->item_height;
413 else
414 remaining = 0;
415 if ((descr->height > descr->item_height) && remaining)
417 if (is_old_app(hwnd))
418 { /* give a margin for error to 16 bits programs - if we need
419 less than the height of the nonclient area, round to the
420 *next* number of items */
421 int ncheight = rect.bottom - rect.top - descr->height;
422 if ((descr->item_height - remaining) <= ncheight)
423 remaining = remaining - descr->item_height;
425 TRACE("[%p]: changing height %d -> %d\n",
426 hwnd, descr->height, descr->height - remaining );
427 SetWindowPos( hwnd, 0, 0, 0, rect.right - rect.left,
428 rect.bottom - rect.top - remaining,
429 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
430 return;
433 TRACE("[%p]: new size = %d,%d\n", hwnd, descr->width, descr->height );
434 LISTBOX_UpdatePage( hwnd, descr );
435 LISTBOX_UpdateScroll( hwnd, descr );
437 /* Invalidate the focused item so it will be repainted correctly */
438 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
440 InvalidateRect( hwnd, &rect, FALSE );
445 /***********************************************************************
446 * LISTBOX_GetItemRect
448 * Get the rectangle enclosing an item, in listbox client coordinates.
449 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
451 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
453 /* Index <= 0 is legal even on empty listboxes */
454 if (index && (index >= descr->nb_items)) return -1;
455 SetRect( rect, 0, 0, descr->width, descr->height );
456 if (descr->style & LBS_MULTICOLUMN)
458 INT col = (index / descr->page_size) -
459 (descr->top_item / descr->page_size);
460 rect->left += col * descr->column_width;
461 rect->right = rect->left + descr->column_width;
462 rect->top += (index % descr->page_size) * descr->item_height;
463 rect->bottom = rect->top + descr->item_height;
465 else if (descr->style & LBS_OWNERDRAWVARIABLE)
467 INT i;
468 rect->right += descr->horz_pos;
469 if ((index >= 0) && (index < descr->nb_items))
471 if (index < descr->top_item)
473 for (i = descr->top_item-1; i >= index; i--)
474 rect->top -= descr->items[i].height;
476 else
478 for (i = descr->top_item; i < index; i++)
479 rect->top += descr->items[i].height;
481 rect->bottom = rect->top + descr->items[index].height;
485 else
487 rect->top += (index - descr->top_item) * descr->item_height;
488 rect->bottom = rect->top + descr->item_height;
489 rect->right += descr->horz_pos;
492 return ((rect->left < descr->width) && (rect->right > 0) &&
493 (rect->top < descr->height) && (rect->bottom > 0));
497 /***********************************************************************
498 * LISTBOX_GetItemFromPoint
500 * Return the item nearest from point (x,y) (in client coordinates).
502 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
504 INT index = descr->top_item;
506 if (!descr->nb_items) return -1; /* No items */
507 if (descr->style & LBS_OWNERDRAWVARIABLE)
509 INT pos = 0;
510 if (y >= 0)
512 while (index < descr->nb_items)
514 if ((pos += descr->items[index].height) > y) break;
515 index++;
518 else
520 while (index > 0)
522 index--;
523 if ((pos -= descr->items[index].height) <= y) break;
527 else if (descr->style & LBS_MULTICOLUMN)
529 if (y >= descr->item_height * descr->page_size) return -1;
530 if (y >= 0) index += y / descr->item_height;
531 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
532 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
534 else
536 index += (y / descr->item_height);
538 if (index < 0) return 0;
539 if (index >= descr->nb_items) return -1;
540 return index;
544 /***********************************************************************
545 * LISTBOX_PaintItem
547 * Paint an item.
549 static void LISTBOX_PaintItem( HWND hwnd, LB_DESCR *descr, HDC hdc,
550 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
552 LB_ITEMDATA *item = NULL;
553 if (index < descr->nb_items) item = &descr->items[index];
555 if (IS_OWNERDRAW(descr))
557 DRAWITEMSTRUCT dis;
558 RECT r;
559 HRGN hrgn;
560 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
562 if (!item)
564 if (action == ODA_FOCUS)
565 DrawFocusRect( hdc, rect );
566 else
567 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
568 return;
571 /* some programs mess with the clipping region when
572 drawing the item, *and* restore the previous region
573 after they are done, so a region has better to exist
574 else everything ends clipped */
575 GetClientRect(hwnd, &r);
576 hrgn = CreateRectRgnIndirect(&r);
577 SelectClipRgn( hdc, hrgn);
578 DeleteObject( hrgn );
580 dis.CtlType = ODT_LISTBOX;
581 dis.CtlID = id;
582 dis.hwndItem = hwnd;
583 dis.itemAction = action;
584 dis.hDC = hdc;
585 dis.itemID = index;
586 dis.itemState = 0;
587 if (item && item->selected) dis.itemState |= ODS_SELECTED;
588 if (!ignoreFocus && (descr->focus_item == index) &&
589 (descr->caret_on) &&
590 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
591 if (!IsWindowEnabled(hwnd)) dis.itemState |= ODS_DISABLED;
592 dis.itemData = item ? item->data : 0;
593 dis.rcItem = *rect;
594 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
595 hwnd, index, item ? debugstr_w(item->str) : "", action,
596 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
597 SendMessageW(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
599 else
601 COLORREF oldText = 0, oldBk = 0;
603 if (action == ODA_FOCUS)
605 DrawFocusRect( hdc, rect );
606 return;
608 if (item && item->selected)
610 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
611 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
614 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
615 hwnd, index, item ? debugstr_w(item->str) : "", action,
616 rect->left, rect->top, rect->right, rect->bottom );
617 if (!item)
618 ExtTextOutW( hdc, rect->left + 1, rect->top,
619 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
620 else if (!(descr->style & LBS_USETABSTOPS))
621 ExtTextOutW( hdc, rect->left + 1, rect->top,
622 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
623 strlenW(item->str), NULL );
624 else
626 /* Output empty string to paint background in the full width. */
627 ExtTextOutW( hdc, rect->left + 1, rect->top,
628 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
629 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
630 item->str, strlenW(item->str),
631 descr->nb_tabs, descr->tabs, 0);
633 if (item && item->selected)
635 SetBkColor( hdc, oldBk );
636 SetTextColor( hdc, oldText );
638 if (!ignoreFocus && (descr->focus_item == index) &&
639 (descr->caret_on) &&
640 (descr->in_focus)) DrawFocusRect( hdc, rect );
645 /***********************************************************************
646 * LISTBOX_SetRedraw
648 * Change the redraw flag.
650 static void LISTBOX_SetRedraw( HWND hwnd, LB_DESCR *descr, BOOL on )
652 if (on)
654 if (!(descr->style & LBS_NOREDRAW)) return;
655 descr->style &= ~LBS_NOREDRAW;
656 if (descr->style & LBS_DISPLAYCHANGED)
657 { /* page was changed while setredraw false, refresh automatically */
658 InvalidateRect(hwnd, NULL, TRUE);
659 if ((descr->top_item + descr->page_size) > descr->nb_items)
660 { /* reset top of page if less than number of items/page */
661 descr->top_item = descr->nb_items - descr->page_size;
662 if (descr->top_item < 0) descr->top_item = 0;
664 descr->style &= ~LBS_DISPLAYCHANGED;
666 LISTBOX_UpdateScroll( hwnd, descr );
668 else descr->style |= LBS_NOREDRAW;
672 /***********************************************************************
673 * LISTBOX_RepaintItem
675 * Repaint a single item synchronously.
677 static void LISTBOX_RepaintItem( HWND hwnd, LB_DESCR *descr, INT index,
678 UINT action )
680 HDC hdc;
681 RECT rect;
682 HFONT oldFont = 0;
683 HBRUSH hbrush, oldBrush = 0;
685 /* Do not repaint the item if the item is not visible */
686 if (!IsWindowVisible(hwnd)) return;
687 if (descr->style & LBS_NOREDRAW)
689 descr->style |= LBS_DISPLAYCHANGED;
690 return;
692 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
693 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE ))) return;
694 if (descr->font) oldFont = SelectObject( hdc, descr->font );
695 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
696 (WPARAM)hdc, (LPARAM)hwnd );
697 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
698 if (!IsWindowEnabled(hwnd))
699 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
700 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
701 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, index, action, FALSE );
702 if (oldFont) SelectObject( hdc, oldFont );
703 if (oldBrush) SelectObject( hdc, oldBrush );
704 ReleaseDC( hwnd, hdc );
708 /***********************************************************************
709 * LISTBOX_InitStorage
711 static LRESULT LISTBOX_InitStorage( HWND hwnd, LB_DESCR *descr, INT nb_items )
713 LB_ITEMDATA *item;
715 nb_items += LB_ARRAY_GRANULARITY - 1;
716 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
717 if (descr->items) {
718 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
719 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
720 nb_items * sizeof(LB_ITEMDATA));
722 else {
723 item = HeapAlloc( GetProcessHeap(), 0,
724 nb_items * sizeof(LB_ITEMDATA));
727 if (!item)
729 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
730 return LB_ERRSPACE;
732 descr->items = item;
733 return LB_OKAY;
737 /***********************************************************************
738 * LISTBOX_SetTabStops
740 static BOOL LISTBOX_SetTabStops( HWND hwnd, LB_DESCR *descr, INT count,
741 LPINT tabs, BOOL short_ints )
743 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
744 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
745 if (!(descr->nb_tabs = count))
747 descr->tabs = NULL;
748 return TRUE;
750 /* FIXME: count = 1 */
751 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
752 descr->nb_tabs * sizeof(INT) )))
753 return FALSE;
754 if (short_ints)
756 INT i;
757 LPINT16 p = (LPINT16)tabs;
759 TRACE("[%p]: settabstops ", hwnd );
760 for (i = 0; i < descr->nb_tabs; i++) {
761 descr->tabs[i] = *p++<<1; /* FIXME */
762 if (TRACE_ON(listbox)) TRACE("%hd ", descr->tabs[i]);
764 if (TRACE_ON(listbox)) TRACE("\n");
766 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
767 /* FIXME: repaint the window? */
768 return TRUE;
772 /***********************************************************************
773 * LISTBOX_GetText
775 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
777 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
778 if (HAS_STRINGS(descr))
780 if (!lParam)
781 return strlenW(descr->items[index].str);
783 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
785 if(unicode)
787 LPWSTR buffer = (LPWSTR)lParam;
788 strcpyW( buffer, descr->items[index].str );
789 return strlenW(buffer);
791 else
793 LPSTR buffer = (LPSTR)lParam;
794 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
796 } else {
797 if (lParam)
798 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
799 return sizeof(DWORD);
804 /***********************************************************************
805 * LISTBOX_FindStringPos
807 * Find the nearest string located before a given string in sort order.
808 * If 'exact' is TRUE, return an error if we don't get an exact match.
810 static INT LISTBOX_FindStringPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str,
811 BOOL exact )
813 INT index, min, max, res = -1;
815 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
816 min = 0;
817 max = descr->nb_items;
818 while (min != max)
820 index = (min + max) / 2;
821 if (HAS_STRINGS(descr))
822 res = lstrcmpiW( str, descr->items[index].str);
823 else
825 COMPAREITEMSTRUCT cis;
826 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
828 cis.CtlType = ODT_LISTBOX;
829 cis.CtlID = id;
830 cis.hwndItem = hwnd;
831 /* note that some application (MetaStock) expects the second item
832 * to be in the listbox */
833 cis.itemID1 = -1;
834 cis.itemData1 = (DWORD)str;
835 cis.itemID2 = index;
836 cis.itemData2 = descr->items[index].data;
837 cis.dwLocaleId = descr->locale;
838 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
840 if (!res) return index;
841 if (res < 0) max = index;
842 else min = index + 1;
844 return exact ? -1 : max;
848 /***********************************************************************
849 * LISTBOX_FindFileStrPos
851 * Find the nearest string located before a given string in directory
852 * sort order (i.e. first files, then directories, then drives).
854 static INT LISTBOX_FindFileStrPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str )
856 INT min, max, res = -1;
858 if (!HAS_STRINGS(descr))
859 return LISTBOX_FindStringPos( hwnd, descr, str, FALSE );
860 min = 0;
861 max = descr->nb_items;
862 while (min != max)
864 INT index = (min + max) / 2;
865 LPCWSTR p = descr->items[index].str;
866 if (*p == '[') /* drive or directory */
868 if (*str != '[') res = -1;
869 else if (p[1] == '-') /* drive */
871 if (str[1] == '-') res = str[2] - p[2];
872 else res = -1;
874 else /* directory */
876 if (str[1] == '-') res = 1;
877 else res = lstrcmpiW( str, p );
880 else /* filename */
882 if (*str == '[') res = 1;
883 else res = lstrcmpiW( str, p );
885 if (!res) return index;
886 if (res < 0) max = index;
887 else min = index + 1;
889 return max;
893 /***********************************************************************
894 * LISTBOX_FindString
896 * Find the item beginning with a given string.
898 static INT LISTBOX_FindString( HWND hwnd, LB_DESCR *descr, INT start,
899 LPCWSTR str, BOOL exact )
901 INT i;
902 LB_ITEMDATA *item;
904 if (start >= descr->nb_items) start = -1;
905 item = descr->items + start + 1;
906 if (HAS_STRINGS(descr))
908 if (!str || ! str[0] ) return LB_ERR;
909 if (exact)
911 for (i = start + 1; i < descr->nb_items; i++, item++)
912 if (!lstrcmpiW( str, item->str )) return i;
913 for (i = 0, item = descr->items; i <= start; i++, item++)
914 if (!lstrcmpiW( str, item->str )) return i;
916 else
918 /* Special case for drives and directories: ignore prefix */
919 #define CHECK_DRIVE(item) \
920 if ((item)->str[0] == '[') \
922 if (!strncmpiW( str, (item)->str+1, len )) return i; \
923 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
924 return i; \
927 INT len = strlenW(str);
928 for (i = start + 1; i < descr->nb_items; i++, item++)
930 if (!strncmpiW( str, item->str, len )) return i;
931 CHECK_DRIVE(item);
933 for (i = 0, item = descr->items; i <= start; i++, item++)
935 if (!strncmpiW( str, item->str, len )) return i;
936 CHECK_DRIVE(item);
938 #undef CHECK_DRIVE
941 else
943 if (exact && (descr->style & LBS_SORT))
944 /* If sorted, use a WM_COMPAREITEM binary search */
945 return LISTBOX_FindStringPos( hwnd, descr, str, TRUE );
947 /* Otherwise use a linear search */
948 for (i = start + 1; i < descr->nb_items; i++, item++)
949 if (item->data == (DWORD)str) return i;
950 for (i = 0, item = descr->items; i <= start; i++, item++)
951 if (item->data == (DWORD)str) return i;
953 return LB_ERR;
957 /***********************************************************************
958 * LISTBOX_GetSelCount
960 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
962 INT i, count;
963 LB_ITEMDATA *item = descr->items;
965 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
966 for (i = count = 0; i < descr->nb_items; i++, item++)
967 if (item->selected) count++;
968 return count;
972 /***********************************************************************
973 * LISTBOX_GetSelItems16
975 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
977 INT i, count;
978 LB_ITEMDATA *item = descr->items;
980 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
981 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
982 if (item->selected) array[count++] = (INT16)i;
983 return count;
987 /***********************************************************************
988 * LISTBOX_GetSelItems
990 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
992 INT i, count;
993 LB_ITEMDATA *item = descr->items;
995 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
996 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
997 if (item->selected) array[count++] = i;
998 return count;
1002 /***********************************************************************
1003 * LISTBOX_Paint
1005 static LRESULT LISTBOX_Paint( HWND hwnd, LB_DESCR *descr, HDC hdc )
1007 INT i, col_pos = descr->page_size - 1;
1008 RECT rect;
1009 RECT focusRect = {-1, -1, -1, -1};
1010 HFONT oldFont = 0;
1011 HBRUSH hbrush, oldBrush = 0;
1013 if (descr->style & LBS_NOREDRAW) return 0;
1015 SetRect( &rect, 0, 0, descr->width, descr->height );
1016 if (descr->style & LBS_MULTICOLUMN)
1017 rect.right = rect.left + descr->column_width;
1018 else if (descr->horz_pos)
1020 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1021 rect.right += descr->horz_pos;
1024 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1025 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1026 (WPARAM)hdc, (LPARAM)hwnd );
1027 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1028 if (!IsWindowEnabled(hwnd)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1030 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1031 (descr->in_focus))
1033 /* Special case for empty listbox: paint focus rect */
1034 rect.bottom = rect.top + descr->item_height;
1035 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, descr->focus_item,
1036 ODA_FOCUS, FALSE );
1037 rect.top = rect.bottom;
1040 /* Paint all the item, regarding the selection
1041 Focus state will be painted after */
1043 for (i = descr->top_item; i < descr->nb_items; i++)
1045 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1046 rect.bottom = rect.top + descr->item_height;
1047 else
1048 rect.bottom = rect.top + descr->items[i].height;
1050 if (i == descr->focus_item)
1052 /* keep the focus rect, to paint the focus item after */
1053 focusRect.left = rect.left;
1054 focusRect.right = rect.right;
1055 focusRect.top = rect.top;
1056 focusRect.bottom = rect.bottom;
1058 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1059 rect.top = rect.bottom;
1061 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1063 if (!IS_OWNERDRAW(descr))
1065 /* Clear the bottom of the column */
1066 if (rect.top < descr->height)
1068 rect.bottom = descr->height;
1069 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1070 &rect, NULL, 0, NULL );
1074 /* Go to the next column */
1075 rect.left += descr->column_width;
1076 rect.right += descr->column_width;
1077 rect.top = 0;
1078 col_pos = descr->page_size - 1;
1080 else
1082 col_pos--;
1083 if (rect.top >= descr->height) break;
1087 /* Paint the focus item now */
1088 if (focusRect.top != focusRect.bottom && descr->caret_on)
1089 LISTBOX_PaintItem( hwnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1091 if (!IS_OWNERDRAW(descr))
1093 /* Clear the remainder of the client area */
1094 if (rect.top < descr->height)
1096 rect.bottom = descr->height;
1097 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1098 &rect, NULL, 0, NULL );
1100 if (rect.right < descr->width)
1102 rect.left = rect.right;
1103 rect.right = descr->width;
1104 rect.top = 0;
1105 rect.bottom = descr->height;
1106 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1107 &rect, NULL, 0, NULL );
1110 if (oldFont) SelectObject( hdc, oldFont );
1111 if (oldBrush) SelectObject( hdc, oldBrush );
1112 return 0;
1116 /***********************************************************************
1117 * LISTBOX_InvalidateItems
1119 * Invalidate all items from a given item. If the specified item is not
1120 * visible, nothing happens.
1122 static void LISTBOX_InvalidateItems( HWND hwnd, LB_DESCR *descr, INT index )
1124 RECT rect;
1126 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1128 if (descr->style & LBS_NOREDRAW)
1130 descr->style |= LBS_DISPLAYCHANGED;
1131 return;
1133 rect.bottom = descr->height;
1134 InvalidateRect( hwnd, &rect, TRUE );
1135 if (descr->style & LBS_MULTICOLUMN)
1137 /* Repaint the other columns */
1138 rect.left = rect.right;
1139 rect.right = descr->width;
1140 rect.top = 0;
1141 InvalidateRect( hwnd, &rect, TRUE );
1146 static void LISTBOX_InvalidateItemRect( HWND hwnd, LB_DESCR *descr, INT index )
1148 RECT rect;
1150 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1151 InvalidateRect( hwnd, &rect, TRUE );
1154 /***********************************************************************
1155 * LISTBOX_GetItemHeight
1157 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1159 if (descr->style & LBS_OWNERDRAWVARIABLE)
1161 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1162 return descr->items[index].height;
1164 else return descr->item_height;
1168 /***********************************************************************
1169 * LISTBOX_SetItemHeight
1171 static LRESULT LISTBOX_SetItemHeight( HWND hwnd, LB_DESCR *descr, INT index,
1172 INT height, BOOL repaint )
1174 if (!height) height = 1;
1176 if (descr->style & LBS_OWNERDRAWVARIABLE)
1178 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1179 TRACE("[%p]: item %d height = %d\n", hwnd, index, height );
1180 descr->items[index].height = height;
1181 LISTBOX_UpdateScroll( hwnd, descr );
1182 if (repaint)
1183 LISTBOX_InvalidateItems( hwnd, descr, index );
1185 else if (height != descr->item_height)
1187 TRACE("[%p]: new height = %d\n", hwnd, height );
1188 descr->item_height = height;
1189 LISTBOX_UpdatePage( hwnd, descr );
1190 LISTBOX_UpdateScroll( hwnd, descr );
1191 if (repaint)
1192 InvalidateRect( hwnd, 0, TRUE );
1194 return LB_OKAY;
1198 /***********************************************************************
1199 * LISTBOX_SetHorizontalPos
1201 static void LISTBOX_SetHorizontalPos( HWND hwnd, LB_DESCR *descr, INT pos )
1203 INT diff;
1205 if (pos > descr->horz_extent - descr->width)
1206 pos = descr->horz_extent - descr->width;
1207 if (pos < 0) pos = 0;
1208 if (!(diff = descr->horz_pos - pos)) return;
1209 TRACE("[%p]: new horz pos = %d\n", hwnd, pos );
1210 descr->horz_pos = pos;
1211 LISTBOX_UpdateScroll( hwnd, descr );
1212 if (abs(diff) < descr->width)
1214 RECT rect;
1215 /* Invalidate the focused item so it will be repainted correctly */
1216 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1217 InvalidateRect( hwnd, &rect, TRUE );
1218 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
1219 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1221 else
1222 InvalidateRect( hwnd, NULL, TRUE );
1226 /***********************************************************************
1227 * LISTBOX_SetHorizontalExtent
1229 static LRESULT LISTBOX_SetHorizontalExtent( HWND hwnd, LB_DESCR *descr,
1230 INT extent )
1232 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1233 return LB_OKAY;
1234 if (extent <= 0) extent = 1;
1235 if (extent == descr->horz_extent) return LB_OKAY;
1236 TRACE("[%p]: new horz extent = %d\n", hwnd, extent );
1237 descr->horz_extent = extent;
1238 if (descr->horz_pos > extent - descr->width)
1239 LISTBOX_SetHorizontalPos( hwnd, descr, extent - descr->width );
1240 else
1241 LISTBOX_UpdateScroll( hwnd, descr );
1242 return LB_OKAY;
1246 /***********************************************************************
1247 * LISTBOX_SetColumnWidth
1249 static LRESULT LISTBOX_SetColumnWidth( HWND hwnd, LB_DESCR *descr, INT width)
1251 if (width == descr->column_width) return LB_OKAY;
1252 TRACE("[%p]: new column width = %d\n", hwnd, width );
1253 descr->column_width = width;
1254 LISTBOX_UpdatePage( hwnd, descr );
1255 return LB_OKAY;
1259 /***********************************************************************
1260 * LISTBOX_SetFont
1262 * Returns the item height.
1264 static INT LISTBOX_SetFont( HWND hwnd, LB_DESCR *descr, HFONT font )
1266 HDC hdc;
1267 HFONT oldFont = 0;
1268 TEXTMETRICW tm;
1270 descr->font = font;
1272 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE )))
1274 ERR("unable to get DC.\n" );
1275 return 16;
1277 if (font) oldFont = SelectObject( hdc, font );
1278 GetTextMetricsW( hdc, &tm );
1279 if (oldFont) SelectObject( hdc, oldFont );
1280 ReleaseDC( hwnd, hdc );
1281 if (!IS_OWNERDRAW(descr))
1282 LISTBOX_SetItemHeight( hwnd, descr, 0, tm.tmHeight, FALSE );
1283 return tm.tmHeight ;
1287 /***********************************************************************
1288 * LISTBOX_MakeItemVisible
1290 * Make sure that a given item is partially or fully visible.
1292 static void LISTBOX_MakeItemVisible( HWND hwnd, LB_DESCR *descr, INT index,
1293 BOOL fully )
1295 INT top;
1297 if (index <= descr->top_item) top = index;
1298 else if (descr->style & LBS_MULTICOLUMN)
1300 INT cols = descr->width;
1301 if (!fully) cols += descr->column_width - 1;
1302 if (cols >= descr->column_width) cols /= descr->column_width;
1303 else cols = 1;
1304 if (index < descr->top_item + (descr->page_size * cols)) return;
1305 top = index - descr->page_size * (cols - 1);
1307 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1309 INT height = fully ? descr->items[index].height : 1;
1310 for (top = index; top > descr->top_item; top--)
1311 if ((height += descr->items[top-1].height) > descr->height) break;
1313 else
1315 if (index < descr->top_item + descr->page_size) return;
1316 if (!fully && (index == descr->top_item + descr->page_size) &&
1317 (descr->height > (descr->page_size * descr->item_height))) return;
1318 top = index - descr->page_size + 1;
1320 LISTBOX_SetTopItem( hwnd, descr, top, TRUE );
1323 /***********************************************************************
1324 * LISTBOX_SetCaretIndex
1326 * NOTES
1327 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1330 static LRESULT LISTBOX_SetCaretIndex( HWND hwnd, LB_DESCR *descr, INT index,
1331 BOOL fully_visible )
1333 INT oldfocus = descr->focus_item;
1335 if (descr->style & LBS_NOSEL) return LB_ERR;
1336 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1337 if (index == oldfocus) return LB_OKAY;
1338 descr->focus_item = index;
1339 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1340 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1342 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1343 if (descr->caret_on && (descr->in_focus))
1344 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1346 return LB_OKAY;
1350 /***********************************************************************
1351 * LISTBOX_SelectItemRange
1353 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1355 static LRESULT LISTBOX_SelectItemRange( HWND hwnd, LB_DESCR *descr, INT first,
1356 INT last, BOOL on )
1358 INT i;
1360 /* A few sanity checks */
1362 if (descr->style & LBS_NOSEL) return LB_ERR;
1363 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1364 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1365 if (last == -1) last = descr->nb_items - 1;
1366 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1367 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1368 /* selected_item reflects last selected/unselected item on multiple sel */
1369 descr->selected_item = last;
1371 if (on) /* Turn selection on */
1373 for (i = first; i <= last; i++)
1375 if (descr->items[i].selected) continue;
1376 descr->items[i].selected = TRUE;
1377 LISTBOX_InvalidateItemRect(hwnd, descr, i);
1379 LISTBOX_SetCaretIndex( hwnd, descr, last, TRUE );
1381 else /* Turn selection off */
1383 for (i = first; i <= last; i++)
1385 if (!descr->items[i].selected) continue;
1386 descr->items[i].selected = FALSE;
1387 LISTBOX_InvalidateItemRect(hwnd, descr, i);
1390 return LB_OKAY;
1393 /***********************************************************************
1394 * LISTBOX_SetSelection
1396 static LRESULT LISTBOX_SetSelection( HWND hwnd, LB_DESCR *descr, INT index,
1397 BOOL on, BOOL send_notify )
1399 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1401 if (descr->style & LBS_NOSEL) return LB_ERR;
1402 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1403 if (descr->style & LBS_MULTIPLESEL)
1405 if (index == -1) /* Select all items */
1406 return LISTBOX_SelectItemRange( hwnd, descr, 0, -1, on );
1407 else /* Only one item */
1408 return LISTBOX_SelectItemRange( hwnd, descr, index, index, on );
1410 else
1412 INT oldsel = descr->selected_item;
1413 if (index == oldsel) return LB_OKAY;
1414 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1415 if (index != -1) descr->items[index].selected = TRUE;
1416 descr->selected_item = index;
1417 if (oldsel != -1) LISTBOX_RepaintItem( hwnd, descr, oldsel, ODA_SELECT );
1418 if (index != -1) LISTBOX_RepaintItem( hwnd, descr, index, ODA_SELECT );
1419 if (send_notify && descr->nb_items) SEND_NOTIFICATION( hwnd, descr,
1420 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1421 else
1422 if( descr->lphc ) /* set selection change flag for parent combo */
1423 descr->lphc->wState |= CBF_SELCHANGE;
1425 return LB_OKAY;
1429 /***********************************************************************
1430 * LISTBOX_MoveCaret
1432 * Change the caret position and extend the selection to the new caret.
1434 static void LISTBOX_MoveCaret( HWND hwnd, LB_DESCR *descr, INT index,
1435 BOOL fully_visible )
1437 INT oldfocus = descr->focus_item;
1439 if ((index < 0) || (index >= descr->nb_items))
1440 return;
1442 /* Important, repaint needs to be done in this order if
1443 you want to mimic Windows behavior:
1444 1. Remove the focus and paint the item
1445 2. Remove the selection and paint the item(s)
1446 3. Set the selection and repaint the item(s)
1447 4. Set the focus to 'index' and repaint the item */
1449 /* 1. remove the focus and repaint the item */
1450 descr->focus_item = -1;
1451 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1452 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1454 /* 2. then turn off the previous selection */
1455 /* 3. repaint the new selected item */
1456 if (descr->style & LBS_EXTENDEDSEL)
1458 if (descr->anchor_item != -1)
1460 INT first = min( index, descr->anchor_item );
1461 INT last = max( index, descr->anchor_item );
1462 if (first > 0)
1463 LISTBOX_SelectItemRange( hwnd, descr, 0, first - 1, FALSE );
1464 LISTBOX_SelectItemRange( hwnd, descr, last + 1, -1, FALSE );
1465 LISTBOX_SelectItemRange( hwnd, descr, first, last, TRUE );
1468 else if (!(descr->style & LBS_MULTIPLESEL))
1470 /* Set selection to new caret item */
1471 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
1474 /* 4. repaint the new item with the focus */
1475 descr->focus_item = index;
1476 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1477 if (descr->caret_on && (descr->in_focus))
1478 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1482 /***********************************************************************
1483 * LISTBOX_InsertItem
1485 static LRESULT LISTBOX_InsertItem( HWND hwnd, LB_DESCR *descr, INT index,
1486 LPWSTR str, DWORD data )
1488 LB_ITEMDATA *item;
1489 INT max_items;
1490 INT oldfocus = descr->focus_item;
1492 if (index == -1) index = descr->nb_items;
1493 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1494 if (!descr->items) max_items = 0;
1495 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1496 if (descr->nb_items == max_items)
1498 /* We need to grow the array */
1499 max_items += LB_ARRAY_GRANULARITY;
1500 if (descr->items)
1501 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1502 max_items * sizeof(LB_ITEMDATA) );
1503 else
1504 item = HeapAlloc( GetProcessHeap(), 0,
1505 max_items * sizeof(LB_ITEMDATA) );
1506 if (!item)
1508 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1509 return LB_ERRSPACE;
1511 descr->items = item;
1514 /* Insert the item structure */
1516 item = &descr->items[index];
1517 if (index < descr->nb_items)
1518 RtlMoveMemory( item + 1, item,
1519 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1520 item->str = str;
1521 item->data = data;
1522 item->height = 0;
1523 item->selected = FALSE;
1524 descr->nb_items++;
1526 /* Get item height */
1528 if (descr->style & LBS_OWNERDRAWVARIABLE)
1530 MEASUREITEMSTRUCT mis;
1531 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
1533 mis.CtlType = ODT_LISTBOX;
1534 mis.CtlID = id;
1535 mis.itemID = index;
1536 mis.itemData = descr->items[index].data;
1537 mis.itemHeight = descr->item_height;
1538 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1539 item->height = mis.itemHeight ? mis.itemHeight : 1;
1540 TRACE("[%p]: measure item %d (%s) = %d\n",
1541 hwnd, index, str ? debugstr_w(str) : "", item->height );
1544 /* Repaint the items */
1546 LISTBOX_UpdateScroll( hwnd, descr );
1547 LISTBOX_InvalidateItems( hwnd, descr, index );
1549 /* Move selection and focused item */
1550 /* If listbox was empty, set focus to the first item */
1551 if (descr->nb_items == 1)
1552 LISTBOX_SetCaretIndex( hwnd, descr, 0, FALSE );
1553 /* single select don't change selection index in win31 */
1554 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1556 descr->selected_item++;
1557 LISTBOX_SetSelection( hwnd, descr, descr->selected_item-1, TRUE, FALSE );
1559 else
1561 if (index <= descr->selected_item)
1563 descr->selected_item++;
1564 descr->focus_item = oldfocus; /* focus not changed */
1567 return LB_OKAY;
1571 /***********************************************************************
1572 * LISTBOX_InsertString
1574 static LRESULT LISTBOX_InsertString( HWND hwnd, LB_DESCR *descr, INT index,
1575 LPCWSTR str )
1577 LPWSTR new_str = NULL;
1578 DWORD data = 0;
1579 LRESULT ret;
1581 if (HAS_STRINGS(descr))
1583 static const WCHAR empty_stringW[] = { 0 };
1584 if (!str) str = empty_stringW;
1585 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1587 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1588 return LB_ERRSPACE;
1590 strcpyW(new_str, str);
1592 else data = (DWORD)str;
1594 if (index == -1) index = descr->nb_items;
1595 if ((ret = LISTBOX_InsertItem( hwnd, descr, index, new_str, data )) != 0)
1597 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1598 return ret;
1601 TRACE("[%p]: added item %d %s\n",
1602 hwnd, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1603 return index;
1607 /***********************************************************************
1608 * LISTBOX_DeleteItem
1610 * Delete the content of an item. 'index' must be a valid index.
1612 static void LISTBOX_DeleteItem( HWND hwnd, LB_DESCR *descr, INT index )
1614 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1615 * while Win95 sends it for all items with user data.
1616 * It's probably better to send it too often than not
1617 * often enough, so this is what we do here.
1619 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1621 DELETEITEMSTRUCT dis;
1622 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
1624 dis.CtlType = ODT_LISTBOX;
1625 dis.CtlID = id;
1626 dis.itemID = index;
1627 dis.hwndItem = hwnd;
1628 dis.itemData = descr->items[index].data;
1629 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1631 if (HAS_STRINGS(descr) && descr->items[index].str)
1632 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1636 /***********************************************************************
1637 * LISTBOX_RemoveItem
1639 * Remove an item from the listbox and delete its content.
1641 static LRESULT LISTBOX_RemoveItem( HWND hwnd, LB_DESCR *descr, INT index )
1643 LB_ITEMDATA *item;
1644 INT max_items;
1646 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1647 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1649 /* We need to invalidate the original rect instead of the updated one. */
1650 LISTBOX_InvalidateItems( hwnd, descr, index );
1652 LISTBOX_DeleteItem( hwnd, descr, index );
1654 /* Remove the item */
1656 item = &descr->items[index];
1657 if (index < descr->nb_items-1)
1658 RtlMoveMemory( item, item + 1,
1659 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1660 descr->nb_items--;
1661 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1663 /* Shrink the item array if possible */
1665 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1666 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1668 max_items -= LB_ARRAY_GRANULARITY;
1669 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1670 max_items * sizeof(LB_ITEMDATA) );
1671 if (item) descr->items = item;
1673 /* Repaint the items */
1675 LISTBOX_UpdateScroll( hwnd, descr );
1676 /* if we removed the scrollbar, reset the top of the list
1677 (correct for owner-drawn ???) */
1678 if (descr->nb_items == descr->page_size)
1679 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1681 /* Move selection and focused item */
1682 if (!IS_MULTISELECT(descr))
1684 if (index == descr->selected_item)
1685 descr->selected_item = -1;
1686 else if (index < descr->selected_item)
1688 descr->selected_item--;
1689 if (ISWIN31) /* win 31 do not change the selected item number */
1690 LISTBOX_SetSelection( hwnd, descr, descr->selected_item + 1, TRUE, FALSE);
1694 if (descr->focus_item >= descr->nb_items)
1696 descr->focus_item = descr->nb_items - 1;
1697 if (descr->focus_item < 0) descr->focus_item = 0;
1699 return LB_OKAY;
1703 /***********************************************************************
1704 * LISTBOX_ResetContent
1706 static void LISTBOX_ResetContent( HWND hwnd, LB_DESCR *descr )
1708 INT i;
1710 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( hwnd, descr, i );
1711 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1712 descr->nb_items = 0;
1713 descr->top_item = 0;
1714 descr->selected_item = -1;
1715 descr->focus_item = 0;
1716 descr->anchor_item = -1;
1717 descr->items = NULL;
1721 /***********************************************************************
1722 * LISTBOX_SetCount
1724 static LRESULT LISTBOX_SetCount( HWND hwnd, LB_DESCR *descr, INT count )
1726 LRESULT ret;
1728 if (HAS_STRINGS(descr)) return LB_ERR;
1729 /* FIXME: this is far from optimal... */
1730 if (count > descr->nb_items)
1732 while (count > descr->nb_items)
1733 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, 0 )) < 0)
1734 return ret;
1736 else if (count < descr->nb_items)
1738 while (count < descr->nb_items)
1739 if ((ret = LISTBOX_RemoveItem( hwnd, descr, -1 )) < 0)
1740 return ret;
1742 return LB_OKAY;
1746 /***********************************************************************
1747 * LISTBOX_Directory
1749 static LRESULT LISTBOX_Directory( HWND hwnd, LB_DESCR *descr, UINT attrib,
1750 LPCWSTR filespec, BOOL long_names )
1752 HANDLE handle;
1753 LRESULT ret = LB_OKAY;
1754 WIN32_FIND_DATAW entry;
1755 int pos;
1757 /* don't scan directory if we just want drives exclusively */
1758 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1759 /* scan directory */
1760 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1762 int le = GetLastError();
1763 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1765 else
1769 WCHAR buffer[270];
1770 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1772 static const WCHAR bracketW[] = { ']',0 };
1773 static const WCHAR dotW[] = { '.',0 };
1774 if (!(attrib & DDL_DIRECTORY) ||
1775 !strcmpW( entry.cFileName, dotW )) continue;
1776 buffer[0] = '[';
1777 if (!long_names && entry.cAlternateFileName[0])
1778 strcpyW( buffer + 1, entry.cAlternateFileName );
1779 else
1780 strcpyW( buffer + 1, entry.cFileName );
1781 strcatW(buffer, bracketW);
1783 else /* not a directory */
1785 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1786 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1788 if ((attrib & DDL_EXCLUSIVE) &&
1789 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1790 continue;
1791 #undef ATTRIBS
1792 if (!long_names && entry.cAlternateFileName[0])
1793 strcpyW( buffer, entry.cAlternateFileName );
1794 else
1795 strcpyW( buffer, entry.cFileName );
1797 if (!long_names) CharLowerW( buffer );
1798 pos = LISTBOX_FindFileStrPos( hwnd, descr, buffer );
1799 if ((ret = LISTBOX_InsertString( hwnd, descr, pos, buffer )) < 0)
1800 break;
1801 } while (FindNextFileW( handle, &entry ));
1802 FindClose( handle );
1806 /* scan drives */
1807 if ((ret >= 0) && (attrib & DDL_DRIVES))
1809 WCHAR buffer[] = {'[','-','a','-',']',0};
1810 WCHAR root[] = {'A',':','\\',0};
1811 int drive;
1812 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1814 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1815 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, buffer )) < 0)
1816 break;
1819 return ret;
1823 /***********************************************************************
1824 * LISTBOX_HandleVScroll
1826 static LRESULT LISTBOX_HandleVScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1828 SCROLLINFO info;
1830 if (descr->style & LBS_MULTICOLUMN) return 0;
1831 switch(LOWORD(wParam))
1833 case SB_LINEUP:
1834 LISTBOX_SetTopItem( hwnd, descr, descr->top_item - 1, TRUE );
1835 break;
1836 case SB_LINEDOWN:
1837 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + 1, TRUE );
1838 break;
1839 case SB_PAGEUP:
1840 LISTBOX_SetTopItem( hwnd, descr, descr->top_item -
1841 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1842 break;
1843 case SB_PAGEDOWN:
1844 LISTBOX_SetTopItem( hwnd, descr, descr->top_item +
1845 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1846 break;
1847 case SB_THUMBPOSITION:
1848 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam), TRUE );
1849 break;
1850 case SB_THUMBTRACK:
1851 info.cbSize = sizeof(info);
1852 info.fMask = SIF_TRACKPOS;
1853 GetScrollInfo( hwnd, SB_VERT, &info );
1854 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos, TRUE );
1855 break;
1856 case SB_TOP:
1857 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1858 break;
1859 case SB_BOTTOM:
1860 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1861 break;
1863 return 0;
1867 /***********************************************************************
1868 * LISTBOX_HandleHScroll
1870 static LRESULT LISTBOX_HandleHScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1872 SCROLLINFO info;
1873 INT page;
1875 if (descr->style & LBS_MULTICOLUMN)
1877 switch(LOWORD(wParam))
1879 case SB_LINELEFT:
1880 LISTBOX_SetTopItem( hwnd, descr, descr->top_item-descr->page_size,
1881 TRUE );
1882 break;
1883 case SB_LINERIGHT:
1884 LISTBOX_SetTopItem( hwnd, 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( hwnd, 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( hwnd, descr,
1897 descr->top_item + page * descr->page_size, TRUE );
1898 break;
1899 case SB_THUMBPOSITION:
1900 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam)*descr->page_size,
1901 TRUE );
1902 break;
1903 case SB_THUMBTRACK:
1904 info.cbSize = sizeof(info);
1905 info.fMask = SIF_TRACKPOS;
1906 GetScrollInfo( hwnd, SB_VERT, &info );
1907 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos*descr->page_size,
1908 TRUE );
1909 break;
1910 case SB_LEFT:
1911 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1912 break;
1913 case SB_RIGHT:
1914 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1915 break;
1918 else if (descr->horz_extent)
1920 switch(LOWORD(wParam))
1922 case SB_LINELEFT:
1923 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos - 1 );
1924 break;
1925 case SB_LINERIGHT:
1926 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos + 1 );
1927 break;
1928 case SB_PAGELEFT:
1929 LISTBOX_SetHorizontalPos( hwnd, descr,
1930 descr->horz_pos - descr->width );
1931 break;
1932 case SB_PAGERIGHT:
1933 LISTBOX_SetHorizontalPos( hwnd, descr,
1934 descr->horz_pos + descr->width );
1935 break;
1936 case SB_THUMBPOSITION:
1937 LISTBOX_SetHorizontalPos( hwnd, descr, HIWORD(wParam) );
1938 break;
1939 case SB_THUMBTRACK:
1940 info.cbSize = sizeof(info);
1941 info.fMask = SIF_TRACKPOS;
1942 GetScrollInfo( hwnd, SB_HORZ, &info );
1943 LISTBOX_SetHorizontalPos( hwnd, descr, info.nTrackPos );
1944 break;
1945 case SB_LEFT:
1946 LISTBOX_SetHorizontalPos( hwnd, descr, 0 );
1947 break;
1948 case SB_RIGHT:
1949 LISTBOX_SetHorizontalPos( hwnd, descr,
1950 descr->horz_extent - descr->width );
1951 break;
1954 return 0;
1957 static LRESULT LISTBOX_HandleMouseWheel(HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1959 short gcWheelDelta = 0;
1960 UINT pulScrollLines = 3;
1962 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1964 gcWheelDelta -= (short) HIWORD(wParam);
1966 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1968 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1969 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1970 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + cLineScroll, TRUE );
1972 return 0;
1975 /***********************************************************************
1976 * LISTBOX_HandleLButtonDown
1978 static LRESULT LISTBOX_HandleLButtonDown( HWND hwnd, LB_DESCR *descr,
1979 WPARAM wParam, INT x, INT y )
1981 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1982 TRACE("[%p]: lbuttondown %d,%d item %d\n", hwnd, x, y, index );
1983 if (!descr->caret_on && (descr->in_focus)) return 0;
1985 if (!descr->in_focus)
1987 if( !descr->lphc ) SetFocus( hwnd );
1988 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1991 if (index == -1) return 0;
1993 if (descr->style & LBS_EXTENDEDSEL)
1995 /* we should perhaps make sure that all items are deselected
1996 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1997 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1998 LISTBOX_SetSelection( hwnd, descr, -1, FALSE, FALSE);
2001 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
2002 if (wParam & MK_CONTROL)
2004 LISTBOX_SetCaretIndex( hwnd, descr, index, FALSE );
2005 LISTBOX_SetSelection( hwnd, descr, index,
2006 !descr->items[index].selected,
2007 (descr->style & LBS_NOTIFY) != 0);
2009 else LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2011 else
2013 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2014 LISTBOX_SetSelection( hwnd, descr, index,
2015 (!(descr->style & LBS_MULTIPLESEL) ||
2016 !descr->items[index].selected),
2017 (descr->style & LBS_NOTIFY) != 0 );
2020 descr->captured = TRUE;
2021 SetCapture( hwnd );
2023 if (!descr->lphc)
2025 if (descr->style & LBS_NOTIFY )
2026 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2027 MAKELPARAM( x, y ) );
2028 if (GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2030 POINT pt;
2032 pt.x = x;
2033 pt.y = y;
2035 if (DragDetect( hwnd, pt ))
2036 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2039 return 0;
2043 /*************************************************************************
2044 * LISTBOX_HandleLButtonDownCombo [Internal]
2046 * Process LButtonDown message for the ComboListBox
2048 nn * PARAMS
2049 * pWnd [I] The windows internal structure
2050 * pDescr [I] The ListBox internal structure
2051 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2052 * x [I] X Mouse Coordinate
2053 * y [I] Y Mouse Coordinate
2055 * RETURNS
2056 * 0 since we are processing the WM_LBUTTONDOWN Message
2058 * NOTES
2059 * This function is only to be used when a ListBox is a ComboListBox
2062 static LRESULT LISTBOX_HandleLButtonDownCombo( HWND hwnd, LB_DESCR *pDescr,
2063 UINT msg, WPARAM wParam, INT x, INT y)
2065 RECT clientRect, screenRect;
2066 POINT mousePos;
2068 mousePos.x = x;
2069 mousePos.y = y;
2071 GetClientRect(hwnd, &clientRect);
2073 if(PtInRect(&clientRect, mousePos))
2075 /* MousePos is in client, resume normal processing */
2076 if (msg == WM_LBUTTONDOWN)
2078 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2079 return LISTBOX_HandleLButtonDown( hwnd, pDescr, wParam, x, y);
2081 else if (pDescr->style & LBS_NOTIFY)
2082 SEND_NOTIFICATION( hwnd, pDescr, LBN_DBLCLK );
2083 return 0;
2085 else
2087 POINT screenMousePos;
2088 HWND hWndOldCapture;
2090 /* Check the Non-Client Area */
2091 screenMousePos = mousePos;
2092 hWndOldCapture = GetCapture();
2093 ReleaseCapture();
2094 GetWindowRect(hwnd, &screenRect);
2095 ClientToScreen(hwnd, &screenMousePos);
2097 if(!PtInRect(&screenRect, screenMousePos))
2099 LISTBOX_SetCaretIndex( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE );
2100 LISTBOX_SetSelection( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2101 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2102 return 0;
2104 else
2106 /* Check to see the NC is a scrollbar */
2107 INT nHitTestType=0;
2108 LONG style = GetWindowLongA( hwnd, GWL_STYLE );
2109 /* Check Vertical scroll bar */
2110 if (style & WS_VSCROLL)
2112 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2113 if (PtInRect( &clientRect, mousePos ))
2115 nHitTestType = HTVSCROLL;
2118 /* Check horizontal scroll bar */
2119 if (style & WS_HSCROLL)
2121 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2122 if (PtInRect( &clientRect, mousePos ))
2124 nHitTestType = HTHSCROLL;
2127 /* Windows sends this message when a scrollbar is clicked
2130 if(nHitTestType != 0)
2132 SendMessageW(hwnd, WM_NCLBUTTONDOWN, nHitTestType,
2133 MAKELONG(screenMousePos.x, screenMousePos.y));
2135 /* Resume the Capture after scrolling is complete
2137 if(hWndOldCapture != 0)
2139 SetCapture(hWndOldCapture);
2143 return 0;
2146 /***********************************************************************
2147 * LISTBOX_HandleLButtonUp
2149 static LRESULT LISTBOX_HandleLButtonUp( HWND hwnd, LB_DESCR *descr )
2151 if (LISTBOX_Timer != LB_TIMER_NONE)
2152 KillSystemTimer( hwnd, LB_TIMER_ID );
2153 LISTBOX_Timer = LB_TIMER_NONE;
2154 if (descr->captured)
2156 descr->captured = FALSE;
2157 if (GetCapture() == hwnd) ReleaseCapture();
2158 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2159 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2161 return 0;
2165 /***********************************************************************
2166 * LISTBOX_HandleTimer
2168 * Handle scrolling upon a timer event.
2169 * Return TRUE if scrolling should continue.
2171 static LRESULT LISTBOX_HandleTimer( HWND hwnd, LB_DESCR *descr,
2172 INT index, TIMER_DIRECTION dir )
2174 switch(dir)
2176 case LB_TIMER_UP:
2177 if (descr->top_item) index = descr->top_item - 1;
2178 else index = 0;
2179 break;
2180 case LB_TIMER_LEFT:
2181 if (descr->top_item) index -= descr->page_size;
2182 break;
2183 case LB_TIMER_DOWN:
2184 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2185 if (index == descr->focus_item) index++;
2186 if (index >= descr->nb_items) index = descr->nb_items - 1;
2187 break;
2188 case LB_TIMER_RIGHT:
2189 if (index + descr->page_size < descr->nb_items)
2190 index += descr->page_size;
2191 break;
2192 case LB_TIMER_NONE:
2193 break;
2195 if (index == descr->focus_item) return FALSE;
2196 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2197 return TRUE;
2201 /***********************************************************************
2202 * LISTBOX_HandleSystemTimer
2204 * WM_SYSTIMER handler.
2206 static LRESULT LISTBOX_HandleSystemTimer( HWND hwnd, LB_DESCR *descr )
2208 if (!LISTBOX_HandleTimer( hwnd, descr, descr->focus_item, LISTBOX_Timer ))
2210 KillSystemTimer( hwnd, LB_TIMER_ID );
2211 LISTBOX_Timer = LB_TIMER_NONE;
2213 return 0;
2217 /***********************************************************************
2218 * LISTBOX_HandleMouseMove
2220 * WM_MOUSEMOVE handler.
2222 static void LISTBOX_HandleMouseMove( HWND hwnd, LB_DESCR *descr,
2223 INT x, INT y )
2225 INT index;
2226 TIMER_DIRECTION dir = LB_TIMER_NONE;
2228 if (!descr->captured) return;
2230 if (descr->style & LBS_MULTICOLUMN)
2232 if (y < 0) y = 0;
2233 else if (y >= descr->item_height * descr->page_size)
2234 y = descr->item_height * descr->page_size - 1;
2236 if (x < 0)
2238 dir = LB_TIMER_LEFT;
2239 x = 0;
2241 else if (x >= descr->width)
2243 dir = LB_TIMER_RIGHT;
2244 x = descr->width - 1;
2247 else
2249 if (y < 0) dir = LB_TIMER_UP; /* above */
2250 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2253 index = LISTBOX_GetItemFromPoint( descr, x, y );
2254 if (index == -1) index = descr->focus_item;
2255 if (!LISTBOX_HandleTimer( hwnd, descr, index, dir )) dir = LB_TIMER_NONE;
2257 /* Start/stop the system timer */
2259 if (dir != LB_TIMER_NONE)
2260 SetSystemTimer( hwnd, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2261 else if (LISTBOX_Timer != LB_TIMER_NONE)
2262 KillSystemTimer( hwnd, LB_TIMER_ID );
2263 LISTBOX_Timer = dir;
2267 /***********************************************************************
2268 * LISTBOX_HandleKeyDown
2270 static LRESULT LISTBOX_HandleKeyDown( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
2272 INT caret = -1;
2273 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2274 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2275 bForceSelection = FALSE; /* only for single select list */
2277 if (descr->style & LBS_WANTKEYBOARDINPUT)
2279 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2280 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2281 (LPARAM)hwnd );
2282 if (caret == -2) return 0;
2284 if (caret == -1) switch(wParam)
2286 case VK_LEFT:
2287 if (descr->style & LBS_MULTICOLUMN)
2289 bForceSelection = FALSE;
2290 if (descr->focus_item >= descr->page_size)
2291 caret = descr->focus_item - descr->page_size;
2292 break;
2294 /* fall through */
2295 case VK_UP:
2296 caret = descr->focus_item - 1;
2297 if (caret < 0) caret = 0;
2298 break;
2299 case VK_RIGHT:
2300 if (descr->style & LBS_MULTICOLUMN)
2302 bForceSelection = FALSE;
2303 if (descr->focus_item + descr->page_size < descr->nb_items)
2304 caret = descr->focus_item + descr->page_size;
2305 break;
2307 /* fall through */
2308 case VK_DOWN:
2309 caret = descr->focus_item + 1;
2310 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2311 break;
2313 case VK_PRIOR:
2314 if (descr->style & LBS_MULTICOLUMN)
2316 INT page = descr->width / descr->column_width;
2317 if (page < 1) page = 1;
2318 caret = descr->focus_item - (page * descr->page_size) + 1;
2320 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2321 if (caret < 0) caret = 0;
2322 break;
2323 case VK_NEXT:
2324 if (descr->style & LBS_MULTICOLUMN)
2326 INT page = descr->width / descr->column_width;
2327 if (page < 1) page = 1;
2328 caret = descr->focus_item + (page * descr->page_size) - 1;
2330 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2331 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2332 break;
2333 case VK_HOME:
2334 caret = 0;
2335 break;
2336 case VK_END:
2337 caret = descr->nb_items - 1;
2338 break;
2339 case VK_SPACE:
2340 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2341 else if (descr->style & LBS_MULTIPLESEL)
2343 LISTBOX_SetSelection( hwnd, descr, descr->focus_item,
2344 !descr->items[descr->focus_item].selected,
2345 (descr->style & LBS_NOTIFY) != 0 );
2347 break;
2348 default:
2349 bForceSelection = FALSE;
2351 if (bForceSelection) /* focused item is used instead of key */
2352 caret = descr->focus_item;
2353 if (caret >= 0)
2355 if ((descr->style & LBS_EXTENDEDSEL) &&
2356 !(GetKeyState( VK_SHIFT ) & 0x8000))
2357 descr->anchor_item = caret;
2358 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2359 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2360 if (descr->style & LBS_NOTIFY)
2362 if( descr->lphc )
2364 /* make sure that combo parent doesn't hide us */
2365 descr->lphc->wState |= CBF_NOROLLUP;
2367 if (descr->nb_items) SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2370 return 0;
2374 /***********************************************************************
2375 * LISTBOX_HandleChar
2377 static LRESULT LISTBOX_HandleChar( HWND hwnd, LB_DESCR *descr, WCHAR charW )
2379 INT caret = -1;
2380 WCHAR str[2];
2382 str[0] = charW;
2383 str[1] = '\0';
2385 if (descr->style & LBS_WANTKEYBOARDINPUT)
2387 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2388 MAKEWPARAM(charW, descr->focus_item),
2389 (LPARAM)hwnd );
2390 if (caret == -2) return 0;
2392 if (caret == -1)
2393 caret = LISTBOX_FindString( hwnd, descr, descr->focus_item, str, FALSE);
2394 if (caret != -1)
2396 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2397 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2398 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2399 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2400 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2402 return 0;
2406 /***********************************************************************
2407 * LISTBOX_Create
2409 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2411 LB_DESCR *descr;
2412 MEASUREITEMSTRUCT mis;
2413 RECT rect;
2415 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2416 return FALSE;
2418 GetClientRect( hwnd, &rect );
2419 descr->owner = GetParent( hwnd );
2420 descr->style = GetWindowLongA( hwnd, GWL_STYLE );
2421 descr->width = rect.right - rect.left;
2422 descr->height = rect.bottom - rect.top;
2423 descr->items = NULL;
2424 descr->nb_items = 0;
2425 descr->top_item = 0;
2426 descr->selected_item = -1;
2427 descr->focus_item = 0;
2428 descr->anchor_item = -1;
2429 descr->item_height = 1;
2430 descr->page_size = 1;
2431 descr->column_width = 150;
2432 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2433 descr->horz_pos = 0;
2434 descr->nb_tabs = 0;
2435 descr->tabs = NULL;
2436 descr->caret_on = lphc ? FALSE : TRUE;
2437 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2438 descr->in_focus = FALSE;
2439 descr->captured = FALSE;
2440 descr->font = 0;
2441 descr->locale = 0; /* FIXME */
2442 descr->lphc = lphc;
2444 if (is_old_app(hwnd) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2446 /* Win95 document "List Box Differences" from MSDN:
2447 If a list box in a version 3.x application has either the
2448 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2449 horizontal and vertical scroll bars.
2451 descr->style |= WS_VSCROLL | WS_HSCROLL;
2454 if( lphc )
2456 TRACE_(combo)("[%p]: resetting owner %p -> %p\n", hwnd, descr->owner, lphc->self );
2457 descr->owner = lphc->self;
2460 SetWindowLongA( hwnd, 0, (LONG)descr );
2462 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2464 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2465 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2466 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2467 descr->item_height = LISTBOX_SetFont( hwnd, descr, 0 );
2469 if (descr->style & LBS_OWNERDRAWFIXED)
2471 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2473 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2474 descr->item_height = lphc->fixedOwnerDrawHeight;
2476 else
2478 UINT id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
2479 mis.CtlType = ODT_LISTBOX;
2480 mis.CtlID = id;
2481 mis.itemID = -1;
2482 mis.itemWidth = 0;
2483 mis.itemData = 0;
2484 mis.itemHeight = descr->item_height;
2485 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2486 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2490 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2491 return TRUE;
2495 /***********************************************************************
2496 * LISTBOX_Destroy
2498 static BOOL LISTBOX_Destroy( HWND hwnd, LB_DESCR *descr )
2500 LISTBOX_ResetContent( hwnd, descr );
2501 SetWindowLongA( hwnd, 0, 0 );
2502 HeapFree( GetProcessHeap(), 0, descr );
2503 return TRUE;
2507 /***********************************************************************
2508 * ListBoxWndProc_common
2510 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2511 WPARAM wParam, LPARAM lParam, BOOL unicode )
2513 LRESULT ret;
2514 LB_DESCR *descr;
2516 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
2518 if (msg == WM_CREATE)
2520 if (!LISTBOX_Create( hwnd, NULL ))
2521 return -1;
2522 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongA( hwnd, 0 ) );
2523 return 0;
2525 /* Ignore all other messages before we get a WM_CREATE */
2526 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2527 DefWindowProcA( hwnd, msg, wParam, lParam );
2530 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2531 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
2532 switch(msg)
2534 case LB_RESETCONTENT16:
2535 case LB_RESETCONTENT:
2536 LISTBOX_ResetContent( hwnd, descr );
2537 LISTBOX_UpdateScroll( hwnd, descr );
2538 InvalidateRect( hwnd, NULL, TRUE );
2539 return 0;
2541 case LB_ADDSTRING16:
2542 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2543 /* fall through */
2544 case LB_ADDSTRING:
2546 INT ret;
2547 LPWSTR textW;
2548 if(unicode || !HAS_STRINGS(descr))
2549 textW = (LPWSTR)lParam;
2550 else
2552 LPSTR textA = (LPSTR)lParam;
2553 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2554 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2555 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2557 wParam = LISTBOX_FindStringPos( hwnd, descr, textW, FALSE );
2558 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2559 if (!unicode && HAS_STRINGS(descr))
2560 HeapFree(GetProcessHeap(), 0, textW);
2561 return ret;
2564 case LB_INSERTSTRING16:
2565 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2566 wParam = (INT)(INT16)wParam;
2567 /* fall through */
2568 case LB_INSERTSTRING:
2570 INT ret;
2571 LPWSTR textW;
2572 if(unicode || !HAS_STRINGS(descr))
2573 textW = (LPWSTR)lParam;
2574 else
2576 LPSTR textA = (LPSTR)lParam;
2577 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2578 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2579 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2581 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2582 if(!unicode && HAS_STRINGS(descr))
2583 HeapFree(GetProcessHeap(), 0, textW);
2584 return ret;
2587 case LB_ADDFILE16:
2588 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2589 /* fall through */
2590 case LB_ADDFILE:
2592 INT ret;
2593 LPWSTR textW;
2594 if(unicode || !HAS_STRINGS(descr))
2595 textW = (LPWSTR)lParam;
2596 else
2598 LPSTR textA = (LPSTR)lParam;
2599 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2600 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2601 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2603 wParam = LISTBOX_FindFileStrPos( hwnd, descr, textW );
2604 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2605 if(!unicode && HAS_STRINGS(descr))
2606 HeapFree(GetProcessHeap(), 0, textW);
2607 return ret;
2610 case LB_DELETESTRING16:
2611 case LB_DELETESTRING:
2612 if (LISTBOX_RemoveItem( hwnd, descr, wParam) != LB_ERR)
2613 return descr->nb_items;
2614 else
2615 return LB_ERR;
2617 case LB_GETITEMDATA16:
2618 case LB_GETITEMDATA:
2619 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2620 return LB_ERR;
2621 return descr->items[wParam].data;
2623 case LB_SETITEMDATA16:
2624 case LB_SETITEMDATA:
2625 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2626 return LB_ERR;
2627 descr->items[wParam].data = (DWORD)lParam;
2628 return LB_OKAY;
2630 case LB_GETCOUNT16:
2631 case LB_GETCOUNT:
2632 return descr->nb_items;
2634 case LB_GETTEXT16:
2635 lParam = (LPARAM)MapSL(lParam);
2636 /* fall through */
2637 case LB_GETTEXT:
2638 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2640 case LB_GETTEXTLEN16:
2641 /* fall through */
2642 case LB_GETTEXTLEN:
2643 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2644 return LB_ERR;
2645 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2646 if (unicode) return strlenW( descr->items[wParam].str );
2647 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2648 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2650 case LB_GETCURSEL16:
2651 case LB_GETCURSEL:
2652 if (descr->nb_items==0)
2653 return LB_ERR;
2654 if (!IS_MULTISELECT(descr))
2655 return descr->selected_item;
2656 /* else */
2657 if (descr->selected_item!=-1)
2658 return descr->selected_item;
2659 /* else */
2660 return descr->focus_item;
2661 /* otherwise, if the user tries to move the selection with the */
2662 /* arrow keys, we will give the application something to choke on */
2663 case LB_GETTOPINDEX16:
2664 case LB_GETTOPINDEX:
2665 return descr->top_item;
2667 case LB_GETITEMHEIGHT16:
2668 case LB_GETITEMHEIGHT:
2669 return LISTBOX_GetItemHeight( descr, wParam );
2671 case LB_SETITEMHEIGHT16:
2672 lParam = LOWORD(lParam);
2673 /* fall through */
2674 case LB_SETITEMHEIGHT:
2675 return LISTBOX_SetItemHeight( hwnd, descr, wParam, lParam, TRUE );
2677 case LB_ITEMFROMPOINT:
2679 POINT pt;
2680 RECT rect;
2682 pt.x = LOWORD(lParam);
2683 pt.y = HIWORD(lParam);
2684 rect.left = 0;
2685 rect.top = 0;
2686 rect.right = descr->width;
2687 rect.bottom = descr->height;
2689 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2690 !PtInRect( &rect, pt ) );
2693 case LB_SETCARETINDEX16:
2694 case LB_SETCARETINDEX:
2695 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2696 if (LISTBOX_SetCaretIndex( hwnd, descr, wParam, !lParam ) == LB_ERR)
2697 return LB_ERR;
2698 else if (ISWIN31)
2699 return wParam;
2700 else
2701 return LB_OKAY;
2703 case LB_GETCARETINDEX16:
2704 case LB_GETCARETINDEX:
2705 return descr->focus_item;
2707 case LB_SETTOPINDEX16:
2708 case LB_SETTOPINDEX:
2709 return LISTBOX_SetTopItem( hwnd, descr, wParam, TRUE );
2711 case LB_SETCOLUMNWIDTH16:
2712 case LB_SETCOLUMNWIDTH:
2713 return LISTBOX_SetColumnWidth( hwnd, descr, wParam );
2715 case LB_GETITEMRECT16:
2717 RECT rect;
2718 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2719 CONV_RECT32TO16( &rect, MapSL(lParam) );
2721 return ret;
2723 case LB_GETITEMRECT:
2724 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2726 case LB_FINDSTRING16:
2727 wParam = (INT)(INT16)wParam;
2728 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2729 /* fall through */
2730 case LB_FINDSTRING:
2732 INT ret;
2733 LPWSTR textW;
2734 if(unicode || !HAS_STRINGS(descr))
2735 textW = (LPWSTR)lParam;
2736 else
2738 LPSTR textA = (LPSTR)lParam;
2739 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2740 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2741 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2743 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2744 if(!unicode && HAS_STRINGS(descr))
2745 HeapFree(GetProcessHeap(), 0, textW);
2746 return ret;
2749 case LB_FINDSTRINGEXACT16:
2750 wParam = (INT)(INT16)wParam;
2751 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2752 /* fall through */
2753 case LB_FINDSTRINGEXACT:
2755 INT ret;
2756 LPWSTR textW;
2757 if(unicode || !HAS_STRINGS(descr))
2758 textW = (LPWSTR)lParam;
2759 else
2761 LPSTR textA = (LPSTR)lParam;
2762 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2763 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2764 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2766 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, TRUE );
2767 if(!unicode && HAS_STRINGS(descr))
2768 HeapFree(GetProcessHeap(), 0, textW);
2769 return ret;
2772 case LB_SELECTSTRING16:
2773 wParam = (INT)(INT16)wParam;
2774 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2775 /* fall through */
2776 case LB_SELECTSTRING:
2778 INT index;
2779 LPWSTR textW;
2781 if(HAS_STRINGS(descr))
2782 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2783 debugstr_a((LPSTR)lParam));
2784 if(unicode || !HAS_STRINGS(descr))
2785 textW = (LPWSTR)lParam;
2786 else
2788 LPSTR textA = (LPSTR)lParam;
2789 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2790 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2791 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2793 index = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2794 if(!unicode && HAS_STRINGS(descr))
2795 HeapFree(GetProcessHeap(), 0, textW);
2796 if (index != LB_ERR)
2798 LISTBOX_MoveCaret( hwnd, descr, index, TRUE );
2799 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
2801 return index;
2804 case LB_GETSEL16:
2805 wParam = (INT)(INT16)wParam;
2806 /* fall through */
2807 case LB_GETSEL:
2808 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2809 return LB_ERR;
2810 return descr->items[wParam].selected;
2812 case LB_SETSEL16:
2813 lParam = (INT)(INT16)lParam;
2814 /* fall through */
2815 case LB_SETSEL:
2816 return LISTBOX_SetSelection( hwnd, descr, lParam, wParam, FALSE );
2818 case LB_SETCURSEL16:
2819 wParam = (INT)(INT16)wParam;
2820 /* fall through */
2821 case LB_SETCURSEL:
2822 if (IS_MULTISELECT(descr)) return LB_ERR;
2823 LISTBOX_SetCaretIndex( hwnd, descr, wParam, TRUE );
2824 return LISTBOX_SetSelection( hwnd, descr, wParam, TRUE, FALSE );
2826 case LB_GETSELCOUNT16:
2827 case LB_GETSELCOUNT:
2828 return LISTBOX_GetSelCount( descr );
2830 case LB_GETSELITEMS16:
2831 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2833 case LB_GETSELITEMS:
2834 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2836 case LB_SELITEMRANGE16:
2837 case LB_SELITEMRANGE:
2838 if (LOWORD(lParam) <= HIWORD(lParam))
2839 return LISTBOX_SelectItemRange( hwnd, descr, LOWORD(lParam),
2840 HIWORD(lParam), wParam );
2841 else
2842 return LISTBOX_SelectItemRange( hwnd, descr, HIWORD(lParam),
2843 LOWORD(lParam), wParam );
2845 case LB_SELITEMRANGEEX16:
2846 case LB_SELITEMRANGEEX:
2847 if ((INT)lParam >= (INT)wParam)
2848 return LISTBOX_SelectItemRange( hwnd, descr, wParam, lParam, TRUE );
2849 else
2850 return LISTBOX_SelectItemRange( hwnd, descr, lParam, wParam, FALSE);
2852 case LB_GETHORIZONTALEXTENT16:
2853 case LB_GETHORIZONTALEXTENT:
2854 return descr->horz_extent;
2856 case LB_SETHORIZONTALEXTENT16:
2857 case LB_SETHORIZONTALEXTENT:
2858 return LISTBOX_SetHorizontalExtent( hwnd, descr, wParam );
2860 case LB_GETANCHORINDEX16:
2861 case LB_GETANCHORINDEX:
2862 return descr->anchor_item;
2864 case LB_SETANCHORINDEX16:
2865 wParam = (INT)(INT16)wParam;
2866 /* fall through */
2867 case LB_SETANCHORINDEX:
2868 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2869 return LB_ERR;
2870 descr->anchor_item = (INT)wParam;
2871 return LB_OKAY;
2873 case LB_DIR16:
2874 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2875 * be set automatically (this is different in Win32) */
2876 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2877 lParam = (LPARAM)MapSL(lParam);
2878 /* fall through */
2879 case LB_DIR:
2881 INT ret;
2882 LPWSTR textW;
2883 if(unicode)
2884 textW = (LPWSTR)lParam;
2885 else
2887 LPSTR textA = (LPSTR)lParam;
2888 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2889 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2890 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2892 ret = LISTBOX_Directory( hwnd, descr, wParam, textW, msg == LB_DIR );
2893 if(!unicode)
2894 HeapFree(GetProcessHeap(), 0, textW);
2895 return ret;
2898 case LB_GETLOCALE:
2899 return descr->locale;
2901 case LB_SETLOCALE:
2902 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2903 return LB_OKAY;
2905 case LB_INITSTORAGE:
2906 return LISTBOX_InitStorage( hwnd, descr, wParam );
2908 case LB_SETCOUNT:
2909 return LISTBOX_SetCount( hwnd, descr, (INT)wParam );
2911 case LB_SETTABSTOPS16:
2912 return LISTBOX_SetTabStops( hwnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2914 case LB_SETTABSTOPS:
2915 return LISTBOX_SetTabStops( hwnd, descr, wParam, (LPINT)lParam, FALSE );
2917 case LB_CARETON16:
2918 case LB_CARETON:
2919 if (descr->caret_on)
2920 return LB_OKAY;
2921 descr->caret_on = TRUE;
2922 if ((descr->focus_item != -1) && (descr->in_focus))
2923 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2924 return LB_OKAY;
2926 case LB_CARETOFF16:
2927 case LB_CARETOFF:
2928 if (!descr->caret_on)
2929 return LB_OKAY;
2930 descr->caret_on = FALSE;
2931 if ((descr->focus_item != -1) && (descr->in_focus))
2932 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2933 return LB_OKAY;
2935 case WM_DESTROY:
2936 return LISTBOX_Destroy( hwnd, descr );
2938 case WM_ENABLE:
2939 InvalidateRect( hwnd, NULL, TRUE );
2940 return 0;
2942 case WM_SETREDRAW:
2943 LISTBOX_SetRedraw( hwnd, descr, wParam != 0 );
2944 return 0;
2946 case WM_GETDLGCODE:
2947 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2949 case WM_PAINT:
2951 PAINTSTRUCT ps;
2952 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( hwnd, &ps );
2953 ret = LISTBOX_Paint( hwnd, descr, hdc );
2954 if( !wParam ) EndPaint( hwnd, &ps );
2956 return ret;
2957 case WM_SIZE:
2958 LISTBOX_UpdateSize( hwnd, descr );
2959 return 0;
2960 case WM_GETFONT:
2961 return (LRESULT)descr->font;
2962 case WM_SETFONT:
2963 LISTBOX_SetFont( hwnd, descr, (HFONT)wParam );
2964 if (lParam) InvalidateRect( hwnd, 0, TRUE );
2965 return 0;
2966 case WM_SETFOCUS:
2967 descr->in_focus = TRUE;
2968 descr->caret_on = TRUE;
2969 if (descr->focus_item != -1)
2970 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2971 SEND_NOTIFICATION( hwnd, descr, LBN_SETFOCUS );
2972 return 0;
2973 case WM_KILLFOCUS:
2974 descr->in_focus = FALSE;
2975 if ((descr->focus_item != -1) && descr->caret_on)
2976 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2977 SEND_NOTIFICATION( hwnd, descr, LBN_KILLFOCUS );
2978 return 0;
2979 case WM_HSCROLL:
2980 return LISTBOX_HandleHScroll( hwnd, descr, wParam );
2981 case WM_VSCROLL:
2982 return LISTBOX_HandleVScroll( hwnd, descr, wParam );
2983 case WM_MOUSEWHEEL:
2984 if (wParam & (MK_SHIFT | MK_CONTROL))
2985 return DefWindowProcW( hwnd, msg, wParam, lParam );
2986 return LISTBOX_HandleMouseWheel( hwnd, descr, wParam );
2987 case WM_LBUTTONDOWN:
2988 return LISTBOX_HandleLButtonDown( hwnd, descr, wParam,
2989 (INT16)LOWORD(lParam),
2990 (INT16)HIWORD(lParam) );
2991 case WM_LBUTTONDBLCLK:
2992 if (descr->style & LBS_NOTIFY)
2993 SEND_NOTIFICATION( hwnd, descr, LBN_DBLCLK );
2994 return 0;
2995 case WM_MOUSEMOVE:
2996 if (GetCapture() == hwnd)
2997 LISTBOX_HandleMouseMove( hwnd, descr, (INT16)LOWORD(lParam),
2998 (INT16)HIWORD(lParam) );
2999 return 0;
3000 case WM_LBUTTONUP:
3001 return LISTBOX_HandleLButtonUp( hwnd, descr );
3002 case WM_KEYDOWN:
3003 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3004 case WM_CHAR:
3006 WCHAR charW;
3007 if(unicode)
3008 charW = (WCHAR)wParam;
3009 else
3011 CHAR charA = (CHAR)wParam;
3012 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3014 return LISTBOX_HandleChar( hwnd, descr, charW );
3016 case WM_SYSTIMER:
3017 return LISTBOX_HandleSystemTimer( hwnd, descr );
3018 case WM_ERASEBKGND:
3019 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3021 RECT rect;
3022 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3023 wParam, (LPARAM)hwnd );
3024 TRACE("hbrush = %p\n", hbrush);
3025 if(!hbrush)
3026 hbrush = GetSysColorBrush(COLOR_WINDOW);
3027 if(hbrush)
3029 GetClientRect(hwnd, &rect);
3030 FillRect((HDC)wParam, &rect, hbrush);
3033 return 1;
3034 case WM_DROPFILES:
3035 if( !descr->lphc )
3036 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3037 SendMessageA( descr->owner, msg, wParam, lParam );
3038 break;
3040 default:
3041 if ((msg >= WM_USER) && (msg < 0xc000))
3042 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3043 hwnd, msg, wParam, lParam );
3044 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3045 DefWindowProcA( hwnd, msg, wParam, lParam );
3047 return 0;
3050 /***********************************************************************
3051 * ListBoxWndProcA
3053 * This is just a wrapper for the real wndproc, it only does window locking
3054 * and unlocking.
3056 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3058 if (!IsWindow(hwnd)) return 0;
3059 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3062 /***********************************************************************
3063 * ListBoxWndProcW
3065 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3067 if (!IsWindow(hwnd)) return 0;
3068 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );
3071 /***********************************************************************
3072 * ComboLBWndProc_common
3074 * The real combo listbox wndproc
3076 static LRESULT WINAPI ComboLBWndProc_common( HWND hwnd, UINT msg,
3077 WPARAM wParam, LPARAM lParam, BOOL unicode )
3079 LRESULT lRet = 0;
3080 LB_DESCR *descr;
3081 LPHEADCOMBO lphc;
3083 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
3085 if (msg == WM_CREATE)
3087 CREATESTRUCTA *lpcs = (CREATESTRUCTA *)lParam;
3088 TRACE_(combo)("\tpassed parent handle = %p\n",lpcs->lpCreateParams);
3089 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3090 return LISTBOX_Create( hwnd, lphc );
3092 /* Ignore all other messages before we get a WM_CREATE */
3093 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3094 DefWindowProcA( hwnd, msg, wParam, lParam );
3097 TRACE_(combo)("[%p]: msg %s wp %08x lp %08lx\n",
3098 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
3100 if ((lphc = descr->lphc) != NULL)
3102 switch( msg )
3104 case WM_MOUSEMOVE:
3105 if ( (CB_GETTYPE(lphc) != CBS_SIMPLE) )
3107 POINT mousePos;
3108 BOOL captured;
3109 RECT clientRect;
3111 mousePos.x = (INT16)LOWORD(lParam);
3112 mousePos.y = (INT16)HIWORD(lParam);
3115 * If we are in a dropdown combobox, we simulate that
3116 * the mouse is captured to show the tracking of the item.
3118 GetClientRect(hwnd, &clientRect);
3120 if (PtInRect( &clientRect, mousePos ))
3122 captured = descr->captured;
3123 descr->captured = TRUE;
3125 LISTBOX_HandleMouseMove( hwnd, descr,
3126 mousePos.x, mousePos.y);
3128 descr->captured = captured;
3131 else
3133 LISTBOX_HandleMouseMove( hwnd, descr,
3134 mousePos.x, mousePos.y);
3137 return 0;
3140 break;
3142 case WM_LBUTTONUP:
3144 POINT mousePos;
3145 RECT clientRect;
3148 * If the mouse button "up" is not in the listbox,
3149 * we make sure there is no selection by re-selecting the
3150 * item that was selected when the listbox was made visible.
3152 mousePos.x = (INT16)LOWORD(lParam);
3153 mousePos.y = (INT16)HIWORD(lParam);
3155 GetClientRect(hwnd, &clientRect);
3158 * When the user clicks outside the combobox and the focus
3159 * is lost, the owning combobox will send a fake buttonup with
3160 * 0xFFFFFFF as the mouse location, we must also revert the
3161 * selection to the original selection.
3163 if ( (lParam == (LPARAM)-1) ||
3164 (!PtInRect( &clientRect, mousePos )) )
3166 LISTBOX_MoveCaret( hwnd, descr, lphc->droppedIndex, FALSE );
3169 return LISTBOX_HandleLButtonUp( hwnd, descr );
3170 case WM_LBUTTONDBLCLK:
3171 case WM_LBUTTONDOWN:
3172 return LISTBOX_HandleLButtonDownCombo(hwnd, descr, msg, wParam,
3173 (INT16)LOWORD(lParam),
3174 (INT16)HIWORD(lParam) );
3175 case WM_NCACTIVATE:
3176 return FALSE;
3177 case WM_KEYDOWN:
3178 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3180 /* for some reason(?) Windows makes it possible to
3181 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3183 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3184 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3185 && (wParam == VK_DOWN || wParam == VK_UP)) )
3187 COMBO_FlipListbox( lphc, FALSE, FALSE );
3188 return 0;
3191 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3193 case LB_SETCURSEL16:
3194 case LB_SETCURSEL:
3195 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3196 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3197 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3198 return lRet;
3199 case WM_NCDESTROY:
3200 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3201 lphc->hWndLBox = 0;
3202 break;
3206 /* default handling: call listbox wnd proc */
3207 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3208 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3210 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3212 return lRet;
3215 /***********************************************************************
3216 * ComboLBWndProcA
3218 * NOTE: in Windows, winproc address of the ComboLBox is the same
3219 * as that of the Listbox.
3221 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3223 if (!IsWindow(hwnd)) return 0;
3224 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3227 /***********************************************************************
3228 * ComboLBWndProcW
3230 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3232 if (!IsWindow(hwnd)) return 0;
3233 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, TRUE );