/* -*- 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 <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);
}
 
// newly initialise dialog after document switch
bool SwRedlineAcceptChild::ReInitDlg(SwDocShell *pDocSh)
{
    bool bRet = SwChildWinWrapper::ReInitDlg(pDocSh);
    if (bRet)  // update immediately, doc switch!
        static_cast<SwModelessRedlineAcceptDlg*>(GetController().get())->Activate();
 
    return bRet;
}
 
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(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_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(*(SW_MOD()->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);
}
 
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;
    bool bIsNotFormated = false;
 
    // 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]);
 
    weld::TreeView& rTreeView = m_pTable->GetWidget();
    SwDocShell* pShell = pSh ? pSh->GetDoc()->GetDocShell() : nullptr;
    bool const bEnable = pShell && !pShell->IsReadOnly()
        && rTreeView.n_children() != 0
        && !pSh->getIDocumentRedlineAccess().GetRedlinePassword().hasElements();
    bool bSel = rTreeView.get_selected(nullptr);
 
    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 InitAuthors 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 = SW_MOD()->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
                    ? const_cast<SwRangeRedline&>(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);
}
 
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_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_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 = SW_MOD()->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
                    ? const_cast<SwRangeRedline&>(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 = [this, pSh, bSelect, bAccept, &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));
 
            bool bIsNotFormatted = true;
 
            // first remove only changes with insertion/deletion, if they exist
            // (format-only changes haven't had real rejection yet, only an
            // approximation: clear direct formatting, so try to warn
            // with the extended button label "Reject All/Clear formatting")
            if ( !bSelect && !bAccept && !m_bOnlyFormatedRedlines )
            {
                SwRedlineTable::size_type nPosition = GetRedlinePos(rEntry);
                const SwRangeRedline& rRedln = pSh->GetRedline(nPosition);
 
                if( RedlineType::Format == rRedln.GetType() )
                    bIsNotFormatted = false;
            }
 
            if (!pData->bDisabled && bIsNotFormatted)
                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 ) = &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 );
        if( nPosition != SwRedlineTable::npos )
            (pSh->*FnAccRej)( nPosition );
 
        // 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 );
                }
                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;
 
    bool bIsNotFormated = false;
    bool bSel = false;
 
    //#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, &bIsNotFormated, &bSel](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
                }
                else
                    bSel = true;
 
                // #98864# find the selected redline (ignore, if the redline is already gone)
                SwRedlineTable::size_type nPos = GetRedlinePos(*xActEntry);
                if (nPos != SwRedlineTable::npos)
                {
 
                    const SwRangeRedline& rRedln = pSh->GetRedline( nPos );
                    bIsNotFormated |= RedlineType::Format != rRedln.GetType();
 
                    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)
                            {
                                const SwRangeRedline& rRedln = pSh->GetRedline( nPos );
                                bIsNotFormated |= RedlineType::Format != rRedln.GetType();
 
                                if (pSh->GotoRedline(nPos, true))
                                {
                                    pSh->SetInSelect();
                                    pSh->EnterAddMode();
                                }
                            }
                        }
                        while ( rTreeView.iter_next_sibling(*xChild) );
                    }
                }
                return false;
            });
 
            pSh->LeaveAddMode();
            pSh->EndAction();
            SwViewShell::SetCareDialog(nullptr);
        }
    }
 
    SwDocShell* pShell = pSh ? pSh->GetDoc()->GetDocShell() : nullptr;
    bool const bEnable = pShell && !pShell->IsReadOnly()
        && !pSh->getIDocumentRedlineAccess().GetRedlinePassword().hasElements();
    m_pTPView->EnableAccept( bEnable && bSel /*&& !bReadonlySel*/ );
    m_pTPView->EnableReject( bEnable && bSel /*&& !bReadonlySel*/ );
    m_pTPView->EnableClearFormat( bEnable && bSel && !bIsNotFormated /*&& !bReadonlySel*/ );
    m_pTPView->EnableAcceptAll( bEnable );
    m_pTPView->EnableRejectAll( bEnable );
    m_pTPView->EnableClearFormatAll( bEnable && m_bOnlyFormatedRedlines );
}
 
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(*(SW_MOD()->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: */

V530 The return value of function 'ExecuteList' is required to be utilized.

V530 The return value of function 'Execute' is required to be utilized.

V530 The return value of function 'lcl_StripAcceptChgDat' is required to be utilized.

V595 The 'pSh' pointer was utilized before it was verified against nullptr. Check lines: 1392, 1397.

V657 It's odd that this function always returns one and the same value.

V1004 The 'pSh' pointer was used unsafely after it was verified against nullptr. Check lines: 340, 374.

V1004 The 'pSh' pointer was used unsafely after it was verified against nullptr. Check lines: 1397, 1399.