/* -*- 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 <sal/log.hxx>
#include <osl/diagnose.h>
#include <svtools/svtresid.hxx>
#include <svtools/imagemgr.hxx>
#include <svtools/querydelete.hxx>
#include <svtools/strings.hrc>
#include <bitmaps.hlst>
#include "contentenumeration.hxx"
#include <com/sun/star/task/InteractionHandler.hpp>
#include <com/sun/star/ucb/XProgressHandler.hpp>
#include <com/sun/star/ucb/XContent.hpp>
#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/ucb/CommandAbortedException.hpp>
#include <com/sun/star/ucb/XCommandInfo.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>

#include <algorithm>
#include <string_view>
#include <vector>
#include <tools/debug.hxx>
#include <tools/urlobj.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <ucbhelper/content.hxx>
#include <ucbhelper/commandenvironment.hxx>
#include <rtl/math.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/typed_flags_set.hxx>
#include <o3tl/string_view.hxx>
#include <osl/mutex.hxx>
#include <osl/conditn.hxx>
#include <salhelper/timer.hxx>
#include <svtools/urlfilter.hxx>
#include <unotools/collatorwrapper.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/intlwrapper.hxx>
#include <unotools/syslocale.hxx>
#include <vcl/svapp.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/settings.hxx>
#include <vcl/timer.hxx>
#include <memory>
#include "fileview.hxx"

using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::task;
using namespace ::com::sun::star::ucb;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::io;
using namespace ::com::sun::star::beans;
using namespace ::comphelper;
using ::svt::SortingData_Impl;
using ::svt::FolderDescriptor;

constexpr OUStringLiteral ALL_FILES_FILTER = u"*.*";

#define COLUMN_TITLE        1
#define COLUMN_TYPE         2
#define COLUMN_SIZE         3
#define COLUMN_DATE         4

#define QUICK_SEARCH_TIMEOUT    1500    // time in mSec before the quicksearch string will be reset

namespace {

enum class FileViewFlags
{
    NONE               = 0x00,
    MULTISELECTION     = 0x02,
    SHOW_TYPE          = 0x04,
    SHOW_NONE          = 0x20,
};

}

namespace o3tl
{
    template<> struct typed_flags<FileViewFlags> : is_typed_flags<FileViewFlags, 0x26> {};
}

namespace
{

    //= CallbackTimer

    class CallbackTimer : public ::salhelper::Timer
    {
    protected:
        SvtFileView_Impl* const m_pTimeoutHandler;

    public:
        explicit CallbackTimer( SvtFileView_Impl* _pHandler ) : m_pTimeoutHandler( _pHandler ) { }

    protected:
        virtual void SAL_CALL onShot() override;
    };

class ViewTabListBox_Impl
{
private:
    rtl::Reference< ::ucbhelper::CommandEnvironment >    mxCmdEnv;
    std::unique_ptr<weld::TreeView> mxTreeView;
    std::unique_ptr<weld::TreeIter> mxScratchIter;

    ::osl::Mutex            maMutex;
    SvtFileView_Impl*       mpParent;
    Timer                   maResetQuickSearch { "fpicker SvtFileView_Impl maResetQuickSearch" };
    OUString                maQuickSearchText;
    sal_uInt32              mnSearchIndex;
    bool                    mbEnableDelete;
    bool                    mbEditing;
    bool const              mbShowType;

    void            DeleteEntries();
    void            DoQuickSearch( sal_Unicode rChar );
    bool            Kill( const OUString& rURL );

public:
    ViewTabListBox_Impl(std::unique_ptr<weld::TreeView> xTreeView, weld::Window* pTopLevel, SvtFileView_Impl* pParent, FileViewFlags nFlags);

    std::unique_ptr<weld::TreeIter> make_iterator() const { return mxTreeView->make_iterator(); }
    void insert(const OUString &rEntry, const OUString& rId, const OUString& rImage, weld::TreeIter& rIter)
    {
        mxTreeView->insert(nullptr, -1, &rEntry, &rId, nullptr, nullptr, false, &rIter);
        mxTreeView->set_image(rIter, rImage);
    }
    void append(const OUString& rId, const OUString& rStr, const OUString& rType, const OUString& rSize, const OUString& rDate, const OUString& rImage)
    {
        mxTreeView->insert(nullptr, -1, &rStr, &rId, nullptr, nullptr, false, mxScratchIter.get());
        mxTreeView->set_image(*mxScratchIter, rImage);
        int nCol = 1;
        if (mbShowType)
            mxTreeView->set_text(*mxScratchIter, rType, nCol++);
        mxTreeView->set_text(*mxScratchIter, rSize, nCol++);
        mxTreeView->set_text(*mxScratchIter, rDate, nCol++);
    }

    void scroll_to_row(const weld::TreeIter& rIter) { mxTreeView->scroll_to_row(rIter); }
    void set_cursor(int nPos) { mxTreeView->set_cursor(nPos); }
    void set_cursor(const weld::TreeIter& rIter) { mxTreeView->set_cursor(rIter); }
    bool get_cursor(weld::TreeIter* pIter) const { return mxTreeView->get_cursor(pIter); }
    bool get_iter_first(weld::TreeIter& rIter) const { return mxTreeView->get_iter_first(rIter); }
    bool get_selected(weld::TreeIter* pIter) const { return mxTreeView->get_selected(pIter); }

    OUString get_selected_text() const
    {
        // tdf#131898 only care about column 0
        int nIndex = mxTreeView->get_selected_index();
        return nIndex != -1 ? mxTreeView->get_text(nIndex, 0) : OUString();
    }

    void unselect_all() { mxTreeView->unselect_all(); }

    OUString get_id(const weld::TreeIter& rIter) { return mxTreeView->get_id(rIter); }

    void connect_row_activated(const Link<weld::TreeView&, bool>& rLink) { mxTreeView->connect_row_activated(rLink); }
    void connect_changed(const Link<weld::TreeView&, void>& rLink) { mxTreeView->connect_changed(rLink); }

    int n_children() const { return mxTreeView->n_children(); }

    void freeze() { mxTreeView->freeze(); }
    void thaw() { mxTreeView->thaw(); }

    void show() { mxTreeView->show(); }
    void hide() { mxTreeView->hide(); }
    bool get_visible() const { return mxTreeView->get_visible(); }

    int count_selected_rows() const { return mxTreeView->count_selected_rows(); }

    void grab_focus() { mxTreeView->grab_focus(); }
    bool has_focus() const { return mxTreeView->has_focus(); }

    void set_help_id(const OUString& rHelpId) { mxTreeView->set_help_id(rHelpId); }
    OUString get_help_id() const { return mxTreeView->get_help_id(); }

    bool IsEditingActive() const { return mbEditing; }

    void end_editing()
    {
        mxTreeView->end_editing();
        mxTreeView->connect_editing(Link<const weld::TreeIter&, bool>(), Link<const IterString&, bool>());
        mbEditing = false;
    }

    void selected_foreach(const std::function<bool(weld::TreeIter&)>& func)
    {
        mxTreeView->selected_foreach(func);
    }

    weld::TreeView* getWidget() const
    {
        return mxTreeView.get();
    }

    void            clear() { mxTreeView->clear(); }

    void            EnableDelete( bool bEnable ) { mbEnableDelete = bEnable; }
    bool            TypeColumnVisible() const { return mbShowType; }

    const rtl::Reference< ::ucbhelper::CommandEnvironment >& GetCommandEnvironment() const { return mxCmdEnv; }

    DECL_LINK(ResetQuickSearch_Impl, Timer *, void);
    DECL_LINK(CommandHdl, const CommandEvent&, bool);
    DECL_LINK(EditingEntryHdl, const weld::TreeIter&, bool);
    typedef std::pair<const weld::TreeIter&, OUString> IterString;
    DECL_LINK(EditedEntryHdl, const IterString&, bool);
    DECL_LINK(KeyInputHdl, const KeyEvent&, bool);

    void            ExecuteContextMenuAction(std::u16string_view rSelectedPopentry);
};

}

//= SvtFileView_Impl
class SvtFileView_Impl  :public ::svt::IEnumerationResultHandler
{
protected:
    SvtFileView*                        m_pAntiImpl;
    Link<SvtFileView*,void>             m_aSelectHandler;

    ::rtl::Reference< ::svt::FileViewContentEnumerator >
                                        m_xContentEnumerator;
    Link<void*,void>                    m_aCurrentAsyncActionHandler;
    ::osl::Condition                    m_aAsyncActionFinished;
    ::rtl::Reference< ::salhelper::Timer > m_xCancelAsyncTimer;
    ::svt::EnumerationResult            m_eAsyncActionResult;
    bool                                m_bRunningAsyncAction;
    bool                                m_bAsyncActionCancelled;

public:

    ::std::vector<std::unique_ptr<SortingData_Impl>>  maContent;
    ::std::vector<std::unique_ptr<SvtContentEntry>> maEntries;
    ::osl::Mutex                        maMutex;

    weld::Window*           m_pTopLevel;
    std::unique_ptr<ViewTabListBox_Impl> mxView;
    std::unique_ptr<weld::IconView> mxIconView;
    sal_uInt16              mnSortColumn;
    bool                    mbAscending     : 1;
    bool const              mbOnlyFolder    : 1;
    sal_Int16               mnSuspendSelectCallback : 1;
    bool                    mbIsFirstResort : 1;

    IntlWrapper const       aIntlWrapper;

    OUString                maViewURL;
    OUString                maCurrentFilter;
    OUString                maFolderImage;
    Link<SvtFileView*,void> maOpenDoneLink;
    Link<SvtFileView*,bool> maDoubleClickHandler;

    Reference< XCommandEnvironment >    mxCmdEnv;

    SvtFileView_Impl(SvtFileView* pAntiImpl, weld::Window* pTopLevel,
                     std::unique_ptr<weld::TreeView> xTreeView,
                     std::unique_ptr<weld::IconView> xIconView,
                     Reference < XCommandEnvironment > const & xEnv,
                     FileViewFlags nFlags,
                     bool bOnlyFolder);

    virtual ~SvtFileView_Impl();

    void                    Clear();

    FileViewResult          GetFolderContent_Impl(
        std::u16string_view rFolder,
        const FileViewAsyncAction* pAsyncDescriptor,
        const css::uno::Sequence< OUString >& rDenyList );

    FileViewResult          GetFolderContent_Impl(
        const FolderDescriptor& _rFolder,
        const FileViewAsyncAction* pAsyncDescriptor,
        const css::uno::Sequence< OUString >& rDenyList );
    void                    FilterFolderContent_Impl( std::u16string_view rFilter );
    void                    CancelRunningAsyncAction();

    void                    OpenFolder_Impl();
    static OUString         ReplaceTabWithString(const OUString& rValue);
    void                    CreateDisplayText_Impl();
    void                    SortFolderContent_Impl();

    void                    EntryRemoved( std::u16string_view rURL );
    void                    EntryRenamed( OUString& rURL,
                                          const OUString& rName );
    const SortingData_Impl& FolderInserted( const OUString& rURL,
                                            const OUString& rTitle );

    int                     GetEntryPos( std::u16string_view rURL );

    void                    SetViewMode( FileViewMode eMode );

    inline void             EnableDelete( bool bEnable );

    void                    Resort_Impl( sal_Int16 nColumn, bool bAscending );
    bool                    SearchNextEntry( sal_uInt32 &nIndex,
                                             std::u16string_view rTitle,
                                             bool bWrapAround );

    void                    SetSelectHandler( const Link<SvtFileView*,void>& rHdl );
    void                    SetDoubleClickHandler(const Link<SvtFileView*,bool>& rHdl);

    void                    ResetCursor();

    void EndEditing()
    {
        if (mxView->IsEditingActive())
            mxView->end_editing();
    }

    void onTimeout();

    void grab_focus()
    {
        if (mxView->get_visible())
            mxView->grab_focus();
        else
            mxIconView->grab_focus();
    }

    bool has_focus() const
    {
        return mxView->has_focus() || mxIconView->has_focus();
    }

    int GetSortColumn() const
    {
        sal_uInt16 nOldSortID = mnSortColumn;
        // skip "TYPE"
        if (!mxView->TypeColumnVisible() && nOldSortID != COLUMN_TITLE)
            --nOldSortID;
        return nOldSortID - 1;
    }

protected:
    DECL_LINK(ChangedHdl, weld::TreeView&, void);
    DECL_LINK(SelectionChangedHdl, weld::IconView&, void);
    DECL_LINK(RowActivatedHdl, weld::TreeView&, bool);
    DECL_LINK(ItemActivatedHdl, weld::IconView&, bool);

    // IEnumerationResultHandler overridables
    virtual void        enumerationDone( ::svt::EnumerationResult eResult ) override;
            void        implEnumerationSuccess();
};

inline void SvtFileView_Impl::EnableDelete( bool bEnable )
{
    mxView->EnableDelete( bEnable );
}

namespace
{
    // functions -------------------------------------------------------------

    OUString CreateExactSizeText( sal_Int64 nSize )
    {
        double fSize( static_cast<double>(nSize) );
        int nDec;

        tools::Long nMega = 1024 * 1024;
        tools::Long nGiga = nMega * 1024;

        OUString aUnitStr(' ');

        if ( nSize < 10000 )
        {
            aUnitStr += SvtResId(STR_SVT_BYTES );
            nDec = 0;
        }
        else if ( nSize < nMega )
        {
            fSize /= 1024;
            aUnitStr += SvtResId(STR_SVT_KB);
            nDec = 1;
        }
        else if ( nSize < nGiga )
        {
            fSize /= nMega;
            aUnitStr += SvtResId(STR_SVT_MB);
            nDec = 2;
        }
        else
        {
            fSize /= nGiga;
            aUnitStr += SvtResId(STR_SVT_GB);
            nDec = 3;
        }

        OUString aSizeStr =
            ::rtl::math::doubleToUString( fSize,
                    rtl_math_StringFormat_F, nDec,
                    SvtSysLocale().GetLocaleData().getNumDecimalSep()[0]) +
            aUnitStr;

        return aSizeStr;
    }
}

ViewTabListBox_Impl::ViewTabListBox_Impl(std::unique_ptr<weld::TreeView> xTreeView,
                                         weld::Window* pTopLevel,
                                         SvtFileView_Impl* pParent,
                                         FileViewFlags nFlags)
    : mxTreeView(std::move(xTreeView))
    , mxScratchIter(mxTreeView->make_iterator())
    , mpParent( pParent )
    , mnSearchIndex( 0 )
    , mbEnableDelete( false )
    , mbEditing( false )
    , mbShowType(nFlags & FileViewFlags::SHOW_TYPE)
{
    std::vector<int> aWidths { 180 };
    if (nFlags & FileViewFlags::SHOW_TYPE)
        aWidths.push_back(140);
    aWidths.push_back(80);
    mxTreeView->set_column_fixed_widths(aWidths);

    if (nFlags & FileViewFlags::MULTISELECTION)
        mxTreeView->set_selection_mode(SelectionMode::Multiple);

    maResetQuickSearch.SetTimeout( QUICK_SEARCH_TIMEOUT );
    maResetQuickSearch.SetInvokeHandler( LINK( this, ViewTabListBox_Impl, ResetQuickSearch_Impl ) );

    const Reference< XComponentContext >& xContext = ::comphelper::getProcessComponentContext();
    Reference< XInteractionHandler > xInteractionHandler(
        InteractionHandler::createWithParent(xContext, pTopLevel->GetXWindow()), UNO_QUERY_THROW);

    mxCmdEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler, Reference< XProgressHandler >() );

    mxTreeView->connect_popup_menu(LINK(this, ViewTabListBox_Impl, CommandHdl));
    mxTreeView->connect_key_press(LINK(this, ViewTabListBox_Impl, KeyInputHdl));
}

IMPL_LINK_NOARG(ViewTabListBox_Impl, EditingEntryHdl, const weld::TreeIter&, bool)
{
    return mbEditing;
}

IMPL_LINK_NOARG(ViewTabListBox_Impl, ResetQuickSearch_Impl, Timer *, void)
{
    ::osl::MutexGuard aGuard( maMutex );

    maQuickSearchText.clear();
    mnSearchIndex = 0;
}

IMPL_LINK(ViewTabListBox_Impl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    if (mbEditing)
        return false;

    bool bHandled = false;

    const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
    if ( 0 == rKeyCode.GetModifier() )
    {
        if ( ( rKeyCode.GetCode() == KEY_DELETE ) &&
                  mbEnableDelete )
        {
            ResetQuickSearch_Impl( nullptr );
            DeleteEntries();
            bHandled = true;
        }
        else if ( ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_NUM ) ||
                  ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_ALPHA ) )
        {
            DoQuickSearch( rKEvt.GetCharCode() );
            bHandled = true;
        }
    }

    if (!bHandled)
        ResetQuickSearch_Impl( nullptr );
    return bHandled;
}

IMPL_LINK(ViewTabListBox_Impl, CommandHdl, const CommandEvent&, rCEvt, bool)
{
    if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
        return false;

    bool bEnableDelete = mbEnableDelete;
    bool bEnableRename = true;

    int nCount = 0;
    mxTreeView->selected_foreach([this, &nCount, &bEnableDelete, &bEnableRename](weld::TreeIter& rEntry){
        ++nCount;

        ::ucbhelper::Content aCnt;
        try
        {
            OUString aURL(weld::fromId<SvtContentEntry*>(
                mxTreeView->get_id(rEntry))->maURL);
            aCnt = ::ucbhelper::Content( aURL, mxCmdEnv, comphelper::getProcessComponentContext() );
        }
        catch( Exception const & )
        {
            bEnableDelete = bEnableRename = false;
        }

        if ( bEnableDelete )
        {
            try
            {
                Reference< XCommandInfo > aCommands = aCnt.getCommands();
                if ( aCommands.is() )
                    bEnableDelete = aCommands->hasCommandByName( u"delete"_ustr );
                else
                    bEnableDelete = false;
            }
            catch( Exception const & )
            {
                bEnableDelete = false;
            }
        }

        if ( bEnableRename )
        {
            try
            {
                Reference< XPropertySetInfo > aProps = aCnt.getProperties();
                if ( aProps.is() )
                {
                    Property aProp = aProps->getPropertyByName(u"Title"_ustr);
                    bEnableRename
                        = !( aProp.Attributes & PropertyAttribute::READONLY );
                }
                else
                    bEnableRename = false;
            }
            catch( Exception const & )
            {
                bEnableRename = false;
            }
        }

        bool bStop = !bEnableDelete && !bEnableRename;
        return bStop;
    });

    if (nCount == 0)
        bEnableDelete = false;
    if (nCount != 1)
        bEnableRename = false;

    if (bEnableDelete || bEnableRename)
    {
        std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(mxTreeView.get(), u"svt/ui/fileviewmenu.ui"_ustr));
        auto xContextMenu = xBuilder->weld_menu(u"menu"_ustr);
        xContextMenu->set_visible(u"delete"_ustr, bEnableDelete);
        xContextMenu->set_visible(u"rename"_ustr, bEnableRename);
        OUString sCommand(xContextMenu->popup_at_rect(mxTreeView.get(), tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1))));
        ExecuteContextMenuAction(sCommand);
    }

    return true;
}

void ViewTabListBox_Impl::ExecuteContextMenuAction(std::u16string_view rSelectedPopupEntry)
{
    if (rSelectedPopupEntry == u"delete")
        DeleteEntries();
    else if (rSelectedPopupEntry == u"rename")
    {
        std::unique_ptr<weld::TreeIter> xEntry = mxTreeView->make_iterator();
        if (mxTreeView->get_selected(xEntry.get()))
        {
            mbEditing = true;

            mxTreeView->connect_editing(LINK(this, ViewTabListBox_Impl, EditingEntryHdl),
                                        LINK(this, ViewTabListBox_Impl, EditedEntryHdl));

            mxTreeView->start_editing(*xEntry);
        }
    }
}

void ViewTabListBox_Impl::DeleteEntries()
{
    short eResult = svtools::QUERYDELETE_YES;

    mxTreeView->selected_foreach([this, &eResult](weld::TreeIter& rCurEntry){
        OUString aURL;
        if (!mxTreeView->get_id(rCurEntry).isEmpty())
            aURL = weld::fromId<SvtContentEntry*>(mxTreeView->get_id(rCurEntry))->maURL;
        if (aURL.isEmpty())
        {
            mxTreeView->unselect(rCurEntry);
            return false;
        }

        bool canDelete = true;
        try
        {
            ::ucbhelper::Content aCnt( aURL, mxCmdEnv, comphelper::getProcessComponentContext() );
            Reference< XCommandInfo > aCommands = aCnt.getCommands();
            if ( aCommands.is() )
                canDelete = aCommands->hasCommandByName( u"delete"_ustr );
            else
                canDelete = false;
        }
        catch( Exception const & )
        {
            canDelete = false;
        }

        if (!canDelete)
        {
            mxTreeView->unselect(rCurEntry);
            return false; // process next entry
        }

        if ( eResult != svtools::QUERYDELETE_ALL )
        {
            INetURLObject aObj( aURL );
            svtools::QueryDeleteDlg_Impl aDlg(
                mxTreeView.get(), aObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset));

            if (mxTreeView->count_selected_rows() > 1)
                aDlg.EnableAllButton();

            eResult = aDlg.run();
        }

        bool bDeleted = false;

        if (eResult == svtools::QUERYDELETE_ALL || eResult == svtools::QUERYDELETE_YES)
        {
            if ( Kill( aURL ) )
            {
                mpParent->EntryRemoved( aURL );
                bDeleted = true;
            }
        }

        if (!bDeleted)
            mxTreeView->unselect(rCurEntry);

        return false;
    });

    mxTreeView->remove_selection();
}

IMPL_LINK(ViewTabListBox_Impl, EditedEntryHdl, const IterString&, rIterString, bool)
{
    mbEditing = false;

    mxTreeView->connect_editing(Link<const weld::TreeIter&, bool>(), Link<const IterString&, bool>());

    const weld::TreeIter& rEntry = rIterString.first;
    OUString sNewText = rIterString.second;

    if (sNewText.isEmpty())
        return false;

    bool bRet = false;

    OUString aURL;
    SvtContentEntry* pData = weld::fromId<SvtContentEntry*>(mxTreeView->get_id(rEntry));

    if ( pData )
        aURL = pData->maURL;

    if ( aURL.isEmpty() )
        return bRet;

    try
    {
        OUString aPropName( u"Title"_ustr );
        bool canRename = true;
        ::ucbhelper::Content aContent( aURL, mxCmdEnv, comphelper::getProcessComponentContext() );

        try
        {
            Reference< XPropertySetInfo > aProps = aContent.getProperties();
            if ( aProps.is() )
            {
                Property aProp = aProps->getPropertyByName( aPropName );
                canRename = !( aProp.Attributes & PropertyAttribute::READONLY );
            }
            else
            {
                canRename = false;
            }
        }
        catch ( Exception const & )
        {
            canRename = false;
        }

        if ( canRename )
        {
            Any aValue;
            aValue <<= sNewText;
            aContent.setPropertyValue( aPropName, aValue );
            mpParent->EntryRenamed(aURL, sNewText);

            if (pData)
                pData->maURL = aURL;

            mxTreeView->set_id(rEntry, weld::toId(pData));

            bRet = true;
        }
    }
    catch( Exception const & )
    {
    }

    return bRet;
}

void ViewTabListBox_Impl::DoQuickSearch( sal_Unicode rChar )
{
    ::osl::MutexGuard aGuard( maMutex );

    maResetQuickSearch.Stop();

    OUString    aLastText = maQuickSearchText;
    sal_uInt32  aLastPos = mnSearchIndex;

    maQuickSearchText += OUString(rChar).toAsciiLowerCase();

    bool bFound = mpParent->SearchNextEntry( mnSearchIndex, maQuickSearchText, false );

    if ( !bFound && ( aLastText.getLength() == 1 ) &&
         ( aLastText == OUStringChar(rChar) ) )
    {
        mnSearchIndex = aLastPos + 1;
        maQuickSearchText = aLastText;
        bFound = mpParent->SearchNextEntry( mnSearchIndex, maQuickSearchText, true );
    }

    if (bFound)
    {
        mxTreeView->unselect_all();
        mxTreeView->select(mnSearchIndex);
        mxTreeView->set_cursor(mnSearchIndex);
        mxTreeView->scroll_to_row(mnSearchIndex);
    }

    maResetQuickSearch.Start();
}

bool ViewTabListBox_Impl::Kill( const OUString& rContent )
{
    bool bRet = true;

    try
    {
        ::ucbhelper::Content aCnt( rContent, mxCmdEnv, comphelper::getProcessComponentContext() );
        aCnt.executeCommand( u"delete"_ustr, Any( true ) );
    }
    catch( css::ucb::CommandAbortedException const & )
    {
        SAL_INFO( "svtools.contnr", "CommandAbortedException" );
        bRet = false;
    }
    catch( Exception const & )
    {
        SAL_INFO( "svtools.contnr", "Any other exception" );
        bRet = false;
    }

    return bRet;
}

SvtFileView::SvtFileView(weld::Window* pTopLevel,
                         std::unique_ptr<weld::TreeView> xTreeView,
                         std::unique_ptr<weld::IconView> xIconView,
                         bool bOnlyFolder, bool bMultiSelection, bool bShowType )
{
    FileViewFlags nFlags = FileViewFlags::NONE;
    if ( bMultiSelection )
        nFlags |= FileViewFlags::MULTISELECTION;
    if ( bShowType )
        nFlags |= FileViewFlags::SHOW_TYPE;

    const Reference< XComponentContext >& xContext = ::comphelper::getProcessComponentContext();
    Reference< XInteractionHandler > xInteractionHandler(
        InteractionHandler::createWithParent(xContext, pTopLevel->GetXWindow()), UNO_QUERY_THROW);
    Reference < XCommandEnvironment > xCmdEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler, Reference< XProgressHandler >() );

    mpImpl.reset(new SvtFileView_Impl(this, pTopLevel, std::move(xTreeView), std::move(xIconView), xCmdEnv, nFlags, bOnlyFolder));

    weld::TreeView* pView = mpImpl->mxView->getWidget();
    pView->connect_column_clicked(LINK(this, SvtFileView, HeaderSelect_Impl));
}

void SvtFileView::grab_focus()
{
    mpImpl->grab_focus();
}

bool SvtFileView::has_focus() const
{
    return mpImpl->has_focus();
}

SvtFileView::~SvtFileView()
{
}

void SvtFileView::SetViewMode( FileViewMode eMode )
{
    mpImpl->SetViewMode( eMode );
}

OUString SvtFileView::GetURL(const weld::TreeIter& rEntry) const
{
    SvtContentEntry* pEntry;
    if (mpImpl->mxView->get_visible())
        pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxView->get_id(rEntry));
    else
        pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxIconView->get_id(rEntry));
    if (pEntry)
        return pEntry->maURL;
    return OUString();
}

OUString SvtFileView::GetCurrentURL() const
{
    SvtContentEntry* pEntry = nullptr;
    OUString aURL;
    if (mpImpl->mxView->get_visible())
    {
        std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxView->make_iterator();
        if (mpImpl->mxView->get_selected(xEntry.get()))
            pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxView->get_id(*xEntry));
    }
    else
    {
        std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxIconView->make_iterator();
        if (mpImpl->mxIconView->get_selected(xEntry.get()))
            pEntry = weld::fromId<SvtContentEntry*>(mpImpl->mxIconView->get_id(*xEntry));
    }
    if (pEntry)
        aURL = pEntry->maURL;
    return aURL;
}

void SvtFileView::CreatedFolder( const OUString& rUrl, const OUString& rNewFolder )
{
    const SortingData_Impl& rEntry = mpImpl->FolderInserted( rUrl, rNewFolder );

    mpImpl->maEntries.emplace_back(std::make_unique<SvtContentEntry>(rUrl, true));
    OUString sId(weld::toId(mpImpl->maEntries.back().get()));

    std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxView->make_iterator();
    mpImpl->mxView->insert(rEntry.maDisplayName, sId, mpImpl->maFolderImage, *xEntry);
    mpImpl->mxView->scroll_to_row(*xEntry);

    std::unique_ptr<weld::TreeIter> xIconEntry = mpImpl->mxIconView->make_iterator();
    mpImpl->mxIconView->insert(-1, &rEntry.maDisplayName, &sId, &mpImpl->maFolderImage, xIconEntry.get());
    mpImpl->mxIconView->scroll_to_item(*xIconEntry);
}

FileViewResult SvtFileView::PreviousLevel( const FileViewAsyncAction* pAsyncDescriptor )
{
    FileViewResult eResult = eFailure;

    OUString sParentURL;
    if ( GetParentURL( sParentURL ) )
        eResult = Initialize( sParentURL, mpImpl->maCurrentFilter, pAsyncDescriptor, maDenyList );

    return eResult;
}

bool SvtFileView::GetParentURL( OUString& rParentURL ) const
{
    bool bRet = false;
    try
    {
        ::ucbhelper::Content aCnt( mpImpl->maViewURL, mpImpl->mxCmdEnv, comphelper::getProcessComponentContext() );
        Reference< XContent > xContent( aCnt.get() );
        Reference< css::container::XChild > xChild( xContent, UNO_QUERY );
        if ( xChild.is() )
        {
            Reference< XContent > xParent( xChild->getParent(), UNO_QUERY );
            if ( xParent.is() )
            {
                rParentURL = xParent->getIdentifier()->getContentIdentifier();
                bRet = !rParentURL.isEmpty() && rParentURL != mpImpl->maViewURL;
            }
        }
    }
    catch( Exception const & )
    {
        // perhaps an unknown url protocol (e.g. "private:newdoc")
    }

    return bRet;
}

OUString SvtFileView::get_help_id() const
{
    return mpImpl->mxView->get_help_id();
}

void SvtFileView::set_help_id(const OUString& rHelpId)
{
    mpImpl->mxView->set_help_id(rHelpId);
}

OUString SvtFileView::get_selected_text() const
{
    if (mpImpl->mxView->get_visible())
        return mpImpl->mxView->get_selected_text();
    return mpImpl->mxIconView->get_selected_text();
}

FileViewResult SvtFileView::Initialize(
    const OUString& rURL,
    const OUString& rFilter,
    const FileViewAsyncAction* pAsyncDescriptor,
    const css::uno::Sequence< OUString >& rDenyList )
{
    weld::WaitObject aWaitCursor(mpImpl->m_pTopLevel);
    maDenyList = rDenyList;

    OUString sPushURL( mpImpl->maViewURL );

    mpImpl->maViewURL = rURL;
    FileViewResult eResult = ExecuteFilter( rFilter, pAsyncDescriptor );
    switch ( eResult )
    {
    case eFailure:
    case eTimeout:
        mpImpl->maViewURL = sPushURL;
        return eResult;

    case eStillRunning:
        OSL_ENSURE( pAsyncDescriptor, "SvtFileView::Initialize: we told it to read synchronously!" );
        [[fallthrough]];
    case eSuccess:
        return eResult;
    }

    OSL_FAIL( "SvtFileView::Initialize: unreachable!" );
    return eFailure;
}

FileViewResult SvtFileView::ExecuteFilter( const OUString& rFilter, const FileViewAsyncAction* pAsyncDescriptor )
{
    mpImpl->maCurrentFilter = rFilter.toAsciiLowerCase();

    mpImpl->Clear();
    FileViewResult eResult = mpImpl->GetFolderContent_Impl(mpImpl->maViewURL, pAsyncDescriptor, maDenyList);
    OSL_ENSURE( ( eResult != eStillRunning ) || pAsyncDescriptor, "SvtFileView::ExecuteFilter: we told it to read synchronously!" );
    return eResult;
}

void SvtFileView::CancelRunningAsyncAction()
{
    mpImpl->CancelRunningAsyncAction();
}

void SvtFileView::SetNoSelection()
{
    mpImpl->mxView->unselect_all();
    mpImpl->mxIconView->unselect_all();
}

void SvtFileView::SetSelectHdl(const Link<SvtFileView*,void>& rHdl)
{
    mpImpl->SetSelectHandler(rHdl);
}

void SvtFileView::SetDoubleClickHdl(const Link<SvtFileView*,bool>& rHdl)
{
    mpImpl->SetDoubleClickHandler(rHdl);
}

sal_uInt32 SvtFileView::GetSelectionCount() const
{
    if (mpImpl->mxView->get_visible())
        return mpImpl->mxView->count_selected_rows();
    return mpImpl->mxIconView->count_selected_items();
}

SvtContentEntry* SvtFileView::FirstSelected() const
{
    if (mpImpl->mxView->get_visible())
    {
        SvtContentEntry* pRet = nullptr;
        std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxView->make_iterator();
        if (mpImpl->mxView->get_selected(xEntry.get()))
            pRet = weld::fromId<SvtContentEntry*>(mpImpl->mxView->get_id(*xEntry));
        return pRet;
    }

    SvtContentEntry* pRet = nullptr;
    std::unique_ptr<weld::TreeIter> xEntry = mpImpl->mxIconView->make_iterator();
    if (mpImpl->mxIconView->get_selected(xEntry.get()))
        pRet = weld::fromId<SvtContentEntry*>(mpImpl->mxIconView->get_id(*xEntry));
    return pRet;
}

const OUString& SvtFileView::GetViewURL() const
{
    return mpImpl->maViewURL;
}

void SvtFileView::SetOpenDoneHdl( const Link<SvtFileView*,void>& rHdl )
{
    mpImpl->maOpenDoneLink = rHdl;
}

void SvtFileView::EnableDelete( bool bEnable )
{
    mpImpl->EnableDelete( bEnable );
}

void SvtFileView::EndInplaceEditing()
{
    return mpImpl->EndEditing();
}

IMPL_LINK(SvtFileView, HeaderSelect_Impl, int, nColumn, void)
{
    sal_uInt16 nItemID = nColumn + 1;
    // skip "TYPE"
    if (!mpImpl->mxView->TypeColumnVisible() && nItemID != COLUMN_TITLE)
        ++nItemID;

    weld::TreeView* pView = mpImpl->mxView->getWidget();
    bool bSortAtoZ = mpImpl->mbAscending;

    //set new arrow positions in headerbar
    if (nItemID != mpImpl->mnSortColumn)
    {
        // remove old indicator, new will be created in OpenFolder_Impl
        pView->set_sort_indicator(TRISTATE_INDET, mpImpl->GetSortColumn());
    }
    else
        bSortAtoZ = !bSortAtoZ;

    mpImpl->Resort_Impl(nItemID, bSortAtoZ);
}

OUString SvtFileView::GetConfigString() const
{
    // sort order
    OUString sRet = OUString::number( mpImpl->mnSortColumn ) + ";";

    bool bUp = mpImpl->mbAscending;
    sRet += OUString::Concat(bUp ? std::u16string_view(u"1") : std::u16string_view(u"0")) + ";";

    weld::TreeView* pView = mpImpl->mxView->getWidget();
    sal_uInt16 nCount = mpImpl->mxView->TypeColumnVisible() ? 4 : 3;
    for (sal_uInt16 i = 0; i < nCount; ++i)
    {
        sal_uInt16 nId = i + 1;
        // skip "TYPE"
        if (!mpImpl->mxView->TypeColumnVisible() && nId != COLUMN_TITLE)
            ++nId;

        sRet += OUString::number( nId )
                + ";"
                + OUString::number(pView->get_column_width(i))
                + ";";
    }

    return comphelper::string::stripEnd(sRet, ';');
}

::std::vector< SvtContentEntry > SvtFileView::GetContent()
{
    ::std::vector< SvtContentEntry > aContent;

    for(auto const& elem : mpImpl->maContent)
    {
        SvtContentEntry aEntry( elem->maTargetURL, elem->mbIsFolder );
        aContent.push_back( aEntry );
    }

    return aContent;
}

void SvtFileView::SetConfigString(std::u16string_view rCfgStr)
{
    sal_Int32 nIdx = 0;
    sal_uInt16 nSortColumn = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx )));
    bool bAscending = static_cast<bool>(static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx ))));

    std::vector<int> aWidths(mpImpl->mxView->TypeColumnVisible() ? 4 : 3, -1);

    while ( nIdx != -1 )
    {
        int nItemId = o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx ));

        int nWidth = o3tl::toInt32(o3tl::getToken(rCfgStr, 0, ';', nIdx ));

        // skip "TYPE"
        if (!mpImpl->mxView->TypeColumnVisible() && nItemId != COLUMN_TITLE)
            --nItemId;
        int nColumn = nItemId - 1;

        if (nColumn >= 0 && o3tl::make_unsigned(nColumn) < aWidths.size())
            aWidths[nColumn] = nWidth;
    }

    weld::TreeView* pView = mpImpl->mxView->getWidget();
    pView->set_column_fixed_widths(aWidths);
    if (mpImpl->mnSortColumn != nSortColumn)
        pView->set_sort_indicator(TRISTATE_INDET, mpImpl->GetSortColumn());
    mpImpl->Resort_Impl(nSortColumn, bAscending);
}

SvtFileView_Impl::SvtFileView_Impl(SvtFileView* pAntiImpl, weld::Window* pTopLevel,
                                   std::unique_ptr<weld::TreeView> xTreeView,
                                   std::unique_ptr<weld::IconView> xIconView,
                                   Reference < XCommandEnvironment > const & xEnv,
                                   FileViewFlags nFlags, bool bOnlyFolder)
    : m_pAntiImpl                ( pAntiImpl )
    , m_eAsyncActionResult       ( ::svt::EnumerationResult::ERROR )
    , m_bRunningAsyncAction      ( false )
    , m_bAsyncActionCancelled    ( false )
    , m_pTopLevel                ( pTopLevel )
    , mxView(new ViewTabListBox_Impl(std::move(xTreeView), pTopLevel, this, nFlags))
    , mxIconView(std::move(xIconView))
    , mnSortColumn               ( COLUMN_TITLE )
    , mbAscending                ( true )
    , mbOnlyFolder               ( bOnlyFolder )
    , mnSuspendSelectCallback    ( 0 )
    , mbIsFirstResort            ( true )
    , aIntlWrapper               ( Application::GetSettings().GetLanguageTag() )
    , maFolderImage              (RID_BMP_FOLDER)
    , mxCmdEnv ( xEnv )
{
    weld::TreeView* pWidget = mxView->getWidget();

    // set the width to something small so it's the parent that decides the final
    // width
    Size aSize(42, pWidget->get_height_rows(7));
    pWidget->set_size_request(aSize.Width(), aSize.Height());
    mxIconView->set_size_request(aSize.Width(), aSize.Height());
}

SvtFileView_Impl::~SvtFileView_Impl()
{
    Clear();
}

void SvtFileView_Impl::Clear()
{
    ::osl::MutexGuard aGuard( maMutex );

    maContent.clear();
}

FileViewResult SvtFileView_Impl::GetFolderContent_Impl(
    std::u16string_view rFolder,
    const FileViewAsyncAction* pAsyncDescriptor,
    const css::uno::Sequence< OUString >& rDenyList )
{
    ::osl::ClearableMutexGuard aGuard( maMutex );
    INetURLObject aFolderObj( rFolder );
    DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );

    FolderDescriptor aFolder( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );

    aGuard.clear();
    return GetFolderContent_Impl( aFolder, pAsyncDescriptor, rDenyList );
}

FileViewResult SvtFileView_Impl::GetFolderContent_Impl(
    const FolderDescriptor& _rFolder,
    const FileViewAsyncAction* pAsyncDescriptor,
    const css::uno::Sequence< OUString >& rDenyList )
{
    DBG_TESTSOLARMUTEX();
    ::osl::ClearableMutexGuard aGuard( maMutex );

    OSL_ENSURE( !m_xContentEnumerator.is(), "SvtFileView_Impl::GetFolderContent_Impl: still running another enumeration!" );
    m_xContentEnumerator.set(new ::svt::FileViewContentEnumerator(
        mxView->GetCommandEnvironment(), maContent, maMutex));
        // TODO: should we cache and re-use this thread?

    if ( !pAsyncDescriptor )
    {
        ::svt::EnumerationResult eResult = m_xContentEnumerator->enumerateFolderContentSync( _rFolder, rDenyList );
        if ( ::svt::EnumerationResult::SUCCESS == eResult )
        {
            implEnumerationSuccess();
            m_xContentEnumerator.clear();
            return eSuccess;
        }
        m_xContentEnumerator.clear();
        return eFailure;
    }

    m_bRunningAsyncAction = true;
    m_bAsyncActionCancelled = false;
    m_eAsyncActionResult = ::svt::EnumerationResult::ERROR;
    m_aAsyncActionFinished.reset();

    // don't (yet) set m_aCurrentAsyncActionHandler to pTimeout->aFinishHandler.
    // By definition, this handler *only* gets called when the result cannot be obtained
    // during the minimum wait time, so it is only set below, when needed.
    m_aCurrentAsyncActionHandler = Link<void*,void>();

    // minimum time to wait
    TimeValue aTimeout;
    sal_Int32 nMinTimeout = pAsyncDescriptor->nMinTimeout;
    OSL_ENSURE( nMinTimeout > 0, "SvtFileView_Impl::GetFolderContent_Impl: invalid minimum timeout!" );
    if ( nMinTimeout <= 0 )
        nMinTimeout = sal_Int32( 1000 );
    aTimeout.Seconds = nMinTimeout / 1000;
    aTimeout.Nanosec = ( nMinTimeout % 1000 ) * 1000000;

    m_xContentEnumerator->enumerateFolderContent( _rFolder, this );

    // wait until the enumeration is finished
    // for this, release our own mutex (which is used by the enumerator thread)
    aGuard.clear();

    ::osl::Condition::Result eResult = ::osl::Condition::result_ok;
    {
        // also release the SolarMutex. Not all code which is needed during the enumeration
        // is Solar-Thread-Safe, in particular there is some code which needs to access
        // string resources (and our resource system relies on the SolarMutex :()
        SolarMutexReleaser aSolarRelease;

        // now wait. Note that if we didn't get a pAsyncDescriptor, then this is an infinite wait.
        eResult = m_aAsyncActionFinished.wait( &aTimeout );
    }

    ::osl::MutexGuard aGuard2( maMutex );
    if ( ::osl::Condition::result_timeout == eResult )
    {
        // maximum time to wait
        OSL_ENSURE(!m_xCancelAsyncTimer,
                   "SvtFileView_Impl::GetFolderContent_Impl: there's still a previous timer!");
        m_xCancelAsyncTimer.set(new CallbackTimer(this));
        sal_Int32 nMaxTimeout = pAsyncDescriptor->nMaxTimeout;
        OSL_ENSURE( nMaxTimeout > nMinTimeout,
            "SvtFileView_Impl::GetFolderContent_Impl: invalid maximum timeout!" );
        if ( nMaxTimeout <= nMinTimeout )
            nMaxTimeout = nMinTimeout + 5000;
        m_xCancelAsyncTimer->setRemainingTime( salhelper::TTimeValue( nMaxTimeout - nMinTimeout ) );
            // we already waited for nMinTimeout milliseconds, so take this into account
        m_xCancelAsyncTimer->start();

        m_aCurrentAsyncActionHandler = pAsyncDescriptor->aFinishHandler;
        DBG_ASSERT( m_aCurrentAsyncActionHandler.IsSet(), "SvtFileView_Impl::GetFolderContent_Impl: nobody interested when it's finished?" );
        maEntries.clear();
        mxView->clear();
        mxIconView->clear();
        return eStillRunning;
    }

    m_bRunningAsyncAction = false;
    switch ( m_eAsyncActionResult )
    {
    case ::svt::EnumerationResult::SUCCESS:
        return eSuccess;

    case ::svt::EnumerationResult::ERROR:
        return eFailure;
    }

    SAL_WARN( "svtools.contnr", "SvtFileView_Impl::GetFolderContent_Impl: unreachable!" );
    return eFailure;
}

void SvtFileView_Impl::FilterFolderContent_Impl( std::u16string_view rFilter )
{
    if ( rFilter.empty() || ( rFilter == ALL_FILES_FILTER ) )
        // when replacing names, there is always something to filter (no view of ".nametranslation.table")
        return;

    ::osl::MutexGuard aGuard( maMutex );

    if ( maContent.empty() )
        return;

    // collect the filter tokens
    ::std::vector< WildCard > aFilters;
    FilterMatch::createWildCardFilterList(rFilter,aFilters);


    // do the filtering
    std::erase_if(maContent,
        [&aFilters](const std::unique_ptr<SortingData_Impl>& rxContent) {
            if (rxContent->mbIsFolder)
                return false;
            // normalize the content title (we always match case-insensitive)
            // 91872 - 11.09.2001 - frank.schoenheit@sun.com
            OUString sCompareString = rxContent->GetFileName(); // filter works on file name, not on title!
            return std::none_of(aFilters.begin(), aFilters.end(), FilterMatch(sCompareString));
        });
}

IMPL_LINK_NOARG(SvtFileView_Impl, ChangedHdl, weld::TreeView&, void)
{
    if (!mnSuspendSelectCallback)
        m_aSelectHandler.Call(m_pAntiImpl);
}

IMPL_LINK_NOARG(SvtFileView_Impl, SelectionChangedHdl, weld::IconView&, void)
{
    if (!mnSuspendSelectCallback)
        m_aSelectHandler.Call(m_pAntiImpl);
}

void SvtFileView_Impl::SetSelectHandler(const Link<SvtFileView*,void>& rHdl)
{
    m_aSelectHandler = rHdl;

    mxView->connect_changed(LINK(this, SvtFileView_Impl, ChangedHdl));
    mxIconView->connect_selection_changed(LINK(this, SvtFileView_Impl, SelectionChangedHdl));
}

IMPL_LINK_NOARG(SvtFileView_Impl, RowActivatedHdl, weld::TreeView&, bool)
{
    return maDoubleClickHandler.Call(m_pAntiImpl);
}

IMPL_LINK_NOARG(SvtFileView_Impl, ItemActivatedHdl, weld::IconView&, bool)
{
    return maDoubleClickHandler.Call(m_pAntiImpl);
}

void SvtFileView_Impl::SetDoubleClickHandler(const Link<SvtFileView*,bool>& rHdl)
{
    maDoubleClickHandler = rHdl;

    mxView->connect_row_activated(LINK(this, SvtFileView_Impl, RowActivatedHdl));
    mxIconView->connect_item_activated(LINK(this, SvtFileView_Impl, ItemActivatedHdl));
}

void SvtFileView_Impl::OpenFolder_Impl()
{
    ::osl::MutexGuard aGuard( maMutex );

    mxView->freeze();
    mxIconView->freeze();
    maEntries.clear();
    mxView->clear();
    mxIconView->clear();

    for (auto const& elem : maContent)
    {
        if (mbOnlyFolder && !elem->mbIsFolder)
            continue;

        // insert entry and set user data
        maEntries.emplace_back(std::make_unique<SvtContentEntry>(elem->maTargetURL, elem->mbIsFolder));
        OUString sId(weld::toId(maEntries.back().get()));
        mxView->append(sId, elem->maDisplayName, elem->maType, elem->maDisplaySize, elem->maDisplayDate, elem->maImage);
        mxIconView->append(sId, elem->maDisplayName, elem->maImage);
    }

    ++mnSuspendSelectCallback;
    mxView->thaw();

    //set sort indicator
    weld::TreeView* pView = mxView->getWidget();
    pView->set_sort_indicator(mbAscending ? TRISTATE_TRUE : TRISTATE_FALSE, GetSortColumn());

    mxIconView->thaw();
    --mnSuspendSelectCallback;

    ResetCursor();
}

void SvtFileView_Impl::ResetCursor()
{
    if (mxView->get_visible())
    {
        std::unique_ptr<weld::TreeIter> xFirst = mxView->make_iterator();
        if (mxView->get_iter_first(*xFirst))
        {
            // set cursor to the first entry
            mxView->set_cursor(*xFirst);
        }
        // deselect
        mxView->unselect_all();
    }
    else
    {
        std::unique_ptr<weld::TreeIter> xFirst = mxIconView->make_iterator();
        if (mxIconView->get_iter_first(*xFirst))
        {
            // set cursor to the first entry
            mxIconView->set_cursor(*xFirst);
        }
        // deselect
        mxIconView->unselect_all();
    }
}

void SvtFileView_Impl::CancelRunningAsyncAction()
{
    DBG_TESTSOLARMUTEX();
    ::osl::MutexGuard aGuard( maMutex );
    if ( !m_xContentEnumerator.is() )
        return;

    m_bAsyncActionCancelled = true;
    m_xContentEnumerator->cancel();
    m_bRunningAsyncAction = false;

    m_xContentEnumerator.clear();
    if ( m_xCancelAsyncTimer.is() && m_xCancelAsyncTimer->isTicking() )
        m_xCancelAsyncTimer->stop();
    m_xCancelAsyncTimer.clear();
}


void SvtFileView_Impl::onTimeout()
{
    SolarMutexGuard aSolarGuard;
    ::osl::MutexGuard aGuard( maMutex );
    if ( !m_bRunningAsyncAction )
        // there might have been a race condition while we waited for the mutex
        return;

    CancelRunningAsyncAction();

    if ( m_aCurrentAsyncActionHandler.IsSet() )
    {
        Application::PostUserEvent( m_aCurrentAsyncActionHandler, reinterpret_cast< void* >( eTimeout ) );
        m_aCurrentAsyncActionHandler = Link<void*,void>();
    }
}


void SvtFileView_Impl::enumerationDone( ::svt::EnumerationResult eResult )
{
    SolarMutexGuard aSolarGuard;
    ::osl::MutexGuard aGuard( maMutex );

    m_xContentEnumerator.clear();
    if ( m_xCancelAsyncTimer.is() && m_xCancelAsyncTimer->isTicking() )
        m_xCancelAsyncTimer->stop();
    m_xCancelAsyncTimer.clear();

    if ( m_bAsyncActionCancelled )
        // this is to prevent race conditions
        return;

    m_eAsyncActionResult = eResult;
    m_bRunningAsyncAction = false;

    m_aAsyncActionFinished.set();

    if ( svt::EnumerationResult::SUCCESS == eResult )
        implEnumerationSuccess();

    if ( m_aCurrentAsyncActionHandler.IsSet() )
    {
        Application::PostUserEvent( m_aCurrentAsyncActionHandler, reinterpret_cast< void* >( m_eAsyncActionResult ) );
        m_aCurrentAsyncActionHandler = Link<void*,void>();
    }
}


void SvtFileView_Impl::implEnumerationSuccess()
{
    FilterFolderContent_Impl( maCurrentFilter );
    SortFolderContent_Impl();
    CreateDisplayText_Impl();
    OpenFolder_Impl();
    maOpenDoneLink.Call( m_pAntiImpl );
}

OUString SvtFileView_Impl::ReplaceTabWithString(const OUString& rValue)
{
    return rValue.replaceAll(u"\t", u"%09");
}

void SvtFileView_Impl::CreateDisplayText_Impl()
{
    ::osl::MutexGuard aGuard( maMutex );

    for (auto const& elem : maContent)
    {
        // title, type, size, date
        elem->maDisplayName = ReplaceTabWithString(elem->GetTitle());
        // folders don't have a size
        if ( ! elem->mbIsFolder )
            elem->maDisplaySize = CreateExactSizeText( elem->maSize );
        // set the date, but volumes have no date
        if ( ! elem->mbIsFolder || ! elem->mbIsVolume )
        {
            SvtSysLocale aSysLocale;
            const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
            elem->maDisplayDate = rLocaleData.getDate( elem->maModDate )
                                  + ", "
                                  + rLocaleData.getTime( elem->maModDate, false );
        }

        // detect image
        if ( elem->mbIsFolder )
        {
            ::svtools::VolumeInfo aVolInfo( elem->mbIsVolume, elem->mbIsRemote,
                                            elem->mbIsRemoveable, elem->mbIsFloppy,
                                            elem->mbIsCompactDisc );
            elem->maImage = SvFileInformationManager::GetFolderImageId(aVolInfo);
        }
        else
            elem->maImage = SvFileInformationManager::GetFileImageId(INetURLObject(elem->maTargetURL));
    }
}

void SvtFileView_Impl::Resort_Impl( sal_Int16 nColumn, bool bAscending )
{
    // TODO: IconView ()
    ::osl::MutexGuard aGuard( maMutex );

    if ( ( nColumn == mnSortColumn ) &&
         ( bAscending == mbAscending ) )
         return;

    // reset the quick search index
    mxView->ResetQuickSearch_Impl( nullptr );

    std::unique_ptr<weld::TreeIter> xEntry(mxView->make_iterator());
    bool bEntry = mxView->get_cursor(xEntry.get());

    OUString aEntryURL;
    if (bEntry && !mxView->get_id(*xEntry).isEmpty())
        aEntryURL = weld::fromId<SvtContentEntry*>(mxView->get_id(*xEntry))->maURL;

    mnSortColumn = nColumn;
    mbAscending = bAscending;

    SortFolderContent_Impl();
    OpenFolder_Impl();

    if ( !mbIsFirstResort )
    {
        int nPos = GetEntryPos( aEntryURL );
        if (nPos != -1 && nPos < mxView->n_children())
        {
            ++mnSuspendSelectCallback;  // #i15668#
            mxView->set_cursor(nPos);
            --mnSuspendSelectCallback;
        }
    }
    else
        mbIsFirstResort = false;
}

static bool                     gbAscending = true;
static sal_Int16                gnColumn = COLUMN_TITLE;
static const CollatorWrapper*   pCollatorWrapper = nullptr;

/* this function returns true, if aOne is less than aTwo
*/
static bool CompareSortingData_Impl( std::unique_ptr<SortingData_Impl> const & aOne, std::unique_ptr<SortingData_Impl> const & aTwo )
{
    assert(pCollatorWrapper && "*CompareSortingData_Impl(): Can't work this way!");

    sal_Int32 nComp;
    bool      bRet = false;
    bool      bEqual = false;

    if ( aOne->mbIsFolder != aTwo->mbIsFolder )
    {
        bRet = aOne->mbIsFolder;

        // !!! pb: #100376# folder always on top
        if ( !gbAscending )
            bRet = !bRet;
    }
    else
    {
        switch ( gnColumn )
        {
            case COLUMN_TITLE:
                // compare case insensitive first
                nComp = pCollatorWrapper->compareString( aOne->GetLowerTitle(), aTwo->GetLowerTitle() );

                if ( nComp == 0 )
                    nComp = pCollatorWrapper->compareString( aOne->GetTitle(), aTwo->GetTitle() );

                if ( nComp < 0 )
                    bRet = true;
                else if ( nComp > 0 )
                    bRet = false;
                else
                    bEqual = true;
                break;
            case COLUMN_TYPE:
                nComp = pCollatorWrapper->compareString( aOne->maType, aTwo->maType );
                if ( nComp < 0 )
                    bRet = true;
                else if ( nComp > 0 )
                    bRet = false;
                else
                    bEqual = true;
                break;
            case COLUMN_SIZE:
                if ( aOne->maSize < aTwo->maSize )
                    bRet = true;
                else if ( aOne->maSize > aTwo->maSize )
                    bRet = false;
                else
                    bEqual = true;
                break;
            case COLUMN_DATE:
                if ( aOne->maModDate < aTwo->maModDate )
                    bRet = true;
                else if ( aOne->maModDate > aTwo->maModDate )
                    bRet = false;
                else
                    bEqual = true;
                break;
            default:
                SAL_INFO( "svtools.contnr", "CompareSortingData_Impl: Compare unknown type!" );
                bRet = false;
        }
    }

    // when the two elements are equal, we must not return sal_True (which would
    // happen if we just return ! ( a < b ) when not sorting ascending )
    if ( bEqual )
        return false;

    return gbAscending ? bRet : !bRet;
}


void SvtFileView_Impl::SortFolderContent_Impl()
{
    ::osl::MutexGuard aGuard( maMutex );

    if ( maContent.size() > 1 )
    {
        gbAscending = mbAscending;
        gnColumn = mnSortColumn;
        pCollatorWrapper = aIntlWrapper.getCaseCollator();

        std::stable_sort( maContent.begin(), maContent.end(), CompareSortingData_Impl );

        pCollatorWrapper = nullptr;
    }
}


void SvtFileView_Impl::EntryRemoved( std::u16string_view rURL )
{
    ::osl::MutexGuard aGuard( maMutex );

    maContent.erase(std::find_if(maContent.begin(), maContent.end(),
                     [&](const std::unique_ptr<SortingData_Impl> & data) { return data->maTargetURL == rURL; }));
}


void SvtFileView_Impl::EntryRenamed( OUString& rURL,
                                     const OUString& rTitle )
{
    ::osl::MutexGuard aGuard( maMutex );

    auto aFoundElem = std::find_if(maContent.begin(), maContent.end(),
                     [&](const std::unique_ptr<SortingData_Impl> & data) { return data->maTargetURL == rURL; });
    if (aFoundElem != maContent.end())
    {
        (*aFoundElem)->SetNewTitle( rTitle );
        (*aFoundElem)->maDisplayName = ReplaceTabWithString(rTitle);

        INetURLObject aURLObj( rURL );
        aURLObj.setName( rTitle, INetURLObject::EncodeMechanism::All );

        rURL = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );

        (*aFoundElem)->maTargetURL = rURL;
    }
}

const SortingData_Impl& SvtFileView_Impl::FolderInserted( const OUString& rURL, const OUString& rTitle )
{
    ::osl::MutexGuard aGuard( maMutex );

    std::unique_ptr<SortingData_Impl> pData(new SortingData_Impl);

    pData->SetNewTitle( rTitle );
    pData->maSize        = 0;
    pData->mbIsFolder    = true;
    pData->maTargetURL   = rURL;

    ::svtools::VolumeInfo aVolInfo;
    pData->maType = SvFileInformationManager::GetFolderDescription( aVolInfo );
    pData->maImage = SvFileInformationManager::GetFolderImageId( aVolInfo );

    // title, type, size, date
    pData->maDisplayName = ReplaceTabWithString(pData->GetTitle());
    // set the date
    SvtSysLocale aSysLocale;
    const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
    pData->maDisplayDate = rLocaleData.getDate( pData->maModDate )
                           + ", "
                           + rLocaleData.getTime( pData->maModDate );

    maContent.push_back( std::move(pData) );

    return *maContent.back();
}

int SvtFileView_Impl::GetEntryPos(std::u16string_view rURL)
{
    ::osl::MutexGuard aGuard( maMutex );

    auto aFoundElem = std::find_if(maContent.begin(), maContent.end(),
          [&](const std::unique_ptr<SortingData_Impl> & data) { return data->maTargetURL == rURL; });
    return aFoundElem != maContent.end() ? std::distance(maContent.begin(), aFoundElem) : -1;
}

void SvtFileView_Impl::SetViewMode( FileViewMode eMode )
{
    switch ( eMode )
    {
        case eDetailedList:
            mxView->show();
            mxIconView->hide();
            break;

        case eIcon:
            mxView->hide();
            mxIconView->show();
            break;

        default:
            mxView->show();
            mxIconView->hide();
    };
}

bool SvtFileView_Impl::SearchNextEntry( sal_uInt32& nIndex, std::u16string_view rTitle, bool bWrapAround )
{
    ::osl::MutexGuard aGuard( maMutex );

    sal_uInt32 nEnd = maContent.size();
    sal_uInt32 nStart = nIndex;
    while ( nIndex < nEnd )
    {
        SortingData_Impl* pData = maContent[ nIndex ].get();
        if ( pData->GetLowerTitle().startsWith( rTitle ) )
            return true;
        ++nIndex;
    }

    if ( bWrapAround )
    {
        nIndex = 0;
        while ( nIndex < nEnd && nIndex <= nStart )
        {
            SortingData_Impl* pData = maContent[ nIndex ].get();
            if ( pData->GetLowerTitle().startsWith( rTitle ) )
                return true;
            ++nIndex;
        }
    }

    return false;
}

namespace {
    void SAL_CALL CallbackTimer::onShot()
    {
        OSL_ENSURE( m_pTimeoutHandler, "CallbackTimer::onShot: nobody interested in?" );
        SvtFileView_Impl* pHandler( m_pTimeoutHandler );
        if ( pHandler )
            pHandler->onTimeout();
    }
}

void SvtFileView::selected_foreach(const std::function<bool(weld::TreeIter&)>& func)
{
    if (mpImpl->mxView->get_visible())
        mpImpl->mxView->selected_foreach(func);
    else
        mpImpl->mxIconView->selected_foreach(func);
}

weld::Widget* SvtFileView::identifier() const
{
    return mpImpl->mxView->getWidget();
}

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