sc/qa/uitest/calc_tests9/tdf144296_automatic_cell_direction.py |   33 ++
 sc/source/ui/app/inputhdl.cxx                                  |  110 ++++++-
 sc/source/ui/inc/viewdata.hxx                                  |    3 
 sc/source/ui/view/viewdata.cxx                                 |  156 
+++++++++-
 4 files changed, 285 insertions(+), 17 deletions(-)

New commits:
commit 057a333d62abd60006434d72c98bbc2cf704f4c4
Author:     Jonathan Clark <[email protected]>
AuthorDate: Fri Feb 27 07:03:15 2026 -0700
Commit:     Jonathan Clark <[email protected]>
CommitDate: Tue Mar 3 22:31:46 2026 +0100

    tdf#65563 sc: Use correct horizontal adjust for cells during editing
    
    This change implements a number of previously-missing special cases to
    Edit Engine construction for cell edit. In general, what Calc displays
    during editing should now be a much closer match to what Calc will
    display after editing is finished.
    
    As an implementation detail of this change, Calc will now automatically
    set new cells containing RTL text to RTL after input is finished.
    
    Change-Id: I9332b8ecb8afd7295301fbbe3785eb09808b94de
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200912
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <[email protected]>

diff --git a/sc/qa/uitest/calc_tests9/tdf144296_automatic_cell_direction.py 
b/sc/qa/uitest/calc_tests9/tdf144296_automatic_cell_direction.py
new file mode 100644
index 000000000000..2dcc50684430
--- /dev/null
+++ b/sc/qa/uitest/calc_tests9/tdf144296_automatic_cell_direction.py
@@ -0,0 +1,33 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/.
+#
+
+from uitest.framework import UITestCase
+from uitest.uihelper.common import get_url_for_data_file
+from libreoffice.calc.document import get_cell_by_position
+from libreoffice.uno.propertyvalue import mkPropertyValues
+
+class Tdf144296(UITestCase):
+    def test_tdf144296_automatic_cell_direction(self):
+        with self.ui_test.create_doc_in_start_center("calc") as document:
+            xCalcDoc = self.xUITest.getTopFocusWindow()
+            xGridWindow = xCalcDoc.getChild("grid_window")
+            xCell = get_cell_by_position(document, 0, 0, 0)
+
+            # Before input, the cell's direction should be inherited
+            self.assertEqual(4, xCell.getPropertyValue("WritingMode"))
+
+            # Type a single RTL character into the cell and dismiss
+            xGridWindow.executeAction("SELECT", mkPropertyValues({"CELL": 
"A1"}))
+            xGridWindow.executeAction("TYPE", mkPropertyValues({"TEXT": "א"}))
+            xGridWindow.executeAction("TYPE", mkPropertyValues({"KEYCODE": 
"RETURN"}))
+
+            # After typing, the cell should now be automatically set 
right-to-left
+            self.assertEqual(1, xCell.getPropertyValue("WritingMode"))
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sc/source/ui/app/inputhdl.cxx b/sc/source/ui/app/inputhdl.cxx
index 42fcfae9a7e9..409b468b7684 100644
--- a/sc/source/ui/app/inputhdl.cxx
+++ b/sc/source/ui/app/inputhdl.cxx
@@ -29,12 +29,15 @@
 #include <editeng/acorrcfg.hxx>
 #include <formula/errorcodes.hxx>
 #include <editeng/adjustitem.hxx>
+#include <editeng/autodiritem.hxx>
 #include <editeng/brushitem.hxx>
 #include <svtools/colorcfg.hxx>
 #include <editeng/colritem.hxx>
 #include <editeng/editobj.hxx>
 #include <editeng/editstat.hxx>
 #include <editeng/editview.hxx>
+#include <editeng/frmdir.hxx>
+#include <editeng/frmdiritem.hxx>
 #include <editeng/langitem.hxx>
 #include <editeng/svxacorr.hxx>
 #include <editeng/unolingu.hxx>
@@ -96,6 +99,8 @@
 #include <gridwin.hxx>
 #include <output.hxx>
 #include <fillinfo.hxx>
+#include <unicode/uchar.h>
+#include <unicode/ubidi.h>
 
 using namespace formula;
 
@@ -2420,23 +2425,93 @@ void ScInputHandler::ForgetLastPattern()
         NotifyChange( pLastState.get(), true );
 }
 
+namespace
+{
+bool CharIsRTL(sal_Unicode cTyped)
+{
+    switch (u_charDirection(cTyped))
+    {
+        case U_RIGHT_TO_LEFT:
+        case U_RIGHT_TO_LEFT_ARABIC:
+        case U_RIGHT_TO_LEFT_EMBEDDING:
+        case U_RIGHT_TO_LEFT_OVERRIDE:
+            return true;
+
+        default:
+            return false;
+    }
+}
+}
+
 void ScInputHandler::UpdateAdjust( sal_Unicode cTyped )
 {
     SvxAdjust eSvxAdjust;
+    bool bAllowAutoRtl = false;
+    bool bAdjustForNumber = false;
     switch (eAttrAdjust)
     {
         case SvxCellHorJustify::Standard:
+            if (cTyped)
             {
-                bool bNumber = false;
-                if (cTyped)                                     // Restarted
-                    bNumber = (cTyped>='0' && cTyped<='9');     // Only 
ciphers are numbers
-                else if ( pActiveViewSh )
+                // Restarted, and at least one character of input is known
+                if (cTyped >= '0' && cTyped <= '9')
                 {
-                    ScDocument& rDoc = 
pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
-                    bNumber = ( rDoc.GetCellType( aCursorPos ) == 
CELLTYPE_VALUE );
+                    // User seems to be typing a number cell
+                    eSvxAdjust = SvxAdjust::Right;
+                    bAdjustForNumber = true;
+                    break;
                 }
-                eSvxAdjust = bNumber ? SvxAdjust::Right : SvxAdjust::Left;
+
+                // User seems to be typing a new text cell. Base the initial 
alignment on
+                // the direction of the new char, per 
getAlignmentFromContext().
+                eSvxAdjust = SvxAdjust::Left;
+                if(CharIsRTL(cTyped))
+                {
+                    eSvxAdjust = SvxAdjust::ParaStart;
+                    bAllowAutoRtl = true;
+                }
+                break;
             }
+
+            if (pActiveViewSh)
+            {
+                // Base the initial alignment on the cell contents, if any
+                // Value-type cells should be right adjusted
+                ScDocument& rDoc = 
pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
+                if (rDoc.GetCellType(aCursorPos) == CELLTYPE_VALUE)
+                {
+                    eSvxAdjust = SvxAdjust::Right;
+                    bAdjustForNumber = true;
+                    break;
+                }
+
+                // Cells with an explit RTL writing direction are always right 
adjusted
+                if (pLastPattern)
+                {
+                    SvxFrameDirection eDir = 
pLastPattern->GetItem(ATTR_WRITINGDIR).GetValue();
+                    if (eDir == SvxFrameDirection::Horizontal_RL_TB)
+                    {
+                        eSvxAdjust = SvxAdjust::Right;
+                        break;
+                    }
+                }
+
+                // Cells that start with an RTL character should be right 
adjusted
+                OUString aStr = rDoc.GetString(aCursorPos);
+                if (aStr.getLength() > 0)
+                {
+                    sal_Int32 nIdx = 0;
+                    bool bFirstCharRTL = 
CharIsRTL(aStr.iterateCodePoints(&nIdx));
+                    eSvxAdjust = bFirstCharRTL ? SvxAdjust::Right : 
SvxAdjust::Left;
+                    break;
+                }
+            }
+
+            // tdf#65563: Special case, a new cell has been created by the 
user interacting
+            // with an IME, but none of the text is known yet. Let EditEngine 
guess the
+            // correct alignment dynamically with ParaStart and automatic RTL.
+            eSvxAdjust = SvxAdjust::ParaStart;
+            bAllowAutoRtl = true;
             break;
         case SvxCellHorJustify::Block:
             eSvxAdjust = SvxAdjust::Block;
@@ -2462,11 +2537,13 @@ void ScInputHandler::UpdateAdjust( sal_Unicode cTyped )
     }
 
     pEditDefaults->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );
+    pEditDefaults->Put(SvxAutoFrameDirectionItem{ bAllowAutoRtl, 
EE_PARA_AUTOWRITINGDIR });
     mpEditEngine->SetDefaults( *pEditDefaults );
 
     if ( pActiveViewSh )
     {
         pActiveViewSh->GetViewData().SetEditAdjust( eSvxAdjust );
+        
pActiveViewSh->GetViewData().SetEditAdjustIsForNumber(bAdjustForNumber);
     }
     mpEditEngine->SetVertical( bAsianVertical );
 }
@@ -3339,6 +3416,25 @@ void ScInputHandler::EnterHandler2(ScEnterMode 
nBlockMode, bool bForget, OUStrin
                 pCellAttrs = 
std::make_unique<ScPatternAttr>(rDoc.getCellAttributeHelper());
                 pCellAttrs->GetFromEditItemSet( &*pCommonAttrs );
             }
+
+            // tdf#144296: Cell direction may have changed automatically due 
to editing cell
+            // contents. Direction is semantically meaningful for RTL 
languages, so preserve
+            // this updated value after editing.
+            if (SfxItemState eState = 
aPara1Attribs.GetItemState(EE_PARA_WRITINGDIR, false, &pItem);
+                eState == SfxItemState::SET)
+            {
+                auto eDirection = static_cast<const 
SvxFrameDirectionItem*>(pItem)->GetValue();
+                if (eDirection != SvxFrameDirection::Environment)
+                {
+                    if (!pCellAttrs)
+                    {
+                        ScDocument& rDoc = 
pActiveViewSh->GetViewData().GetDocument();
+                        pCellAttrs = 
std::make_unique<ScPatternAttr>(rDoc.getCellAttributeHelper());
+                    }
+
+                    pCellAttrs->ItemSetPut(SvxFrameDirectionItem{ eDirection, 
ATTR_WRITINGDIR });
+                }
+            }
         }
 
         // Clear ParaAttribs (including adjustment)
diff --git a/sc/source/ui/inc/viewdata.hxx b/sc/source/ui/inc/viewdata.hxx
index d07e848b7d9d..e62c27f224c5 100644
--- a/sc/source/ui/inc/viewdata.hxx
+++ b/sc/source/ui/inc/viewdata.hxx
@@ -334,6 +334,7 @@ private:
     bool                bSelCtrlMouseClick:1;       // special selection 
handling for ctrl-mouse-click
     bool                bMoveArea:1;
     bool                bEditHighlight:1;
+    bool                bEditAdjustIsForNumber:1;
 
     bool                bGrowing;
     sal_Int16           nFormulaBarLines;           // Visible lines in the 
formula bar
@@ -555,6 +556,8 @@ public:
 
     SvxAdjust       GetEditAdjust() const {return eEditAdjust; }
     void            SetEditAdjust( SvxAdjust eNewEditAdjust ) { eEditAdjust = 
eNewEditAdjust; }
+    bool            GetEditAdjustIsForNumber() const { return 
bEditAdjustIsForNumber; }
+    void            SetEditAdjustIsForNumber(bool bNew) { 
bEditAdjustIsForNumber = bNew; }
 
                     // TRUE: Cell is merged
     bool            GetMergeSizePixel( SCCOL nX, SCROW nY, tools::Long& 
rSizeXPix, tools::Long& rSizeYPix ) const;
diff --git a/sc/source/ui/view/viewdata.cxx b/sc/source/ui/view/viewdata.cxx
index ba453d40cbaa..0b35ba3ec831 100644
--- a/sc/source/ui/view/viewdata.cxx
+++ b/sc/source/ui/view/viewdata.cxx
@@ -827,6 +827,7 @@ ScViewData::ScViewData(ScDocument* pDoc, ScDocShell* 
pDocSh, ScTabViewShell* pVi
         bSelCtrlMouseClick( false ),
         bMoveArea ( false ),
         bEditHighlight ( false ),
+        bEditAdjustIsForNumber ( false ),
         bGrowing (false),
         nFormulaBarLines(1),
         m_nLOKPageUpDownOffset( 0 )
@@ -1693,11 +1694,23 @@ void ScViewData::SetEditEngine( ScSplitPos eWhich,
 
         //  For growing use only the alignment value from the attribute, 
numbers
         //  (existing or started) with default alignment extend to the right.
-        bool bGrowCentered = ( eJust == SvxCellHorJustify::Center );
-        bool bGrowToLeft = ( eJust == SvxCellHorJustify::Right );      // 
visual left
+        // tdf#144296: Except RTL languages, which should extend to the left
+        // tdf#65563: ...and new cells with ambiguous adjust, which can do 
either
+        bool bGrowCentered = (eJust == SvxCellHorJustify::Center);
+        bool bGrowToLeft = (eJust == SvxCellHorJustify::Right); // visual left
+        if (eJust == SvxCellHorJustify::Standard && GetEditAdjust() == 
SvxAdjust::Right
+            && !GetEditAdjustIsForNumber())
+        {
+            // tdf#144296: Right-adjusted standard non-number cells contain 
RTL text.
+            // These should extend to the left, in proper reading order.
+            bGrowToLeft = true;
+        }
+
+        bool bGrowDynamic = (GetEditAdjust() == SvxAdjust::ParaStart);
+
         bool bLOKRTLInvert = (bLOKActive && bLayoutRTL);
-        if ( bAsianVertical )
-            bGrowCentered = bGrowToLeft = false;   // keep old behavior for 
asian mode
+        if (bAsianVertical)
+            bGrowCentered = bGrowToLeft = bGrowDynamic = false; // keep old 
behavior for asian mode
 
         tools::Long nSizeXPix, nSizeXPTwips = 0;
 
@@ -1739,6 +1752,12 @@ void ScViewData::SetEditEngine( ScSplitPos eWhich,
                     nSizeXPTwips = aPTwipsRect.GetWidth() + 2 * 
std::min(nLeftPTwips, nRightPTwips);
                 }
             }
+            else if (bGrowDynamic)
+            {
+                nSizeXPix = aPixRect.GetWidth();
+                if (bLOKPrintTwips)
+                    nSizeXPTwips = aPTwipsRect.GetWidth();
+            }
             else if ( (bGrowToLeft && !bLOKRTLInvert) || (!bGrowToLeft && 
bLOKRTLInvert) )
             {
                 nSizeXPix = aPixRect.Right();   // space that's available in 
the window when growing to the left
@@ -1950,14 +1969,106 @@ void ScViewData::EditGrowX()
         SAL_WARN("sc.viewdata", "No Pattern Found for: Col: " << nEditCol << 
", Row: " << nEditRow << ", Tab: " << nCurrentTab);
         pPattern = 
&rLocalDoc.getCellAttributeHelper().getDefaultCellAttribute();
     }
-    SvxCellHorJustify eJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
-    bool bGrowCentered = ( eJust == SvxCellHorJustify::Center );
-    bool bGrowToLeft = ( eJust == SvxCellHorJustify::Right );      // visual 
left
+    SvxCellHorJustify eJust = pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue();
+    bool bGrowCentered = (eJust == SvxCellHorJustify::Center);
+    bool bGrowToLeft = (eJust == SvxCellHorJustify::Right); // visual left
+    if (eJust == SvxCellHorJustify::Standard && GetEditAdjust() == 
SvxAdjust::Right
+        && !GetEditAdjustIsForNumber())
+    {
+        // tdf#144296: Right-adjusted standard non-number cells contain RTL 
text.
+        // These should extend to the left, in proper reading order.
+        bGrowToLeft = true;
+    }
+
+    bool bGrowDynamic = (GetEditAdjust() == SvxAdjust::ParaStart);
+
+    // tdf#65563: When typing with an IME, the initial adjust may be unknown. 
In this
+    // case, it will be logically set to ParaStart, and the physical adjust 
may change
+    // during input. Check the EE contents for the current para direction and 
set up
+    // grow/move as if it had the same logical adjust.
+    bool bGrowDynamicRTL = false;
+    if (bGrowDynamic)
+    {
+        bGrowDynamicRTL = pEngine->IsRightToLeft(0);
+
+        // Set this to intentionally reuse the left/right growth logic.
+        // Note that the move logic for ParaStart is intentionally different.
+        bGrowToLeft = bGrowDynamicRTL;
+    }
+
     bool bGrowBackwards = bGrowToLeft;                          // logical left
     if ( bLayoutRTL )
         bGrowBackwards = !bGrowBackwards;                       // invert on 
RTL sheet
-    if ( bAsianVertical )
-        bGrowCentered = bGrowToLeft = bGrowBackwards = false;   // keep old 
behavior for asian mode
+    if (bAsianVertical)
+        bGrowCentered = bGrowToLeft = bGrowBackwards = bGrowDynamic
+            = false; // keep old behavior for asian mode
+
+    if (bGrowDynamic)
+    {
+        bChanged = true;
+
+        // The entire edit area and position may change after every keystroke.
+        // Reset everything and re-grow from the origin cell.
+        const ScMergeAttr* pMergeAttr = &pPattern->GetItem(ATTR_MERGE);
+        nEditEndCol = nEditCol;
+        if (pMergeAttr->GetColMerge() > 1)
+            nEditEndCol += pMergeAttr->GetColMerge() - 1;
+        nEditStartCol = nEditCol;
+
+        auto aTempArea = ScEditUtil(mrDoc, nEditCol, nEditRow, 
CurrentTabForData(),
+                           GetScrPos(nEditCol, nEditRow, eWhich), 
pWin->GetOutDev(), nPPTX, nPPTY,
+                           GetZoomX(), GetZoomY())
+                    .GetEditArea(pPattern, true);
+        aTempArea = pWin->PixelToLogic(aTempArea, GetLogicMode());
+        aArea.SetLeft(aTempArea.Left());
+        aArea.SetRight(aTempArea.Right());
+
+        if (bGrowToLeft)
+        {
+            Size aPaperSize = pEngine->GetPaperSize();
+            aPaperSize.setWidth(aArea.Right());
+            pEngine->SetPaperSize(aPaperSize);
+        }
+        else
+        {
+            tools::Long nGridWidthPx = pView->GetGridWidth(eHWhich);
+            Size aGridSize{ nGridWidthPx, 1 };
+            aGridSize = pWin->PixelToLogic(aGridSize, GetLogicMode());
+
+            Size aPaperSize = pEngine->GetPaperSize();
+            aPaperSize.setWidth(aGridSize.Width() - aArea.Left());
+            pEngine->SetPaperSize(aPaperSize);
+        }
+
+        if (bLOKPrintTwips)
+        {
+            auto aTempAreaPTwips = ScEditUtil(mrDoc, nEditCol, nEditRow, 
CurrentTabForData(),
+                                     GetPrintTwipsPos(nEditCol, nEditRow), 
pWin->GetOutDev(), nPPTX,
+                                     nPPTY, GetZoomX(), GetZoomY(), true /* 
bInPrintTwips */)
+                              .GetEditArea(pPattern, true);
+            aAreaPTwips.SetLeft(aTempAreaPTwips.Left());
+            aAreaPTwips.SetRight(aTempAreaPTwips.Right());
+
+            if (bGrowToLeft)
+            {
+                Size aPaperSize = pEngine->GetLOKSpecialPaperSize();
+                aPaperSize.setWidth(aAreaPTwips.Right());
+                pEngine->SetLOKSpecialPaperSize(aPaperSize);
+            }
+            else
+            {
+                tools::Long nGridWidthPx = pView->GetGridWidth(eHWhich);
+                Size aGridSize{ nGridWidthPx, 1 };
+                aGridSize
+                    = OutputDevice::LogicToLogic(pWin->PixelToLogic(aGridSize, 
GetLogicMode()),
+                                                 GetLogicMode(), MapMode{ 
MapUnit::MapTwip });
+
+                Size aPaperSize = pEngine->GetLOKSpecialPaperSize();
+                aPaperSize.setWidth(aGridSize.Width() - aAreaPTwips.Left());
+                pEngine->SetLOKSpecialPaperSize(aPaperSize);
+            }
+        }
+    }
 
     bool bUnevenGrow = false;
     if ( bGrowCentered )
@@ -2099,7 +2210,7 @@ void ScViewData::EditGrowX()
     if (!bChanged)
         return;
 
-    if ( bMoveArea || bGrowCentered || bGrowBackwards || bLayoutRTL )
+    if (bMoveArea || bGrowCentered || bGrowDynamic || bGrowBackwards || 
bLayoutRTL)
     {
         tools::Rectangle aVis = pCurView->GetVisArea();
         tools::Rectangle aVisPTwips;
@@ -2125,6 +2236,31 @@ void ScViewData::EditGrowX()
                 aVisPTwips.SetRight( aVisPTwips.Left() + nVisSizePTwips - 1 );
             }
         }
+        else if (bGrowDynamic)
+        {
+            if (bGrowDynamicRTL)
+            {
+                aVis.SetRight(aSize.Width() - 1);
+                aVis.SetLeft(aSize.Width() - aArea.GetWidth());
+
+                if (bLOKPrintTwips)
+                {
+                    aVisPTwips.SetRight(aSizePTwips.Width() - 1);
+                    aVisPTwips.SetLeft(aSizePTwips.Width() - 
aAreaPTwips.GetWidth());
+                }
+            }
+            else
+            {
+                aVis.SetLeft(0);
+                aVis.SetRight(aArea.GetWidth());
+
+                if (bLOKPrintTwips)
+                {
+                    aVisPTwips.SetLeft(0);
+                    aVisPTwips.SetRight(aArea.GetWidth());
+                }
+            }
+        }
         else if ( bGrowToLeft )
         {
             //  switch to right-aligned (undo?) and reset VisArea to the right

Reply via email to