tdf#152872 sw: conditionally hide paragraph breaks
Add a 3rd kind of hiding to SwRootFrame and CheckParaRedlineMerge().
This is quite simple as only consecutive paragraphs are merged.
There is an existing similar feature described in
http://www.openoffice.org/specs/writer/hidden_text/hidden_text.sxw
which results in 0-height text frames if all text is hidden
- but that is unconditional, while Word shows the paragraph when
control chars are shown, and hides it otherwise *iff* its paragraph
marker is hidden (and there's no page break on it).
Change-Id: I8290962ea58278e17b8f84bf6b2ca4bb2325aa8f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145162
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx
index 8412f1a..dd2973f 100644
--- a/sw/inc/doc.hxx
+++ b/sw/inc/doc.hxx
@@ -142,6 +142,7 @@ namespace sw::mark { class MarkManager; }
namespace sw {
enum class RedlineMode;
enum class FieldmarkMode;
enum class ParagraphBreakMode;
class MetaFieldManager;
class UndoManager;
class IShellCursorSupplier;
@@ -1339,7 +1340,7 @@ public:
// insert section (the ODF kind of section, not the nodesarray kind)
SwSection * InsertSwSection(SwPaM const& rRange, SwSectionData &,
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode> const* pTOXBase,
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode> const* pTOXBase,
SfxItemSet const*const pAttr, bool const bUpdate = true);
static sal_uInt16 IsInsRegionAvailable( const SwPaM& rRange,
const SwNode** ppSttNd = nullptr );
diff --git a/sw/qa/extras/layout/data/hidden-para-separator.docx b/sw/qa/extras/layout/data/hidden-para-separator.docx
new file mode 100644
index 0000000..1d5d260
--- /dev/null
+++ b/sw/qa/extras/layout/data/hidden-para-separator.docx
Binary files differ
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index b8ba46b..c5b4bbe 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -636,6 +636,43 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf149711_importDOCXMoveToParagraphMar
assertXPath(pXmlDoc, "/root/page[1]/body/txt", 5);
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf152872)
{
createSwDoc("hidden-para-separator.docx");
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "/root/page[1]/body/txt", 2);
assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout", "portion", "C DE");
assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion", 0); // 5 is empty
assertXPath(pXmlDoc, "/root/page/body/txt[2]/infos/bounds", "height", "379");
dispatchCommand(mxComponent, ".uno:ControlCodes", {});
discardDumpedLayout();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "/root/page[1]/body/txt", 5);
assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout", "portion", "C ");
assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout", "portion", "D");
// 3 is an empty paragraph with RES_CHRATR_HIDDEN which results in 0-height
// frame; ideally it should only be hidden when control codes are hidden
// and be a full-height frame now, but that needs more work...
assertXPath(pXmlDoc, "/root/page/body/txt[3]/infos/bounds", "height", "0");
assertXPath(pXmlDoc, "/root/page/body/txt[4]/SwParaPortion/SwLineLayout", "portion", "E");
assertXPath(pXmlDoc, "/root/page/body/txt[5]/SwParaPortion", 0); // 5 is empty
assertXPath(pXmlDoc, "/root/page/body/txt[5]/infos/bounds", "height", "379");
dispatchCommand(mxComponent, ".uno:ControlCodes", {});
discardDumpedLayout();
pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "/root/page[1]/body/txt", 2);
assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout", "portion", "C DE");
assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion", 0); // 5 is empty
assertXPath(pXmlDoc, "/root/page/body/txt[2]/infos/bounds", "height", "379");
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf151954)
{
createSwDoc("tdf151954.docx");
diff --git a/sw/source/core/doc/doctxm.cxx b/sw/source/core/doc/doctxm.cxx
index 5578a50..5f54e53 100644
--- a/sw/source/core/doc/doctxm.cxx
+++ b/sw/source/core/doc/doctxm.cxx
@@ -363,12 +363,13 @@ SwTOXBaseSection* SwDoc::InsertTableOf( const SwPaM& aPam,
OUString sSectNm = GetUniqueTOXBaseName( *rTOX.GetTOXType(), rTOX.GetTOXName() );
SwSectionData aSectionData( SectionType::ToxContent, sSectNm );
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode> const tmp(
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode> const tmp(
&rTOX,
pLayout && pLayout->IsHideRedlines()
? sw::RedlineMode::Hidden
: sw::RedlineMode::Shown,
pLayout ? pLayout->GetFieldmarkMode() : sw::FieldmarkMode::ShowBoth);
pLayout ? pLayout->GetFieldmarkMode() : sw::FieldmarkMode::ShowBoth,
pLayout ? pLayout->GetParagraphBreakMode() : sw::ParagraphBreakMode::Shown);
SwTOXBaseSection *const pNewSection = dynamic_cast<SwTOXBaseSection *>(
InsertSwSection(aPam, aSectionData, & tmp, pSet, false));
if (pNewSection)
diff --git a/sw/source/core/docnode/ndsect.cxx b/sw/source/core/docnode/ndsect.cxx
index 8a53f83..f4fe9b9 100644
--- a/sw/source/core/docnode/ndsect.cxx
+++ b/sw/source/core/docnode/ndsect.cxx
@@ -152,7 +152,7 @@ static void lcl_CheckEmptyLayFrame( SwNodes const & rNds, SwSectionData& rSectio
SwSection *
SwDoc::InsertSwSection(SwPaM const& rRange, SwSectionData & rNewData,
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode> const*const pTOXBaseAndMode,
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode> const*const pTOXBaseAndMode,
SfxItemSet const*const pAttr, bool const bUpdate)
{
const SwNode* pPrvNd = nullptr;
diff --git a/sw/source/core/inc/UndoSection.hxx b/sw/source/core/inc/UndoSection.hxx
index 85b15a8..5be018a 100644
--- a/sw/source/core/inc/UndoSection.hxx
+++ b/sw/source/core/inc/UndoSection.hxx
@@ -35,13 +35,14 @@ class SwTOXBase;
namespace sw {
enum class RedlineMode;
enum class FieldmarkMode;
enum class ParagraphBreakMode;
};
class SwUndoInsSection final : public SwUndo, private SwUndRng
{
private:
const std::unique_ptr<SwSectionData> m_pSectionData;
std::optional<std::tuple<std::unique_ptr<SwTOXBase>, sw::RedlineMode, sw::FieldmarkMode>> m_xTOXBase; /// set iff section is TOX
std::optional<std::tuple<std::unique_ptr<SwTOXBase>, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode>> m_xTOXBase; /// set iff section is TOX
const std::unique_ptr<SfxItemSet> m_pAttrSet;
std::unique_ptr<SwHistory> m_pHistory;
std::unique_ptr<SwRedlineData> m_pRedlData;
@@ -56,7 +57,7 @@ private:
public:
SwUndoInsSection(SwPaM const&, SwSectionData const&,
SfxItemSet const* pSet,
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode> const* pTOXBase);
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode> const* pTOXBase);
virtual ~SwUndoInsSection() override;
diff --git a/sw/source/core/inc/rootfrm.hxx b/sw/source/core/inc/rootfrm.hxx
index 3d00fe1..32344bf0 100644
--- a/sw/source/core/inc/rootfrm.hxx
+++ b/sw/source/core/inc/rootfrm.hxx
@@ -47,6 +47,7 @@ namespace sw {
};
enum class FieldmarkMode { ShowCommand = 1, ShowResult = 2, ShowBoth = 3 };
enum class ParagraphBreakMode { Shown, Hidden };
};
enum class SwInvalidateFlags
@@ -121,6 +122,7 @@ class SW_DLLPUBLIC SwRootFrame final : public SwLayoutFrame
bool mbLayoutFreezed;
bool mbHideRedlines;
sw::FieldmarkMode m_FieldmarkMode;
sw::ParagraphBreakMode m_ParagraphBreakMode;
/**
* For BrowseMode
@@ -421,10 +423,9 @@ public:
bool IsHideRedlines() const { return mbHideRedlines; }
void SetHideRedlines(bool);
sw::FieldmarkMode GetFieldmarkMode() const { return m_FieldmarkMode; }
void SetFieldmarkMode(sw::FieldmarkMode);
bool HasMergedParas() const {
return IsHideRedlines() || GetFieldmarkMode() != sw::FieldmarkMode::ShowBoth;
}
void SetFieldmarkMode(sw::FieldmarkMode, sw::ParagraphBreakMode);
sw::ParagraphBreakMode GetParagraphBreakMode() const { return m_ParagraphBreakMode; }
bool HasMergedParas() const;
};
inline tools::Long SwRootFrame::GetBrowseWidth() const
diff --git a/sw/source/core/layout/newfrm.cxx b/sw/source/core/layout/newfrm.cxx
index c09c557..5ca11e3 100644
--- a/sw/source/core/layout/newfrm.cxx
+++ b/sw/source/core/layout/newfrm.cxx
@@ -418,6 +418,9 @@ SwRootFrame::SwRootFrame( SwFrameFormat *pFormat, SwViewShell * pSh ) :
m_FieldmarkMode(pSh->GetViewOptions()->IsFieldName()
? sw::FieldmarkMode::ShowCommand
: sw::FieldmarkMode::ShowResult),
m_ParagraphBreakMode(pSh->GetViewOptions()->IsParagraph()
? sw::ParagraphBreakMode::Shown
: sw::ParagraphBreakMode::Hidden),
mnBrowseWidth(MIN_BROWSE_WIDTH),
mpTurbo( nullptr ),
mpLastPage( nullptr ),
diff --git a/sw/source/core/layout/wsfrm.cxx b/sw/source/core/layout/wsfrm.cxx
index 2d59a1c..f753a99 100644
--- a/sw/source/core/layout/wsfrm.cxx
+++ b/sw/source/core/layout/wsfrm.cxx
@@ -4706,23 +4706,26 @@ void SwRootFrame::SetHideRedlines(bool const bHideRedlines)
}
// TODO: remove temporary ShowBoth
sw::FieldmarkMode const eMode(m_FieldmarkMode);
sw::ParagraphBreakMode const ePBMode(m_ParagraphBreakMode);
if (HasMergedParas())
{
m_FieldmarkMode = sw::FieldmarkMode::ShowBoth;
m_ParagraphBreakMode = sw::ParagraphBreakMode::Shown;
mbHideRedlines = false;
UnHide(*this);
}
if (bHideRedlines || eMode != m_FieldmarkMode)
if (bHideRedlines || eMode != m_FieldmarkMode || ePBMode != m_ParagraphBreakMode)
{
m_FieldmarkMode = eMode;
m_ParagraphBreakMode = ePBMode;
mbHideRedlines = bHideRedlines;
UnHide(*this);
}
}
void SwRootFrame::SetFieldmarkMode(sw::FieldmarkMode const eMode)
void SwRootFrame::SetFieldmarkMode(sw::FieldmarkMode const eFMMode, sw::ParagraphBreakMode const ePBMode)
{
if (eMode == m_FieldmarkMode)
if (eFMMode == m_FieldmarkMode && ePBMode == m_ParagraphBreakMode)
{
return;
}
@@ -4732,14 +4735,23 @@ void SwRootFrame::SetFieldmarkMode(sw::FieldmarkMode const eMode)
{
mbHideRedlines = false;
m_FieldmarkMode = sw::FieldmarkMode::ShowBoth;
m_ParagraphBreakMode = sw::ParagraphBreakMode::Shown;
UnHide(*this);
}
if (eMode != sw::FieldmarkMode::ShowBoth || isHideRedlines)
if (isHideRedlines || eFMMode != sw::FieldmarkMode::ShowBoth || ePBMode == sw::ParagraphBreakMode::Hidden)
{
mbHideRedlines = isHideRedlines;
m_FieldmarkMode = eMode;
m_FieldmarkMode = eFMMode;
m_ParagraphBreakMode = ePBMode;
UnHide(*this);
}
}
bool SwRootFrame::HasMergedParas() const
{
return IsHideRedlines()
|| GetFieldmarkMode() != sw::FieldmarkMode::ShowBoth
|| GetParagraphBreakMode() == sw::ParagraphBreakMode::Hidden;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
index 2b77d86..acf8355 100644
--- a/sw/source/core/text/redlnitr.cxx
+++ b/sw/source/core/text/redlnitr.cxx
@@ -47,8 +47,11 @@
#include <vcl/svapp.hxx>
#include "redlnitr.hxx"
#include <extinput.hxx>
#include <fmtpdsc.hxx>
#include <editeng/charhiddenitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/udlnitem.hxx>
using namespace ::com::sun::star;
@@ -62,12 +65,15 @@ private:
IDocumentMarkAccess const& m_rIDMA;
bool const m_isHideRedlines;
sw::FieldmarkMode const m_eFieldmarkMode;
bool const m_isHideParagraphBreaks;
SwPosition const m_Start;
/// next redline
SwRedlineTable::size_type m_RedlineIndex;
/// next fieldmark
std::pair<sw::mark::IFieldmark const*, std::optional<SwPosition>> m_Fieldmark;
std::optional<SwPosition> m_oNextFieldmarkHide;
/// previous paragraph break - because m_pStartPos/EndPos are non-owning
std::optional<std::pair<SwPosition, SwPosition>> m_oParagraphBreak;
/// current start/end pair
SwPosition const* m_pStartPos;
SwPosition const* m_pEndPos;
@@ -77,11 +83,13 @@ public:
SwPosition const* GetEndPos() const { return m_pEndPos; }
HideIterator(SwTextNode & rTextNode,
bool const isHideRedlines, sw::FieldmarkMode const eMode)
bool const isHideRedlines, sw::FieldmarkMode const eMode,
sw::ParagraphBreakMode const ePBMode)
: m_rIDRA(rTextNode.getIDocumentRedlineAccess())
, m_rIDMA(*rTextNode.getIDocumentMarkAccess())
, m_isHideRedlines(isHideRedlines)
, m_eFieldmarkMode(eMode)
, m_isHideParagraphBreaks(ePBMode == sw::ParagraphBreakMode::Hidden)
, m_Start(rTextNode, 0)
, m_RedlineIndex(isHideRedlines ? m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any) : SwRedlineTable::npos)
, m_pStartPos(nullptr)
@@ -188,12 +196,65 @@ public:
m_pEndPos = &*m_Fieldmark.second;
return true;
}
else // nothing
else
{
assert(!pNextRedlineHide && !m_oNextFieldmarkHide);
m_pStartPos = nullptr;
m_pEndPos = nullptr;
return false;
auto const hasHiddenItem = [](auto const& rNode) {
auto const& rpSet(rNode.GetAttr(RES_PARATR_LIST_AUTOFMT).GetStyleHandle());
return rpSet ? rpSet->Get(RES_CHRATR_HIDDEN).GetValue() : false;
};
auto const hasBreakBefore = [](SwTextNode const& rNode) {
if (rNode.GetAttr(RES_PAGEDESC).GetPageDesc())
{
return true;
}
switch (rNode.GetAttr(RES_BREAK).GetBreak())
{
case SvxBreak::ColumnBefore:
case SvxBreak::ColumnBoth:
case SvxBreak::PageBefore:
case SvxBreak::PageBoth:
return true;
default:
break;
}
return false;
};
auto const hasBreakAfter = [](SwTextNode const& rNode) {
switch (rNode.GetAttr(RES_BREAK).GetBreak())
{
case SvxBreak::ColumnAfter:
case SvxBreak::ColumnBoth:
case SvxBreak::PageAfter:
case SvxBreak::PageBoth:
return true;
default:
break;
}
return false;
};
if (m_isHideParagraphBreaks
// only merge if next node is also text node
&& m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->IsTextNode()
&& hasHiddenItem(*m_pEndPos->GetNode().GetTextNode())
// no merge if there's a page break on any node
&& !hasBreakBefore(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode())
// first node, see SwTextFrame::GetBreak()
&& !hasBreakAfter(*m_Start.GetNode().GetTextNode()))
{
m_oParagraphBreak.emplace(
SwPosition(*m_pEndPos->GetNode().GetTextNode(), m_pEndPos->GetNode().GetTextNode()->Len()),
SwPosition(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode(), 0));
m_pStartPos = &m_oParagraphBreak->first;
m_pEndPos = &m_oParagraphBreak->second;
return true;
}
else // nothing
{
m_pStartPos = nullptr;
m_pEndPos = nullptr;
return false;
}
}
}
};
@@ -221,7 +282,9 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode,
sal_Int32 nLastEnd(0);
for (auto iter = HideIterator(rTextNode,
rFrame.getRootFrame()->IsHideRedlines(),
rFrame.getRootFrame()->GetFieldmarkMode()); iter.Next(); )
rFrame.getRootFrame()->GetFieldmarkMode(),
rFrame.getRootFrame()->GetParagraphBreakMode());
iter.Next(); )
{
SwPosition const*const pStart(iter.GetStartPos());
SwPosition const*const pEnd(iter.GetEndPos());
diff --git a/sw/source/core/undo/unsect.cxx b/sw/source/core/undo/unsect.cxx
index b812821..4e75aa9 100644
--- a/sw/source/core/undo/unsect.cxx
+++ b/sw/source/core/undo/unsect.cxx
@@ -77,7 +77,7 @@ static std::optional<SfxItemSet> lcl_GetAttrSet( const SwSection& rSect )
SwUndoInsSection::SwUndoInsSection(
SwPaM const& rPam, SwSectionData const& rNewData,
SfxItemSet const*const pSet,
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode> const*const pTOXBase)
std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode> const*const pTOXBase)
: SwUndo( SwUndoId::INSSECTION, &rPam.GetDoc() ), SwUndRng( rPam )
, m_pSectionData(new SwSectionData(rNewData))
, m_pAttrSet( (pSet && pSet->Count()) ? new SfxItemSet( *pSet ) : nullptr )
@@ -90,7 +90,8 @@ SwUndoInsSection::SwUndoInsSection(
m_xTOXBase.emplace(
std::make_unique<SwTOXBase>(*std::get<0>(*pTOXBase)),
std::get<1>(*pTOXBase),
std::get<2>(*pTOXBase));
std::get<2>(*pTOXBase),
std::get<3>(*pTOXBase));
SwDoc& rDoc = rPam.GetDoc();
if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
@@ -187,18 +188,20 @@ void SwUndoInsSection::RedoImpl(::sw::UndoRedoContext & rContext)
SwRootFrame const* pLayout(nullptr);
SwRootFrame * pLayoutToReset(nullptr);
sw::FieldmarkMode eFieldmarkMode{};
sw::ParagraphBreakMode eParagraphBreakMode{};
comphelper::ScopeGuard g([&]() {
if (pLayoutToReset)
{
pLayoutToReset->SetHideRedlines(std::get<1>(*m_xTOXBase) == sw::RedlineMode::Shown);
pLayoutToReset->SetFieldmarkMode(eFieldmarkMode);
pLayoutToReset->SetFieldmarkMode(eFieldmarkMode, eParagraphBreakMode);
}
});
o3tl::sorted_vector<SwRootFrame *> layouts(rDoc.GetAllLayouts());
for (SwRootFrame const*const p : layouts)
{
if ((std::get<1>(*m_xTOXBase) == sw::RedlineMode::Hidden) == p->IsHideRedlines()
&& std::get<2>(*m_xTOXBase) == p->GetFieldmarkMode())
&& std::get<2>(*m_xTOXBase) == p->GetFieldmarkMode()
&& std::get<3>(*m_xTOXBase) == p->GetParagraphBreakMode())
{
pLayout = p;
break;
@@ -209,8 +212,9 @@ void SwUndoInsSection::RedoImpl(::sw::UndoRedoContext & rContext)
assert(!layouts.empty()); // must have one layout
pLayoutToReset = *layouts.begin();
eFieldmarkMode = pLayoutToReset->GetFieldmarkMode();
eParagraphBreakMode = pLayoutToReset->GetParagraphBreakMode();
pLayoutToReset->SetHideRedlines(std::get<1>(*m_xTOXBase) == sw::RedlineMode::Hidden);
pLayoutToReset->SetFieldmarkMode(std::get<2>(*m_xTOXBase));
pLayoutToReset->SetFieldmarkMode(std::get<2>(*m_xTOXBase), std::get<3>(*m_xTOXBase));
pLayout = pLayoutToReset;
}
pUpdateTOX = rDoc.InsertTableOf( *rPam.GetPoint(),
diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx
index 402f4f2..a1ea83c 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -2322,11 +2322,15 @@ void SwViewShell::ImplApplyViewOptions( const SwViewOption &rOpt )
// - Of course, the screen is something completely different than the printer ...
bool const isToggleFieldNames(mpOpt->IsFieldName() != rOpt.IsFieldName());
if (mpOpt->IsFieldName() != rOpt.IsFieldName())
if (mpOpt->IsFieldName() != rOpt.IsFieldName()
|| mpOpt->IsParagraph() != rOpt.IsParagraph())
{
GetLayout()->SetFieldmarkMode( rOpt.IsFieldName()
? sw::FieldmarkMode::ShowCommand
: sw::FieldmarkMode::ShowResult );
: sw::FieldmarkMode::ShowResult,
rOpt.IsParagraph()
? sw::ParagraphBreakMode::Shown
: sw::ParagraphBreakMode::Hidden);
bReformat = true;
}