sw/inc/crsrsh.hxx                   |    3 +
 sw/inc/docsh.hxx                    |    3 +
 sw/qa/extras/uiwriter/uiwriter3.cxx |   44 ++++++++++++++++++++
 sw/source/core/crsr/crsrsh.cxx      |   39 ++++++++++++++++++
 sw/source/uibase/app/docst.cxx      |   78 +++++++++++++++++++++++++++++++++++-
 5 files changed, 166 insertions(+), 1 deletion(-)

New commits:
commit 7a35f3dc7419d833b8f47069c4df63e900ccb880
Author:     László Németh <nem...@numbertext.org>
AuthorDate: Fri Oct 25 00:38:35 2024 +0200
Commit:     László Németh <nem...@numbertext.org>
CommitDate: Sat Oct 26 17:35:09 2024 +0200

    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 <nem...@numbertext.org>

diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx
index 02a08289a889..806b1b7d2472 100644
--- a/sw/inc/crsrsh.hxx
+++ b/sw/inc/crsrsh.hxx
@@ -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.
      */
diff --git a/sw/inc/docsh.hxx b/sw/inc/docsh.hxx
index f29c4ed78134..858c778ac784 100644
--- a/sw/inc/docsh.hxx
+++ b/sw/inc/docsh.hxx
@@ -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,
diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx 
b/sw/qa/extras/uiwriter/uiwriter3.cxx
index f7c31f4e76d8..cbd860f0aabf 100644
--- a/sw/qa/extras/uiwriter/uiwriter3.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter3.cxx
@@ -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     1"_ustr, getParagraph(3)->getString());
+}
+
 CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf144840)
 {
     createSwDoc("tdf144840.odt");
diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx
index fce43323b19b..6386b8673856 100644
--- a/sw/source/core/crsr/crsrsh.cxx
+++ b/sw/source/core/crsr/crsrsh.cxx
@@ -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())
diff --git a/sw/source/uibase/app/docst.cxx b/sw/source/uibase/app/docst.cxx
index d6be439d4259..e81ccb9aff8d 100644
--- a/sw/source/uibase/app/docst.cxx
+++ b/sw/source/uibase/app/docst.cxx
@@ -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;
         }

Reply via email to