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