/* -*- 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 <redline.hxx>
#include <tools/datetime.hxx>
#include <tools/lineend.hxx>
#include <svl/eitem.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/dispatch.hxx>
#include <svx/ctredlin.hxx>
#include <svx/postattr.hxx>
#include <utility>
#include <vcl/commandevent.hxx>
#include <swtypes.hxx>
#include <wrtsh.hxx>
#include <view.hxx>
#include <swmodule.hxx>
#include <redlndlg.hxx>
#include <swwait.hxx>
#include <uitool.hxx>
#include <o3tl/string_view.hxx>
#include <o3tl/temporary.hxx>

#include <cmdid.h>
#include <strings.hrc>

// -> #111827#
#include <swundo.hxx>
#include <SwRewriter.hxx>
// <- #111827#

#include <vector>
#include <svx/svxdlg.hxx>
#include <osl/diagnose.h>
#include <bitmaps.hlst>

#include <docsh.hxx>

#include <IDocumentRedlineAccess.hxx>
#include <usrpref.hxx>
#include <memory>

SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(SwRedlineAcceptChild, FN_REDLINE_ACCEPT)

SwRedlineAcceptChild::SwRedlineAcceptChild(vcl::Window* _pParent,
                                           sal_uInt16 nId,
                                           SfxBindings* pBindings,
                                           SfxChildWinInfo* pInfo)
    : SwChildWinWrapper(_pParent, nId)
{
    auto xDlg = std::make_shared<SwModelessRedlineAcceptDlg>(pBindings, this, _pParent->GetFrameWeld());
    SetController(xDlg);
    xDlg->Initialize(pInfo);
}

SwModelessRedlineAcceptDlg::SwModelessRedlineAcceptDlg(
    SfxBindings* _pBindings, SwChildWinWrapper* pChild, weld::Window *pParent)
    : SfxModelessDialogController(_pBindings, pChild, pParent,
        u"svx/ui/acceptrejectchangesdialog.ui"_ustr, u"AcceptRejectChangesDialog"_ustr)
    , m_xContentArea(m_xBuilder->weld_container(u"container"_ustr))
    , m_pChildWin(pChild)
{
    m_xImplDlg.reset(new SwRedlineAcceptDlg(m_xDialog, m_xBuilder.get(), m_xContentArea.get()));
}

void SwModelessRedlineAcceptDlg::Activate()
{
    if (mbInDestruction)
        return;

    SwView *pView = ::GetActiveView();
    if (!pView) // can happen when switching to another app, when a Listbox in dialog
        return; // had the focus previously (actually THs Bug)

    SwDocShell *pDocSh = pView->GetDocShell();

    if (m_pChildWin->GetOldDocShell() != pDocSh)
    {   // doc-switch
        SwWait aWait( *pDocSh, false );
        SwWrtShell* pSh = pView->GetWrtShellPtr();
        if (!pSh)
            return;

        m_pChildWin->SetOldDocShell(pDocSh);  // avoid recursion (using modified-Hdl)

        bool bMod = pSh->IsModified();
        SfxBoolItem aShow(FN_REDLINE_SHOW, true);
        pSh->GetView().GetViewFrame().GetDispatcher()->ExecuteList(
            FN_REDLINE_SHOW, SfxCallMode::SYNCHRON|SfxCallMode::RECORD,
            { &aShow });
        if (!bMod)
            pSh->ResetModified();
        m_xImplDlg->Init();
        SfxModelessDialogController::Activate();

        return;
    }

    SfxModelessDialogController::Activate();
    m_xImplDlg->Activate();
}

void SwModelessRedlineAcceptDlg::Initialize(SfxChildWinInfo* pInfo)
{
    if (pInfo != nullptr)
        m_xImplDlg->Initialize(pInfo->aExtraString);

    SfxModelessDialogController::Initialize(pInfo);
}

void SwModelessRedlineAcceptDlg::FillInfo(SfxChildWinInfo& rInfo) const
{
    SfxModelessDialogController::FillInfo(rInfo);
    m_xImplDlg->FillInfo(rInfo.aExtraString);
}

SwModelessRedlineAcceptDlg::~SwModelessRedlineAcceptDlg()
{
    mbInDestruction = true;
}

namespace
{
const SwRedlineData* lcl_get_selected_redlinedata(const weld::TreeView& rTreeView)
{
    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    if (rTreeView.get_selected(xEntry.get()))
    {
        RedlinData* pRedlinData = weld::fromId<RedlinData*>(rTreeView.get_id(*xEntry));
        if (rTreeView.get_iter_depth(*xEntry))
            return static_cast<SwRedlineDataChild*>(pRedlinData->pData)->pChild;
        else
            return static_cast<SwRedlineDataParent*>(pRedlinData->pData)->pData;
    }
    return nullptr;
}

void lcl_reselect(weld::TreeView& rTreeView, const SwRedlineData* pSelectedEntryRedlineData)
{
    if (!pSelectedEntryRedlineData)
    {
        rTreeView.set_cursor(-1);
        return;
    }
    rTreeView.all_foreach(
        [&rTreeView, &pSelectedEntryRedlineData](weld::TreeIter& rIter)
        {
            RedlinData* pRedlinData = weld::fromId<RedlinData*>(rTreeView.get_id(rIter));
            const SwRedlineData* pRedlineData;
            if (rTreeView.get_iter_depth(rIter))
                pRedlineData = static_cast<SwRedlineDataChild*>(pRedlinData->pData)->pChild;
            else
                pRedlineData = static_cast<SwRedlineDataParent*>(pRedlinData->pData)->pData;
            if (pRedlineData == pSelectedEntryRedlineData)
            {
                rTreeView.set_cursor(rIter);
                return true;
            }
            return false;
        });
}
}

SwRedlineAcceptDlg::SwRedlineAcceptDlg(std::shared_ptr<weld::Window> xParent, weld::Builder *pBuilder,
                                       weld::Container *pContentArea, bool bAutoFormat)
    : m_xParentDlg(std::move(xParent))
    , m_aSelectTimer("SwRedlineAcceptDlg m_aSelectTimer")
    , m_sInserted(SwResId(STR_REDLINE_INSERTED))
    , m_sDeleted(SwResId(STR_REDLINE_DELETED))
    , m_sFormated(SwResId(STR_REDLINE_FORMATTED))
    , m_sTableChgd(SwResId(STR_REDLINE_TABLECHG))
    , m_sFormatCollSet(SwResId(STR_REDLINE_FMTCOLLSET))
    , m_sAutoFormat(SwResId(STR_REDLINE_AUTOFMT))
    , m_bOnlyFormatedRedlines(false)
    , m_bRedlnAutoFormat(bAutoFormat)
    , m_bInhibitActivate(false)
    , m_bHasTrackedColumn(false)
    , m_xTabPagesCTRL(new SvxAcceptChgCtr(pContentArea))
    , m_xPopup(pBuilder->weld_menu(u"writermenu"_ustr))
    , m_xSortMenu(pBuilder->weld_menu(u"writersortmenu"_ustr))
{
    m_pTPView = m_xTabPagesCTRL->GetViewPage();

    m_pTable = m_pTPView->GetTableControl();
    m_pTable->SetWriterView();

    m_pTPView->GetSortByComboBoxControl()->set_active(4);

    m_pTPView->SetSortByComboBoxChangedHdl(
        LINK(this, SwRedlineAcceptDlg, SortByComboBoxChangedHdl));

    m_pTPView->SetAcceptClickHdl(LINK(this, SwRedlineAcceptDlg, AcceptHdl));
    m_pTPView->SetAcceptAllClickHdl(LINK(this, SwRedlineAcceptDlg, AcceptAllHdl));
    m_pTPView->SetRejectClickHdl(LINK(this, SwRedlineAcceptDlg, RejectHdl));
    m_pTPView->SetRejectAllClickHdl(LINK(this, SwRedlineAcceptDlg, RejectAllHdl));
    m_pTPView->SetUndoClickHdl(LINK(this, SwRedlineAcceptDlg, UndoHdl));
    //tdf#89227 default to disabled, and only enable if possible to accept/reject
    m_pTPView->EnableAccept(false);
    m_pTPView->EnableReject(false);
    m_pTPView->EnableClearFormat(false);
    m_pTPView->EnableAcceptAll(false);
    m_pTPView->EnableRejectAll(false);
    m_pTPView->EnableClearFormatAll(false);

    m_xTabPagesCTRL->GetFilterPage()->SetReadyHdl(LINK(this, SwRedlineAcceptDlg, FilterChangedHdl));

    weld::ComboBox* pActLB = m_xTabPagesCTRL->GetFilterPage()->GetLbAction();
    pActLB->append_text(m_sInserted);
    pActLB->append_text(m_sDeleted);
    pActLB->append_text(m_sFormated);
    pActLB->append_text(m_sTableChgd);

    if (HasRedlineAutoFormat())
    {
        pActLB->append_text(m_sFormatCollSet);
        pActLB->append_text(m_sAutoFormat);
        m_pTPView->ShowUndo();
        m_pTPView->DisableUndo();     // no UNDO events yet
    }

    pActLB->set_active(0);

    weld::TreeView& rTreeView = m_pTable->GetWidget();
    rTreeView.set_selection_mode(SelectionMode::Multiple);

    rTreeView.connect_selection_changed(LINK(this, SwRedlineAcceptDlg, SelectHdl));
    rTreeView.connect_popup_menu(LINK(this, SwRedlineAcceptDlg, CommandHdl));

    // avoid multiple selection of the same texts:
    m_aSelectTimer.SetTimeout(100);
    m_aSelectTimer.SetInvokeHandler(LINK(this, SwRedlineAcceptDlg, GotoHdl));

    // we want to receive SfxHintId::SwRedlineContentAtPos
    StartListening(*(SwModule::get()->GetView()->GetDocShell()));
}

SwRedlineAcceptDlg::~SwRedlineAcceptDlg()
{
}

void SwRedlineAcceptDlg::Init(SwRedlineTable::size_type nStart)
{
    std::optional<SwWait> oWait;
    SwView* pView = GetActiveView();
    if (pView)
        oWait.emplace(*pView->GetDocShell(), false);
    weld::TreeView& rTreeView = m_pTable->GetWidget();
    m_aUsedSeqNo.clear();

    // tdf#162018 keep the selected entry selected
    const SwRedlineData* pSelectedEntryRedlineData = lcl_get_selected_redlinedata(rTreeView);

    // tdf#162337 tracked change selection when the Manage Changes dialog is initially opened
    if (pView && m_bInitialSelect)
    {
        m_bInitialSelect = false;
        SwWrtShell* pSh = pView->GetWrtShellPtr();
        if (pSh)
        {
            const SwRangeRedline* pCurrRedline = pSh->GetCurrRedline();
            if (pCurrRedline)
            {
                // Select current redline
                SwRedlineTable::size_type nPos
                    = pSh->FindRedlineOfData(pCurrRedline->GetRedlineData());
                pSh->GotoRedline(nPos, true);
                pSh->SetInSelect();
            }
            else
            {
                // Select the next redline if there is one
                pSh->AssureStdMode();
                pCurrRedline = pSh->SelNextRedline();
            }
            if (pCurrRedline)
                pSelectedEntryRedlineData = &pCurrRedline->GetRedlineData();
        }
    }

    rTreeView.freeze();
    if (nStart)
        RemoveParents(nStart, m_RedlineParents.size() - 1);
    else
    {
        rTreeView.clear();
        m_RedlinData.clear();
        m_RedlineChildren.clear();
        m_RedlineParents.erase(m_RedlineParents.begin() + nStart, m_RedlineParents.end());
    }
    rTreeView.thaw();

    // insert parents
    InsertParents(nStart);
    InitAuthors();

    lcl_reselect(rTreeView, pSelectedEntryRedlineData);
    EnableControls(pView);
}

static bool isAcceptRejectCommandsEnabled(const SwView& rView)
{
    // Check the state of the command, including read-only mode and special cases
    // like LOK AllowChangeComments mode
    return rView.GetViewFrame().GetDispatcher()->QueryState(FN_REDLINE_ACCEPT_ALL,
                                                            o3tl::temporary(SfxPoolItemHolder()))
           != SfxItemState::DISABLED;
}

void SwRedlineAcceptDlg::InitAuthors()
{
    if (!m_xTabPagesCTRL)
        return;

    SwView *pView = ::GetActiveView();
    if (!pView)
        return;
    SwWrtShell* pSh = pView->GetWrtShellPtr();

    SvxTPFilter *pFilterPage = m_xTabPagesCTRL->GetFilterPage();

    std::vector<OUString> aStrings;
    OUString sOldAuthor(pFilterPage->GetSelectedAuthor());
    pFilterPage->ClearAuthors();

    SwRedlineTable::size_type nCount = pSh ? pSh->GetRedlineCount() : 0;

    m_bOnlyFormatedRedlines = true;

    // determine authors
    for ( SwRedlineTable::size_type i = 0; i < nCount; i++)
    {
        const SwRangeRedline& rRedln = pSh->GetRedline(i);

        if( m_bOnlyFormatedRedlines && RedlineType::Format != rRedln.GetType() )
            m_bOnlyFormatedRedlines = false;

        aStrings.push_back(rRedln.GetAuthorString());

        for (sal_uInt16 nStack = 1; nStack < rRedln.GetStackCount(); nStack++)
        {
            aStrings.push_back(rRedln.GetAuthorString(nStack));
        }
    }

    std::sort(aStrings.begin(), aStrings.end());
    aStrings.erase(std::unique(aStrings.begin(), aStrings.end()), aStrings.end());

    for (auto const & i: aStrings)
        pFilterPage->InsertAuthor(i);

    if (pFilterPage->SelectAuthor(sOldAuthor) == -1 && !aStrings.empty())
        pFilterPage->SelectAuthor(aStrings[0]);
}

void SwRedlineAcceptDlg::EnableControls(const SwView* pView)
{
    if (!pView)
        return;
    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return;

    weld::TreeView& rTreeView = m_pTable->GetWidget();
    bool const bEnable = isAcceptRejectCommandsEnabled(*pView)
        && rTreeView.n_children() != 0
        && !pSh->getIDocumentRedlineAccess().GetRedlinePassword().hasElements();
    bool bSel = rTreeView.get_selected(nullptr);

    bool bIsNotFormated = false;
    rTreeView.selected_foreach([this, pSh, &bIsNotFormated](weld::TreeIter& rEntry){
        // find the selected redline
        // (fdo#57874: ignore, if the redline is already gone)
        SwRedlineTable::size_type nPos = GetRedlinePos(rEntry);
        if( nPos != SwRedlineTable::npos )
        {
            const SwRangeRedline& rRedln = pSh->GetRedline( nPos );

            bIsNotFormated |= RedlineType::Format != rRedln.GetType();
        }
        return false;
    });

    m_pTPView->EnableAccept( bEnable && bSel );
    m_pTPView->EnableReject( bEnable && bSel );
    m_pTPView->EnableClearFormat( bEnable && !bIsNotFormated && bSel );
    m_pTPView->EnableAcceptAll( bEnable );
    m_pTPView->EnableRejectAll( bEnable );
    m_pTPView->EnableClearFormatAll( bEnable &&
                                m_bOnlyFormatedRedlines );
}

const OUString & SwRedlineAcceptDlg::GetActionImage(const SwRangeRedline& rRedln, sal_uInt16 nStack,
                                            bool bTableChanges, bool bRowChanges)
{
    switch (rRedln.GetType(nStack))
    {
        case RedlineType::Insert:  return bTableChanges
            ? bRowChanges
                ? BMP_REDLINE_ROW_INSERTION
                : BMP_REDLINE_COL_INSERTION
            : rRedln.IsMoved()
                ? BMP_REDLINE_MOVED_INSERTION
                : rRedln.IsAnnotation()
                    ? BMP_REDLINE_COMMENT_INSERTION
                    : BMP_REDLINE_INSERTED;
        case RedlineType::Delete:  return bTableChanges
            ? bRowChanges
                 ? BMP_REDLINE_ROW_DELETION
                 : BMP_REDLINE_COL_DELETION
            : rRedln.IsMoved()
                ? BMP_REDLINE_MOVED_DELETION
                : rRedln.IsAnnotation()
                    ? BMP_REDLINE_COMMENT_DELETION
                    : BMP_REDLINE_DELETED;
        case RedlineType::Format:  return BMP_REDLINE_FORMATTED;
        case RedlineType::ParagraphFormat: return BMP_REDLINE_FORMATTED;
        case RedlineType::Table:   return BMP_REDLINE_TABLECHG;
        case RedlineType::FmtColl: return BMP_REDLINE_FMTCOLLSET;
        default: break;
    }

    return EMPTY_OUSTRING;
}

const OUString & SwRedlineAcceptDlg::GetActionText(const SwRangeRedline& rRedln, sal_uInt16 nStack)
{
    switch( rRedln.GetType(nStack) )
    {
        case RedlineType::Insert:   return m_sInserted;
        case RedlineType::Delete:   return m_sDeleted;
        case RedlineType::Format:   return m_sFormated;
        case RedlineType::ParagraphFormat:   return m_sFormated;
        case RedlineType::Table:    return m_sTableChgd;
        case RedlineType::FmtColl:  return m_sFormatCollSet;
        default:;//prevent warning
    }

    return EMPTY_OUSTRING;
}

// newly initialise after activation
void SwRedlineAcceptDlg::Activate()
{
    // prevent update if flag is set (#102547#)
    if( m_bInhibitActivate )
        return;

    SwView *pView = ::GetActiveView();
    if (!pView) // can happen when switching to another app
    {
        m_pTPView->EnableAccept(false);
        m_pTPView->EnableReject(false);
        m_pTPView->EnableClearFormat(false);
        m_pTPView->EnableAcceptAll(false);
        m_pTPView->EnableRejectAll(false);
        m_pTPView->EnableClearFormatAll(false);
        return; // had the focus previously
    }

    SwWait aWait( *pView->GetDocShell(), false );

    if (pView->GetDocShell()->IsReadOnly())
    {
        m_pTPView->EnableAccept(false);
        m_pTPView->EnableReject(false);
        m_pTPView->EnableClearFormat(false);
        m_pTPView->EnableAcceptAll(false);
        m_pTPView->EnableRejectAll(false);
        m_pTPView->EnableClearFormatAll(false);
        // note: enabling is done in EnableControls below
    }

    m_aUsedSeqNo.clear();

    // did something change?
    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return;

    // tdf#162018 keep the selected entry selected
    weld::TreeView& rTreeView = m_pTable->GetWidget();
    const SwRedlineData* pSelectedEntryRedlineData = lcl_get_selected_redlinedata(m_pTable->GetWidget());

    SwRedlineTable::size_type nCount = pSh->GetRedlineCount();

    // check the number of pointers
    for ( SwRedlineTable::size_type i = 0; i < nCount; i++)
    {
        const SwRangeRedline& rRedln = pSh->GetRedline(i);

        if (i >= m_RedlineParents.size())
        {
            // new entries have been appended
            Init(i);
            return;
        }

        SwRedlineDataParent *const pParent = m_RedlineParents[i].get();
        if (&rRedln.GetRedlineData() != pParent->pData)
        {
            // Redline-Parents were inserted, changed or deleted
            i = CalcDiff(i, false);
            if (i == SwRedlineTable::npos)
            {
                lcl_reselect(rTreeView, pSelectedEntryRedlineData);
                return;
            }
            continue;
        }

        const SwRedlineData *pRedlineData = rRedln.GetRedlineData().Next();
        const SwRedlineDataChild *pBackupData = pParent->pNext;

        if (!pRedlineData && pBackupData)
        {
            // Redline-Children were deleted
            i = CalcDiff(i, true);
            if (i == SwRedlineTable::npos)
            {
                lcl_reselect(rTreeView, pSelectedEntryRedlineData);
                return;
            }
            continue;
        }
        else
        {
            while (pRedlineData)
            {
                if (!pBackupData || pRedlineData != pBackupData->pChild)
                {
                    // Redline-Children were inserted, changed or deleted
                    i = CalcDiff(i, true);
                    if (i == SwRedlineTable::npos)
                    {
                        lcl_reselect(rTreeView, pSelectedEntryRedlineData);
                        return;
                    }

                    // here was a continue; targetted to the outer loop
                    // now a break will do, as there is nothing after it in the outer loop
                    break;
                }
                pBackupData = pBackupData->pNext;
                pRedlineData = pRedlineData->Next();
            }
        }
    }

    if (nCount != m_RedlineParents.size())
    {
        // Redlines were deleted at the end
        Init(nCount);
        return;
    }

    // check comment
    bool bIsShowChangesInMargin = SwModule::get()->GetUsrPref(false)->IsShowChangesInMargin();
    for (SwRedlineTable::size_type i = 0; i < nCount; i++)
    {
        const SwRangeRedline& rRedln = pSh->GetRedline(i);
        SwRedlineDataParent *const pParent = m_RedlineParents[i].get();

        if(rRedln.GetComment() != pParent->sComment)
        {
            bool bShowDeletedTextAsComment = bIsShowChangesInMargin &&
                RedlineType::Delete == rRedln.GetType() && rRedln.GetComment().isEmpty();
            const OUString sComment = bShowDeletedTextAsComment
                    ? rRedln.GetDescr()
                    : rRedln.GetComment();
            if (pParent->xTLBParent)
            {
                // update only comment
                rTreeView.set_text(*pParent->xTLBParent, sComment.replace('\n', ' '), 3);
            }
            pParent->sComment = sComment;
        }
    }

    InitAuthors();

    lcl_reselect(rTreeView, pSelectedEntryRedlineData);
    EnableControls(pView);
}

void SwRedlineAcceptDlg::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
{
    if (rHint.GetId() == SfxHintId::SwRedlineContentAtPos)
    {
        SwView* pView = GetActiveView();
        if (!pView)
            return;

        SwWrtShell* pSh = pView->GetWrtShellPtr();
        if (!pSh)
            return;

        const SwRangeRedline* pRangeRedline = pSh->GetCurrRedline();
        if (!pRangeRedline)
            return;

        const SwRedlineData& rRedlineData = pRangeRedline->GetRedlineData();

        weld::TreeView& rTreeView = m_pTable->GetWidget();
        rTreeView.all_foreach([&rTreeView, &rRedlineData](weld::TreeIter& rIter) {
            RedlinData* pRedlinData = weld::fromId<RedlinData*>(rTreeView.get_id(rIter));
            const SwRedlineData* pRedlineData;
            if (rTreeView.get_iter_depth(rIter))
                pRedlineData = static_cast<SwRedlineDataChild*>(pRedlinData->pData)->pChild;
            else
                pRedlineData = static_cast<SwRedlineDataParent*>(pRedlinData->pData)->pData;
            if (pRedlineData == &rRedlineData)
            {
                rTreeView.set_cursor(rIter);
                return true;
            }
            return false;
        });
    }
}

SwRedlineTable::size_type SwRedlineAcceptDlg::CalcDiff(SwRedlineTable::size_type nStart, bool bChild)
{
    if (!nStart || m_bHasTrackedColumn)
    {
        Init();
        return SwRedlineTable::npos;
    }

    weld::TreeView& rTreeView = m_pTable->GetWidget();
    rTreeView.freeze();
    SwView *pView = ::GetActiveView();
    if (!pView)
        return SwRedlineTable::npos;

    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return SwRedlineTable::npos;

    bool bHasRedlineAutoFormat = HasRedlineAutoFormat();
    SwRedlineDataParent *const pParent = m_RedlineParents[nStart].get();
    const SwRangeRedline& rRedln = pSh->GetRedline(nStart);

    if (bChild)     // should actually never happen, but just in case...
    {
        // throw away all entry's children and initialise newly
        SwRedlineDataChild* pBackupData = const_cast<SwRedlineDataChild*>(pParent->pNext);
        SwRedlineDataChild* pNext;

        while (pBackupData)
        {
            pNext = const_cast<SwRedlineDataChild*>(pBackupData->pNext);
            if (pBackupData->xTLBChild)
                rTreeView.remove(*pBackupData->xTLBChild);

            auto it = std::find_if(m_RedlineChildren.begin(), m_RedlineChildren.end(),
                [&pBackupData](const std::unique_ptr<SwRedlineDataChild>& rChildPtr) { return rChildPtr.get() == pBackupData; });
            if (it != m_RedlineChildren.end())
                m_RedlineChildren.erase(it);

            pBackupData = pNext;
        }
        pParent->pNext = nullptr;

        // insert new children
        InsertChildren(pParent, rRedln, bHasRedlineAutoFormat);

        rTreeView.thaw();
        return nStart;
    }

    // have entries been deleted?
    const SwRedlineData *pRedlineData = &rRedln.GetRedlineData();
    for (SwRedlineTable::size_type i = nStart + 1; i < m_RedlineParents.size(); i++)
    {
        if (m_RedlineParents[i]->pData == pRedlineData)
        {
            // remove entries from nStart to i-1
            RemoveParents(nStart, i - 1);
            rTreeView.thaw();
            return nStart - 1;
        }
    }

    // entries been inserted?
    SwRedlineTable::size_type nCount = pSh->GetRedlineCount();
    pRedlineData = m_RedlineParents[nStart]->pData;

    for (SwRedlineTable::size_type i = nStart + 1; i < nCount; i++)
    {
        if (&pSh->GetRedline(i).GetRedlineData() == pRedlineData)
        {
            rTreeView.thaw();
            // insert entries from nStart to i-1
            InsertParents(nStart, i - 1);
            return nStart - 1;
        }
    }

    rTreeView.thaw();
    Init(nStart);   // adjust all entries until the end
    return SwRedlineTable::npos;
}

void SwRedlineAcceptDlg::InsertChildren(SwRedlineDataParent *pParent, const SwRangeRedline& rRedln, bool bHasRedlineAutoFormat)
{
    SwRedlineDataChild *pLastRedlineChild = nullptr;
    const SwRedlineData *pRedlineData = &rRedln.GetRedlineData();
    bool bAutoFormatRedline = rRedln.IsAutoFormat();

    weld::TreeView& rTreeView = m_pTable->GetWidget();

    OUString sAction = GetActionText(rRedln);
    bool bValidParent = m_sFilterAction.isEmpty() || m_sFilterAction == sAction;
    bValidParent = bValidParent && m_pTable->IsValidEntry(rRedln.GetAuthorString(), rRedln.GetTimeStamp(), rRedln.GetComment());
    if (bHasRedlineAutoFormat)
    {

        if (pParent->pData->GetSeqNo())
        {
            std::pair<SwRedlineDataParentSortArr::const_iterator,bool> const ret
                = m_aUsedSeqNo.insert(pParent);
            if (ret.second) // already there
            {
                if (pParent->xTLBParent)
                {
                    rTreeView.set_text(*(*ret.first)->xTLBParent, m_sAutoFormat, 0);
                    rTreeView.remove(*pParent->xTLBParent);
                    pParent->xTLBParent.reset();
                }
                return;
            }
        }
        bValidParent = bValidParent && bAutoFormatRedline;
    }
    bool bValidTree = bValidParent;

    for (sal_uInt16 nStack = 1; nStack < rRedln.GetStackCount(); nStack++)
    {
        pRedlineData = pRedlineData->Next();

        SwRedlineDataChild* pRedlineChild = new SwRedlineDataChild;
        pRedlineChild->pChild = pRedlineData;
        m_RedlineChildren.push_back(std::unique_ptr<SwRedlineDataChild>(pRedlineChild));

        if ( pLastRedlineChild )
            pLastRedlineChild->pNext = pRedlineChild;
        else
            pParent->pNext = pRedlineChild;

        sAction = GetActionText(rRedln, nStack);
        bool bValidChild = m_sFilterAction.isEmpty() || m_sFilterAction == sAction;
        bValidChild = bValidChild && m_pTable->IsValidEntry(rRedln.GetAuthorString(nStack), rRedln.GetTimeStamp(nStack), rRedln.GetComment());
        if (bHasRedlineAutoFormat)
            bValidChild = bValidChild && bAutoFormatRedline;
        bValidTree |= bValidChild;

        if (bValidChild)
        {
            std::unique_ptr<RedlinData> pData(new RedlinData);
            pData->pData = pRedlineChild;
            pData->bDisabled = true;

            OUString sImage(GetActionImage(rRedln, nStack));
            const OUString& sAuthor = rRedln.GetAuthorString(nStack);
            pData->aDateTime = rRedln.GetTimeStamp(nStack);
            pData->eType = rRedln.GetType(nStack);
            OUString sDateEntry = GetAppLangDateTimeString(pData->aDateTime);
            const OUString& sComment = rRedln.GetComment(nStack);

            std::unique_ptr<weld::TreeIter> xChild(rTreeView.make_iterator());
            OUString sId(weld::toId(pData.get()));
            rTreeView.insert(pParent->xTLBParent.get(), -1, nullptr, &sId, nullptr, nullptr,
                             false, xChild.get());
            m_RedlinData.push_back(std::move(pData));

            rTreeView.set_image(*xChild, sImage, -1);
            rTreeView.set_text(*xChild, sAuthor, 1);
            rTreeView.set_text(*xChild, sDateEntry, 2);
            rTreeView.set_text(*xChild, sComment, 3);

            pRedlineChild->xTLBChild = std::move(xChild);
            if (!bValidParent)
                rTreeView.expand_row(*pParent->xTLBParent);
        }
        else
            pRedlineChild->xTLBChild.reset();

        pLastRedlineChild = pRedlineChild;
    }

    if (pLastRedlineChild)
        pLastRedlineChild->pNext = nullptr;

    if (!bValidTree && pParent->xTLBParent)
    {
        rTreeView.remove(*pParent->xTLBParent);
        pParent->xTLBParent.reset();
        if (bHasRedlineAutoFormat)
            m_aUsedSeqNo.erase(pParent);
    }
}

void SwRedlineAcceptDlg::RemoveParents(SwRedlineTable::size_type nStart, SwRedlineTable::size_type nEnd)
{
    SwView *pView = ::GetActiveView();
    if (!pView)
        return;

    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return;

    SwRedlineTable::size_type nCount = pSh->GetRedlineCount();

    std::vector<const weld::TreeIter*> aLBoxArr;

    weld::TreeView& rTreeView = m_pTable->GetWidget();

    // because of Bug of TLB that ALWAYS calls the SelectHandler at Remove:
    rTreeView.connect_selection_changed(Link<weld::TreeView&, void>());

    bool bChildrenRemoved = false;
    rTreeView.thaw();
    rTreeView.unselect_all();

    // set the cursor after the last entry because otherwise performance problem in TLB.
    // TLB would otherwise reset the cursor at every Remove (expensive)
    SwRedlineTable::size_type nPos = std::min(nCount, m_RedlineParents.size());
    weld::TreeIter *pCurEntry = nullptr;
    while( ( pCurEntry == nullptr ) && ( nPos > 0 ) )
    {
        --nPos;
        pCurEntry = m_RedlineParents[nPos]->xTLBParent.get();
    }

    if (pCurEntry)
        rTreeView.set_cursor(*pCurEntry);

    rTreeView.freeze();

    for (SwRedlineTable::size_type i = nStart; i <= nEnd; i++)
    {
        if (!bChildrenRemoved && m_RedlineParents[i]->pNext)
        {
            SwRedlineDataChild * pChildPtr =
                const_cast<SwRedlineDataChild*>(m_RedlineParents[i]->pNext);
            auto it = std::find_if(m_RedlineChildren.begin(), m_RedlineChildren.end(),
                [&pChildPtr](const std::unique_ptr<SwRedlineDataChild>& rChildPtr) { return rChildPtr.get() == pChildPtr; });
            if (it != m_RedlineChildren.end())
            {
                sal_uInt16 nChildren = 0;
                while (pChildPtr)
                {
                    pChildPtr = const_cast<SwRedlineDataChild*>(pChildPtr->pNext);
                    nChildren++;
                }

                m_RedlineChildren.erase(it, it + nChildren);
                bChildrenRemoved = true;
            }
        }
        weld::TreeIter *const pEntry = m_RedlineParents[i]->xTLBParent.get();
        if (pEntry)
            aLBoxArr.push_back(pEntry);
    }

    std::sort(aLBoxArr.begin(), aLBoxArr.end(), [&rTreeView](const weld::TreeIter* a, const weld::TreeIter* b) {
        return rTreeView.iter_compare(*a, *b) == -1;
    });
    // clear TLB from behind
    for (auto it = aLBoxArr.rbegin(); it != aLBoxArr.rend(); ++it)
    {
        const weld::TreeIter* pIter = *it;
        rTreeView.remove(*pIter);
    }

    rTreeView.thaw();
    rTreeView.connect_selection_changed(LINK(this, SwRedlineAcceptDlg, SelectHdl));
    // unfortunately by Remove it was selected from the TLB always again ...
    rTreeView.unselect_all();
    rTreeView.freeze();

    m_RedlineParents.erase(m_RedlineParents.begin() + nStart, m_RedlineParents.begin() + nEnd + 1);
}

void SwRedlineAcceptDlg::InsertParents(SwRedlineTable::size_type nStart, SwRedlineTable::size_type nEnd)
{
    SwView *pView = ::GetActiveView();
    if (!pView)
        return;

    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return;

    bool bHasRedlineAutoFormat = HasRedlineAutoFormat();

    SwRedlineTable::size_type nCount = pSh->GetRedlineCount();
    nEnd = std::min(nEnd, (nCount - 1)); // also treats nEnd=SwRedlineTable::npos (until the end)

    // reset m_bHasTrackedColumn before searching tracked column again
    if ( m_bHasTrackedColumn && nStart == 0 )
        m_bHasTrackedColumn = false;

    if (nEnd == SwRedlineTable::npos)
        return;     // no redlines in the document

    weld::TreeView& rTreeView = m_pTable->GetWidget();

    SwRedlineDataParent* pRedlineParent;

    rTreeView.freeze();
    if (m_pTable->IsSorted())
        rTreeView.make_unsorted();

    bool bIsShowChangesInMargin = SwModule::get()->GetUsrPref(false)->IsShowChangesInMargin();

    // collect redlines of tracked table/row/column insertion/deletions under a single tree list
    // item to accept/reject the table change with a single click on Accept/Reject
    // Note: because update of the tree list depends on parent count, we don't modify
    // m_RedlineParents, only store the 2nd and more redlines as children of the tree list
    // item of the first redline

    // count of items stored as children (to adjust parent position)
    SwRedlineTable::size_type nSkipRedlines = 0;
    // count of items of the actual table change stored as children =
    // redlines of the change - 1 (first redline is associated to the parent tree list item)
    SwRedlineTable::size_type nSkipRedline = 0;

    // descriptor redline of the tracked table row/column
    SwRedlineTable::size_type nRowChange = 0;

    // first redlines of the tracked table rows/columns, base of the parent tree lists
    // of the other SwRangeRedlines of the tracked table rows or columns
    std::vector<SwRedlineTable::size_type> aTableParents;

    // show all redlines as tree list items,
    // redlines of a tracked table (row) insertion/deletion showed as children of a single parent
    for (SwRedlineTable::size_type i = nStart; i <= nEnd; i++)
    {
        const SwRangeRedline& rRedln = pSh->GetRedline(i);
        const SwRedlineData *pRedlineData = &rRedln.GetRedlineData();
        // redline is a child associated to this table row/column change
        SwRedlineTable::size_type nTableParent = SwRedlineTable::npos;

        pRedlineParent = new SwRedlineDataParent;
        pRedlineParent->pData    = pRedlineData;
        pRedlineParent->pNext    = nullptr;

        // handle tracked table row changes
        const SwTableBox* pTableBox;
        const SwTableLine* pTableLine;
        bool bChange = false;
        bool bRowChange = false;
        if ( // not recognized yet as tracked table row change
             nullptr != ( pTableBox = pSh->GetRedline(i).Start()->GetNode().GetTableBox() ) &&
             nullptr != ( pTableLine = pTableBox->GetUpper() ) &&
             // it's a tracked row (or column change) based on the cached row data
             ( RedlineType::None != pTableLine->GetRedlineType() ||
               RedlineType::None != pTableBox->GetRedlineType() ) )
        {
            // start redline search from the start from the tracked row/column change
            SwRedlineTable::size_type nStartPos =
                                        nRowChange > nSkipRedline ? nRowChange - nSkipRedline : 0;
            bChange = true;
            bRowChange = RedlineType::None != pTableLine->GetRedlineType();
            nRowChange = bRowChange
                            ? pTableLine->UpdateTextChangesOnly(nStartPos)
                            : pTableBox->GetRedline();
            // redline is there in a tracked table change
            if ( SwRedlineTable::npos != nRowChange )
            {
                // join the consecutive deleted/inserted rows/columns under a single treebox item,
                // if they have the same redline data (equal type, author and time stamp)
                for (size_t j = 0; j < aTableParents.size(); j++)
                {
                    // note: CanCombine() allows a time frame to join the changes within a short
                    // time period: this avoid of falling apart of the tracked columns inserted
                    // by several clicks
                    if ( pSh->GetRedline(nRowChange).GetRedlineData()
                             .CanCombine(pSh->GetRedline(aTableParents[j]).GetRedlineData()) )
                    {
                        nSkipRedline++;
                        nTableParent = aTableParents[j];
                        break;
                    }

                }

                if ( SwRedlineTable::npos == nTableParent )
                {
                    // table redline didn't fit in the stored ones, create new parent
                    aTableParents.push_back(i);
                }

                // it needs major tree update later because of tracked table columns
                if ( !m_bHasTrackedColumn && !bRowChange )
                {
                    m_bHasTrackedColumn = true;
                }
            }
            else
            {
                // redline is not in a tracked table change
                bChange = bRowChange = false;
            }
        }

        // empty parent cache for the last table
        if ( !pTableBox )
        {
            aTableParents.clear();
        }

        bool bShowDeletedTextAsComment = bIsShowChangesInMargin &&
                RedlineType::Delete == rRedln.GetType() && rRedln.GetComment().isEmpty();
        const OUString sComment = bShowDeletedTextAsComment
                    ? rRedln.GetDescr()
                    : rRedln.GetComment();
        pRedlineParent->sComment = sComment.replace('\n', ' ');
        m_RedlineParents.insert(m_RedlineParents.begin() + i,
                std::unique_ptr<SwRedlineDataParent>(pRedlineParent));

        std::unique_ptr<RedlinData> pData(new RedlinData);
        pData->pData = pRedlineParent;
        pData->bDisabled = false;

        // use descriptor SwRangeRedline of the changed row, if needed to show
        // the correct redline type, author and time stamp of the tracked row change
        const SwRangeRedline& rChangeRedln = pSh->GetRedline(bChange ? nRowChange : i);

        OUString sImage = GetActionImage(rChangeRedln, 0, bChange && aTableParents.back() == i, bRowChange );
        OUString sAuthor = rChangeRedln.GetAuthorString(0);
        pData->aDateTime = rChangeRedln.GetTimeStamp(0);
        pData->eType = rChangeRedln.GetType(0);
        OUString sDateEntry = GetAppLangDateTimeString(pData->aDateTime);

        OUString sId = weld::toId(pData.get());
        std::unique_ptr<weld::TreeIter> xParent(rTreeView.make_iterator());

        if ( !bChange || aTableParents.back() == i )
        {
            rTreeView.insert(nullptr, i - nSkipRedlines, nullptr, &sId, nullptr, nullptr, false, xParent.get());
            // before this was a tracked table change with more than a single redline
            if ( nSkipRedline > 0 )
            {
                nSkipRedlines += nSkipRedline;
                nSkipRedline = 0;
            }
        }
        else
        {
            // put 2nd or more redlines of deleted/inserted rows as children of their first redline
            SwRedlineDataParent *const pParent = m_RedlineParents[nTableParent].get();
            rTreeView.insert(pParent->xTLBParent.get(), -1, nullptr, &sId, nullptr, nullptr, false, xParent.get());
        }

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

        rTreeView.set_image(*xParent, sImage, -1);
        rTreeView.set_text(*xParent, sAuthor, 1);
        rTreeView.set_text(*xParent, sDateEntry, 2);
        rTreeView.set_text(*xParent, sComment, 3);

        pRedlineParent->xTLBParent = std::move(xParent);

        InsertChildren(pRedlineParent, rRedln, bHasRedlineAutoFormat);
    }
    rTreeView.thaw();
    if (m_pTable->IsSorted())
        rTreeView.make_sorted();
}

void SwRedlineAcceptDlg::CallAcceptReject( bool bSelect, bool bAccept )
{
    SwView *pView = ::GetActiveView();
    if (!pView)
        return;

    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return;

    int nPos = -1;

    typedef std::vector<std::unique_ptr<weld::TreeIter>> ListBoxEntries_t;
    ListBoxEntries_t aRedlines;

    // don't activate
    OSL_ENSURE( !m_bInhibitActivate,
                "recursive call of CallAcceptReject?");
    m_bInhibitActivate = true;

    weld::TreeView& rTreeView = m_pTable->GetWidget();

    auto lambda = [bSelect, &rTreeView, &nPos, &aRedlines](weld::TreeIter& rEntry) {
        if (!rTreeView.get_iter_depth(rEntry))
        {
            if (bSelect && nPos == -1)
                nPos = rTreeView.get_iter_index_in_parent(rEntry);

            RedlinData *pData = weld::fromId<RedlinData*>(rTreeView.get_id(rEntry));

            if (!pData->bDisabled)
                aRedlines.emplace_back(rTreeView.make_iterator(&rEntry));
        }
        return false;
    };

    // collect redlines-to-be-accepted/rejected in aRedlines vector
    if (bSelect)
        rTreeView.selected_foreach(lambda);
    else
        rTreeView.all_foreach(lambda);

    bool (SwEditShell::*FnAccRej)( SwRedlineTable::size_type, bool ) = &SwEditShell::AcceptRedline;
    if( !bAccept )
        FnAccRej = &SwEditShell::RejectRedline;

    SwWait aWait( *pSh->GetView().GetDocShell(), true );
    pSh->StartAction();

    bool bMoreRedlines( aRedlines.size() > 1 ||
        // single item with children, e.g. multiple redlines of a table or table row deletion/insertion
        ( aRedlines.size() == 1 && rTreeView.iter_n_children(*aRedlines[0]) > 0 ) );

    // don't add extra Undo label to a single item only with redline stack (i.e. old changes
    // on the same text range, stored only in OOXML)
    if ( bMoreRedlines && aRedlines.size() == 1 )
    {
        std::unique_ptr<weld::TreeIter> xChild(rTreeView.make_iterator( &*aRedlines[0] ));
        RedlinData *pData = weld::fromId<RedlinData*>(rTreeView.get_id(*xChild));
        if ( pData->bDisabled )
            bMoreRedlines = false;
    }

    if ( bMoreRedlines )
    {
        OUString aTmpStr;
        {
            SwRewriter aRewriter;
            aRewriter.AddRule(UndoArg1,
                              OUString::number(aRedlines.size()));
            aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES));
        }

        SwRewriter aRewriter;
        aRewriter.AddRule(UndoArg1, aTmpStr);

        pSh->StartUndo(bAccept? SwUndoId::ACCEPT_REDLINE : SwUndoId::REJECT_REDLINE,
                       &aRewriter);
    }

    // accept/reject the redlines in aRedlines. The absolute
    // position may change during the process (e.g. when two redlines
    // are merged in result of another one being deleted), so the
    // position must be resolved late and checked before using it.
    // (cf #102547#)
    for (const auto& rRedLine : aRedlines)
    {
        SwRedlineTable::size_type nPosition = GetRedlinePos( *rRedLine );

        // bSelect is false for "accept/reject all", true when only accepting/rejecting one or more
        // selected changes. Only use direct accept/reject for explicitly selected changes.
        bool bDirect = bSelect;

        if( nPosition != SwRedlineTable::npos )
            (pSh->*FnAccRej)( nPosition, bDirect );

        // handle redlines of table rows, stored as children of the item associated
        // to the deleted/inserted table row(s)
        std::unique_ptr<weld::TreeIter> xChild(rTreeView.make_iterator( &*rRedLine ));
        if ( rTreeView.iter_children(*xChild) )
        {
            RedlinData *pData = weld::fromId<RedlinData*>(rTreeView.get_id(*xChild));
            // disabled for redline stack, but not for redlines of table rows
            if ( !pData->bDisabled )
            {
                do
                {
                    nPosition = GetRedlinePos( *xChild );
                    if( nPosition != SwRedlineTable::npos )
                        (pSh->*FnAccRej)( nPosition, bDirect );
                }
                while ( rTreeView.iter_next_sibling(*xChild) );
            }
        }
    }

    if ( bMoreRedlines )
    {
        pSh->EndUndo();
    }

    pSh->EndAction();

    m_bInhibitActivate = false;
    Activate();

    if (nPos != -1 && rTreeView.n_children())
    {
        if (nPos >= rTreeView.n_children())
            nPos = rTreeView.n_children() - 1;
        rTreeView.select(nPos);
        rTreeView.scroll_to_row(nPos);
        rTreeView.set_cursor(nPos);
        SelectHdl(rTreeView);
    }
    m_pTPView->EnableUndo();
}

SwRedlineTable::size_type SwRedlineAcceptDlg::GetRedlinePos(const weld::TreeIter& rEntry)
{
    SwView* pView = GetActiveView();
    if (!pView)
        return SwRedlineTable::npos;

    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return SwRedlineTable::npos;

    weld::TreeView& rTreeView = m_pTable->GetWidget();
    return pSh->FindRedlineOfData( *static_cast<SwRedlineDataParent*>(weld::fromId<RedlinData*>(
                                    rTreeView.get_id(rEntry))->pData)->pData );
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, SortByComboBoxChangedHdl, SvxTPView*, void)
{
    SwView* pView = GetActiveView();
    if (!pView)
        return;
    SwWait aWait(*pView->GetDocShell(), false);
    auto nSortMode = m_pTPView->GetSortByComboBoxControl()->get_active();
    if (nSortMode == 4)
        nSortMode = -1;
    m_pTable->HeaderBarClick(nSortMode);
    if (nSortMode == -1)
        Init();
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, AcceptHdl, SvxTPView*, void)
{
    CallAcceptReject( true, true );
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, AcceptAllHdl, SvxTPView*, void)
{
    CallAcceptReject( false, true );
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, RejectHdl, SvxTPView*, void)
{
    CallAcceptReject( true, false );
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, RejectAllHdl, SvxTPView*, void)
{
    CallAcceptReject( false, false );
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, UndoHdl, SvxTPView*, void)
{
    if (SwView* pView = GetActiveView())
    {
        pView->GetViewFrame().GetDispatcher()->
                    Execute(SID_UNDO, SfxCallMode::SYNCHRON);
        const SfxPoolItemHolder aResult(pView->GetSlotState(SID_UNDO));
        m_pTPView->EnableUndo(aResult.is());
    }

    Activate();
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, FilterChangedHdl, SvxTPFilter*, void)
{
    SvxTPFilter *pFilterTP = m_xTabPagesCTRL->GetFilterPage();

    if (pFilterTP->IsAction())
        m_sFilterAction = pFilterTP->GetLbAction()->get_active_text();
    else
        m_sFilterAction.clear();

    Init();
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, SelectHdl, weld::TreeView&, void)
{
    m_aSelectTimer.Start();
}

IMPL_LINK_NOARG(SwRedlineAcceptDlg, GotoHdl, Timer *, void)
{
    m_aSelectTimer.Stop();

    SwView* pView = GetActiveView();
    if (!pView)
        return;

    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return;

    //#98883# don't select redlines while the dialog is not focused
    //#107938# But not only ask pTable if it has the focus. To move
    //         the selection to the selected redline any child of pParentDlg
    //         may the focus.
    if (!m_xParentDlg || m_xParentDlg->has_toplevel_focus())
    {
        weld::TreeView& rTreeView = m_pTable->GetWidget();
        std::unique_ptr<weld::TreeIter> xActEntry(rTreeView.make_iterator());
        if (rTreeView.get_selected(xActEntry.get()))
        {
            pSh->StartAction();
            pSh->EnterStdMode();
            SwViewShell::SetCareDialog(m_xParentDlg);

            rTreeView.selected_foreach([this, pSh, &rTreeView, &xActEntry](weld::TreeIter& rEntry){
                rTreeView.copy_iterator(rEntry, *xActEntry);
                if (rTreeView.get_iter_depth(rEntry))
                {
                    rTreeView.iter_parent(*xActEntry);
                    if (rTreeView.is_selected(*xActEntry))
                        return false;   // don't select twice
                }

                // #98864# find the selected redline (ignore, if the redline is already gone)
                SwRedlineTable::size_type nPos = GetRedlinePos(*xActEntry);
                if (nPos != SwRedlineTable::npos)
                {
                    if (pSh->GotoRedline(nPos, true))
                    {
                        pSh->SetInSelect();
                        pSh->EnterAddMode();
                    }
                }

                // select all redlines of tracked table rows
                std::unique_ptr<weld::TreeIter> xChild(rTreeView.make_iterator( &*xActEntry ));
                if ( rTreeView.iter_children(*xChild) )
                {
                    RedlinData *pData = reinterpret_cast<RedlinData*>(rTreeView.get_id(*xChild).toInt64());
                    // disabled for redline stack, but not for redlines of table rows
                    if ( !pData->bDisabled )
                    {
                        do
                        {
                            nPos = GetRedlinePos(*xChild);
                            if (nPos != SwRedlineTable::npos)
                            {
                                if (pSh->GotoRedline(nPos, true))
                                {
                                    pSh->SetInSelect();
                                    pSh->EnterAddMode();
                                }
                            }
                        }
                        while ( rTreeView.iter_next_sibling(*xChild) );
                    }
                }
                return false;
            });

            pSh->LeaveAddMode();
            pSh->EndAction();
            SwViewShell::SetCareDialog(nullptr);
        }
    }

    EnableControls(pView);
}

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

    SwView* pView = GetActiveView();
    if (!pView)
        return false;

    SwWrtShell* pSh = pView->GetWrtShellPtr();
    if (!pSh)
        return false;

    const SwRangeRedline *pRed = nullptr;

    weld::TreeView& rTreeView = m_pTable->GetWidget();
    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    bool bEntry = rTreeView.get_selected(xEntry.get());
    if (bEntry)
    {
        std::unique_ptr<weld::TreeIter> xTopEntry(rTreeView.make_iterator(xEntry.get()));

        if (rTreeView.get_iter_depth(*xTopEntry))
            rTreeView.iter_parent(*xTopEntry);

        SwRedlineTable::size_type nPos = GetRedlinePos(*xTopEntry);

        // disable commenting for protected areas
        if (nPos != SwRedlineTable::npos && (pRed = pSh->GotoRedline(nPos, true)) != nullptr)
        {
            if( pSh->IsCursorPtAtEnd() )
                pSh->SwapPam();
            pSh->SetInSelect();
        }
    }

    m_xPopup->set_sensitive(u"writeredit"_ustr, bEntry && pRed &&
                                          !rTreeView.get_iter_depth(*xEntry) &&
                                          rTreeView.count_selected_rows() == 1);
    m_xPopup->set_sensitive(u"writersort"_ustr, rTreeView.n_children() != 0);
    int nColumn = rTreeView.get_sort_column();
    if (nColumn == -1)
        nColumn = 4;
    for (sal_Int32 i = 0; i < 5; ++i)
        m_xSortMenu->set_active(u"writersort" + OUString::number(i), i == nColumn);

    OUString sCommand = m_xPopup->popup_at_rect(&rTreeView, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));

    if (sCommand == "writeredit")
    {
        if (bEntry)
        {
            if (rTreeView.get_iter_depth(*xEntry))
                rTreeView.iter_parent(*xEntry);

            SwRedlineTable::size_type nPos = GetRedlinePos(*xEntry);

            if (nPos == SwRedlineTable::npos)
                return true;

            const SwRangeRedline &rRedline = pSh->GetRedline(nPos);

            OUString sComment = convertLineEnd(rRedline.GetComment(), GetSystemLineEnd());
            SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
            ::DialogGetRanges fnGetRange = pFact->GetDialogGetRangesFunc();
            SfxItemSet aSet( pSh->GetAttrPool(), fnGetRange() );

            aSet.Put(SvxPostItTextItem(sComment, SID_ATTR_POSTIT_TEXT));
            aSet.Put(SvxPostItAuthorItem(rRedline.GetAuthorString(), SID_ATTR_POSTIT_AUTHOR));

            aSet.Put(SvxPostItDateItem( GetAppLangDateTimeString(
                        rRedline.GetRedlineData().GetTimeStamp() ),
                        SID_ATTR_POSTIT_DATE ));

            ScopedVclPtr<AbstractSvxPostItDialog> pDlg(pFact->CreateSvxPostItDialog(&rTreeView, aSet));

            pDlg->HideAuthor();

            TranslateId pResId;
            switch( rRedline.GetType() )
            {
                case RedlineType::Insert:
                    pResId = STR_REDLINE_INSERTED;
                    break;
                case RedlineType::Delete:
                    pResId = STR_REDLINE_DELETED;
                    break;
                case RedlineType::Format:
                case RedlineType::ParagraphFormat:
                    pResId = STR_REDLINE_FORMATTED;
                    break;
                case RedlineType::Table:
                    pResId = STR_REDLINE_TABLECHG;
                    break;
                default:;//prevent warning
            }
            OUString sTitle(SwResId(STR_REDLINE_COMMENT));
            if (pResId)
                sTitle += SwResId(pResId);
            pDlg->SetText(sTitle);

            SwViewShell::SetCareDialog(pDlg->GetDialog());

            if ( pDlg->Execute() == RET_OK )
            {
                const SfxItemSet* pOutSet = pDlg->GetOutputItemSet();
                OUString sMsg(pOutSet->Get(SID_ATTR_POSTIT_TEXT).GetValue());

                // insert / change comment
                pSh->SetRedlineComment(sMsg);
                rTreeView.set_text(*xEntry, sMsg.replace('\n', ' '), 3);
            }

            SwViewShell::SetCareDialog(nullptr);
            pDlg.disposeAndClear();
        }
    }
    else if (!sCommand.isEmpty())
    {
        int nSortMode = o3tl::toInt32(sCommand.subView(10));

        if (nSortMode == 4 && nColumn == 4)
            return true;  // we already have it

        m_pTPView->GetSortByComboBoxControl()->set_active(nSortMode);

        if (nSortMode == 4)
            nSortMode = -1; // unsorted / sorted by position

        SwWait aWait( *pView->GetDocShell(), false );
        m_pTable->HeaderBarClick(nSortMode);
        if (nSortMode == -1)
            Init();             // newly fill everything
    }
    return true;
}

namespace
{
    OUString lcl_StripAcceptChgDat(OUString &rExtraString)
    {
        OUString aStr;
        while(true)
        {
            sal_Int32 nPos = rExtraString.indexOf("AcceptChgDat:");
            if (nPos == -1)
                break;
            // try to read the alignment string "ALIGN:(...)"; if none existing,
            // it's an old version
            sal_Int32 n1 = rExtraString.indexOf('(', nPos);
            if (n1 != -1)
            {
                sal_Int32 n2 = rExtraString.indexOf(')', n1);
                if (n2 != -1)
                {
                    // cut out the alignment string
                    aStr = rExtraString.copy(nPos, n2 - nPos + 1);
                    rExtraString = rExtraString.replaceAt(nPos, n2 - nPos + 1, u"");
                    aStr = aStr.copy(n1 - nPos + 1);
                }
            }
        }
        return aStr;
    }
}

void SwRedlineAcceptDlg::Initialize(OUString& rExtraString)
{
    if (rExtraString.isEmpty())
        return;

    OUString aStr = lcl_StripAcceptChgDat(rExtraString);
    if (aStr.isEmpty())
        return;

    int nCount = aStr.toInt32();
    if (nCount <= 2)
        return;

    std::vector<int> aEndPos;

    for (int i = 0; i < nCount; ++i)
    {
        sal_Int32 n1 = aStr.indexOf(';');
        aStr = aStr.copy( n1+1 );
        aEndPos.push_back(aStr.toInt32());
    }

    bool bUseless = false;

    std::vector<int> aWidths;
    for (int i = 1; i < nCount; ++i)
    {
        aWidths.push_back(aEndPos[i] - aEndPos[i - 1]);
        if (aWidths.back() <= 0)
            bUseless = true;
    }

    if (!bUseless)
    {
        // turn column end points back to column widths, ignoring the small
        // value used for the expander column
        weld::TreeView& rTreeView = m_pTable->GetWidget();
        rTreeView.set_column_fixed_widths(aWidths);
    }
}

void SwRedlineAcceptDlg::FillInfo(OUString &rExtraData) const
{
    //remove any old one before adding a new one
    lcl_StripAcceptChgDat(rExtraData);
    rExtraData += "AcceptChgDat:(";

    const int nTabCount = 4;

    rExtraData += OUString::number(nTabCount);
    rExtraData += ";";

    weld::TreeView& rTreeView = m_pTable->GetWidget();
    std::vector<int> aWidths;
    // turn column widths back into column end points for compatibility
    // with how they used to be stored, including a small value for the
    // expander column
    aWidths.push_back(rTreeView.get_checkbox_column_width());
    for (int i = 0; i < nTabCount - 1; ++i)
    {
        int nWidth = rTreeView.get_column_width(i);
        assert(nWidth > 0 && "suspicious to get a value like this");
        aWidths.push_back(aWidths.back() + nWidth);
    }

    for (auto a : aWidths)
    {
        rExtraData += OUString::number(a);
        rExtraData += ";";
    }
    rExtraData += ")";
}

SwRedlineAcceptPanel::SwRedlineAcceptPanel(weld::Widget* pParent)
    : PanelLayout(pParent, u"ManageChangesPanel"_ustr, u"modules/swriter/ui/managechangessidebar.ui"_ustr)
    , mxContentArea(m_xBuilder->weld_container(u"content_area"_ustr))
{
    mpImplDlg.reset(new SwRedlineAcceptDlg(nullptr, m_xBuilder.get(), mxContentArea.get()));

    mpImplDlg->Init();

    // we want to receive SfxHintId::DocChanged
    StartListening(*(SwModule::get()->GetView()->GetDocShell()));
}

SwRedlineAcceptPanel::~SwRedlineAcceptPanel()
{
}

void SwRedlineAcceptPanel::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
{
    if (mpImplDlg && rHint.GetId() == SfxHintId::DocChanged)
        mpImplDlg->Activate();
}

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