diff options
-rw-r--r-- | sc/qa/unit/tiledrendering/tiledrendering.cxx | 14 | ||||
-rw-r--r-- | sc/source/ui/app/inputhdl.cxx | 101 | ||||
-rw-r--r-- | sc/source/ui/inc/inputhdl.hxx | 6 | ||||
-rw-r--r-- | sc/source/ui/view/tabvwsh4.cxx | 5 |
4 files changed, 101 insertions, 25 deletions
diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx b/sc/qa/unit/tiledrendering/tiledrendering.cxx index 3cfde4952b16..ac0ca40d241d 100644 --- a/sc/qa/unit/tiledrendering/tiledrendering.cxx +++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx @@ -2712,12 +2712,12 @@ void ScTiledRenderingTest::testAutoInputExactMatch() ScAddress aA8(0, 7, 0); lcl_typeCharsInCell("S", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "S" in A8 - // Should not autocomplete as there are multiple matches starting with "S". - CPPUNIT_ASSERT_EQUAL_MESSAGE("1: A8 should have just S (should not autocomplete)", OUString("S"), pDoc->GetString(aA8)); + // Should show the partial completion "i". + CPPUNIT_ASSERT_EQUAL_MESSAGE("1: A8 should have partial completion Si", OUString("Si"), pDoc->GetString(aA8)); lcl_typeCharsInCell("Si", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Si" in A8 - // Should not autocomplete as there are multiple matches starting with "Si". - CPPUNIT_ASSERT_EQUAL_MESSAGE("2: A8 should not autocomplete", OUString("Si"), pDoc->GetString(aA8)); + // Should not show any suggestions. + CPPUNIT_ASSERT_EQUAL_MESSAGE("2: A8 should not show suggestions", OUString("Si"), pDoc->GetString(aA8)); lcl_typeCharsInCell("Sim", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Sim" in A8 // Should autocomplete to "Simple" which is the only match. @@ -2727,9 +2727,9 @@ void ScTiledRenderingTest::testAutoInputExactMatch() // Should autocomplete to "Sing" which is the only match. CPPUNIT_ASSERT_EQUAL_MESSAGE("4: A8 should autocomplete", OUString("Sing"), pDoc->GetString(aA8)); - lcl_typeCharsInCell("Cas", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Cas" in A8 - // Should not autocomplete as there are multiple matches starting with "Cas". - CPPUNIT_ASSERT_EQUAL_MESSAGE("5: A8 should not autocomplete", OUString("Cas"), pDoc->GetString(aA8)); + lcl_typeCharsInCell("C", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "C" in A8 + // Should show the partial completion "as". + CPPUNIT_ASSERT_EQUAL_MESSAGE("5: A8 should have partial completion Cas", OUString("Cas"), pDoc->GetString(aA8)); lcl_typeCharsInCell("Cast", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Cast" in A8 // Should autocomplete to "Castle" which is the only match. diff --git a/sc/source/ui/app/inputhdl.cxx b/sc/source/ui/app/inputhdl.cxx index 9c553cec8614..97c86b34b00d 100644 --- a/sc/source/ui/app/inputhdl.cxx +++ b/sc/source/ui/app/inputhdl.cxx @@ -162,15 +162,42 @@ OUString getExactMatch(const ScTypedCaseStrSet& rDataSet, const OUString& rStrin return rString; } +// This assumes that rResults is a sorted ring w.r.t ScTypedStrData::LessCaseInsensitive() or +// in the reverse direction, whose origin is specified by nRingOrigin. +sal_Int32 getLongestCommonPrefixLength(const std::vector<OUString>& rResults, const OUString& rUserEntry, sal_Int32 nRingOrigin) +{ + sal_Int32 nResults = rResults.size(); + if (!nResults) + return 0; + + if (nResults == 1) + return rResults[0].getLength(); + + sal_Int32 nMinLen = rUserEntry.getLength(); + sal_Int32 nLastIdx = nRingOrigin ? nRingOrigin - 1 : nResults - 1; + const OUString& rFirst = rResults[nRingOrigin]; + const OUString& rLast = rResults[nLastIdx]; + const sal_Int32 nMaxLen = std::min(rFirst.getLength(), rLast.getLength()); + + for (sal_Int32 nLen = nMaxLen; nLen > nMinLen; --nLen) + { + if (ScGlobal::GetTransliteration().isMatch(rFirst.copy(0, nLen), rLast)) + return nLen; + } + + return nMinLen; +} + ScTypedCaseStrSet::const_iterator findTextAll( const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const & itPos, - const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack, size_t nMax = 0) + const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack, sal_Int32* pLongestPrefixLen = nullptr) { rResultVec.clear(); // clear contents if (!rDataSet.size()) return rDataSet.end(); + sal_Int32 nRingOrigin = 0; size_t nCount = 0; ScTypedCaseStrSet::const_iterator retit; if ( bBack ) // Backwards @@ -198,7 +225,10 @@ ScTypedCaseStrSet::const_iterator findTextAll( { ++it; if ( it == rDataSet.rend() ) // go to the first if reach the end + { it = rDataSet.rbegin(); + nRingOrigin = nCount; + } if ( bFirstTime ) bFirstTime = false; @@ -221,8 +251,6 @@ ScTypedCaseStrSet::const_iterator findTextAll( std::advance(retit, nPos); } ++nCount; - if (nMax > 0 && nCount >= nMax) - break; } } else // Forwards @@ -238,7 +266,10 @@ ScTypedCaseStrSet::const_iterator findTextAll( { ++it; if ( it == rDataSet.end() ) // go to the first if reach the end + { it = rDataSet.begin(); + nRingOrigin = nCount; + } if ( bFirstTime ) bFirstTime = false; @@ -255,11 +286,21 @@ ScTypedCaseStrSet::const_iterator findTextAll( if ( nCount == 0 ) retit = it; // remember first match iterator ++nCount; - if (nMax > 0 && nCount >= nMax) - break; } } + if (pLongestPrefixLen) + { + if (nRingOrigin >= static_cast<sal_Int32>(nCount)) + { + // All matches were picked when rDataSet was read in one direction. + nRingOrigin = 0; + } + // rResultsVec is a sorted ring with nRingOrigin "origin". + // The direction of sorting is not important for getLongestCommonPrefixLength. + *pLongestPrefixLen = getLongestCommonPrefixLength(rResultVec, rStart, nRingOrigin); + } + if ( nCount > 0 ) // at least one function has matched return retit; return rDataSet.end(); // no matching text found @@ -788,6 +829,7 @@ ScInputHandler::ScInputHandler() bProtected( false ), bLastIsSymbol( false ), mbDocumentDisposing(false), + mbPartialPrefix(false), nValidation( 0 ), eAttrAdjust( SvxCellHorJustify::Standard ), aScaleX( 1,1 ), @@ -1971,24 +2013,28 @@ void ScInputHandler::UseColData() // When typing std::vector< OUString > aResultVec; OUString aNew; + sal_Int32 nLongestPrefixLen = 0; miAutoPosColumn = pColumnData->end(); - miAutoPosColumn = findTextAll(*pColumnData, miAutoPosColumn, aText, aResultVec, false, 2); - bool bShowCompletion = (aResultVec.size() == 1); - bUseTab = (aResultVec.size() == 2); - if (bUseTab) + mbPartialPrefix = false; + miAutoPosColumn = findTextAll(*pColumnData, miAutoPosColumn, aText, aResultVec, false, &nLongestPrefixLen); + + if (nLongestPrefixLen <= 0 || aResultVec.empty()) + return; + + if (aResultVec.size() > 1) { - // Allow cycling through possible matches using shortcut. - // Make miAutoPosColumn invalid so that Ctrl+TAB provides the first matching one. + mbPartialPrefix = true; + bUseTab = true; // Allow Ctrl (+ Shift + ) + TAB cycling. miAutoPosColumn = pColumnData->end(); - aAutoSearch = aText; - return; - } - if (!bShowCompletion) - return; + // Display the rest of longest common prefix as suggestion. + aNew = aResultVec[0].copy(0, nLongestPrefixLen); + } + else + { + aNew = aResultVec[0]; + } - assert(miAutoPosColumn != pColumnData->end()); - aNew = aResultVec[0]; // Strings can contain line endings (e.g. due to dBase import), // which would result in multiple paragraphs here, which is not desirable. //! Then GetExactMatch doesn't work either @@ -2047,6 +2093,7 @@ void ScInputHandler::NextAutoEntry( bool bBack ) // match found! miAutoPosColumn = itNew; bInOwnChange = true; // disable ModifyHdl (reset below) + mbPartialPrefix = false; lcl_RemoveLineEnd( aNew ); OUString aIns = aNew.copy(aAutoSearch.getLength()); @@ -2942,6 +2989,7 @@ void ScInputHandler::EnterHandler( ScEnterMode nBlockMode ) if (bInEnterHandler) return; bInEnterHandler = true; bInOwnChange = true; // disable ModifyHdl (reset below) + mbPartialPrefix = false; ImplCreateEditEngine(); @@ -3306,6 +3354,7 @@ void ScInputHandler::CancelHandler() ImplCreateEditEngine(); bModified = false; + mbPartialPrefix = false; // Don't rely on ShowRefFrame switching the active view synchronously // execute the function directly on the correct view's bindings instead @@ -3598,6 +3647,22 @@ bool ScInputHandler::KeyInput( const KeyEvent& rKEvt, bool bStartEdit /* = false // Alt-Return and Alt-Ctrl-* are accepted. Everything else with ALT are not. return false; + // There is a partial autocomplete suggestion. + // Allow its completion with right arrow key (without modifiers). + if (mbPartialPrefix && nCode == KEY_RIGHT && !bControl && !bShift && !bAlt && + (pTopView || pTableView)) + { + if (pTopView) + pTopView->PostKeyEvent(KeyEvent(0, css::awt::Key::MOVE_TO_END_OF_PARAGRAPH)); + if (pTableView) + pTableView->PostKeyEvent(KeyEvent(0, css::awt::Key::MOVE_TO_END_OF_PARAGRAPH)); + + mbPartialPrefix = false; + + // Indicate that this event has been consumed and ScTabViewShell should not act on this. + return true; + } + if (!bControl && nCode == KEY_TAB) { // Normal TAB moves the cursor right. diff --git a/sc/source/ui/inc/inputhdl.hxx b/sc/source/ui/inc/inputhdl.hxx index aabac21240a0..fb3880e97a69 100644 --- a/sc/source/ui/inc/inputhdl.hxx +++ b/sc/source/ui/inc/inputhdl.hxx @@ -103,6 +103,9 @@ private: bool bProtected:1; bool bLastIsSymbol:1; bool mbDocumentDisposing:1; + /// To indicate if there is a partial prefix completion. + bool mbPartialPrefix:1; + sal_uLong nValidation; SvxCellHorJustify eAttrAdjust; @@ -266,6 +269,9 @@ public: bool IsInEnterHandler() const { return bInEnterHandler; } bool IsInOwnChange() const { return bInOwnChange; } + /// Returns true if there is a partial autocomplete suggestion. + bool HasPartialComplete() const { return mbPartialPrefix; }; + bool IsModalMode( const SfxObjectShell* pDocSh ); void ForgetLastPattern(); diff --git a/sc/source/ui/view/tabvwsh4.cxx b/sc/source/ui/view/tabvwsh4.cxx index e32bc217b090..8130ac4c0e12 100644 --- a/sc/source/ui/view/tabvwsh4.cxx +++ b/sc/source/ui/view/tabvwsh4.cxx @@ -1240,6 +1240,11 @@ bool ScTabViewShell::TabKeyInput(const KeyEvent& rKEvt) default: bIsType = true; } + else if (nCode == KEY_RIGHT && !bControl && !bShift && !bAlt) + { + ScInputHandler* pHdl = pScMod->GetInputHdl(this); + bIsType = pHdl && pHdl->HasPartialComplete(); + } if( bIsType ) bUsed = pScMod->InputKeyEvent( rKEvt ); // input |