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