/* -*- 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 <svl/undo.hxx>
#include <unotools/textsearch.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/collatorwrapper.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/basedlgs.hxx>
#include <vcl/commandevent.hxx>
#include <o3tl/string_view.hxx>

#include <acredlin.hxx>
#include <global.hxx>
#include <reffact.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <scresid.hxx>
#include <strings.hrc>
#include <scmod.hxx>
#include <tabvwsh.hxx>

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

#define RD_SPECIAL_NONE         0
#define RD_SPECIAL_CONTENT      1
#define RD_SPECIAL_VISCONTENT   2


ScRedlinData::ScRedlinData()
{
    nInfo=RD_SPECIAL_NONE;
    nActionNo=0;
    pData=nullptr;
    bDisabled=false;
    bIsRejectable=false;
    bIsAcceptable=false;
    nTable=SCTAB_MAX;
    nCol=SCCOL_MAX;
    nRow=SCROW_MAX;
}

ScRedlinData::~ScRedlinData()
{
    nInfo=RD_SPECIAL_NONE;
    nActionNo=0;
    pData=nullptr;
    bDisabled=false;
    bIsRejectable=false;
    bIsAcceptable=false;
}


ScAcceptChgDlg::ScAcceptChgDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent,
    ScViewData& rViewData)
    : SfxModelessDialogController(pB, pCW, pParent,
        u"svx/ui/acceptrejectchangesdialog.ui"_ustr, u"AcceptRejectChangesDialog"_ustr)
    , aSelectionIdle( "ScAcceptChgDlg  aSelectionIdle" )
    , aReOpenIdle("ScAcceptChgDlg ReOpenIdle")
    , pViewData( &rViewData )
    , pDoc( &rViewData.GetDocument() )
    , aStrInsertCols(ScResId(STR_CHG_INSERT_COLS))
    , aStrInsertRows(ScResId(STR_CHG_INSERT_ROWS))
    , aStrInsertTabs(ScResId(STR_CHG_INSERT_TABS))
    , aStrDeleteCols(ScResId(STR_CHG_DELETE_COLS))
    , aStrDeleteRows(ScResId(STR_CHG_DELETE_ROWS))
    , aStrDeleteTabs(ScResId(STR_CHG_DELETE_TABS))
    , aStrMove(ScResId(STR_CHG_MOVE))
    , aStrContent(ScResId(STR_CHG_CONTENT))
    , aStrReject(ScResId(STR_CHG_REJECT))
    , aStrAllAccepted(ScResId(STR_CHG_ACCEPTED))
    , aStrAllRejected(ScResId(STR_CHG_REJECTED))
    , aStrNoEntry(ScResId(STR_CHG_NO_ENTRY))
    , aStrContentWithChild(ScResId(STR_CHG_CONTENT_WITH_CHILD))
    , aStrChildContent(ScResId(STR_CHG_CHILD_CONTENT))
    , aStrChildOrgContent(ScResId(STR_CHG_CHILD_ORGCONTENT))
    , aStrEmpty(ScResId(STR_CHG_EMPTY))
    , aUnknown(u"Unknown"_ustr)
    , bIgnoreMsg(false)
    , bNoSelection(false)
    , bHasFilterEntry(false)
    , bUseColor(false)
    , m_xContentArea(m_xDialog->weld_content_area())
    , m_xPopup(m_xBuilder->weld_menu(u"calcmenu"_ustr))
    , m_xSortMenu(m_xBuilder->weld_menu(u"calcsortmenu"_ustr))
{
    m_xAcceptChgCtr.reset(new SvxAcceptChgCtr(m_xContentArea.get()));
    nAcceptCount=0;
    nRejectCount=0;
    aReOpenIdle.SetInvokeHandler(LINK( this, ScAcceptChgDlg, ReOpenTimerHdl ));

    pTPFilter = m_xAcceptChgCtr->GetFilterPage();
    pTPView = m_xAcceptChgCtr->GetViewPage();

    // tdf#136062 Don't use "Reject/Clear formatting" instead of "Reject" buttons in Calc
    pTPView->EnableClearFormat(false);
    pTPView->EnableClearFormatAll(false);

    pTheView = pTPView->GetTableControl();
    pTheView->SetCalcView();
    aSelectionIdle.SetInvokeHandler(LINK( this, ScAcceptChgDlg, UpdateSelectionHdl ));

    pTPFilter->SetReadyHdl(LINK( this, ScAcceptChgDlg, FilterHandle ));
    pTPFilter->SetRefHdl(LINK( this, ScAcceptChgDlg, RefHandle ));
    pTPFilter->HideRange(false);
    pTPView->SetRejectClickHdl( LINK( this, ScAcceptChgDlg,RejectHandle));
    pTPView->SetAcceptClickHdl( LINK(this, ScAcceptChgDlg, AcceptHandle));
    pTPView->SetRejectAllClickHdl( LINK( this, ScAcceptChgDlg,RejectAllHandle));
    pTPView->SetAcceptAllClickHdl( LINK(this, ScAcceptChgDlg, AcceptAllHandle));

    weld::TreeView& rTreeView = pTheView->GetWidget();
    rTreeView.connect_expanding(LINK(this, ScAcceptChgDlg, ExpandingHandle));
    rTreeView.connect_selection_changed(LINK(this, ScAcceptChgDlg, SelectHandle));
    rTreeView.connect_popup_menu(LINK(this, ScAcceptChgDlg, CommandHdl));
    rTreeView.set_sort_func([this](const weld::TreeIter& rLeft, const weld::TreeIter& rRight){
        return ColCompareHdl(rLeft, rRight);
    });
    rTreeView.set_selection_mode(SelectionMode::Multiple);

    Init();

    UpdateView();

    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    if (rTreeView.get_iter_first(*xEntry))
        rTreeView.select(*xEntry);
}

ScAcceptChgDlg::~ScAcceptChgDlg()
{
    ClearView();
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();

    if (pChanges)
    {
        Link<ScChangeTrack&,void> aLink;
        pChanges->SetModifiedLink(aLink);
    }
}

void ScAcceptChgDlg::ReInit(ScViewData& rViewData)
{
    pViewData = &rViewData;
    pDoc = &rViewData.GetDocument();

    bNoSelection=false;
    bIgnoreMsg=false;
    nAcceptCount=0;
    nRejectCount=0;

    //  don't call Init here (switching between views), just set link below
    //  (dialog is just hidden, not deleted anymore, when switching views)
    ClearView();
    UpdateView();

    ScChangeTrack* pChanges = pDoc->GetChangeTrack();
    if ( pChanges )
        pChanges->SetModifiedLink( LINK( this, ScAcceptChgDlg, ChgTrackModHdl ) );
}

void ScAcceptChgDlg::Init()
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();

    if(pChanges!=nullptr)
    {
        pChanges->SetModifiedLink( LINK( this, ScAcceptChgDlg,ChgTrackModHdl));
        aChangeViewSet.SetTheAuthorToShow(pChanges->GetUser());
        pTPFilter->ClearAuthors();
        const std::set<OUString>& rUserColl = pChanges->GetUserCollection();
        for (const auto& rItem : rUserColl)
            pTPFilter->InsertAuthor(rItem);
    }

    ScChangeViewSettings* pViewSettings=pDoc->GetChangeViewSettings();
    if ( pViewSettings!=nullptr )
        aChangeViewSet = *pViewSettings;
    // adjust TimeField for filter tabpage
    aChangeViewSet.AdjustDateMode( *pDoc );

    pTPFilter->CheckDate(aChangeViewSet.HasDate());

    DateTime aEmpty(DateTime::EMPTY);

    DateTime aDateTime(aChangeViewSet.GetTheFirstDateTime());
    if (aDateTime != aEmpty)
    {
        pTPFilter->SetFirstDate(aDateTime);
        pTPFilter->SetFirstTime(aDateTime);
    }
    aDateTime = aChangeViewSet.GetTheLastDateTime();
    if (aDateTime != aEmpty)
    {
        pTPFilter->SetLastDate(aDateTime);
        pTPFilter->SetLastTime(aDateTime);
    }

    pTPFilter->SetDateMode(static_cast<sal_uInt16>(aChangeViewSet.GetTheDateMode()));
    pTPFilter->CheckComment(aChangeViewSet.HasComment());
    pTPFilter->SetComment(aChangeViewSet.GetTheComment());

    pTPFilter->CheckAuthor(aChangeViewSet.HasAuthor());
    OUString aString=aChangeViewSet.GetTheAuthorToShow();
    if(!aString.isEmpty())
    {
        pTPFilter->SelectAuthor(aString);
        if(pTPFilter->GetSelectedAuthor()!=aString)
        {
            pTPFilter->InsertAuthor(aString);
            pTPFilter->SelectAuthor(aString);
        }
    }
    else
        pTPFilter->SelectedAuthorPos(0);

    pTPFilter->CheckRange(aChangeViewSet.HasRange());

    aRangeList=aChangeViewSet.GetTheRangeList();

    if( !aChangeViewSet.GetTheRangeList().empty() )
    {
        const ScRange & rRangeEntry = aChangeViewSet.GetTheRangeList().front();
        OUString aRefStr(rRangeEntry.Format(*pDoc, ScRefFlags::RANGE_ABS_3D));
        pTPFilter->SetRange(aRefStr);
    }

    // init filter
    if(!(pTPFilter->IsDate()||pTPFilter->IsRange()||
        pTPFilter->IsAuthor()||pTPFilter->IsComment()))
        return;

    pTheView->SetFilterDate(pTPFilter->IsDate());
    pTheView->SetDateTimeMode(pTPFilter->GetDateMode());
    pTheView->SetFirstDate(pTPFilter->GetFirstDate());
    pTheView->SetLastDate(pTPFilter->GetLastDate());
    pTheView->SetFirstTime(pTPFilter->GetFirstTime());
    pTheView->SetLastTime(pTPFilter->GetLastTime());
    pTheView->SetFilterAuthor(pTPFilter->IsAuthor());
    pTheView->SetAuthor(pTPFilter->GetSelectedAuthor());

    pTheView->SetFilterComment(pTPFilter->IsComment());

    utl::SearchParam aSearchParam( pTPFilter->GetComment(),
            utl::SearchParam::SearchType::Regexp,false );

    pTheView->SetCommentParams(&aSearchParam);

    pTheView->UpdateFilterTest();
}

void ScAcceptChgDlg::ClearView()
{
    nAcceptCount=0;
    nRejectCount=0;
    weld::TreeView& rTreeView = pTheView->GetWidget();
    rTreeView.all_foreach(
        [&rTreeView](weld::TreeIter& rEntry)
        {
            ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rEntry));
            delete pEntryData;
            return false;
        });
    rTreeView.freeze();
    rTreeView.clear();
    rTreeView.thaw();
}

OUString* ScAcceptChgDlg::MakeTypeString(ScChangeActionType eType)
{
    OUString* pStr;

    switch(eType)
    {

        case SC_CAT_INSERT_COLS:    pStr=&aStrInsertCols;break;
        case SC_CAT_INSERT_ROWS:    pStr=&aStrInsertRows;break;
        case SC_CAT_INSERT_TABS:    pStr=&aStrInsertTabs;break;
        case SC_CAT_DELETE_COLS:    pStr=&aStrDeleteCols;break;
        case SC_CAT_DELETE_ROWS:    pStr=&aStrDeleteRows;break;
        case SC_CAT_DELETE_TABS:    pStr=&aStrDeleteTabs;break;
        case SC_CAT_MOVE:           pStr=&aStrMove;break;
        case SC_CAT_CONTENT:        pStr=&aStrContent;break;
        case SC_CAT_REJECT:         pStr=&aStrReject;break;
        default:                    pStr=&aUnknown;break;
    }
    return pStr;
}

bool ScAcceptChgDlg::IsValidAction(const ScChangeAction* pScChangeAction)
{
    if(pScChangeAction==nullptr) return false;

    bool bFlag = false;

    ScRange aRef=pScChangeAction->GetBigRange().MakeRange(*pDoc);
    const OUString& aUser=pScChangeAction->GetUser();
    DateTime aDateTime=pScChangeAction->GetDateTime();

    ScChangeActionType eType=pScChangeAction->GetType();
    OUString aDesc;

    OUString aComment = pScChangeAction->GetComment().replaceAll("\n", "");

    if(eType==SC_CAT_CONTENT)
    {
        if(!pScChangeAction->IsDialogParent())
            aDesc = pScChangeAction->GetDescription(*pDoc, true);
    }
    else
        aDesc = pScChangeAction->GetDescription(*pDoc, !pScChangeAction->IsMasterDelete());

    if (!aDesc.isEmpty())
    {
        aComment += " (" + aDesc + ")";
    }

    if (pTheView->IsValidEntry(aUser, aDateTime, aComment))
    {
        if(pTPFilter->IsRange())
        {
            for ( size_t i = 0, nRanges = aRangeList.size(); i < nRanges; ++i )
            {
                ScRange const & rRangeEntry = aRangeList[ i ];
                if (rRangeEntry.Intersects(aRef)) {
                    bFlag = true;
                    break;
                }
            }
        }
        else
            bFlag=true;
    }

    return bFlag;
}

std::unique_ptr<weld::TreeIter> ScAcceptChgDlg::AppendChangeAction(
    const ScChangeAction* pScChangeAction, bool bCreateOnDemand,
    const weld::TreeIter* pParent, bool bDelMaster, bool bDisabled)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();

    if(pScChangeAction==nullptr || pChanges==nullptr) return nullptr;

    bool bFlag = false;

    ScRange aRef=pScChangeAction->GetBigRange().MakeRange(*pDoc);
    DateTime aDateTime=pScChangeAction->GetDateTime();

    ScChangeActionType eType=pScChangeAction->GetType();
    OUString aActionString;
    OUString aRefStr;
    OUString aUser;
    OUString aDate;
    OUString aDesc;

    std::unique_ptr<ScRedlinData> pNewData(new ScRedlinData);
    pNewData->pData=const_cast<ScChangeAction *>(pScChangeAction);
    pNewData->nActionNo=pScChangeAction->GetActionNumber();
    pNewData->bIsAcceptable=pScChangeAction->IsClickable();
    pNewData->bIsRejectable=pScChangeAction->IsRejectable();
    pNewData->bDisabled=!pNewData->bIsAcceptable || bDisabled;
    pNewData->aDateTime=aDateTime;
    pNewData->nRow  = aRef.aStart.Row();
    pNewData->nCol  = aRef.aStart.Col();
    pNewData->nTable= aRef.aStart.Tab();

    if(eType==SC_CAT_CONTENT)
    {
        if(pScChangeAction->IsDialogParent())
        {
            aActionString = aStrContentWithChild;
            pNewData->nInfo=RD_SPECIAL_VISCONTENT;
            pNewData->bIsRejectable=false;
            pNewData->bIsAcceptable=false;
        }
        else
        {
            aActionString = *MakeTypeString(eType);
            aDesc = pScChangeAction->GetDescription(*pDoc, true);
        }
    }
    else
    {
        aActionString = *MakeTypeString(eType);

        if(bDelMaster)
        {
            aDesc = pScChangeAction->GetDescription(*pDoc,true);
            pNewData->bDisabled=true;
            pNewData->bIsRejectable=false;
        }
        else
            aDesc = pScChangeAction->GetDescription(*pDoc, !pScChangeAction->IsMasterDelete());

    }

    aRefStr = pScChangeAction->GetRefString(*pDoc, true);

    bool bIsGenerated = false;

    if(!pChanges->IsGenerated(pScChangeAction->GetActionNumber()))
    {
        aUser = pScChangeAction->GetUser();
        aDate = ScGlobal::getLocaleData().getDate(aDateTime) + " " + ScGlobal::getLocaleData().getTime(aDateTime);
        bIsGenerated = false;
    }
    else
    {
        bIsGenerated = true;
    }

    OUString aComment = pScChangeAction->GetComment().replaceAll("\n", "");

    if (!aDesc.isEmpty())
    {
        aComment +=  " (" + aDesc + ")";
    }

    if (pTheView->IsValidEntry(aUser, aDateTime) || bIsGenerated)
    {
        if (pTheView->IsValidComment(aComment))
        {
            if(pTPFilter->IsRange())
            {
                for ( size_t i = 0, nRanges = aRangeList.size(); i < nRanges; ++i )
                {
                    ScRange const & rRangeEntry = aRangeList[ i ];
                    if( rRangeEntry.Intersects(aRef) )
                    {
                        bHasFilterEntry=true;
                        bFlag=true;
                        break;
                    }
                }
            }
            else if(!bIsGenerated)
            {
                bHasFilterEntry=true;
                bFlag=true;
            }
        }
    }

    weld::TreeView& rTreeView = pTheView->GetWidget();
    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    OUString sId(weld::toId(pNewData.release()));
    rTreeView.insert(pParent, -1, &aActionString, &sId, nullptr, nullptr, bCreateOnDemand, xEntry.get());
    rTreeView.set_text( *xEntry, aRefStr, 1);
    if (!aUser.isEmpty())
        rTreeView.set_text( *xEntry, aUser, 2);
    if (!aDate.isEmpty())
        rTreeView.set_text( *xEntry, aDate, 3);
    if (!aComment.isEmpty())
        rTreeView.set_text( *xEntry, aComment, 4);
    if (!bFlag && bUseColor && !pParent)
    {
        rTreeView.set_font_color(*xEntry, COL_LIGHTBLUE);
    }
    else if (bFlag && bUseColor && pParent)
    {
        rTreeView.set_font_color(*xEntry, COL_GREEN);

        std::unique_ptr<weld::TreeIter> xExpEntry(rTreeView.make_iterator(pParent));

        while (!rTreeView.get_row_expanded(*xExpEntry))
        {
            if (rTreeView.get_iter_depth(*xExpEntry))
                rTreeView.expand_row(*xExpEntry);

            if (!rTreeView.iter_parent(*xExpEntry))
                break;
        }
    }
    return xEntry;
}

std::unique_ptr<weld::TreeIter> ScAcceptChgDlg::AppendFilteredAction(
    const ScChangeAction* pScChangeAction, ScChangeActionState eState,
    bool bCreateOnDemand,
    const weld::TreeIter* pParent, bool bDelMaster, bool bDisabled)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();

    if(pScChangeAction==nullptr || pChanges==nullptr) return nullptr;

    bool bIsGenerated = pChanges->IsGenerated(pScChangeAction->GetActionNumber());

    bool bFlag = false;

    ScRange aRef=pScChangeAction->GetBigRange().MakeRange(*pDoc);
    const OUString& aUser=pScChangeAction->GetUser();
    DateTime aDateTime=pScChangeAction->GetDateTime();

    if (pTheView->IsValidEntry(aUser, aDateTime) || bIsGenerated)
    {
        if(pTPFilter->IsRange())
        {
            for ( size_t i = 0, nRanges = aRangeList.size(); i < nRanges; ++i )
            {
                ScRange const & rRangeEntry=aRangeList[ i ];
                if( rRangeEntry.Intersects(aRef) )
                {
                    if( pScChangeAction->GetState()==eState )
                        bFlag = true;
                    break;
                }
            }
        }
        else if(pScChangeAction->GetState()==eState && !bIsGenerated)
            bFlag = true;
    }

    std::unique_ptr<weld::TreeIter> xEntry;
    if(bFlag)
    {
        ScChangeActionType eType=pScChangeAction->GetType();
        OUString aActionString;
        OUString aDesc;

        std::unique_ptr<ScRedlinData> pNewData(new ScRedlinData);
        pNewData->pData=const_cast<ScChangeAction *>(pScChangeAction);
        pNewData->nActionNo=pScChangeAction->GetActionNumber();
        pNewData->bIsAcceptable=pScChangeAction->IsClickable();
        pNewData->bIsRejectable=pScChangeAction->IsRejectable();
        pNewData->bDisabled=!pNewData->bIsAcceptable || bDisabled;
        pNewData->aDateTime=aDateTime;
        pNewData->nRow  = aRef.aStart.Row();
        pNewData->nCol  = aRef.aStart.Col();
        pNewData->nTable= aRef.aStart.Tab();

        if(eType==SC_CAT_CONTENT)
        {
            if(pScChangeAction->IsDialogParent())
            {
                aActionString=aStrContentWithChild;
                pNewData->nInfo=RD_SPECIAL_VISCONTENT;
                pNewData->bIsRejectable=false;
                pNewData->bIsAcceptable=false;
            }
            else
            {
                aActionString=*MakeTypeString(eType);
                aDesc = pScChangeAction->GetDescription(*pDoc, true);
            }
        }
        else
        {
            aActionString=*MakeTypeString(eType);

            if(bDelMaster)
            {
                aDesc = pScChangeAction->GetDescription(*pDoc,true);
                pNewData->bDisabled=true;
                pNewData->bIsRejectable=false;
            }
            else
                aDesc = pScChangeAction->GetDescription(*pDoc,!pScChangeAction->IsMasterDelete());

        }


        OUString aComment = pScChangeAction->GetComment().replaceAll("\n", "");
        if (!aDesc.isEmpty())
        {
            aComment += " (" + aDesc + ")";
        }
        if (pTheView->IsValidComment(aComment))
        {
            weld::TreeView& rTreeView = pTheView->GetWidget();
            xEntry = rTreeView.make_iterator();
            OUString sId(weld::toId(pNewData.release()));
            rTreeView.insert(pParent, -1, &aActionString, &sId, nullptr, nullptr, bCreateOnDemand, xEntry.get());

            OUString aRefStr = pScChangeAction->GetRefString(*pDoc, true);
            rTreeView.set_text(*xEntry, aRefStr, 1);

            if (!bIsGenerated)
            {
                rTreeView.set_text(*xEntry, aUser, 2);
                OUString sDate = ScGlobal::getLocaleData().getDate(aDateTime) + " " + ScGlobal::getLocaleData().getTime(aDateTime);
                rTreeView.set_text(*xEntry, sDate, 3);
            }

            rTreeView.set_text(*xEntry, aComment, 4);
        }
    }
    return xEntry;
}

std::unique_ptr<weld::TreeIter> ScAcceptChgDlg::InsertChangeActionContent(const ScChangeActionContent* pScChangeAction,
                                                                          const weld::TreeIter& rParent, sal_uLong nSpecial)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();

    if(pScChangeAction==nullptr || pChanges==nullptr) return nullptr;

    bool bIsGenerated = pChanges->IsGenerated(pScChangeAction->GetActionNumber());

    bool bFlag = false;

    ScRange aRef=pScChangeAction->GetBigRange().MakeRange(*pDoc);
    OUString aUser=pScChangeAction->GetUser();
    DateTime aDateTime=pScChangeAction->GetDateTime();

    if (pTheView->IsValidEntry(aUser, aDateTime) || bIsGenerated)
    {
        if(pTPFilter->IsRange())
        {
            for ( size_t i = 0, nRanges = aRangeList.size(); i < nRanges; ++i )
            {
                ScRange const & rRangeEntry = aRangeList[ i ];
                if( rRangeEntry.Intersects(aRef) )
                {
                    bFlag=true;
                    break;
                }
            }
        }
        else if(!bIsGenerated)
            bFlag=true;
    }

    OUString aContent;
    OUString aRefStr;
    OUString aDate;
    OUString aDesc;

    if(nSpecial==RD_SPECIAL_CONTENT)
    {
        aContent = pScChangeAction->GetOldString(*pDoc);
        if (aContent.isEmpty())
            aContent = aStrEmpty;
        aDesc = aStrChildOrgContent + ": " + aContent;
    }
    else
    {
        const OUString aTmp( pScChangeAction->GetNewString(*pDoc));
        if (aTmp.isEmpty())
            aContent = aStrEmpty;
        else
            aContent = "\'" + aTmp + "\'";
        aDesc = aStrChildContent + aContent;
    }

    aRefStr = pScChangeAction->GetRefString(*pDoc, true);

    if(!bIsGenerated)
    {
        // aUser is kept.
        aDate = ScGlobal::getLocaleData().getDate(aDateTime) + " " + ScGlobal::getLocaleData().getTime(aDateTime);
    }
    else
    {
        aUser.clear();
    }

    OUString aComment = pScChangeAction->GetComment().replaceAll("\n", "");

    if(!aDesc.isEmpty())
    {
        aComment += " (" + aDesc + ")";
    }

    std::unique_ptr<ScRedlinData> pNewData(new ScRedlinData);
    pNewData->nInfo=nSpecial;
    pNewData->pData=const_cast<ScChangeActionContent *>(pScChangeAction);
    pNewData->nActionNo=pScChangeAction->GetActionNumber();
    pNewData->bIsAcceptable=pScChangeAction->IsClickable();
    pNewData->bIsRejectable=false;
    pNewData->bDisabled=!pNewData->bIsAcceptable;
    pNewData->aDateTime=aDateTime;
    pNewData->nRow  = aRef.aStart.Row();
    pNewData->nCol  = aRef.aStart.Col();
    pNewData->nTable= aRef.aStart.Tab();

    weld::TreeView& rTreeView = pTheView->GetWidget();
    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    OUString sId(weld::toId(pNewData.release()));
    rTreeView.insert(&rParent, -1, &aContent, &sId, nullptr, nullptr, false, xEntry.get());
    rTreeView.set_text( *xEntry, aRefStr, 1);
    if (!aUser.isEmpty())
        rTreeView.set_text( *xEntry, aUser, 2);
    if (!aDate.isEmpty())
        rTreeView.set_text( *xEntry, aDate, 3);
    if (!aComment.isEmpty())
        rTreeView.set_text( *xEntry, aComment, 4);
    if (pTheView->IsValidComment(aComment) && bFlag)
        bHasFilterEntry=true;
    else
    {
        rTreeView.set_font_color(*xEntry, COL_LIGHTBLUE);
    }
    return xEntry;
}

void ScAcceptChgDlg::UpdateView()
{
    std::unique_ptr<weld::TreeIter> xParent;
    ScChangeTrack* pChanges=nullptr;
    const ScChangeAction* pScChangeAction=nullptr;
    m_xDialog->set_busy_cursor(true);
    weld::TreeView& rTreeView = pTheView->GetWidget();
    rTreeView.freeze();
    bool bFilterFlag = pTPFilter->IsDate() || pTPFilter->IsRange() ||
        pTPFilter->IsAuthor() || pTPFilter->IsComment();

    bUseColor = bFilterFlag;

    pChanges=pDoc->GetChangeTrack();
    if(pChanges!=nullptr)
        pScChangeAction=pChanges->GetFirst();
    bool bTheFlag = false;

    while(pScChangeAction!=nullptr)
    {
        bHasFilterEntry=false;
        switch (pScChangeAction->GetState())
        {
            case SC_CAS_VIRGIN:

                if (pScChangeAction->IsDialogRoot())
                {
                    bool bOnDemandChildren = !bFilterFlag && pScChangeAction->IsDialogParent();
                    if (pScChangeAction->IsDialogParent())
                        xParent = AppendChangeAction(pScChangeAction, bOnDemandChildren);
                    else
                        xParent = AppendFilteredAction(pScChangeAction, SC_CAS_VIRGIN, bOnDemandChildren);
                }
                else
                    xParent.reset();

                bTheFlag=true;
                break;

            case SC_CAS_ACCEPTED:
                xParent.reset();
                nAcceptCount++;
                break;

            case SC_CAS_REJECTED:
                xParent.reset();
                nRejectCount++;
                break;
        }

        if (xParent && pScChangeAction->IsDialogParent() && bFilterFlag)
        {
            bool bTestFlag = bHasFilterEntry;
            bHasFilterEntry=false;
            if (Expand(pChanges, pScChangeAction, *xParent, !bTestFlag) && !bTestFlag)
                rTreeView.remove(*xParent);
        }

        pScChangeAction=pScChangeAction->GetNext();
    }

    if( bTheFlag && (!pDoc->IsDocEditable() || pChanges->IsProtected()) )
        bTheFlag=false;

    pTPView->EnableAccept(bTheFlag);
    pTPView->EnableAcceptAll(bTheFlag);
    pTPView->EnableReject(bTheFlag);
    pTPView->EnableRejectAll(bTheFlag);

    if (nAcceptCount>0)
        rTreeView.insert(nullptr, -1, &aStrAllAccepted, nullptr, nullptr, nullptr, true, nullptr);
    if (nRejectCount>0)
        rTreeView.insert(nullptr, -1, &aStrAllRejected, nullptr, nullptr, nullptr, true, nullptr);
    rTreeView.thaw();
    m_xDialog->set_busy_cursor(false);
    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    if (rTreeView.get_iter_first(*xEntry))
        rTreeView.select(*xEntry);
}

IMPL_LINK_NOARG(ScAcceptChgDlg, RefHandle, SvxTPFilter*, void)
{
    sal_uInt16 nId  =ScSimpleRefDlgWrapper::GetChildWindowId();

    ScModule::get()->SetRefDialog(nId, true);

    SfxViewFrame& rViewFrm = pViewData->GetViewShell()->GetViewFrame();
    ScSimpleRefDlgWrapper* pWnd = static_cast<ScSimpleRefDlgWrapper*>(rViewFrm.GetChildWindow( nId ));

    if(pWnd!=nullptr)
    {
        sal_uInt16 nAcceptId=ScAcceptChgDlgWrapper::GetChildWindowId();
        rViewFrm.ShowChildWindow(nAcceptId,false);
        pWnd->SetCloseHdl(LINK( this, ScAcceptChgDlg,RefInfoHandle));
        pWnd->SetRefString(pTPFilter->GetRange());
        ScSimpleRefDlgWrapper::SetAutoReOpen(false);
        auto xWin = pWnd->GetController();
        m_xDialog->hide();
        xWin->set_title(m_xDialog->get_title());
        pWnd->StartRefInput();
    }
}

IMPL_LINK( ScAcceptChgDlg, RefInfoHandle, const OUString*, pResult, void)
{
    sal_uInt16 nId = ScAcceptChgDlgWrapper::GetChildWindowId();

    ScSimpleRefDlgWrapper::SetAutoReOpen(true);

    SfxViewFrame& rViewFrm = pViewData->GetViewShell()->GetViewFrame();
    if (pResult)
    {
        pTPFilter->SetRange(*pResult);
        FilterHandle(pTPFilter);

        rViewFrm.ShowChildWindow(nId);
    }
    else
    {
        rViewFrm.SetChildWindow(nId, false);
    }
}

IMPL_LINK( ScAcceptChgDlg, FilterHandle, SvxTPFilter*, pRef, void )
{
    if(pRef!=nullptr)
    {
        ClearView();
        aRangeList.RemoveAll();
        aRangeList.Parse(pTPFilter->GetRange(), *pDoc);
        UpdateView();
    }
}

IMPL_LINK( ScAcceptChgDlg, RejectHandle, SvxTPView*, pRef, void )
{
    m_xDialog->set_busy_cursor(true);

    bIgnoreMsg=true;
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();

    if(pRef!=nullptr)
    {
        weld::TreeView& rTreeView = pTheView->GetWidget();
        rTreeView.selected_foreach([this, pChanges, &rTreeView](weld::TreeIter& rEntry){
            ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rEntry));
            if (pEntryData)
            {
                ScChangeAction* pScChangeAction= static_cast<ScChangeAction*>(pEntryData->pData);
                if (pScChangeAction->GetType()==SC_CAT_INSERT_TABS)
                    pViewData->SetTabNo(0);
                pChanges->Reject(pScChangeAction);
            }
            return false;
        });
        ScDocShell* pDocSh=pViewData->GetDocShell();
        pDocSh->PostPaintExtras();
        pDocSh->PostPaintGridAll();
        pDocSh->GetUndoManager()->Clear();
        pDocSh->SetDocumentModified();
        ClearView();
        UpdateView();
    }

    m_xDialog->set_busy_cursor(false);

    bIgnoreMsg=false;
}
IMPL_LINK( ScAcceptChgDlg, AcceptHandle, SvxTPView*, pRef, void )
{
    m_xDialog->set_busy_cursor(true);

    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    bIgnoreMsg=true;
    if(pRef!=nullptr)
    {
        weld::TreeView& rTreeView = pTheView->GetWidget();
        rTreeView.selected_foreach([pChanges, &rTreeView](weld::TreeIter& rEntry) {
            ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rEntry));
            if (pEntryData)
            {
                ScChangeAction* pScChangeAction=
                        static_cast<ScChangeAction*>(pEntryData->pData);
                if(pScChangeAction->GetType()==SC_CAT_CONTENT)
                {
                    if(pEntryData->nInfo==RD_SPECIAL_CONTENT)
                        pChanges->SelectContent(pScChangeAction,true);
                    else
                        pChanges->SelectContent(pScChangeAction);
                }
                else
                    pChanges->Accept(pScChangeAction);
            }
            return false;
        });
        ScDocShell* pDocSh=pViewData->GetDocShell();
        pDocSh->PostPaintExtras();
        pDocSh->PostPaintGridAll();
        pDocSh->SetDocumentModified();
        ClearView();
        UpdateView();
    }
    m_xDialog->set_busy_cursor(false);
    bIgnoreMsg=false;
}

void ScAcceptChgDlg::RejectFiltered()
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    const ScChangeAction* pScChangeAction=nullptr;

    if(pChanges!=nullptr)
    {
        pScChangeAction=pChanges->GetLast();
    }

    while(pScChangeAction!=nullptr)
    {
        if(pScChangeAction->IsDialogRoot())
            if(IsValidAction(pScChangeAction))
                pChanges->Reject(const_cast<ScChangeAction*>(pScChangeAction));

        pScChangeAction=pScChangeAction->GetPrev();
    }
}
void ScAcceptChgDlg::AcceptFiltered()
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    const ScChangeAction* pScChangeAction=nullptr;

    if(pChanges!=nullptr)
        pScChangeAction=pChanges->GetLast();

    while(pScChangeAction!=nullptr)
    {
        if(pScChangeAction->IsDialogRoot())
            if(IsValidAction(pScChangeAction))
                pChanges->Accept(const_cast<ScChangeAction*>(pScChangeAction));

        pScChangeAction=pScChangeAction->GetPrev();
    }
}

IMPL_LINK_NOARG(ScAcceptChgDlg, RejectAllHandle, SvxTPView*, void)
{
    m_xDialog->set_busy_cursor(true);
    bIgnoreMsg=true;
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    if(pChanges!=nullptr)
    {
        if(pTPFilter->IsDate()||pTPFilter->IsAuthor()||pTPFilter->IsRange()||pTPFilter->IsComment())
            RejectFiltered();
        else
            pChanges->RejectAll();

        pViewData->SetTabNo(0);

        ScDocShell* pDocSh=pViewData->GetDocShell();
        pDocSh->PostPaintExtras();
        pDocSh->PostPaintGridAll();
        pDocSh->GetUndoManager()->Clear();
        pDocSh->SetDocumentModified();
        ClearView();
        UpdateView();
    }
    m_xDialog->set_busy_cursor(false);

    bIgnoreMsg=false;
}

IMPL_LINK_NOARG(ScAcceptChgDlg, AcceptAllHandle, SvxTPView*, void)
{
    m_xDialog->set_busy_cursor(true);

    bIgnoreMsg=true;
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    if(pChanges!=nullptr)
    {
        if(pTPFilter->IsDate()||pTPFilter->IsAuthor()||pTPFilter->IsRange()||pTPFilter->IsComment())
            AcceptFiltered();
        else
            pChanges->AcceptAll();

        ScDocShell* pDocSh=pViewData->GetDocShell();
        pDocSh->PostPaintExtras();
        pDocSh->PostPaintGridAll();
        pDocSh->SetDocumentModified();
        ClearView();
        UpdateView();
    }
    bIgnoreMsg=false;

    m_xDialog->set_busy_cursor(false);
}

IMPL_LINK_NOARG(ScAcceptChgDlg, SelectHandle, weld::TreeView&, void)
{
    if (!bNoSelection)
        aSelectionIdle.Start();

    bNoSelection=false;
}

void ScAcceptChgDlg::GetDependents(const ScChangeAction* pScChangeAction,
                                   ScChangeActionMap& aActionMap,
                                   const weld::TreeIter& rEntry)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();

    weld::TreeView& rTreeView = pTheView->GetWidget();
    std::unique_ptr<weld::TreeIter> xParent(rTreeView.make_iterator(&rEntry));
    if (rTreeView.iter_parent(*xParent))
    {
        ScRedlinData *pParentData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xParent));
        ScChangeAction* pParentAction=static_cast<ScChangeAction*>(pParentData->pData);

        if(pParentAction!=pScChangeAction)
            pChanges->GetDependents(const_cast<ScChangeAction*>(pScChangeAction),
                        aActionMap,pScChangeAction->IsMasterDelete());
        else
            pChanges->GetDependents( const_cast<ScChangeAction*>(pScChangeAction),
                        aActionMap );
    }
    else
        pChanges->GetDependents(const_cast<ScChangeAction*>(pScChangeAction),
                    aActionMap, pScChangeAction->IsMasterDelete() );
}

bool ScAcceptChgDlg::InsertContentChildren(ScChangeActionMap* pActionMap, const weld::TreeIter& rParent)
{
    bool bTheTestFlag = true;
    weld::TreeView& rTreeView = pTheView->GetWidget();
    ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rParent));
    const ScChangeAction* pScChangeAction = static_cast<ScChangeAction*>(pEntryData->pData);
    bool bParentInserted = false;
    // If the parent is a MatrixOrigin then place it in the right order before
    // the MatrixReferences. Also if it is the first content change at this
    // position don't insert the first dependent MatrixReference as the special
    // content (original value) but insert the predecessor of the MatrixOrigin
    // itself instead.
    if ( pScChangeAction->GetType() == SC_CAT_CONTENT &&
            static_cast<const ScChangeActionContent*>(pScChangeAction)->IsMatrixOrigin() )
    {
        pActionMap->insert( ::std::make_pair( pScChangeAction->GetActionNumber(),
            const_cast<ScChangeAction*>( pScChangeAction ) ) );
        bParentInserted = true;
    }

    ScChangeActionMap::iterator itChangeAction = std::find_if(pActionMap->begin(), pActionMap->end(),
        [](const std::pair<sal_uLong, ScChangeAction*>& rEntry) { return rEntry.second->GetState() == SC_CAS_VIRGIN; });

    if( itChangeAction == pActionMap->end() )
        return true;

    std::unique_ptr<weld::TreeIter> xOriginal = InsertChangeActionContent(
        dynamic_cast<const ScChangeActionContent*>( itChangeAction->second ),
        rParent, RD_SPECIAL_CONTENT );

    if (xOriginal)
    {
        bTheTestFlag=false;
        ScRedlinData *pParentData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xOriginal));
        pParentData->pData=const_cast<ScChangeAction *>(pScChangeAction);
        pParentData->nActionNo=pScChangeAction->GetActionNumber();
        pParentData->bIsAcceptable=pScChangeAction->IsRejectable(); // select old value
        pParentData->bIsRejectable=false;
        pParentData->bDisabled=false;
    }
    while( itChangeAction != pActionMap->end() )
    {
        if( itChangeAction->second->GetState() == SC_CAS_VIRGIN )
        {
            std::unique_ptr<weld::TreeIter> xEntry =
                InsertChangeActionContent( dynamic_cast<const ScChangeActionContent*>( itChangeAction->second ),
                    rParent, RD_SPECIAL_NONE );

            if (xEntry)
                bTheTestFlag=false;
        }
        ++itChangeAction;
    }

    if ( !bParentInserted )
    {
        std::unique_ptr<weld::TreeIter> xEntry =
            InsertChangeActionContent(static_cast<const ScChangeActionContent*>(
                                pScChangeAction),rParent,RD_SPECIAL_NONE);

        if (xEntry)
        {
            bTheTestFlag=false;
            ScRedlinData *pParentData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xEntry));
            pParentData->pData=const_cast<ScChangeAction *>(pScChangeAction);
            pParentData->nActionNo=pScChangeAction->GetActionNumber();
            pParentData->bIsAcceptable=pScChangeAction->IsClickable();
            pParentData->bIsRejectable=false;
            pParentData->bDisabled=false;
        }
    }

    return bTheTestFlag;
}

bool ScAcceptChgDlg::InsertAcceptedORejected(const weld::TreeIter& rParent)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    bool bTheTestFlag = true;

    ScChangeActionState eState = SC_CAS_VIRGIN;
    weld::TreeView& rTreeView = pTheView->GetWidget();
    OUString aString = rTreeView.get_text(rParent, 0);
    OUString a2String = aString.copy(0, aStrAllAccepted.getLength());
    if (a2String == aStrAllAccepted)
        eState=SC_CAS_ACCEPTED;
    else
    {
        a2String = aString.copy(0, aStrAllRejected.getLength());
        if (a2String == aStrAllRejected)
            eState=SC_CAS_REJECTED;
    }

    ScChangeAction* pScChangeAction = pChanges->GetFirst();
    while (pScChangeAction)
    {
        if (pScChangeAction->GetState()==eState &&
            AppendFilteredAction(pScChangeAction, eState, false, &rParent))
            bTheTestFlag=false;
        pScChangeAction=pScChangeAction->GetNext();
    }
    return bTheTestFlag;
}

bool ScAcceptChgDlg::InsertChildren(ScChangeActionMap* pActionMap, const weld::TreeIter& rParent)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    bool bTheTestFlag = true;

    for( const auto& rChangeAction : *pActionMap )
    {
        std::unique_ptr<weld::TreeIter> xEntry = AppendChangeAction(rChangeAction.second, false, &rParent, false, true);

        if (xEntry)
        {
            bTheTestFlag=false;

            weld::TreeView& rTreeView = pTheView->GetWidget();
            ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xEntry));
            pEntryData->bIsRejectable=false;
            pEntryData->bIsAcceptable=false;
            pEntryData->bDisabled=true;

            if (rChangeAction.second->IsDialogParent())
                Expand(pChanges, rChangeAction.second, *xEntry);
        }
    }
    return bTheTestFlag;
}

bool ScAcceptChgDlg::InsertDeletedChildren(const ScChangeAction* pScChangeAction,
                                           ScChangeActionMap* pActionMap, const weld::TreeIter& rParent)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    bool bTheTestFlag = true;
    std::unique_ptr<weld::TreeIter> xEntry;

    for( const auto& rChangeAction : *pActionMap )
    {

        if( pScChangeAction != rChangeAction.second )
            xEntry = AppendChangeAction(rChangeAction.second, false, &rParent, false, true);
        else
            xEntry = AppendChangeAction(rChangeAction.second, false, &rParent, true, true);

        if (xEntry)
        {
            weld::TreeView& rTreeView = pTheView->GetWidget();
            ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xEntry));
            pEntryData->bIsRejectable=false;
            pEntryData->bIsAcceptable=false;
            pEntryData->bDisabled=true;

            bTheTestFlag=false;

            if (rChangeAction.second->IsDialogParent())
                Expand(pChanges, rChangeAction.second, *xEntry);
        }
    }
    return bTheTestFlag;
}

bool ScAcceptChgDlg::Expand(const ScChangeTrack* pChanges, const ScChangeAction* pScChangeAction,
                            const weld::TreeIter& rEntry, bool bFilter)
{
    bool bTheTestFlag = true;

    if (pChanges && pScChangeAction)
    {
        ScChangeActionMap aActionMap;

        GetDependents(pScChangeAction, aActionMap, rEntry);

        switch(pScChangeAction->GetType())
        {
            case SC_CAT_CONTENT:
            {
                InsertContentChildren(&aActionMap, rEntry);
                bTheTestFlag=!bHasFilterEntry;
                break;
            }
            case SC_CAT_DELETE_COLS:
            case SC_CAT_DELETE_ROWS:
            case SC_CAT_DELETE_TABS:
            {
                InsertDeletedChildren(pScChangeAction, &aActionMap, rEntry);
                bTheTestFlag=!bHasFilterEntry;
                break;
            }
            default:
            {
                if(!bFilter)
                    bTheTestFlag = InsertChildren(&aActionMap, rEntry);
                break;
            }
        }
        aActionMap.clear();
    }
    return bTheTestFlag;
}

IMPL_LINK(ScAcceptChgDlg, ExpandingHandle, const weld::TreeIter&, rEntry, bool)
{
    ScChangeTrack* pChanges=pDoc->GetChangeTrack();
    if (pChanges)
    {
        m_xDialog->set_busy_cursor(true);
        ScChangeActionMap aActionMap;
        weld::TreeView& rTreeView = pTheView->GetWidget();
        ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rEntry));
        if (!rTreeView.iter_has_child(rEntry))
        {
            bool bTheTestFlag = true;

            if (pEntryData)
            {
                ScChangeAction* pScChangeAction=static_cast<ScChangeAction*>(pEntryData->pData);

                GetDependents(pScChangeAction, aActionMap, rEntry);

                switch (pScChangeAction->GetType())
                {
                    case SC_CAT_CONTENT:
                    {
                        bTheTestFlag = InsertContentChildren( &aActionMap, rEntry );
                        break;
                    }
                    case SC_CAT_DELETE_COLS:
                    case SC_CAT_DELETE_ROWS:
                    case SC_CAT_DELETE_TABS:
                    {
                        bTheTestFlag = InsertDeletedChildren( pScChangeAction, &aActionMap, rEntry );
                        break;
                    }
                    default:
                    {
                        bTheTestFlag = InsertChildren( &aActionMap, rEntry );
                        break;
                    }
                }
                aActionMap.clear();

            }
            else
            {
                bTheTestFlag = InsertAcceptedORejected(rEntry);
            }
            if (bTheTestFlag)
            {
                std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
                rTreeView.insert(&rEntry, -1, &aStrNoEntry, nullptr, nullptr, nullptr, false, xEntry.get());
                rTreeView.set_font_color(*xEntry, COL_GRAY);
            }
        }
        m_xDialog->set_busy_cursor(false);
    }
    return true;
}

void ScAcceptChgDlg::AppendChanges(const ScChangeTrack* pChanges,sal_uLong nStartAction,
                                   sal_uLong nEndAction)
{
    if(pChanges==nullptr)
        return;

    std::unique_ptr<weld::TreeIter> xParent;
    m_xDialog->set_busy_cursor(true);
    weld::TreeView& rTreeView = pTheView->GetWidget();
    rTreeView.freeze();

    bool bTheFlag = false;

    bool bFilterFlag = pTPFilter->IsDate() || pTPFilter->IsRange() ||
        pTPFilter->IsAuthor() || pTPFilter->IsComment();

    bUseColor = bFilterFlag;

    for(sal_uLong i=nStartAction;i<=nEndAction;i++)
    {
        const ScChangeAction* pScChangeAction=pChanges->GetAction(i);
        if(pScChangeAction==nullptr) continue;

        switch (pScChangeAction->GetState())
        {
            case SC_CAS_VIRGIN:

                if (pScChangeAction->IsDialogRoot())
                {
                    bool bOnDemandChildren = !bFilterFlag && pScChangeAction->IsDialogParent();
                    if (pScChangeAction->IsDialogParent())
                        xParent = AppendChangeAction(pScChangeAction, bOnDemandChildren);
                    else
                        xParent = AppendFilteredAction(pScChangeAction, SC_CAS_VIRGIN, bOnDemandChildren);
                }
                else
                    xParent.reset();

                bTheFlag=true;
                break;

            case SC_CAS_ACCEPTED:
                xParent.reset();
                nAcceptCount++;
                break;

            case SC_CAS_REJECTED:
                xParent.reset();
                nRejectCount++;
                break;
        }

        if (xParent && pScChangeAction->IsDialogParent() && bFilterFlag)
        {
            bool bTestFlag = bHasFilterEntry;
            bHasFilterEntry = false;
            if (Expand(pChanges,pScChangeAction,*xParent,!bTestFlag)&&!bTestFlag)
                rTreeView.remove(*xParent);
        }
    }

    if( bTheFlag && (!pDoc->IsDocEditable() || pChanges->IsProtected()) )
        bTheFlag=false;

    pTPView->EnableAccept(bTheFlag);
    pTPView->EnableAcceptAll(bTheFlag);
    pTPView->EnableReject(bTheFlag);
    pTPView->EnableRejectAll(bTheFlag);

    rTreeView.thaw();
    m_xDialog->set_busy_cursor(false);
}

void ScAcceptChgDlg::RemoveEntries(sal_uLong nStartAction,sal_uLong nEndAction)
{
    weld::TreeView& rTreeView = pTheView->GetWidget();

    ScRedlinData *pEntryData=nullptr;
    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    if (rTreeView.get_cursor(xEntry.get()))
        pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xEntry));

    if (!rTreeView.get_iter_first(*xEntry))
        return;

    sal_uLong nAction=0;
    if (pEntryData)
        nAction=pEntryData->nActionNo;

    if (nAction>=nStartAction && nAction<=nEndAction)
        rTreeView.set_cursor(*xEntry);

    std::vector<OUString> aIdsToRemove;

    do
    {
        OUString sId(rTreeView.get_id(*xEntry));
        pEntryData = weld::fromId<ScRedlinData*>(sId);
        if (pEntryData)
        {
            nAction = pEntryData->nActionNo;
            if (nStartAction <= nAction && nAction <= nEndAction)
            {
                aIdsToRemove.push_back(sId);
                delete pEntryData;
            }
        }
    }
    while (rTreeView.iter_next(*xEntry));

    rTreeView.freeze();

    // MUST do it backwards, don't delete parents before children and GPF
    for (auto it = aIdsToRemove.rbegin(); it != aIdsToRemove.rend(); ++it)
        rTreeView.remove_id(*it);

    rTreeView.thaw();
}

void ScAcceptChgDlg::UpdateEntries(const ScChangeTrack* pChgTrack, sal_uLong nStartAction,sal_uLong nEndAction)
{
    weld::TreeView& rTreeView = pTheView->GetWidget();
    rTreeView.freeze();

    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    std::unique_ptr<weld::TreeIter> xLastEntry(rTreeView.make_iterator());
    std::unique_ptr<weld::TreeIter> xNextEntry(rTreeView.make_iterator());

    bool bEntry = rTreeView.get_iter_first(*xEntry);
    bool bLastEntry = false;

    while (bEntry)
    {
        bool bRemove = false;
        ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xEntry));
        if (pEntryData)
        {
            ScChangeAction* pScChangeAction=
                    static_cast<ScChangeAction*>(pEntryData->pData);

            sal_uLong nAction=pScChangeAction->GetActionNumber();

            if(nStartAction<=nAction && nAction<=nEndAction) bRemove=true;
        }

        bool bNextEntry;
        if (bRemove)
        {
            rTreeView.remove(*xEntry);
            delete pEntryData;

            if (!bLastEntry)
                bLastEntry = rTreeView.get_iter_first(*xLastEntry);
            if (bLastEntry)
            {
                rTreeView.copy_iterator(*xLastEntry, *xNextEntry);
                bNextEntry = rTreeView.iter_next(*xNextEntry);
                if (!bNextEntry)
                {
                    rTreeView.copy_iterator(*xLastEntry, *xNextEntry);
                    bLastEntry = false;
                }
            }
            else
                bNextEntry = false;
        }
        else
        {
            rTreeView.copy_iterator(*xEntry, *xLastEntry);
            bLastEntry = true;

            rTreeView.copy_iterator(*xEntry, *xNextEntry);
            bNextEntry = rTreeView.iter_next(*xNextEntry);
        }

        rTreeView.copy_iterator(*xNextEntry, *xEntry);
        bEntry = bNextEntry;
    }

    AppendChanges(pChgTrack,nStartAction,nEndAction);

    rTreeView.thaw();
}

IMPL_LINK( ScAcceptChgDlg, ChgTrackModHdl, ScChangeTrack&, rChgTrack, void)
{
    ScChangeTrackMsgQueue& aMsgQueue= rChgTrack.GetMsgQueue();

    sal_uLong   nStartAction;
    sal_uLong   nEndAction;

    for (const auto& rMsg : aMsgQueue)
    {
        nStartAction = rMsg.nStartAction;
        nEndAction = rMsg.nEndAction;

        if(!bIgnoreMsg)
        {
            bNoSelection=true;

            switch(rMsg.eMsgType)
            {
                case ScChangeTrackMsgType::Append:
                                    AppendChanges(&rChgTrack,nStartAction,nEndAction);
                                    break;
                case ScChangeTrackMsgType::Remove:
                                    RemoveEntries(nStartAction,nEndAction);
                                    break;
                case ScChangeTrackMsgType::Parent:
                case ScChangeTrackMsgType::Change: //bNeedsUpdate=true;
                                    UpdateEntries(&rChgTrack,nStartAction,nEndAction);
                                    break;
                default: assert(false); break;
            }
        }
    }

    aMsgQueue.clear();
}

IMPL_LINK_NOARG(ScAcceptChgDlg, ReOpenTimerHdl, Timer *, void)
{
    ScSimpleRefDlgWrapper::SetAutoReOpen(true);
    m_xAcceptChgCtr->ShowFilterPage();
    RefHandle(nullptr);
}

IMPL_LINK_NOARG(ScAcceptChgDlg, UpdateSelectionHdl, Timer *, void)
{
    ScTabView* pTabView = pViewData->GetView();

    bool bAcceptFlag = true;
    bool bRejectFlag = true;

    pTabView->DoneBlockMode();  // clears old marking
    weld::TreeView& rTreeView = pTheView->GetWidget();
    std::vector<const ScChangeAction*> aActions;
    rTreeView.selected_foreach([&rTreeView, &bAcceptFlag, &bRejectFlag, &aActions](weld::TreeIter& rEntry){
        ScRedlinData* pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rEntry));
        if (pEntryData)
        {
            bRejectFlag &= pEntryData->bIsRejectable;
            bAcceptFlag &= pEntryData->bIsAcceptable;

            const ScChangeAction* pScChangeAction = static_cast<ScChangeAction*>(pEntryData->pData);
            if( pScChangeAction && (pScChangeAction->GetType() != SC_CAT_DELETE_TABS) &&
                    (!pEntryData->bDisabled || pScChangeAction->IsVisible()) )
            {
                aActions.push_back(pScChangeAction);
            }
        }
        else
        {
            bAcceptFlag = false;
            bRejectFlag = false;
        }
        return false;
    });

    bool bContMark = false;
    for (size_t i = 0, nCount = aActions.size(); i < nCount; ++i)
    {
        const ScBigRange& rBigRange = aActions[i]->GetBigRange();
        if (rBigRange.IsValid(*pDoc) && m_xDialog->has_toplevel_focus())
        {
            bool bSetCursor = i == nCount - 1;
            pTabView->MarkRange(rBigRange.MakeRange(*pDoc), bSetCursor, bContMark);
            bContMark = true;
        }
    }

    ScChangeTrack* pChanges = pDoc->GetChangeTrack();
    bool bEnable = pDoc->IsDocEditable() && pChanges && !pChanges->IsProtected();
    pTPView->EnableAccept( bAcceptFlag && bEnable );
    pTPView->EnableReject( bRejectFlag && bEnable );
}

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

    weld::TreeView& rTreeView = pTheView->GetWidget();
    std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator());
    bool bEntry = rTreeView.get_cursor(xEntry.get());
    if (bEntry)
        rTreeView.select(*xEntry);

    int nSortedCol = rTreeView.get_sort_column();
    for (sal_Int32 i = 0; i < 5; ++i)
        m_xSortMenu->set_active("calcsort" + OUString::number(i), i == nSortedCol);

    m_xPopup->set_sensitive(u"calcedit"_ustr, false);

    if (pDoc->IsDocEditable() && bEntry)
    {
        ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xEntry));
        if (pEntryData)
        {
            ScChangeAction* pScChangeAction = static_cast<ScChangeAction*>(pEntryData->pData);
            if (pScChangeAction && !rTreeView.get_iter_depth(*xEntry))
                m_xPopup->set_sensitive(u"calcedit"_ustr, true);
        }
    }

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

    if (!sCommand.isEmpty())
    {
        if (sCommand == "calcedit")
        {
            if (bEntry)
            {
                ScRedlinData *pEntryData = weld::fromId<ScRedlinData*>(rTreeView.get_id(*xEntry));
                if (pEntryData)
                {
                    ScChangeAction* pScChangeAction = static_cast<ScChangeAction*>(pEntryData->pData);
                    pViewData->GetDocShell()->ExecuteChangeCommentDialog(pScChangeAction, m_xDialog.get(), false);
                }
            }
        }
        else
        {
            int nDialogCol = o3tl::toInt32(sCommand.subView(8));
            pTheView->HeaderBarClick(nDialogCol);
        }
    }

    return true;
}

namespace
{
    //at one point we were writing multiple AcceptChgDat strings,
    //so strip all of them and keep the results of the last one
    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 it is missing
            // we have an old version
            sal_Int32 n1 = rExtraString.indexOf('(', nPos);
            if ( n1 != -1 )
            {
                sal_Int32 n2 = rExtraString.indexOf(')', n1);
                if ( n2 != -1 )
                {
                    // cut out 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 ScAcceptChgDlg::Initialize(SfxChildWinInfo* pInfo)
{
    OUString aStr;
    if (pInfo && !pInfo->aExtraString.isEmpty())
        aStr = lcl_StripAcceptChgDat(pInfo->aExtraString);

    SfxModelessDialogController::Initialize(pInfo);

    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());
    }

    std::vector<int> aWidths;
    for (int i = 1; i < nCount; ++i)
        aWidths.push_back(aEndPos[i] - aEndPos[i - 1]);

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

void ScAcceptChgDlg::FillInfo(SfxChildWinInfo& rInfo) const
{
    SfxModelessDialogController::FillInfo(rInfo);
    //remove any old one before adding a new one
    lcl_StripAcceptChgDat(rInfo.aExtraString);
    rInfo.aExtraString += "AcceptChgDat:(";

    const int nTabCount = 5;

    rInfo.aExtraString += OUString::number(nTabCount);
    rInfo.aExtraString += ";";

    weld::TreeView& rTreeView = pTheView->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)
        aWidths.push_back(aWidths.back() + rTreeView.get_column_width(i));

    for (auto a : aWidths)
    {
        rInfo.aExtraString += OUString::number(a);
        rInfo.aExtraString += ";";
    }
    rInfo.aExtraString += ")";
}

#define CALC_DATE       3
#define CALC_POS        1

int ScAcceptChgDlg::ColCompareHdl(const weld::TreeIter& rLeft, const weld::TreeIter& rRight) const
{
    weld::TreeView& rTreeView = pTheView->GetWidget();

    sal_Int32 nCompare = 0;
    SCCOL nSortCol = rTreeView.get_sort_column();

    if (CALC_DATE == nSortCol)
    {
        RedlinData *pLeftData = weld::fromId<RedlinData*>(rTreeView.get_id(rLeft));
        RedlinData *pRightData = weld::fromId<RedlinData*>(rTreeView.get_id(rRight));
        if (pLeftData && pRightData)
        {
            if(pLeftData->aDateTime < pRightData->aDateTime)
                nCompare = -1;
            else if(pLeftData->aDateTime > pRightData->aDateTime)
                nCompare = 1;
            return nCompare;
        }
    }
    else if (CALC_POS == nSortCol)
    {
        ScRedlinData *pLeftData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rLeft));
        ScRedlinData *pRightData = weld::fromId<ScRedlinData*>(rTreeView.get_id(rRight));
        if (pLeftData && pRightData)
        {
            nCompare = 1;

            if(pLeftData->nTable < pRightData->nTable)
                nCompare = -1;
            else if(pLeftData->nTable == pRightData->nTable)
            {
                if(pLeftData->nRow < pRightData->nRow)
                    nCompare = -1;
                else if(pLeftData->nRow == pRightData->nRow)
                {
                    if(pLeftData->nCol < pRightData->nCol)
                        nCompare = -1;
                    else if(pLeftData->nCol == pRightData->nCol)
                        nCompare = 0;
                }
            }

            return nCompare;
        }
    }

    return ScGlobal::GetCaseCollator().compareString(rTreeView.get_text(rLeft, nSortCol),
                                                      rTreeView.get_text(rRight, nSortCol));
}

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