/*
 * Common Item Dialog
 *
 * Copyright 2010,2011 David Hedberg
 *
 * 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>

#define COBJMACROS
#define NONAMELESSUNION
#define NONAMELESSSTRUCT

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "wingdi.h"
#include "winreg.h"
#include "shlwapi.h"

#include "commdlg.h"
#include "cdlg.h"
#include "filedlgbrowser.h"

#include "wine/debug.h"
#include "wine/list.h"

#define IDC_NAV_TOOLBAR      200
#define IDC_NAVBACK          201
#define IDC_NAVFORWARD       202

#include <initguid.h>
/* This seems to be another version of IID_IFileDialogCustomize. If
 * there is any difference I have yet to find it. */
DEFINE_GUID(IID_IFileDialogCustomizeAlt, 0x8016B7B3, 0x3D49, 0x4504, 0xA0,0xAA, 0x2A,0x37,0x49,0x4E,0x60,0x6F);

WINE_DEFAULT_DEBUG_CHANNEL(commdlg);

static const WCHAR notifysink_childW[] = {'n','f','s','_','c','h','i','l','d',0};
static const WCHAR floatnotifysinkW[] = {'F','l','o','a','t','N','o','t','i','f','y','S','i','n','k',0};

enum ITEMDLG_TYPE {
    ITEMDLG_TYPE_OPEN,
    ITEMDLG_TYPE_SAVE
};

enum ITEMDLG_CCTRL_TYPE {
    IDLG_CCTRL_MENU,
    IDLG_CCTRL_PUSHBUTTON,
    IDLG_CCTRL_COMBOBOX,
    IDLG_CCTRL_RADIOBUTTONLIST,
    IDLG_CCTRL_CHECKBUTTON,
    IDLG_CCTRL_EDITBOX,
    IDLG_CCTRL_SEPARATOR,
    IDLG_CCTRL_TEXT
};

typedef struct {
    HWND hwnd, wrapper_hwnd;
    UINT id, dlgid;
    enum ITEMDLG_CCTRL_TYPE type;
    CDCONTROLSTATEF cdcstate;
    struct list entry;
} customctrl;

typedef struct {
    struct list entry;
    IFileDialogEvents *pfde;
    DWORD cookie;
} events_client;

typedef struct FileDialogImpl {
    IFileDialog2 IFileDialog2_iface;
    union {
        IFileOpenDialog IFileOpenDialog_iface;
        IFileSaveDialog IFileSaveDialog_iface;
    } u;
    enum ITEMDLG_TYPE dlg_type;
    IExplorerBrowserEvents IExplorerBrowserEvents_iface;
    IServiceProvider       IServiceProvider_iface;
    ICommDlgBrowser3       ICommDlgBrowser3_iface;
    IOleWindow             IOleWindow_iface;
    IFileDialogCustomize   IFileDialogCustomize_iface;
    LONG ref;

    FILEOPENDIALOGOPTIONS options;
    COMDLG_FILTERSPEC *filterspecs;
    UINT filterspec_count;
    UINT filetypeindex;

    struct list events_clients;
    DWORD events_next_cookie;

    IShellItemArray *psia_selection;
    IShellItemArray *psia_results;
    IShellItem *psi_defaultfolder;
    IShellItem *psi_setfolder;
    IShellItem *psi_folder;

    HWND dlg_hwnd;
    IExplorerBrowser *peb;
    DWORD ebevents_cookie;

    LPWSTR set_filename;
    LPWSTR default_ext;
    LPWSTR custom_title;
    LPWSTR custom_okbutton;
    LPWSTR custom_cancelbutton;
    LPWSTR custom_filenamelabel;

    UINT cctrl_width, cctrl_def_height, cctrls_cols;
    HWND cctrls_hwnd;
    struct list cctrls;
    UINT_PTR cctrl_next_dlgid;
} FileDialogImpl;

/**************************************************************************
 * Event wrappers.
 */
static HRESULT events_OnFileOk(FileDialogImpl *This)
{
    events_client *cursor;
    HRESULT hr = S_OK;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        TRACE("Notifying %p\n", cursor);
        hr = IFileDialogEvents_OnFileOk(cursor->pfde, (IFileDialog*)&This->IFileDialog2_iface);
        if(FAILED(hr) && hr != E_NOTIMPL)
            break;
    }

    if(hr == E_NOTIMPL)
        hr = S_OK;

    return hr;
}

static HRESULT events_OnFolderChanging(FileDialogImpl *This, IShellItem *folder)
{
    events_client *cursor;
    HRESULT hr = S_OK;
    TRACE("%p (%p)\n", This, folder);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        TRACE("Notifying %p\n", cursor);
        hr = IFileDialogEvents_OnFolderChanging(cursor->pfde, (IFileDialog*)&This->IFileDialog2_iface, folder);
        if(FAILED(hr) && hr != E_NOTIMPL)
            break;
    }

    if(hr == E_NOTIMPL)
        hr = S_OK;

    return hr;
}

static void events_OnFolderChange(FileDialogImpl *This)
{
    events_client *cursor;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        TRACE("Notifying %p\n", cursor);
        IFileDialogEvents_OnFolderChange(cursor->pfde, (IFileDialog*)&This->IFileDialog2_iface);
    }
}

static void events_OnSelectionChange(FileDialogImpl *This)
{
    events_client *cursor;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        TRACE("Notifying %p\n", cursor);
        IFileDialogEvents_OnSelectionChange(cursor->pfde, (IFileDialog*)&This->IFileDialog2_iface);
    }
}

static inline HRESULT get_cctrl_event(IFileDialogEvents *pfde, IFileDialogControlEvents **pfdce)
{
    return IFileDialogEvents_QueryInterface(pfde, &IID_IFileDialogControlEvents, (void**)pfdce);
}

static HRESULT cctrl_event_OnButtonClicked(FileDialogImpl *This, DWORD ctl_id)
{
    events_client *cursor;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        IFileDialogControlEvents *pfdce;
        if(SUCCEEDED(get_cctrl_event(cursor->pfde, &pfdce)))
        {
            TRACE("Notifying %p\n", cursor);
            IFileDialogControlEvents_OnButtonClicked(pfdce, &This->IFileDialogCustomize_iface, ctl_id);
            IFileDialogControlEvents_Release(pfdce);
        }
    }

    return S_OK;
}

static HRESULT cctrl_event_OnItemSelected(FileDialogImpl *This, DWORD ctl_id, DWORD item_id)
{
    events_client *cursor;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        IFileDialogControlEvents *pfdce;
        if(SUCCEEDED(get_cctrl_event(cursor->pfde, &pfdce)))
        {
            TRACE("Notifying %p\n", cursor);
            IFileDialogControlEvents_OnItemSelected(pfdce, &This->IFileDialogCustomize_iface, ctl_id, item_id);
            IFileDialogControlEvents_Release(pfdce);
        }
    }

    return S_OK;
}

static HRESULT cctrl_event_OnCheckButtonToggled(FileDialogImpl *This, DWORD ctl_id, BOOL checked)
{
    events_client *cursor;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        IFileDialogControlEvents *pfdce;
        if(SUCCEEDED(get_cctrl_event(cursor->pfde, &pfdce)))
        {
            TRACE("Notifying %p\n", cursor);
            IFileDialogControlEvents_OnCheckButtonToggled(pfdce, &This->IFileDialogCustomize_iface, ctl_id, checked);
            IFileDialogControlEvents_Release(pfdce);
        }
    }

    return S_OK;
}

static HRESULT cctrl_event_OnControlActivating(FileDialogImpl *This,
                                                  DWORD ctl_id)
{
    events_client *cursor;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY(cursor, &This->events_clients, events_client, entry)
    {
        IFileDialogControlEvents *pfdce;
        if(SUCCEEDED(get_cctrl_event(cursor->pfde, &pfdce)))
        {
            TRACE("Notifying %p\n", cursor);
            IFileDialogControlEvents_OnControlActivating(pfdce, &This->IFileDialogCustomize_iface, ctl_id);
            IFileDialogControlEvents_Release(pfdce);
        }
    }

    return S_OK;
}

/**************************************************************************
 * Helper functions.
 */
static UINT get_file_name(FileDialogImpl *This, LPWSTR *str)
{
    HWND hwnd_edit = GetDlgItem(This->dlg_hwnd, IDC_FILENAME);
    UINT len;

    if(!hwnd_edit)
    {
        if(This->set_filename)
        {
            len = lstrlenW(This->set_filename);
            *str = CoTaskMemAlloc(sizeof(WCHAR)*(len+1));
            lstrcpyW(*str, This->set_filename);
            return len;
        }
        return FALSE;
    }

    len = SendMessageW(hwnd_edit, WM_GETTEXTLENGTH, 0, 0);
    *str = CoTaskMemAlloc(sizeof(WCHAR)*(len+1));
    if(!*str)
        return FALSE;

    SendMessageW(hwnd_edit, WM_GETTEXT, len+1, (LPARAM)*str);
    return len;
}

static BOOL set_file_name(FileDialogImpl *This, LPCWSTR str)
{
    HWND hwnd_edit = GetDlgItem(This->dlg_hwnd, IDC_FILENAME);

    if(This->set_filename)
        LocalFree(This->set_filename);

    This->set_filename = StrDupW(str);

    return SendMessageW(hwnd_edit, WM_SETTEXT, 0, (LPARAM)str);
}

static void fill_filename_from_selection(FileDialogImpl *This)
{
    IShellItem *psi;
    LPWSTR *names;
    HRESULT hr;
    UINT item_count, valid_count;
    UINT len_total, i;

    if(!This->psia_selection)
        return;

    hr = IShellItemArray_GetCount(This->psia_selection, &item_count);
    if(FAILED(hr) || !item_count)
        return;

    names = HeapAlloc(GetProcessHeap(), 0, item_count*sizeof(LPWSTR));

    /* Get names of the selected items */
    valid_count = 0; len_total = 0;
    for(i = 0; i < item_count; i++)
    {
        hr = IShellItemArray_GetItemAt(This->psia_selection, i, &psi);
        if(SUCCEEDED(hr))
        {
            UINT attr;

            hr = IShellItem_GetAttributes(psi, SFGAO_FOLDER, &attr);
            if(SUCCEEDED(hr) && (attr & SFGAO_FOLDER))
                continue; /* FIXME: FOS_PICKFOLDERS */

            hr = IShellItem_GetDisplayName(psi, SIGDN_PARENTRELATIVEPARSING, &names[valid_count]);
            if(SUCCEEDED(hr))
            {
                len_total += lstrlenW(names[valid_count]) + 3;
                valid_count++;
            }
            IShellItem_Release(psi);
        }
    }

    if(valid_count == 1)
    {
        set_file_name(This, names[0]);
        CoTaskMemFree(names[0]);
    }
    else if(valid_count > 1)
    {
        LPWSTR string = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*len_total);
        LPWSTR cur_point = string;

        for(i = 0; i < valid_count; i++)
        {
            LPWSTR file = names[i];
            *cur_point++ = '\"';
            lstrcpyW(cur_point, file);
            cur_point += lstrlenW(file);
            *cur_point++ = '\"';
            *cur_point++ = ' ';
            CoTaskMemFree(file);
        }
        *(cur_point-1) = '\0';

        set_file_name(This, string);
        HeapFree(GetProcessHeap(), 0, string);
    }

    HeapFree(GetProcessHeap(), 0, names);
    return;
}

static LPWSTR get_first_ext_from_spec(LPWSTR buf, LPCWSTR spec)
{
    WCHAR *endpos, *ext;

    lstrcpyW(buf, spec);
    if( (endpos = StrChrW(buf, ';')) )
        *endpos = '\0';

    ext = PathFindExtensionW(buf);
    if(StrChrW(ext, '*'))
        return NULL;

    return ext;
}

static HRESULT on_default_action(FileDialogImpl *This)
{
    IShellFolder *psf_parent, *psf_desktop;
    LPITEMIDLIST *pidla;
    LPITEMIDLIST current_folder;
    LPWSTR fn_iter, files, tmp_files;
    UINT file_count = 0, len, i;
    int open_action;
    HRESULT hr, ret = E_FAIL;

    len = get_file_name(This, &tmp_files);
    if(len)
    {
        UINT size_used;
        file_count = COMDLG32_SplitFileNames(tmp_files, len, &files, &size_used);
    }
    if(!file_count) return E_FAIL;

    hr = SHGetIDListFromObject((IUnknown*)This->psi_folder, &current_folder);
    if(FAILED(hr))
    {
        ERR("Failed to get pidl for current directory.\n");
        return hr;
    }

    TRACE("Acting on %d file(s).\n", file_count);

    pidla = HeapAlloc(GetProcessHeap(), 0, sizeof(LPITEMIDLIST) * file_count);
    open_action = ONOPEN_OPEN;
    fn_iter = files;

    for(i = 0; i < file_count && open_action == ONOPEN_OPEN; i++)
    {
        WCHAR canon_filename[MAX_PATH];
        psf_parent = NULL;

        COMDLG32_GetCanonicalPath(current_folder, fn_iter, canon_filename);

        if( (This->options & FOS_NOVALIDATE) &&
            !(This->options & FOS_FILEMUSTEXIST) )
            open_action = ONOPEN_OPEN;
        else
            open_action = ONOPEN_BROWSE;

        open_action = FILEDLG95_ValidatePathAction(canon_filename, &psf_parent, This->dlg_hwnd,
                                                   This->options & ~FOS_FILEMUSTEXIST,
                                                   (This->dlg_type == ITEMDLG_TYPE_SAVE),
                                                   open_action);

        /* Add the proper extension */
        if(open_action == ONOPEN_OPEN)
        {
            static const WCHAR dotW[] = {'.',0};

            if(This->dlg_type == ITEMDLG_TYPE_SAVE)
            {
                WCHAR extbuf[MAX_PATH], *newext = NULL;

                if(This->filterspec_count)
                {
                    newext = get_first_ext_from_spec(extbuf, This->filterspecs[This->filetypeindex].pszSpec);
                }
                else if(This->default_ext)
                {
                    lstrcpyW(extbuf, dotW);
                    lstrcatW(extbuf, This->default_ext);
                    newext = extbuf;
                }

                if(newext)
                {
                    WCHAR *ext = PathFindExtensionW(canon_filename);
                    if(lstrcmpW(ext, newext))
                        lstrcatW(canon_filename, newext);
                }
            }
            else
            {
                if( !(This->options & FOS_NOVALIDATE) && (This->options & FOS_FILEMUSTEXIST) &&
                    !PathFileExistsW(canon_filename))
                {
                    if(This->default_ext)
                    {
                        lstrcatW(canon_filename, dotW);
                        lstrcatW(canon_filename, This->default_ext);

                        if(!PathFileExistsW(canon_filename))
                        {
                            FILEDLG95_OnOpenMessage(This->dlg_hwnd, 0, IDS_FILENOTEXISTING);
                            open_action = ONOPEN_BROWSE;
                        }
                    }
                    else
                    {
                        FILEDLG95_OnOpenMessage(This->dlg_hwnd, 0, IDS_FILENOTEXISTING);
                        open_action = ONOPEN_BROWSE;
                    }
                }
            }
        }

        pidla[i] = COMDLG32_SHSimpleIDListFromPathAW(canon_filename);

        if(psf_parent && !(open_action == ONOPEN_BROWSE))
            IShellItem_Release(psf_parent);

        fn_iter += (WCHAR)lstrlenW(fn_iter) + 1;
    }

    HeapFree(GetProcessHeap(), 0, files);
    ILFree(current_folder);

    if((This->options & FOS_PICKFOLDERS) && open_action == ONOPEN_BROWSE)
        open_action = ONOPEN_OPEN; /* FIXME: Multiple folders? */

    switch(open_action)
    {
    case ONOPEN_SEARCH:
        FIXME("Filtering not implemented.\n");
        break;

    case ONOPEN_BROWSE:
        hr = IExplorerBrowser_BrowseToObject(This->peb, (IUnknown*)psf_parent, SBSP_DEFBROWSER);
        if(FAILED(hr))
            ERR("Failed to browse to directory: %08x\n", hr);

        IShellItem_Release(psf_parent);
        break;

    case ONOPEN_OPEN:
        if(events_OnFileOk(This) != S_OK)
            break;

        hr = SHGetDesktopFolder(&psf_desktop);
        if(SUCCEEDED(hr))
        {
            if(This->psia_results)
                IShellItemArray_Release(This->psia_results);

            hr = SHCreateShellItemArray(NULL, psf_desktop, file_count, (PCUITEMID_CHILD_ARRAY)pidla,
                                        &This->psia_results);
            if(SUCCEEDED(hr))
                ret = S_OK;

            IShellFolder_Release(psf_desktop);
        }
        break;

    default:
        ERR("Failed.\n");
        break;
    }

    /* Clean up */
    for(i = 0; i < file_count; i++)
        ILFree(pidla[i]);
    HeapFree(GetProcessHeap(), 0, pidla);

    /* Success closes the dialog */
    return ret;
}

/**************************************************************************
 * Control functions.
 */
static inline customctrl *get_cctrl_from_dlgid(FileDialogImpl *This, DWORD dlgid)
{
    customctrl *ctrl;

    LIST_FOR_EACH_ENTRY(ctrl, &This->cctrls, customctrl, entry)
        if(ctrl->dlgid == dlgid)
            return ctrl;

    ERR("Failed to find control with dialog id %d\n", dlgid);
    return NULL;
}

static inline customctrl *get_cctrl(FileDialogImpl *This, DWORD ctlid)
{
    customctrl *ctrl;

    LIST_FOR_EACH_ENTRY(ctrl, &This->cctrls, customctrl, entry)
        if(ctrl->id == ctlid)
            return ctrl;

    ERR("Failed to find control with control id %d\n", ctlid);
    return NULL;
}

static void ctrl_resize(HWND hctrl, UINT min_width, UINT max_width, BOOL multiline)
{
    LPWSTR text;
    UINT len, final_width;
    UINT lines, final_height;
    SIZE size;
    RECT rc;
    HDC hdc;
    WCHAR *c;

    TRACE("\n");

    len = SendMessageW(hctrl, WM_GETTEXTLENGTH, 0, 0);
    text = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*(len+1));
    if(!text) return;
    SendMessageW(hctrl, WM_GETTEXT, len+1, (LPARAM)text);

    hdc = GetDC(hctrl);
    GetTextExtentPoint32W(hdc, text, lstrlenW(text), &size);
    ReleaseDC(hctrl, hdc);

    if(len && multiline)
    {
        /* FIXME: line-wrap */
        for(lines = 1, c = text; *c != '\0'; c++)
            if(*c == '\n') lines++;

        final_height = size.cy*lines + 2*4;
    }
    else
    {
        GetWindowRect(hctrl, &rc);
        final_height = rc.bottom - rc.top;
    }

    final_width = min(max(size.cx, min_width) + 4, max_width);
    SetWindowPos(hctrl, NULL, 0, 0, final_width, final_height,
                 SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);

    HeapFree(GetProcessHeap(), 0, text);
}

static void customctrl_resize(FileDialogImpl *This, customctrl *ctrl)
{
    RECT rc;

    switch(ctrl->type)
    {
    case IDLG_CCTRL_PUSHBUTTON:
    case IDLG_CCTRL_COMBOBOX:
    case IDLG_CCTRL_CHECKBUTTON:
    case IDLG_CCTRL_TEXT:
        ctrl_resize(ctrl->hwnd, 160, 160, TRUE);
        GetWindowRect(ctrl->hwnd, &rc);
        SetWindowPos(ctrl->wrapper_hwnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
                     SWP_NOZORDER|SWP_NOMOVE|SWP_NOZORDER);
        break;
    case IDLG_CCTRL_RADIOBUTTONLIST:
    case IDLG_CCTRL_EDITBOX:
    case IDLG_CCTRL_SEPARATOR:
    case IDLG_CCTRL_MENU:
        /* Nothing */
        break;
    }
}

static LRESULT notifysink_on_create(HWND hwnd, CREATESTRUCTW *crs)
{
    FileDialogImpl *This = crs->lpCreateParams;
    TRACE("%p\n", This);

    SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LPARAM)This);
    return TRUE;
}

static LRESULT notifysink_on_bn_clicked(FileDialogImpl *This, HWND hwnd, WPARAM wparam)
{
    customctrl *ctrl = get_cctrl_from_dlgid(This, LOWORD(wparam));

    TRACE("%p, %lx\n", This, wparam);

    if(ctrl)
    {
        if(ctrl->type == IDLG_CCTRL_CHECKBUTTON)
        {
            BOOL checked = (SendMessageW(ctrl->hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED);
            cctrl_event_OnCheckButtonToggled(This, ctrl->id, checked);
        }
        else
            cctrl_event_OnButtonClicked(This, ctrl->id);
    }

    return TRUE;
}

static LRESULT notifysink_on_cbn_selchange(FileDialogImpl *This, HWND hwnd, WPARAM wparam)
{
    customctrl *ctrl = get_cctrl_from_dlgid(This, LOWORD(wparam));
    TRACE("%p, %p (%lx)\n", This, ctrl, wparam);

    if(ctrl)
    {
        UINT index = SendMessageW(ctrl->hwnd, CB_GETCURSEL, 0, 0);
        UINT selid = SendMessageW(ctrl->hwnd, CB_GETITEMDATA, index, 0);

        cctrl_event_OnItemSelected(This, ctrl->id, selid);
    }
    return TRUE;
}

static LRESULT notifysink_on_tvn_dropdown(FileDialogImpl *This, LPARAM lparam)
{
    NMTOOLBARW *nmtb = (NMTOOLBARW*)lparam;
    customctrl *ctrl = get_cctrl_from_dlgid(This, GetDlgCtrlID(nmtb->hdr.hwndFrom));
    POINT pt = { 0, nmtb->rcButton.bottom };
    TBBUTTON tbb;
    UINT idcmd;

    TRACE("%p, %p (%lx)\n", This, ctrl, lparam);

    if(ctrl)
    {
        cctrl_event_OnControlActivating(This,ctrl->id);

        SendMessageW(ctrl->hwnd, TB_GETBUTTON, 0, (LPARAM)&tbb);
        ClientToScreen(ctrl->hwnd, &pt);
        idcmd = TrackPopupMenu((HMENU)tbb.dwData, TPM_RETURNCMD, pt.x, pt.y, 0, This->dlg_hwnd, NULL);
        if(idcmd)
            cctrl_event_OnItemSelected(This, ctrl->id, idcmd);
    }

    return TBDDRET_DEFAULT;
}

static LRESULT notifysink_on_wm_command(FileDialogImpl *This, HWND hwnd, WPARAM wparam, LPARAM lparam)
{
    switch(HIWORD(wparam))
    {
    case BN_CLICKED:          return notifysink_on_bn_clicked(This, hwnd, wparam);
    case CBN_SELCHANGE:       return notifysink_on_cbn_selchange(This, hwnd, wparam);
    }

    return FALSE;
}

static LRESULT notifysink_on_wm_notify(FileDialogImpl *This, HWND hwnd, WPARAM wparam, LPARAM lparam)
{
    NMHDR *nmhdr = (NMHDR*)lparam;

    switch(nmhdr->code)
    {
    case TBN_DROPDOWN:        return notifysink_on_tvn_dropdown(This, lparam);
    }

    return FALSE;
}

static LRESULT CALLBACK notifysink_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    FileDialogImpl *This = (FileDialogImpl*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
    HWND hwnd_child;
    RECT rc;

    switch(message)
    {
    case WM_NCCREATE:         return notifysink_on_create(hwnd, (CREATESTRUCTW*)lparam);
    case WM_COMMAND:          return notifysink_on_wm_command(This, hwnd, wparam, lparam);
    case WM_NOTIFY:           return notifysink_on_wm_notify(This, hwnd, wparam, lparam);
    case WM_SIZE:
        hwnd_child = GetPropW(hwnd, notifysink_childW);
        GetClientRect(hwnd, &rc);
        SetWindowPos(hwnd_child, NULL, 0, 0, rc.right, rc.bottom, SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
        return TRUE;
    }

    return DefWindowProcW(hwnd, message, wparam, lparam);
}

static HRESULT cctrl_create_new(FileDialogImpl *This, DWORD id,
                                LPCWSTR text, LPCWSTR wndclass, DWORD ctrl_wsflags,
                                DWORD ctrl_exflags, UINT height, customctrl **ppctrl)
{
    HWND ns_hwnd, control_hwnd;
    DWORD wsflags = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS;
    customctrl *ctrl;

    if(get_cctrl(This, id))
        return E_UNEXPECTED; /* Duplicate id */

    ns_hwnd = CreateWindowExW(0, floatnotifysinkW, NULL, wsflags,
                              0, 0, This->cctrl_width, height, This->cctrls_hwnd,
                              (HMENU)This->cctrl_next_dlgid, COMDLG32_hInstance, This);
    control_hwnd = CreateWindowExW(ctrl_exflags, wndclass, text, wsflags | ctrl_wsflags,
                                   0, 0, This->cctrl_width, height, ns_hwnd,
                                   (HMENU)This->cctrl_next_dlgid, COMDLG32_hInstance, 0);

    if(!ns_hwnd || !control_hwnd)
    {
        ERR("Failed to create wrapper (%p) or control (%p)\n", ns_hwnd, control_hwnd);
        DestroyWindow(ns_hwnd);
        DestroyWindow(control_hwnd);

        return E_FAIL;
    }

    SetPropW(ns_hwnd, notifysink_childW, control_hwnd);

    ctrl = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(customctrl));
    if(!ctrl)
        return E_OUTOFMEMORY;

    ctrl->hwnd = control_hwnd;
    ctrl->wrapper_hwnd = ns_hwnd;
    ctrl->id = id;
    ctrl->dlgid = This->cctrl_next_dlgid;
    ctrl->cdcstate = CDCS_ENABLED | CDCS_VISIBLE;
    list_add_tail(&This->cctrls, &ctrl->entry);
    if(ppctrl) *ppctrl = ctrl;

    This->cctrl_next_dlgid++;
    return S_OK;
}

/**************************************************************************
 * Container functions.
 */
static UINT ctrl_container_resize(FileDialogImpl *This, UINT container_width)
{
    UINT container_height;
    UINT column_width;
    UINT nr_of_cols;
    UINT max_control_height, total_height = 0;
    UINT cur_col_pos, cur_row_pos;
    customctrl *ctrl;
    BOOL fits_height;
    static const UINT col_indent = 100; /* The first column is indented 100px */
    static const UINT cspacing = 90;    /* Columns are spaced with 90px */
    static const UINT rspacing = 4;     /* Rows are spaced with 4 px. */

    /* Given the new width of the container, this function determines the
     * needed height of the container and places the controls according to
     * the new layout. Returns the new height.
     */

    TRACE("%p\n", This);

    column_width = This->cctrl_width + cspacing;
    nr_of_cols = (container_width - col_indent + cspacing) / column_width;

    /* We don't need to do anything unless the number of visible columns has changed. */
    if(nr_of_cols == This->cctrls_cols)
    {
        RECT rc;
        GetWindowRect(This->cctrls_hwnd, &rc);
        return rc.bottom - rc.top;
    }

   This->cctrls_cols = nr_of_cols;

    /* Get the size of the tallest control, and the total size of
     * all the controls to figure out the number of slots we need.
     */
    max_control_height = 0;
    LIST_FOR_EACH_ENTRY(ctrl, &This->cctrls, customctrl, entry)
    {
        if(ctrl->cdcstate & CDCS_VISIBLE)
        {
            RECT rc;
            UINT control_height;
            GetWindowRect(ctrl->wrapper_hwnd, &rc);
            control_height = rc.bottom - rc.top;
            max_control_height = max(max_control_height, control_height);

            total_height +=  control_height + rspacing;
        }
    }

    if(!total_height)
        return 0;

    container_height = max(total_height / nr_of_cols, max_control_height + rspacing);
    TRACE("Guess: container_height: %d\n",container_height);

    /* Incrementally increase container_height until all the controls
     * fit.
     */
    do {
        UINT columns_needed = 1;
        cur_row_pos = 0;

        fits_height = TRUE;
        LIST_FOR_EACH_ENTRY(ctrl, &This->cctrls, customctrl, entry)
        {
            if(ctrl->cdcstate & CDCS_VISIBLE)
            {
                RECT rc;
                UINT control_height;
                GetWindowRect(ctrl->wrapper_hwnd, &rc);
                control_height = rc.bottom - rc.top;

                if(cur_row_pos + control_height > container_height)
                {
                    if(++columns_needed > nr_of_cols)
                    {
                        container_height += 1;
                        fits_height = FALSE;
                        break;
                    }
                    cur_row_pos = 0;
                }

                cur_row_pos += control_height + rspacing;
            }
        }
    } while(!fits_height);

    TRACE("Final container height: %d\n", container_height);

    /* Move the controls to their final destination
     */
    cur_col_pos = col_indent, cur_row_pos = 0;
    LIST_FOR_EACH_ENTRY(ctrl, &This->cctrls, customctrl, entry)
    {
        if(ctrl->cdcstate & CDCS_VISIBLE)
        {
            RECT rc;
            UINT control_height;
            GetWindowRect(ctrl->wrapper_hwnd, &rc);
            control_height = rc.bottom - rc.top;

            if(cur_row_pos + control_height > container_height)
            {
                cur_row_pos = 0;
                cur_col_pos += This->cctrl_width + cspacing;
            }

            SetWindowPos(ctrl->wrapper_hwnd, NULL, cur_col_pos, cur_row_pos, 0, 0,
                         SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);

            cur_row_pos += control_height + rspacing;
        }
    }

    /* Sanity check */
    if(cur_row_pos + This->cctrl_width > container_width)
        ERR("-- Failed to place controls properly.\n");

    return container_height;
}

static void ctrl_container_reparent(FileDialogImpl *This, HWND parent)
{
    LONG wndstyle;

    if(parent)
    {
        customctrl *ctrl;
        HFONT font;

        wndstyle = GetWindowLongW(This->cctrls_hwnd, GWL_STYLE);
        wndstyle &= ~(WS_POPUP);
        wndstyle |= WS_CHILD;
        SetWindowLongW(This->cctrls_hwnd, GWL_STYLE, wndstyle);

        SetParent(This->cctrls_hwnd, parent);
        ShowWindow(This->cctrls_hwnd, TRUE);

        /* Set the fonts to match the dialog font. */
        font = (HFONT)SendMessageW(parent, WM_GETFONT, 0, 0);
        if(!font)
            ERR("Failed to get font handle from dialog.\n");

        LIST_FOR_EACH_ENTRY(ctrl, &This->cctrls, customctrl, entry)
        {
            if(font) SendMessageW(ctrl->hwnd, WM_SETFONT, (WPARAM)font, TRUE);
            customctrl_resize(This, ctrl);
        }
    }
    else
    {
        ShowWindow(This->cctrls_hwnd, FALSE);

        wndstyle = GetWindowLongW(This->cctrls_hwnd, GWL_STYLE);
        wndstyle &= ~(WS_CHILD);
        wndstyle |= WS_POPUP;
        SetWindowLongW(This->cctrls_hwnd, GWL_STYLE, wndstyle);

        SetParent(This->cctrls_hwnd, NULL);
    }
}

static LRESULT ctrl_container_on_create(HWND hwnd, CREATESTRUCTW *crs)
{
    FileDialogImpl *This = crs->lpCreateParams;
    TRACE("%p\n", This);

    SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LPARAM)This);
    return TRUE;
}

static LRESULT ctrl_container_on_wm_destroy(FileDialogImpl *This)
{
    customctrl *cur1, *cur2;
    TRACE("%p\n", This);

    LIST_FOR_EACH_ENTRY_SAFE(cur1, cur2, &This->cctrls, customctrl, entry)
    {
        TRACE("Freeing control %p\n", cur1);
        list_remove(&cur1->entry);

        if(cur1->type == IDLG_CCTRL_MENU)
        {
            TBBUTTON tbb;
            SendMessageW(cur1->hwnd, TB_GETBUTTON, 0, (LPARAM)&tbb);
            DestroyMenu((HMENU)tbb.dwData);
        }

        DestroyWindow(cur1->hwnd);
        HeapFree(GetProcessHeap(), 0, cur1);
    }

    return TRUE;
}

static LRESULT CALLBACK ctrl_container_wndproc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
    FileDialogImpl *This = (FileDialogImpl*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);

    switch(umessage)
    {
    case WM_NCCREATE:         return ctrl_container_on_create(hwnd, (CREATESTRUCTW*)lparam);
    case WM_DESTROY:          return ctrl_container_on_wm_destroy(This);
    default:                  return DefWindowProcW(hwnd, umessage, wparam, lparam);
    }

    return FALSE;
}

static HRESULT init_custom_controls(FileDialogImpl *This)
{
    WNDCLASSW wc;
    static const WCHAR ctrl_container_classname[] =
        {'i','d','l','g','_','c','o','n','t','a','i','n','e','r','_','p','a','n','e',0};

    InitCommonControlsEx(NULL);

    This->cctrl_width = 160;      /* Controls have a fixed width */
    This->cctrl_def_height = 23;
    This->cctrls_cols = 0;

    This->cctrl_next_dlgid = 0x2000;
    list_init(&This->cctrls);

    if( !GetClassInfoW(COMDLG32_hInstance, ctrl_container_classname, &wc) )
    {
        wc.style            = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc      = ctrl_container_wndproc;
        wc.cbClsExtra       = 0;
        wc.cbWndExtra       = 0;
        wc.hInstance        = COMDLG32_hInstance;
        wc.hIcon            = 0;
        wc.hCursor          = LoadCursorW(0, (LPWSTR)IDC_ARROW);
        wc.hbrBackground    = (HBRUSH)(COLOR_BTNFACE + 1);
        wc.lpszMenuName     = NULL;
        wc.lpszClassName    = ctrl_container_classname;

        if(!RegisterClassW(&wc)) return E_FAIL;
    }

    This->cctrls_hwnd = CreateWindowExW(0, ctrl_container_classname, NULL,
                                        WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
                                        0, 0, 0, 0, NULL, 0,
                                        COMDLG32_hInstance, (void*)This);
    if(!This->cctrls_hwnd)
        return E_FAIL;

    SetWindowLongW(This->cctrls_hwnd, GWL_STYLE, WS_TABSTOP);

    /* Register class for  */
    if( !GetClassInfoW(COMDLG32_hInstance, floatnotifysinkW, &wc) ||
        wc.hInstance != COMDLG32_hInstance)
    {
        wc.style            = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc      = notifysink_proc;
        wc.cbClsExtra       = 0;
        wc.cbWndExtra       = 0;
        wc.hInstance        = COMDLG32_hInstance;
        wc.hIcon            = 0;
        wc.hCursor          = LoadCursorW(0, (LPWSTR)IDC_ARROW);
        wc.hbrBackground    = (HBRUSH)(COLOR_BTNFACE + 1);
        wc.lpszMenuName     = NULL;
        wc.lpszClassName    = floatnotifysinkW;

        if (!RegisterClassW(&wc))
            ERR("Failed to register FloatNotifySink window class.\n");
    }

    return S_OK;
}

/**************************************************************************
 * Window related functions.
 */
static SIZE update_layout(FileDialogImpl *This)
{
    HDWP hdwp;
    HWND hwnd;
    RECT dialog_rc;
    RECT cancel_rc, open_rc;
    RECT filetype_rc, filename_rc, filenamelabel_rc;
    RECT toolbar_rc, ebrowser_rc, customctrls_rc;
    int missing_width, missing_height;
    static const UINT vspacing = 4, hspacing = 4;
    SIZE ret;

    GetClientRect(This->dlg_hwnd, &dialog_rc);

    missing_width = max(0, 320 - dialog_rc.right);
    missing_height = max(0, 200 - dialog_rc.bottom);

    if(missing_width || missing_height)
    {
        TRACE("Missing (%d, %d)\n", missing_width, missing_height);
        ret.cx = missing_width;
        ret.cy = missing_height;
        return ret;
    }

    /****
     * Calculate the size of the dialog and all the parts.
     */

    /* Cancel button */
    hwnd = GetDlgItem(This->dlg_hwnd, IDCANCEL);
    if(hwnd)
    {
        int cancel_width, cancel_height;
        GetWindowRect(hwnd, &cancel_rc);
        cancel_width = cancel_rc.right - cancel_rc.left;
        cancel_height = cancel_rc.bottom - cancel_rc.top;

        cancel_rc.left = dialog_rc.right - cancel_width - hspacing;
        cancel_rc.top = dialog_rc.bottom - cancel_height - vspacing;
        cancel_rc.right = cancel_rc.left + cancel_width;
        cancel_rc.bottom = cancel_rc.top + cancel_height;
    }

    /* Open/Save button */
    hwnd = GetDlgItem(This->dlg_hwnd, IDOK);
    if(hwnd)
    {
        int open_width, open_height;
        GetWindowRect(hwnd, &open_rc);
        open_width = open_rc.right - open_rc.left;
        open_height = open_rc.bottom - open_rc.top;

        open_rc.left = cancel_rc.left - open_width - hspacing;
        open_rc.top = cancel_rc.top;
        open_rc.right = open_rc.left + open_width;
        open_rc.bottom = open_rc.top + open_height;
    }

    /* The filetype combobox. */
    hwnd = GetDlgItem(This->dlg_hwnd, IDC_FILETYPE);
    if(hwnd)
    {
        int filetype_width, filetype_height;
        GetWindowRect(hwnd, &filetype_rc);

        filetype_width = filetype_rc.right - filetype_rc.left;
        filetype_height = filetype_rc.bottom - filetype_rc.top;

        filetype_rc.right = cancel_rc.right;

        filetype_rc.left = filetype_rc.right - filetype_width;
        filetype_rc.top = cancel_rc.top - filetype_height - vspacing;
        filetype_rc.bottom = filetype_rc.top + filetype_height;

        if(!This->filterspec_count)
            filetype_rc.left = filetype_rc.right;
    }

    /* Filename label. */
    hwnd = GetDlgItem(This->dlg_hwnd, IDC_FILENAMESTATIC);
    if(hwnd)
    {
        int filetypelabel_width, filetypelabel_height;
        GetWindowRect(hwnd, &filenamelabel_rc);

        filetypelabel_width = filenamelabel_rc.right - filenamelabel_rc.left;
        filetypelabel_height = filenamelabel_rc.bottom - filenamelabel_rc.top;

        filenamelabel_rc.left = 160; /* FIXME */
        filenamelabel_rc.top = filetype_rc.top;
        filenamelabel_rc.right = filenamelabel_rc.left + filetypelabel_width;
        filenamelabel_rc.bottom = filenamelabel_rc.top + filetypelabel_height;
    }

    /* Filename edit box. */
    hwnd = GetDlgItem(This->dlg_hwnd, IDC_FILENAME);
    if(hwnd)
    {
        int filename_width, filename_height;
        GetWindowRect(hwnd, &filename_rc);

        filename_width = filetype_rc.left - filenamelabel_rc.right - hspacing*2;
        filename_height = filename_rc.bottom - filename_rc.top;

        filename_rc.left = filenamelabel_rc.right + hspacing;
        filename_rc.top = filetype_rc.top;
        filename_rc.right = filename_rc.left + filename_width;
        filename_rc.bottom = filename_rc.top + filename_height;
    }

    hwnd = GetDlgItem(This->dlg_hwnd, IDC_NAV_TOOLBAR);
    if(hwnd)
    {
        GetWindowRect(hwnd, &toolbar_rc);
        MapWindowPoints(NULL, This->dlg_hwnd, (POINT*)&toolbar_rc, 2);
    }

    /* The custom controls */
    customctrls_rc.left = dialog_rc.left + vspacing;
    customctrls_rc.right = dialog_rc.right - vspacing;
    customctrls_rc.bottom = filename_rc.top - hspacing;
    customctrls_rc.top = customctrls_rc.bottom -
        ctrl_container_resize(This, customctrls_rc.right - customctrls_rc.left);

    /* The ExplorerBrowser control. */
    ebrowser_rc.left = dialog_rc.left + vspacing;
    ebrowser_rc.top = toolbar_rc.bottom + vspacing;
    ebrowser_rc.right = dialog_rc.right - hspacing;
    ebrowser_rc.bottom = customctrls_rc.top - hspacing;

    /****
     * Move everything to the right place.
     */

    /* FIXME: The Save Dialog uses a slightly different layout. */
    hdwp = BeginDeferWindowPos(7);

    if(hdwp && This->peb)
        IExplorerBrowser_SetRect(This->peb, &hdwp, ebrowser_rc);

    if(hdwp && This->cctrls_hwnd)
        DeferWindowPos(hdwp, This->cctrls_hwnd, NULL,
                       customctrls_rc.left, customctrls_rc.top,
                       customctrls_rc.right - customctrls_rc.left, customctrls_rc.bottom - customctrls_rc.top,
                       SWP_NOZORDER | SWP_NOACTIVATE);

    /* The default controls */
    if(hdwp && (hwnd = GetDlgItem(This->dlg_hwnd, IDC_FILETYPE)) )
        DeferWindowPos(hdwp, hwnd, NULL, filetype_rc.left, filetype_rc.top, 0, 0,
                       SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);

    if(hdwp && (hwnd = GetDlgItem(This->dlg_hwnd, IDC_FILENAME)) )
        DeferWindowPos(hdwp, hwnd, NULL, filename_rc.left, filename_rc.top,
                       filename_rc.right - filename_rc.left, filename_rc.bottom - filename_rc.top,
                       SWP_NOZORDER | SWP_NOACTIVATE);

    if(hdwp && (hwnd = GetDlgItem(This->dlg_hwnd, IDC_FILENAMESTATIC)) )
        DeferWindowPos(hdwp, hwnd, NULL, filenamelabel_rc.left, filenamelabel_rc.top, 0, 0,
                       SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);

    if(hdwp && (hwnd = GetDlgItem(This->dlg_hwnd, IDOK)) )
        DeferWindowPos(hdwp, hwnd, NULL, open_rc.left, open_rc.top, 0, 0,
                       SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);

    if(hdwp && (hwnd = GetDlgItem(This->dlg_hwnd, IDCANCEL)) )
        DeferWindowPos(hdwp, hwnd, NULL, cancel_rc.left, cancel_rc.top, 0, 0,
                       SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);

    if(hdwp)
        EndDeferWindowPos(hdwp);
    else
        ERR("Failed to position dialog controls.\n");

    ret.cx = 0; ret.cy = 0;
    return ret;
}

static HRESULT init_explorerbrowser(FileDialogImpl *This)
{
    IShellItem *psi_folder;
    FOLDERSETTINGS fos;
    RECT rc = {0};
    HRESULT hr;

    /* Create ExplorerBrowser instance */
    OleInitialize(NULL);

    hr = CoCreateInstance(&CLSID_ExplorerBrowser, NULL, CLSCTX_INPROC_SERVER,
                          &IID_IExplorerBrowser, (void**)&This->peb);
    if(FAILED(hr))
    {
        ERR("Failed to instantiate ExplorerBrowser control.\n");
        return hr;
    }

    IExplorerBrowser_SetOptions(This->peb, EBO_SHOWFRAMES);

    hr = IExplorerBrowser_Initialize(This->peb, This->dlg_hwnd, &rc, NULL);
    if(FAILED(hr))
    {
        ERR("Failed to initialize the ExplorerBrowser control.\n");
        IExplorerBrowser_Release(This->peb);
        This->peb = NULL;
        return hr;
    }
    hr = IExplorerBrowser_Advise(This->peb, &This->IExplorerBrowserEvents_iface, &This->ebevents_cookie);
    if(FAILED(hr))
        ERR("Advise (ExplorerBrowser) failed.\n");

    /* Get previous options? */
    fos.ViewMode = fos.fFlags = 0;
    if(!(This->options & FOS_ALLOWMULTISELECT))
        fos.fFlags |= FWF_SINGLESEL;

    IExplorerBrowser_SetFolderSettings(This->peb, &fos);

    hr = IUnknown_SetSite((IUnknown*)This->peb, (IUnknown*)This);
    if(FAILED(hr))
        ERR("SetSite (ExplorerBrowser) failed.\n");

    /* Browse somewhere */
    psi_folder = This->psi_setfolder ? This->psi_setfolder : This->psi_defaultfolder;
    IExplorerBrowser_BrowseToObject(This->peb, (IUnknown*)psi_folder, SBSP_DEFBROWSER);

    return S_OK;
}

static void init_toolbar(FileDialogImpl *This, HWND hwnd)
{
    HWND htoolbar;
    TBADDBITMAP tbab;
    TBBUTTON button[2];

    htoolbar = CreateWindowExW(0, TOOLBARCLASSNAMEW, NULL, TBSTYLE_FLAT | WS_CHILD | WS_VISIBLE,
                               0, 0, 0, 0,
                               hwnd, (HMENU)IDC_NAV_TOOLBAR, NULL, NULL);

    tbab.hInst = HINST_COMMCTRL;
    tbab.nID = IDB_HIST_LARGE_COLOR;
    SendMessageW(htoolbar, TB_ADDBITMAP, 0, (LPARAM)&tbab);

    button[0].iBitmap = HIST_BACK;
    button[0].idCommand = IDC_NAVBACK;
    button[0].fsState = TBSTATE_ENABLED;
    button[0].fsStyle = BTNS_BUTTON;
    button[0].dwData = 0;
    button[0].iString = 0;

    button[1].iBitmap = HIST_FORWARD;
    button[1].idCommand = IDC_NAVFORWARD;
    button[1].fsState = TBSTATE_ENABLED;
    button[1].fsStyle = BTNS_BUTTON;
    button[1].dwData = 0;
    button[1].iString = 0;

    SendMessageW(htoolbar, TB_ADDBUTTONSW, 2, (LPARAM)button);
    SendMessageW(htoolbar, TB_SETBUTTONSIZE, 0, MAKELPARAM(24,24));
    SendMessageW(htoolbar, TB_AUTOSIZE, 0, 0);
}

static void update_control_text(FileDialogImpl *This)
{
    HWND hitem;
    if(This->custom_title)
        SetWindowTextW(This->dlg_hwnd, This->custom_title);

    if(This->custom_okbutton &&
       (hitem = GetDlgItem(This->dlg_hwnd, IDOK)))
    {
        SetWindowTextW(hitem, This->custom_okbutton);
        ctrl_resize(hitem, 50, 250, FALSE);
    }

    if(This->custom_cancelbutton &&
       (hitem = GetDlgItem(This->dlg_hwnd, IDCANCEL)))
    {
        SetWindowTextW(hitem, This->custom_cancelbutton);
        ctrl_resize(hitem, 50, 250, FALSE);
    }

    if(This->custom_filenamelabel &&
       (hitem = GetDlgItem(This->dlg_hwnd, IDC_FILENAMESTATIC)))
    {
        SetWindowTextW(hitem, This->custom_filenamelabel);
        ctrl_resize(hitem, 50, 250, FALSE);
    }
}

static LRESULT on_wm_initdialog(HWND hwnd, LPARAM lParam)
{
    FileDialogImpl *This = (FileDialogImpl*)lParam;
    HWND hitem;

    TRACE("(%p, %p)\n", This, hwnd);

    SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LPARAM)This);
    This->dlg_hwnd = hwnd;

    hitem = GetDlgItem(This->dlg_hwnd, pshHelp);
    if(hitem) ShowWindow(hitem, SW_HIDE);

    hitem = GetDlgItem(This->dlg_hwnd, IDC_FILETYPESTATIC);
    if(hitem) ShowWindow(hitem, SW_HIDE);

    /* Fill filetypes combobox, or hide it. */
    hitem = GetDlgItem(This->dlg_hwnd, IDC_FILETYPE);
    if(This->filterspec_count)
    {
        UINT i;
        for(i = 0; i < This->filterspec_count; i++)
            SendMessageW(hitem, CB_ADDSTRING, 0, (LPARAM)This->filterspecs[i].pszName);

        SendMessageW(hitem, CB_SETCURSEL, This->filetypeindex, 0);
    }
    else
        ShowWindow(hitem, SW_HIDE);

    if(This->set_filename &&
       (hitem = GetDlgItem(This->dlg_hwnd, IDC_FILENAME)) )
        SendMessageW(hitem, WM_SETTEXT, 0, (LPARAM)This->set_filename);

    ctrl_container_reparent(This, This->dlg_hwnd);
    init_explorerbrowser(This);
    init_toolbar(This, hwnd);
    update_control_text(This);
    update_layout(This);

    return TRUE;
}

static LRESULT on_wm_size(FileDialogImpl *This)
{
    update_layout(This);
    return FALSE;
}

static LRESULT on_wm_getminmaxinfo(FileDialogImpl *This, LPARAM lparam)
{
    MINMAXINFO *mmi = (MINMAXINFO*)lparam;
    TRACE("%p (%p)\n", This, mmi);

    /* FIXME */
    mmi->ptMinTrackSize.x = 640;
    mmi->ptMinTrackSize.y = 480;

    return FALSE;
}

static LRESULT on_wm_destroy(FileDialogImpl *This)
{
    TRACE("%p\n", This);

    if(This->peb)
    {
        IExplorerBrowser_Destroy(This->peb);
        IExplorerBrowser_Release(This->peb);
        This->peb = NULL;
    }

    ctrl_container_reparent(This, NULL);
    This->dlg_hwnd = NULL;

    return TRUE;
}

static LRESULT on_idok(FileDialogImpl *This)
{
    TRACE("%p\n", This);

    if(SUCCEEDED(on_default_action(This)))
        EndDialog(This->dlg_hwnd, S_OK);

    return FALSE;
}

static LRESULT on_idcancel(FileDialogImpl *This)
{
    TRACE("%p\n", This);

    EndDialog(This->dlg_hwnd, HRESULT_FROM_WIN32(ERROR_CANCELLED));

    return FALSE;
}

static LRESULT on_browse_back(FileDialogImpl *This)
{
    TRACE("%p\n", This);
    IExplorerBrowser_BrowseToIDList(This->peb, NULL, SBSP_NAVIGATEBACK);
    return FALSE;
}

static LRESULT on_browse_forward(FileDialogImpl *This)
{
    TRACE("%p\n", This);
    IExplorerBrowser_BrowseToIDList(This->peb, NULL, SBSP_NAVIGATEFORWARD);
    return FALSE;
}

static LRESULT on_command_filetype(FileDialogImpl *This, WPARAM wparam, LPARAM lparam)
{
    if(HIWORD(wparam) == CBN_SELCHANGE)
    {
        IShellView *psv;
        HRESULT hr;
        LPWSTR filename;
        UINT prev_index = This->filetypeindex;

        This->filetypeindex = SendMessageW((HWND)lparam, CB_GETCURSEL, 0, 0);
        TRACE("File type selection changed to %d.\n", This->filetypeindex);

        if(prev_index == This->filetypeindex)
            return FALSE;

        hr = IExplorerBrowser_GetCurrentView(This->peb, &IID_IShellView, (void**)&psv);
        if(SUCCEEDED(hr))
        {
            IShellView_Refresh(psv);
            IShellView_Release(psv);
        }

        if(This->dlg_type == ITEMDLG_TYPE_SAVE && get_file_name(This, &filename))
        {
            WCHAR buf[MAX_PATH], extbuf[MAX_PATH], *ext;

            ext = get_first_ext_from_spec(extbuf, This->filterspecs[This->filetypeindex].pszSpec);
            if(ext)
            {
                lstrcpyW(buf, filename);

                if(PathMatchSpecW(buf, This->filterspecs[prev_index].pszSpec))
                    PathRemoveExtensionW(buf);

                lstrcatW(buf, ext);
                set_file_name(This, buf);
            }
            CoTaskMemFree(filename);
        }
    }

    return FALSE;
}

static LRESULT on_wm_command(FileDialogImpl *This, WPARAM wparam, LPARAM lparam)
{
    switch(LOWORD(wparam))
    {
    case IDOK:                return on_idok(This);
    case IDCANCEL:            return on_idcancel(This);
    case IDC_NAVBACK:         return on_browse_back(This);
    case IDC_NAVFORWARD:      return on_browse_forward(This);
    case IDC_FILETYPE:        return on_command_filetype(This, wparam, lparam);
    default:                  TRACE("Unknown command.\n");
    }
    return FALSE;
}

static LRESULT CALLBACK itemdlg_dlgproc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
    FileDialogImpl *This = (FileDialogImpl*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);

    switch(umessage)
    {
    case WM_INITDIALOG:       return on_wm_initdialog(hwnd, lparam);
    case WM_COMMAND:          return on_wm_command(This, wparam, lparam);
    case WM_SIZE:             return on_wm_size(This);
    case WM_GETMINMAXINFO:    return on_wm_getminmaxinfo(This, lparam);
    case WM_DESTROY:          return on_wm_destroy(This);
    }

    return FALSE;
}

static HRESULT create_dialog(FileDialogImpl *This, HWND parent)
{
    INT_PTR res;

    SetLastError(0);
    res = DialogBoxParamW(COMDLG32_hInstance,
                          MAKEINTRESOURCEW(NEWFILEOPENV3ORD),
                          parent, itemdlg_dlgproc, (LPARAM)This);
    This->dlg_hwnd = NULL;
    if(res == -1)
    {
        ERR("Failed to show dialog (LastError: %d)\n", GetLastError());
        return E_FAIL;
    }

    TRACE("Returning 0x%08x\n", (HRESULT)res);
    return (HRESULT)res;
}

/**************************************************************************
 * IFileDialog implementation
 */
static inline FileDialogImpl *impl_from_IFileDialog2(IFileDialog2 *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, IFileDialog2_iface);
}

static HRESULT WINAPI IFileDialog2_fnQueryInterface(IFileDialog2 *iface,
                                                    REFIID riid,
                                                    void **ppvObject)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%s, %p)\n", This, debugstr_guid(riid), ppvObject);

    *ppvObject = NULL;
    if(IsEqualGUID(riid, &IID_IUnknown) ||
       IsEqualGUID(riid, &IID_IFileDialog) ||
       IsEqualGUID(riid, &IID_IFileDialog2))
    {
        *ppvObject = iface;
    }
    else if(IsEqualGUID(riid, &IID_IFileOpenDialog) && This->dlg_type == ITEMDLG_TYPE_OPEN)
    {
        *ppvObject = &This->u.IFileOpenDialog_iface;
    }
    else if(IsEqualGUID(riid, &IID_IFileSaveDialog) && This->dlg_type == ITEMDLG_TYPE_SAVE)
    {
        *ppvObject = &This->u.IFileSaveDialog_iface;
    }
    else if(IsEqualGUID(riid, &IID_IExplorerBrowserEvents))
    {
        *ppvObject = &This->IExplorerBrowserEvents_iface;
    }
    else if(IsEqualGUID(riid, &IID_IServiceProvider))
    {
        *ppvObject = &This->IServiceProvider_iface;
    }
    else if(IsEqualGUID(&IID_ICommDlgBrowser3, riid) ||
            IsEqualGUID(&IID_ICommDlgBrowser2, riid) ||
            IsEqualGUID(&IID_ICommDlgBrowser, riid))
    {
        *ppvObject = &This->ICommDlgBrowser3_iface;
    }
    else if(IsEqualGUID(&IID_IOleWindow, riid))
    {
        *ppvObject = &This->IOleWindow_iface;
    }
    else if(IsEqualGUID(riid, &IID_IFileDialogCustomize) ||
            IsEqualGUID(riid, &IID_IFileDialogCustomizeAlt))
    {
        *ppvObject = &This->IFileDialogCustomize_iface;
    }
    else
        FIXME("Unknown interface requested: %s.\n", debugstr_guid(riid));

    if(*ppvObject)
    {
        IUnknown_AddRef((IUnknown*)*ppvObject);
        return S_OK;
    }

    return E_NOINTERFACE;
}

static ULONG WINAPI IFileDialog2_fnAddRef(IFileDialog2 *iface)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    LONG ref = InterlockedIncrement(&This->ref);
    TRACE("%p - ref %d\n", This, ref);

    return ref;
}

static ULONG WINAPI IFileDialog2_fnRelease(IFileDialog2 *iface)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    LONG ref = InterlockedDecrement(&This->ref);
    TRACE("%p - ref %d\n", This, ref);

    if(!ref)
    {
        UINT i;
        for(i = 0; i < This->filterspec_count; i++)
        {
            LocalFree((void*)This->filterspecs[i].pszName);
            LocalFree((void*)This->filterspecs[i].pszSpec);
        }
        HeapFree(GetProcessHeap(), 0, This->filterspecs);

        DestroyWindow(This->cctrls_hwnd);

        if(This->psi_defaultfolder) IShellItem_Release(This->psi_defaultfolder);
        if(This->psi_setfolder)     IShellItem_Release(This->psi_setfolder);
        if(This->psi_folder)        IShellItem_Release(This->psi_folder);
        if(This->psia_selection)    IShellItemArray_Release(This->psia_selection);
        if(This->psia_results)      IShellItemArray_Release(This->psia_results);

        LocalFree(This->set_filename);
        LocalFree(This->default_ext);
        LocalFree(This->custom_title);
        LocalFree(This->custom_okbutton);
        LocalFree(This->custom_cancelbutton);
        LocalFree(This->custom_filenamelabel);

        HeapFree(GetProcessHeap(), 0, This);
    }

    return ref;
}

static HRESULT WINAPI IFileDialog2_fnShow(IFileDialog2 *iface, HWND hwndOwner)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%p)\n", iface, hwndOwner);

    return create_dialog(This, hwndOwner);
}

static HRESULT WINAPI IFileDialog2_fnSetFileTypes(IFileDialog2 *iface, UINT cFileTypes,
                                                  const COMDLG_FILTERSPEC *rgFilterSpec)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    UINT i;
    TRACE("%p (%d, %p)\n", This, cFileTypes, rgFilterSpec);

    if(This->filterspecs)
        return E_UNEXPECTED;

    if(!rgFilterSpec)
        return E_INVALIDARG;

    if(!cFileTypes)
        return S_OK;

    This->filterspecs = HeapAlloc(GetProcessHeap(), 0, sizeof(COMDLG_FILTERSPEC)*cFileTypes);
    for(i = 0; i < cFileTypes; i++)
    {
        This->filterspecs[i].pszName = StrDupW(rgFilterSpec[i].pszName);
        This->filterspecs[i].pszSpec = StrDupW(rgFilterSpec[i].pszSpec);
    }
    This->filterspec_count = cFileTypes;

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnSetFileTypeIndex(IFileDialog2 *iface, UINT iFileType)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%d)\n", This, iFileType);

    if(!This->filterspecs)
        return E_FAIL;

    if(iFileType >= This->filterspec_count)
        This->filetypeindex = This->filterspec_count - 1;
    else
        This->filetypeindex = iFileType;

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnGetFileTypeIndex(IFileDialog2 *iface, UINT *piFileType)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%p)\n", This, piFileType);

    if(!piFileType)
        return E_INVALIDARG;

    *piFileType = This->filetypeindex;

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnAdvise(IFileDialog2 *iface, IFileDialogEvents *pfde, DWORD *pdwCookie)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    events_client *client;
    TRACE("%p (%p, %p)\n", This, pfde, pdwCookie);

    if(!pfde || !pdwCookie)
        return E_INVALIDARG;

    client = HeapAlloc(GetProcessHeap(), 0, sizeof(events_client));
    client->pfde = pfde;
    client->cookie = ++This->events_next_cookie;

    IFileDialogEvents_AddRef(pfde);
    *pdwCookie = client->cookie;

    list_add_tail(&This->events_clients, &client->entry);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnUnadvise(IFileDialog2 *iface, DWORD dwCookie)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    events_client *client, *found = NULL;
    TRACE("%p (%d)\n", This, dwCookie);

    LIST_FOR_EACH_ENTRY(client, &This->events_clients, events_client, entry)
    {
        if(client->cookie == dwCookie)
        {
            found = client;
            break;
        }
    }

    if(found)
    {
        list_remove(&found->entry);
        IFileDialogEvents_Release(found->pfde);
        HeapFree(GetProcessHeap(), 0, found);
        return S_OK;
    }

    return E_INVALIDARG;
}

static HRESULT WINAPI IFileDialog2_fnSetOptions(IFileDialog2 *iface, FILEOPENDIALOGOPTIONS fos)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (0x%x)\n", This, fos);

    This->options = fos;

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnGetOptions(IFileDialog2 *iface, FILEOPENDIALOGOPTIONS *pfos)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%p)\n", This, pfos);

    if(!pfos)
        return E_INVALIDARG;

    *pfos = This->options;

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnSetDefaultFolder(IFileDialog2 *iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%p)\n", This, psi);
    if(This->psi_defaultfolder)
        IShellItem_Release(This->psi_defaultfolder);

    This->psi_defaultfolder = psi;

    if(This->psi_defaultfolder)
        IShellItem_AddRef(This->psi_defaultfolder);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnSetFolder(IFileDialog2 *iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%p)\n", This, psi);
    if(This->psi_setfolder)
        IShellItem_Release(This->psi_setfolder);

    This->psi_setfolder = psi;

    if(This->psi_setfolder)
        IShellItem_AddRef(This->psi_setfolder);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnGetFolder(IFileDialog2 *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%p)\n", This, ppsi);
    if(!ppsi)
        return E_INVALIDARG;

    /* FIXME:
       If the dialog is shown, return the current(ly selected) folder. */

    *ppsi = NULL;
    if(This->psi_folder)
        *ppsi = This->psi_folder;
    else if(This->psi_setfolder)
        *ppsi = This->psi_setfolder;
    else if(This->psi_defaultfolder)
        *ppsi = This->psi_defaultfolder;

    if(*ppsi)
    {
        IShellItem_AddRef(*ppsi);
        return S_OK;
    }

    return E_FAIL;
}

static HRESULT WINAPI IFileDialog2_fnGetCurrentSelection(IFileDialog2 *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    HRESULT hr;
    TRACE("%p (%p)\n", This, ppsi);

    if(!ppsi)
        return E_INVALIDARG;

    if(This->psia_selection)
    {
        /* FIXME: Check filename edit box */
        hr = IShellItemArray_GetItemAt(This->psia_selection, 0, ppsi);
        return hr;
    }

    return E_FAIL;
}

static HRESULT WINAPI IFileDialog2_fnSetFileName(IFileDialog2 *iface, LPCWSTR pszName)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%s)\n", iface, debugstr_w(pszName));

    set_file_name(This, pszName);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnGetFileName(IFileDialog2 *iface, LPWSTR *pszName)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%p)\n", iface, pszName);

    if(!pszName)
        return E_INVALIDARG;

    *pszName = NULL;
    if(get_file_name(This, pszName))
        return S_OK;
    else
        return E_FAIL;
}

static HRESULT WINAPI IFileDialog2_fnSetTitle(IFileDialog2 *iface, LPCWSTR pszTitle)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%s)\n", This, debugstr_w(pszTitle));

    LocalFree(This->custom_title);
    This->custom_title = StrDupW(pszTitle);
    update_control_text(This);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnSetOkButtonLabel(IFileDialog2 *iface, LPCWSTR pszText)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%s)\n", This, debugstr_w(pszText));

    LocalFree(This->custom_okbutton);
    This->custom_okbutton = StrDupW(pszText);
    update_control_text(This);
    update_layout(This);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnSetFileNameLabel(IFileDialog2 *iface, LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%s)\n", This, debugstr_w(pszLabel));

    LocalFree(This->custom_filenamelabel);
    This->custom_filenamelabel = StrDupW(pszLabel);
    update_control_text(This);
    update_layout(This);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnGetResult(IFileDialog2 *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    HRESULT hr;
    TRACE("%p (%p)\n", This, ppsi);

    if(!ppsi)
        return E_INVALIDARG;

    if(This->psia_results)
    {
        UINT item_count;
        hr = IShellItemArray_GetCount(This->psia_results, &item_count);
        if(SUCCEEDED(hr))
        {
            if(item_count != 1)
                return E_FAIL;

            /* Adds a reference. */
            hr = IShellItemArray_GetItemAt(This->psia_results, 0, ppsi);
        }

        return hr;
    }

    return E_UNEXPECTED;
}

static HRESULT WINAPI IFileDialog2_fnAddPlace(IFileDialog2 *iface, IShellItem *psi, FDAP fdap)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    FIXME("stub - %p (%p, %d)\n", This, psi, fdap);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialog2_fnSetDefaultExtension(IFileDialog2 *iface, LPCWSTR pszDefaultExtension)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%s)\n", This, debugstr_w(pszDefaultExtension));

    LocalFree(This->default_ext);
    This->default_ext = StrDupW(pszDefaultExtension);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnClose(IFileDialog2 *iface, HRESULT hr)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (0x%08x)\n", This, hr);

    if(This->dlg_hwnd)
        EndDialog(This->dlg_hwnd, hr);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnSetClientGuid(IFileDialog2 *iface, REFGUID guid)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    FIXME("stub - %p (%s)\n", This, debugstr_guid(guid));
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialog2_fnClearClientData(IFileDialog2 *iface)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    FIXME("stub - %p\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialog2_fnSetFilter(IFileDialog2 *iface, IShellItemFilter *pFilter)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    FIXME("stub - %p (%p)\n", This, pFilter);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialog2_fnSetCancelButtonLabel(IFileDialog2 *iface, LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    TRACE("%p (%s)\n", This, debugstr_w(pszLabel));

    LocalFree(This->custom_cancelbutton);
    This->custom_cancelbutton = StrDupW(pszLabel);
    update_control_text(This);
    update_layout(This);

    return S_OK;
}

static HRESULT WINAPI IFileDialog2_fnSetNavigationRoot(IFileDialog2 *iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileDialog2(iface);
    FIXME("stub - %p (%p)\n", This, psi);
    return E_NOTIMPL;
}

static const IFileDialog2Vtbl vt_IFileDialog2 = {
    IFileDialog2_fnQueryInterface,
    IFileDialog2_fnAddRef,
    IFileDialog2_fnRelease,
    IFileDialog2_fnShow,
    IFileDialog2_fnSetFileTypes,
    IFileDialog2_fnSetFileTypeIndex,
    IFileDialog2_fnGetFileTypeIndex,
    IFileDialog2_fnAdvise,
    IFileDialog2_fnUnadvise,
    IFileDialog2_fnSetOptions,
    IFileDialog2_fnGetOptions,
    IFileDialog2_fnSetDefaultFolder,
    IFileDialog2_fnSetFolder,
    IFileDialog2_fnGetFolder,
    IFileDialog2_fnGetCurrentSelection,
    IFileDialog2_fnSetFileName,
    IFileDialog2_fnGetFileName,
    IFileDialog2_fnSetTitle,
    IFileDialog2_fnSetOkButtonLabel,
    IFileDialog2_fnSetFileNameLabel,
    IFileDialog2_fnGetResult,
    IFileDialog2_fnAddPlace,
    IFileDialog2_fnSetDefaultExtension,
    IFileDialog2_fnClose,
    IFileDialog2_fnSetClientGuid,
    IFileDialog2_fnClearClientData,
    IFileDialog2_fnSetFilter,
    IFileDialog2_fnSetCancelButtonLabel,
    IFileDialog2_fnSetNavigationRoot
};

/**************************************************************************
 * IFileOpenDialog
 */
static inline FileDialogImpl *impl_from_IFileOpenDialog(IFileOpenDialog *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, u.IFileOpenDialog_iface);
}

static HRESULT WINAPI IFileOpenDialog_fnQueryInterface(IFileOpenDialog *iface,
                                                       REFIID riid, void **ppvObject)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppvObject);
}

static ULONG WINAPI IFileOpenDialog_fnAddRef(IFileOpenDialog *iface)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_AddRef(&This->IFileDialog2_iface);
}

static ULONG WINAPI IFileOpenDialog_fnRelease(IFileOpenDialog *iface)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_Release(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IFileOpenDialog_fnShow(IFileOpenDialog *iface, HWND hwndOwner)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_Show(&This->IFileDialog2_iface, hwndOwner);
}

static HRESULT WINAPI IFileOpenDialog_fnSetFileTypes(IFileOpenDialog *iface, UINT cFileTypes,
                                                     const COMDLG_FILTERSPEC *rgFilterSpec)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetFileTypes(&This->IFileDialog2_iface, cFileTypes, rgFilterSpec);
}

static HRESULT WINAPI IFileOpenDialog_fnSetFileTypeIndex(IFileOpenDialog *iface, UINT iFileType)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetFileTypeIndex(&This->IFileDialog2_iface, iFileType);
}

static HRESULT WINAPI IFileOpenDialog_fnGetFileTypeIndex(IFileOpenDialog *iface, UINT *piFileType)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_GetFileTypeIndex(&This->IFileDialog2_iface, piFileType);
}

static HRESULT WINAPI IFileOpenDialog_fnAdvise(IFileOpenDialog *iface, IFileDialogEvents *pfde,
                                               DWORD *pdwCookie)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_Advise(&This->IFileDialog2_iface, pfde, pdwCookie);
}

static HRESULT WINAPI IFileOpenDialog_fnUnadvise(IFileOpenDialog *iface, DWORD dwCookie)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_Unadvise(&This->IFileDialog2_iface, dwCookie);
}

static HRESULT WINAPI IFileOpenDialog_fnSetOptions(IFileOpenDialog *iface, FILEOPENDIALOGOPTIONS fos)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetOptions(&This->IFileDialog2_iface, fos);
}

static HRESULT WINAPI IFileOpenDialog_fnGetOptions(IFileOpenDialog *iface, FILEOPENDIALOGOPTIONS *pfos)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_GetOptions(&This->IFileDialog2_iface, pfos);
}

static HRESULT WINAPI IFileOpenDialog_fnSetDefaultFolder(IFileOpenDialog *iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetDefaultFolder(&This->IFileDialog2_iface, psi);
}

static HRESULT WINAPI IFileOpenDialog_fnSetFolder(IFileOpenDialog *iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetFolder(&This->IFileDialog2_iface, psi);
}

static HRESULT WINAPI IFileOpenDialog_fnGetFolder(IFileOpenDialog *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_GetFolder(&This->IFileDialog2_iface, ppsi);
}

static HRESULT WINAPI IFileOpenDialog_fnGetCurrentSelection(IFileOpenDialog *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_GetCurrentSelection(&This->IFileDialog2_iface, ppsi);
}

static HRESULT WINAPI IFileOpenDialog_fnSetFileName(IFileOpenDialog *iface, LPCWSTR pszName)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetFileName(&This->IFileDialog2_iface, pszName);
}

static HRESULT WINAPI IFileOpenDialog_fnGetFileName(IFileOpenDialog *iface, LPWSTR *pszName)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_GetFileName(&This->IFileDialog2_iface, pszName);
}

static HRESULT WINAPI IFileOpenDialog_fnSetTitle(IFileOpenDialog *iface, LPCWSTR pszTitle)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetTitle(&This->IFileDialog2_iface, pszTitle);
}

static HRESULT WINAPI IFileOpenDialog_fnSetOkButtonLabel(IFileOpenDialog *iface, LPCWSTR pszText)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetOkButtonLabel(&This->IFileDialog2_iface, pszText);
}

static HRESULT WINAPI IFileOpenDialog_fnSetFileNameLabel(IFileOpenDialog *iface, LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetFileNameLabel(&This->IFileDialog2_iface, pszLabel);
}

static HRESULT WINAPI IFileOpenDialog_fnGetResult(IFileOpenDialog *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_GetResult(&This->IFileDialog2_iface, ppsi);
}

static HRESULT WINAPI IFileOpenDialog_fnAddPlace(IFileOpenDialog *iface, IShellItem *psi, FDAP fdap)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_AddPlace(&This->IFileDialog2_iface, psi, fdap);
}

static HRESULT WINAPI IFileOpenDialog_fnSetDefaultExtension(IFileOpenDialog *iface,
                                                            LPCWSTR pszDefaultExtension)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetDefaultExtension(&This->IFileDialog2_iface, pszDefaultExtension);
}

static HRESULT WINAPI IFileOpenDialog_fnClose(IFileOpenDialog *iface, HRESULT hr)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_Close(&This->IFileDialog2_iface, hr);
}

static HRESULT WINAPI IFileOpenDialog_fnSetClientGuid(IFileOpenDialog *iface, REFGUID guid)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetClientGuid(&This->IFileDialog2_iface, guid);
}

static HRESULT WINAPI IFileOpenDialog_fnClearClientData(IFileOpenDialog *iface)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_ClearClientData(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IFileOpenDialog_fnSetFilter(IFileOpenDialog *iface, IShellItemFilter *pFilter)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    return IFileDialog2_SetFilter(&This->IFileDialog2_iface, pFilter);
}

static HRESULT WINAPI IFileOpenDialog_fnGetResults(IFileOpenDialog *iface, IShellItemArray **ppenum)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    TRACE("%p (%p)\n", This, ppenum);

    *ppenum = This->psia_results;

    if(*ppenum)
    {
        IShellItemArray_AddRef(*ppenum);
        return S_OK;
    }

    return E_FAIL;
}

static HRESULT WINAPI IFileOpenDialog_fnGetSelectedItems(IFileOpenDialog *iface, IShellItemArray **ppsai)
{
    FileDialogImpl *This = impl_from_IFileOpenDialog(iface);
    TRACE("%p (%p)\n", This, ppsai);

    if(This->psia_selection)
    {
        *ppsai = This->psia_selection;
        IShellItemArray_AddRef(*ppsai);
        return S_OK;
    }

    return E_FAIL;
}

static const IFileOpenDialogVtbl vt_IFileOpenDialog = {
    IFileOpenDialog_fnQueryInterface,
    IFileOpenDialog_fnAddRef,
    IFileOpenDialog_fnRelease,
    IFileOpenDialog_fnShow,
    IFileOpenDialog_fnSetFileTypes,
    IFileOpenDialog_fnSetFileTypeIndex,
    IFileOpenDialog_fnGetFileTypeIndex,
    IFileOpenDialog_fnAdvise,
    IFileOpenDialog_fnUnadvise,
    IFileOpenDialog_fnSetOptions,
    IFileOpenDialog_fnGetOptions,
    IFileOpenDialog_fnSetDefaultFolder,
    IFileOpenDialog_fnSetFolder,
    IFileOpenDialog_fnGetFolder,
    IFileOpenDialog_fnGetCurrentSelection,
    IFileOpenDialog_fnSetFileName,
    IFileOpenDialog_fnGetFileName,
    IFileOpenDialog_fnSetTitle,
    IFileOpenDialog_fnSetOkButtonLabel,
    IFileOpenDialog_fnSetFileNameLabel,
    IFileOpenDialog_fnGetResult,
    IFileOpenDialog_fnAddPlace,
    IFileOpenDialog_fnSetDefaultExtension,
    IFileOpenDialog_fnClose,
    IFileOpenDialog_fnSetClientGuid,
    IFileOpenDialog_fnClearClientData,
    IFileOpenDialog_fnSetFilter,
    IFileOpenDialog_fnGetResults,
    IFileOpenDialog_fnGetSelectedItems
};

/**************************************************************************
 * IFileSaveDialog
 */
static inline FileDialogImpl *impl_from_IFileSaveDialog(IFileSaveDialog *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, u.IFileSaveDialog_iface);
}

static HRESULT WINAPI IFileSaveDialog_fnQueryInterface(IFileSaveDialog *iface,
                                                       REFIID riid,
                                                       void **ppvObject)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppvObject);
}

static ULONG WINAPI IFileSaveDialog_fnAddRef(IFileSaveDialog *iface)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_AddRef(&This->IFileDialog2_iface);
}

static ULONG WINAPI IFileSaveDialog_fnRelease(IFileSaveDialog *iface)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_Release(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IFileSaveDialog_fnShow(IFileSaveDialog *iface, HWND hwndOwner)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_Show(&This->IFileDialog2_iface, hwndOwner);
}

static HRESULT WINAPI IFileSaveDialog_fnSetFileTypes(IFileSaveDialog *iface, UINT cFileTypes,
                                                     const COMDLG_FILTERSPEC *rgFilterSpec)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetFileTypes(&This->IFileDialog2_iface, cFileTypes, rgFilterSpec);
}

static HRESULT WINAPI IFileSaveDialog_fnSetFileTypeIndex(IFileSaveDialog *iface, UINT iFileType)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetFileTypeIndex(&This->IFileDialog2_iface, iFileType);
}

static HRESULT WINAPI IFileSaveDialog_fnGetFileTypeIndex(IFileSaveDialog *iface, UINT *piFileType)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_GetFileTypeIndex(&This->IFileDialog2_iface, piFileType);
}

static HRESULT WINAPI IFileSaveDialog_fnAdvise(IFileSaveDialog *iface, IFileDialogEvents *pfde,
                                               DWORD *pdwCookie)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_Advise(&This->IFileDialog2_iface, pfde, pdwCookie);
}

static HRESULT WINAPI IFileSaveDialog_fnUnadvise(IFileSaveDialog *iface, DWORD dwCookie)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_Unadvise(&This->IFileDialog2_iface, dwCookie);
}

static HRESULT WINAPI IFileSaveDialog_fnSetOptions(IFileSaveDialog *iface, FILEOPENDIALOGOPTIONS fos)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetOptions(&This->IFileDialog2_iface, fos);
}

static HRESULT WINAPI IFileSaveDialog_fnGetOptions(IFileSaveDialog *iface, FILEOPENDIALOGOPTIONS *pfos)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_GetOptions(&This->IFileDialog2_iface, pfos);
}

static HRESULT WINAPI IFileSaveDialog_fnSetDefaultFolder(IFileSaveDialog *iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetDefaultFolder(&This->IFileDialog2_iface, psi);
}

static HRESULT WINAPI IFileSaveDialog_fnSetFolder(IFileSaveDialog *iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetFolder(&This->IFileDialog2_iface, psi);
}

static HRESULT WINAPI IFileSaveDialog_fnGetFolder(IFileSaveDialog *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_GetFolder(&This->IFileDialog2_iface, ppsi);
}

static HRESULT WINAPI IFileSaveDialog_fnGetCurrentSelection(IFileSaveDialog *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_GetCurrentSelection(&This->IFileDialog2_iface, ppsi);
}

static HRESULT WINAPI IFileSaveDialog_fnSetFileName(IFileSaveDialog *iface, LPCWSTR pszName)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetFileName(&This->IFileDialog2_iface, pszName);
}

static HRESULT WINAPI IFileSaveDialog_fnGetFileName(IFileSaveDialog *iface, LPWSTR *pszName)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_GetFileName(&This->IFileDialog2_iface, pszName);
}

static HRESULT WINAPI IFileSaveDialog_fnSetTitle(IFileSaveDialog *iface, LPCWSTR pszTitle)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetTitle(&This->IFileDialog2_iface, pszTitle);
}

static HRESULT WINAPI IFileSaveDialog_fnSetOkButtonLabel(IFileSaveDialog *iface, LPCWSTR pszText)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetOkButtonLabel(&This->IFileDialog2_iface, pszText);
}

static HRESULT WINAPI IFileSaveDialog_fnSetFileNameLabel(IFileSaveDialog *iface, LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetFileNameLabel(&This->IFileDialog2_iface, pszLabel);
}

static HRESULT WINAPI IFileSaveDialog_fnGetResult(IFileSaveDialog *iface, IShellItem **ppsi)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_GetResult(&This->IFileDialog2_iface, ppsi);
}

static HRESULT WINAPI IFileSaveDialog_fnAddPlace(IFileSaveDialog *iface, IShellItem *psi, FDAP fdap)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_AddPlace(&This->IFileDialog2_iface, psi, fdap);
}

static HRESULT WINAPI IFileSaveDialog_fnSetDefaultExtension(IFileSaveDialog *iface,
                                                            LPCWSTR pszDefaultExtension)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetDefaultExtension(&This->IFileDialog2_iface, pszDefaultExtension);
}

static HRESULT WINAPI IFileSaveDialog_fnClose(IFileSaveDialog *iface, HRESULT hr)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_Close(&This->IFileDialog2_iface, hr);
}

static HRESULT WINAPI IFileSaveDialog_fnSetClientGuid(IFileSaveDialog *iface, REFGUID guid)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetClientGuid(&This->IFileDialog2_iface, guid);
}

static HRESULT WINAPI IFileSaveDialog_fnClearClientData(IFileSaveDialog *iface)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_ClearClientData(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IFileSaveDialog_fnSetFilter(IFileSaveDialog *iface, IShellItemFilter *pFilter)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    return IFileDialog2_SetFilter(&This->IFileDialog2_iface, pFilter);
}

static HRESULT WINAPI IFileSaveDialog_fnSetSaveAsItem(IFileSaveDialog* iface, IShellItem *psi)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    FIXME("stub - %p (%p)\n", This, psi);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileSaveDialog_fnSetProperties(IFileSaveDialog* iface, IPropertyStore *pStore)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    FIXME("stub - %p (%p)\n", This, pStore);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileSaveDialog_fnSetCollectedProperties(IFileSaveDialog* iface,
                                                               IPropertyDescriptionList *pList,
                                                               BOOL fAppendDefault)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    FIXME("stub - %p (%p, %d)\n", This, pList, fAppendDefault);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileSaveDialog_fnGetProperties(IFileSaveDialog* iface, IPropertyStore **ppStore)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    FIXME("stub - %p (%p)\n", This, ppStore);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileSaveDialog_fnApplyProperties(IFileSaveDialog* iface,
                                                        IShellItem *psi,
                                                        IPropertyStore *pStore,
                                                        HWND hwnd,
                                                        IFileOperationProgressSink *pSink)
{
    FileDialogImpl *This = impl_from_IFileSaveDialog(iface);
    FIXME("%p (%p, %p, %p, %p)\n", This, psi, pStore, hwnd, pSink);
    return E_NOTIMPL;
}

static const IFileSaveDialogVtbl vt_IFileSaveDialog = {
    IFileSaveDialog_fnQueryInterface,
    IFileSaveDialog_fnAddRef,
    IFileSaveDialog_fnRelease,
    IFileSaveDialog_fnShow,
    IFileSaveDialog_fnSetFileTypes,
    IFileSaveDialog_fnSetFileTypeIndex,
    IFileSaveDialog_fnGetFileTypeIndex,
    IFileSaveDialog_fnAdvise,
    IFileSaveDialog_fnUnadvise,
    IFileSaveDialog_fnSetOptions,
    IFileSaveDialog_fnGetOptions,
    IFileSaveDialog_fnSetDefaultFolder,
    IFileSaveDialog_fnSetFolder,
    IFileSaveDialog_fnGetFolder,
    IFileSaveDialog_fnGetCurrentSelection,
    IFileSaveDialog_fnSetFileName,
    IFileSaveDialog_fnGetFileName,
    IFileSaveDialog_fnSetTitle,
    IFileSaveDialog_fnSetOkButtonLabel,
    IFileSaveDialog_fnSetFileNameLabel,
    IFileSaveDialog_fnGetResult,
    IFileSaveDialog_fnAddPlace,
    IFileSaveDialog_fnSetDefaultExtension,
    IFileSaveDialog_fnClose,
    IFileSaveDialog_fnSetClientGuid,
    IFileSaveDialog_fnClearClientData,
    IFileSaveDialog_fnSetFilter,
    IFileSaveDialog_fnSetSaveAsItem,
    IFileSaveDialog_fnSetProperties,
    IFileSaveDialog_fnSetCollectedProperties,
    IFileSaveDialog_fnGetProperties,
    IFileSaveDialog_fnApplyProperties
};

/**************************************************************************
 * IExplorerBrowserEvents implementation
 */
static inline FileDialogImpl *impl_from_IExplorerBrowserEvents(IExplorerBrowserEvents *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, IExplorerBrowserEvents_iface);
}

static HRESULT WINAPI IExplorerBrowserEvents_fnQueryInterface(IExplorerBrowserEvents *iface,
                                                              REFIID riid, void **ppvObject)
{
    FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface);
    TRACE("%p (%s, %p)\n", This, debugstr_guid(riid), ppvObject);

    return IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppvObject);
}

static ULONG WINAPI IExplorerBrowserEvents_fnAddRef(IExplorerBrowserEvents *iface)
{
    FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface);
    TRACE("%p\n", This);
    return IFileDialog2_AddRef(&This->IFileDialog2_iface);
}

static ULONG WINAPI IExplorerBrowserEvents_fnRelease(IExplorerBrowserEvents *iface)
{
    FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface);
    TRACE("%p\n", This);
    return IFileDialog2_Release(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IExplorerBrowserEvents_fnOnNavigationPending(IExplorerBrowserEvents *iface,
                                                                   PCIDLIST_ABSOLUTE pidlFolder)
{
    FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface);
    IShellItem *psi;
    HRESULT hr;
    TRACE("%p (%p)\n", This, pidlFolder);

    hr = SHCreateItemFromIDList(pidlFolder, &IID_IShellItem, (void**)&psi);
    if(SUCCEEDED(hr))
    {
        hr = events_OnFolderChanging(This, psi);
        IShellItem_Release(psi);

        /* The ExplorerBrowser treats S_FALSE as S_OK, we don't. */
        if(hr == S_FALSE)
            hr = E_FAIL;

        return hr;
    }
    else
        ERR("Failed to convert pidl (%p) to a shellitem.\n", pidlFolder);

    return S_OK;
}

static HRESULT WINAPI IExplorerBrowserEvents_fnOnViewCreated(IExplorerBrowserEvents *iface,
                                                             IShellView *psv)
{
    FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface);
    TRACE("%p (%p)\n", This, psv);
    return S_OK;
}

static HRESULT WINAPI IExplorerBrowserEvents_fnOnNavigationComplete(IExplorerBrowserEvents *iface,
                                                                    PCIDLIST_ABSOLUTE pidlFolder)
{
    FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface);
    HRESULT hr;
    TRACE("%p (%p)\n", This, pidlFolder);

    if(This->psi_folder)
        IShellItem_Release(This->psi_folder);

    hr = SHCreateItemFromIDList(pidlFolder, &IID_IShellItem, (void**)&This->psi_folder);
    if(FAILED(hr))
    {
        ERR("Failed to get the current folder.\n");
        This->psi_folder = NULL;
    }

    events_OnFolderChange(This);

    return S_OK;
}

static HRESULT WINAPI IExplorerBrowserEvents_fnOnNavigationFailed(IExplorerBrowserEvents *iface,
                                                                  PCIDLIST_ABSOLUTE pidlFolder)
{
    FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface);
    TRACE("%p (%p)\n", This, pidlFolder);
    return S_OK;
}

static const IExplorerBrowserEventsVtbl vt_IExplorerBrowserEvents = {
    IExplorerBrowserEvents_fnQueryInterface,
    IExplorerBrowserEvents_fnAddRef,
    IExplorerBrowserEvents_fnRelease,
    IExplorerBrowserEvents_fnOnNavigationPending,
    IExplorerBrowserEvents_fnOnViewCreated,
    IExplorerBrowserEvents_fnOnNavigationComplete,
    IExplorerBrowserEvents_fnOnNavigationFailed
};

/**************************************************************************
 * IServiceProvider implementation
 */
static inline FileDialogImpl *impl_from_IServiceProvider(IServiceProvider *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, IServiceProvider_iface);
}

static HRESULT WINAPI IServiceProvider_fnQueryInterface(IServiceProvider *iface,
                                                        REFIID riid, void **ppvObject)
{
    FileDialogImpl *This = impl_from_IServiceProvider(iface);
    TRACE("%p\n", This);
    return IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppvObject);
}

static ULONG WINAPI IServiceProvider_fnAddRef(IServiceProvider *iface)
{
    FileDialogImpl *This = impl_from_IServiceProvider(iface);
    TRACE("%p\n", This);
    return IFileDialog2_AddRef(&This->IFileDialog2_iface);
}

static ULONG WINAPI IServiceProvider_fnRelease(IServiceProvider *iface)
{
    FileDialogImpl *This = impl_from_IServiceProvider(iface);
    TRACE("%p\n", This);
    return IFileDialog2_Release(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IServiceProvider_fnQueryService(IServiceProvider *iface,
                                                      REFGUID guidService,
                                                      REFIID riid, void **ppv)
{
    FileDialogImpl *This = impl_from_IServiceProvider(iface);
    HRESULT hr = E_FAIL;
    TRACE("%p (%s, %s, %p)\n", This, debugstr_guid(guidService), debugstr_guid(riid), ppv);

    *ppv = NULL;
    if(IsEqualGUID(guidService, &SID_STopLevelBrowser) && This->peb)
        hr = IExplorerBrowser_QueryInterface(This->peb, riid, ppv);
    else if(IsEqualGUID(guidService, &SID_SExplorerBrowserFrame))
        hr = IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppv);
    else
        FIXME("Interface %s requested from unknown service %s\n",
              debugstr_guid(riid), debugstr_guid(guidService));

    return hr;
}

static const IServiceProviderVtbl vt_IServiceProvider = {
    IServiceProvider_fnQueryInterface,
    IServiceProvider_fnAddRef,
    IServiceProvider_fnRelease,
    IServiceProvider_fnQueryService
};

/**************************************************************************
 * ICommDlgBrowser3 implementation
 */
static inline FileDialogImpl *impl_from_ICommDlgBrowser3(ICommDlgBrowser3 *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, ICommDlgBrowser3_iface);
}

static HRESULT WINAPI ICommDlgBrowser3_fnQueryInterface(ICommDlgBrowser3 *iface,
                                                        REFIID riid, void **ppvObject)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    TRACE("%p\n", This);
    return IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppvObject);
}

static ULONG WINAPI ICommDlgBrowser3_fnAddRef(ICommDlgBrowser3 *iface)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    TRACE("%p\n", This);
    return IFileDialog2_AddRef(&This->IFileDialog2_iface);
}

static ULONG WINAPI ICommDlgBrowser3_fnRelease(ICommDlgBrowser3 *iface)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    TRACE("%p\n", This);
    return IFileDialog2_Release(&This->IFileDialog2_iface);
}

static HRESULT WINAPI ICommDlgBrowser3_fnOnDefaultCommand(ICommDlgBrowser3 *iface,
                                                          IShellView *shv)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    HRESULT hr;
    TRACE("%p (%p)\n", This, shv);

    hr = on_default_action(This);

    if(SUCCEEDED(hr))
        EndDialog(This->dlg_hwnd, S_OK);

    return S_OK;
}

static HRESULT WINAPI ICommDlgBrowser3_fnOnStateChange(ICommDlgBrowser3 *iface,
                                                       IShellView *shv, ULONG uChange )
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    IDataObject *new_selection;
    HRESULT hr;
    TRACE("%p (%p, %x)\n", This, shv, uChange);

    switch(uChange)
    {
    case CDBOSC_SELCHANGE:
        if(This->psia_selection)
        {
            IShellItemArray_Release(This->psia_selection);
            This->psia_selection = NULL;
        }

        hr = IShellView_GetItemObject(shv, SVGIO_SELECTION, &IID_IDataObject, (void**)&new_selection);
        if(SUCCEEDED(hr))
        {
            hr = SHCreateShellItemArrayFromDataObject(new_selection, &IID_IShellItemArray,
                                                      (void**)&This->psia_selection);
            if(SUCCEEDED(hr))
            {
                fill_filename_from_selection(This);
                events_OnSelectionChange(This);
            }

            IDataObject_Release(new_selection);
        }
        break;
    default:
        TRACE("Unhandled state change\n");
    }
    return S_OK;
}

static HRESULT WINAPI ICommDlgBrowser3_fnIncludeObject(ICommDlgBrowser3 *iface,
                                                       IShellView *shv, LPCITEMIDLIST pidl)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    IShellItem *psi;
    LPWSTR filename;
    LPITEMIDLIST parent_pidl;
    HRESULT hr;
    ULONG attr;
    TRACE("%p (%p, %p)\n", This, shv, pidl);

    if(!This->filterspec_count)
        return S_OK;

    hr = SHGetIDListFromObject((IUnknown*)shv, &parent_pidl);
    if(SUCCEEDED(hr))
    {
        LPITEMIDLIST full_pidl = ILCombine(parent_pidl, pidl);
        hr = SHCreateItemFromIDList(full_pidl, &IID_IShellItem, (void**)&psi);
        ILFree(parent_pidl);
        ILFree(full_pidl);
    }
    if(FAILED(hr))
    {
        ERR("Failed to get shellitem (%08x).\n", hr);
        return S_OK;
    }

    hr = IShellItem_GetAttributes(psi, SFGAO_FOLDER|SFGAO_LINK, &attr);
    if(FAILED(hr) || (attr & (SFGAO_FOLDER | SFGAO_LINK)))
    {
        IShellItem_Release(psi);
        return S_OK;
    }

    hr = S_OK;
    if(SUCCEEDED(IShellItem_GetDisplayName(psi, SIGDN_PARENTRELATIVEPARSING, &filename)))
    {
        if(!PathMatchSpecW(filename, This->filterspecs[This->filetypeindex].pszSpec))
            hr = S_FALSE;
        CoTaskMemFree(filename);
    }

    IShellItem_Release(psi);
    return hr;
}

static HRESULT WINAPI ICommDlgBrowser3_fnNotify(ICommDlgBrowser3 *iface,
                                                IShellView *ppshv, DWORD dwNotifyType)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    FIXME("Stub: %p (%p, 0x%x)\n", This, ppshv, dwNotifyType);
    return E_NOTIMPL;
}

static HRESULT WINAPI ICommDlgBrowser3_fnGetDefaultMenuText(ICommDlgBrowser3 *iface,
                                                            IShellView *pshv,
                                                            LPWSTR pszText, int cchMax)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    FIXME("Stub: %p (%p, %p, %d)\n", This, pshv, pszText, cchMax);
    return E_NOTIMPL;
}

static HRESULT WINAPI ICommDlgBrowser3_fnGetViewFlags(ICommDlgBrowser3 *iface, DWORD *pdwFlags)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    FIXME("Stub: %p (%p)\n", This, pdwFlags);
    return E_NOTIMPL;
}

static HRESULT WINAPI ICommDlgBrowser3_fnOnColumnClicked(ICommDlgBrowser3 *iface,
                                                         IShellView *pshv, int iColumn)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    FIXME("Stub: %p (%p, %d)\n", This, pshv, iColumn);
    return E_NOTIMPL;
}

static HRESULT WINAPI ICommDlgBrowser3_fnGetCurrentFilter(ICommDlgBrowser3 *iface,
                                                          LPWSTR pszFileSpec, int cchFileSpec)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    FIXME("Stub: %p (%p, %d)\n", This, pszFileSpec, cchFileSpec);
    return E_NOTIMPL;
}

static HRESULT WINAPI ICommDlgBrowser3_fnOnPreviewCreated(ICommDlgBrowser3 *iface,
                                                          IShellView *pshv)
{
    FileDialogImpl *This = impl_from_ICommDlgBrowser3(iface);
    FIXME("Stub: %p (%p)\n", This, pshv);
    return E_NOTIMPL;
}

static const ICommDlgBrowser3Vtbl vt_ICommDlgBrowser3 = {
    ICommDlgBrowser3_fnQueryInterface,
    ICommDlgBrowser3_fnAddRef,
    ICommDlgBrowser3_fnRelease,
    ICommDlgBrowser3_fnOnDefaultCommand,
    ICommDlgBrowser3_fnOnStateChange,
    ICommDlgBrowser3_fnIncludeObject,
    ICommDlgBrowser3_fnNotify,
    ICommDlgBrowser3_fnGetDefaultMenuText,
    ICommDlgBrowser3_fnGetViewFlags,
    ICommDlgBrowser3_fnOnColumnClicked,
    ICommDlgBrowser3_fnGetCurrentFilter,
    ICommDlgBrowser3_fnOnPreviewCreated
};

/**************************************************************************
 * IOleWindow implementation
 */
static inline FileDialogImpl *impl_from_IOleWindow(IOleWindow *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, IOleWindow_iface);
}

static HRESULT WINAPI IOleWindow_fnQueryInterface(IOleWindow *iface, REFIID riid, void **ppvObject)
{
    FileDialogImpl *This = impl_from_IOleWindow(iface);
    return IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppvObject);
}

static ULONG WINAPI IOleWindow_fnAddRef(IOleWindow *iface)
{
    FileDialogImpl *This = impl_from_IOleWindow(iface);
    return IFileDialog2_AddRef(&This->IFileDialog2_iface);
}

static ULONG WINAPI IOleWindow_fnRelease(IOleWindow *iface)
{
    FileDialogImpl *This = impl_from_IOleWindow(iface);
    return IFileDialog2_Release(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IOleWindow_fnContextSensitiveHelp(IOleWindow *iface, BOOL fEnterMOde)
{
    FileDialogImpl *This = impl_from_IOleWindow(iface);
    FIXME("Stub: %p (%d)\n", This, fEnterMOde);
    return E_NOTIMPL;
}

static HRESULT WINAPI IOleWindow_fnGetWindow(IOleWindow *iface, HWND *phwnd)
{
    FileDialogImpl *This = impl_from_IOleWindow(iface);
    TRACE("%p (%p)\n", This, phwnd);
    *phwnd = This->dlg_hwnd;
    return S_OK;
}

static const IOleWindowVtbl vt_IOleWindow = {
    IOleWindow_fnQueryInterface,
    IOleWindow_fnAddRef,
    IOleWindow_fnRelease,
    IOleWindow_fnGetWindow,
    IOleWindow_fnContextSensitiveHelp
};

/**************************************************************************
 * IFileDialogCustomize implementation
 */
static inline FileDialogImpl *impl_from_IFileDialogCustomize(IFileDialogCustomize *iface)
{
    return CONTAINING_RECORD(iface, FileDialogImpl, IFileDialogCustomize_iface);
}

static HRESULT WINAPI IFileDialogCustomize_fnQueryInterface(IFileDialogCustomize *iface,
                                                            REFIID riid, void **ppvObject)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    return IFileDialog2_QueryInterface(&This->IFileDialog2_iface, riid, ppvObject);
}

static ULONG WINAPI IFileDialogCustomize_fnAddRef(IFileDialogCustomize *iface)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    return IFileDialog2_AddRef(&This->IFileDialog2_iface);
}

static ULONG WINAPI IFileDialogCustomize_fnRelease(IFileDialogCustomize *iface)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    return IFileDialog2_Release(&This->IFileDialog2_iface);
}

static HRESULT WINAPI IFileDialogCustomize_fnEnableOpenDropDown(IFileDialogCustomize *iface,
                                                                DWORD dwIDCtl)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p (%d)\n", This, dwIDCtl);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddMenu(IFileDialogCustomize *iface,
                                                     DWORD dwIDCtl,
                                                     LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl;
    TBBUTTON tbb;
    HRESULT hr;
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pszLabel);

    hr = cctrl_create_new(This, dwIDCtl, NULL, TOOLBARCLASSNAMEW,
                          TBSTYLE_FLAT | CCS_NODIVIDER, 0,
                          This->cctrl_def_height, &ctrl);
    if(SUCCEEDED(hr))
    {
        ctrl->type = IDLG_CCTRL_MENU;

        /* Add the actual button with a popup menu. */
        tbb.iBitmap = I_IMAGENONE;
        tbb.dwData = (DWORD_PTR)CreatePopupMenu();
        tbb.iString = (DWORD_PTR)pszLabel;
        tbb.fsState = TBSTATE_ENABLED;
        tbb.fsStyle = BTNS_WHOLEDROPDOWN;
        tbb.idCommand = 1;

        SendMessageW(ctrl->hwnd, TB_ADDBUTTONSW, 1, (LPARAM)&tbb);
    }

    return hr;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddPushButton(IFileDialogCustomize *iface,
                                                           DWORD dwIDCtl,
                                                           LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl;
    HRESULT hr;
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pszLabel);

    hr = cctrl_create_new(This, dwIDCtl, pszLabel, WC_BUTTONW, BS_MULTILINE, 0,
                          This->cctrl_def_height, &ctrl);
    if(SUCCEEDED(hr))
        ctrl->type = IDLG_CCTRL_PUSHBUTTON;

    return hr;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddComboBox(IFileDialogCustomize *iface,
                                                         DWORD dwIDCtl)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl;
    HRESULT hr;
    TRACE("%p (%d)\n", This, dwIDCtl);

    hr =  cctrl_create_new(This, dwIDCtl, NULL, WC_COMBOBOXW, CBS_DROPDOWNLIST, 0,
                           This->cctrl_def_height, &ctrl);
    if(SUCCEEDED(hr))
        ctrl->type = IDLG_CCTRL_COMBOBOX;

    return hr;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddRadioButtonList(IFileDialogCustomize *iface,
                                                                DWORD dwIDCtl)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p (%d)\n", This, dwIDCtl);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddCheckButton(IFileDialogCustomize *iface,
                                                            DWORD dwIDCtl,
                                                            LPCWSTR pszLabel,
                                                            BOOL bChecked)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl;
    HRESULT hr;
    TRACE("%p (%d, %p, %d)\n", This, dwIDCtl, pszLabel, bChecked);

    hr = cctrl_create_new(This, dwIDCtl, pszLabel, WC_BUTTONW, BS_AUTOCHECKBOX, 0,
                          This->cctrl_def_height, &ctrl);
    if(SUCCEEDED(hr))
    {
        ctrl->type = IDLG_CCTRL_CHECKBUTTON;
        SendMessageW(ctrl->hwnd, BM_SETCHECK, bChecked ? BST_CHECKED : BST_UNCHECKED, 0);
    }

    return hr;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddEditBox(IFileDialogCustomize *iface,
                                                        DWORD dwIDCtl,
                                                        LPCWSTR pszText)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl;
    HRESULT hr;
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pszText);

    hr = cctrl_create_new(This, dwIDCtl, pszText, WC_EDITW, ES_AUTOHSCROLL, WS_EX_CLIENTEDGE,
                          This->cctrl_def_height, &ctrl);
    if(SUCCEEDED(hr))
        ctrl->type = IDLG_CCTRL_EDITBOX;

    return hr;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddSeparator(IFileDialogCustomize *iface,
                                                          DWORD dwIDCtl)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl;
    HRESULT hr;
    TRACE("%p (%d)\n", This, dwIDCtl);

    hr = cctrl_create_new(This, dwIDCtl, NULL, WC_STATICW, SS_ETCHEDHORZ, 0,
                          GetSystemMetrics(SM_CYEDGE), &ctrl);
    if(SUCCEEDED(hr))
        ctrl->type = IDLG_CCTRL_SEPARATOR;

    return hr;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddText(IFileDialogCustomize *iface,
                                                     DWORD dwIDCtl,
                                                     LPCWSTR pszText)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl;
    HRESULT hr;
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pszText);

    hr = cctrl_create_new(This, dwIDCtl, pszText, WC_STATICW, 0, 0,
                          This->cctrl_def_height, &ctrl);
    if(SUCCEEDED(hr))
        ctrl->type = IDLG_CCTRL_TEXT;

    return hr;
}

static HRESULT WINAPI IFileDialogCustomize_fnSetControlLabel(IFileDialogCustomize *iface,
                                                             DWORD dwIDCtl,
                                                             LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pszLabel);

    if(!ctrl) return E_INVALIDARG;

    switch(ctrl->type)
    {
    case IDLG_CCTRL_MENU:
    case IDLG_CCTRL_PUSHBUTTON:
    case IDLG_CCTRL_CHECKBUTTON:
    case IDLG_CCTRL_TEXT:
        SendMessageW(ctrl->hwnd, WM_SETTEXT, 0, (LPARAM)pszLabel);
        break;
    default:
        break;
    }

    return S_OK;
}

static HRESULT WINAPI IFileDialogCustomize_fnGetControlState(IFileDialogCustomize *iface,
                                                             DWORD dwIDCtl,
                                                             CDCONTROLSTATEF *pdwState)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pdwState);

    if(!ctrl) return E_NOTIMPL;

    *pdwState = ctrl->cdcstate;
    return S_OK;
}

static HRESULT WINAPI IFileDialogCustomize_fnSetControlState(IFileDialogCustomize *iface,
                                                             DWORD dwIDCtl,
                                                             CDCONTROLSTATEF dwState)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This,dwIDCtl);
    TRACE("%p (%d, %x)\n", This, dwIDCtl, dwState);

    if(ctrl)
    {
        LONG wndstyle = GetWindowLongW(ctrl->hwnd, GWL_STYLE);

        if(dwState & CDCS_ENABLED)
            wndstyle &= ~(WS_DISABLED);
        else
            wndstyle |= WS_DISABLED;

        if(dwState & CDCS_VISIBLE)
            wndstyle |= WS_VISIBLE;
        else
            wndstyle &= ~(WS_VISIBLE);

        SetWindowLongW(ctrl->hwnd, GWL_STYLE, wndstyle);

        /* We save the state separately since at least one application
         * relies on being able to hide a control. */
        ctrl->cdcstate = dwState;
    }

    return S_OK;
}

static HRESULT WINAPI IFileDialogCustomize_fnGetEditBoxText(IFileDialogCustomize *iface,
                                                            DWORD dwIDCtl,
                                                            WCHAR **ppszText)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    WCHAR len, *text;
    TRACE("%p (%d, %p)\n", This, dwIDCtl, ppszText);

    if(!ctrl || !(len = SendMessageW(ctrl->hwnd, WM_GETTEXTLENGTH, 0, 0)))
        return E_FAIL;

    text = CoTaskMemAlloc(sizeof(WCHAR)*(len+1));
    if(!text) return E_FAIL;

    SendMessageW(ctrl->hwnd, WM_GETTEXT, len+1, (LPARAM)text);
    *ppszText = text;
    return S_OK;
}

static HRESULT WINAPI IFileDialogCustomize_fnSetEditBoxText(IFileDialogCustomize *iface,
                                                            DWORD dwIDCtl,
                                                            LPCWSTR pszText)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %s)\n", This, dwIDCtl, debugstr_w(pszText));

    if(!ctrl || ctrl->type != IDLG_CCTRL_EDITBOX)
        return E_FAIL;

    SendMessageW(ctrl->hwnd, WM_SETTEXT, 0, (LPARAM)pszText);
    return S_OK;
}

static HRESULT WINAPI IFileDialogCustomize_fnGetCheckButtonState(IFileDialogCustomize *iface,
                                                                 DWORD dwIDCtl,
                                                                 BOOL *pbChecked)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pbChecked);

    if(ctrl)
        *pbChecked = (SendMessageW(ctrl->hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED);

    return S_OK;
}

static HRESULT WINAPI IFileDialogCustomize_fnSetCheckButtonState(IFileDialogCustomize *iface,
                                                                 DWORD dwIDCtl,
                                                                 BOOL bChecked)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %d)\n", This, dwIDCtl, bChecked);

    if(ctrl)
        SendMessageW(ctrl->hwnd, BM_SETCHECK, bChecked ? BST_CHECKED:BST_UNCHECKED, 0);

    return S_OK;
}

static UINT get_combobox_index_from_id(HWND cb_hwnd, DWORD dwIDItem)
{
    UINT count = SendMessageW(cb_hwnd, CB_GETCOUNT, 0, 0);
    UINT i;
    if(!count || (count == CB_ERR))
        return -1;

    for(i = 0; i < count; i++)
        if(SendMessageW(cb_hwnd, CB_GETITEMDATA, i, 0) == dwIDItem)
            return i;

    TRACE("Item with id %d not found in combobox %p (item count: %d)\n", dwIDItem, cb_hwnd, count);
    return -1;
}

static HRESULT WINAPI IFileDialogCustomize_fnAddControlItem(IFileDialogCustomize *iface,
                                                            DWORD dwIDCtl,
                                                            DWORD dwIDItem,
                                                            LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %d, %s)\n", This, dwIDCtl, dwIDItem, debugstr_w(pszLabel));

    if(!ctrl) return E_FAIL;

    switch(ctrl->type)
    {
    case IDLG_CCTRL_COMBOBOX:
    {
        UINT index;

        if(get_combobox_index_from_id(ctrl->hwnd, dwIDItem) != -1)
            return E_INVALIDARG;

        index = SendMessageW(ctrl->hwnd, CB_ADDSTRING, 0, (LPARAM)pszLabel);
        SendMessageW(ctrl->hwnd, CB_SETITEMDATA, index, dwIDItem);

        return S_OK;
    }
    case IDLG_CCTRL_MENU:
    {
        TBBUTTON tbb;
        SendMessageW(ctrl->hwnd, TB_GETBUTTON, 0, (LPARAM)&tbb);

        if(GetMenuState((HMENU)tbb.dwData, dwIDItem, MF_BYCOMMAND) != -1)
            return E_INVALIDARG;

        AppendMenuW((HMENU)tbb.dwData, MF_STRING, dwIDItem, pszLabel);
        return S_OK;
    }
    default:
        break;
    }

    return E_NOINTERFACE; /* win7 */
}

static HRESULT WINAPI IFileDialogCustomize_fnRemoveControlItem(IFileDialogCustomize *iface,
                                                               DWORD dwIDCtl,
                                                               DWORD dwIDItem)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %d)\n", This, dwIDCtl, dwIDItem);

    if(!ctrl) return E_FAIL;

    switch(ctrl->type)
    {
    case IDLG_CCTRL_COMBOBOX:
    {
        UINT i, count = SendMessageW(ctrl->hwnd, CB_GETCOUNT, 0, 0);
        if(!count || (count == CB_ERR))
            return E_FAIL;

        for(i = 0; i < count; i++)
            if(SendMessageW(ctrl->hwnd, CB_GETITEMDATA, 0, 0) == dwIDItem)
            {
                if(SendMessageW(ctrl->hwnd, CB_DELETESTRING, i, 0) == CB_ERR)
                    return E_FAIL;
                return S_OK;
            }

        return E_UNEXPECTED;
    }
    case IDLG_CCTRL_MENU:
    {
        TBBUTTON tbb;
        HMENU hmenu;
        SendMessageW(ctrl->hwnd, TB_GETBUTTON, 0, (LPARAM)&tbb);
        hmenu = (HMENU)tbb.dwData;

        if(!hmenu || !DeleteMenu(hmenu, dwIDItem, MF_BYCOMMAND))
            return E_UNEXPECTED;

        return S_OK;
    }
    default:
        break;
    }

    return E_FAIL;
}

static HRESULT WINAPI IFileDialogCustomize_fnRemoveAllControlItems(IFileDialogCustomize *iface,
                                                                   DWORD dwIDCtl)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    TRACE("%p (%d)\n", This, dwIDCtl);

    /* Not implemented by native */
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnGetControlItemState(IFileDialogCustomize *iface,
                                                                 DWORD dwIDCtl,
                                                                 DWORD dwIDItem,
                                                                 CDCONTROLSTATEF *pdwState)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnSetControlItemState(IFileDialogCustomize *iface,
                                                                 DWORD dwIDCtl,
                                                                 DWORD dwIDItem,
                                                                 CDCONTROLSTATEF dwState)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnGetSelectedControlItem(IFileDialogCustomize *iface,
                                                                    DWORD dwIDCtl,
                                                                    DWORD *pdwIDItem)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %p)\n", This, dwIDCtl, pdwIDItem);

    if(!ctrl) return E_FAIL;

    switch(ctrl->type)
    {
    case IDLG_CCTRL_COMBOBOX:
    {
        UINT index = SendMessageW(ctrl->hwnd, CB_GETCURSEL, 0, 0);
        if(index == CB_ERR)
            return E_FAIL;

        *pdwIDItem = SendMessageW(ctrl->hwnd, CB_GETITEMDATA, index, 0);
        return S_OK;
    }
    default:
        FIXME("Unsupported control type %d\n", ctrl->type);
    }

    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnSetSelectedControlItem(IFileDialogCustomize *iface,
                                                                    DWORD dwIDCtl,
                                                                    DWORD dwIDItem)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    customctrl *ctrl = get_cctrl(This, dwIDCtl);
    TRACE("%p (%d, %d)\n", This, dwIDCtl, dwIDItem);

    if(!ctrl) return E_INVALIDARG;

    switch(ctrl->type)
    {
    case IDLG_CCTRL_COMBOBOX:
    {
        UINT index = get_combobox_index_from_id(ctrl->hwnd, dwIDItem);

        if(index == -1)
            return E_INVALIDARG;

        if(SendMessageW(ctrl->hwnd, CB_SETCURSEL, index, 0) == CB_ERR)
            return E_FAIL;

        return S_OK;
    }
    default:
        FIXME("Unsupported control type %d\n", ctrl->type);
    }

    return E_INVALIDARG;
}

static HRESULT WINAPI IFileDialogCustomize_fnStartVisualGroup(IFileDialogCustomize *iface,
                                                              DWORD dwIDCtl,
                                                              LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p (%d, %s)\n", This, dwIDCtl, debugstr_w(pszLabel));
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnEndVisualGroup(IFileDialogCustomize *iface)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnMakeProminent(IFileDialogCustomize *iface,
                                                           DWORD dwIDCtl)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p (%d)\n", This, dwIDCtl);
    return E_NOTIMPL;
}

static HRESULT WINAPI IFileDialogCustomize_fnSetControlItemText(IFileDialogCustomize *iface,
                                                                DWORD dwIDCtl,
                                                                DWORD dwIDItem,
                                                                LPCWSTR pszLabel)
{
    FileDialogImpl *This = impl_from_IFileDialogCustomize(iface);
    FIXME("stub - %p (%d, %d, %p)\n", This, dwIDCtl, dwIDItem, debugstr_w(pszLabel));
    return E_NOTIMPL;
}

static const IFileDialogCustomizeVtbl vt_IFileDialogCustomize = {
    IFileDialogCustomize_fnQueryInterface,
    IFileDialogCustomize_fnAddRef,
    IFileDialogCustomize_fnRelease,
    IFileDialogCustomize_fnEnableOpenDropDown,
    IFileDialogCustomize_fnAddMenu,
    IFileDialogCustomize_fnAddPushButton,
    IFileDialogCustomize_fnAddComboBox,
    IFileDialogCustomize_fnAddRadioButtonList,
    IFileDialogCustomize_fnAddCheckButton,
    IFileDialogCustomize_fnAddEditBox,
    IFileDialogCustomize_fnAddSeparator,
    IFileDialogCustomize_fnAddText,
    IFileDialogCustomize_fnSetControlLabel,
    IFileDialogCustomize_fnGetControlState,
    IFileDialogCustomize_fnSetControlState,
    IFileDialogCustomize_fnGetEditBoxText,
    IFileDialogCustomize_fnSetEditBoxText,
    IFileDialogCustomize_fnGetCheckButtonState,
    IFileDialogCustomize_fnSetCheckButtonState,
    IFileDialogCustomize_fnAddControlItem,
    IFileDialogCustomize_fnRemoveControlItem,
    IFileDialogCustomize_fnRemoveAllControlItems,
    IFileDialogCustomize_fnGetControlItemState,
    IFileDialogCustomize_fnSetControlItemState,
    IFileDialogCustomize_fnGetSelectedControlItem,
    IFileDialogCustomize_fnSetSelectedControlItem,
    IFileDialogCustomize_fnStartVisualGroup,
    IFileDialogCustomize_fnEndVisualGroup,
    IFileDialogCustomize_fnMakeProminent,
    IFileDialogCustomize_fnSetControlItemText
};

static HRESULT FileDialog_constructor(IUnknown *pUnkOuter, REFIID riid, void **ppv, enum ITEMDLG_TYPE type)
{
    FileDialogImpl *fdimpl;
    HRESULT hr;
    IShellFolder *psf;
    TRACE("%p, %s, %p\n", pUnkOuter, debugstr_guid(riid), ppv);

    if(!ppv)
        return E_POINTER;
    if(pUnkOuter)
        return CLASS_E_NOAGGREGATION;

    fdimpl = HeapAlloc(GetProcessHeap(), 0, sizeof(FileDialogImpl));
    if(!fdimpl)
        return E_OUTOFMEMORY;

    fdimpl->ref = 1;
    fdimpl->IFileDialog2_iface.lpVtbl = &vt_IFileDialog2;
    fdimpl->IExplorerBrowserEvents_iface.lpVtbl = &vt_IExplorerBrowserEvents;
    fdimpl->IServiceProvider_iface.lpVtbl = &vt_IServiceProvider;
    fdimpl->ICommDlgBrowser3_iface.lpVtbl = &vt_ICommDlgBrowser3;
    fdimpl->IOleWindow_iface.lpVtbl = &vt_IOleWindow;
    fdimpl->IFileDialogCustomize_iface.lpVtbl = &vt_IFileDialogCustomize;

    if(type == ITEMDLG_TYPE_OPEN)
    {
        fdimpl->dlg_type = ITEMDLG_TYPE_OPEN;
        fdimpl->u.IFileOpenDialog_iface.lpVtbl = &vt_IFileOpenDialog;
        fdimpl->options = FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
        fdimpl->custom_title = fdimpl->custom_okbutton = NULL;
    }
    else
    {
        WCHAR buf[16];
        fdimpl->dlg_type = ITEMDLG_TYPE_SAVE;
        fdimpl->u.IFileSaveDialog_iface.lpVtbl = &vt_IFileSaveDialog;
        fdimpl->options = FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;

        LoadStringW(COMDLG32_hInstance, IDS_SAVE, buf, sizeof(buf)/sizeof(WCHAR));
        fdimpl->custom_title = StrDupW(buf);
        fdimpl->custom_okbutton = StrDupW(buf);
    }

    fdimpl->filterspecs = NULL;
    fdimpl->filterspec_count = 0;
    fdimpl->filetypeindex = 0;

    fdimpl->psia_selection = fdimpl->psia_results = NULL;
    fdimpl->psi_setfolder = fdimpl->psi_folder = NULL;

    list_init(&fdimpl->events_clients);
    fdimpl->events_next_cookie = 0;

    fdimpl->dlg_hwnd = NULL;
    fdimpl->peb = NULL;

    fdimpl->set_filename = NULL;
    fdimpl->default_ext = NULL;
    fdimpl->custom_cancelbutton = fdimpl->custom_filenamelabel = NULL;

    /* FIXME: The default folder setting should be restored for the
     * application if it was previously set. */
    SHGetDesktopFolder(&psf);
    SHGetItemFromObject((IUnknown*)psf, &IID_IShellItem, (void**)&fdimpl->psi_defaultfolder);
    IShellFolder_Release(psf);

    hr = init_custom_controls(fdimpl);
    if(FAILED(hr))
    {
        ERR("Failed to initialize custom controls (0x%08x).\n", hr);
        IUnknown_Release((IUnknown*)fdimpl);
        return E_FAIL;
    }

    hr = IUnknown_QueryInterface((IUnknown*)fdimpl, riid, ppv);
    IUnknown_Release((IUnknown*)fdimpl);
    return hr;
}

HRESULT FileOpenDialog_Constructor(IUnknown *pUnkOuter, REFIID riid, void **ppv)
{
    return FileDialog_constructor(pUnkOuter, riid, ppv, ITEMDLG_TYPE_OPEN);
}

HRESULT FileSaveDialog_Constructor(IUnknown *pUnkOuter, REFIID riid, void **ppv)
{
    return FileDialog_constructor(pUnkOuter, riid, ppv, ITEMDLG_TYPE_SAVE);
}