tdf#48459 sw inline heading: apply it on the selected words

Selected text at the beginning of a paragraph (<= 75 characters)
become text frame based inline heading at applying a paragraph style
(using Formatting toolbar, context menu, Ctrl-1...Ctrl-5 or
Styles sidebar panel).

If the whole paragraph is selected, or if no or multiple
paragraphs are selected, formatting is still applied on the
whole paragraphs.

Using text frames for inline heading is ODF 1.0 compliant
and fully back-compatible with the older Writer versions.

The new inline heading frame contains direct formatting
to zero the upper and bottom paragraph margin to solve
interoperability issues: in MSO, margins of heading styles
are zeroed by using the style separators.

Note: lack of inline heading was a showstopper for creating
APA-, IEEE-, MIL-STD-961E-format, legal etc. documents.

Note: recent Formula frame style will be replaced by the
planned Inline Heading, which will be used by the DOCX filter
to export OOXML style separators instead of text frames.

Change-Id: I6722dcaef046bdbca2fe044d175806fa8c65278c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175580
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
This commit is contained in:
László Németh
2024-10-26 17:35:09 +02:00
parent 50cb6fb476
commit 7a35f3dc74
5 changed files with 166 additions and 1 deletions
+3
View File
@@ -552,6 +552,9 @@ public:
// Check if selection is within one paragraph.
bool IsSelOnePara() const;
// Check if selection starts a paragraph.
bool IsSelStartPara() const;
/*
* Returns SRectangle, at which the cursor is located.
*/
+3
View File
@@ -54,6 +54,7 @@ class SwDrawModel;
class SwViewShell;
class SwDocStyleSheetPool;
class SwXTextDocument;
class SwTextFormatColl;
namespace svt
{
class EmbeddedObjectRef;
@@ -143,6 +144,8 @@ class SW_DLLPUBLIC SwDocShell
SAL_DLLPRIVATE void Delete(const OUString &rName, SfxStyleFamily nFamily);
SAL_DLLPRIVATE void Hide(const OUString &rName, SfxStyleFamily nFamily, bool bHidden);
SAL_DLLPRIVATE bool MakeInlineHeading(SwWrtShell *pSh, SwTextFormatColl* pColl,
const sal_uInt16 nMode);
SAL_DLLPRIVATE SfxStyleFamily ApplyStyles(const OUString &rName,
const SfxStyleFamily nFamily,
SwWrtShell* pShell,
+44
View File
@@ -1315,6 +1315,50 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf147206)
u"HyperLinkURL"_ustr));
}
CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf48459)
{
createSwDoc();
SwDoc* pDoc = getSwDoc();
SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
// insert paragraph text
pWrtShell->Insert(u"Heading and normal text"_ustr);
// select the first word (proposed for inline heading)
pWrtShell->SttEndDoc(/*bStart=*/true);
pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 7, /*bBasicCall=*/false);
// apply styles only on the selected word -> create inline heading
uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence({
{ "Style", uno::Any(u"Heading 1"_ustr) },
{ "FamilyName", uno::Any(u"ParagraphStyles"_ustr) },
});
dispatchCommand(mxComponent, u".uno:StyleApply"_ustr, aPropertyValues);
uno::Reference<frame::XFrames> xFrames = mxDesktop->getFrames();
// inline heading frame
CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xFrames->getCount());
pWrtShell->EndOfSection(false);
// insert table of contents to check ToC content (containing only the inline heading)
SwTOXMgr mgr(pWrtShell);
SwTOXDescription desc{ TOX_CONTENT };
mgr.UpdateOrInsertTOX(desc, nullptr, nullptr);
CPPUNIT_ASSERT_EQUAL(int(3), getParagraphs());
// first paragraph: selected text moved to the inline heading frame
CPPUNIT_ASSERT_EQUAL(u" and normal text"_ustr, getParagraph(1)->getString());
// ToC title
CPPUNIT_ASSERT_EQUAL(u"Table of Contents"_ustr, getParagraph(2)->getString());
// ToC contains only the inline heading
CPPUNIT_ASSERT_EQUAL(u"Heading\t1"_ustr, getParagraph(3)->getString());
}
CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf144840)
{
createSwDoc("tdf144840.odt");
+39
View File
@@ -1394,6 +1394,45 @@ bool SwCursorShell::IsSelOnePara() const
return false;
}
bool SwCursorShell::IsSelStartPara() const
{
if (m_pCurrentCursor->IsMultiSelection())
{
return false;
}
if (m_pCurrentCursor->GetPoint()->GetContentIndex() == 0 ||
m_pCurrentCursor->GetMark()->GetContentIndex() == 0)
{
return true;
}
if (GetLayout()->HasMergedParas())
{
SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
if (pNode)
{
SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
pNode->getLayoutFrame(GetLayout())));
if (pFrame)
{
return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
== TextFrameIndex(0);
}
}
SwTextNode const*const pNode2(m_pCurrentCursor->GetMark()->GetNode().GetTextNode());
if (pNode2)
{
SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
pNode2->getLayoutFrame(GetLayout())));
if (pFrame)
{
return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetMark())
== TextFrameIndex(0);
}
}
}
return false;
}
bool SwCursorShell::IsSttPara() const
{
if (GetLayout()->HasMergedParas())
+77 -1
View File
@@ -85,6 +85,10 @@
#include <docmodel/theme/Theme.hxx>
#include <svx/svdpage.hxx>
#include <officecfg/Office/Common.hxx>
#include <fmtfsize.hxx>
#include <svl/ptitem.hxx>
#include <editeng/sizeitem.hxx>
#include <editeng/ulspitem.hxx>
using namespace ::com::sun::star;
@@ -1170,6 +1174,71 @@ void SwDocShell::Hide(const OUString &rName, SfxStyleFamily nFamily, bool bHidde
}
}
#define MAX_CHAR_IN_INLINE_HEADING 75
bool SwDocShell::MakeInlineHeading(SwWrtShell *pSh, SwTextFormatColl* pColl, const sal_uInt16 nMode)
{
// insert an inline heading frame, if only MAX_CHAR_IN_INLINE_HEADING or less
// characters are selected beginning of a single paragraph, but not the full paragraph
// TODO extend it for multiple selections
if ( pSh->IsSelOnePara() && !pSh->IsSelFullPara() && pSh->IsSelStartPara() &&
GetView()->GetSelectionText().getLength() < MAX_CHAR_IN_INLINE_HEADING &&
0 < GetView()->GetSelectionText().getLength() )
{
SwTextFormatColl *pLocal = pColl? pColl: (*GetDoc()->GetTextFormatColls())[0];
// put inside a single Undo
SwRewriter aRewriter;
aRewriter.AddRule(UndoArg1, pLocal->GetName());
GetWrtShell()->StartUndo(SwUndoId::SETFMTCOLL, &aRewriter);
// anchor as character
SfxUInt16Item aAnchor(FN_INSERT_FRAME, static_cast<sal_uInt16>(1));
SvxSizeItem aSizeItem(FN_PARAM_2, Size(1, 1));
GetView()->GetViewFrame().GetDispatcher()->ExecuteList(FN_INSERT_FRAME,
SfxCallMode::SYNCHRON|SfxCallMode::RECORD, { &aAnchor, &aSizeItem });
if ( pSh->IsFrameSelected() )
{
// use the borderless frame style "Formula"
// TODO add a new frame style "Inline Heading"
SwDocStyleSheet* pStyle2 = static_cast<SwDocStyleSheet*>(
m_xBasePool->Find( "Formula", SfxStyleFamily::Frame));
pSh->SetFrameFormat( pStyle2->GetFrameFormat() );
// set variable width frame to extend for the width of the text content
SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END - 1> aSet(pSh->GetAttrPool());
pSh->GetFlyFrameAttr( aSet );
SwTwips nMinWidth = 100;
SwFormatFrameSize aSize(SwFrameSize::Variable, nMinWidth, nMinWidth);
aSize.SetWidthSizeType(SwFrameSize::Variable);
aSet.Put(aSize);
pSh->SetFlyFrameAttr( aSet );
// select the text content of the frame, and apply the paragraph style
pSh->UnSelectFrame();
pSh->LeaveSelFrameMode();
pSh->MoveSection( GoCurrSection, fnSectionEnd );
pSh->SelAll();
pSh->SetTextFormatColl( pColl, true, (nMode & KEY_MOD1) ? SetAttrMode::REMOVE_ALL_ATTR : SetAttrMode::DEFAULT);
// zero the upper and lower margins of the paragraph (also an interoperability issue)
SfxItemSetFixed<RES_UL_SPACE, RES_UL_SPACE> aSet2(pSh->GetAttrPool());
pSh->GetCurAttr( aSet2 );
SvxULSpaceItem aUL( 0, 0, RES_UL_SPACE );
pSh->SetAttrItem( aUL );
// leave the inline heading frame
GetView()->GetViewFrame().GetDispatcher()->Execute(FN_ESCAPE, SfxCallMode::ASYNCHRON);
GetView()->GetViewFrame().GetDispatcher()->Execute(FN_ESCAPE, SfxCallMode::ASYNCHRON);
GetView()->GetViewFrame().GetDispatcher()->Execute(FN_ESCAPE, SfxCallMode::SYNCHRON);
GetWrtShell()->EndUndo();
return true;
}
}
return false;
}
// apply template
SfxStyleFamily SwDocShell::ApplyStyles(const OUString &rName, SfxStyleFamily nFamily,
SwWrtShell* pShell, const sal_uInt16 nMode )
@@ -1211,10 +1280,17 @@ SfxStyleFamily SwDocShell::ApplyStyles(const OUString &rName, SfxStyleFamily nFa
// outline node become folded content of the previous outline node if the previous
// outline node's content is folded.
MakeAllOutlineContentTemporarilyVisible a(GetDoc());
// if the first 75 or less characters are selected, but not the full paragraph,
// create an inline heading from the selected text
SwTextFormatColl* pColl = pStyle->GetCollection();
if ( MakeInlineHeading( pSh, pColl, nMode ) )
break;
// #i62675#
// clear also list attributes at affected text nodes, if paragraph
// style has the list style attribute set.
pSh->SetTextFormatColl( pStyle->GetCollection(), true, (nMode & KEY_MOD1) ? SetAttrMode::REMOVE_ALL_ATTR : SetAttrMode::DEFAULT);
pSh->SetTextFormatColl( pColl, true, (nMode & KEY_MOD1) ? SetAttrMode::REMOVE_ALL_ATTR : SetAttrMode::DEFAULT);
}
break;
}