/* -*- 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 "newhelp.hxx"
#include <sfx2/sfxresid.hxx>
#include "helpinterceptor.hxx"
#include <helper.hxx>
#include <srchdlg.hxx>
#include <sfx2/sfxhelp.hxx>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <o3tl/string_view.hxx>

#include <sfx2/strings.hrc>
#include <helpids.h>
#include <bitmaps.hlst>

#include <rtl/ustrbuf.hxx>
#include <comphelper/configurationhelper.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/frame/XComponentLoader.hpp>
#include <com/sun/star/frame/XTitle.hpp>
#include <com/sun/star/frame/XLayoutManager.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/frame/XDispatch.hpp>
#include <com/sun/star/frame/XDispatchProvider.hpp>
#include <com/sun/star/frame/Frame.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/style/XStyle.hpp>
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/text/XTextCursor.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/text/XTextViewCursor.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/ucb/CommandAbortedException.hpp>
#include <com/sun/star/util/URL.hpp>
#include <com/sun/star/util/XSearchable.hpp>
#include <com/sun/star/util/XSearchDescriptor.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <com/sun/star/util/XURLTransformer.hpp>
#include <com/sun/star/util/XModifiable.hpp>
#include <com/sun/star/util/XCloseable.hpp>
#include <com/sun/star/util/CloseVetoException.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
#include <com/sun/star/view/XViewSettingsSupplier.hpp>
#include <unotools/historyoptions.hxx>
#include <unotools/viewoptions.hxx>
#include <tools/urlobj.hxx>
#include <svtools/imagemgr.hxx>
#include <svtools/miscopt.hxx>
#include <utility>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/i18nhelp.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <vcl/unohelp.hxx>
#include <vcl/weld.hxx>

#include <ucbhelper/content.hxx>
#include <unotools/ucbhelper.hxx>

#include <string_view>
#include <unordered_map>
#include <vector>

using namespace ::ucbhelper;
using namespace ::com::sun::star::ucb;

using namespace ::com::sun::star;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::frame;
using namespace ::com::sun::star::i18n;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::style;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::util;
using namespace ::com::sun::star::view;

using namespace ::comphelper;

// defines ---------------------------------------------------------------

constexpr OUString CONFIGNAME_HELPWIN = u"OfficeHelp"_ustr;
constexpr OUString CONFIGNAME_INDEXWIN = u"OfficeHelpIndex"_ustr;
constexpr OUString CONFIGNAME_SEARCHPAGE = u"OfficeHelpSearch"_ustr;
constexpr OUString IMAGE_URL = u"private:factory/"_ustr;

constexpr OUString PROPERTY_KEYWORDLIST = u"KeywordList"_ustr;
constexpr OUString PROPERTY_KEYWORDREF = u"KeywordRef"_ustr;
constexpr OUString PROPERTY_ANCHORREF = u"KeywordAnchorForRef"_ustr;
constexpr OUString PROPERTY_TITLEREF = u"KeywordTitleForRef"_ustr;
constexpr OUString PROPERTY_TITLE = u"Title"_ustr;
constexpr OUString HELP_URL = u"vnd.sun.star.help://"_ustr;
constexpr OUStringLiteral HELP_SEARCH_TAG = u"/?Query=";
constexpr OUString USERITEM_NAME = u"UserItem"_ustr;

constexpr OUStringLiteral PACKAGE_SETUP = u"/org.openoffice.Setup";
constexpr OUString PATH_OFFICE_FACTORIES = u"Office/Factories/"_ustr;
constexpr OUString KEY_HELP_ON_OPEN = u"ooSetupFactoryHelpOnOpen"_ustr;
constexpr OUStringLiteral KEY_UI_NAME = u"ooSetupFactoryUIName";

namespace sfx2
{


    /** Prepare a search string for searching or selecting.
        For searching every search word needs the postfix '*' and the delimiter ' ' if necessary.
        For selecting the delimiter '|' is required to search with regular expressions.
        Samples:
        search string | output for searching | output for selecting
        -----------------------------------------------------------
        "text"        | "text*"              | "text"
        "text*"       | "text*"              | "text"
        "text menu"   | "text* menu*"        | "text|menu"
    */
    static OUString PrepareSearchString( const OUString& rSearchString,
                                const Reference< XBreakIterator >& xBreak, bool bForSearch )
    {
        OUStringBuffer sSearchStr;
        sal_Int32 nStartPos = 0;
        const lang::Locale aLocale = Application::GetSettings().GetUILanguageTag().getLocale();
        Boundary aBoundary = xBreak->getWordBoundary(
            rSearchString, nStartPos, aLocale, WordType::ANYWORD_IGNOREWHITESPACES, true );

        while ( aBoundary.startPos < aBoundary.endPos )
        {
            nStartPos = aBoundary.endPos;
            OUString sSearchToken( rSearchString.copy(
                static_cast<sal_uInt16>(aBoundary.startPos), static_cast<sal_uInt16>(aBoundary.endPos) - static_cast<sal_uInt16>(aBoundary.startPos) ) );
            if ( !sSearchToken.isEmpty() && ( sSearchToken.getLength() > 1 || sSearchToken[0] != '.' ) )
            {
                if ( bForSearch && sSearchToken[ sSearchToken.getLength() - 1 ] != '*' )
                    sSearchToken += "*";

                if ( sSearchToken.getLength() > 1 ||
                     ( sSearchToken.getLength() > 0 && sSearchToken[ 0 ] != '*' ) )
                {
                    if ( !sSearchStr.isEmpty() )
                    {
                        if ( bForSearch )
                            sSearchStr.append(" ");
                        else
                            sSearchStr.append("|");
                    }
                    sSearchStr.append(sSearchToken);
                }
            }
            aBoundary = xBreak->nextWord( rSearchString, nStartPos,
                                          aLocale, WordType::ANYWORD_IGNOREWHITESPACES );
        }

        return sSearchStr.makeStringAndClear();
    }

// namespace sfx2
}


// struct IndexEntry_Impl ------------------------------------------------

namespace {

struct IndexEntry_Impl
{
    bool            m_bSubEntry;
    OUString        m_aURL;

    IndexEntry_Impl( OUString aURL, bool bSubEntry ) :
        m_bSubEntry( bSubEntry ), m_aURL(std::move( aURL )) {}
};

// struct ContentEntry_Impl ----------------------------------------------

struct ContentEntry_Impl
{
    OUString    aURL;
    bool        bIsFolder;

    ContentEntry_Impl( OUString _aURL, bool bFolder ) :
        aURL(std::move( _aURL )), bIsFolder( bFolder ) {}
};

}

void ContentTabPage_Impl::InitRoot()
{
    std::vector< OUString > aList =
        SfxContentHelper::GetHelpTreeViewContents( u"vnd.sun.star.hier://com.sun.star.help.TreeView/"_ustr );

    for (const OUString & aRow : aList)
    {
        sal_Int32 nIdx = 0;
        OUString aTitle = aRow.getToken( 0, '\t', nIdx );
        OUString aURL = aRow.getToken( 0, '\t', nIdx );
        sal_Unicode cFolder = o3tl::getToken(aRow, 0, '\t', nIdx )[0];
        bool bIsFolder = ( '1' == cFolder );
        OUString sId;
        if (bIsFolder)
            sId = weld::toId(new ContentEntry_Impl(aURL, true));
        m_xContentBox->insert(nullptr, -1, &aTitle, &sId, nullptr, nullptr, true, m_xScratchIter.get());
        m_xContentBox->set_image(*m_xScratchIter, aClosedBookImage);
    }
}

void ContentTabPage_Impl::ClearChildren(const weld::TreeIter* pParent)
{
    std::unique_ptr<weld::TreeIter> xEntry = m_xContentBox->make_iterator(pParent);
    bool bEntry = m_xContentBox->iter_children(*xEntry);
    while (bEntry)
    {
        ClearChildren(xEntry.get());
        delete weld::fromId<ContentEntry_Impl*>(m_xContentBox->get_id(*xEntry));
        bEntry = m_xContentBox->iter_next_sibling(*xEntry);
    }

}

IMPL_LINK(ContentTabPage_Impl, ExpandingHdl, const weld::TreeIter&, rIter, bool)
{
    ContentEntry_Impl* pContentEntry = weld::fromId<ContentEntry_Impl*>(m_xContentBox->get_id(rIter));
    if (!m_xContentBox->iter_has_child(rIter))
    {
        try
        {
            if (pContentEntry)
            {
                std::vector<OUString > aList = SfxContentHelper::GetHelpTreeViewContents(pContentEntry->aURL);

                for (const OUString & aRow : aList)
                {
                    sal_Int32 nIdx = 0;
                    OUString aTitle = aRow.getToken( 0, '\t', nIdx );
                    OUString aURL = aRow.getToken( 0, '\t', nIdx );
                    sal_Unicode cFolder = o3tl::getToken(aRow, 0, '\t', nIdx )[0];
                    bool bIsFolder = ( '1' == cFolder );
                    if ( bIsFolder )
                    {
                        OUString sId = weld::toId(new ContentEntry_Impl(aURL, true));
                        m_xContentBox->insert(&rIter, -1, &aTitle, &sId, nullptr, nullptr, true, m_xScratchIter.get());
                        m_xContentBox->set_image(*m_xScratchIter, aClosedBookImage);
                    }
                    else
                    {
                        Any aAny( ::utl::UCBContentHelper::GetProperty( aURL, u"TargetURL"_ustr ) );
                        OUString sId;
                        OUString aTargetURL;
                        if ( aAny >>= aTargetURL )
                            sId = weld::toId(new ContentEntry_Impl(aTargetURL, false));
                        m_xContentBox->insert(&rIter, -1, &aTitle, &sId, nullptr, nullptr, false, m_xScratchIter.get());
                        m_xContentBox->set_image(*m_xScratchIter, aDocumentImage);
                    }
                }
            }
        }
        catch (const Exception&)
        {
            TOOLS_WARN_EXCEPTION( "sfx.appl", "ContentListBox_Impl::RequestingChildren(): unexpected exception" );
        }
    }

    if (!pContentEntry || pContentEntry->bIsFolder)
        m_xContentBox->set_image(rIter, aOpenBookImage);

    return true;
}

IMPL_LINK(ContentTabPage_Impl, CollapsingHdl, const weld::TreeIter&, rIter, bool)
{
    ContentEntry_Impl* pContentEntry = weld::fromId<ContentEntry_Impl*>(m_xContentBox->get_id(rIter));
    if (!pContentEntry || pContentEntry->bIsFolder)
        m_xContentBox->set_image(rIter, aClosedBookImage);

    return true;
}

OUString ContentTabPage_Impl::GetSelectedEntry() const
{
    OUString aRet;
    ContentEntry_Impl* pEntry = weld::fromId<ContentEntry_Impl*>(m_xContentBox->get_selected_id());
    if (pEntry && !pEntry->bIsFolder)
        aRet = pEntry->aURL;
    return aRet;
}

// class HelpTabPage_Impl ------------------------------------------------
HelpTabPage_Impl::HelpTabPage_Impl(weld::Widget* pParent, SfxHelpIndexWindow_Impl* pIdxWin,
    const OUString& rID, const OUString& rUIXMLDescription)
    : BuilderPage(pParent, nullptr, rUIXMLDescription, rID)
    , m_pIdxWin(pIdxWin)
{
}

HelpTabPage_Impl::~HelpTabPage_Impl()
{
}

// class ContentTabPage_Impl ---------------------------------------------
ContentTabPage_Impl::ContentTabPage_Impl(weld::Widget* pParent, SfxHelpIndexWindow_Impl* pIdxWin)
    : HelpTabPage_Impl(pParent, pIdxWin, u"HelpContentPage"_ustr,
        u"sfx/ui/helpcontentpage.ui"_ustr)
    , m_xContentBox(m_xBuilder->weld_tree_view(u"content"_ustr))
    , m_xScratchIter(m_xContentBox->make_iterator())
    , aOpenBookImage(BMP_HELP_CONTENT_BOOK_OPEN)
    , aClosedBookImage(BMP_HELP_CONTENT_BOOK_CLOSED)
    , aDocumentImage(BMP_HELP_CONTENT_DOC)
{
    m_xContentBox->set_size_request(m_xContentBox->get_approximate_digit_width() * 30,
                                    m_xContentBox->get_height_rows(20));
    m_xContentBox->connect_row_activated(LINK(this, ContentTabPage_Impl, DoubleClickHdl));
    m_xContentBox->connect_expanding(LINK(this, ContentTabPage_Impl, ExpandingHdl));
    m_xContentBox->connect_collapsing(LINK(this, ContentTabPage_Impl, CollapsingHdl));

    InitRoot();
}

IMPL_LINK_NOARG(ContentTabPage_Impl, DoubleClickHdl, weld::TreeView&, bool)
{
    aDoubleClickHdl.Call(nullptr);
    return false;
}

void ContentTabPage_Impl::SetDoubleClickHdl(const Link<LinkParamNone*, void>& rLink)
{
    aDoubleClickHdl = rLink;
}

ContentTabPage_Impl::~ContentTabPage_Impl()
{
    std::unique_ptr<weld::TreeIter> xEntry = m_xContentBox->make_iterator();
    bool bEntry = m_xContentBox->get_iter_first(*xEntry);
    while (bEntry)
    {
        ClearChildren(xEntry.get());
        delete weld::fromId<ContentEntry_Impl*>(m_xContentBox->get_id(*xEntry));
        bEntry = m_xContentBox->iter_next_sibling(*xEntry);
    }
}

void IndexTabPage_Impl::SelectExecutableEntry()
{
    sal_Int32 nPos = m_xIndexList->find_text(m_xIndexEntry->get_text());
    if (nPos == -1)
        return;

    sal_Int32 nOldPos = nPos;
    OUString aEntryText;
    IndexEntry_Impl* pEntry = weld::fromId<IndexEntry_Impl*>(m_xIndexList->get_id(nPos));
    sal_Int32 nCount = m_xIndexList->n_children();
    while ( nPos < nCount && ( !pEntry || pEntry->m_aURL.isEmpty() ) )
    {
        pEntry = weld::fromId<IndexEntry_Impl*>(m_xIndexList->get_id(++nPos));
        aEntryText = m_xIndexList->get_text(nPos);
    }

    if ( nOldPos != nPos )
        m_xIndexEntry->set_text(aEntryText);
}

// class IndexTabPage_Impl -----------------------------------------------
IndexTabPage_Impl::IndexTabPage_Impl(weld::Widget* pParent, SfxHelpIndexWindow_Impl* pIdxWin)
    : HelpTabPage_Impl(pParent, pIdxWin, u"HelpIndexPage"_ustr, u"sfx/ui/helpindexpage.ui"_ustr)
    , m_xIndexEntry(m_xBuilder->weld_entry(u"termentry"_ustr))
    , m_xIndexList(m_xBuilder->weld_tree_view(u"termlist"_ustr))
    , m_xOpenBtn(m_xBuilder->weld_button(u"display"_ustr))
    , aFactoryIdle("sfx2 appl IndexTabPage_Impl Factory")
    , aAutoCompleteIdle("sfx2 appl IndexTabPage_Impl AutoComplete")
    , aKeywordTimer("sfx2::IndexTabPage_Impl aKeywordTimer")
    , bIsActivated(false)
    , nRowHeight(m_xIndexList->get_height_rows(1))
    , nAllHeight(0)
    , nLastCharCode(0)
{
    m_xIndexList->set_size_request(m_xIndexList->get_approximate_digit_width() * 30, -1);

    m_xOpenBtn->connect_clicked(LINK(this, IndexTabPage_Impl, OpenHdl));
    aFactoryIdle.SetInvokeHandler( LINK(this, IndexTabPage_Impl, IdleHdl ));
    aAutoCompleteIdle.SetInvokeHandler( LINK(this, IndexTabPage_Impl, AutoCompleteHdl ));
    aKeywordTimer.SetInvokeHandler( LINK( this, IndexTabPage_Impl, TimeoutHdl ) );
    m_xIndexList->connect_row_activated(LINK(this, IndexTabPage_Impl, DoubleClickHdl));
    m_xIndexList->connect_selection_changed(LINK(this, IndexTabPage_Impl, TreeChangeHdl));
    m_xIndexList->connect_custom_get_size(LINK(this, IndexTabPage_Impl, CustomGetSizeHdl));
    m_xIndexList->connect_custom_render(LINK(this, IndexTabPage_Impl, CustomRenderHdl));
    m_xIndexList->set_column_custom_renderer(0, true);
    m_xIndexList->connect_size_allocate(LINK(this, IndexTabPage_Impl, ResizeHdl));
    m_xIndexEntry->connect_key_press(LINK(this, IndexTabPage_Impl, KeyInputHdl));
    m_xIndexEntry->connect_changed(LINK(this, IndexTabPage_Impl, EntryChangeHdl));
    m_xIndexEntry->connect_activate(LINK(this, IndexTabPage_Impl, ActivateHdl));
}

IMPL_LINK(IndexTabPage_Impl, ResizeHdl, const Size&, rSize, void)
{
    nAllHeight = rSize.Height();
}

IMPL_LINK_NOARG(IndexTabPage_Impl, CustomGetSizeHdl, weld::TreeView::get_size_args, Size)
{
    return Size(m_xIndexList->get_size_request().Width(), nRowHeight);
}

IMPL_LINK(IndexTabPage_Impl, CustomRenderHdl, weld::TreeView::render_args, aPayload, void)
{
    vcl::RenderContext& rRenderContext = std::get<0>(aPayload);
    const ::tools::Rectangle& rRect = std::get<1>(aPayload);
    bool bSelected = std::get<2>(aPayload);
    const OUString& rId = std::get<3>(aPayload);

    auto popIt = rRenderContext.ScopedPush(vcl::PushFlags::TEXTCOLOR);
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    if (bSelected)
        rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
    else
        rRenderContext.SetTextColor(rStyleSettings.GetDialogTextColor());

    Point aPos(rRect.TopLeft());
    aPos.AdjustY((rRect.GetHeight() - rRenderContext.GetTextHeight()) / 2);

    int nIndex = m_xIndexList->find_id(rId);
    OUString aEntry(m_xIndexList->get_text(nIndex));

    IndexEntry_Impl* pEntry = weld::fromId<IndexEntry_Impl*>(rId);
    if (pEntry && pEntry->m_bSubEntry)
    {
        // indent sub entries
        aPos.AdjustX(8);
        sal_Int32 nPos = aEntry.indexOf(';');
        rRenderContext.DrawText(aPos, (nPos !=-1) ? aEntry.copy(nPos + 1) : aEntry);
    }
    else
        rRenderContext.DrawText(aPos, aEntry);
}

IMPL_LINK_NOARG(IndexTabPage_Impl, TreeChangeHdl, weld::TreeView&, void)
{
    m_xIndexEntry->set_text(m_xIndexList->get_selected_text());
}

IMPL_LINK_NOARG(IndexTabPage_Impl, EntryChangeHdl, weld::Entry&, void)
{
    switch (nLastCharCode)
    {
        case css::awt::Key::DELETE_WORD_BACKWARD:
        case css::awt::Key::DELETE_WORD_FORWARD:
        case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
        case css::awt::Key::DELETE_TO_END_OF_LINE:
        case KEY_BACKSPACE:
        case KEY_DELETE:
            aAutoCompleteIdle.Stop();
            break;
        default:
            aAutoCompleteIdle.Start();
            break;
    }
}

IMPL_LINK(IndexTabPage_Impl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    const vcl::KeyCode& rKCode = rKEvt.GetKeyCode();
    if (rKCode.GetModifier()) // only with no modifiers held
        return false;

    sal_uInt16 nCode = rKCode.GetCode();

    if (nCode == KEY_UP || nCode == KEY_PAGEUP ||
        nCode == KEY_DOWN || nCode == KEY_PAGEDOWN)
    {
//        disable_notify_events();
        sal_Int32 nIndex = m_xIndexList->get_selected_index();
        sal_Int32 nOrigIndex = nIndex;
        sal_Int32 nCount = m_xIndexList->n_children();
        if (nIndex == -1)
        {
            m_xIndexList->set_cursor(0);
            m_xIndexList->select(0);
            m_xIndexEntry->set_text(m_xIndexList->get_selected_text());
        }
        else
        {
            if (nCode == KEY_UP)
                --nIndex;
            else if (nCode == KEY_DOWN)
                ++nIndex;
            else if (nCode == KEY_PAGEUP)
            {
                int nVisRows = nAllHeight / nRowHeight;
                nIndex -= nVisRows;
            }
            else if (nCode == KEY_PAGEDOWN)
            {
                int nVisRows = nAllHeight / nRowHeight;
                nIndex += nVisRows;
            }

            if (nIndex < 0)
                nIndex = 0;
            if (nIndex >= nCount)
                nIndex = nCount - 1;

            if (nIndex != nOrigIndex)
            {
                m_xIndexList->set_cursor(nIndex);
                m_xIndexList->select(nIndex);
                m_xIndexEntry->set_text(m_xIndexList->get_selected_text());
            }

//            m_xIndexList->grab_focus();
//            g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
//            m_xIndexEntry->set_text(m_xIndexList->get_selected_text());
//            m_xIndexEntry->grab_focus();
        }
        m_xIndexEntry->select_region(0, -1);
//        enable_notify_events();
//        m_bTreeChange = true;
//        m_pEntry->fire_signal_changed();
//        m_bTreeChange = false;
        return true;
    }

    nLastCharCode = nCode;
    return false;
}

IndexTabPage_Impl::~IndexTabPage_Impl()
{
    ClearIndex();
}

namespace sfx2 {

    typedef std::unordered_map< OUString, int > KeywordInfo;
}

void IndexTabPage_Impl::InitializeIndex()
{
    weld::WaitObject aWaitCursor(m_pIdxWin->GetFrameWeld());

    // By now more than 256 equal entries are not allowed
    sal_Unicode append[256];
    for(sal_Unicode & k : append)
        k =  ' ';

    sfx2::KeywordInfo aInfo;
    m_xIndexList->freeze();

    try
    {
        OUStringBuffer aURL(HELP_URL + sFactory);
        AppendConfigToken(aURL, true);

        Content aCnt( aURL.makeStringAndClear(), Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
        css::uno::Reference< css::beans::XPropertySetInfo > xInfo = aCnt.getProperties();
        if ( xInfo->hasPropertyByName( PROPERTY_ANCHORREF ) )
        {
            css::uno::Sequence< OUString > aPropSeq{ PROPERTY_KEYWORDLIST, PROPERTY_KEYWORDREF,
                                                     PROPERTY_ANCHORREF, PROPERTY_TITLEREF };

            // abi: use one possibly remote call only
            css::uno::Sequence< css::uno::Any > aAnySeq =
                  aCnt.getPropertyValues( aPropSeq );

            css::uno::Sequence< OUString > aKeywordList;
            css::uno::Sequence< css::uno::Sequence< OUString > > aKeywordRefList;
            css::uno::Sequence< css::uno::Sequence< OUString > > aAnchorRefList;
            css::uno::Sequence< css::uno::Sequence< OUString > > aTitleRefList;

            if ( ( aAnySeq[0] >>= aKeywordList ) && ( aAnySeq[1] >>= aKeywordRefList ) &&
                 ( aAnySeq[2] >>= aAnchorRefList ) && ( aAnySeq[3] >>= aTitleRefList ) )
            {
                int ndx,tmp;
                OUString aIndex, aTempString;
                sfx2::KeywordInfo::iterator it;

                for ( int i = 0; i < aKeywordList.getLength(); ++i )
                {
                    // abi: Do not copy, but use references
                    const OUString& aKeywordPair = aKeywordList[i];
                    DBG_ASSERT( !aKeywordPair.isEmpty(), "invalid help index" );
                    const css::uno::Sequence< OUString >& aRefList = aKeywordRefList[i];
                    const css::uno::Sequence< OUString >& aAnchorList = aAnchorRefList[i];
                    const css::uno::Sequence< OUString >& aTitleList = aTitleRefList[i];

                    DBG_ASSERT( aRefList.getLength() == aAnchorList.getLength(),"reference list and title list of different length" );

                    ndx = aKeywordPair.indexOf( ';' );
                    const bool insert = ndx != -1;

                    OUString sId;

                    if ( insert )
                    {
                        aTempString = aKeywordPair.copy( 0, ndx );
                        if ( aIndex != aTempString )
                        {
                            aIndex = aTempString;
                            it = aInfo.emplace(aTempString, 0).first;
                            sId = weld::toId(new IndexEntry_Impl(OUString(), false));
                            if ( (tmp = it->second++) != 0)
                                m_xIndexList->append(
                                    sId, aTempString + std::u16string_view(append, tmp));
                            else
                                m_xIndexList->append(sId, aTempString);
                        }
                    }
                    else
                        aIndex.clear();

                    sal_uInt32 nRefListLen = aRefList.getLength();

                    DBG_ASSERT( aAnchorList.hasElements(), "*IndexTabPage_Impl::InitializeIndex(): AnchorList is empty!" );
                    DBG_ASSERT( nRefListLen, "*IndexTabPage_Impl::InitializeIndex(): RefList is empty!" );

                    if ( aAnchorList.hasElements() && nRefListLen )
                    {
                        if ( aAnchorList[0].getLength() > 0 )
                        {
                            sId = weld::toId(new IndexEntry_Impl(aRefList[0] + "#" + aAnchorList[0], insert));
                        }
                        else
                            sId = weld::toId(new IndexEntry_Impl(aRefList[0], insert));
                    }

                    // Assume the token is trimmed
                    it = aInfo.emplace(aKeywordPair, 0).first;
                    if ((tmp = it->second++) != 0)
                        m_xIndexList->append(sId, aKeywordPair + std::u16string_view(append, tmp));
                    else
                        m_xIndexList->append(sId, aKeywordPair);

                    for ( sal_uInt32 j = 1; j < nRefListLen ; ++j )
                    {
                        aTempString = aKeywordPair + " - " + aTitleList[j];

                        if ( aAnchorList[j].getLength() > 0 )
                            sId = weld::toId(new IndexEntry_Impl(aRefList[j] + "#" + aAnchorList[j], insert));
                        else
                            sId = weld::toId(new IndexEntry_Impl(aRefList[j], insert));

                        it = aInfo.emplace(aTempString, 0).first;
                        if ( (tmp = it->second++) != 0 )
                            m_xIndexList->append(
                                sId, aTempString + std::u16string_view(append, tmp));
                        else
                            m_xIndexList->append(sId, aTempString);
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "IndexTabPage_Impl::InitializeIndex(): unexpected exception" );
    }

    m_xIndexList->thaw();

    if ( !sKeyword.isEmpty() )
        aKeywordLink.Call( *this );
}

void IndexTabPage_Impl::ClearIndex()
{
    const sal_Int32 nCount = m_xIndexList->n_children();
    for ( sal_Int32 i = 0; i < nCount; ++i )
        delete weld::fromId<IndexEntry_Impl*>(m_xIndexList->get_id(i));
    m_xIndexList->clear();
}

IMPL_LINK_NOARG(IndexTabPage_Impl, OpenHdl, weld::Button&, void)
{
    aDoubleClickHdl.Call(nullptr);
}

IMPL_LINK_NOARG(IndexTabPage_Impl, ActivateHdl, weld::Entry&, bool)
{
    aDoubleClickHdl.Call(nullptr);
    return true;
}

IMPL_LINK_NOARG(IndexTabPage_Impl, DoubleClickHdl, weld::TreeView&, bool)
{
    aDoubleClickHdl.Call(nullptr);
    return true;
}

IMPL_LINK_NOARG(IndexTabPage_Impl, IdleHdl, Timer*, void)
{
    InitializeIndex();
}

int IndexTabPage_Impl::starts_with(const OUString& rStr, int nStartRow, bool bCaseSensitive)
{
    const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();

    int nRet = nStartRow;
    int nCount = m_xIndexList->n_children();
    while (nRet < nCount)
    {
        OUString aStr(m_xIndexList->get_text(nRet));
        const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
        if (bMatch)
            return nRet;
        ++nRet;
    }

    return -1;
}

IMPL_LINK_NOARG(IndexTabPage_Impl, AutoCompleteHdl, Timer*, void)
{
    OUString aStartText = m_xIndexEntry->get_text();
    int nStartPos, nEndPos;
    m_xIndexEntry->get_selection_bounds(nStartPos, nEndPos);
    int nMaxSelection = std::max(nStartPos, nEndPos);
    if (nMaxSelection != aStartText.getLength())
        return;

    int nActive = m_xIndexList->get_selected_index();
    int nStart = nActive;

    if (nStart == -1)
        nStart = 0;

    // Try match case insensitive from current position
    int nPos = starts_with(aStartText, nStart, false);
    if (nPos == -1 && nStart != 0)
    {
        // Try match case insensitive, but from start
        nPos = starts_with(aStartText, 0, false);
    }

    if (nPos == -1)
    {
        // Try match case sensitive from current position
        nPos = starts_with(aStartText, nStart, true);
        if (nPos == -1 && nStart != 0)
        {
            // Try match case sensitive, but from start
            nPos = starts_with(aStartText, 0, true);
        }
    }

    if (nPos != -1)
    {
        m_xIndexList->set_cursor(nPos);
        m_xIndexList->select(nPos);
        OUString aText = m_xIndexList->get_text(nPos);
        if (aText != aStartText)
            m_xIndexEntry->set_text(aText);
        m_xIndexEntry->select_region(aText.getLength(), aStartText.getLength());
    }
}

IMPL_LINK( IndexTabPage_Impl, TimeoutHdl, Timer*, pTimer, void)
{
    if(&aKeywordTimer == pTimer && !sKeyword.isEmpty())
        aKeywordLink.Call(*this);
}

void IndexTabPage_Impl::Activate()
{
    if ( !bIsActivated )
    {
        bIsActivated = true;
        aFactoryIdle.Start();
    }
}

void IndexTabPage_Impl::SetDoubleClickHdl(const Link<LinkParamNone*, void>& rLink)
{
    aDoubleClickHdl = rLink;
}

void IndexTabPage_Impl::SetFactory( const OUString& rFactory )
{
    OUString sNewFactory( rFactory );
    DBG_ASSERT( !sNewFactory.isEmpty(), "empty factory" );
    bool bValid = m_pIdxWin->IsValidFactory( rFactory );

    if ( sFactory.isEmpty() && !bValid )
    {
        sNewFactory = SfxHelp::GetDefaultHelpModule();
        bValid = true;
    }

    if ( sNewFactory != sFactory && bValid )
    {
        sFactory = sNewFactory;
        ClearIndex();
        if ( bIsActivated )
            aFactoryIdle.Start();
    }
}

OUString IndexTabPage_Impl::GetSelectedEntry() const
{
    OUString aRet;
    IndexEntry_Impl* pEntry = weld::fromId<IndexEntry_Impl*>(m_xIndexList->get_id(m_xIndexList->find_text(m_xIndexEntry->get_text())));
    if (pEntry)
        aRet = pEntry->m_aURL;
    return aRet;
}

void IndexTabPage_Impl::SetKeyword( const OUString& rKeyword )
{
    sKeyword = rKeyword;

    if (m_xIndexList->n_children() > 0)
        aKeywordTimer.Start();
    else if ( !bIsActivated )
        aFactoryIdle.Start();
}


bool IndexTabPage_Impl::HasKeyword() const
{
    bool bRet = false;
    if ( !sKeyword.isEmpty() )
    {
        sal_Int32 nPos = m_xIndexList->find_text( sKeyword );
        bRet = nPos != -1;
    }

    return bRet;
}


bool IndexTabPage_Impl::HasKeywordIgnoreCase()
{
    bool bRet = false;
    if ( !sKeyword.isEmpty() )
    {
        sal_Int32 nEntries = m_xIndexList->n_children();
        const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetLocaleI18nHelper();
        for ( sal_Int32 n = 0; n < nEntries; n++)
        {
            const OUString sIndexItem {m_xIndexList->get_text(n)};
            if (rI18nHelper.MatchString( sIndexItem, sKeyword ))
            {
                sKeyword = sIndexItem;
                bRet = true;
            }
        }
    }

    return bRet;
}

void IndexTabPage_Impl::OpenKeyword()
{
    if ( !sKeyword.isEmpty() )
    {
        m_xIndexEntry->set_text(sKeyword);
        aDoubleClickHdl.Call(nullptr);
        sKeyword.clear();
    }
}

IMPL_LINK_NOARG(SearchTabPage_Impl, ActivateHdl, weld::ComboBox&, bool)
{
    Search();
    return true;
}

// class SearchTabPage_Impl ----------------------------------------------

SearchTabPage_Impl::SearchTabPage_Impl(weld::Widget* pParent, SfxHelpIndexWindow_Impl* pIdxWin)
    : HelpTabPage_Impl(pParent, pIdxWin, u"HelpSearchPage"_ustr,
        u"sfx/ui/helpsearchpage.ui"_ustr)
    , m_xSearchED(m_xBuilder->weld_combo_box(u"search"_ustr))
    , m_xSearchBtn(m_xBuilder->weld_button(u"find"_ustr))
    , m_xFullWordsCB(m_xBuilder->weld_check_button(u"completewords"_ustr))
    , m_xScopeCB(m_xBuilder->weld_check_button(u"headings"_ustr))
    , m_xResultsLB(m_xBuilder->weld_tree_view(u"results"_ustr))
    , m_xOpenBtn(m_xBuilder->weld_button(u"display"_ustr))
    , xBreakIterator(vcl::unohelper::CreateBreakIterator())
{
    m_xResultsLB->set_size_request(m_xResultsLB->get_approximate_digit_width() * 30,
                                   m_xResultsLB->get_height_rows(15));

    m_xSearchBtn->connect_clicked(LINK(this, SearchTabPage_Impl, ClickHdl));
    m_xSearchED->connect_changed(LINK(this, SearchTabPage_Impl, ModifyHdl));
    m_xSearchED->connect_entry_activate(LINK(this, SearchTabPage_Impl, ActivateHdl));
    m_xOpenBtn->connect_clicked(LINK(this, SearchTabPage_Impl, OpenHdl));
    m_xResultsLB->connect_row_activated(LINK(this, SearchTabPage_Impl, DoubleClickHdl));

    SvtViewOptions aViewOpt( EViewType::TabPage, CONFIGNAME_SEARCHPAGE );
    if ( aViewOpt.Exists() )
    {
        OUString aUserData;
        Any aUserItem = aViewOpt.GetUserItem( USERITEM_NAME );
        if ( aUserItem >>= aUserData )
        {
            sal_Int32 nIdx {0};
            bool bChecked = o3tl::toInt32(o3tl::getToken(aUserData, 0, ';', nIdx)) == 1;
            m_xFullWordsCB->set_active(bChecked);
            bChecked = o3tl::toInt32(o3tl::getToken(aUserData, 0, ';', nIdx)) == 1;
            m_xScopeCB->set_active(bChecked);

            while ( nIdx > 0 )
            {
                m_xSearchED->append_text( INetURLObject::decode(
                    o3tl::getToken(aUserData, 0, ';', nIdx),
                    INetURLObject::DecodeMechanism::WithCharset ) );
            }
        }
    }

    ModifyHdl(*m_xSearchED);
}

SearchTabPage_Impl::~SearchTabPage_Impl()
{
    SvtViewOptions aViewOpt( EViewType::TabPage, CONFIGNAME_SEARCHPAGE );
    OUStringBuffer aUserData =
            OUString::number(m_xFullWordsCB->get_active() ? 1 : 0) +
            ";" +
            OUString::number(m_xScopeCB->get_active() ? 1 : 0);
    sal_Int32 nCount = std::min(m_xSearchED->get_count(), 10);  // save only 10 entries

    for ( sal_Int32 i = 0; i < nCount; ++i )
    {
        aUserData.append(";" +
            INetURLObject::encode(
                m_xSearchED->get_text(i),
                INetURLObject::PART_UNO_PARAM_VALUE,
                INetURLObject::EncodeMechanism::All ));
    }

    Any aUserItem( aUserData.makeStringAndClear() );
    aViewOpt.SetUserItem( USERITEM_NAME, aUserItem );

    m_xSearchED.reset();
    m_xSearchBtn.reset();
    m_xFullWordsCB.reset();
    m_xScopeCB.reset();
    m_xResultsLB.reset();
    m_xOpenBtn.reset();
}

void SearchTabPage_Impl::ClearSearchResults()
{
    m_xResultsLB->clear();
}

void SearchTabPage_Impl::RememberSearchText( const OUString& rSearchText )
{
    for (sal_Int32 i = 0, nEntryCount = m_xSearchED->get_count(); i < nEntryCount; ++i)
    {
        if (rSearchText == m_xSearchED->get_text(i))
        {
            m_xSearchED->remove(i);
            break;
        }
    }

    m_xSearchED->insert_text(0, rSearchText);
}

IMPL_LINK_NOARG(SearchTabPage_Impl, ClickHdl, weld::Button&, void)
{
    Search();
}

void SearchTabPage_Impl::Search()
{
    OUString aSearchText = comphelper::string::strip(m_xSearchED->get_active_text(), ' ');
    if ( aSearchText.isEmpty() )
        return;

    std::unique_ptr<weld::WaitObject> xWaitCursor(new weld::WaitObject(m_pIdxWin->GetFrameWeld()));
    ClearSearchResults();
    RememberSearchText( aSearchText );
    OUStringBuffer aSearchURL(HELP_URL + aFactory + HELP_SEARCH_TAG);
    if (!m_xFullWordsCB->get_active())
        aSearchText = sfx2::PrepareSearchString( aSearchText, xBreakIterator, true );
    aSearchURL.append(aSearchText);
    AppendConfigToken(aSearchURL, false);
    if (m_xScopeCB->get_active())
        aSearchURL.append("&Scope=Heading");
    std::vector< OUString > aFactories = SfxContentHelper::GetResultSet(aSearchURL.makeStringAndClear());
    for (const OUString & rRow : aFactories)
    {
        sal_Int32 nIdx = 0;
        OUString aTitle = rRow.getToken(0, '\t', nIdx);
        OUString sURL(rRow.getToken(1, '\t', nIdx));
        m_xResultsLB->append(sURL, aTitle);
    }
    xWaitCursor.reset();

    if ( aFactories.empty() )
    {
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xContainer.get(),
                                                                 VclMessageType::Info, VclButtonsType::Ok,
                                                                 SfxResId(STR_INFO_NOSEARCHRESULTS)));
        xBox->run();
    }
}

IMPL_LINK_NOARG(SearchTabPage_Impl, OpenHdl, weld::Button&, void)
{
    aDoubleClickHdl.Call(nullptr);
}

IMPL_LINK(SearchTabPage_Impl, ModifyHdl, weld::ComboBox&, rComboBox, void)
{
    OUString aSearchText = comphelper::string::strip(m_xSearchED->get_active_text(), ' ');
    m_xSearchBtn->set_sensitive(!aSearchText.isEmpty());

    if (rComboBox.changed_by_direct_pick())
        Search();
}

IMPL_LINK_NOARG(SearchTabPage_Impl, DoubleClickHdl, weld::TreeView&, bool)
{
    aDoubleClickHdl.Call(nullptr);
    return true;
}

void SearchTabPage_Impl::SetDoubleClickHdl(const Link<LinkParamNone*, void>& rLink)
{
    aDoubleClickHdl = rLink;
}

OUString SearchTabPage_Impl::GetSelectedEntry() const
{
    return m_xResultsLB->get_selected_id();
}

void SearchTabPage_Impl::ClearPage()
{
    ClearSearchResults();
    m_xSearchED->set_entry_text(OUString());
}

bool SearchTabPage_Impl::OpenKeyword( const OUString& rKeyword )
{
    bool bRet = false;
    m_xSearchED->set_entry_text(rKeyword);
    Search();
    if (m_xResultsLB->n_children() > 0)
    {
        // found keyword -> open it
        m_xResultsLB->select(0);
        OpenHdl(*m_xOpenBtn);
        bRet = true;
    }
    return bRet;
}

// class BookmarksTabPage_Impl -------------------------------------------

void BookmarksTabPage_Impl::DoAction(std::u16string_view rAction)
{
    if (rAction == u"display")
        aDoubleClickHdl.Call(nullptr);
    else if (rAction == u"rename")
    {
        sal_Int32 nPos = m_xBookmarksBox->get_selected_index();
        if (nPos != -1)
        {
            SfxAddHelpBookmarkDialog_Impl aDlg(m_xBookmarksBox.get(), true);
            aDlg.SetTitle(m_xBookmarksBox->get_text(nPos));
            if (aDlg.run() == RET_OK)
            {
                OUString sURL = m_xBookmarksBox->get_id(nPos);
                m_xBookmarksBox->remove(nPos);
                m_xBookmarksBox->append(sURL, aDlg.GetTitle(),
                    SvFileInformationManager::GetImageId(INetURLObject(rtl::Concat2View(IMAGE_URL+INetURLObject(sURL).GetHost()))));
                m_xBookmarksBox->select(m_xBookmarksBox->n_children() - 1);
            }
        }
    }
    else if (rAction == u"delete")
    {
        sal_Int32 nPos = m_xBookmarksBox->get_selected_index();
        if (nPos != -1)
        {
            m_xBookmarksBox->remove(nPos);
            const sal_Int32 nCount = m_xBookmarksBox->n_children();
            if (nCount)
            {
                if (nPos >= nCount)
                    nPos = nCount - 1;
                m_xBookmarksBox->select(nPos);
            }
        }
    }
}

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

    std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(m_xBookmarksBox.get(), u"sfx/ui/bookmarkmenu.ui"_ustr));
    std::unique_ptr<weld::Menu> xMenu = xBuilder->weld_menu(u"menu"_ustr);

    OUString sIdent = xMenu->popup_at_rect(m_xBookmarksBox.get(), ::tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
    if (!sIdent.isEmpty())
        DoAction(sIdent);
    return true;
}

IMPL_LINK(BookmarksTabPage_Impl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    bool bHandled = false;
    sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
    if (KEY_DELETE == nCode && m_xBookmarksBox->n_children() > 0)
    {
        DoAction(u"delete");
        bHandled = true;
    }
    return bHandled;
}

// class BookmarksTabPage_Impl -------------------------------------------
BookmarksTabPage_Impl::BookmarksTabPage_Impl(weld::Widget* pParent, SfxHelpIndexWindow_Impl* _pIdxWin)
    : HelpTabPage_Impl(pParent, _pIdxWin, u"HelpBookmarkPage"_ustr,
        u"sfx/ui/helpbookmarkpage.ui"_ustr)
    , m_xBookmarksBox(m_xBuilder->weld_tree_view(u"bookmarks"_ustr))
    , m_xBookmarksPB(m_xBuilder->weld_button(u"display"_ustr))
{
    m_xBookmarksBox->set_size_request(m_xBookmarksBox->get_approximate_digit_width() * 30,
                                      m_xBookmarksBox->get_height_rows(20));

    m_xBookmarksPB->connect_clicked( LINK(this, BookmarksTabPage_Impl, OpenHdl));
    m_xBookmarksBox->connect_row_activated(LINK(this, BookmarksTabPage_Impl, DoubleClickHdl));
    m_xBookmarksBox->connect_popup_menu(LINK(this, BookmarksTabPage_Impl, CommandHdl));
    m_xBookmarksBox->connect_key_press(LINK(this, BookmarksTabPage_Impl, KeyInputHdl));

    // load bookmarks from configuration
    const std::vector< SvtHistoryOptions::HistoryItem > aBookmarkSeq = SvtHistoryOptions::GetList( EHistoryType::HelpBookmarks );
    for ( const auto& rBookmark : aBookmarkSeq )
    {
        AddBookmarks( rBookmark.sTitle, rBookmark.sURL );
    }
}

BookmarksTabPage_Impl::~BookmarksTabPage_Impl()
{
    // save bookmarks to configuration
    SvtHistoryOptions::Clear( EHistoryType::HelpBookmarks );
    const sal_Int32 nCount = m_xBookmarksBox->n_children();
    for (sal_Int32 i = 0; i < nCount; ++i)
    {
        SvtHistoryOptions::AppendItem(EHistoryType::HelpBookmarks, m_xBookmarksBox->get_id(i), u""_ustr,
                                      m_xBookmarksBox->get_text(i), std::nullopt, std::nullopt);
    }

    m_xBookmarksBox.reset();
    m_xBookmarksPB.reset();
}

IMPL_LINK_NOARG(BookmarksTabPage_Impl, OpenHdl, weld::Button&, void)
{
    aDoubleClickHdl.Call(nullptr);
}

IMPL_LINK_NOARG(BookmarksTabPage_Impl, DoubleClickHdl, weld::TreeView&, bool)
{
    aDoubleClickHdl.Call(nullptr);
    return true;
}

void BookmarksTabPage_Impl::SetDoubleClickHdl(const Link<LinkParamNone*, void>& rLink)
{
    aDoubleClickHdl = rLink;
}

OUString BookmarksTabPage_Impl::GetSelectedEntry() const
{
    return m_xBookmarksBox->get_selected_id();
}

void BookmarksTabPage_Impl::AddBookmarks(const OUString& rTitle, const OUString& rURL)
{
    const OUString aImageURL {IMAGE_URL + INetURLObject(rURL).GetHost()};
    m_xBookmarksBox->append(rURL, rTitle, SvFileInformationManager::GetImageId(INetURLObject(aImageURL)));
}

OUString SfxHelpWindow_Impl::buildHelpURL(std::u16string_view sFactory        ,
                                          std::u16string_view sContent        ,
                                          std::u16string_view sAnchor)
{
    OUStringBuffer sHelpURL(256);
    sHelpURL.append(HELP_URL + sFactory + sContent);
    AppendConfigToken(sHelpURL, true/*bUseQuestionMark*/);
    if (!sAnchor.empty())
        sHelpURL.append(sAnchor);
    return sHelpURL.makeStringAndClear();
}

void SfxHelpWindow_Impl::loadHelpContent(const OUString& sHelpURL, bool bAddToHistory)
{
    Reference< XComponentLoader > xLoader(getTextFrame(), UNO_QUERY);
    if (!xLoader.is())
        return;

    // If a print job runs do not open a new page
    Reference< XFrame2 >     xTextFrame      = pTextWin->getFrame();
    Reference< XController > xTextController ;
    if (xTextFrame.is())
        xTextController = xTextFrame->getController ();
    if ( xTextController.is() && !xTextController->suspend( true ) )
    {
        xTextController->suspend( false );
        return;
    }

    // save url to history
    if (bAddToHistory)
        pHelpInterceptor->addURL(sHelpURL);

    if ( !IsWait() )
        EnterWait();
    bool bSuccess = false;
// TODO implement locale fallback ... see below    while(true)
    {
        try
        {
            Reference< XComponent > xContent = xLoader->loadComponentFromURL(sHelpURL, u"_self"_ustr, 0, Sequence< PropertyValue >());
            if (xContent.is())
            {
                bSuccess = true;
            }
        }
        catch(const RuntimeException&)
            { throw; }
        catch(const Exception&)
            { /*break;*/ }

        /* TODO try next locale ...
                no further locale available? => break loop and show error page
        */
    }
    openDone(sHelpURL, bSuccess);
    if ( IsWait() )
        LeaveWait();
}

IMPL_LINK(SfxHelpIndexWindow_Impl, ActivatePageHdl, const OUString&, rPage, void)
{
    GetPage(rPage)->Activate();
}

SfxHelpIndexWindow_Impl::SfxHelpIndexWindow_Impl(SfxHelpWindow_Impl* _pParent, weld::Container* pContainer)
    : m_xBuilder(Application::CreateBuilder(pContainer, u"sfx/ui/helpcontrol.ui"_ustr))
    , m_xContainer(m_xBuilder->weld_container(u"HelpControl"_ustr))
    , m_xActiveLB(m_xBuilder->weld_combo_box(u"active"_ustr))
    , m_xTabCtrl(m_xBuilder->weld_notebook(u"tabcontrol"_ustr))
    , aIdle("sfx2 appl SfxHelpIndexWindow_Impl")
    , aIndexKeywordLink(LINK(this, SfxHelpIndexWindow_Impl, KeywordHdl))
    , pParentWin(_pParent)
    , bIsInitDone(false)
{
    // create the pages
    GetContentPage();
    GetIndexPage();
    GetSearchPage();
    GetBookmarksPage();

    OUString sPageId(u"index"_ustr);
    SvtViewOptions aViewOpt( EViewType::TabDialog, CONFIGNAME_INDEXWIN );
    if ( aViewOpt.Exists() )
    {
        OUString sSavedPageId = aViewOpt.GetPageID();
        if (m_xTabCtrl->get_page_index(sSavedPageId) != -1)
            sPageId = sSavedPageId;
    }
    m_xTabCtrl->set_current_page(sPageId);
    ActivatePageHdl(sPageId);
    m_xActiveLB->connect_changed(LINK(this, SfxHelpIndexWindow_Impl, SelectHdl));

    m_xTabCtrl->connect_enter_page(LINK(this, SfxHelpIndexWindow_Impl, ActivatePageHdl));

    aIdle.SetInvokeHandler( LINK( this, SfxHelpIndexWindow_Impl, InitHdl ) );
    aIdle.Start();

    m_xContainer->show();
}

SfxHelpIndexWindow_Impl::~SfxHelpIndexWindow_Impl()
{
    SvtViewOptions aViewOpt(EViewType::TabDialog, CONFIGNAME_INDEXWIN);
    aViewOpt.SetPageID(m_xTabCtrl->get_current_page_ident());

    xCPage.reset();
    xIPage.reset();
    xSPage.reset();
    xBPage.reset();
}

void SfxHelpIndexWindow_Impl::Initialize()
{
    OUStringBuffer aHelpURL(HELP_URL);
    AppendConfigToken(aHelpURL, true);
    std::vector<OUString> aFactories = SfxContentHelper::GetResultSet(aHelpURL.makeStringAndClear());
    for (const OUString & rRow : aFactories)
    {
        sal_Int32 nIdx = 0;
        OUString aTitle = rRow.getToken( 0, '\t', nIdx ); // token 0
        std::u16string_view aURL = o3tl::getToken(rRow, 1, '\t', nIdx ); // token 2
        OUString aFactory(INetURLObject(aURL).GetHost());
        m_xActiveLB->append(aFactory, aTitle);
    }

    if (m_xActiveLB->get_active() == -1)
        SetActiveFactory();
}

void SfxHelpIndexWindow_Impl::SetActiveFactory()
{
    DBG_ASSERT( xIPage, "index page not initialized" );
    if (!bIsInitDone && !m_xActiveLB->get_count())
    {
        aIdle.Stop();
        InitHdl( nullptr );
    }

    for (sal_Int32 i = 0, nEntryCount = m_xActiveLB->get_count(); i < nEntryCount; ++i)
    {
        OUString aFactory = m_xActiveLB->get_id(i);
        aFactory = aFactory.toAsciiLowerCase();
        if (aFactory == xIPage->GetFactory())
        {
            if (m_xActiveLB->get_active() != i)
            {
                m_xActiveLB->set_active(i);
                aSelectFactoryLink.Call(nullptr);
            }
            break;
        }
    }
}

HelpTabPage_Impl* SfxHelpIndexWindow_Impl::GetPage(std::u16string_view  rName)
{
    HelpTabPage_Impl* pPage = nullptr;

    if (rName == u"contents")
        pPage = GetContentPage();
    else if (rName == u"index")
        pPage = GetIndexPage();
    else if (rName == u"find")
        pPage = GetSearchPage();
    else if (rName == u"bookmarks")
        pPage = GetBookmarksPage();

    assert(pPage && "SfxHelpIndexWindow_Impl::GetCurrentPage(): no current page");

    return pPage;
}

IMPL_LINK_NOARG(SfxHelpIndexWindow_Impl, SelectHdl, weld::ComboBox&, void)
{
    aIdle.Start();
}

IMPL_LINK_NOARG(SfxHelpIndexWindow_Impl, InitHdl, Timer *, void)
{
    bIsInitDone = true;
    Initialize();

    // now use the timer for selection
    aIdle.SetInvokeHandler( LINK( this, SfxHelpIndexWindow_Impl, SelectFactoryHdl ) );
    aIdle.SetPriority( TaskPriority::LOWEST );
}

IMPL_LINK_NOARG(SfxHelpIndexWindow_Impl, SelectFactoryHdl, Timer *, void)
{
    OUString aFactory = m_xActiveLB->get_active_id();
    if (!aFactory.isEmpty())
    {
        SetFactory(aFactory.toAsciiLowerCase(), false);
        aSelectFactoryLink.Call(this);
    }
}

IMPL_LINK_NOARG(SfxHelpIndexWindow_Impl, KeywordHdl, IndexTabPage_Impl&, void)
{
    // keyword found on index?
    bool bIndex = xIPage->HasKeyword();

    if( !bIndex)
        bIndex = xIPage->HasKeywordIgnoreCase();
    // then set index or search page as current.
    OUString sPageId = bIndex ? u"index"_ustr : u"find"_ustr;
    if (sPageId != m_xTabCtrl->get_current_page_ident())
        m_xTabCtrl->set_current_page(sPageId);

    // at last we open the keyword
    if ( bIndex )
        xIPage->OpenKeyword();
    else if ( !xSPage->OpenKeyword( sKeyword ) )
        pParentWin->ShowStartPage();
}

IMPL_LINK_NOARG(SfxHelpIndexWindow_Impl, IndexTabPageDoubleClickHdl, LinkParamNone*, void)
{
    aPageDoubleClickLink.Call(nullptr);
}

void SfxHelpIndexWindow_Impl::SetDoubleClickHdl(const Link<LinkParamNone*, void>& rLink)
{
    aPageDoubleClickLink = rLink;
}

IMPL_LINK_NOARG(SfxHelpIndexWindow_Impl, ContentTabPageDoubleClickHdl, LinkParamNone*, void)
{
    aPageDoubleClickLink.Call(nullptr);
}

IMPL_LINK_NOARG(SfxHelpIndexWindow_Impl, TabPageDoubleClickHdl, LinkParamNone*, void)
{
    aPageDoubleClickLink.Call(nullptr);
}

void SfxHelpIndexWindow_Impl::SetFactory( const OUString& rFactory, bool bActive )
{
    if ( !rFactory.isEmpty() )
    {
        GetIndexPage()->SetFactory( rFactory );
        // the index page made a check if rFactory is valid,
        // so the index page always returns a valid factory
        GetSearchPage()->SetFactory( GetIndexPage()->GetFactory() );
        if ( bActive )
            SetActiveFactory();
    }
}

OUString SfxHelpIndexWindow_Impl::GetSelectedEntry() const
{
    OUString sRet;

    OUString sName(m_xTabCtrl->get_current_page_ident());

    if (sName == "contents")
    {
        sRet = xCPage->GetSelectedEntry();
    }
    else if (sName == "index")
    {
        sRet = xIPage->GetSelectedEntry();
    }
    else if (sName == "find")
    {
        sRet = xSPage->GetSelectedEntry();
    }
    else if (sName == "bookmarks")
    {
        sRet = xBPage->GetSelectedEntry();
    }

    return sRet;
}

void SfxHelpIndexWindow_Impl::AddBookmarks( const OUString& rTitle, const OUString& rURL )
{
    GetBookmarksPage()->AddBookmarks( rTitle, rURL );
}

bool SfxHelpIndexWindow_Impl::IsValidFactory( std::u16string_view _rFactory )
{
    bool bValid = false;
    for (sal_Int32 i = 0, nEntryCount = m_xActiveLB->get_count(); i < nEntryCount; ++i)
    {
        OUString aFactory = m_xActiveLB->get_id(i);
        if (aFactory == _rFactory)
        {
            bValid = true;
            break;
        }
    }
    return bValid;
}

void SfxHelpIndexWindow_Impl::ClearSearchPage()
{
    if ( xSPage )
        xSPage->ClearPage();
}

void SfxHelpIndexWindow_Impl::GrabFocusBack()
{
    OUString sName(m_xTabCtrl->get_current_page_ident());

    if (sName == "contents" && xCPage)
        xCPage->SetFocusOnBox();
    else if (sName == "index" && xIPage)
        xIPage->SetFocusOnBox();
    else if (sName == "find" && xSPage)
        xSPage->SetFocusOnBox();
    else if (sName == "bookmarks" && xBPage)
        xBPage->SetFocusOnBox();
}

bool SfxHelpIndexWindow_Impl::HasFocusOnEdit() const
{
    bool bRet = false;
    OUString sName(m_xTabCtrl->get_current_page_ident());
    if (sName == "index" && xIPage)
        bRet = xIPage->HasFocusOnEdit();
    else if (sName == "find" && xSPage)
        bRet = xSPage->HasFocusOnEdit();
    return bRet;
}

OUString SfxHelpIndexWindow_Impl::GetSearchText() const
{
    OUString sRet;
    OUString sName(m_xTabCtrl->get_current_page_ident());
    if (sName == "find" && xSPage)
        sRet = xSPage->GetSearchText();
    return sRet;
}

bool SfxHelpIndexWindow_Impl::IsFullWordSearch() const
{
    bool bRet = false;
    OUString sName(m_xTabCtrl->get_current_page_ident());
    if (sName == "find" && xSPage)
        bRet = xSPage->IsFullWordSearch();
    return bRet;
}

void SfxHelpIndexWindow_Impl::OpenKeyword( const OUString& rKeyword )
{
    sKeyword = rKeyword;
    DBG_ASSERT( xIPage, "invalid index page" );
    xIPage->SetKeyword( sKeyword );
}

void SfxHelpIndexWindow_Impl::SelectExecutableEntry()
{
    OUString sName(m_xTabCtrl->get_current_page_ident());
    if (sName == "index" && xIPage )
        xIPage->SelectExecutableEntry();
}

weld::Window* SfxHelpIndexWindow_Impl::GetFrameWeld() const
{
    return pParentWin->GetFrameWeld();
}

// class TextWin_Impl ----------------------------------------------------
TextWin_Impl::TextWin_Impl( vcl::Window* p ) : DockingWindow( p, 0 )
{
}

bool TextWin_Impl::EventNotify( NotifyEvent& rNEvt )
{
    if( ( rNEvt.GetType() == NotifyEventType::KEYINPUT ) && rNEvt.GetKeyEvent()->GetKeyCode().GetCode() == KEY_TAB )
        return GetParent()->EventNotify( rNEvt );
    else
        return DockingWindow::EventNotify( rNEvt );
}


// remove docking area acceptor from layoutmanager, so it will not layout anything further .-)
static void lcl_disableLayoutOfFrame(const Reference< XFrame2 >& xFrame)
{
    xFrame->setLayoutManager( Reference< XLayoutManager >() );
}

// class SfxHelpTextWindow_Impl ------------------------------------------

SfxHelpTextWindow_Impl::SfxHelpTextWindow_Impl(SfxHelpWindow_Impl* pHelpWin, weld::Builder& rBuilder, vcl::Window* pParent) :

    Window( pParent, WB_CLIPCHILDREN | WB_TABSTOP | WB_DIALOGCONTROL ),

    xToolBox            ( rBuilder.weld_toolbar(u"toolbar"_ustr) ),
    xOnStartupCB        ( rBuilder.weld_check_button(u"checkbutton"_ustr) ),
    xMenu               ( rBuilder.weld_menu(u"menu"_ustr) ),
    aSelectIdle         ( "sfx2 appl SfxHelpTextWindow_Impl Select" ),
    aIndexOnImage       ( BMP_HELP_TOOLBOX_INDEX_ON ),
    aIndexOffImage      ( BMP_HELP_TOOLBOX_INDEX_OFF ),
    aIndexOnText        ( SfxResId( STR_HELP_BUTTON_INDEX_ON ) ),
    aIndexOffText       ( SfxResId( STR_HELP_BUTTON_INDEX_OFF ) ),
    aOnStartupText      ( SfxResId( RID_HELP_ONSTARTUP_TEXT ) ),
    xHelpWin            ( pHelpWin ),
    pTextWin            ( VclPtr<TextWin_Impl>::Create( this ) ),
    bIsDebug            ( false ),
    bIsIndexOn          ( false ),
    bIsInClose          ( false ),
    bIsFullWordSearch   ( false )
{
    xFrame = Frame::create( ::comphelper::getProcessComponentContext() );
    xFrame->initialize( VCLUnoHelper::GetInterface ( pTextWin ) );
    xFrame->setName( u"OFFICE_HELP"_ustr );
    lcl_disableLayoutOfFrame(xFrame);

    xToolBox->set_help_id(HID_HELP_TOOLBOX);

    xToolBox->set_item_tooltip_text(u"index"_ustr, aIndexOffText );
    xToolBox->set_item_help_id(u"index"_ustr, HID_HELP_TOOLBOXITEM_INDEX);
    xToolBox->set_item_help_id(u"backward"_ustr, HID_HELP_TOOLBOXITEM_BACKWARD);
    xToolBox->set_item_help_id(u"forward"_ustr, HID_HELP_TOOLBOXITEM_FORWARD);
    xToolBox->set_item_help_id(u"start"_ustr, HID_HELP_TOOLBOXITEM_START);
    xToolBox->set_item_help_id(u"print"_ustr, HID_HELP_TOOLBOXITEM_PRINT);
    xToolBox->set_item_help_id(u"bookmarks"_ustr, HID_HELP_TOOLBOXITEM_BOOKMARKS );
    xToolBox->set_item_help_id(u"searchdialog"_ustr, HID_HELP_TOOLBOXITEM_SEARCHDIALOG);

    InitToolBoxImages();
    InitOnStartupBox();
    xOnStartupCB->connect_toggled(LINK(this, SfxHelpTextWindow_Impl, CheckHdl));

    aSelectIdle.SetInvokeHandler( LINK( this, SfxHelpTextWindow_Impl, SelectHdl ) );
    aSelectIdle.SetPriority( TaskPriority::LOWEST );

    char* pEnv = getenv( "help_debug" );
    if ( pEnv )
        bIsDebug = true;

    SvtMiscOptions().AddListenerLink( LINK( this, SfxHelpTextWindow_Impl, NotifyHdl ) );
}

SfxHelpTextWindow_Impl::~SfxHelpTextWindow_Impl()
{
    disposeOnce();
}

void SfxHelpTextWindow_Impl::dispose()
{
    bIsInClose = true;
    SvtMiscOptions().RemoveListenerLink( LINK( this, SfxHelpTextWindow_Impl, NotifyHdl ) );
    m_xSrchDlg.reset();
    xToolBox.reset();
    xOnStartupCB.reset();
    xHelpWin.reset();
    pTextWin.disposeAndClear();
    vcl::Window::dispose();
}

bool SfxHelpTextWindow_Impl::HasSelection() const
{
    // is there any selection in the text and not only a cursor?
    bool bRet = false;
    Reference < XTextRange > xRange = getCursor();
    if ( xRange.is() )
    {
        Reference < XText > xText = xRange->getText();
        Reference < XTextCursor > xCursor = xText->createTextCursorByRange( xRange );
        bRet = !xCursor->isCollapsed();
    }

    return bRet;
}

void SfxHelpTextWindow_Impl::InitToolBoxImages()
{
    xToolBox->set_item_icon_name(u"index"_ustr, bIsIndexOn ? aIndexOffImage : aIndexOnImage);
}

void SfxHelpTextWindow_Impl::InitOnStartupBox()
{
    sCurrentFactory = SfxHelp::GetCurrentModuleIdentifier();

    const Reference< XComponentContext >& xContext = ::comphelper::getProcessComponentContext();
    const OUString sPath { PATH_OFFICE_FACTORIES + sCurrentFactory };

    // Attention: This check boy knows two states:
    // 1) Reading of the config key fails with an exception or by getting an empty Any (!) => check box must be hidden
    // 2) We read sal_True/sal_False => check box must be shown and enabled/disabled

    bool bHideBox = true;
    bool bHelpAtStartup = false;
    try
    {
        xConfiguration = ConfigurationHelper::openConfig(
            xContext, PACKAGE_SETUP, EConfigurationModes::Standard );
        if ( xConfiguration.is() )
        {
            Any aAny = ConfigurationHelper::readRelativeKey( xConfiguration, sPath, KEY_HELP_ON_OPEN );
            if (aAny >>= bHelpAtStartup)
                bHideBox = false;
        }
    }
    catch( Exception& )
    {
        bHideBox = true;
    }

    if ( bHideBox )
        xOnStartupCB->hide();
    else
    {
        // detect module name
        OUString sModuleName;

        if ( xConfiguration.is() )
        {
            OUString sTemp;
            try
            {
                Any aAny = ConfigurationHelper::readRelativeKey( xConfiguration, sPath, KEY_UI_NAME );
                aAny >>= sTemp;
            }
            catch( Exception const & )
            {
                TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpTextWindow_Impl::InitOnStartupBox()" );
            }
            sModuleName = sTemp;
        }

        if ( !sModuleName.isEmpty() )
        {
            // set module name in checkbox text
            xOnStartupCB->set_label(aOnStartupText.replaceFirst("%MODULENAME", sModuleName));
            // and show it
            xOnStartupCB->show();
            // set check state
            xOnStartupCB->set_active(bHelpAtStartup);
            xOnStartupCB->save_state();
        }
    }
}

Reference< XBreakIterator > const & SfxHelpTextWindow_Impl::GetBreakIterator()
{
    if ( !xBreakIterator.is() )
        xBreakIterator = vcl::unohelper::CreateBreakIterator();
    DBG_ASSERT( xBreakIterator.is(), "Could not create BreakIterator" );
    return xBreakIterator;
}

Reference< XTextRange > SfxHelpTextWindow_Impl::getCursor() const
{
    // return the current cursor
    Reference< XTextRange > xCursor;

    try
    {
        Reference < XSelectionSupplier > xSelSup( xFrame->getController(), UNO_QUERY );
        if ( xSelSup.is() )
        {
            Any aAny = xSelSup->getSelection();
            Reference < XIndexAccess > xSelection;
            if ( aAny >>= xSelection )
            {
                if ( xSelection->getCount() == 1 )
                {
                    aAny = xSelection->getByIndex(0);
                    aAny >>= xCursor;
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpTextWindow_Impl::getCursor(): unexpected exception" );
    }

    return xCursor;
}


bool SfxHelpTextWindow_Impl::isHandledKey( const vcl::KeyCode& _rKeyCode )
{
    bool bRet = false;
    sal_uInt16 nCode = _rKeyCode.GetCode();

    // the keys <CTRL><A> (select all), <CTRL><C> (copy),
    //          <CTRL><F> (find), <CTRL><P> (print) and <CTRL><W> (close window)
    // were handled in help
    if ( _rKeyCode.IsMod1() &&
         ( KEY_A == nCode || KEY_C == nCode || KEY_F == nCode || KEY_P == nCode || KEY_W == nCode ) )
    {
        if ( KEY_F == nCode )
            DoSearch();
        else
            bRet = true;
    }

    return bRet;
}


IMPL_LINK_NOARG(SfxHelpTextWindow_Impl, SelectHdl, Timer *, void)
{
    try
    {
        // select the words, which are equal to the search text of the search page
        Reference < XController > xController = xFrame->getController();
        if ( xController.is() )
        {
            // get document
            Reference < XSearchable > xSearchable( xController->getModel(), UNO_QUERY );
            if ( xSearchable.is() )
            {
                // create descriptor, set string and find all words
                Reference < XSearchDescriptor > xSrchDesc = xSearchable->createSearchDescriptor();
                xSrchDesc->setPropertyValue( u"SearchRegularExpression"_ustr, Any( true ) );
                if ( bIsFullWordSearch )
                    xSrchDesc->setPropertyValue( u"SearchWords"_ustr, Any( true ) );

                xSrchDesc->setSearchString( sfx2::PrepareSearchString( aSearchText, GetBreakIterator(), false ) );
                Reference< XIndexAccess > xSelection = xSearchable->findAll( xSrchDesc );

                // then select all found words
                Reference < XSelectionSupplier > xSelectionSup( xController, UNO_QUERY );
                if ( xSelectionSup.is() )
                {
                    xSelectionSup->select( Any(xSelection) );
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpTextWindow_Impl::SelectHdl(): unexpected exception" );
    }
}


IMPL_LINK_NOARG( SfxHelpTextWindow_Impl, NotifyHdl, LinkParamNone*, void )
{
    InitToolBoxImages();
    Resize();
}

IMPL_LINK( SfxHelpTextWindow_Impl, FindHdl, sfx2::SearchDialog&, rDlg, void )
{
    FindHdl(&rDlg);
}
void SfxHelpTextWindow_Impl::FindHdl(sfx2::SearchDialog* pDlg)
{
    try
    {
        // select the words, which are equal to the search text of the search page
        Reference < XController > xController = xFrame->getController();
        if ( xController.is() )
        {
            // get document
            Reference < XSearchable > xSearchable( xController->getModel(), UNO_QUERY );
            if ( xSearchable.is() )
            {
                bool bWrapAround = ( nullptr == pDlg );
                if ( bWrapAround )
                    pDlg = m_xSrchDlg.get();
                assert(pDlg && "invalid search dialog");

                // create descriptor, set string and find all words
                Reference < XSearchDescriptor > xSrchDesc = xSearchable->createSearchDescriptor();
                xSrchDesc->setPropertyValue( u"SearchWords"_ustr, Any(pDlg->IsOnlyWholeWords()) );
                xSrchDesc->setPropertyValue( u"SearchCaseSensitive"_ustr, Any(pDlg->IsMarchCase()) );
                xSrchDesc->setPropertyValue( u"SearchBackwards"_ustr, Any(pDlg->IsSearchBackwards()) );
                xSrchDesc->setSearchString( pDlg->GetSearchText() );
                Reference< XInterface > xSelection;
                Reference< XTextRange > xCursor = getCursor();

                if ( xCursor.is() )
                {
                    if ( pDlg->IsSearchBackwards() )
                        xCursor = xCursor->getStart();
                    xSelection = xSearchable->findNext( xCursor, xSrchDesc );
                }
                else
                    xSelection = xSearchable->findFirst( xSrchDesc );

                // then select the found word
                if ( xSelection.is() )
                {
                    Reference < XSelectionSupplier > xSelectionSup( xController, UNO_QUERY );
                    if ( xSelectionSup.is() )
                    {
                        xSelectionSup->select( Any(xSelection) );
                    }
                }
                else if ( pDlg->IsWrapAround() && !bWrapAround )
                {
                    Reference < text::XTextViewCursorSupplier > xCrsrSupp( xController, uno::UNO_QUERY );
                    Reference < text::XTextViewCursor > xTVCrsr = xCrsrSupp->getViewCursor();
                    if ( xTVCrsr.is() )
                    {
                        Reference < text::XTextDocument > xDoc( xController->getModel(), uno::UNO_QUERY );
                        Reference < text::XText > xText = xDoc->getText();
                        if ( xText.is() )
                        {
                            if ( pDlg->IsSearchBackwards() )
                                xTVCrsr->gotoRange( xText->getEnd(), false );
                            else
                                xTVCrsr->gotoRange( xText->getStart(), false );
                            FindHdl( nullptr );
                        }
                    }
                }
                else
                {
                    assert(m_xSrchDlg && "no search dialog");
                    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xSrchDlg->getDialog(),
                                                              VclMessageType::Info, VclButtonsType::Ok, SfxResId(STR_INFO_NOSEARCHTEXTFOUND)));
                    xBox->run();
                    m_xSrchDlg->SetFocusOnEdit();
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpTextWindow_Impl::SelectHdl(): unexpected exception" );
    }
}

IMPL_LINK_NOARG(SfxHelpTextWindow_Impl, CloseHdl, LinkParamNone*, void)
{
    m_xSrchDlg.reset();
}

IMPL_LINK_NOARG(SfxHelpTextWindow_Impl, CheckHdl, weld::Toggleable&, void)
{
    if ( !xConfiguration.is() )
        return;

    bool bChecked = xOnStartupCB->get_active();
    try
    {
        ConfigurationHelper::writeRelativeKey(
            xConfiguration, PATH_OFFICE_FACTORIES + sCurrentFactory, KEY_HELP_ON_OPEN, Any( bChecked ) );
        ConfigurationHelper::flush( xConfiguration );
    }
    catch( Exception const & )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpTextWindow_Impl::CheckHdl()" );
    }
}

void SfxHelpTextWindow_Impl::Resize()
{
    Size aSize = GetOutputSizePixel();
    pTextWin->SetPosSizePixel( Point(0, 0), aSize );
}

bool SfxHelpTextWindow_Impl::PreNotify( NotifyEvent& rNEvt )
{
    bool bDone = false;
    NotifyEventType nType = rNEvt.GetType();
    if ( NotifyEventType::COMMAND == nType && rNEvt.GetCommandEvent() )
    {
        const CommandEvent* pCmdEvt = rNEvt.GetCommandEvent();
        vcl::Window* pCmdWin = rNEvt.GetWindow();

        if ( pCmdEvt->GetCommand() == CommandEventId::ContextMenu && pCmdWin != this )
        {
            Point aPos;
            if ( pCmdEvt->IsMouseEvent() )
                aPos = pCmdEvt->GetMousePosPixel();
            else
                aPos = Point( pTextWin->GetPosPixel().X() + 20, 20 );

            xMenu->clear();

            if (bIsIndexOn)
                xMenu->append(u"index"_ustr, aIndexOffText, BMP_HELP_TOOLBOX_INDEX_OFF);
            else
                xMenu->append(u"index"_ustr, aIndexOnText, BMP_HELP_TOOLBOX_INDEX_ON);

            xMenu->append_separator(u"separator1"_ustr);
            xMenu->append(u"backward"_ustr, SfxResId(STR_HELP_BUTTON_PREV), BMP_HELP_TOOLBOX_PREV);
            xMenu->set_sensitive(u"backward"_ustr, xHelpWin->HasHistoryPredecessor());
            xMenu->append(u"forward"_ustr, SfxResId(STR_HELP_BUTTON_NEXT), BMP_HELP_TOOLBOX_NEXT);
            xMenu->set_sensitive(u"forward"_ustr, xHelpWin->HasHistorySuccessor());
            xMenu->append(u"start"_ustr, SfxResId(STR_HELP_BUTTON_START), BMP_HELP_TOOLBOX_START);
            xMenu->append_separator(u"separator2"_ustr);
            xMenu->append(u"print"_ustr, SfxResId(STR_HELP_BUTTON_PRINT), BMP_HELP_TOOLBOX_PRINT);
            xMenu->append(u"bookmarks"_ustr, SfxResId(STR_HELP_BUTTON_ADDBOOKMARK), BMP_HELP_TOOLBOX_BOOKMARKS);
            xMenu->append(u"searchdialog"_ustr, SfxResId(STR_HELP_BUTTON_SEARCHDIALOG), BMP_HELP_TOOLBOX_SEARCHDIALOG);
            xMenu->append_separator(u"separator3"_ustr);
            xMenu->append_check(u"selectionmode"_ustr, SfxResId(STR_HELP_MENU_TEXT_SELECTION_MODE));
            URL aURL;
            aURL.Complete = ".uno:SelectTextMode";
            Reference< util::XURLTransformer > xTrans( util::URLTransformer::create( ::comphelper::getProcessComponentContext() ) );
            xTrans->parseStrict(aURL);
            Reference < XDispatch > xDisp = xFrame->queryDispatch( aURL, OUString(), 0 );
            if(xDisp.is())
            {
                rtl::Reference<HelpStatusListener_Impl> pStateListener =
                                        new HelpStatusListener_Impl(xDisp, aURL );
                FeatureStateEvent rEvent = pStateListener->GetStateEvent();
                bool bCheck = false;
                rEvent.State >>= bCheck;
                xMenu->set_active(u"selectionmode"_ustr, bCheck);
            }
            xMenu->append_separator(u"separator4"_ustr);
            xMenu->append(u"copy"_ustr, SfxResId(STR_HELP_MENU_TEXT_COPY), BMP_HELP_TOOLBOX_COPY);
            xMenu->set_sensitive(u"copy"_ustr, HasSelection());

            if ( bIsDebug )
            {
                xMenu->append_separator(u"separator5"_ustr);
                xMenu->append(u"sourceview"_ustr, SfxResId(STR_HELP_BUTTON_SOURCEVIEW));
            }

            int x, y, width, height;
            weld::Window* pTopLevel = GetFrameWeld();
            xHelpWin->GetContainer()->get_extents_relative_to(*pTopLevel, x, y, width, height);
            aPos.AdjustX(x);
            aPos.AdjustY(y);

            xHelpWin->DoAction(xMenu->popup_at_rect(pTopLevel, tools::Rectangle(aPos, Size(1,1))));
            bDone = true;
        }
    }
    else if ( NotifyEventType::KEYINPUT == nType && rNEvt.GetKeyEvent() )
    {
        const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
        const vcl::KeyCode& rKeyCode = pKEvt->GetKeyCode();
        sal_uInt16 nKeyGroup = rKeyCode.GetGroup();
        sal_uInt16 nKey = rKeyCode.GetCode();
        if ( KEYGROUP_ALPHA == nKeyGroup &&  !isHandledKey( rKeyCode ) )
        {
            // do nothing disables the writer accelerators
            bDone = true;
        }
        else if ( rKeyCode.IsMod1() && ( KEY_F4 == nKey || KEY_W == nKey ) )
        {
            // <CTRL><F4> or <CTRL><W> -> close top frame
            xHelpWin->CloseWindow();
            bDone = true;
        }
        else if ( KEY_TAB == nKey && xOnStartupCB->has_focus() )
        {
            xToolBox->grab_focus();
            bDone = true;
        }
    }

    return bDone || Window::PreNotify( rNEvt );
}


void SfxHelpTextWindow_Impl::GetFocus()
{
    if ( bIsInClose )
        return;

    try
    {
        if( xFrame.is() )
        {
            Reference< css::awt::XWindow > xWindow = xFrame->getComponentWindow();
            if( xWindow.is() )
                xWindow->setFocus();
        }
    }
    catch( Exception const & )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpTextWindow_Impl::GetFocus()" );
    }
}


void SfxHelpTextWindow_Impl::DataChanged( const DataChangedEvent& rDCEvt )
{
    Window::DataChanged( rDCEvt );

    if ( ( ( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
           ( rDCEvt.GetType() == DataChangedEventType::DISPLAY ) ) &&
         ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ) )
    {
        SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFaceColor() ) );
        InitToolBoxImages();
    }
}

void SfxHelpTextWindow_Impl::ToggleIndex( bool bOn )
{
    bIsIndexOn = bOn;
    if ( bIsIndexOn )
    {
        xToolBox->set_item_icon_name(u"index"_ustr, aIndexOffImage);
        xToolBox->set_item_tooltip_text(u"index"_ustr, aIndexOffText);
    }
    else
    {
        xToolBox->set_item_icon_name(u"index"_ustr, aIndexOnImage);
        xToolBox->set_item_tooltip_text(u"index"_ustr, aIndexOnText);
    }
}

void SfxHelpTextWindow_Impl::SelectSearchText( const OUString& rSearchText, bool _bIsFullWordSearch )
{
    aSearchText = rSearchText;
    bIsFullWordSearch = _bIsFullWordSearch;
    aSelectIdle.Start();
}


void SfxHelpTextWindow_Impl::SetPageStyleHeaderOff() const
{
    bool bSetOff = false;
    // set off the pagestyle header to prevent print output of the help URL
    try
    {
        Reference < XController > xController = xFrame->getController();
        Reference < XSelectionSupplier > xSelSup( xController, UNO_QUERY );
        if ( xSelSup.is() )
        {
            Reference < XIndexAccess > xSelection;
            if ( xSelSup->getSelection() >>= xSelection )
            {
                Reference < XTextRange > xRange;
                if ( xSelection->getByIndex(0) >>= xRange )
                {
                    Reference < XText > xText = xRange->getText();
                    Reference < XPropertySet > xProps( xText->createTextCursorByRange( xRange ), UNO_QUERY );
                    OUString sStyleName;
                    if ( xProps->getPropertyValue( u"PageStyleName"_ustr ) >>= sStyleName )
                    {
                        Reference < XStyleFamiliesSupplier > xStyles( xController->getModel(), UNO_QUERY );
                        Reference < XNameContainer > xContainer;
                        if ( xStyles->getStyleFamilies()->getByName( u"PageStyles"_ustr )
                             >>= xContainer )
                        {
                            Reference < XStyle > xStyle;
                            if ( xContainer->getByName( sStyleName ) >>= xStyle )
                            {
                                Reference < XPropertySet > xPropSet( xStyle, UNO_QUERY );
                                xPropSet->setPropertyValue( u"HeaderIsOn"_ustr,  Any( false ) );

                                Reference< XModifiable > xReset(xStyles, UNO_QUERY);
                                xReset->setModified(false);
                                bSetOff = true;
                            }
                        }
                    }
                }
            }
        }
    }
    catch( Exception const & )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpTextWindow_Impl::SetPageStyleHeaderOff()" );
    }

    SAL_WARN_IF( !bSetOff, "sfx.appl", "SfxHelpTextWindow_Impl::SetPageStyleHeaderOff(): set off failed" );
}


void SfxHelpTextWindow_Impl::CloseFrame()
{
    bIsInClose = true;
    try
    {
        css::uno::Reference< css::util::XCloseable > xCloseable  ( xFrame, css::uno::UNO_QUERY );
        if (xCloseable.is())
            xCloseable->close(true);
    }
    catch( css::util::CloseVetoException& )
    {
    }
}


void SfxHelpTextWindow_Impl::DoSearch()
{
    if (m_xSrchDlg)
        return;

    // create the search dialog
    m_xSrchDlg = std::make_shared<sfx2::SearchDialog>(pTextWin->GetFrameWeld(), "HelpSearchDialog");
    // set handler
    m_xSrchDlg->SetFindHdl( LINK( this, SfxHelpTextWindow_Impl, FindHdl ) );
    m_xSrchDlg->SetCloseHdl( LINK( this, SfxHelpTextWindow_Impl, CloseHdl ) );
    // get selected text of the help page to set it as the search text
    Reference< XTextRange > xCursor = getCursor();
    if ( xCursor.is() )
    {
        OUString sText = xCursor->getString();
        if ( !sText.isEmpty() )
            m_xSrchDlg->SetSearchText( sText );
    }
    sfx2::SearchDialog::runAsync(m_xSrchDlg);
}

void SfxHelpWindow_Impl::GetFocus()
{
    if (pTextWin)
        pTextWin->GrabFocus();
    else
        ResizableDockingWindow::GetFocus();
}

void SfxHelpWindow_Impl::MakeLayout()
{
    Split();

    m_xHelpPaneWindow->set_visible(bIndex);
}

IMPL_LINK(SfxHelpWindow_Impl, ResizeHdl, const Size&, rSize, void)
{
    int nNewWidth = rSize.Width();
    if (!nNewWidth)
        return;
    if (bSplit)
        nIndexSize = round(m_xContainer->get_position() * 100.0 / nNewWidth);
    nWidth = nNewWidth;
    Split();
    nIndexSize = round(m_xContainer->get_position() * 100.0 / nWidth);
}

void SfxHelpWindow_Impl::Split()
{
    if (!nWidth)
        return;
    m_xContainer->set_position(nWidth * nIndexSize / 100);
    bSplit = true;
}

void SfxHelpWindow_Impl::LoadConfig()
{
    SvtViewOptions aViewOpt( EViewType::Window, CONFIGNAME_HELPWIN );
    if ( !aViewOpt.Exists() )
        return;
    bIndex = aViewOpt.IsVisible();

    Any aUserItem = aViewOpt.GetUserItem( USERITEM_NAME );
    OUString aUserData;
    if ( aUserItem >>= aUserData )
    {
        DBG_ASSERT( comphelper::string::getTokenCount(aUserData, ';') == 6, "invalid user data" );
        sal_Int32 nIdx = 0;
        nIndexSize = o3tl::toInt32(o3tl::getToken(aUserData, 0, ';', nIdx ));
        o3tl::getToken(aUserData, 0, ';', nIdx); // ignore nTextSize
        sal_Int32 nOldWidth = o3tl::toInt32(o3tl::getToken(aUserData, 0, ';', nIdx ));
        sal_Int32 nOldHeight = o3tl::toInt32(o3tl::getToken(aUserData, 0, ';', nIdx ));
        aWinSize = Size(nOldWidth, nOldHeight);
        aWinPos.setX( o3tl::toInt32(o3tl::getToken(aUserData, 0, ';', nIdx )) );
        aWinPos.setY( o3tl::toInt32(o3tl::getToken(aUserData, 0, ';', nIdx )) );
    }

    pTextWin->ToggleIndex( bIndex );
}

void SfxHelpWindow_Impl::SaveConfig()
{
    SvtViewOptions aViewOpt( EViewType::Window, CONFIGNAME_HELPWIN );
    sal_Int32 nW = 0, nH = 0;

    if ( xWindow.is() )
    {
        css::awt::Rectangle aRect = xWindow->getPosSize();
        nW = aRect.Width;
        nH = aRect.Height;
    }

    aViewOpt.SetVisible( bIndex );
    VclPtr<vcl::Window> pScreenWin = VCLUnoHelper::GetWindow( xWindow );
    aWinPos = pScreenWin->GetWindowExtentsAbsolute().TopLeft();
    if (bSplit)
        nIndexSize = round(m_xContainer->get_position() * 100.0 / nWidth);
    const OUString aUserData = OUString::number( nIndexSize )
        + ";" + OUString::number( 100 - nIndexSize )
        + ";" + OUString::number( nW )
        + ";" + OUString::number( nH )
        + ";" + OUString::number( aWinPos.X() )
        + ";" + OUString::number( aWinPos.Y() );

    aViewOpt.SetUserItem( USERITEM_NAME, Any( aUserData ) );
}

void SfxHelpWindow_Impl::ShowStartPage()
{
    loadHelpContent(SfxHelpWindow_Impl::buildHelpURL(xIndexWin->GetFactory(), u"/start", u""));
}

IMPL_LINK(SfxHelpWindow_Impl, SelectHdl, const OUString&, rCurItem, void)
{
    bGrabFocusToToolBox = pTextWin->GetToolBox().has_focus();
    DoAction(rCurItem);
}

IMPL_LINK_NOARG(SfxHelpWindow_Impl, OpenHdl, LinkParamNone*, void)
{
    xIndexWin->SelectExecutableEntry();
    OUString aEntry = xIndexWin->GetSelectedEntry();

    if ( aEntry.isEmpty() )
        return;

    OUString sHelpURL;

    bool bComplete = aEntry.toAsciiLowerCase().match("vnd.sun.star.help");

    if (bComplete)
        sHelpURL = aEntry;
    else
    {
        std::u16string_view aId;
        OUString aAnchor('#');
        if ( comphelper::string::getTokenCount(aEntry, '#') == 2 )
        {
            sal_Int32 nIdx{ 0 };
            aId = o3tl::getToken(aEntry, 0, '#', nIdx );
            aAnchor += o3tl::getToken(aEntry, 0, '#', nIdx );
        }
        else
            aId = aEntry;

        sHelpURL = SfxHelpWindow_Impl::buildHelpURL(xIndexWin->GetFactory(), Concat2View(OUString::Concat("/") + aId), aAnchor);
    }

    loadHelpContent(sHelpURL);
}

IMPL_LINK( SfxHelpWindow_Impl, SelectFactoryHdl, SfxHelpIndexWindow_Impl* , pWin, void )
{
    if ( sTitle.isEmpty() )
        sTitle = GetParent()->GetText();

    Reference< XTitle > xTitle(xFrame, UNO_QUERY);
    if (xTitle.is ())
        xTitle->setTitle(sTitle + " - " + xIndexWin->GetActiveFactoryTitle());

    if ( pWin )
        ShowStartPage();
    xIndexWin->ClearSearchPage();
}


IMPL_LINK( SfxHelpWindow_Impl, ChangeHdl, HelpListener_Impl&, rListener, void )
{
    SetFactory( rListener.GetFactory() );
}


void SfxHelpWindow_Impl::openDone(std::u16string_view sURL    ,
                                        bool         bSuccess)
{
    INetURLObject aObj( sURL );
    if ( aObj.GetProtocol() == INetProtocol::VndSunStarHelp )
        SetFactory( aObj.GetHost() );
    if ( IsWait() )
        LeaveWait();
    if ( bGrabFocusToToolBox )
    {
        pTextWin->GetToolBox().grab_focus();
        bGrabFocusToToolBox = false;
    }
    else
        xIndexWin->GrabFocusBack();
    if ( !bSuccess )
        return;

    // set some view settings: "prevent help tips" and "helpid == 68245"
    try
    {
        Reference < XController > xController = pTextWin->getFrame()->getController();
        if ( xController.is() )
        {
            Reference < XViewSettingsSupplier > xSettings( xController, UNO_QUERY );
            Reference < XPropertySet > xViewProps = xSettings->getViewSettings();
            Reference< XPropertySetInfo > xInfo = xViewProps->getPropertySetInfo();
            xViewProps->setPropertyValue( u"ShowContentTips"_ustr, Any( false ) );
            xViewProps->setPropertyValue( u"ShowGraphics"_ustr, Any( true ) );
            xViewProps->setPropertyValue( u"ShowTables"_ustr, Any( true ) );
            xViewProps->setPropertyValue( u"HelpURL"_ustr, Any( u"HID:SFX2_HID_HELP_ONHELP"_ustr ) );
            OUString sProperty( u"IsExecuteHyperlinks"_ustr );
            if ( xInfo->hasPropertyByName( sProperty ) )
                xViewProps->setPropertyValue( sProperty, Any( true ) );
            xController->restoreViewData(Any());
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpWindow_Impl::OpenDoneHdl(): unexpected exception" );
    }

    // When the SearchPage opens the help doc, then select all words, which are equal to its text
    OUString sSearchText = comphelper::string::strip(xIndexWin->GetSearchText(), ' ');
    if ( !sSearchText.isEmpty() )
        pTextWin->SelectSearchText( sSearchText, xIndexWin->IsFullWordSearch() );

    // no page style header -> this prevents a print output of the URL
    pTextWin->SetPageStyleHeaderOff();
}


SfxHelpWindow_Impl::SfxHelpWindow_Impl(
    const css::uno::Reference < css::frame::XFrame2 >& rFrame,
    vcl::Window* pParent ) :

    ResizableDockingWindow(pParent),

    xFrame              ( rFrame ),
    pTextWin            ( nullptr ),
    pHelpInterceptor    ( new HelpInterceptor_Impl() ),
    pHelpListener       ( new HelpListener_Impl( pHelpInterceptor ) ),
    bIndex              ( true ),
    bGrabFocusToToolBox ( false ),
    bSplit              ( false ),
    nWidth              ( 0 ),
    nIndexSize          ( 40 ), // % of width
    aWinPos             ( 0, 0 ),
    aWinSize            ( 0, 0 ),
    sTitle              ( pParent->GetText() )
{
    SetStyle(GetStyle() & ~WB_DOCKABLE);

    SetHelpId( HID_HELP_WINDOW );

    m_xBuilder = Application::CreateInterimBuilder(m_xBox.get(), u"sfx/ui/helpwindow.ui"_ustr, false);
    m_xContainer = m_xBuilder->weld_paned(u"HelpWindow"_ustr);
    m_xContainer->connect_size_allocate(LINK(this, SfxHelpWindow_Impl, ResizeHdl));
    m_xHelpPaneWindow = m_xBuilder->weld_container(u"helppanewindow"_ustr);
    m_xHelpTextWindow = m_xBuilder->weld_container(u"helptextwindow"_ustr);
    m_xHelpTextXWindow = m_xHelpTextWindow->CreateChildFrame();

    pHelpInterceptor->InitWaiter( this );
    xIndexWin.reset(new SfxHelpIndexWindow_Impl(this, m_xHelpPaneWindow.get()));
    xIndexWin->SetDoubleClickHdl( LINK( this, SfxHelpWindow_Impl, OpenHdl ) );
    xIndexWin->SetSelectFactoryHdl( LINK( this, SfxHelpWindow_Impl, SelectFactoryHdl ) );

    pTextWin = VclPtr<SfxHelpTextWindow_Impl>::Create(this, *m_xBuilder, VCLUnoHelper::GetWindow(m_xHelpTextXWindow));
    Reference < XFrames > xFrames = rFrame->getFrames();
    xFrames->append( Reference<XFrame>(pTextWin->getFrame(), UNO_QUERY_THROW) );
    pTextWin->SetSelectHdl( LINK( this, SfxHelpWindow_Impl, SelectHdl ) );
    pTextWin->Show();
    pHelpInterceptor->setInterception( Reference<XFrame>(pTextWin->getFrame(), UNO_QUERY_THROW) );
    pHelpListener->SetChangeHdl( LINK( this, SfxHelpWindow_Impl, ChangeHdl ) );
    LoadConfig();
}

SfxHelpWindow_Impl::~SfxHelpWindow_Impl()
{
    disposeOnce();
}

void SfxHelpWindow_Impl::dispose()
{
    SaveConfig();
    xIndexWin.reset();
    pTextWin->CloseFrame();
    pTextWin.disposeAndClear();

    m_xHelpTextXWindow->dispose();
    m_xHelpTextXWindow.clear();
    m_xHelpTextWindow.reset();
    m_xHelpPaneWindow.reset();
    m_xContainer.reset();
    m_xBuilder.reset();

    ResizableDockingWindow::dispose();
}

bool SfxHelpWindow_Impl::PreNotify( NotifyEvent& rNEvt )
{
    bool bHandled = false;
    if ( rNEvt.GetType() == NotifyEventType::KEYINPUT )
    {
        // Backward == <ALT><LEFT> or <BACKSPACE> Forward == <ALT><RIGHT>
        const vcl::KeyCode& rKeyCode = rNEvt.GetKeyEvent()->GetKeyCode();
        sal_uInt16 nKey = rKeyCode.GetCode();
        if ( ( rKeyCode.IsMod2() && ( KEY_LEFT == nKey || KEY_RIGHT == nKey ) ) ||
             ( !rKeyCode.GetModifier() && KEY_BACKSPACE == nKey && !xIndexWin->HasFocusOnEdit() ) )
        {
            DoAction( rKeyCode.GetCode() == KEY_RIGHT ? u"forward" : u"backward" );
            bHandled = true;
        }
        else if ( rKeyCode.IsMod1() && ( KEY_F4 == nKey || KEY_W == nKey ) )
        {
            // <CTRL><F4> or <CTRL><W> -> close top frame
            CloseWindow();
            bHandled = true;
        }
    }
    return bHandled || Window::PreNotify( rNEvt );
}

void SfxHelpWindow_Impl::setContainerWindow( const Reference < css::awt::XWindow >& xWin )
{
    xWindow = xWin;
    MakeLayout();
    if (xWindow.is())
    {
        VclPtr<vcl::Window> pScreenWin = VCLUnoHelper::GetWindow(xWindow);
        if (aWinSize.Width() && aWinSize.Height())
            pScreenWin->SetPosSizePixel(Point(aWinPos), aWinSize);
        else
            pScreenWin->SetPosPixel(Point(aWinPos));
    }
}

void SfxHelpWindow_Impl::SetFactory( const OUString& rFactory )
{
    xIndexWin->SetFactory( rFactory, true );
}

void SfxHelpWindow_Impl::SetHelpURL( std::u16string_view rURL )
{
    INetURLObject aObj( rURL );
    if ( aObj.GetProtocol() == INetProtocol::VndSunStarHelp )
        SetFactory( aObj.GetHost() );
}

void SfxHelpWindow_Impl::DoAction(std::u16string_view  rActionId)
{
    if (rActionId == u"index")
    {
        bIndex = !bIndex;
        MakeLayout();
        pTextWin->ToggleIndex( bIndex );
    }
    else if (rActionId == u"start")
    {
        ShowStartPage();
    }
    else if (rActionId == u"backward" || rActionId == u"forward")
    {
        URL aURL;
        aURL.Complete = ".uno:Backward";
        if (rActionId == u"forward")
            aURL.Complete = ".uno:Forward";
        Reference< util::XURLTransformer > xTrans( util::URLTransformer::create( ::comphelper::getProcessComponentContext() ) );
        xTrans->parseStrict(aURL);
        pHelpInterceptor->dispatch( aURL, Sequence < PropertyValue >() );
    }
    else if (rActionId == u"searchdialog")
    {
        pTextWin->DoSearch();
    }
    else if (rActionId == u"print" || rActionId == u"sourceview" || rActionId == u"copy" || rActionId == u"selectionmode")
    {
        Reference < XDispatchProvider > xProv = pTextWin->getFrame();
        if ( xProv.is() )
        {
            URL aURL;
            if (rActionId == u"print")
                aURL.Complete = ".uno:Print";
            else if (rActionId == u"sourceview")
                aURL.Complete = ".uno:SourceView";
            else if (rActionId == u"copy")
                aURL.Complete = ".uno:Copy";
            else // rActionId == "selectionmode"
                aURL.Complete = ".uno:SelectTextMode";
            Reference< util::XURLTransformer > xTrans( util::URLTransformer::create( ::comphelper::getProcessComponentContext() ) );
            xTrans->parseStrict(aURL);
            Reference < XDispatch > xDisp = xProv->queryDispatch( aURL, OUString(), 0 );
            if ( xDisp.is() )
                xDisp->dispatch( aURL, Sequence < PropertyValue >() );
        }
    }
    else if (rActionId == u"bookmarks")
    {
        OUString aURL = pHelpInterceptor->GetCurrentURL();
        if ( !aURL.isEmpty() )
        {
            try
            {
                Content aCnt( aURL, Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
                css::uno::Reference< css::beans::XPropertySetInfo > xInfo = aCnt.getProperties();
                if ( xInfo->hasPropertyByName( PROPERTY_TITLE ) )
                {
                    css::uno::Any aAny = aCnt.getPropertyValue( PROPERTY_TITLE );
                    OUString aValue;
                    if ( aAny >>= aValue )
                    {
                        SfxAddHelpBookmarkDialog_Impl aDlg(GetFrameWeld(), false);
                        aDlg.SetTitle(aValue);
                        if (aDlg.run() == RET_OK )
                        {
                            xIndexWin->AddBookmarks( aDlg.GetTitle(), aURL );
                        }
                    }
                }
            }
            catch( Exception& )
            {
                TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpWindow_Impl::DoAction(): unexpected exception" );
            }
        }
    }
}

void SfxHelpWindow_Impl::CloseWindow()
{
    try
    {
        // search for top frame
        Reference< XFramesSupplier > xCreator = getTextFrame()->getCreator();
        while ( xCreator.is() && !xCreator->isTop() )
        {
            xCreator = xCreator->getCreator();
        }

        // when found, close it
        if ( xCreator.is() && xCreator->isTop() )
        {
            Reference < XCloseable > xCloser( xCreator, UNO_QUERY );
            if ( xCloser.is() )
                xCloser->close( false );
        }
    }
    catch( Exception const & )
    {
        TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelpWindow_Impl::CloseWindow()" );
    }
}


void SfxHelpWindow_Impl::UpdateToolbox()
{
    pTextWin->GetToolBox().set_item_sensitive(u"backward"_ustr, pHelpInterceptor->HasHistoryPred());
    pTextWin->GetToolBox().set_item_sensitive(u"forward"_ustr, pHelpInterceptor->HasHistorySucc());
}


bool SfxHelpWindow_Impl::HasHistoryPredecessor() const
{
    return pHelpInterceptor->HasHistoryPred();
}


bool SfxHelpWindow_Impl::HasHistorySuccessor() const
{
    return pHelpInterceptor->HasHistorySucc();
}

// class SfxAddHelpBookmarkDialog_Impl -----------------------------------

SfxAddHelpBookmarkDialog_Impl::SfxAddHelpBookmarkDialog_Impl(weld::Widget* pParent, bool bRename)
    : GenericDialogController(pParent, u"sfx/ui/bookmarkdialog.ui"_ustr, u"BookmarkDialog"_ustr)
    , m_xTitleED(m_xBuilder->weld_entry(u"entry"_ustr))
    , m_xAltTitle(m_xBuilder->weld_label(u"alttitle"_ustr))
{
    if (bRename)
        m_xDialog->set_title(m_xAltTitle->get_label());
}

void SfxAddHelpBookmarkDialog_Impl::SetTitle(const OUString& rTitle)
{
    m_xTitleED->set_text(rTitle);
    m_xTitleED->select_region(0, -1);
}

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