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