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 70
    HWND         hwndEdit;
    WNDPROC      prevEditWndProc;  /* previous Edit WNDPROC value */
71
    WNDPROC      prevComboWndProc; /* previous Combo WNDPROC value */
72
    DWORD        dwExtStyle;
73
    INT          selected;         /* index of selected item */
74
    DWORD        flags;            /* WINE internal flags */
75
    HFONT        defaultFont;
76 77
    HFONT        font;
    INT          nb_items;         /* Number of items */
78 79
    BOOL         unicode;          /* TRUE if this window is Unicode   */
    BOOL         NtfUnicode;       /* TRUE if parent wants notify in Unicode */
80
    CBE_ITEMDATA *edit;            /* item data for edit item */
81
    CBE_ITEMDATA *items;           /* Array of items */
82
} COMBOEX_INFO;
83

84
/* internal flags in the COMBOEX_INFO structure */
85
#define  WCBE_ACTEDIT		0x00000001  /* Edit active i.e.
86 87 88
                                             * CBEN_BEGINEDIT issued
                                             * but CBEN_ENDEDIT{A|W}
                                             * not yet issued. */
89
#define  WCBE_EDITCHG		0x00000002  /* Edit issued EN_CHANGE */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
90
#define  WCBE_EDITHASCHANGED	(WCBE_ACTEDIT | WCBE_EDITCHG)
91 92 93
#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 */
94

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
95
#define ID_CB_EDIT		1001
Alexandre Julliard's avatar
Alexandre Julliard committed
96

97 98 99

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



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

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

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

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

124 125 126 127 128
static const WCHAR COMBOEX_SUBCLASS_PROP[] = {
    'C','C','C','o','m','b','o','E','x','3','2',
    'S','u','b','c','l','a','s','s','I','n','f','o',0
};

129
#define COMBOEX_GetInfoPtr(hwnd) ((COMBOEX_INFO *)GetWindowLongPtrW (hwnd, 0))
Alexandre Julliard's avatar
Alexandre Julliard committed
130 131


132
/* Things common to the entire DLL */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
133 134
static LRESULT WINAPI COMBOEX_EditWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
static LRESULT WINAPI COMBOEX_ComboWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
135
static LRESULT COMBOEX_Destroy (COMBOEX_INFO *infoPtr);
136
typedef INT (WINAPI *cmp_func_t)(LPCWSTR, LPCWSTR);
137

138
static inline BOOL is_textW(LPCWSTR str)
139 140 141 142
{
    return str && str != LPSTR_TEXTCALLBACKW;
}

143
static inline BOOL is_textA(LPCSTR str)
144 145 146 147
{
    return str && str != LPSTR_TEXTCALLBACKA;
}

148
static inline LPCSTR debugstr_txt(LPCWSTR str)
149 150 151 152 153
{
    if (str == LPSTR_TEXTCALLBACKW) return "(callback)";
    return debugstr_w(str);
}

154
static void COMBOEX_DumpItem (CBE_ITEMDATA const *item)
155
{
156 157 158 159
    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);
160 161
    if (item->mask & CBEIF_TEXT)
        TRACE("item %p - pszText=%s\n", item, debugstr_txt(item->pszText));
162 163 164
}


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


177
static inline CBE_ITEMDATA *get_item_data(COMBOEX_INFO *infoPtr, INT index)
178
{
179
    return (CBE_ITEMDATA *)SendMessageW (infoPtr->hwndCombo, CB_GETITEMDATA,
180
		                         (WPARAM)index, 0);
181 182
}

183
static inline cmp_func_t get_cmp_func(COMBOEX_INFO const *infoPtr)
184 185 186
{
    return infoPtr->dwExtStyle & CBES_EX_CASESENSITIVE ? lstrcmpW : lstrcmpiW;
}
187

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


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

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

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
221 222 223
	if (code == CBEN_ENDEDITW) code = CBEN_ENDEDITA;
	else if (code == CBEN_GETDISPINFOW) code = CBEN_GETDISPINFOA;
	else if (code == CBEN_DRAGBEGINW) code = CBEN_DRAGBEGINA;
224

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

227
	if (astr && hdr->ceItem.pszText == (LPWSTR)astr)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
228
	    hdr->ceItem.pszText = wstr;
229

230
	Free(astr);
231

Guy L. Albertelli's avatar
Guy L. Albertelli committed
232 233 234 235 236
	return ret;
    }
}


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

246 247 248 249 250
        memcpy (&neea.hdr, &neew->hdr, sizeof(NMHDR));
        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
251

252
        return COMBOEX_Notify (infoPtr, CBEN_ENDEDITA, &neea.hdr);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
253 254 255 256
    }
}


257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
static void COMBOEX_NotifyDragBegin(COMBOEX_INFO *infoPtr, LPCWSTR wstr)
{
    /* 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);
    }
}


277 278
static void COMBOEX_FreeText (CBE_ITEMDATA *item)
{
279
    if (is_textW(item->pszText)) Free(item->pszText);
280
    item->pszText = 0;
281
    Free(item->pszTemp);
282 283 284 285
    item->pszTemp = 0;
}


286
static INT COMBOEX_GetIndex(COMBOEX_INFO const *infoPtr, CBE_ITEMDATA const *item)
287
{
288
    CBE_ITEMDATA const *moving;
289
    INT index;
290

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    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;
}


306 307 308 309 310
static LPCWSTR COMBOEX_GetText(COMBOEX_INFO *infoPtr, CBE_ITEMDATA *item)
{
    NMCOMBOBOXEXW nmce;
    LPWSTR text, buf;
    INT len;
311 312

    if (item->pszText != LPSTR_TEXTCALLBACKW)
313
	return item->pszText;
314

315 316
    ZeroMemory(&nmce, sizeof(nmce));
    nmce.ceItem.mask = CBEIF_TEXT;
317
    nmce.ceItem.lParam = item->lParam;
318
    nmce.ceItem.iItem = COMBOEX_GetIndex(infoPtr, item);
319 320 321 322
    COMBOEX_NotifyItem(infoPtr, CBEN_GETDISPINFOW, &nmce);

    if (is_textW(nmce.ceItem.pszText)) {
	len = MultiByteToWideChar (CP_ACP, 0, (LPSTR)nmce.ceItem.pszText, -1, NULL, 0);
323
	buf = (LPWSTR)Alloc ((len + 1)*sizeof(WCHAR));
324 325 326 327 328 329
	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 {
330
	    Free(item->pszTemp);
331 332 333 334 335
	    item->pszTemp = buf;
	}
	text = buf;
    } else
	text = nmce.ceItem.pszText;
336

337 338 339 340 341 342
    if (nmce.ceItem.mask & CBEIF_DI_SETITEM)
	item->pszText = text;
    return text;
}


343
static void COMBOEX_GetComboFontSize (COMBOEX_INFO *infoPtr, SIZE *size)
344
{
345
    static const WCHAR strA[] = { 'A', 0 };
346 347 348 349
    HFONT nfont, ofont;
    HDC mydc;

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


359
static void COMBOEX_CopyItem (CBE_ITEMDATA *item, COMBOBOXEXITEMW *cit)
360 361
{
    if (cit->mask & CBEIF_TEXT) {
362 363 364
        /*
         * when given a text buffer actually use that buffer
         */
365 366 367 368 369 370
        if (cit->pszText) {
	    if (is_textW(item->pszText))
                lstrcpynW(cit->pszText, item->pszText, cit->cchTextMax);
	    else
		cit->pszText[0] = 0;
        } else {
371 372 373
            cit->pszText        = item->pszText;
            cit->cchTextMax     = item->cchTextMax;
        }
374 375 376 377 378 379 380 381 382 383 384 385 386 387
    }
    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;
}


388
static void COMBOEX_AdjustEditPos (COMBOEX_INFO *infoPtr)
389 390
{
    SIZE mysize;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
391
    INT x, y, w, h, xioff;
392 393 394
    RECT rect;

    if (!infoPtr->hwndEdit) return;
395

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

403 404 405 406 407 408
    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);
409
    TRACE("Combo font x=%d, y=%d\n", mysize.cx, mysize.cy);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
410
    x = xioff + CBE_STARTOFFSET + 1;
411 412
    w = rect.right-rect.left - x - GetSystemMetrics(SM_CXVSCROLL) - 1;
    h = mysize.cy + 1;
413
    y = rect.bottom - h - 1;
414

415
    TRACE("Combo client (%d,%d)-(%d,%d), setting Edit to (%d,%d)-(%d,%d)\n",
416 417
	  rect.left, rect.top, rect.right, rect.bottom, x, y, x + w, y + h);
    SetWindowPos(infoPtr->hwndEdit, HWND_TOP, x, y, w, h,
418 419 420 421
		 SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOZORDER);
}


422
static void COMBOEX_ReSize (COMBOEX_INFO *infoPtr)
423 424
{
    SIZE mysize;
425
    LONG cy;
426 427 428
    IMAGEINFO iinfo;

    COMBOEX_GetComboFontSize (infoPtr, &mysize);
429
    cy = mysize.cy + CBE_EXTRA;
430
    if (infoPtr->himl && ImageList_GetImageInfo(infoPtr->himl, 0, &iinfo)) {
431
	cy = max (iinfo.rcImage.bottom - iinfo.rcImage.top, cy);
432
	TRACE("upgraded height due to image:  height=%d\n", cy);
433
    }
434
    SendMessageW (infoPtr->hwndSelf, CB_SETITEMHEIGHT, (WPARAM)-1, (LPARAM)cy);
435
    if (infoPtr->hwndCombo) {
436
        SendMessageW (infoPtr->hwndCombo, CB_SETITEMHEIGHT,
437
		      (WPARAM) 0, (LPARAM) cy);
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
	if ( !(infoPtr->flags & CBES_EX_NOSIZELIMIT)) {
	    RECT comboRect;
	    if (GetWindowRect(infoPtr->hwndCombo, &comboRect)) {
		RECT ourRect;
		if (GetWindowRect(infoPtr->hwndSelf, &ourRect)) {
		    if (comboRect.bottom > ourRect.bottom) {
			POINT pt = { ourRect.left, ourRect.top };
			if (ScreenToClient(infoPtr->hwndSelf, &pt))
			    MoveWindow( infoPtr->hwndSelf, pt.x, pt.y, ourRect.right - ourRect.left,
					comboRect.bottom - comboRect.top, FALSE);
		    }
		}
	    }
	}
    }
453 454 455
}


456
static void COMBOEX_SetEditText (COMBOEX_INFO *infoPtr, CBE_ITEMDATA *item)
457 458 459 460 461 462 463
{
    if (!infoPtr->hwndEdit) return;
    /* native issues the following messages to the {Edit} control */
    /*      WM_SETTEXT (0,addr)     */
    /*      EM_SETSEL32 (0,0)       */
    /*      EM_SETSEL32 (0,-1)      */
    if (item->mask & CBEIF_TEXT) {
464
	SendMessageW (infoPtr->hwndEdit, WM_SETTEXT, 0, (LPARAM)COMBOEX_GetText(infoPtr, item));
465 466
	SendMessageW (infoPtr->hwndEdit, EM_SETSEL, 0, 0);
	SendMessageW (infoPtr->hwndEdit, EM_SETSEL, 0, -1);
467 468 469
    }
}

470

471
static CBE_ITEMDATA * COMBOEX_FindItem(COMBOEX_INFO *infoPtr, INT index)
472 473 474 475
{
    CBE_ITEMDATA *item;
    INT i;

476
    if ((index >= infoPtr->nb_items) || (index < -1))
477 478 479 480 481 482 483 484
	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)) {
485
	item = item->next;
486 487 488
	i--;
    }
    if (!item || (i != index)) {
489
	ERR("COMBOBOXEX item structures broken. Please report!\n");
490 491 492 493 494 495
	return 0;
    }
    return item;
}


496
static inline BOOL COMBOEX_HasEdit(COMBOEX_INFO const *infoPtr)
497
{
498
    return infoPtr->hwndEdit ? TRUE : FALSE;
499
}
500

501

502 503
/* ***  CBEM_xxx message support  *** */

504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
static UINT COMBOEX_GetListboxText(COMBOEX_INFO *infoPtr, int n, LPWSTR buf)
{
    CBE_ITEMDATA *item;
    LPCWSTR str;

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

    str = COMBOEX_GetText(infoPtr, item);

    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;
    }
}

530

531
static INT COMBOEX_DeleteItem (COMBOEX_INFO *infoPtr, INT index)
532
{
533
    CBE_ITEMDATA const *item;
534

535
    TRACE("(index=%d)\n", index);
536 537

    /* if item number requested does not exist then return failure */
538
    if ((index >= infoPtr->nb_items) || (index < 0)) return CB_ERR;
539
    if (!(item = COMBOEX_FindItem(infoPtr, index))) return CB_ERR;
540 541 542 543 544 545

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

    return infoPtr->nb_items;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
546 547


548
static BOOL COMBOEX_GetItemW (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMW *cit)
Alexandre Julliard's avatar
Alexandre Julliard committed
549
{
550
    INT index = cit->iItem;
551 552
    CBE_ITEMDATA *item;

553
    TRACE("(...)\n");
554 555

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

    /* if the item is the edit control and there is no edit control, skip */
559
    if ((index == -1) && !COMBOEX_HasEdit(infoPtr)) return FALSE;
560

561
    if (!(item = COMBOEX_FindItem(infoPtr, index))) return FALSE;
562

563
    COMBOEX_CopyItem (item, cit);
564

565 566
    return TRUE;
}
567

Alexandre Julliard's avatar
Alexandre Julliard committed
568

569
static BOOL COMBOEX_GetItemA (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMA *cit)
570 571 572
{
    COMBOBOXEXITEMW tmpcit;

573
    TRACE("(...)\n");
574 575 576

    tmpcit.mask = cit->mask;
    tmpcit.iItem = cit->iItem;
577
    tmpcit.pszText = 0;
578
    if(!COMBOEX_GetItemW (infoPtr, &tmpcit)) return FALSE;
579

580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
    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;
599 600 601 602 603

    return TRUE;
}


604
static inline BOOL COMBOEX_HasEditChanged (COMBOEX_INFO const *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
605
{
606 607
    return COMBOEX_HasEdit(infoPtr) &&
	   (infoPtr->flags & WCBE_EDITHASCHANGED) == WCBE_EDITHASCHANGED;
Alexandre Julliard's avatar
Alexandre Julliard committed
608 609 610
}


611
static INT COMBOEX_InsertItemW (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMW const *cit)
612 613 614
{
    INT index;
    CBE_ITEMDATA *item;
615 616 617
    NMCOMBOBOXEXW nmcit;

    TRACE("\n");
618

619
    if (TRACE_ON(comboex)) COMBOEX_DumpInput (cit);
620

621 622 623
    /* get real index of item to insert */
    index = cit->iItem;
    if (index == -1) index = infoPtr->nb_items;
624
    if (index > infoPtr->nb_items) return -1;
625

626
    /* get zero-filled space and chain it in */
627
    if(!(item = (CBE_ITEMDATA *)Alloc (sizeof(*item)))) return -1;
628

629 630 631 632 633 634 635 636 637 638 639
    /* 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) {
640
	    moving = moving->next;
641 642 643
	    i--;
	}
	if (!moving) {
644
	    ERR("COMBOBOXEX item structures broken. Please report!\n");
645
	    Free(item);
646 647 648 649 650 651 652
	    return -1;
	}
	item->next = moving->next;
	moving->next = item;
    }

    /* fill in our hidden item structure */
653
    item->mask = cit->mask;
654
    if (item->mask & CBEIF_TEXT) {
655
	INT len = 0;
656

657
        if (is_textW(cit->pszText)) len = strlenW (cit->pszText);
658
	if (len > 0) {
659
	    item->pszText = (LPWSTR)Alloc ((len + 1)*sizeof(WCHAR));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
660
	    if (!item->pszText) {
661
		Free(item);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
662 663
		return -1;
	    }
664
	    strcpyW (item->pszText, cit->pszText);
665
	}
666 667 668
	else if (cit->pszText == LPSTR_TEXTCALLBACKW)
	    item->pszText = LPSTR_TEXTCALLBACKW;
        item->cchTextMax = cit->cchTextMax;
669 670
    }
    if (item->mask & CBEIF_IMAGE)
671
        item->iImage = cit->iImage;
672
    if (item->mask & CBEIF_SELECTEDIMAGE)
673
        item->iSelectedImage = cit->iSelectedImage;
674
    if (item->mask & CBEIF_OVERLAY)
675
        item->iOverlay = cit->iOverlay;
676
    if (item->mask & CBEIF_INDENT)
677
        item->iIndent = cit->iIndent;
678
    if (item->mask & CBEIF_LPARAM)
679
        item->lParam = cit->lParam;
680 681
    infoPtr->nb_items++;

682
    if (TRACE_ON(comboex)) COMBOEX_DumpItem (item);
683

684
    SendMessageW (infoPtr->hwndCombo, CB_INSERTSTRING,
685 686
		  (WPARAM)cit->iItem, (LPARAM)item);

687
    memset (&nmcit.ceItem, 0, sizeof(nmcit.ceItem));
688
    COMBOEX_CopyItem (item, &nmcit.ceItem);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
689
    COMBOEX_NotifyItem (infoPtr, CBEN_INSERTITEM, &nmcit);
690

691 692 693 694
    return index;

}

Alexandre Julliard's avatar
Alexandre Julliard committed
695

696
static INT COMBOEX_InsertItemA (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMA const *cit)
697
{
698
    COMBOBOXEXITEMW citW;
699
    LPWSTR wstr = NULL;
700
    INT	ret;
701 702

    memcpy(&citW,cit,sizeof(COMBOBOXEXITEMA));
703 704
    if (cit->mask & CBEIF_TEXT && is_textA(cit->pszText)) {
	INT len = MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, NULL, 0);
705
	wstr = (LPWSTR)Alloc ((len + 1)*sizeof(WCHAR));
706 707 708
	if (!wstr) return -1;
	MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, wstr, len);
	citW.pszText = wstr;
709
    }
710 711
    ret = COMBOEX_InsertItemW(infoPtr, &citW);

712
    Free(wstr);
713

714 715 716 717
    return ret;
}


718
static DWORD
719
COMBOEX_SetExtendedStyle (COMBOEX_INFO *infoPtr, DWORD mask, DWORD style)
Alexandre Julliard's avatar
Alexandre Julliard committed
720 721 722
{
    DWORD dwTemp;

723
    TRACE("(mask=x%08x, style=0x%08x)\n", mask, style);
Alexandre Julliard's avatar
Alexandre Julliard committed
724 725 726

    dwTemp = infoPtr->dwExtStyle;

727 728
    if (mask)
	infoPtr->dwExtStyle = (infoPtr->dwExtStyle & ~mask) | style;
Alexandre Julliard's avatar
Alexandre Julliard committed
729
    else
730
	infoPtr->dwExtStyle = style;
Alexandre Julliard's avatar
Alexandre Julliard committed
731

732
    /* see if we need to change the word break proc on the edit */
733 734 735
    if ((infoPtr->dwExtStyle ^ dwTemp) & CBES_EX_PATHWORDBREAKPROC)
        SetPathWordBreakProc(infoPtr->hwndEdit, 
            (infoPtr->dwExtStyle & CBES_EX_PATHWORDBREAKPROC) ? TRUE : FALSE);
736

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
737 738 739
    /* test if the control's appearance has changed */
    mask = CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT;
    if ((infoPtr->dwExtStyle & mask) != (dwTemp & mask)) {
740
	/* if state of EX_NOEDITIMAGE changes, invalidate all */
741
	TRACE("EX_NOEDITIMAGE state changed to %d\n",
742 743
	      infoPtr->dwExtStyle & CBES_EX_NOEDITIMAGE);
	InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
744 745 746 747
	COMBOEX_AdjustEditPos (infoPtr);
	if (infoPtr->hwndEdit)
	    InvalidateRect (infoPtr->hwndEdit, NULL, TRUE);
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
748

749
    return dwTemp;
Alexandre Julliard's avatar
Alexandre Julliard committed
750 751 752
}


753
static HIMAGELIST COMBOEX_SetImageList (COMBOEX_INFO *infoPtr, HIMAGELIST himl)
Alexandre Julliard's avatar
Alexandre Julliard committed
754
{
755
    HIMAGELIST himlTemp = infoPtr->himl;
Alexandre Julliard's avatar
Alexandre Julliard committed
756

757
    TRACE("(...)\n");
Alexandre Julliard's avatar
Alexandre Julliard committed
758

759
    infoPtr->himl = himl;
Alexandre Julliard's avatar
Alexandre Julliard committed
760

761
    COMBOEX_ReSize (infoPtr);
762
    InvalidateRect (infoPtr->hwndCombo, NULL, TRUE);
763

764 765
    /* reposition the Edit control based on whether icon exists */
    COMBOEX_AdjustEditPos (infoPtr);
766
    return himlTemp;
Alexandre Julliard's avatar
Alexandre Julliard committed
767 768
}

769
static BOOL COMBOEX_SetItemW (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMW *cit)
770
{
771
    INT index = cit->iItem;
772 773
    CBE_ITEMDATA *item;

774
    if (TRACE_ON(comboex)) COMBOEX_DumpInput (cit);
775 776

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

779
    /* if the item is the edit control and there is no edit control, skip */
780
    if ((index == -1) && !COMBOEX_HasEdit(infoPtr)) return FALSE;
781

782
    if (!(item = COMBOEX_FindItem(infoPtr, index))) return FALSE;
783

784
    /* add/change stuff to the internal item structure */
785 786
    item->mask |= cit->mask;
    if (cit->mask & CBEIF_TEXT) {
787
	INT len = 0;
788

789 790
	COMBOEX_FreeText(item);
        if (is_textW(cit->pszText)) len = strlenW(cit->pszText);
791
	if (len > 0) {
792
	    item->pszText = (LPWSTR)Alloc ((len + 1)*sizeof(WCHAR));
793
	    if (!item->pszText) return FALSE;
794 795 796 797
	    strcpyW(item->pszText, cit->pszText);
	} else if (cit->pszText == LPSTR_TEXTCALLBACKW)
	    item->pszText = LPSTR_TEXTCALLBACKW;
        item->cchTextMax = cit->cchTextMax;
798 799
    }
    if (cit->mask & CBEIF_IMAGE)
800
        item->iImage = cit->iImage;
801
    if (cit->mask & CBEIF_SELECTEDIMAGE)
802
        item->iSelectedImage = cit->iSelectedImage;
803
    if (cit->mask & CBEIF_OVERLAY)
804
        item->iOverlay = cit->iOverlay;
805
    if (cit->mask & CBEIF_INDENT)
806
        item->iIndent = cit->iIndent;
807
    if (cit->mask & CBEIF_LPARAM)
808
        cit->lParam = cit->lParam;
809

810
    if (TRACE_ON(comboex)) COMBOEX_DumpItem (item);
811

812 813 814 815 816
    /* if original request was to update edit control, do some fast foot work */
    if (cit->iItem == -1) {
	COMBOEX_SetEditText (infoPtr, item);
	RedrawWindow (infoPtr->hwndCombo, 0, 0, RDW_ERASE | RDW_INVALIDATE);
    }
817 818 819
    return TRUE;
}

820
static BOOL COMBOEX_SetItemA (COMBOEX_INFO *infoPtr, COMBOBOXEXITEMA const *cit)
821
{
822 823 824
    COMBOBOXEXITEMW citW;
    LPWSTR wstr = NULL;
    BOOL ret;
825

826
    memcpy(&citW, cit, sizeof(COMBOBOXEXITEMA));
827 828
    if ((cit->mask & CBEIF_TEXT) && is_textA(cit->pszText)) {
	INT len = MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, NULL, 0);
829
	wstr = (LPWSTR)Alloc ((len + 1)*sizeof(WCHAR));
830 831 832
	if (!wstr) return FALSE;
	MultiByteToWideChar (CP_ACP, 0, cit->pszText, -1, wstr, len);
	citW.pszText = wstr;
833
    }
834
    ret = COMBOEX_SetItemW(infoPtr, &citW);
835

836
    Free(wstr);
837 838 839

    return ret;
}
840 841


842
static BOOL COMBOEX_SetUnicodeFormat (COMBOEX_INFO *infoPtr, BOOL value)
843
{
844
    BOOL bTemp = infoPtr->unicode;
845

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

848
    infoPtr->unicode = value;
849 850 851 852

    return bTemp;
}

853

854 855
/* ***  CB_xxx message support  *** */

856
static INT
857
COMBOEX_FindStringExact (COMBOEX_INFO *infoPtr, INT start, LPCWSTR str)
858
{
859 860 861
    INT i;
    cmp_func_t cmptext = get_cmp_func(infoPtr);
    INT count = SendMessageW (infoPtr->hwndCombo, CB_GETCOUNT, 0, 0);
862 863 864

    /* now search from after starting loc and wrapping back to start */
    for(i=start+1; i<count; i++) {
865 866
	CBE_ITEMDATA *item = get_item_data(infoPtr, i);
	if (cmptext(COMBOEX_GetText(infoPtr, item), str) == 0) return i;
867 868
    }
    for(i=0; i<=start; i++) {
869 870
	CBE_ITEMDATA *item = get_item_data(infoPtr, i);
	if (cmptext(COMBOEX_GetText(infoPtr, item), str) == 0) return i;
871 872 873 874 875
    }
    return CB_ERR;
}


Frank Richter's avatar
Frank Richter committed
876
static DWORD_PTR COMBOEX_GetItemData (COMBOEX_INFO *infoPtr, INT index)
877
{
878 879
    CBE_ITEMDATA const *item1;
    CBE_ITEMDATA const *item2;
Frank Richter's avatar
Frank Richter committed
880
    DWORD_PTR ret = 0;
881

882
    item1 = get_item_data(infoPtr, index);
883 884 885 886 887 888
    if ((item1 != NULL) && ((LRESULT)item1 != CB_ERR)) {
	item2 = COMBOEX_FindItem (infoPtr, index);
	if (item2 != item1) {
	    ERR("data structures damaged!\n");
	    return CB_ERR;
	}
889 890 891
	if (item1->mask & CBEIF_LPARAM) ret = item1->lParam;
	TRACE("returning 0x%08lx\n", ret);
    } else {
Frank Richter's avatar
Frank Richter committed
892
        ret = (DWORD_PTR)item1;
893
        TRACE("non-valid result from combo, returning 0x%08lx\n", ret);
894
    }
895
    return ret;
896 897 898
}


899
static INT COMBOEX_SetCursel (COMBOEX_INFO *infoPtr, INT index)
900 901
{
    CBE_ITEMDATA *item;
902
    INT sel;
903

904 905
    if (!(item = COMBOEX_FindItem(infoPtr, index)))
	return SendMessageW (infoPtr->hwndCombo, CB_SETCURSEL, index, 0);
906

907
    TRACE("selecting item %d text=%s\n", index, debugstr_txt(item->pszText));
908
    infoPtr->selected = index;
909

910
    sel = (INT)SendMessageW (infoPtr->hwndCombo, CB_SETCURSEL, index, 0);
911
    COMBOEX_SetEditText (infoPtr, item);
912
    return sel;
913 914 915
}


Frank Richter's avatar
Frank Richter committed
916
static DWORD_PTR COMBOEX_SetItemData (COMBOEX_INFO *infoPtr, INT index, DWORD_PTR data)
917
{
918 919
    CBE_ITEMDATA *item1;
    CBE_ITEMDATA const *item2;
920

921
    item1 = get_item_data(infoPtr, index);
922 923 924 925 926 927 928
    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;
929 930
	item1->lParam = data;
	TRACE("setting lparam to 0x%08lx\n", data);
931 932
	return 0;
    }
Frank Richter's avatar
Frank Richter committed
933 934
    TRACE("non-valid result from combo %p\n", item1);
    return (DWORD_PTR)item1;
935 936 937
}


938
static INT COMBOEX_SetItemHeight (COMBOEX_INFO const *infoPtr, INT index, UINT height)
939
{
940
    RECT cb_wrect, cbx_wrect, cbx_crect;
941

942 943
    /* First, lets forward the message to the normal combo control
       just like Windows.     */
944 945 946
    if (infoPtr->hwndCombo)
       if (SendMessageW (infoPtr->hwndCombo, CB_SETITEMHEIGHT,
			 index, height) == CB_ERR) return CB_ERR;
947 948

    GetWindowRect (infoPtr->hwndCombo, &cb_wrect);
949 950
    GetWindowRect (infoPtr->hwndSelf, &cbx_wrect);
    GetClientRect (infoPtr->hwndSelf, &cbx_crect);
951
    /* the height of comboex as height of the combo + comboex border */
952 953 954
    height = cb_wrect.bottom-cb_wrect.top
             + cbx_wrect.bottom-cbx_wrect.top
             - (cbx_crect.bottom-cbx_crect.top);
955
    TRACE("EX window=(%d,%d)-(%d,%d), client=(%d,%d)-(%d,%d)\n",
956 957
	  cbx_wrect.left, cbx_wrect.top, cbx_wrect.right, cbx_wrect.bottom,
	  cbx_crect.left, cbx_crect.top, cbx_crect.right, cbx_crect.bottom);
958
    TRACE("CB window=(%d,%d)-(%d,%d), EX setting=(0,0)-(%d,%d)\n",
959 960
	  cb_wrect.left, cb_wrect.top, cb_wrect.right, cb_wrect.bottom,
	  cbx_wrect.right-cbx_wrect.left, height);
961 962
    SetWindowPos (infoPtr->hwndSelf, HWND_TOP, 0, 0,
		  cbx_wrect.right-cbx_wrect.left, height,
963 964
		  SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);

965
    return 0;
966
}
Alexandre Julliard's avatar
Alexandre Julliard committed
967 968


969 970 971
/* ***  WM_xxx message support  *** */


972
static LRESULT COMBOEX_Create (HWND hwnd, CREATESTRUCTA const *cs)
Alexandre Julliard's avatar
Alexandre Julliard committed
973
{
974 975 976
    static const WCHAR COMBOBOX[] = { 'C', 'o', 'm', 'b', 'o', 'B', 'o', 'x', 0 };
    static const WCHAR EDIT[] = { 'E', 'D', 'I', 'T', 0 };
    static const WCHAR NIL[] = { 0 };
Alexandre Julliard's avatar
Alexandre Julliard committed
977
    COMBOEX_INFO *infoPtr;
978
    LOGFONTW mylogfont;
979
    RECT wnrc1, clrc1, cmbwrc;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
980
    INT i;
Alexandre Julliard's avatar
Alexandre Julliard committed
981 982

    /* allocate memory for info structure */
983
    infoPtr = (COMBOEX_INFO *)Alloc (sizeof(COMBOEX_INFO));
984
    if (!infoPtr) return -1;
985 986

    /* initialize info structure */
987
    /* note that infoPtr is allocated zero-filled */
988

989
    infoPtr->hwndSelf = hwnd;
990
    infoPtr->selected = -1;
Alexandre Julliard's avatar
Alexandre Julliard committed
991

992
    infoPtr->unicode = IsWindowUnicode (hwnd);
993
    infoPtr->hwndNotify = cs->hwndParent;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
994

995
    i = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)hwnd, NF_QUERY);
996 997
    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
998 999
	i = NFR_ANSI;
    }
1000
    infoPtr->NtfUnicode = (i == NFR_UNICODE);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1001

1002
    SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
1003 1004

    /* create combo box */
1005 1006
    GetWindowRect(hwnd, &wnrc1);
    GetClientRect(hwnd, &clrc1);
1007
    TRACE("EX window=(%d,%d)-(%d,%d) client=(%d,%d)-(%d,%d)\n",
1008 1009
	  wnrc1.left, wnrc1.top, wnrc1.right, wnrc1.bottom,
	  clrc1.left, clrc1.top, clrc1.right, clrc1.bottom);
1010 1011 1012 1013

    /* 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      */
1014
    /* DROPDOWNLIST does not have an EDIT control, but we need one.     */
1015 1016 1017
    /* We also need to place the edit control at the proper location    */
    /* (allow space for the icons).                                     */

1018
    infoPtr->hwndCombo = CreateWindowW (COMBOBOX, NIL,
1019
			 /* following line added to match native */
1020
                         WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VSCROLL |
1021
                         CBS_NOINTEGRALHEIGHT | CBS_DROPDOWNLIST |
1022
			 /* was base and is necessary */
1023 1024
			 WS_CHILD | WS_VISIBLE | CBS_OWNERDRAWFIXED |
			 GetWindowLongW (hwnd, GWL_STYLE),
1025
			 cs->y, cs->x, cs->cx, cs->cy, hwnd,
1026 1027
			 (HMENU) GetWindowLongPtrW (hwnd, GWLP_ID),
			 (HINSTANCE)GetWindowLongPtrW (hwnd, GWLP_HINSTANCE), NULL);
1028

1029
    /*
1030 1031 1032 1033 1034 1035 1036 1037
     * native does the following at this point according to trace:
     *  GetWindowThreadProcessId(hwndCombo,0)
     *  GetCurrentThreadId()
     *  GetWindowThreadProcessId(hwndCombo, &???)
     *  GetCurrentProcessId()
     */

    /*
1038
     * Setup a property to hold the pointer to the COMBOBOXEX
1039 1040
     * data structure.
     */
1041
    SetPropW(infoPtr->hwndCombo, COMBOEX_SUBCLASS_PROP, hwnd);
1042 1043
    infoPtr->prevComboWndProc = (WNDPROC)SetWindowLongPtrW(infoPtr->hwndCombo,
	                        GWLP_WNDPROC, (DWORD_PTR)COMBOEX_ComboWndProc);
1044
    infoPtr->font = (HFONT)SendMessageW (infoPtr->hwndCombo, WM_GETFONT, 0, 0);
1045 1046 1047 1048 1049 1050 1051


    /*
     * 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) {
1052
	infoPtr->hwndEdit = CreateWindowExW (0, EDIT, NIL,
1053 1054
		    WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | ES_AUTOHSCROLL,
		    0, 0, 0, 0,  /* will set later */
1055
		    infoPtr->hwndCombo,
1056 1057
		    (HMENU) GetWindowLongPtrW (hwnd, GWLP_ID),
		    (HINSTANCE)GetWindowLongPtrW (hwnd, GWLP_HINSTANCE), NULL);
1058 1059 1060 1061 1062 1063 1064 1065 1066

	/* native does the following at this point according to trace:
	 *  GetWindowThreadProcessId(hwndEdit,0)
	 *  GetCurrentThreadId()
	 *  GetWindowThreadProcessId(hwndEdit, &???)
	 *  GetCurrentProcessId()
	 */

	/*
1067
	 * Setup a property to hold the pointer to the COMBOBOXEX
1068 1069
	 * data structure.
	 */
1070
        SetPropW(infoPtr->hwndEdit, COMBOEX_SUBCLASS_PROP, hwnd);
1071 1072
	infoPtr->prevEditWndProc = (WNDPROC)SetWindowLongPtrW(infoPtr->hwndEdit,
				 GWLP_WNDPROC, (DWORD_PTR)COMBOEX_EditWndProc);
1073
	infoPtr->font = (HFONT)SendMessageW(infoPtr->hwndCombo, WM_GETFONT, 0, 0);
1074
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1075

1076 1077 1078 1079 1080
    /*
     * Locate the default font if necessary and then set it in
     * all associated controls
     */
    if (!infoPtr->font) {
1081
	SystemParametersInfoW (SPI_GETICONTITLELOGFONT, sizeof(mylogfont),
1082
			       &mylogfont, 0);
1083
	infoPtr->font = infoPtr->defaultFont = CreateFontIndirectW (&mylogfont);
1084
    }
1085
    SendMessageW (infoPtr->hwndCombo, WM_SETFONT, (WPARAM)infoPtr->font, 0);
1086
    if (infoPtr->hwndEdit) {
1087 1088
	SendMessageW (infoPtr->hwndEdit, WM_SETFONT, (WPARAM)infoPtr->font, 0);
	SendMessageW (infoPtr->hwndEdit, EM_SETMARGINS, (WPARAM)EC_USEFONTINFO, 0);
1089 1090
    }

1091
    COMBOEX_ReSize (infoPtr);
1092 1093 1094 1095 1096 1097

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

    GetWindowRect(hwnd, &wnrc1);
    GetClientRect(hwnd, &clrc1);
    GetWindowRect(infoPtr->hwndCombo, &cmbwrc);
1098
    TRACE("EX window=(%d,%d)-(%d,%d) client=(%d,%d)-(%d,%d) CB wnd=(%d,%d)-(%d,%d)\n",
1099 1100 1101
	  wnrc1.left, wnrc1.top, wnrc1.right, wnrc1.bottom,
	  clrc1.left, clrc1.top, clrc1.right, clrc1.bottom,
	  cmbwrc.left, cmbwrc.top, cmbwrc.right, cmbwrc.bottom);
1102
    SetWindowPos(infoPtr->hwndCombo, HWND_TOP,
1103 1104 1105 1106
		 0, 0, wnrc1.right-wnrc1.left, wnrc1.bottom-wnrc1.top,
		 SWP_NOACTIVATE | SWP_NOREDRAW);

    GetWindowRect(infoPtr->hwndCombo, &cmbwrc);
1107
    TRACE("CB window=(%d,%d)-(%d,%d)\n",
1108
	  cmbwrc.left, cmbwrc.top, cmbwrc.right, cmbwrc.bottom);
1109
    SetWindowPos(hwnd, HWND_TOP,
1110 1111 1112 1113 1114 1115
		 0, 0, cmbwrc.right-cmbwrc.left, cmbwrc.bottom-cmbwrc.top,
		 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);

    COMBOEX_AdjustEditPos (infoPtr);

    /*
1116
     * Create an item structure to represent the data in the
1117
     * EDIT control. It is allocated zero-filled.
1118
     */
1119
    infoPtr->edit = (CBE_ITEMDATA *)Alloc (sizeof (CBE_ITEMDATA));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1120 1121 1122 1123
    if (!infoPtr->edit) {
	COMBOEX_Destroy(infoPtr);
	return -1;
    }
1124 1125 1126 1127 1128

    return 0;
}


1129
static LRESULT COMBOEX_Command (COMBOEX_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
1130 1131 1132 1133 1134
{
    LRESULT lret;
    INT command = HIWORD(wParam);
    CBE_ITEMDATA *item = 0;
    WCHAR wintext[520];
1135
    INT cursel, n, oldItem;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1136
    NMCBEENDEDITW cbeend;
1137
    DWORD oldflags;
1138
    HWND parent = infoPtr->hwndNotify;
1139 1140 1141 1142 1143 1144

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

    switch (command)
    {
    case CBN_DROPDOWN:
1145 1146 1147
	SetFocus (infoPtr->hwndCombo);
	ShowWindow (infoPtr->hwndEdit, SW_HIDE);
	return SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1148

1149 1150
    case CBN_CLOSEUP:
	SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1151
	/*
1152
	 * from native trace of first dropdown after typing in URL in IE4
1153 1154 1155 1156 1157 1158 1159 1160 1161
	 *  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)
1162
	 * the rest is supposition
1163
	 */
1164 1165 1166
	ShowWindow (infoPtr->hwndEdit, SW_SHOW);
	InvalidateRect (infoPtr->hwndCombo, 0, TRUE);
	InvalidateRect (infoPtr->hwndEdit, 0, TRUE);
1167 1168
	cursel = SendMessageW (infoPtr->hwndCombo, CB_GETCURSEL, 0, 0);
	if (cursel == -1) {
1169
            cmp_func_t cmptext = get_cmp_func(infoPtr);
1170 1171 1172 1173
	    /* 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++){
1174
                item = get_item_data(infoPtr, cursel);
Frank Richter's avatar
Frank Richter committed
1175
		if ((INT_PTR)item == CB_ERR) break;
1176
		if (!cmptext(COMBOEX_GetText(infoPtr, item), wintext)) break;
1177
	    }
Frank Richter's avatar
Frank Richter committed
1178
	    if ((cursel == n) || ((INT_PTR)item == CB_ERR)) {
1179 1180 1181 1182 1183 1184 1185
		TRACE("failed to find match??? item=%p cursel=%d\n",
		      item, cursel);
		if (infoPtr->hwndEdit)
		    SetFocus(infoPtr->hwndEdit);
		return 0;
	    }
	}
1186
	else {
1187
            item = get_item_data(infoPtr, cursel);
Frank Richter's avatar
Frank Richter committed
1188
	    if ((INT_PTR)item == CB_ERR) {
1189 1190 1191 1192 1193 1194 1195
		TRACE("failed to find match??? item=%p cursel=%d\n",
		      item, cursel);
		if (infoPtr->hwndEdit)
		    SetFocus(infoPtr->hwndEdit);
		return 0;
	    }
	}
1196 1197 1198 1199 1200 1201 1202

	/* 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);
1203 1204 1205 1206
	    cbeend.iNewSelection = SendMessageW (infoPtr->hwndCombo,
						 CB_GETCURSEL, 0, 0);
	    cbeend.iWhy = CBENF_DROPDOWN;

1207
	    if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, COMBOEX_GetText(infoPtr, item))) return 0;
1208
	}
1209 1210 1211 1212 1213

	/* 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;
1214
	    SendMessageW (infoPtr->hwndSelf, CB_SETCURSEL, cursel, 0);
1215 1216
	    SetFocus(infoPtr->hwndCombo);
	}
1217 1218
	return 0;

1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236
    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))) {
	    ERR("item %d not found. Problem!\n", oldItem);
	    break;
1237 1238
	}
	infoPtr->selected = oldItem;
1239
	COMBOEX_SetEditText (infoPtr, item);
1240
	return SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1241 1242

    case CBN_SELENDOK:
1243
	/*
1244 1245 1246
	 * We have to change the handle since we are the control
	 * issuing the message. IE4 depends on this.
	 */
1247
	return SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260

    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
	 */
1261
	SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1262
	if (infoPtr->flags & WCBE_ACTEDIT) {
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1263
	    GetWindowTextW (infoPtr->hwndEdit, wintext, 260);
1264 1265 1266 1267 1268 1269
	    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);
1270
	    if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, wintext)) return 0;
1271 1272 1273 1274 1275
	}
	/* possible CB_GETCURSEL */
	InvalidateRect (infoPtr->hwndCombo, 0, 0);
	return 0;

1276
    default:
1277
	/*
1278 1279 1280 1281 1282
	 * 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.
	 */
1283
	lret = SendMessageW (parent, WM_COMMAND, wParam, (LPARAM)infoPtr->hwndSelf);
1284 1285 1286 1287 1288 1289 1290 1291
	if (infoPtr->hwndEdit)
	    SetFocus(infoPtr->hwndEdit);
	return lret;
    }
    return 0;
}


1292
static BOOL COMBOEX_WM_DeleteItem (COMBOEX_INFO *infoPtr, DELETEITEMSTRUCT const *dis)
1293 1294 1295
{
    CBE_ITEMDATA *item, *olditem;
    NMCOMBOBOXEXW nmcit;
1296
    UINT i;
1297

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

1301
    if (dis->itemID >= infoPtr->nb_items) return FALSE;
1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314

    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)) {
1315
	    item = item->next;
1316 1317 1318
	    i--;
	}
	if (!item->next || (i != dis->itemID)) {
1319
	    ERR("COMBOBOXEX item structures broken. Please report!\n");
1320 1321 1322
	    return FALSE;
	}
	olditem = item->next;
1323
	item->next = item->next->next;
1324 1325 1326
    }
    infoPtr->nb_items--;

1327
    memset (&nmcit.ceItem, 0, sizeof(nmcit.ceItem));
1328
    COMBOEX_CopyItem (olditem, &nmcit.ceItem);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1329
    COMBOEX_NotifyItem (infoPtr, CBEN_DELETEITEM, &nmcit);
1330

1331
    COMBOEX_FreeText(olditem);
1332
    Free(olditem);
1333 1334 1335 1336 1337

    return TRUE;
}


1338
static LRESULT COMBOEX_DrawItem (COMBOEX_INFO *infoPtr, DRAWITEMSTRUCT const *dis)
1339
{
1340
    static const WCHAR nil[] = { 0 };
1341
    CBE_ITEMDATA *item = 0;
1342 1343
    SIZE txtsize;
    RECT rect;
1344
    LPCWSTR str = nil;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1345
    UINT xbase, x, y;
1346
    INT len;
1347
    COLORREF nbkc, ntxc, bkc, txc;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1348
    int drawimage, drawstate, xioff;
1349 1350 1351

    if (!IsWindowEnabled(infoPtr->hwndCombo)) return 0;

1352
    TRACE("DRAWITEMSTRUCT: CtlType=0x%08x CtlID=0x%08x\n",
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1353
	  dis->CtlType, dis->CtlID);
1354
    TRACE("itemID=0x%08x itemAction=0x%08x itemState=0x%08x\n",
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1355
	  dis->itemID, dis->itemAction, dis->itemState);
1356
    TRACE("hWnd=%p hDC=%p (%d,%d)-(%d,%d) itemData=0x%08lx\n",
1357 1358
	  dis->hwndItem, dis->hDC, dis->rcItem.left,
	  dis->rcItem.top, dis->rcItem.right, dis->rcItem.bottom,
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1359
	  dis->itemData);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1360

1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372
    /* 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) {
1373
	if ( ( (dis->itemAction & ODA_FOCUS) && (dis->itemState & ODS_SELECTED)) ||
1374
	     ( (dis->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)) && (dis->itemState & ODS_FOCUS) ) ) {
1375

1376
            TRACE("drawing item -1 special focus, rect=(%d,%d)-(%d,%d)\n",
1377 1378 1379
		  dis->rcItem.left, dis->rcItem.top,
		  dis->rcItem.right, dis->rcItem.bottom);
	}
1380
	else if ((dis->CtlType == ODT_COMBOBOX) &&
1381
		 (dis->itemAction == ODA_DRAWENTIRE)) {
1382 1383 1384 1385 1386
	    /* draw of edit control data */

	    /* testing */
	    {
		RECT exrc, cbrc, edrc;
1387
		GetWindowRect (infoPtr->hwndSelf, &exrc);
1388 1389 1390 1391
		GetWindowRect (infoPtr->hwndCombo, &cbrc);
		edrc.left=edrc.top=edrc.right=edrc.bottom=-1;
		if (infoPtr->hwndEdit)
		    GetWindowRect (infoPtr->hwndEdit, &edrc);
1392
		TRACE("window rects ex=(%d,%d)-(%d,%d), cb=(%d,%d)-(%d,%d), ed=(%d,%d)-(%d,%d)\n",
1393 1394 1395 1396 1397 1398
		      exrc.left, exrc.top, exrc.right, exrc.bottom,
		      cbrc.left, cbrc.top, cbrc.right, cbrc.bottom,
		      edrc.left, edrc.top, edrc.right, edrc.bottom);
	    }
	}
	else {
1399
            ERR("NOT drawing item  -1 special focus, rect=(%d,%d)-(%d,%d), action=%08x, state=%08x\n",
1400 1401 1402 1403 1404
		dis->rcItem.left, dis->rcItem.top,
		dis->rcItem.right, dis->rcItem.bottom,
		dis->itemAction, dis->itemState);
	    return 0;
	}
1405 1406
    }

1407 1408 1409
    /* 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
1410 1411

	if (infoPtr->hwndEdit) {
1412
	    INT len;
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1413 1414

	    /* free previous text of edit item */
1415 1416
	    COMBOEX_FreeText(item);
	    item->mask &= ~CBEIF_TEXT;
1417
	    if( (len = GetWindowTextLengthW(infoPtr->hwndEdit)) ) {
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1418
		item->mask |= CBEIF_TEXT;
1419
		item->pszText = (LPWSTR)Alloc ((len + 1)*sizeof(WCHAR));
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1420 1421
		if (item->pszText)
		    GetWindowTextW(infoPtr->hwndEdit, item->pszText, len+1);
1422

1423
	       TRACE("edit control hwndEdit=%p, text len=%d str=%s\n",
1424
		     infoPtr->hwndEdit, len, debugstr_txt(item->pszText));
1425 1426 1427 1428
	    }
	}
    }

1429

1430 1431
    /* if the item pointer is not set, then get the data and locate it */
    if (!item) {
1432
        item = get_item_data(infoPtr, dis->itemID);
1433
	if (item == (CBE_ITEMDATA *)CB_ERR) {
1434
	    ERR("invalid item for id %d\n", dis->itemID);
1435 1436
	    return 0;
	}
1437
    }
1438

1439
    if (TRACE_ON(comboex)) COMBOEX_DumpItem (item);
1440 1441

    xbase = CBE_STARTOFFSET;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1442 1443 1444 1445 1446 1447
    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;
1448
	    nmce.ceItem.lParam = item->lParam;
1449
	    nmce.ceItem.iItem = dis->itemID;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471
	    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)) {
1472 1473
	    /* 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
1474 1475 1476 1477 1478 1479 1480
	     */
	    if (infoPtr->flags & WCBE_EDITFOCUSED) {
	        if (item->mask & CBEIF_SELECTEDIMAGE)
		    drawimage = item->iSelectedImage;
		drawstate = ILD_SELECTED;
	    }
	} else {
1481
	    /* if we don't have an edit control, use
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1482 1483 1484 1485 1486 1487 1488 1489
	     * the requested state.
	     */
	    if (dis->itemState & ODS_SELECTED) {
		if (item->mask & CBEIF_SELECTEDIMAGE)
		    drawimage = item->iSelectedImage;
		drawstate = ILD_SELECTED;
	    }
	}
1490 1491
    }

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1492 1493 1494 1495 1496 1497 1498
    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;

1499
    /* setup pointer to text to be drawn */
1500 1501
    str = COMBOEX_GetText(infoPtr, item);
    if (!str) str = nil;
1502 1503 1504

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

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

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1509 1510 1511 1512
    	if (drawimage == I_IMAGECALLBACK) {
	    NMCOMBOBOXEXW nmce;
	    ZeroMemory(&nmce, sizeof(nmce));
	    nmce.ceItem.mask = (drawstate == ILD_NORMAL) ? CBEIF_IMAGE : CBEIF_SELECTEDIMAGE;
1513
	    nmce.ceItem.lParam = item->lParam;
1514
	    nmce.ceItem.iItem = dis->itemID;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528
	    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;
1529
	    nmce.ceItem.lParam = item->lParam;
1530
	    nmce.ceItem.iItem = dis->itemID;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1531 1532 1533 1534
	    COMBOEX_NotifyItem(infoPtr, CBEN_GETDISPINFOW, &nmce);
	    if (nmce.ceItem.mask & CBEIF_DI_SETITEM)
		item->iOverlay = nmce.ceItem.iOverlay;
	    overlay = nmce.ceItem.iOverlay;
1535
	}
1536 1537

	if (drawimage >= 0 &&
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1538 1539
	    !(infoPtr->dwExtStyle & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT))) {
	    if (overlay > 0) ImageList_SetOverlayImage (infoPtr->himl, overlay, 1);
1540
	    ImageList_Draw (infoPtr->himl, drawimage, dis->hDC, xbase, dis->rcItem.top,
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1541
			    drawstate | (overlay > 0 ? INDEXTOOVERLAYMASK(1) : 0));
1542
	}
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1543 1544

	/* now draw the text */
1545
	if (!IsWindowVisible (infoPtr->hwndEdit)) {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558
	    nbkc = GetSysColor ((dis->itemState & ODS_SELECTED) ?
				COLOR_HIGHLIGHT : COLOR_WINDOW);
	    bkc = SetBkColor (dis->hDC, nbkc);
	    ntxc = GetSysColor ((dis->itemState & ODS_SELECTED) ?
				COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
	    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;
1559
            TRACE("drawing item %d text, rect=(%d,%d)-(%d,%d)\n",
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1560 1561 1562 1563 1564
	          dis->itemID, rect.left, rect.top, rect.right, rect.bottom);
	    ExtTextOutW (dis->hDC, x, y, ETO_OPAQUE | ETO_CLIPPED,
		         &rect, str, len, 0);
	    SetBkColor (dis->hDC, bkc);
	    SetTextColor (dis->hDC, txc);
1565
	}
1566
    }
1567

1568 1569 1570 1571 1572 1573
    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);
1574 1575
    }

Alexandre Julliard's avatar
Alexandre Julliard committed
1576 1577 1578 1579
    return 0;
}


1580
static LRESULT COMBOEX_Destroy (COMBOEX_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
1581 1582
{
    if (infoPtr->hwndCombo)
1583
	DestroyWindow (infoPtr->hwndCombo);
Alexandre Julliard's avatar
Alexandre Julliard committed
1584

1585 1586
    Free (infoPtr->edit);
    infoPtr->edit = 0;
1587

1588
    if (infoPtr->items) {
1589 1590 1591 1592
        CBE_ITEMDATA *item, *next;

	item = infoPtr->items;
	while (item) {
1593
	    next = item->next;
1594
	    COMBOEX_FreeText (item);
1595
	    Free (item);
1596
	    item = next;
1597
	}
1598
	infoPtr->items = 0;
1599
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1600

1601
    if (infoPtr->defaultFont)
1602
	DeleteObject (infoPtr->defaultFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
1603 1604

    /* free comboex info data */
1605
    Free (infoPtr);
1606
    SetWindowLongPtrW (infoPtr->hwndSelf, 0, 0);
Alexandre Julliard's avatar
Alexandre Julliard committed
1607 1608 1609 1610
    return 0;
}


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

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

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

    return 0;
}


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

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


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


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

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

1662 1663
    COMBOEX_AdjustEditPos (infoPtr);

Alexandre Julliard's avatar
Alexandre Julliard committed
1664 1665 1666 1667
    return 0;
}


1668
static LRESULT COMBOEX_WindowPosChanging (COMBOEX_INFO *infoPtr, WINDOWPOS *wp)
1669 1670
{
    RECT cbx_wrect, cbx_crect, cb_wrect;
1671
    INT width, height;
1672

1673 1674
    GetWindowRect (infoPtr->hwndSelf, &cbx_wrect);
    GetClientRect (infoPtr->hwndSelf, &cbx_crect);
1675 1676 1677 1678
    GetWindowRect (infoPtr->hwndCombo, &cb_wrect);

    /* width is winpos value + border width of comboex */
    width = wp->cx
1679 1680
	    + (cbx_wrect.right-cbx_wrect.left)
            - (cbx_crect.right-cbx_crect.left);
1681

1682 1683
    TRACE("winpos=(%d,%d %dx%d) flags=0x%08x\n",
	  wp->x, wp->y, wp->cx, wp->cy, wp->flags);
1684
    TRACE("EX window=(%d,%d)-(%d,%d), client=(%d,%d)-(%d,%d)\n",
1685 1686
	  cbx_wrect.left, cbx_wrect.top, cbx_wrect.right, cbx_wrect.bottom,
	  cbx_crect.left, cbx_crect.top, cbx_crect.right, cbx_crect.bottom);
1687
    TRACE("CB window=(%d,%d)-(%d,%d), EX setting=(0,0)-(%d,%d)\n",
1688 1689 1690
	  cb_wrect.left, cb_wrect.top, cb_wrect.right, cb_wrect.bottom,
	  width, cb_wrect.bottom-cb_wrect.top);

1691 1692 1693 1694
    if (width) SetWindowPos (infoPtr->hwndCombo, HWND_TOP, 0, 0,
			     width,
			     cb_wrect.bottom-cb_wrect.top,
			     SWP_NOACTIVATE);
1695

1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706
    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);
    if (wp->cy < height) wp->cy = height;
    if (infoPtr->hwndEdit) {
	COMBOEX_AdjustEditPos (infoPtr);
	InvalidateRect (infoPtr->hwndCombo, 0, TRUE);
    }
1707 1708 1709 1710 1711 1712 1713

    return 0;
}

static LRESULT WINAPI
COMBOEX_EditWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
1714
    HWND hwndComboex = (HWND)GetPropW(hwnd, COMBOEX_SUBCLASS_PROP);
1715
    COMBOEX_INFO *infoPtr = COMBOEX_GetInfoPtr (hwndComboex);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
1716 1717
    NMCBEENDEDITW cbeend;
    WCHAR edit_text[260];
1718
    COLORREF obkc;
1719 1720
    HDC hDC;
    RECT rect;
1721
    LRESULT lret;
1722

1723
    TRACE("hwnd=%p msg=%x wparam=%lx lParam=%lx, info_ptr=%p\n",
1724 1725 1726 1727 1728 1729 1730
	  hwnd, uMsg, wParam, lParam, infoPtr);

    if (!infoPtr) return 0;

    switch (uMsg)
    {

1731
	case WM_CHAR:
1732 1733 1734
	    /* handle (ignore) the return character */
	    if (wParam == VK_RETURN) return 0;
	    /* all other characters pass into the real Edit */
1735
	    return CallWindowProcW (infoPtr->prevEditWndProc,
1736 1737
				   hwnd, uMsg, wParam, lParam);

1738
	case WM_ERASEBKGND:
1739
	    /*
1740
	     * The following was determined by traces of the native
1741 1742
	     */
            hDC = (HDC) wParam;
1743
	    obkc = SetBkColor (hDC, GetSysColor (COLOR_WINDOW));
1744
            GetClientRect (hwnd, &rect);
1745
            TRACE("erasing (%d,%d)-(%d,%d)\n",
1746 1747 1748
		  rect.left, rect.top, rect.right, rect.bottom);
	    ExtTextOutW (hDC, 0, 0, ETO_OPAQUE, &rect, 0, 0, 0);
            SetBkColor (hDC, obkc);
1749
	    return CallWindowProcW (infoPtr->prevEditWndProc,
1750 1751
				   hwnd, uMsg, wParam, lParam);

1752 1753
	case WM_KEYDOWN: {
	    INT oldItem, selected, step = 1;
1754 1755 1756 1757 1758 1759 1760
	    CBE_ITEMDATA *item;

	    switch ((INT)wParam)
	    {
	    case VK_ESCAPE:
		/* native version seems to do following for COMBOEX */
		/*
1761
		 *   GetWindowTextW(Edit,&?, 0x104)             x
1762 1763
		 *   CB_GETCURSEL to Combo rets -1              x
		 *   WM_NOTIFY to COMBOEX parent (rebar)        x
1764
		 *     (CBEN_ENDEDIT{A|W}
1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777
		 *      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
1778
		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
1779

1780
		infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);
1781
		cbeend.fChanged = FALSE;
1782
		cbeend.iNewSelection = SendMessageW (infoPtr->hwndCombo,
1783 1784 1785
						     CB_GETCURSEL, 0, 0);
		cbeend.iWhy = CBENF_ESCAPE;

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

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

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

1823
		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
1824

1825 1826 1827 1828 1829
		infoPtr->flags &= ~(WCBE_ACTEDIT | WCBE_EDITCHG);
		selected = SendMessageW (infoPtr->hwndCombo,
					 CB_GETCURSEL, 0, 0);

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

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

1863 1864 1865 1866 1867 1868 1869 1870
	    case VK_UP:
		step = -1;
	    case VK_DOWN:
		/* by default, step is 1 */
		oldItem = SendMessageW (infoPtr->hwndSelf, CB_GETCURSEL, 0, 0);
		if (oldItem >= 0 && oldItem + step >= 0)
		    SendMessageW (infoPtr->hwndSelf, CB_SETCURSEL, oldItem + step, 0);
	    	return 0;
1871
	    default:
1872
		return CallWindowProcW (infoPtr->prevEditWndProc,
1873 1874 1875
				       hwnd, uMsg, wParam, lParam);
	    }
	    return 0;
1876 1877
            }

1878
	case WM_SETFOCUS:
1879
	    /* remember the focus to set state of icon */
1880
	    lret = CallWindowProcW (infoPtr->prevEditWndProc,
1881 1882 1883 1884
				   hwnd, uMsg, wParam, lParam);
	    infoPtr->flags |= WCBE_EDITFOCUSED;
	    return lret;

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

		GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
1894 1895 1896 1897 1898
		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
1899
		COMBOEX_NotifyEndEdit (infoPtr, &cbeend, edit_text);
1900 1901
	    }
	    /* fall through */
1902

1903
	default:
1904
	    return CallWindowProcW (infoPtr->prevEditWndProc,
1905 1906 1907 1908 1909 1910 1911 1912
				   hwnd, uMsg, wParam, lParam);
    }
}


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

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

    if (!infoPtr) return 0;

    switch (uMsg)
    {
1931 1932 1933 1934 1935 1936 1937 1938

    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;
1939
	    return CallWindowProcW (infoPtr->prevComboWndProc,
1940 1941 1942 1943
				   hwnd, uMsg, wParam, lParam);

    case WM_ERASEBKGND:
	    /*
1944
	     * The following was determined by traces of the native
1945 1946
	     */
            hDC = (HDC) wParam;
1947
	    obkc = SetBkColor (hDC, GetSysColor (COLOR_WINDOW));
1948
            GetClientRect (hwnd, &rect);
1949
            TRACE("erasing (%d,%d)-(%d,%d)\n",
1950 1951 1952
		  rect.left, rect.top, rect.right, rect.bottom);
	    ExtTextOutW (hDC, 0, 0, ETO_OPAQUE, &rect, 0, 0, 0);
            SetBkColor (hDC, obkc);
1953
	    return CallWindowProcW (infoPtr->prevComboWndProc,
1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966
				   hwnd, uMsg, wParam, lParam);

    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 CallWindowProcW (infoPtr->prevComboWndProc,
1969 1970
				   hwnd, uMsg, wParam, lParam);

1971 1972 1973 1974 1975
    case WM_LBUTTONDOWN:
	    GetClientRect (hwnd, &rect);
	    rect.bottom = rect.top + SendMessageW(infoPtr->hwndSelf,
			                          CB_GETITEMHEIGHT, -1, 0);
	    rect.left = rect.right - GetSystemMetrics(SM_CXVSCROLL);
1976 1977
	    pt.x = (short)LOWORD(lParam);
	    pt.y = (short)HIWORD(lParam);
1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007
	    if (PtInRect(&rect, pt))
		return CallWindowProcW (infoPtr->prevComboWndProc,
				        hwnd, uMsg, wParam, lParam);
	    infoPtr->flags |= WCBE_MOUSECAPTURED;
	    SetCapture(hwnd);
	    break;

    case WM_LBUTTONUP:
	    if (!(infoPtr->flags & WCBE_MOUSECAPTURED))
		return CallWindowProcW (infoPtr->prevComboWndProc,
				        hwnd, uMsg, wParam, lParam);
	    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;
	    }
	    return CallWindowProcW (infoPtr->prevComboWndProc,
			            hwnd, uMsg, wParam, lParam);

2008
    case WM_COMMAND:
2009
	    switch (HIWORD(wParam)) {
2010

2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021
	    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
2022
		 *  GetWindowTextW(Edit)
2023 2024 2025 2026 2027 2028 2029 2030 2031
		 *  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
2032
		    GetWindowTextW (infoPtr->hwndEdit, edit_text, 260);
2033 2034 2035 2036 2037 2038
		    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);
2039
		    if (COMBOEX_NotifyEndEdit (infoPtr, &cbeend, edit_text)) return 0;
2040 2041 2042 2043 2044
		}
		/* possible CB_GETCURSEL */
		InvalidateRect (infoPtr->hwndCombo, 0, 0);
		if (focusedhwnd)
		    SendMessageW (infoPtr->hwndCombo, WM_KILLFOCUS,
2045
				  (WPARAM)focusedhwnd, 0);
2046 2047 2048
		return 0;

	    case EN_SETFOCUS: {
2049
		/*
2050 2051
		 * For EN_SETFOCUS this issues the same calls and messages
		 *  as the native seems to do.
2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062
		 *
		 * 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)
2063 2064 2065
		 */
		NMHDR hdr;

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

2074
	    case EN_CHANGE: {
2075
		/*
2076 2077 2078
		 * For EN_CHANGE this issues the same calls and messages
		 *  as the native seems to do.
		 */
2079
		WCHAR edit_text[260];
2080
		LPCWSTR lastwrk;
2081
                cmp_func_t cmptext = get_cmp_func(infoPtr);
2082

2083 2084
		INT selected = SendMessageW (infoPtr->hwndCombo,
					     CB_GETCURSEL, 0, 0);
2085

2086
		/* lstrlenW( lastworkingURL ) */
2087

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

2097
		TRACE("handling EN_CHANGE, selected = %d, selected_text=%s\n",
2098
		      selected, debugstr_w(lastwrk));
2099 2100
		TRACE("handling EN_CHANGE, edittext=%s\n",
		      debugstr_w(edit_text));
2101

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

2114 2115 2116 2117
	    case LBN_SELCHANGE:
		/*
		 * Therefore from traces there is no additional code here
		 */
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 2146 2147 2148
		/*
		 * 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 */
2149
    default:
2150
	    return CallWindowProcW (infoPtr->prevComboWndProc,
2151 2152
				   hwnd, uMsg, wParam, lParam);
    }
2153 2154 2155 2156
    return 0;
}


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

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

2164
    if (!infoPtr) {
2165
	if (uMsg == WM_CREATE)
2166
	    return COMBOEX_Create (hwnd, (LPCREATESTRUCTA)lParam);
2167
	if (uMsg == WM_NCCREATE)
2168 2169
	    COMBOEX_NCCreate (hwnd);
        return DefWindowProcW (hwnd, uMsg, wParam, lParam);
2170
    }
2171

Alexandre Julliard's avatar
Alexandre Julliard committed
2172 2173
    switch (uMsg)
    {
2174 2175
        case CBEM_DELETEITEM:
	    return COMBOEX_DeleteItem (infoPtr, wParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2176 2177

	case CBEM_GETCOMBOCONTROL:
2178
	    return (LRESULT)infoPtr->hwndCombo;
Alexandre Julliard's avatar
Alexandre Julliard committed
2179 2180

	case CBEM_GETEDITCONTROL:
2181
	    return (LRESULT)infoPtr->hwndEdit;
Alexandre Julliard's avatar
Alexandre Julliard committed
2182 2183

	case CBEM_GETEXTENDEDSTYLE:
2184
	    return infoPtr->dwExtStyle;
Alexandre Julliard's avatar
Alexandre Julliard committed
2185 2186

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

2189
	case CBEM_GETITEMA:
2190
	    return (LRESULT)COMBOEX_GetItemA (infoPtr, (COMBOBOXEXITEMA *)lParam);
2191

2192
	case CBEM_GETITEMW:
2193
	    return (LRESULT)COMBOEX_GetItemW (infoPtr, (COMBOBOXEXITEMW *)lParam);
2194

2195
	case CBEM_GETUNICODEFORMAT:
2196
	    return infoPtr->unicode;
Alexandre Julliard's avatar
Alexandre Julliard committed
2197

2198
	case CBEM_HASEDITCHANGED:
2199
	    return COMBOEX_HasEditChanged (infoPtr);
2200

2201
	case CBEM_INSERTITEMA:
2202
	    return COMBOEX_InsertItemA (infoPtr, (COMBOBOXEXITEMA *)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2203

2204
	case CBEM_INSERTITEMW:
2205
	    return COMBOEX_InsertItemW (infoPtr, (COMBOBOXEXITEMW *)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2206

2207
	case CBEM_SETEXSTYLE:
Alexandre Julliard's avatar
Alexandre Julliard committed
2208
	case CBEM_SETEXTENDEDSTYLE:
2209
	    return COMBOEX_SetExtendedStyle (infoPtr, (DWORD)wParam, (DWORD)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2210 2211

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

2214
	case CBEM_SETITEMA:
2215
	    return COMBOEX_SetItemA (infoPtr, (COMBOBOXEXITEMA *)lParam);
2216

2217
	case CBEM_SETITEMW:
2218
	    return COMBOEX_SetItemW (infoPtr, (COMBOBOXEXITEMW *)lParam);
2219

2220
	case CBEM_SETUNICODEFORMAT:
2221
	    return COMBOEX_SetUnicodeFormat (infoPtr, wParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2222

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2223 2224
	/*case CBEM_SETWINDOWTHEME:
	    FIXME("CBEM_SETWINDOWTHEME: stub\n");*/
2225

2226 2227 2228 2229
	case WM_SETTEXT:
	case WM_GETTEXT:
            return SendMessageW(infoPtr->hwndEdit, uMsg, wParam, lParam);

2230 2231 2232 2233 2234 2235
	case CB_GETLBTEXT:
            return COMBOEX_GetListboxText(infoPtr, wParam, (LPWSTR)lParam);

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

2236
/*   Combo messages we are not sure if we need to process or just forward */
2237 2238 2239 2240 2241 2242
	case CB_GETDROPPEDCONTROLRECT:
	case CB_GETITEMHEIGHT:
	case CB_GETEXTENDEDUI:
	case CB_LIMITTEXT:
	case CB_RESETCONTENT:
	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, lParam);
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
        case WM_MEASUREITEM:
2293
            return COMBOEX_MeasureItem (infoPtr, (MEASUREITEMSTRUCT *)lParam);
2294

Guy L. Albertelli's avatar
Guy L. Albertelli committed
2295
        case WM_NOTIFYFORMAT:
2296
	    return COMBOEX_NotifyFormat (infoPtr, lParam);
Guy L. Albertelli's avatar
Guy L. Albertelli committed
2297

Alexandre Julliard's avatar
Alexandre Julliard committed
2298
	case WM_SIZE:
2299
	    return COMBOEX_Size (infoPtr, LOWORD(lParam), HIWORD(lParam));
Alexandre Julliard's avatar
Alexandre Julliard committed
2300

2301
        case WM_WINDOWPOSCHANGING:
2302
	    return COMBOEX_WindowPosChanging (infoPtr, (WINDOWPOS *)lParam);
2303

2304 2305 2306 2307
        case WM_SETFOCUS:
            SetFocus(infoPtr->hwndCombo);
            return 0;

Alexandre Julliard's avatar
Alexandre Julliard committed
2308
	default:
2309
	    if ((uMsg >= WM_USER) && (uMsg < WM_APP))
2310
		ERR("unknown msg %04x wp=%08lx lp=%08lx\n",uMsg,wParam,lParam);
2311
	    return DefWindowProcW (hwnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2312 2313 2314 2315
    }
}


2316
void COMBOEX_Register (void)
Alexandre Julliard's avatar
Alexandre Julliard committed
2317
{
2318
    WNDCLASSW wndClass;
Alexandre Julliard's avatar
Alexandre Julliard committed
2319

2320
    ZeroMemory (&wndClass, sizeof(WNDCLASSW));
Alexandre Julliard's avatar
Alexandre Julliard committed
2321
    wndClass.style         = CS_GLOBALCLASS;
2322
    wndClass.lpfnWndProc   = COMBOEX_WindowProc;
Alexandre Julliard's avatar
Alexandre Julliard committed
2323 2324
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(COMBOEX_INFO *);
2325
    wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2326
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2327
    wndClass.lpszClassName = WC_COMBOBOXEXW;
2328

2329
    RegisterClassW (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
2330 2331
}

2332

2333
void COMBOEX_Unregister (void)
2334
{
2335
    UnregisterClassW (WC_COMBOBOXEXW, NULL);
2336
}