diff options
Diffstat (limited to 'sw/source/uibase/misc')
-rw-r--r-- | sw/source/uibase/misc/glosdoc.cxx | 627 | ||||
-rw-r--r-- | sw/source/uibase/misc/glshell.cxx | 271 | ||||
-rw-r--r-- | sw/source/uibase/misc/numberingtypelistbox.cxx | 137 | ||||
-rw-r--r-- | sw/source/uibase/misc/redlndlg.cxx | 1261 | ||||
-rw-r--r-- | sw/source/uibase/misc/swruler.cxx | 371 |
5 files changed, 2667 insertions, 0 deletions
diff --git a/sw/source/uibase/misc/glosdoc.cxx b/sw/source/uibase/misc/glosdoc.cxx new file mode 100644 index 000000000..868158732 --- /dev/null +++ b/sw/source/uibase/misc/glosdoc.cxx @@ -0,0 +1,627 @@ +/* -*- 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 <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 <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(const OUString& sPath, const OUString& sName) +{ + return sPath + "/" + sName + SwGlossaries::GetExtension(); +} + +OUString lcl_CheckFileName( const OUString& rNewFilePath, + const OUString& rNewGroupName ) +{ + const sal_Int32 nLen = rNewGroupName.getLength(); + 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 = rNewGroupName[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; + } + + OUString rSG = SwGlossaries::GetExtension(); + //generate generic name + utl::TempFile aTemp("group", true, &rSG, &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==sTemp.getToken(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 = sTemp.getToken(1, GLOS_DELIM).toUInt32(); + + 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 OUString sNewPath(rGroupName.getToken(1, GLOS_DELIM)); + sal_uInt16 nNewPath = static_cast<sal_uInt16>(sNewPath.toInt32()); + if (static_cast<size_t>(nNewPath) >= m_PathArr.size()) + return false; + const OUString sNewFilePath(m_PathArr[nNewPath]); + const OUString sNewGroup = lcl_CheckFileName(sNewFilePath, rGroupName.getToken(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 = static_cast<sal_uInt16>(rOldGroup.getToken(1, GLOS_DELIM).toInt32()); + if (static_cast<size_t>(nOldPath) >= m_PathArr.size()) + return false; + + const OUString sOldFileURL = + lcl_FullPathName(m_PathArr[nOldPath], rOldGroup.getToken(0, GLOS_DELIM)); + + if (!FStatHelper::IsDocument( sOldFileURL )) + { + OSL_FAIL("group doesn't exist!"); + return false; + } + + sal_uInt16 nNewPath = static_cast<sal_uInt16>(rNewGroup.getToken(1, GLOS_DELIM).toInt32()); + if (static_cast<size_t>(nNewPath) >= m_PathArr.size()) + return false; + + const OUString sNewFileName = lcl_CheckFileName(m_PathArr[nNewPath], + rNewGroup.getToken(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); + } + + std::unique_ptr<SwTextBlocks> pNewBlock(new SwTextBlocks( sNewFileURL )); + pNewBlock->SetName(rNewTitle); + + return true; +} + +// Deletes a text block group +bool SwGlossaries::DelGroupDoc(const OUString &rName) +{ + sal_uInt16 nPath = static_cast<sal_uInt16>(rName.getToken(1, GLOS_DELIM).toInt32()); + if (static_cast<size_t>(nPath) >= m_PathArr.size()) + return false; + const OUString sBaseName(rName.getToken(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 = static_cast<sal_uInt16>(rName.getToken(1, GLOS_DELIM).toInt32()); + std::unique_ptr<SwTextBlocks> pTmp; + if (static_cast<size_t>(nPath) < m_PathArr.size()) + { + const OUString sFileURL = + lcl_FullPathName(m_PathArr[nPath], rName.getToken(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.copy( 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.getStr(); +} + +void SwGlossaries::UpdateGlosPath(bool bFull) +{ + SvtPathOptions aPathOpt; + const OUString& aNewPath( aPathOpt.GetAutoTextPath() ); + bool bPathChanged = m_aPath != aNewPath; + if (bFull || bPathChanged) + { + 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( *new StringErrorInfo( + 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() +{ + ErrCode nPathError = *new StringErrorInfo(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()) + { + auto it = std::find(m_GlosArr.begin(), m_GlosArr.end(), rGroup); + if (it != m_GlosArr.end()) + { + { + // tell the UNO AutoTextGroup object that it's not valid anymore + for ( UnoAutoTextGroups::iterator aLoop = m_aGlossaryGroups.begin(); + aLoop != m_aGlossaryGroups.end(); + ) + { + Reference< container::XNamed > xNamed( aLoop->get(), UNO_QUERY ); + if ( !xNamed.is() ) + { + aLoop = m_aGlossaryGroups.erase(aLoop); + } + else if ( xNamed->getName() == rGroup ) + { + static_cast< SwXAutoTextGroup* >( xNamed.get() )->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(); + ) + { + auto pEntry = comphelper::getUnoTunnelImplementation<SwXAutoTextEntry>(aLoop->get()); + if ( pEntry && ( pEntry->GetGroupName() == rGroup ) ) + { + pEntry->Invalidate(); + aLoop = m_aGlossaryEntries.erase( aLoop ); + } + else + ++aLoop; + } + } + + m_GlosArr.erase(it); + } + } +} + +OUString SwGlossaries::GetCompleteGroupName( const OUString& rGroupName ) +{ + const size_t nCount = GetGroupCnt(); + // when the group name was created internally the path is here as well + sal_Int32 nIndex = 0; + const OUString sGroupName(rGroupName.getToken(0, GLOS_DELIM, nIndex)); + const bool bPathLen = !rGroupName.getToken(0, GLOS_DELIM, nIndex).isEmpty(); + for ( size_t i = 0; i < nCount; i++ ) + { + const OUString sGrpName = GetGroupName(i); + if (bPathLen) + { + if (rGroupName == sGrpName) + return sGrpName; + } + else + { + if (sGroupName == sGrpName.getToken(0, GLOS_DELIM)) + return sGrpName; + } + } + return OUString(); +} + +void SwGlossaries::InvalidateUNOOjects() +{ + // invalidate all the AutoTextGroup-objects + for (const auto& rGroup : m_aGlossaryGroups) + { + Reference< text::XAutoTextGroup > xGroup( rGroup.get(), UNO_QUERY ); + if ( xGroup.is() ) + static_cast< SwXAutoTextGroup* >( xGroup.get() )->Invalidate(); + } + UnoAutoTextGroups aTmpg; + m_aGlossaryGroups.swap( aTmpg ); + + // invalidate all the AutoTextEntry-objects + for (const auto& rEntry : m_aGlossaryEntries) + { + auto pEntry = comphelper::getUnoTunnelImplementation<SwXAutoTextEntry>(rEntry.get()); + if ( pEntry ) + pEntry->Invalidate(); + } + UnoAutoTextEntries aTmpe; + m_aGlossaryEntries.swap( aTmpe ); +} + +Reference< text::XAutoTextGroup > SwGlossaries::GetAutoTextGroup( const OUString& _rGroupName ) +{ + bool _bCreate = true; + // first, find the name with path-extension + const OUString sCompleteGroupName = GetCompleteGroupName( _rGroupName ); + + Reference< text::XAutoTextGroup > xGroup; + + // look up the group in the cache + UnoAutoTextGroups::iterator aSearch = m_aGlossaryGroups.begin(); + for ( ; aSearch != m_aGlossaryGroups.end(); ) + { + auto pSwGroup = comphelper::getUnoTunnelImplementation<SwXAutoTextGroup>(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(); + + Reference< text::XAutoTextEntry > xReturn; + + UnoAutoTextEntries::iterator aSearch( m_aGlossaryEntries.begin() ); + for ( ; aSearch != m_aGlossaryEntries.end(); ) + { + Reference< lang::XUnoTunnel > xEntryTunnel( aSearch->get(), UNO_QUERY ); + + SwXAutoTextEntry* pEntry = nullptr; + if ( xEntryTunnel.is() ) + pEntry = reinterpret_cast< SwXAutoTextEntry* >( xEntryTunnel->getSomething( SwXAutoTextEntry::getUnoTunnelId() ) ); + else + { + // 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 000000000..1e3c664f1 --- /dev/null +++ b/sw/source/uibase/misc/glshell.cxx @@ -0,0 +1,271 @@ +/* -*- 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 ) + { + if( !rSh.HasName() ) + { + rReq.SetReturnValue( SfxBoolItem( 0, rSh.Save() ) ); + } + else + { + const SfxBoolItem* pRes = static_cast< const SfxBoolItem* >( + rSh.ExecuteSlot( rReq, + rSh.SfxObjectShell::GetInterface() )); + 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(), aGroupName, aShortName, aLongName ); + else + { + SetModified( false ); + return false; + } +} + +SwWebGlosDocShell::SwWebGlosDocShell() + : SwWebDocShell() +{ +} + +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(), aGroupName, aShortName, 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<SfxItemSet>( + xDocSh->GetDoc()->GetAttrPool(), + svl::Items< + SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN, + SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC, + FN_PARAM_ADDPRINTER, FN_PARAM_ADDPRINTER>{}); + 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 000000000..d484cce5d --- /dev/null +++ b/sw/source/uibase/misc/numberingtypelistbox.cxx @@ -0,0 +1,137 @@ +/* -*- 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(aTypes.begin(), aTypes.end(), nValue) != aTypes.end(); + } + } + if (bInsert) + { + OUString sId(OUString::number(nValue)); + m_xWidget->insert(nPos, SvxNumberingTypeTable::GetString(i), &sId, nullptr, nullptr); + } + } + if (nTypeFlags & SwInsertNumTypes::Extended) + { + for (sal_Int16 nCurrent : 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 000000000..a18506497 --- /dev/null +++ b/sw/source/uibase/misc/redlndlg.cxx @@ -0,0 +1,1261 @@ +/* -*- 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 <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 <helpids.h> +#include <cmdid.h> +#include <strings.hrc> + +// -> #111827# +#include <swundo.hxx> +#include <SwRewriter.hxx> +// <- #111827# + +#include <vector> +#include <svx/svxdlg.hxx> +#include <bitmaps.hlst> + +#include <docsh.hxx> + +#include <IDocumentRedlineAccess.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_xDialog->weld_content_area()) + , 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 (pChildWin->GetOldDocShell() != pDocSh) + { // doc-switch + SwWait aWait( *pDocSh, false ); + SwWrtShell* pSh = pView->GetWrtShellPtr(); + + 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(const std::shared_ptr<weld::Window>& rParent, weld::Builder *pBuilder, + weld::Container *pContentArea, bool bAutoFormat) + : m_xParentDlg(rParent) + , 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_xTabPagesCTRL(new SvxAcceptChgCtr(pContentArea, m_xParentDlg.get(), pBuilder)) + , m_xPopup(pBuilder->weld_menu("writermenu")) +{ + m_xTabPagesCTRL->set_help_id(HID_REDLINE_CTRL); + 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->EnableAcceptAll(false); + m_pTPView->EnableRejectAll(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) +{ + SwWait aWait( *::GetActiveView()->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_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() +{ + SwWrtShell* pSh = ::GetActiveView()->GetWrtShellPtr(); + + if (!m_xTabPagesCTRL) + return; + + SvxTPFilter *pFilterPage = m_xTabPagesCTRL->GetFilterPage(); + + std::vector<OUString> aStrings; + OUString sOldAuthor(pFilterPage->GetSelectedAuthor()); + pFilterPage->ClearAuthors(); + + SwRedlineTable::size_type nCount = pSh->GetRedlineCount(); + + 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 bEnable = 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) +{ + switch (rRedln.GetType(nStack)) + { + case RedlineType::Insert: return BMP_REDLINE_INSERTED; + case RedlineType::Delete: return 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->EnableAcceptAll(false); + m_pTPView->EnableRejectAll(false); + return; // had the focus previously + } + + SwWait aWait( *pView->GetDocShell(), false ); + + m_aUsedSeqNo.clear(); + + // did something change? + SwWrtShell* pSh = pView->GetWrtShellPtr(); + 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 + if ((i = CalcDiff(i, false)) == SwRedlineTable::npos) + return; + continue; + } + + const SwRedlineData *pRedlineData = rRedln.GetRedlineData().Next(); + const SwRedlineDataChild *pBackupData = pParent->pNext; + + if (!pRedlineData && pBackupData) + { + // Redline-Children were deleted + if ((i = CalcDiff(i, true)) == SwRedlineTable::npos) + return; + continue; + } + else + { + while (pRedlineData) + { + if (pRedlineData != pBackupData->pChild) + { + // Redline-Children were inserted, changed or deleted + if ((i = CalcDiff(i, true)) == SwRedlineTable::npos) + return; + continue; + } + 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(); + 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) + { + if (pParent->xTLBParent) + { + // update only comment + const OUString& sComment(rRedln.GetComment()); + rTreeView.set_text(*pParent->xTLBParent, sComment.replace('\n', ' '), 3); + } + pParent->sComment = rRedln.GetComment(); + } + } + + InitAuthors(); +} + +SwRedlineTable::size_type SwRedlineAcceptDlg::CalcDiff(SwRedlineTable::size_type nStart, bool bChild) +{ + if (!nStart) + { + Init(); + return SwRedlineTable::npos; + } + + weld::TreeView& rTreeView = m_pTable->GetWidget(); + rTreeView.freeze(); + SwView *pView = ::GetActiveView(); + SwWrtShell* pSh = pView->GetWrtShellPtr(); + 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) + { + // insert entries from nStart to i-1 + InsertParents(nStart, i - 1); + rTreeView.thaw(); + 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(OUString::number(reinterpret_cast<sal_Int64>(pData.release()))); + rTreeView.insert(pParent->xTLBParent.get(), -1, nullptr, &sId, nullptr, nullptr, + nullptr, false, xChild.get()); + + 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) +{ + SwWrtShell* pSh = ::GetActiveView()->GetWrtShellPtr(); + 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(); + SwWrtShell* pSh = pView->GetWrtShellPtr(); + bool bHasRedlineAutoFormat = HasRedlineAutoFormat(); + + SwRedlineTable::size_type nCount = pSh->GetRedlineCount(); + nEnd = std::min(nEnd, (nCount - 1)); // also treats nEnd=SwRedlineTable::npos (until the end) + + 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(); + if( nullptr == (pCurrRedline = pSh->SelNextRedline())) + pCurrRedline = pSh->SelPrevRedline(); + pSh->SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent); + } + } + else + pCurrRedline = nullptr; + + rTreeView.freeze(); + if (m_pTable->IsSorted()) + rTreeView.make_unsorted(); + for (SwRedlineTable::size_type i = nStart; i <= nEnd; i++) + { + const SwRangeRedline& rRedln = pSh->GetRedline(i); + const SwRedlineData *pRedlineData = &rRedln.GetRedlineData(); + + pRedlineParent = new SwRedlineDataParent; + pRedlineParent->pData = pRedlineData; + pRedlineParent->pNext = nullptr; + const OUString& sComment(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; + + OUString sImage = GetActionImage(rRedln); + OUString sAuthor = rRedln.GetAuthorString(0); + pData->aDateTime = rRedln.GetTimeStamp(0); + pData->eType = rRedln.GetType(0); + OUString sDateEntry = GetAppLangDateTimeString(pData->aDateTime); + + OUString sId = OUString::number(reinterpret_cast<sal_Int64>(pData.release())); + std::unique_ptr<weld::TreeIter> xParent(rTreeView.make_iterator()); + rTreeView.insert(nullptr, i, nullptr, &sId, nullptr, nullptr, nullptr, false, xParent.get()); + + 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 ) +{ + SwWrtShell* pSh = ::GetActiveView()->GetWrtShellPtr(); + 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 = reinterpret_cast<RedlinData*>(rTreeView.get_id(rEntry).toInt64()); + + 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(); + + if (aRedlines.size() > 1) + { + 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 ); + } + + if (aRedlines.size() > 1) + { + 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) +{ + SwWrtShell* pSh = ::GetActiveView()->GetWrtShellPtr(); + weld::TreeView& rTreeView = m_pTable->GetWidget(); + return pSh->FindRedlineOfData( *static_cast<SwRedlineDataParent*>(reinterpret_cast<RedlinData*>( + rTreeView.get_id(rEntry).toInt64())->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) +{ + SwView * pView = ::GetActiveView(); + pView->GetViewFrame()->GetDispatcher()-> + Execute(SID_UNDO, SfxCallMode::SYNCHRON); + m_pTPView->EnableUndo(pView->GetSlotState(SID_UNDO) != nullptr); + + 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) +{ + SwWrtShell* pSh = ::GetActiveView()->GetWrtShellPtr(); + m_aSelectTimer.Stop(); + + 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(); + } + } + return false; + }); + + pSh->LeaveAddMode(); + pSh->EndAction(); + SwViewShell::SetCareDialog(nullptr); + } + } + + bool bEnable = !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->EnableRejectAll( bEnable ); + m_pTPView->EnableClearFormatAll( bEnable && m_bOnlyFormatedRedlines ); +} + +IMPL_LINK(SwRedlineAcceptDlg, CommandHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + SwWrtShell* pSh = ::GetActiveView()->GetWrtShellPtr(); + 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_xPopup->set_active("writersort" + OString::number(i), i == nColumn); + + OString 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(); + + const char* pResId = nullptr; + 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 = sCommand.copy(10).toInt32(); + + if (nSortMode == 4 && nColumn == 4) + return true; // we already have it + if (nSortMode == 4) + nSortMode = -1; // unsorted / sorted by position + + SwWait aWait( *::GetActiveView()->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, ""); + aStr = aStr.copy(n1 - nPos + 1); + } + } + } + return aStr; + } +} + +void SwRedlineAcceptDlg::Initialize(OUString& rExtraString) +{ + if (!rExtraString.isEmpty()) + { + OUString aStr = lcl_StripAcceptChgDat(rExtraString); + if (!aStr.isEmpty()) + { + int nCount = aStr.toInt32(); + if (nCount > 2) + { + 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(vcl::Window* pParent, const css::uno::Reference<css::frame::XFrame>& rFrame) + : PanelLayout(pParent, "ManageChangesPanel", "modules/swriter/ui/managechangessidebar.ui", rFrame) + , 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() +{ + disposeOnce(); +} + +void SwRedlineAcceptPanel::dispose() +{ + mpImplDlg.reset(); + mxContentArea.reset(); + PanelLayout::dispose(); +} + +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 000000000..680031a26 --- /dev/null +++ b/sw/source/uibase/misc/swruler.cxx @@ -0,0 +1,371 @@ +/* -*- 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 <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/window.hxx> +#include <vcl/settings.hxx> +#include <strings.hrc> +#include <comphelper/lok.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <boost/property_tree/json_parser.hpp> + +#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, long nX, long nY, long nSize, + const Color& rColor, bool bCollapsed) +{ + tools::Polygon aTriaglePolygon(4); + + if (bCollapsed) + { + if (AllSettings::GetLayoutRTL()) // < + { + aTriaglePolygon.SetPoint({ nX + nSize / 2, nY }, 0); + aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1); + aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2); + aTriaglePolygon.SetPoint({ nX + nSize / 2, nY }, 3); + } + else // > + { + aTriaglePolygon.SetPoint({ nX, nY }, 0); + aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1); + aTriaglePolygon.SetPoint({ nX, nY + nSize }, 2); + aTriaglePolygon.SetPoint({ nX, nY }, 3); + } + } + else // v + { + aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0); + aTriaglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1); + aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2); + aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3); + } + + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rColor); + rRenderContext.DrawPolygon(aTriaglePolygon); +} +} + +// 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) + , mnFadeRate(0) + , maVirDev(VclPtr<VirtualDevice>::Create(*this)) +{ + // Set fading timeout: 5 x 40ms = 200ms + maFadeTimer.SetTimeout(40); + maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler)); + maFadeTimer.SetDebugName("sw::SwCommentRuler maFadeTimer"); + + // 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 long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1; + const 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 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().IsInside(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().IsInside(aMousePos); + if (mbIsHighlighted != bWasHighlighted) + // Do start fading + maFadeTimer.Start(); +} + +void SwCommentRuler::MouseButtonDown(const MouseEvent& rMEvt) +{ + Point aMousePos = rMEvt.GetPosPixel(); + if (!rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().IsInside(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(); +} + +std::string SwCommentRuler::CreateJsonNotification() +{ + boost::property_tree::ptree jsonNotif; + + // 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. + jsonNotif.put("margin1", convertTwipToMm100(GetMargin1())); + jsonNotif.put("margin2", convertTwipToMm100(GetMargin2())); + jsonNotif.put("leftOffset", convertTwipToMm100(GetNullOffset())); + jsonNotif.put("pageOffset", convertTwipToMm100(GetPageOffset())); + + // GetPageWidth() on the other hand does return a value in twips. + // So here convertTwipToMm100() really does produce actual mm100. Fun. + jsonNotif.put("pageWidth", convertTwipToMm100(GetPageWidth())); + + boost::property_tree::ptree 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()) + { + boost::property_tree::ptree element; + element.put("position", convertTwipToMm100(tab.nPos)); + element.put("style", tab.nStyle); + tabs.push_back(std::make_pair("", element)); + } + + jsonNotif.add_child("tabs", tabs); + + RulerUnitData aUnitData = GetCurrentRulerUnit(); + jsonNotif.put("unit", aUnitData.aUnitStr); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, jsonNotif); + return aStream.str(); +} + +void SwCommentRuler::NotifyKit() +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + const std::string test = CreateJsonNotification(); + mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE, + test.c_str()); +} + +void SwCommentRuler::Update() +{ + tools::Rectangle aPreviousControlRect = GetCommentControlRegion(); + SvxRuler::Update(); + if (aPreviousControlRect != GetCommentControlRegion()) + Invalidate(); + NotifyKit(); +} + +void SwCommentRuler::UpdateCommentHelpText() +{ + const char* 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 unsigned long nSidebarWidth = pPostItMgr->GetSidebarWidth(true); + + //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel + 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 + long nTop = 4; + // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled + long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true); + 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: */ |