/*
 * Unit test of the IShellView
 *
 * Copyright 2010 Nikolay Sivov for CodeWeavers
 *
 * 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 <stdarg.h>
#include <stdio.h>

#define COBJMACROS
#define CONST_VTABLE

#include "windef.h"
#include "winbase.h"
#include "wtypes.h"
#include "shellapi.h"

#include "shlguid.h"
#include "shlobj.h"
#include "shobjidl.h"
#include "shlwapi.h"
#include "ocidl.h"
#include "oleauto.h"

#include "initguid.h"

#include "wine/test.h"

#include "msg.h"

#define LISTVIEW_SEQ_INDEX  0
#define NUM_MSG_SEQUENCES   1

DEFINE_GUID(IID_IPersistHistory, 0x91a565c1, 0xe38f, 0x11d0, 0x94, 0xbf, 0x00, 0xa0, 0xc9, 0x05, 0x5c, 0xbf);

static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];

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;

    trace("listview: %p, %04x, %08lx, %08lx\n", hwnd, message, wParam, lParam);

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

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

static HWND subclass_listview(HWND hwnd)
{
    WNDPROC oldproc;
    HWND listview;

    /* listview is a first child */
    listview = FindWindowExA(hwnd, NULL, WC_LISTVIEWA, NULL);
    if(!listview)
    {
        /* .. except for some versions of Windows XP, where things
           are slightly more complicated. */
        HWND hwnd_tmp;
        hwnd_tmp = FindWindowExA(hwnd, NULL, "DUIViewWndClassName", NULL);
        hwnd_tmp = FindWindowExA(hwnd_tmp, NULL, "DirectUIHWND", NULL);
        hwnd_tmp = FindWindowExA(hwnd_tmp, NULL, "CtrlNotifySink", NULL);
        listview = FindWindowExA(hwnd_tmp, NULL, WC_LISTVIEWA, NULL);
    }

    oldproc = (WNDPROC)SetWindowLongPtrA(listview, GWLP_WNDPROC,
                                        (LONG_PTR)listview_subclass_proc);
    SetWindowLongPtrA(listview, GWLP_USERDATA, (LONG_PTR)oldproc);

    return listview;
}

static UINT get_msg_count(struct msg_sequence **seq, int sequence_index, UINT message)
{
    struct msg_sequence *msg_seq = seq[sequence_index];
    UINT i, count = 0;

    for(i = 0; i < msg_seq->count ; i++)
        if(msg_seq->sequence[i].message == message)
            count++;

    return count;
}

/* Checks that every message in the sequence seq is also present in
 * the UINT array msgs */
static void verify_msgs_in_(struct msg_sequence *seq, const UINT *msgs,
                           const char *file, int line)
{
    UINT i, j, msg, failcount = 0;
    for(i = 0; i < seq->count; i++)
    {
        BOOL found = FALSE;
        msg = seq->sequence[i].message;
        for(j = 0; msgs[j] != 0; j++)
            if(msgs[j] == msg) found = TRUE;

        if(!found)
        {
            failcount++;
            trace("Unexpected message %d\n", msg);
        }
    }
    ok_(file, line) (!failcount, "%d failures.\n", failcount);
    flush_sequences(sequences, NUM_MSG_SEQUENCES);
}

#define verify_msgs_in(seq, msgs)               \
    verify_msgs_in_(seq, msgs, __FILE__, __LINE__)

/* dummy IDataObject implementation */
typedef struct {
    IDataObject IDataObject_iface;
    LONG ref;
} IDataObjectImpl;

static const IDataObjectVtbl IDataObjectImpl_Vtbl;

static inline IDataObjectImpl *impl_from_IDataObject(IDataObject *iface)
{
    return CONTAINING_RECORD(iface, IDataObjectImpl, IDataObject_iface);
}

static IDataObject* IDataObjectImpl_Construct(void)
{
    IDataObjectImpl *obj;

    obj = HeapAlloc(GetProcessHeap(), 0, sizeof(*obj));
    obj->IDataObject_iface.lpVtbl = &IDataObjectImpl_Vtbl;
    obj->ref = 1;

    return &obj->IDataObject_iface;
}

static HRESULT WINAPI IDataObjectImpl_QueryInterface(IDataObject *iface, REFIID riid, void **ppvObj)
{
    IDataObjectImpl *This = impl_from_IDataObject(iface);

    if (IsEqualIID(riid, &IID_IUnknown) ||
        IsEqualIID(riid, &IID_IDataObject))
    {
        *ppvObj = This;
    }

    if(*ppvObj)
    {
        IDataObject_AddRef(iface);
        return S_OK;
    }

    return E_NOINTERFACE;
}

static ULONG WINAPI IDataObjectImpl_AddRef(IDataObject * iface)
{
    IDataObjectImpl *This = impl_from_IDataObject(iface);
    return InterlockedIncrement(&This->ref);
}

static ULONG WINAPI IDataObjectImpl_Release(IDataObject * iface)
{
    IDataObjectImpl *This = impl_from_IDataObject(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    if (!ref)
    {
        HeapFree(GetProcessHeap(), 0, This);
        return 0;
    }
    return ref;
}

static HRESULT WINAPI IDataObjectImpl_GetData(IDataObject *iface, FORMATETC *pformat, STGMEDIUM *pmedium)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_GetDataHere(IDataObject *iface, FORMATETC *pformat, STGMEDIUM *pmedium)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_QueryGetData(IDataObject *iface, FORMATETC *pformat)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_GetCanonicalFormatEtc(
    IDataObject *iface, FORMATETC *pformatIn, FORMATETC *pformatOut)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_SetData(
    IDataObject *iface, FORMATETC *pformat, STGMEDIUM *pmedium, BOOL release)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_EnumFormatEtc(
    IDataObject *iface, DWORD direction, IEnumFORMATETC **ppenumFormatEtc)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_DAdvise(
    IDataObject *iface, FORMATETC *pformatetc, DWORD advf, IAdviseSink *pSink, DWORD *pConnection)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_DUnadvise(IDataObject *iface, DWORD connection)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IDataObjectImpl_EnumDAdvise(IDataObject *iface, IEnumSTATDATA **ppenumAdvise)
{
    return E_NOTIMPL;
}

static const IDataObjectVtbl IDataObjectImpl_Vtbl =
{
    IDataObjectImpl_QueryInterface,
    IDataObjectImpl_AddRef,
    IDataObjectImpl_Release,
    IDataObjectImpl_GetData,
    IDataObjectImpl_GetDataHere,
    IDataObjectImpl_QueryGetData,
    IDataObjectImpl_GetCanonicalFormatEtc,
    IDataObjectImpl_SetData,
    IDataObjectImpl_EnumFormatEtc,
    IDataObjectImpl_DAdvise,
    IDataObjectImpl_DUnadvise,
    IDataObjectImpl_EnumDAdvise
};

/* dummy IShellBrowser implementation */
typedef struct {
    IShellBrowser IShellBrowser_iface;
    LONG ref;
} IShellBrowserImpl;

static const IShellBrowserVtbl IShellBrowserImpl_Vtbl;

static inline IShellBrowserImpl *impl_from_IShellBrowser(IShellBrowser *iface)
{
    return CONTAINING_RECORD(iface, IShellBrowserImpl, IShellBrowser_iface);
}

static IShellBrowser* IShellBrowserImpl_Construct(void)
{
    IShellBrowserImpl *browser;

    browser = HeapAlloc(GetProcessHeap(), 0, sizeof(*browser));
    browser->IShellBrowser_iface.lpVtbl = &IShellBrowserImpl_Vtbl;
    browser->ref = 1;

    return &browser->IShellBrowser_iface;
}

static HRESULT WINAPI IShellBrowserImpl_QueryInterface(IShellBrowser *iface,
                                            REFIID riid,
                                            LPVOID *ppvObj)
{
    IShellBrowserImpl *This = impl_from_IShellBrowser(iface);

    *ppvObj = NULL;

    if(IsEqualIID(riid, &IID_IUnknown)   ||
       IsEqualIID(riid, &IID_IOleWindow) ||
       IsEqualIID(riid, &IID_IShellBrowser))
    {
        *ppvObj = This;
    }

    if(*ppvObj)
    {
        IShellBrowser_AddRef(iface);
        return S_OK;
    }

    return E_NOINTERFACE;
}

static ULONG WINAPI IShellBrowserImpl_AddRef(IShellBrowser * iface)
{
    IShellBrowserImpl *This = impl_from_IShellBrowser(iface);
    return InterlockedIncrement(&This->ref);
}

static ULONG WINAPI IShellBrowserImpl_Release(IShellBrowser * iface)
{
    IShellBrowserImpl *This = impl_from_IShellBrowser(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    if (!ref)
    {
        HeapFree(GetProcessHeap(), 0, This);
        return 0;
    }
    return ref;
}

static HRESULT WINAPI IShellBrowserImpl_GetWindow(IShellBrowser *iface,
                                           HWND *phwnd)
{
    if (phwnd) *phwnd = GetDesktopWindow();
    return S_OK;
}

static HRESULT WINAPI IShellBrowserImpl_ContextSensitiveHelp(IShellBrowser *iface,
                                                      BOOL fEnterMode)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_BrowseObject(IShellBrowser *iface,
                                              LPCITEMIDLIST pidl,
                                              UINT wFlags)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_EnableModelessSB(IShellBrowser *iface,
                                              BOOL fEnable)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_GetControlWindow(IShellBrowser *iface,
                                              UINT id,
                                              HWND *lphwnd)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_GetViewStateStream(IShellBrowser *iface,
                                                DWORD mode,
                                                LPSTREAM *pStrm)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_InsertMenusSB(IShellBrowser *iface,
                                           HMENU hmenuShared,
                                           LPOLEMENUGROUPWIDTHS lpMenuWidths)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_OnViewWindowActive(IShellBrowser *iface,
                                                IShellView *ppshv)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_QueryActiveShellView(IShellBrowser *iface,
                                                  IShellView **ppshv)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_RemoveMenusSB(IShellBrowser *iface,
                                           HMENU hmenuShared)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_SendControlMsg(IShellBrowser *iface,
                                            UINT id,
                                            UINT uMsg,
                                            WPARAM wParam,
                                            LPARAM lParam,
                                            LRESULT *pret)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_SetMenuSB(IShellBrowser *iface,
                                       HMENU hmenuShared,
                                       HOLEMENU holemenuReserved,
                                       HWND hwndActiveObject)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_SetStatusTextSB(IShellBrowser *iface,
                                             LPCOLESTR lpszStatusText)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_SetToolbarItems(IShellBrowser *iface,
                                             LPTBBUTTON lpButtons,
                                             UINT nButtons,
                                             UINT uFlags)

{
    return E_NOTIMPL;
}

static HRESULT WINAPI IShellBrowserImpl_TranslateAcceleratorSB(IShellBrowser *iface,
                                                    LPMSG lpmsg,
                                                    WORD wID)

{
    return E_NOTIMPL;
}

static const IShellBrowserVtbl IShellBrowserImpl_Vtbl =
{
    IShellBrowserImpl_QueryInterface,
    IShellBrowserImpl_AddRef,
    IShellBrowserImpl_Release,
    IShellBrowserImpl_GetWindow,
    IShellBrowserImpl_ContextSensitiveHelp,
    IShellBrowserImpl_InsertMenusSB,
    IShellBrowserImpl_SetMenuSB,
    IShellBrowserImpl_RemoveMenusSB,
    IShellBrowserImpl_SetStatusTextSB,
    IShellBrowserImpl_EnableModelessSB,
    IShellBrowserImpl_TranslateAcceleratorSB,
    IShellBrowserImpl_BrowseObject,
    IShellBrowserImpl_GetViewStateStream,
    IShellBrowserImpl_GetControlWindow,
    IShellBrowserImpl_SendControlMsg,
    IShellBrowserImpl_QueryActiveShellView,
    IShellBrowserImpl_OnViewWindowActive,
    IShellBrowserImpl_SetToolbarItems
};

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

static const struct message folderview_getspacing_seq[] = {
    { LVM_GETITEMSPACING, wparam|sent, FALSE },
    { 0 }
};

static const struct message folderview_getselectionmarked_seq[] = {
    { LVM_GETSELECTIONMARK, sent },
    { 0 }
};

static const struct message folderview_getfocused_seq[] = {
    { LVM_GETNEXTITEM, sent|wparam|lparam|optional, -1, LVNI_FOCUSED },
    { 0 }
};

static const struct message folderview_itemcount_seq[] = {
    { LVM_GETITEMCOUNT, sent },
    { 0 }
};

static void test_IShellView_CreateViewWindow(void)
{
    IShellFolder *desktop;
    FOLDERSETTINGS settings;
    IShellView *view;
    IDropTarget *dt;
    HWND hwnd_view;
    HRESULT hr;
    RECT r = {0};

    hr = SHGetDesktopFolder(&desktop);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellFolder_CreateViewObject(desktop, NULL, &IID_IShellView, (void**)&view);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

if (0)
{
    /* crashes on native */
    IShellView_CreateViewWindow(view, NULL, &settings, NULL, NULL, NULL);
}

    settings.ViewMode = FVM_ICON;
    settings.fFlags = 0;
    hwnd_view = (HWND)0xdeadbeef;
    hr = IShellView_CreateViewWindow(view, NULL, &settings, NULL, NULL, &hwnd_view);
    ok(hr == E_UNEXPECTED, "got (0x%08x)\n", hr);
    ok(hwnd_view == 0, "got %p\n", hwnd_view);

    hwnd_view = (HWND)0xdeadbeef;
    hr = IShellView_CreateViewWindow(view, NULL, &settings, NULL, &r, &hwnd_view);
    ok(hr == E_UNEXPECTED, "got (0x%08x)\n", hr);
    ok(hwnd_view == 0, "got %p\n", hwnd_view);

    /* ::DragLeave without drag operation */
    hr = IShellView_QueryInterface(view, &IID_IDropTarget, (void**)&dt);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    hr = IDropTarget_DragLeave(dt);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    IDropTarget_Release(dt);

    IShellView_Release(view);
    IShellFolder_Release(desktop);
}

static void test_IFolderView(void)
{
    IShellFolder *desktop, *folder;
    FOLDERSETTINGS settings;
    IShellView *view;
    IShellBrowser *browser;
    IFolderView *fv;
    HWND hwnd_view, hwnd_list;
    PITEMID_CHILD pidl;
    HRESULT hr;
    INT ret, count;
    POINT pt;
    LONG ref1, ref2;
    RECT r;

    hr = SHGetDesktopFolder(&desktop);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellFolder_CreateViewObject(desktop, NULL, &IID_IShellView, (void**)&view);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellView_QueryInterface(view, &IID_IFolderView, (void**)&fv);
    if (hr != S_OK)
    {
        win_skip("IFolderView not supported by desktop folder\n");
        IShellView_Release(view);
        IShellFolder_Release(desktop);
        return;
    }

    /* call methods before window creation */
    hr = IFolderView_GetSpacing(fv, NULL);
    ok(hr == S_FALSE || broken(hr == S_OK) /* win7 */, "got (0x%08x)\n", hr);

    pidl = (void*)0xdeadbeef;
    hr = IFolderView_Item(fv, 0, &pidl);
    ok(hr == E_INVALIDARG || broken(hr == E_FAIL) /* < Vista */, "got (0x%08x)\n", hr);
    ok(pidl == 0 || broken(pidl == (void*)0xdeadbeef) /* < Vista */, "got %p\n", pidl);

if (0)
{
    /* crashes on Vista and Win2k8 - List not created yet case */
    IFolderView_GetSpacing(fv, &pt);

    /* crashes on XP */
    IFolderView_GetSelectionMarkedItem(fv, NULL);
    IFolderView_GetFocusedItem(fv, NULL);

    /* crashes on Vista+ */
    IFolderView_Item(fv, 0, NULL);
}

    browser = IShellBrowserImpl_Construct();

    settings.ViewMode = FVM_ICON;
    settings.fFlags = 0;
    hwnd_view = (HWND)0xdeadbeef;
    r.left = r.top = 0;
    r.right = r.bottom = 100;
    hr = IShellView_CreateViewWindow(view, NULL, &settings, browser, &r, &hwnd_view);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    ok(IsWindow(hwnd_view), "got %p\n", hwnd_view);

    hwnd_list = subclass_listview(hwnd_view);
    if (!hwnd_list)
    {
        win_skip("Failed to subclass ListView control\n");
        IShellBrowser_Release(browser);
        IFolderView_Release(fv);
        IShellView_Release(view);
        IShellFolder_Release(desktop);
        return;
    }

    /* IFolderView::GetSpacing */
    flush_sequences(sequences, NUM_MSG_SEQUENCES);
    hr = IFolderView_GetSpacing(fv, NULL);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    ok_sequence(sequences, LISTVIEW_SEQ_INDEX, empty_seq, "IFolderView::GetSpacing, empty", FALSE);

    flush_sequences(sequences, NUM_MSG_SEQUENCES);
    hr = IFolderView_GetSpacing(fv, &pt);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    /* fails with empty sequence on win7 for unknown reason */
    if (sequences[LISTVIEW_SEQ_INDEX]->count)
    {
        ok_sequence(sequences, LISTVIEW_SEQ_INDEX, folderview_getspacing_seq, "IFolderView::GetSpacing", FALSE);
        ok(pt.x > 0, "got %d\n", pt.x);
        ok(pt.y > 0, "got %d\n", pt.y);
        ret = SendMessageA(hwnd_list, LVM_GETITEMSPACING, 0, 0);
        ok(pt.x == LOWORD(ret) && pt.y == HIWORD(ret), "got (%d, %d)\n", LOWORD(ret), HIWORD(ret));
    }

    /* IFolderView::ItemCount */
if (0)
{
    /* crashes on XP */
    IFolderView_ItemCount(fv, SVGIO_ALLVIEW, NULL);
}

    flush_sequences(sequences, NUM_MSG_SEQUENCES);
    IFolderView_ItemCount(fv, SVGIO_ALLVIEW, &count);

    /* IFolderView::GetSelectionMarkedItem */
if (0)
{
    /* crashes on XP */
    IFolderView_GetSelectionMarkedItem(fv, NULL);
}

    flush_sequences(sequences, NUM_MSG_SEQUENCES);
    hr = IFolderView_GetSelectionMarkedItem(fv, &ret);
    if (count)
        ok(hr == S_OK, "got (0x%08x)\n", hr);
    else
        ok(hr == S_FALSE, "got (0x%08x)\n", hr);
    ok_sequence(sequences, LISTVIEW_SEQ_INDEX, folderview_getselectionmarked_seq,
                "IFolderView::GetSelectionMarkedItem", FALSE);

    /* IFolderView::GetFocusedItem */
    flush_sequences(sequences, NUM_MSG_SEQUENCES);
    hr = IFolderView_GetFocusedItem(fv, &ret);
    if (count)
        ok(hr == S_OK, "got (0x%08x)\n", hr);
    else
        ok(hr == S_FALSE, "got (0x%08x)\n", hr);
    ok_sequence(sequences, LISTVIEW_SEQ_INDEX, folderview_getfocused_seq,
                "IFolderView::GetFocusedItem", FALSE);

    /* IFolderView::GetFolder, just return pointer */
if (0)
{
    /* crashes on XP */
    IFolderView_GetFolder(fv, NULL, (void**)&folder);
    IFolderView_GetFolder(fv, NULL, NULL);
}

    hr = IFolderView_GetFolder(fv, &IID_IShellFolder, NULL);
    ok(hr == E_POINTER, "got (0x%08x)\n", hr);

    ref1 = IShellFolder_AddRef(desktop);
    IShellFolder_Release(desktop);
    hr = IFolderView_GetFolder(fv, &IID_IShellFolder, (void**)&folder);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    ref2 = IShellFolder_AddRef(desktop);
    IShellFolder_Release(desktop);
    ok(ref1 == ref2 || ref1 + 1 == ref2, /* >= vista */
       "expected same refcount, got %d\n", ref2);
    ok(desktop == folder, "\n");

    IShellBrowser_Release(browser);
    IFolderView_Release(fv);
    IShellView_Release(view);
    IShellFolder_Release(desktop);
}

static void test_GetItemObject(void)
{
    IShellFolder *desktop;
    IShellView *view;
    IUnknown *unk;
    HRESULT hr;

    hr = SHGetDesktopFolder(&desktop);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellFolder_CreateViewObject(desktop, NULL, &IID_IShellView, (void**)&view);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    /* from documentation three interfaces are supported for SVGIO_BACKGROUND:
       IContextMenu, IDispatch, IPersistHistory */
    hr = IShellView_GetItemObject(view, SVGIO_BACKGROUND, &IID_IContextMenu, (void**)&unk);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    IUnknown_Release(unk);

    unk = NULL;
    hr = IShellView_GetItemObject(view, SVGIO_BACKGROUND, &IID_IDispatch, (void**)&unk);
    todo_wine ok(hr == S_OK || broken(hr == E_NOTIMPL) /* NT4 */, "got (0x%08x)\n", hr);
    if (unk) IUnknown_Release(unk);

    unk = NULL;
    hr = IShellView_GetItemObject(view, SVGIO_BACKGROUND, &IID_IPersistHistory, (void**)&unk);
    todo_wine ok(hr == S_OK || broken(hr == E_NOTIMPL) /* W9x, NT4 */, "got (0x%08x)\n", hr);
    if (unk) IUnknown_Release(unk);

    /* example of unsupported interface, base for IPersistHistory */
    hr = IShellView_GetItemObject(view, SVGIO_BACKGROUND, &IID_IPersist, (void**)&unk);
    ok(hr == E_NOINTERFACE || broken(hr == E_NOTIMPL) /* W2K */, "got (0x%08x)\n", hr);

    IShellView_Release(view);
    IShellFolder_Release(desktop);
}

static void test_IShellFolderView(void)
{
    IShellFolderView *folderview;
    IShellFolder *desktop;
    IShellView *view;
    IDataObject *obj;
    UINT i;
    HRESULT hr;

    hr = SHGetDesktopFolder(&desktop);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellFolder_CreateViewObject(desktop, NULL, &IID_IShellView, (void**)&view);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellView_QueryInterface(view, &IID_IShellFolderView, (void**)&folderview);
    if (hr != S_OK)
    {
        win_skip("IShellView doesn't provide IShellFolderView on this platform\n");
        IShellView_Release(view);
        IShellFolder_Release(desktop);
        return;
    }

    /* ::MoveIcons */
    obj = IDataObjectImpl_Construct();
    hr = IShellFolderView_MoveIcons(folderview, obj);
    ok(hr == E_NOTIMPL || broken(hr == S_OK) /* W98 */, "got (0x%08x)\n", hr);
    IDataObject_Release(obj);

    /* ::SetRedraw without list created */
    hr = IShellFolderView_SetRedraw(folderview, TRUE);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    /* ::QuerySupport */
    hr = IShellFolderView_QuerySupport(folderview, NULL);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    i = 0xdeadbeef;
    hr = IShellFolderView_QuerySupport(folderview, &i);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    ok(i == 0xdeadbeef, "got %d\n", i);

    /* ::RemoveObject */
    i = 0xdeadbeef;
    hr = IShellFolderView_RemoveObject(folderview, NULL, &i);
    ok(hr == S_OK || hr == E_FAIL, "got (0x%08x)\n", hr);
    if (hr == S_OK) ok(i == 0 || broken(i == 0xdeadbeef) /* Vista, 2k8 */,
                       "got %d\n", i);

    IShellFolderView_Release(folderview);

    IShellView_Release(view);
    IShellFolder_Release(desktop);
}

static void test_IOleWindow(void)
{
    IShellFolder *desktop;
    IShellView *view;
    HRESULT hr;

    hr = SHGetDesktopFolder(&desktop);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellFolder_CreateViewObject(desktop, NULL, &IID_IShellView, (void**)&view);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    /* IShellView::ContextSensitiveHelp */
    hr = IShellView_ContextSensitiveHelp(view, TRUE);
    ok(hr == E_NOTIMPL, "got (0x%08x)\n", hr);
    hr = IShellView_ContextSensitiveHelp(view, FALSE);
    ok(hr == E_NOTIMPL, "got (0x%08x)\n", hr);

    IShellView_Release(view);
    IShellFolder_Release(desktop);
}

static const struct message folderview_setcurrentviewmode1_2_prevista[] = {
    { LVM_SETVIEW, sent|wparam, LV_VIEW_ICON},
    { LVM_SETIMAGELIST, sent|wparam, 0},
    { LVM_SETIMAGELIST, sent|wparam, 1},
    { 0x105a, sent},
    { LVM_SETBKIMAGEW, sent|optional},    /* w2k3 */
    { LVM_GETBKCOLOR, sent|optional},     /* w2k3 */
    { LVM_GETTEXTBKCOLOR, sent|optional}, /* w2k3 */
    { LVM_GETTEXTCOLOR, sent|optional},   /* w2k3 */
    { LVM_SETEXTENDEDLISTVIEWSTYLE, sent|optional|wparam, 0xc8}, /* w2k3 */
    { LVM_ARRANGE, sent },
    { LVM_ARRANGE, sent|optional },       /* WinXP */
    { 0 }
};

static const struct message folderview_setcurrentviewmode3_prevista[] = {
    { LVM_SETVIEW, sent|wparam, LV_VIEW_LIST},
    { LVM_SETIMAGELIST, sent|wparam, 0},
    { LVM_SETIMAGELIST, sent|wparam, 1},
    { 0x105a, sent},
    { LVM_SETBKIMAGEW, sent|optional},    /* w2k3 */
    { LVM_GETBKCOLOR, sent|optional},     /* w2k3 */
    { LVM_GETTEXTBKCOLOR, sent|optional}, /* w2k3 */
    { LVM_GETTEXTCOLOR, sent|optional},   /* w2k3 */
    { LVM_SETEXTENDEDLISTVIEWSTYLE, sent|optional|wparam, 0xc8}, /* w2k3 */
    { 0 }
};

static const struct message folderview_setcurrentviewmode4_prevista[] = {
    { LVM_GETHEADER, sent},
    { LVM_GETITEMCOUNT, sent|optional },
    { LVM_SETSELECTEDCOLUMN, sent},
    { WM_NOTIFY, sent },
    { WM_NOTIFY, sent },
    { WM_NOTIFY, sent },
    { WM_NOTIFY, sent },
    { LVM_SETVIEW, sent|wparam, LV_VIEW_DETAILS},
    { LVM_SETIMAGELIST, sent|wparam, 0},
    { LVM_SETIMAGELIST, sent|wparam, 1},
    { 0x105a, sent},
    { LVM_SETBKIMAGEW, sent|optional},    /* w2k3 */
    { LVM_GETBKCOLOR, sent|optional},     /* w2k3 */
    { LVM_GETTEXTBKCOLOR, sent|optional}, /* w2k3 */
    { LVM_GETTEXTCOLOR, sent|optional},   /* w2k3 */
    { LVM_SETEXTENDEDLISTVIEWSTYLE, sent|optional|wparam, 0xc8}, /* w2k3 */
    { 0 }
};

/* XP, SetCurrentViewMode(5)
   108e - LVM_SETVIEW (LV_VIEW_ICON);
   1036 - LVM_SETEXTEDEDLISTVIEWSTYLE (0x8000, 0)
   100c/104c repeated X times
   1003 - LVM_SETIMAGELIST
   1035 - LVM_SETICONSPACING
   1004 - LVM_GETITEMCOUNT
   105a - ?
   1016 - LVM_ARRANGE
   1016 - LVM_ARRANGE
*/

/* XP, SetCurrentViewMode(6)
   1036 - LVM_SETEXTENDEDLISTVIEWSTYLE (0x8000, 0)
   1035 - LVM_SETICONSPACING
   1003 - LVM_SETIMAGELIST
   1003 - LVM_SETIMAGELIST
   100c/104c repeated X times
   10a2 - LVM_SETTILEVIEWINFO
   108e - LVM_SETVIEW (LV_VIEW_TILE)
   1003 - LVM_SETIMAGELIST
   105a - ?
   1016 - LVM_ARRANGE
   1016 - LVM_ARRANGE
*/

/* XP, SetCurrentViewMode (7)
   10a2 - LVM_SETTILEVIEWINFO
   108e - LVM_SETVIEW (LV_VIEW_ICON)
   1004/10a4 (LVM_GETITEMCOUNT/LVM_SETTILEINFO) X times
   1016 - LVM_ARRANGE
   1016 - LVM_ARRANGE
   ...
   LVM_SETEXTENDEDLISTVIEWSTYLE (0x40000, 0x40000)
   ...
   LVM_SETEXTENDEDLISTVIEWSTYLE (0x8000, 0x8000)
*/

static void test_GetSetCurrentViewMode(void)
{
    IShellFolder *desktop;
    IShellView *sview;
    IFolderView *fview;
    IShellBrowser *browser;
    FOLDERSETTINGS fs;
    UINT viewmode;
    HWND hwnd;
    RECT rc = {0, 0, 10, 10};
    HRESULT hr;
    UINT i;
    static const int winxp_res[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    static const int win2k3_res[11] = {0, 1, 2, 3, 4, 5, 6, 5, 8, 0, 0};
    static const int vista_res[11] = {0, 1, 5, 3, 4, 5, 6, 7, 7, 0, 0};
    static const int win7_res[11] = {1, 1, 1, 3, 4, 1, 6, 1, 8, 8, 8};

    hr = SHGetDesktopFolder(&desktop);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellFolder_CreateViewObject(desktop, NULL, &IID_IShellView, (void**)&sview);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    fs.ViewMode = 1;
    fs.fFlags = 0;
    browser = IShellBrowserImpl_Construct();
    hr = IShellView_CreateViewWindow(sview, NULL, &fs, browser, &rc, &hwnd);
    ok(hr == S_OK || broken(hr == S_FALSE /*Win2k*/ ), "got (0x%08x)\n", hr);

    hr = IShellView_QueryInterface(sview, &IID_IFolderView, (void**)&fview);
    ok(hr == S_OK || broken(hr == E_NOINTERFACE), "got (0x%08x)\n", hr);
    if(SUCCEEDED(hr))
    {
        HWND hwnd_lv;
        UINT count;

        if (0)
        {
            /* Crashes under Win7/WinXP */
            IFolderView_GetCurrentViewMode(fview, NULL);
        }

        hr = IFolderView_GetCurrentViewMode(fview, &viewmode);
        ok(hr == S_OK, "got (0x%08x)\n", hr);
        ok(viewmode == 1, "ViewMode was %d\n", viewmode);

        hr = IFolderView_SetCurrentViewMode(fview, FVM_AUTO);
        ok(hr == S_OK, "got (0x%08x)\n", hr);

        hr = IFolderView_SetCurrentViewMode(fview, 0);
        ok(hr == E_INVALIDARG || broken(hr == S_OK),
           "got (0x%08x)\n", hr);

        hr = IFolderView_GetCurrentViewMode(fview, &viewmode);
        ok(hr == S_OK, "got (0x%08x)\n", hr);

        for(i = 1; i < 9; i++)
        {
            hr = IFolderView_SetCurrentViewMode(fview, i);
            ok(hr == S_OK || (i == 8 && hr == E_INVALIDARG /*Vista*/),
               "(%d) got (0x%08x)\n", i, hr);

            hr = IFolderView_GetCurrentViewMode(fview, &viewmode);
            ok(hr == S_OK, "(%d) got (0x%08x)\n", i, hr);

            /* Wine currently behaves like winxp here. */
            ok((viewmode == win7_res[i]) || (viewmode == vista_res[i]) ||
               (viewmode == win2k3_res[i]) || (viewmode == winxp_res[i]),
               "(%d) got %d\n",i , viewmode);
        }

        hr = IFolderView_SetCurrentViewMode(fview, 9);
        ok(hr == E_INVALIDARG || broken(hr == S_OK),
           "got (0x%08x)\n", hr);

        /* Test messages */
        hwnd_lv = subclass_listview(hwnd);
        ok(hwnd_lv != NULL, "Failed to subclass listview\n");
        if(hwnd_lv)
        {
            /* Vista seems to set the viewmode by other means than
               sending messages. At least no related messages are
               captured by subclassing.
            */
            BOOL vista_plus = FALSE;
            static const UINT vista_plus_msgs[] = {
                WM_SETREDRAW, WM_NOTIFY, WM_NOTIFYFORMAT, WM_QUERYUISTATE,
                WM_MENUCHAR, WM_WINDOWPOSCHANGING, WM_NCCALCSIZE, WM_WINDOWPOSCHANGED,
                WM_PARENTNOTIFY, LVM_GETHEADER, 0 };

            flush_sequences(sequences, NUM_MSG_SEQUENCES);
            hr = IFolderView_SetCurrentViewMode(fview, 1);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            /* WM_SETREDRAW is not sent in versions before Vista. */
            vista_plus = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, WM_SETREDRAW);
            if(vista_plus)
                verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
            else
                ok_sequence(sequences, LISTVIEW_SEQ_INDEX, folderview_setcurrentviewmode1_2_prevista,
                            "IFolderView::SetCurrentViewMode(1)", TRUE);

            hr = IFolderView_SetCurrentViewMode(fview, 2);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            if(vista_plus)
                verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
            else
                ok_sequence(sequences, LISTVIEW_SEQ_INDEX, folderview_setcurrentviewmode1_2_prevista,
                            "IFolderView::SetCurrentViewMode(2)", TRUE);

            hr = IFolderView_SetCurrentViewMode(fview, 3);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            if(vista_plus)
                verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
            else
                ok_sequence(sequences, LISTVIEW_SEQ_INDEX, folderview_setcurrentviewmode3_prevista,
                            "IFolderView::SetCurrentViewMode(3)", TRUE);

            hr = IFolderView_SetCurrentViewMode(fview, 4);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            if(vista_plus)
                verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
            else
                ok_sequence(sequences, LISTVIEW_SEQ_INDEX, folderview_setcurrentviewmode4_prevista,
                            "IFolderView::SetCurrentViewMode(4)", TRUE);

            hr = IFolderView_SetCurrentViewMode(fview, 5);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            todo_wine
            {
                if(vista_plus)
                {
                    verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
                }
                else
                {
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETVIEW);
                    ok(count == 1, "LVM_SETVIEW sent %d times.\n", count);
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETEXTENDEDLISTVIEWSTYLE);
                    ok(count == 1 || count == 2, "LVM_SETEXTENDEDLISTVIEWSTYLE sent %d times.\n", count);
                    flush_sequences(sequences, NUM_MSG_SEQUENCES);
                }
            }

            hr = IFolderView_SetCurrentViewMode(fview, 6);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            todo_wine
            {
                if(vista_plus)
                {
                    verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
                }
                else
                {
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETVIEW);
                    ok(count == 1, "LVM_SETVIEW sent %d times.\n", count);
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETEXTENDEDLISTVIEWSTYLE);
                    ok(count == 1 || count == 2, "LVM_SETEXTENDEDLISTVIEWSTYLE sent %d times.\n", count);
                    flush_sequences(sequences, NUM_MSG_SEQUENCES);
                }
            }

            hr = IFolderView_SetCurrentViewMode(fview, 7);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            todo_wine
            {
                if(vista_plus)
                {
                    verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
                }
                else
                {
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETVIEW);
                    ok(count == 1, "LVM_SETVIEW sent %d times.\n", count);
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETEXTENDEDLISTVIEWSTYLE);
                    ok(count == 2, "LVM_SETEXTENDEDLISTVIEWSTYLE sent %d times.\n", count);
                    flush_sequences(sequences, NUM_MSG_SEQUENCES);
                }
            }

            hr = IFolderView_SetCurrentViewMode(fview, 8);
            ok(hr == S_OK || broken(hr == E_INVALIDARG /* Vista */), "got 0x%08x\n", hr);
            todo_wine
            {
                if(vista_plus)
                {
                    verify_msgs_in(sequences[LISTVIEW_SEQ_INDEX], vista_plus_msgs);
                }
                else
                {
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETVIEW);
                    ok(count == 1, "LVM_SETVIEW sent %d times.\n", count);
                    count = get_msg_count(sequences, LISTVIEW_SEQ_INDEX, LVM_SETEXTENDEDLISTVIEWSTYLE);
                    ok(count == 2, "LVM_SETEXTENDEDLISTVIEWSTYLE sent %d times.\n", count);
                    flush_sequences(sequences, NUM_MSG_SEQUENCES);
                }
            }

            hr = IFolderView_GetCurrentViewMode(fview, &viewmode);
            ok(hr == S_OK, "Failed to get current viewmode.\n");
            ok_sequence(sequences, LISTVIEW_SEQ_INDEX, empty_seq,
                        "IFolderView::GetCurrentViewMode", FALSE);
        }

        IFolderView_Release(fview);
    }
    else
    {
        skip("No IFolderView for the desktop folder.\n");
    }

    IShellBrowser_Release(browser);
    IShellView_DestroyViewWindow(sview);
    IShellView_Release(sview);
    IShellFolder_Release(desktop);
}

static void test_IOleCommandTarget(void)
{
    IShellFolder *psf_desktop;
    IShellView *psv;
    IOleCommandTarget *poct;
    HRESULT hr;

    hr = SHGetDesktopFolder(&psf_desktop);
    ok(hr == S_OK, "got (0x%08x)\n", hr);

    hr = IShellFolder_CreateViewObject(psf_desktop, NULL, &IID_IShellView, (void**)&psv);
    ok(hr == S_OK, "got (0x%08x)\n", hr);
    if(SUCCEEDED(hr))
    {
        hr = IShellView_QueryInterface(psv, &IID_IOleCommandTarget, (void**)&poct);
        ok(hr == S_OK || broken(hr == E_NOINTERFACE) /* Win95/NT4 */, "Got 0x%08x\n", hr);
        if(SUCCEEDED(hr))
        {
            OLECMD oc;

            hr = IOleCommandTarget_QueryStatus(poct, NULL, 0, NULL, NULL);
            ok(hr == E_INVALIDARG, "Got 0x%08x\n", hr);

            oc.cmdID = 1;
            hr = IOleCommandTarget_QueryStatus(poct, NULL, 0, &oc, NULL);
            ok(hr == OLECMDERR_E_UNKNOWNGROUP, "Got 0x%08x\n", hr);

            oc.cmdID = 1;
            hr = IOleCommandTarget_QueryStatus(poct, NULL, 1, &oc, NULL);
            ok(hr == OLECMDERR_E_UNKNOWNGROUP, "Got 0x%08x\n", hr);

            hr = IOleCommandTarget_Exec(poct, NULL, 0, 0, NULL, NULL);
            ok(hr == OLECMDERR_E_UNKNOWNGROUP, "Got 0x%08x\n", hr);

            IOleCommandTarget_Release(poct);
        }

        IShellView_Release(psv);
    }

    IShellFolder_Release(psf_desktop);
}

START_TEST(shlview)
{
    OleInitialize(NULL);

    init_msg_sequences(sequences, NUM_MSG_SEQUENCES);

    test_IShellView_CreateViewWindow();
    test_IFolderView();
    test_GetItemObject();
    test_IShellFolderView();
    test_IOleWindow();
    test_GetSetCurrentViewMode();
    test_IOleCommandTarget();

    OleUninitialize();
}