Remove the COUNT_OF macro.
[wine.git] / controls / listbox.c
blob360650816b5b559abd016078d46bb66e4420be5d
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 <stdio.h>
24 #include "windef.h"
25 #include "wingdi.h"
26 #include "wine/winuser16.h"
27 #include "wine/winbase16.h"
28 #include "wine/unicode.h"
29 #include "winuser.h"
30 #include "winerror.h"
31 #include "spy.h"
32 #include "user.h"
33 #include "controls.h"
34 #include "wine/debug.h"
35 #include "win.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
38 WINE_DECLARE_DEBUG_CHANNEL(combo);
40 /* Unimplemented yet:
41 * - LBS_USETABSTOPS
42 * - Locale handling
44 * Probably needs improvement:
45 * - LBS_NOSEL
48 /* Items array granularity */
49 #define LB_ARRAY_GRANULARITY 16
51 /* Scrolling timeout in ms */
52 #define LB_SCROLL_TIMEOUT 50
54 /* Listbox system timer id */
55 #define LB_TIMER_ID 2
57 /* flag listbox changed while setredraw false - internal style */
58 #define LBS_DISPLAYCHANGED 0x80000000
60 /* Item structure */
61 typedef struct
63 LPWSTR str; /* Item text */
64 BOOL selected; /* Is item selected? */
65 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
66 DWORD data; /* User data */
67 } LB_ITEMDATA;
69 /* Listbox structure */
70 typedef struct
72 HWND owner; /* Owner window to send notifications to */
73 UINT style; /* Window style */
74 INT width; /* Window width */
75 INT height; /* Window height */
76 LB_ITEMDATA *items; /* Array of items */
77 INT nb_items; /* Number of items */
78 INT top_item; /* Top visible item */
79 INT selected_item; /* Selected item */
80 INT focus_item; /* Item that has the focus */
81 INT anchor_item; /* Anchor item for extended selection */
82 INT item_height; /* Default item height */
83 INT page_size; /* Items per listbox page */
84 INT column_width; /* Column width for multi-column listboxes */
85 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
86 INT horz_pos; /* Horizontal position */
87 INT nb_tabs; /* Number of tabs in array */
88 INT *tabs; /* Array of tabs */
89 BOOL caret_on; /* Is caret on? */
90 BOOL captured; /* Is mouse captured? */
91 BOOL in_focus;
92 HFONT font; /* Current font */
93 LCID locale; /* Current locale for string comparisons */
94 LPHEADCOMBO lphc; /* ComboLBox */
95 } LB_DESCR;
98 #define IS_OWNERDRAW(descr) \
99 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
101 #define HAS_STRINGS(descr) \
102 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
105 #define IS_MULTISELECT(descr) \
106 ((descr)->style & LBS_MULTIPLESEL || ((descr)->style & LBS_EXTENDEDSEL))
108 #define SEND_NOTIFICATION(hwnd,descr,code) \
109 (SendMessageW( (descr)->owner, WM_COMMAND, \
110 MAKEWPARAM( GetWindowLongA((hwnd),GWL_ID), (code)), (LPARAM)(hwnd) ))
112 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
114 /* Current timer status */
115 typedef enum
117 LB_TIMER_NONE,
118 LB_TIMER_UP,
119 LB_TIMER_LEFT,
120 LB_TIMER_DOWN,
121 LB_TIMER_RIGHT
122 } TIMER_DIRECTION;
124 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
126 static LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
127 static LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
128 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
129 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
131 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
133 /*********************************************************************
134 * listbox class descriptor
136 const struct builtin_class_descr LISTBOX_builtin_class =
138 "ListBox", /* name */
139 CS_GLOBALCLASS | CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
140 ListBoxWndProcA, /* procA */
141 ListBoxWndProcW, /* procW */
142 sizeof(LB_DESCR *), /* extra */
143 IDC_ARROWA, /* cursor */
144 0 /* brush */
148 /*********************************************************************
149 * combolbox class descriptor
151 const struct builtin_class_descr COMBOLBOX_builtin_class =
153 "ComboLBox", /* name */
154 CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS, /* style */
155 ComboLBWndProcA, /* procA */
156 ComboLBWndProcW, /* procW */
157 sizeof(LB_DESCR *), /* extra */
158 IDC_ARROWA, /* cursor */
159 0 /* brush */
163 /* check whether app is a Win 3.1 app */
164 inline static BOOL is_old_app( HWND hwnd )
166 return (GetExpWinVer16( GetWindowLongA(hwnd,GWL_HINSTANCE) ) & 0xFF00 ) == 0x0300;
170 /***********************************************************************
171 * LISTBOX_Dump
173 void LISTBOX_Dump( HWND hwnd )
175 INT i;
176 LB_ITEMDATA *item;
177 LB_DESCR *descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 );
179 TRACE( "Listbox:\n" );
180 TRACE( "hwnd=%04x descr=%08x items=%d top=%d\n",
181 hwnd, (UINT)descr, descr->nb_items,
182 descr->top_item );
183 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
185 TRACE( "%4d: %-40s %d %08lx %3d\n",
186 i, debugstr_w(item->str), item->selected, item->data, item->height );
191 /***********************************************************************
192 * LISTBOX_GetCurrentPageSize
194 * Return the current page size
196 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
198 INT i, height;
199 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
200 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
202 if ((height += descr->items[i].height) > descr->height) break;
204 if (i == descr->top_item) return 1;
205 else return i - descr->top_item;
209 /***********************************************************************
210 * LISTBOX_GetMaxTopIndex
212 * Return the maximum possible index for the top of the listbox.
214 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
216 INT max, page;
218 if (descr->style & LBS_OWNERDRAWVARIABLE)
220 page = descr->height;
221 for (max = descr->nb_items - 1; max >= 0; max--)
222 if ((page -= descr->items[max].height) < 0) break;
223 if (max < descr->nb_items - 1) max++;
225 else if (descr->style & LBS_MULTICOLUMN)
227 if ((page = descr->width / descr->column_width) < 1) page = 1;
228 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
229 max = (max - page) * descr->page_size;
231 else
233 max = descr->nb_items - descr->page_size;
235 if (max < 0) max = 0;
236 return max;
240 /***********************************************************************
241 * LISTBOX_UpdateScroll
243 * Update the scrollbars. Should be called whenever the content
244 * of the listbox changes.
246 static void LISTBOX_UpdateScroll( HWND hwnd, LB_DESCR *descr )
248 SCROLLINFO info;
250 /* Check the listbox scroll bar flags individually before we call
251 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
252 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
253 scroll bar when we do not need one.
254 if (!(descr->style & WS_VSCROLL)) return;
257 /* It is important that we check descr->style, and not wnd->dwStyle,
258 for WS_VSCROLL, as the former is exactly the one passed in
259 argument to CreateWindow.
260 In Windows (and from now on in Wine :) a listbox created
261 with such a style (no WS_SCROLL) does not update
262 the scrollbar with listbox-related data, thus letting
263 the programmer use it for his/her own purposes. */
265 if (descr->style & LBS_NOREDRAW) return;
266 info.cbSize = sizeof(info);
268 if (descr->style & LBS_MULTICOLUMN)
270 info.nMin = 0;
271 info.nMax = (descr->nb_items - 1) / descr->page_size;
272 info.nPos = descr->top_item / descr->page_size;
273 info.nPage = descr->width / descr->column_width;
274 if (info.nPage < 1) info.nPage = 1;
275 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
276 if (descr->style & LBS_DISABLENOSCROLL)
277 info.fMask |= SIF_DISABLENOSCROLL;
278 if (descr->style & WS_HSCROLL)
279 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
280 info.nMax = 0;
281 info.fMask = SIF_RANGE;
282 if (descr->style & WS_VSCROLL)
283 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
285 else
287 info.nMin = 0;
288 info.nMax = descr->nb_items - 1;
289 info.nPos = descr->top_item;
290 info.nPage = LISTBOX_GetCurrentPageSize( descr );
291 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
292 if (descr->style & LBS_DISABLENOSCROLL)
293 info.fMask |= SIF_DISABLENOSCROLL;
294 if (descr->style & WS_VSCROLL)
295 SetScrollInfo( hwnd, SB_VERT, &info, TRUE );
297 if (descr->horz_extent)
299 info.nMin = 0;
300 info.nMax = descr->horz_extent - 1;
301 info.nPos = descr->horz_pos;
302 info.nPage = descr->width;
303 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
304 if (descr->style & LBS_DISABLENOSCROLL)
305 info.fMask |= SIF_DISABLENOSCROLL;
306 if (descr->style & WS_HSCROLL)
307 SetScrollInfo( hwnd, SB_HORZ, &info, TRUE );
313 /***********************************************************************
314 * LISTBOX_SetTopItem
316 * Set the top item of the listbox, scrolling up or down if necessary.
318 static LRESULT LISTBOX_SetTopItem( HWND hwnd, LB_DESCR *descr, INT index,
319 BOOL scroll )
321 INT max = LISTBOX_GetMaxTopIndex( descr );
322 if (index > max) index = max;
323 if (index < 0) index = 0;
324 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
325 if (descr->top_item == index) return LB_OKAY;
326 if (descr->style & LBS_MULTICOLUMN)
328 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
329 if (scroll && (abs(diff) < descr->width))
330 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
331 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
333 else
334 scroll = FALSE;
336 else if (scroll)
338 INT diff;
339 if (descr->style & LBS_OWNERDRAWVARIABLE)
341 INT i;
342 diff = 0;
343 if (index > descr->top_item)
345 for (i = index - 1; i >= descr->top_item; i--)
346 diff -= descr->items[i].height;
348 else
350 for (i = index; i < descr->top_item; i++)
351 diff += descr->items[i].height;
354 else
355 diff = (descr->top_item - index) * descr->item_height;
357 if (abs(diff) < descr->height)
358 ScrollWindowEx( hwnd, 0, diff, NULL, NULL, 0, NULL,
359 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
360 else
361 scroll = FALSE;
363 if (!scroll) InvalidateRect( hwnd, NULL, TRUE );
364 descr->top_item = index;
365 LISTBOX_UpdateScroll( hwnd, descr );
366 return LB_OKAY;
370 /***********************************************************************
371 * LISTBOX_UpdatePage
373 * Update the page size. Should be called when the size of
374 * the client area or the item height changes.
376 static void LISTBOX_UpdatePage( HWND hwnd, LB_DESCR *descr )
378 INT page_size;
380 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
381 page_size = 1;
382 if (page_size == descr->page_size) return;
383 descr->page_size = page_size;
384 if (descr->style & LBS_MULTICOLUMN)
385 InvalidateRect( hwnd, NULL, TRUE );
386 LISTBOX_SetTopItem( hwnd, descr, descr->top_item, FALSE );
390 /***********************************************************************
391 * LISTBOX_UpdateSize
393 * Update the size of the listbox. Should be called when the size of
394 * the client area changes.
396 static void LISTBOX_UpdateSize( HWND hwnd, LB_DESCR *descr )
398 RECT rect;
400 GetClientRect( hwnd, &rect );
401 descr->width = rect.right - rect.left;
402 descr->height = rect.bottom - rect.top;
403 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
405 INT remaining;
406 RECT rect;
408 GetWindowRect( hwnd, &rect );
409 if(descr->item_height != 0)
410 remaining = descr->height % descr->item_height;
411 else
412 remaining = 0;
413 if ((descr->height > descr->item_height) && remaining)
415 if (is_old_app(hwnd))
416 { /* give a margin for error to 16 bits programs - if we need
417 less than the height of the nonclient area, round to the
418 *next* number of items */
419 int ncheight = rect.bottom - rect.top - descr->height;
420 if ((descr->item_height - remaining) <= ncheight)
421 remaining = remaining - descr->item_height;
423 TRACE("[%04x]: changing height %d -> %d\n",
424 hwnd, descr->height, descr->height - remaining );
425 SetWindowPos( hwnd, 0, 0, 0, rect.right - rect.left,
426 rect.bottom - rect.top - remaining,
427 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
428 return;
431 TRACE("[%04x]: new size = %d,%d\n", hwnd, descr->width, descr->height );
432 LISTBOX_UpdatePage( hwnd, descr );
433 LISTBOX_UpdateScroll( hwnd, descr );
435 /* Invalidate the focused item so it will be repainted correctly */
436 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
438 InvalidateRect( hwnd, &rect, FALSE );
443 /***********************************************************************
444 * LISTBOX_GetItemRect
446 * Get the rectangle enclosing an item, in listbox client coordinates.
447 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
449 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
451 /* Index <= 0 is legal even on empty listboxes */
452 if (index && (index >= descr->nb_items)) return -1;
453 SetRect( rect, 0, 0, descr->width, descr->height );
454 if (descr->style & LBS_MULTICOLUMN)
456 INT col = (index / descr->page_size) -
457 (descr->top_item / descr->page_size);
458 rect->left += col * descr->column_width;
459 rect->right = rect->left + descr->column_width;
460 rect->top += (index % descr->page_size) * descr->item_height;
461 rect->bottom = rect->top + descr->item_height;
463 else if (descr->style & LBS_OWNERDRAWVARIABLE)
465 INT i;
466 rect->right += descr->horz_pos;
467 if ((index >= 0) && (index < descr->nb_items))
469 if (index < descr->top_item)
471 for (i = descr->top_item-1; i >= index; i--)
472 rect->top -= descr->items[i].height;
474 else
476 for (i = descr->top_item; i < index; i++)
477 rect->top += descr->items[i].height;
479 rect->bottom = rect->top + descr->items[index].height;
483 else
485 rect->top += (index - descr->top_item) * descr->item_height;
486 rect->bottom = rect->top + descr->item_height;
487 rect->right += descr->horz_pos;
490 return ((rect->left < descr->width) && (rect->right > 0) &&
491 (rect->top < descr->height) && (rect->bottom > 0));
495 /***********************************************************************
496 * LISTBOX_GetItemFromPoint
498 * Return the item nearest from point (x,y) (in client coordinates).
500 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
502 INT index = descr->top_item;
504 if (!descr->nb_items) return -1; /* No items */
505 if (descr->style & LBS_OWNERDRAWVARIABLE)
507 INT pos = 0;
508 if (y >= 0)
510 while (index < descr->nb_items)
512 if ((pos += descr->items[index].height) > y) break;
513 index++;
516 else
518 while (index > 0)
520 index--;
521 if ((pos -= descr->items[index].height) <= y) break;
525 else if (descr->style & LBS_MULTICOLUMN)
527 if (y >= descr->item_height * descr->page_size) return -1;
528 if (y >= 0) index += y / descr->item_height;
529 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
530 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
532 else
534 index += (y / descr->item_height);
536 if (index < 0) return 0;
537 if (index >= descr->nb_items) return -1;
538 return index;
542 /***********************************************************************
543 * LISTBOX_PaintItem
545 * Paint an item.
547 static void LISTBOX_PaintItem( HWND hwnd, LB_DESCR *descr, HDC hdc,
548 const RECT *rect, INT index, UINT action, BOOL ignoreFocus )
550 LB_ITEMDATA *item = NULL;
551 if (index < descr->nb_items) item = &descr->items[index];
553 if (IS_OWNERDRAW(descr))
555 DRAWITEMSTRUCT dis;
556 RECT r;
557 HRGN hrgn;
558 UINT id = GetWindowLongA( hwnd, GWL_ID );
560 if (!item)
562 if (action == ODA_FOCUS)
563 DrawFocusRect( hdc, rect );
564 else
565 FIXME("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
566 return;
569 /* some programs mess with the clipping region when
570 drawing the item, *and* restore the previous region
571 after they are done, so a region has better to exist
572 else everything ends clipped */
573 GetClientRect(hwnd, &r);
574 hrgn = CreateRectRgnIndirect(&r);
575 SelectClipRgn( hdc, hrgn);
576 DeleteObject( hrgn );
578 dis.CtlType = ODT_LISTBOX;
579 dis.CtlID = id;
580 dis.hwndItem = hwnd;
581 dis.itemAction = action;
582 dis.hDC = hdc;
583 dis.itemID = index;
584 dis.itemState = 0;
585 if (item && item->selected) dis.itemState |= ODS_SELECTED;
586 if (!ignoreFocus && (descr->focus_item == index) &&
587 (descr->caret_on) &&
588 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
589 if (!IsWindowEnabled(hwnd)) dis.itemState |= ODS_DISABLED;
590 dis.itemData = item ? item->data : 0;
591 dis.rcItem = *rect;
592 TRACE("[%04x]: drawitem %d (%s) action=%02x state=%02x rect=%d,%d-%d,%d\n",
593 hwnd, index, item ? debugstr_w(item->str) : "", action,
594 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
595 SendMessageW(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
597 else
599 COLORREF oldText = 0, oldBk = 0;
601 if (action == ODA_FOCUS)
603 DrawFocusRect( hdc, rect );
604 return;
606 if (item && item->selected)
608 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
609 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
612 TRACE("[%04x]: painting %d (%s) action=%02x rect=%d,%d-%d,%d\n",
613 hwnd, index, item ? debugstr_w(item->str) : "", action,
614 rect->left, rect->top, rect->right, rect->bottom );
615 if (!item)
616 ExtTextOutW( hdc, rect->left + 1, rect->top,
617 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
618 else if (!(descr->style & LBS_USETABSTOPS))
619 ExtTextOutW( hdc, rect->left + 1, rect->top,
620 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
621 strlenW(item->str), NULL );
622 else
624 /* Output empty string to paint background in the full width. */
625 ExtTextOutW( hdc, rect->left + 1, rect->top,
626 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
627 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
628 item->str, strlenW(item->str),
629 descr->nb_tabs, descr->tabs, 0);
631 if (item && item->selected)
633 SetBkColor( hdc, oldBk );
634 SetTextColor( hdc, oldText );
636 if (!ignoreFocus && (descr->focus_item == index) &&
637 (descr->caret_on) &&
638 (descr->in_focus)) DrawFocusRect( hdc, rect );
643 /***********************************************************************
644 * LISTBOX_SetRedraw
646 * Change the redraw flag.
648 static void LISTBOX_SetRedraw( HWND hwnd, LB_DESCR *descr, BOOL on )
650 if (on)
652 if (!(descr->style & LBS_NOREDRAW)) return;
653 descr->style &= ~LBS_NOREDRAW;
654 if (descr->style & LBS_DISPLAYCHANGED)
655 { /* page was changed while setredraw false, refresh automatically */
656 InvalidateRect(hwnd, NULL, TRUE);
657 if ((descr->top_item + descr->page_size) > descr->nb_items)
658 { /* reset top of page if less than number of items/page */
659 descr->top_item = descr->nb_items - descr->page_size;
660 if (descr->top_item < 0) descr->top_item = 0;
662 descr->style &= ~LBS_DISPLAYCHANGED;
664 LISTBOX_UpdateScroll( hwnd, descr );
666 else descr->style |= LBS_NOREDRAW;
670 /***********************************************************************
671 * LISTBOX_RepaintItem
673 * Repaint a single item synchronously.
675 static void LISTBOX_RepaintItem( HWND hwnd, LB_DESCR *descr, INT index,
676 UINT action )
678 HDC hdc;
679 RECT rect;
680 HFONT oldFont = 0;
681 HBRUSH hbrush, oldBrush = 0;
683 /* Do not repaint the item if the item is not visible */
684 if (!IsWindowVisible(hwnd)) return;
685 if (descr->style & LBS_NOREDRAW)
687 descr->style |= LBS_DISPLAYCHANGED;
688 return;
690 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
691 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE ))) return;
692 if (descr->font) oldFont = SelectObject( hdc, descr->font );
693 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
694 hdc, (LPARAM)hwnd );
695 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
696 if (!IsWindowEnabled(hwnd))
697 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
698 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
699 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, index, action, FALSE );
700 if (oldFont) SelectObject( hdc, oldFont );
701 if (oldBrush) SelectObject( hdc, oldBrush );
702 ReleaseDC( hwnd, hdc );
706 /***********************************************************************
707 * LISTBOX_InitStorage
709 static LRESULT LISTBOX_InitStorage( HWND hwnd, LB_DESCR *descr, INT nb_items )
711 LB_ITEMDATA *item;
713 nb_items += LB_ARRAY_GRANULARITY - 1;
714 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
715 if (descr->items)
716 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
717 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
718 nb_items * sizeof(LB_ITEMDATA) )))
720 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
721 return LB_ERRSPACE;
723 descr->items = item;
724 return LB_OKAY;
728 /***********************************************************************
729 * LISTBOX_SetTabStops
731 static BOOL LISTBOX_SetTabStops( HWND hwnd, LB_DESCR *descr, INT count,
732 LPINT tabs, BOOL short_ints )
734 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
735 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
736 if (!(descr->nb_tabs = count))
738 descr->tabs = NULL;
739 return TRUE;
741 /* FIXME: count = 1 */
742 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
743 descr->nb_tabs * sizeof(INT) )))
744 return FALSE;
745 if (short_ints)
747 INT i;
748 LPINT16 p = (LPINT16)tabs;
750 TRACE("[%04x]: settabstops ", hwnd );
751 for (i = 0; i < descr->nb_tabs; i++) {
752 descr->tabs[i] = *p++<<1; /* FIXME */
753 if (TRACE_ON(listbox)) DPRINTF("%hd ", descr->tabs[i]);
755 if (TRACE_ON(listbox)) DPRINTF("\n");
757 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
758 /* FIXME: repaint the window? */
759 return TRUE;
763 /***********************************************************************
764 * LISTBOX_GetText
766 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPARAM lParam, BOOL unicode )
768 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
769 if (HAS_STRINGS(descr))
771 if (!lParam)
772 return strlenW(descr->items[index].str);
774 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
776 if(unicode)
778 LPWSTR buffer = (LPWSTR)lParam;
779 strcpyW( buffer, descr->items[index].str );
780 return strlenW(buffer);
782 else
784 LPSTR buffer = (LPSTR)lParam;
785 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, buffer, 0x7FFFFFFF, NULL, NULL) - 1;
787 } else {
788 if (lParam)
789 *((LPDWORD)lParam)=*(LPDWORD)(&descr->items[index].data);
790 return sizeof(DWORD);
795 /***********************************************************************
796 * LISTBOX_FindStringPos
798 * Find the nearest string located before a given string in sort order.
799 * If 'exact' is TRUE, return an error if we don't get an exact match.
801 static INT LISTBOX_FindStringPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str,
802 BOOL exact )
804 INT index, min, max, res = -1;
806 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
807 min = 0;
808 max = descr->nb_items;
809 while (min != max)
811 index = (min + max) / 2;
812 if (HAS_STRINGS(descr))
813 res = lstrcmpiW( descr->items[index].str, str );
814 else
816 COMPAREITEMSTRUCT cis;
817 UINT id = GetWindowLongA( hwnd, GWL_ID );
819 cis.CtlType = ODT_LISTBOX;
820 cis.CtlID = id;
821 cis.hwndItem = hwnd;
822 cis.itemID1 = index;
823 cis.itemData1 = descr->items[index].data;
824 cis.itemID2 = -1;
825 cis.itemData2 = (DWORD)str;
826 cis.dwLocaleId = descr->locale;
827 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
829 if (!res) return index;
830 if (res > 0) max = index;
831 else min = index + 1;
833 return exact ? -1 : max;
837 /***********************************************************************
838 * LISTBOX_FindFileStrPos
840 * Find the nearest string located before a given string in directory
841 * sort order (i.e. first files, then directories, then drives).
843 static INT LISTBOX_FindFileStrPos( HWND hwnd, LB_DESCR *descr, LPCWSTR str )
845 INT min, max, res = -1;
847 if (!HAS_STRINGS(descr))
848 return LISTBOX_FindStringPos( hwnd, descr, str, FALSE );
849 min = 0;
850 max = descr->nb_items;
851 while (min != max)
853 INT index = (min + max) / 2;
854 LPCWSTR p = descr->items[index].str;
855 if (*p == '[') /* drive or directory */
857 if (*str != '[') res = -1;
858 else if (p[1] == '-') /* drive */
860 if (str[1] == '-') res = str[2] - p[2];
861 else res = -1;
863 else /* directory */
865 if (str[1] == '-') res = 1;
866 else res = lstrcmpiW( str, p );
869 else /* filename */
871 if (*str == '[') res = 1;
872 else res = lstrcmpiW( str, p );
874 if (!res) return index;
875 if (res < 0) max = index;
876 else min = index + 1;
878 return max;
882 /***********************************************************************
883 * LISTBOX_FindString
885 * Find the item beginning with a given string.
887 static INT LISTBOX_FindString( HWND hwnd, LB_DESCR *descr, INT start,
888 LPCWSTR str, BOOL exact )
890 INT i;
891 LB_ITEMDATA *item;
893 if (start >= descr->nb_items) start = -1;
894 item = descr->items + start + 1;
895 if (HAS_STRINGS(descr))
897 if (!str || ! str[0] ) return LB_ERR;
898 if (exact)
900 for (i = start + 1; i < descr->nb_items; i++, item++)
901 if (!lstrcmpiW( str, item->str )) return i;
902 for (i = 0, item = descr->items; i <= start; i++, item++)
903 if (!lstrcmpiW( str, item->str )) return i;
905 else
907 /* Special case for drives and directories: ignore prefix */
908 #define CHECK_DRIVE(item) \
909 if ((item)->str[0] == '[') \
911 if (!strncmpiW( str, (item)->str+1, len )) return i; \
912 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
913 return i; \
916 INT len = strlenW(str);
917 for (i = start + 1; i < descr->nb_items; i++, item++)
919 if (!strncmpiW( str, item->str, len )) return i;
920 CHECK_DRIVE(item);
922 for (i = 0, item = descr->items; i <= start; i++, item++)
924 if (!strncmpiW( str, item->str, len )) return i;
925 CHECK_DRIVE(item);
927 #undef CHECK_DRIVE
930 else
932 if (exact && (descr->style & LBS_SORT))
933 /* If sorted, use a WM_COMPAREITEM binary search */
934 return LISTBOX_FindStringPos( hwnd, descr, str, TRUE );
936 /* Otherwise use a linear search */
937 for (i = start + 1; i < descr->nb_items; i++, item++)
938 if (item->data == (DWORD)str) return i;
939 for (i = 0, item = descr->items; i <= start; i++, item++)
940 if (item->data == (DWORD)str) return i;
942 return LB_ERR;
946 /***********************************************************************
947 * LISTBOX_GetSelCount
949 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
951 INT i, count;
952 LB_ITEMDATA *item = descr->items;
954 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
955 for (i = count = 0; i < descr->nb_items; i++, item++)
956 if (item->selected) count++;
957 return count;
961 /***********************************************************************
962 * LISTBOX_GetSelItems16
964 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
966 INT i, count;
967 LB_ITEMDATA *item = descr->items;
969 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
970 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
971 if (item->selected) array[count++] = (INT16)i;
972 return count;
976 /***********************************************************************
977 * LISTBOX_GetSelItems
979 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
981 INT i, count;
982 LB_ITEMDATA *item = descr->items;
984 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
985 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
986 if (item->selected) array[count++] = i;
987 return count;
991 /***********************************************************************
992 * LISTBOX_Paint
994 static LRESULT LISTBOX_Paint( HWND hwnd, LB_DESCR *descr, HDC hdc )
996 INT i, col_pos = descr->page_size - 1;
997 RECT rect;
998 RECT focusRect = {-1, -1, -1, -1};
999 HFONT oldFont = 0;
1000 HBRUSH hbrush, oldBrush = 0;
1002 if (descr->style & LBS_NOREDRAW) return 0;
1004 SetRect( &rect, 0, 0, descr->width, descr->height );
1005 if (descr->style & LBS_MULTICOLUMN)
1006 rect.right = rect.left + descr->column_width;
1007 else if (descr->horz_pos)
1009 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1010 rect.right += descr->horz_pos;
1013 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1014 hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1015 hdc, (LPARAM)hwnd );
1016 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1017 if (!IsWindowEnabled(hwnd)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1019 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1020 (descr->in_focus))
1022 /* Special case for empty listbox: paint focus rect */
1023 rect.bottom = rect.top + descr->item_height;
1024 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, descr->focus_item,
1025 ODA_FOCUS, FALSE );
1026 rect.top = rect.bottom;
1029 /* Paint all the item, regarding the selection
1030 Focus state will be painted after */
1032 for (i = descr->top_item; i < descr->nb_items; i++)
1034 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1035 rect.bottom = rect.top + descr->item_height;
1036 else
1037 rect.bottom = rect.top + descr->items[i].height;
1039 if (i == descr->focus_item)
1041 /* keep the focus rect, to paint the focus item after */
1042 focusRect.left = rect.left;
1043 focusRect.right = rect.right;
1044 focusRect.top = rect.top;
1045 focusRect.bottom = rect.bottom;
1047 LISTBOX_PaintItem( hwnd, descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1048 rect.top = rect.bottom;
1050 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1052 if (!IS_OWNERDRAW(descr))
1054 /* Clear the bottom of the column */
1055 if (rect.top < descr->height)
1057 rect.bottom = descr->height;
1058 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1059 &rect, NULL, 0, NULL );
1063 /* Go to the next column */
1064 rect.left += descr->column_width;
1065 rect.right += descr->column_width;
1066 rect.top = 0;
1067 col_pos = descr->page_size - 1;
1069 else
1071 col_pos--;
1072 if (rect.top >= descr->height) break;
1076 /* Paint the focus item now */
1077 if (focusRect.top != focusRect.bottom && descr->caret_on)
1078 LISTBOX_PaintItem( hwnd, descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1080 if (!IS_OWNERDRAW(descr))
1082 /* Clear the remainder of the client area */
1083 if (rect.top < descr->height)
1085 rect.bottom = descr->height;
1086 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1087 &rect, NULL, 0, NULL );
1089 if (rect.right < descr->width)
1091 rect.left = rect.right;
1092 rect.right = descr->width;
1093 rect.top = 0;
1094 rect.bottom = descr->height;
1095 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1096 &rect, NULL, 0, NULL );
1099 if (oldFont) SelectObject( hdc, oldFont );
1100 if (oldBrush) SelectObject( hdc, oldBrush );
1101 return 0;
1105 /***********************************************************************
1106 * LISTBOX_InvalidateItems
1108 * Invalidate all items from a given item. If the specified item is not
1109 * visible, nothing happens.
1111 static void LISTBOX_InvalidateItems( HWND hwnd, LB_DESCR *descr, INT index )
1113 RECT rect;
1115 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1117 if (descr->style & LBS_NOREDRAW)
1119 descr->style |= LBS_DISPLAYCHANGED;
1120 return;
1122 rect.bottom = descr->height;
1123 InvalidateRect( hwnd, &rect, TRUE );
1124 if (descr->style & LBS_MULTICOLUMN)
1126 /* Repaint the other columns */
1127 rect.left = rect.right;
1128 rect.right = descr->width;
1129 rect.top = 0;
1130 InvalidateRect( hwnd, &rect, TRUE );
1136 /***********************************************************************
1137 * LISTBOX_GetItemHeight
1139 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1141 if (descr->style & LBS_OWNERDRAWVARIABLE)
1143 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1144 return descr->items[index].height;
1146 else return descr->item_height;
1150 /***********************************************************************
1151 * LISTBOX_SetItemHeight
1153 static LRESULT LISTBOX_SetItemHeight( HWND hwnd, LB_DESCR *descr, INT index,
1154 INT height, BOOL repaint )
1156 if (!height) height = 1;
1158 if (descr->style & LBS_OWNERDRAWVARIABLE)
1160 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1161 TRACE("[%04x]: item %d height = %d\n", hwnd, index, height );
1162 descr->items[index].height = height;
1163 LISTBOX_UpdateScroll( hwnd, descr );
1164 if (repaint)
1165 LISTBOX_InvalidateItems( hwnd, descr, index );
1167 else if (height != descr->item_height)
1169 TRACE("[%04x]: new height = %d\n", hwnd, height );
1170 descr->item_height = height;
1171 LISTBOX_UpdatePage( hwnd, descr );
1172 LISTBOX_UpdateScroll( hwnd, descr );
1173 if (repaint)
1174 InvalidateRect( hwnd, 0, TRUE );
1176 return LB_OKAY;
1180 /***********************************************************************
1181 * LISTBOX_SetHorizontalPos
1183 static void LISTBOX_SetHorizontalPos( HWND hwnd, LB_DESCR *descr, INT pos )
1185 INT diff;
1187 if (pos > descr->horz_extent - descr->width)
1188 pos = descr->horz_extent - descr->width;
1189 if (pos < 0) pos = 0;
1190 if (!(diff = descr->horz_pos - pos)) return;
1191 TRACE("[%04x]: new horz pos = %d\n", hwnd, pos );
1192 descr->horz_pos = pos;
1193 LISTBOX_UpdateScroll( hwnd, descr );
1194 if (abs(diff) < descr->width)
1195 ScrollWindowEx( hwnd, diff, 0, NULL, NULL, 0, NULL,
1196 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1197 else
1198 InvalidateRect( hwnd, NULL, TRUE );
1202 /***********************************************************************
1203 * LISTBOX_SetHorizontalExtent
1205 static LRESULT LISTBOX_SetHorizontalExtent( HWND hwnd, LB_DESCR *descr,
1206 INT extent )
1208 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1209 return LB_OKAY;
1210 if (extent <= 0) extent = 1;
1211 if (extent == descr->horz_extent) return LB_OKAY;
1212 TRACE("[%04x]: new horz extent = %d\n", hwnd, extent );
1213 descr->horz_extent = extent;
1214 if (descr->horz_pos > extent - descr->width)
1215 LISTBOX_SetHorizontalPos( hwnd, descr, extent - descr->width );
1216 else
1217 LISTBOX_UpdateScroll( hwnd, descr );
1218 return LB_OKAY;
1222 /***********************************************************************
1223 * LISTBOX_SetColumnWidth
1225 static LRESULT LISTBOX_SetColumnWidth( HWND hwnd, LB_DESCR *descr, INT width)
1227 if (width == descr->column_width) return LB_OKAY;
1228 TRACE("[%04x]: new column width = %d\n", hwnd, width );
1229 descr->column_width = width;
1230 LISTBOX_UpdatePage( hwnd, descr );
1231 return LB_OKAY;
1235 /***********************************************************************
1236 * LISTBOX_SetFont
1238 * Returns the item height.
1240 static INT LISTBOX_SetFont( HWND hwnd, LB_DESCR *descr, HFONT font )
1242 HDC hdc;
1243 HFONT oldFont = 0;
1244 TEXTMETRICW tm;
1246 descr->font = font;
1248 if (!(hdc = GetDCEx( hwnd, 0, DCX_CACHE )))
1250 ERR("unable to get DC.\n" );
1251 return 16;
1253 if (font) oldFont = SelectObject( hdc, font );
1254 GetTextMetricsW( hdc, &tm );
1255 if (oldFont) SelectObject( hdc, oldFont );
1256 ReleaseDC( hwnd, hdc );
1257 if (!IS_OWNERDRAW(descr))
1258 LISTBOX_SetItemHeight( hwnd, descr, 0, tm.tmHeight, FALSE );
1259 return tm.tmHeight ;
1263 /***********************************************************************
1264 * LISTBOX_MakeItemVisible
1266 * Make sure that a given item is partially or fully visible.
1268 static void LISTBOX_MakeItemVisible( HWND hwnd, LB_DESCR *descr, INT index,
1269 BOOL fully )
1271 INT top;
1273 if (index <= descr->top_item) top = index;
1274 else if (descr->style & LBS_MULTICOLUMN)
1276 INT cols = descr->width;
1277 if (!fully) cols += descr->column_width - 1;
1278 if (cols >= descr->column_width) cols /= descr->column_width;
1279 else cols = 1;
1280 if (index < descr->top_item + (descr->page_size * cols)) return;
1281 top = index - descr->page_size * (cols - 1);
1283 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1285 INT height = fully ? descr->items[index].height : 1;
1286 for (top = index; top > descr->top_item; top--)
1287 if ((height += descr->items[top-1].height) > descr->height) break;
1289 else
1291 if (index < descr->top_item + descr->page_size) return;
1292 if (!fully && (index == descr->top_item + descr->page_size) &&
1293 (descr->height > (descr->page_size * descr->item_height))) return;
1294 top = index - descr->page_size + 1;
1296 LISTBOX_SetTopItem( hwnd, descr, top, TRUE );
1299 /***********************************************************************
1300 * LISTBOX_SetCaretIndex
1302 * NOTES
1303 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1306 static LRESULT LISTBOX_SetCaretIndex( HWND hwnd, LB_DESCR *descr, INT index,
1307 BOOL fully_visible )
1309 INT oldfocus = descr->focus_item;
1311 if (descr->style & LBS_NOSEL) return LB_ERR;
1312 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1313 if (index == oldfocus) return LB_OKAY;
1314 descr->focus_item = index;
1315 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1316 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1318 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1319 if (descr->caret_on && (descr->in_focus))
1320 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1322 return LB_OKAY;
1326 /***********************************************************************
1327 * LISTBOX_SelectItemRange
1329 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1331 static LRESULT LISTBOX_SelectItemRange( HWND hwnd, LB_DESCR *descr, INT first,
1332 INT last, BOOL on )
1334 INT i;
1336 /* A few sanity checks */
1338 if (descr->style & LBS_NOSEL) return LB_ERR;
1339 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1340 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1341 if (last == -1) last = descr->nb_items - 1;
1342 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1343 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1344 /* selected_item reflects last selected/unselected item on multiple sel */
1345 descr->selected_item = last;
1347 if (on) /* Turn selection on */
1349 for (i = first; i <= last; i++)
1351 if (descr->items[i].selected) continue;
1352 descr->items[i].selected = TRUE;
1353 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1355 LISTBOX_SetCaretIndex( hwnd, descr, last, TRUE );
1357 else /* Turn selection off */
1359 for (i = first; i <= last; i++)
1361 if (!descr->items[i].selected) continue;
1362 descr->items[i].selected = FALSE;
1363 LISTBOX_RepaintItem( hwnd, descr, i, ODA_SELECT );
1366 return LB_OKAY;
1369 /***********************************************************************
1370 * LISTBOX_SetSelection
1372 static LRESULT LISTBOX_SetSelection( HWND hwnd, LB_DESCR *descr, INT index,
1373 BOOL on, BOOL send_notify )
1375 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1377 if (descr->style & LBS_NOSEL) return LB_ERR;
1378 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1379 if (descr->style & LBS_MULTIPLESEL)
1381 if (index == -1) /* Select all items */
1382 return LISTBOX_SelectItemRange( hwnd, descr, 0, -1, on );
1383 else /* Only one item */
1384 return LISTBOX_SelectItemRange( hwnd, descr, index, index, on );
1386 else
1388 INT oldsel = descr->selected_item;
1389 if (index == oldsel) return LB_OKAY;
1390 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1391 if (index != -1) descr->items[index].selected = TRUE;
1392 descr->selected_item = index;
1393 if (oldsel != -1) LISTBOX_RepaintItem( hwnd, descr, oldsel, ODA_SELECT );
1394 if (index != -1) LISTBOX_RepaintItem( hwnd, descr, index, ODA_SELECT );
1395 if (send_notify && descr->nb_items) SEND_NOTIFICATION( hwnd, descr,
1396 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1397 else
1398 if( descr->lphc ) /* set selection change flag for parent combo */
1399 descr->lphc->wState |= CBF_SELCHANGE;
1401 return LB_OKAY;
1405 /***********************************************************************
1406 * LISTBOX_MoveCaret
1408 * Change the caret position and extend the selection to the new caret.
1410 static void LISTBOX_MoveCaret( HWND hwnd, LB_DESCR *descr, INT index,
1411 BOOL fully_visible )
1413 INT oldfocus = descr->focus_item;
1415 if ((index < 0) || (index >= descr->nb_items))
1416 return;
1418 /* Important, repaint needs to be done in this order if
1419 you want to mimic Windows behavior:
1420 1. Remove the focus and paint the item
1421 2. Remove the selection and paint the item(s)
1422 3. Set the selection and repaint the item(s)
1423 4. Set the focus to 'index' and repaint the item */
1425 /* 1. remove the focus and repaint the item */
1426 descr->focus_item = -1;
1427 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1428 LISTBOX_RepaintItem( hwnd, descr, oldfocus, ODA_FOCUS );
1430 /* 2. then turn off the previous selection */
1431 /* 3. repaint the new selected item */
1432 if (descr->style & LBS_EXTENDEDSEL)
1434 if (descr->anchor_item != -1)
1436 INT first = min( index, descr->anchor_item );
1437 INT last = max( index, descr->anchor_item );
1438 if (first > 0)
1439 LISTBOX_SelectItemRange( hwnd, descr, 0, first - 1, FALSE );
1440 LISTBOX_SelectItemRange( hwnd, descr, last + 1, -1, FALSE );
1441 LISTBOX_SelectItemRange( hwnd, descr, first, last, TRUE );
1444 else if (!(descr->style & LBS_MULTIPLESEL))
1446 /* Set selection to new caret item */
1447 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
1450 /* 4. repaint the new item with the focus */
1451 descr->focus_item = index;
1452 LISTBOX_MakeItemVisible( hwnd, descr, index, fully_visible );
1453 if (descr->caret_on && (descr->in_focus))
1454 LISTBOX_RepaintItem( hwnd, descr, index, ODA_FOCUS );
1458 /***********************************************************************
1459 * LISTBOX_InsertItem
1461 static LRESULT LISTBOX_InsertItem( HWND hwnd, LB_DESCR *descr, INT index,
1462 LPWSTR str, DWORD data )
1464 LB_ITEMDATA *item;
1465 INT max_items;
1466 INT oldfocus = descr->focus_item;
1468 if (index == -1) index = descr->nb_items;
1469 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1470 if (!descr->items) max_items = 0;
1471 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1472 if (descr->nb_items == max_items)
1474 /* We need to grow the array */
1475 max_items += LB_ARRAY_GRANULARITY;
1476 if (!(item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1477 max_items * sizeof(LB_ITEMDATA) )))
1479 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1480 return LB_ERRSPACE;
1482 descr->items = item;
1485 /* Insert the item structure */
1487 item = &descr->items[index];
1488 if (index < descr->nb_items)
1489 RtlMoveMemory( item + 1, item,
1490 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1491 item->str = str;
1492 item->data = data;
1493 item->height = 0;
1494 item->selected = FALSE;
1495 descr->nb_items++;
1497 /* Get item height */
1499 if (descr->style & LBS_OWNERDRAWVARIABLE)
1501 MEASUREITEMSTRUCT mis;
1502 UINT id = GetWindowLongA( hwnd, GWL_ID );
1504 mis.CtlType = ODT_LISTBOX;
1505 mis.CtlID = id;
1506 mis.itemID = index;
1507 mis.itemData = descr->items[index].data;
1508 mis.itemHeight = descr->item_height;
1509 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1510 item->height = mis.itemHeight ? mis.itemHeight : 1;
1511 TRACE("[%04x]: measure item %d (%s) = %d\n",
1512 hwnd, index, str ? debugstr_w(str) : "", item->height );
1515 /* Repaint the items */
1517 LISTBOX_UpdateScroll( hwnd, descr );
1518 LISTBOX_InvalidateItems( hwnd, descr, index );
1520 /* Move selection and focused item */
1521 /* If listbox was empty, set focus to the first item */
1522 if (descr->nb_items == 1)
1523 LISTBOX_SetCaretIndex( hwnd, descr, 0, FALSE );
1524 /* single select don't change selection index in win31 */
1525 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1527 descr->selected_item++;
1528 LISTBOX_SetSelection( hwnd, descr, descr->selected_item-1, TRUE, FALSE );
1530 else
1532 if (index <= descr->selected_item)
1534 descr->selected_item++;
1535 descr->focus_item = oldfocus; /* focus not changed */
1538 return LB_OKAY;
1542 /***********************************************************************
1543 * LISTBOX_InsertString
1545 static LRESULT LISTBOX_InsertString( HWND hwnd, LB_DESCR *descr, INT index,
1546 LPCWSTR str )
1548 LPWSTR new_str = NULL;
1549 DWORD data = 0;
1550 LRESULT ret;
1552 if (HAS_STRINGS(descr))
1554 static const WCHAR empty_stringW[] = { 0 };
1555 if (!str) str = empty_stringW;
1556 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1558 SEND_NOTIFICATION( hwnd, descr, LBN_ERRSPACE );
1559 return LB_ERRSPACE;
1561 strcpyW(new_str, str);
1563 else data = (DWORD)str;
1565 if (index == -1) index = descr->nb_items;
1566 if ((ret = LISTBOX_InsertItem( hwnd, descr, index, new_str, data )) != 0)
1568 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1569 return ret;
1572 TRACE("[%04x]: added item %d %s\n",
1573 hwnd, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1574 return index;
1578 /***********************************************************************
1579 * LISTBOX_DeleteItem
1581 * Delete the content of an item. 'index' must be a valid index.
1583 static void LISTBOX_DeleteItem( HWND hwnd, LB_DESCR *descr, INT index )
1585 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1586 * while Win95 sends it for all items with user data.
1587 * It's probably better to send it too often than not
1588 * often enough, so this is what we do here.
1590 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1592 DELETEITEMSTRUCT dis;
1593 UINT id = GetWindowLongA( hwnd, GWL_ID );
1595 dis.CtlType = ODT_LISTBOX;
1596 dis.CtlID = id;
1597 dis.itemID = index;
1598 dis.hwndItem = hwnd;
1599 dis.itemData = descr->items[index].data;
1600 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1602 if (HAS_STRINGS(descr) && descr->items[index].str)
1603 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1607 /***********************************************************************
1608 * LISTBOX_RemoveItem
1610 * Remove an item from the listbox and delete its content.
1612 static LRESULT LISTBOX_RemoveItem( HWND hwnd, LB_DESCR *descr, INT index )
1614 LB_ITEMDATA *item;
1615 INT max_items;
1617 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1618 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1620 /* We need to invalidate the original rect instead of the updated one. */
1621 LISTBOX_InvalidateItems( hwnd, descr, index );
1623 LISTBOX_DeleteItem( hwnd, descr, index );
1625 /* Remove the item */
1627 item = &descr->items[index];
1628 if (index < descr->nb_items-1)
1629 RtlMoveMemory( item, item + 1,
1630 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1631 descr->nb_items--;
1632 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1634 /* Shrink the item array if possible */
1636 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1637 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1639 max_items -= LB_ARRAY_GRANULARITY;
1640 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1641 max_items * sizeof(LB_ITEMDATA) );
1642 if (item) descr->items = item;
1644 /* Repaint the items */
1646 LISTBOX_UpdateScroll( hwnd, descr );
1647 /* if we removed the scrollbar, reset the top of the list
1648 (correct for owner-drawn ???) */
1649 if (descr->nb_items == descr->page_size)
1650 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1652 /* Move selection and focused item */
1653 if (!IS_MULTISELECT(descr))
1655 if (index == descr->selected_item)
1656 descr->selected_item = -1;
1657 else if (index < descr->selected_item)
1659 descr->selected_item--;
1660 if (ISWIN31) /* win 31 do not change the selected item number */
1661 LISTBOX_SetSelection( hwnd, descr, descr->selected_item + 1, TRUE, FALSE);
1665 if (descr->focus_item >= descr->nb_items)
1667 descr->focus_item = descr->nb_items - 1;
1668 if (descr->focus_item < 0) descr->focus_item = 0;
1670 return LB_OKAY;
1674 /***********************************************************************
1675 * LISTBOX_ResetContent
1677 static void LISTBOX_ResetContent( HWND hwnd, LB_DESCR *descr )
1679 INT i;
1681 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( hwnd, descr, i );
1682 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1683 descr->nb_items = 0;
1684 descr->top_item = 0;
1685 descr->selected_item = -1;
1686 descr->focus_item = 0;
1687 descr->anchor_item = -1;
1688 descr->items = NULL;
1692 /***********************************************************************
1693 * LISTBOX_SetCount
1695 static LRESULT LISTBOX_SetCount( HWND hwnd, LB_DESCR *descr, INT count )
1697 LRESULT ret;
1699 if (HAS_STRINGS(descr)) return LB_ERR;
1700 /* FIXME: this is far from optimal... */
1701 if (count > descr->nb_items)
1703 while (count > descr->nb_items)
1704 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, 0 )) < 0)
1705 return ret;
1707 else if (count < descr->nb_items)
1709 while (count < descr->nb_items)
1710 if ((ret = LISTBOX_RemoveItem( hwnd, descr, -1 )) < 0)
1711 return ret;
1713 return LB_OKAY;
1717 /***********************************************************************
1718 * LISTBOX_Directory
1720 static LRESULT LISTBOX_Directory( HWND hwnd, LB_DESCR *descr, UINT attrib,
1721 LPCWSTR filespec, BOOL long_names )
1723 HANDLE handle;
1724 LRESULT ret = LB_OKAY;
1725 WIN32_FIND_DATAW entry;
1726 int pos;
1728 /* don't scan directory if we just want drives exclusively */
1729 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1730 /* scan directory */
1731 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1733 if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1735 else
1739 WCHAR buffer[270];
1740 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1742 static const WCHAR bracketW[] = { ']',0 };
1743 static const WCHAR dotW[] = { '.',0 };
1744 if (!(attrib & DDL_DIRECTORY) ||
1745 !strcmpW( entry.cAlternateFileName, dotW )) continue;
1746 buffer[0] = '[';
1747 if (long_names) strcpyW( buffer + 1, entry.cFileName );
1748 else strcpyW( buffer + 1, entry.cAlternateFileName );
1749 strcatW(buffer, bracketW);
1751 else /* not a directory */
1753 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1754 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1756 if ((attrib & DDL_EXCLUSIVE) &&
1757 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1758 continue;
1759 #undef ATTRIBS
1760 if (long_names) strcpyW( buffer, entry.cFileName );
1761 else strcpyW( buffer, entry.cAlternateFileName );
1763 if (!long_names) CharLowerW( buffer );
1764 pos = LISTBOX_FindFileStrPos( hwnd, descr, buffer );
1765 if ((ret = LISTBOX_InsertString( hwnd, descr, pos, buffer )) < 0)
1766 break;
1767 } while (FindNextFileW( handle, &entry ));
1768 FindClose( handle );
1772 /* scan drives */
1773 if ((ret >= 0) && (attrib & DDL_DRIVES))
1775 WCHAR buffer[] = {'[','-','a','-',']',0};
1776 WCHAR root[] = {'A',':','\\',0};
1777 int drive;
1778 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1780 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1781 if ((ret = LISTBOX_InsertString( hwnd, descr, -1, buffer )) < 0)
1782 break;
1785 return ret;
1789 /***********************************************************************
1790 * LISTBOX_HandleVScroll
1792 static LRESULT LISTBOX_HandleVScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1794 SCROLLINFO info;
1796 if (descr->style & LBS_MULTICOLUMN) return 0;
1797 switch(LOWORD(wParam))
1799 case SB_LINEUP:
1800 LISTBOX_SetTopItem( hwnd, descr, descr->top_item - 1, TRUE );
1801 break;
1802 case SB_LINEDOWN:
1803 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + 1, TRUE );
1804 break;
1805 case SB_PAGEUP:
1806 LISTBOX_SetTopItem( hwnd, descr, descr->top_item -
1807 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1808 break;
1809 case SB_PAGEDOWN:
1810 LISTBOX_SetTopItem( hwnd, descr, descr->top_item +
1811 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1812 break;
1813 case SB_THUMBPOSITION:
1814 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam), TRUE );
1815 break;
1816 case SB_THUMBTRACK:
1817 info.cbSize = sizeof(info);
1818 info.fMask = SIF_TRACKPOS;
1819 GetScrollInfo( hwnd, SB_VERT, &info );
1820 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos, TRUE );
1821 break;
1822 case SB_TOP:
1823 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1824 break;
1825 case SB_BOTTOM:
1826 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1827 break;
1829 return 0;
1833 /***********************************************************************
1834 * LISTBOX_HandleHScroll
1836 static LRESULT LISTBOX_HandleHScroll( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1838 SCROLLINFO info;
1839 INT page;
1841 if (descr->style & LBS_MULTICOLUMN)
1843 switch(LOWORD(wParam))
1845 case SB_LINELEFT:
1846 LISTBOX_SetTopItem( hwnd, descr, descr->top_item-descr->page_size,
1847 TRUE );
1848 break;
1849 case SB_LINERIGHT:
1850 LISTBOX_SetTopItem( hwnd, descr, descr->top_item+descr->page_size,
1851 TRUE );
1852 break;
1853 case SB_PAGELEFT:
1854 page = descr->width / descr->column_width;
1855 if (page < 1) page = 1;
1856 LISTBOX_SetTopItem( hwnd, descr,
1857 descr->top_item - page * descr->page_size, TRUE );
1858 break;
1859 case SB_PAGERIGHT:
1860 page = descr->width / descr->column_width;
1861 if (page < 1) page = 1;
1862 LISTBOX_SetTopItem( hwnd, descr,
1863 descr->top_item + page * descr->page_size, TRUE );
1864 break;
1865 case SB_THUMBPOSITION:
1866 LISTBOX_SetTopItem( hwnd, descr, HIWORD(wParam)*descr->page_size,
1867 TRUE );
1868 break;
1869 case SB_THUMBTRACK:
1870 info.cbSize = sizeof(info);
1871 info.fMask = SIF_TRACKPOS;
1872 GetScrollInfo( hwnd, SB_VERT, &info );
1873 LISTBOX_SetTopItem( hwnd, descr, info.nTrackPos*descr->page_size,
1874 TRUE );
1875 break;
1876 case SB_LEFT:
1877 LISTBOX_SetTopItem( hwnd, descr, 0, TRUE );
1878 break;
1879 case SB_RIGHT:
1880 LISTBOX_SetTopItem( hwnd, descr, descr->nb_items, TRUE );
1881 break;
1884 else if (descr->horz_extent)
1886 switch(LOWORD(wParam))
1888 case SB_LINELEFT:
1889 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos - 1 );
1890 break;
1891 case SB_LINERIGHT:
1892 LISTBOX_SetHorizontalPos( hwnd, descr, descr->horz_pos + 1 );
1893 break;
1894 case SB_PAGELEFT:
1895 LISTBOX_SetHorizontalPos( hwnd, descr,
1896 descr->horz_pos - descr->width );
1897 break;
1898 case SB_PAGERIGHT:
1899 LISTBOX_SetHorizontalPos( hwnd, descr,
1900 descr->horz_pos + descr->width );
1901 break;
1902 case SB_THUMBPOSITION:
1903 LISTBOX_SetHorizontalPos( hwnd, descr, HIWORD(wParam) );
1904 break;
1905 case SB_THUMBTRACK:
1906 info.cbSize = sizeof(info);
1907 info.fMask = SIF_TRACKPOS;
1908 GetScrollInfo( hwnd, SB_HORZ, &info );
1909 LISTBOX_SetHorizontalPos( hwnd, descr, info.nTrackPos );
1910 break;
1911 case SB_LEFT:
1912 LISTBOX_SetHorizontalPos( hwnd, descr, 0 );
1913 break;
1914 case SB_RIGHT:
1915 LISTBOX_SetHorizontalPos( hwnd, descr,
1916 descr->horz_extent - descr->width );
1917 break;
1920 return 0;
1923 static LRESULT LISTBOX_HandleMouseWheel(HWND hwnd, LB_DESCR *descr, WPARAM wParam )
1925 short gcWheelDelta = 0;
1926 UINT pulScrollLines = 3;
1928 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1930 gcWheelDelta -= (short) HIWORD(wParam);
1932 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1934 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1935 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1936 LISTBOX_SetTopItem( hwnd, descr, descr->top_item + cLineScroll, TRUE );
1938 return 0;
1941 /***********************************************************************
1942 * LISTBOX_HandleLButtonDown
1944 static LRESULT LISTBOX_HandleLButtonDown( HWND hwnd, LB_DESCR *descr,
1945 WPARAM wParam, INT x, INT y )
1947 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1948 TRACE("[%04x]: lbuttondown %d,%d item %d\n", hwnd, x, y, index );
1949 if (!descr->caret_on && (descr->in_focus)) return 0;
1951 if (!descr->in_focus)
1953 if( !descr->lphc ) SetFocus( hwnd );
1954 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1957 if (index == -1) return 0;
1959 if (descr->style & LBS_EXTENDEDSEL)
1961 /* we should perhaps make sure that all items are deselected
1962 FIXME: needed for !LBS_EXTENDEDSEL, too ?
1963 if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1964 LISTBOX_SetSelection( hwnd, descr, -1, FALSE, FALSE);
1967 if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1968 if (wParam & MK_CONTROL)
1970 LISTBOX_SetCaretIndex( hwnd, descr, index, FALSE );
1971 LISTBOX_SetSelection( hwnd, descr, index,
1972 !descr->items[index].selected,
1973 (descr->style & LBS_NOTIFY) != 0);
1975 else LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1977 else
1979 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
1980 LISTBOX_SetSelection( hwnd, descr, index,
1981 (!(descr->style & LBS_MULTIPLESEL) ||
1982 !descr->items[index].selected),
1983 (descr->style & LBS_NOTIFY) != 0 );
1986 descr->captured = TRUE;
1987 SetCapture( hwnd );
1989 if (!descr->lphc)
1991 if (descr->style & LBS_NOTIFY )
1992 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
1993 MAKELPARAM( x, y ) );
1994 if (GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
1996 POINT pt;
1998 pt.x = x;
1999 pt.y = y;
2001 if (DragDetect( hwnd, pt ))
2002 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2005 return 0;
2009 /*************************************************************************
2010 * LISTBOX_HandleLButtonDownCombo [Internal]
2012 * Process LButtonDown message for the ComboListBox
2014 nn * PARAMS
2015 * pWnd [I] The windows internal structure
2016 * pDescr [I] The ListBox internal structure
2017 * wParam [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2018 * x [I] X Mouse Coordinate
2019 * y [I] Y Mouse Coordinate
2021 * RETURNS
2022 * 0 since we are processing the WM_LBUTTONDOWN Message
2024 * NOTES
2025 * This function is only to be used when a ListBox is a ComboListBox
2028 static LRESULT LISTBOX_HandleLButtonDownCombo( HWND hwnd, LB_DESCR *pDescr,
2029 UINT msg, WPARAM wParam, INT x, INT y)
2031 RECT clientRect, screenRect;
2032 POINT mousePos;
2034 mousePos.x = x;
2035 mousePos.y = y;
2037 GetClientRect(hwnd, &clientRect);
2039 if(PtInRect(&clientRect, mousePos))
2041 /* MousePos is in client, resume normal processing */
2042 if (msg == WM_LBUTTONDOWN)
2044 pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
2045 return LISTBOX_HandleLButtonDown( hwnd, pDescr, wParam, x, y);
2047 else if (pDescr->style & LBS_NOTIFY)
2048 SEND_NOTIFICATION( hwnd, pDescr, LBN_DBLCLK );
2049 return 0;
2051 else
2053 POINT screenMousePos;
2054 HWND hWndOldCapture;
2056 /* Check the Non-Client Area */
2057 screenMousePos = mousePos;
2058 hWndOldCapture = GetCapture();
2059 ReleaseCapture();
2060 GetWindowRect(hwnd, &screenRect);
2061 ClientToScreen(hwnd, &screenMousePos);
2063 if(!PtInRect(&screenRect, screenMousePos))
2065 LISTBOX_SetCaretIndex( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE );
2066 LISTBOX_SetSelection( hwnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
2067 COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
2068 return 0;
2070 else
2072 /* Check to see the NC is a scrollbar */
2073 INT nHitTestType=0;
2074 LONG style = GetWindowLongA( hwnd, GWL_STYLE );
2075 /* Check Vertical scroll bar */
2076 if (style & WS_VSCROLL)
2078 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2079 if (PtInRect( &clientRect, mousePos ))
2081 nHitTestType = HTVSCROLL;
2084 /* Check horizontal scroll bar */
2085 if (style & WS_HSCROLL)
2087 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2088 if (PtInRect( &clientRect, mousePos ))
2090 nHitTestType = HTHSCROLL;
2093 /* Windows sends this message when a scrollbar is clicked
2096 if(nHitTestType != 0)
2098 SendMessageW(hwnd, WM_NCLBUTTONDOWN, nHitTestType,
2099 MAKELONG(screenMousePos.x, screenMousePos.y));
2101 /* Resume the Capture after scrolling is complete
2103 if(hWndOldCapture != 0)
2105 SetCapture(hWndOldCapture);
2109 return 0;
2112 /***********************************************************************
2113 * LISTBOX_HandleLButtonUp
2115 static LRESULT LISTBOX_HandleLButtonUp( HWND hwnd, LB_DESCR *descr )
2117 if (LISTBOX_Timer != LB_TIMER_NONE)
2118 KillSystemTimer( hwnd, LB_TIMER_ID );
2119 LISTBOX_Timer = LB_TIMER_NONE;
2120 if (descr->captured)
2122 descr->captured = FALSE;
2123 if (GetCapture() == hwnd) ReleaseCapture();
2124 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2125 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2127 return 0;
2131 /***********************************************************************
2132 * LISTBOX_HandleTimer
2134 * Handle scrolling upon a timer event.
2135 * Return TRUE if scrolling should continue.
2137 static LRESULT LISTBOX_HandleTimer( HWND hwnd, LB_DESCR *descr,
2138 INT index, TIMER_DIRECTION dir )
2140 switch(dir)
2142 case LB_TIMER_UP:
2143 if (descr->top_item) index = descr->top_item - 1;
2144 else index = 0;
2145 break;
2146 case LB_TIMER_LEFT:
2147 if (descr->top_item) index -= descr->page_size;
2148 break;
2149 case LB_TIMER_DOWN:
2150 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2151 if (index == descr->focus_item) index++;
2152 if (index >= descr->nb_items) index = descr->nb_items - 1;
2153 break;
2154 case LB_TIMER_RIGHT:
2155 if (index + descr->page_size < descr->nb_items)
2156 index += descr->page_size;
2157 break;
2158 case LB_TIMER_NONE:
2159 break;
2161 if (index == descr->focus_item) return FALSE;
2162 LISTBOX_MoveCaret( hwnd, descr, index, FALSE );
2163 return TRUE;
2167 /***********************************************************************
2168 * LISTBOX_HandleSystemTimer
2170 * WM_SYSTIMER handler.
2172 static LRESULT LISTBOX_HandleSystemTimer( HWND hwnd, LB_DESCR *descr )
2174 if (!LISTBOX_HandleTimer( hwnd, descr, descr->focus_item, LISTBOX_Timer ))
2176 KillSystemTimer( hwnd, LB_TIMER_ID );
2177 LISTBOX_Timer = LB_TIMER_NONE;
2179 return 0;
2183 /***********************************************************************
2184 * LISTBOX_HandleMouseMove
2186 * WM_MOUSEMOVE handler.
2188 static void LISTBOX_HandleMouseMove( HWND hwnd, LB_DESCR *descr,
2189 INT x, INT y )
2191 INT index;
2192 TIMER_DIRECTION dir = LB_TIMER_NONE;
2194 if (!descr->captured) return;
2196 if (descr->style & LBS_MULTICOLUMN)
2198 if (y < 0) y = 0;
2199 else if (y >= descr->item_height * descr->page_size)
2200 y = descr->item_height * descr->page_size - 1;
2202 if (x < 0)
2204 dir = LB_TIMER_LEFT;
2205 x = 0;
2207 else if (x >= descr->width)
2209 dir = LB_TIMER_RIGHT;
2210 x = descr->width - 1;
2213 else
2215 if (y < 0) dir = LB_TIMER_UP; /* above */
2216 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2219 index = LISTBOX_GetItemFromPoint( descr, x, y );
2220 if (index == -1) index = descr->focus_item;
2221 if (!LISTBOX_HandleTimer( hwnd, descr, index, dir )) dir = LB_TIMER_NONE;
2223 /* Start/stop the system timer */
2225 if (dir != LB_TIMER_NONE)
2226 SetSystemTimer( hwnd, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2227 else if (LISTBOX_Timer != LB_TIMER_NONE)
2228 KillSystemTimer( hwnd, LB_TIMER_ID );
2229 LISTBOX_Timer = dir;
2233 /***********************************************************************
2234 * LISTBOX_HandleKeyDown
2236 static LRESULT LISTBOX_HandleKeyDown( HWND hwnd, LB_DESCR *descr, WPARAM wParam )
2238 INT caret = -1;
2239 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2240 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2241 bForceSelection = FALSE; /* only for single select list */
2243 if (descr->style & LBS_WANTKEYBOARDINPUT)
2245 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2246 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2247 hwnd );
2248 if (caret == -2) return 0;
2250 if (caret == -1) switch(wParam)
2252 case VK_LEFT:
2253 if (descr->style & LBS_MULTICOLUMN)
2255 bForceSelection = FALSE;
2256 if (descr->focus_item >= descr->page_size)
2257 caret = descr->focus_item - descr->page_size;
2258 break;
2260 /* fall through */
2261 case VK_UP:
2262 caret = descr->focus_item - 1;
2263 if (caret < 0) caret = 0;
2264 break;
2265 case VK_RIGHT:
2266 if (descr->style & LBS_MULTICOLUMN)
2268 bForceSelection = FALSE;
2269 if (descr->focus_item + descr->page_size < descr->nb_items)
2270 caret = descr->focus_item + descr->page_size;
2271 break;
2273 /* fall through */
2274 case VK_DOWN:
2275 caret = descr->focus_item + 1;
2276 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2277 break;
2279 case VK_PRIOR:
2280 if (descr->style & LBS_MULTICOLUMN)
2282 INT page = descr->width / descr->column_width;
2283 if (page < 1) page = 1;
2284 caret = descr->focus_item - (page * descr->page_size) + 1;
2286 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2287 if (caret < 0) caret = 0;
2288 break;
2289 case VK_NEXT:
2290 if (descr->style & LBS_MULTICOLUMN)
2292 INT page = descr->width / descr->column_width;
2293 if (page < 1) page = 1;
2294 caret = descr->focus_item + (page * descr->page_size) - 1;
2296 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2297 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2298 break;
2299 case VK_HOME:
2300 caret = 0;
2301 break;
2302 case VK_END:
2303 caret = descr->nb_items - 1;
2304 break;
2305 case VK_SPACE:
2306 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2307 else if (descr->style & LBS_MULTIPLESEL)
2309 LISTBOX_SetSelection( hwnd, descr, descr->focus_item,
2310 !descr->items[descr->focus_item].selected,
2311 (descr->style & LBS_NOTIFY) != 0 );
2313 break;
2314 default:
2315 bForceSelection = FALSE;
2317 if (bForceSelection) /* focused item is used instead of key */
2318 caret = descr->focus_item;
2319 if (caret >= 0)
2321 if ((descr->style & LBS_EXTENDEDSEL) &&
2322 !(GetKeyState( VK_SHIFT ) & 0x8000))
2323 descr->anchor_item = caret;
2324 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2325 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2326 if (descr->style & LBS_NOTIFY)
2328 if( descr->lphc )
2330 /* make sure that combo parent doesn't hide us */
2331 descr->lphc->wState |= CBF_NOROLLUP;
2333 if (descr->nb_items) SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2336 return 0;
2340 /***********************************************************************
2341 * LISTBOX_HandleChar
2343 static LRESULT LISTBOX_HandleChar( HWND hwnd, LB_DESCR *descr, WCHAR charW )
2345 INT caret = -1;
2346 WCHAR str[2];
2348 str[0] = charW;
2349 str[1] = '\0';
2351 if (descr->style & LBS_WANTKEYBOARDINPUT)
2353 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2354 MAKEWPARAM(charW, descr->focus_item),
2355 (LPARAM)hwnd );
2356 if (caret == -2) return 0;
2358 if (caret == -1)
2359 caret = LISTBOX_FindString( hwnd, descr, descr->focus_item, str, FALSE);
2360 if (caret != -1)
2362 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2363 LISTBOX_SetSelection( hwnd, descr, caret, TRUE, FALSE);
2364 LISTBOX_MoveCaret( hwnd, descr, caret, TRUE );
2365 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2366 SEND_NOTIFICATION( hwnd, descr, LBN_SELCHANGE );
2368 return 0;
2372 /***********************************************************************
2373 * LISTBOX_Create
2375 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2377 LB_DESCR *descr;
2378 MEASUREITEMSTRUCT mis;
2379 RECT rect;
2381 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2382 return FALSE;
2384 GetClientRect( hwnd, &rect );
2385 descr->owner = GetParent( hwnd );
2386 descr->style = GetWindowLongA( hwnd, GWL_STYLE );
2387 descr->width = rect.right - rect.left;
2388 descr->height = rect.bottom - rect.top;
2389 descr->items = NULL;
2390 descr->nb_items = 0;
2391 descr->top_item = 0;
2392 descr->selected_item = -1;
2393 descr->focus_item = 0;
2394 descr->anchor_item = -1;
2395 descr->item_height = 1;
2396 descr->page_size = 1;
2397 descr->column_width = 150;
2398 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2399 descr->horz_pos = 0;
2400 descr->nb_tabs = 0;
2401 descr->tabs = NULL;
2402 descr->caret_on = lphc ? FALSE : TRUE;
2403 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2404 descr->in_focus = FALSE;
2405 descr->captured = FALSE;
2406 descr->font = 0;
2407 descr->locale = 0; /* FIXME */
2408 descr->lphc = lphc;
2410 if (is_old_app(hwnd) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2412 /* Win95 document "List Box Differences" from MSDN:
2413 If a list box in a version 3.x application has either the
2414 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2415 horizontal and vertical scroll bars.
2417 descr->style |= WS_VSCROLL | WS_HSCROLL;
2420 if( lphc )
2422 TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2423 hwnd, descr->owner, lphc->self );
2424 descr->owner = lphc->self;
2427 SetWindowLongA( hwnd, 0, (LONG)descr );
2429 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2431 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2432 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2433 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2434 descr->item_height = LISTBOX_SetFont( hwnd, descr, 0 );
2436 if (descr->style & LBS_OWNERDRAWFIXED)
2438 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2440 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2441 descr->item_height = lphc->fixedOwnerDrawHeight;
2443 else
2445 UINT id = GetWindowLongA( hwnd, GWL_ID );
2446 mis.CtlType = ODT_LISTBOX;
2447 mis.CtlID = id;
2448 mis.itemID = -1;
2449 mis.itemWidth = 0;
2450 mis.itemData = 0;
2451 mis.itemHeight = descr->item_height;
2452 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2453 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2457 TRACE("owner: %04x, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2458 return TRUE;
2462 /***********************************************************************
2463 * LISTBOX_Destroy
2465 static BOOL LISTBOX_Destroy( HWND hwnd, LB_DESCR *descr )
2467 LISTBOX_ResetContent( hwnd, descr );
2468 SetWindowLongA( hwnd, 0, 0 );
2469 HeapFree( GetProcessHeap(), 0, descr );
2470 return TRUE;
2474 /***********************************************************************
2475 * ListBoxWndProc_common
2477 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2478 WPARAM wParam, LPARAM lParam, BOOL unicode )
2480 LRESULT ret;
2481 LB_DESCR *descr;
2483 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
2485 if (msg == WM_CREATE)
2487 if (!LISTBOX_Create( hwnd, NULL ))
2488 return -1;
2489 TRACE("creating wnd=%04x descr=%lx\n", hwnd, GetWindowLongA( hwnd, 0 ) );
2490 return 0;
2492 /* Ignore all other messages before we get a WM_CREATE */
2493 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2494 DefWindowProcA( hwnd, msg, wParam, lParam );
2497 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2498 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
2499 switch(msg)
2501 case LB_RESETCONTENT16:
2502 case LB_RESETCONTENT:
2503 LISTBOX_ResetContent( hwnd, descr );
2504 LISTBOX_UpdateScroll( hwnd, descr );
2505 InvalidateRect( hwnd, NULL, TRUE );
2506 return 0;
2508 case LB_ADDSTRING16:
2509 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2510 /* fall through */
2511 case LB_ADDSTRING:
2513 INT ret;
2514 LPWSTR textW;
2515 if(unicode || !HAS_STRINGS(descr))
2516 textW = (LPWSTR)lParam;
2517 else
2519 LPSTR textA = (LPSTR)lParam;
2520 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2521 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2522 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2524 wParam = LISTBOX_FindStringPos( hwnd, descr, textW, FALSE );
2525 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2526 if (!unicode && HAS_STRINGS(descr))
2527 HeapFree(GetProcessHeap(), 0, textW);
2528 return ret;
2531 case LB_INSERTSTRING16:
2532 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2533 wParam = (INT)(INT16)wParam;
2534 /* fall through */
2535 case LB_INSERTSTRING:
2537 INT ret;
2538 LPWSTR textW;
2539 if(unicode || !HAS_STRINGS(descr))
2540 textW = (LPWSTR)lParam;
2541 else
2543 LPSTR textA = (LPSTR)lParam;
2544 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2545 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2546 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2548 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2549 if(!unicode && HAS_STRINGS(descr))
2550 HeapFree(GetProcessHeap(), 0, textW);
2551 return ret;
2554 case LB_ADDFILE16:
2555 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2556 /* fall through */
2557 case LB_ADDFILE:
2559 INT ret;
2560 LPWSTR textW;
2561 if(unicode || !HAS_STRINGS(descr))
2562 textW = (LPWSTR)lParam;
2563 else
2565 LPSTR textA = (LPSTR)lParam;
2566 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2567 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2568 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2570 wParam = LISTBOX_FindFileStrPos( hwnd, descr, textW );
2571 ret = LISTBOX_InsertString( hwnd, descr, wParam, textW );
2572 if(!unicode && HAS_STRINGS(descr))
2573 HeapFree(GetProcessHeap(), 0, textW);
2574 return ret;
2577 case LB_DELETESTRING16:
2578 case LB_DELETESTRING:
2579 if (LISTBOX_RemoveItem( hwnd, descr, wParam) != LB_ERR)
2580 return descr->nb_items;
2581 else
2582 return LB_ERR;
2584 case LB_GETITEMDATA16:
2585 case LB_GETITEMDATA:
2586 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2587 return LB_ERR;
2588 return descr->items[wParam].data;
2590 case LB_SETITEMDATA16:
2591 case LB_SETITEMDATA:
2592 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2593 return LB_ERR;
2594 descr->items[wParam].data = (DWORD)lParam;
2595 return LB_OKAY;
2597 case LB_GETCOUNT16:
2598 case LB_GETCOUNT:
2599 return descr->nb_items;
2601 case LB_GETTEXT16:
2602 lParam = (LPARAM)MapSL(lParam);
2603 /* fall through */
2604 case LB_GETTEXT:
2605 return LISTBOX_GetText( descr, wParam, lParam, unicode );
2607 case LB_GETTEXTLEN16:
2608 /* fall through */
2609 case LB_GETTEXTLEN:
2610 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2611 return LB_ERR;
2612 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2613 if (unicode) return strlenW( descr->items[wParam].str );
2614 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2615 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2617 case LB_GETCURSEL16:
2618 case LB_GETCURSEL:
2619 if (descr->nb_items==0)
2620 return LB_ERR;
2621 if (!IS_MULTISELECT(descr))
2622 return descr->selected_item;
2623 /* else */
2624 if (descr->selected_item!=-1)
2625 return descr->selected_item;
2626 /* else */
2627 return descr->focus_item;
2628 /* otherwise, if the user tries to move the selection with the */
2629 /* arrow keys, we will give the application something to choke on */
2630 case LB_GETTOPINDEX16:
2631 case LB_GETTOPINDEX:
2632 return descr->top_item;
2634 case LB_GETITEMHEIGHT16:
2635 case LB_GETITEMHEIGHT:
2636 return LISTBOX_GetItemHeight( descr, wParam );
2638 case LB_SETITEMHEIGHT16:
2639 lParam = LOWORD(lParam);
2640 /* fall through */
2641 case LB_SETITEMHEIGHT:
2642 return LISTBOX_SetItemHeight( hwnd, descr, wParam, lParam, TRUE );
2644 case LB_ITEMFROMPOINT:
2646 POINT pt;
2647 RECT rect;
2649 pt.x = LOWORD(lParam);
2650 pt.y = HIWORD(lParam);
2651 rect.left = 0;
2652 rect.top = 0;
2653 rect.right = descr->width;
2654 rect.bottom = descr->height;
2656 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2657 !PtInRect( &rect, pt ) );
2660 case LB_SETCARETINDEX16:
2661 case LB_SETCARETINDEX:
2662 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2663 if (LISTBOX_SetCaretIndex( hwnd, descr, wParam, !lParam ) == LB_ERR)
2664 return LB_ERR;
2665 else if (ISWIN31)
2666 return wParam;
2667 else
2668 return LB_OKAY;
2670 case LB_GETCARETINDEX16:
2671 case LB_GETCARETINDEX:
2672 return descr->focus_item;
2674 case LB_SETTOPINDEX16:
2675 case LB_SETTOPINDEX:
2676 return LISTBOX_SetTopItem( hwnd, descr, wParam, TRUE );
2678 case LB_SETCOLUMNWIDTH16:
2679 case LB_SETCOLUMNWIDTH:
2680 return LISTBOX_SetColumnWidth( hwnd, descr, wParam );
2682 case LB_GETITEMRECT16:
2684 RECT rect;
2685 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2686 CONV_RECT32TO16( &rect, MapSL(lParam) );
2688 return ret;
2690 case LB_GETITEMRECT:
2691 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2693 case LB_FINDSTRING16:
2694 wParam = (INT)(INT16)wParam;
2695 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2696 /* fall through */
2697 case LB_FINDSTRING:
2699 INT ret;
2700 LPWSTR textW;
2701 if(unicode || !HAS_STRINGS(descr))
2702 textW = (LPWSTR)lParam;
2703 else
2705 LPSTR textA = (LPSTR)lParam;
2706 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2707 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2708 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2710 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2711 if(!unicode && HAS_STRINGS(descr))
2712 HeapFree(GetProcessHeap(), 0, textW);
2713 return ret;
2716 case LB_FINDSTRINGEXACT16:
2717 wParam = (INT)(INT16)wParam;
2718 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2719 /* fall through */
2720 case LB_FINDSTRINGEXACT:
2722 INT ret;
2723 LPWSTR textW;
2724 if(unicode || !HAS_STRINGS(descr))
2725 textW = (LPWSTR)lParam;
2726 else
2728 LPSTR textA = (LPSTR)lParam;
2729 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2730 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2731 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2733 ret = LISTBOX_FindString( hwnd, descr, wParam, textW, TRUE );
2734 if(!unicode && HAS_STRINGS(descr))
2735 HeapFree(GetProcessHeap(), 0, textW);
2736 return ret;
2739 case LB_SELECTSTRING16:
2740 wParam = (INT)(INT16)wParam;
2741 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2742 /* fall through */
2743 case LB_SELECTSTRING:
2745 INT index;
2746 LPWSTR textW;
2748 if(HAS_STRINGS(descr))
2749 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2750 debugstr_a((LPSTR)lParam));
2751 if(unicode || !HAS_STRINGS(descr))
2752 textW = (LPWSTR)lParam;
2753 else
2755 LPSTR textA = (LPSTR)lParam;
2756 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2757 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2758 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2760 index = LISTBOX_FindString( hwnd, descr, wParam, textW, FALSE );
2761 if(!unicode && HAS_STRINGS(descr))
2762 HeapFree(GetProcessHeap(), 0, textW);
2763 if (index != LB_ERR)
2765 LISTBOX_SetCaretIndex( hwnd, descr, index, TRUE );
2766 LISTBOX_SetSelection( hwnd, descr, index, TRUE, FALSE );
2768 return index;
2771 case LB_GETSEL16:
2772 wParam = (INT)(INT16)wParam;
2773 /* fall through */
2774 case LB_GETSEL:
2775 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2776 return LB_ERR;
2777 return descr->items[wParam].selected;
2779 case LB_SETSEL16:
2780 lParam = (INT)(INT16)lParam;
2781 /* fall through */
2782 case LB_SETSEL:
2783 return LISTBOX_SetSelection( hwnd, descr, lParam, wParam, FALSE );
2785 case LB_SETCURSEL16:
2786 wParam = (INT)(INT16)wParam;
2787 /* fall through */
2788 case LB_SETCURSEL:
2789 if (IS_MULTISELECT(descr)) return LB_ERR;
2790 LISTBOX_SetCaretIndex( hwnd, descr, wParam, TRUE );
2791 return LISTBOX_SetSelection( hwnd, descr, wParam, TRUE, FALSE );
2793 case LB_GETSELCOUNT16:
2794 case LB_GETSELCOUNT:
2795 return LISTBOX_GetSelCount( descr );
2797 case LB_GETSELITEMS16:
2798 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2800 case LB_GETSELITEMS:
2801 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2803 case LB_SELITEMRANGE16:
2804 case LB_SELITEMRANGE:
2805 if (LOWORD(lParam) <= HIWORD(lParam))
2806 return LISTBOX_SelectItemRange( hwnd, descr, LOWORD(lParam),
2807 HIWORD(lParam), wParam );
2808 else
2809 return LISTBOX_SelectItemRange( hwnd, descr, HIWORD(lParam),
2810 LOWORD(lParam), wParam );
2812 case LB_SELITEMRANGEEX16:
2813 case LB_SELITEMRANGEEX:
2814 if ((INT)lParam >= (INT)wParam)
2815 return LISTBOX_SelectItemRange( hwnd, descr, wParam, lParam, TRUE );
2816 else
2817 return LISTBOX_SelectItemRange( hwnd, descr, lParam, wParam, FALSE);
2819 case LB_GETHORIZONTALEXTENT16:
2820 case LB_GETHORIZONTALEXTENT:
2821 return descr->horz_extent;
2823 case LB_SETHORIZONTALEXTENT16:
2824 case LB_SETHORIZONTALEXTENT:
2825 return LISTBOX_SetHorizontalExtent( hwnd, descr, wParam );
2827 case LB_GETANCHORINDEX16:
2828 case LB_GETANCHORINDEX:
2829 return descr->anchor_item;
2831 case LB_SETANCHORINDEX16:
2832 wParam = (INT)(INT16)wParam;
2833 /* fall through */
2834 case LB_SETANCHORINDEX:
2835 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2836 return LB_ERR;
2837 descr->anchor_item = (INT)wParam;
2838 return LB_OKAY;
2840 case LB_DIR16:
2841 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2842 * be set automatically (this is different in Win32) */
2843 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2844 lParam = (LPARAM)MapSL(lParam);
2845 /* fall through */
2846 case LB_DIR:
2848 INT ret;
2849 LPWSTR textW;
2850 if(unicode)
2851 textW = (LPWSTR)lParam;
2852 else
2854 LPSTR textA = (LPSTR)lParam;
2855 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2856 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2857 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2859 ret = LISTBOX_Directory( hwnd, descr, wParam, textW, msg == LB_DIR );
2860 if(!unicode)
2861 HeapFree(GetProcessHeap(), 0, textW);
2862 return ret;
2865 case LB_GETLOCALE:
2866 return descr->locale;
2868 case LB_SETLOCALE:
2869 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2870 return LB_OKAY;
2872 case LB_INITSTORAGE:
2873 return LISTBOX_InitStorage( hwnd, descr, wParam );
2875 case LB_SETCOUNT:
2876 return LISTBOX_SetCount( hwnd, descr, (INT)wParam );
2878 case LB_SETTABSTOPS16:
2879 return LISTBOX_SetTabStops( hwnd, descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2881 case LB_SETTABSTOPS:
2882 return LISTBOX_SetTabStops( hwnd, descr, wParam, (LPINT)lParam, FALSE );
2884 case LB_CARETON16:
2885 case LB_CARETON:
2886 if (descr->caret_on)
2887 return LB_OKAY;
2888 descr->caret_on = TRUE;
2889 if ((descr->focus_item != -1) && (descr->in_focus))
2890 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2891 return LB_OKAY;
2893 case LB_CARETOFF16:
2894 case LB_CARETOFF:
2895 if (!descr->caret_on)
2896 return LB_OKAY;
2897 descr->caret_on = FALSE;
2898 if ((descr->focus_item != -1) && (descr->in_focus))
2899 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2900 return LB_OKAY;
2902 case WM_DESTROY:
2903 return LISTBOX_Destroy( hwnd, descr );
2905 case WM_ENABLE:
2906 InvalidateRect( hwnd, NULL, TRUE );
2907 return 0;
2909 case WM_SETREDRAW:
2910 LISTBOX_SetRedraw( hwnd, descr, wParam != 0 );
2911 return 0;
2913 case WM_GETDLGCODE:
2914 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2916 case WM_PAINT:
2918 PAINTSTRUCT ps;
2919 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( hwnd, &ps );
2920 ret = LISTBOX_Paint( hwnd, descr, hdc );
2921 if( !wParam ) EndPaint( hwnd, &ps );
2923 return ret;
2924 case WM_SIZE:
2925 LISTBOX_UpdateSize( hwnd, descr );
2926 return 0;
2927 case WM_GETFONT:
2928 return descr->font;
2929 case WM_SETFONT:
2930 LISTBOX_SetFont( hwnd, descr, (HFONT)wParam );
2931 if (lParam) InvalidateRect( hwnd, 0, TRUE );
2932 return 0;
2933 case WM_SETFOCUS:
2934 descr->in_focus = TRUE;
2935 descr->caret_on = TRUE;
2936 if (descr->focus_item != -1)
2937 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2938 SEND_NOTIFICATION( hwnd, descr, LBN_SETFOCUS );
2939 return 0;
2940 case WM_KILLFOCUS:
2941 descr->in_focus = FALSE;
2942 if ((descr->focus_item != -1) && descr->caret_on)
2943 LISTBOX_RepaintItem( hwnd, descr, descr->focus_item, ODA_FOCUS );
2944 SEND_NOTIFICATION( hwnd, descr, LBN_KILLFOCUS );
2945 return 0;
2946 case WM_HSCROLL:
2947 return LISTBOX_HandleHScroll( hwnd, descr, wParam );
2948 case WM_VSCROLL:
2949 return LISTBOX_HandleVScroll( hwnd, descr, wParam );
2950 case WM_MOUSEWHEEL:
2951 if (wParam & (MK_SHIFT | MK_CONTROL))
2952 return DefWindowProcW( hwnd, msg, wParam, lParam );
2953 return LISTBOX_HandleMouseWheel( hwnd, descr, wParam );
2954 case WM_LBUTTONDOWN:
2955 return LISTBOX_HandleLButtonDown( hwnd, descr, wParam,
2956 (INT16)LOWORD(lParam),
2957 (INT16)HIWORD(lParam) );
2958 case WM_LBUTTONDBLCLK:
2959 if (descr->style & LBS_NOTIFY)
2960 SEND_NOTIFICATION( hwnd, descr, LBN_DBLCLK );
2961 return 0;
2962 case WM_MOUSEMOVE:
2963 if (GetCapture() == hwnd)
2964 LISTBOX_HandleMouseMove( hwnd, descr, (INT16)LOWORD(lParam),
2965 (INT16)HIWORD(lParam) );
2966 return 0;
2967 case WM_LBUTTONUP:
2968 return LISTBOX_HandleLButtonUp( hwnd, descr );
2969 case WM_KEYDOWN:
2970 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
2971 case WM_CHAR:
2973 WCHAR charW;
2974 if(unicode)
2975 charW = (WCHAR)wParam;
2976 else
2978 CHAR charA = (CHAR)wParam;
2979 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
2981 return LISTBOX_HandleChar( hwnd, descr, charW );
2983 case WM_SYSTIMER:
2984 return LISTBOX_HandleSystemTimer( hwnd, descr );
2985 case WM_ERASEBKGND:
2986 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2988 RECT rect;
2989 HBRUSH hbrush = SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
2990 wParam, (LPARAM)hwnd );
2991 TRACE("hbrush = %04x\n", hbrush);
2992 if(!hbrush)
2993 hbrush = GetSysColorBrush(COLOR_WINDOW);
2994 if(hbrush)
2996 GetClientRect(hwnd, &rect);
2997 FillRect((HDC)wParam, &rect, hbrush);
3000 return 1;
3001 case WM_DROPFILES:
3002 if( !descr->lphc )
3003 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3004 SendMessageA( descr->owner, msg, wParam, lParam );
3005 break;
3007 case WM_DROPOBJECT:
3008 case WM_QUERYDROPOBJECT:
3009 case WM_DRAGSELECT:
3010 case WM_DRAGMOVE:
3011 if( !descr->lphc )
3013 LPDRAGINFO16 dragInfo = MapSL( lParam );
3014 dragInfo->l = LISTBOX_GetItemFromPoint( descr, dragInfo->pt.x,
3015 dragInfo->pt.y );
3016 return SendMessage16( HWND_16(descr->owner), msg, wParam, lParam );
3018 break;
3020 default:
3021 if ((msg >= WM_USER) && (msg < 0xc000))
3022 WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
3023 hwnd, msg, wParam, lParam );
3024 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3025 DefWindowProcA( hwnd, msg, wParam, lParam );
3027 return 0;
3030 /***********************************************************************
3031 * ListBoxWndProcA
3033 * This is just a wrapper for the real wndproc, it only does window locking
3034 * and unlocking.
3036 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3038 if (!IsWindow(hwnd)) return 0;
3039 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3042 /***********************************************************************
3043 * ListBoxWndProcW
3045 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3047 if (!IsWindow(hwnd)) return 0;
3048 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );
3051 /***********************************************************************
3052 * ComboLBWndProc_common
3054 * The real combo listbox wndproc
3056 static LRESULT WINAPI ComboLBWndProc_common( HWND hwnd, UINT msg,
3057 WPARAM wParam, LPARAM lParam, BOOL unicode )
3059 LRESULT lRet = 0;
3060 LB_DESCR *descr;
3061 LPHEADCOMBO lphc;
3063 if (!(descr = (LB_DESCR *)GetWindowLongA( hwnd, 0 )))
3065 if (msg == WM_CREATE)
3067 CREATESTRUCTA *lpcs = (CREATESTRUCTA *)lParam;
3068 TRACE_(combo)("\tpassed parent handle = %p\n",lpcs->lpCreateParams);
3069 lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
3070 return LISTBOX_Create( hwnd, lphc );
3072 /* Ignore all other messages before we get a WM_CREATE */
3073 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3074 DefWindowProcA( hwnd, msg, wParam, lParam );
3077 TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
3078 hwnd, SPY_GetMsgName(msg, hwnd), wParam, lParam );
3080 if ((lphc = descr->lphc) != NULL)
3082 switch( msg )
3084 case WM_MOUSEMOVE:
3085 if ( (TWEAK_WineLook > WIN31_LOOK) &&
3086 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
3088 POINT mousePos;
3089 BOOL captured;
3090 RECT clientRect;
3092 mousePos.x = (INT16)LOWORD(lParam);
3093 mousePos.y = (INT16)HIWORD(lParam);
3096 * If we are in a dropdown combobox, we simulate that
3097 * the mouse is captured to show the tracking of the item.
3099 GetClientRect(hwnd, &clientRect);
3101 if (PtInRect( &clientRect, mousePos ))
3103 captured = descr->captured;
3104 descr->captured = TRUE;
3106 LISTBOX_HandleMouseMove( hwnd, descr,
3107 mousePos.x, mousePos.y);
3109 descr->captured = captured;
3112 else
3114 LISTBOX_HandleMouseMove( hwnd, descr,
3115 mousePos.x, mousePos.y);
3118 return 0;
3121 /* else we are in Win3.1 look, go with the default behavior. */
3122 break;
3124 case WM_LBUTTONUP:
3125 if (TWEAK_WineLook > WIN31_LOOK)
3127 POINT mousePos;
3128 RECT clientRect;
3131 * If the mouse button "up" is not in the listbox,
3132 * we make sure there is no selection by re-selecting the
3133 * item that was selected when the listbox was made visible.
3135 mousePos.x = (INT16)LOWORD(lParam);
3136 mousePos.y = (INT16)HIWORD(lParam);
3138 GetClientRect(hwnd, &clientRect);
3141 * When the user clicks outside the combobox and the focus
3142 * is lost, the owning combobox will send a fake buttonup with
3143 * 0xFFFFFFF as the mouse location, we must also revert the
3144 * selection to the original selection.
3146 if ( (lParam == (LPARAM)-1) ||
3147 (!PtInRect( &clientRect, mousePos )) )
3149 LISTBOX_MoveCaret( hwnd, descr, lphc->droppedIndex, FALSE );
3152 return LISTBOX_HandleLButtonUp( hwnd, descr );
3153 case WM_LBUTTONDBLCLK:
3154 case WM_LBUTTONDOWN:
3155 return LISTBOX_HandleLButtonDownCombo(hwnd, descr, msg, wParam,
3156 (INT16)LOWORD(lParam),
3157 (INT16)HIWORD(lParam) );
3158 case WM_NCACTIVATE:
3159 return FALSE;
3160 case WM_KEYDOWN:
3161 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3163 /* for some reason(?) Windows makes it possible to
3164 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3166 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3167 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3168 && (wParam == VK_DOWN || wParam == VK_UP)) )
3170 COMBO_FlipListbox( lphc, FALSE, FALSE );
3171 return 0;
3174 return LISTBOX_HandleKeyDown( hwnd, descr, wParam );
3176 case LB_SETCURSEL16:
3177 case LB_SETCURSEL:
3178 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3179 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3180 lRet =(lRet == LB_ERR) ? lRet : descr->selected_item;
3181 return lRet;
3182 case WM_NCDESTROY:
3183 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3184 lphc->hWndLBox = 0;
3185 break;
3189 /* default handling: call listbox wnd proc */
3190 lRet = unicode ? ListBoxWndProcW( hwnd, msg, wParam, lParam ) :
3191 ListBoxWndProcA( hwnd, msg, wParam, lParam );
3193 TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3195 return lRet;
3198 /***********************************************************************
3199 * ComboLBWndProcA
3201 * NOTE: in Windows, winproc address of the ComboLBox is the same
3202 * as that of the Listbox.
3204 LRESULT WINAPI ComboLBWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3206 if (!IsWindow(hwnd)) return 0;
3207 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3210 /***********************************************************************
3211 * ComboLBWndProcW
3213 LRESULT WINAPI ComboLBWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3215 if (!IsWindow(hwnd)) return 0;
3216 return ComboLBWndProc_common( hwnd, msg, wParam, lParam, TRUE );