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