comctl32/imagelist: Use proper color format for merged image lists.
[wine] / dlls / user32 / listbox.c
1 /*
2  * Listbox controls
3  *
4  * Copyright 1996 Alexandre Julliard
5  *
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.
10  *
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.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * NOTES
21  *
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.
24  * 
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.
28  *
29  * TODO:
30  *    - GetListBoxInfo()
31  *    - LB_GETLISTBOXINFO
32  *    - LBS_NODATA
33  */
34
35 #include <string.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include "windef.h"
40 #include "winbase.h"
41 #include "wingdi.h"
42 #include "wine/unicode.h"
43 #include "user_private.h"
44 #include "controls.h"
45 #include "wine/exception.h"
46 #include "wine/debug.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
49
50 /* Items array granularity */
51 #define LB_ARRAY_GRANULARITY 16
52
53 /* Scrolling timeout in ms */
54 #define LB_SCROLL_TIMEOUT 50
55
56 /* Listbox system timer id */
57 #define LB_TIMER_ID  2
58
59 /* flag listbox changed while setredraw false - internal style */
60 #define LBS_DISPLAYCHANGED 0x80000000
61
62 /* Item structure */
63 typedef struct
64 {
65     LPWSTR    str;       /* Item text */
66     BOOL      selected;  /* Is item selected? */
67     UINT      height;    /* Item height (only for OWNERDRAWVARIABLE) */
68     ULONG_PTR data;      /* User data */
69 } LB_ITEMDATA;
70
71 /* Listbox structure */
72 typedef struct
73 {
74     HWND        self;           /* Our own window handle */
75     HWND        owner;          /* Owner window to send notifications to */
76     UINT        style;          /* Window style */
77     INT         width;          /* Window width */
78     INT         height;         /* Window height */
79     LB_ITEMDATA  *items;        /* Array of items */
80     INT         nb_items;       /* Number of items */
81     INT         top_item;       /* Top visible item */
82     INT         selected_item;  /* Selected item */
83     INT         focus_item;     /* Item that has the focus */
84     INT         anchor_item;    /* Anchor item for extended selection */
85     INT         item_height;    /* Default item height */
86     INT         page_size;      /* Items per listbox page */
87     INT         column_width;   /* Column width for multi-column listboxes */
88     INT         horz_extent;    /* Horizontal extent (0 if no hscroll) */
89     INT         horz_pos;       /* Horizontal position */
90     INT         nb_tabs;        /* Number of tabs in array */
91     INT        *tabs;           /* Array of tabs */
92     INT         avg_char_width; /* Average width of characters */
93     INT         wheel_remain;   /* Left over scroll amount */
94     BOOL        caret_on;       /* Is caret on? */
95     BOOL        captured;       /* Is mouse captured? */
96     BOOL        in_focus;
97     HFONT       font;           /* Current font */
98     LCID          locale;       /* Current locale for string comparisons */
99     LPHEADCOMBO   lphc;         /* ComboLBox */
100 } LB_DESCR;
101
102
103 #define IS_OWNERDRAW(descr) \
104     ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
105
106 #define HAS_STRINGS(descr) \
107     (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108
109
110 #define IS_MULTISELECT(descr) \
111     ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
112      !((descr)->style & LBS_NOSEL))
113
114 #define SEND_NOTIFICATION(descr,code) \
115     (SendMessageW( (descr)->owner, WM_COMMAND, \
116      MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
117
118 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
119
120 /* Current timer status */
121 typedef enum
122 {
123     LB_TIMER_NONE,
124     LB_TIMER_UP,
125     LB_TIMER_LEFT,
126     LB_TIMER_DOWN,
127     LB_TIMER_RIGHT
128 } TIMER_DIRECTION;
129
130 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
131
132 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
133
134 /*********************************************************************
135  * listbox class descriptor
136  */
137 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
138 const struct builtin_class_descr LISTBOX_builtin_class =
139 {
140     listboxW,             /* name */
141     CS_DBLCLKS /*| CS_PARENTDC*/,  /* style */
142     WINPROC_LISTBOX,      /* proc */
143     sizeof(LB_DESCR *),   /* extra */
144     IDC_ARROW,            /* cursor */
145     0                     /* brush */
146 };
147
148
149 /*********************************************************************
150  * combolbox class descriptor
151  */
152 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
153 const struct builtin_class_descr COMBOLBOX_builtin_class =
154 {
155     combolboxW,           /* name */
156     CS_DBLCLKS | CS_SAVEBITS,  /* style */
157     WINPROC_LISTBOX,      /* proc */
158     sizeof(LB_DESCR *),   /* extra */
159     IDC_ARROW,            /* cursor */
160     0                     /* brush */
161 };
162
163
164 /***********************************************************************
165  *           LISTBOX_GetCurrentPageSize
166  *
167  * Return the current page size
168  */
169 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
170 {
171     INT i, height;
172     if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
173     for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
174     {
175         if ((height += descr->items[i].height) > descr->height) break;
176     }
177     if (i == descr->top_item) return 1;
178     else return i - descr->top_item;
179 }
180
181
182 /***********************************************************************
183  *           LISTBOX_GetMaxTopIndex
184  *
185  * Return the maximum possible index for the top of the listbox.
186  */
187 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
188 {
189     INT max, page;
190
191     if (descr->style & LBS_OWNERDRAWVARIABLE)
192     {
193         page = descr->height;
194         for (max = descr->nb_items - 1; max >= 0; max--)
195             if ((page -= descr->items[max].height) < 0) break;
196         if (max < descr->nb_items - 1) max++;
197     }
198     else if (descr->style & LBS_MULTICOLUMN)
199     {
200         if ((page = descr->width / descr->column_width) < 1) page = 1;
201         max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
202         max = (max - page) * descr->page_size;
203     }
204     else
205     {
206         max = descr->nb_items - descr->page_size;
207     }
208     if (max < 0) max = 0;
209     return max;
210 }
211
212
213 /***********************************************************************
214  *           LISTBOX_UpdateScroll
215  *
216  * Update the scrollbars. Should be called whenever the content
217  * of the listbox changes.
218  */
219 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
220 {
221     SCROLLINFO info;
222
223     /* Check the listbox scroll bar flags individually before we call
224        SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
225        no WS_VSCROLL, we end up with an uninitialized, visible horizontal
226        scroll bar when we do not need one.
227     if (!(descr->style & WS_VSCROLL)) return;
228     */
229
230     /*   It is important that we check descr->style, and not wnd->dwStyle,
231        for WS_VSCROLL, as the former is exactly the one passed in
232        argument to CreateWindow.
233          In Windows (and from now on in Wine :) a listbox created
234        with such a style (no WS_SCROLL) does not update
235        the scrollbar with listbox-related data, thus letting
236        the programmer use it for his/her own purposes. */
237
238     if (descr->style & LBS_NOREDRAW) return;
239     info.cbSize = sizeof(info);
240
241     if (descr->style & LBS_MULTICOLUMN)
242     {
243         info.nMin  = 0;
244         info.nMax  = (descr->nb_items - 1) / descr->page_size;
245         info.nPos  = descr->top_item / descr->page_size;
246         info.nPage = descr->width / descr->column_width;
247         if (info.nPage < 1) info.nPage = 1;
248         info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
249         if (descr->style & LBS_DISABLENOSCROLL)
250             info.fMask |= SIF_DISABLENOSCROLL;
251         if (descr->style & WS_HSCROLL)
252             SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
253         info.nMax = 0;
254         info.fMask = SIF_RANGE;
255         if (descr->style & WS_VSCROLL)
256             SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
257     }
258     else
259     {
260         info.nMin  = 0;
261         info.nMax  = descr->nb_items - 1;
262         info.nPos  = descr->top_item;
263         info.nPage = LISTBOX_GetCurrentPageSize( descr );
264         info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
265         if (descr->style & LBS_DISABLENOSCROLL)
266             info.fMask |= SIF_DISABLENOSCROLL;
267         if (descr->style & WS_VSCROLL)
268             SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
269
270         if (descr->horz_extent)
271         {
272             info.nMin  = 0;
273             info.nMax  = descr->horz_extent - 1;
274             info.nPos  = descr->horz_pos;
275             info.nPage = descr->width;
276             info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
277             if (descr->style & LBS_DISABLENOSCROLL)
278                 info.fMask |= SIF_DISABLENOSCROLL;
279             if (descr->style & WS_HSCROLL)
280                 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
281         }
282     }
283 }
284
285
286 /***********************************************************************
287  *           LISTBOX_SetTopItem
288  *
289  * Set the top item of the listbox, scrolling up or down if necessary.
290  */
291 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
292 {
293     INT max = LISTBOX_GetMaxTopIndex( descr );
294
295     TRACE("setting top item %d, scroll %d\n", index, scroll);
296
297     if (index > max) index = max;
298     if (index < 0) index = 0;
299     if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
300     if (descr->top_item == index) return LB_OKAY;
301     if (scroll)
302     {
303         INT diff;
304         if (descr->style & LBS_MULTICOLUMN)
305             diff = (descr->top_item - index) / descr->page_size * descr->column_width;
306         else if (descr->style & LBS_OWNERDRAWVARIABLE)
307         {
308             INT i;
309             diff = 0;
310             if (index > descr->top_item)
311             {
312                 for (i = index - 1; i >= descr->top_item; i--)
313                     diff -= descr->items[i].height;
314             }
315             else
316             {
317                 for (i = index; i < descr->top_item; i++)
318                     diff += descr->items[i].height;
319             }
320         }
321         else
322             diff = (descr->top_item - index) * descr->item_height;
323
324         ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
325                         SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
326     }
327     if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
328     descr->top_item = index;
329     LISTBOX_UpdateScroll( descr );
330     return LB_OKAY;
331 }
332
333
334 /***********************************************************************
335  *           LISTBOX_UpdatePage
336  *
337  * Update the page size. Should be called when the size of
338  * the client area or the item height changes.
339  */
340 static void LISTBOX_UpdatePage( LB_DESCR *descr )
341 {
342     INT page_size;
343
344     if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
345                        page_size = 1;
346     if (page_size == descr->page_size) return;
347     descr->page_size = page_size;
348     if (descr->style & LBS_MULTICOLUMN)
349         InvalidateRect( descr->self, NULL, TRUE );
350     LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
351 }
352
353
354 /***********************************************************************
355  *           LISTBOX_UpdateSize
356  *
357  * Update the size of the listbox. Should be called when the size of
358  * the client area changes.
359  */
360 static void LISTBOX_UpdateSize( LB_DESCR *descr )
361 {
362     RECT rect;
363
364     GetClientRect( descr->self, &rect );
365     descr->width  = rect.right - rect.left;
366     descr->height = rect.bottom - rect.top;
367     if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
368     {
369         INT remaining;
370         RECT rect;
371
372         GetWindowRect( descr->self, &rect );
373         if(descr->item_height != 0)
374             remaining = descr->height % descr->item_height;
375         else
376             remaining = 0;
377         if ((descr->height > descr->item_height) && remaining)
378         {
379             TRACE("[%p]: changing height %d -> %d\n",
380                   descr->self, descr->height, descr->height - remaining );
381             SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
382                           rect.bottom - rect.top - remaining,
383                           SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
384             return;
385         }
386     }
387     TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
388     LISTBOX_UpdatePage( descr );
389     LISTBOX_UpdateScroll( descr );
390
391     /* Invalidate the focused item so it will be repainted correctly */
392     if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
393     {
394         InvalidateRect( descr->self, &rect, FALSE );
395     }
396 }
397
398
399 /***********************************************************************
400  *           LISTBOX_GetItemRect
401  *
402  * Get the rectangle enclosing an item, in listbox client coordinates.
403  * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
404  */
405 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
406 {
407     /* Index <= 0 is legal even on empty listboxes */
408     if (index && (index >= descr->nb_items))
409     {
410         memset(rect, 0, sizeof(*rect));
411         SetLastError(ERROR_INVALID_INDEX);
412         return LB_ERR;
413     }
414     SetRect( rect, 0, 0, descr->width, descr->height );
415     if (descr->style & LBS_MULTICOLUMN)
416     {
417         INT col = (index / descr->page_size) -
418                         (descr->top_item / descr->page_size);
419         rect->left += col * descr->column_width;
420         rect->right = rect->left + descr->column_width;
421         rect->top += (index % descr->page_size) * descr->item_height;
422         rect->bottom = rect->top + descr->item_height;
423     }
424     else if (descr->style & LBS_OWNERDRAWVARIABLE)
425     {
426         INT i;
427         rect->right += descr->horz_pos;
428         if ((index >= 0) && (index < descr->nb_items))
429         {
430             if (index < descr->top_item)
431             {
432                 for (i = descr->top_item-1; i >= index; i--)
433                     rect->top -= descr->items[i].height;
434             }
435             else
436             {
437                 for (i = descr->top_item; i < index; i++)
438                     rect->top += descr->items[i].height;
439             }
440             rect->bottom = rect->top + descr->items[index].height;
441
442         }
443     }
444     else
445     {
446         rect->top += (index - descr->top_item) * descr->item_height;
447         rect->bottom = rect->top + descr->item_height;
448         rect->right += descr->horz_pos;
449     }
450
451     TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
452
453     return ((rect->left < descr->width) && (rect->right > 0) &&
454             (rect->top < descr->height) && (rect->bottom > 0));
455 }
456
457
458 /***********************************************************************
459  *           LISTBOX_GetItemFromPoint
460  *
461  * Return the item nearest from point (x,y) (in client coordinates).
462  */
463 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
464 {
465     INT index = descr->top_item;
466
467     if (!descr->nb_items) return -1;  /* No items */
468     if (descr->style & LBS_OWNERDRAWVARIABLE)
469     {
470         INT pos = 0;
471         if (y >= 0)
472         {
473             while (index < descr->nb_items)
474             {
475                 if ((pos += descr->items[index].height) > y) break;
476                 index++;
477             }
478         }
479         else
480         {
481             while (index > 0)
482             {
483                 index--;
484                 if ((pos -= descr->items[index].height) <= y) break;
485             }
486         }
487     }
488     else if (descr->style & LBS_MULTICOLUMN)
489     {
490         if (y >= descr->item_height * descr->page_size) return -1;
491         if (y >= 0) index += y / descr->item_height;
492         if (x >= 0) index += (x / descr->column_width) * descr->page_size;
493         else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
494     }
495     else
496     {
497         index += (y / descr->item_height);
498     }
499     if (index < 0) return 0;
500     if (index >= descr->nb_items) return -1;
501     return index;
502 }
503
504
505 /***********************************************************************
506  *           LISTBOX_PaintItem
507  *
508  * Paint an item.
509  */
510 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect, 
511                                INT index, UINT action, BOOL ignoreFocus )
512 {
513     LB_ITEMDATA *item = NULL;
514     if (index < descr->nb_items) item = &descr->items[index];
515
516     if (IS_OWNERDRAW(descr))
517     {
518         DRAWITEMSTRUCT dis;
519         RECT r;
520         HRGN hrgn;
521
522         if (!item)
523         {
524             if (action == ODA_FOCUS)
525                 DrawFocusRect( hdc, rect );
526             else
527                 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
528             return;
529         }
530
531         /* some programs mess with the clipping region when
532         drawing the item, *and* restore the previous region
533         after they are done, so a region has better to exist
534         else everything ends clipped */
535         GetClientRect(descr->self, &r);
536         hrgn = set_control_clipping( hdc, &r );
537
538         dis.CtlType      = ODT_LISTBOX;
539         dis.CtlID        = GetWindowLongPtrW( descr->self, GWLP_ID );
540         dis.hwndItem     = descr->self;
541         dis.itemAction   = action;
542         dis.hDC          = hdc;
543         dis.itemID       = index;
544         dis.itemState    = 0;
545         if (item->selected) dis.itemState |= ODS_SELECTED;
546         if (!ignoreFocus && (descr->focus_item == index) &&
547             (descr->caret_on) &&
548             (descr->in_focus)) dis.itemState |= ODS_FOCUS;
549         if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
550         dis.itemData     = item->data;
551         dis.rcItem       = *rect;
552         TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
553               descr->self, index, debugstr_w(item->str), action,
554               dis.itemState, wine_dbgstr_rect(rect) );
555         SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
556         SelectClipRgn( hdc, hrgn );
557         if (hrgn) DeleteObject( hrgn );
558     }
559     else
560     {
561         COLORREF oldText = 0, oldBk = 0;
562
563         if (action == ODA_FOCUS)
564         {
565             DrawFocusRect( hdc, rect );
566             return;
567         }
568         if (item && item->selected)
569         {
570             oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
571             oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
572         }
573
574         TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
575               descr->self, index, item ? debugstr_w(item->str) : "", action,
576               wine_dbgstr_rect(rect) );
577         if (!item)
578             ExtTextOutW( hdc, rect->left + 1, rect->top,
579                            ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
580         else if (!(descr->style & LBS_USETABSTOPS))
581             ExtTextOutW( hdc, rect->left + 1, rect->top,
582                          ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
583                          strlenW(item->str), NULL );
584         else
585         {
586             /* Output empty string to paint background in the full width. */
587             ExtTextOutW( hdc, rect->left + 1, rect->top,
588                          ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
589             TabbedTextOutW( hdc, rect->left + 1 , rect->top,
590                             item->str, strlenW(item->str),
591                             descr->nb_tabs, descr->tabs, 0);
592         }
593         if (item && item->selected)
594         {
595             SetBkColor( hdc, oldBk );
596             SetTextColor( hdc, oldText );
597         }
598         if (!ignoreFocus && (descr->focus_item == index) &&
599             (descr->caret_on) &&
600             (descr->in_focus)) DrawFocusRect( hdc, rect );
601     }
602 }
603
604
605 /***********************************************************************
606  *           LISTBOX_SetRedraw
607  *
608  * Change the redraw flag.
609  */
610 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
611 {
612     if (on)
613     {
614         if (!(descr->style & LBS_NOREDRAW)) return;
615         descr->style &= ~LBS_NOREDRAW;
616         if (descr->style & LBS_DISPLAYCHANGED)
617         {     /* page was changed while setredraw false, refresh automatically */
618             InvalidateRect(descr->self, NULL, TRUE);
619             if ((descr->top_item + descr->page_size) > descr->nb_items)
620             {      /* reset top of page if less than number of items/page */
621                 descr->top_item = descr->nb_items - descr->page_size;
622                 if (descr->top_item < 0) descr->top_item = 0;
623             }
624             descr->style &= ~LBS_DISPLAYCHANGED;
625         }
626         LISTBOX_UpdateScroll( descr );
627     }
628     else descr->style |= LBS_NOREDRAW;
629 }
630
631
632 /***********************************************************************
633  *           LISTBOX_RepaintItem
634  *
635  * Repaint a single item synchronously.
636  */
637 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
638 {
639     HDC hdc;
640     RECT rect;
641     HFONT oldFont = 0;
642     HBRUSH hbrush, oldBrush = 0;
643
644     /* Do not repaint the item if the item is not visible */
645     if (!IsWindowVisible(descr->self)) return;
646     if (descr->style & LBS_NOREDRAW)
647     {
648         descr->style |= LBS_DISPLAYCHANGED;
649         return;
650     }
651     if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
652     if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
653     if (descr->font) oldFont = SelectObject( hdc, descr->font );
654     hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
655                                    (WPARAM)hdc, (LPARAM)descr->self );
656     if (hbrush) oldBrush = SelectObject( hdc, hbrush );
657     if (!IsWindowEnabled(descr->self))
658         SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
659     SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
660     LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
661     if (oldFont) SelectObject( hdc, oldFont );
662     if (oldBrush) SelectObject( hdc, oldBrush );
663     ReleaseDC( descr->self, hdc );
664 }
665
666
667 /***********************************************************************
668  *           LISTBOX_DrawFocusRect
669  */
670 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
671 {
672     HDC hdc;
673     RECT rect;
674     HFONT oldFont = 0;
675
676     /* Do not repaint the item if the item is not visible */
677     if (!IsWindowVisible(descr->self)) return;
678
679     if (descr->focus_item == -1) return;
680     if (!descr->caret_on || !descr->in_focus) return;
681
682     if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
683     if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
684     if (descr->font) oldFont = SelectObject( hdc, descr->font );
685     if (!IsWindowEnabled(descr->self))
686         SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
687     SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
688     LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
689     if (oldFont) SelectObject( hdc, oldFont );
690     ReleaseDC( descr->self, hdc );
691 }
692
693
694 /***********************************************************************
695  *           LISTBOX_InitStorage
696  */
697 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
698 {
699     LB_ITEMDATA *item;
700
701     nb_items += LB_ARRAY_GRANULARITY - 1;
702     nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
703     if (descr->items) {
704         nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
705         item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
706                               nb_items * sizeof(LB_ITEMDATA));
707     }
708     else {
709         item = HeapAlloc( GetProcessHeap(), 0,
710                               nb_items * sizeof(LB_ITEMDATA));
711     }
712
713     if (!item)
714     {
715         SEND_NOTIFICATION( descr, LBN_ERRSPACE );
716         return LB_ERRSPACE;
717     }
718     descr->items = item;
719     return LB_OKAY;
720 }
721
722
723 /***********************************************************************
724  *           LISTBOX_SetTabStops
725  */
726 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
727 {
728     INT i;
729
730     if (!(descr->style & LBS_USETABSTOPS))
731     {
732         SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
733         return FALSE;
734     }
735
736     HeapFree( GetProcessHeap(), 0, descr->tabs );
737     if (!(descr->nb_tabs = count))
738     {
739         descr->tabs = NULL;
740         return TRUE;
741     }
742     if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
743                                             descr->nb_tabs * sizeof(INT) )))
744         return FALSE;
745     memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
746
747     /* convert into "dialog units"*/
748     for (i = 0; i < descr->nb_tabs; i++)
749         descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
750
751     return TRUE;
752 }
753
754
755 /***********************************************************************
756  *           LISTBOX_GetText
757  */
758 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
759 {
760     DWORD len;
761
762     if ((index < 0) || (index >= descr->nb_items))
763     {
764         SetLastError(ERROR_INVALID_INDEX);
765         return LB_ERR;
766     }
767     if (HAS_STRINGS(descr))
768     {
769         if (!buffer)
770         {
771             len = strlenW(descr->items[index].str);
772             if( unicode )
773                 return len;
774             return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
775                                         NULL, 0, NULL, NULL );
776         }
777
778         TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
779
780         __TRY  /* hide a Delphi bug that passes a read-only buffer */
781         {
782             if(unicode)
783             {
784                 strcpyW( buffer, descr->items[index].str );
785                 len = strlenW(buffer);
786             }
787             else
788             {
789                 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
790                                           (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
791             }
792         }
793         __EXCEPT_PAGE_FAULT
794         {
795             WARN( "got an invalid buffer (Delphi bug?)\n" );
796             SetLastError( ERROR_INVALID_PARAMETER );
797             return LB_ERR;
798         }
799         __ENDTRY
800     } else {
801         if (buffer)
802             *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
803         len = sizeof(DWORD);
804     }
805     return len;
806 }
807
808 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
809 {
810     INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
811     if (ret == CSTR_LESS_THAN)
812         return -1;
813     if (ret == CSTR_EQUAL)
814         return 0;
815     if (ret == CSTR_GREATER_THAN)
816         return 1;
817     return -1;
818 }
819
820 /***********************************************************************
821  *           LISTBOX_FindStringPos
822  *
823  * Find the nearest string located before a given string in sort order.
824  * If 'exact' is TRUE, return an error if we don't get an exact match.
825  */
826 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
827 {
828     INT index, min, max, res = -1;
829
830     if (!(descr->style & LBS_SORT)) return -1;  /* Add it at the end */
831     min = 0;
832     max = descr->nb_items;
833     while (min != max)
834     {
835         index = (min + max) / 2;
836         if (HAS_STRINGS(descr))
837             res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
838         else
839         {
840             COMPAREITEMSTRUCT cis;
841             UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
842
843             cis.CtlType    = ODT_LISTBOX;
844             cis.CtlID      = id;
845             cis.hwndItem   = descr->self;
846             /* note that some application (MetaStock) expects the second item
847              * to be in the listbox */
848             cis.itemID1    = -1;
849             cis.itemData1  = (ULONG_PTR)str;
850             cis.itemID2    = index;
851             cis.itemData2  = descr->items[index].data;
852             cis.dwLocaleId = descr->locale;
853             res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
854         }
855         if (!res) return index;
856         if (res < 0) max = index;
857         else min = index + 1;
858     }
859     return exact ? -1 : max;
860 }
861
862
863 /***********************************************************************
864  *           LISTBOX_FindFileStrPos
865  *
866  * Find the nearest string located before a given string in directory
867  * sort order (i.e. first files, then directories, then drives).
868  */
869 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
870 {
871     INT min, max, res = -1;
872
873     if (!HAS_STRINGS(descr))
874         return LISTBOX_FindStringPos( descr, str, FALSE );
875     min = 0;
876     max = descr->nb_items;
877     while (min != max)
878     {
879         INT index = (min + max) / 2;
880         LPCWSTR p = descr->items[index].str;
881         if (*p == '[')  /* drive or directory */
882         {
883             if (*str != '[') res = -1;
884             else if (p[1] == '-')  /* drive */
885             {
886                 if (str[1] == '-') res = str[2] - p[2];
887                 else res = -1;
888             }
889             else  /* directory */
890             {
891                 if (str[1] == '-') res = 1;
892                 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
893             }
894         }
895         else  /* filename */
896         {
897             if (*str == '[') res = 1;
898             else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
899         }
900         if (!res) return index;
901         if (res < 0) max = index;
902         else min = index + 1;
903     }
904     return max;
905 }
906
907
908 /***********************************************************************
909  *           LISTBOX_FindString
910  *
911  * Find the item beginning with a given string.
912  */
913 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
914 {
915     INT i;
916     LB_ITEMDATA *item;
917
918     if (start >= descr->nb_items) start = -1;
919     item = descr->items + start + 1;
920     if (HAS_STRINGS(descr))
921     {
922         if (!str || ! str[0] ) return LB_ERR;
923         if (exact)
924         {
925             for (i = start + 1; i < descr->nb_items; i++, item++)
926                 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
927             for (i = 0, item = descr->items; i <= start; i++, item++)
928                 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
929         }
930         else
931         {
932  /* Special case for drives and directories: ignore prefix */
933 #define CHECK_DRIVE(item) \
934     if ((item)->str[0] == '[') \
935     { \
936         if (!strncmpiW( str, (item)->str+1, len )) return i; \
937         if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
938         return i; \
939     }
940
941             INT len = strlenW(str);
942             for (i = start + 1; i < descr->nb_items; i++, item++)
943             {
944                if (!strncmpiW( str, item->str, len )) return i;
945                CHECK_DRIVE(item);
946             }
947             for (i = 0, item = descr->items; i <= start; i++, item++)
948             {
949                if (!strncmpiW( str, item->str, len )) return i;
950                CHECK_DRIVE(item);
951             }
952 #undef CHECK_DRIVE
953         }
954     }
955     else
956     {
957         if (exact && (descr->style & LBS_SORT))
958             /* If sorted, use a WM_COMPAREITEM binary search */
959             return LISTBOX_FindStringPos( descr, str, TRUE );
960
961         /* Otherwise use a linear search */
962         for (i = start + 1; i < descr->nb_items; i++, item++)
963             if (item->data == (ULONG_PTR)str) return i;
964         for (i = 0, item = descr->items; i <= start; i++, item++)
965             if (item->data == (ULONG_PTR)str) return i;
966     }
967     return LB_ERR;
968 }
969
970
971 /***********************************************************************
972  *           LISTBOX_GetSelCount
973  */
974 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
975 {
976     INT i, count;
977     const LB_ITEMDATA *item = descr->items;
978
979     if (!(descr->style & LBS_MULTIPLESEL) ||
980         (descr->style & LBS_NOSEL))
981       return LB_ERR;
982     for (i = count = 0; i < descr->nb_items; i++, item++)
983         if (item->selected) count++;
984     return count;
985 }
986
987
988 /***********************************************************************
989  *           LISTBOX_GetSelItems
990  */
991 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
992 {
993     INT i, count;
994     const LB_ITEMDATA *item = descr->items;
995
996     if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
997     for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
998         if (item->selected) array[count++] = i;
999     return count;
1000 }
1001
1002
1003 /***********************************************************************
1004  *           LISTBOX_Paint
1005  */
1006 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1007 {
1008     INT i, col_pos = descr->page_size - 1;
1009     RECT rect;
1010     RECT focusRect = {-1, -1, -1, -1};
1011     HFONT oldFont = 0;
1012     HBRUSH hbrush, oldBrush = 0;
1013
1014     if (descr->style & LBS_NOREDRAW) return 0;
1015
1016     SetRect( &rect, 0, 0, descr->width, descr->height );
1017     if (descr->style & LBS_MULTICOLUMN)
1018         rect.right = rect.left + descr->column_width;
1019     else if (descr->horz_pos)
1020     {
1021         SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1022         rect.right += descr->horz_pos;
1023     }
1024
1025     if (descr->font) oldFont = SelectObject( hdc, descr->font );
1026     hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1027                                    (WPARAM)hdc, (LPARAM)descr->self );
1028     if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1029     if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1030
1031     if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1032         (descr->in_focus))
1033     {
1034         /* Special case for empty listbox: paint focus rect */
1035         rect.bottom = rect.top + descr->item_height;
1036         ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1037                      &rect, NULL, 0, NULL );
1038         LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1039         rect.top = rect.bottom;
1040     }
1041
1042     /* Paint all the item, regarding the selection
1043        Focus state will be painted after  */
1044
1045     for (i = descr->top_item; i < descr->nb_items; i++)
1046     {
1047         if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1048             rect.bottom = rect.top + descr->item_height;
1049         else
1050             rect.bottom = rect.top + descr->items[i].height;
1051
1052         if (i == descr->focus_item)
1053         {
1054             /* keep the focus rect, to paint the focus item after */
1055             focusRect.left = rect.left;
1056             focusRect.right = rect.right;
1057             focusRect.top = rect.top;
1058             focusRect.bottom = rect.bottom;
1059         }
1060         LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1061         rect.top = rect.bottom;
1062
1063         if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1064         {
1065             if (!IS_OWNERDRAW(descr))
1066             {
1067                 /* Clear the bottom of the column */
1068                 if (rect.top < descr->height)
1069                 {
1070                     rect.bottom = descr->height;
1071                     ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1072                                    &rect, NULL, 0, NULL );
1073                 }
1074             }
1075
1076             /* Go to the next column */
1077             rect.left += descr->column_width;
1078             rect.right += descr->column_width;
1079             rect.top = 0;
1080             col_pos = descr->page_size - 1;
1081         }
1082         else
1083         {
1084             col_pos--;
1085             if (rect.top >= descr->height) break;
1086         }
1087     }
1088
1089     /* Paint the focus item now */
1090     if (focusRect.top != focusRect.bottom &&
1091         descr->caret_on && descr->in_focus)
1092         LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1093
1094     if (!IS_OWNERDRAW(descr))
1095     {
1096         /* Clear the remainder of the client area */
1097         if (rect.top < descr->height)
1098         {
1099             rect.bottom = descr->height;
1100             ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1101                            &rect, NULL, 0, NULL );
1102         }
1103         if (rect.right < descr->width)
1104         {
1105             rect.left   = rect.right;
1106             rect.right  = descr->width;
1107             rect.top    = 0;
1108             rect.bottom = descr->height;
1109             ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1110                            &rect, NULL, 0, NULL );
1111         }
1112     }
1113     if (oldFont) SelectObject( hdc, oldFont );
1114     if (oldBrush) SelectObject( hdc, oldBrush );
1115     return 0;
1116 }
1117
1118
1119 /***********************************************************************
1120  *           LISTBOX_InvalidateItems
1121  *
1122  * Invalidate all items from a given item. If the specified item is not
1123  * visible, nothing happens.
1124  */
1125 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1126 {
1127     RECT rect;
1128
1129     if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1130     {
1131         if (descr->style & LBS_NOREDRAW)
1132         {
1133             descr->style |= LBS_DISPLAYCHANGED;
1134             return;
1135         }
1136         rect.bottom = descr->height;
1137         InvalidateRect( descr->self, &rect, TRUE );
1138         if (descr->style & LBS_MULTICOLUMN)
1139         {
1140             /* Repaint the other columns */
1141             rect.left  = rect.right;
1142             rect.right = descr->width;
1143             rect.top   = 0;
1144             InvalidateRect( descr->self, &rect, TRUE );
1145         }
1146     }
1147 }
1148
1149 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1150 {
1151     RECT rect;
1152
1153     if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1154         InvalidateRect( descr->self, &rect, TRUE );
1155 }
1156
1157 /***********************************************************************
1158  *           LISTBOX_GetItemHeight
1159  */
1160 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1161 {
1162     if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1163     {
1164         if ((index < 0) || (index >= descr->nb_items))
1165         {
1166             SetLastError(ERROR_INVALID_INDEX);
1167             return LB_ERR;
1168         }
1169         return descr->items[index].height;
1170     }
1171     else return descr->item_height;
1172 }
1173
1174
1175 /***********************************************************************
1176  *           LISTBOX_SetItemHeight
1177  */
1178 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1179 {
1180     if (height > MAXBYTE)
1181         return -1;
1182
1183     if (!height) height = 1;
1184
1185     if (descr->style & LBS_OWNERDRAWVARIABLE)
1186     {
1187         if ((index < 0) || (index >= descr->nb_items))
1188         {
1189             SetLastError(ERROR_INVALID_INDEX);
1190             return LB_ERR;
1191         }
1192         TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1193         descr->items[index].height = height;
1194         LISTBOX_UpdateScroll( descr );
1195         if (repaint)
1196             LISTBOX_InvalidateItems( descr, index );
1197     }
1198     else if (height != descr->item_height)
1199     {
1200         TRACE("[%p]: new height = %d\n", descr->self, height );
1201         descr->item_height = height;
1202         LISTBOX_UpdatePage( descr );
1203         LISTBOX_UpdateScroll( descr );
1204         if (repaint)
1205             InvalidateRect( descr->self, 0, TRUE );
1206     }
1207     return LB_OKAY;
1208 }
1209
1210
1211 /***********************************************************************
1212  *           LISTBOX_SetHorizontalPos
1213  */
1214 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1215 {
1216     INT diff;
1217
1218     if (pos > descr->horz_extent - descr->width)
1219         pos = descr->horz_extent - descr->width;
1220     if (pos < 0) pos = 0;
1221     if (!(diff = descr->horz_pos - pos)) return;
1222     TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1223     descr->horz_pos = pos;
1224     LISTBOX_UpdateScroll( descr );
1225     if (abs(diff) < descr->width)
1226     {
1227         RECT rect;
1228         /* Invalidate the focused item so it will be repainted correctly */
1229         if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1230             InvalidateRect( descr->self, &rect, TRUE );
1231         ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1232                           SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1233     }
1234     else
1235         InvalidateRect( descr->self, NULL, TRUE );
1236 }
1237
1238
1239 /***********************************************************************
1240  *           LISTBOX_SetHorizontalExtent
1241  */
1242 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1243 {
1244     if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1245         return LB_OKAY;
1246     if (extent <= 0) extent = 1;
1247     if (extent == descr->horz_extent) return LB_OKAY;
1248     TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1249     descr->horz_extent = extent;
1250     if (descr->horz_pos > extent - descr->width)
1251         LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1252     else
1253         LISTBOX_UpdateScroll( descr );
1254     return LB_OKAY;
1255 }
1256
1257
1258 /***********************************************************************
1259  *           LISTBOX_SetColumnWidth
1260  */
1261 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1262 {
1263     if (width == descr->column_width) return LB_OKAY;
1264     TRACE("[%p]: new column width = %d\n", descr->self, width );
1265     descr->column_width = width;
1266     LISTBOX_UpdatePage( descr );
1267     return LB_OKAY;
1268 }
1269
1270
1271 /***********************************************************************
1272  *           LISTBOX_SetFont
1273  *
1274  * Returns the item height.
1275  */
1276 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1277 {
1278     HDC hdc;
1279     HFONT oldFont = 0;
1280     const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1281     SIZE sz;
1282
1283     descr->font = font;
1284
1285     if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1286     {
1287         ERR("unable to get DC.\n" );
1288         return 16;
1289     }
1290     if (font) oldFont = SelectObject( hdc, font );
1291     GetTextExtentPointA( hdc, alphabet, 52, &sz);
1292     if (oldFont) SelectObject( hdc, oldFont );
1293     ReleaseDC( descr->self, hdc );
1294
1295     descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1296     if (!IS_OWNERDRAW(descr))
1297         LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1298     return sz.cy;
1299 }
1300
1301
1302 /***********************************************************************
1303  *           LISTBOX_MakeItemVisible
1304  *
1305  * Make sure that a given item is partially or fully visible.
1306  */
1307 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1308 {
1309     INT top;
1310
1311     TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1312
1313     if (index <= descr->top_item) top = index;
1314     else if (descr->style & LBS_MULTICOLUMN)
1315     {
1316         INT cols = descr->width;
1317         if (!fully) cols += descr->column_width - 1;
1318         if (cols >= descr->column_width) cols /= descr->column_width;
1319         else cols = 1;
1320         if (index < descr->top_item + (descr->page_size * cols)) return;
1321         top = index - descr->page_size * (cols - 1);
1322     }
1323     else if (descr->style & LBS_OWNERDRAWVARIABLE)
1324     {
1325         INT height = fully ? descr->items[index].height : 1;
1326         for (top = index; top > descr->top_item; top--)
1327             if ((height += descr->items[top-1].height) > descr->height) break;
1328     }
1329     else
1330     {
1331         if (index < descr->top_item + descr->page_size) return;
1332         if (!fully && (index == descr->top_item + descr->page_size) &&
1333             (descr->height > (descr->page_size * descr->item_height))) return;
1334         top = index - descr->page_size + 1;
1335     }
1336     LISTBOX_SetTopItem( descr, top, TRUE );
1337 }
1338
1339 /***********************************************************************
1340  *           LISTBOX_SetCaretIndex
1341  *
1342  * NOTES
1343  *   index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1344  *
1345  */
1346 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1347 {
1348     INT oldfocus = descr->focus_item;
1349
1350     TRACE("old focus %d, index %d\n", oldfocus, index);
1351
1352     if (descr->style & LBS_NOSEL) return LB_ERR;
1353     if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1354     if (index == oldfocus) return LB_OKAY;
1355
1356     LISTBOX_DrawFocusRect( descr, FALSE );
1357     descr->focus_item = index;
1358
1359     LISTBOX_MakeItemVisible( descr, index, fully_visible );
1360     LISTBOX_DrawFocusRect( descr, TRUE );
1361
1362     return LB_OKAY;
1363 }
1364
1365
1366 /***********************************************************************
1367  *           LISTBOX_SelectItemRange
1368  *
1369  * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1370  */
1371 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1372                                         INT last, BOOL on )
1373 {
1374     INT i;
1375
1376     /* A few sanity checks */
1377
1378     if (descr->style & LBS_NOSEL) return LB_ERR;
1379     if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1380
1381     if (!descr->nb_items) return LB_OKAY;
1382
1383     if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1384     if (first < 0) first = 0;
1385     if (last < first) return LB_OKAY;
1386
1387     if (on)  /* Turn selection on */
1388     {
1389         for (i = first; i <= last; i++)
1390         {
1391             if (descr->items[i].selected) continue;
1392             descr->items[i].selected = TRUE;
1393             LISTBOX_InvalidateItemRect(descr, i);
1394         }
1395     }
1396     else  /* Turn selection off */
1397     {
1398         for (i = first; i <= last; i++)
1399         {
1400             if (!descr->items[i].selected) continue;
1401             descr->items[i].selected = FALSE;
1402             LISTBOX_InvalidateItemRect(descr, i);
1403         }
1404     }
1405     return LB_OKAY;
1406 }
1407
1408 /***********************************************************************
1409  *           LISTBOX_SetSelection
1410  */
1411 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1412                                      BOOL on, BOOL send_notify )
1413 {
1414     TRACE( "cur_sel=%d index=%d notify=%s\n",
1415            descr->selected_item, index, send_notify ? "YES" : "NO" );
1416
1417     if (descr->style & LBS_NOSEL)
1418     {
1419         descr->selected_item = index;
1420         return LB_ERR;
1421     }
1422     if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1423     if (descr->style & LBS_MULTIPLESEL)
1424     {
1425         if (index == -1)  /* Select all items */
1426             return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1427         else  /* Only one item */
1428             return LISTBOX_SelectItemRange( descr, index, index, on );
1429     }
1430     else
1431     {
1432         INT oldsel = descr->selected_item;
1433         if (index == oldsel) return LB_OKAY;
1434         if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1435         if (index != -1) descr->items[index].selected = TRUE;
1436         if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1437         descr->selected_item = index;
1438         if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1439         if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1440                                (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1441         else
1442             if( descr->lphc ) /* set selection change flag for parent combo */
1443                 descr->lphc->wState |= CBF_SELCHANGE;
1444     }
1445     return LB_OKAY;
1446 }
1447
1448
1449 /***********************************************************************
1450  *           LISTBOX_MoveCaret
1451  *
1452  * Change the caret position and extend the selection to the new caret.
1453  */
1454 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1455 {
1456     TRACE("old focus %d, index %d\n", descr->focus_item, index);
1457
1458     if ((index <  0) || (index >= descr->nb_items))
1459         return;
1460
1461     /* Important, repaint needs to be done in this order if
1462        you want to mimic Windows behavior:
1463        1. Remove the focus and paint the item
1464        2. Remove the selection and paint the item(s)
1465        3. Set the selection and repaint the item(s)
1466        4. Set the focus to 'index' and repaint the item */
1467
1468     /* 1. remove the focus and repaint the item */
1469     LISTBOX_DrawFocusRect( descr, FALSE );
1470
1471     /* 2. then turn off the previous selection */
1472     /* 3. repaint the new selected item */
1473     if (descr->style & LBS_EXTENDEDSEL)
1474     {
1475         if (descr->anchor_item != -1)
1476         {
1477             INT first = min( index, descr->anchor_item );
1478             INT last  = max( index, descr->anchor_item );
1479             if (first > 0)
1480                 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1481             LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1482             LISTBOX_SelectItemRange( descr, first, last, TRUE );
1483         }
1484     }
1485     else if (!(descr->style & LBS_MULTIPLESEL))
1486     {
1487         /* Set selection to new caret item */
1488         LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1489     }
1490
1491     /* 4. repaint the new item with the focus */
1492     descr->focus_item = index;
1493     LISTBOX_MakeItemVisible( descr, index, fully_visible );
1494     LISTBOX_DrawFocusRect( descr, TRUE );
1495 }
1496
1497
1498 /***********************************************************************
1499  *           LISTBOX_InsertItem
1500  */
1501 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1502                                    LPWSTR str, ULONG_PTR data )
1503 {
1504     LB_ITEMDATA *item;
1505     INT max_items;
1506     INT oldfocus = descr->focus_item;
1507
1508     if (index == -1) index = descr->nb_items;
1509     else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1510     if (!descr->items) max_items = 0;
1511     else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1512     if (descr->nb_items == max_items)
1513     {
1514         /* We need to grow the array */
1515         max_items += LB_ARRAY_GRANULARITY;
1516         if (descr->items)
1517             item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1518                                   max_items * sizeof(LB_ITEMDATA) );
1519         else
1520             item = HeapAlloc( GetProcessHeap(), 0,
1521                                   max_items * sizeof(LB_ITEMDATA) );
1522         if (!item)
1523         {
1524             SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1525             return LB_ERRSPACE;
1526         }
1527         descr->items = item;
1528     }
1529
1530     /* Insert the item structure */
1531
1532     item = &descr->items[index];
1533     if (index < descr->nb_items)
1534         RtlMoveMemory( item + 1, item,
1535                        (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1536     item->str      = str;
1537     item->data     = data;
1538     item->height   = 0;
1539     item->selected = FALSE;
1540     descr->nb_items++;
1541
1542     /* Get item height */
1543
1544     if (descr->style & LBS_OWNERDRAWVARIABLE)
1545     {
1546         MEASUREITEMSTRUCT mis;
1547         UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1548
1549         mis.CtlType    = ODT_LISTBOX;
1550         mis.CtlID      = id;
1551         mis.itemID     = index;
1552         mis.itemData   = descr->items[index].data;
1553         mis.itemHeight = descr->item_height;
1554         SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1555         item->height = mis.itemHeight ? mis.itemHeight : 1;
1556         TRACE("[%p]: measure item %d (%s) = %d\n",
1557               descr->self, index, str ? debugstr_w(str) : "", item->height );
1558     }
1559
1560     /* Repaint the items */
1561
1562     LISTBOX_UpdateScroll( descr );
1563     LISTBOX_InvalidateItems( descr, index );
1564
1565     /* Move selection and focused item */
1566     /* If listbox was empty, set focus to the first item */
1567     if (descr->nb_items == 1)
1568          LISTBOX_SetCaretIndex( descr, 0, FALSE );
1569     /* single select don't change selection index in win31 */
1570     else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1571     {
1572         descr->selected_item++;
1573         LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1574     }
1575     else
1576     {
1577         if (index <= descr->selected_item)
1578         {
1579             descr->selected_item++;
1580             descr->focus_item = oldfocus; /* focus not changed */
1581         }
1582     }
1583     return LB_OKAY;
1584 }
1585
1586
1587 /***********************************************************************
1588  *           LISTBOX_InsertString
1589  */
1590 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1591 {
1592     LPWSTR new_str = NULL;
1593     ULONG_PTR data = 0;
1594     LRESULT ret;
1595
1596     if (HAS_STRINGS(descr))
1597     {
1598         static const WCHAR empty_stringW[] = { 0 };
1599         if (!str) str = empty_stringW;
1600         if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1601         {
1602             SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1603             return LB_ERRSPACE;
1604         }
1605         strcpyW(new_str, str);
1606     }
1607     else data = (ULONG_PTR)str;
1608
1609     if (index == -1) index = descr->nb_items;
1610     if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1611     {
1612         HeapFree( GetProcessHeap(), 0, new_str );
1613         return ret;
1614     }
1615
1616     TRACE("[%p]: added item %d %s\n",
1617           descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1618     return index;
1619 }
1620
1621
1622 /***********************************************************************
1623  *           LISTBOX_DeleteItem
1624  *
1625  * Delete the content of an item. 'index' must be a valid index.
1626  */
1627 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1628 {
1629     /* save the item data before it gets freed by LB_RESETCONTENT */
1630     ULONG_PTR item_data = descr->items[index].data;
1631     LPWSTR item_str = descr->items[index].str;
1632
1633     if (!descr->nb_items)
1634         SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1635
1636     /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1637      *       while Win95 sends it for all items with user data.
1638      *       It's probably better to send it too often than not
1639      *       often enough, so this is what we do here.
1640      */
1641     if (IS_OWNERDRAW(descr) || item_data)
1642     {
1643         DELETEITEMSTRUCT dis;
1644         UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1645
1646         dis.CtlType  = ODT_LISTBOX;
1647         dis.CtlID    = id;
1648         dis.itemID   = index;
1649         dis.hwndItem = descr->self;
1650         dis.itemData = item_data;
1651         SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1652     }
1653     if (HAS_STRINGS(descr))
1654         HeapFree( GetProcessHeap(), 0, item_str );
1655 }
1656
1657
1658 /***********************************************************************
1659  *           LISTBOX_RemoveItem
1660  *
1661  * Remove an item from the listbox and delete its content.
1662  */
1663 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1664 {
1665     LB_ITEMDATA *item;
1666     INT max_items;
1667
1668     if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1669
1670     /* We need to invalidate the original rect instead of the updated one. */
1671     LISTBOX_InvalidateItems( descr, index );
1672
1673     descr->nb_items--;
1674     LISTBOX_DeleteItem( descr, index );
1675
1676     if (!descr->nb_items) return LB_OKAY;
1677
1678     /* Remove the item */
1679
1680     item = &descr->items[index];
1681     if (index < descr->nb_items)
1682         RtlMoveMemory( item, item + 1,
1683                        (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1684     if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1685
1686     /* Shrink the item array if possible */
1687
1688     max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1689     if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1690     {
1691         max_items -= LB_ARRAY_GRANULARITY;
1692         item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1693                             max_items * sizeof(LB_ITEMDATA) );
1694         if (item) descr->items = item;
1695     }
1696     /* Repaint the items */
1697
1698     LISTBOX_UpdateScroll( descr );
1699     /* if we removed the scrollbar, reset the top of the list
1700       (correct for owner-drawn ???) */
1701     if (descr->nb_items == descr->page_size)
1702         LISTBOX_SetTopItem( descr, 0, TRUE );
1703
1704     /* Move selection and focused item */
1705     if (!IS_MULTISELECT(descr))
1706     {
1707         if (index == descr->selected_item)
1708             descr->selected_item = -1;
1709         else if (index < descr->selected_item)
1710         {
1711             descr->selected_item--;
1712             if (ISWIN31) /* win 31 do not change the selected item number */
1713                LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1714         }
1715     }
1716
1717     if (descr->focus_item >= descr->nb_items)
1718     {
1719           descr->focus_item = descr->nb_items - 1;
1720           if (descr->focus_item < 0) descr->focus_item = 0;
1721     }
1722     return LB_OKAY;
1723 }
1724
1725
1726 /***********************************************************************
1727  *           LISTBOX_ResetContent
1728  */
1729 static void LISTBOX_ResetContent( LB_DESCR *descr )
1730 {
1731     INT i;
1732
1733     for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1734     HeapFree( GetProcessHeap(), 0, descr->items );
1735     descr->nb_items      = 0;
1736     descr->top_item      = 0;
1737     descr->selected_item = -1;
1738     descr->focus_item    = 0;
1739     descr->anchor_item   = -1;
1740     descr->items         = NULL;
1741 }
1742
1743
1744 /***********************************************************************
1745  *           LISTBOX_SetCount
1746  */
1747 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1748 {
1749     LRESULT ret;
1750
1751     if (HAS_STRINGS(descr))
1752     {
1753         SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1754         return LB_ERR;
1755     }
1756
1757     /* FIXME: this is far from optimal... */
1758     if (count > descr->nb_items)
1759     {
1760         while (count > descr->nb_items)
1761             if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1762                 return ret;
1763     }
1764     else if (count < descr->nb_items)
1765     {
1766         while (count < descr->nb_items)
1767             if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1768                 return ret;
1769     }
1770     return LB_OKAY;
1771 }
1772
1773
1774 /***********************************************************************
1775  *           LISTBOX_Directory
1776  */
1777 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1778                                   LPCWSTR filespec, BOOL long_names )
1779 {
1780     HANDLE handle;
1781     LRESULT ret = LB_OKAY;
1782     WIN32_FIND_DATAW entry;
1783     int pos;
1784     LRESULT maxinsert = LB_ERR;
1785
1786     /* don't scan directory if we just want drives exclusively */
1787     if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1788         /* scan directory */
1789         if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1790         {
1791              int le = GetLastError();
1792             if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1793         }
1794         else
1795         {
1796             do
1797             {
1798                 WCHAR buffer[270];
1799                 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1800                 {
1801                     static const WCHAR bracketW[]  = { ']',0 };
1802                     static const WCHAR dotW[] = { '.',0 };
1803                     if (!(attrib & DDL_DIRECTORY) ||
1804                         !strcmpW( entry.cFileName, dotW )) continue;
1805                     buffer[0] = '[';
1806                     if (!long_names && entry.cAlternateFileName[0])
1807                         strcpyW( buffer + 1, entry.cAlternateFileName );
1808                     else
1809                         strcpyW( buffer + 1, entry.cFileName );
1810                     strcatW(buffer, bracketW);
1811                 }
1812                 else  /* not a directory */
1813                 {
1814 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1815                  FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1816
1817                     if ((attrib & DDL_EXCLUSIVE) &&
1818                         ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1819                         continue;
1820 #undef ATTRIBS
1821                     if (!long_names && entry.cAlternateFileName[0])
1822                         strcpyW( buffer, entry.cAlternateFileName );
1823                     else
1824                         strcpyW( buffer, entry.cFileName );
1825                 }
1826                 if (!long_names) CharLowerW( buffer );
1827                 pos = LISTBOX_FindFileStrPos( descr, buffer );
1828                 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1829                     break;
1830                 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1831             } while (FindNextFileW( handle, &entry ));
1832             FindClose( handle );
1833         }
1834     }
1835     if (ret >= 0)
1836     {
1837         ret = maxinsert;
1838
1839         /* scan drives */
1840         if (attrib & DDL_DRIVES)
1841         {
1842             WCHAR buffer[] = {'[','-','a','-',']',0};
1843             WCHAR root[] = {'A',':','\\',0};
1844             int drive;
1845             for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1846             {
1847                 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1848                 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1849                     break;
1850             }
1851         }
1852     }
1853     return ret;
1854 }
1855
1856
1857 /***********************************************************************
1858  *           LISTBOX_HandleVScroll
1859  */
1860 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1861 {
1862     SCROLLINFO info;
1863
1864     if (descr->style & LBS_MULTICOLUMN) return 0;
1865     switch(scrollReq)
1866     {
1867     case SB_LINEUP:
1868         LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1869         break;
1870     case SB_LINEDOWN:
1871         LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1872         break;
1873     case SB_PAGEUP:
1874         LISTBOX_SetTopItem( descr, descr->top_item -
1875                             LISTBOX_GetCurrentPageSize( descr ), TRUE );
1876         break;
1877     case SB_PAGEDOWN:
1878         LISTBOX_SetTopItem( descr, descr->top_item +
1879                             LISTBOX_GetCurrentPageSize( descr ), TRUE );
1880         break;
1881     case SB_THUMBPOSITION:
1882         LISTBOX_SetTopItem( descr, pos, TRUE );
1883         break;
1884     case SB_THUMBTRACK:
1885         info.cbSize = sizeof(info);
1886         info.fMask = SIF_TRACKPOS;
1887         GetScrollInfo( descr->self, SB_VERT, &info );
1888         LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1889         break;
1890     case SB_TOP:
1891         LISTBOX_SetTopItem( descr, 0, TRUE );
1892         break;
1893     case SB_BOTTOM:
1894         LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1895         break;
1896     }
1897     return 0;
1898 }
1899
1900
1901 /***********************************************************************
1902  *           LISTBOX_HandleHScroll
1903  */
1904 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1905 {
1906     SCROLLINFO info;
1907     INT page;
1908
1909     if (descr->style & LBS_MULTICOLUMN)
1910     {
1911         switch(scrollReq)
1912         {
1913         case SB_LINELEFT:
1914             LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1915                                 TRUE );
1916             break;
1917         case SB_LINERIGHT:
1918             LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1919                                 TRUE );
1920             break;
1921         case SB_PAGELEFT:
1922             page = descr->width / descr->column_width;
1923             if (page < 1) page = 1;
1924             LISTBOX_SetTopItem( descr,
1925                              descr->top_item - page * descr->page_size, TRUE );
1926             break;
1927         case SB_PAGERIGHT:
1928             page = descr->width / descr->column_width;
1929             if (page < 1) page = 1;
1930             LISTBOX_SetTopItem( descr,
1931                              descr->top_item + page * descr->page_size, TRUE );
1932             break;
1933         case SB_THUMBPOSITION:
1934             LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1935             break;
1936         case SB_THUMBTRACK:
1937             info.cbSize = sizeof(info);
1938             info.fMask  = SIF_TRACKPOS;
1939             GetScrollInfo( descr->self, SB_VERT, &info );
1940             LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1941                                 TRUE );
1942             break;
1943         case SB_LEFT:
1944             LISTBOX_SetTopItem( descr, 0, TRUE );
1945             break;
1946         case SB_RIGHT:
1947             LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1948             break;
1949         }
1950     }
1951     else if (descr->horz_extent)
1952     {
1953         switch(scrollReq)
1954         {
1955         case SB_LINELEFT:
1956             LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1957             break;
1958         case SB_LINERIGHT:
1959             LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1960             break;
1961         case SB_PAGELEFT:
1962             LISTBOX_SetHorizontalPos( descr,
1963                                       descr->horz_pos - descr->width );
1964             break;
1965         case SB_PAGERIGHT:
1966             LISTBOX_SetHorizontalPos( descr,
1967                                       descr->horz_pos + descr->width );
1968             break;
1969         case SB_THUMBPOSITION:
1970             LISTBOX_SetHorizontalPos( descr, pos );
1971             break;
1972         case SB_THUMBTRACK:
1973             info.cbSize = sizeof(info);
1974             info.fMask = SIF_TRACKPOS;
1975             GetScrollInfo( descr->self, SB_HORZ, &info );
1976             LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1977             break;
1978         case SB_LEFT:
1979             LISTBOX_SetHorizontalPos( descr, 0 );
1980             break;
1981         case SB_RIGHT:
1982             LISTBOX_SetHorizontalPos( descr,
1983                                       descr->horz_extent - descr->width );
1984             break;
1985         }
1986     }
1987     return 0;
1988 }
1989
1990 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1991 {
1992     UINT pulScrollLines = 3;
1993
1994     SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1995
1996     /* if scrolling changes direction, ignore left overs */
1997     if ((delta < 0 && descr->wheel_remain < 0) ||
1998         (delta > 0 && descr->wheel_remain > 0))
1999         descr->wheel_remain += delta;
2000     else
2001         descr->wheel_remain = delta;
2002
2003     if (descr->wheel_remain && pulScrollLines)
2004     {
2005         int cLineScroll;
2006         pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2007         cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2008         descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2009         LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2010     }
2011     return 0;
2012 }
2013
2014 /***********************************************************************
2015  *           LISTBOX_HandleLButtonDown
2016  */
2017 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2018 {
2019     INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2020
2021     TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2022           descr->self, x, y, index, descr->focus_item);
2023
2024     if (!descr->caret_on && (descr->in_focus)) return 0;
2025
2026     if (!descr->in_focus)
2027     {
2028         if( !descr->lphc ) SetFocus( descr->self );
2029         else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2030     }
2031
2032     if (index == -1) return 0;
2033
2034     if (!descr->lphc)
2035     {
2036         if (descr->style & LBS_NOTIFY )
2037             SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2038                             MAKELPARAM( x, y ) );
2039     }
2040
2041     descr->captured = TRUE;
2042     SetCapture( descr->self );
2043
2044     if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2045     {
2046         /* we should perhaps make sure that all items are deselected
2047            FIXME: needed for !LBS_EXTENDEDSEL, too ?
2048            if (!(keys & (MK_SHIFT|MK_CONTROL)))
2049            LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2050         */
2051
2052         if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2053         if (keys & MK_CONTROL)
2054         {
2055             LISTBOX_SetCaretIndex( descr, index, FALSE );
2056             LISTBOX_SetSelection( descr, index,
2057                                   !descr->items[index].selected,
2058                                   (descr->style & LBS_NOTIFY) != 0);
2059         }
2060         else
2061         {
2062             LISTBOX_MoveCaret( descr, index, FALSE );
2063
2064             if (descr->style & LBS_EXTENDEDSEL)
2065             {
2066                 LISTBOX_SetSelection( descr, index,
2067                                descr->items[index].selected,
2068                               (descr->style & LBS_NOTIFY) != 0 );
2069             }
2070             else
2071             {
2072                 LISTBOX_SetSelection( descr, index,
2073                                !descr->items[index].selected,
2074                               (descr->style & LBS_NOTIFY) != 0 );
2075             }
2076         }
2077     }
2078     else
2079     {
2080         descr->anchor_item = index;
2081         LISTBOX_MoveCaret( descr, index, FALSE );
2082         LISTBOX_SetSelection( descr, index,
2083                               TRUE, (descr->style & LBS_NOTIFY) != 0 );
2084     }
2085
2086     if (!descr->lphc)
2087     {
2088         if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2089         {
2090             POINT pt;
2091
2092             pt.x = x;
2093             pt.y = y;
2094
2095             if (DragDetect( descr->self, pt ))
2096                 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2097         }
2098     }
2099     return 0;
2100 }
2101
2102
2103 /*************************************************************************
2104  * LISTBOX_HandleLButtonDownCombo [Internal]
2105  *
2106  * Process LButtonDown message for the ComboListBox
2107  *
2108  * PARAMS
2109  *     pWnd       [I] The windows internal structure
2110  *     pDescr     [I] The ListBox internal structure
2111  *     keys       [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2112  *     x          [I] X Mouse Coordinate
2113  *     y          [I] Y Mouse Coordinate
2114  *
2115  * RETURNS
2116  *     0 since we are processing the WM_LBUTTONDOWN Message
2117  *
2118  * NOTES
2119  *  This function is only to be used when a ListBox is a ComboListBox
2120  */
2121
2122 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2123 {
2124     RECT clientRect, screenRect;
2125     POINT mousePos;
2126
2127     mousePos.x = x;
2128     mousePos.y = y;
2129
2130     GetClientRect(descr->self, &clientRect);
2131
2132     if(PtInRect(&clientRect, mousePos))
2133     {
2134        /* MousePos is in client, resume normal processing */
2135         if (msg == WM_LBUTTONDOWN)
2136         {
2137            descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2138            return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2139         }
2140         else if (descr->style & LBS_NOTIFY)
2141             SEND_NOTIFICATION( descr, LBN_DBLCLK );
2142     }
2143     else
2144     {
2145         POINT screenMousePos;
2146         HWND hWndOldCapture;
2147
2148         /* Check the Non-Client Area */
2149         screenMousePos = mousePos;
2150         hWndOldCapture = GetCapture();
2151         ReleaseCapture();
2152         GetWindowRect(descr->self, &screenRect);
2153         ClientToScreen(descr->self, &screenMousePos);
2154
2155         if(!PtInRect(&screenRect, screenMousePos))
2156         {
2157             LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2158             LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2159             COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2160         }
2161         else
2162         {
2163             /* Check to see the NC is a scrollbar */
2164             INT nHitTestType=0;
2165             LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2166             /* Check Vertical scroll bar */
2167             if (style & WS_VSCROLL)
2168             {
2169                 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2170                 if (PtInRect( &clientRect, mousePos ))
2171                     nHitTestType = HTVSCROLL;
2172             }
2173               /* Check horizontal scroll bar */
2174             if (style & WS_HSCROLL)
2175             {
2176                 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2177                 if (PtInRect( &clientRect, mousePos ))
2178                     nHitTestType = HTHSCROLL;
2179             }
2180             /* Windows sends this message when a scrollbar is clicked
2181              */
2182
2183             if(nHitTestType != 0)
2184             {
2185                 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2186                              MAKELONG(screenMousePos.x, screenMousePos.y));
2187             }
2188             /* Resume the Capture after scrolling is complete
2189              */
2190             if(hWndOldCapture != 0)
2191                 SetCapture(hWndOldCapture);
2192         }
2193     }
2194     return 0;
2195 }
2196
2197 /***********************************************************************
2198  *           LISTBOX_HandleLButtonUp
2199  */
2200 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2201 {
2202     if (LISTBOX_Timer != LB_TIMER_NONE)
2203         KillSystemTimer( descr->self, LB_TIMER_ID );
2204     LISTBOX_Timer = LB_TIMER_NONE;
2205     if (descr->captured)
2206     {
2207         descr->captured = FALSE;
2208         if (GetCapture() == descr->self) ReleaseCapture();
2209         if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2210             SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2211     }
2212     return 0;
2213 }
2214
2215
2216 /***********************************************************************
2217  *           LISTBOX_HandleTimer
2218  *
2219  * Handle scrolling upon a timer event.
2220  * Return TRUE if scrolling should continue.
2221  */
2222 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2223 {
2224     switch(dir)
2225     {
2226     case LB_TIMER_UP:
2227         if (descr->top_item) index = descr->top_item - 1;
2228         else index = 0;
2229         break;
2230     case LB_TIMER_LEFT:
2231         if (descr->top_item) index -= descr->page_size;
2232         break;
2233     case LB_TIMER_DOWN:
2234         index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2235         if (index == descr->focus_item) index++;
2236         if (index >= descr->nb_items) index = descr->nb_items - 1;
2237         break;
2238     case LB_TIMER_RIGHT:
2239         if (index + descr->page_size < descr->nb_items)
2240             index += descr->page_size;
2241         break;
2242     case LB_TIMER_NONE:
2243         break;
2244     }
2245     if (index == descr->focus_item) return FALSE;
2246     LISTBOX_MoveCaret( descr, index, FALSE );
2247     return TRUE;
2248 }
2249
2250
2251 /***********************************************************************
2252  *           LISTBOX_HandleSystemTimer
2253  *
2254  * WM_SYSTIMER handler.
2255  */
2256 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2257 {
2258     if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2259     {
2260         KillSystemTimer( descr->self, LB_TIMER_ID );
2261         LISTBOX_Timer = LB_TIMER_NONE;
2262     }
2263     return 0;
2264 }
2265
2266
2267 /***********************************************************************
2268  *           LISTBOX_HandleMouseMove
2269  *
2270  * WM_MOUSEMOVE handler.
2271  */
2272 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2273                                      INT x, INT y )
2274 {
2275     INT index;
2276     TIMER_DIRECTION dir = LB_TIMER_NONE;
2277
2278     if (!descr->captured) return;
2279
2280     if (descr->style & LBS_MULTICOLUMN)
2281     {
2282         if (y < 0) y = 0;
2283         else if (y >= descr->item_height * descr->page_size)
2284             y = descr->item_height * descr->page_size - 1;
2285
2286         if (x < 0)
2287         {
2288             dir = LB_TIMER_LEFT;
2289             x = 0;
2290         }
2291         else if (x >= descr->width)
2292         {
2293             dir = LB_TIMER_RIGHT;
2294             x = descr->width - 1;
2295         }
2296     }
2297     else
2298     {
2299         if (y < 0) dir = LB_TIMER_UP;  /* above */
2300         else if (y >= descr->height) dir = LB_TIMER_DOWN;  /* below */
2301     }
2302
2303     index = LISTBOX_GetItemFromPoint( descr, x, y );
2304     if (index == -1) index = descr->focus_item;
2305     if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2306
2307     /* Start/stop the system timer */
2308
2309     if (dir != LB_TIMER_NONE)
2310         SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2311     else if (LISTBOX_Timer != LB_TIMER_NONE)
2312         KillSystemTimer( descr->self, LB_TIMER_ID );
2313     LISTBOX_Timer = dir;
2314 }
2315
2316
2317 /***********************************************************************
2318  *           LISTBOX_HandleKeyDown
2319  */
2320 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2321 {
2322     INT caret = -1;
2323     BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2324     if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2325         bForceSelection = FALSE; /* only for single select list */
2326
2327     if (descr->style & LBS_WANTKEYBOARDINPUT)
2328     {
2329         caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2330                                 MAKEWPARAM(LOWORD(key), descr->focus_item),
2331                                 (LPARAM)descr->self );
2332         if (caret == -2) return 0;
2333     }
2334     if (caret == -1) switch(key)
2335     {
2336     case VK_LEFT:
2337         if (descr->style & LBS_MULTICOLUMN)
2338         {
2339             bForceSelection = FALSE;
2340             if (descr->focus_item >= descr->page_size)
2341                 caret = descr->focus_item - descr->page_size;
2342             break;
2343         }
2344         /* fall through */
2345     case VK_UP:
2346         caret = descr->focus_item - 1;
2347         if (caret < 0) caret = 0;
2348         break;
2349     case VK_RIGHT:
2350         if (descr->style & LBS_MULTICOLUMN)
2351         {
2352             bForceSelection = FALSE;
2353             if (descr->focus_item + descr->page_size < descr->nb_items)
2354                 caret = descr->focus_item + descr->page_size;
2355             break;
2356         }
2357         /* fall through */
2358     case VK_DOWN:
2359         caret = descr->focus_item + 1;
2360         if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2361         break;
2362
2363     case VK_PRIOR:
2364         if (descr->style & LBS_MULTICOLUMN)
2365         {
2366             INT page = descr->width / descr->column_width;
2367             if (page < 1) page = 1;
2368             caret = descr->focus_item - (page * descr->page_size) + 1;
2369         }
2370         else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2371         if (caret < 0) caret = 0;
2372         break;
2373     case VK_NEXT:
2374         if (descr->style & LBS_MULTICOLUMN)
2375         {
2376             INT page = descr->width / descr->column_width;
2377             if (page < 1) page = 1;
2378             caret = descr->focus_item + (page * descr->page_size) - 1;
2379         }
2380         else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2381         if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2382         break;
2383     case VK_HOME:
2384         caret = 0;
2385         break;
2386     case VK_END:
2387         caret = descr->nb_items - 1;
2388         break;
2389     case VK_SPACE:
2390         if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2391         else if (descr->style & LBS_MULTIPLESEL)
2392         {
2393             LISTBOX_SetSelection( descr, descr->focus_item,
2394                                   !descr->items[descr->focus_item].selected,
2395                                   (descr->style & LBS_NOTIFY) != 0 );
2396         }
2397         break;
2398     default:
2399         bForceSelection = FALSE;
2400     }
2401     if (bForceSelection) /* focused item is used instead of key */
2402         caret = descr->focus_item;
2403     if (caret >= 0)
2404     {
2405         if (((descr->style & LBS_EXTENDEDSEL) &&
2406             !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2407             !IS_MULTISELECT(descr))
2408             descr->anchor_item = caret;
2409         LISTBOX_MoveCaret( descr, caret, TRUE );
2410
2411         if (descr->style & LBS_MULTIPLESEL)
2412             descr->selected_item = caret;
2413         else
2414             LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2415         if (descr->style & LBS_NOTIFY)
2416         {
2417             if (descr->lphc && IsWindowVisible( descr->self ))
2418             {
2419                 /* make sure that combo parent doesn't hide us */
2420                 descr->lphc->wState |= CBF_NOROLLUP;
2421             }
2422             if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2423         }
2424     }
2425     return 0;
2426 }
2427
2428
2429 /***********************************************************************
2430  *           LISTBOX_HandleChar
2431  */
2432 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2433 {
2434     INT caret = -1;
2435     WCHAR str[2];
2436
2437     str[0] = charW;
2438     str[1] = '\0';
2439
2440     if (descr->style & LBS_WANTKEYBOARDINPUT)
2441     {
2442         caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2443                                 MAKEWPARAM(charW, descr->focus_item),
2444                                 (LPARAM)descr->self );
2445         if (caret == -2) return 0;
2446     }
2447     if (caret == -1)
2448         caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2449     if (caret != -1)
2450     {
2451         if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2452            LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2453         LISTBOX_MoveCaret( descr, caret, TRUE );
2454         if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2455             SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2456     }
2457     return 0;
2458 }
2459
2460
2461 /***********************************************************************
2462  *           LISTBOX_Create
2463  */
2464 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2465 {
2466     LB_DESCR *descr;
2467     MEASUREITEMSTRUCT mis;
2468     RECT rect;
2469
2470     if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2471         return FALSE;
2472
2473     GetClientRect( hwnd, &rect );
2474     descr->self          = hwnd;
2475     descr->owner         = GetParent( descr->self );
2476     descr->style         = GetWindowLongW( descr->self, GWL_STYLE );
2477     descr->width         = rect.right - rect.left;
2478     descr->height        = rect.bottom - rect.top;
2479     descr->items         = NULL;
2480     descr->nb_items      = 0;
2481     descr->top_item      = 0;
2482     descr->selected_item = -1;
2483     descr->focus_item    = 0;
2484     descr->anchor_item   = -1;
2485     descr->item_height   = 1;
2486     descr->page_size     = 1;
2487     descr->column_width  = 150;
2488     descr->horz_extent   = (descr->style & WS_HSCROLL) ? 1 : 0;
2489     descr->horz_pos      = 0;
2490     descr->nb_tabs       = 0;
2491     descr->tabs          = NULL;
2492     descr->wheel_remain  = 0;
2493     descr->caret_on      = !lphc;
2494     if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2495     descr->in_focus      = FALSE;
2496     descr->captured      = FALSE;
2497     descr->font          = 0;
2498     descr->locale        = GetUserDefaultLCID();
2499     descr->lphc          = lphc;
2500
2501     if( lphc )
2502     {
2503         TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2504         descr->owner = lphc->self;
2505     }
2506
2507     SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2508
2509 /*    if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2510  */
2511     if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2512     if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2513     if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2514     descr->item_height = LISTBOX_SetFont( descr, 0 );
2515
2516     if (descr->style & LBS_OWNERDRAWFIXED)
2517     {
2518         if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2519         {
2520             /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2521           descr->item_height = lphc->fixedOwnerDrawHeight;
2522         }
2523         else
2524         {
2525             UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2526             mis.CtlType    = ODT_LISTBOX;
2527             mis.CtlID      = id;
2528             mis.itemID     = -1;
2529             mis.itemWidth  =  0;
2530             mis.itemData   =  0;
2531             mis.itemHeight = descr->item_height;
2532             SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2533             descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2534         }
2535     }
2536
2537     TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2538     return TRUE;
2539 }
2540
2541
2542 /***********************************************************************
2543  *           LISTBOX_Destroy
2544  */
2545 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2546 {
2547     LISTBOX_ResetContent( descr );
2548     SetWindowLongPtrW( descr->self, 0, 0 );
2549     HeapFree( GetProcessHeap(), 0, descr );
2550     return TRUE;
2551 }
2552
2553
2554 /***********************************************************************
2555  *           ListBoxWndProc_common
2556  */
2557 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2558 {
2559     LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2560     LPHEADCOMBO lphc = 0;
2561     LRESULT ret;
2562
2563     if (!descr)
2564     {
2565         if (!IsWindow(hwnd)) return 0;
2566
2567         if (msg == WM_CREATE)
2568         {
2569             CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2570             if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2571             if (!LISTBOX_Create( hwnd, lphc )) return -1;
2572             TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2573             return 0;
2574         }
2575         /* Ignore all other messages before we get a WM_CREATE */
2576         return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2577                          DefWindowProcA( hwnd, msg, wParam, lParam );
2578     }
2579     if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2580
2581     TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2582           descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2583
2584     switch(msg)
2585     {
2586     case LB_RESETCONTENT:
2587         LISTBOX_ResetContent( descr );
2588         LISTBOX_UpdateScroll( descr );
2589         InvalidateRect( descr->self, NULL, TRUE );
2590         return 0;
2591
2592     case LB_ADDSTRING:
2593     {
2594         INT ret;
2595         LPWSTR textW;
2596         if(unicode || !HAS_STRINGS(descr))
2597             textW = (LPWSTR)lParam;
2598         else
2599         {
2600             LPSTR textA = (LPSTR)lParam;
2601             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2602             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2603                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2604             else
2605                 return LB_ERRSPACE;
2606         }
2607         wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2608         ret = LISTBOX_InsertString( descr, wParam, textW );
2609         if (!unicode && HAS_STRINGS(descr))
2610             HeapFree(GetProcessHeap(), 0, textW);
2611         return ret;
2612     }
2613
2614     case LB_INSERTSTRING:
2615     {
2616         INT ret;
2617         LPWSTR textW;
2618         if(unicode || !HAS_STRINGS(descr))
2619             textW = (LPWSTR)lParam;
2620         else
2621         {
2622             LPSTR textA = (LPSTR)lParam;
2623             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2624             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2625                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2626             else
2627                 return LB_ERRSPACE;
2628         }
2629         ret = LISTBOX_InsertString( descr, wParam, textW );
2630         if(!unicode && HAS_STRINGS(descr))
2631             HeapFree(GetProcessHeap(), 0, textW);
2632         return ret;
2633     }
2634
2635     case LB_ADDFILE:
2636     {
2637         INT ret;
2638         LPWSTR textW;
2639         if(unicode || !HAS_STRINGS(descr))
2640             textW = (LPWSTR)lParam;
2641         else
2642         {
2643             LPSTR textA = (LPSTR)lParam;
2644             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2645             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2646                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2647             else
2648                 return LB_ERRSPACE;
2649         }
2650         wParam = LISTBOX_FindFileStrPos( descr, textW );
2651         ret = LISTBOX_InsertString( descr, wParam, textW );
2652         if(!unicode && HAS_STRINGS(descr))
2653             HeapFree(GetProcessHeap(), 0, textW);
2654         return ret;
2655     }
2656
2657     case LB_DELETESTRING:
2658         if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2659             return descr->nb_items;
2660         else
2661         {
2662             SetLastError(ERROR_INVALID_INDEX);
2663             return LB_ERR;
2664         }
2665
2666     case LB_GETITEMDATA:
2667         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2668         {
2669             SetLastError(ERROR_INVALID_INDEX);
2670             return LB_ERR;
2671         }
2672         return descr->items[wParam].data;
2673
2674     case LB_SETITEMDATA:
2675         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2676         {
2677             SetLastError(ERROR_INVALID_INDEX);
2678             return LB_ERR;
2679         }
2680         descr->items[wParam].data = lParam;
2681         /* undocumented: returns TRUE, not LB_OKAY (0) */
2682         return TRUE;
2683
2684     case LB_GETCOUNT:
2685         return descr->nb_items;
2686
2687     case LB_GETTEXT:
2688         return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2689
2690     case LB_GETTEXTLEN:
2691         if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2692         {
2693             SetLastError(ERROR_INVALID_INDEX);
2694             return LB_ERR;
2695         }
2696         if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2697         if (unicode) return strlenW( descr->items[wParam].str );
2698         return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2699                                     strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2700
2701     case LB_GETCURSEL:
2702         if (descr->nb_items == 0)
2703             return LB_ERR;
2704         if (!IS_MULTISELECT(descr))
2705             return descr->selected_item;
2706         if (descr->selected_item != -1)
2707             return descr->selected_item;
2708         return descr->focus_item;
2709         /* otherwise, if the user tries to move the selection with the    */
2710         /* arrow keys, we will give the application something to choke on */
2711     case LB_GETTOPINDEX:
2712         return descr->top_item;
2713
2714     case LB_GETITEMHEIGHT:
2715         return LISTBOX_GetItemHeight( descr, wParam );
2716
2717     case LB_SETITEMHEIGHT:
2718         return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2719
2720     case LB_ITEMFROMPOINT:
2721         {
2722             POINT pt;
2723             RECT rect;
2724             int index;
2725             BOOL hit = TRUE;
2726
2727             /* The hiword of the return value is not a client area
2728                hittest as suggested by MSDN, but rather a hittest on
2729                the returned listbox item. */
2730
2731             if(descr->nb_items == 0)
2732                 return 0x1ffff;      /* win9x returns 0x10000, we copy winnt */
2733
2734             pt.x = (short)LOWORD(lParam);
2735             pt.y = (short)HIWORD(lParam);
2736
2737             SetRect(&rect, 0, 0, descr->width, descr->height);
2738
2739             if(!PtInRect(&rect, pt))
2740             {
2741                 pt.x = min(pt.x, rect.right - 1);
2742                 pt.x = max(pt.x, 0);
2743                 pt.y = min(pt.y, rect.bottom - 1);
2744                 pt.y = max(pt.y, 0);
2745                 hit = FALSE;
2746             }
2747
2748             index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2749
2750             if(index == -1)
2751             {
2752                 index = descr->nb_items - 1;
2753                 hit = FALSE;
2754             }
2755             return MAKELONG(index, hit ? 0 : 1);
2756         }
2757
2758     case LB_SETCARETINDEX:
2759         if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2760         if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2761             return LB_ERR;
2762         else if (ISWIN31)
2763              return wParam;
2764         else
2765              return LB_OKAY;
2766
2767     case LB_GETCARETINDEX:
2768         return descr->focus_item;
2769
2770     case LB_SETTOPINDEX:
2771         return LISTBOX_SetTopItem( descr, wParam, TRUE );
2772
2773     case LB_SETCOLUMNWIDTH:
2774         return LISTBOX_SetColumnWidth( descr, wParam );
2775
2776     case LB_GETITEMRECT:
2777         return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2778
2779     case LB_FINDSTRING:
2780     {
2781         INT ret;
2782         LPWSTR textW;
2783         if(unicode || !HAS_STRINGS(descr))
2784             textW = (LPWSTR)lParam;
2785         else
2786         {
2787             LPSTR textA = (LPSTR)lParam;
2788             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2789             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2790                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2791         }
2792         ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2793         if(!unicode && HAS_STRINGS(descr))
2794             HeapFree(GetProcessHeap(), 0, textW);
2795         return ret;
2796     }
2797
2798     case LB_FINDSTRINGEXACT:
2799     {
2800         INT ret;
2801         LPWSTR textW;
2802         if(unicode || !HAS_STRINGS(descr))
2803             textW = (LPWSTR)lParam;
2804         else
2805         {
2806             LPSTR textA = (LPSTR)lParam;
2807             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2808             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2809                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2810         }
2811         ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2812         if(!unicode && HAS_STRINGS(descr))
2813             HeapFree(GetProcessHeap(), 0, textW);
2814         return ret;
2815     }
2816
2817     case LB_SELECTSTRING:
2818     {
2819         INT index;
2820         LPWSTR textW;
2821
2822         if(HAS_STRINGS(descr))
2823             TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2824                                                      debugstr_a((LPSTR)lParam));
2825         if(unicode || !HAS_STRINGS(descr))
2826             textW = (LPWSTR)lParam;
2827         else
2828         {
2829             LPSTR textA = (LPSTR)lParam;
2830             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2831             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2832                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2833         }
2834         index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2835         if(!unicode && HAS_STRINGS(descr))
2836             HeapFree(GetProcessHeap(), 0, textW);
2837         if (index != LB_ERR)
2838         {
2839             LISTBOX_MoveCaret( descr, index, TRUE );
2840             LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2841         }
2842         return index;
2843     }
2844
2845     case LB_GETSEL:
2846         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2847             return LB_ERR;
2848         return descr->items[wParam].selected;
2849
2850     case LB_SETSEL:
2851         return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2852
2853     case LB_SETCURSEL:
2854         if (IS_MULTISELECT(descr)) return LB_ERR;
2855         LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2856         ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2857         if (ret != LB_ERR) ret = descr->selected_item;
2858         return ret;
2859
2860     case LB_GETSELCOUNT:
2861         return LISTBOX_GetSelCount( descr );
2862
2863     case LB_GETSELITEMS:
2864         return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2865
2866     case LB_SELITEMRANGE:
2867         if (LOWORD(lParam) <= HIWORD(lParam))
2868             return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2869                                             HIWORD(lParam), wParam );
2870         else
2871             return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2872                                             LOWORD(lParam), wParam );
2873
2874     case LB_SELITEMRANGEEX:
2875         if ((INT)lParam >= (INT)wParam)
2876             return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2877         else
2878             return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2879
2880     case LB_GETHORIZONTALEXTENT:
2881         return descr->horz_extent;
2882
2883     case LB_SETHORIZONTALEXTENT:
2884         return LISTBOX_SetHorizontalExtent( descr, wParam );
2885
2886     case LB_GETANCHORINDEX:
2887         return descr->anchor_item;
2888
2889     case LB_SETANCHORINDEX:
2890         if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2891         {
2892             SetLastError(ERROR_INVALID_INDEX);
2893             return LB_ERR;
2894         }
2895         descr->anchor_item = (INT)wParam;
2896         return LB_OKAY;
2897
2898     case LB_DIR:
2899     {
2900         INT ret;
2901         LPWSTR textW;
2902         if(unicode)
2903             textW = (LPWSTR)lParam;
2904         else
2905         {
2906             LPSTR textA = (LPSTR)lParam;
2907             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2908             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2909                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2910         }
2911         ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2912         if(!unicode)
2913             HeapFree(GetProcessHeap(), 0, textW);
2914         return ret;
2915     }
2916
2917     case LB_GETLOCALE:
2918         return descr->locale;
2919
2920     case LB_SETLOCALE:
2921     {
2922         LCID ret;
2923         if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2924             return LB_ERR;
2925         ret = descr->locale;
2926         descr->locale = (LCID)wParam;
2927         return ret;
2928     }
2929
2930     case LB_INITSTORAGE:
2931         return LISTBOX_InitStorage( descr, wParam );
2932
2933     case LB_SETCOUNT:
2934         return LISTBOX_SetCount( descr, (INT)wParam );
2935
2936     case LB_SETTABSTOPS:
2937         return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2938
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;
2946
2947     case LB_CARETOFF:
2948         if (!descr->caret_on)
2949             return LB_OKAY;
2950         descr->caret_on = FALSE;
2951         if ((descr->focus_item != -1) && (descr->in_focus))
2952             LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2953         return LB_OKAY;
2954
2955     case LB_GETLISTBOXINFO:
2956         FIXME("LB_GETLISTBOXINFO: stub!\n");
2957         return 0;
2958
2959     case WM_DESTROY:
2960         return LISTBOX_Destroy( descr );
2961
2962     case WM_ENABLE:
2963         InvalidateRect( descr->self, NULL, TRUE );
2964         return 0;
2965
2966     case WM_SETREDRAW:
2967         LISTBOX_SetRedraw( descr, wParam != 0 );
2968         return 0;
2969
2970     case WM_GETDLGCODE:
2971         return DLGC_WANTARROWS | DLGC_WANTCHARS;
2972
2973     case WM_PRINTCLIENT:
2974     case WM_PAINT:
2975         {
2976             PAINTSTRUCT ps;
2977             HDC hdc = ( wParam ) ? ((HDC)wParam) :  BeginPaint( descr->self, &ps );
2978             ret = LISTBOX_Paint( descr, hdc );
2979             if( !wParam ) EndPaint( descr->self, &ps );
2980         }
2981         return ret;
2982     case WM_SIZE:
2983         LISTBOX_UpdateSize( descr );
2984         return 0;
2985     case WM_GETFONT:
2986         return (LRESULT)descr->font;
2987     case WM_SETFONT:
2988         LISTBOX_SetFont( descr, (HFONT)wParam );
2989         if (lParam) InvalidateRect( descr->self, 0, TRUE );
2990         return 0;
2991     case WM_SETFOCUS:
2992         descr->in_focus = TRUE;
2993         descr->caret_on = TRUE;
2994         if (descr->focus_item != -1)
2995             LISTBOX_DrawFocusRect( descr, TRUE );
2996         SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2997         return 0;
2998     case WM_KILLFOCUS:
2999         descr->in_focus = FALSE;
3000         descr->wheel_remain = 0;
3001         if ((descr->focus_item != -1) && descr->caret_on)
3002             LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3003         SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3004         return 0;
3005     case WM_HSCROLL:
3006         return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3007     case WM_VSCROLL:
3008         return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3009     case WM_MOUSEWHEEL:
3010         if (wParam & (MK_SHIFT | MK_CONTROL))
3011             return DefWindowProcW( descr->self, msg, wParam, lParam );
3012         return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3013     case WM_LBUTTONDOWN:
3014         if (lphc)
3015             return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3016                                                   (INT16)LOWORD(lParam),
3017                                                   (INT16)HIWORD(lParam) );
3018         return LISTBOX_HandleLButtonDown( descr, wParam,
3019                                           (INT16)LOWORD(lParam),
3020                                           (INT16)HIWORD(lParam) );
3021     case WM_LBUTTONDBLCLK:
3022         if (lphc)
3023             return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3024                                                   (INT16)LOWORD(lParam),
3025                                                   (INT16)HIWORD(lParam) );
3026         if (descr->style & LBS_NOTIFY)
3027             SEND_NOTIFICATION( descr, LBN_DBLCLK );
3028         return 0;
3029     case WM_MOUSEMOVE:
3030         if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3031         {
3032             BOOL    captured = descr->captured;
3033             POINT   mousePos;
3034             RECT    clientRect;
3035
3036             mousePos.x = (INT16)LOWORD(lParam);
3037             mousePos.y = (INT16)HIWORD(lParam);
3038
3039             /*
3040              * If we are in a dropdown combobox, we simulate that
3041              * the mouse is captured to show the tracking of the item.
3042              */
3043             if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3044                 descr->captured = TRUE;
3045
3046             LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3047
3048             descr->captured = captured;
3049         } 
3050         else if (GetCapture() == descr->self)
3051         {
3052             LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3053                                      (INT16)HIWORD(lParam) );
3054         }
3055         return 0;
3056     case WM_LBUTTONUP:
3057         if (lphc)
3058         {
3059             POINT mousePos;
3060             RECT  clientRect;
3061
3062             /*
3063              * If the mouse button "up" is not in the listbox,
3064              * we make sure there is no selection by re-selecting the
3065              * item that was selected when the listbox was made visible.
3066              */
3067             mousePos.x = (INT16)LOWORD(lParam);
3068             mousePos.y = (INT16)HIWORD(lParam);
3069
3070             GetClientRect(descr->self, &clientRect);
3071
3072             /*
3073              * When the user clicks outside the combobox and the focus
3074              * is lost, the owning combobox will send a fake buttonup with
3075              * 0xFFFFFFF as the mouse location, we must also revert the
3076              * selection to the original selection.
3077              */
3078             if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3079                 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3080         }
3081         return LISTBOX_HandleLButtonUp( descr );
3082     case WM_KEYDOWN:
3083         if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3084         {
3085             /* for some reason Windows makes it possible to
3086              * show/hide ComboLBox by sending it WM_KEYDOWNs */
3087
3088             if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3089                 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3090                   && (wParam == VK_DOWN || wParam == VK_UP)) )
3091             {
3092                 COMBO_FlipListbox( lphc, FALSE, FALSE );
3093                 return 0;
3094             }
3095         }
3096         return LISTBOX_HandleKeyDown( descr, wParam );
3097     case WM_CHAR:
3098     {
3099         WCHAR charW;
3100         if(unicode)
3101             charW = (WCHAR)wParam;
3102         else
3103         {
3104             CHAR charA = (CHAR)wParam;
3105             MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3106         }
3107         return LISTBOX_HandleChar( descr, charW );
3108     }
3109     case WM_SYSTIMER:
3110         return LISTBOX_HandleSystemTimer( descr );
3111     case WM_ERASEBKGND:
3112         if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3113         {
3114             RECT rect;
3115             HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3116                                               wParam, (LPARAM)descr->self );
3117             TRACE("hbrush = %p\n", hbrush);
3118             if(!hbrush)
3119                 hbrush = GetSysColorBrush(COLOR_WINDOW);
3120             if(hbrush)
3121             {
3122                 GetClientRect(descr->self, &rect);
3123                 FillRect((HDC)wParam, &rect, hbrush);
3124             }
3125         }
3126         return 1;
3127     case WM_DROPFILES:
3128         if( lphc ) return 0;
3129         return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3130                          SendMessageA( descr->owner, msg, wParam, lParam );
3131
3132     case WM_NCDESTROY:
3133         if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3134             lphc->hWndLBox = 0;
3135         break;
3136
3137     case WM_NCACTIVATE:
3138         if (lphc) return 0;
3139         break;
3140
3141     default:
3142         if ((msg >= WM_USER) && (msg < 0xc000))
3143             WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3144                  hwnd, msg, wParam, lParam );
3145     }
3146
3147     return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3148                      DefWindowProcA( hwnd, msg, wParam, lParam );
3149 }