Skip to content
Snippets Groups Projects
listview.c 229 KiB
Newer Older
/*
 * ListView tests
 *
 * Copyright 2006 Mike McCormack for CodeWeavers
 * Copyright 2007 George Gov
 * Copyright 2009-2014 Nikolay Sivov
 *
 * 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
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdio.h>
#include <windows.h>
#include <commctrl.h>

#include "wine/test.h"
static HIMAGELIST (WINAPI *pImageList_Create)(int, int, UINT, int, int);
static BOOL (WINAPI *pImageList_Destroy)(HIMAGELIST);
static int (WINAPI *pImageList_Add)(HIMAGELIST, HBITMAP, HBITMAP);
static BOOL (WINAPI *p_TrackMouseEvent)(TRACKMOUSEEVENT *);

enum seq_index {
    PARENT_SEQ_INDEX,
    PARENT_FULL_SEQ_INDEX,
    PARENT_CD_SEQ_INDEX,
    LISTVIEW_SEQ_INDEX,
    EDITBOX_SEQ_INDEX,
    COMBINED_SEQ_INDEX,
    NUM_MSG_SEQUENCES
};

#define LISTVIEW_ID 0
#define HEADER_ID   1

#define expect(expected, got) ok(got == expected, "Expected %d, got %d\n", expected, got)
#define expect2(expected1, expected2, got1, got2) ok(expected1 == got1 && expected2 == got2, \
       "expected (%d,%d), got (%d,%d)\n", expected1, expected2, got1, got2)
static const WCHAR testparentclassW[] =
    {'L','i','s','t','v','i','e','w',' ','t','e','s','t',' ','p','a','r','e','n','t','W', 0};

static HWND hwndparent, hwndparentW;
/* prevents edit box creation, LVN_BEGINLABELEDIT return value */
static BOOL blockEdit;
/* return nonzero on NM_HOVER */
static BOOL g_block_hover;
/* notification data for LVN_ITEMCHANGED */
static NMLISTVIEW g_nmlistview;
/* notification data for LVN_ITEMCHANGING */
static NMLISTVIEW g_nmlistview_changing;
/* format reported to control:
   -1 falls to defproc, anything else returned */
/* indicates we're running < 5.80 version */
static BOOL g_is_below_5;
/* item data passed to LVN_GETDISPINFOA */
static LVITEMA g_itema;
/* alter notification code A->W */
static BOOL g_disp_A_to_W;
/* dispinfo data sent with LVN_LVN_ENDLABELEDIT */
static NMLVDISPINFOA g_editbox_disp_info;
/* when this is set focus will be tested on LVN_DELETEITEM */
static BOOL g_focus_test_LVN_DELETEITEM;
/* Whether to send WM_KILLFOCUS to the edit control during LVN_ENDLABELEDIT */
static BOOL g_WM_KILLFOCUS_on_LVN_ENDLABELEDIT;
static HWND subclass_editbox(HWND hwndListview);
static void init_functions(void)
{
    HMODULE hComCtl32 = LoadLibraryA("comctl32.dll");

#define X(f) p##f = (void*)GetProcAddress(hComCtl32, #f);
    X(ImageList_Create);
    X(ImageList_Destroy);
    X(ImageList_Add);
    X(_TrackMouseEvent);
#undef X
}

static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];

static const struct message create_ownerdrawfixed_parent_seq[] = {
    { WM_NOTIFYFORMAT, sent },
    { WM_QUERYUISTATE, sent|optional }, /* Win2K and higher */
    { WM_MEASUREITEM, sent },
    { WM_PARENTNOTIFY, sent },
    { 0 }
};

static const struct message redraw_listview_seq[] = {
    { WM_PAINT,      sent|id,            0, 0, LISTVIEW_ID },
    { WM_PAINT,      sent|id,            0, 0, HEADER_ID },
    { WM_NCPAINT,    sent|id|defwinproc, 0, 0, HEADER_ID },
    { WM_ERASEBKGND, sent|id|defwinproc|optional, 0, 0, HEADER_ID },
    { WM_NOTIFY,     sent|id|defwinproc, 0, 0, LISTVIEW_ID },
    { WM_NCPAINT,    sent|id|defwinproc, 0, 0, LISTVIEW_ID },
    { WM_ERASEBKGND, sent|id|defwinproc|optional, 0, 0, LISTVIEW_ID },
static const struct message listview_icon_spacing_seq[] = {
    { LVM_SETICONSPACING, sent|lparam, 0, MAKELPARAM(20, 30) },
    { LVM_SETICONSPACING, sent|lparam, 0, MAKELPARAM(25, 35) },
    { LVM_SETICONSPACING, sent|lparam, 0, MAKELPARAM(-1, -1) },
    { 0 }
};

static const struct message listview_color_seq[] = {
    { LVM_SETBKCOLOR,     sent|lparam, 0, RGB(0,0,0) },
    { LVM_GETBKCOLOR,     sent },
    { LVM_SETTEXTCOLOR,   sent|lparam, 0, RGB(0,0,0) },
    { LVM_GETTEXTCOLOR,   sent },
    { LVM_SETTEXTBKCOLOR, sent|lparam, 0, RGB(0,0,0) },
    { LVM_GETTEXTBKCOLOR, sent },

    { LVM_SETBKCOLOR,     sent|lparam, 0, RGB(100,50,200) },
    { LVM_GETBKCOLOR,     sent },
    { LVM_SETTEXTCOLOR,   sent|lparam, 0, RGB(100,50,200) },
    { LVM_GETTEXTCOLOR,   sent },
    { LVM_SETTEXTBKCOLOR, sent|lparam, 0, RGB(100,50,200) },
    { LVM_GETTEXTBKCOLOR, sent },

    { LVM_SETBKCOLOR,     sent|lparam, 0, CLR_NONE },
    { LVM_GETBKCOLOR,     sent },
    { LVM_SETTEXTCOLOR,   sent|lparam, 0, CLR_NONE },
    { LVM_GETTEXTCOLOR,   sent },
    { LVM_SETTEXTBKCOLOR, sent|lparam, 0, CLR_NONE },
    { LVM_GETTEXTBKCOLOR, sent },

    { LVM_SETBKCOLOR,     sent|lparam, 0, RGB(255,255,255) },
    { LVM_GETBKCOLOR,     sent },
    { LVM_SETTEXTCOLOR,   sent|lparam, 0, RGB(255,255,255) },
    { LVM_GETTEXTCOLOR,   sent },
    { LVM_SETTEXTBKCOLOR, sent|lparam, 0, RGB(255,255,255) },
    { LVM_GETTEXTBKCOLOR, sent },
    { 0 }
};

static const struct message listview_item_count_seq[] = {
    { LVM_GETITEMCOUNT,   sent },
    { LVM_INSERTITEMA,    sent },
    { LVM_INSERTITEMA,    sent },
    { LVM_INSERTITEMA,    sent },
    { LVM_GETITEMCOUNT,   sent },
    { LVM_DELETEITEM,     sent|wparam, 2 },
    { WM_NCPAINT,         sent|optional },
    { WM_ERASEBKGND,      sent|optional },
    { LVM_GETITEMCOUNT,   sent },
    { LVM_DELETEALLITEMS, sent },
    { LVM_GETITEMCOUNT,   sent },
    { LVM_INSERTITEMA,    sent },
    { LVM_INSERTITEMA,    sent },
    { LVM_GETITEMCOUNT,   sent },
    { LVM_INSERTITEMA,    sent },
    { LVM_GETITEMCOUNT,   sent },
    { 0 }
};

static const struct message listview_itempos_seq[] = {
    { LVM_INSERTITEMA,     sent },
    { LVM_INSERTITEMA,     sent },
    { LVM_INSERTITEMA,     sent },
    { LVM_SETITEMPOSITION, sent|wparam|lparam, 1, MAKELPARAM(10,5) },
    { WM_NCPAINT,          sent|optional },
    { WM_ERASEBKGND,       sent|optional },
    { LVM_GETITEMPOSITION, sent|wparam,        1 },
    { LVM_SETITEMPOSITION, sent|wparam|lparam, 2, MAKELPARAM(0,0) },
    { LVM_GETITEMPOSITION, sent|wparam,        2 },
    { LVM_SETITEMPOSITION, sent|wparam|lparam, 0, MAKELPARAM(20,20) },
    { LVM_GETITEMPOSITION, sent|wparam,        0 },
    { 0 }
};

static const struct message listview_ownerdata_switchto_seq[] = {
    { WM_STYLECHANGING,    sent },
    { WM_STYLECHANGED,     sent },
    { 0 }
};

static const struct message listview_getorderarray_seq[] = {
    { LVM_GETCOLUMNORDERARRAY, sent|id|wparam, 2, 0, LISTVIEW_ID },
    { HDM_GETORDERARRAY,       sent|id|wparam, 2, 0, HEADER_ID },
    { LVM_GETCOLUMNORDERARRAY, sent|id|wparam, 0, 0, LISTVIEW_ID },
    { HDM_GETORDERARRAY,       sent|id|wparam, 0, 0, HEADER_ID },
static const struct message listview_setorderarray_seq[] = {
    { LVM_SETCOLUMNORDERARRAY, sent|id|wparam, 2, 0, LISTVIEW_ID },
    { HDM_SETORDERARRAY,       sent|id|wparam, 2, 0, HEADER_ID },
    { LVM_SETCOLUMNORDERARRAY, sent|id|wparam, 0, 0, LISTVIEW_ID },
    { HDM_SETORDERARRAY,       sent|id|wparam, 0, 0, HEADER_ID },
    { 0 }
};

static const struct message empty_seq[] = {
    { 0 }
};

static const struct message parent_focus_change_ownerdata_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { WM_NOTIFY, sent|id, 0, 0, LVN_GETDISPINFOA },
    { 0 }
};

static const struct message forward_erasebkgnd_parent_seq[] = {
    { WM_ERASEBKGND, sent },
    { 0 }
};

static const struct message ownerdata_select_focus_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { WM_NOTIFY, sent|id, 0, 0, LVN_GETDISPINFOA },
    { WM_NOTIFY, sent|id|optional, 0, 0, LVN_GETDISPINFOA }, /* version 4.7x */
static const struct message ownerdata_setstate_all_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { 0 }
};

static const struct message ownerdata_defocus_all_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { WM_NOTIFY, sent|id, 0, 0, LVN_GETDISPINFOA },
    { WM_NOTIFY, sent|id|optional, 0, 0, LVN_GETDISPINFOA },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { 0 }
};

static const struct message ownerdata_deselect_all_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ODCACHEHINT },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { 0 }
};

static const struct message change_all_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },

    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },

    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },

    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },

    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { 0 }
};

static const struct message changing_all_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { 0 }
};

static const struct message textcallback_set_again_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED  },
    { 0 }
};

static const struct message single_getdispinfo_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_GETDISPINFOA },
    { 0 }
};

static const struct message getitemposition_seq1[] = {
    { LVM_GETITEMPOSITION, sent|id, 0, 0, LISTVIEW_ID },
    { 0 }
};

static const struct message getitemposition_seq2[] = {
    { LVM_GETITEMPOSITION, sent|id, 0, 0, LISTVIEW_ID },
    { HDM_GETITEMRECT, sent|id, 0, 0, HEADER_ID },
    { 0 }
};

static const struct message getsubitemrect_seq[] = {
    { LVM_GETSUBITEMRECT, sent|id|wparam, -1, 0, LISTVIEW_ID },
    { HDM_GETITEMRECT, sent|id, 0, 0, HEADER_ID },
    { LVM_GETSUBITEMRECT, sent|id|wparam, 0, 0, LISTVIEW_ID },
    { HDM_GETITEMRECT, sent|id, 0, 0, HEADER_ID },
    { LVM_GETSUBITEMRECT, sent|id|wparam, -10, 0, LISTVIEW_ID },
    { HDM_GETITEMRECT, sent|id, 0, 0, HEADER_ID },
    { LVM_GETSUBITEMRECT, sent|id|wparam, 20, 0, LISTVIEW_ID },
    { HDM_GETITEMRECT, sent|id, 0, 0, HEADER_ID },
    { 0 }
};

static const struct message editbox_create_pos[] = {
    /* sequence sent after LVN_BEGINLABELEDIT */
    /* next two are 4.7x specific */
    { WM_WINDOWPOSCHANGED, sent|optional },

    { WM_WINDOWPOSCHANGING, sent|optional },
    { WM_NCCALCSIZE, sent },
    { WM_WINDOWPOSCHANGED, sent },
    { WM_MOVE, sent|defwinproc },
    { WM_SIZE, sent|defwinproc },
    /* the rest is todo, skipped in 4.7x */
    { WM_WINDOWPOSCHANGING, sent|optional },
    { WM_WINDOWPOSCHANGED, sent|optional },
static const struct message scroll_parent_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_BEGINSCROLL },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ENDSCROLL },
    { 0 }
};

static const struct message setredraw_seq[] = {
    { WM_SETREDRAW, sent|id|wparam, FALSE, 0, LISTVIEW_ID },
    { 0 }
};

static const struct message lvs_ex_transparentbkgnd_seq[] = {
    { WM_PRINTCLIENT, sent|lparam, 0, PRF_ERASEBKGND },
    { 0 }
};

static const struct message edit_end_nochange[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ENDLABELEDITA },
    { WM_NOTIFY, sent|id, 0, 0, NM_CUSTOMDRAW },     /* todo */
    { WM_NOTIFY, sent|id, 0, 0, NM_SETFOCUS },
    { 0 }
};

static const struct message hover_parent[] = {
    { WM_GETDLGCODE, sent }, /* todo_wine */
    { WM_NOTIFY, sent|id, 0, 0, NM_HOVER },
    { 0 }
};

static const struct message listview_destroy[] = {
    { 0x0090, sent|optional }, /* Vista */
    { WM_PARENTNOTIFY, sent },
    { WM_SHOWWINDOW, sent },
    { WM_WINDOWPOSCHANGING, sent },
    { WM_WINDOWPOSCHANGED, sent|optional },
    { WM_DESTROY, sent },
    { WM_NOTIFY, sent|id, 0, 0, LVN_DELETEALLITEMS },
    { WM_NCDESTROY, sent },
    { 0 }
};

static const struct message listview_ownerdata_destroy[] = {
    { 0x0090, sent|optional }, /* Vista */
    { WM_PARENTNOTIFY, sent },
    { WM_SHOWWINDOW, sent },
    { WM_WINDOWPOSCHANGING, sent },
    { WM_WINDOWPOSCHANGED, sent|optional },
    { WM_DESTROY, sent },
    { WM_NCDESTROY, sent },
    { 0 }
};

static const struct message listview_ownerdata_deleteall[] = {
    { LVM_DELETEALLITEMS, sent },
    { WM_NOTIFY, sent|id, 0, 0, LVN_DELETEALLITEMS },
    { 0 }
};

static const struct message listview_header_changed_seq[] = {
    { LVM_SETCOLUMNA, sent },
    { WM_NOTIFY, sent|id|defwinproc, 0, 0, LISTVIEW_ID },
    { WM_NOTIFY, sent|id|defwinproc, 0, 0, LISTVIEW_ID },
    { 0 }
};

static const struct message parent_header_click_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_COLUMNCLICK },
    { WM_NOTIFY, sent|id, 0, 0, HDN_ITEMCLICKA },
    { 0 }
};

static const struct message parent_header_divider_dclick_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, HDN_ITEMCHANGINGA },
    { WM_NOTIFY, sent|id, 0, 0, NM_CUSTOMDRAW },
    { WM_NOTIFY, sent|id, 0, 0, NM_CUSTOMDRAW },
    { WM_NOTIFY, sent|id, 0, 0, HDN_ITEMCHANGEDA },
    { WM_NOTIFY, sent|id, 0, 0, HDN_DIVIDERDBLCLICKA },
    { 0 }
};

static const struct message listview_set_imagelist[] = {
    { LVM_SETIMAGELIST, sent|id, 0, 0, LISTVIEW_ID },
    { 0 }
};

static const struct message listview_header_set_imagelist[] = {
    { LVM_SETIMAGELIST, sent|id, 0, 0, LISTVIEW_ID },
    { HDM_SETIMAGELIST, sent|id, 0, 0, HEADER_ID },
    { 0 }
};

static const struct message parent_insert_focused_seq[] = {
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
    { WM_NOTIFY, sent|id, 0, 0, LVN_INSERTITEM },
    { 0 }
};

static const struct message parent_report_cd_seq[] = {
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_PREPAINT },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPREPAINT },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPREPAINT|CDDS_SUBITEM },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPOSTPAINT|CDDS_SUBITEM },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPREPAINT|CDDS_SUBITEM },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPOSTPAINT|CDDS_SUBITEM },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPOSTPAINT },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_POSTPAINT },
    { 0 }
};

static const struct message parent_list_cd_seq[] = {
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_PREPAINT },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPREPAINT },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_ITEMPOSTPAINT },
    { WM_NOTIFY, sent|id|custdraw, 0, 0, NM_CUSTOMDRAW, CDDS_POSTPAINT },
    { 0 }
};

static const struct message listview_end_label_edit[] = {
    { WM_NOTIFY,  sent|id, 0, 0, LVN_ENDLABELEDITA },
    { WM_NOTIFY,  sent|id, 0, 0, LVN_ITEMCHANGING},
    { WM_NOTIFY,  sent|id, 0, 0, LVN_ITEMCHANGED },
    { WM_NOTIFY,  sent|id|optional, 0, 0, NM_CUSTOMDRAW }, /* XP */
    { WM_NOTIFY,  sent|id, 0, 0, NM_SETFOCUS },
    { 0 }
};

static const struct message listview_end_label_edit_kill_focus[] = {
    { WM_NOTIFY,  sent|id, 0, 0, LVN_ENDLABELEDITA },
    { WM_COMMAND, sent|id|optional, 0, 0, EN_KILLFOCUS }, /* todo: not sent by wine yet */
    { WM_NOTIFY,  sent|id, 0, 0, LVN_ITEMCHANGING },
    { WM_NOTIFY,  sent|id, 0, 0, LVN_ITEMCHANGED },
    { WM_NOTIFY,  sent|id|optional, 0, 0, NM_CUSTOMDRAW }, /* XP */
    { WM_NOTIFY,  sent|id, 0, 0, NM_SETFOCUS },
    { 0 }
};

static LRESULT WINAPI parent_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static LONG defwndproc_counter = 0;
    msg.message = message;
    msg.flags = sent|wparam|lparam;
    if (defwndproc_counter) msg.flags |= defwinproc;
    msg.wParam = wParam;
    msg.lParam = lParam;
    if (message == WM_NOTIFY && lParam) msg.id = ((NMHDR*)lParam)->code;
    if (message == WM_COMMAND) msg.id = HIWORD(wParam);
    /* log system messages, except for painting */
    if (message < WM_USER &&
        message != WM_PAINT &&
        message != WM_ERASEBKGND &&
        message != WM_NCPAINT &&
        message != WM_NCHITTEST &&
        message != WM_GETTEXT &&
        message != WM_GETICON &&
        message != WM_DEVICECHANGE)
    {
        add_message(sequences, PARENT_SEQ_INDEX, &msg);
        add_message(sequences, COMBINED_SEQ_INDEX, &msg);
    add_message(sequences, PARENT_FULL_SEQ_INDEX, &msg);
      case WM_NOTIFY:
      {
          switch (((NMHDR*)lParam)->code)
          {
          case LVN_BEGINLABELEDITA:
              /* subclass edit box */
              if (!blockEdit)
                  edit = subclass_editbox(((NMHDR*)lParam)->hwndFrom);
              if (edit)
              {
                  INT len = SendMessageA(edit, EM_GETLIMITTEXT, 0, 0);
                  ok(len == 259 || broken(len == 260) /* includes NULL in NT4 */,
                      "text limit %d, expected 259\n", len);
              }
              /* always accept new item text */
              NMLVDISPINFOA *di = (NMLVDISPINFOA*)lParam;
              g_editbox_disp_info = *di;

              /* edit control still available from this notification */
              edit = (HWND)SendMessageA(((NMHDR*)lParam)->hwndFrom, LVM_GETEDITCONTROL, 0, 0);
              ok(IsWindow(edit), "expected valid edit control handle\n");
              ok((GetWindowLongA(edit, GWL_STYLE) & ES_MULTILINE) == 0, "edit is multiline\n");
              if (g_WM_KILLFOCUS_on_LVN_ENDLABELEDIT)
                  SendMessageA(edit, WM_KILLFOCUS, 0, 0);

          case LVN_ITEMCHANGING:
              {
                  NMLISTVIEW *nmlv = (NMLISTVIEW*)lParam;
                  g_nmlistview_changing = *nmlv;
              }
              break;
          case LVN_ITEMCHANGED:
              {
                  NMLISTVIEW *nmlv = (NMLISTVIEW*)lParam;
          case LVN_GETDISPINFOA:
              {
                  NMLVDISPINFOA *dispinfo = (NMLVDISPINFOA*)lParam;
                  g_itema = dispinfo->item;

                  if (g_disp_A_to_W && (dispinfo->item.mask & LVIF_TEXT))
                  {
                      static const WCHAR testW[] = {'T','E','S','T',0};
                      dispinfo->hdr.code = LVN_GETDISPINFOW;
                      memcpy(dispinfo->item.pszText, testW, sizeof(testW));
                  }

                  /* test control buffer size for text, 10 used to mask cases when control
                     is using caller buffer to process LVM_GETITEM for example */
                  if (dispinfo->item.mask & LVIF_TEXT && dispinfo->item.cchTextMax > 10)
                      ok(dispinfo->item.cchTextMax == 260 ||
                         broken(dispinfo->item.cchTextMax == 264) /* NT4 reports aligned size */,
                      "buffer size %d\n", dispinfo->item.cchTextMax);
          case LVN_DELETEITEM:
              if (g_focus_test_LVN_DELETEITEM)
              {
                  NMLISTVIEW *nmlv = (NMLISTVIEW*)lParam;
                  UINT state;

                  state = SendMessageA(((NMHDR*)lParam)->hwndFrom, LVM_GETITEMSTATE, nmlv->iItem, LVIS_FOCUSED);
                  ok(state == 0, "got state %x\n", state);
              }
              break;
          case NM_HOVER:
              if (g_block_hover) return 1;
              break;
          }
          break;
      }
      case WM_NOTIFYFORMAT:
      {
          /* force to return format */
          if (lParam == NF_QUERY && notifyFormat != -1) return notifyFormat;
          break;
      }
    if (IsWindowUnicode(hwnd))
        ret = DefWindowProcW(hwnd, message, wParam, lParam);
    else
        ret = DefWindowProcA(hwnd, message, wParam, lParam);
static BOOL register_parent_wnd_class(BOOL Unicode)
    WNDCLASSA clsA;
    WNDCLASSW clsW;

    if (Unicode)
    {
        clsW.style = 0;
        clsW.lpfnWndProc = parent_wnd_proc;
        clsW.cbClsExtra = 0;
        clsW.cbWndExtra = 0;
        clsW.hInstance = GetModuleHandleW(NULL);
        clsW.hIcon = 0;
        clsW.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
        clsW.hbrBackground = GetStockObject(WHITE_BRUSH);
        clsW.lpszMenuName = NULL;
        clsW.lpszClassName = testparentclassW;
    }
    else
    {
        clsA.style = 0;
        clsA.lpfnWndProc = parent_wnd_proc;
        clsA.cbClsExtra = 0;
        clsA.cbWndExtra = 0;
        clsA.hInstance = GetModuleHandleA(NULL);
        clsA.hIcon = 0;
        clsA.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
        clsA.hbrBackground = GetStockObject(WHITE_BRUSH);
        clsA.lpszMenuName = NULL;
        clsA.lpszClassName = "Listview test parent class";
    }

    return Unicode ? RegisterClassW(&clsW) : RegisterClassA(&clsA);
static HWND create_parent_window(BOOL Unicode)
    static const WCHAR nameW[] = {'t','e','s','t','p','a','r','e','n','t','n','a','m','e','W',0};

    if (!register_parent_wnd_class(Unicode))
        hwnd = CreateWindowExW(0, testparentclassW, nameW,
                               WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
                               WS_MAXIMIZEBOX | WS_VISIBLE,
                               0, 0, 100, 100,
                               GetDesktopWindow(), NULL, GetModuleHandleW(NULL), NULL);
    else
        hwnd = CreateWindowExA(0, "Listview test parent class",
                               "Listview test parent window",
                               WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
                               WS_MAXIMIZEBOX | WS_VISIBLE,
                               0, 0, 100, 100,
                               GetDesktopWindow(), NULL, GetModuleHandleA(NULL), NULL);
    SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE );
    return hwnd;
}

static LRESULT WINAPI listview_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    WNDPROC oldproc = (WNDPROC)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
    static LONG defwndproc_counter = 0;
    LRESULT ret;
    struct message msg;

    msg.message = message;
    msg.flags = sent|wparam|lparam;
    if (defwndproc_counter) msg.flags |= defwinproc;
    msg.wParam = wParam;
    msg.lParam = lParam;
    msg.id = LISTVIEW_ID;
    add_message(sequences, LISTVIEW_SEQ_INDEX, &msg);
    add_message(sequences, COMBINED_SEQ_INDEX, &msg);
    ret = CallWindowProcA(oldproc, hwnd, message, wParam, lParam);
static HWND create_listview_control(DWORD style)
    HWND hwnd;
    RECT rect;

    GetClientRect(hwndparent, &rect);
    hwnd = CreateWindowExA(0, WC_LISTVIEWA, "foo",
                           WS_CHILD | WS_BORDER | WS_VISIBLE | style,
                           0, 0, rect.right, rect.bottom,
                           hwndparent, NULL, GetModuleHandleA(NULL), NULL);
    ok(hwnd != NULL, "gle=%d\n", GetLastError());

    oldproc = (WNDPROC)SetWindowLongPtrA(hwnd, GWLP_WNDPROC,
                                        (LONG_PTR)listview_subclass_proc);
    SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)oldproc);
/* unicode listview window with specified parent */
static HWND create_listview_controlW(DWORD style, HWND parent)
{
    HWND hwnd;
    RECT rect;
    static const WCHAR nameW[] = {'f','o','o',0};

    GetClientRect(parent, &rect);
    hwnd = CreateWindowExW(0, WC_LISTVIEWW, nameW,
                           WS_CHILD | WS_BORDER | WS_VISIBLE | style,
                           0, 0, rect.right, rect.bottom,
                           parent, NULL, GetModuleHandleW(NULL), NULL);
    ok(hwnd != NULL, "gle=%d\n", GetLastError());

    oldproc = (WNDPROC)SetWindowLongPtrW(hwnd, GWLP_WNDPROC,
                                        (LONG_PTR)listview_subclass_proc);
    SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)oldproc);
static BOOL is_win_xp(void)
{
    HWND hwnd, header;
    BOOL ret;

    hwnd = create_listview_control(LVS_ICON);
    SendMessageA(hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_HEADERINALLVIEWS, LVS_EX_HEADERINALLVIEWS);
    header = (HWND)SendMessageA(hwnd, LVM_GETHEADER, 0, 0);
    ret = !IsWindow(header);

    DestroyWindow(hwnd);

    return ret;
}

static LRESULT WINAPI header_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    WNDPROC oldproc = (WNDPROC)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
    static LONG defwndproc_counter = 0;
    struct message msg = { 0 };
    LRESULT ret;

    msg.message = message;
    msg.flags = sent|wparam|lparam;
    if (defwndproc_counter) msg.flags |= defwinproc;
    msg.wParam = wParam;
    msg.lParam = lParam;
    msg.id = HEADER_ID;
    add_message(sequences, LISTVIEW_SEQ_INDEX, &msg);

    defwndproc_counter++;
    ret = CallWindowProcA(oldproc, hwnd, message, wParam, lParam);
    defwndproc_counter--;
    return ret;
}

static HWND subclass_header(HWND hwndListview)
{
    hwnd = (HWND)SendMessageA(hwndListview, LVM_GETHEADER, 0, 0);
    oldproc = (WNDPROC)SetWindowLongPtrA(hwnd, GWLP_WNDPROC,
                                         (LONG_PTR)header_subclass_proc);
    SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)oldproc);
static LRESULT WINAPI editbox_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    WNDPROC oldproc = (WNDPROC)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
    struct message msg = { 0 };
    LRESULT ret;

    msg.message = message;
    msg.flags = sent|wparam|lparam;
    if (defwndproc_counter) msg.flags |= defwinproc;
    msg.wParam = wParam;
    msg.lParam = lParam;

    /* all we need is sizing */
    if (message == WM_WINDOWPOSCHANGING ||
        message == WM_NCCALCSIZE ||
        message == WM_WINDOWPOSCHANGED ||
        message == WM_MOVE ||
        message == WM_SIZE)
    {
        add_message(sequences, EDITBOX_SEQ_INDEX, &msg);
    }

    defwndproc_counter++;
    ret = CallWindowProcA(oldproc, hwnd, message, wParam, lParam);
    defwndproc_counter--;
    return ret;
}

static HWND subclass_editbox(HWND hwndListview)
{
    hwnd = (HWND)SendMessageA(hwndListview, LVM_GETEDITCONTROL, 0, 0);
    oldproc = (WNDPROC)SetWindowLongPtrA(hwnd, GWLP_WNDPROC,
                                         (LONG_PTR)editbox_subclass_proc);
    SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)oldproc);
/* Performs a single LVM_HITTEST test */
static void test_lvm_hittest_(HWND hwnd, INT x, INT y, INT item, UINT flags, UINT broken_flags,
                              BOOL todo_item, BOOL todo_flags, int line)

    lpht.pt.x = x;
    lpht.pt.y = y;
    lpht.iSubItem = 10;

    ret = SendMessageA(hwnd, LVM_HITTEST, 0, (LPARAM)&lpht);
    todo_wine_if(todo_item)
        ok_(__FILE__, line)(ret == item, "Expected %d retval, got %d\n", item, ret);
        ok_(__FILE__, line)(lpht.iItem == item, "Expected %d item, got %d\n", item, lpht.iItem);
        ok_(__FILE__, line)(lpht.iSubItem == 10, "Expected subitem not overwrited\n");
    }

    if (todo_flags)
    {
        todo_wine
            ok_(__FILE__, line)(lpht.flags == flags, "Expected flags 0x%x, got 0x%x\n", flags, lpht.flags);
    else if (broken_flags)
        ok_(__FILE__, line)(lpht.flags == flags || broken(lpht.flags == broken_flags),
                            "Expected flags %x, got %x\n", flags, lpht.flags);
        ok_(__FILE__, line)(lpht.flags == flags, "Expected flags 0x%x, got 0x%x\n", flags, lpht.flags);
#define test_lvm_hittest(a,b,c,d,e,f,g,h) test_lvm_hittest_(a,b,c,d,e,f,g,h,__LINE__)

/* Performs a single LVM_SUBITEMHITTEST test */
static void test_lvm_subitemhittest_(HWND hwnd, INT x, INT y, INT item, INT subitem, UINT flags,
                                     BOOL todo_item, BOOL todo_subitem, BOOL todo_flags, int line)
    ret = SendMessageA(hwnd, LVM_SUBITEMHITTEST, 0, (LPARAM)&lpht);
    todo_wine_if(todo_item)
        ok_(__FILE__, line)(ret == item, "Expected %d retval, got %d\n", item, ret);
        ok_(__FILE__, line)(lpht.iItem == item, "Expected %d item, got %d\n", item, lpht.iItem);
    }

    todo_wine_if(todo_subitem)
        ok_(__FILE__, line)(lpht.iSubItem == subitem, "Expected subitem %d, got %d\n", subitem, lpht.iSubItem);

    todo_wine_if(todo_flags)
        ok_(__FILE__, line)(lpht.flags == flags, "Expected flags 0x%x, got 0x%x\n", flags, lpht.flags);
#define test_lvm_subitemhittest(a,b,c,d,e,f,g,h,i) test_lvm_subitemhittest_(a,b,c,d,e,f,g,h,i,__LINE__)

    HIMAGELIST himl;
    HBITMAP hbmp;
    RECT r1, r2;
    static CHAR hello[] = "hello";
    himl = pImageList_Create(40, 40, 0, 4, 4);
    ok(himl != NULL, "failed to create imagelist\n");

    hbmp = CreateBitmap(40, 40, 1, 1, NULL);
    ok(hbmp != NULL, "failed to create bitmap\n");

    r = pImageList_Add(himl, hbmp, 0);
    ok(r == 0, "should be zero\n");

    hwnd = CreateWindowExA(0, WC_LISTVIEWA, "foo", LVS_OWNERDRAWFIXED,
                10, 10, 100, 200, hwndparent, NULL, NULL, NULL);
    ok(hwnd != NULL, "failed to create listview window\n");

    r = SendMessageA(hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 0,
                    LVS_EX_UNDERLINEHOT | LVS_EX_FLATSB | LVS_EX_ONECLICKACTIVATE);

    ok(r == 0, "should return zero\n");

    r = SendMessageA(hwnd, LVM_SETIMAGELIST, 0, (LPARAM)himl);
    ok(r == 0, "should return zero\n");

    r = SendMessageA(hwnd, LVM_SETICONSPACING, 0, MAKELONG(100,50));
    /* returns dimensions */

    r = SendMessageA(hwnd, LVM_GETITEMCOUNT, 0, 0);
    ok(r == 0, "should be zero items\n");

    item.mask = LVIF_IMAGE | LVIF_TEXT;
    item.iItem = 0;
    item.iSubItem = 1;
    item.iImage = 0;
    r = SendMessageA(hwnd, LVM_INSERTITEMA, 0, (LPARAM) &item);
    ok(r == -1, "should fail\n");

    item.iSubItem = 0;
    item.pszText = hello;
    r = SendMessageA(hwnd, LVM_INSERTITEMA, 0, (LPARAM) &item);
    ok(r == 0, "should not fail\n");

    SetRect(&r1, LVIR_ICON, 0, 0, 0);
    r = SendMessageA(hwnd, LVM_GETITEMRECT, 0, (LPARAM) &r1);
    r = SendMessageA(hwnd, LVM_DELETEALLITEMS, 0, 0);
    ok(r == TRUE, "should not fail\n");

    item.iSubItem = 0;
    item.pszText = hello;
    r = SendMessageA(hwnd, LVM_INSERTITEMA, 0, (LPARAM) &item);
    ok(r == 0, "should not fail\n");

    SetRect(&r2, LVIR_ICON, 0, 0, 0);
    r = SendMessageA(hwnd, LVM_GETITEMRECT, 0, (LPARAM) &r2);
    ok(EqualRect(&r1, &r2), "rectangle should be the same\n");

    /* I_IMAGECALLBACK set for item, try to get image with invalid subitem. */
    hwnd = create_listview_control(LVS_REPORT);
    ok(hwnd != NULL, "Failed to create listview.\n");

    memset(&item, 0, sizeof(item));
    item.mask = LVIF_IMAGE;
    item.iImage = I_IMAGECALLBACK;
    r = SendMessageA(hwnd, LVM_INSERTITEMA, 0, (LPARAM)&item);
    ok(!r, "Failed to insert item.\n");

    flush_sequences(sequences, NUM_MSG_SEQUENCES);

    memset(&item, 0, sizeof(item));
    item.mask = LVIF_IMAGE;
    r = SendMessageA(hwnd, LVM_GETITEMA, 0, (LPARAM)&item);
    ok(r, "Failed to get item.\n");

    ok_sequence(sequences, PARENT_SEQ_INDEX, single_getdispinfo_parent_seq, "get image dispinfo 1", FALSE);

    flush_sequences(sequences, NUM_MSG_SEQUENCES);

    memset(&item, 0, sizeof(item));
    item.mask = LVIF_IMAGE;
    item.iSubItem = 1;
    r = SendMessageA(hwnd, LVM_GETITEMA, 0, (LPARAM)&item);
    ok(r, "Failed to get item.\n");

    ok_sequence(sequences, PARENT_SEQ_INDEX, empty_seq, "get image dispinfo 2", FALSE);

    DestroyWindow(hwnd);
    static CHAR text[]  = "Text",
                text2[] = "Text2",
                text3[] = "Text3";