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