Moved all Win16 definitions out of the standard Windows headers.
[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     LISTBOX_UpdateScroll( wnd, descr );
1624     InvalidateRect( wnd->hwndSelf, NULL, TRUE );
1625 }
1626
1627
1628 /***********************************************************************
1629  *           LISTBOX_SetCount
1630  */
1631 static LRESULT LISTBOX_SetCount( WND *wnd, LB_DESCR *descr, INT count )
1632 {
1633     LRESULT ret;
1634
1635     if (HAS_STRINGS(descr)) return LB_ERR;
1636     /* FIXME: this is far from optimal... */
1637     if (count > descr->nb_items)
1638     {
1639         while (count > descr->nb_items)
1640             if ((ret = LISTBOX_InsertString( wnd, descr, -1, 0 )) < 0)
1641                 return ret;
1642     }
1643     else if (count < descr->nb_items)
1644     {
1645         while (count < descr->nb_items)
1646             if ((ret = LISTBOX_RemoveItem( wnd, descr, -1 )) < 0)
1647                 return ret;
1648     }
1649     return LB_OKAY;
1650 }
1651
1652
1653 /***********************************************************************
1654  *           LISTBOX_Directory
1655  */
1656 static LRESULT LISTBOX_Directory( WND *wnd, LB_DESCR *descr, UINT attrib,
1657                                   LPCSTR filespec, BOOL long_names )
1658 {
1659     HANDLE handle;
1660     LRESULT ret = LB_OKAY;
1661     WIN32_FIND_DATAA entry;
1662     int pos;
1663
1664     if ((handle = FindFirstFileA(filespec,&entry)) == INVALID_HANDLE_VALUE)
1665     {
1666         if (GetLastError() != ERROR_NO_MORE_FILES) return LB_ERR;
1667     }
1668     else
1669     {
1670         do
1671         {
1672             char buffer[270];
1673             if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1674             {
1675                 if (!(attrib & DDL_DIRECTORY) ||
1676                     !strcmp( entry.cAlternateFileName, "." )) continue;
1677                 if (long_names) sprintf( buffer, "[%s]", entry.cFileName );
1678                 else sprintf( buffer, "[%s]", entry.cAlternateFileName );
1679             }
1680             else  /* not a directory */
1681             {
1682 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1683                  FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1684
1685                 if ((attrib & DDL_EXCLUSIVE) &&
1686                     ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1687                     continue;
1688 #undef ATTRIBS
1689                 if (long_names) strcpy( buffer, entry.cFileName );
1690                 else strcpy( buffer, entry.cAlternateFileName );
1691             }
1692             if (!long_names) CharLowerA( buffer );
1693             pos = LISTBOX_FindFileStrPos( wnd, descr, buffer );
1694             if ((ret = LISTBOX_InsertString( wnd, descr, pos, buffer )) < 0)
1695                 break;
1696         } while (FindNextFileA( handle, &entry ));
1697         FindClose( handle );
1698     }
1699
1700     if ((ret >= 0) && (attrib & DDL_DRIVES))
1701     {
1702         char buffer[] = "[-a-]";
1703         int drive;
1704         for (drive = 0; drive < MAX_DOS_DRIVES; drive++, buffer[2]++)
1705         {
1706             if (!DRIVE_IsValid(drive)) continue;
1707             if ((ret = LISTBOX_InsertString( wnd, descr, -1, buffer )) < 0)
1708                 break;
1709         }
1710     }
1711     return ret;
1712 }
1713
1714
1715 /***********************************************************************
1716  *           LISTBOX_HandleVScroll
1717  */
1718 static LRESULT LISTBOX_HandleVScroll( WND *wnd, LB_DESCR *descr,
1719                                       WPARAM wParam, LPARAM lParam )
1720 {
1721     SCROLLINFO info;
1722
1723     if (descr->style & LBS_MULTICOLUMN) return 0;
1724     switch(LOWORD(wParam))
1725     {
1726     case SB_LINEUP:
1727         LISTBOX_SetTopItem( wnd, descr, descr->top_item - 1, TRUE );
1728         break;
1729     case SB_LINEDOWN:
1730         LISTBOX_SetTopItem( wnd, descr, descr->top_item + 1, TRUE );
1731         break;
1732     case SB_PAGEUP:
1733         LISTBOX_SetTopItem( wnd, descr, descr->top_item -
1734                             LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1735         break;
1736     case SB_PAGEDOWN:
1737         LISTBOX_SetTopItem( wnd, descr, descr->top_item +
1738                             LISTBOX_GetCurrentPageSize( wnd, descr ), TRUE );
1739         break;
1740     case SB_THUMBPOSITION:
1741         LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam), TRUE );
1742         break;
1743     case SB_THUMBTRACK:
1744         info.cbSize = sizeof(info);
1745         info.fMask = SIF_TRACKPOS;
1746         GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1747         LISTBOX_SetTopItem( wnd, descr, info.nTrackPos, TRUE );
1748         break;
1749     case SB_TOP:
1750         LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1751         break;
1752     case SB_BOTTOM:
1753         LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1754         break;
1755     }
1756     return 0;
1757 }
1758
1759
1760 /***********************************************************************
1761  *           LISTBOX_HandleHScroll
1762  */
1763 static LRESULT LISTBOX_HandleHScroll( WND *wnd, LB_DESCR *descr,
1764                                       WPARAM wParam, LPARAM lParam )
1765 {
1766     SCROLLINFO info;
1767     INT page;
1768
1769     if (descr->style & LBS_MULTICOLUMN)
1770     {
1771         switch(LOWORD(wParam))
1772         {
1773         case SB_LINELEFT:
1774             LISTBOX_SetTopItem( wnd, descr, descr->top_item-descr->page_size,
1775                                 TRUE );
1776             break;
1777         case SB_LINERIGHT:
1778             LISTBOX_SetTopItem( wnd, descr, descr->top_item+descr->page_size,
1779                                 TRUE );
1780             break;
1781         case SB_PAGELEFT:
1782             page = descr->width / descr->column_width;
1783             if (page < 1) page = 1;
1784             LISTBOX_SetTopItem( wnd, descr,
1785                              descr->top_item - page * descr->page_size, TRUE );
1786             break;
1787         case SB_PAGERIGHT:
1788             page = descr->width / descr->column_width;
1789             if (page < 1) page = 1;
1790             LISTBOX_SetTopItem( wnd, descr,
1791                              descr->top_item + page * descr->page_size, TRUE );
1792             break;
1793         case SB_THUMBPOSITION:
1794             LISTBOX_SetTopItem( wnd, descr, HIWORD(wParam)*descr->page_size,
1795                                 TRUE );
1796             break;
1797         case SB_THUMBTRACK:
1798             info.cbSize = sizeof(info);
1799             info.fMask  = SIF_TRACKPOS;
1800             GetScrollInfo( wnd->hwndSelf, SB_VERT, &info );
1801             LISTBOX_SetTopItem( wnd, descr, info.nTrackPos*descr->page_size,
1802                                 TRUE );
1803             break;
1804         case SB_LEFT:
1805             LISTBOX_SetTopItem( wnd, descr, 0, TRUE );
1806             break;
1807         case SB_RIGHT:
1808             LISTBOX_SetTopItem( wnd, descr, descr->nb_items, TRUE );
1809             break;
1810         }
1811     }
1812     else if (descr->horz_extent)
1813     {
1814         switch(LOWORD(wParam))
1815         {
1816         case SB_LINELEFT:
1817             LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos - 1 );
1818             break;
1819         case SB_LINERIGHT:
1820             LISTBOX_SetHorizontalPos( wnd, descr, descr->horz_pos + 1 );
1821             break;
1822         case SB_PAGELEFT:
1823             LISTBOX_SetHorizontalPos( wnd, descr,
1824                                       descr->horz_pos - descr->width );
1825             break;
1826         case SB_PAGERIGHT:
1827             LISTBOX_SetHorizontalPos( wnd, descr,
1828                                       descr->horz_pos + descr->width );
1829             break;
1830         case SB_THUMBPOSITION:
1831             LISTBOX_SetHorizontalPos( wnd, descr, HIWORD(wParam) );
1832             break;
1833         case SB_THUMBTRACK:
1834             info.cbSize = sizeof(info);
1835             info.fMask = SIF_TRACKPOS;
1836             GetScrollInfo( wnd->hwndSelf, SB_HORZ, &info );
1837             LISTBOX_SetHorizontalPos( wnd, descr, info.nTrackPos );
1838             break;
1839         case SB_LEFT:
1840             LISTBOX_SetHorizontalPos( wnd, descr, 0 );
1841             break;
1842         case SB_RIGHT:
1843             LISTBOX_SetHorizontalPos( wnd, descr,
1844                                       descr->horz_extent - descr->width );
1845             break;
1846         }
1847     }
1848     return 0;
1849 }
1850
1851 static LRESULT LISTBOX_HandleMouseWheel(WND *wnd, LB_DESCR *descr,WPARAM wParam, LPARAM lParam )
1852 {
1853     short gcWheelDelta = 0;
1854     UINT pulScrollLines = 3;
1855
1856     SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1857
1858     gcWheelDelta -= (short) HIWORD(wParam);
1859
1860     if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
1861     {
1862         int cLineScroll = (int) min((UINT) descr->page_size, pulScrollLines);
1863         cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
1864         LISTBOX_SetTopItem( wnd, descr, descr->top_item + cLineScroll, TRUE );
1865     }
1866     return 0;
1867 }
1868
1869 /***********************************************************************
1870  *           LISTBOX_HandleLButtonDown
1871  */
1872 static LRESULT LISTBOX_HandleLButtonDown( WND *wnd, LB_DESCR *descr,
1873                                           WPARAM wParam, INT x, INT y )
1874 {
1875     INT index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
1876     TRACE("[%04x]: lbuttondown %d,%d item %d\n",
1877                  wnd->hwndSelf, x, y, index );
1878     if (!descr->caret_on && (descr->in_focus)) return 0;
1879
1880     if (!descr->in_focus)
1881     {
1882         if( !descr->lphc ) SetFocus( wnd->hwndSelf );
1883         else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit
1884                                              : descr->lphc->self->hwndSelf );
1885     }
1886
1887     if (index != -1)
1888     {
1889         if (descr->style & LBS_EXTENDEDSEL)
1890         {
1891             /* we should perhaps make sure that all items are deselected
1892                FIXME: needed for !LBS_EXTENDEDSEL, too ?
1893             if (!(wParam & (MK_SHIFT|MK_CONTROL)))
1894                 LISTBOX_SetSelection( wnd, descr, -1, FALSE, FALSE);
1895             */
1896
1897             if (!(wParam & MK_SHIFT)) descr->anchor_item = index;
1898             if (wParam & MK_CONTROL)
1899             {
1900                 LISTBOX_SetCaretIndex( wnd, descr, index, FALSE );
1901                 LISTBOX_SetSelection( wnd, descr, index,
1902                                       !descr->items[index].selected,
1903                                       (descr->style & LBS_NOTIFY) != 0);
1904             }
1905             else LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1906         }
1907         else
1908         {
1909             LISTBOX_MoveCaret( wnd, descr, index, FALSE );
1910             LISTBOX_SetSelection( wnd, descr, index,
1911                                   (!(descr->style & LBS_MULTIPLESEL) ||
1912                                    !descr->items[index].selected),
1913                                   (descr->style & LBS_NOTIFY) != 0 );
1914         }
1915     }
1916
1917     descr->captured = TRUE;
1918     SetCapture( wnd->hwndSelf );
1919     if (index != -1 && !descr->lphc)
1920     {
1921         if (descr->style & LBS_NOTIFY )
1922             SendMessageA( descr->owner, WM_LBTRACKPOINT, index,
1923                             MAKELPARAM( x, y ) );
1924         if (wnd->dwExStyle & WS_EX_DRAGDETECT)
1925         {
1926             POINT pt;
1927             
1928             pt.x = x;
1929             pt.y = y;
1930
1931             if (DragDetect( wnd->hwndSelf, pt ))
1932                 SendMessageA( descr->owner, WM_BEGINDRAG, 0, 0 );
1933         }
1934     }
1935     return 0;
1936 }
1937
1938
1939 /*************************************************************************
1940  * LISTBOX_HandleLButtonDownCombo [Internal] 
1941  *
1942  * Process LButtonDown message for the ComboListBox
1943  *
1944  * PARAMS
1945  *     pWnd       [I] The windows internal structure
1946  *     pDescr     [I] The ListBox internal structure
1947  *     wParam     [I] Key Flag (WM_LBUTTONDOWN doc for more info)
1948  *     x          [I] X Mouse Coordinate
1949  *     y          [I] Y Mouse Coordinate
1950  *
1951  * RETURNS
1952  *     0 since we are processing the WM_LBUTTONDOWN Message
1953  *
1954  * NOTES
1955  *  This function is only to be used when a ListBox is a ComboListBox
1956  */
1957
1958 static LRESULT LISTBOX_HandleLButtonDownCombo( WND *pWnd, LB_DESCR *pDescr,
1959                                                UINT msg, WPARAM wParam, INT x, INT y)
1960 {
1961     RECT clientRect, screenRect;
1962     POINT mousePos;
1963
1964     mousePos.x = x;
1965     mousePos.y = y;
1966
1967     GetClientRect(pWnd->hwndSelf, &clientRect);
1968
1969     if(PtInRect(&clientRect, mousePos))
1970     {  
1971        /* MousePos is in client, resume normal processing */
1972         if (msg == WM_LBUTTONDOWN)
1973         {
1974            pDescr->lphc->droppedIndex = pDescr->nb_items ? pDescr->selected_item : -1;
1975            return LISTBOX_HandleLButtonDown( pWnd, pDescr, wParam, x, y);
1976         }
1977         else if (pDescr->style & LBS_NOTIFY)
1978             SEND_NOTIFICATION( pWnd, pDescr, LBN_DBLCLK );
1979         return 0;
1980     }
1981     else
1982     {
1983         POINT screenMousePos;
1984         HWND hWndOldCapture;
1985
1986         /* Check the Non-Client Area */
1987         screenMousePos = mousePos;
1988         hWndOldCapture = GetCapture();
1989         ReleaseCapture();
1990         GetWindowRect(pWnd->hwndSelf, &screenRect);
1991         ClientToScreen(pWnd->hwndSelf, &screenMousePos);
1992
1993         if(!PtInRect(&screenRect, screenMousePos))
1994         { 
1995             LISTBOX_SetSelection( pWnd, pDescr, pDescr->lphc->droppedIndex, FALSE, FALSE );
1996             COMBO_FlipListbox( pDescr->lphc, FALSE, FALSE );
1997             return 0;
1998         }
1999         else
2000         {
2001             /* Check to see the NC is a scrollbar */
2002             INT nHitTestType=0;
2003             /* Check Vertical scroll bar */
2004             if (pWnd->dwStyle & WS_VSCROLL)
2005             {
2006                 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2007                 if (PtInRect( &clientRect, mousePos )) 
2008                 {
2009                     nHitTestType = HTVSCROLL;
2010                 }
2011             }
2012               /* Check horizontal scroll bar */
2013             if (pWnd->dwStyle & WS_HSCROLL)
2014             {
2015                 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2016                 if (PtInRect( &clientRect, mousePos ))
2017                 {
2018                     nHitTestType = HTHSCROLL;
2019                 }
2020             }
2021             /* Windows sends this message when a scrollbar is clicked 
2022              */
2023             
2024             if(nHitTestType != 0)
2025             {
2026                 SendMessageA(pWnd->hwndSelf, WM_NCLBUTTONDOWN, nHitTestType, 
2027                     MAKELONG(screenMousePos.x, screenMousePos.y));
2028             }
2029             /* Resume the Capture after scrolling is complete 
2030              */
2031             if(hWndOldCapture != 0)
2032             {
2033                 SetCapture(hWndOldCapture);
2034             }
2035         }
2036     }
2037     return 0;
2038 }
2039
2040 /***********************************************************************
2041  *           LISTBOX_HandleLButtonUp
2042  */
2043 static LRESULT LISTBOX_HandleLButtonUp( WND *wnd, LB_DESCR *descr )
2044 {
2045     if (LISTBOX_Timer != LB_TIMER_NONE)
2046         KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2047     LISTBOX_Timer = LB_TIMER_NONE;
2048     if (descr->captured)
2049     {
2050         descr->captured = FALSE;
2051         if (GetCapture() == wnd->hwndSelf) ReleaseCapture();
2052         if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2053             SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2054     }
2055     return 0;
2056 }
2057
2058
2059 /***********************************************************************
2060  *           LISTBOX_HandleTimer
2061  *
2062  * Handle scrolling upon a timer event.
2063  * Return TRUE if scrolling should continue.
2064  */
2065 static LRESULT LISTBOX_HandleTimer( WND *wnd, LB_DESCR *descr,
2066                                     INT index, TIMER_DIRECTION dir )
2067 {
2068     switch(dir)
2069     {
2070     case LB_TIMER_UP:
2071         if (descr->top_item) index = descr->top_item - 1;
2072         else index = 0;
2073         break;
2074     case LB_TIMER_LEFT:
2075         if (descr->top_item) index -= descr->page_size;
2076         break;
2077     case LB_TIMER_DOWN:
2078         index = descr->top_item + LISTBOX_GetCurrentPageSize( wnd, descr );
2079         if (index == descr->focus_item) index++;
2080         if (index >= descr->nb_items) index = descr->nb_items - 1;
2081         break;
2082     case LB_TIMER_RIGHT:
2083         if (index + descr->page_size < descr->nb_items)
2084             index += descr->page_size;
2085         break;
2086     case LB_TIMER_NONE:
2087         break;
2088     }
2089     if (index == descr->focus_item) return FALSE;
2090     LISTBOX_MoveCaret( wnd, descr, index, FALSE );
2091     return TRUE;
2092 }
2093
2094
2095 /***********************************************************************
2096  *           LISTBOX_HandleSystemTimer
2097  *
2098  * WM_SYSTIMER handler.
2099  */
2100 static LRESULT LISTBOX_HandleSystemTimer( WND *wnd, LB_DESCR *descr )
2101 {
2102     if (!LISTBOX_HandleTimer( wnd, descr, descr->focus_item, LISTBOX_Timer ))
2103     {
2104         KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2105         LISTBOX_Timer = LB_TIMER_NONE;
2106     }
2107     return 0;
2108 }
2109
2110
2111 /***********************************************************************
2112  *           LISTBOX_HandleMouseMove
2113  *
2114  * WM_MOUSEMOVE handler.
2115  */
2116 static void LISTBOX_HandleMouseMove( WND *wnd, LB_DESCR *descr,
2117                                      INT x, INT y )
2118 {
2119     INT index;
2120     TIMER_DIRECTION dir = LB_TIMER_NONE;
2121
2122     if (!descr->captured) return;
2123
2124     if (descr->style & LBS_MULTICOLUMN)
2125     {
2126         if (y < 0) y = 0;
2127         else if (y >= descr->item_height * descr->page_size)
2128             y = descr->item_height * descr->page_size - 1;
2129
2130         if (x < 0)
2131         {
2132             dir = LB_TIMER_LEFT;
2133             x = 0;
2134         }
2135         else if (x >= descr->width)
2136         {
2137             dir = LB_TIMER_RIGHT;
2138             x = descr->width - 1;
2139         }
2140     }
2141     else
2142     {
2143         if (y < 0) dir = LB_TIMER_UP;  /* above */
2144         else if (y >= descr->height) dir = LB_TIMER_DOWN;  /* below */
2145     }
2146
2147     index = LISTBOX_GetItemFromPoint( wnd, descr, x, y );
2148     if (index == -1) index = descr->focus_item;
2149     if (!LISTBOX_HandleTimer( wnd, descr, index, dir )) dir = LB_TIMER_NONE;
2150
2151     /* Start/stop the system timer */
2152
2153     if (dir != LB_TIMER_NONE)
2154         SetSystemTimer( wnd->hwndSelf, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2155     else if (LISTBOX_Timer != LB_TIMER_NONE)
2156         KillSystemTimer( wnd->hwndSelf, LB_TIMER_ID );
2157     LISTBOX_Timer = dir;
2158 }
2159
2160
2161 /***********************************************************************
2162  *           LISTBOX_HandleKeyDown
2163  */
2164 static LRESULT LISTBOX_HandleKeyDown( WND *wnd, LB_DESCR *descr, WPARAM wParam )
2165 {
2166     INT caret = -1;
2167     BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2168     if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2169        bForceSelection = FALSE; /* only for single select list */
2170
2171     if (descr->style & LBS_WANTKEYBOARDINPUT)
2172     {
2173         caret = SendMessageA( descr->owner, WM_VKEYTOITEM,
2174                                 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2175                                 wnd->hwndSelf );
2176         if (caret == -2) return 0;
2177     }
2178     if (caret == -1) switch(wParam)
2179     {
2180     case VK_LEFT:
2181         if (descr->style & LBS_MULTICOLUMN)
2182         {
2183             bForceSelection = FALSE;
2184             if (descr->focus_item >= descr->page_size)
2185                 caret = descr->focus_item - descr->page_size;
2186             break;
2187         }
2188         /* fall through */
2189     case VK_UP:
2190         caret = descr->focus_item - 1;
2191         if (caret < 0) caret = 0;
2192         break;
2193     case VK_RIGHT:
2194         if (descr->style & LBS_MULTICOLUMN)
2195         {
2196             bForceSelection = FALSE;
2197             if (descr->focus_item + descr->page_size < descr->nb_items)
2198                 caret = descr->focus_item + descr->page_size;
2199             break;
2200         }
2201         /* fall through */
2202     case VK_DOWN:
2203         caret = descr->focus_item + 1;
2204         if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2205         break;
2206
2207     case VK_PRIOR:
2208         if (descr->style & LBS_MULTICOLUMN)
2209         {
2210             INT page = descr->width / descr->column_width;
2211             if (page < 1) page = 1;
2212             caret = descr->focus_item - (page * descr->page_size) + 1;
2213         }
2214         else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(wnd,descr)+1;
2215         if (caret < 0) caret = 0;
2216         break;
2217     case VK_NEXT:
2218         if (descr->style & LBS_MULTICOLUMN)
2219         {
2220             INT page = descr->width / descr->column_width;
2221             if (page < 1) page = 1;
2222             caret = descr->focus_item + (page * descr->page_size) - 1;
2223         }
2224         else caret = descr->focus_item+LISTBOX_GetCurrentPageSize(wnd,descr)-1;
2225         if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2226         break;
2227     case VK_HOME:
2228         caret = 0;
2229         break;
2230     case VK_END:
2231         caret = descr->nb_items - 1;
2232         break;
2233     case VK_SPACE:
2234         if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2235         else if (descr->style & LBS_MULTIPLESEL)
2236         {
2237             LISTBOX_SetSelection( wnd, descr, descr->focus_item,
2238                                   !descr->items[descr->focus_item].selected,
2239                                   (descr->style & LBS_NOTIFY) != 0 );
2240         }
2241         break;
2242     default:
2243         bForceSelection = FALSE;
2244     }
2245     if (bForceSelection) /* focused item is used instead of key */
2246         caret = descr->focus_item;
2247     if (caret >= 0)
2248     {
2249         if ((descr->style & LBS_EXTENDEDSEL) &&
2250             !(GetKeyState( VK_SHIFT ) & 0x8000))
2251             descr->anchor_item = caret;
2252         LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2253         LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2254         if (descr->style & LBS_NOTIFY)
2255         {
2256             if( descr->lphc && CB_GETTYPE(descr->lphc) != CBS_SIMPLE )
2257             {
2258                 /* make sure that combo parent doesn't hide us */
2259                 descr->lphc->wState |= CBF_NOROLLUP;
2260             }
2261             if (descr->nb_items) SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2262         }
2263     }
2264     return 0;
2265 }
2266
2267
2268 /***********************************************************************
2269  *           LISTBOX_HandleChar
2270  */
2271 static LRESULT LISTBOX_HandleChar( WND *wnd, LB_DESCR *descr,
2272                                    WPARAM wParam )
2273 {
2274     INT caret = -1;
2275     char str[2];
2276     
2277     str[0] = wParam & 0xff; 
2278     str[1] = '\0';
2279
2280     if (descr->style & LBS_WANTKEYBOARDINPUT)
2281     {
2282         caret = SendMessageA( descr->owner, WM_CHARTOITEM,
2283                                 MAKEWPARAM(LOWORD(wParam), descr->focus_item),
2284                                 wnd->hwndSelf );
2285         if (caret == -2) return 0;
2286     }
2287     if (caret == -1)
2288         caret = LISTBOX_FindString( wnd, descr, descr->focus_item, str, FALSE);
2289     if (caret != -1)
2290     {
2291         if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2292            LISTBOX_SetSelection( wnd, descr, caret, TRUE, FALSE);
2293         LISTBOX_MoveCaret( wnd, descr, caret, TRUE );
2294         if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2295             SEND_NOTIFICATION( wnd, descr, LBN_SELCHANGE );
2296     }
2297     return 0;
2298 }
2299
2300
2301 /***********************************************************************
2302  *           LISTBOX_Create
2303  */
2304 static BOOL LISTBOX_Create( WND *wnd, LPHEADCOMBO lphc )
2305 {
2306     LB_DESCR *descr;
2307     MEASUREITEMSTRUCT mis;
2308     RECT rect;
2309
2310     if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2311         return FALSE;
2312     if (!(descr->heap = HeapCreate( 0, 0x10000, 0 )))
2313     {
2314         HeapFree( GetProcessHeap(), 0, descr );
2315         return FALSE;
2316     }
2317     GetClientRect( wnd->hwndSelf, &rect );
2318     descr->owner         = GetParent( wnd->hwndSelf );
2319     descr->style         = wnd->dwStyle;
2320     descr->width         = rect.right - rect.left;
2321     descr->height        = rect.bottom - rect.top;
2322     descr->items         = NULL;
2323     descr->nb_items      = 0;
2324     descr->top_item      = 0;
2325     descr->selected_item = -1;
2326     descr->focus_item    = 0;
2327     descr->anchor_item   = -1;
2328     descr->item_height   = 1;
2329     descr->page_size     = 1;
2330     descr->column_width  = 150;
2331     descr->horz_extent   = (wnd->dwStyle & WS_HSCROLL) ? 1 : 0;
2332     descr->horz_pos      = 0;
2333     descr->nb_tabs       = 0;
2334     descr->tabs          = NULL;
2335     descr->caret_on      = lphc ? FALSE : TRUE;
2336     descr->in_focus      = FALSE;
2337     descr->captured      = FALSE;
2338     descr->font          = 0;
2339     descr->locale        = 0;  /* FIXME */
2340     descr->lphc          = lphc;
2341
2342     if( ( GetExpWinVer16( wnd->hInstance ) & 0xFF00 ) == 0x0300
2343         && ( descr->style & ( WS_VSCROLL | WS_HSCROLL ) ) )
2344     {
2345         /* Win95 document "List Box Differences" from MSDN:
2346            If a list box in a version 3.x application has either the
2347            WS_HSCROLL or WS_VSCROLL style, the list box receives both
2348            horizontal and vertical scroll bars.
2349         */
2350         descr->style |= WS_VSCROLL | WS_HSCROLL;
2351     }
2352
2353     if( lphc )
2354     {
2355         TRACE_(combo)("[%04x]: resetting owner %04x -> %04x\n",
2356                      wnd->hwndSelf, descr->owner, lphc->self->hwndSelf );
2357         descr->owner = lphc->self->hwndSelf;
2358     }
2359
2360     *(LB_DESCR **)wnd->wExtra = descr;
2361
2362 /*    if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2363  */
2364     if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2365     if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2366     if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2367     descr->item_height = LISTBOX_SetFont( wnd, descr, 0 );
2368
2369     if (descr->style & LBS_OWNERDRAWFIXED)
2370     {
2371         if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2372         {
2373             /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2374           descr->item_height = lphc->fixedOwnerDrawHeight;
2375         }
2376         else
2377         {
2378             mis.CtlType    = ODT_LISTBOX;
2379             mis.CtlID      = wnd->wIDmenu;
2380             mis.itemID     = -1;
2381             mis.itemWidth  =  0;
2382             mis.itemData   =  0;
2383             mis.itemHeight = descr->item_height;
2384             SendMessageA( descr->owner, WM_MEASUREITEM, wnd->wIDmenu, (LPARAM)&mis );
2385             descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2386         }
2387     }
2388
2389     return TRUE;
2390 }
2391
2392
2393 /***********************************************************************
2394  *           LISTBOX_Destroy
2395  */
2396 static BOOL LISTBOX_Destroy( WND *wnd, LB_DESCR *descr )
2397 {
2398     LISTBOX_ResetContent( wnd, descr );
2399     HeapDestroy( descr->heap );
2400     HeapFree( GetProcessHeap(), 0, descr );
2401     wnd->wExtra[0] = 0;
2402     return TRUE;
2403 }
2404
2405
2406 /***********************************************************************
2407  *           ListBoxWndProc
2408  */
2409 static inline LRESULT WINAPI ListBoxWndProc_locked( WND* wnd, UINT msg,
2410                                       WPARAM wParam, LPARAM lParam )
2411 {
2412     LRESULT ret;
2413     LB_DESCR *descr;
2414     HWND        hwnd = wnd->hwndSelf;
2415
2416     if (!wnd) return 0;
2417     if (!(descr = *(LB_DESCR **)wnd->wExtra))
2418     {
2419         switch (msg)
2420         {
2421             case WM_CREATE:
2422             {
2423                 if (!LISTBOX_Create( wnd, NULL ))
2424                      return -1;
2425                 TRACE("creating wnd=%04x descr=%p\n",
2426                       hwnd, *(LB_DESCR **)wnd->wExtra );
2427                 return 0;
2428             }
2429             case WM_NCCREATE:
2430             {
2431                 /*
2432                  * When a listbox is not in a combobox and the look
2433                  * is win95,  the WS_BORDER style is replaced with 
2434                  * the WS_EX_CLIENTEDGE style.
2435                  */
2436                 if ( (TWEAK_WineLook > WIN31_LOOK) &&
2437                      (wnd->dwStyle & WS_BORDER) )
2438                 {
2439                     wnd->dwExStyle |= WS_EX_CLIENTEDGE;
2440                     wnd->dwStyle     &= ~ WS_BORDER;
2441                 }
2442             }
2443         }
2444
2445         /* Ignore all other messages before we get a WM_CREATE */
2446         return DefWindowProcA( hwnd, msg, wParam, lParam );
2447     }
2448
2449     TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
2450                  wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2451     switch(msg)
2452     {
2453     case LB_RESETCONTENT16:
2454     case LB_RESETCONTENT:
2455         LISTBOX_ResetContent( wnd, descr );
2456         return 0;
2457
2458     case LB_ADDSTRING16:
2459         if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2460         /* fall through */
2461     case LB_ADDSTRING:
2462         wParam = LISTBOX_FindStringPos( wnd, descr, (LPCSTR)lParam, FALSE );
2463         return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2464
2465     case LB_INSERTSTRING16:
2466         if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2467         wParam = (INT)(INT16)wParam;
2468         /* fall through */
2469     case LB_INSERTSTRING:
2470         return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2471
2472     case LB_ADDFILE16:
2473         if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2474         /* fall through */
2475     case LB_ADDFILE:
2476         wParam = LISTBOX_FindFileStrPos( wnd, descr, (LPCSTR)lParam );
2477         return LISTBOX_InsertString( wnd, descr, wParam, (LPCSTR)lParam );
2478
2479     case LB_DELETESTRING16:
2480     case LB_DELETESTRING:
2481         if (LISTBOX_RemoveItem( wnd, descr, wParam) != LB_ERR)
2482            return descr->nb_items;
2483         else
2484            return LB_ERR;       
2485
2486     case LB_GETITEMDATA16:
2487     case LB_GETITEMDATA:
2488         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2489             return LB_ERR;
2490         return descr->items[wParam].data;
2491
2492     case LB_SETITEMDATA16:
2493     case LB_SETITEMDATA:
2494         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2495             return LB_ERR;
2496         descr->items[wParam].data = (DWORD)lParam;
2497         return LB_OKAY;
2498
2499     case LB_GETCOUNT16:
2500     case LB_GETCOUNT:
2501         return descr->nb_items;
2502
2503     case LB_GETTEXT16:
2504         lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2505         /* fall through */
2506     case LB_GETTEXT:
2507         return LISTBOX_GetText( wnd, descr, wParam, (LPSTR)lParam );
2508
2509     case LB_GETTEXTLEN16:
2510         /* fall through */
2511     case LB_GETTEXTLEN:
2512         if (wParam >= descr->nb_items)
2513             return LB_ERR;
2514         return (HAS_STRINGS(descr) ? strlen(descr->items[wParam].str)
2515                                    : sizeof(DWORD));
2516
2517     case LB_GETCURSEL16:
2518     case LB_GETCURSEL:
2519         if (descr->nb_items==0)
2520           return LB_ERR;
2521         if (!IS_MULTISELECT(descr))
2522           return descr->selected_item;
2523         /* else */
2524         if (descr->selected_item!=-1)
2525           return descr->selected_item;
2526         /* else */
2527         return descr->focus_item;
2528         /* otherwise, if the user tries to move the selection with the    */
2529         /* arrow keys, we will give the application something to choke on */
2530     case LB_GETTOPINDEX16:
2531     case LB_GETTOPINDEX:
2532         return descr->top_item;
2533
2534     case LB_GETITEMHEIGHT16:
2535     case LB_GETITEMHEIGHT:
2536         return LISTBOX_GetItemHeight( wnd, descr, wParam );
2537
2538     case LB_SETITEMHEIGHT16:
2539         lParam = LOWORD(lParam);
2540         /* fall through */
2541     case LB_SETITEMHEIGHT:
2542         return LISTBOX_SetItemHeight( wnd, descr, wParam, lParam );
2543
2544     case LB_ITEMFROMPOINT:
2545         {
2546             POINT pt;
2547             RECT rect;
2548
2549             pt.x = LOWORD(lParam);
2550             pt.y = HIWORD(lParam);
2551             rect.left = 0;
2552             rect.top = 0;
2553             rect.right = descr->width;
2554             rect.bottom = descr->height;
2555
2556             return MAKELONG( LISTBOX_GetItemFromPoint(wnd, descr, pt.x, pt.y),
2557                              !PtInRect( &rect, pt ) );
2558         }
2559
2560     case LB_SETCARETINDEX16:
2561     case LB_SETCARETINDEX:
2562         if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2563         if (LISTBOX_SetCaretIndex( wnd, descr, wParam, !lParam ) == LB_ERR)
2564             return LB_ERR;
2565         else if (ISWIN31)
2566              return wParam;
2567         else
2568              return LB_OKAY;
2569
2570     case LB_GETCARETINDEX16:
2571     case LB_GETCARETINDEX:
2572         return descr->focus_item;
2573
2574     case LB_SETTOPINDEX16:
2575     case LB_SETTOPINDEX:
2576         return LISTBOX_SetTopItem( wnd, descr, wParam, TRUE );
2577
2578     case LB_SETCOLUMNWIDTH16:
2579     case LB_SETCOLUMNWIDTH:
2580         return LISTBOX_SetColumnWidth( wnd, descr, wParam );
2581
2582     case LB_GETITEMRECT16:
2583         {
2584             RECT rect;
2585             ret = LISTBOX_GetItemRect( wnd, descr, (INT16)wParam, &rect );
2586             CONV_RECT32TO16( &rect, (RECT16 *)PTR_SEG_TO_LIN(lParam) );
2587         }
2588         return ret;
2589
2590     case LB_GETITEMRECT:
2591         return LISTBOX_GetItemRect( wnd, descr, wParam, (RECT *)lParam );
2592
2593     case LB_FINDSTRING16:
2594         wParam = (INT)(INT16)wParam;
2595         if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2596         /* fall through */
2597     case LB_FINDSTRING:
2598         return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, FALSE );
2599
2600     case LB_FINDSTRINGEXACT16:
2601         wParam = (INT)(INT16)wParam;
2602         if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2603         /* fall through */
2604     case LB_FINDSTRINGEXACT:
2605         return LISTBOX_FindString( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2606
2607     case LB_SELECTSTRING16:
2608         wParam = (INT)(INT16)wParam;
2609         if (HAS_STRINGS(descr)) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2610         /* fall through */
2611     case LB_SELECTSTRING:
2612         {
2613             INT index = LISTBOX_FindString( wnd, descr, wParam,
2614                                               (LPCSTR)lParam, FALSE );
2615             if (index == LB_ERR)
2616                 return LB_ERR;
2617             LISTBOX_SetSelection( wnd, descr, index, TRUE, FALSE );
2618             return index;
2619         }
2620
2621     case LB_GETSEL16:
2622         wParam = (INT)(INT16)wParam;
2623         /* fall through */
2624     case LB_GETSEL:
2625         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2626             return LB_ERR;
2627         return descr->items[wParam].selected;
2628
2629     case LB_SETSEL16:
2630         lParam = (INT)(INT16)lParam;
2631         /* fall through */
2632     case LB_SETSEL:
2633         return LISTBOX_SetSelection( wnd, descr, lParam, wParam, FALSE );
2634
2635     case LB_SETCURSEL16:
2636         wParam = (INT)(INT16)wParam;
2637         /* fall through */
2638     case LB_SETCURSEL:
2639         if (IS_MULTISELECT(descr)) return LB_ERR;
2640         LISTBOX_SetCaretIndex( wnd, descr, wParam, TRUE );  
2641         return LISTBOX_SetSelection( wnd, descr, wParam, TRUE, FALSE );
2642
2643     case LB_GETSELCOUNT16:
2644     case LB_GETSELCOUNT:
2645         return LISTBOX_GetSelCount( wnd, descr );
2646
2647     case LB_GETSELITEMS16:
2648         return LISTBOX_GetSelItems16( wnd, descr, wParam,
2649                                       (LPINT16)PTR_SEG_TO_LIN(lParam) );
2650
2651     case LB_GETSELITEMS:
2652         return LISTBOX_GetSelItems( wnd, descr, wParam, (LPINT)lParam );
2653
2654     case LB_SELITEMRANGE16:
2655     case LB_SELITEMRANGE:
2656         if (LOWORD(lParam) <= HIWORD(lParam))
2657             return LISTBOX_SelectItemRange( wnd, descr, LOWORD(lParam),
2658                                             HIWORD(lParam), wParam );
2659         else
2660             return LISTBOX_SelectItemRange( wnd, descr, HIWORD(lParam),
2661                                             LOWORD(lParam), wParam );
2662
2663     case LB_SELITEMRANGEEX16:
2664     case LB_SELITEMRANGEEX:
2665         if ((INT)lParam >= (INT)wParam)
2666             return LISTBOX_SelectItemRange( wnd, descr, wParam, lParam, TRUE );
2667         else
2668             return LISTBOX_SelectItemRange( wnd, descr, lParam, wParam, FALSE);
2669
2670     case LB_GETHORIZONTALEXTENT16:
2671     case LB_GETHORIZONTALEXTENT:
2672         return descr->horz_extent;
2673
2674     case LB_SETHORIZONTALEXTENT16:
2675     case LB_SETHORIZONTALEXTENT:
2676         return LISTBOX_SetHorizontalExtent( wnd, descr, wParam );
2677
2678     case LB_GETANCHORINDEX16:
2679     case LB_GETANCHORINDEX:
2680         return descr->anchor_item;
2681
2682     case LB_SETANCHORINDEX16:
2683         wParam = (INT)(INT16)wParam;
2684         /* fall through */
2685     case LB_SETANCHORINDEX:
2686         if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2687             return LB_ERR;
2688         descr->anchor_item = (INT)wParam;
2689         return LB_OKAY;
2690
2691     case LB_DIR16:
2692         return LISTBOX_Directory( wnd, descr, wParam,
2693                                   (LPCSTR)PTR_SEG_TO_LIN(lParam), FALSE );
2694
2695     case LB_DIR:
2696         return LISTBOX_Directory( wnd, descr, wParam, (LPCSTR)lParam, TRUE );
2697
2698     case LB_GETLOCALE:
2699         return descr->locale;
2700
2701     case LB_SETLOCALE:
2702         descr->locale = (LCID)wParam;  /* FIXME: should check for valid lcid */
2703         return LB_OKAY;
2704
2705     case LB_INITSTORAGE:
2706         return LISTBOX_InitStorage( wnd, descr, wParam, (DWORD)lParam );
2707
2708     case LB_SETCOUNT:
2709         return LISTBOX_SetCount( wnd, descr, (INT)wParam );
2710
2711     case LB_SETTABSTOPS16:
2712         return LISTBOX_SetTabStops( wnd, descr, (INT)(INT16)wParam,
2713                                     (LPINT)PTR_SEG_TO_LIN(lParam), TRUE );
2714
2715     case LB_SETTABSTOPS:
2716         return LISTBOX_SetTabStops( wnd, descr, wParam, (LPINT)lParam, FALSE );
2717
2718     case LB_CARETON16:
2719     case LB_CARETON:
2720         if (descr->caret_on)
2721             return LB_OKAY;
2722         descr->caret_on = TRUE;
2723         if ((descr->focus_item != -1) && (descr->in_focus))
2724             LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2725         return LB_OKAY;
2726
2727     case LB_CARETOFF16:
2728     case LB_CARETOFF:
2729         if (!descr->caret_on)
2730             return LB_OKAY;
2731         descr->caret_on = FALSE;
2732         if ((descr->focus_item != -1) && (descr->in_focus))
2733             LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2734         return LB_OKAY;
2735         
2736     case WM_DESTROY:
2737         return LISTBOX_Destroy( wnd, descr );
2738
2739     case WM_ENABLE:
2740         InvalidateRect( hwnd, NULL, TRUE );
2741         return 0;
2742
2743     case WM_SETREDRAW:
2744         LISTBOX_SetRedraw( wnd, descr, wParam != 0 );
2745         return 0;
2746
2747     case WM_GETDLGCODE:
2748         return DLGC_WANTARROWS | DLGC_WANTCHARS;
2749
2750     case WM_PAINT:
2751         {
2752             PAINTSTRUCT ps;
2753             HDC hdc = ( wParam ) ? ((HDC)wParam)
2754                                    :  BeginPaint( hwnd, &ps );
2755             ret = LISTBOX_Paint( wnd, descr, hdc );
2756             if( !wParam ) EndPaint( hwnd, &ps );
2757         }
2758         return ret;
2759     case WM_SIZE:
2760         LISTBOX_UpdateSize( wnd, descr );
2761         return 0;
2762     case WM_GETFONT:
2763         return descr->font;
2764     case WM_SETFONT:
2765         LISTBOX_SetFont( wnd, descr, (HFONT)wParam );
2766         if (lParam) InvalidateRect( wnd->hwndSelf, 0, TRUE );
2767         return 0;
2768     case WM_SETFOCUS:
2769         descr->in_focus = TRUE;
2770         descr->caret_on = TRUE;
2771         if (descr->focus_item != -1)
2772             LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2773         SEND_NOTIFICATION( wnd, descr, LBN_SETFOCUS );
2774         return 0;
2775     case WM_KILLFOCUS:
2776         descr->in_focus = FALSE;
2777         if ((descr->focus_item != -1) && descr->caret_on)
2778             LISTBOX_RepaintItem( wnd, descr, descr->focus_item, ODA_FOCUS );
2779         SEND_NOTIFICATION( wnd, descr, LBN_KILLFOCUS );
2780         return 0;
2781     case WM_HSCROLL:
2782         return LISTBOX_HandleHScroll( wnd, descr, wParam, lParam );
2783     case WM_VSCROLL:
2784         return LISTBOX_HandleVScroll( wnd, descr, wParam, lParam );
2785     case WM_MOUSEACTIVATE:
2786         return MA_NOACTIVATE;
2787     case WM_MOUSEWHEEL:
2788         if (wParam & (MK_SHIFT | MK_CONTROL))
2789             return DefWindowProcA( hwnd, msg, wParam, lParam );
2790         return LISTBOX_HandleMouseWheel( wnd, descr, wParam, lParam );
2791     case WM_LBUTTONDOWN:
2792         return LISTBOX_HandleLButtonDown( wnd, descr, wParam,
2793                                           (INT16)LOWORD(lParam),
2794                                           (INT16)HIWORD(lParam) );
2795     case WM_LBUTTONDBLCLK:
2796         if (descr->style & LBS_NOTIFY)
2797             SEND_NOTIFICATION( wnd, descr, LBN_DBLCLK );
2798         return 0;
2799     case WM_MOUSEMOVE:
2800         if (GetCapture() == hwnd)
2801             LISTBOX_HandleMouseMove( wnd, descr, (INT16)LOWORD(lParam),
2802                                      (INT16)HIWORD(lParam) );
2803         return 0;
2804     case WM_LBUTTONUP:
2805         return LISTBOX_HandleLButtonUp( wnd, descr );
2806     case WM_KEYDOWN:
2807         return LISTBOX_HandleKeyDown( wnd, descr, wParam );
2808     case WM_CHAR:
2809         return LISTBOX_HandleChar( wnd, descr, wParam );
2810     case WM_SYSTIMER:
2811         return LISTBOX_HandleSystemTimer( wnd, descr );
2812     case WM_ERASEBKGND:
2813         if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
2814         {
2815             RECT rect;
2816             HBRUSH hbrush = SendMessageA( descr->owner, WM_CTLCOLORLISTBOX,
2817                                               wParam, (LPARAM)wnd->hwndSelf );
2818             GetClientRect(hwnd, &rect);
2819             if (hbrush) FillRect( (HDC)wParam, &rect, hbrush );
2820         }
2821         return 1;
2822     case WM_DROPFILES:
2823         if( !descr->lphc ) 
2824             return SendMessageA( descr->owner, msg, wParam, lParam );
2825         break;
2826
2827     case WM_DROPOBJECT:
2828     case WM_QUERYDROPOBJECT:
2829     case WM_DRAGSELECT:
2830     case WM_DRAGMOVE:
2831         if( !descr->lphc )
2832         {
2833             LPDRAGINFO16 dragInfo = (LPDRAGINFO16)PTR_SEG_TO_LIN( (SEGPTR)lParam );
2834             dragInfo->l = LISTBOX_GetItemFromPoint( wnd, descr, dragInfo->pt.x,
2835                                                 dragInfo->pt.y );
2836             return SendMessageA( descr->owner, msg, wParam, lParam );
2837         }
2838         break;
2839
2840     default:
2841         if ((msg >= WM_USER) && (msg < 0xc000))
2842             WARN("[%04x]: unknown msg %04x wp %08x lp %08lx\n",
2843                          hwnd, msg, wParam, lParam );
2844         return DefWindowProcA( hwnd, msg, wParam, lParam );
2845     }
2846     return 0;
2847 }
2848
2849 /***********************************************************************
2850  *           ListBoxWndProc
2851  *
2852  * This is just a wrapper for the real wndproc, it only does window locking
2853  * and unlocking.
2854  */
2855 LRESULT WINAPI ListBoxWndProc( HWND hwnd, UINT msg,
2856                                WPARAM wParam, LPARAM lParam )
2857 {
2858     WND*        wndPtr = WIN_FindWndPtr( hwnd );
2859     LRESULT     res = ListBoxWndProc_locked(wndPtr,msg,wParam,lParam);
2860
2861     WIN_ReleaseWndPtr(wndPtr);
2862     return res;
2863 }
2864
2865 /***********************************************************************
2866  *           COMBO_Directory
2867  */
2868 LRESULT COMBO_Directory( LPHEADCOMBO lphc, UINT attrib, LPSTR dir, BOOL bLong)
2869 {
2870     WND *wnd = WIN_FindWndPtr( lphc->hWndLBox );
2871
2872     if( wnd )
2873     {
2874         LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2875         if( descr )
2876         {
2877             LRESULT     lRet = LISTBOX_Directory( wnd, descr, attrib, dir, bLong );
2878
2879             RedrawWindow( lphc->self->hwndSelf, NULL, 0, 
2880                             RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
2881             WIN_ReleaseWndPtr(wnd);
2882             return lRet;
2883         }
2884         WIN_ReleaseWndPtr(wnd);
2885     }
2886     return CB_ERR;
2887 }
2888
2889 /***********************************************************************
2890  *           ComboLBWndProc_locked
2891  *
2892  * The real combo listbox wndproc, but called with locked WND struct.
2893  */
2894 static inline LRESULT WINAPI ComboLBWndProc_locked( WND* wnd, UINT msg,
2895                                WPARAM wParam, LPARAM lParam )
2896 {
2897     LRESULT lRet = 0;
2898     HWND hwnd = wnd->hwndSelf;
2899
2900     if (wnd)
2901     {
2902         LB_DESCR *descr = *(LB_DESCR **)wnd->wExtra;
2903
2904         TRACE_(combo)("[%04x]: msg %s wp %08x lp %08lx\n",
2905                      wnd->hwndSelf, SPY_GetMsgName(msg), wParam, lParam );
2906
2907         if( descr || msg == WM_CREATE )
2908         {
2909             LPHEADCOMBO lphc = (descr) ? descr->lphc : NULL;
2910
2911             switch( msg )
2912             {
2913                 case WM_CREATE:
2914 #define lpcs    ((LPCREATESTRUCTA)lParam)
2915                      TRACE_(combo)("\tpassed parent handle = 0x%08x\n", 
2916                                   (UINT)lpcs->lpCreateParams);
2917
2918                      lphc = (LPHEADCOMBO)(lpcs->lpCreateParams);
2919 #undef  lpcs
2920                      return LISTBOX_Create( wnd, lphc );
2921                 case WM_MOUSEMOVE:
2922                      if ( (TWEAK_WineLook > WIN31_LOOK) &&
2923                           (CB_GETTYPE(lphc) != CBS_SIMPLE) )
2924                      {
2925                        POINT   mousePos;
2926                        BOOL    captured;
2927                        RECT    clientRect;
2928
2929                        mousePos.x = (INT16)LOWORD(lParam);
2930                        mousePos.y = (INT16)HIWORD(lParam);
2931
2932                        /*
2933                         * If we are in a dropdown combobox, we simulate that
2934                         * the mouse is captured to show the tracking of the item.
2935                         */
2936                        GetClientRect(hwnd, &clientRect);
2937
2938                        if (PtInRect( &clientRect, mousePos ))
2939                        {
2940                            captured = descr->captured;
2941                            descr->captured = TRUE;                       
2942                            
2943                            LISTBOX_HandleMouseMove( wnd, descr, 
2944                                                     mousePos.x, mousePos.y);
2945
2946                            descr->captured = captured;
2947
2948                        }
2949                        else
2950                        {
2951                            LISTBOX_HandleMouseMove( wnd, descr, 
2952                                                     mousePos.x, mousePos.y);
2953                        }
2954
2955                        return 0;
2956
2957                      }
2958                      else
2959                      {
2960                        /*
2961                         * If we are in Win3.1 look, go with the default behavior.
2962                         */
2963                        return ListBoxWndProc( hwnd, msg, wParam, lParam );
2964                      }
2965                 case WM_LBUTTONUP:
2966                      if (TWEAK_WineLook > WIN31_LOOK)
2967                      {
2968                        POINT mousePos;
2969                        RECT  clientRect;
2970
2971                        /*
2972                         * If the mouse button "up" is not in the listbox,
2973                         * we make sure there is no selection by re-selecting the
2974                         * item that was selected when the listbox was made visible.
2975                         */
2976                        mousePos.x = (INT16)LOWORD(lParam);
2977                        mousePos.y = (INT16)HIWORD(lParam);
2978
2979                        GetClientRect(hwnd, &clientRect);
2980
2981                        /*
2982                         * When the user clicks outside the combobox and the focus
2983                         * is lost, the owning combobox will send a fake buttonup with
2984                         * 0xFFFFFFF as the mouse location, we must also revert the
2985                         * selection to the original selection.
2986                         */
2987                        if ( (lParam == 0xFFFFFFFF) ||
2988                             (!PtInRect( &clientRect, mousePos )) )
2989                        {
2990                          LISTBOX_MoveCaret( wnd,
2991                                             descr, 
2992                                             lphc->droppedIndex, 
2993                                             FALSE );
2994                        }
2995                      }
2996                      return LISTBOX_HandleLButtonUp( wnd, descr );
2997                 case WM_LBUTTONDBLCLK:
2998                 case WM_LBUTTONDOWN:
2999                      return LISTBOX_HandleLButtonDownCombo(wnd, descr, msg, wParam, 
3000                                           (INT16)LOWORD(lParam),
3001                                           (INT16)HIWORD(lParam) );
3002                 case WM_MOUSEACTIVATE:
3003                      return MA_NOACTIVATE;
3004                 case WM_NCACTIVATE:
3005                      return FALSE;
3006                 case WM_KEYDOWN:
3007                      if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3008                      {
3009                          /* for some reason(?) Windows makes it possible to
3010                           * show/hide ComboLBox by sending it WM_KEYDOWNs */
3011
3012                          if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3013                              ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3014                                && (wParam == VK_DOWN || wParam == VK_UP)) )
3015                          {
3016                              COMBO_FlipListbox( lphc, FALSE, FALSE );
3017                              return 0;
3018                          }
3019                      }
3020                      return LISTBOX_HandleKeyDown( wnd, descr, wParam );
3021
3022                 case LB_SETCURSEL16:
3023                 case LB_SETCURSEL:
3024                      lRet = ListBoxWndProc( hwnd, msg, wParam, lParam );
3025                      lRet =(lRet == LB_ERR) ? lRet : descr->selected_item; 
3026                      return lRet;
3027                 case WM_NCDESTROY:
3028                      if( CB_GETTYPE(lphc) != CBS_SIMPLE )
3029                          lphc->hWndLBox = 0;
3030                      /* fall through */
3031
3032                 default:
3033                     return ListBoxWndProc( hwnd, msg, wParam, lParam );
3034             }
3035         }
3036         lRet = DefWindowProcA( hwnd, msg, wParam, lParam );
3037
3038         TRACE_(combo)("\t default on msg [%04x]\n", (UINT16)msg );
3039     }
3040     return lRet;
3041 }
3042
3043 /***********************************************************************
3044  *           ComboLBWndProc
3045  *
3046  *  NOTE: in Windows, winproc address of the ComboLBox is the same 
3047  *        as that of the Listbox.
3048  * 
3049  * This is just a wrapper for the real wndproc, it only does window locking
3050  * and unlocking.
3051  */
3052 LRESULT WINAPI ComboLBWndProc( HWND hwnd, UINT msg,
3053                                WPARAM wParam, LPARAM lParam )
3054 {
3055     WND *wnd = WIN_FindWndPtr( hwnd );
3056     LRESULT res = ComboLBWndProc_locked(wnd,msg,wParam,lParam);
3057
3058     WIN_ReleaseWndPtr(wnd);
3059     return res;
3060 }