/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>

#include <memory>

#include "VistaFilePickerImpl.hxx"

#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
#include <com/sun/star/ui/dialogs/ControlActions.hpp>
#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
#include <com/sun/star/beans/StringPair.hpp>
#include <com/sun/star/awt/XWindow.hpp>
#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp>
#include <com/sun/star/lang/SystemDependent.hpp>
#include <comphelper/sequence.hxx>
#include <fpicker/strings.hrc>
#include <fpicker/fpsofficeResMgr.hxx>
#include <osl/file.hxx>
#include <rtl/process.h>
#include <o3tl/char16_t2wchar_t.hxx>
#include <o3tl/string_view.hxx>
#include <vcl/svapp.hxx>
#include "WinImplHelper.hxx"

#include <shlguid.h>
#include <shlobj.h>

static bool is_current_process_window(HWND hwnd)
{
    DWORD pid;
    GetWindowThreadProcessId(hwnd, &pid);
    return (pid == GetCurrentProcessId());
}

static HWND choose_parent_window()
{
    HWND hwnd_parent = GetForegroundWindow();
    if (!is_current_process_window(hwnd_parent))
       hwnd_parent = GetDesktopWindow();
    return hwnd_parent;
}

namespace {

bool createFolderItem(OUString const& url, sal::systools::COMReference<IShellItem>& folder)
{
    OUString path;
    if (osl::FileBase::getSystemPathFromFileURL(url, path)
        != osl::FileBase::E_None)
    {
        return false;
    }
    HRESULT res = SHCreateItemFromParsingName(
        o3tl::toW(path.getStr()), nullptr,
        IID_PPV_ARGS(&folder));
    return SUCCEEDED(res);
}

}

namespace fpicker{
namespace win32{
namespace vista{


// types, const etcpp.


const ::sal_Int16 INVALID_CONTROL_ID     = -1;
const ::sal_Int16 INVALID_CONTROL_ACTION = -1;

// Guids used for IFileDialog::SetClientGuid
const GUID CLIENTID_FILEDIALOG_SIMPLE        = {0xB8628FD3, 0xA3F5, 0x4845, 0x9B, 0x62, 0xD5, 0x1E, 0xDF, 0x97, 0xC4, 0x83};
const GUID CLIENTID_FILEDIALOG_OPTIONS       = {0x93ED486F, 0x0D04, 0x4807, 0x8C, 0x44, 0xAC, 0x26, 0xCB, 0x6C, 0x5D, 0x36};
const GUID CLIENTID_FILESAVE_PASSWORD        = {0xC12D4F4C, 0x4D41, 0x4D4F, 0x97, 0xEF, 0x87, 0xF9, 0x8D, 0xB6, 0x1E, 0xA6};
const GUID CLIENTID_FILESAVE_SELECTION       = {0x5B2482B3, 0x0358, 0x4E09, 0xAA, 0x64, 0x2B, 0x76, 0xB2, 0xA0, 0xDD, 0xFE};
const GUID CLIENTID_FILESAVE_TEMPLATE        = {0x9996D877, 0x20D5, 0x424B, 0x9C, 0x2E, 0xD3, 0xB6, 0x31, 0xEC, 0xF7, 0xCE};
const GUID CLIENTID_FILEOPEN_LINK_TEMPLATE   = {0x32237796, 0x1509, 0x49D1, 0xBB, 0x7E, 0x63, 0xAD, 0x36, 0xAE, 0x86, 0x8C};
const GUID CLIENTID_FILEOPEN_LINK_ANCHOR     = {0xBE3188CB, 0x399A, 0x45AE, 0x8F, 0x78, 0x75, 0x17, 0xAF, 0x26, 0x81, 0xEA};
const GUID CLIENTID_FILEOPEN_PLAY            = {0x32CFB147, 0xF5AE, 0x4F90, 0xA1, 0xF1, 0x81, 0x20, 0x72, 0xBB, 0x2F, 0xC5};
const GUID CLIENTID_FILEOPEN_LINK            = {0x39AC4BAE, 0x7D2D, 0x46BC, 0xBE, 0x2E, 0xF8, 0x8C, 0xB5, 0x65, 0x5E, 0x6A};


class TDialogImplBase
{
public:
    TDialogImplBase(IFileDialog* iDialog)
        : m_iDialog(iDialog)
    {
    }

    virtual ~TDialogImplBase() = default;

    TFileDialog getComPtr() { return m_iDialog; }
    virtual sal::systools::COMReference<IShellItemArray> getResult(bool bInExecute)
    {
        sal::systools::COMReference<IShellItem> iItem;
        if (m_iDialog.is())
        {
            if (bInExecute)
                m_iDialog->GetCurrentSelection(&iItem);
            else
                m_iDialog->GetResult(&iItem);
        }
        void* iItems = nullptr;
        if (iItem.is())
            SHCreateShellItemArrayFromShellItem(iItem.get(), IID_IShellItemArray, &iItems);
        return static_cast<IShellItemArray*>(iItems);
    }

private:
    TFileDialog m_iDialog;
};

namespace {

template <class ComPtrDialog, REFCLSID CLSID> class TDialogImpl : public TDialogImplBase
{
public:
    TDialogImpl()
        : TDialogImplBase(ComPtrDialog(CLSID).get())
    {
    }
};

class TOpenDialogImpl : public TDialogImpl<TFileOpenDialog, CLSID_FileOpenDialog>
{
public:
    sal::systools::COMReference<IShellItemArray> getResult(bool bInExecute) override
    {
        sal::systools::COMReference<IShellItemArray> iItems;
        TFileOpenDialog iDialog(getComPtr(), sal::systools::COM_QUERY_THROW);
        bool bGetResult = false;
        if (!iDialog.is())
            bGetResult = true;
        else if (FAILED(bInExecute ? iDialog->GetSelectedItems(&iItems) : iDialog->GetResults(&iItems)))
            bGetResult = true;

        if (bGetResult)
            iItems = TDialogImplBase::getResult(bInExecute);

        return iItems;
    }
};

}

using TSaveDialogImpl = TDialogImpl<TFileSaveDialog, CLSID_FileSaveDialog>;
using TFolderPickerDialogImpl = TDialogImpl<TFileOpenDialog, CLSID_FileOpenDialog>;


static OUString lcl_getURLFromShellItem (IShellItem* pItem)
{
    sal::systools::CoTaskMemAllocated<wchar_t> pStr;
    HRESULT hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pStr);
    if (FAILED(hr))
    {
        // tdf#155176: One could think that querying SIGDN_URL would go first. But Windows uses
        // current 8-bit codepage for the filenames, and URL-encodes those octets. So check it
        // only after SIGDN_FILESYSPATH query failed (can it ever happen?)
        if (SUCCEEDED(pItem->GetDisplayName(SIGDN_URL, &pStr)))
            return OUString(o3tl::toU(pStr));

        hr = pItem->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &pStr);
        if (SUCCEEDED(hr))
        {
            GUID known_folder_id;
            wchar_t* pStr2 = pStr;
            if (pStr2[0] == ':' && pStr2[1] == ':' && pStr2[2] == '{')
                pStr2 += 2;
            hr = IIDFromString(pStr2, &known_folder_id);
            if (SUCCEEDED(hr))
                hr = SHGetKnownFolderPath(known_folder_id, 0, nullptr, &pStr);
        }
    }

    // Default fallback
    if (FAILED(hr))
        hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &pStr);

    OUString sURL;
    if (SUCCEEDED(hr))
        ::osl::FileBase::getFileURLFromSystemPath(OUString(o3tl::toU(pStr)), sURL);
    return sURL;
}

// Vista file picker shows the filter mask next to filter name in the list; so we need to remove the
// mask from the filter name to avoid duplicating masks
static OUString lcl_AdjustFilterName(const OUString& sName)
{
    const sal_Int32 idx = sName.indexOf("(.");
    return (idx > 0) ? OUString(o3tl::trim(sName.subView(0, idx))) : sName;
}

// rvStrings holds the OUStrings, pointers to which data are stored in returned COMDLG_FILTERSPEC
static ::std::vector<COMDLG_FILTERSPEC> lcl_buildFilterList(CFilterContainer& rContainer,
                                                            std::vector<OUString>& rvStrings)
{
    ::std::vector< COMDLG_FILTERSPEC > lList  ;
    CFilterContainer::FILTER_ENTRY_T   aFilter;

    rContainer.beginEnumFilter( );
    while( rContainer.getNextFilter(aFilter) )
    {
        COMDLG_FILTERSPEC aSpec;

        rvStrings.push_back(lcl_AdjustFilterName(aFilter.first)); // to avoid dangling pointer
        aSpec.pszName = o3tl::toW(rvStrings.back().getStr());
        aSpec.pszSpec = o3tl::toW(aFilter.second.getStr());

        lList.push_back(aSpec);
    }

    return lList;
}


VistaFilePickerImpl::VistaFilePickerImpl()
    : m_lFilters     ()
    , m_iEventHandler(new VistaFilePickerEventHandler(this))
    , m_bInExecute   (false)
    , m_bWasExecuted (false)
    , m_hParentWindow(nullptr)
    , m_sDirectory   ()
    , m_sFilename    ()
{
}


VistaFilePickerImpl::~VistaFilePickerImpl()
{
}


void VistaFilePickerImpl::doRequest(Request& rRequest)
{
    try
    {
        switch(rRequest.getRequest())
        {
            case E_ADD_PICKER_LISTENER :
                    impl_sta_addFilePickerListener(rRequest);
                    break;

            case E_REMOVE_PICKER_LISTENER :
                    impl_sta_removeFilePickerListener(rRequest);
                    break;

            case E_APPEND_FILTER :
                    impl_sta_appendFilter(rRequest);
                    break;

            case E_APPEND_FILTERGROUP :
                    impl_sta_appendFilterGroup(rRequest);
                    break;

            case E_SET_CURRENT_FILTER :
                    impl_sta_setCurrentFilter(rRequest);
                    break;

            case E_GET_CURRENT_FILTER :
                    impl_sta_getCurrentFilter(rRequest);
                    break;

            case E_CREATE_OPEN_DIALOG :
                    impl_sta_CreateOpenDialog(rRequest);
                    break;

            case E_CREATE_SAVE_DIALOG :
                    impl_sta_CreateSaveDialog(rRequest);
                    break;

            case E_CREATE_FOLDER_PICKER:
                    impl_sta_CreateFolderPicker(rRequest);
                    break;

            case E_SET_MULTISELECTION_MODE :
                    impl_sta_SetMultiSelectionMode(rRequest);
                    break;

            case E_SET_TITLE :
                    impl_sta_SetTitle(rRequest);
                    break;

            case E_SET_FILENAME:
                impl_sta_SetFileName(rRequest);
                break;

            case E_SET_DIRECTORY :
                    impl_sta_SetDirectory(rRequest);
                    break;

            case E_GET_DIRECTORY :
                    impl_sta_GetDirectory(rRequest);
                    break;

            case E_SET_DEFAULT_NAME :
                    impl_sta_SetDefaultName(rRequest);
                    break;

            case E_GET_SELECTED_FILES :
                    impl_sta_getSelectedFiles(rRequest);
                    break;

            case E_SHOW_DIALOG_MODAL :
                    impl_sta_ShowDialogModal(rRequest);
                    break;

            case E_SET_CONTROL_VALUE :
                    impl_sta_SetControlValue(rRequest);
                    break;

            case E_GET_CONTROL_VALUE :
                    impl_sta_GetControlValue(rRequest);
                    break;

            case E_SET_CONTROL_LABEL :
                    impl_sta_SetControlLabel(rRequest);
                    break;

            case E_GET_CONTROL_LABEL :
                    impl_sta_GetControlLabel(rRequest);
                    break;

            case E_ENABLE_CONTROL :
                    impl_sta_EnableControl(rRequest);
                    break;

            // no default: let the compiler detect changes on enum ERequest !
        }
    }
    catch(...)
    {}
}


void VistaFilePickerImpl::impl_sta_addFilePickerListener(Request& rRequest)
{
    const css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener = rRequest.getArgumentOrDefault(PROP_PICKER_LISTENER, css::uno::Reference< css::ui::dialogs::XFilePickerListener >());
    if ( ! xListener.is())
        return;

    if (m_iEventHandler.is())
    {
        auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get());
        pHandlerImpl->addFilePickerListener(xListener);
    }
}


void VistaFilePickerImpl::impl_sta_removeFilePickerListener(Request& rRequest)
{
    const css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener = rRequest.getArgumentOrDefault(PROP_PICKER_LISTENER, css::uno::Reference< css::ui::dialogs::XFilePickerListener >());
    if ( ! xListener.is())
        return;

    if (m_iEventHandler.is())
    {
        auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get());
        pHandlerImpl->removeFilePickerListener(xListener);
    }
}


void VistaFilePickerImpl::impl_sta_appendFilter(Request& rRequest)
{
    const OUString sTitle  = rRequest.getArgumentOrDefault(PROP_FILTER_TITLE, OUString());
    const OUString sFilter = rRequest.getArgumentOrDefault(PROP_FILTER_VALUE, OUString());

    m_lFilters.addFilter(sTitle, sFilter);
}


void VistaFilePickerImpl::impl_sta_appendFilterGroup(Request& rRequest)
{
    const css::uno::Sequence< css::beans::StringPair > aFilterGroup  =
        rRequest.getArgumentOrDefault(PROP_FILTER_GROUP, css::uno::Sequence< css::beans::StringPair >());

    if ( m_lFilters.numFilter() > 0 && aFilterGroup.getLength() > 0 )
        m_lFilters.addFilter( STRING_SEPARATOR, "", true );

    ::sal_Int32 c = aFilterGroup.getLength();
    ::sal_Int32 i = 0;
    for (i=0; i<c; ++i)
    {
        const css::beans::StringPair& rFilter = aFilterGroup[i];
        m_lFilters.addFilter(rFilter.First, rFilter.Second);
    }
}


void VistaFilePickerImpl::impl_sta_setCurrentFilter(Request& rRequest)
{
    const OUString sTitle  = rRequest.getArgumentOrDefault(PROP_FILTER_TITLE, OUString());

    m_lFilters.setCurrentFilter(sTitle);
}


void VistaFilePickerImpl::impl_sta_getCurrentFilter(Request& rRequest)
{
    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    UINT        nIndex  = UINT_MAX;
    HRESULT     hResult = iDialog->GetFileTypeIndex(&nIndex);
    if (
        ( FAILED(hResult)    ) ||
        ( nIndex == UINT_MAX )      // COM dialog sometimes return S_OK for empty filter lists .-(
       )
        return;

    OUString sTitle;
    ::sal_Int32     nRealIndex = nIndex-1; // COM dialog base on 1 ... filter container on 0 .-)
    if (
        (nRealIndex >= 0                         ) &&
        (m_lFilters.getFilterNameByIndex(nRealIndex, sTitle))
       )
        rRequest.setArgument(PROP_FILTER_TITLE, sTitle);
    else if ( nRealIndex == -1 ) // Dialog not visible yet
    {
        sTitle = m_lFilters.getCurrentFilter();
        rRequest.setArgument(PROP_FILTER_TITLE, sTitle);
    }
}


template <class TDialogImplClass> void VistaFilePickerImpl::impl_sta_CreateDialog()
{
    m_pDialog = std::make_shared<TDialogImplClass>();
}


void VistaFilePickerImpl::impl_sta_InitDialog(Request& rRequest, DWORD nOrFlags)
{
    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    DWORD nFlags = 0;
    iDialog->GetOptions ( &nFlags );

    nFlags &= ~FOS_FORCESHOWHIDDEN;
    nFlags |=  FOS_PATHMUSTEXIST;
    nFlags |=  FOS_DONTADDTORECENT;
    nFlags |= nOrFlags;

    iDialog->SetOptions ( nFlags );

    css::uno::Reference<css::awt::XWindow> xWindow = rRequest.getArgumentOrDefault(PROP_PARENT_WINDOW, css::uno::Reference<css::awt::XWindow>());
    if(xWindow.is())
    {
        css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysDepWin(xWindow,css::uno::UNO_QUERY);
        if(xSysDepWin.is()) {
            css::uno::Sequence<sal_Int8> aProcessIdent(16);
            rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray()));
            css::uno::Any aAny = xSysDepWin->getWindowHandle(aProcessIdent,css::lang::SystemDependent::SYSTEM_WIN32);
            sal_Int64 tmp = 0;
            aAny >>= tmp;
            if(tmp != 0)
            {
                m_hParentWindow = reinterpret_cast<HWND>(tmp);
            }
        }
    }

    ::sal_Int32 nFeatures = rRequest.getArgumentOrDefault(PROP_FEATURES, ::sal_Int32(0));
    ::sal_Int32 nTemplate = rRequest.getArgumentOrDefault(PROP_TEMPLATE_DESCR, ::sal_Int32(0));
    impl_sta_enableFeatures(nFeatures, nTemplate);

    if (m_iEventHandler.is())
    {
        auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get());
        pHandlerImpl->startListening(iDialog);
    }
}


void VistaFilePickerImpl::impl_sta_CreateOpenDialog(Request& rRequest)
{
    impl_sta_CreateDialog<TOpenDialogImpl>();
    impl_sta_InitDialog(rRequest, FOS_FILEMUSTEXIST | FOS_OVERWRITEPROMPT);
}


void VistaFilePickerImpl::impl_sta_CreateSaveDialog(Request& rRequest)
{
    impl_sta_CreateDialog<TSaveDialogImpl>();
    impl_sta_InitDialog(rRequest, FOS_FILEMUSTEXIST | FOS_OVERWRITEPROMPT);
}


void VistaFilePickerImpl::impl_sta_CreateFolderPicker(Request& rRequest)
{
    impl_sta_CreateDialog<TFolderPickerDialogImpl>();
    impl_sta_InitDialog(rRequest, FOS_PICKFOLDERS);
}


const ::sal_Int32 GROUP_VERSION         =   1;
const ::sal_Int32 GROUP_TEMPLATE        =   2;
const ::sal_Int32 GROUP_IMAGETEMPLATE   =   3;
const ::sal_Int32 GROUP_CHECKBOXES      =   4;
const ::sal_Int32 GROUP_IMAGEANCHOR     =   5;


static void setLabelToControl(TFileDialogCustomize iCustom, sal_uInt16 nControlId)
{
    OUString aLabel = CResourceProvider::getResString(nControlId);
    aLabel = SOfficeToWindowsLabel(aLabel);
    iCustom->SetControlLabel(nControlId, o3tl::toW(aLabel.getStr()) );
}


void VistaFilePickerImpl::impl_sta_enableFeatures(::sal_Int32 nFeatures, ::sal_Int32 nTemplate)
{
    GUID aGUID = {};
    switch (nTemplate)
    {
        case css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE :
        case css::ui::dialogs::TemplateDescription::FILEOPEN_PREVIEW :
        case css::ui::dialogs::TemplateDescription::FILESAVE_SIMPLE :
            aGUID = CLIENTID_FILEDIALOG_SIMPLE;
            break;

        case css::ui::dialogs::TemplateDescription::FILEOPEN_READONLY_VERSION :
        case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS :
            aGUID = CLIENTID_FILEDIALOG_OPTIONS;
            break;

        case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD :
            aGUID = CLIENTID_FILESAVE_PASSWORD;
            break;

        case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION :
        case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_SELECTION :
            aGUID = CLIENTID_FILESAVE_SELECTION;
            break;

        case css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_TEMPLATE :
            aGUID = CLIENTID_FILESAVE_TEMPLATE;
            break;

        case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE :
            aGUID = CLIENTID_FILEOPEN_LINK_TEMPLATE;
            break;

        case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR :
            aGUID = CLIENTID_FILEOPEN_LINK_ANCHOR;
            break;

        case css::ui::dialogs::TemplateDescription::FILEOPEN_PLAY :
        case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PLAY :
            aGUID = CLIENTID_FILEOPEN_PLAY;
            break;

        case css::ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW :
            aGUID = CLIENTID_FILEOPEN_LINK;
            break;
    }
    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (iDialog.is())
        iDialog->SetClientGuid ( aGUID );

    TFileDialogCustomize iCustom = impl_getCustomizeInterface();
    if (!iCustom.is())
        return;

    if ((nFeatures & FEATURE_VERSION) == FEATURE_VERSION)
    {
        iCustom->StartVisualGroup (GROUP_VERSION, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_VERSION).replaceFirst("~","").getStr()));
        iCustom->AddComboBox      (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_VERSION);
        iCustom->EndVisualGroup   ();
        iCustom->MakeProminent    (GROUP_VERSION);
    }

    if ((nFeatures & FEATURE_TEMPLATE) == FEATURE_TEMPLATE)
    {
        iCustom->StartVisualGroup (GROUP_TEMPLATE, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_TEMPLATES).replaceFirst("~","").getStr()));
        iCustom->AddComboBox      (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_TEMPLATE);
        iCustom->EndVisualGroup   ();
        iCustom->MakeProminent    (GROUP_TEMPLATE);
    }

    if ((nFeatures & FEATURE_IMAGETEMPLATE) == FEATURE_IMAGETEMPLATE)
    {
        iCustom->StartVisualGroup (GROUP_IMAGETEMPLATE, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_IMAGE_TEMPLATE).replaceFirst("~","").getStr()));
        iCustom->AddComboBox      (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE);
        iCustom->EndVisualGroup   ();
        iCustom->MakeProminent    (GROUP_IMAGETEMPLATE);
    }

    if ((nFeatures & FEATURE_IMAGEANCHOR) == FEATURE_IMAGEANCHOR)
    {
        iCustom->StartVisualGroup (GROUP_IMAGEANCHOR, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_IMAGE_ANCHOR).replaceFirst("~","").getStr()));
        iCustom->AddComboBox      (css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR);
        iCustom->EndVisualGroup   ();
        iCustom->MakeProminent    (GROUP_IMAGEANCHOR);
    }

    iCustom->StartVisualGroup (GROUP_CHECKBOXES, L"");

    sal_uInt16 nControlId(0);
    if ((nFeatures & FEATURE_AUTOEXTENSION) == FEATURE_AUTOEXTENSION)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION;
        iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_AUTO_EXTENSION).replaceFirst("~","").getStr()), true);
        setLabelToControl(iCustom, nControlId);
    }

    if ((nFeatures & FEATURE_PASSWORD) == FEATURE_PASSWORD)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PASSWORD;
        iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_PASSWORD).replaceFirst("~","").getStr()), false);
        setLabelToControl(iCustom, nControlId);
    }

    if ((nFeatures & FEATURE_GPGPASSWORD) == FEATURE_GPGPASSWORD)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_GPGENCRYPTION;
        iCustom->AddCheckButton (nControlId, L"GpgPassword", false);
        setLabelToControl(iCustom, nControlId);
    }

    if ((nFeatures & FEATURE_GPGSIGN) == FEATURE_GPGSIGN)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_GPGSIGN;
        iCustom->AddCheckButton (nControlId, L"GpgSign", false);
        setLabelToControl(iCustom, nControlId);
    }

    if ((nFeatures & FEATURE_READONLY) == FEATURE_READONLY)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_READONLY;
        iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_READONLY).replaceFirst("~","").getStr()), false);
        setLabelToControl(iCustom, nControlId);
    }

    if ((nFeatures & FEATURE_FILTEROPTIONS) == FEATURE_FILTEROPTIONS)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS;
        iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_FILTER_OPTIONS).replaceFirst("~","").getStr()), false);
        setLabelToControl(iCustom, nControlId);
    }

    if ((nFeatures & FEATURE_LINK) == FEATURE_LINK)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK;
        iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_INSERT_AS_LINK).replaceFirst("~","").getStr()), false);
        setLabelToControl(iCustom, nControlId);
    }

    if ((nFeatures & FEATURE_SELECTION) == FEATURE_SELECTION)
    {
        nControlId = css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_SELECTION;
        iCustom->AddCheckButton (nControlId, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_SELECTION).replaceFirst("~","").getStr()), false);
        setLabelToControl(iCustom, nControlId);
    }

    /* can be ignored ... new COM dialog supports preview native now  !
    if ((nFeatures & FEATURE_PREVIEW) == FEATURE_PREVIEW)
        iCustom->AddCheckButton (css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PREVIEW, L"Preview", false);
    */

    iCustom->EndVisualGroup();

    if ((nFeatures & FEATURE_PLAY) == FEATURE_PLAY)
        iCustom->AddPushButton (css::ui::dialogs::ExtendedFilePickerElementIds::PUSHBUTTON_PLAY, o3tl::toW(FpsResId(STR_SVT_FILEPICKER_PLAY).replaceFirst("~","").getStr()));

}


void VistaFilePickerImpl::impl_sta_SetMultiSelectionMode(Request& rRequest)
{
    const bool bMultiSelection = rRequest.getArgumentOrDefault(PROP_MULTISELECTION_MODE, true);

    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    DWORD nFlags = 0;
    iDialog->GetOptions(&nFlags);

    if (bMultiSelection)
        nFlags |=  FOS_ALLOWMULTISELECT;
    else
        nFlags &= ~FOS_ALLOWMULTISELECT;

    iDialog->SetOptions ( nFlags );
}


void VistaFilePickerImpl::impl_sta_SetTitle(Request& rRequest)
{
    OUString sTitle = rRequest.getArgumentOrDefault(PROP_TITLE, OUString());

    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    iDialog->SetTitle(o3tl::toW(sTitle.getStr()));
}


void VistaFilePickerImpl::impl_sta_SetFileName(Request& rRequest)
{
    OUString sFileName = rRequest.getArgumentOrDefault(PROP_FILENAME, OUString());

    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    iDialog->SetFileName(o3tl::toW(sFileName.getStr()));
}


void VistaFilePickerImpl::impl_sta_SetDirectory(Request& rRequest)
{
    OUString sDirectory = rRequest.getArgumentOrDefault(PROP_DIRECTORY, OUString());

    if( !m_bInExecute)
    {
        // Vista stores last used folders for file dialogs
        // so we don't want the application to change the folder
        // in most cases.
        // Store the requested folder in the meantime and decide later
        // what to do
        m_sDirectory = sDirectory;
    }

    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    sal::systools::COMReference<IShellItem> pFolder;
    if ( !createFolderItem(sDirectory, pFolder) )
        return;

    iDialog->SetFolder(pFolder.get());
}

OUString VistaFilePickerImpl::GetDirectory()
{
    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return OUString();
    sal::systools::COMReference<IShellItem> pFolder;
    HRESULT hResult = iDialog->GetFolder( &pFolder );
    if ( FAILED(hResult) )
        return OUString();
    return lcl_getURLFromShellItem(pFolder.get());
}

void VistaFilePickerImpl::impl_sta_GetDirectory(Request& rRequest)
{
    const OUString sFolder = m_sDirectory.isEmpty() ? GetDirectory() : m_sDirectory;
    if (!sFolder.isEmpty())
        rRequest.setArgument(PROP_DIRECTORY, sFolder);
}

void VistaFilePickerImpl::impl_sta_SetDefaultName(Request& rRequest)
{
    OUString sFilename = rRequest.getArgumentOrDefault(PROP_FILENAME, OUString());
    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    TFileDialogCustomize iCustom = impl_getCustomizeInterface();
    if ( ! iCustom.is())
        return;

    // if we have the autoextension check box set, remove (or change ???) the extension of the filename
    // so that the autoextension mechanism can do its job
    BOOL bValue = FALSE;
    HRESULT hResult = iCustom->GetCheckButtonState( css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, &bValue);
    if ( FAILED(hResult) )
        return;
    if ( bValue )
    {
        sal_Int32 nSepPos = sFilename.lastIndexOf( '.' );
        if ( -1 != nSepPos )
            sFilename = sFilename.copy(0, nSepPos);
    }

    iDialog->SetFileName (o3tl::toW(sFilename.getStr()));
    m_sFilename = sFilename;
}


void VistaFilePickerImpl::impl_sta_setFiltersOnDialog()
{
    std::vector<OUString> vStrings; // to hold the adjusted filter names, pointers to which will be
                                    // stored in lFilters
    ::std::vector< COMDLG_FILTERSPEC > lFilters       = lcl_buildFilterList(m_lFilters, vStrings);
    OUString                    sCurrentFilter = m_lFilters.getCurrentFilter();
    sal_Int32                          nCurrentFilter = m_lFilters.getFilterPos(sCurrentFilter);
    TFileDialog                        iDialog        = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;
    TFileDialogCustomize               iCustomize     = impl_getCustomizeInterface();
    if (!iCustomize.is())
        return;

    if (lFilters.empty())
        return;

    COMDLG_FILTERSPEC   *pFilt = lFilters.data();
    iDialog->SetFileTypes(lFilters.size(), pFilt/*&lFilters[0]*/);
    iDialog->SetFileTypeIndex(nCurrentFilter + 1);

    BOOL bValue = FALSE;
    HRESULT hResult = iCustomize->GetCheckButtonState( css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, &bValue);
    if ( FAILED(hResult) )
        return;

    if ( bValue )
    {
        PCWSTR lpFilterExt = lFilters[0].pszSpec;

        lpFilterExt = wcsrchr( lpFilterExt, '.' );
        if ( lpFilterExt )
            lpFilterExt++;
        iDialog->SetDefaultExtension( lpFilterExt );
    }

}


void VistaFilePickerImpl::impl_sta_getSelectedFiles(Request& rRequest)
{
    if (m_pDialog == nullptr)
        return;

    // ask dialog for results
    // we must react different if dialog is in execute or not .-(
    sal::systools::COMReference<IShellItemArray> iItems = m_pDialog->getResult(m_bInExecute);
    if (!iItems.is())
        return;

    // convert and pack results
    std::vector< OUString > lFiles;
    if (DWORD nCount; SUCCEEDED(iItems->GetCount(&nCount)))
    {
        for (DWORD i = 0; i < nCount; ++i)
        {
            if (sal::systools::COMReference<IShellItem> iItem;
                SUCCEEDED(iItems->GetItemAt(i, &iItem)))
            {
                if (const OUString sURL = lcl_getURLFromShellItem(iItem.get()); !sURL.isEmpty())
                    lFiles.push_back(sURL);
            }
        }
    }

    rRequest.setArgument(PROP_SELECTED_FILES, comphelper::containerToSequence(lFiles));
}


void VistaFilePickerImpl::impl_sta_ShowDialogModal(Request& rRequest)
{
    impl_sta_setFiltersOnDialog();

    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    // it's important to know if we are showing the dialog.
    // Some dialog interface methods can't be called then or some
    // tasks must be done differently .-) (e.g. see impl_sta_getSelectedFiles())
    m_bInExecute = true;

    m_bWasExecuted = true;

    // we set the directory only if we have a save dialog and a filename
    // for the other cases, the file dialog remembers its last location
    // according to its client guid.
    if( m_sDirectory.getLength())
    {
        sal::systools::COMReference<IShellItem> pFolder;
        if ( createFolderItem(m_sDirectory, pFolder) )
        {
            if (m_sFilename.getLength())
            {
                OUString aFileURL(m_sDirectory);
                sal_Int32 nIndex = aFileURL.lastIndexOf('/');
                if (nIndex != aFileURL.getLength()-1)
                    aFileURL += "/";
                aFileURL += m_sFilename;

                TFileDialogCustomize iCustom = impl_getCustomizeInterface();
                if (!iCustom.is())
                    return;

                BOOL bValue = FALSE;
                HRESULT hResult = iCustom->GetCheckButtonState( css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, &bValue);
                if ( bValue )
                {
                    UINT nFileType;
                    hResult = iDialog->GetFileTypeIndex(&nFileType);
                    if ( SUCCEEDED(hResult) && nFileType > 0 )
                    {
                        // COM dialog base on 1 ... filter container on 0 .-)
                        ::size_t nRealIndex = nFileType-1;
                        OUString sFilter;
                        if (m_lFilters.getFilterByIndex(nRealIndex, sFilter))
                        {
                            const sal_Int32 idx = sFilter.indexOf('.');
                            if (idx >= 0)
                                aFileURL += sFilter.subView(idx);
                        }
                    }
                }

                // Check existence of file. Set folder only for this special case
                OUString aSystemPath;
                osl_getSystemPathFromFileURL( aFileURL.pData, &aSystemPath.pData );

                WIN32_FIND_DATAW aFindFileData;
                HANDLE  hFind = FindFirstFileW( o3tl::toW(aSystemPath.getStr()), &aFindFileData );
                if (hFind != INVALID_HANDLE_VALUE)
                    iDialog->SetFolder(pFolder.get());
                else
                    hResult = iDialog->AddPlace(pFolder.get(), FDAP_TOP);

                FindClose( hFind );
            }
            else
                iDialog->AddPlace(pFolder.get(), FDAP_TOP);
        }
    }

    HRESULT hResult = E_FAIL;
    try
    {
        // tdf#146007: Make sure we don't hold solar mutex: COM may need to forward
        // the execution to the main thread, and holding solar mutex could deadlock
        SolarMutexReleaser r;
        // show dialog and wait for user decision
        hResult = iDialog->Show(m_hParentWindow ? m_hParentWindow
                                                : choose_parent_window()); // parent window needed
    }
    catch(...)
    {}

    m_bInExecute = false;

    if (m_iEventHandler.is())
    {
        auto* pHandlerImpl = static_cast<VistaFilePickerEventHandler*>(m_iEventHandler.get());
        pHandlerImpl->stopListening();
    }

    if ( FAILED(hResult) )
        return;

    impl_sta_getSelectedFiles(rRequest);
    rRequest.setArgument(PROP_DIALOG_SHOW_RESULT, true);
}


TFileDialog VistaFilePickerImpl::impl_getBaseDialogInterface()
{
    TFileDialog iDialog;

    if (m_pDialog != nullptr)
        iDialog = m_pDialog->getComPtr();

    return iDialog;
}


TFileDialogCustomize VistaFilePickerImpl::impl_getCustomizeInterface()
{
    if (m_pDialog != nullptr)
        return { m_pDialog->getComPtr(), sal::systools::COM_QUERY_THROW };

    return {};
}


static void lcl_removeControlItemsWorkaround(const TFileDialogCustomize& iCustom   ,
                                            ::sal_Int16           nControlId)
{
    (void)iCustom->SetSelectedControlItem(nControlId, 1000); // Don't care if this fails (useless?)
    DWORD i = 0;
    HRESULT hResult = S_OK;
    while ( SUCCEEDED(hResult) )
        hResult = iCustom->RemoveControlItem(nControlId, i++);
}


void VistaFilePickerImpl::impl_sta_SetControlValue(Request& rRequest)
{
    ::sal_Int16   nId     = rRequest.getArgumentOrDefault(PROP_CONTROL_ID    , INVALID_CONTROL_ID    );
    ::sal_Int16   nAction = rRequest.getArgumentOrDefault(PROP_CONTROL_ACTION, INVALID_CONTROL_ACTION);
    css::uno::Any aValue  = rRequest.getValue(PROP_CONTROL_VALUE);

    // don't check for right values here ...
    // most parameters are optional !

    TFileDialogCustomize iCustom = impl_getCustomizeInterface();
    if ( ! iCustom.is())
        return;

    switch (nId)
    {
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PASSWORD :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_READONLY :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK :
        //case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PREVIEW : // can be ignored ... preview is supported native now !
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_SELECTION :
            {
                bool bValue   = false;
                aValue >>= bValue;
                iCustom->SetCheckButtonState(nId, bValue);
            }
            break;

        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_VERSION :
        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_TEMPLATE :
        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE :
        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR :
            {
                HRESULT hResult;
                switch (nAction)
                {
                    case css::ui::dialogs::ControlActions::DELETE_ITEMS :
                        {
                            hResult = iCustom->RemoveAllControlItems(nId);
                            if ( FAILED(hResult) )
                                lcl_removeControlItemsWorkaround(iCustom, nId);
                        }
                        break;

                    case css::ui::dialogs::ControlActions::ADD_ITEMS :
                        {
                            aValue >>= m_lItems;
                            for (::sal_Int32 i=0; i<m_lItems.getLength(); ++i)
                            {
                                const OUString& sItem = m_lItems[i];
                                hResult = iCustom->AddControlItem(nId, i, o3tl::toW(sItem.getStr()));
                            }
                        }
                        break;

                    case css::ui::dialogs::ControlActions::SET_SELECT_ITEM :
                        {
                            ::sal_Int32 nItem    = 0;
                            aValue >>= nItem;
                            hResult = iCustom->SetSelectedControlItem(nId, nItem);
                        }
                        break;
                }
            }
            break;

        case css::ui::dialogs::ExtendedFilePickerElementIds::PUSHBUTTON_PLAY :
            {
            }
            break;
        }
}


void VistaFilePickerImpl::impl_sta_GetControlValue(Request& rRequest)
{
    ::sal_Int16 nId     = rRequest.getArgumentOrDefault(PROP_CONTROL_ID    , INVALID_CONTROL_ID    );

    // don't check for right values here ...
    // most parameters are optional !

    TFileDialogCustomize iCustom = impl_getCustomizeInterface();
    if ( ! iCustom.is())
        return;

    css::uno::Any aValue;
    if( m_bWasExecuted )
        switch (nId)
        {
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PASSWORD :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_GPGENCRYPTION :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_GPGSIGN :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_READONLY :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS :
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK :
        //case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PREVIEW : // can be ignored ... preview is supported native now !
        case css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_SELECTION :
            {
                BOOL    bValue  = FALSE;
                HRESULT hResult = iCustom->GetCheckButtonState(nId, &bValue);
                if ( SUCCEEDED(hResult) )
                    aValue <<= bool(bValue);
            }
            break;
        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_VERSION:
        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_TEMPLATE:
        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE:
        case css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR:
            {
                DWORD    bValue = 0;
                HRESULT hResult = iCustom->GetSelectedControlItem(nId, &bValue);
                if ( SUCCEEDED(hResult) )
                {
                    const OUString& sItem = m_lItems[bValue];
                    aValue <<= sItem;
                }
            }
            break;
        }

    if (aValue.hasValue())
        rRequest.setArgument(PROP_CONTROL_VALUE, aValue);
}


void VistaFilePickerImpl::impl_sta_SetControlLabel(Request& rRequest)
{
    ::sal_Int16     nId    = rRequest.getArgumentOrDefault(PROP_CONTROL_ID   , INVALID_CONTROL_ID  );
    OUString sLabel = rRequest.getArgumentOrDefault(PROP_CONTROL_LABEL, OUString() );

    // don't check for right values here ...
    // most parameters are optional !

    TFileDialogCustomize iCustom = impl_getCustomizeInterface();
    if ( ! iCustom.is())
        return;
    iCustom->SetControlLabel (nId, o3tl::toW(sLabel.getStr()));
}


void VistaFilePickerImpl::impl_sta_GetControlLabel(Request& /*rRequest*/)
{
}


void VistaFilePickerImpl::impl_sta_EnableControl(Request& rRequest)
{
    ::sal_Int16 nId      = rRequest.getArgumentOrDefault(PROP_CONTROL_ID    , INVALID_CONTROL_ID  );
    bool bEnabled = rRequest.getArgumentOrDefault(PROP_CONTROL_ENABLE, true);

    // don't check for right values here ...
    // most parameters are optional !

    TFileDialogCustomize iCustom = impl_getCustomizeInterface();
    if ( ! iCustom.is())
        return;

    CDCONTROLSTATEF eState = CDCS_VISIBLE;
    if (bEnabled)
        eState |= CDCS_ENABLED;
    else
        eState |= CDCS_INACTIVE;

    iCustom->SetControlState(nId, eState);
}

void VistaFilePickerImpl::impl_SetDefaultExtension( const OUString& currentFilter )
{
   TFileDialog iDialog = impl_getBaseDialogInterface();
   if (!iDialog.is())
        return;

   if (currentFilter.getLength())
   {
        OUString FilterExt;
        m_lFilters.getFilterByName(currentFilter, FilterExt);

        sal_Int32 posOfPoint = FilterExt.indexOf(L'.');
        const sal_Unicode* pFirstExtStart = FilterExt.getStr() + posOfPoint + 1;

        sal_Int32 posOfSemiColon = FilterExt.indexOf(L';') - 1;
        if (posOfSemiColon < 0)
            posOfSemiColon = FilterExt.getLength() - 1;

        FilterExt = OUString(pFirstExtStart, posOfSemiColon - posOfPoint);
        iDialog->SetDefaultExtension ( o3tl::toW(FilterExt.getStr()) );
   }
}

void VistaFilePickerImpl::onAutoExtensionChanged (bool bChecked)
{
    const OUString sFilter = m_lFilters.getCurrentFilter ();
    OUString sExt    ;
    if (!m_lFilters.getFilterByName(sFilter, sExt))
        return;

    TFileDialog iDialog = impl_getBaseDialogInterface();
    if (!iDialog.is())
        return;

    PCWSTR pExt = nullptr;
    if ( bChecked )
    {
        pExt = o3tl::toW(sExt.getStr());
        pExt = wcsrchr( pExt, '.' );
        if ( pExt )
            pExt++;
    }
    iDialog->SetDefaultExtension( pExt );
}

bool VistaFilePickerImpl::onFileTypeChanged( UINT /*nTypeIndex*/ )
{
    return true;
}

void VistaFilePickerImpl::onDirectoryChanged()
{
    m_sDirectory = GetDirectory();
}

} // namespace vista
} // namespace win32
} // namespace fpicker

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
