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