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