Avoid using the MAKEPOINTS macro, it's broken on big endian.
[wine/wine-kai.git] / dlls / user / listbox.c
blob84b2e05e3578cc05ec8952a57a192720617e137f
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
20 * NOTES
22 * This code was audited for completeness against the documented features
23 * of Comctl32.dll version 6.0 on Oct. 9, 2004, by Dimitrie O. Paun.
25 * Unless otherwise noted, we believe this code to be complete, as per
26 * the specification mentioned above.
27 * If you discover missing features, or bugs, please note them below.
29 * TODO:
30 * - GetListBoxInfo()
31 * - LB_GETLISTBOXINFO
32 * - LBS_NODATA
33 * - LB_SETLOCALE: some FIXMEs remain
34 * - LBS_USETABSTOPS: some FIXMEs remain
37 #include <string.h>
38 #include <stdlib.h>
39 #include <stdarg.h>
40 #include <stdio.h>
41 #include "windef.h"
42 #include "winbase.h"
43 #include "wingdi.h"
44 #include "wine/winuser16.h"
45 #include "wine/winbase16.h"
46 #include "wownt32.h"
47 #include "wine/unicode.h"
48 #include "winuser.h"
49 #include "winerror.h"
50 #include "message.h"
51 #include "user.h"
52 #include "controls.h"
53 #include "wine/debug.h"
54 #include "win.h"
56 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
58 /* Items array granularity */
59 #define LB_ARRAY_GRANULARITY 16
61 /* Scrolling timeout in ms */
62 #define LB_SCROLL_TIMEOUT 50
64 /* Listbox system timer id */
65 #define LB_TIMER_ID 2
67 /* flag listbox changed while setredraw false - internal style */
68 #define LBS_DISPLAYCHANGED 0x80000000
70 /* Item structure */
71 typedef struct
73 LPWSTR str; /* Item text */
74 BOOL selected; /* Is item selected? */
75 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */
76 DWORD data; /* User data */
77 } LB_ITEMDATA;
79 /* Listbox structure */
80 typedef struct
82 HWND self; /* Our own window handle */
83 HWND owner; /* Owner window to send notifications to */
84 UINT style; /* Window style */
85 INT width; /* Window width */
86 INT height; /* Window height */
87 LB_ITEMDATA *items; /* Array of items */
88 INT nb_items; /* Number of items */
89 INT top_item; /* Top visible item */
90 INT selected_item; /* Selected item */
91 INT focus_item; /* Item that has the focus */
92 INT anchor_item; /* Anchor item for extended selection */
93 INT item_height; /* Default item height */
94 INT page_size; /* Items per listbox page */
95 INT column_width; /* Column width for multi-column listboxes */
96 INT horz_extent; /* Horizontal extent (0 if no hscroll) */
97 INT horz_pos; /* Horizontal position */
98 INT nb_tabs; /* Number of tabs in array */
99 INT *tabs; /* Array of tabs */
100 BOOL caret_on; /* Is caret on? */
101 BOOL captured; /* Is mouse captured? */
102 BOOL in_focus;
103 HFONT font; /* Current font */
104 LCID locale; /* Current locale for string comparisons */
105 LPHEADCOMBO lphc; /* ComboLBox */
106 } LB_DESCR;
109 #define IS_OWNERDRAW(descr) \
110 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
112 #define HAS_STRINGS(descr) \
113 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
116 #define IS_MULTISELECT(descr) \
117 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
118 !((descr)->style & LBS_NOSEL))
120 #define SEND_NOTIFICATION(descr,code) \
121 (SendMessageW( (descr)->owner, WM_COMMAND, \
122 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
124 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
126 /* Current timer status */
127 typedef enum
129 LB_TIMER_NONE,
130 LB_TIMER_UP,
131 LB_TIMER_LEFT,
132 LB_TIMER_DOWN,
133 LB_TIMER_RIGHT
134 } TIMER_DIRECTION;
136 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
138 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
139 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
141 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect );
143 /*********************************************************************
144 * listbox class descriptor
146 const struct builtin_class_descr LISTBOX_builtin_class =
148 "ListBox", /* name */
149 CS_DBLCLKS /*| CS_PARENTDC*/, /* style */
150 ListBoxWndProcA, /* procA */
151 ListBoxWndProcW, /* procW */
152 sizeof(LB_DESCR *), /* extra */
153 IDC_ARROW, /* cursor */
154 0 /* brush */
158 /*********************************************************************
159 * combolbox class descriptor
161 const struct builtin_class_descr COMBOLBOX_builtin_class =
163 "ComboLBox", /* name */
164 CS_DBLCLKS | CS_SAVEBITS, /* style */
165 ListBoxWndProcA, /* procA */
166 ListBoxWndProcW, /* procW */
167 sizeof(LB_DESCR *), /* extra */
168 IDC_ARROW, /* cursor */
169 0 /* brush */
173 /* check whether app is a Win 3.1 app */
174 inline static BOOL is_old_app( LB_DESCR *descr )
176 return (GetExpWinVer16( GetWindowLongPtrW(descr->self, GWLP_HINSTANCE) ) & 0xFF00 ) == 0x0300;
180 /***********************************************************************
181 * LISTBOX_Dump
183 void LISTBOX_Dump( LB_DESCR *descr )
185 INT i;
186 LB_ITEMDATA *item;
188 TRACE( "Listbox:\n" );
189 TRACE( "hwnd=%p descr=%08x items=%d top=%d\n",
190 descr->self, (UINT)descr, descr->nb_items, descr->top_item );
191 for (i = 0, item = descr->items; i < descr->nb_items; i++, item++)
193 TRACE( "%4d: %-40s %d %08lx %3d\n",
194 i, debugstr_w(item->str), item->selected, item->data, item->height );
199 /***********************************************************************
200 * LISTBOX_GetCurrentPageSize
202 * Return the current page size
204 static INT LISTBOX_GetCurrentPageSize( LB_DESCR *descr )
206 INT i, height;
207 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
208 for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
210 if ((height += descr->items[i].height) > descr->height) break;
212 if (i == descr->top_item) return 1;
213 else return i - descr->top_item;
217 /***********************************************************************
218 * LISTBOX_GetMaxTopIndex
220 * Return the maximum possible index for the top of the listbox.
222 static INT LISTBOX_GetMaxTopIndex( LB_DESCR *descr )
224 INT max, page;
226 if (descr->style & LBS_OWNERDRAWVARIABLE)
228 page = descr->height;
229 for (max = descr->nb_items - 1; max >= 0; max--)
230 if ((page -= descr->items[max].height) < 0) break;
231 if (max < descr->nb_items - 1) max++;
233 else if (descr->style & LBS_MULTICOLUMN)
235 if ((page = descr->width / descr->column_width) < 1) page = 1;
236 max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
237 max = (max - page) * descr->page_size;
239 else
241 max = descr->nb_items - descr->page_size;
243 if (max < 0) max = 0;
244 return max;
248 /***********************************************************************
249 * LISTBOX_UpdateScroll
251 * Update the scrollbars. Should be called whenever the content
252 * of the listbox changes.
254 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
256 SCROLLINFO info;
258 /* Check the listbox scroll bar flags individually before we call
259 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
260 no WS_VSCROLL, we end up with an uninitialized, visible horizontal
261 scroll bar when we do not need one.
262 if (!(descr->style & WS_VSCROLL)) return;
265 /* It is important that we check descr->style, and not wnd->dwStyle,
266 for WS_VSCROLL, as the former is exactly the one passed in
267 argument to CreateWindow.
268 In Windows (and from now on in Wine :) a listbox created
269 with such a style (no WS_SCROLL) does not update
270 the scrollbar with listbox-related data, thus letting
271 the programmer use it for his/her own purposes. */
273 if (descr->style & LBS_NOREDRAW) return;
274 info.cbSize = sizeof(info);
276 if (descr->style & LBS_MULTICOLUMN)
278 info.nMin = 0;
279 info.nMax = (descr->nb_items - 1) / descr->page_size;
280 info.nPos = descr->top_item / descr->page_size;
281 info.nPage = descr->width / descr->column_width;
282 if (info.nPage < 1) info.nPage = 1;
283 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
284 if (descr->style & LBS_DISABLENOSCROLL)
285 info.fMask |= SIF_DISABLENOSCROLL;
286 if (descr->style & WS_HSCROLL)
287 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
288 info.nMax = 0;
289 info.fMask = SIF_RANGE;
290 if (descr->style & WS_VSCROLL)
291 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
293 else
295 info.nMin = 0;
296 info.nMax = descr->nb_items - 1;
297 info.nPos = descr->top_item;
298 info.nPage = LISTBOX_GetCurrentPageSize( descr );
299 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
300 if (descr->style & LBS_DISABLENOSCROLL)
301 info.fMask |= SIF_DISABLENOSCROLL;
302 if (descr->style & WS_VSCROLL)
303 SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
305 if (descr->horz_extent)
307 info.nMin = 0;
308 info.nMax = descr->horz_extent - 1;
309 info.nPos = descr->horz_pos;
310 info.nPage = descr->width;
311 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
312 if (descr->style & LBS_DISABLENOSCROLL)
313 info.fMask |= SIF_DISABLENOSCROLL;
314 if (descr->style & WS_HSCROLL)
315 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
321 /***********************************************************************
322 * LISTBOX_SetTopItem
324 * Set the top item of the listbox, scrolling up or down if necessary.
326 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
328 INT max = LISTBOX_GetMaxTopIndex( descr );
329 if (index > max) index = max;
330 if (index < 0) index = 0;
331 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
332 if (descr->top_item == index) return LB_OKAY;
333 if (descr->style & LBS_MULTICOLUMN)
335 INT diff = (descr->top_item - index) / descr->page_size * descr->column_width;
336 if (scroll && (abs(diff) < descr->width))
337 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
338 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
340 else
341 scroll = FALSE;
343 else if (scroll)
345 INT diff;
346 if (descr->style & LBS_OWNERDRAWVARIABLE)
348 INT i;
349 diff = 0;
350 if (index > descr->top_item)
352 for (i = index - 1; i >= descr->top_item; i--)
353 diff -= descr->items[i].height;
355 else
357 for (i = index; i < descr->top_item; i++)
358 diff += descr->items[i].height;
361 else
362 diff = (descr->top_item - index) * descr->item_height;
364 if (abs(diff) < descr->height)
365 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
366 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
367 else
368 scroll = FALSE;
370 if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
371 descr->top_item = index;
372 LISTBOX_UpdateScroll( descr );
373 return LB_OKAY;
377 /***********************************************************************
378 * LISTBOX_UpdatePage
380 * Update the page size. Should be called when the size of
381 * the client area or the item height changes.
383 static void LISTBOX_UpdatePage( LB_DESCR *descr )
385 INT page_size;
387 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
388 page_size = 1;
389 if (page_size == descr->page_size) return;
390 descr->page_size = page_size;
391 if (descr->style & LBS_MULTICOLUMN)
392 InvalidateRect( descr->self, NULL, TRUE );
393 LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
397 /***********************************************************************
398 * LISTBOX_UpdateSize
400 * Update the size of the listbox. Should be called when the size of
401 * the client area changes.
403 static void LISTBOX_UpdateSize( LB_DESCR *descr )
405 RECT rect;
407 GetClientRect( descr->self, &rect );
408 descr->width = rect.right - rect.left;
409 descr->height = rect.bottom - rect.top;
410 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
412 INT remaining;
413 RECT rect;
415 GetWindowRect( descr->self, &rect );
416 if(descr->item_height != 0)
417 remaining = descr->height % descr->item_height;
418 else
419 remaining = 0;
420 if ((descr->height > descr->item_height) && remaining)
422 if (is_old_app(descr))
423 { /* give a margin for error to 16 bits programs - if we need
424 less than the height of the nonclient area, round to the
425 *next* number of items */
426 int ncheight = rect.bottom - rect.top - descr->height;
427 if ((descr->item_height - remaining) <= ncheight)
428 remaining = remaining - descr->item_height;
430 TRACE("[%p]: changing height %d -> %d\n",
431 descr->self, descr->height, descr->height - remaining );
432 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
433 rect.bottom - rect.top - remaining,
434 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
435 return;
438 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
439 LISTBOX_UpdatePage( descr );
440 LISTBOX_UpdateScroll( descr );
442 /* Invalidate the focused item so it will be repainted correctly */
443 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
445 InvalidateRect( descr->self, &rect, FALSE );
450 /***********************************************************************
451 * LISTBOX_GetItemRect
453 * Get the rectangle enclosing an item, in listbox client coordinates.
454 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
456 static LRESULT LISTBOX_GetItemRect( LB_DESCR *descr, INT index, RECT *rect )
458 /* Index <= 0 is legal even on empty listboxes */
459 if (index && (index >= descr->nb_items)) return -1;
460 SetRect( rect, 0, 0, descr->width, descr->height );
461 if (descr->style & LBS_MULTICOLUMN)
463 INT col = (index / descr->page_size) -
464 (descr->top_item / descr->page_size);
465 rect->left += col * descr->column_width;
466 rect->right = rect->left + descr->column_width;
467 rect->top += (index % descr->page_size) * descr->item_height;
468 rect->bottom = rect->top + descr->item_height;
470 else if (descr->style & LBS_OWNERDRAWVARIABLE)
472 INT i;
473 rect->right += descr->horz_pos;
474 if ((index >= 0) && (index < descr->nb_items))
476 if (index < descr->top_item)
478 for (i = descr->top_item-1; i >= index; i--)
479 rect->top -= descr->items[i].height;
481 else
483 for (i = descr->top_item; i < index; i++)
484 rect->top += descr->items[i].height;
486 rect->bottom = rect->top + descr->items[index].height;
490 else
492 rect->top += (index - descr->top_item) * descr->item_height;
493 rect->bottom = rect->top + descr->item_height;
494 rect->right += descr->horz_pos;
497 return ((rect->left < descr->width) && (rect->right > 0) &&
498 (rect->top < descr->height) && (rect->bottom > 0));
502 /***********************************************************************
503 * LISTBOX_GetItemFromPoint
505 * Return the item nearest from point (x,y) (in client coordinates).
507 static INT LISTBOX_GetItemFromPoint( LB_DESCR *descr, INT x, INT y )
509 INT index = descr->top_item;
511 if (!descr->nb_items) return -1; /* No items */
512 if (descr->style & LBS_OWNERDRAWVARIABLE)
514 INT pos = 0;
515 if (y >= 0)
517 while (index < descr->nb_items)
519 if ((pos += descr->items[index].height) > y) break;
520 index++;
523 else
525 while (index > 0)
527 index--;
528 if ((pos -= descr->items[index].height) <= y) break;
532 else if (descr->style & LBS_MULTICOLUMN)
534 if (y >= descr->item_height * descr->page_size) return -1;
535 if (y >= 0) index += y / descr->item_height;
536 if (x >= 0) index += (x / descr->column_width) * descr->page_size;
537 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
539 else
541 index += (y / descr->item_height);
543 if (index < 0) return 0;
544 if (index >= descr->nb_items) return -1;
545 return index;
549 /***********************************************************************
550 * LISTBOX_PaintItem
552 * Paint an item.
554 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect,
555 INT index, UINT action, BOOL ignoreFocus )
557 LB_ITEMDATA *item = NULL;
558 if (index < descr->nb_items) item = &descr->items[index];
560 if (IS_OWNERDRAW(descr))
562 DRAWITEMSTRUCT dis;
563 RECT r;
564 HRGN hrgn;
565 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
567 if (!item)
569 if (action == ODA_FOCUS)
570 DrawFocusRect( hdc, rect );
571 else
572 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
573 return;
576 /* some programs mess with the clipping region when
577 drawing the item, *and* restore the previous region
578 after they are done, so a region has better to exist
579 else everything ends clipped */
580 GetClientRect(descr->self, &r);
581 hrgn = CreateRectRgnIndirect(&r);
582 SelectClipRgn( hdc, hrgn);
583 DeleteObject( hrgn );
585 dis.CtlType = ODT_LISTBOX;
586 dis.CtlID = id;
587 dis.hwndItem = descr->self;
588 dis.itemAction = action;
589 dis.hDC = hdc;
590 dis.itemID = index;
591 dis.itemState = 0;
592 if (item && item->selected) dis.itemState |= ODS_SELECTED;
593 if (!ignoreFocus && (descr->focus_item == index) &&
594 (descr->caret_on) &&
595 (descr->in_focus)) dis.itemState |= ODS_FOCUS;
596 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
597 dis.itemData = item ? item->data : 0;
598 dis.rcItem = *rect;
599 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%ld,%ld-%ld,%ld\n",
600 descr->self, index, item ? debugstr_w(item->str) : "", action,
601 dis.itemState, rect->left, rect->top, rect->right, rect->bottom );
602 SendMessageW(descr->owner, WM_DRAWITEM, id, (LPARAM)&dis);
604 else
606 COLORREF oldText = 0, oldBk = 0;
608 if (action == ODA_FOCUS)
610 DrawFocusRect( hdc, rect );
611 return;
613 if (item && item->selected)
615 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
616 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
619 TRACE("[%p]: painting %d (%s) action=%02x rect=%ld,%ld-%ld,%ld\n",
620 descr->self, index, item ? debugstr_w(item->str) : "", action,
621 rect->left, rect->top, rect->right, rect->bottom );
622 if (!item)
623 ExtTextOutW( hdc, rect->left + 1, rect->top,
624 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
625 else if (!(descr->style & LBS_USETABSTOPS))
626 ExtTextOutW( hdc, rect->left + 1, rect->top,
627 ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
628 strlenW(item->str), NULL );
629 else
631 /* Output empty string to paint background in the full width. */
632 ExtTextOutW( hdc, rect->left + 1, rect->top,
633 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
634 TabbedTextOutW( hdc, rect->left + 1 , rect->top,
635 item->str, strlenW(item->str),
636 descr->nb_tabs, descr->tabs, 0);
638 if (item && item->selected)
640 SetBkColor( hdc, oldBk );
641 SetTextColor( hdc, oldText );
643 if (!ignoreFocus && (descr->focus_item == index) &&
644 (descr->caret_on) &&
645 (descr->in_focus)) DrawFocusRect( hdc, rect );
650 /***********************************************************************
651 * LISTBOX_SetRedraw
653 * Change the redraw flag.
655 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
657 if (on)
659 if (!(descr->style & LBS_NOREDRAW)) return;
660 descr->style &= ~LBS_NOREDRAW;
661 if (descr->style & LBS_DISPLAYCHANGED)
662 { /* page was changed while setredraw false, refresh automatically */
663 InvalidateRect(descr->self, NULL, TRUE);
664 if ((descr->top_item + descr->page_size) > descr->nb_items)
665 { /* reset top of page if less than number of items/page */
666 descr->top_item = descr->nb_items - descr->page_size;
667 if (descr->top_item < 0) descr->top_item = 0;
669 descr->style &= ~LBS_DISPLAYCHANGED;
671 LISTBOX_UpdateScroll( descr );
673 else descr->style |= LBS_NOREDRAW;
677 /***********************************************************************
678 * LISTBOX_RepaintItem
680 * Repaint a single item synchronously.
682 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
684 HDC hdc;
685 RECT rect;
686 HFONT oldFont = 0;
687 HBRUSH hbrush, oldBrush = 0;
689 /* Do not repaint the item if the item is not visible */
690 if (!IsWindowVisible(descr->self)) return;
691 if (descr->style & LBS_NOREDRAW)
693 descr->style |= LBS_DISPLAYCHANGED;
694 return;
696 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
697 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
698 if (descr->font) oldFont = SelectObject( hdc, descr->font );
699 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
700 (WPARAM)hdc, (LPARAM)descr->self );
701 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
702 if (!IsWindowEnabled(descr->self))
703 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
704 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
705 LISTBOX_PaintItem( descr, hdc, &rect, index, action, FALSE );
706 if (oldFont) SelectObject( hdc, oldFont );
707 if (oldBrush) SelectObject( hdc, oldBrush );
708 ReleaseDC( descr->self, hdc );
712 /***********************************************************************
713 * LISTBOX_InitStorage
715 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
717 LB_ITEMDATA *item;
719 nb_items += LB_ARRAY_GRANULARITY - 1;
720 nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
721 if (descr->items) {
722 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
723 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
724 nb_items * sizeof(LB_ITEMDATA));
726 else {
727 item = HeapAlloc( GetProcessHeap(), 0,
728 nb_items * sizeof(LB_ITEMDATA));
731 if (!item)
733 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
734 return LB_ERRSPACE;
736 descr->items = item;
737 return LB_OKAY;
741 /***********************************************************************
742 * LISTBOX_SetTabStops
744 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs, BOOL short_ints )
746 if (!(descr->style & LBS_USETABSTOPS)) return TRUE;
747 if (descr->tabs) HeapFree( GetProcessHeap(), 0, descr->tabs );
748 if (!(descr->nb_tabs = count))
750 descr->tabs = NULL;
751 return TRUE;
753 /* FIXME: count = 1 */
754 if (!(descr->tabs = (INT *)HeapAlloc( GetProcessHeap(), 0,
755 descr->nb_tabs * sizeof(INT) )))
756 return FALSE;
757 if (short_ints)
759 INT i;
760 LPINT16 p = (LPINT16)tabs;
762 TRACE("[%p]: settabstops ", descr->self );
763 for (i = 0; i < descr->nb_tabs; i++) {
764 descr->tabs[i] = *p++<<1; /* FIXME */
765 TRACE("%hd ", descr->tabs[i]);
767 TRACE("\n");
769 else memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
770 /* FIXME: repaint the window? */
771 return TRUE;
775 /***********************************************************************
776 * LISTBOX_GetText
778 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
780 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
781 if (HAS_STRINGS(descr))
783 if (!buffer)
785 DWORD len = strlenW(descr->items[index].str);
786 if( unicode )
787 return len;
788 return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
789 NULL, 0, NULL, NULL );
792 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
794 if(unicode)
796 strcpyW( buffer, descr->items[index].str );
797 return strlenW(buffer);
799 else
801 return WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1, (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
803 } else {
804 if (buffer)
805 *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
806 return sizeof(DWORD);
811 /***********************************************************************
812 * LISTBOX_FindStringPos
814 * Find the nearest string located before a given string in sort order.
815 * If 'exact' is TRUE, return an error if we don't get an exact match.
817 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
819 INT index, min, max, res = -1;
821 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */
822 min = 0;
823 max = descr->nb_items;
824 while (min != max)
826 index = (min + max) / 2;
827 if (HAS_STRINGS(descr))
828 res = lstrcmpiW( str, descr->items[index].str);
829 else
831 COMPAREITEMSTRUCT cis;
832 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
834 cis.CtlType = ODT_LISTBOX;
835 cis.CtlID = id;
836 cis.hwndItem = descr->self;
837 /* note that some application (MetaStock) expects the second item
838 * to be in the listbox */
839 cis.itemID1 = -1;
840 cis.itemData1 = (DWORD)str;
841 cis.itemID2 = index;
842 cis.itemData2 = descr->items[index].data;
843 cis.dwLocaleId = descr->locale;
844 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
846 if (!res) return index;
847 if (res < 0) max = index;
848 else min = index + 1;
850 return exact ? -1 : max;
854 /***********************************************************************
855 * LISTBOX_FindFileStrPos
857 * Find the nearest string located before a given string in directory
858 * sort order (i.e. first files, then directories, then drives).
860 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
862 INT min, max, res = -1;
864 if (!HAS_STRINGS(descr))
865 return LISTBOX_FindStringPos( descr, str, FALSE );
866 min = 0;
867 max = descr->nb_items;
868 while (min != max)
870 INT index = (min + max) / 2;
871 LPCWSTR p = descr->items[index].str;
872 if (*p == '[') /* drive or directory */
874 if (*str != '[') res = -1;
875 else if (p[1] == '-') /* drive */
877 if (str[1] == '-') res = str[2] - p[2];
878 else res = -1;
880 else /* directory */
882 if (str[1] == '-') res = 1;
883 else res = lstrcmpiW( str, p );
886 else /* filename */
888 if (*str == '[') res = 1;
889 else res = lstrcmpiW( str, p );
891 if (!res) return index;
892 if (res < 0) max = index;
893 else min = index + 1;
895 return max;
899 /***********************************************************************
900 * LISTBOX_FindString
902 * Find the item beginning with a given string.
904 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
906 INT i;
907 LB_ITEMDATA *item;
909 if (start >= descr->nb_items) start = -1;
910 item = descr->items + start + 1;
911 if (HAS_STRINGS(descr))
913 if (!str || ! str[0] ) return LB_ERR;
914 if (exact)
916 for (i = start + 1; i < descr->nb_items; i++, item++)
917 if (!lstrcmpiW( str, item->str )) return i;
918 for (i = 0, item = descr->items; i <= start; i++, item++)
919 if (!lstrcmpiW( str, item->str )) return i;
921 else
923 /* Special case for drives and directories: ignore prefix */
924 #define CHECK_DRIVE(item) \
925 if ((item)->str[0] == '[') \
927 if (!strncmpiW( str, (item)->str+1, len )) return i; \
928 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
929 return i; \
932 INT len = strlenW(str);
933 for (i = start + 1; i < descr->nb_items; i++, item++)
935 if (!strncmpiW( str, item->str, len )) return i;
936 CHECK_DRIVE(item);
938 for (i = 0, item = descr->items; i <= start; i++, item++)
940 if (!strncmpiW( str, item->str, len )) return i;
941 CHECK_DRIVE(item);
943 #undef CHECK_DRIVE
946 else
948 if (exact && (descr->style & LBS_SORT))
949 /* If sorted, use a WM_COMPAREITEM binary search */
950 return LISTBOX_FindStringPos( descr, str, TRUE );
952 /* Otherwise use a linear search */
953 for (i = start + 1; i < descr->nb_items; i++, item++)
954 if (item->data == (DWORD)str) return i;
955 for (i = 0, item = descr->items; i <= start; i++, item++)
956 if (item->data == (DWORD)str) return i;
958 return LB_ERR;
962 /***********************************************************************
963 * LISTBOX_GetSelCount
965 static LRESULT LISTBOX_GetSelCount( LB_DESCR *descr )
967 INT i, count;
968 LB_ITEMDATA *item = descr->items;
970 if (!(descr->style & LBS_MULTIPLESEL) ||
971 (descr->style & LBS_NOSEL))
972 return LB_ERR;
973 for (i = count = 0; i < descr->nb_items; i++, item++)
974 if (item->selected) count++;
975 return count;
979 /***********************************************************************
980 * LISTBOX_GetSelItems16
982 static LRESULT LISTBOX_GetSelItems16( LB_DESCR *descr, INT16 max, LPINT16 array )
984 INT i, count;
985 LB_ITEMDATA *item = descr->items;
987 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
988 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
989 if (item->selected) array[count++] = (INT16)i;
990 return count;
994 /***********************************************************************
995 * LISTBOX_GetSelItems
997 static LRESULT LISTBOX_GetSelItems( LB_DESCR *descr, INT max, LPINT array )
999 INT i, count;
1000 LB_ITEMDATA *item = descr->items;
1002 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1003 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
1004 if (item->selected) array[count++] = i;
1005 return count;
1009 /***********************************************************************
1010 * LISTBOX_Paint
1012 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1014 INT i, col_pos = descr->page_size - 1;
1015 RECT rect;
1016 RECT focusRect = {-1, -1, -1, -1};
1017 HFONT oldFont = 0;
1018 HBRUSH hbrush, oldBrush = 0;
1020 if (descr->style & LBS_NOREDRAW) return 0;
1022 SetRect( &rect, 0, 0, descr->width, descr->height );
1023 if (descr->style & LBS_MULTICOLUMN)
1024 rect.right = rect.left + descr->column_width;
1025 else if (descr->horz_pos)
1027 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1028 rect.right += descr->horz_pos;
1031 if (descr->font) oldFont = SelectObject( hdc, descr->font );
1032 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1033 (WPARAM)hdc, (LPARAM)descr->self );
1034 if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1035 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1037 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1038 (descr->in_focus))
1040 /* Special case for empty listbox: paint focus rect */
1041 rect.bottom = rect.top + descr->item_height;
1042 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1043 &rect, NULL, 0, NULL );
1044 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1045 rect.top = rect.bottom;
1048 /* Paint all the item, regarding the selection
1049 Focus state will be painted after */
1051 for (i = descr->top_item; i < descr->nb_items; i++)
1053 if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1054 rect.bottom = rect.top + descr->item_height;
1055 else
1056 rect.bottom = rect.top + descr->items[i].height;
1058 if (i == descr->focus_item)
1060 /* keep the focus rect, to paint the focus item after */
1061 focusRect.left = rect.left;
1062 focusRect.right = rect.right;
1063 focusRect.top = rect.top;
1064 focusRect.bottom = rect.bottom;
1066 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1067 rect.top = rect.bottom;
1069 if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1071 if (!IS_OWNERDRAW(descr))
1073 /* Clear the bottom of the column */
1074 if (rect.top < descr->height)
1076 rect.bottom = descr->height;
1077 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1078 &rect, NULL, 0, NULL );
1082 /* Go to the next column */
1083 rect.left += descr->column_width;
1084 rect.right += descr->column_width;
1085 rect.top = 0;
1086 col_pos = descr->page_size - 1;
1088 else
1090 col_pos--;
1091 if (rect.top >= descr->height) break;
1095 /* Paint the focus item now */
1096 if (focusRect.top != focusRect.bottom &&
1097 descr->caret_on && descr->in_focus)
1098 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1100 if (!IS_OWNERDRAW(descr))
1102 /* Clear the remainder of the client area */
1103 if (rect.top < descr->height)
1105 rect.bottom = descr->height;
1106 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1107 &rect, NULL, 0, NULL );
1109 if (rect.right < descr->width)
1111 rect.left = rect.right;
1112 rect.right = descr->width;
1113 rect.top = 0;
1114 rect.bottom = descr->height;
1115 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1116 &rect, NULL, 0, NULL );
1119 if (oldFont) SelectObject( hdc, oldFont );
1120 if (oldBrush) SelectObject( hdc, oldBrush );
1121 return 0;
1125 /***********************************************************************
1126 * LISTBOX_InvalidateItems
1128 * Invalidate all items from a given item. If the specified item is not
1129 * visible, nothing happens.
1131 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1133 RECT rect;
1135 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1137 if (descr->style & LBS_NOREDRAW)
1139 descr->style |= LBS_DISPLAYCHANGED;
1140 return;
1142 rect.bottom = descr->height;
1143 InvalidateRect( descr->self, &rect, TRUE );
1144 if (descr->style & LBS_MULTICOLUMN)
1146 /* Repaint the other columns */
1147 rect.left = rect.right;
1148 rect.right = descr->width;
1149 rect.top = 0;
1150 InvalidateRect( descr->self, &rect, TRUE );
1155 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1157 RECT rect;
1159 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1160 InvalidateRect( descr->self, &rect, TRUE );
1163 /***********************************************************************
1164 * LISTBOX_GetItemHeight
1166 static LRESULT LISTBOX_GetItemHeight( LB_DESCR *descr, INT index )
1168 if (descr->style & LBS_OWNERDRAWVARIABLE)
1170 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1171 return descr->items[index].height;
1173 else return descr->item_height;
1177 /***********************************************************************
1178 * LISTBOX_SetItemHeight
1180 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1182 if (!height) height = 1;
1184 if (descr->style & LBS_OWNERDRAWVARIABLE)
1186 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1187 TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1188 descr->items[index].height = height;
1189 LISTBOX_UpdateScroll( descr );
1190 if (repaint)
1191 LISTBOX_InvalidateItems( descr, index );
1193 else if (height != descr->item_height)
1195 TRACE("[%p]: new height = %d\n", descr->self, height );
1196 descr->item_height = height;
1197 LISTBOX_UpdatePage( descr );
1198 LISTBOX_UpdateScroll( descr );
1199 if (repaint)
1200 InvalidateRect( descr->self, 0, TRUE );
1202 return LB_OKAY;
1206 /***********************************************************************
1207 * LISTBOX_SetHorizontalPos
1209 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1211 INT diff;
1213 if (pos > descr->horz_extent - descr->width)
1214 pos = descr->horz_extent - descr->width;
1215 if (pos < 0) pos = 0;
1216 if (!(diff = descr->horz_pos - pos)) return;
1217 TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1218 descr->horz_pos = pos;
1219 LISTBOX_UpdateScroll( descr );
1220 if (abs(diff) < descr->width)
1222 RECT rect;
1223 /* Invalidate the focused item so it will be repainted correctly */
1224 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1225 InvalidateRect( descr->self, &rect, TRUE );
1226 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1227 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1229 else
1230 InvalidateRect( descr->self, NULL, TRUE );
1234 /***********************************************************************
1235 * LISTBOX_SetHorizontalExtent
1237 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1239 if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1240 return LB_OKAY;
1241 if (extent <= 0) extent = 1;
1242 if (extent == descr->horz_extent) return LB_OKAY;
1243 TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1244 descr->horz_extent = extent;
1245 if (descr->horz_pos > extent - descr->width)
1246 LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1247 else
1248 LISTBOX_UpdateScroll( descr );
1249 return LB_OKAY;
1253 /***********************************************************************
1254 * LISTBOX_SetColumnWidth
1256 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1258 if (width == descr->column_width) return LB_OKAY;
1259 TRACE("[%p]: new column width = %d\n", descr->self, width );
1260 descr->column_width = width;
1261 LISTBOX_UpdatePage( descr );
1262 return LB_OKAY;
1266 /***********************************************************************
1267 * LISTBOX_SetFont
1269 * Returns the item height.
1271 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1273 HDC hdc;
1274 HFONT oldFont = 0;
1275 TEXTMETRICW tm;
1277 descr->font = font;
1279 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1281 ERR("unable to get DC.\n" );
1282 return 16;
1284 if (font) oldFont = SelectObject( hdc, font );
1285 GetTextMetricsW( hdc, &tm );
1286 if (oldFont) SelectObject( hdc, oldFont );
1287 ReleaseDC( descr->self, hdc );
1288 if (!IS_OWNERDRAW(descr))
1289 LISTBOX_SetItemHeight( descr, 0, tm.tmHeight, FALSE );
1290 return tm.tmHeight;
1294 /***********************************************************************
1295 * LISTBOX_MakeItemVisible
1297 * Make sure that a given item is partially or fully visible.
1299 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1301 INT top;
1303 if (index <= descr->top_item) top = index;
1304 else if (descr->style & LBS_MULTICOLUMN)
1306 INT cols = descr->width;
1307 if (!fully) cols += descr->column_width - 1;
1308 if (cols >= descr->column_width) cols /= descr->column_width;
1309 else cols = 1;
1310 if (index < descr->top_item + (descr->page_size * cols)) return;
1311 top = index - descr->page_size * (cols - 1);
1313 else if (descr->style & LBS_OWNERDRAWVARIABLE)
1315 INT height = fully ? descr->items[index].height : 1;
1316 for (top = index; top > descr->top_item; top--)
1317 if ((height += descr->items[top-1].height) > descr->height) break;
1319 else
1321 if (index < descr->top_item + descr->page_size) return;
1322 if (!fully && (index == descr->top_item + descr->page_size) &&
1323 (descr->height > (descr->page_size * descr->item_height))) return;
1324 top = index - descr->page_size + 1;
1326 LISTBOX_SetTopItem( descr, top, TRUE );
1329 /***********************************************************************
1330 * LISTBOX_SetCaretIndex
1332 * NOTES
1333 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1336 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1338 INT oldfocus = descr->focus_item;
1340 if (descr->style & LBS_NOSEL) return LB_ERR;
1341 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1342 if (index == oldfocus) return LB_OKAY;
1343 descr->focus_item = index;
1344 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1345 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1347 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1348 if (descr->caret_on && (descr->in_focus))
1349 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1351 return LB_OKAY;
1355 /***********************************************************************
1356 * LISTBOX_SelectItemRange
1358 * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1360 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1361 INT last, BOOL on )
1363 INT i;
1365 /* A few sanity checks */
1367 if (descr->style & LBS_NOSEL) return LB_ERR;
1368 if ((last == -1) && (descr->nb_items == 0)) return LB_OKAY;
1369 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1370 if (last == -1) last = descr->nb_items - 1;
1371 if ((first < 0) || (first >= descr->nb_items)) return LB_ERR;
1372 if ((last < 0) || (last >= descr->nb_items)) return LB_ERR;
1373 /* selected_item reflects last selected/unselected item on multiple sel */
1374 descr->selected_item = last;
1376 if (on) /* Turn selection on */
1378 for (i = first; i <= last; i++)
1380 if (descr->items[i].selected) continue;
1381 descr->items[i].selected = TRUE;
1382 LISTBOX_InvalidateItemRect(descr, i);
1384 LISTBOX_SetCaretIndex( descr, last, TRUE );
1386 else /* Turn selection off */
1388 for (i = first; i <= last; i++)
1390 if (!descr->items[i].selected) continue;
1391 descr->items[i].selected = FALSE;
1392 LISTBOX_InvalidateItemRect(descr, i);
1395 return LB_OKAY;
1398 /***********************************************************************
1399 * LISTBOX_SetSelection
1401 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1402 BOOL on, BOOL send_notify )
1404 TRACE( "index=%d notify=%s\n", index, send_notify ? "YES" : "NO" );
1406 if (descr->style & LBS_NOSEL)
1408 descr->selected_item = index;
1409 return LB_ERR;
1411 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1412 if (descr->style & LBS_MULTIPLESEL)
1414 if (index == -1) /* Select all items */
1415 return LISTBOX_SelectItemRange( descr, 0, -1, on );
1416 else /* Only one item */
1417 return LISTBOX_SelectItemRange( descr, index, index, on );
1419 else
1421 INT oldsel = descr->selected_item;
1422 if (index == oldsel) return LB_OKAY;
1423 if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1424 if (index != -1) descr->items[index].selected = TRUE;
1425 descr->selected_item = index;
1426 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1427 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1428 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1429 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1430 else
1431 if( descr->lphc ) /* set selection change flag for parent combo */
1432 descr->lphc->wState |= CBF_SELCHANGE;
1434 return LB_OKAY;
1438 /***********************************************************************
1439 * LISTBOX_MoveCaret
1441 * Change the caret position and extend the selection to the new caret.
1443 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1445 INT oldfocus = descr->focus_item;
1447 if ((index < 0) || (index >= descr->nb_items))
1448 return;
1450 /* Important, repaint needs to be done in this order if
1451 you want to mimic Windows behavior:
1452 1. Remove the focus and paint the item
1453 2. Remove the selection and paint the item(s)
1454 3. Set the selection and repaint the item(s)
1455 4. Set the focus to 'index' and repaint the item */
1457 /* 1. remove the focus and repaint the item */
1458 descr->focus_item = -1;
1459 if ((oldfocus != -1) && descr->caret_on && (descr->in_focus))
1460 LISTBOX_RepaintItem( descr, oldfocus, ODA_FOCUS );
1462 /* 2. then turn off the previous selection */
1463 /* 3. repaint the new selected item */
1464 if (descr->style & LBS_EXTENDEDSEL)
1466 if (descr->anchor_item != -1)
1468 INT first = min( index, descr->anchor_item );
1469 INT last = max( index, descr->anchor_item );
1470 if (first > 0)
1471 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1472 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1473 LISTBOX_SelectItemRange( descr, first, last, TRUE );
1476 else if (!(descr->style & LBS_MULTIPLESEL))
1478 /* Set selection to new caret item */
1479 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1482 /* 4. repaint the new item with the focus */
1483 descr->focus_item = index;
1484 LISTBOX_MakeItemVisible( descr, index, fully_visible );
1485 if (descr->caret_on && (descr->in_focus))
1486 LISTBOX_RepaintItem( descr, index, ODA_FOCUS );
1490 /***********************************************************************
1491 * LISTBOX_InsertItem
1493 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1494 LPWSTR str, DWORD data )
1496 LB_ITEMDATA *item;
1497 INT max_items;
1498 INT oldfocus = descr->focus_item;
1500 if (index == -1) index = descr->nb_items;
1501 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1502 if (!descr->items) max_items = 0;
1503 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1504 if (descr->nb_items == max_items)
1506 /* We need to grow the array */
1507 max_items += LB_ARRAY_GRANULARITY;
1508 if (descr->items)
1509 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1510 max_items * sizeof(LB_ITEMDATA) );
1511 else
1512 item = HeapAlloc( GetProcessHeap(), 0,
1513 max_items * sizeof(LB_ITEMDATA) );
1514 if (!item)
1516 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1517 return LB_ERRSPACE;
1519 descr->items = item;
1522 /* Insert the item structure */
1524 item = &descr->items[index];
1525 if (index < descr->nb_items)
1526 RtlMoveMemory( item + 1, item,
1527 (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1528 item->str = str;
1529 item->data = data;
1530 item->height = 0;
1531 item->selected = FALSE;
1532 descr->nb_items++;
1534 /* Get item height */
1536 if (descr->style & LBS_OWNERDRAWVARIABLE)
1538 MEASUREITEMSTRUCT mis;
1539 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1541 mis.CtlType = ODT_LISTBOX;
1542 mis.CtlID = id;
1543 mis.itemID = index;
1544 mis.itemData = descr->items[index].data;
1545 mis.itemHeight = descr->item_height;
1546 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1547 item->height = mis.itemHeight ? mis.itemHeight : 1;
1548 TRACE("[%p]: measure item %d (%s) = %d\n",
1549 descr->self, index, str ? debugstr_w(str) : "", item->height );
1552 /* Repaint the items */
1554 LISTBOX_UpdateScroll( descr );
1555 LISTBOX_InvalidateItems( descr, index );
1557 /* Move selection and focused item */
1558 /* If listbox was empty, set focus to the first item */
1559 if (descr->nb_items == 1)
1560 LISTBOX_SetCaretIndex( descr, 0, FALSE );
1561 /* single select don't change selection index in win31 */
1562 else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1564 descr->selected_item++;
1565 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1567 else
1569 if (index <= descr->selected_item)
1571 descr->selected_item++;
1572 descr->focus_item = oldfocus; /* focus not changed */
1575 return LB_OKAY;
1579 /***********************************************************************
1580 * LISTBOX_InsertString
1582 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1584 LPWSTR new_str = NULL;
1585 DWORD data = 0;
1586 LRESULT ret;
1588 if (HAS_STRINGS(descr))
1590 static const WCHAR empty_stringW[] = { 0 };
1591 if (!str) str = empty_stringW;
1592 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1594 SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1595 return LB_ERRSPACE;
1597 strcpyW(new_str, str);
1599 else data = (DWORD)str;
1601 if (index == -1) index = descr->nb_items;
1602 if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1604 if (new_str) HeapFree( GetProcessHeap(), 0, new_str );
1605 return ret;
1608 TRACE("[%p]: added item %d %s\n",
1609 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1610 return index;
1614 /***********************************************************************
1615 * LISTBOX_DeleteItem
1617 * Delete the content of an item. 'index' must be a valid index.
1619 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1621 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1622 * while Win95 sends it for all items with user data.
1623 * It's probably better to send it too often than not
1624 * often enough, so this is what we do here.
1626 if (IS_OWNERDRAW(descr) || descr->items[index].data)
1628 DELETEITEMSTRUCT dis;
1629 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1631 dis.CtlType = ODT_LISTBOX;
1632 dis.CtlID = id;
1633 dis.itemID = index;
1634 dis.hwndItem = descr->self;
1635 dis.itemData = descr->items[index].data;
1636 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1638 if (HAS_STRINGS(descr) && descr->items[index].str)
1639 HeapFree( GetProcessHeap(), 0, descr->items[index].str );
1643 /***********************************************************************
1644 * LISTBOX_RemoveItem
1646 * Remove an item from the listbox and delete its content.
1648 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1650 LB_ITEMDATA *item;
1651 INT max_items;
1653 if ((index == -1) && (descr->nb_items > 0)) index = descr->nb_items - 1;
1654 else if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1656 /* We need to invalidate the original rect instead of the updated one. */
1657 LISTBOX_InvalidateItems( descr, index );
1659 LISTBOX_DeleteItem( descr, index );
1661 /* Remove the item */
1663 item = &descr->items[index];
1664 if (index < descr->nb_items-1)
1665 RtlMoveMemory( item, item + 1,
1666 (descr->nb_items - index - 1) * sizeof(LB_ITEMDATA) );
1667 descr->nb_items--;
1668 if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1670 /* Shrink the item array if possible */
1672 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1673 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1675 max_items -= LB_ARRAY_GRANULARITY;
1676 item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1677 max_items * sizeof(LB_ITEMDATA) );
1678 if (item) descr->items = item;
1680 /* Repaint the items */
1682 LISTBOX_UpdateScroll( descr );
1683 /* if we removed the scrollbar, reset the top of the list
1684 (correct for owner-drawn ???) */
1685 if (descr->nb_items == descr->page_size)
1686 LISTBOX_SetTopItem( descr, 0, TRUE );
1688 /* Move selection and focused item */
1689 if (!IS_MULTISELECT(descr))
1691 if (index == descr->selected_item)
1692 descr->selected_item = -1;
1693 else if (index < descr->selected_item)
1695 descr->selected_item--;
1696 if (ISWIN31) /* win 31 do not change the selected item number */
1697 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1701 if (descr->focus_item >= descr->nb_items)
1703 descr->focus_item = descr->nb_items - 1;
1704 if (descr->focus_item < 0) descr->focus_item = 0;
1706 return LB_OKAY;
1710 /***********************************************************************
1711 * LISTBOX_ResetContent
1713 static void LISTBOX_ResetContent( LB_DESCR *descr )
1715 INT i;
1717 for (i = 0; i < descr->nb_items; i++) LISTBOX_DeleteItem( descr, i );
1718 if (descr->items) HeapFree( GetProcessHeap(), 0, descr->items );
1719 descr->nb_items = 0;
1720 descr->top_item = 0;
1721 descr->selected_item = -1;
1722 descr->focus_item = 0;
1723 descr->anchor_item = -1;
1724 descr->items = NULL;
1728 /***********************************************************************
1729 * LISTBOX_SetCount
1731 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1733 LRESULT ret;
1735 if (HAS_STRINGS(descr)) return LB_ERR;
1736 /* FIXME: this is far from optimal... */
1737 if (count > descr->nb_items)
1739 while (count > descr->nb_items)
1740 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1741 return ret;
1743 else if (count < descr->nb_items)
1745 while (count < descr->nb_items)
1746 if ((ret = LISTBOX_RemoveItem( descr, -1 )) < 0)
1747 return ret;
1749 return LB_OKAY;
1753 /***********************************************************************
1754 * LISTBOX_Directory
1756 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1757 LPCWSTR filespec, BOOL long_names )
1759 HANDLE handle;
1760 LRESULT ret = LB_OKAY;
1761 WIN32_FIND_DATAW entry;
1762 int pos;
1764 /* don't scan directory if we just want drives exclusively */
1765 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1766 /* scan directory */
1767 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1769 int le = GetLastError();
1770 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1772 else
1776 WCHAR buffer[270];
1777 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1779 static const WCHAR bracketW[] = { ']',0 };
1780 static const WCHAR dotW[] = { '.',0 };
1781 if (!(attrib & DDL_DIRECTORY) ||
1782 !strcmpW( entry.cFileName, dotW )) continue;
1783 buffer[0] = '[';
1784 if (!long_names && entry.cAlternateFileName[0])
1785 strcpyW( buffer + 1, entry.cAlternateFileName );
1786 else
1787 strcpyW( buffer + 1, entry.cFileName );
1788 strcatW(buffer, bracketW);
1790 else /* not a directory */
1792 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1793 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1795 if ((attrib & DDL_EXCLUSIVE) &&
1796 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1797 continue;
1798 #undef ATTRIBS
1799 if (!long_names && entry.cAlternateFileName[0])
1800 strcpyW( buffer, entry.cAlternateFileName );
1801 else
1802 strcpyW( buffer, entry.cFileName );
1804 if (!long_names) CharLowerW( buffer );
1805 pos = LISTBOX_FindFileStrPos( descr, buffer );
1806 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1807 break;
1808 } while (FindNextFileW( handle, &entry ));
1809 FindClose( handle );
1813 /* scan drives */
1814 if ((ret >= 0) && (attrib & DDL_DRIVES))
1816 WCHAR buffer[] = {'[','-','a','-',']',0};
1817 WCHAR root[] = {'A',':','\\',0};
1818 int drive;
1819 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1821 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1822 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1823 break;
1826 return ret;
1830 /***********************************************************************
1831 * LISTBOX_HandleVScroll
1833 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1835 SCROLLINFO info;
1837 if (descr->style & LBS_MULTICOLUMN) return 0;
1838 switch(scrollReq)
1840 case SB_LINEUP:
1841 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1842 break;
1843 case SB_LINEDOWN:
1844 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1845 break;
1846 case SB_PAGEUP:
1847 LISTBOX_SetTopItem( descr, descr->top_item -
1848 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1849 break;
1850 case SB_PAGEDOWN:
1851 LISTBOX_SetTopItem( descr, descr->top_item +
1852 LISTBOX_GetCurrentPageSize( descr ), TRUE );
1853 break;
1854 case SB_THUMBPOSITION:
1855 LISTBOX_SetTopItem( descr, pos, TRUE );
1856 break;
1857 case SB_THUMBTRACK:
1858 info.cbSize = sizeof(info);
1859 info.fMask = SIF_TRACKPOS;
1860 GetScrollInfo( descr->self, SB_VERT, &info );
1861 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1862 break;
1863 case SB_TOP:
1864 LISTBOX_SetTopItem( descr, 0, TRUE );
1865 break;
1866 case SB_BOTTOM:
1867 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1868 break;
1870 return 0;
1874 /***********************************************************************
1875 * LISTBOX_HandleHScroll
1877 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1879 SCROLLINFO info;
1880 INT page;
1882 if (descr->style & LBS_MULTICOLUMN)
1884 switch(scrollReq)
1886 case SB_LINELEFT:
1887 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1888 TRUE );
1889 break;
1890 case SB_LINERIGHT:
1891 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1892 TRUE );
1893 break;
1894 case SB_PAGELEFT:
1895 page = descr->width / descr->column_width;
1896 if (page < 1) page = 1;
1897 LISTBOX_SetTopItem( descr,
1898 descr->top_item - page * descr->page_size, TRUE );
1899 break;
1900 case SB_PAGERIGHT:
1901 page = descr->width / descr->column_width;
1902 if (page < 1) page = 1;
1903 LISTBOX_SetTopItem( descr,
1904 descr->top_item + page * descr->page_size, TRUE );
1905 break;
1906 case SB_THUMBPOSITION:
1907 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1908 break;
1909 case SB_THUMBTRACK:
1910 info.cbSize = sizeof(info);
1911 info.fMask = SIF_TRACKPOS;
1912 GetScrollInfo( descr->self, SB_VERT, &info );
1913 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1914 TRUE );
1915 break;
1916 case SB_LEFT:
1917 LISTBOX_SetTopItem( descr, 0, TRUE );
1918 break;
1919 case SB_RIGHT:
1920 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1921 break;
1924 else if (descr->horz_extent)
1926 switch(scrollReq)
1928 case SB_LINELEFT:
1929 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1930 break;
1931 case SB_LINERIGHT:
1932 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1933 break;
1934 case SB_PAGELEFT:
1935 LISTBOX_SetHorizontalPos( descr,
1936 descr->horz_pos - descr->width );
1937 break;
1938 case SB_PAGERIGHT:
1939 LISTBOX_SetHorizontalPos( descr,
1940 descr->horz_pos + descr->width );
1941 break;
1942 case SB_THUMBPOSITION:
1943 LISTBOX_SetHorizontalPos( descr, pos );
1944 break;
1945 case SB_THUMBTRACK:
1946 info.cbSize = sizeof(info);
1947 info.fMask = SIF_TRACKPOS;
1948 GetScrollInfo( descr->self, SB_HORZ, &info );
1949 LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1950 break;
1951 case SB_LEFT:
1952 LISTBOX_SetHorizontalPos( descr, 0 );
1953 break;
1954 case SB_RIGHT:
1955 LISTBOX_SetHorizontalPos( descr,
1956 descr->horz_extent - descr->width );
1957 break;
1960 return 0;
1963 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1965 short gcWheelDelta = 0;
1966 UINT pulScrollLines = 3;
1968 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1970 gcWheelDelta -= delta;
1972 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1974 int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1975 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1976 LISTBOX_SetTopItem( descr, descr->top_item + cLineScroll, TRUE );
1978 return 0;
1981 /***********************************************************************
1982 * LISTBOX_HandleLButtonDown
1984 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
1986 INT index = LISTBOX_GetItemFromPoint( descr, x, y );
1987 TRACE("[%p]: lbuttondown %d,%d item %d\n", descr->self, x, y, index );
1988 if (!descr->caret_on && (descr->in_focus)) return 0;
1990 if (!descr->in_focus)
1992 if( !descr->lphc ) SetFocus( descr->self );
1993 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
1996 if (index == -1) return 0;
1998 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2000 /* we should perhaps make sure that all items are deselected
2001 FIXME: needed for !LBS_EXTENDEDSEL, too ?
2002 if (!(keys & (MK_SHIFT|MK_CONTROL)))
2003 LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2006 if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2007 if (keys & MK_CONTROL)
2009 LISTBOX_SetCaretIndex( descr, index, FALSE );
2010 LISTBOX_SetSelection( descr, index,
2011 !descr->items[index].selected,
2012 (descr->style & LBS_NOTIFY) != 0);
2014 else
2016 LISTBOX_MoveCaret( descr, index, FALSE );
2018 if (descr->style & LBS_MULTIPLESEL)
2020 LISTBOX_SetSelection( descr, index,
2021 (!(descr->style & LBS_MULTIPLESEL) ||
2022 !descr->items[index].selected),
2023 (descr->style & LBS_NOTIFY) != 0 );
2027 else
2029 descr->anchor_item = index;
2030 LISTBOX_MoveCaret( descr, index, FALSE );
2031 LISTBOX_SetSelection( descr, index,
2032 TRUE, (descr->style & LBS_NOTIFY) != 0 );
2035 descr->captured = TRUE;
2036 SetCapture( descr->self );
2038 if (!descr->lphc)
2040 if (descr->style & LBS_NOTIFY )
2041 SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2042 MAKELPARAM( x, y ) );
2043 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2045 POINT pt;
2047 pt.x = x;
2048 pt.y = y;
2050 if (DragDetect( descr->self, pt ))
2051 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2054 return 0;
2058 /*************************************************************************
2059 * LISTBOX_HandleLButtonDownCombo [Internal]
2061 * Process LButtonDown message for the ComboListBox
2063 * PARAMS
2064 * pWnd [I] The windows internal structure
2065 * pDescr [I] The ListBox internal structure
2066 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2067 * x [I] X Mouse Coordinate
2068 * y [I] Y Mouse Coordinate
2070 * RETURNS
2071 * 0 since we are processing the WM_LBUTTONDOWN Message
2073 * NOTES
2074 * This function is only to be used when a ListBox is a ComboListBox
2077 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2079 RECT clientRect, screenRect;
2080 POINT mousePos;
2082 mousePos.x = x;
2083 mousePos.y = y;
2085 GetClientRect(descr->self, &clientRect);
2087 if(PtInRect(&clientRect, mousePos))
2089 /* MousePos is in client, resume normal processing */
2090 if (msg == WM_LBUTTONDOWN)
2092 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2093 return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2095 else if (descr->style & LBS_NOTIFY)
2096 SEND_NOTIFICATION( descr, LBN_DBLCLK );
2098 else
2100 POINT screenMousePos;
2101 HWND hWndOldCapture;
2103 /* Check the Non-Client Area */
2104 screenMousePos = mousePos;
2105 hWndOldCapture = GetCapture();
2106 ReleaseCapture();
2107 GetWindowRect(descr->self, &screenRect);
2108 ClientToScreen(descr->self, &screenMousePos);
2110 if(!PtInRect(&screenRect, screenMousePos))
2112 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2113 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2114 COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2116 else
2118 /* Check to see the NC is a scrollbar */
2119 INT nHitTestType=0;
2120 LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2121 /* Check Vertical scroll bar */
2122 if (style & WS_VSCROLL)
2124 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2125 if (PtInRect( &clientRect, mousePos ))
2126 nHitTestType = HTVSCROLL;
2128 /* Check horizontal scroll bar */
2129 if (style & WS_HSCROLL)
2131 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2132 if (PtInRect( &clientRect, mousePos ))
2133 nHitTestType = HTHSCROLL;
2135 /* Windows sends this message when a scrollbar is clicked
2138 if(nHitTestType != 0)
2140 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2141 MAKELONG(screenMousePos.x, screenMousePos.y));
2143 /* Resume the Capture after scrolling is complete
2145 if(hWndOldCapture != 0)
2146 SetCapture(hWndOldCapture);
2149 return 0;
2152 /***********************************************************************
2153 * LISTBOX_HandleLButtonUp
2155 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2157 if (LISTBOX_Timer != LB_TIMER_NONE)
2158 KillSystemTimer( descr->self, LB_TIMER_ID );
2159 LISTBOX_Timer = LB_TIMER_NONE;
2160 if (descr->captured)
2162 descr->captured = FALSE;
2163 if (GetCapture() == descr->self) ReleaseCapture();
2164 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2165 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2167 return 0;
2171 /***********************************************************************
2172 * LISTBOX_HandleTimer
2174 * Handle scrolling upon a timer event.
2175 * Return TRUE if scrolling should continue.
2177 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2179 switch(dir)
2181 case LB_TIMER_UP:
2182 if (descr->top_item) index = descr->top_item - 1;
2183 else index = 0;
2184 break;
2185 case LB_TIMER_LEFT:
2186 if (descr->top_item) index -= descr->page_size;
2187 break;
2188 case LB_TIMER_DOWN:
2189 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2190 if (index == descr->focus_item) index++;
2191 if (index >= descr->nb_items) index = descr->nb_items - 1;
2192 break;
2193 case LB_TIMER_RIGHT:
2194 if (index + descr->page_size < descr->nb_items)
2195 index += descr->page_size;
2196 break;
2197 case LB_TIMER_NONE:
2198 break;
2200 if (index == descr->focus_item) return FALSE;
2201 LISTBOX_MoveCaret( descr, index, FALSE );
2202 return TRUE;
2206 /***********************************************************************
2207 * LISTBOX_HandleSystemTimer
2209 * WM_SYSTIMER handler.
2211 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2213 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2215 KillSystemTimer( descr->self, LB_TIMER_ID );
2216 LISTBOX_Timer = LB_TIMER_NONE;
2218 return 0;
2222 /***********************************************************************
2223 * LISTBOX_HandleMouseMove
2225 * WM_MOUSEMOVE handler.
2227 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2228 INT x, INT y )
2230 INT index;
2231 TIMER_DIRECTION dir = LB_TIMER_NONE;
2233 if (!descr->captured) return;
2235 if (descr->style & LBS_MULTICOLUMN)
2237 if (y < 0) y = 0;
2238 else if (y >= descr->item_height * descr->page_size)
2239 y = descr->item_height * descr->page_size - 1;
2241 if (x < 0)
2243 dir = LB_TIMER_LEFT;
2244 x = 0;
2246 else if (x >= descr->width)
2248 dir = LB_TIMER_RIGHT;
2249 x = descr->width - 1;
2252 else
2254 if (y < 0) dir = LB_TIMER_UP; /* above */
2255 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */
2258 index = LISTBOX_GetItemFromPoint( descr, x, y );
2259 if (index == -1) index = descr->focus_item;
2260 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2262 /* Start/stop the system timer */
2264 if (dir != LB_TIMER_NONE)
2265 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2266 else if (LISTBOX_Timer != LB_TIMER_NONE)
2267 KillSystemTimer( descr->self, LB_TIMER_ID );
2268 LISTBOX_Timer = dir;
2272 /***********************************************************************
2273 * LISTBOX_HandleKeyDown
2275 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2277 INT caret = -1;
2278 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2279 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2280 bForceSelection = FALSE; /* only for single select list */
2282 if (descr->style & LBS_WANTKEYBOARDINPUT)
2284 caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2285 MAKEWPARAM(LOWORD(key), descr->focus_item),
2286 (LPARAM)descr->self );
2287 if (caret == -2) return 0;
2289 if (caret == -1) switch(key)
2291 case VK_LEFT:
2292 if (descr->style & LBS_MULTICOLUMN)
2294 bForceSelection = FALSE;
2295 if (descr->focus_item >= descr->page_size)
2296 caret = descr->focus_item - descr->page_size;
2297 break;
2299 /* fall through */
2300 case VK_UP:
2301 caret = descr->focus_item - 1;
2302 if (caret < 0) caret = 0;
2303 break;
2304 case VK_RIGHT:
2305 if (descr->style & LBS_MULTICOLUMN)
2307 bForceSelection = FALSE;
2308 if (descr->focus_item + descr->page_size < descr->nb_items)
2309 caret = descr->focus_item + descr->page_size;
2310 break;
2312 /* fall through */
2313 case VK_DOWN:
2314 caret = descr->focus_item + 1;
2315 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2316 break;
2318 case VK_PRIOR:
2319 if (descr->style & LBS_MULTICOLUMN)
2321 INT page = descr->width / descr->column_width;
2322 if (page < 1) page = 1;
2323 caret = descr->focus_item - (page * descr->page_size) + 1;
2325 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2326 if (caret < 0) caret = 0;
2327 break;
2328 case VK_NEXT:
2329 if (descr->style & LBS_MULTICOLUMN)
2331 INT page = descr->width / descr->column_width;
2332 if (page < 1) page = 1;
2333 caret = descr->focus_item + (page * descr->page_size) - 1;
2335 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2336 if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2337 break;
2338 case VK_HOME:
2339 caret = 0;
2340 break;
2341 case VK_END:
2342 caret = descr->nb_items - 1;
2343 break;
2344 case VK_SPACE:
2345 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2346 else if (descr->style & LBS_MULTIPLESEL)
2348 LISTBOX_SetSelection( descr, descr->focus_item,
2349 !descr->items[descr->focus_item].selected,
2350 (descr->style & LBS_NOTIFY) != 0 );
2352 break;
2353 default:
2354 bForceSelection = FALSE;
2356 if (bForceSelection) /* focused item is used instead of key */
2357 caret = descr->focus_item;
2358 if (caret >= 0)
2360 if (((descr->style & LBS_EXTENDEDSEL) &&
2361 !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2362 !IS_MULTISELECT(descr))
2363 descr->anchor_item = caret;
2364 LISTBOX_MoveCaret( descr, caret, TRUE );
2366 if (descr->style & LBS_MULTIPLESEL)
2367 descr->selected_item = caret;
2368 else
2369 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2370 if (descr->style & LBS_NOTIFY)
2372 if( descr->lphc )
2374 /* make sure that combo parent doesn't hide us */
2375 descr->lphc->wState |= CBF_NOROLLUP;
2377 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2380 return 0;
2384 /***********************************************************************
2385 * LISTBOX_HandleChar
2387 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2389 INT caret = -1;
2390 WCHAR str[2];
2392 str[0] = charW;
2393 str[1] = '\0';
2395 if (descr->style & LBS_WANTKEYBOARDINPUT)
2397 caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2398 MAKEWPARAM(charW, descr->focus_item),
2399 (LPARAM)descr->self );
2400 if (caret == -2) return 0;
2402 if (caret == -1)
2403 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2404 if (caret != -1)
2406 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2407 LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2408 LISTBOX_MoveCaret( descr, caret, TRUE );
2409 if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2410 SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2412 return 0;
2416 /***********************************************************************
2417 * LISTBOX_Create
2419 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2421 LB_DESCR *descr;
2422 MEASUREITEMSTRUCT mis;
2423 RECT rect;
2425 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2426 return FALSE;
2428 GetClientRect( hwnd, &rect );
2429 descr->self = hwnd;
2430 descr->owner = GetParent( descr->self );
2431 descr->style = GetWindowLongW( descr->self, GWL_STYLE );
2432 descr->width = rect.right - rect.left;
2433 descr->height = rect.bottom - rect.top;
2434 descr->items = NULL;
2435 descr->nb_items = 0;
2436 descr->top_item = 0;
2437 descr->selected_item = -1;
2438 descr->focus_item = 0;
2439 descr->anchor_item = -1;
2440 descr->item_height = 1;
2441 descr->page_size = 1;
2442 descr->column_width = 150;
2443 descr->horz_extent = (descr->style & WS_HSCROLL) ? 1 : 0;
2444 descr->horz_pos = 0;
2445 descr->nb_tabs = 0;
2446 descr->tabs = NULL;
2447 descr->caret_on = lphc ? FALSE : TRUE;
2448 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2449 descr->in_focus = FALSE;
2450 descr->captured = FALSE;
2451 descr->font = 0;
2452 descr->locale = 0; /* FIXME */
2453 descr->lphc = lphc;
2455 if (is_old_app(descr) && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2457 /* Win95 document "List Box Differences" from MSDN:
2458 If a list box in a version 3.x application has either the
2459 WS_HSCROLL or WS_VSCROLL style, the list box receives both
2460 horizontal and vertical scroll bars.
2462 descr->style |= WS_VSCROLL | WS_HSCROLL;
2465 if( lphc )
2467 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2468 descr->owner = lphc->self;
2471 SetWindowLongW( descr->self, 0, (LONG)descr );
2473 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2475 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2476 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2477 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2478 descr->item_height = LISTBOX_SetFont( descr, 0 );
2480 if (descr->style & LBS_OWNERDRAWFIXED)
2482 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2484 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2485 descr->item_height = lphc->fixedOwnerDrawHeight;
2487 else
2489 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2490 mis.CtlType = ODT_LISTBOX;
2491 mis.CtlID = id;
2492 mis.itemID = -1;
2493 mis.itemWidth = 0;
2494 mis.itemData = 0;
2495 mis.itemHeight = descr->item_height;
2496 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2497 descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2501 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2502 return TRUE;
2506 /***********************************************************************
2507 * LISTBOX_Destroy
2509 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2511 LISTBOX_ResetContent( descr );
2512 SetWindowLongW( descr->self, 0, 0 );
2513 HeapFree( GetProcessHeap(), 0, descr );
2514 return TRUE;
2518 /***********************************************************************
2519 * ListBoxWndProc_common
2521 static LRESULT WINAPI ListBoxWndProc_common( HWND hwnd, UINT msg,
2522 WPARAM wParam, LPARAM lParam, BOOL unicode )
2524 LB_DESCR *descr = (LB_DESCR *)GetWindowLongW( hwnd, 0 );
2525 LPHEADCOMBO lphc = 0;
2526 LRESULT ret;
2528 if (!descr)
2530 if (!IsWindow(hwnd)) return 0;
2532 if (msg == WM_CREATE)
2534 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2535 if (lpcs->style & LBS_COMBOBOX) lphc = (LPHEADCOMBO)lpcs->lpCreateParams;
2536 if (!LISTBOX_Create( hwnd, lphc )) return -1;
2537 TRACE("creating wnd=%p descr=%lx\n", hwnd, GetWindowLongW( hwnd, 0 ) );
2538 return 0;
2540 /* Ignore all other messages before we get a WM_CREATE */
2541 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2542 DefWindowProcA( hwnd, msg, wParam, lParam );
2544 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2546 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
2547 descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2549 switch(msg)
2551 case LB_RESETCONTENT16:
2552 case LB_RESETCONTENT:
2553 LISTBOX_ResetContent( descr );
2554 LISTBOX_UpdateScroll( descr );
2555 InvalidateRect( descr->self, NULL, TRUE );
2556 return 0;
2558 case LB_ADDSTRING16:
2559 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2560 /* fall through */
2561 case LB_ADDSTRING:
2563 INT ret;
2564 LPWSTR textW;
2565 if(unicode || !HAS_STRINGS(descr))
2566 textW = (LPWSTR)lParam;
2567 else
2569 LPSTR textA = (LPSTR)lParam;
2570 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2571 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2572 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2574 wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2575 ret = LISTBOX_InsertString( descr, wParam, textW );
2576 if (!unicode && HAS_STRINGS(descr))
2577 HeapFree(GetProcessHeap(), 0, textW);
2578 return ret;
2581 case LB_INSERTSTRING16:
2582 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2583 wParam = (INT)(INT16)wParam;
2584 /* fall through */
2585 case LB_INSERTSTRING:
2587 INT ret;
2588 LPWSTR textW;
2589 if(unicode || !HAS_STRINGS(descr))
2590 textW = (LPWSTR)lParam;
2591 else
2593 LPSTR textA = (LPSTR)lParam;
2594 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2595 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2596 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2598 ret = LISTBOX_InsertString( descr, wParam, textW );
2599 if(!unicode && HAS_STRINGS(descr))
2600 HeapFree(GetProcessHeap(), 0, textW);
2601 return ret;
2604 case LB_ADDFILE16:
2605 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2606 /* fall through */
2607 case LB_ADDFILE:
2609 INT ret;
2610 LPWSTR textW;
2611 if(unicode || !HAS_STRINGS(descr))
2612 textW = (LPWSTR)lParam;
2613 else
2615 LPSTR textA = (LPSTR)lParam;
2616 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2617 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2618 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2620 wParam = LISTBOX_FindFileStrPos( descr, textW );
2621 ret = LISTBOX_InsertString( descr, wParam, textW );
2622 if(!unicode && HAS_STRINGS(descr))
2623 HeapFree(GetProcessHeap(), 0, textW);
2624 return ret;
2627 case LB_DELETESTRING16:
2628 case LB_DELETESTRING:
2629 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2630 return descr->nb_items;
2631 else
2632 return LB_ERR;
2634 case LB_GETITEMDATA16:
2635 case LB_GETITEMDATA:
2636 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2637 return LB_ERR;
2638 return descr->items[wParam].data;
2640 case LB_SETITEMDATA16:
2641 case LB_SETITEMDATA:
2642 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2643 return LB_ERR;
2644 descr->items[wParam].data = (DWORD)lParam;
2645 return LB_OKAY;
2647 case LB_GETCOUNT16:
2648 case LB_GETCOUNT:
2649 return descr->nb_items;
2651 case LB_GETTEXT16:
2652 lParam = (LPARAM)MapSL(lParam);
2653 /* fall through */
2654 case LB_GETTEXT:
2655 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2657 case LB_GETTEXTLEN16:
2658 /* fall through */
2659 case LB_GETTEXTLEN:
2660 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2661 return LB_ERR;
2662 if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2663 if (unicode) return strlenW( descr->items[wParam].str );
2664 return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2665 strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2667 case LB_GETCURSEL16:
2668 case LB_GETCURSEL:
2669 if (descr->nb_items == 0)
2670 return LB_ERR;
2671 if (!IS_MULTISELECT(descr))
2672 return descr->selected_item;
2673 if (descr->selected_item != -1)
2674 return descr->selected_item;
2675 return descr->focus_item;
2676 /* otherwise, if the user tries to move the selection with the */
2677 /* arrow keys, we will give the application something to choke on */
2678 case LB_GETTOPINDEX16:
2679 case LB_GETTOPINDEX:
2680 return descr->top_item;
2682 case LB_GETITEMHEIGHT16:
2683 case LB_GETITEMHEIGHT:
2684 return LISTBOX_GetItemHeight( descr, wParam );
2686 case LB_SETITEMHEIGHT16:
2687 lParam = LOWORD(lParam);
2688 /* fall through */
2689 case LB_SETITEMHEIGHT:
2690 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2692 case LB_ITEMFROMPOINT:
2694 POINT pt;
2695 RECT rect;
2697 pt.x = LOWORD(lParam);
2698 pt.y = HIWORD(lParam);
2699 rect.left = 0;
2700 rect.top = 0;
2701 rect.right = descr->width;
2702 rect.bottom = descr->height;
2704 return MAKELONG( LISTBOX_GetItemFromPoint(descr, pt.x, pt.y),
2705 !PtInRect( &rect, pt ) );
2708 case LB_SETCARETINDEX16:
2709 case LB_SETCARETINDEX:
2710 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2711 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2712 return LB_ERR;
2713 else if (ISWIN31)
2714 return wParam;
2715 else
2716 return LB_OKAY;
2718 case LB_GETCARETINDEX16:
2719 case LB_GETCARETINDEX:
2720 return descr->focus_item;
2722 case LB_SETTOPINDEX16:
2723 case LB_SETTOPINDEX:
2724 return LISTBOX_SetTopItem( descr, wParam, TRUE );
2726 case LB_SETCOLUMNWIDTH16:
2727 case LB_SETCOLUMNWIDTH:
2728 return LISTBOX_SetColumnWidth( descr, wParam );
2730 case LB_GETITEMRECT16:
2732 RECT rect;
2733 RECT16 *r16 = MapSL(lParam);
2734 ret = LISTBOX_GetItemRect( descr, (INT16)wParam, &rect );
2735 r16->left = rect.left;
2736 r16->top = rect.top;
2737 r16->right = rect.right;
2738 r16->bottom = rect.bottom;
2740 return ret;
2742 case LB_GETITEMRECT:
2743 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2745 case LB_FINDSTRING16:
2746 wParam = (INT)(INT16)wParam;
2747 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2748 /* fall through */
2749 case LB_FINDSTRING:
2751 INT ret;
2752 LPWSTR textW;
2753 if(unicode || !HAS_STRINGS(descr))
2754 textW = (LPWSTR)lParam;
2755 else
2757 LPSTR textA = (LPSTR)lParam;
2758 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2759 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2760 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2762 ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2763 if(!unicode && HAS_STRINGS(descr))
2764 HeapFree(GetProcessHeap(), 0, textW);
2765 return ret;
2768 case LB_FINDSTRINGEXACT16:
2769 wParam = (INT)(INT16)wParam;
2770 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2771 /* fall through */
2772 case LB_FINDSTRINGEXACT:
2774 INT ret;
2775 LPWSTR textW;
2776 if(unicode || !HAS_STRINGS(descr))
2777 textW = (LPWSTR)lParam;
2778 else
2780 LPSTR textA = (LPSTR)lParam;
2781 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2782 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2783 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2785 ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2786 if(!unicode && HAS_STRINGS(descr))
2787 HeapFree(GetProcessHeap(), 0, textW);
2788 return ret;
2791 case LB_SELECTSTRING16:
2792 wParam = (INT)(INT16)wParam;
2793 if (HAS_STRINGS(descr)) lParam = (LPARAM)MapSL(lParam);
2794 /* fall through */
2795 case LB_SELECTSTRING:
2797 INT index;
2798 LPWSTR textW;
2800 if(HAS_STRINGS(descr))
2801 TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2802 debugstr_a((LPSTR)lParam));
2803 if(unicode || !HAS_STRINGS(descr))
2804 textW = (LPWSTR)lParam;
2805 else
2807 LPSTR textA = (LPSTR)lParam;
2808 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2809 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2810 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2812 index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2813 if(!unicode && HAS_STRINGS(descr))
2814 HeapFree(GetProcessHeap(), 0, textW);
2815 if (index != LB_ERR)
2817 LISTBOX_MoveCaret( descr, index, TRUE );
2818 LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2820 return index;
2823 case LB_GETSEL16:
2824 wParam = (INT)(INT16)wParam;
2825 /* fall through */
2826 case LB_GETSEL:
2827 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2828 return LB_ERR;
2829 return descr->items[wParam].selected;
2831 case LB_SETSEL16:
2832 lParam = (INT)(INT16)lParam;
2833 /* fall through */
2834 case LB_SETSEL:
2835 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2837 case LB_SETCURSEL16:
2838 wParam = (INT)(INT16)wParam;
2839 /* fall through */
2840 case LB_SETCURSEL:
2841 if (IS_MULTISELECT(descr)) return LB_ERR;
2842 LISTBOX_SetCaretIndex( descr, wParam, TRUE );
2843 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2844 if (lphc && ret != LB_ERR) ret = descr->selected_item;
2845 return ret;
2847 case LB_GETSELCOUNT16:
2848 case LB_GETSELCOUNT:
2849 return LISTBOX_GetSelCount( descr );
2851 case LB_GETSELITEMS16:
2852 return LISTBOX_GetSelItems16( descr, wParam, (LPINT16)MapSL(lParam) );
2854 case LB_GETSELITEMS:
2855 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2857 case LB_SELITEMRANGE16:
2858 case LB_SELITEMRANGE:
2859 if (LOWORD(lParam) <= HIWORD(lParam))
2860 return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2861 HIWORD(lParam), wParam );
2862 else
2863 return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2864 LOWORD(lParam), wParam );
2866 case LB_SELITEMRANGEEX16:
2867 case LB_SELITEMRANGEEX:
2868 if ((INT)lParam >= (INT)wParam)
2869 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2870 else
2871 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2873 case LB_GETHORIZONTALEXTENT16:
2874 case LB_GETHORIZONTALEXTENT:
2875 return descr->horz_extent;
2877 case LB_SETHORIZONTALEXTENT16:
2878 case LB_SETHORIZONTALEXTENT:
2879 return LISTBOX_SetHorizontalExtent( descr, wParam );
2881 case LB_GETANCHORINDEX16:
2882 case LB_GETANCHORINDEX:
2883 return descr->anchor_item;
2885 case LB_SETANCHORINDEX16:
2886 wParam = (INT)(INT16)wParam;
2887 /* fall through */
2888 case LB_SETANCHORINDEX:
2889 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2890 return LB_ERR;
2891 descr->anchor_item = (INT)wParam;
2892 return LB_OKAY;
2894 case LB_DIR16:
2895 /* according to Win16 docs, DDL_DRIVES should make DDL_EXCLUSIVE
2896 * be set automatically (this is different in Win32) */
2897 if (wParam & DDL_DRIVES) wParam |= DDL_EXCLUSIVE;
2898 lParam = (LPARAM)MapSL(lParam);
2899 /* fall through */
2900 case LB_DIR:
2902 INT ret;
2903 LPWSTR textW;
2904 if(unicode)
2905 textW = (LPWSTR)lParam;
2906 else
2908 LPSTR textA = (LPSTR)lParam;
2909 INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2910 if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2911 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2913 ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2914 if(!unicode)
2915 HeapFree(GetProcessHeap(), 0, textW);
2916 return ret;
2919 case LB_GETLOCALE:
2920 return descr->locale;
2922 case LB_SETLOCALE:
2923 descr->locale = (LCID)wParam; /* FIXME: should check for valid lcid */
2924 return LB_OKAY;
2926 case LB_INITSTORAGE:
2927 return LISTBOX_InitStorage( descr, wParam );
2929 case LB_SETCOUNT:
2930 return LISTBOX_SetCount( descr, (INT)wParam );
2932 case LB_SETTABSTOPS16:
2933 return LISTBOX_SetTabStops( descr, (INT)(INT16)wParam, MapSL(lParam), TRUE );
2935 case LB_SETTABSTOPS:
2936 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam, FALSE );
2938 case LB_CARETON16:
2939 case LB_CARETON:
2940 if (descr->caret_on)
2941 return LB_OKAY;
2942 descr->caret_on = TRUE;
2943 if ((descr->focus_item != -1) && (descr->in_focus))
2944 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2945 return LB_OKAY;
2947 case LB_CARETOFF16:
2948 case LB_CARETOFF:
2949 if (!descr->caret_on)
2950 return LB_OKAY;
2951 descr->caret_on = FALSE;
2952 if ((descr->focus_item != -1) && (descr->in_focus))
2953 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2954 return LB_OKAY;
2956 case WM_DESTROY:
2957 return LISTBOX_Destroy( descr );
2959 case WM_ENABLE:
2960 InvalidateRect( descr->self, NULL, TRUE );
2961 return 0;
2963 case WM_SETREDRAW:
2964 LISTBOX_SetRedraw( descr, wParam != 0 );
2965 return 0;
2967 case WM_GETDLGCODE:
2968 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2970 case WM_PAINT:
2972 PAINTSTRUCT ps;
2973 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps );
2974 ret = LISTBOX_Paint( descr, hdc );
2975 if( !wParam ) EndPaint( descr->self, &ps );
2977 return ret;
2978 case WM_SIZE:
2979 LISTBOX_UpdateSize( descr );
2980 return 0;
2981 case WM_GETFONT:
2982 return (LRESULT)descr->font;
2983 case WM_SETFONT:
2984 LISTBOX_SetFont( descr, (HFONT)wParam );
2985 if (lParam) InvalidateRect( descr->self, 0, TRUE );
2986 return 0;
2987 case WM_SETFOCUS:
2988 descr->in_focus = TRUE;
2989 descr->caret_on = TRUE;
2990 if (descr->focus_item != -1)
2991 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2992 SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2993 return 0;
2994 case WM_KILLFOCUS:
2995 descr->in_focus = FALSE;
2996 if ((descr->focus_item != -1) && descr->caret_on)
2997 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2998 SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
2999 return 0;
3000 case WM_HSCROLL:
3001 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3002 case WM_VSCROLL:
3003 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3004 case WM_MOUSEWHEEL:
3005 if (wParam & (MK_SHIFT | MK_CONTROL))
3006 return DefWindowProcW( descr->self, msg, wParam, lParam );
3007 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3008 case WM_LBUTTONDOWN:
3009 if (lphc)
3010 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3011 (INT16)LOWORD(lParam),
3012 (INT16)HIWORD(lParam) );
3013 return LISTBOX_HandleLButtonDown( descr, wParam,
3014 (INT16)LOWORD(lParam),
3015 (INT16)HIWORD(lParam) );
3016 case WM_LBUTTONDBLCLK:
3017 if (lphc)
3018 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3019 (INT16)LOWORD(lParam),
3020 (INT16)HIWORD(lParam) );
3021 if (descr->style & LBS_NOTIFY)
3022 SEND_NOTIFICATION( descr, LBN_DBLCLK );
3023 return 0;
3024 case WM_MOUSEMOVE:
3025 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3027 BOOL captured = descr->captured;
3028 POINT mousePos;
3029 RECT clientRect;
3031 mousePos.x = (INT16)LOWORD(lParam);
3032 mousePos.y = (INT16)HIWORD(lParam);
3035 * If we are in a dropdown combobox, we simulate that
3036 * the mouse is captured to show the tracking of the item.
3038 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3039 descr->captured = TRUE;
3041 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3043 descr->captured = captured;
3045 else if (GetCapture() == descr->self)
3047 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3048 (INT16)HIWORD(lParam) );
3050 return 0;
3051 case WM_LBUTTONUP:
3052 if (lphc)
3054 POINT mousePos;
3055 RECT clientRect;
3058 * If the mouse button "up" is not in the listbox,
3059 * we make sure there is no selection by re-selecting the
3060 * item that was selected when the listbox was made visible.
3062 mousePos.x = (INT16)LOWORD(lParam);
3063 mousePos.y = (INT16)HIWORD(lParam);
3065 GetClientRect(descr->self, &clientRect);
3068 * When the user clicks outside the combobox and the focus
3069 * is lost, the owning combobox will send a fake buttonup with
3070 * 0xFFFFFFF as the mouse location, we must also revert the
3071 * selection to the original selection.
3073 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3074 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3076 return LISTBOX_HandleLButtonUp( descr );
3077 case WM_KEYDOWN:
3078 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3080 /* for some reason Windows makes it possible to
3081 * show/hide ComboLBox by sending it WM_KEYDOWNs */
3083 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3084 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3085 && (wParam == VK_DOWN || wParam == VK_UP)) )
3087 COMBO_FlipListbox( lphc, FALSE, FALSE );
3088 return 0;
3091 return LISTBOX_HandleKeyDown( descr, wParam );
3092 case WM_CHAR:
3094 WCHAR charW;
3095 if(unicode)
3096 charW = (WCHAR)wParam;
3097 else
3099 CHAR charA = (CHAR)wParam;
3100 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3102 return LISTBOX_HandleChar( descr, charW );
3104 case WM_SYSTIMER:
3105 return LISTBOX_HandleSystemTimer( descr );
3106 case WM_ERASEBKGND:
3107 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3109 RECT rect;
3110 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3111 wParam, (LPARAM)descr->self );
3112 TRACE("hbrush = %p\n", hbrush);
3113 if(!hbrush)
3114 hbrush = GetSysColorBrush(COLOR_WINDOW);
3115 if(hbrush)
3117 GetClientRect(descr->self, &rect);
3118 FillRect((HDC)wParam, &rect, hbrush);
3121 return 1;
3122 case WM_DROPFILES:
3123 if( lphc ) return 0;
3124 return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3125 SendMessageA( descr->owner, msg, wParam, lParam );
3127 case WM_NCDESTROY:
3128 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3129 lphc->hWndLBox = 0;
3130 break;
3132 case WM_NCACTIVATE:
3133 if (lphc) return 0;
3134 break;
3136 default:
3137 if ((msg >= WM_USER) && (msg < 0xc000))
3138 WARN("[%p]: unknown msg %04x wp %08x lp %08lx\n",
3139 hwnd, msg, wParam, lParam );
3142 return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3143 DefWindowProcA( hwnd, msg, wParam, lParam );
3146 /***********************************************************************
3147 * ListBoxWndProcA
3149 static LRESULT WINAPI ListBoxWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3151 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, FALSE );
3154 /***********************************************************************
3155 * ListBoxWndProcW
3157 static LRESULT WINAPI ListBoxWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
3159 return ListBoxWndProc_common( hwnd, msg, wParam, lParam, TRUE );