/* -*- 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 <comphelper/string.hxx>
#include <svl/urlbmk.hxx>
#include <svl/stritem.hxx>
#include <vcl/graphicfilter.hxx>
#include <sot/formats.hxx>
#include <sot/filelist.hxx>
#include <sfx2/event.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewfrm.hxx>
#include <tools/urlobj.hxx>
#include <osl/diagnose.h>
#include <swtypes.hxx>
#include <swmodule.hxx>
#include <view.hxx>
#include <navicfg.hxx>
#include <wrtsh.hxx>
#include <docsh.hxx>
#include <navipi.hxx>
#include <edtwin.hxx>
#include <sfx2/app.hxx>
#include <cmdid.h>
#include <helpids.h>

#include <strings.hrc>
#include <bitmaps.hlst>

#include <memory>

#include <o3tl/enumrange.hxx>

#include <workctrl.hxx>

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::frame;

// Filter the control characters out of the Outline-Entry
OUString SwNavigationPI::CleanEntry(const OUString& rEntry)
{
    if (rEntry.isEmpty())
        return rEntry;

    OUStringBuffer aEntry(rEntry);
    for (sal_Int32 i = 0; i < rEntry.getLength(); ++i)
        if(aEntry[i] == 10 || aEntry[i] == 9)
            aEntry[i] = 0x20;

    return aEntry.makeStringAndClear();
}

// Execution of the drag operation with and without the children.

void SwNavigationPI::MoveOutline(SwOutlineNodes::size_type nSource, SwOutlineNodes::size_type nTarget)
{
    SwView *pView = GetCreateView();
    SwWrtShell &rSh = pView->GetWrtShell();
    if(nTarget < nSource || nTarget == SwOutlineNodes::npos)
        nTarget ++;
    if ( !rSh.IsOutlineMovable( nSource ))
        return;

    SwOutlineNodes::difference_type nMove = nTarget-nSource; //( nDir<0 ) ? 1 : 0 ;
    rSh.GotoOutline(nSource);
    rSh.MakeOutlineSel(nSource, nSource, true);
    // While moving, the selected children does not counting.
    const SwOutlineNodes::size_type nLastOutlinePos = rSh.GetOutlinePos(MAXLEVEL);
    if(nMove > 1 && nLastOutlinePos < nTarget)
    {
        if(!rSh.IsCursorPtAtEnd())
            rSh.SwapPam();
        nMove -= nLastOutlinePos - nSource;
    }
    if( nMove < 1 || nLastOutlinePos < nTarget )
        rSh.MoveOutlinePara( nMove );
    rSh.ClearMark();
    rSh.GotoOutline( nSource + nMove);

}

// After goto cancel the status frame selection
static void lcl_UnSelectFrame(SwWrtShell *pSh)
{
    if (pSh->IsFrameSelected())
    {
        pSh->UnSelectFrame();
        pSh->LeaveSelFrameMode();
    }
}

// Select the document view
IMPL_LINK(SwNavigationPI, DocListBoxSelectHdl, weld::ComboBox&, rBox, void)
{
    int nEntryIdx = rBox.get_active();
    SwView *pView ;
    pView = SwModule::GetFirstView();
    while (nEntryIdx-- && pView)
    {
        pView = SwModule::GetNextView(pView);
    }
    if(!pView)
    {
        nEntryIdx == 0 ?
            m_xContentTree->ShowHiddenShell():
                m_xContentTree->ShowActualView();

    }
    else
    {
        m_xContentTree->SetConstantShell(pView->GetWrtShellPtr());
    }
}

void SwNavigationPI::UpdateNavigateBy()
{
    SfxUInt32Item aParam(FN_NAV_ELEMENT, m_pNavigateByComboBox->get_active_id().toUInt32());
    const SfxPoolItem* aArgs[2];
    aArgs[0] = &aParam;
    aArgs[1] = nullptr;
    SfxDispatcher* pDispatcher = GetCreateView()->GetFrame()->GetDispatcher();
    pDispatcher->Execute(FN_NAV_ELEMENT, SfxCallMode::SYNCHRON, aArgs);
}

IMPL_LINK(SwNavigationPI, NavigateByComboBoxSelectHdl, weld::ComboBox&, rComboBox, void)
{
    m_xContentTree->SelectContentType(rComboBox.get_active_text());
    UpdateNavigateBy();
}

// Filling of the list box for outline view or documents
// The PI will be set to full size
void SwNavigationPI::FillBox()
{
    if(m_pContentWrtShell)
    {
        m_xContentTree->SetHiddenShell( m_pContentWrtShell );
        m_xContentTree->Display(  false );
    }
    else
    {
        SwView *pView = GetCreateView();
        if(!pView)
        {
            m_xContentTree->SetActiveShell(nullptr);
        }
        else if( pView != m_pActContView)
        {
            SwWrtShell* pWrtShell = pView->GetWrtShellPtr();
            m_xContentTree->SetActiveShell(pWrtShell);
        }
        else
            m_xContentTree->Display( true );
        m_pActContView = pView;
        if (m_pActContView)
            m_xContentTree->UpdateTracking();
    }
}

void SwNavigationPI::UsePage()
{
    SwView *pView = GetCreateView();
    SwWrtShell *pSh = pView ? &pView->GetWrtShell() : nullptr;
    m_xEdit->set_value(1);
    if (pSh)
    {
        const sal_uInt16 nPageCnt = pSh->GetPageCnt();
        sal_uInt16 nPhyPage, nVirPage;
        pSh->GetPageNum(nPhyPage, nVirPage);

        m_xEdit->set_max(nPageCnt);
        m_xEdit->set_width_chars(3);
        m_xEdit->set_value(nPhyPage);
    }
}

// Select handler of the toolboxes
IMPL_LINK(SwNavigationPI, ToolBoxSelectHdl, const OString&, rCommand, void)
{
    SwView *pView = GetCreateView();
    if (!pView)
        return;
    SwWrtShell &rSh = pView->GetWrtShell();
    // Get MouseModifier for Outline-Move

    int nFuncId = 0;
    bool bFocusToDoc = false;
    if (rCommand == ".uno:ScrollToPrevious" || rCommand == ".uno:ScrollToNext")
    {
        bool *pbNext = new bool(true);
        if (rCommand == ".uno:ScrollToPrevious")
            *pbNext = false;
        pView->MoveNavigationHdl(pbNext);
    }
    else if (rCommand == "root")
    {
        m_xContentTree->ToggleToRoot();
    }
    else if (rCommand == "listbox")
    {
        if (ParentIsFloatingWindow(m_xNavigatorDlg))
        {
            if (IsZoomedIn())
            {
                ZoomOut();
            }
            else
            {
                ZoomIn();
            }
        }
        return;
    }
    // Functions that will trigger a direct action.
    else if (rCommand == "footer")
    {
        rSh.MoveCursor();
        const FrameTypeFlags eType = rSh.GetFrameType(nullptr,false);
        if (eType & FrameTypeFlags::FOOTER)
        {
            if (rSh.EndPg())
                nFuncId = FN_END_OF_PAGE;
        }
        else if (rSh.GotoFooterText())
            nFuncId = FN_TO_FOOTER;
        bFocusToDoc = true;
    }
    else if (rCommand == "header")
    {
        rSh.MoveCursor();
        const FrameTypeFlags eType = rSh.GetFrameType(nullptr,false);
        if (eType & FrameTypeFlags::HEADER)
        {
            if (rSh.SttPg())
                nFuncId = FN_START_OF_PAGE;
        }
        else if (rSh.GotoHeaderText())
            nFuncId = FN_TO_HEADER;
        bFocusToDoc = true;
    }
    else if (rCommand == "anchor")
    {
        rSh.MoveCursor();
        const FrameTypeFlags eFrameType = rSh.GetFrameType(nullptr,false);
            // Jump from the footnote to the anchor.
        if (eFrameType & FrameTypeFlags::FOOTNOTE)
        {
            if (rSh.GotoFootnoteAnchor())
                nFuncId = FN_FOOTNOTE_TO_ANCHOR;
        }
            // Otherwise, jump to the first footnote text;
            // go to the next footnote if this is not possible;
            // if this is also not possible got to the footnote before.
        else
        {
            if (rSh.GotoFootnoteText())
                nFuncId = FN_FOOTNOTE_TO_ANCHOR;
            else if (rSh.GotoNextFootnoteAnchor())
                nFuncId = FN_NEXT_FOOTNOTE;
            else if (rSh.GotoPrevFootnoteAnchor())
                nFuncId = FN_PREV_FOOTNOTE;
        }
        bFocusToDoc = true;
    }
    else if (rCommand == "reminder")
    {
        rSh.GetView().GetViewFrame()->GetDispatcher()->Execute(FN_SET_REMINDER, SfxCallMode::ASYNCHRON);
    }
    else if (rCommand == "chapterdown" ||
             rCommand == "movedown" ||
             rCommand == "chapterup" ||
             rCommand == "moveup" ||
             rCommand == "promote" ||
             rCommand == "demote" ||
             rCommand == "edit")
    {
        if (IsGlobalMode())
            m_xGlobalTree->ExecCommand(rCommand);
        else
        {
            // Standard: sublevels are taken
            // do not take sublevels with Ctrl
            bool bOutlineWithChildren = (KEY_MOD1 != m_xContent6ToolBox->get_modifier_state());
            m_xContentTree->ExecCommand(rCommand, bOutlineWithChildren);
        }
    }
    else if (rCommand == "contenttoggle" || rCommand == "globaltoggle")
    {
        ToggleTree();
        bool bGlobalMode = IsGlobalMode();
        m_pConfig->SetGlobalActive(bGlobalMode);
        m_xGlobalToolBox->set_item_active("globaltoggle", bGlobalMode);
        m_xContent1ToolBox->set_item_active("contenttoggle", bGlobalMode);
    }
    else if (rCommand == "save")
    {
        bool bSave = rSh.IsGlblDocSaveLinks();
        rSh.SetGlblDocSaveLinks( !bSave );
        m_xGlobalToolBox->set_item_active(rCommand, !bSave);
    }
    else if (rCommand == "dragmode")
        m_xContent6ToolBox->set_menu_item_active("dragmode", !m_xContent6ToolBox->get_menu_item_active("dragmode"));
    else if (rCommand == "headings")
        m_xContent5ToolBox->set_menu_item_active("headings", !m_xContent5ToolBox->get_menu_item_active("headings"));
    else if (rCommand == "update")
        m_xGlobalToolBox->set_menu_item_active("update", !m_xGlobalToolBox->get_menu_item_active("update"));
    else if (rCommand == "insert")
        m_xGlobalToolBox->set_menu_item_active("insert", !m_xGlobalToolBox->get_menu_item_active("insert"));

    if (nFuncId)
        lcl_UnSelectFrame(&rSh);
    if (bFocusToDoc)
        pView->GetEditWin().GrabFocus();
}

// Click handler of the toolboxes
IMPL_LINK(SwNavigationPI, ToolBoxClickHdl, const OString&, rCommand, void)
{
    if (!m_xGlobalToolBox->get_menu_item_active(rCommand))
        return;

    if (rCommand == "update")
        m_xGlobalTree->TbxMenuHdl(rCommand, *m_xUpdateMenu);
    else if (rCommand == "insert")
        m_xGlobalTree->TbxMenuHdl(rCommand, *m_xInsertMenu);
}

IMPL_LINK(SwNavigationPI, ToolBox6DropdownClickHdl, const OString&, rCommand, void)
{
    if (!m_xContent6ToolBox->get_menu_item_active(rCommand))
        return;

    if (rCommand != "dragmode")
        return;

    switch (m_nRegionMode)
    {
        case RegionMode::NONE:
            m_xDragModeMenu->set_active("hyperlink", true);
            break;
        case RegionMode::LINK:
            m_xDragModeMenu->set_active("link", true);
            break;
        case RegionMode::EMBEDDED:
            m_xDragModeMenu->set_active("copy", true);
            break;
    }
}

IMPL_LINK(SwNavigationPI, DropModeMenuSelectHdl, const OString&, rIdent, void)
{
    if (rIdent == "hyperlink")
        SetRegionDropMode(RegionMode::NONE);
    else if (rIdent == "link")
        SetRegionDropMode(RegionMode::LINK);
    else if (rIdent == "copy")
        SetRegionDropMode(RegionMode::EMBEDDED);
}

IMPL_LINK(SwNavigationPI, GlobalMenuSelectHdl, const OString&, rIdent, void)
{
    m_xGlobalTree->ExecuteContextMenuAction(rIdent);
}

IMPL_LINK(SwNavigationPI, ToolBox5DropdownClickHdl, const OString&, rCommand, void)
{
    if (!m_xContent5ToolBox->get_menu_item_active(rCommand))
        return;

    if (rCommand == "headings")
        m_xHeadingsMenu->set_active(OString::number(m_xContentTree->GetOutlineLevel()), true);
}

// Action-Handler Edit:
// Switches to the page if the structure view is not turned on.
bool SwNavigationPI::EditAction()
{
    SwView *pView = GetCreateView();
    if (!pView)
        return false;

    if (m_aPageChgIdle.IsActive())
        m_aPageChgIdle.Stop();

    // if the user has clicked into the document, forget about changing the page
    if (pView->GetEditWin().HasFocus())
        return false;

    SwWrtShell &rSh = m_pCreateView->GetWrtShell();
    sal_uInt16 nNewPage = m_xEdit->get_value();

    rSh.GotoPage(nNewPage, true);
    m_pCreateView->GetViewFrame()->GetBindings().Invalidate(FN_STAT_PAGE);

    return true;
}

void SwNavigationPI::ZoomOut()
{
    if (!IsZoomedIn())
        return;
    SfxNavigator* pNav = m_xNavigatorDlg.get();
    if (!pNav)
        return;
    m_bIsZoomedIn = false;
    FillBox();
    if (IsGlobalMode())
    {
        m_xGlobalBox->show();
        m_xGlobalTree->ShowTree();
    }
    else
    {
        m_xContentBox->show();
        m_xContentTree->ShowTree();
        m_xDocListBox->show();
    }

    pNav->InvalidateChildSizeCache();
    Size aOptimalSize(pNav->GetOptimalSize());
    Size aNewSize(pNav->GetOutputSizePixel());
    aNewSize.setHeight( m_aExpandedSize.Height() );
    pNav->SetMinOutputSizePixel(aOptimalSize);
    pNav->SetOutputSizePixel(aNewSize);

    m_xContentTree->Select(); // Enable toolbox
    m_pConfig->SetSmall(false);
    m_xContent6ToolBox->set_item_active("listbox", true);
}

void SwNavigationPI::ZoomIn()
{
    if (IsZoomedIn())
        return;
    SfxNavigator* pNav = m_xNavigatorDlg.get();
    if (!pNav)
        return;

    m_aExpandedSize = m_xNavigatorDlg->GetSizePixel();

    m_xContentBox->hide();
    m_xContentTree->HideTree();
    m_xGlobalBox->hide();
    m_xGlobalTree->HideTree();
    m_xDocListBox->hide();
    m_bIsZoomedIn = true;

    pNav->InvalidateChildSizeCache();
    Size aOptimalSize(pNav->GetOptimalSize());
    Size aNewSize(pNav->GetOutputSizePixel());
    aNewSize.setHeight( aOptimalSize.Height() );
    pNav->SetMinOutputSizePixel(aOptimalSize);
    pNav->SetOutputSizePixel(aNewSize);

    m_xContentTree->Select(); // Enable toolbox

    m_pConfig->SetSmall(true);
    m_xContent6ToolBox->set_item_active("listbox", false);
}

namespace {

enum StatusIndex
{
    IDX_STR_HIDDEN = 0,
    IDX_STR_ACTIVE = 1,
    IDX_STR_INACTIVE = 2
};

}

std::unique_ptr<PanelLayout> SwNavigationPI::Create(weld::Widget* pParent,
    const css::uno::Reference<css::frame::XFrame>& rxFrame,
    SfxBindings* pBindings)
{
    if( pParent == nullptr )
        throw css::lang::IllegalArgumentException("no parent window given to SwNavigationPI::Create", nullptr, 0);
    if( !rxFrame.is() )
        throw css::lang::IllegalArgumentException("no XFrame given to SwNavigationPI::Create", nullptr, 0);
    if( pBindings == nullptr )
        throw css::lang::IllegalArgumentException("no SfxBindings given to SwNavigationPI::Create", nullptr, 0);
    return std::make_unique<SwNavigationPI>(pParent, rxFrame, pBindings, nullptr);
}

SwNavigationPI::SwNavigationPI(weld::Widget* pParent,
    const css::uno::Reference<css::frame::XFrame>& rxFrame,
    SfxBindings* _pBindings, SfxNavigator* pNavigatorDlg)
    : PanelLayout(pParent, "NavigatorPanel", "modules/swriter/ui/navigatorpanel.ui")
    , m_aDocFullName(SID_DOCFULLNAME, *_pBindings, *this)
    , m_aPageStats(FN_STAT_PAGE, *_pBindings, *this)
    , m_xContent1ToolBox(m_xBuilder->weld_toolbar("content1"))
    , m_xContent2ToolBox(m_xBuilder->weld_toolbar("content2"))
    , m_xContent3ToolBox(m_xBuilder->weld_toolbar("content3"))
    , m_xContent4ToolBox(m_xBuilder->weld_toolbar("content4"))
    , m_xContent5ToolBox(m_xBuilder->weld_toolbar("content5"))
    , m_xContent6ToolBox(m_xBuilder->weld_toolbar("content6"))
    , m_xContent2Dispatch(new ToolbarUnoDispatcher(*m_xContent2ToolBox, *m_xBuilder, rxFrame))
    , m_xContent3Dispatch(new ToolbarUnoDispatcher(*m_xContent3ToolBox, *m_xBuilder, rxFrame))
    , m_xHeadingsMenu(m_xBuilder->weld_menu("headingsmenu"))
    , m_xDragModeMenu(m_xBuilder->weld_menu("dragmodemenu"))
    , m_xUpdateMenu(m_xBuilder->weld_menu("updatemenu"))
    , m_xInsertMenu(m_xBuilder->weld_menu("insertmenu"))
    , m_xGlobalToolBox(m_xBuilder->weld_toolbar("global"))
    , m_xEdit(m_xBuilder->weld_spin_button("spinbutton"))
    , m_xContentBox(m_xBuilder->weld_widget("contentbox"))
    , m_xContentTree(new SwContentTree(m_xBuilder->weld_tree_view("contenttree"), this))
    , m_xGlobalBox(m_xBuilder->weld_widget("globalbox"))
    , m_xGlobalTree(new SwGlobalTree(m_xBuilder->weld_tree_view("globaltree"), this))
    , m_xDocListBox(m_xBuilder->weld_combo_box("documents"))
    , m_aPageChgIdle("SwNavigationPI m_aPageChgIdle")
    , m_xNavigatorDlg(pNavigatorDlg)
    , m_pContentView(nullptr)
    , m_pContentWrtShell(nullptr)
    , m_pActContView(nullptr)
    , m_pCreateView(nullptr)
    , m_pConfig(SW_MOD()->GetNavigationConfig())
    , m_rBindings(*_pBindings)
    , m_nRegionMode(RegionMode::NONE)
    , m_bIsZoomedIn(false)
    , m_bGlobalMode(false)
{
    m_xContainer->connect_container_focus_changed(LINK(this, SwNavigationPI, SetFocusChildHdl));

    Reference<XToolbarController> xController =
            m_xContent2Dispatch->GetControllerForCommand(".uno:NavElement");
    NavElementToolBoxControl* pToolBoxControl =
            dynamic_cast<NavElementToolBoxControl*>(xController.get());
    assert(pToolBoxControl);
    m_pNavigateByComboBox = pToolBoxControl->GetComboBox();

    // Restore content tree settings before calling UpdateInitShow. UpdateInitShow calls Fillbox,
    // which calls Display and UpdateTracking. Incorrect outline levels could be displayed and
    // unexpected content tracking could occur if these content tree settings are not done before.
    m_xContentTree->SetOutlineLevel(static_cast<sal_uInt8>(m_pConfig->GetOutlineLevel()));
    m_xContentTree->SetOutlineTracking(static_cast<sal_uInt8>(m_pConfig->GetOutlineTracking()));
    for (ContentTypeId eCntTypeId : o3tl::enumrange<ContentTypeId>())
    {
        if (eCntTypeId != ContentTypeId::OUTLINE)
            m_xContentTree->SetContentTypeTracking(
                        eCntTypeId, m_pConfig->IsContentTypeTrack(eCntTypeId));
    }

    if (const ContentTypeId nRootType = m_pConfig->GetRootType();
            nRootType != ContentTypeId::UNKNOWN)
    {
        m_xContentTree->SetRootType(nRootType);
        m_xContent5ToolBox->set_item_active("root", true);
        if (nRootType == ContentTypeId::OUTLINE || nRootType == ContentTypeId::DRAWOBJECT)
            m_xContentTree->set_selection_mode(SelectionMode::Multiple);
        else
            m_xContentTree->set_selection_mode(SelectionMode::Single);
    }
    else
        m_xContentTree->set_selection_mode(SelectionMode::Single);

    UpdateInitShow();

    GetCreateView();

    m_xContent1ToolBox->set_help_id(HID_NAVIGATOR_TOOLBOX);
    m_xContent2ToolBox->set_help_id(HID_NAVIGATOR_TOOLBOX);
    m_xContent3ToolBox->set_help_id(HID_NAVIGATOR_TOOLBOX);
    m_xContent4ToolBox->set_help_id(HID_NAVIGATOR_TOOLBOX);
    m_xContent5ToolBox->set_help_id(HID_NAVIGATOR_TOOLBOX);
    m_xContent6ToolBox->set_help_id(HID_NAVIGATOR_TOOLBOX);
    m_xGlobalToolBox->set_help_id(HID_NAVIGATOR_GLOBAL_TOOLBOX);
    m_xDocListBox->set_help_id(HID_NAVIGATOR_LISTBOX);
    m_xDocListBox->set_size_request(42, -1); // set a nominal width so it takes width of surroundings

    // Insert the numeric field in the toolbox.
    m_xEdit->set_accessible_name(m_xEdit->get_tooltip_text());
    m_xEdit->set_width_chars(3);
    m_xEdit->connect_activate(LINK(this, SwNavigationPI, EditActionHdl));
    m_xEdit->connect_value_changed(LINK(this, SwNavigationPI, PageEditModifyHdl));
    m_xEdit->set_help_id("modules/swriter/ui/navigatorpanel/numericfield");

    if (!IsGlobalDoc())
    {
        m_xContent1ToolBox->set_item_visible("contenttoggle", false);
    }

    const TranslateId REGIONNAME_ARY[] =
    {
        STR_HYPERLINK,
        STR_LINK_REGION,
        STR_COPY_REGION
    };

    const TranslateId REGIONMODE_ARY[] =
    {
        STR_HIDDEN,
        STR_ACTIVE,
        STR_INACTIVE
    };

    static_assert(SAL_N_ELEMENTS(REGIONNAME_ARY) == SAL_N_ELEMENTS(REGIONMODE_ARY), "### unexpected size!");
    static_assert(SAL_N_ELEMENTS(REGIONNAME_ARY) == static_cast<sal_uInt16>(RegionMode::EMBEDDED) + 1, "### unexpected size!");

    for (sal_uInt16 i = 0; i <= static_cast<sal_uInt16>(RegionMode::EMBEDDED); ++i)
    {
        m_aStatusArr[i] = SwResId(REGIONMODE_ARY[i]);
    }

    m_aStatusArr[3] = SwResId(STR_ACTIVE_VIEW);

    bool bFloatingNavigator = ParentIsFloatingWindow(m_xNavigatorDlg);

    SetRegionDropMode(m_pConfig->GetRegionMode());

    m_xContentTree->ShowTree();
    m_xContent6ToolBox->set_item_active("listbox", true);
    m_xContent6ToolBox->set_item_sensitive("listbox", bFloatingNavigator);

//  TreeListBox for global document
    m_xGlobalTree->set_selection_mode(SelectionMode::Multiple);

//  Handler
    Link<const OString&, void> aLk = LINK(this, SwNavigationPI, ToolBoxSelectHdl);
    m_xContent1ToolBox->connect_clicked(aLk);
    m_xContent3ToolBox->connect_clicked(aLk);
    m_xContent5ToolBox->connect_clicked(aLk);
    m_xContent6ToolBox->connect_clicked(aLk);
    m_xGlobalToolBox->connect_clicked(aLk);
    m_xDocListBox->connect_changed(LINK(this, SwNavigationPI, DocListBoxSelectHdl));
    m_xContent5ToolBox->set_item_menu("headings", m_xHeadingsMenu.get());
    m_xHeadingsMenu->connect_activate(LINK(this, SwNavigationPI, HeadingsMenuSelectHdl));
    m_xContent5ToolBox->connect_menu_toggled(LINK(this, SwNavigationPI, ToolBox5DropdownClickHdl));
    m_xContent6ToolBox->set_item_menu("dragmode", m_xDragModeMenu.get());
    m_xDragModeMenu->connect_activate(LINK(this, SwNavigationPI, DropModeMenuSelectHdl));
    m_xContent6ToolBox->connect_menu_toggled(LINK(this, SwNavigationPI, ToolBox6DropdownClickHdl));
    m_xGlobalToolBox->set_item_menu("update", m_xUpdateMenu.get());
    m_xUpdateMenu->connect_activate(LINK(this, SwNavigationPI, GlobalMenuSelectHdl));
    m_xGlobalToolBox->set_item_menu("insert", m_xInsertMenu.get());
    m_xInsertMenu->connect_activate(LINK(this, SwNavigationPI, GlobalMenuSelectHdl));
    m_xGlobalToolBox->connect_menu_toggled(LINK(this, SwNavigationPI, ToolBoxClickHdl));
    m_xGlobalToolBox->set_item_active("globaltoggle", true);
    m_pNavigateByComboBox->connect_changed(LINK(this, SwNavigationPI, NavigateByComboBoxSelectHdl));

//  set toolbar of both modes to widest of each
    m_xGlobalToolBox->set_size_request(m_xContent1ToolBox->get_preferred_size().Width() +
                                       m_xContent2ToolBox->get_preferred_size().Width() +
                                       m_xContent3ToolBox->get_preferred_size().Width() +
                                       m_xContent4ToolBox->get_preferred_size().Width(), -1);

    StartListening(*SfxGetpApp());

    if(IsGlobalDoc())
    {
        SwView *pActView = GetCreateView();
        if (pActView && pActView->GetWrtShellPtr())
            m_xGlobalToolBox->set_item_active("save",
                        pActView->GetWrtShellPtr()->IsGlblDocSaveLinks());
        if (m_pConfig->IsGlobalActive())
            ToggleTree();
        if (bFloatingNavigator)
            m_xGlobalTree->grab_focus();
    }
    else if (bFloatingNavigator)
        m_xContentTree->grab_focus();
    UsePage();
    m_aPageChgIdle.SetInvokeHandler(LINK(this, SwNavigationPI, ChangePageHdl));
    m_aPageChgIdle.SetPriority(TaskPriority::LOWEST);

    m_xContentTree->set_accessible_name(SwResId(STR_ACCESS_TL_CONTENT));
    m_xGlobalTree->set_accessible_name(SwResId(STR_ACCESS_TL_GLOBAL));
    m_xDocListBox->set_accessible_name(m_aStatusArr[3]);

    m_aExpandedSize = m_xContainer->get_preferred_size();
}

weld::Window* SwNavigationPI::GetFrameWeld() const
{
    if (m_xNavigatorDlg)
        return m_xNavigatorDlg->GetFrameWeld();
    return PanelLayout::GetFrameWeld();
}

SwNavigationPI::~SwNavigationPI()
{
    if (IsGlobalDoc() && !IsGlobalMode())
    {
        SwView *pView = GetCreateView();
        SwWrtShell &rSh = pView->GetWrtShell();
        if (!rSh.IsAllProtect())
            pView->GetDocShell()->SetReadOnlyUI(false);
    }

    EndListening(*SfxGetpApp());

    if (m_pxObjectShell)
    {
        if (m_pxObjectShell->Is())
            (*m_pxObjectShell)->DoClose();
        m_pxObjectShell.reset();
    }

    m_xDocListBox.reset();
    m_xGlobalTree.reset();
    m_xGlobalBox.reset();
    m_xContentTree.reset();
    m_xContentBox.reset();
    m_xGlobalToolBox.reset();
    m_xEdit.reset();
    m_xHeadingsMenu.reset();
    m_xDragModeMenu.reset();
    m_xUpdateMenu.reset();
    m_xInsertMenu.reset();
    m_xContent2Dispatch.reset();
    m_xContent3Dispatch.reset();
    m_xContent1ToolBox.reset();
    m_xContent2ToolBox.reset();
    m_xContent3ToolBox.reset();
    m_xContent4ToolBox.reset();
    m_xContent5ToolBox.reset();
    m_xContent6ToolBox.reset();

    m_aPageChgIdle.Stop();

    m_aDocFullName.dispose();
    m_aPageStats.dispose();
}

void SwNavigationPI::NotifyItemUpdate(sal_uInt16 nSID, SfxItemState /*eState*/,
                                      const SfxPoolItem* /*pState*/)
{
    if (nSID == SID_DOCFULLNAME)
    {
        SwView *pActView = GetCreateView();
        if(pActView)
        {
            SwWrtShell* pWrtShell = pActView->GetWrtShellPtr();
            m_xContentTree->SetActiveShell(pWrtShell);
            bool bGlobal = IsGlobalDoc();
            m_xContent1ToolBox->set_item_visible("contenttoggle", bGlobal);
            if ((!bGlobal && IsGlobalMode()) || (!IsGlobalMode() && m_pConfig->IsGlobalActive()))
            {
                ToggleTree();
            }
            if (bGlobal)
            {
                m_xGlobalToolBox->set_item_active("save", pWrtShell->IsGlblDocSaveLinks());
            }
        }
        else
        {
            m_xContentTree->SetActiveShell(nullptr);
        }
        UpdateListBox();
    }
    else if (nSID == FN_STAT_PAGE)
    {
        SwView *pActView = GetCreateView();
        if(pActView)
        {
            SwWrtShell &rSh = pActView->GetWrtShell();
            m_xEdit->set_max(rSh.GetPageCnt());
            m_xEdit->set_width_chars(3);
        }
    }
}

void SwNavigationPI::UpdateInitShow()
{
    // if the parent isn't a float, then the navigator is displayed in
    // the sidebar or is otherwise docked. While the navigator could change
    // its size, the sidebar can not, and the navigator would just waste
    // space. Therefore disable this button.
    bool bParentIsFloatingWindow(ParentIsFloatingWindow(m_xNavigatorDlg));
    m_xContent6ToolBox->set_item_sensitive("listbox", bParentIsFloatingWindow);
    // show content if docked
    if (!bParentIsFloatingWindow && IsZoomedIn())
        ZoomOut();
    if (!IsZoomedIn())
        FillBox();
}

IMPL_LINK_NOARG(SwNavigationPI, SetFocusChildHdl, weld::Container&, void)
{
    // update documents listbox
    UpdateListBox();
}

// Notification on modified DocInfo
void SwNavigationPI::Notify( SfxBroadcaster& rBrdc, const SfxHint& rHint )
{
    if(&rBrdc == m_pCreateView)
    {
        if (rHint.GetId() == SfxHintId::Dying)
        {
            EndListening(*m_pCreateView);
            m_pCreateView = nullptr;
            m_xContentTree->SetActiveShell(nullptr);
        }
    }
    else
    {
        if (const SfxEventHint* pHint = dynamic_cast<const SfxEventHint*>(&rHint))
        {
            SfxEventHintId eEventId = pHint->GetEventId();
            if (eEventId == SfxEventHintId::OpenDoc)
            {
                SwView *pActView = GetCreateView();
                if(pActView)
                {
                    SwWrtShell* pWrtShell = pActView->GetWrtShellPtr();
                    m_xContentTree->SetActiveShell(pWrtShell);
                    if (m_xGlobalTree->get_visible())
                    {
                        bool bUpdateAll = m_xGlobalTree->Update(false);
                        // If no update is needed, then update the font colors
                        // at the entries of broken links.
                        m_xGlobalTree->Display(!bUpdateAll);
                    }
                }
            }
        }
    }
}

IMPL_LINK( SwNavigationPI, HeadingsMenuSelectHdl, const OString&, rMenuId, void )
{
    if (!rMenuId.isEmpty())
        m_xContentTree->SetOutlineLevel(rMenuId.toUInt32());
}

void SwNavigationPI::UpdateListBox()
{
    if (!m_xDocListBox) // disposed
        return;

    m_xDocListBox->freeze();
    m_xDocListBox->clear();
    SwView *pActView = GetCreateView();
    bool bDisable = pActView == nullptr;
    SwView *pView = SwModule::GetFirstView();
    sal_Int32 nCount = 0;
    sal_Int32 nAct = 0;
    sal_Int32 nConstPos = 0;
    const SwView* pConstView = m_xContentTree->IsConstantView() &&
                                m_xContentTree->GetActiveWrtShell() ?
                                    &m_xContentTree->GetActiveWrtShell()->GetView():
                                        nullptr;
    while (pView)
    {
        SfxObjectShell* pDoc = pView->GetDocShell();
        // #i53333# don't show help pages here
        if ( !pDoc->IsHelpDocument() )
        {
            OUString sEntry = pDoc->GetTitle() + " (";
            if (pView == pActView)
            {
                nAct = nCount;
                sEntry += m_aStatusArr[IDX_STR_ACTIVE];
            }
            else
                sEntry += m_aStatusArr[IDX_STR_INACTIVE];
            sEntry += ")";
            m_xDocListBox->append_text(sEntry);

            if (pConstView && pView == pConstView)
                nConstPos = nCount;

            nCount++;
        }
        pView = SwModule::GetNextView(pView);
    }
    m_xDocListBox->append_text(m_aStatusArr[3]); // "Active Window"
    nCount++;

    if(m_xContentTree->GetHiddenWrtShell())
    {
        OUString sEntry = m_xContentTree->GetHiddenWrtShell()->GetView().
                                        GetDocShell()->GetTitle() +
            " (" +
            m_aStatusArr[IDX_STR_HIDDEN] +
            ")";
        m_xDocListBox->append_text(sEntry);
        bDisable = false;
    }

    m_xDocListBox->thaw();

    if(m_xContentTree->IsActiveView())
    {
        //Either the name of the current Document or "Active Document".
        m_xDocListBox->set_active(pActView ? nAct : --nCount);
    }
    else if(m_xContentTree->IsHiddenView())
    {
        m_xDocListBox->set_active(nCount);
    }
    else
        m_xDocListBox->set_active(nConstPos);

    m_xDocListBox->set_sensitive(!bDisable);
}

IMPL_LINK(SwNavigationPI, DoneLink, SfxPoolItem const *, pItem, void)
{
    const SfxViewFrameItem* pFrameItem = dynamic_cast<SfxViewFrameItem const *>( pItem  );
    if( !pFrameItem )
        return;

    SfxViewFrame* pFrame =  pFrameItem->GetFrame();
    if(pFrame)
    {
        m_xContentTree->clear();
        m_pContentView = dynamic_cast<SwView*>( pFrame->GetViewShell() );
        OSL_ENSURE(m_pContentView, "no SwView");
        if(m_pContentView)
            m_pContentWrtShell = m_pContentView->GetWrtShellPtr();
        else
            m_pContentWrtShell = nullptr;
        m_pxObjectShell.reset( new SfxObjectShellLock(pFrame->GetObjectShell()) );
        FillBox();
    }
}

OUString SwNavigationPI::CreateDropFileName( const TransferableDataHelper& rData )
{
    OUString sFileName;
    SotClipboardFormatId nFormat;
    if( rData.HasFormat( nFormat = SotClipboardFormatId::FILE_LIST ))
    {
        FileList aFileList;
        rData.GetFileList( nFormat, aFileList );
        sFileName = aFileList.GetFile( 0 );
    }
    else if( rData.HasFormat( nFormat = SotClipboardFormatId::STRING ) ||
              rData.HasFormat( nFormat = SotClipboardFormatId::SIMPLE_FILE ) ||
             rData.HasFormat( nFormat = SotClipboardFormatId::FILENAME ))
    {
        (void)rData.GetString(nFormat, sFileName);
    }
    else if( rData.HasFormat( nFormat = SotClipboardFormatId::SOLK ) ||
                rData.HasFormat( nFormat = SotClipboardFormatId::NETSCAPE_BOOKMARK )||
                rData.HasFormat( nFormat = SotClipboardFormatId::FILECONTENT ) ||
                rData.HasFormat( nFormat = SotClipboardFormatId::FILEGRPDESCRIPTOR ) ||
                rData.HasFormat( nFormat = SotClipboardFormatId::UNIFORMRESOURCELOCATOR ))
    {
        INetBookmark aBkmk { OUString(), OUString() };
        if (rData.GetINetBookmark(nFormat, aBkmk))
            sFileName = aBkmk.GetURL();
    }
    if( !sFileName.isEmpty() )
    {
        sFileName = INetURLObject( sFileName ).GetMainURL( INetURLObject::DecodeMechanism::NONE );
    }
    return sFileName;
}

sal_Int8 SwNavigationPI::AcceptDrop()
{
    return ( !m_xContentTree->IsInDrag() &&
        ( m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::SIMPLE_FILE ) ||
          m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::STRING ) ||
          m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::SOLK ) ||
           m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::NETSCAPE_BOOKMARK )||
           m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::FILECONTENT ) ||
           m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::FILEGRPDESCRIPTOR ) ||
           m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::UNIFORMRESOURCELOCATOR ) ||
           m_xContentTree->IsDropFormatSupported( SotClipboardFormatId::FILENAME )))
        ? DND_ACTION_COPY
        : DND_ACTION_NONE;
}

sal_Int8 SwNavigationPI::ExecuteDrop( const ExecuteDropEvent& rEvt )
{
    TransferableDataHelper aData( rEvt.maDropEvent.Transferable );
    sal_Int8 nRet = DND_ACTION_NONE;
    if (m_xContentTree->IsInDrag())
        return nRet;

    OUString sFileName = SwNavigationPI::CreateDropFileName(aData);
    if (sFileName.isEmpty())
        return nRet;

    INetURLObject aTemp(sFileName);
    GraphicDescriptor aDesc(aTemp);
    if (aDesc.Detect())   // accept no graphics
        return nRet;

    if (-1 != sFileName.indexOf('#'))
        return nRet;

    if (m_sContentFileName.isEmpty() || m_sContentFileName != sFileName)
    {
        nRet = rEvt.mnAction;
        sFileName = comphelper::string::stripEnd(sFileName, 0);
        m_sContentFileName = sFileName;
        if(m_pxObjectShell)
        {
            m_xContentTree->SetHiddenShell( nullptr );
            (*m_pxObjectShell)->DoClose();
            m_pxObjectShell.reset();
        }
        SfxStringItem aFileItem(SID_FILE_NAME, sFileName );
        SfxStringItem aOptionsItem( SID_OPTIONS, "HRC" );
        SfxLinkItem aLink( SID_DONELINK,
                            LINK( this, SwNavigationPI, DoneLink ) );
        if (SwView* pView = GetActiveView())
            pView->GetViewFrame()->GetDispatcher()->ExecuteList(
                        SID_OPENDOC, SfxCallMode::ASYNCHRON,
                        { &aFileItem, &aOptionsItem, &aLink });
    }
    return nRet;
}

void SwNavigationPI::SetRegionDropMode(RegionMode nNewMode)
{
    m_nRegionMode = nNewMode;
    m_pConfig->SetRegionMode( m_nRegionMode );

    OUString sImageId;
    switch (nNewMode)
    {
        case RegionMode::NONE:
            sImageId = RID_BMP_DROP_REGION;
            break;
        case RegionMode::LINK:
            sImageId = RID_BMP_DROP_LINK;
            break;
        case RegionMode::EMBEDDED:
            sImageId = RID_BMP_DROP_COPY;
            break;
    }
    m_xContent6ToolBox->set_item_icon_name("dragmode", sImageId);
}

void SwNavigationPI::ToggleTree()
{
    bool bGlobalDoc = IsGlobalDoc();
    if (!IsGlobalMode() && bGlobalDoc)
    {
        if (IsZoomedIn())
            ZoomOut();
        m_xGlobalBox->show();
        m_xGlobalTree->ShowTree();
        m_xGlobalToolBox->show();
        m_xContentBox->hide();
        m_xContentTree->HideTree();
        m_xContent1ToolBox->hide();
        m_xContent2ToolBox->hide();
        m_xContent3ToolBox->hide();
        m_xContent4ToolBox->hide();
        m_xContent5ToolBox->hide();
        m_xContent6ToolBox->hide();
        m_xDocListBox->hide();
        SetGlobalMode(true);
    }
    else
    {
        m_xGlobalBox->hide();
        m_xGlobalTree->HideTree();
        m_xGlobalToolBox->hide();
        if (!IsZoomedIn())
        {
            m_xContentBox->show();
            m_xContentTree->ShowTree();
            m_xContent1ToolBox->show();
            m_xContent2ToolBox->show();
            m_xContent3ToolBox->show();
            m_xContent4ToolBox->show();
            m_xContent5ToolBox->show();
            m_xContent6ToolBox->show();
            m_xDocListBox->show();
        }
        SetGlobalMode(false);
    }
}

bool SwNavigationPI::IsGlobalDoc() const
{
    bool bRet = false;
    SwView *pView = GetCreateView();
    if (pView)
    {
        SwWrtShell &rSh = pView->GetWrtShell();
        bRet = rSh.IsGlobalDoc();
    }
    return bRet;
}

IMPL_LINK_NOARG(SwNavigationPI, ChangePageHdl, Timer *, void)
{
    if (!m_xDocListBox) // disposed
        return;
    // tdf#134959 if the SpinButton changed value this Timer was launched, now
    // change to the desired page, but we leave focus where it currently is,
    // i.e. typically remaining in the spinbutton, or whatever other widget the
    // user moved to in the meantime
    EditAction();
}

void SwNavigationPI::SelectNavigateByContentType(const OUString& rContentTypeName)
{
    if (auto nPos = m_pNavigateByComboBox->find_text(rContentTypeName); nPos != -1)
    {
        m_pNavigateByComboBox->set_active(nPos);
        UpdateNavigateBy();
    }
}

IMPL_LINK_NOARG(SwNavigationPI, EditActionHdl, weld::Entry&, bool)
{
    // tdf#134959 if the user presses enter to activate the Entry
    // go to the page, and on success we move focus to the document
    if (EditAction())
        m_pCreateView->GetEditWin().GrabFocus();
    return true;
}

IMPL_LINK_NOARG(SwNavigationPI, PageEditModifyHdl, weld::SpinButton&, void)
{
    if (m_aPageChgIdle.IsActive())
        m_aPageChgIdle.Stop();
    m_aPageChgIdle.Start();
}

SwView*  SwNavigationPI::GetCreateView() const
{
    if (!m_pCreateView)
    {
        SwView* pView = SwModule::GetFirstView();
        while (pView)
        {
            if(&pView->GetViewFrame()->GetBindings() == &m_rBindings)
            {
                const_cast<SwNavigationPI*>(this)->m_pCreateView = pView;
                const_cast<SwNavigationPI*>(this)->StartListening(*m_pCreateView);
                break;
            }
            pView = SwModule::GetNextView(pView);
        }
    }
    return m_pCreateView;
}

class SwNavigatorWin : public SfxNavigator
{
private:
    std::unique_ptr<SwNavigationPI> m_xNavi;
public:
    SwNavigatorWin(SfxBindings* _pBindings, SfxChildWindow* _pMgr,
                   vcl::Window* pParent, SfxChildWinInfo* pInfo);
    virtual void StateChanged(StateChangedType nStateChange) override;
    virtual void dispose() override
    {
        m_xNavi.reset();
        SfxNavigator::dispose();
    }
    virtual ~SwNavigatorWin() override
    {
        disposeOnce();
    }
};

SwNavigatorWin::SwNavigatorWin(SfxBindings* _pBindings, SfxChildWindow* _pMgr,
                               vcl::Window* pParent, SfxChildWinInfo* pInfo)
    : SfxNavigator(_pBindings, _pMgr, pParent, pInfo)
    , m_xNavi(std::make_unique<SwNavigationPI>(m_xContainer.get(), _pBindings->GetActiveFrame(), _pBindings, this))
{
    _pBindings->Invalidate(SID_NAVIGATOR);

    SwNavigationConfig* pNaviConfig = SW_MOD()->GetNavigationConfig();

    SetMinOutputSizePixel(GetOptimalSize());
    if (pNaviConfig->IsSmall())
        m_xNavi->ZoomIn();
}

void SwNavigatorWin::StateChanged(StateChangedType nStateChange)
{
    SfxNavigator::StateChanged(nStateChange);
    if (nStateChange == StateChangedType::InitShow)
        m_xNavi->UpdateInitShow();
}

SFX_IMPL_DOCKINGWINDOW(SwNavigatorWrapper, SID_NAVIGATOR);

SwNavigatorWrapper::SwNavigatorWrapper(vcl::Window *_pParent, sal_uInt16 nId,
                                       SfxBindings* pBindings, SfxChildWinInfo* pInfo)
    : SfxNavigatorWrapper(_pParent, nId)
{
    SetWindow(VclPtr<SwNavigatorWin>::Create(pBindings, this, _pParent, pInfo));
    Initialize();
}

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