summaryrefslogtreecommitdiffstats
path: root/sw/source/uibase/misc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/uibase/misc
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/uibase/misc')
-rw-r--r--sw/source/uibase/misc/glosdoc.cxx622
-rw-r--r--sw/source/uibase/misc/glshell.cxx270
-rw-r--r--sw/source/uibase/misc/numberingtypelistbox.cxx138
-rw-r--r--sw/source/uibase/misc/redlndlg.cxx1550
-rw-r--r--sw/source/uibase/misc/swruler.cxx367
5 files changed, 2947 insertions, 0 deletions
diff --git a/sw/source/uibase/misc/glosdoc.cxx b/sw/source/uibase/misc/glosdoc.cxx
new file mode 100644
index 0000000000..7047fb6b6b
--- /dev/null
+++ b/sw/source/uibase/misc/glosdoc.cxx
@@ -0,0 +1,622 @@
+/* -*- 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 <algorithm>
+#include <string_view>
+
+#include <com/sun/star/container/XNamed.hpp>
+#include <comphelper/servicehelper.hxx>
+
+#include <unotools/transliterationwrapper.hxx>
+
+#include <vcl/errinf.hxx>
+#include <osl/diagnose.h>
+#include <rtl/character.hxx>
+#include <svl/urihelper.hxx>
+#include <svl/fstathelper.hxx>
+#include <unotools/pathoptions.hxx>
+#include <unotools/tempfile.hxx>
+#include <o3tl/string_view.hxx>
+#include <swtypes.hxx>
+#include <glosdoc.hxx>
+#include <shellio.hxx>
+#include <swunohelper.hxx>
+
+#include <unoatxt.hxx>
+#include <swerror.h>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace
+{
+
+OUString lcl_FullPathName(std::u16string_view sPath, std::u16string_view sName)
+{
+ return OUString::Concat(sPath) + "/" + sName + SwGlossaries::GetExtension();
+}
+
+OUString lcl_CheckFileName( const OUString& rNewFilePath,
+ std::u16string_view aNewGroupName )
+{
+ const sal_Int32 nLen = aNewGroupName.size();
+ OUStringBuffer aBuf(nLen);
+ //group name should contain only A-Z and a-z and spaces
+ for( sal_Int32 i=0; i < nLen; ++i )
+ {
+ const sal_Unicode cChar = aNewGroupName[i];
+ if (rtl::isAsciiAlphanumeric(cChar) ||
+ cChar == '_' || cChar == 0x20)
+ {
+ aBuf.append(cChar);
+ }
+ }
+
+ const OUString sRet = aBuf.makeStringAndClear().trim();
+ if ( !sRet.isEmpty() )
+ {
+ if (!FStatHelper::IsDocument( lcl_FullPathName(rNewFilePath, sRet) ))
+ return sRet;
+ }
+
+ //generate generic name
+ utl::TempFileNamed aTemp(u"group", true, SwGlossaries::GetExtension(), &rNewFilePath);
+ aTemp.EnableKillingFile();
+
+ INetURLObject aTempURL( aTemp.GetURL() );
+ return aTempURL.GetBase();
+}
+
+}
+
+// supplies the default group's name
+OUString SwGlossaries::GetDefName()
+{
+ return "standard";
+
+}
+
+// supplies the number of text block groups
+size_t SwGlossaries::GetGroupCnt()
+{
+ return GetNameList().size();
+}
+
+// supplies the group's name
+bool SwGlossaries::FindGroupName(OUString& rGroup)
+{
+ // if the group name doesn't contain a path, a suitable group entry
+ // can the searched for here;
+ const size_t nCount = GetGroupCnt();
+ for(size_t i = 0; i < nCount; ++i)
+ {
+ const OUString sTemp(GetGroupName(i));
+ if (rGroup == o3tl::getToken(sTemp, 0, GLOS_DELIM))
+ {
+ rGroup = sTemp;
+ return true;
+ }
+ }
+ // you can search two times because for more directories the case sensitive
+ // name could occur multiple times
+ const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore();
+ for(size_t i = 0; i < nCount; ++i)
+ {
+ const OUString sTemp( GetGroupName( i ));
+ sal_uInt16 nPath = o3tl::toUInt32(o3tl::getToken(sTemp, 1, GLOS_DELIM));
+
+ if (!SWUnoHelper::UCB_IsCaseSensitiveFileName( m_PathArr[nPath] )
+ && rSCmp.isEqual( rGroup, sTemp.getToken( 0, GLOS_DELIM) ) )
+ {
+ rGroup = sTemp;
+ return true;
+ }
+ }
+ return false;
+}
+
+OUString const & SwGlossaries::GetGroupName(size_t nGroupId)
+{
+ OSL_ENSURE(nGroupId < m_GlosArr.size(),
+ "SwGlossaries::GetGroupName: index out of bounds");
+ return m_GlosArr[nGroupId];
+}
+
+OUString SwGlossaries::GetGroupTitle( const OUString& rGroupName )
+{
+ OUString sRet;
+ OUString sGroup(rGroupName);
+ if (sGroup.indexOf(GLOS_DELIM)<0)
+ FindGroupName(sGroup);
+ std::unique_ptr<SwTextBlocks> pGroup = GetGroupDoc(sGroup);
+ if(pGroup)
+ {
+ sRet = pGroup->GetName();
+ }
+ return sRet;
+}
+
+// supplies the group rName's text block document
+std::unique_ptr<SwTextBlocks> SwGlossaries::GetGroupDoc(const OUString &rName,
+ bool bCreate)
+{
+ // insert to the list of text blocks if applicable
+ if(bCreate && !m_GlosArr.empty())
+ {
+ if (std::none_of(m_GlosArr.begin(), m_GlosArr.end(),
+ [&rName](const OUString& rEntry) { return rEntry == rName; }))
+ { // block not in the list
+ m_GlosArr.push_back(rName);
+ }
+ }
+ return GetGlosDoc( rName, bCreate );
+}
+
+// Creates a new document with the group name. temporarily also created as file
+// so that groups remain there later (without access).
+bool SwGlossaries::NewGroupDoc(OUString& rGroupName, const OUString& rTitle)
+{
+ const std::u16string_view sNewPath(o3tl::getToken(rGroupName, 1, GLOS_DELIM));
+ sal_uInt16 nNewPath = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(sNewPath));
+ if (static_cast<size_t>(nNewPath) >= m_PathArr.size())
+ return false;
+ const OUString sNewFilePath(m_PathArr[nNewPath]);
+ const OUString sNewGroup = lcl_CheckFileName(sNewFilePath, o3tl::getToken(rGroupName, 0, GLOS_DELIM))
+ + OUStringChar(GLOS_DELIM) + sNewPath;
+ std::unique_ptr<SwTextBlocks> pBlock = GetGlosDoc( sNewGroup );
+ if(pBlock)
+ {
+ GetNameList().push_back(sNewGroup);
+ pBlock->SetName(rTitle);
+ rGroupName = sNewGroup;
+ return true;
+ }
+ return false;
+}
+
+bool SwGlossaries::RenameGroupDoc(
+ const OUString& rOldGroup, OUString& rNewGroup, const OUString& rNewTitle )
+{
+ sal_uInt16 nOldPath = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rOldGroup, 1, GLOS_DELIM)));
+ if (static_cast<size_t>(nOldPath) >= m_PathArr.size())
+ return false;
+
+ const OUString sOldFileURL =
+ lcl_FullPathName(m_PathArr[nOldPath], o3tl::getToken(rOldGroup, 0, GLOS_DELIM));
+
+ if (!FStatHelper::IsDocument( sOldFileURL ))
+ {
+ OSL_FAIL("group doesn't exist!");
+ return false;
+ }
+
+ sal_uInt16 nNewPath = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rNewGroup, 1, GLOS_DELIM)));
+ if (static_cast<size_t>(nNewPath) >= m_PathArr.size())
+ return false;
+
+ const OUString sNewFileName = lcl_CheckFileName(m_PathArr[nNewPath],
+ o3tl::getToken(rNewGroup, 0, GLOS_DELIM));
+ const OUString sNewFileURL = lcl_FullPathName(m_PathArr[nNewPath], sNewFileName);
+
+ if (FStatHelper::IsDocument( sNewFileURL ))
+ {
+ OSL_FAIL("group already exists!");
+ return false;
+ }
+
+ if (!SWUnoHelper::UCB_MoveFile(sOldFileURL, sNewFileURL ))
+ return false;
+
+ RemoveFileFromList( rOldGroup );
+
+ rNewGroup = sNewFileName + OUStringChar(GLOS_DELIM) + OUString::number(nNewPath);
+ if (m_GlosArr.empty())
+ {
+ GetNameList();
+ }
+ else
+ {
+ m_GlosArr.push_back(rNewGroup);
+ }
+
+ SwTextBlocks aNewBlock( sNewFileURL );
+ aNewBlock.SetName(rNewTitle);
+
+ return true;
+}
+
+// Deletes a text block group
+bool SwGlossaries::DelGroupDoc(std::u16string_view rName)
+{
+ sal_uInt16 nPath = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rName, 1, GLOS_DELIM)));
+ if (static_cast<size_t>(nPath) >= m_PathArr.size())
+ return false;
+ const std::u16string_view sBaseName(o3tl::getToken(rName, 0, GLOS_DELIM));
+ const OUString sFileURL = lcl_FullPathName(m_PathArr[nPath], sBaseName);
+ const OUString aName = sBaseName + OUStringChar(GLOS_DELIM) + OUString::number(nPath);
+ // Even if the file doesn't exist it has to be deleted from
+ // the list of text block regions
+ // no && because of CFfront
+ bool bRemoved = SWUnoHelper::UCB_DeleteFile( sFileURL );
+ OSL_ENSURE(bRemoved, "file has not been removed");
+ RemoveFileFromList( aName );
+ return bRemoved;
+}
+
+SwGlossaries::~SwGlossaries()
+{
+ InvalidateUNOOjects();
+}
+
+// read a block document
+std::unique_ptr<SwTextBlocks> SwGlossaries::GetGlosDoc( const OUString &rName, bool bCreate ) const
+{
+ sal_uInt16 nPath = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(rName, 1, GLOS_DELIM)));
+ std::unique_ptr<SwTextBlocks> pTmp;
+ if (static_cast<size_t>(nPath) < m_PathArr.size())
+ {
+ const OUString sFileURL =
+ lcl_FullPathName(m_PathArr[nPath], o3tl::getToken(rName, 0, GLOS_DELIM));
+
+ bool bExist = false;
+ if(!bCreate)
+ bExist = FStatHelper::IsDocument( sFileURL );
+
+ if (bCreate || bExist)
+ {
+ pTmp.reset(new SwTextBlocks( sFileURL ));
+ bool bOk = true;
+ if( pTmp->GetError() )
+ {
+ ErrorHandler::HandleError( pTmp->GetError() );
+ bOk = ! pTmp->GetError().IsError();
+ }
+
+ if( bOk && pTmp->GetName().isEmpty() )
+ pTmp->SetName( rName );
+ }
+ }
+
+ return pTmp;
+}
+
+// access to the list of names; read in if applicable
+std::vector<OUString> & SwGlossaries::GetNameList()
+{
+ if (m_GlosArr.empty())
+ {
+ const OUString sExt( SwGlossaries::GetExtension() );
+ for (size_t i = 0; i < m_PathArr.size(); ++i)
+ {
+ std::vector<OUString> aFiles;
+
+ SWUnoHelper::UCB_GetFileListOfFolder(m_PathArr[i], aFiles, &sExt);
+ for (const OUString& aTitle : aFiles)
+ {
+ const OUString sName( aTitle.subView( 0, aTitle.getLength() - sExt.getLength() )
+ + OUStringChar(GLOS_DELIM) + OUString::number( static_cast<sal_Int16>(i) ));
+ m_GlosArr.push_back(sName);
+ }
+ }
+ if (m_GlosArr.empty())
+ {
+ // the standard block is inside of the path's first part
+ m_GlosArr.emplace_back(SwGlossaries::GetDefName() + OUStringChar(GLOS_DELIM) + "0" );
+ }
+ }
+ return m_GlosArr;
+}
+
+SwGlossaries::SwGlossaries()
+{
+ UpdateGlosPath(true);
+}
+
+// set new path and recreate internal array
+static OUString lcl_makePath(const std::vector<OUString>& rPaths)
+{
+ std::vector<OUString>::const_iterator aIt(rPaths.begin());
+ const std::vector<OUString>::const_iterator aEnd(rPaths.end());
+ OUStringBuffer aPath(*aIt);
+ for (++aIt; aIt != aEnd; ++aIt)
+ {
+ aPath.append(SVT_SEARCHPATH_DELIMITER);
+ const INetURLObject aTemp(*aIt);
+ aPath.append(aTemp.GetFull());
+ }
+ return aPath.makeStringAndClear();
+}
+
+void SwGlossaries::UpdateGlosPath(bool bFull)
+{
+ SvtPathOptions aPathOpt;
+ const OUString& aNewPath( aPathOpt.GetAutoTextPath() );
+ bool bPathChanged = m_aPath != aNewPath;
+ if (!(bFull || bPathChanged))
+ return;
+
+ m_aPath = aNewPath;
+
+ m_PathArr.clear();
+
+ std::vector<OUString> aDirArr;
+ std::vector<OUString> aInvalidPaths;
+ if (!m_aPath.isEmpty())
+ {
+ sal_Int32 nIndex = 0;
+ do
+ {
+ const OUString sPth = URIHelper::SmartRel2Abs(
+ INetURLObject(),
+ m_aPath.getToken(0, SVT_SEARCHPATH_DELIMITER, nIndex),
+ URIHelper::GetMaybeFileHdl());
+ if (!aDirArr.empty() &&
+ std::find(aDirArr.begin(), aDirArr.end(), sPth) != aDirArr.end())
+ {
+ continue;
+ }
+ aDirArr.push_back(sPth);
+ if( !FStatHelper::IsFolder( sPth ) )
+ aInvalidPaths.push_back(sPth);
+ else
+ m_PathArr.push_back(sPth);
+ }
+ while (nIndex>=0);
+ }
+
+ if (m_aPath.isEmpty() || !aInvalidPaths.empty())
+ {
+ std::sort(aInvalidPaths.begin(), aInvalidPaths.end());
+ aInvalidPaths.erase(std::unique(aInvalidPaths.begin(), aInvalidPaths.end()), aInvalidPaths.end());
+ if (bPathChanged || (m_aInvalidPaths != aInvalidPaths))
+ {
+ m_aInvalidPaths = aInvalidPaths;
+ // wrong path, that means AutoText directory doesn't exist
+
+ ErrorHandler::HandleError( ErrCodeMsg(
+ ERR_AUTOPATH_ERROR, lcl_makePath(m_aInvalidPaths),
+ DialogMask::ButtonsOk | DialogMask::MessageError ) );
+ m_bError = true;
+ }
+ else
+ m_bError = false;
+ }
+ else
+ m_bError = false;
+
+ if (!m_GlosArr.empty())
+ {
+ m_GlosArr.clear();
+ GetNameList();
+ }
+}
+
+void SwGlossaries::ShowError()
+{
+ ErrCodeMsg nPathError(ERR_AUTOPATH_ERROR, lcl_makePath(m_aInvalidPaths), DialogMask::ButtonsOk );
+ ErrorHandler::HandleError( nPathError );
+}
+
+OUString SwGlossaries::GetExtension()
+{
+ return ".bau";
+}
+
+void SwGlossaries::RemoveFileFromList( const OUString& rGroup )
+{
+ if (m_GlosArr.empty())
+ return;
+
+ auto it = std::find(m_GlosArr.begin(), m_GlosArr.end(), rGroup);
+ if (it == m_GlosArr.end())
+ return;
+
+ {
+ // tell the UNO AutoTextGroup object that it's not valid anymore
+ for ( UnoAutoTextGroups::iterator aLoop = m_aGlossaryGroups.begin();
+ aLoop != m_aGlossaryGroups.end();
+ )
+ {
+ rtl::Reference< SwXAutoTextGroup > xNamed( aLoop->get() );
+ if ( !xNamed.is() )
+ {
+ aLoop = m_aGlossaryGroups.erase(aLoop);
+ }
+ else if ( xNamed->getName() == rGroup )
+ {
+ xNamed->Invalidate();
+ // note that this static_cast works because we know that the array only
+ // contains SwXAutoTextGroup implementation
+ m_aGlossaryGroups.erase( aLoop );
+ break;
+ } else
+ ++aLoop;
+ }
+ }
+
+ {
+ // tell all our UNO AutoTextEntry objects that they're not valid anymore
+ for ( UnoAutoTextEntries::iterator aLoop = m_aGlossaryEntries.begin();
+ aLoop != m_aGlossaryEntries.end();
+ )
+ {
+ rtl::Reference<SwXAutoTextEntry> pEntry = aLoop->get();
+ if ( pEntry && ( pEntry->GetGroupName() == rGroup ) )
+ {
+ pEntry->Invalidate();
+ aLoop = m_aGlossaryEntries.erase( aLoop );
+ }
+ else
+ ++aLoop;
+ }
+ }
+
+ m_GlosArr.erase(it);
+}
+
+OUString SwGlossaries::GetCompleteGroupName( std::u16string_view rGroupName )
+{
+ const size_t nCount = GetGroupCnt();
+ // when the group name was created internally the path is here as well
+ sal_Int32 nIndex = 0;
+ const std::u16string_view sGroupName(o3tl::getToken(rGroupName, 0, GLOS_DELIM, nIndex));
+ const bool bPathLen = !o3tl::getToken(rGroupName, 0, GLOS_DELIM, nIndex).empty();
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ const OUString sGrpName = GetGroupName(i);
+ if (bPathLen)
+ {
+ if (rGroupName == sGrpName)
+ return sGrpName;
+ }
+ else
+ {
+ if (sGroupName == o3tl::getToken(sGrpName, 0, GLOS_DELIM))
+ return sGrpName;
+ }
+ }
+ return OUString();
+}
+
+void SwGlossaries::InvalidateUNOOjects()
+{
+ // invalidate all the AutoTextGroup-objects
+ for (const auto& rGroup : m_aGlossaryGroups)
+ {
+ rtl::Reference< SwXAutoTextGroup > xGroup( rGroup.get() );
+ if ( xGroup.is() )
+ xGroup->Invalidate();
+ }
+ UnoAutoTextGroups().swap(m_aGlossaryGroups);
+
+ // invalidate all the AutoTextEntry-objects
+ for (const auto& rEntry : m_aGlossaryEntries)
+ {
+ rtl::Reference<SwXAutoTextEntry> pEntry = rEntry.get();
+ if ( pEntry )
+ pEntry->Invalidate();
+ }
+ UnoAutoTextEntries().swap(m_aGlossaryEntries);
+}
+
+Reference< text::XAutoTextGroup > SwGlossaries::GetAutoTextGroup( std::u16string_view _rGroupName )
+{
+ bool _bCreate = true;
+ // first, find the name with path-extension
+ const OUString sCompleteGroupName = GetCompleteGroupName( _rGroupName );
+
+ rtl::Reference< SwXAutoTextGroup > xGroup;
+
+ // look up the group in the cache
+ UnoAutoTextGroups::iterator aSearch = m_aGlossaryGroups.begin();
+ for ( ; aSearch != m_aGlossaryGroups.end(); )
+ {
+ rtl::Reference<SwXAutoTextGroup> pSwGroup = aSearch->get();
+ if ( !pSwGroup )
+ {
+ // the object is dead in the meantime -> remove from cache
+ aSearch = m_aGlossaryGroups.erase( aSearch );
+ continue;
+ }
+
+ if ( _rGroupName == pSwGroup->getName() )
+ { // the group is already cached
+ if ( !sCompleteGroupName.isEmpty() )
+ { // the group still exists -> return it
+ xGroup = pSwGroup;
+ break;
+ }
+ else
+ {
+ // this group does not exist (anymore) -> release the cached UNO object for it
+ aSearch = m_aGlossaryGroups.erase( aSearch );
+ // so it won't be created below
+ _bCreate = false;
+ break;
+ }
+ }
+
+ ++aSearch;
+ }
+
+ if ( !xGroup.is() && _bCreate )
+ {
+ xGroup = new SwXAutoTextGroup( sCompleteGroupName, this );
+ // cache it
+ m_aGlossaryGroups.emplace_back( xGroup );
+ }
+
+ return xGroup;
+}
+
+Reference< text::XAutoTextEntry > SwGlossaries::GetAutoTextEntry(
+ const OUString& rCompleteGroupName,
+ const OUString& rGroupName,
+ const OUString& rEntryName )
+{
+ //standard must be created
+ bool bCreate = ( rCompleteGroupName == GetDefName() );
+ std::unique_ptr< SwTextBlocks > pGlosGroup( GetGroupDoc( rCompleteGroupName, bCreate ) );
+
+ if (!pGlosGroup || pGlosGroup->GetError())
+ throw lang::WrappedTargetException();
+
+ sal_uInt16 nIdx = pGlosGroup->GetIndex( rEntryName );
+ if ( USHRT_MAX == nIdx )
+ throw container::NoSuchElementException();
+
+ rtl::Reference< SwXAutoTextEntry > xReturn;
+
+ UnoAutoTextEntries::iterator aSearch( m_aGlossaryEntries.begin() );
+ for ( ; aSearch != m_aGlossaryEntries.end(); )
+ {
+ rtl::Reference< SwXAutoTextEntry > pEntry( aSearch->get() );
+
+ if ( !pEntry )
+ {
+ // the object is dead in the meantime -> remove from cache
+ aSearch = m_aGlossaryEntries.erase( aSearch );
+ continue;
+ }
+
+ if ( pEntry
+ && pEntry->GetGroupName() == rGroupName
+ && pEntry->GetEntryName() == rEntryName
+ )
+ {
+ xReturn = pEntry;
+ break;
+ }
+
+ ++aSearch;
+ }
+
+ if ( !xReturn.is() )
+ {
+ xReturn = new SwXAutoTextEntry( this, rGroupName, rEntryName );
+ // cache it
+ m_aGlossaryEntries.emplace_back( xReturn );
+ }
+
+ return xReturn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/uibase/misc/glshell.cxx b/sw/source/uibase/misc/glshell.cxx
new file mode 100644
index 0000000000..59ede231e8
--- /dev/null
+++ b/sw/source/uibase/misc/glshell.cxx
@@ -0,0 +1,270 @@
+/* -*- 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 <sal/config.h>
+
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/frame/XTitle.hpp>
+#include <svl/eitem.hxx>
+#include <svl/stritem.hxx>
+#include <sfx2/printer.hxx>
+#include <sfx2/request.hxx>
+#include <svl/macitem.hxx>
+#include <gloshdl.hxx>
+
+#include <editeng/acorrcfg.hxx>
+#include <sfx2/objface.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <wrtsh.hxx>
+#include <view.hxx>
+#include <glshell.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentDeviceAccess.hxx>
+#include <IDocumentState.hxx>
+#include <glosdoc.hxx>
+#include <shellio.hxx>
+#include <initui.hxx>
+#include <cmdid.h>
+#include <strings.hrc>
+
+#define ShellClass_SwWebGlosDocShell
+#define ShellClass_SwGlosDocShell
+
+#include <sfx2/msg.hxx>
+#include <swslots.hxx>
+#include <memory>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+SFX_IMPL_SUPERCLASS_INTERFACE(SwGlosDocShell, SwDocShell)
+
+void SwGlosDocShell::InitInterface_Impl()
+{
+}
+
+SFX_IMPL_SUPERCLASS_INTERFACE(SwWebGlosDocShell, SwWebDocShell)
+
+void SwWebGlosDocShell::InitInterface_Impl()
+{
+}
+
+
+static void lcl_Execute( SwDocShell& rSh, SfxRequest& rReq )
+{
+ if ( rReq.GetSlot() != SID_SAVEDOC )
+ return;
+
+ if( !rSh.HasName() )
+ {
+ rReq.SetReturnValue( SfxBoolItem( 0, rSh.Save() ) );
+ }
+ else
+ {
+ const SfxPoolItemHolder& rResult(
+ rSh.ExecuteSlot(rReq,
+ rSh.SfxObjectShell::GetInterface()));
+ const SfxBoolItem* pRes(static_cast< const SfxBoolItem* >(rResult.getItem()));
+ if( pRes->GetValue() )
+ rSh.GetDoc()->getIDocumentState().ResetModified();
+ }
+}
+
+static void lcl_GetState( SwDocShell& rSh, SfxItemSet& rSet )
+{
+ if( SfxItemState::DEFAULT >= rSet.GetItemState( SID_SAVEDOC, false ))
+ {
+ if( !rSh.GetDoc()->getIDocumentState().IsModified() )
+ rSet.DisableItem( SID_SAVEDOC );
+ else
+ rSet.Put( SfxStringItem( SID_SAVEDOC, SwResId(STR_SAVE_GLOSSARY)));
+ }
+}
+
+static bool lcl_Save( SwWrtShell& rSh, const OUString& rGroupName,
+ const OUString& rShortNm, const OUString& rLongNm )
+{
+ const SvxAutoCorrCfg& rCfg = SvxAutoCorrCfg::Get();
+ std::unique_ptr<SwTextBlocks> pBlock(::GetGlossaries()->GetGroupDoc( rGroupName ));
+
+ SvxMacro aStart { OUString(), OUString() };
+ SvxMacro aEnd { OUString(), OUString() };
+ SwGlossaryHdl* pGlosHdl;
+
+ pGlosHdl = rSh.GetView().GetGlosHdl();
+ pGlosHdl->GetMacros( rShortNm, aStart, aEnd, pBlock.get() );
+
+ sal_uInt16 nRet = rSh.SaveGlossaryDoc( *pBlock, rLongNm, rShortNm,
+ rCfg.IsSaveRelFile(),
+ pBlock->IsOnlyTextBlock( rShortNm ) );
+
+ if(aStart.HasMacro() || aEnd.HasMacro() )
+ {
+ SvxMacro* pStart = aStart.HasMacro() ? &aStart : nullptr;
+ SvxMacro* pEnd = aEnd.HasMacro() ? &aEnd : nullptr;
+ pGlosHdl->SetMacros( rShortNm, pStart, pEnd, pBlock.get() );
+ }
+
+ rSh.EnterStdMode();
+ if( USHRT_MAX != nRet )
+ rSh.ResetModified();
+ return nRet != USHRT_MAX;
+}
+
+SwGlosDocShell::SwGlosDocShell(bool bNewShow)
+ : SwDocShell( bNewShow
+ ? SfxObjectCreateMode::STANDARD : SfxObjectCreateMode::INTERNAL )
+{
+}
+
+SwGlosDocShell::~SwGlosDocShell( )
+{
+}
+
+void SwGlosDocShell::Execute( SfxRequest& rReq )
+{
+ ::lcl_Execute( *this, rReq );
+}
+
+void SwGlosDocShell::GetState( SfxItemSet& rSet )
+{
+ ::lcl_GetState( *this, rSet );
+}
+
+bool SwGlosDocShell::Save()
+{
+ // In case of an API object which holds this document, it is possible that the WrtShell is already
+ // dead. For instance, if the doc is modified via this API object, and then, upon office shutdown,
+ // the document's view is closed (by the SFX framework) _before_ the API object is release and
+ // tries to save the doc, again.
+ // 96380 - 2002-03-03 - fs@openoffice.org
+ if ( GetWrtShell() )
+ return ::lcl_Save( *GetWrtShell(), m_aGroupName, m_aShortName, m_aLongName );
+ else
+ {
+ SetModified( false );
+ return false;
+ }
+}
+
+SwWebGlosDocShell::SwWebGlosDocShell()
+{
+}
+
+SwWebGlosDocShell::~SwWebGlosDocShell( )
+{
+}
+
+void SwWebGlosDocShell::Execute( SfxRequest& rReq )
+{
+ ::lcl_Execute( *this, rReq );
+}
+
+void SwWebGlosDocShell::GetState( SfxItemSet& rSet )
+{
+ ::lcl_GetState( *this, rSet );
+}
+
+bool SwWebGlosDocShell::Save()
+{
+ // same comment as in SwGlosDocShell::Save - see there
+ if ( GetWrtShell() )
+ return ::lcl_Save( *GetWrtShell(), m_aGroupName, m_aShortName, m_aLongName );
+ else
+ {
+ SetModified( false );
+ return false;
+ }
+}
+
+SwDocShellRef SwGlossaries::EditGroupDoc( const OUString& rGroup, const OUString& rShortName, bool bShow )
+{
+ SwDocShellRef xDocSh;
+
+ std::unique_ptr<SwTextBlocks> pGroup = GetGroupDoc( rGroup );
+ if (pGroup && pGroup->GetCount())
+ {
+ // query which view is registered. In WebWriter there is no normal view
+ SfxInterfaceId nViewId = nullptr != SwView::Factory() ? SFX_INTERFACE_SFXDOCSH : SfxInterfaceId(6);
+ const OUString sLongName = pGroup->GetLongName(pGroup->GetIndex( rShortName ));
+
+ if( SfxInterfaceId(6) == nViewId )
+ {
+ SwWebGlosDocShell* pDocSh = new SwWebGlosDocShell();
+ xDocSh = pDocSh;
+ pDocSh->DoInitNew();
+ pDocSh->SetLongName( sLongName );
+ pDocSh->SetShortName( rShortName);
+ pDocSh->SetGroupName( rGroup );
+ }
+ else
+ {
+ SwGlosDocShell* pDocSh = new SwGlosDocShell(bShow);
+ xDocSh = pDocSh;
+ pDocSh->DoInitNew();
+ pDocSh->SetLongName( sLongName );
+ pDocSh->SetShortName( rShortName );
+ pDocSh->SetGroupName( rGroup );
+ }
+
+ // set document title
+ SfxViewFrame* pFrame = bShow ? SfxViewFrame::LoadDocument( *xDocSh, nViewId ) : SfxViewFrame::LoadHiddenDocument( *xDocSh, nViewId );
+ const OUString aDocTitle(SwResId( STR_GLOSSARY ) + " " + sLongName);
+
+ bool const bDoesUndo =
+ xDocSh->GetDoc()->GetIDocumentUndoRedo().DoesUndo();
+ xDocSh->GetDoc()->GetIDocumentUndoRedo().DoUndo( false );
+
+ xDocSh->GetWrtShell()->InsertGlossary( *pGroup, rShortName );
+ if( !xDocSh->GetDoc()->getIDocumentDeviceAccess().getPrinter( false ) )
+ {
+ // we create a default SfxPrinter.
+ // ItemSet is deleted by Sfx!
+ auto pSet = std::make_unique<SfxItemSetFixed<
+ SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN,
+ SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC,
+ FN_PARAM_ADDPRINTER, FN_PARAM_ADDPRINTER>>
+ ( xDocSh->GetDoc()->GetAttrPool() );
+ VclPtr<SfxPrinter> pPrinter = VclPtr<SfxPrinter>::Create( std::move(pSet) );
+
+ // and append it to the document.
+ xDocSh->GetDoc()->getIDocumentDeviceAccess().setPrinter( pPrinter, true, true );
+ }
+
+ xDocSh->SetTitle( aDocTitle );
+ try
+ {
+ // set the UI-title
+ uno::Reference< frame::XTitle > xTitle( xDocSh->GetModel(), uno::UNO_QUERY_THROW );
+ xTitle->setTitle( aDocTitle );
+ }
+ catch (const uno::Exception&)
+ {
+ }
+
+ xDocSh->GetDoc()->GetIDocumentUndoRedo().DoUndo( bDoesUndo );
+ xDocSh->GetDoc()->getIDocumentState().ResetModified();
+ if ( bShow )
+ pFrame->GetFrame().Appear();
+ }
+ return xDocSh;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/uibase/misc/numberingtypelistbox.cxx b/sw/source/uibase/misc/numberingtypelistbox.cxx
new file mode 100644
index 0000000000..94a26bcb9f
--- /dev/null
+++ b/sw/source/uibase/misc/numberingtypelistbox.cxx
@@ -0,0 +1,138 @@
+/* -*- 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 <numberingtypelistbox.hxx>
+#include <com/sun/star/style/NumberingType.hpp>
+#include <com/sun/star/text/DefaultNumberingProvider.hpp>
+#include <com/sun/star/text/XDefaultNumberingProvider.hpp>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/text/XNumberingTypeInfo.hpp>
+#include <editeng/numitem.hxx>
+#include <svx/strarray.hxx>
+#include <osl/diagnose.h>
+
+using namespace com::sun::star;
+
+struct SwNumberingTypeListBox_Impl
+{
+ uno::Reference<text::XNumberingTypeInfo> xInfo;
+};
+
+SwNumberingTypeListBox::SwNumberingTypeListBox(std::unique_ptr<weld::ComboBox> pWidget)
+ : m_xWidget(std::move(pWidget))
+ , m_xImpl(new SwNumberingTypeListBox_Impl)
+{
+ uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
+ uno::Reference<text::XDefaultNumberingProvider> xDefNum
+ = text::DefaultNumberingProvider::create(xContext);
+ m_xImpl->xInfo.set(xDefNum, uno::UNO_QUERY);
+}
+
+SwNumberingTypeListBox::~SwNumberingTypeListBox() {}
+
+void SwNumberingTypeListBox::Reload(SwInsertNumTypes nTypeFlags)
+{
+ m_xWidget->clear();
+ uno::Sequence<sal_Int16> aTypes;
+ if (nTypeFlags & SwInsertNumTypes::Extended)
+ {
+ if (m_xImpl->xInfo.is())
+ aTypes = m_xImpl->xInfo->getSupportedNumberingTypes();
+ }
+
+ for (size_t i = 0; i < SvxNumberingTypeTable::Count(); i++)
+ {
+ sal_IntPtr nValue = SvxNumberingTypeTable::GetValue(i);
+ bool bInsert = true;
+ int nPos = -1;
+ switch (nValue)
+ {
+ case style::NumberingType::NUMBER_NONE:
+ bInsert = bool(nTypeFlags & SwInsertNumTypes::NoNumbering);
+ nPos = 0;
+
+ break;
+ case style::NumberingType::CHAR_SPECIAL:
+ bInsert = false;
+
+ break;
+ case style::NumberingType::PAGE_DESCRIPTOR:
+ bInsert = false;
+
+ break;
+ case style::NumberingType::BITMAP:
+ bInsert = false;
+
+ break;
+ case style::NumberingType::BITMAP | LINK_TOKEN:
+ bInsert = false;
+
+ break;
+ default:
+ if (nValue > style::NumberingType::CHARS_LOWER_LETTER_N)
+ {
+ // Insert only if offered by i18n framework per configuration.
+ bInsert = std::find(std::cbegin(aTypes), std::cend(aTypes), nValue)
+ != std::cend(aTypes);
+ }
+ }
+ if (bInsert)
+ {
+ OUString sId(OUString::number(nValue));
+ m_xWidget->insert(nPos, SvxNumberingTypeTable::GetString(i), &sId, nullptr, nullptr);
+ }
+ }
+ if (!(nTypeFlags & SwInsertNumTypes::Extended))
+ return;
+
+ for (sal_Int16 nCurrent : std::as_const(aTypes))
+ {
+ if (nCurrent > style::NumberingType::CHARS_LOWER_LETTER_N)
+ {
+ if (m_xWidget->find_id(OUString::number(nCurrent)) == -1)
+ {
+ m_xWidget->append(OUString::number(nCurrent),
+ m_xImpl->xInfo->getNumberingIdentifier(nCurrent));
+ }
+ }
+ }
+ m_xWidget->set_active(0);
+}
+
+SvxNumType SwNumberingTypeListBox::GetSelectedNumberingType() const
+{
+ SvxNumType nRet = SVX_NUM_CHARS_UPPER_LETTER;
+ int nSelPos = m_xWidget->get_active();
+ if (nSelPos != -1)
+ nRet = static_cast<SvxNumType>(m_xWidget->get_id(nSelPos).toInt32());
+#if OSL_DEBUG_LEVEL > 0
+ else
+ OSL_FAIL("NumberingTypeListBox not selected");
+#endif
+ return nRet;
+}
+
+bool SwNumberingTypeListBox::SelectNumberingType(SvxNumType nType)
+{
+ int nPos = m_xWidget->find_id(OUString::number(nType));
+ m_xWidget->set_active(nPos);
+ return nPos != -1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/uibase/misc/redlndlg.cxx b/sw/source/uibase/misc/redlndlg.cxx
new file mode 100644
index 0000000000..0461276140
--- /dev/null
+++ b/sw/source/uibase/misc/redlndlg.cxx
@@ -0,0 +1,1550 @@
+/* -*- 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,
+ "svx/ui/acceptrejectchangesdialog.ui", "AcceptRejectChangesDialog")
+ , m_xContentArea(m_xBuilder->weld_container("container"))
+ , m_pChildWin(pChild)
+{
+ m_xImplDlg.reset(new SwRedlineAcceptDlg(m_xDialog, m_xBuilder.get(), m_xContentArea.get()));
+}
+
+void SwModelessRedlineAcceptDlg::Activate()
+{
+ 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()
+{
+}
+
+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("writermenu"))
+ , m_xSortMenu(pBuilder->weld_menu("writersortmenu"))
+{
+ m_pTPView = m_xTabPagesCTRL->GetViewPage();
+
+ m_pTable = m_pTPView->GetTableControl();
+ m_pTable->SetWriterView();
+
+ 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));
+}
+
+SwRedlineAcceptDlg::~SwRedlineAcceptDlg()
+{
+}
+
+void SwRedlineAcceptDlg::Init(SwRedlineTable::size_type nStart)
+{
+ std::optional<SwWait> oWait;
+ if (SwView *pView = GetActiveView())
+ oWait.emplace(*pView->GetDocShell(), false);
+ weld::TreeView& rTreeView = m_pTable->GetWidget();
+ m_aUsedSeqNo.clear();
+
+ 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();
+
+ // #i69618# this moves the list box to the right position, visually
+ std::unique_ptr<weld::TreeIter> xSelEntry(rTreeView.make_iterator());
+ if (rTreeView.get_selected(xSelEntry.get()))
+ rTreeView.scroll_to_row(*xSelEntry); //#i70937#, force the scroll
+}
+
+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();
+ bool const bEnable = pSh && !pSh->GetDoc()->GetDocShell()->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 );
+}
+
+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 OUString();
+}
+
+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 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;
+
+ 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)
+ 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)
+ 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)
+ 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
+ weld::TreeView& rTreeView = m_pTable->GetWidget();
+ 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();
+}
+
+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));
+ OUString sAuthor = rRedln.GetAuthorString(nStack);
+ pData->aDateTime = rRedln.GetTimeStamp(nStack);
+ pData->eType = rRedln.GetType(nStack);
+ OUString sDateEntry = GetAppLangDateTimeString(pData->aDateTime);
+ 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;
+ const SwRangeRedline* pCurrRedline;
+ if (!nStart && !rTreeView.get_selected(nullptr))
+ {
+ pCurrRedline = pSh->GetCurrRedline();
+ if( !pCurrRedline )
+ {
+ pSh->SwCursorShell::Push();
+ pCurrRedline = pSh->SelNextRedline();
+ if( nullptr == pCurrRedline )
+ pCurrRedline = pSh->SelPrevRedline();
+ pSh->SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent);
+ }
+ }
+ else
+ pCurrRedline = nullptr;
+
+ 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);
+
+ if (pCurrRedline == &rRedln)
+ {
+ rTreeView.thaw();
+ rTreeView.set_cursor(*xParent);
+ rTreeView.select(*xParent);
+ rTreeView.scroll_to_row(*xParent);
+ rTreeView.freeze();
+ }
+
+ 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, 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(nullptr != aResult.getItem());
+ }
+
+ 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);
+ }
+ }
+
+ bool const bEnable = !pSh->GetDoc()->GetDocShell()->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("writeredit", bEntry && pRed &&
+ !rTreeView.get_iter_depth(*xEntry) &&
+ rTreeView.count_selected_rows() == 1);
+ m_xPopup->set_sensitive("writersort", 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);
+
+ /* enable again once we have redline comments in the margin
+ sComment = rRedline.GetComment();
+ if ( !sComment.Len() )
+ GetActiveView()->GetDocShell()->Broadcast(SwRedlineHint(&rRedline,SWREDLINE_INSERTED));
+ const_cast<SwRangeRedline&>(rRedline).Broadcast(SwRedlineHint(&rRedline,SWREDLINE_FOCUS));
+ */
+
+ 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);
+ Init();
+ }
+
+ 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
+ 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, "ManageChangesPanel", "modules/swriter/ui/managechangessidebar.ui")
+ , mxContentArea(m_xBuilder->weld_container("content_area"))
+{
+ 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: */
diff --git a/sw/source/uibase/misc/swruler.cxx b/sw/source/uibase/misc/swruler.cxx
new file mode 100644
index 0000000000..0ccdec2f46
--- /dev/null
+++ b/sw/source/uibase/misc/swruler.cxx
@@ -0,0 +1,367 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+// Design proposal: https://wiki.documentfoundation.org/Design/Whiteboards/Comments_Ruler_Control
+
+#include <swruler.hxx>
+
+#include <viewsh.hxx>
+#include <edtwin.hxx>
+#include <PostItMgr.hxx>
+#include <view.hxx>
+#include <cmdid.h>
+#include <sfx2/request.hxx>
+#include <tools/UnitConversion.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+#include <tools/json_writer.hxx>
+#include <strings.hrc>
+#include <comphelper/lok.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+#define CONTROL_BORDER_WIDTH 1
+
+namespace
+{
+/**
+ * Draw a little arrow / triangle with different directions
+ *
+ * \param nX left coordinate of arrow square
+ * \param nY top coordinate of arrow square
+ * \param nSize size of the long triangle side / arrow square
+ * \param Color arrow color
+ * \param bCollapsed if the arrow should display the collapsed state
+ */
+void ImplDrawArrow(vcl::RenderContext& rRenderContext, tools::Long nX, tools::Long nY,
+ tools::Long nSize, const Color& rColor, bool bCollapsed)
+{
+ tools::Polygon aTrianglePolygon(4);
+
+ if (bCollapsed)
+ {
+ if (AllSettings::GetLayoutRTL()) // <
+ {
+ aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
+ aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
+ aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
+ aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
+ }
+ else // >
+ {
+ aTrianglePolygon.SetPoint({ nX, nY }, 0);
+ aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
+ aTrianglePolygon.SetPoint({ nX, nY + nSize }, 2);
+ aTrianglePolygon.SetPoint({ nX, nY }, 3);
+ }
+ }
+ else // v
+ {
+ aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
+ aTrianglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
+ aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
+ aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
+ }
+
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rColor);
+ rRenderContext.DrawPolygon(aTrianglePolygon);
+}
+}
+
+// Constructor
+SwCommentRuler::SwCommentRuler(SwViewShell* pViewSh, vcl::Window* pParent, SwEditWin* pWin,
+ SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings,
+ WinBits nWinStyle)
+ : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
+ , mpViewShell(pViewSh)
+ , mpSwWin(pWin)
+ , mbIsHighlighted(false)
+ , maFadeTimer("sw::SwCommentRuler maFadeTimer")
+ , mnFadeRate(0)
+ , maVirDev(VclPtr<VirtualDevice>::Create(*GetOutDev()))
+{
+ // Set fading timeout: 5 x 40ms = 200ms
+ maFadeTimer.SetTimeout(40);
+ maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
+
+ // we have a little bit more space, as we don't draw ruler ticks
+ vcl::Font aFont(maVirDev->GetFont());
+ aFont.SetFontHeight(aFont.GetFontHeight() + 1);
+ maVirDev->SetFont(aFont);
+}
+
+SwCommentRuler::~SwCommentRuler() { disposeOnce(); }
+
+void SwCommentRuler::dispose()
+{
+ mpSwWin.clear();
+ SvxRuler::dispose();
+}
+
+void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return; // no need to waste time on startup
+
+ SvxRuler::Paint(rRenderContext, rRect);
+
+ // Don't draw if there is not any note
+ if (mpViewShell->GetPostItMgr() && mpViewShell->GetPostItMgr()->HasNotes())
+ DrawCommentControl(rRenderContext);
+}
+
+void SwCommentRuler::DrawCommentControl(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ const bool bIsCollapsed = !mpViewShell->GetPostItMgr()->ShowNotes();
+ const tools::Rectangle aControlRect = GetCommentControlRegion();
+
+ maVirDev->SetOutputSizePixel(aControlRect.GetSize());
+
+ // set colors
+ if (!bIsCollapsed)
+ {
+ if (mbIsHighlighted)
+ maVirDev->SetFillColor(
+ GetFadedColor(rStyleSettings.GetHighlightColor(), rStyleSettings.GetDialogColor()));
+ else
+ maVirDev->SetFillColor(rStyleSettings.GetDialogColor());
+ maVirDev->SetLineColor(rStyleSettings.GetShadowColor());
+ }
+ else
+ {
+ if (mbIsHighlighted)
+ maVirDev->SetFillColor(GetFadedColor(rStyleSettings.GetHighlightColor(),
+ rStyleSettings.GetWorkspaceColor()));
+ else
+ maVirDev->SetFillColor(rStyleSettings.GetWorkspaceColor());
+ maVirDev->SetLineColor();
+ }
+ Color aTextColor = GetFadedColor(rStyleSettings.GetHighlightTextColor(),
+ rStyleSettings.GetButtonTextColor());
+ maVirDev->SetTextColor(aTextColor);
+
+ // calculate label and arrow positions
+ const OUString aLabel = SwResId(STR_COMMENTS_LABEL);
+ const tools::Long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1;
+ const tools::Long nTrianglePad = maVirDev->GetTextHeight() / 4;
+
+ Point aLabelPos(0, (aControlRect.GetHeight() - maVirDev->GetTextHeight()) / 2);
+ Point aArrowPos(0, (aControlRect.GetHeight() - nTriangleSize) / 2);
+
+ if (!AllSettings::GetLayoutRTL()) // | > Comments |
+ {
+ aArrowPos.setX(nTrianglePad);
+ aLabelPos.setX(aArrowPos.X() + nTriangleSize + nTrianglePad);
+ }
+ else // RTL => | Comments < |
+ {
+ const tools::Long nLabelWidth = maVirDev->GetTextWidth(aLabel);
+ if (!bIsCollapsed)
+ {
+ aArrowPos.setX(aControlRect.GetWidth() - 1 - nTrianglePad - CONTROL_BORDER_WIDTH
+ - nTriangleSize);
+ aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
+ }
+ else
+ {
+ // if comments are collapsed, left align the text, because otherwise it's very likely to be invisible
+ aArrowPos.setX(nLabelWidth + nTrianglePad + nTriangleSize);
+ aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
+ }
+ }
+
+ // draw control
+ maVirDev->DrawRect(tools::Rectangle(Point(), aControlRect.GetSize()));
+ maVirDev->DrawText(aLabelPos, aLabel);
+ ImplDrawArrow(*maVirDev, aArrowPos.X(), aArrowPos.Y(), nTriangleSize, aTextColor, bIsCollapsed);
+ rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(),
+ aControlRect.GetSize(), *maVirDev);
+}
+
+// Just accept double-click outside comment control
+void SwCommentRuler::Command(const CommandEvent& rCEvt)
+{
+ Point aMousePos = rCEvt.GetMousePosPixel();
+ // Ignore command request if it is inside Comment Control
+ if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes()
+ || !GetCommentControlRegion().Contains(aMousePos))
+ SvxRuler::Command(rCEvt);
+}
+
+void SwCommentRuler::MouseMove(const MouseEvent& rMEvt)
+{
+ SvxRuler::MouseMove(rMEvt);
+ if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes())
+ return;
+
+ UpdateCommentHelpText();
+
+ Point aMousePos = rMEvt.GetPosPixel();
+ bool bWasHighlighted = mbIsHighlighted;
+ mbIsHighlighted = GetCommentControlRegion().Contains(aMousePos);
+ if (mbIsHighlighted != bWasHighlighted)
+ // Do start fading
+ maFadeTimer.Start();
+}
+
+void SwCommentRuler::MouseButtonDown(const MouseEvent& rMEvt)
+{
+ Point aMousePos = rMEvt.GetPosPixel();
+ if (!rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().Contains(aMousePos))
+ {
+ SvxRuler::MouseButtonDown(rMEvt);
+ return;
+ }
+
+ // Toggle notes visibility
+ SwView& rView = mpSwWin->GetView();
+ SfxRequest aRequest(rView.GetViewFrame(), SID_TOGGLE_NOTES);
+ rView.ExecViewOptions(aRequest);
+
+ // It is inside comment control, so update help text
+ UpdateCommentHelpText();
+
+ Invalidate();
+}
+
+void SwCommentRuler::CreateJsonNotification(tools::JsonWriter& rJsonWriter)
+{
+ // Note that GetMargin1(), GetMargin2(), GetNullOffset(), and GetPageOffset() return values in
+ // pixels. Not twips. So "converting" the returned values with convertTwipToMm100() is quite
+ // wrong. (Also, even if the return values actually were in twips, it is questionable why we
+ // would want to pass them in mm100, as all other length values in the LOKit protocol apparently
+ // are in twips.)
+
+ // Anyway, as the consuming code in Online mostly seems to work anyway, it is likely that it
+ // would work as well even if the values in pixels were passed without a bogus "conversion" to
+ // mm100. But let's keep this as is for now.
+
+ // Also note that in desktop LibreOffice, these pixel values for the ruler of course change as
+ // one changes the zoom level. (Can be seen if one temporarily modifies the NotifyKit() function
+ // below to call this CreateJsonNotification() function and print its result in all cases even
+ // without LibreOfficeKit::isActive().) But in both web-based Online and in the iOS app, the
+ // zoom level from the point of view of this code here apparently does not change even if one
+ // zooms from the Online code's point of view.
+ rJsonWriter.put("margin1", convertTwipToMm100(GetMargin1()));
+ rJsonWriter.put("margin2", convertTwipToMm100(GetMargin2()));
+ rJsonWriter.put("leftOffset", convertTwipToMm100(GetNullOffset()));
+ rJsonWriter.put("pageOffset", convertTwipToMm100(GetPageOffset()));
+
+ // GetPageWidth() on the other hand does return a value in twips.
+ // So here convertTwipToMm100() really does produce actual mm100. Fun.
+ rJsonWriter.put("pageWidth", convertTwipToMm100(GetPageWidth()));
+
+ {
+ auto tabsNode = rJsonWriter.startNode("tabs");
+
+ // The RulerTab array elements that GetTabs() returns have their nPos field in twips. So these
+ // too are actual mm100.
+ for (auto const& tab : GetTabs())
+ {
+ auto tabNode = rJsonWriter.startNode("");
+ rJsonWriter.put("position", convertTwipToMm100(tab.nPos));
+ rJsonWriter.put("style", tab.nStyle);
+ }
+ }
+
+ RulerUnitData aUnitData = GetCurrentRulerUnit();
+ rJsonWriter.put("unit", aUnitData.aUnitStr);
+}
+
+void SwCommentRuler::NotifyKit()
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ tools::JsonWriter aJsonWriter;
+ CreateJsonNotification(aJsonWriter);
+ OString pJsonData = aJsonWriter.finishAndGetAsOString();
+ mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE,
+ pJsonData);
+}
+
+void SwCommentRuler::Update()
+{
+ tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
+ SvxRuler::Update();
+ if (aPreviousControlRect != GetCommentControlRegion())
+ Invalidate();
+ NotifyKit();
+}
+
+void SwCommentRuler::UpdateCommentHelpText()
+{
+ TranslateId pTooltipResId;
+ if (mpViewShell->GetPostItMgr()->ShowNotes())
+ pTooltipResId = STR_HIDE_COMMENTS;
+ else
+ pTooltipResId = STR_SHOW_COMMENTS;
+ SetQuickHelpText(SwResId(pTooltipResId));
+}
+
+// TODO Make Ruler return its central rectangle instead of margins.
+tools::Rectangle SwCommentRuler::GetCommentControlRegion()
+{
+ SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr();
+
+ //rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
+ //triggers an update of the uiview, but the result of the ctor hasn't been
+ //set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
+ if (!pPostItMgr)
+ return tools::Rectangle();
+
+ const tools::ULong nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
+
+ //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
+ tools::Long nLeft = GetPageOffset();
+ if (GetTextRTL())
+ nLeft += GetBorderOffset() - nSidebarWidth;
+ else
+ nLeft += GetWinOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
+
+ // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
+ tools::Long nTop = 4;
+ // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
+ tools::Long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
+ tools::Long nBottom = nTop + GetRulerVirHeight() - 3;
+
+ tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
+ return aRect;
+}
+
+Color SwCommentRuler::GetFadedColor(const Color& rHighColor, const Color& rLowColor)
+{
+ if (!maFadeTimer.IsActive())
+ return mbIsHighlighted ? rHighColor : rLowColor;
+
+ Color aColor = rHighColor;
+ aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
+ return aColor;
+}
+
+IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer*, void)
+{
+ const int nStep = 25;
+ if (mbIsHighlighted && mnFadeRate < 100)
+ mnFadeRate += nStep;
+ else if (!mbIsHighlighted && mnFadeRate > 0)
+ mnFadeRate -= nStep;
+ else
+ return;
+
+ Invalidate();
+
+ if (mnFadeRate != 0 && mnFadeRate != 100)
+ maFadeTimer.Start();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */