comboex.c 70.5 KB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
1
/*
2
 * ComboBoxEx control
3 4
 *
 * Copyright 1998, 1999 Eric Kohl
5 6
 * Copyright 2000, 2001, 2002 Guy Albertelli <galberte@neo.lrun.com>
 * Copyright 2002 Dimitrie O. Paun
7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21
 *
22 23 24 25 26
 * NOTE
 * 
 * This code was audited for completeness against the documented features
 * of Comctl32.dll version 6.0 on Sep. 9, 2002, by Dimitrie O. Paun.
 * 
Robert Shearman's avatar
Robert Shearman committed
27
 * Unless otherwise noted, we believe this code to be complete, as per
28 29 30
 * the specification mentioned above.
 * If you discover missing features, or bugs, please note them below.
 * 
Alexandre Julliard's avatar
Alexandre Julliard committed
31 32
 */

33
#include <stdarg.h>
34
#include <string.h>
35
#include "windef.h"
36
#include "winbase.h"
37 38 39
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
40
#include "commctrl.h"
41
#include "comctl32.h"
42
#include "wine/debug.h"
43
#include "wine/unicode.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
44

45
WINE_DEFAULT_DEBUG_CHANNEL(comboex);
46

47
/* Item structure */
48
typedef struct _CBE_ITEMDATA
49
{
50
    struct _CBE_ITEMDATA *next;
51 52
    UINT         mask;
    LPWSTR       pszText;
53
    LPWSTR       pszTemp;
54 55 56 57 58 59
    int          cchTextMax;
    int          iImage;
    int          iSelectedImage;
    int          iOverlay;
    int          iIndent;
    LPARAM       lParam;
60 61 62
} CBE_ITEMDATA;

/* ComboBoxEx structure */
63 64
typedef struct
{
65
    HIMAGELIST   himl;
66
    HWND         hwndSelf;         /* my own hwnd */
67
    HWND         hwndNotify;       /* my parent hwnd */
68
    HWND         hwndCombo;
69
    HWND         hwndEdit;
70
    DWORD        dwExtStyle;
71
    INT          selected;         /* index of selected item */
72
    DWORD        flags;            /* WINE internal flags */
73
    HFONT        defaultFont;
74 75
    HFONT        font;
    INT          nb_items;         /* Number of items */
76 77
    BOOL         unicode;          /* TRUE if this window is Unicode   */
    BOOL         NtfUnicode;       /* TRUE if parent wants notify in Unicode */
78
    CBE_ITEMDATA *edit;            /* item data for edit item */
79
    CBE_ITEMDATA *items;           /* Array of items */
80
} COMBOEX_INFO;
81

82
/* internal flags in the COMBOEX_INFO structure */
83
#define  WCBE_ACTEDIT		0x00000001  /* Edit active i.e.
84 85 86
                                             * CBEN_BEGINEDIT issued
                                             * but CBEN_ENDEDIT{A|W}
                                             * not yet issued. */
87
#define  WCBE_EDITCHG		0x00000002  /* Edit issued EN_CHANGE */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
88
#define  WCBE_EDITHASCHANGED	(WCBE_ACTEDIT | WCBE_EDITCHG)
89 90 91
#define  WCBE_EDITFOCUSED	0x00000004  /* Edit control has focus */
#define  WCBE_MOUSECAPTURED	0x00000008  /* Combo has captured mouse */
#define  WCBE_MOUSEDRAGGED      0x00000010  /* User has dragged in combo */
92

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
93
#define ID_CB_EDIT		1001
Alexandre Julliard's avatar
Alexandre Julliard committed
94

95 96 97

/*
 * Special flag set in DRAWITEMSTRUCT itemState field. It is set by
98 99
 * the ComboEx version of the Combo Window Proc so that when the
 * WM_DRAWITEM message is then passed to ComboEx, we know that this
100
 * particular WM_DRAWITEM message is for listbox only items. Any message
101 102
 * without this flag is then for the Edit control field.
 *
103
 * We really cannot use the ODS_COMBOBOXEDIT flag because MSDN states that
104 105
 * only version 4.0 applications will have ODS_COMBOBOXEDIT set.
 */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
106
#define ODS_COMBOEXLBOX		0x4000
107 108 109



110
/* Height in pixels of control over the amount of the selected font */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
111
#define CBE_EXTRA		3
112 113

/* Indent amount per MS documentation */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
114
#define CBE_INDENT		10
115 116

/* Offset in pixels from left side for start of image or text */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
117
#define CBE_STARTOFFSET		6
118 119

/* Offset between image and text */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
120
#define CBE_SEP			4
121

122 123
#define COMBO_SUBCLASSID  1
#define EDIT_SUBCLASSID   2
124

125
#define COMBOEX_GetInfoPtr(hwnd) ((COMBOEX_INFO *)GetWindowLongPtrW (hwnd, 0))
Alexandre Julliard's avatar
Alexandre Julliard committed
126

127 128 129 130
static LRESULT CALLBACK COMBOEX_EditWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                                             UINT_PTR uId, DWORD_PTR ref_data);
static LRESULT CALLBACK COMBOEX_ComboWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                                              UINT_PTR uId, DWORD_PTR ref_data);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
131
static LRESULT COMBOEX_Destroy (COMBOEX_INFO *infoPtr);
132
typedef INT (WINAPI *cmp_func_t)(LPCWSTR, LPCWSTR);
133

134
static inline BOOL is_textW(LPCWSTR str)
135 136 137 138
{
    return str && str != LPSTR_TEXTCALLBACKW;
}

139
static inline BOOL is_textA(LPCSTR str)
140 141 142 143
{
    return str && str != LPSTR_TEXTCALLBACKA;
}

144
static inline LPCSTR debugstr_txt(LPCWSTR str)
145 146 147 148 149
{
    if (str == LPSTR_TEXTCALLBACKW) return "(callback)";
    return debugstr_w(str);
}

150
static void COMBOEX_DumpItem (CBE_ITEMDATA const *item)
151
{
152 153 154 155
    TRACE("item %p - mask=%08x, pszText=%p, cchTM=%d, iImage=%d\n",
          item, item->mask, item->pszText, item->cchTextMax, item->iImage);
    TRACE("item %p - iSelectedImage=%d, iOverlay=%d, iIndent=%d, lParam=%08lx\n",
          item, item->iSelectedImage, item->iOverlay, item->iIndent, item->lParam);
156 157
    if (item->mask & CBEIF_TEXT)
        TRACE("item %p - pszText=%s\n", item, debugstr_txt(item->pszText));
158 159 160
}


161
static void COMBOEX_DumpInput (COMBOBOXEXITEMW const *input)
162
{
163
    TRACE("input - mask=%08x, iItem=%ld, pszText=%p, cchTM=%d, iImage=%d\n",
164 165
          input->mask, input->iItem, input->pszText, input->cchTextMax,
          input->iImage);
166 167
    if (input->mask & CBEIF_TEXT)
        TRACE("input - pszText=<%s>\n", debugstr_txt(input->pszText));
168 169
    TRACE("input - iSelectedImage=%d, iOverlay=%d, iIndent=%d, lParam=%08lx\n",
          input->iSelectedImage, input->iOverlay, input->iIndent, input->lParam);
170 171 172
}


173
static inline CBE_ITEMDATA *get_item_data(const COMBOEX_INFO *infoPtr, INT index)
174
{
175
    return (CBE_ITEMDATA *)SendMessageW (infoPtr->hwndCombo, CB_GETITEMDATA,
176
                                         index, 0);
177 178
}

179
static inline cmp_func_t get_cmp_func(COMBOEX_INFO const *infoPtr)
180 181 182
{
    return infoPtr->dwExtStyle & CBES_EX_CASESENSITIVE ? lstrcmpW : lstrcmpiW;
}
183

184
static INT COMBOEX_Notify (const COMBOEX_INFO *infoPtr, INT code, NMHDR *hdr)
185 186 187 188
{
    hdr->idFrom = GetDlgCtrlID (infoPtr->hwndSelf);
    hdr->hwndFrom = infoPtr->hwndSelf;
    hdr->code = code;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
189
    if (infoPtr->NtfUnicode)
190
	return SendMessageW (infoPtr->hwndNotify, WM_NOTIFY, 0, (LPARAM)hdr);
191
    else
192
	return SendMessageA (infoPtr->hwndNotify, WM_NOTIFY, 0, (LPARAM)hdr);
193 194 195
}


Guy L. Albertelli's avatar
Guy L. Albertelli committed
196
static INT
197
COMBOEX_NotifyItem (const COMBOEX_INFO *infoPtr, UINT code, NMCOMBOBOXEXW *hdr)
Guy L. Albertelli's avatar
Guy L. Albertelli committed
198 199 200
{
    /* Change the Text item from Unicode to ANSI if necessary for NOTIFY */
    if (infoPtr->NtfUnicode)
201
	return COMBOEX_Notify (infoPtr, code, &hdr->hdr);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
202
    else {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
203 204
	LPWSTR wstr = hdr->ceItem.pszText;
	LPSTR astr = 0;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
205 206
	INT ret, len = 0;

207
	if ((hdr->ceItem.mask & CBEIF_TEXT) && is_textW(wstr)) {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
208
	    len = WideCharToMultiByte (CP_ACP, 0, wstr, -1, 0, 0, NULL, NULL);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
209
	    if (len > 0) {
210
                astr = Alloc ((len + 1)*sizeof(CHAR));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
211 212
		if (!astr) return 0;
		WideCharToMultiByte (CP_ACP, 0, wstr, -1, astr, len, 0, 0);
213
		hdr->ceItem.pszText = (LPWSTR)astr;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
214 215 216
	    }
	}

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
217 218 219
	if (code == CBEN_ENDEDITW) code = CBEN_ENDEDITA;
	else if (code == CBEN_GETDISPINFOW) code = CBEN_GETDISPINFOA;
	else if (code == CBEN_DRAGBEGINW) code = CBEN_DRAGBEGINA;
220

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
221 222
	ret = COMBOEX_Notify (infoPtr, code, (NMHDR *)hdr);

223
	if (astr && hdr->ceItem.pszText == (LPWSTR)astr)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
224
	    hdr->ceItem.pszText = wstr;
225

226
	Free(astr);
227

Guy L. Albertelli's avatar
Guy L. Albertelli committed
228 229 230 231 232
	return ret;
    }
}


233
static INT COMBOEX_NotifyEndEdit (const COMBOEX_INFO *infoPtr, NMCBEENDEDITW *neew, LPCWSTR wstr)
Guy L. Albertelli's avatar
Guy L. Albertelli committed
234 235
{
    /* Change the Text item from Unicode to ANSI if necessary for NOTIFY */
236 237 238 239 240
    if (infoPtr->NtfUnicode) {
	lstrcpynW(neew->szText, wstr, CBEMAXSTRLEN);
	return COMBOEX_Notify (infoPtr, CBEN_ENDEDITW, &neew->hdr);
    } else {
	NMCBEENDEDITA neea;
241

242
        neea.hdr = neew->hdr;
243 244 245 246
        neea.fChanged = neew->fChanged;
        neea.iNewSelection = neew->iNewSelection;
        WideCharToMultiByte (CP_ACP, 0, wstr, -1, neea.szText, CBEMAXSTRLEN, 0, 0);
        neea.iWhy = neew->iWhy;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
247

248
        return COMBOEX_Notify (infoPtr, CBEN_ENDEDITA, &neea.hdr);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
249 250 251 252
    }
}


253
static void COMBOEX_NotifyDragBegin(const COMBOEX_INFO *infoPtr, LPCWSTR wstr)
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
{
    /* Change the Text item from Unicode to ANSI if necessary for NOTIFY */
    if (infoPtr->NtfUnicode) {
        NMCBEDRAGBEGINW ndbw;

	ndbw.iItemid = -1;
	lstrcpynW(ndbw.szText, wstr, CBEMAXSTRLEN);
	COMBOEX_Notify (infoPtr, CBEN_DRAGBEGINW, &ndbw.hdr);
    } else {
	NMCBEDRAGBEGINA ndba;

	ndba.iItemid = -1;
	WideCharToMultiByte (CP_ACP, 0, wstr, -1, ndba.szText, CBEMAXSTRLEN, 0, 0);

	COMBOEX_Notify (infoPtr, CBEN_DRAGBEGINA, &ndba.hdr);
    }
}


273 274
static void COMBOEX_FreeText (CBE_ITEMDATA *item)
{
275
    if (is_textW(item->pszText)) Free(item->pszText);
276
    item->pszText = 0;
277
    Free(item->pszTemp);
278 279 280 281
    item->pszTemp = 0;
}


282
static INT COMBOEX_GetIndex(COMBOEX_INFO const *infoPtr, CBE_ITEMDATA const *item)
283
{
284
    CBE_ITEMDATA const *moving;
285
    INT index;
286

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    moving = infoPtr->items;
    index = infoPtr->nb_items - 1;

    while (moving && (moving != item)) {
        moving = moving->next;
        index--;
    }
    if (!moving || (index < 0)) {
        ERR("COMBOBOXEX item structures broken. Please report!\n");
        return -1;
    }
    return index;
}


302
static LPCWSTR COMBOEX_GetText(const COMBOEX_INFO *infoPtr, CBE_ITEMDATA *item)
303 304 305 306
{
    NMCOMBOBOXEXW nmce;
    LPWSTR text, buf;
    INT len;
307 308

    if (item->pszText != LPSTR_TEXTCALLBACKW)
309
	return item->pszText;
310

311 312
    ZeroMemory(&nmce, sizeof(nmce));
    nmce.ceItem.mask = CBEIF_TEXT;
313
    nmce.ceItem.lParam = item->lParam;
314
    nmce.ceItem.iItem = COMBOEX_GetIndex(infoPtr, item);
315 316 317 318
    COMBOEX_NotifyItem(infoPtr, CBEN_GETDISPINFOW, &nmce);

    if (is_textW(nmce.ceItem.pszText)) {
	len = MultiByteToWideChar (CP_ACP, 0, (LPSTR)nmce.ceItem.pszText, -1, NULL, 0);
319
        buf = Alloc ((len + 1)*sizeof(WCHAR));
320 321 322 323 324 325
	if (buf)
	    MultiByteToWideChar (CP_ACP, 0, (LPSTR)nmce.ceItem.pszText, -1, buf, len);
	if (nmce.ceItem.mask & CBEIF_DI_SETITEM) {
	    COMBOEX_FreeText(item);
	    item->pszText = buf;
	} else {
326
	    Free(item->pszTemp);
327 328 329 330 331
	    item->pszTemp = buf;
	}
	text = buf;
    } else
	text = nmce.ceItem.pszText;
332

333 334 335 336 337 338
    if (nmce.ceItem.mask & CBEIF_DI_SETITEM)
	item->pszText = text;
    return text;
}


339
static void COMBOEX_GetComboFontSize (const COMBOEX_INFO *infoPtr, SIZE *size)
340
{
341
    static const WCHAR strA[] = { 'A', 0 };
342 343 344 345
    HFONT nfont, ofont;
    HDC mydc;

    mydc = GetDC (0); /* why the entire screen???? */
346
    nfont = (HFONT)SendMessageW (infoPtr->hwndCombo, WM_GETFONT, 0, 0);
347
    ofont = SelectObject (mydc, nfont);
348
    GetTextExtentPointW (mydc, strA, 1, size);
349 350
    SelectObject (mydc, ofont);
    ReleaseDC (0, mydc);
351
    TRACE("selected font hwnd=%p, height=%d\n", nfont, size->cy);
352 353 354
}


355
static void COMBOEX_CopyItem (const CBE_ITEMDATA *item, COMBOBOXEXITEMW *cit)
356 357
{
    if (cit->mask & CBEIF_TEXT) {
358 359 360
        /*
         * when given a text buffer actually use that buffer
         */
361 362 363 364 365 366
        if (cit->pszText) {
	    if (is_textW(item->pszText))
                lstrcpynW(cit->pszText, item->pszText, cit->cchTextMax);
	    else
		cit->pszText[0] = 0;
        } else {
367 368 369
            cit->pszText        = item->pszText;
            cit->cchTextMax     = item->cchTextMax;
        }
370 371 372 373 374 375 376 377 378 379 380 381 382 383
    }
    if (cit->mask & CBEIF_IMAGE)
	cit->iImage         = item->iImage;
    if (cit->mask & CBEIF_SELECTEDIMAGE)
	cit->iSelectedImage = item->iSelectedImage;
    if (cit->mask & CBEIF_OVERLAY)
	cit->iOverlay       = item->iOverlay;
    if (cit->mask & CBEIF_INDENT)
	cit->iIndent        = item->iIndent;
    if (cit->mask & CBEIF_LPARAM)
	cit->lParam         = item->lParam;
}


384
static void COMBOEX_AdjustEditPos (const COMBOEX_INFO *infoPtr)
385 386
{
    SIZE mysize;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
387
    INT x, y, w, h, xioff;
388 389 390
    RECT rect;

    if (!infoPtr->hwndEdit) return;
391

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
392 393 394
    if (infoPtr->himl && !(infoPtr->dwExtStyle & CBES_EX_NOEDITIMAGEINDENT)) {
    	IMAGEINFO iinfo;
        iinfo.rcImage.left = iinfo.rcImage.right = 0;
395
	ImageList_GetImageInfo(infoPtr->himl, 0, &iinfo);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
396 397 398
	xioff = iinfo.rcImage.right - iinfo.rcImage.left + CBE_SEP;
    }  else xioff = 0;

399 400 401 402 403 404
    GetClientRect (infoPtr->hwndCombo, &rect);
    InflateRect (&rect, -2, -2);
    InvalidateRect (infoPtr->hwndCombo, &rect, TRUE);

    /* reposition the Edit control based on whether icon exists */
    COMBOEX_GetComboFontSize (infoPtr, &mysize);
405
    TRACE("Combo font x=%d, y=%d\n", mysize.cx, mysize.cy);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
406
    x = xioff + CBE_STARTOFFSET + 1;
407 408
    w = rect.right-rect.left - x - GetSystemMetrics(SM_CXVSCROLL) - 1;
    h = mysize.cy + 1;
409
    y = rect.bottom - h - 1;
410

411 412
    TRACE("Combo client (%s), setting Edit to (%d,%d)-(%d,%d)\n",
          wine_dbgstr_rect(&rect), x, y, x + w, y + h);
413
    SetWindowPos(infoPtr->hwndEdit, HWND_TOP, x, y, w, h,
414 415 416 417
		 SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOZORDER);
}


418
static void COMBOEX_ReSize (const COMBOEX_INFO *infoPtr)
419 420
{
    SIZE mysize;
421
    LONG cy;
422 423 424
    IMAGEINFO iinfo;

    COMBOEX_GetComboFontSize (infoPtr, &mysize);
425
    cy = mysize.cy + CBE_EXTRA;
426
    if (infoPtr->himl && ImageList_GetImageInfo(infoPtr->himl, 0, &iinfo)) {
427
	cy = max (iinfo.rcImage.bottom - iinfo.rcImage.top, cy);
428
	TRACE("upgraded height due to image:  height=%d\n", cy);
429
    }
430
    SendMessageW (infoPtr->hwndSelf, CB_SETITEMHEIGHT, -1, cy);
431
    if (infoPtr->hwndCombo) {
432
        SendMessageW (infoPtr->hwndCombo, CB_SETITEMHEIGHT, 0, cy);
433
	if ( !(infoPtr->flags & CBES_EX_NOSIZELIMIT)) {
434 435 436 437 438 439 440
	    RECT comboRect, ourRect;
	    GetWindowRect(infoPtr->hwndCombo, &comboRect);
            GetWindowRect(infoPtr->hwndSelf, &ourRect);
            if (comboRect.bottom > ourRect.bottom)
                SetWindowPos( infoPtr->hwndSelf, 0, 0, 0, ourRect.right - ourRect.left,
                              comboRect.bottom - comboRect.top,
                              SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW );
441 442
	}
    }
443 444 445
}


446
static void COMBOEX_SetEditText (const COMBOEX_INFO *infoPtr, CBE_ITEMDATA *item)
447 448
{
    if (!infoPtr->hwndEdit) return;
449

450
    if (item->mask & CBEIF_TEXT) {
451
	SendMessageW (infoPtr->hwndEdit, WM_SETTEXT, 0, (LPARAM)COMBOEX_GetText(infoPtr, item));
452 453
	SendMessageW (infoPtr->hwndEdit, EM_SETSEL, 0, 0);
	SendMessageW (infoPtr->hwndEdit, EM_SETSEL, 0, -1);
454 455 456
    }
}

457

458
static CBE_ITEMDATA * COMBOEX_FindItem(const COMBOEX_INFO *infoPtr, INT_PTR index)
459 460 461 462
{
    CBE_ITEMDATA *item;
    INT i;

463
    if ((index >= infoPtr->nb_items) || (index < -1))
464 465 466 467 468 469 470 471
	return 0;
    if (index == -1)
	return infoPtr->edit;
    item = infoPtr->items;
    i = infoPtr->nb_items - 1;

    /* find the item in the list */
    while (item && (i > index)) {
472
	item = item->next;
473 474 475
	i--;
    }
    if (!item || (i != index)) {
476
	ERR("COMBOBOXEX item structures broken. Please report!\n");
477 478 479 480 481
	return 0;
    }
    return item;
}

482 483
/* ***  CBEM_xxx message support  *** */

484
static UINT COMBOEX_GetListboxText(const COMBOEX_INFO *infoPtr, INT_PTR n, LPWSTR buf)
485 486 487 488 489 490 491 492 493
{
    CBE_ITEMDATA *item;
    LPCWSTR str;

    item = COMBOEX_FindItem(infoPtr, n);
    if (!item)
        return 0;

    str = COMBOEX_GetText(infoPtr, item);
494 495 496 497 498 499 500 501 502 503 504
    if (!str)
    {
        if (buf)
        {
            if (infoPtr->unicode)
                buf[0] = 0;
            else
                *((LPSTR)buf) = 0;
        }
        return 0;
    }
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520

    if (infoPtr->unicode)
    {
        if (buf)
            lstrcpyW(buf, str);
        return lstrlenW(str);
    }
    else
    {
        UINT r;
        r = WideCharToMultiByte(CP_ACP, 0, str, -1, (LPSTR)buf, 0x40000000, NULL, NULL);
        if (r) r--;
        return r;
    }
}

521

522
static INT COMBOEX_DeleteItem (const COMBOEX_INFO *infoPtr, INT_PTR index)
523
{
524
    TRACE("(index=%ld)\n", index);
525 526

    /* if item number requested does not exist then return failure */
527
    if ((index >= infoPtr->nb_items) || (index < 0)) return CB_ERR;
528
    if (!COMBOEX_FindItem(infoPtr, index)) return CB_ERR;
529 530

    /* doing this will result in WM_DELETEITEM being issued */
531
    SendMessageW (infoPtr->hwndCombo, CB_DELETESTRING, index, 0);
532 533 534

    return infoPtr->nb_items;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
535 536


537
static BOOL COMBOEX_GetItemW (const COMBOEX_INFO *infoPtr, COMBOBOXEXITEMW *cit)
Alexandre Julliard's avatar
Alexandre Julliard committed
538
{
539
    INT_PTR index = cit->iItem;
540 541
    CBE_ITEMDATA *item;

542
    TRACE("\n");
543 544

    /* if item number requested does not exist then return failure */
545
    if ((index >= infoPtr->nb_items) || (index < -1)) return FALSE;
546 547

    /* if the item is the edit control and there is no edit control, skip */
548
    if ((index == -1) && !infoPtr->hwndEdit) return FALSE;
549

550
    if (!(item = COMBOEX_FindItem(infoPtr, index))) return FALSE;
551

552
    COMBOEX_CopyItem (item, cit);
553

554 555
    return TRUE;
}
556

Alexandre Julliard's avatar
Alexandre Julliard committed
557

558
static BOOL COMBOEX_GetItemA (const COMBOEX_INFO *infoPtr, COMBOBOXEXITEMA *cit)
559 560 561
{
    COMBOBOXEXITEMW tmpcit;

562
    TRACE("\n");
563 564 565

    tmpcit.mask = cit->mask;
    tmpcit.iItem = cit->iItem;
566
    tmpcit.pszText = 0;
567
    if(!COMBOEX_GetItemW (infoPtr, &tmpcit)) return FALSE;
568

569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
    if (cit->mask & CBEIF_TEXT)
    {
        if (is_textW(tmpcit.pszText) && cit->pszText)
            WideCharToMultiByte(CP_ACP, 0, tmpcit.pszText, -1,
                                cit->pszText, cit->cchTextMax, NULL, NULL);
        else if (cit->pszText) cit->pszText[0] = 0;
        else cit->pszText = (LPSTR)tmpcit.pszText;
    }

    if (cit->mask & CBEIF_IMAGE)
        cit->iImage = tmpcit.iImage;
    if (cit->mask & CBEIF_SELECTEDIMAGE)
        cit->iSelectedImage = tmpcit.iSelectedImage;
    if (cit->mask & CBEIF_OVERLAY)
        cit->iOverlay = tmpcit.iOverlay;
    if (cit->mask & CBEIF_INDENT)
        cit->iIndent = tmpcit.iIndent;
    if (cit->mask & CBEIF_LPARAM)
        cit->lParam = tmpcit.lParam;
588 589 590 591 592

    return TRUE;
}


593
static inline BOOL COMBOEX_HasEditChanged (COMBOEX_INFO const *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
594
{
595
    return infoPtr->hwndEdit && (infoPtr->flags & WCBE_EDITHASCHANGED) == WCBE_EDITHASCHANGED;
Alexandre Julliard's avatar
Alexandre Julliard committed
596 597 598
}


599
static INT COMBOEX_InsertItemW (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMW const *cit)
600
{
601
    INT_PTR index;
602
    CBE_ITEMDATA *item;
603 604 605
    NMCOMBOBOXEXW nmcit;

    TRACE("\n");
606

607
    if (TRACE_ON(comboex)) COMBOEX_DumpInput (cit);
608

609 610 611
    /* get real index of item to insert */
    index = cit->iItem;
    if (index == -1) index = infoPtr->nb_items;
612
    if (index > infoPtr->nb_items) return -1;
613

614
    /* get zero-filled space and chain it in */
615
    if(!(item = Alloc (sizeof(*item)))) return -1;
616

617 618 619 620 621 622 623 624 625 626 627
    /* locate position to insert new item in */
    if (index == infoPtr->nb_items) {
        /* fast path for iItem = -1 */
        item->next = infoPtr->items;
	infoPtr->items = item;
    }
    else {
        INT i = infoPtr->nb_items-1;
	CBE_ITEMDATA *moving = infoPtr->items;

	while ((i > index) && moving) {
628
	    moving = moving->next;
629 630 631
	    i--;
	}
	if (!moving) {
632
	    ERR("COMBOBOXEX item structures broken. Please report!\n");
633
	    Free(item);
634 635 636 637 638 639 640
	    return -1;
	}
	item->next = moving->next;
	moving->next = item;
    }

    /* fill in our hidden item structure */
641
    item->mask = cit->mask;
642
    if (item->mask & CBEIF_TEXT) {
643
	INT len = 0;
644

645
        if (is_textW(cit->pszText)) len = strlenW (cit->pszText);
646
	if (len > 0) {
647
            item->pszText = Alloc ((len + 1)*sizeof(WCHAR));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
648
	    if (!item->pszText) {
649
		Free(item);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
650 651
		return -1;
	    }
652
	    strcpyW (item->pszText, cit->pszText);
653
	}
654 655 656
	else if (cit->pszText == LPSTR_TEXTCALLBACKW)
	    item->pszText = LPSTR_TEXTCALLBACKW;
        item->cchTextMax = cit->cchTextMax;
657 658
    }
    if (item->mask & CBEIF_IMAGE)
659
        item->iImage = cit->iImage;
660
    if (item->mask & CBEIF_SELECTEDIMAGE)
661
        item->iSelectedImage = cit->iSelectedImage;
662
    if (item->mask & CBEIF_OVERLAY)
663
        item->iOverlay = cit->iOverlay;
664
    if (item->mask & CBEIF_INDENT)
665
        item->iIndent = cit->iIndent;
666
    if (item->mask & CBEIF_LPARAM)
667
        item->lParam = cit->lParam;
668 669
    infoPtr->nb_items++;

670
    if (TRACE_ON(comboex)) COMBOEX_DumpItem (item);
671

672
    SendMessageW (infoPtr->hwndCombo, CB_INSERTSTRING, cit->iItem, (LPARAM)item);
673

674
    memset (&nmcit.ceItem, 0, sizeof(nmcit.ceItem));
675
    nmcit.ceItem.mask=~0;
676
    COMBOEX_CopyItem (item, &nmcit.ceItem);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
677
    COMBOEX_NotifyItem (infoPtr, CBEN_INSERTITEM, &nmcit);
678

679 680 681 682
    return index;

}

Alexandre Julliard's avatar
Alexandre Julliard committed
683

684
static INT COMBOEX_InsertItemA (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMA const *cit)
685
{
686
    COMBOBOXEXITEMW citW;
687
    LPWSTR wstr = NULL;
688
    INT	ret;
689 690

    memcpy(&citW,cit,sizeof(COMBOBOXEXITEMA));
691 692
    if (cit->mask & CBEIF_TEXT && is_textA(cit->pszText)) {
	INT len = MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, NULL, 0);
693
        wstr = Alloc ((len + 1)*sizeof(WCHAR));
694 695 696
	if (!wstr) return -1;
	MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, wstr, len);
	citW.pszText = wstr;
697
    }
698 699
    ret = COMBOEX_InsertItemW(infoPtr, &citW);

700
    Free(wstr);
701

702 703 704 705
    return ret;
}


706
static DWORD
707
COMBOEX_SetExtendedStyle (COMBOEX_INFO *infoPtr, DWORD mask, DWORD style)
Alexandre Julliard's avatar
Alexandre Julliard committed
708 709 710
{
    DWORD dwTemp;

711
    TRACE("(mask=x%08x, style=0x%08x)\n", mask, style);
Alexandre Julliard's avatar
Alexandre Julliard committed
712 713 714

    dwTemp = infoPtr->dwExtStyle;

715 716
    if (mask)
	infoPtr->dwExtStyle = (infoPtr->dwExtStyle & ~mask) | style;
Alexandre Julliard's avatar
Alexandre Julliard committed
717
    else
718
	infoPtr->dwExtStyle = style;
Alexandre Julliard's avatar
Alexandre Julliard committed
719

720
    /* see if we need to change the word break proc on the edit */
721 722
    if ((infoPtr->dwExtStyle ^ dwTemp) & CBES_EX_PATHWORDBREAKPROC)
        SetPathWordBreakProc(infoPtr->hwndEdit, 
723
            (infoPtr->dwExtStyle & CBES_EX_PATHWORDBREAKPROC) != 0);
724

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
725 726 727
    /* test if the control's appearance has changed */
    mask = CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT;
    if ((infoPtr->dwExtStyle & mask) != (dwTemp & mask)) {
728
	/* if state of EX_NOEDITIMAGE changes, invalidate all */
729
	TRACE("EX_NOEDITIMAGE state changed to %d\n",
730 731
	      infoPtr->dwExtStyle & CBES_EX_NOEDITIMAGE);
	InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
732 733 734 735
	COMBOEX_AdjustEditPos (infoPtr);
	if (infoPtr->hwndEdit)
	    InvalidateRect (infoPtr->hwndEdit, NULL, TRUE);
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
736

737
    return dwTemp;
Alexandre Julliard's avatar
Alexandre Julliard committed
738 739 740
}


741
static HIMAGELIST COMBOEX_SetImageList (COMBOEX_INFO *infoPtr, HIMAGELIST himl)
Alexandre Julliard's avatar
Alexandre Julliard committed
742
{
743
    HIMAGELIST himlTemp = infoPtr->himl;
Alexandre Julliard's avatar
Alexandre Julliard committed
744

745
    TRACE("\n");
Alexandre Julliard's avatar
Alexandre Julliard committed
746

747
    infoPtr->himl = himl;
Alexandre Julliard's avatar
Alexandre Julliard committed
748

749
    COMBOEX_ReSize (infoPtr);
750
    InvalidateRect (infoPtr->hwndCombo, NULL, TRUE);
751

752 753
    /* reposition the Edit control based on whether icon exists */
    COMBOEX_AdjustEditPos (infoPtr);
754
    return himlTemp;
Alexandre Julliard's avatar
Alexandre Julliard committed
755 756
}

757
static BOOL COMBOEX_SetItemW (const COMBOEX_INFO *infoPtr, const COMBOBOXEXITEMW *cit)
758
{
759
    INT_PTR index = cit->iItem;
760 761
    CBE_ITEMDATA *item;

762
    if (TRACE_ON(comboex)) COMBOEX_DumpInput (cit);
763 764

    /* if item number requested does not exist then return failure */
765
    if ((index >= infoPtr->nb_items) || (index < -1)) return FALSE;
766

767
    /* if the item is the edit control and there is no edit control, skip */
768
    if ((index == -1) && !infoPtr->hwndEdit) return FALSE;
769

770
    if (!(item = COMBOEX_FindItem(infoPtr, index))) return FALSE;
771

772
    /* add/change stuff to the internal item structure */
773 774
    item->mask |= cit->mask;
    if (cit->mask & CBEIF_TEXT) {
775
	INT len = 0;
776

777 778
	COMBOEX_FreeText(item);
        if (is_textW(cit->pszText)) len = strlenW(cit->pszText);
779
	if (len > 0) {
780
            item->pszText = Alloc ((len + 1)*sizeof(WCHAR));
781
	    if (!item->pszText) return FALSE;
782 783 784 785
	    strcpyW(item->pszText, cit->pszText);
	} else if (cit->pszText == LPSTR_TEXTCALLBACKW)
	    item->pszText = LPSTR_TEXTCALLBACKW;
        item->cchTextMax = cit->cchTextMax;
786 787
    }
    if (cit->mask & CBEIF_IMAGE)
788
        item->iImage = cit->iImage;
789
    if (cit->mask & CBEIF_SELECTEDIMAGE)
790
        item->iSelectedImage = cit->iSelectedImage;
791
    if (cit->mask & CBEIF_OVERLAY)
792
        item->iOverlay = cit->iOverlay;
793
    if (cit->mask & CBEIF_INDENT)
794
        item->iIndent = cit->iIndent;
795
    if (cit->mask & CBEIF_LPARAM)
796
        item->lParam = cit->lParam;
797

798
    if (TRACE_ON(comboex)) COMBOEX_DumpItem (item);
799

800
    /* if original request was to update edit control, do some fast foot work */
801
    if (cit->iItem == -1 && cit->mask & CBEIF_TEXT) {
802 803 804
	COMBOEX_SetEditText (infoPtr, item);
	RedrawWindow (infoPtr->hwndCombo, 0, 0, RDW_ERASE | RDW_INVALIDATE);
    }
805 806 807
    return TRUE;
}

808
static BOOL COMBOEX_SetItemA (const COMBOEX_INFO *infoPtr, COMBOBOXEXITEMA const *cit)
809
{
810 811 812
    COMBOBOXEXITEMW citW;
    LPWSTR wstr = NULL;
    BOOL ret;
813

814
    memcpy(&citW, cit, sizeof(COMBOBOXEXITEMA));
815 816
    if ((cit->mask & CBEIF_TEXT) && is_textA(cit->pszText)) {
	INT len = MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, NULL, 0);
817
        wstr = Alloc ((len + 1)*sizeof(WCHAR));
818 819 820
	if (!wstr) return FALSE;
	MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, wstr, len);
	citW.pszText = wstr;
821
    }
822
    ret = COMBOEX_SetItemW(infoPtr, &citW);
823

824
    Free(wstr);
825 826 827

    return ret;
}
828 829


830
static BOOL COMBOEX_SetUnicodeFormat (COMBOEX_INFO *infoPtr, BOOL value)
831
{
832
    BOOL bTemp = infoPtr->unicode;
833

834
    TRACE("to %s, was %s\n", value ? "TRUE":"FALSE", bTemp ? "TRUE":"FALSE");
835

836
    infoPtr->unicode = value;
837 838 839 840

    return bTemp;
}

841

842 843
/* ***  CB_xxx message support  *** */

844
static INT
845
COMBOEX_FindStringExact (const COMBOEX_INFO *infoPtr, INT start, LPCWSTR str)
846
{
847 848 849
    INT i;
    cmp_func_t cmptext = get_cmp_func(infoPtr);
    INT count = SendMessageW (infoPtr->hwndCombo, CB_GETCOUNT, 0, 0);
850 851 852

    /* now search from after starting loc and wrapping back to start */
    for(i=start+1; i<count; i++) {
853
	CBE_ITEMDATA *item = get_item_data(infoPtr, i);
854
	if ((LRESULT)item == CB_ERR) continue;
855
	if (cmptext(COMBOEX_GetText(infoPtr, item), str) == 0) return i;
856 857
    }
    for(i=0; i<=start; i++) {
858
	CBE_ITEMDATA *item = get_item_data(infoPtr, i);
859
	if ((LRESULT)item == CB_ERR) continue;
860
	if (cmptext(COMBOEX_GetText(infoPtr, item), str) == 0) return i;
861 862 863 864 865
    }
    return CB_ERR;
}


866
static DWORD_PTR COMBOEX_GetItemData (const COMBOEX_INFO *infoPtr, INT_PTR index)
867
{
868 869
    CBE_ITEMDATA const *item1;
    CBE_ITEMDATA const *item2;
Frank Richter's avatar
Frank Richter committed
870
    DWORD_PTR ret = 0;
871

872
    item1 = get_item_data(infoPtr, index);
873 874 875 876 877 878
    if ((item1 != NULL) && ((LRESULT)item1 != CB_ERR)) {
	item2 = COMBOEX_FindItem (infoPtr, index);
	if (item2 != item1) {
	    ERR("data structures damaged!\n");
	    return CB_ERR;
	}
879 880 881
	if (item1->mask & CBEIF_LPARAM) ret = item1->lParam;
	TRACE("returning 0x%08lx\n", ret);
    } else {
Frank Richter's avatar
Frank Richter committed
882
        ret = (DWORD_PTR)item1;
883
        TRACE("non-valid result from combo, returning 0x%08lx\n", ret);
884
    }
885
    return ret;
886 887 888
}


889
static INT COMBOEX_SetCursel (COMBOEX_INFO *infoPtr, INT_PTR index)
890 891
{
    CBE_ITEMDATA *item;
892
    INT sel;
893

894 895
    if (!(item = COMBOEX_FindItem(infoPtr, index)))
	return SendMessageW (infoPtr->hwndCombo, CB_SETCURSEL, index, 0);
896

897
    TRACE("selecting item %ld text=%s\n", index, debugstr_txt(item->pszText));
898
    infoPtr->selected = index;
899

900
    sel = (INT)SendMessageW (infoPtr->hwndCombo, CB_SETCURSEL, index, 0);
901
    COMBOEX_SetEditText (infoPtr, item);
902
    return sel;
903 904 905
}


906
static DWORD_PTR COMBOEX_SetItemData (const COMBOEX_INFO *infoPtr, INT_PTR index, DWORD_PTR data)
907
{
908 909
    CBE_ITEMDATA *item1;
    CBE_ITEMDATA const *item2;
910

911
    item1 = get_item_data(infoPtr, index);
912 913 914 915 916 917 918
    if ((item1 != NULL) && ((LRESULT)item1 != CB_ERR)) {
	item2 = COMBOEX_FindItem (infoPtr, index);
	if (item2 != item1) {
	    ERR("data structures damaged!\n");
	    return CB_ERR;
	}
	item1->mask |= CBEIF_LPARAM;
919 920
	item1->lParam = data;
	TRACE("setting lparam to 0x%08lx\n", data);
921 922
	return 0;
    }
Frank Richter's avatar
Frank Richter committed
923 924
    TRACE("non-valid result from combo %p\n", item1);
    return (DWORD_PTR)item1;
925 926 927
}


928
static INT COMBOEX_SetItemHeight (COMBOEX_INFO const *infoPtr, INT index, UINT height)
929
{
930
    RECT cb_wrect, cbx_wrect, cbx_crect;
931

932 933
    /* First, lets forward the message to the normal combo control
       just like Windows.     */
934 935 936
    if (infoPtr->hwndCombo)
       if (SendMessageW (infoPtr->hwndCombo, CB_SETITEMHEIGHT,
			 index, height) == CB_ERR) return CB_ERR;
937 938

    GetWindowRect (infoPtr->hwndCombo, &cb_wrect);
939 940
    GetWindowRect (infoPtr->hwndSelf, &cbx_wrect);
    GetClientRect (infoPtr->hwndSelf, &cbx_crect);
941
    /* the height of comboex as height of the combo + comboex border */
942 943 944
    height = cb_wrect.bottom-cb_wrect.top
             + cbx_wrect.bottom-cbx_wrect.top
             - (cbx_crect.bottom-cbx_crect.top);
945 946 947 948
    TRACE("EX window=(%s), client=(%s)\n",
          wine_dbgstr_rect(&cbx_wrect), wine_dbgstr_rect(&cbx_crect));
    TRACE("CB window=(%s), EX setting=(0,0)-(%d,%d)\n",
          wine_dbgstr_rect(&cbx_wrect), cbx_wrect.right-cbx_wrect.left, height);
949 950
    SetWindowPos (infoPtr->hwndSelf, HWND_TOP, 0, 0,
		  cbx_wrect.right-cbx_wrect.left, height,
951 952
		  SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);

953
    return 0;
954
}
Alexandre Julliard's avatar
Alexandre Julliard committed
955 956


957 958 959
/* ***  WM_xxx message support  *** */


960
static LRESULT COMBOEX_Create (HWND hwnd, CREATESTRUCTA const *cs)
Alexandre Julliard's avatar
Alexandre Julliard committed
961
{
962
    static const WCHAR NIL[] = { 0 };
Alexandre Julliard's avatar
Alexandre Julliard committed
963
    COMBOEX_INFO *infoPtr;
964
    LOGFONTW mylogfont;
965
    RECT win_rect;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
966
    INT i;
Alexandre Julliard's avatar
Alexandre Julliard committed
967 968

    /* allocate memory for info structure */
969
    infoPtr = Alloc (sizeof(COMBOEX_INFO));
970
    if (!infoPtr) return -1;
971 972

    /* initialize info structure */
973
    /* note that infoPtr is allocated zero-filled */
974

975
    infoPtr->hwndSelf = hwnd;
976
    infoPtr->selected = -1;
Alexandre Julliard's avatar
Alexandre Julliard committed
977

978
    infoPtr->unicode = IsWindowUnicode (hwnd);
979
    infoPtr->hwndNotify = cs->hwndParent;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
980

981
    i = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)hwnd, NF_QUERY);
982 983
    if ((i != NFR_ANSI) && (i != NFR_UNICODE)) {
	WARN("wrong response to WM_NOTIFYFORMAT (%d), assuming ANSI\n", i);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
984 985
	i = NFR_ANSI;
    }
986
    infoPtr->NtfUnicode = (i == NFR_UNICODE);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
987

988
    SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
989

990 991 992 993 994 995 996
    if (TRACE_ON(comboex)) {
	RECT client, rect;
	GetWindowRect(hwnd, &rect);
	GetClientRect(hwnd, &client);
	TRACE("EX window=(%s), client=(%s)\n",
		wine_dbgstr_rect(&rect), wine_dbgstr_rect(&client));
    }
997 998 999 1000

    /* Native version of ComboEx creates the ComboBox with DROPDOWNLIST */
    /* specified. It then creates it's own version of the EDIT control  */
    /* and makes the ComboBox the parent. This is because a normal      */
1001
    /* DROPDOWNLIST does not have an EDIT control, but we need one.     */
1002 1003 1004
    /* We also need to place the edit control at the proper location    */
    /* (allow space for the icons).                                     */

1005
    infoPtr->hwndCombo = CreateWindowW (WC_COMBOBOXW, NIL,
1006
			 /* following line added to match native */
1007
                         WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VSCROLL |
1008
                         CBS_NOINTEGRALHEIGHT | CBS_DROPDOWNLIST |
1009
			 /* was base and is necessary */
1010 1011
			 WS_CHILD | WS_VISIBLE | CBS_OWNERDRAWFIXED |
			 GetWindowLongW (hwnd, GWL_STYLE),
1012
			 cs->y, cs->x, cs->cx, cs->cy, hwnd,
1013 1014
			 (HMENU) GetWindowLongPtrW (hwnd, GWLP_ID),
			 (HINSTANCE)GetWindowLongPtrW (hwnd, GWLP_HINSTANCE), NULL);
1015

1016
    /*
1017 1018 1019 1020 1021 1022 1023
     * native does the following at this point according to trace:
     *  GetWindowThreadProcessId(hwndCombo,0)
     *  GetCurrentThreadId()
     *  GetWindowThreadProcessId(hwndCombo, &???)
     *  GetCurrentProcessId()
     */

1024 1025
    SetWindowSubclass(infoPtr->hwndCombo, COMBOEX_ComboWndProc, COMBO_SUBCLASSID,
                      (DWORD_PTR)hwnd);
1026
    infoPtr->font = (HFONT)SendMessageW (infoPtr->hwndCombo, WM_GETFONT, 0, 0);
1027 1028 1029 1030 1031 1032

    /*
     * Now create our own EDIT control so we can position it.
     * It is created only for CBS_DROPDOWN style
     */
    if ((cs->style & CBS_DROPDOWNLIST) == CBS_DROPDOWN) {
1033
	infoPtr->hwndEdit = CreateWindowExW (0, WC_EDITW, NIL,
1034 1035
		    WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | ES_AUTOHSCROLL,
		    0, 0, 0, 0,  /* will set later */
1036
		    infoPtr->hwndCombo,
1037 1038
		    (HMENU) GetWindowLongPtrW (hwnd, GWLP_ID),
		    (HINSTANCE)GetWindowLongPtrW (hwnd, GWLP_HINSTANCE), NULL);
1039 1040 1041 1042 1043 1044 1045

	/* native does the following at this point according to trace:
	 *  GetWindowThreadProcessId(hwndEdit,0)
	 *  GetCurrentThreadId()
	 *  GetWindowThreadProcessId(hwndEdit, &???)
	 *  GetCurrentProcessId()
	 */
1046 1047
	SetWindowSubclass(infoPtr->hwndEdit, COMBOEX_EditWndProc, EDIT_SUBCLASSID,
	                  (DWORD_PTR)hwnd);
1048

1049
	infoPtr->font = (HFONT)SendMessageW(infoPtr->hwndCombo, WM_GETFONT, 0, 0);
1050
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1051

1052 1053 1054 1055 1056
    /*
     * Locate the default font if necessary and then set it in
     * all associated controls
     */
    if (!infoPtr->font) {
1057
	SystemParametersInfoW (SPI_GETICONTITLELOGFONT, sizeof(mylogfont),
1058
			       &mylogfont, 0);
1059
	infoPtr->font = infoPtr->defaultFont = CreateFontIndirectW (&mylogfont);
1060
    }
1061
    SendMessageW (infoPtr->hwndCombo, WM_SETFONT, (WPARAM)infoPtr->font, 0);
1062
    if (infoPtr->hwndEdit) {
1063
	SendMessageW (infoPtr->hwndEdit, WM_SETFONT, (WPARAM)infoPtr->font, 0);
1064
       SendMessageW (infoPtr->hwndEdit, EM_SETMARGINS, EC_USEFONTINFO, 0);
1065 1066
    }

1067
    COMBOEX_ReSize (infoPtr);
1068 1069 1070

    /* Above is fairly certain, below is much less certain. */

1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
    GetWindowRect(hwnd, &win_rect);

    if (TRACE_ON(comboex)) {
	RECT client, rect;
	GetClientRect(hwnd, &client);
	GetWindowRect(infoPtr->hwndCombo, &rect);
	TRACE("EX window=(%s) client=(%s) CB wnd=(%s)\n",
		wine_dbgstr_rect(&win_rect), wine_dbgstr_rect(&client),
		wine_dbgstr_rect(&rect));
    }
    SetWindowPos(infoPtr->hwndCombo, HWND_TOP, 0, 0,
		 win_rect.right - win_rect.left, win_rect.bottom - win_rect.top,
1083 1084
		 SWP_NOACTIVATE | SWP_NOREDRAW);

1085 1086 1087 1088
    GetWindowRect(infoPtr->hwndCombo, &win_rect);
    TRACE("CB window=(%s)\n", wine_dbgstr_rect(&win_rect));
    SetWindowPos(hwnd, HWND_TOP, 0, 0,
		 win_rect.right - win_rect.left, win_rect.bottom - win_rect.top,
1089 1090 1091 1092 1093
		 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);

    COMBOEX_AdjustEditPos (infoPtr);

    /*
1094
     * Create an item structure to represent the data in the
1095
     * EDIT control. It is allocated zero-filled.
1096
     */
1097
    infoPtr->edit = Alloc (sizeof (CBE_ITEMDATA));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1098 1099 1100 1101
    if (!infoPtr->edit) {
	COMBOEX_Destroy(infoPtr);
	return -1;
    }
1102 1103 1104 1105 1106

    return 0;
}


1107
static LRESULT COMBOEX_Command (COMBOEX_INFO *infoPtr, WPARAM wParam)
1108 1109 1110 1111 1112
{
    LRESULT lret;
    INT command = HIWORD(wParam);
    CBE_ITEMDATA *item = 0;
    WCHAR wintext[520];
1113 1114
    INT cursel, n;
    INT_PTR oldItem;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1115
    NMCBEENDEDITW cbeend;
1116
    DWORD oldflags;
1117
    HWND parent = infoPtr->hwndNotify;
1118 1119 1120 1121 1122 1123

    TRACE("for command %d\n", command);

    switch (command)
    {
    case CBN_DROPDOWN:
1124 1125 1126 1127
        SetFocus (infoPtr->hwndCombo);
        ShowWindow (infoPtr->hwndEdit, SW_HIDE);
        infoPtr->flags |= WCBE_ACTEDIT;
        return SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1128 1129
    case CBN_CLOSEUP:
	SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1130
	/*
1131
	 * from native trace of first dropdown after typing in URL in IE4
1132 1133 1134 1135 1136 1137 1138 1139 1140
	 *  CB_GETCURSEL(Combo)
	 *  GetWindowText(Edit)
	 *  CB_GETCURSEL(Combo)
	 *  CB_GETCOUNT(Combo)
	 *  CB_GETITEMDATA(Combo, n)
	 *  WM_NOTIFY(parent, CBEN_ENDEDITA|W)
	 *  CB_GETCURSEL(Combo)
	 *  CB_SETCURSEL(COMBOEX, n)
	 *  SetFocus(Combo)
1141
	 * the rest is supposition
1142
	 */
1143 1144
	ShowWindow (infoPtr->hwndEdit, SW_SHOW);
	InvalidateRect (infoPtr->hwndCombo, 0, TRUE);
1145
	if (infoPtr->hwndEdit) InvalidateRect (infoPtr->hwndEdit, 0, TRUE);
1146 1147
	cursel = SendMessageW (infoPtr->hwndCombo, CB_GETCURSEL, 0, 0);
	if (cursel == -1) {
1148
            cmp_func_t cmptext = get_cmp_func(infoPtr);
1149 1150 1151 1152
	    /* find match from edit against those in Combobox */
	    GetWindowTextW (infoPtr->hwndEdit, wintext, 520);
	    n = SendMessageW (infoPtr->hwndCombo, CB_GETCOUNT, 0, 0);
	    for (cursel = 0; cursel < n; cursel++){
1153
                item = get_item_data(infoPtr, cursel);
Frank Richter's avatar
Frank Richter committed
1154
		if ((INT_PTR)item == CB_ERR) break;
1155
		if (!cmptext(COMBOEX_GetText(infoPtr, item), wintext)) break;
1156
	    }
Frank Richter's avatar
Frank Richter committed
1157
	    if ((cursel == n) || ((INT_PTR)item == CB_ERR)) {
1158 1159
		TRACE("failed to find match??? item=%p cursel=%d\n",
		      item, cursel);
1160
		if (infoPtr->hwndEdit) SetFocus(infoPtr->hwndEdit);
1161 1162 1163
		return 0;
	    }
	}
1164
	else {
1165
            item = get_item_data(infoPtr, cursel);
Frank Richter's avatar
Frank Richter committed
1166
	    if ((INT_PTR)item == CB_ERR) {
1167 1168
		TRACE("failed to find match??? item=%p cursel=%d\n",
		      item, cursel);
1169
		if (infoPtr->hwndEdit) SetFocus(infoPtr->hwndEdit);
1170 1171 1172
		return 0;
	    }
	}
1173 1174 1175 1176 1177 1178 1179

	/* Save flags for testing and reset them */
	oldflags = infoPtr->flags;
	infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);

	if (oldflags & WCBE_ACTEDIT) {
	    cbeend.fChanged = (oldflags & WCBE_EDITCHG);
1180 1181 1182 1183
	    cbeend.iNewSelection = SendMessageW (infoPtr->hwndCombo,
						 CB_GETCURSEL, 0, 0);
	    cbeend.iWhy = CBENF_DROPDOWN;

1184
	    if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, COMBOEX_GetText(infoPtr, item))) return 0;
1185
	}
1186 1187 1188 1189 1190

	/* if selection has changed the set the new current selection */
	cursel = SendMessageW (infoPtr->hwndCombo, CB_GETCURSEL, 0, 0);
	if ((oldflags & WCBE_EDITCHG) || (cursel != infoPtr->selected)) {
	    infoPtr->selected = cursel;
1191
	    SendMessageW (infoPtr->hwndSelf, CB_SETCURSEL, cursel, 0);
1192 1193
	    SetFocus(infoPtr->hwndCombo);
	}
1194 1195
	return 0;

1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211
    case CBN_SELCHANGE:
	/*
	 * CB_GETCURSEL(Combo)
	 * CB_GETITEMDATA(Combo)   < simulated by COMBOEX_FindItem
	 * lstrlenA
	 * WM_SETTEXT(Edit)
	 * WM_GETTEXTLENGTH(Edit)
	 * WM_GETTEXT(Edit)
	 * EM_SETSEL(Edit, 0,0)
	 * WM_GETTEXTLENGTH(Edit)
	 * WM_GETTEXT(Edit)
	 * EM_SETSEL(Edit, 0,len)
	 * return WM_COMMAND to parent
	 */
	oldItem = SendMessageW (infoPtr->hwndCombo, CB_GETCURSEL, 0, 0);
	if (!(item = COMBOEX_FindItem(infoPtr, oldItem))) {
1212
	    ERR("item %ld not found. Problem!\n", oldItem);
1213
	    break;
1214 1215
	}
	infoPtr->selected = oldItem;
1216
	COMBOEX_SetEditText (infoPtr, item);
1217
	return SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1218 1219

    case CBN_SELENDOK:
1220
    case CBN_SELENDCANCEL:
1221
	/*
1222 1223 1224
	 * We have to change the handle since we are the control
	 * issuing the message. IE4 depends on this.
	 */
1225
	return SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238

    case CBN_KILLFOCUS:
	/*
	 * from native trace:
	 *
	 *  pass to parent
	 *  WM_GETTEXT(Edit, 104)
	 *  CB_GETCURSEL(Combo) rets -1
	 *  WM_NOTIFY(CBEN_ENDEDITA) with CBENF_KILLFOCUS
	 *  CB_GETCURSEL(Combo)
	 *  InvalidateRect(Combo, 0, 0)
	 *  return 0
	 */
1239
	SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1240
	if (infoPtr->flags & WCBE_ACTEDIT) {
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1241
	    GetWindowTextW (infoPtr->hwndEdit, wintext, 260);
1242 1243 1244 1245 1246 1247
	    cbeend.fChanged = (infoPtr->flags & WCBE_EDITCHG);
	    cbeend.iNewSelection = SendMessageW (infoPtr->hwndCombo,
						 CB_GETCURSEL, 0, 0);
	    cbeend.iWhy = CBENF_KILLFOCUS;

	    infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);
1248
	    if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, wintext)) return 0;
1249 1250 1251 1252 1253
	}
	/* possible CB_GETCURSEL */
	InvalidateRect (infoPtr->hwndCombo, 0, 0);
	return 0;

1254 1255 1256
    case CBN_SETFOCUS:
        return SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);

1257
    default:
1258
	/*
1259 1260 1261 1262 1263
	 * We have to change the handle since we are the control
	 * issuing the message. IE4 depends on this.
	 * We also need to set the focus back to the Edit control
	 * after passing the command to the parent of the ComboEx.
	 */
1264
	lret = SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1265
	if (infoPtr->hwndEdit) SetFocus(infoPtr->hwndEdit);
1266 1267 1268 1269 1270 1271
	return lret;
    }
    return 0;
}


1272
static BOOL COMBOEX_WM_DeleteItem (COMBOEX_INFO *infoPtr, DELETEITEMSTRUCT const *dis)
1273 1274 1275
{
    CBE_ITEMDATA *item, *olditem;
    NMCOMBOBOXEXW nmcit;
1276
    UINT i;
1277

1278
    TRACE("CtlType=%08x, CtlID=%08x, itemID=%08x, hwnd=%p, data=%08lx\n",
1279 1280
	  dis->CtlType, dis->CtlID, dis->itemID, dis->hwndItem, dis->itemData);

1281
    if (dis->itemID >= infoPtr->nb_items) return FALSE;
1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294

    olditem = infoPtr->items;
    i = infoPtr->nb_items - 1;

    if (i == dis->itemID) {
	infoPtr->items = infoPtr->items->next;
    }
    else {
	item = olditem;
	i--;

	/* find the prior item in the list */
	while (item->next && (i > dis->itemID)) {
1295
	    item = item->next;
1296 1297 1298
	    i--;
	}
	if (!item->next || (i != dis->itemID)) {
1299
	    ERR("COMBOBOXEX item structures broken. Please report!\n");
1300 1301 1302
	    return FALSE;
	}
	olditem = item->next;
1303
	item->next = item->next->next;
1304 1305 1306
    }
    infoPtr->nb_items--;

1307
    memset (&nmcit.ceItem, 0, sizeof(nmcit.ceItem));
1308
    nmcit.ceItem.mask=~0;
1309
    COMBOEX_CopyItem (olditem, &nmcit.ceItem);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1310
    COMBOEX_NotifyItem (infoPtr, CBEN_DELETEITEM, &nmcit);
1311

1312
    COMBOEX_FreeText(olditem);
1313
    Free(olditem);
1314 1315 1316 1317 1318

    return TRUE;
}


1319
static LRESULT COMBOEX_DrawItem (const COMBOEX_INFO *infoPtr, DRAWITEMSTRUCT const *dis)
1320
{
1321
    static const WCHAR nil[] = { 0 };
1322
    CBE_ITEMDATA *item = 0;
1323 1324
    SIZE txtsize;
    RECT rect;
1325
    LPCWSTR str = nil;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1326
    UINT xbase, x, y;
1327
    INT len;
1328
    COLORREF nbkc, ntxc, bkc, txc;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1329
    int drawimage, drawstate, xioff;
1330

1331
    TRACE("DRAWITEMSTRUCT: CtlType=0x%08x CtlID=0x%08x\n",
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1332
	  dis->CtlType, dis->CtlID);
1333
    TRACE("itemID=0x%08x itemAction=0x%08x itemState=0x%08x\n",
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1334
	  dis->itemID, dis->itemAction, dis->itemState);
1335 1336
    TRACE("hWnd=%p hDC=%p (%s) itemData=0x%08lx\n",
          dis->hwndItem, dis->hDC, wine_dbgstr_rect(&dis->rcItem), dis->itemData);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1337

1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349
    /* MSDN says:                                                       */
    /*     "itemID - Specifies the menu item identifier for a menu      */
    /*      item or the index of the item in a list box or combo box.   */
    /*      For an empty list box or combo box, this member can be -1.  */
    /*      This allows the application to draw only the focus          */
    /*      rectangle at the coordinates specified by the rcItem        */
    /*      member even though there are no items in the control.       */
    /*      This indicates to the user whether the list box or combo    */
    /*      box has the focus. How the bits are set in the itemAction   */
    /*      member determines whether the rectangle is to be drawn as   */
    /*      though the list box or combo box has the focus.             */
    if (dis->itemID == 0xffffffff) {
1350
	if ( ( (dis->itemAction & ODA_FOCUS) && (dis->itemState & ODS_SELECTED)) ||
1351
	     ( (dis->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)) && (dis->itemState & ODS_FOCUS) ) ) {
1352

1353 1354
            TRACE("drawing item -1 special focus, rect=(%s)\n",
                  wine_dbgstr_rect(&dis->rcItem));
1355
	}
1356
	else if ((dis->CtlType == ODT_COMBOBOX) &&
1357
		 (dis->itemAction == ODA_DRAWENTIRE)) {
1358 1359
	    /* draw of edit control data */

1360
	    if (TRACE_ON(comboex)) {
1361
		RECT exrc, cbrc, edrc;
1362
		GetWindowRect (infoPtr->hwndSelf, &exrc);
1363
		GetWindowRect (infoPtr->hwndCombo, &cbrc);
1364
		edrc.left = edrc.top = edrc.right = edrc.bottom = -1;
1365
		if (infoPtr->hwndEdit) GetWindowRect (infoPtr->hwndEdit, &edrc);
1366 1367 1368
                TRACE("window rects ex=(%s), cb=(%s), ed=(%s)\n",
                      wine_dbgstr_rect(&exrc), wine_dbgstr_rect(&cbrc),
                      wine_dbgstr_rect(&edrc));
1369 1370 1371
	    }
	}
	else {
1372 1373 1374
            ERR("NOT drawing item  -1 special focus, rect=(%s), action=%08x, state=%08x\n",
                wine_dbgstr_rect(&dis->rcItem),
                dis->itemAction, dis->itemState);
1375 1376
	    return 0;
	}
1377 1378
    }

1379 1380 1381
    /* If draw item is -1 (edit control) setup the item pointer */
    if (dis->itemID == 0xffffffff) {
	item = infoPtr->edit;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1382 1383 1384

	if (infoPtr->hwndEdit) {
	    /* free previous text of edit item */
1385 1386
	    COMBOEX_FreeText(item);
	    item->mask &= ~CBEIF_TEXT;
1387
	    if( (len = GetWindowTextLengthW(infoPtr->hwndEdit)) ) {
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1388
		item->mask |= CBEIF_TEXT;
1389
                item->pszText = Alloc ((len + 1)*sizeof(WCHAR));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1390 1391
		if (item->pszText)
		    GetWindowTextW(infoPtr->hwndEdit, item->pszText, len+1);
1392

1393
	       TRACE("edit control hwndEdit=%p, text len=%d str=%s\n",
1394
		     infoPtr->hwndEdit, len, debugstr_txt(item->pszText));
1395 1396 1397 1398
	    }
	}
    }

1399

1400 1401
    /* if the item pointer is not set, then get the data and locate it */
    if (!item) {
1402
        item = get_item_data(infoPtr, dis->itemID);
1403
	if (item == (CBE_ITEMDATA *)CB_ERR) {
1404
	    ERR("invalid item for id %d\n", dis->itemID);
1405 1406
	    return 0;
	}
1407
    }
1408

1409
    if (TRACE_ON(comboex)) COMBOEX_DumpItem (item);
1410 1411

    xbase = CBE_STARTOFFSET;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1412 1413 1414 1415 1416 1417
    if ((item->mask & CBEIF_INDENT) && (dis->itemState & ODS_COMBOEXLBOX)) {
	INT indent = item->iIndent;
	if (indent == I_INDENTCALLBACK) {
	    NMCOMBOBOXEXW nmce;
	    ZeroMemory(&nmce, sizeof(nmce));
	    nmce.ceItem.mask = CBEIF_INDENT;
1418
	    nmce.ceItem.lParam = item->lParam;
1419
	    nmce.ceItem.iItem = dis->itemID;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441
	    COMBOEX_NotifyItem(infoPtr, CBEN_GETDISPINFOW, &nmce);
	    if (nmce.ceItem.mask & CBEIF_DI_SETITEM)
		item->iIndent = nmce.ceItem.iIndent;
	    indent = nmce.ceItem.iIndent;
	}
        xbase += (indent * CBE_INDENT);
    }

    drawimage = -2;
    drawstate = ILD_NORMAL;
    if (item->mask & CBEIF_IMAGE)
	drawimage = item->iImage;
    if (dis->itemState & ODS_COMBOEXLBOX) {
	/* drawing listbox entry */
	if (dis->itemState & ODS_SELECTED) {
	    if (item->mask & CBEIF_SELECTEDIMAGE)
	        drawimage = item->iSelectedImage;
	    drawstate = ILD_SELECTED;
	}
    } else {
	/* drawing combo/edit entry */
	if (IsWindowVisible(infoPtr->hwndEdit)) {
1442 1443
	    /* if we have an edit control, the slave the
             * selection state to the Edit focus state
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1444 1445 1446 1447 1448 1449 1450
	     */
	    if (infoPtr->flags & WCBE_EDITFOCUSED) {
	        if (item->mask & CBEIF_SELECTEDIMAGE)
		    drawimage = item->iSelectedImage;
		drawstate = ILD_SELECTED;
	    }
	} else {
1451
	    /* if we don't have an edit control, use
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1452 1453 1454 1455 1456 1457 1458 1459
	     * the requested state.
	     */
	    if (dis->itemState & ODS_SELECTED) {
		if (item->mask & CBEIF_SELECTEDIMAGE)
		    drawimage = item->iSelectedImage;
		drawstate = ILD_SELECTED;
	    }
	}
1460 1461
    }

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1462 1463 1464 1465 1466 1467 1468
    if (infoPtr->himl && !(infoPtr->dwExtStyle & CBES_EX_NOEDITIMAGEINDENT)) {
    	IMAGEINFO iinfo;
        iinfo.rcImage.left = iinfo.rcImage.right = 0;
	ImageList_GetImageInfo(infoPtr->himl, 0, &iinfo);
	xioff = iinfo.rcImage.right - iinfo.rcImage.left + CBE_SEP;
    }  else xioff = 0;

1469
    /* setup pointer to text to be drawn */
1470 1471
    str = COMBOEX_GetText(infoPtr, item);
    if (!str) str = nil;
1472 1473 1474

    len = strlenW (str);
    GetTextExtentPoint32W (dis->hDC, str, len, &txtsize);
1475

1476
    if (dis->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)) {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1477
	int overlay = item->iOverlay;
1478

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1479 1480 1481 1482
    	if (drawimage == I_IMAGECALLBACK) {
	    NMCOMBOBOXEXW nmce;
	    ZeroMemory(&nmce, sizeof(nmce));
	    nmce.ceItem.mask = (drawstate == ILD_NORMAL) ? CBEIF_IMAGE : CBEIF_SELECTEDIMAGE;
1483
	    nmce.ceItem.lParam = item->lParam;
1484
	    nmce.ceItem.iItem = dis->itemID;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498
	    COMBOEX_NotifyItem(infoPtr, CBEN_GETDISPINFOW, &nmce);
	    if (drawstate == ILD_NORMAL) {
	    	if (nmce.ceItem.mask & CBEIF_DI_SETITEM) item->iImage = nmce.ceItem.iImage;
	    	drawimage = nmce.ceItem.iImage;
	    } else if (drawstate == ILD_SELECTED) {
	        if (nmce.ceItem.mask & CBEIF_DI_SETITEM) item->iSelectedImage = nmce.ceItem.iSelectedImage;
	        drawimage =  nmce.ceItem.iSelectedImage;
	    } else ERR("Bad draw state = %d\n", drawstate);
        }

	if (overlay == I_IMAGECALLBACK) {
	    NMCOMBOBOXEXW nmce;
	    ZeroMemory(&nmce, sizeof(nmce));
	    nmce.ceItem.mask = CBEIF_OVERLAY;
1499
	    nmce.ceItem.lParam = item->lParam;
1500
	    nmce.ceItem.iItem = dis->itemID;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1501 1502 1503 1504
	    COMBOEX_NotifyItem(infoPtr, CBEN_GETDISPINFOW, &nmce);
	    if (nmce.ceItem.mask & CBEIF_DI_SETITEM)
		item->iOverlay = nmce.ceItem.iOverlay;
	    overlay = nmce.ceItem.iOverlay;
1505
	}
1506 1507

	if (drawimage >= 0 &&
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1508 1509
	    !(infoPtr->dwExtStyle & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT))) {
	    if (overlay > 0) ImageList_SetOverlayImage (infoPtr->himl, overlay, 1);
1510
	    ImageList_Draw (infoPtr->himl, drawimage, dis->hDC, xbase, dis->rcItem.top,
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1511
			    drawstate | (overlay > 0 ? INDEXTOOVERLAYMASK(1) : 0));
1512
	}
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1513 1514

	/* now draw the text */
1515
	if (!IsWindowVisible (infoPtr->hwndEdit)) {
1516 1517
	    nbkc = (dis->itemState & ODS_SELECTED) ?
	            comctl32_color.clrHighlight : comctl32_color.clrWindow;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1518
	    bkc = SetBkColor (dis->hDC, nbkc);
1519 1520
	    ntxc = (dis->itemState & ODS_SELECTED) ?
	            comctl32_color.clrHighlightText : comctl32_color.clrWindowText;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1521 1522 1523 1524 1525 1526 1527 1528
	    txc = SetTextColor (dis->hDC, ntxc);
	    x = xbase + xioff;
	    y = dis->rcItem.top +
	        (dis->rcItem.bottom - dis->rcItem.top - txtsize.cy) / 2;
	    rect.left = x;
	    rect.right = x + txtsize.cx;
	    rect.top = dis->rcItem.top + 1;
	    rect.bottom = dis->rcItem.bottom - 1;
1529 1530
            TRACE("drawing item %d text, rect=(%s)\n",
                  dis->itemID, wine_dbgstr_rect(&rect));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1531 1532 1533 1534
	    ExtTextOutW (dis->hDC, x, y, ETO_OPAQUE | ETO_CLIPPED,
		         &rect, str, len, 0);
	    SetBkColor (dis->hDC, bkc);
	    SetTextColor (dis->hDC, txc);
1535
	}
1536
    }
1537

1538 1539 1540 1541 1542 1543
    if (dis->itemAction & ODA_FOCUS) {
	rect.left = xbase + xioff - 1;
	rect.right = rect.left + txtsize.cx + 2;
	rect.top = dis->rcItem.top;
	rect.bottom = dis->rcItem.bottom;
	DrawFocusRect(dis->hDC, &rect);
1544 1545
    }

Alexandre Julliard's avatar
Alexandre Julliard committed
1546 1547 1548 1549
    return 0;
}


1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570
static void COMBOEX_ResetContent (COMBOEX_INFO *infoPtr)
{
    if (infoPtr->items)
    {
        CBE_ITEMDATA *item, *next;

        item = infoPtr->items;
        while (item) {
            next = item->next;
            COMBOEX_FreeText (item);
            Free (item);
            item = next;
        }
        infoPtr->items = 0;
    }

    infoPtr->selected = -1;
    infoPtr->nb_items = 0;
}


1571
static LRESULT COMBOEX_Destroy (COMBOEX_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
1572 1573
{
    if (infoPtr->hwndCombo)
1574 1575 1576 1577
        RemoveWindowSubclass(infoPtr->hwndCombo, COMBOEX_ComboWndProc, COMBO_SUBCLASSID);

    if (infoPtr->hwndEdit)
        RemoveWindowSubclass(infoPtr->hwndEdit, COMBOEX_EditWndProc, EDIT_SUBCLASSID);
Alexandre Julliard's avatar
Alexandre Julliard committed
1578

1579
    COMBOEX_FreeText (infoPtr->edit);
1580 1581
    Free (infoPtr->edit);
    infoPtr->edit = 0;
1582

1583
    COMBOEX_ResetContent (infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
1584

1585
    if (infoPtr->defaultFont)
1586
	DeleteObject (infoPtr->defaultFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
1587

1588 1589
    SetWindowLongPtrW (infoPtr->hwndSelf, 0, 0);

Alexandre Julliard's avatar
Alexandre Julliard committed
1590
    /* free comboex info data */
1591
    Free (infoPtr);
1592

Alexandre Julliard's avatar
Alexandre Julliard committed
1593 1594 1595 1596
    return 0;
}


1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612
static LRESULT COMBOEX_Enable (COMBOEX_INFO *infoPtr, BOOL enable)
{
    TRACE("hwnd=%p, enable=%s\n", infoPtr->hwndSelf, enable ? "TRUE":"FALSE");

    if (infoPtr->hwndEdit)
       EnableWindow(infoPtr->hwndEdit, enable);

    EnableWindow(infoPtr->hwndCombo, enable);

    /* Force the control to repaint when the enabled state changes. */
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);

    return 1;
}


1613
static LRESULT COMBOEX_MeasureItem (COMBOEX_INFO const *infoPtr, MEASUREITEMSTRUCT *mis)
1614
{
1615
    static const WCHAR strW[] = { 'W', 0 };
1616
    SIZE mysize;
1617
    HDC hdc;
1618 1619

    hdc = GetDC (0);
1620
    GetTextExtentPointW (hdc, strW, 1, &mysize);
1621 1622 1623
    ReleaseDC (0, hdc);
    mis->itemHeight = mysize.cy + CBE_EXTRA;

1624
    TRACE("adjusted height hwnd=%p, height=%d\n",
1625
	  infoPtr->hwndSelf, mis->itemHeight);
1626 1627 1628 1629 1630

    return 0;
}


1631
static LRESULT COMBOEX_NCCreate (HWND hwnd)
1632 1633 1634 1635
{
    /* WARNING: The COMBOEX_INFO structure is not yet created */
    DWORD oldstyle, newstyle;

1636
    oldstyle = (DWORD)GetWindowLongW (hwnd, GWL_STYLE);
1637
    newstyle = oldstyle & ~(WS_VSCROLL | WS_HSCROLL | WS_BORDER);
1638
    if (newstyle != oldstyle) {
1639
	TRACE("req style %08x, resetting style %08x\n",
1640
	      oldstyle, newstyle);
1641
	SetWindowLongW (hwnd, GWL_STYLE, newstyle);
1642 1643 1644 1645 1646
    }
    return 1;
}


1647
static LRESULT COMBOEX_NotifyFormat (COMBOEX_INFO *infoPtr, LPARAM lParam)
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1648 1649
{
    if (lParam == NF_REQUERY) {
1650
	INT i = SendMessageW(infoPtr->hwndNotify,
1651
			 WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1652 1653
	infoPtr->NtfUnicode = (i == NFR_UNICODE) ? 1 : 0;
    }
1654
    return infoPtr->NtfUnicode ? NFR_UNICODE : NFR_ANSI;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1655 1656 1657
}


1658
static LRESULT COMBOEX_Size (COMBOEX_INFO *infoPtr, INT width, INT height)
Alexandre Julliard's avatar
Alexandre Julliard committed
1659
{
1660
    TRACE("(width=%d, height=%d)\n", width, height);
Alexandre Julliard's avatar
Alexandre Julliard committed
1661

1662
    MoveWindow (infoPtr->hwndCombo, 0, 0, width, height, TRUE);
Alexandre Julliard's avatar
Alexandre Julliard committed
1663

1664 1665
    COMBOEX_AdjustEditPos (infoPtr);

Alexandre Julliard's avatar
Alexandre Julliard committed
1666 1667 1668 1669
    return 0;
}


1670
static LRESULT COMBOEX_SetRedraw(const COMBOEX_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
1671 1672 1673 1674 1675 1676 1677
{
    LRESULT ret = DefWindowProcW( infoPtr->hwndSelf, WM_SETREDRAW, wParam, lParam );
    if (wParam) RedrawWindow( infoPtr->hwndSelf, NULL, 0, RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN );
    return ret;
}


1678
static LRESULT COMBOEX_WindowPosChanging (const COMBOEX_INFO *infoPtr, WINDOWPOS *wp)
1679 1680
{
    RECT cbx_wrect, cbx_crect, cb_wrect;
1681
    INT width, height;
1682

1683 1684
    GetWindowRect (infoPtr->hwndSelf, &cbx_wrect);
    GetClientRect (infoPtr->hwndSelf, &cbx_crect);
1685 1686 1687 1688
    GetWindowRect (infoPtr->hwndCombo, &cb_wrect);

    /* width is winpos value + border width of comboex */
    width = wp->cx
1689 1690
	    + (cbx_wrect.right-cbx_wrect.left)
            - (cbx_crect.right-cbx_crect.left);
1691

1692 1693
    TRACE("winpos=(%d,%d %dx%d) flags=0x%08x\n",
	  wp->x, wp->y, wp->cx, wp->cy, wp->flags);
1694 1695 1696 1697
    TRACE("EX window=(%s), client=(%s)\n",
          wine_dbgstr_rect(&cbx_wrect), wine_dbgstr_rect(&cbx_crect));
    TRACE("CB window=(%s), EX setting=(0,0)-(%d,%d)\n",
          wine_dbgstr_rect(&cbx_wrect), width, cb_wrect.bottom-cb_wrect.top);
1698

1699 1700 1701 1702
    if (width) SetWindowPos (infoPtr->hwndCombo, HWND_TOP, 0, 0,
			     width,
			     cb_wrect.bottom-cb_wrect.top,
			     SWP_NOACTIVATE);
1703

1704 1705 1706 1707 1708 1709
    GetWindowRect (infoPtr->hwndCombo, &cb_wrect);

    /* height is combo window height plus border width of comboex */
    height =   (cb_wrect.bottom-cb_wrect.top)
	     + (cbx_wrect.bottom-cbx_wrect.top)
             - (cbx_crect.bottom-cbx_crect.top);
1710
    wp->cy = height;
1711 1712 1713 1714
    if (infoPtr->hwndEdit) {
	COMBOEX_AdjustEditPos (infoPtr);
	InvalidateRect (infoPtr->hwndCombo, 0, TRUE);
    }
1715 1716 1717 1718

    return 0;
}

1719 1720 1721
static LRESULT CALLBACK
COMBOEX_EditWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                     UINT_PTR uId, DWORD_PTR ref_data)
1722
{
1723
    COMBOEX_INFO *infoPtr = COMBOEX_GetInfoPtr ((HWND)ref_data);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1724 1725
    NMCBEENDEDITW cbeend;
    WCHAR edit_text[260];
1726
    COLORREF obkc;
1727 1728
    HDC hDC;
    RECT rect;
1729
    LRESULT lret;
1730

1731
    TRACE("hwnd=%p msg=%x wparam=%lx lParam=%lx, info_ptr=%p\n",
1732 1733 1734 1735 1736 1737 1738
	  hwnd, uMsg, wParam, lParam, infoPtr);

    if (!infoPtr) return 0;

    switch (uMsg)
    {

1739
	case WM_CHAR:
1740 1741 1742
	    /* handle (ignore) the return character */
	    if (wParam == VK_RETURN) return 0;
	    /* all other characters pass into the real Edit */
1743
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
1744

1745
	case WM_ERASEBKGND:
1746
	    /*
1747
	     * The following was determined by traces of the native
1748 1749
	     */
            hDC = (HDC) wParam;
1750
	    obkc = SetBkColor (hDC, comctl32_color.clrWindow);
1751
            GetClientRect (hwnd, &rect);
1752
            TRACE("erasing (%s)\n", wine_dbgstr_rect(&rect));
1753 1754
	    ExtTextOutW (hDC, 0, 0, ETO_OPAQUE, &rect, 0, 0, 0);
            SetBkColor (hDC, obkc);
1755
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
1756

1757
	case WM_KEYDOWN: {
1758
	    INT_PTR oldItem, selected;
1759 1760 1761 1762 1763 1764 1765
	    CBE_ITEMDATA *item;

	    switch ((INT)wParam)
	    {
	    case VK_ESCAPE:
		/* native version seems to do following for COMBOEX */
		/*
1766
		 *   GetWindowTextW(Edit,&?, 0x104)             x
1767 1768
		 *   CB_GETCURSEL to Combo rets -1              x
		 *   WM_NOTIFY to COMBOEX parent (rebar)        x
1769
		 *     (CBEN_ENDEDIT{A|W}
1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782
		 *      fChanged = FALSE                        x
		 *      inewSelection = -1                      x
		 *      txt="www.hoho"                          x
		 *      iWhy = 3                                x
		 *   CB_GETCURSEL to Combo rets -1              x
		 *   InvalidateRect(Combo, 0)                   x
		 *   WM_SETTEXT to Edit                         x
		 *   EM_SETSEL to Edit (0,0)                    x
		 *   EM_SETSEL to Edit (0,-1)                   x
		 *   RedrawWindow(Combo, 0, 0, 5)               x
		 */
		TRACE("special code for VK_ESCAPE\n");

Guy L. Albertelli's avatar
Guy L. Albertelli committed
1783
		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
1784

1785
		infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);
1786
		cbeend.fChanged = FALSE;
1787
		cbeend.iNewSelection = SendMessageW (infoPtr->hwndCombo,
1788 1789 1790
						     CB_GETCURSEL, 0, 0);
		cbeend.iWhy = CBENF_ESCAPE;

1791
		if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, edit_text)) return 0;
1792
		oldItem = SendMessageW (infoPtr->hwndCombo, CB_GETCURSEL, 0, 0);
1793 1794
		InvalidateRect (infoPtr->hwndCombo, 0, 0);
		if (!(item = COMBOEX_FindItem(infoPtr, oldItem))) {
1795
		    ERR("item %ld not found. Problem!\n", oldItem);
1796 1797
		    break;
		}
1798
		infoPtr->selected = oldItem;
1799
		COMBOEX_SetEditText (infoPtr, item);
1800
		RedrawWindow (infoPtr->hwndCombo, 0, 0, RDW_ERASE |
1801 1802 1803 1804 1805 1806
			      RDW_INVALIDATE);
		break;

	    case VK_RETURN:
		/* native version seems to do following for COMBOEX */
		/*
1807
		 *   GetWindowTextW(Edit,&?, 0x104)             x
1808 1809
		 *   CB_GETCURSEL to Combo  rets -1             x
		 *   CB_GETCOUNT to Combo  rets 0
1810
		 *   if >0 loop
1811
		 *       CB_GETITEMDATA to match
1812
		 * *** above 3 lines simulated by FindItem      x
1813
		 *   WM_NOTIFY to COMBOEX parent (rebar)        x
1814
		 *     (CBEN_ENDEDIT{A|W}                       x
1815
		 *        fChanged = TRUE (-1)                  x
1816
		 *        iNewSelection = -1 or selected        x
1817 1818 1819
		 *        txt=                                  x
		 *        iWhy = 2 (CBENF_RETURN)               x
		 *   CB_GETCURSEL to Combo  rets -1             x
1820
		 *   if -1 send CB_SETCURSEL to Combo -1        x
1821 1822 1823 1824 1825 1826 1827
		 *   InvalidateRect(Combo, 0, 0)                x
		 *   SetFocus(Edit)                             x
		 *   CallWindowProc(406615a8, Edit, 0x100, 0xd, 0x1c0001)
		 */

		TRACE("special code for VK_RETURN\n");

1828
		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
1829

1830 1831 1832 1833 1834
		infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);
		selected = SendMessageW (infoPtr->hwndCombo,
					 CB_GETCURSEL, 0, 0);

		if (selected != -1) {
1835
                    cmp_func_t cmptext = get_cmp_func(infoPtr);
1836
		    item = COMBOEX_FindItem (infoPtr, selected);
1837
		    TRACE("handling VK_RETURN, selected = %ld, selected_text=%s\n",
1838
			  selected, debugstr_txt(item->pszText));
1839 1840
		    TRACE("handling VK_RETURN, edittext=%s\n",
			  debugstr_w(edit_text));
1841
		    if (cmptext (COMBOEX_GetText(infoPtr, item), edit_text)) {
1842 1843 1844 1845 1846 1847
			/* strings not equal -- indicate edit has changed */
			selected = -1;
		    }
		}

		cbeend.iNewSelection = selected;
1848
		cbeend.fChanged = TRUE;
1849
		cbeend.iWhy = CBENF_RETURN;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1850
		if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, edit_text)) {
1851 1852 1853
		    /* abort the change, restore previous */
		    TRACE("Notify requested abort of change\n");
		    COMBOEX_SetEditText (infoPtr, infoPtr->edit);
1854
		    RedrawWindow (infoPtr->hwndCombo, 0, 0, RDW_ERASE |
1855
				  RDW_INVALIDATE);
1856 1857
		    return 0;
		}
1858 1859 1860
		oldItem = SendMessageW (infoPtr->hwndCombo,CB_GETCURSEL, 0, 0);
		if (oldItem != -1) {
		    /* if something is selected, then deselect it */
1861
                    SendMessageW (infoPtr->hwndCombo, CB_SETCURSEL, -1, 0);
1862
		}
1863 1864 1865 1866
		InvalidateRect (infoPtr->hwndCombo, 0, 0);
		SetFocus(infoPtr->hwndEdit);
		break;

1867 1868
	    case VK_UP:
	    case VK_DOWN:
1869 1870 1871
	    {
		INT step = wParam == VK_DOWN ? 1 : -1;

1872 1873 1874 1875
		oldItem = SendMessageW (infoPtr->hwndSelf, CB_GETCURSEL, 0, 0);
		if (oldItem >= 0 && oldItem + step >= 0)
		    SendMessageW (infoPtr->hwndSelf, CB_SETCURSEL, oldItem + step, 0);
	    	return 0;
1876
	    }
1877
	    default:
1878
		return DefSubclassProc(hwnd, uMsg, wParam, lParam);
1879 1880
	    }
	    return 0;
1881 1882
            }

1883
	case WM_SETFOCUS:
1884
	    /* remember the focus to set state of icon */
1885
	    lret = DefSubclassProc(hwnd, uMsg, wParam, lParam);
1886 1887 1888
	    infoPtr->flags |= WCBE_EDITFOCUSED;
	    return lret;

1889
	case WM_KILLFOCUS:
1890
	    /*
1891
	     * do NOTIFY CBEN_ENDEDIT with CBENF_KILLFOCUS
1892
	     */
1893
	    infoPtr->flags &= ~WCBE_EDITFOCUSED;
1894 1895
	    if (infoPtr->flags & WCBE_ACTEDIT) {
		infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1896 1897

		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
1898 1899 1900 1901 1902
		cbeend.fChanged = FALSE;
		cbeend.iNewSelection = SendMessageW (infoPtr->hwndCombo,
						     CB_GETCURSEL, 0, 0);
		cbeend.iWhy = CBENF_KILLFOCUS;

Guy L. Albertelli's avatar
Guy L. Albertelli committed
1903
		COMBOEX_NotifyEndEdit (infoPtr, &cbeend, edit_text);
1904 1905
	    }
	    /* fall through */
1906

1907
	default:
1908
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
1909 1910 1911 1912
    }
}


1913 1914 1915
static LRESULT CALLBACK
COMBOEX_ComboWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                      UINT_PTR uId, DWORD_PTR ref_data)
1916
{
1917
    COMBOEX_INFO *infoPtr = COMBOEX_GetInfoPtr ((HWND)ref_data);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1918
    NMCBEENDEDITW cbeend;
1919
    NMMOUSE nmmse;
1920
    COLORREF obkc;
1921
    HDC hDC;
1922
    HWND focusedhwnd;
1923
    RECT rect;
1924
    POINT pt;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1925
    WCHAR edit_text[260];
1926

1927
    TRACE("hwnd=%p msg=%x wparam=%lx lParam=%lx, info_ptr=%p\n",
1928 1929 1930 1931 1932 1933
	  hwnd, uMsg, wParam, lParam, infoPtr);

    if (!infoPtr) return 0;

    switch (uMsg)
    {
1934 1935 1936 1937 1938 1939 1940 1941

    case WM_DRAWITEM:
	    /*
	     * The only way this message should come is from the
	     * child Listbox issuing the message. Flag this so
	     * that ComboEx knows this is listbox.
	     */
	    ((DRAWITEMSTRUCT *)lParam)->itemState |= ODS_COMBOEXLBOX;
1942
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
1943 1944 1945

    case WM_ERASEBKGND:
	    /*
1946
	     * The following was determined by traces of the native
1947 1948
	     */
            hDC = (HDC) wParam;
1949
	    obkc = SetBkColor (hDC, comctl32_color.clrWindow);
1950
            GetClientRect (hwnd, &rect);
1951
            TRACE("erasing (%s)\n", wine_dbgstr_rect(&rect));
1952 1953
	    ExtTextOutW (hDC, 0, 0, ETO_OPAQUE, &rect, 0, 0, 0);
            SetBkColor (hDC, obkc);
1954
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966

    case WM_SETCURSOR:
	    /*
	     *  WM_NOTIFY to comboex parent (rebar)
	     *   with NM_SETCURSOR with extra words of 0,0,0,0,0x02010001
	     *  CallWindowProc (previous)
	     */
	    nmmse.dwItemSpec = 0;
	    nmmse.dwItemData = 0;
	    nmmse.pt.x = 0;
	    nmmse.pt.y = 0;
	    nmmse.dwHitInfo = lParam;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1967
	    COMBOEX_Notify (infoPtr, NM_SETCURSOR, (NMHDR *)&nmmse);
1968
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
1969

1970 1971 1972 1973 1974
    case WM_LBUTTONDOWN:
	    GetClientRect (hwnd, &rect);
	    rect.bottom = rect.top + SendMessageW(infoPtr->hwndSelf,
			                          CB_GETITEMHEIGHT, -1, 0);
	    rect.left = rect.right - GetSystemMetrics(SM_CXVSCROLL);
1975 1976
	    pt.x = (short)LOWORD(lParam);
	    pt.y = (short)HIWORD(lParam);
1977
	    if (PtInRect(&rect, pt))
1978 1979
		return DefSubclassProc(hwnd, uMsg, wParam, lParam);

1980 1981 1982 1983 1984 1985
	    infoPtr->flags |= WCBE_MOUSECAPTURED;
	    SetCapture(hwnd);
	    break;

    case WM_LBUTTONUP:
	    if (!(infoPtr->flags & WCBE_MOUSECAPTURED))
1986 1987
		return DefSubclassProc(hwnd, uMsg, wParam, lParam);

1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003
	    ReleaseCapture();
	    infoPtr->flags &= ~WCBE_MOUSECAPTURED;
	    if (infoPtr->flags & WCBE_MOUSEDRAGGED) {
		infoPtr->flags &= ~WCBE_MOUSEDRAGGED;
	    } else {
		SendMessageW(hwnd, CB_SHOWDROPDOWN, TRUE, 0);
	    }
	    break;

    case WM_MOUSEMOVE:
	    if ( (infoPtr->flags & WCBE_MOUSECAPTURED) &&
		!(infoPtr->flags & WCBE_MOUSEDRAGGED)) {
		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
		COMBOEX_NotifyDragBegin(infoPtr, edit_text);
		infoPtr->flags |= WCBE_MOUSEDRAGGED;
	    }
2004
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
2005

2006
    case WM_COMMAND:
2007
	    switch (HIWORD(wParam)) {
2008

2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019
	    case EN_UPDATE:
		/* traces show that COMBOEX does not issue CBN_EDITUPDATE
		 * on the EN_UPDATE
		 */
		return 0;

	    case EN_KILLFOCUS:
		/*
		 * Native does:
		 *
		 *  GetFocus() retns AA
2020
		 *  GetWindowTextW(Edit)
2021 2022 2023 2024 2025 2026 2027 2028 2029
		 *  CB_GETCURSEL(Combo) (got -1)
		 *  WM_NOTIFY(CBEN_ENDEDITA) with CBENF_KILLFOCUS
		 *  CB_GETCURSEL(Combo) (got -1)
		 *  InvalidateRect(Combo, 0, 0)
		 *  WM_KILLFOCUS(Combo, AA)
		 *  return 0;
		 */
		focusedhwnd = GetFocus();
		if (infoPtr->flags & WCBE_ACTEDIT) {
Guy L. Albertelli's avatar
Guy L. Albertelli committed
2030
		    GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
2031 2032 2033 2034 2035 2036
		    cbeend.fChanged = (infoPtr->flags & WCBE_EDITCHG);
		    cbeend.iNewSelection = SendMessageW (infoPtr->hwndCombo,
							 CB_GETCURSEL, 0, 0);
		    cbeend.iWhy = CBENF_KILLFOCUS;

		    infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);
2037
		    if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, edit_text)) return 0;
2038 2039 2040 2041 2042
		}
		/* possible CB_GETCURSEL */
		InvalidateRect (infoPtr->hwndCombo, 0, 0);
		if (focusedhwnd)
		    SendMessageW (infoPtr->hwndCombo, WM_KILLFOCUS,
2043
				  (WPARAM)focusedhwnd, 0);
2044 2045 2046
		return 0;

	    case EN_SETFOCUS: {
2047
		/*
2048 2049
		 * For EN_SETFOCUS this issues the same calls and messages
		 *  as the native seems to do.
2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060
		 *
		 * for some cases however native does the following:
		 *   (noticed after SetFocus during LBUTTONDOWN on
		 *    on dropdown arrow)
		 *  WM_GETTEXTLENGTH (Edit);
		 *  WM_GETTEXT (Edit, len+1, str);
		 *  EM_SETSEL (Edit, 0, 0);
		 *  WM_GETTEXTLENGTH (Edit);
		 *  WM_GETTEXT (Edit, len+1, str);
		 *  EM_SETSEL (Edit, 0, len);
		 *  WM_NOTIFY (parent, CBEN_BEGINEDIT)
2061 2062 2063
		 */
		NMHDR hdr;

2064 2065
		SendMessageW (infoPtr->hwndEdit, EM_SETSEL, 0, 0);
		SendMessageW (infoPtr->hwndEdit, EM_SETSEL, 0, -1);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
2066
		COMBOEX_Notify (infoPtr, CBEN_BEGINEDIT, &hdr);
2067
		infoPtr->flags |= WCBE_ACTEDIT;
2068
		infoPtr->flags &= ~WCBE_EDITCHG; /* no change yet */
2069
		return 0;
2070
	        }
2071

2072
	    case EN_CHANGE: {
2073
		/*
2074 2075 2076
		 * For EN_CHANGE this issues the same calls and messages
		 *  as the native seems to do.
		 */
2077
		LPCWSTR lastwrk;
2078
                cmp_func_t cmptext = get_cmp_func(infoPtr);
2079

2080 2081
		INT_PTR selected = SendMessageW (infoPtr->hwndCombo,
                                                 CB_GETCURSEL, 0, 0);
2082

2083
		/* lstrlenW( lastworkingURL ) */
2084

2085 2086 2087 2088
		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
		if (selected == -1) {
		    lastwrk = infoPtr->edit->pszText;
		}
2089
		else {
2090
		    CBE_ITEMDATA *item = COMBOEX_FindItem (infoPtr, selected);
2091
		    lastwrk = COMBOEX_GetText(infoPtr, item);
2092 2093
		}

2094
		TRACE("handling EN_CHANGE, selected = %ld, selected_text=%s\n",
2095
		      selected, debugstr_w(lastwrk));
2096 2097
		TRACE("handling EN_CHANGE, edittext=%s\n",
		      debugstr_w(edit_text));
2098

2099 2100
		/* cmptext is between lastworkingURL and GetWindowText */
		if (cmptext (lastwrk, edit_text)) {
2101 2102
		    /* strings not equal -- indicate edit has changed */
		    infoPtr->flags |= WCBE_EDITCHG;
2103
		}
2104
		SendMessageW ( infoPtr->hwndNotify, WM_COMMAND,
2105 2106
			       MAKEWPARAM(GetDlgCtrlID (infoPtr->hwndSelf),
					  CBN_EDITCHANGE),
2107
			       (LPARAM)infoPtr->hwndSelf);
2108
		return 0;
2109
	        }
2110

2111 2112 2113 2114
	    case LBN_SELCHANGE:
		/*
		 * Therefore from traces there is no additional code here
		 */
2115

2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145
		/*
		 * Using native COMCTL32 gets the following:
		 *  1 == SHDOCVW.DLL  issues call/message
		 *  2 == COMCTL32.DLL  issues call/message
		 *  3 == WINE  issues call/message
		 *
		 *
		 * for LBN_SELCHANGE:
		 *    1  CB_GETCURSEL(ComboEx)
		 *    1  CB_GETDROPPEDSTATE(ComboEx)
		 *    1  CallWindowProc( *2* for WM_COMMAND(LBN_SELCHANGE)
		 *    2  CallWindowProc( *3* for WM_COMMAND(LBN_SELCHANGE)
		 **   call CBRollUp( xxx, TRUE for LBN_SELCHANGE, TRUE)
		 *    3  WM_COMMAND(ComboEx, CBN_SELENDOK)
		 *      WM_USER+49(ComboLB, 1,0)  <=============!!!!!!!!!!!
		 *    3  ShowWindow(ComboLB, SW_HIDE)
		 *    3  RedrawWindow(Combo,  RDW_UPDATENOW)
		 *    3  WM_COMMAND(ComboEX, CBN_CLOSEUP)
		 **   end of CBRollUp
		 *    3  WM_COMMAND(ComboEx, CBN_SELCHANGE)  (echo to parent)
		 *    ?  LB_GETCURSEL              <==|
		 *    ?  LB_GETTEXTLEN                |
		 *    ?  LB_GETTEXT                   | Needs to be added to
		 *    ?  WM_CTLCOLOREDIT(ComboEx)     | Combo processing
		 *    ?  LB_GETITEMDATA               |
		 *    ?  WM_DRAWITEM(ComboEx)      <==|
		 */
	    default:
		break;
	    }/* fall through */
2146
    default:
2147
	    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
2148
    }
2149 2150 2151 2152
    return 0;
}


2153
static LRESULT WINAPI
2154
COMBOEX_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
2155
{
Guy L. Albertelli's avatar
Guy L. Albertelli committed
2156 2157
    COMBOEX_INFO *infoPtr = COMBOEX_GetInfoPtr (hwnd);

2158
    TRACE("hwnd=%p msg=%x wparam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2159

2160
    if (!infoPtr) {
2161
	if (uMsg == WM_CREATE)
2162
	    return COMBOEX_Create (hwnd, (LPCREATESTRUCTA)lParam);
2163
	if (uMsg == WM_NCCREATE)
2164 2165
	    COMBOEX_NCCreate (hwnd);
        return DefWindowProcW (hwnd, uMsg, wParam, lParam);
2166
    }
2167

Alexandre Julliard's avatar
Alexandre Julliard committed
2168 2169
    switch (uMsg)
    {
2170 2171
        case CBEM_DELETEITEM:
	    return COMBOEX_DeleteItem (infoPtr, wParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2172 2173

	case CBEM_GETCOMBOCONTROL:
2174
	    return (LRESULT)infoPtr->hwndCombo;
Alexandre Julliard's avatar
Alexandre Julliard committed
2175 2176

	case CBEM_GETEDITCONTROL:
2177
	    return (LRESULT)infoPtr->hwndEdit;
Alexandre Julliard's avatar
Alexandre Julliard committed
2178 2179

	case CBEM_GETEXTENDEDSTYLE:
2180
	    return infoPtr->dwExtStyle;
Alexandre Julliard's avatar
Alexandre Julliard committed
2181 2182

	case CBEM_GETIMAGELIST:
2183
	    return (LRESULT)infoPtr->himl;
Alexandre Julliard's avatar
Alexandre Julliard committed
2184

2185
	case CBEM_GETITEMA:
2186
	    return (LRESULT)COMBOEX_GetItemA (infoPtr, (COMBOBOXEXITEMA *)lParam);
2187

2188
	case CBEM_GETITEMW:
2189
	    return (LRESULT)COMBOEX_GetItemW (infoPtr, (COMBOBOXEXITEMW *)lParam);
2190

2191
	case CBEM_GETUNICODEFORMAT:
2192
	    return infoPtr->unicode;
Alexandre Julliard's avatar
Alexandre Julliard committed
2193

2194
	case CBEM_HASEDITCHANGED:
2195
	    return COMBOEX_HasEditChanged (infoPtr);
2196

2197
	case CBEM_INSERTITEMA:
2198
	    return COMBOEX_InsertItemA (infoPtr, (COMBOBOXEXITEMA *)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2199

2200
	case CBEM_INSERTITEMW:
2201
	    return COMBOEX_InsertItemW (infoPtr, (COMBOBOXEXITEMW *)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2202

2203
	case CBEM_SETEXSTYLE:
Alexandre Julliard's avatar
Alexandre Julliard committed
2204
	case CBEM_SETEXTENDEDSTYLE:
2205
	    return COMBOEX_SetExtendedStyle (infoPtr, (DWORD)wParam, (DWORD)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2206 2207

	case CBEM_SETIMAGELIST:
2208
	    return (LRESULT)COMBOEX_SetImageList (infoPtr, (HIMAGELIST)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2209

2210
	case CBEM_SETITEMA:
2211
	    return COMBOEX_SetItemA (infoPtr, (COMBOBOXEXITEMA *)lParam);
2212

2213
	case CBEM_SETITEMW:
2214
	    return COMBOEX_SetItemW (infoPtr, (COMBOBOXEXITEMW *)lParam);
2215

2216
	case CBEM_SETUNICODEFORMAT:
2217
	    return COMBOEX_SetUnicodeFormat (infoPtr, wParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2218

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2219 2220
	/*case CBEM_SETWINDOWTHEME:
	    FIXME("CBEM_SETWINDOWTHEME: stub\n");*/
2221

2222 2223
	case WM_SETTEXT:
	case WM_GETTEXT:
2224
	case WM_GETTEXTLENGTH:
2225 2226
            return SendMessageW(infoPtr->hwndEdit, uMsg, wParam, lParam);

2227 2228 2229 2230 2231 2232
	case CB_GETLBTEXT:
            return COMBOEX_GetListboxText(infoPtr, wParam, (LPWSTR)lParam);

	case CB_GETLBTEXTLEN:
            return COMBOEX_GetListboxText(infoPtr, wParam, NULL);

2233 2234 2235 2236
	case CB_RESETCONTENT:
            COMBOEX_ResetContent(infoPtr);
            /* fall through */

2237
/*   Combo messages we are not sure if we need to process or just forward */
2238 2239 2240 2241 2242
	case CB_GETDROPPEDCONTROLRECT:
	case CB_GETITEMHEIGHT:
	case CB_GETEXTENDEDUI:
	case CB_LIMITTEXT:
	case CB_SELECTSTRING:
2243 2244

/*   Combo messages OK to just forward to the regular COMBO */
2245
	case CB_GETCOUNT:
2246
	case CB_GETCURSEL:
2247
	case CB_GETDROPPEDSTATE:
2248 2249 2250
        case CB_SETDROPPEDWIDTH:
        case CB_SETEXTENDEDUI:
        case CB_SHOWDROPDOWN:
2251
	    return SendMessageW (infoPtr->hwndCombo, uMsg, wParam, lParam);
2252

2253 2254
/*   Combo messages we need to process specially */
        case CB_FINDSTRINGEXACT:
2255
	    return COMBOEX_FindStringExact (infoPtr, (INT)wParam, (LPCWSTR)lParam);
2256

2257
	case CB_GETITEMDATA:
2258
	    return COMBOEX_GetItemData (infoPtr, (INT)wParam);
2259

2260
	case CB_SETCURSEL:
2261
	    return COMBOEX_SetCursel (infoPtr, (INT)wParam);
2262

2263
	case CB_SETITEMDATA:
Frank Richter's avatar
Frank Richter committed
2264
	    return COMBOEX_SetItemData (infoPtr, (INT)wParam, (DWORD_PTR)lParam);
2265

2266
	case CB_SETITEMHEIGHT:
2267
	    return COMBOEX_SetItemHeight (infoPtr, (INT)wParam, (UINT)lParam);
2268

Alexandre Julliard's avatar
Alexandre Julliard committed
2269

2270 2271

/*   Window messages passed to parent */
2272
	case WM_COMMAND:
2273
	    return COMBOEX_Command (infoPtr, wParam);
2274

2275
	case WM_NOTIFY:
Guy L. Albertelli's avatar
Guy L. Albertelli committed
2276
	    if (infoPtr->NtfUnicode)
2277
		return SendMessageW (infoPtr->hwndNotify, uMsg, wParam, lParam);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
2278
	    else
2279
		return SendMessageA (infoPtr->hwndNotify, uMsg, wParam, lParam);
2280 2281


2282 2283
/*   Window messages we need to process */
        case WM_DELETEITEM:
2284
	    return COMBOEX_WM_DeleteItem (infoPtr, (DELETEITEMSTRUCT *)lParam);
2285

2286
        case WM_DRAWITEM:
2287
            return COMBOEX_DrawItem (infoPtr, (DRAWITEMSTRUCT *)lParam);
2288

Alexandre Julliard's avatar
Alexandre Julliard committed
2289
	case WM_DESTROY:
2290
	    return COMBOEX_Destroy (infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
2291

2292 2293 2294
	case WM_ENABLE:
	    return COMBOEX_Enable (infoPtr, (BOOL)wParam);

2295
        case WM_MEASUREITEM:
2296
            return COMBOEX_MeasureItem (infoPtr, (MEASUREITEMSTRUCT *)lParam);
2297

Guy L. Albertelli's avatar
Guy L. Albertelli committed
2298
        case WM_NOTIFYFORMAT:
2299
	    return COMBOEX_NotifyFormat (infoPtr, lParam);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
2300

Alexandre Julliard's avatar
Alexandre Julliard committed
2301
	case WM_SIZE:
2302
	    return COMBOEX_Size (infoPtr, LOWORD(lParam), HIWORD(lParam));
Alexandre Julliard's avatar
Alexandre Julliard committed
2303

2304 2305 2306
        case WM_SETREDRAW:
            return COMBOEX_SetRedraw(infoPtr, wParam, lParam);

2307
        case WM_WINDOWPOSCHANGING:
2308
	    return COMBOEX_WindowPosChanging (infoPtr, (WINDOWPOS *)lParam);
2309

2310
        case WM_SETFOCUS:
2311 2312
            if (infoPtr->hwndEdit) SetFocus( infoPtr->hwndEdit );
            else SetFocus( infoPtr->hwndCombo );
2313 2314
            return 0;

2315 2316 2317 2318
        case WM_SYSCOLORCHANGE:
            COMCTL32_RefreshSysColors();
            return 0;

Alexandre Julliard's avatar
Alexandre Julliard committed
2319
	default:
2320
	    if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
2321
		ERR("unknown msg %04x wp=%08lx lp=%08lx\n",uMsg,wParam,lParam);
2322
	    return DefWindowProcW (hwnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2323 2324 2325 2326
    }
}


2327
void COMBOEX_Register (void)
Alexandre Julliard's avatar
Alexandre Julliard committed
2328
{
2329
    WNDCLASSW wndClass;
Alexandre Julliard's avatar
Alexandre Julliard committed
2330

2331
    ZeroMemory (&wndClass, sizeof(WNDCLASSW));
Alexandre Julliard's avatar
Alexandre Julliard committed
2332
    wndClass.style         = CS_GLOBALCLASS;
2333
    wndClass.lpfnWndProc   = COMBOEX_WindowProc;
Alexandre Julliard's avatar
Alexandre Julliard committed
2334 2335
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(COMBOEX_INFO *);
2336
    wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2337
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2338
    wndClass.lpszClassName = WC_COMBOBOXEXW;
2339

2340
    RegisterClassW (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
2341 2342
}

2343

2344
void COMBOEX_Unregister (void)
2345
{
2346
    UnregisterClassW (WC_COMBOBOXEXW, NULL);
2347
}