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
