diff --git a/cui/source/inc/paragrph.hxx b/cui/source/inc/paragrph.hxx index ff6482526ed3..9d95e1adfa9b 100644 --- a/cui/source/inc/paragrph.hxx +++ b/cui/source/inc/paragrph.hxx @@ -151,6 +151,7 @@ class SvxParaAlignTabPage : public SfxTabPage std::unique_ptr m_xVertAlignSdr; std::unique_ptr m_xTextDirectionLB; + std::unique_ptr m_xAutoTextDirectionCB; /// word spacing std::unique_ptr m_xLabelWordSpacing; diff --git a/cui/source/tabpages/paragrph.cxx b/cui/source/tabpages/paragrph.cxx index 978c4811b8cf..427fb7356654 100644 --- a/cui/source/tabpages/paragrph.cxx +++ b/cui/source/tabpages/paragrph.cxx @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -77,7 +78,8 @@ const WhichRangesContainer SvxParaAlignTabPage::pSdrAlignRanges( svl::Items< SDRATTR_TEXT_VERTADJUST, SDRATTR_TEXT_VERTADJUST, // 1076 SID_ATTR_PARA_ADJUST, SID_ATTR_PARA_ADJUST , // 10027 - SID_ATTR_FRAMEDIRECTION, SID_ATTR_FRAMEDIRECTION // 10944 + SID_ATTR_FRAMEDIRECTION, SID_ATTR_FRAMEDIRECTION, // 10944 + SID_ATTR_PARA_AUTOFRAMEDIRECTION, SID_ATTR_PARA_AUTOFRAMEDIRECTION >); const WhichRangesContainer SvxExtParagraphTabPage::pExtRanges(svl::Items< @@ -1282,6 +1284,7 @@ SvxParaAlignTabPage::SvxParaAlignTabPage(weld::Container* pPage, weld::DialogCon , m_xVertAlign(m_xBuilder->weld_label(u"labelFL_VERTALIGN"_ustr)) , m_xVertAlignSdr(m_xBuilder->weld_label(u"labelST_VERTALIGN_SDR"_ustr)) , m_xTextDirectionLB(new svx::FrameDirectionListBox(m_xBuilder->weld_combo_box(u"comboLB_TEXTDIRECTION"_ustr))) + , m_xAutoTextDirectionCB(m_xBuilder->weld_check_button(u"checkCB_AUTOTEXTDIRECTION"_ustr)) , m_xLabelWordSpacing(m_xBuilder->weld_label(u"labelWordSpacing"_ustr)) , m_xLabelMinimum(m_xBuilder->weld_label(u"labelMinimum"_ustr)) , m_xLabelDesired(m_xBuilder->weld_label(u"labelDesired"_ustr)) @@ -1458,6 +1461,13 @@ bool SvxParaAlignTabPage::FillItemSet( SfxItemSet* rOutSet ) } } + if (m_xAutoTextDirectionCB->get_state_changed_from_saved()) + { + rOutSet->Put(SvxAutoFrameDirectionItem(m_xAutoTextDirectionCB->get_active(), + GetWhich(SID_ATTR_PARA_AUTOFRAMEDIRECTION))); + bModified = true; + } + return bModified; } @@ -1618,6 +1628,16 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xTextDirectionLB->save_value(); } + _nWhich = GetWhich(SID_ATTR_PARA_AUTOFRAMEDIRECTION); + eItemState = rSet->GetItemState(_nWhich); + if (eItemState >= SfxItemState::DEFAULT) + { + const SvxAutoFrameDirectionItem& rSnap + = static_cast(rSet->Get(_nWhich)); + m_xAutoTextDirectionCB->set_active(rSnap.GetValue()); + } + + m_xAutoTextDirectionCB->save_state(); m_xSnapToGridCB->save_state(); m_xVertAlignLB->save_value(); m_xLeft->save_state(); @@ -1642,6 +1662,7 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) void SvxParaAlignTabPage::ChangesApplied() { m_xTextDirectionLB->save_value(); + m_xAutoTextDirectionCB->save_state(); m_xSnapToGridCB->save_state(); m_xVertAlignLB->save_value(); m_xLeft->save_state(); diff --git a/cui/uiconfig/ui/paragalignpage.ui b/cui/uiconfig/ui/paragalignpage.ui index 46644dba15bf..4f0a095d9ef7 100644 --- a/cui/uiconfig/ui/paragalignpage.ui +++ b/cui/uiconfig/ui/paragalignpage.ui @@ -418,36 +418,62 @@ 0 none - + + True False - 12 - 6 - 6 - + True False - _Text direction: - True - comboLB_TEXTDIRECTION - 0 + 12 + 6 + 6 + + + True + False + _Text direction: + True + comboLB_TEXTDIRECTION + 0 + + + False + True + 0 + + + + + True + False + + + False + True + 1 + + - False - True - 0 + 0 + 0 - + + Automatically set direction True - False + True + False + 22 + True + True - False - True - 1 + 0 + 1 diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx index 05d7084a278b..581a22a88d30 100644 --- a/editeng/source/editeng/editdoc.cxx +++ b/editeng/source/editeng/editdoc.cxx @@ -19,6 +19,7 @@ #include +#include #include #include #include @@ -1031,6 +1032,14 @@ void YrsInsertAttribImplImpl(YrsWrite const& yw, SfxPoolItem const& rItm, attrName = "EE_PARA_WRITINGDIR"; break; } + case EE_PARA_AUTOWRITINGDIR: + { + SvxAutoFrameDirectionItem const& rItem{ static_cast( + rItm) }; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_PARA_AUTOWRITINGDIR"; + break; + } case EE_PARA_HANGINGPUNCTUATION: { SvxHangingPunctuationItem const& rItem{static_cast(rItm)}; @@ -2118,6 +2127,13 @@ void YrsImplInsertAttr(SfxItemSet & rSet, ::std::vector *const pRemo rSet.Put(item); break; } + case EE_PARA_AUTOWRITINGDIR: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxAutoFrameDirectionitem const item{ rValue.value.flag == Y_TRUE, nWhich }; + rSet.Put(item); + break; + } case EE_PARA_HANGINGPUNCTUATION: { yvalidate(rValue.tag == Y_JSON_BOOL); diff --git a/editeng/source/editeng/eerdll.cxx b/editeng/source/editeng/eerdll.cxx index e828897160e1..bd154781d87b 100644 --- a/editeng/source/editeng/eerdll.cxx +++ b/editeng/source/editeng/eerdll.cxx @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -118,6 +119,7 @@ ItemInfoPackage& getItemInfoPackageEditEngine() { EE_PARA_TABS, new SvxTabStopItem( 0, 0, SvxTabAdjust::Left, EE_PARA_TABS ), SID_ATTR_TABSTOP, SFX_ITEMINFOFLAG_NONE }, { EE_PARA_JUST_METHOD, new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, EE_PARA_JUST_METHOD ), SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, SFX_ITEMINFOFLAG_NONE }, { EE_PARA_VER_JUST, new SvxVerJustifyItem( SvxCellVerJustify::Standard, EE_PARA_VER_JUST ), SID_ATTR_ALIGN_VER_JUSTIFY, SFX_ITEMINFOFLAG_NONE }, + { EE_PARA_AUTOWRITINGDIR, new SvxAutoFrameDirectionItem( false, EE_PARA_AUTOWRITINGDIR ), SID_ATTR_PARA_AUTOFRAMEDIRECTION, SFX_ITEMINFOFLAG_NONE }, { EE_CHAR_COLOR, new SvxColorItem( COL_AUTO, EE_CHAR_COLOR ), SID_ATTR_CHAR_COLOR, SFX_ITEMINFOFLAG_SUPPORT_SURROGATE }, // EE_CHAR_FONTINFO, EE_CHAR_FONTINFO_CJK and EE_CHAR_FONTINFO_CTL need on-demand initialization diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx index 72eadc1b79ac..aba08ab2f2ba 100644 --- a/editeng/source/editeng/impedit.hxx +++ b/editeng/source/editeng/impedit.hxx @@ -1044,6 +1044,7 @@ public: EditPaM InsertLineBreak(const EditSelection& aEditSelection); EditPaM InsertTab(const EditSelection& rEditSelection); EditPaM InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld); + void UpdateAutoParaDirection(const EditSelection& rCurSel); bool UpdateFields(); ErrCode Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs = nullptr ); diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx index c72239dedc11..6d64243e1ffe 100644 --- a/editeng/source/editeng/impedit2.cxx +++ b/editeng/source/editeng/impedit2.cxx @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -466,6 +468,9 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) mpIMEInfos->nLen = pData->GetText().getLength(); } + // tdf#162120: Automatically adjust paragraph directions after edit + UpdateAutoParaDirection(EditSelection{ mpIMEInfos->aPos }); + ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); FormatAndLayout( pView ); @@ -2532,6 +2537,10 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) UpdateSelectionsDelete(deleted); UpdateSelections(); + + // tdf#162120: Automatically adjust paragraph directions after edit + UpdateAutoParaDirection(rCurSel); + TextModified(); return aStartPaM; } @@ -2763,6 +2772,9 @@ EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, aPaM.SetIndex( aPaM.GetIndex()+1 ); // does not do EditDoc-Method anymore } + // tdf#162120: Automatically adjust paragraph direction after edit + UpdateAutoParaDirection(EditSelection{ aPaM }); + TextModified(); if ( bUndoAction ) @@ -2771,6 +2783,53 @@ EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, return aPaM; } +void ImpEditEngine::UpdateAutoParaDirection(const EditSelection& rCurSel) +{ + EditPaM aPaM(rCurSel.Min()); + + auto nPara = maEditDoc.GetPos(aPaM.GetNode()); + if (nPara >= GetParaPortions().Count() || !HasParaAttrib(nPara, EE_PARA_AUTOWRITINGDIR)) + { + return; + } + + const SvxAutoFrameDirectionItem& rItem = GetParaAttrib(nPara, EE_PARA_AUTOWRITINGDIR); + if (!rItem.GetValue()) + { + return; + } + + bool bIsAlreadyRtl = IsRightToLeft(nPara); + + bool bShouldBeRtl = bIsAlreadyRtl; + switch (i18nutil::GuessParagraphDirection(aPaM.GetNode()->GetString())) + { + case i18nutil::ParagraphDirection::Ambiguous: + bShouldBeRtl = bIsAlreadyRtl; + break; + + case i18nutil::ParagraphDirection::LeftToRight: + bShouldBeRtl = false; + break; + + case i18nutil::ParagraphDirection::RightToLeft: + bShouldBeRtl = true; + break; + } + + if (bShouldBeRtl == bIsAlreadyRtl) + { + return; + } + + SvxFrameDirection eNeeded + = bShouldBeRtl ? SvxFrameDirection::Horizontal_RL_TB : SvxFrameDirection::Horizontal_LR_TB; + + SfxItemSet aSet{ GetParaAttribs(nPara) }; + aSet.Put(SvxFrameDirectionItem{ eNeeded, EE_PARA_WRITINGDIR }); + SetParaAttribs(nPara, aSet); +} + EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr) { UndoActionStart( EDITUNDO_INSERT ); diff --git a/editeng/source/items/paraitem.cxx b/editeng/source/items/paraitem.cxx index 9c16c3f78dca..f49b1f986717 100644 --- a/editeng/source/items/paraitem.cxx +++ b/editeng/source/items/paraitem.cxx @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -1514,5 +1515,24 @@ bool SvxParaGridItem::GetPresentation( return true; } +SvxAutoFrameDirectionItem::SvxAutoFrameDirectionItem(bool bValue, const sal_uInt16 nId) + : SfxBoolItem(nId, bValue) +{ +} + +SvxAutoFrameDirectionItem* SvxAutoFrameDirectionItem::Clone(SfxItemPool*) const +{ + return new SvxAutoFrameDirectionItem(*this); +} + +bool SvxAutoFrameDirectionItem::GetPresentation(SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString& rText, const IntlWrapper& /*rIntl*/) const +{ + rText = GetValue() ? EditResId(RID_SVXITEMS_AUTOFRAMEDIRECTION_ON) + : EditResId(RID_SVXITEMS_AUTOFRAMEDIRECTION_OFF); + + return true; +} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/i18nutil/CppunitTest_i18nutil.mk b/i18nutil/CppunitTest_i18nutil.mk index cdf79a2d2268..56cf5889f9ef 100644 --- a/i18nutil/CppunitTest_i18nutil.mk +++ b/i18nutil/CppunitTest_i18nutil.mk @@ -14,6 +14,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,i18nutil)) $(eval $(call gb_CppunitTest_use_sdk_api,i18nutil)) $(eval $(call gb_CppunitTest_add_exception_objects,i18nutil,\ + i18nutil/qa/cppunit/test_guessparadirection \ i18nutil/qa/cppunit/test_kashida \ i18nutil/qa/cppunit/test_scriptchangescanner \ )) diff --git a/i18nutil/Library_i18nutil.mk b/i18nutil/Library_i18nutil.mk index 8e68c3ba00c2..cf166a476401 100644 --- a/i18nutil/Library_i18nutil.mk +++ b/i18nutil/Library_i18nutil.mk @@ -44,6 +44,7 @@ $(eval $(call gb_Library_use_libraries,i18nutil,\ $(eval $(call gb_Library_add_exception_objects,i18nutil,\ i18nutil/source/utility/casefolding \ + i18nutil/source/utility/guessparadirection \ i18nutil/source/utility/kashida \ i18nutil/source/utility/oneToOneMapping \ i18nutil/source/utility/paper \ diff --git a/i18nutil/qa/cppunit/test_guessparadirection.cxx b/i18nutil/qa/cppunit/test_guessparadirection.cxx new file mode 100644 index 000000000000..1995fc18dbf4 --- /dev/null +++ b/i18nutil/qa/cppunit/test_guessparadirection.cxx @@ -0,0 +1,75 @@ +/* -*- 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/. + */ + +#include +#include +#include +#include +#include +#include + +using namespace i18nutil; + +namespace +{ +class GuessParagraphDirectionTest : public CppUnit::TestFixture +{ +public: + void testEmpty(); + void testFirstStrongLTR(); + void testFirstStrongRTL(); + void testFirstStrongAR(); + void testIsolate(); + + CPPUNIT_TEST_SUITE(GuessParagraphDirectionTest); + CPPUNIT_TEST(testEmpty); + CPPUNIT_TEST(testFirstStrongLTR); + CPPUNIT_TEST(testFirstStrongRTL); + CPPUNIT_TEST(testFirstStrongAR); + CPPUNIT_TEST(testIsolate); + CPPUNIT_TEST_SUITE_END(); +}; + +void GuessParagraphDirectionTest::testEmpty() +{ + auto aText = u""_ustr; + CPPUNIT_ASSERT_EQUAL(ParagraphDirection::Ambiguous, GuessParagraphDirection(aText)); +} + +void GuessParagraphDirectionTest::testFirstStrongLTR() +{ + auto aText = u"..aاא.."_ustr; + CPPUNIT_ASSERT_EQUAL(ParagraphDirection::LeftToRight, GuessParagraphDirection(aText)); +} + +void GuessParagraphDirectionTest::testFirstStrongRTL() +{ + auto aText = u"..אaا.."_ustr; + CPPUNIT_ASSERT_EQUAL(ParagraphDirection::RightToLeft, GuessParagraphDirection(aText)); +} + +void GuessParagraphDirectionTest::testFirstStrongAR() +{ + auto aText = u"..اaא.."_ustr; + CPPUNIT_ASSERT_EQUAL(ParagraphDirection::RightToLeft, GuessParagraphDirection(aText)); +} + +void GuessParagraphDirectionTest::testIsolate() +{ + // U+2066: LRI + // U+2067: RLI + // U+2069: PDI + auto aText = u"...\u2069.\u2067aaa\u2066אאא\u2069.\u2069.."_ustr; + CPPUNIT_ASSERT_EQUAL(ParagraphDirection::Ambiguous, GuessParagraphDirection(aText)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(GuessParagraphDirectionTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/i18nutil/source/utility/guessparadirection.cxx b/i18nutil/source/utility/guessparadirection.cxx new file mode 100644 index 000000000000..f882b2a6c928 --- /dev/null +++ b/i18nutil/source/utility/guessparadirection.cxx @@ -0,0 +1,65 @@ +/* -*- 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/. + */ + +#include +#include +#include +#include + +i18nutil::ParagraphDirection i18nutil::GuessParagraphDirection(const OUString& rText) +{ + // tdf#162120: This algorithm implements Unicode UAX #9 3.3.1 + // for automatically determining the paragraph level without + // override from a higher-level protocol. + // The algorithm has been extended to explicitly communicate + // an ambiguous case, rather than defaulting to LTR. + + sal_Int32 nBase = 0; + + sal_Int32 nIsolateLevel = 0; + while (nBase < rText.getLength()) + { + auto nChar = rText.iterateCodePoints(&nBase); + auto nCharDir = u_charDirection(nChar); + + switch (nCharDir) + { + case U_POP_DIRECTIONAL_ISOLATE: + nIsolateLevel = std::max(sal_Int32{ 0 }, nIsolateLevel - 1); + break; + + case U_LEFT_TO_RIGHT_ISOLATE: + case U_RIGHT_TO_LEFT_ISOLATE: + ++nIsolateLevel; + break; + + case U_LEFT_TO_RIGHT: + if (nIsolateLevel == 0) + { + return ParagraphDirection::LeftToRight; + } + break; + + case U_RIGHT_TO_LEFT: + case U_RIGHT_TO_LEFT_ARABIC: + if (nIsolateLevel == 0) + { + return ParagraphDirection::RightToLeft; + } + break; + + default: + break; + } + } + + return ParagraphDirection::Ambiguous; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/editeng/autodiritem.hxx b/include/editeng/autodiritem.hxx new file mode 100644 index 000000000000..2b3cf3c4715a --- /dev/null +++ b/include/editeng/autodiritem.hxx @@ -0,0 +1,37 @@ +/* -*- 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 . + */ +#pragma once + +#include +#include + +class EDITENG_DLLPUBLIC SvxAutoFrameDirectionItem final : public SfxBoolItem +{ +public: + DECLARE_ITEM_TYPE_FUNCTION(SvxAutoFrameDirectionItem) + SvxAutoFrameDirectionItem(const bool bValue, const sal_uInt16 nId); + + virtual SvxAutoFrameDirectionItem* Clone(SfxItemPool* pPool = nullptr) const override; + + virtual bool GetPresentation(SfxItemPresentation ePres, MapUnit eCoreMetric, + MapUnit ePresMetric, OUString& rText, + const IntlWrapper&) const override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/editeng/editids.hrc b/include/editeng/editids.hrc index 6a1a0e81f339..72b4a5a6d8ad 100644 --- a/include/editeng/editids.hrc +++ b/include/editeng/editids.hrc @@ -160,6 +160,7 @@ class SvxScriptHintItem; #define SID_PARA_VERTALIGN TypedWhichId( SID_SVX_START + 925 ) #define SID_ATTR_FRAMEDIRECTION TypedWhichId( SID_SVX_START + 944 ) #define SID_ATTR_PARA_SNAPTOGRID TypedWhichId( SID_SVX_START + 945 ) +#define SID_ATTR_PARA_AUTOFRAMEDIRECTION TypedWhichId( SID_SVX_START + 946 ) #define SID_ATTR_PARA_LRSPACE_VERTICAL ( SID_SVX_START + 947 ) #define SID_ATTR_PARA_LEFT_TO_RIGHT ( SID_SVX_START + 950 ) #define SID_ATTR_PARA_RIGHT_TO_LEFT ( SID_SVX_START + 951 ) diff --git a/include/editeng/editrids.hrc b/include/editeng/editrids.hrc index fa82e0d90f33..a257b9004b58 100644 --- a/include/editeng/editrids.hrc +++ b/include/editeng/editrids.hrc @@ -301,6 +301,8 @@ #define RID_SVXITEMS_FRMDIR_VERT_TOP_RIGHT90 NC_("RID_SVXITEMS_FRMDIR_Vert_TOP_RIGHT90", "Text direction right-to-left (vertical, all characters rotated)") #define RID_SVXITEMS_PARASNAPTOGRID_ON NC_("RID_SVXITEMS_PARASNAPTOGRID_ON", "Paragraph snaps to text grid (if active)") #define RID_SVXITEMS_PARASNAPTOGRID_OFF NC_("RID_SVXITEMS_PARASNAPTOGRID_OFF", "Paragraph does not snap to text grid") +#define RID_SVXITEMS_AUTOFRAMEDIRECTION_ON NC_("RID_SVXITEMS_AUTOFRAMEDIRECTION_ON", "Text direction is automatically updated") +#define RID_SVXITEMS_AUTOFRAMEDIRECTION_OFF NC_("RID_SVXITEMS_AUTOFRAMEDIRECTION_OFF", "Text direction is not automatically updated") #define RID_SVXITEMS_CHARHIDDEN_FALSE NC_("RID_SVXITEMS_CHARHIDDEN_FALSE", "Not hidden") #define RID_SVXITEMS_CHARHIDDEN_TRUE NC_("RID_SVXITEMS_CHARHIDDEN_TRUE", "Hidden") diff --git a/include/editeng/eeitem.hxx b/include/editeng/eeitem.hxx index ce8315ec4e27..89b85207a24a 100644 --- a/include/editeng/eeitem.hxx +++ b/include/editeng/eeitem.hxx @@ -27,6 +27,7 @@ class SfxBoolItem; class SfxGrabBagItem; class SfxInt16Item; class SvxAdjustItem; +class SvxAutoFrameDirectionItem; class SvxAutoKernItem; class SvxCaseMapItem; class SvxCharReliefItem; @@ -93,7 +94,8 @@ inline constexpr TypedWhichId EE_PARA_JUST inline constexpr TypedWhichId EE_PARA_TABS (EE_PARA_START+17); inline constexpr TypedWhichId EE_PARA_JUST_METHOD (EE_PARA_START+18); inline constexpr TypedWhichId EE_PARA_VER_JUST (EE_PARA_START+19); -inline constexpr sal_uInt16 EE_PARA_END (EE_PARA_START + 19); +inline constexpr TypedWhichId EE_PARA_AUTOWRITINGDIR (EE_PARA_START+20); +inline constexpr sal_uInt16 EE_PARA_END (EE_PARA_START + 20); // Character attributes: inline constexpr sal_uInt16 EE_CHAR_START (EE_PARA_END + 1); diff --git a/include/editeng/unotext.hxx b/include/editeng/unotext.hxx index f2bc22fd4dba..37e82293dbb3 100644 --- a/include/editeng/unotext.hxx +++ b/include/editeng/unotext.hxx @@ -170,7 +170,8 @@ struct SfxItemPropertyMapEntry; {u"ParaLetterSpacingMaximum"_ustr, EE_PARA_JUST, ::cppu::UnoType::get(), 0, MID_LETTER_SPACING_MAX }, \ {u"ParaScaleWidthMinimum"_ustr, EE_PARA_JUST, ::cppu::UnoType::get(), 0, MID_SCALE_WIDTH_MIN }, \ {u"ParaScaleWidthMaximum"_ustr, EE_PARA_JUST, ::cppu::UnoType::get(), 0, MID_SCALE_WIDTH_MAX }, \ - {u"WritingMode"_ustr, EE_PARA_WRITINGDIR, ::cppu::UnoType::get(), 0, 0 } + {u"WritingMode"_ustr, EE_PARA_WRITINGDIR, ::cppu::UnoType::get(), 0, 0 }, \ + {u"WritingModeAutomatic"_ustr, EE_PARA_AUTOWRITINGDIR, ::cppu::UnoType::get(), 0, 0 } class SvxFieldData; diff --git a/include/i18nutil/guessparadirection.hxx b/include/i18nutil/guessparadirection.hxx new file mode 100644 index 000000000000..6ad5a5d8d04d --- /dev/null +++ b/include/i18nutil/guessparadirection.hxx @@ -0,0 +1,30 @@ +/* -*- 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/. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace i18nutil +{ +enum class ParagraphDirection +{ + Ambiguous, + LeftToRight, + RightToLeft +}; + +I18NUTIL_DLLPUBLIC ParagraphDirection GuessParagraphDirection(const OUString& rText); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/svl/poolitem.hxx b/include/svl/poolitem.hxx index ceebf33f24f3..fbe4b6a62336 100644 --- a/include/svl/poolitem.hxx +++ b/include/svl/poolitem.hxx @@ -309,6 +309,7 @@ enum class SfxItemType : sal_uInt16 Svx3DTextureProjectionXItemType, Svx3DTextureProjectionYItemType, SvxAdjustItemType, + SvxAutoFrameDirectionItemType, SvxAutoKernItemType, SvxB3DVectorItemType, SvxBitmapListItemType, diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx index 828de31eb2aa..cd1cb76a3cd7 100644 --- a/include/xmloff/xmltoken.hxx +++ b/include/xmloff/xmltoken.hxx @@ -2214,6 +2214,7 @@ namespace xmloff::token { XML_WRAP_CONTOUR_MODE, XML_WRAP_OPTION, XML_WRITING_MODE, + XML_WRITING_MODE_AUTOMATIC, XML_WWW, XML_X, XML_X1, diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx index a44b9e78eb73..aaa90df40c6c 100644 --- a/sw/inc/editsh.hxx +++ b/sw/inc/editsh.hxx @@ -161,6 +161,8 @@ class SAL_DLLPUBLIC_RTTI SwEditShell : public SwCursorShell using SwViewShell::UpdateFields; using sw::BroadcastingModify::GetInfo; + void UpdateSelectionAutoParaDirection(); + public: /// Edit (all selected ranges). void Insert( sal_Unicode, bool bOnlyCurrCursor = false ); diff --git a/sw/inc/hintids.hxx b/sw/inc/hintids.hxx index 9636779a1cc6..39676270559c 100644 --- a/sw/inc/hintids.hxx +++ b/sw/inc/hintids.hxx @@ -35,6 +35,7 @@ class SwFormatContentControl; class SvXMLAttrContainerItem; class SwMsgPoolItem; class SfxBoolItem; +class SvxAutoFrameDirectionItem; class SvxColorItem; class SvxLeftMarginItem; class SvxTextLeftMarginItem; @@ -314,9 +315,13 @@ inline constexpr TypedWhichId RES_PARATR_SNAPTOGRID(RES_PARATR_ inline constexpr TypedWhichId RES_PARATR_CONNECT_BORDER(RES_PARATR_BEGIN + 15); inline constexpr TypedWhichId RES_PARATR_OUTLINELEVEL(RES_PARATR_BEGIN + 16); -inline constexpr TypedWhichId RES_PARATR_RSID(RES_PARATR_BEGIN + 17); -inline constexpr TypedWhichId RES_PARATR_GRABBAG(RES_PARATR_BEGIN + 18); -inline constexpr sal_uInt16 RES_PARATR_END(RES_PARATR_BEGIN + 19); +inline constexpr TypedWhichId RES_PARATR_AUTOFRAMEDIR(RES_PARATR_BEGIN + + 17); +// DocumentRedlineManager assumes RSID and GRABBAG are the last paragraph attributes. +// Insert new paragraph attributes before them. +inline constexpr TypedWhichId RES_PARATR_RSID(RES_PARATR_BEGIN + 18); +inline constexpr TypedWhichId RES_PARATR_GRABBAG(RES_PARATR_BEGIN + 19); +inline constexpr sal_uInt16 RES_PARATR_END(RES_PARATR_BEGIN + 20); // list attributes for paragraphs. // intentionally these list attributes are not contained in paragraph styles diff --git a/sw/inc/inspectorproperties.hrc b/sw/inc/inspectorproperties.hrc index 5c7d5f2c62cd..d6dedab2f9f3 100644 --- a/sw/inc/inspectorproperties.hrc +++ b/sw/inc/inspectorproperties.hrc @@ -275,6 +275,7 @@ #define RID_UNVISITED_CHAR_STYLE_NAME NC_("RID_ATTRIBUTE_NAMES_MAP", "Unvisited Char Style Name") #define RID_VISITED_CHAR_STYLE_NAME NC_("RID_ATTRIBUTE_NAMES_MAP", "Visited Char Style Name") #define RID_WRITING_MODE NC_("RID_ATTRIBUTE_NAMES_MAP", "Writing Mode") +#define RID_WRITING_MODE_AUTOMATIC NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Auto Writing Mode") /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/inc/paratr.hxx b/sw/inc/paratr.hxx index ae222249fe70..947e9a7c665e 100644 --- a/sw/inc/paratr.hxx +++ b/sw/inc/paratr.hxx @@ -26,6 +26,7 @@ #include "swatrset.hxx" #include "format.hxx" #include "charfmt.hxx" +#include #include #include #include @@ -232,6 +233,8 @@ inline const SvxParaGridItem &SwAttrSet::GetParaGrid(bool bInP) const { return Get( RES_PARATR_SNAPTOGRID, bInP ); } inline const SwParaConnectBorderItem &SwAttrSet::GetParaConnectBorder(bool bInP) const { return Get( RES_PARATR_CONNECT_BORDER, bInP ); } +inline const SvxAutoFrameDirectionItem &SwAttrSet::GetAutoFrameDirection(bool bInP) const + { return Get( RES_PARATR_AUTOFRAMEDIR, bInP ); } // Implementation of paragraph-attributes methods of SwFormat inline const SvxLineSpacingItem &SwFormat::GetLineSpacing(bool bInP) const diff --git a/sw/inc/swatrset.hxx b/sw/inc/swatrset.hxx index 8e3df5fcb145..67709a849665 100644 --- a/sw/inc/swatrset.hxx +++ b/sw/inc/swatrset.hxx @@ -130,6 +130,7 @@ class SvxHangingPunctuationItem; class SvxForbiddenRuleItem; class SvxParaVertAlignItem; class SvxParaGridItem; +class SvxAutoFrameDirectionItem; class SwParaConnectBorderItem; // TableBox attributes @@ -322,6 +323,7 @@ public: inline const SvxParaVertAlignItem &GetParaVertAlign(bool = true) const; inline const SvxParaGridItem &GetParaGrid(bool = true) const; inline const SwParaConnectBorderItem &GetParaConnectBorder(bool = true ) const; + inline const SvxAutoFrameDirectionItem &GetAutoFrameDirection(bool = true) const; // Tablebox attributes - implementation in cellatr.hxx inline const SwTableBoxNumFormat &GetTableBoxNumFormat( bool = true ) const; diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index 341777e80b23..57ed686f8442 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -736,6 +736,7 @@ inline constexpr OUString UNO_NAME_CELL_NAME = u"CellName"_ustr; inline constexpr OUString UNO_NAME_PARA_USER_DEFINED_ATTRIBUTES = u"ParaUserDefinedAttributes"_ustr; inline constexpr OUString UNO_NAME_MERGE_LAST_PARA = u"MergeLastPara"_ustr; inline constexpr OUString UNO_NAME_WRITING_MODE = u"WritingMode"_ustr; +inline constexpr OUString UNO_NAME_PARA_WRITING_MODE_AUTOMATIC = u"WritingModeAutomatic"_ustr; inline constexpr OUString UNO_NAME_GRID_COLOR = u"GridColor"_ustr; inline constexpr OUString UNO_NAME_GRID_LINES = u"GridLines"_ustr; inline constexpr OUString UNO_NAME_GRID_BASE_HEIGHT = u"GridBaseHeight"_ustr; diff --git a/sw/qa/extras/odfexport/data/tdf162120-style-writing-mode-automatic.fodt b/sw/qa/extras/odfexport/data/tdf162120-style-writing-mode-automatic.fodt new file mode 100644 index 000000000000..bf3cc0283cf9 --- /dev/null +++ b/sw/qa/extras/odfexport/data/tdf162120-style-writing-mode-automatic.fodt @@ -0,0 +1,147 @@ + + + 2025-10-30T08:33:30.0034574232025-10-31T10:32:54.029847023PT2M18S2LibreOfficeDev/26.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/08b6c9f129d48adf1e4850742651c78f750af488 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This paragraph is normal + + This paragraph is auto + + This paragraph is also normal + + This paragraph is auto by a named style + + + + This paragraph is normal too + + But this one is auto + + And finally, this one is normal + + + + + \ No newline at end of file diff --git a/sw/qa/extras/odfexport/odfexport4.cxx b/sw/qa/extras/odfexport/odfexport4.cxx index 087e19731391..e7391f1ed17a 100644 --- a/sw/qa/extras/odfexport/odfexport4.cxx +++ b/sw/qa/extras/odfexport/odfexport4.cxx @@ -1696,6 +1696,19 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf118350StartEndPreserved) "text-align", u"end"); } +CPPUNIT_TEST_FIXTURE(Test, testTdf162120StyleWritingModeAutomaticSerialization) +{ + loadAndReload("tdf162120-style-writing-mode-automatic.fodt"); + + auto pStylesDoc = parseExport(u"styles.xml"_ustr); + assertXPath(pStylesDoc, "//style:paragraph-properties[@style:writing-mode-automatic]", 1); + assertXPath(pStylesDoc, "//style:style[@style:name='AutoStyle']/style:paragraph-properties", + "writing-mode-automatic", u"true"); + + auto pContentDoc = parseExport(u"content.xml"_ustr); + assertXPath(pContentDoc, "//style:paragraph-properties[@style:writing-mode-automatic]", 2); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/uiwriter/data/tdf162120-auto-rtl.fodt b/sw/qa/extras/uiwriter/data/tdf162120-auto-rtl.fodt new file mode 100644 index 000000000000..e5d61c5b3058 --- /dev/null +++ b/sw/qa/extras/uiwriter/data/tdf162120-auto-rtl.fodt @@ -0,0 +1,122 @@ + + + 2025-11-06T11:23:30.1872369692025-11-06T11:30:37.198630783PT4M9S2LibreOfficeDev/26.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/cb898851676f98ce58e79d4ffd1c0081b0003335 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + اa + + + \ No newline at end of file diff --git a/sw/qa/extras/uiwriter/uiwriter11.cxx b/sw/qa/extras/uiwriter/uiwriter11.cxx index 5e4d2b10e5b9..cc642e56d600 100644 --- a/sw/qa/extras/uiwriter/uiwriter11.cxx +++ b/sw/qa/extras/uiwriter/uiwriter11.cxx @@ -374,6 +374,29 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, testTdf108791) } } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, testTdf162120AutoRTL) +{ + createSwDoc("tdf162120-auto-rtl.fodt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // The initial direction should be RTL + CPPUNIT_ASSERT_EQUAL(short(1), + getProperty(getRun(getParagraph(1), 1), u"WritingMode"_ustr)); + + // Insert a strong LTR character at the start of the paragraph. + // The writing mode should automatically switch to LTR. + pWrtShell->Insert(u"a"_ustr); + CPPUNIT_ASSERT_EQUAL(short(0), + getProperty(getRun(getParagraph(1), 1), u"WritingMode"_ustr)); + + // Delete the leading LTR character. + // The writing mode should switch back to RTL. + pWrtShell->DelLeft(); + CPPUNIT_ASSERT_EQUAL(short(1), + getProperty(getRun(getParagraph(1), 1), u"WritingMode"_ustr)); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/uitest/styleInspector/styleInspector.py b/sw/qa/uitest/styleInspector/styleInspector.py index a9ab1fc940ee..1f044dfc4dec 100644 --- a/sw/qa/uitest/styleInspector/styleInspector.py +++ b/sw/qa/uitest/styleInspector/styleInspector.py @@ -26,7 +26,7 @@ class styleNavigator(UITestCase): # The cursor is on text without formatting and default style self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -36,7 +36,7 @@ class styleNavigator(UITestCase): # The cursor is on text with direct formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) @@ -54,7 +54,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph direct formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) xParDirFormatting = xListBox.getChild('1') self.assertEqual(7, len(xParDirFormatting.getChildren())) @@ -75,7 +75,7 @@ class styleNavigator(UITestCase): xParStyle = xListBox.getChild('0') self.assertEqual(3, len(xParStyle.getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xParStyle.getChild('0'))['Text']) - self.assertEqual(155, len(xParStyle.getChild('0').getChildren())) + self.assertEqual(156, len(xParStyle.getChild('0').getChildren())) self.assertEqual("Heading\t", get_state_as_dict(xParStyle.getChild('1'))['Text']) self.assertEqual(28, len(xParStyle.getChild('1').getChildren())) @@ -116,7 +116,7 @@ class styleNavigator(UITestCase): xParStyle = xListBox.getChild('0') self.assertEqual(3, len(xParStyle.getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xParStyle.getChild('0'))['Text']) - self.assertEqual(155, len(xParStyle.getChild('0').getChildren())) + self.assertEqual(156, len(xParStyle.getChild('0').getChildren())) self.assertEqual("Body Text\t", get_state_as_dict(xParStyle.getChild('1'))['Text']) self.assertEqual(6, len(xParStyle.getChild('1').getChildren())) @@ -151,7 +151,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -161,7 +161,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph metadata showed under direct paragraph formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) xParDirFormatting = xListBox.getChild('1') self.assertEqual(1, len(xParDirFormatting.getChildren())) @@ -214,7 +214,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -224,7 +224,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph metadata showed under direct paragraph formatting self.assertEqual(1, len(xListBox.getChild('1').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('1').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('1').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('1').getChild('0').getChildren())) # Outer bookmark xBookmarkFormatting = xListBox.getChild('0') @@ -271,7 +271,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) diff --git a/sw/qa/uitest/styleInspector/tdf137513.py b/sw/qa/uitest/styleInspector/tdf137513.py index b615f8af8d2b..b5271f487593 100644 --- a/sw/qa/uitest/styleInspector/tdf137513.py +++ b/sw/qa/uitest/styleInspector/tdf137513.py @@ -35,7 +35,7 @@ class tdf137513(UITestCase): self.assertEqual(2, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) self.assertEqual("Table Contents\t", get_state_as_dict(xListBox.getChild('0').getChild('1'))['Text']) - self.assertEqual(155, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(156, len(xListBox.getChild('0').getChild('0').getChildren())) xTableContent = xListBox.getChild('0').getChild('1') self.assertEqual(5, len(xTableContent.getChildren())) diff --git a/sw/source/core/bastyp/init.cxx b/sw/source/core/bastyp/init.cxx index 21a5cc10c32f..bfc29059a774 100644 --- a/sw/source/core/bastyp/init.cxx +++ b/sw/source/core/bastyp/init.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -376,6 +377,7 @@ std::unique_ptr createItemInfoPackageSwAttributes() { RES_PARATR_SNAPTOGRID, new SvxParaGridItem( true, RES_PARATR_SNAPTOGRID ), SID_ATTR_PARA_SNAPTOGRID, SFX_ITEMINFOFLAG_NONE }, { RES_PARATR_CONNECT_BORDER, new SwParaConnectBorderItem, SID_ATTR_BORDER_CONNECT, SFX_ITEMINFOFLAG_NONE }, { RES_PARATR_OUTLINELEVEL, new SfxUInt16Item( RES_PARATR_OUTLINELEVEL, 0 ), SID_ATTR_PARA_OUTLINE_LEVEL, SFX_ITEMINFOFLAG_NONE }, + { RES_PARATR_AUTOFRAMEDIR, new SvxAutoFrameDirectionItem( false, RES_PARATR_AUTOFRAMEDIR ), SID_ATTR_PARA_AUTOFRAMEDIRECTION, SFX_ITEMINFOFLAG_NONE }, { RES_PARATR_RSID, new SvxRsidItem( 0, RES_PARATR_RSID ), 0, SFX_ITEMINFOFLAG_NONE }, { RES_PARATR_GRABBAG, new SfxGrabBagItem( RES_PARATR_GRABBAG ), SID_ATTR_PARA_GRABBAG, SFX_ITEMINFOFLAG_NONE }, diff --git a/sw/source/core/doc/dbgoutsw.cxx b/sw/source/core/doc/dbgoutsw.cxx index cfb85bb2b291..3e54aa44a8f3 100644 --- a/sw/source/core/doc/dbgoutsw.cxx +++ b/sw/source/core/doc/dbgoutsw.cxx @@ -159,6 +159,7 @@ static std::map & GetItemWhichMap() { RES_PARATR_VERTALIGN , "PARATR_VERTALIGN" }, { RES_PARATR_SNAPTOGRID , "PARATR_SNAPTOGRID" }, { RES_PARATR_CONNECT_BORDER , "PARATR_CONNECT_BORDER" }, + { RES_PARATR_AUTOFRAMEDIR, "PARATR_AUTOFRAMEDIR" }, { RES_FILL_ORDER , "FILL_ORDER" }, { RES_FRM_SIZE , "FRM_SIZE" }, { RES_PAPER_BIN , "PAPER_BIN" }, diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx index 654b9b0fb547..bcf151df3b37 100644 --- a/sw/source/core/edit/eddel.cxx +++ b/sw/source/core/edit/eddel.cxx @@ -164,6 +164,9 @@ bool SwEditShell::Delete(bool const isArtificialSelection, bool goLeft) DeleteSel(rPaM, isArtificialSelection, goLeft, &bUndo); } + // tdf#162120: Automatically adjust paragraph directions after delete + UpdateSelectionAutoParaDirection(); + // If undo container then close here if( bUndo ) { diff --git a/sw/source/core/edit/editsh.cxx b/sw/source/core/edit/editsh.cxx index 9a69d60e1565..742f4c7e22cf 100644 --- a/sw/source/core/edit/editsh.cxx +++ b/sw/source/core/edit/editsh.cxx @@ -24,6 +24,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -59,6 +62,58 @@ using namespace com::sun::star; +void SwEditShell::UpdateSelectionAutoParaDirection() +{ + for (SwPaM& rPaM : getShellCursor(true)->GetRingContainer()) + { + auto* pNode = rPaM.GetPointNode().GetTextNode(); + if (!pNode) + { + continue; + } + + const SvxAutoFrameDirectionItem* pAutoItem + = pNode->GetSwAttrSet().GetItemIfSet(RES_PARATR_AUTOFRAMEDIR); + if (!pAutoItem || !pAutoItem->GetValue()) + { + continue; + } + + Point aPt; + std::pair const tmp(aPt, false); + const SwTextFrame* pFrame + = static_cast(pNode->getLayoutFrame(GetLayout(), rPaM.GetPoint(), &tmp)); + + bool bIsAlreadyRtl = pFrame->IsRightToLeft(); + + bool bShouldBeRtl = bIsAlreadyRtl; + switch (i18nutil::GuessParagraphDirection(pNode->GetText())) + { + case i18nutil::ParagraphDirection::Ambiguous: + bShouldBeRtl = bIsAlreadyRtl; + break; + + case i18nutil::ParagraphDirection::LeftToRight: + bShouldBeRtl = false; + break; + + case i18nutil::ParagraphDirection::RightToLeft: + bShouldBeRtl = true; + break; + } + + if (bShouldBeRtl == bIsAlreadyRtl) + { + continue; + } + + SvxFrameDirection eNeeded = bShouldBeRtl ? SvxFrameDirection::Horizontal_RL_TB + : SvxFrameDirection::Horizontal_LR_TB; + rPaM.GetDoc().getIDocumentContentOperations().InsertPoolItem( + rPaM, SvxFrameDirectionItem{ eNeeded, RES_FRAMEDIR }); + } +} + void SwEditShell::Insert( sal_Unicode c, bool bOnlyCurrCursor ) { StartAllAction(); @@ -73,6 +128,9 @@ void SwEditShell::Insert( sal_Unicode c, bool bOnlyCurrCursor ) } + // tdf#162120: Automatically adjust paragraph direction after insert + UpdateSelectionAutoParaDirection(); + EndAllAction(); } @@ -162,6 +220,9 @@ void SwEditShell::Insert2(const OUString &rStr, const bool bForceExpandHints ) SetInFrontOfLabel( false ); // #i27615# + // tdf#162120: Automatically adjust paragraph direction after insert + UpdateSelectionAutoParaDirection(); + EndAllAction(); } @@ -978,6 +1039,9 @@ OUString SwEditShell::DeleteExtTextInput( bool bInsText ) if ( ! bInsText && IsOverwriteCursor() ) *GetCursor()->GetPoint() = aPos; + // tdf#162120: Automatically adjust paragraph direction after insert + UpdateSelectionAutoParaDirection(); + EndAllAction(); } return sRet; diff --git a/sw/source/core/unocore/unomapproperties.hxx b/sw/source/core/unocore/unomapproperties.hxx index 0e6ee62e52af..e2dfba35bf15 100644 --- a/sw/source/core/unocore/unomapproperties.hxx +++ b/sw/source/core/unocore/unomapproperties.hxx @@ -255,6 +255,7 @@ { UNO_NAME_CHAR_SHADING_VALUE, RES_CHRATR_BACKGROUND, cppu::UnoType::get(), PROPERTY_NONE, MID_SHADING_VALUE }, \ { UNO_NAME_PARA_INTEROP_GRAB_BAG, RES_PARATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType >::get(), PROPERTY_NONE, 0 }, \ { UNO_NAME_CHAR_SCRIPT_HINT, RES_CHRATR_SCRIPT_HINT, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_SCRIPTHINT }, \ + { UNO_NAME_PARA_WRITING_MODE_AUTOMATIC, RES_PARATR_AUTOFRAMEDIR, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, 0 }, \ #define COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN \ COMMON_CRSR_PARA_PROPERTIES_WITHOUT_FN_01 \ @@ -547,7 +548,8 @@ { UNO_NAME_OUTLINE_LEVEL, RES_PARATR_OUTLINELEVEL,cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, 0}, \ { UNO_NAME_HIDDEN, FN_UNO_HIDDEN, cppu::UnoType::get(), PROPERTY_NONE, 0}, \ { UNO_NAME_STYLE_INTEROP_GRAB_BAG, FN_UNO_STYLE_INTEROP_GRAB_BAG, cppu::UnoType< cppu::UnoSequenceType >::get(), PROPERTY_NONE, 0}, \ - { UNO_NAME_PARA_INTEROP_GRAB_BAG, RES_PARATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType >::get(), PROPERTY_NONE, 0}, + { UNO_NAME_PARA_INTEROP_GRAB_BAG, RES_PARATR_GRABBAG, cppu::UnoType< cppu::UnoSequenceType >::get(), PROPERTY_NONE, 0}, \ + { UNO_NAME_PARA_WRITING_MODE_AUTOMATIC, RES_PARATR_AUTOFRAMEDIR, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, 0}, #define COMMON_ACCESSIBILITY_TEXT_ATTRIBUTE \ { UNO_NAME_CHAR_BACK_COLOR, RES_CHRATR_BACKGROUND, cppu::UnoType::get(), PROPERTY_NONE ,MID_BACK_COLOR }, \ diff --git a/sw/source/filter/html/css1atr.cxx b/sw/source/filter/html/css1atr.cxx index 85d5368a3dfb..fa4f4fec235e 100644 --- a/sw/source/filter/html/css1atr.cxx +++ b/sw/source/filter/html/css1atr.cxx @@ -3484,6 +3484,7 @@ SwAttrFnTab const aCSS1AttrFnTab = { /* RES_PARATR_SNAPTOGRID*/ nullptr, // new /* RES_PARATR_CONNECT_TO_BORDER */ nullptr, // new /* RES_PARATR_OUTLINELEVEL */ nullptr, // new since cws outlinelevel +/* RES_PARATR_AUTOFRAMEDIR */ nullptr, /* RES_PARATR_RSID */ nullptr, // new /* RES_PARATR_GRABBAG */ nullptr, diff --git a/sw/source/filter/html/htmlatr.cxx b/sw/source/filter/html/htmlatr.cxx index 026d6c1060f7..1a5b991efd12 100644 --- a/sw/source/filter/html/htmlatr.cxx +++ b/sw/source/filter/html/htmlatr.cxx @@ -3361,6 +3361,7 @@ const SwAttrFnTab aHTMLAttrFnTab = { /* RES_PARATR_SNAPTOGRID*/ nullptr, // new /* RES_PARATR_CONNECT_TO_BORDER */ nullptr, // new /* RES_PARATR_OUTLINELEVEL */ nullptr, +/* RES_PARATR_AUTOFRAMEDIR */ nullptr, /* RES_PARATR_RSID */ nullptr, /* RES_PARATR_GRABBAG */ nullptr, diff --git a/sw/source/uibase/shells/txtattr.cxx b/sw/source/uibase/shells/txtattr.cxx index eee4503fb501..8197b5682323 100644 --- a/sw/source/uibase/shells/txtattr.cxx +++ b/sw/source/uibase/shells/txtattr.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -327,6 +328,7 @@ void SwTextShell::ExecParaAttr(SfxRequest &rReq) // Get both attributes immediately isn't more expensive!! SfxItemSet aSet(SfxItemSet::makeFixedSfxItemSet(GetPool())); sal_uInt16 nSlot = rReq.GetSlot(); switch (nSlot) @@ -406,6 +408,10 @@ SET_LINESPACE: SvxFrameDirection::Horizontal_LR_TB : SvxFrameDirection::Horizontal_RL_TB; aSet.Put( SvxFrameDirectionItem( eFrameDirection, RES_FRAMEDIR ) ); + // tdf#162120: The paragraph direction has been manually set by the user. + // Don't automatically adjust the paragraph direction anymore. + aSet.Put(SvxAutoFrameDirectionItem(false, RES_PARATR_AUTOFRAMEDIR)); + if (bChgAdjust) { SvxAdjust eAdjust = (SID_ATTR_PARA_LEFT_TO_RIGHT == nSlot) ? diff --git a/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx b/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx index 33a27b191cac..e8eb46377abd 100644 --- a/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx +++ b/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx @@ -339,6 +339,7 @@ static OUString PropertyNametoRID(const OUString& rName) { "UnvisitedCharStyleName", RID_UNVISITED_CHAR_STYLE_NAME }, { "VisitedCharStyleName", RID_VISITED_CHAR_STYLE_NAME }, { "WritingMode", RID_WRITING_MODE }, + { "WritingModeAutomatic", RID_WRITING_MODE_AUTOMATIC }, { "BorderColor", RID_BORDER_COLOR }, { "BorderInnerLineWidth", RID_BORDER_INNER_LINE_WIDTH }, { "BorderLineDistance", RID_BORDER_LINE_DISTANCE }, diff --git a/xmloff/inc/xmlprop.hxx b/xmloff/inc/xmlprop.hxx index 657ddf1eeae8..689220a4819e 100644 --- a/xmloff/inc/xmlprop.hxx +++ b/xmloff/inc/xmlprop.hxx @@ -691,6 +691,7 @@ inline constexpr OUString PROP_WidthType = u"WidthType"_ustr; inline constexpr OUString PROP_WrapInfluenceOnPosition = u"WrapInfluenceOnPosition"_ustr; inline constexpr OUString PROP_WrapTextAtFlyStart = u"WrapTextAtFlyStart"_ustr; inline constexpr OUString PROP_WritingMode = u"WritingMode"_ustr; +inline constexpr OUString PROP_WritingModeAutomatic = u"WritingModeAutomatic"_ustr; inline constexpr OUString PROP_XName = u"XName"_ustr; inline constexpr OUString PROP_YName = u"YName"_ustr; diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index 4b8c63d5936c..f8cfdddf335a 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -2227,6 +2227,7 @@ namespace xmloff::token { TOKEN( "wrap-contour-mode", XML_WRAP_CONTOUR_MODE ), TOKEN( "wrap-option", XML_WRAP_OPTION ), TOKEN( "writing-mode", XML_WRITING_MODE ), + TOKEN( "writing-mode-automatic", XML_WRITING_MODE_AUTOMATIC ), TOKEN( "www", XML_WWW ), TOKEN( "x", XML_X ), TOKEN( "x1", XML_X1 ), diff --git a/xmloff/source/text/txtprmap.cxx b/xmloff/source/text/txtprmap.cxx index 593006cf0ec3..f239a75b32a1 100644 --- a/xmloff/source/text/txtprmap.cxx +++ b/xmloff/source/text/txtprmap.cxx @@ -465,6 +465,9 @@ XMLPropertyMapEntry constexpr aXMLParaPropMap[] = MP_ED( PROP_WritingMode, XML_NAMESPACE_STYLE, XML_WRITING_MODE, XML_TYPE_TEXT_WRITING_MODE_WITH_DEFAULT, CTF_TEXTWRITINGMODE ), + // RES_PARATR_AUTOFRAMEDIR + MP_E( PROP_WritingModeAutomatic, XML_NAMESPACE_STYLE, XML_WRITING_MODE_AUTOMATIC, XML_TYPE_BOOL, 0 ), + MP_E( PROP_ParaIsConnectBorder, XML_NAMESPACE_STYLE, XML_JOIN_BORDER, XML_TYPE_BOOL, 0 ), MP_E( PROP_DefaultOutlineLevel, XML_NAMESPACE_STYLE, XML_DEFAULT_OUTLINE_LEVEL, XML_TYPE_TEXT_NUMBER8_ONE_BASED|MID_FLAG_SPECIAL_ITEM_EXPORT|MID_FLAG_NO_PROPERTY_IMPORT, CTF_DEFAULT_OUTLINE_LEVEL ), diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index 9576187b7522..28317a175325 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -2127,6 +2127,7 @@ wrap-contour wrap-contour-mode wrap-option writing-mode +writing-mode-automatic www x x1