summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2019-10-07 17:46:36 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2019-10-08 14:30:55 +0200
commitef2ec07b4113fdadf863352c832af657b5ae205c (patch)
tree8cf3b8cd14fb3aca94c8c8838223553044cc43e1
parentcf1b5ae4c5f7c9111a745199ac993742f9007263 (diff)
tdf#128009: Allow spaces in AutoText suggestions
Currently autotext entries with long names starting with spaces, or containing spaces after first or second character, would never be suggested when SvxAutoCorrCfg::IsAutoTextTip() gives true (set in Tools -> AutoCorrect -> [x] Display remainder of name as suggestion while typing), because only a single word no less than 3 chars long left to cursor is considered a candidate for the name matching. This change allows to consider multiple chunks of text left to the cursor as the candidates for name matching. The chunks are 3-9 characters long, may start only between words, and have spaces, including leading. Thus, AutoText entries with long names like " Dr Foo" will now be suggested for an entry like "lorem dr f". Change-Id: If91c957341a4f4b281acb0e4ada558706ea2f8c1 Reviewed-on: https://gerrit.libreoffice.org/80392 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
-rw-r--r--editeng/source/misc/svxacorr.cxx54
-rw-r--r--include/editeng/svxacorr.hxx10
-rw-r--r--sw/inc/editsh.hxx6
-rw-r--r--sw/source/core/edit/edws.cxx27
-rw-r--r--sw/source/uibase/docvw/edtwin.cxx196
-rw-r--r--sw/source/uibase/inc/edtwin.hxx5
-rw-r--r--sw/source/uibase/inc/gloslst.hxx3
-rw-r--r--sw/source/uibase/utlui/gloslst.cxx61
8 files changed, 249 insertions, 113 deletions
diff --git a/editeng/source/misc/svxacorr.cxx b/editeng/source/misc/svxacorr.cxx
index 1e3f8f680194..8b46f0f8e043 100644
--- a/editeng/source/misc/svxacorr.cxx
+++ b/editeng/source/misc/svxacorr.cxx
@@ -1548,12 +1548,12 @@ bool SvxAutoCorrect::AddWrtSttException( const OUString& rNew,
return pLists && pLists->AddToWrdSttExceptList(rNew);
}
-bool SvxAutoCorrect::GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc,
- const OUString& rTxt, sal_Int32 nPos,
- OUString& rWord )
+OUString SvxAutoCorrect::GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt,
+ sal_Int32 nPos)
{
+ OUString sRet;
if( !nPos )
- return false;
+ return sRet;
sal_Int32 nEnde = nPos;
@@ -1561,7 +1561,7 @@ bool SvxAutoCorrect::GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc,
if( ( nPos < rTxt.getLength() &&
!IsWordDelim( rTxt[ nPos ])) ||
IsWordDelim( rTxt[ --nPos ]))
- return false;
+ return sRet;
while( nPos && !IsWordDelim( rTxt[ --nPos ]))
;
@@ -1574,20 +1574,54 @@ bool SvxAutoCorrect::GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc,
while( lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos ]) )
if( ++nCapLttrPos >= nEnde )
- return false;
+ return sRet;
if( 3 > nEnde - nCapLttrPos )
- return false;
+ return sRet;
const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos );
CharClass& rCC = GetCharClass(eLang);
if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nEnde ))
- return false;
+ return sRet;
- rWord = rTxt.copy( nCapLttrPos, nEnde - nCapLttrPos );
- return true;
+ sRet = rTxt.copy( nCapLttrPos, nEnde - nCapLttrPos );
+ return sRet;
+}
+
+// static
+std::vector<OUString> SvxAutoCorrect::GetChunkForAutoText(const OUString& rTxt,
+ const sal_Int32 nPos)
+{
+ constexpr sal_Int32 nMinLen = 3;
+ constexpr sal_Int32 nMaxLen = 9;
+ std::vector<OUString> aRes;
+ if (nPos >= nMinLen)
+ {
+ sal_Int32 nBegin = std::max<sal_Int32>(nPos - nMaxLen, 0);
+ // TODO: better detect word boundaries (not only whitespaces, but also e.g. punctuation)
+ if (nBegin > 0 && !IsWordDelim(rTxt[nBegin-1]))
+ {
+ while (nBegin + nMinLen <= nPos && !IsWordDelim(rTxt[nBegin]))
+ ++nBegin;
+ }
+ if (nBegin + nMinLen <= nPos)
+ {
+ OUString sRes = rTxt.copy(nBegin, nPos - nBegin);
+ aRes.push_back(sRes);
+ bool bLastStartedWithDelim = IsWordDelim(sRes[0]);
+ for (sal_Int32 i = 1; i <= sRes.getLength() - nMinLen; ++i)
+ {
+ bool bAdd = bLastStartedWithDelim;
+ bLastStartedWithDelim = IsWordDelim(sRes[i]);
+ bAdd = bAdd || bLastStartedWithDelim;
+ if (bAdd)
+ aRes.push_back(sRes.copy(i));
+ }
+ }
+ }
+ return aRes;
}
bool SvxAutoCorrect::CreateLanguageFile( const LanguageTag& rLanguageTag, bool bNewFile )
diff --git a/include/editeng/svxacorr.hxx b/include/editeng/svxacorr.hxx
index 5803938ce966..46c3df27f18f 100644
--- a/include/editeng/svxacorr.hxx
+++ b/include/editeng/svxacorr.hxx
@@ -291,8 +291,13 @@ public:
// Return for the autotext expansion the previous word,
// AutoCorrect - corresponding algorithm
- bool GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc, const OUString& rTxt,
- sal_Int32 nPos, OUString& rWord );
+ OUString GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt, sal_Int32 nPos);
+
+ // Returns vector candidates for AutoText name match, starting with the longest string between
+ // 3 and 9 characters long, that is a chunk of text starting with a whitespace or with a word's
+ // first character, and ending at the current cursor position or empty string if no such string
+ // exists
+ static std::vector<OUString> GetChunkForAutoText(const OUString& rTxt, sal_Int32 nPos);
// Search for the words in the replacement table.
// rText - check in this text the words of the list
@@ -331,6 +336,7 @@ public:
// Query/Set the current settings of AutoCorrect
ACFlags GetFlags() const { return nFlags; }
SvxSwAutoFormatFlags& GetSwFlags() { return aSwFlags;}
+ const SvxSwAutoFormatFlags& GetSwFlags() const { return aSwFlags; }
bool IsAutoCorrFlag( ACFlags nFlag ) const
{ return bool(nFlags & nFlag); }
void SetAutoCorrFlag( ACFlags nFlag, bool bOn = true );
diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx
index 29271604343d..53b91f44db98 100644
--- a/sw/inc/editsh.hxx
+++ b/sw/inc/editsh.hxx
@@ -816,7 +816,11 @@ public:
/// Call AutoCorrect
void AutoCorrect( SvxAutoCorrect& rACorr, bool bInsertMode,
sal_Unicode cChar );
- bool GetPrevAutoCorrWord(SvxAutoCorrect& rACorr, OUString& rWord);
+ OUString GetPrevAutoCorrWord(SvxAutoCorrect& rACorr);
+
+ // We consider no more than 9 characters before the cursor, and they must not start in the
+ // middle of a word (leading spaces are OK)
+ std::vector<OUString> GetChunkForAutoText();
/// Set our styles according to the respective rules.
void AutoFormat( const SvxSwAutoFormatFlags* pAFlags );
diff --git a/sw/source/core/edit/edws.cxx b/sw/source/core/edit/edws.cxx
index 1abceee1b75d..dd5381cbb9eb 100644
--- a/sw/source/core/edit/edws.cxx
+++ b/sw/source/core/edit/edws.cxx
@@ -279,11 +279,11 @@ void SwEditShell::SetNewDoc()
GetDoc()->getIDocumentState().SetNewDoc(true);
}
-bool SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr, OUString& rWord)
+OUString SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr)
{
SET_CURR_SHELL( this );
- bool bRet;
+ OUString sRet;
SwPaM* pCursor = getShellCursor( true );
SwTextNode* pTNd = pCursor->GetNode().GetTextNode();
if (pTNd)
@@ -291,12 +291,25 @@ bool SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr, OUString& rWord)
SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, 0 );
SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout())));
TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint()));
- bRet = rACorr.GetPrevAutoCorrWord( aSwAutoCorrDoc,
- pFrame->GetText(), sal_Int32(nPos), rWord );
+ sRet = rACorr.GetPrevAutoCorrWord(aSwAutoCorrDoc, pFrame->GetText(), sal_Int32(nPos));
}
- else
- bRet = false;
- return bRet;
+ return sRet;
+}
+
+std::vector<OUString> SwEditShell::GetChunkForAutoText()
+{
+ SET_CURR_SHELL(this);
+
+ std::vector<OUString> aRet;
+ SwPaM* pCursor = getShellCursor(true);
+ SwTextNode* pTNd = pCursor->GetNode().GetTextNode();
+ if (pTNd)
+ {
+ const auto pFrame = static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout()));
+ TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint()));
+ aRet = SvxAutoCorrect::GetChunkForAutoText(pFrame->GetText(), sal_Int32(nPos));
+ }
+ return aRet;
}
SwAutoCompleteWord& SwEditShell::GetAutoCompleteWords()
diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx
index 494983820978..9271e4ec3aa8 100644
--- a/sw/source/uibase/docvw/edtwin.cxx
+++ b/sw/source/uibase/docvw/edtwin.cxx
@@ -264,12 +264,11 @@ public:
/// Assists with auto-completion of AutoComplete words and AutoText names.
struct QuickHelpData
{
- /// Strings that at least partially match an input word.
- std::vector<OUString> m_aHelpStrings;
+ /// Strings that at least partially match an input word, and match length.
+ std::vector<std::pair<OUString, sal_uInt16>> m_aHelpStrings;
/// Index of the current help string.
sal_uInt16 nCurArrPos;
- /// Length of the input word associated with the help data.
- sal_uInt16 nLen;
+ static constexpr sal_uInt16 nNoPos = std::numeric_limits<sal_uInt16>::max();
/// Help data stores AutoText names rather than AutoComplete words.
bool m_bIsAutoText;
@@ -287,10 +286,12 @@ struct QuickHelpData
void Move( QuickHelpData& rCpy );
void ClearContent();
- void Start( SwWrtShell& rSh, sal_uInt16 nWrdLen );
+ void Start(SwWrtShell& rSh, bool bRestart);
void Stop( SwWrtShell& rSh );
- bool HasContent() const { return !m_aHelpStrings.empty() && 0 != nLen; }
+ bool HasContent() const { return !m_aHelpStrings.empty() && nCurArrPos != nNoPos; }
+ const OUString& CurStr() const { return m_aHelpStrings[nCurArrPos].first; }
+ sal_uInt16 CurLen() const { return m_aHelpStrings[nCurArrPos].second; }
/// Next help string.
void Next( bool bEndLess )
@@ -2563,7 +2564,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
// replace the word or abbreviation with the auto text
rSh.StartUndo( SwUndoId::START );
- OUString sFnd( aTmpQHD.m_aHelpStrings[ aTmpQHD.nCurArrPos ] );
+ OUString sFnd(aTmpQHD.CurStr());
if( aTmpQHD.m_bIsAutoText )
{
SwGlossaryList* pList = ::GetGlossaryList();
@@ -2572,7 +2573,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
if(pList->GetShortName( sFnd, sShrtNm, sGroup))
{
rSh.SttSelect();
- rSh.ExtendSelection( false, aTmpQHD.nLen );
+ rSh.ExtendSelection(false, aTmpQHD.CurLen());
SwGlossaryHdl* pGlosHdl = GetView().GetGlosHdl();
pGlosHdl->SetCurGroup(sGroup, true);
pGlosHdl->InsertGlossary( sShrtNm);
@@ -2581,7 +2582,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
}
else
{
- sFnd = sFnd.copy( aTmpQHD.nLen );
+ sFnd = sFnd.copy(aTmpQHD.CurLen());
rSh.Insert( sFnd );
m_pQuickHlpData->m_bAppendSpace = !pACorr ||
pACorr->GetSwFlags().bAutoCmpltAppendBlanc;
@@ -2592,7 +2593,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
case SwKeyState::NextPrevGlossary:
m_pQuickHlpData->Move( aTmpQHD );
- m_pQuickHlpData->Start( rSh, USHRT_MAX );
+ m_pQuickHlpData->Start(rSh, false);
break;
case SwKeyState::EditFormula:
@@ -2664,13 +2665,12 @@ KEYINPUT_CHECKTABLE_INSDEL:
g_bFlushCharBuffer = bSave;
// maybe show Tip-Help
- OUString sWord;
- if( bNormalChar && pACfg && pACorr &&
- ( pACfg->IsAutoTextTip() ||
- pACorr->GetSwFlags().bAutoCompleteWords ) &&
- rSh.GetPrevAutoCorrWord( *pACorr, sWord ) )
+ if (bNormalChar)
{
- ShowAutoTextCorrectQuickHelp(sWord, pACfg, pACorr);
+ const bool bAutoTextShown
+ = pACfg->IsAutoTextTip() && ShowAutoText(rSh.GetChunkForAutoText());
+ if (!bAutoTextShown && pACorr && pACorr->GetSwFlags().bAutoCompleteWords)
+ ShowAutoCorrectQuickHelp(rSh.GetPrevAutoCorrWord(*pACorr), *pACorr);
}
}
@@ -5393,18 +5393,13 @@ void SwEditWin::Command( const CommandEvent& rCEvt )
m_rView.GetViewFrame()->GetBindings().GetRecorder();
if(!xRecorder.is())
{
- SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get();
+ SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get();
+ if (!rACfg.IsAutoTextTip() || !ShowAutoText(rSh.GetChunkForAutoText()))
+ {
SvxAutoCorrect* pACorr = rACfg.GetAutoCorrect();
- if( pACorr &&
- // If autocompletion required...
- ( rACfg.IsAutoTextTip() ||
- pACorr->GetSwFlags().bAutoCompleteWords ) &&
- // ... and extraction of last word from text input was successful...
- rSh.GetPrevAutoCorrWord( *pACorr, sWord ) )
- {
- // ... request for auto completion help to be shown.
- ShowAutoTextCorrectQuickHelp(sWord, &rACfg, pACorr, true);
- }
+ if (pACorr && pACorr->GetSwFlags().bAutoCompleteWords)
+ ShowAutoCorrectQuickHelp(rSh.GetPrevAutoCorrWord(*pACorr), *pACorr);
+ }
}
}
}
@@ -5870,7 +5865,6 @@ void QuickHelpData::Move( QuickHelpData& rCpy )
m_aHelpStrings.swap( rCpy.m_aHelpStrings );
m_bIsDisplayed = rCpy.m_bIsDisplayed;
- nLen = rCpy.nLen;
nCurArrPos = rCpy.nCurArrPos;
m_bAppendSpace = rCpy.m_bAppendSpace;
m_bIsTip = rCpy.m_bIsTip;
@@ -5879,7 +5873,7 @@ void QuickHelpData::Move( QuickHelpData& rCpy )
void QuickHelpData::ClearContent()
{
- nLen = nCurArrPos = 0;
+ nCurArrPos = nNoPos;
m_bIsDisplayed = m_bAppendSpace = false;
nTipId = nullptr;
m_aHelpStrings.clear();
@@ -5887,11 +5881,10 @@ void QuickHelpData::ClearContent()
m_bIsAutoText = true;
}
-void QuickHelpData::Start( SwWrtShell& rSh, sal_uInt16 nWrdLen )
+void QuickHelpData::Start(SwWrtShell& rSh, const bool bRestart)
{
- if( USHRT_MAX != nWrdLen )
+ if (bRestart)
{
- nLen = nWrdLen;
nCurArrPos = 0;
}
m_bIsDisplayed = true;
@@ -5903,13 +5896,13 @@ void QuickHelpData::Start( SwWrtShell& rSh, sal_uInt16 nWrdLen )
rSh.GetCharRect().Pos() )));
aPt.AdjustY( -3 );
nTipId = Help::ShowPopover(&rWin, tools::Rectangle( aPt, Size( 1, 1 )),
- m_aHelpStrings[ nCurArrPos ],
+ CurStr(),
QuickHelpFlags::Left | QuickHelpFlags::Bottom);
}
else
{
- OUString sStr( m_aHelpStrings[ nCurArrPos ] );
- sStr = sStr.copy( nLen );
+ OUString sStr(CurStr());
+ sStr = sStr.copy(CurLen());
sal_uInt16 nL = sStr.getLength();
const ExtTextInputAttr nVal = ExtTextInputAttr::DottedUnderline |
ExtTextInputAttr::Highlight;
@@ -5986,23 +5979,24 @@ void QuickHelpData::FillStrArr( SwWrtShell const & rSh, const OUString& rWord )
if( rStr.getLength() > rWord.getLength() &&
rCC.lowercase( rStr, 0, rWord.getLength() ) == sWordLower )
{
+ OUString sStr;
+
//fdo#61251 if it's an exact match, ensure unchanged replacement
//exists as a candidate
if (rStr.startsWith(rWord))
- m_aHelpStrings.push_back(rStr);
+ m_aHelpStrings.emplace_back(rStr, rWord.getLength());
+ else
+ sStr = rStr; // to be added if no case conversion is performed below
if ( aWordCase == CASE_LOWER )
- m_aHelpStrings.push_back( rCC.lowercase( rStr ) );
+ sStr = rCC.lowercase(rStr);
else if ( aWordCase == CASE_SENTENCE )
- {
- OUString sTmp = rCC.lowercase( rStr );
- sTmp = sTmp.replaceAt( 0, 1, OUString(rStr[0]) );
- m_aHelpStrings.push_back( sTmp );
- }
+ sStr = rCC.lowercase(rStr).replaceAt(0, 1, OUString(rStr[0]));
else if ( aWordCase == CASE_UPPER )
- m_aHelpStrings.push_back( rCC.uppercase( rStr ) );
- else // CASE_OTHER - use retrieved capitalization
- m_aHelpStrings.push_back( rStr );
+ sStr = rCC.uppercase(rStr);
+
+ if (!sStr.isEmpty())
+ m_aHelpStrings.emplace_back(sStr, rWord.getLength());
}
}
}
@@ -6027,7 +6021,7 @@ void QuickHelpData::FillStrArr( SwWrtShell const & rSh, const OUString& rWord )
// only for "201" or "2016-..." (to avoid unintentional text
// insertion at line ending, for example typing "30 January 2016")
if (rWord.getLength() != 4 && rStrToday.startsWith(rWord))
- m_aHelpStrings.push_back(rStrToday);
+ m_aHelpStrings.emplace_back(rStrToday, rWord.getLength());
}
// Add matching words from AutoCompleteWord list
@@ -6044,22 +6038,25 @@ void QuickHelpData::FillStrArr( SwWrtShell const & rSh, const OUString& rWord )
if (!rStrToday.isEmpty() && aCompletedString.startsWith(rWord))
continue;
+ OUString sStr;
+
//fdo#61251 if it's an exact match, ensure unchanged replacement
//exists as a candidate
if (aCompletedString.startsWith(rWord))
- m_aHelpStrings.push_back(aCompletedString);
- if ( aWordCase == CASE_LOWER )
- m_aHelpStrings.push_back( rCC.lowercase( aCompletedString ) );
- else if ( aWordCase == CASE_SENTENCE )
- {
- OUString sTmp = rCC.lowercase( aCompletedString );
- sTmp = sTmp.replaceAt( 0, 1, OUString(aCompletedString[0]) );
- m_aHelpStrings.push_back( sTmp );
- }
- else if ( aWordCase == CASE_UPPER )
- m_aHelpStrings.push_back( rCC.uppercase( aCompletedString ) );
- else // CASE_OTHER - use retrieved capitalization
- m_aHelpStrings.push_back( aCompletedString );
+ m_aHelpStrings.emplace_back(aCompletedString, rWord.getLength());
+ else
+ sStr = aCompletedString; // to be added if no case conversion is performed below
+
+ if (aWordCase == CASE_LOWER)
+ sStr = rCC.lowercase(aCompletedString);
+ else if (aWordCase == CASE_SENTENCE)
+ sStr = rCC.lowercase(aCompletedString)
+ .replaceAt(0, 1, OUString(aCompletedString[0]));
+ else if (aWordCase == CASE_UPPER)
+ sStr = rCC.uppercase(aCompletedString);
+
+ if (!sStr.isEmpty())
+ m_aHelpStrings.emplace_back(aCompletedString, rWord.getLength());
}
}
}
@@ -6075,15 +6072,16 @@ public:
{
}
- bool operator()(const OUString& s1, const OUString& s2) const
+ bool operator()(const std::pair<OUString, sal_uInt16>& s1,
+ const std::pair<OUString, sal_uInt16>& s2) const
{
- int nRet = s1.compareToIgnoreAsciiCase(s2);
+ int nRet = s1.first.compareToIgnoreAsciiCase(s2.first);
if (nRet == 0)
{
//fdo#61251 sort stuff that starts with the exact rOrigWord before
//another ignore-case candidate
- int n1StartsWithOrig = s1.startsWith(m_rOrigWord) ? 0 : 1;
- int n2StartsWithOrig = s2.startsWith(m_rOrigWord) ? 0 : 1;
+ int n1StartsWithOrig = s1.first.startsWith(m_rOrigWord) ? 0 : 1;
+ int n2StartsWithOrig = s2.first.startsWith(m_rOrigWord) ? 0 : 1;
return n1StartsWithOrig < n2StartsWithOrig;
}
return nRet < 0;
@@ -6092,9 +6090,10 @@ public:
struct EqualIgnoreCaseAscii
{
- bool operator()(const OUString& s1, const OUString& s2) const
+ bool operator()(const std::pair<OUString, sal_uInt16>& s1,
+ const std::pair<OUString, sal_uInt16>& s2) const
{
- return s1.equalsIgnoreAsciiCase(s2);
+ return s1.first.equalsIgnoreAsciiCase(s2.first);
}
};
@@ -6107,33 +6106,74 @@ void QuickHelpData::SortAndFilter(const OUString &rOrigWord)
m_aHelpStrings.end(),
CompareIgnoreCaseAsciiFavorExact(rOrigWord) );
- std::vector<OUString>::iterator it = std::unique( m_aHelpStrings.begin(),
- m_aHelpStrings.end(),
- EqualIgnoreCaseAscii() );
+ const auto& it
+ = std::unique(m_aHelpStrings.begin(), m_aHelpStrings.end(), EqualIgnoreCaseAscii());
m_aHelpStrings.erase( it, m_aHelpStrings.end() );
nCurArrPos = 0;
}
-void SwEditWin::ShowAutoTextCorrectQuickHelp(
- const OUString& rWord, SvxAutoCorrCfg const * pACfg, SvxAutoCorrect* pACorr,
- bool bFromIME )
+// For a given chunk of typed text between 3 and 9 characters long that may start at a word boundary
+// or in a whitespace and may include whitespaces, SwEditShell::GetChunkForAutoTextcreates a list of
+// possible candidates for long AutoText names. Let's say, we have typed text "lorem ipsum dr f";
+// and the cursor is right after the "f". SwEditShell::GetChunkForAutoText would take " dr f",
+// since it's the longest chunk to the left of the cursor no longer than 9 characters, not starting
+// in the middle of a word. Then it would create this list from it (in this order, longest first):
+// " dr f"
+// " dr f"
+// "dr f"
+// It cannot add "r f", because it starts in the middle of the word "dr"; also it cannot give " f",
+// because it's only 2 characters long.
+// Now the result of SwEditShell::GetChunkForAutoText is passed here to SwEditWin::ShowAutoText, and
+// then to SwGlossaryList::HasLongName, where all existing autotext entries' long names are tested
+// if they start with one of the list elements. The matches are sorted according the position of the
+// candidate that matched first, then alhpabetically inside the group of suggestions for a given
+// candidate. Say, if we have these AutoText entry long names:
+// "Dr Frodo"
+// "Dr Credo"
+// "Or Bilbo"
+// "dr foo"
+// " Dr Fuzz"
+// " dr Faust"
+// the resulting list would be:
+// " Dr Fuzz" -> matches the first (longest) item in the candidates list
+// " dr Faust" -> matches the second candidate item
+// "Dr Foo" -> first item of the two matching the third candidate; alphabetically sorted
+// "Dr Frodo" -> second item of the two matching the third candidate; alphabetically sorted
+// Each of the resulting suggestions knows the length of the candidate it replaces, so accepting the
+// first suggestion would replace 6 characters before cursor, while tabbing to and accepting the
+// last suggestion would replace only 4 characters to the left of cursor.
+bool SwEditWin::ShowAutoText(const std::vector<OUString>& rChunkCandidates)
{
- SwWrtShell& rSh = m_rView.GetWrtShell();
m_pQuickHlpData->ClearContent();
- if( pACfg->IsAutoTextTip() )
+ if (!rChunkCandidates.empty())
{
SwGlossaryList* pList = ::GetGlossaryList();
- pList->HasLongName( rWord, &m_pQuickHlpData->m_aHelpStrings );
+ pList->HasLongName(rChunkCandidates, m_pQuickHlpData->m_aHelpStrings);
}
+ if (!m_pQuickHlpData->m_aHelpStrings.empty())
+ {
+ m_pQuickHlpData->Start(m_rView.GetWrtShell(), true);
+ }
+ return !m_pQuickHlpData->m_aHelpStrings.empty();
+}
+
+void SwEditWin::ShowAutoCorrectQuickHelp(
+ const OUString& rWord, SvxAutoCorrect& rACorr,
+ bool bFromIME )
+{
+ if (rWord.isEmpty())
+ return;
+ SwWrtShell& rSh = m_rView.GetWrtShell();
+ m_pQuickHlpData->ClearContent();
+
if( m_pQuickHlpData->m_aHelpStrings.empty() &&
- pACorr->GetSwFlags().bAutoCompleteWords )
+ rACorr.GetSwFlags().bAutoCompleteWords )
{
m_pQuickHlpData->m_bIsAutoText = false;
m_pQuickHlpData->m_bIsTip = bFromIME ||
- !pACorr ||
- pACorr->GetSwFlags().bAutoCmpltShowAsTip;
+ rACorr.GetSwFlags().bAutoCmpltShowAsTip;
// Get the necessary data to show help text.
m_pQuickHlpData->FillStrArr( rSh, rWord );
@@ -6142,7 +6182,7 @@ void SwEditWin::ShowAutoTextCorrectQuickHelp(
if( !m_pQuickHlpData->m_aHelpStrings.empty() )
{
m_pQuickHlpData->SortAndFilter(rWord);
- m_pQuickHlpData->Start( rSh, rWord.getLength() );
+ m_pQuickHlpData->Start(rSh, true);
}
}
diff --git a/sw/source/uibase/inc/edtwin.hxx b/sw/source/uibase/inc/edtwin.hxx
index 0f7d992734ad..c17c9003d263 100644
--- a/sw/source/uibase/inc/edtwin.hxx
+++ b/sw/source/uibase/inc/edtwin.hxx
@@ -189,8 +189,9 @@ class SW_DLLPUBLIC SwEditWin final : public vcl::Window,
virtual OUString GetSurroundingText() const override;
virtual Selection GetSurroundingTextSelection() const override;
- void ShowAutoTextCorrectQuickHelp( const OUString& rWord, SvxAutoCorrCfg const * pACfg,
- SvxAutoCorrect* pACorr, bool bFromIME = false );
+ void ShowAutoCorrectQuickHelp(const OUString& rWord, SvxAutoCorrect& rACorr,
+ bool bFromIME = false);
+ bool ShowAutoText(const std::vector<OUString>& rChunkCandidates);
/// Returns true if in header/footer area, or in the header/footer control.
bool IsInHeaderFooter( const Point &rDocPt, FrameControlType &rControl ) const;
diff --git a/sw/source/uibase/inc/gloslst.hxx b/sw/source/uibase/inc/gloslst.hxx
index 038b046f2067..21debbcfac5e 100644
--- a/sw/source/uibase/inc/gloslst.hxx
+++ b/sw/source/uibase/inc/gloslst.hxx
@@ -58,7 +58,8 @@ public:
SwGlossaryList();
virtual ~SwGlossaryList() override;
- void HasLongName(const OUString& rBegin, std::vector<OUString> *pLongNames);
+ void HasLongName(const std::vector<OUString>& rBeginCandidates,
+ std::vector<std::pair<OUString, sal_uInt16>>& rLongNames);
bool GetShortName(const OUString& rLongName,
OUString& rShortName, OUString& rGroupName );
diff --git a/sw/source/uibase/utlui/gloslst.cxx b/sw/source/uibase/utlui/gloslst.cxx
index 58e4521b35dd..eea76b98181a 100644
--- a/sw/source/uibase/utlui/gloslst.cxx
+++ b/sw/source/uibase/utlui/gloslst.cxx
@@ -364,32 +364,69 @@ void SwGlossaryList::FillGroup(AutoTextGroup* pGroup, SwGlossaries* pGlossaries)
// Give back all (not exceeding FIND_MAX_GLOS) found modules
// with matching beginning.
-void SwGlossaryList::HasLongName(const OUString& rBegin, std::vector<OUString> *pLongNames)
+void SwGlossaryList::HasLongName(const std::vector<OUString>& rBeginCandidates,
+ std::vector<std::pair<OUString, sal_uInt16>>& rLongNames)
{
if(!bFilled)
Update();
- sal_uInt16 nFound = 0;
- const size_t nCount = aGroupArr.size();
- sal_Int32 nBeginLen = rBegin.getLength();
const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore();
+ // We store results for all candidate words in separate lists, so that later
+ // we can sort them according to the candidate position
+ std::vector<std::vector<OUString>> aResults(rBeginCandidates.size());
- for(size_t i = 0; i < nCount; ++i)
+ // We can't break after FIND_MAX_GLOS items found, since first items may have ended up in
+ // lower-priority lists, and those from higher-priority lists are yet to come. So process all.
+ for (const auto& pGroup : aGroupArr)
{
- AutoTextGroup* pGroup = aGroupArr[i].get();
sal_Int32 nIdx{ 0 };
for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
{
OUString sBlock = pGroup->sLongNames.getToken(0, STRING_DELIM, nIdx);
- if( nBeginLen + 1 < sBlock.getLength() &&
- rSCmp.isEqual( sBlock.copy(0, nBeginLen), rBegin ))
+ for (size_t i = 0; i < rBeginCandidates.size(); ++i)
{
- pLongNames->push_back( sBlock );
- nFound++;
- if(FIND_MAX_GLOS == nFound)
- break;
+ const OUString& s = rBeginCandidates[i];
+ if (s.getLength() + 1 < sBlock.getLength()
+ && rSCmp.isEqual(sBlock.copy(0, s.getLength()), s))
+ {
+ aResults[i].push_back(sBlock);
+ }
}
}
}
+
+ std::vector<std::pair<OUString, sal_uInt16>> aAllResults;
+ // Sort and concatenate all result lists. See QuickHelpData::SortAndFilter
+ for (size_t i = 0; i < rBeginCandidates.size(); ++i)
+ {
+ std::sort(aResults[i].begin(), aResults[i].end(),
+ [origWord = rBeginCandidates[i]](const OUString& s1, const OUString& s2) {
+ int nRet = s1.compareToIgnoreAsciiCase(s2);
+ if (nRet == 0)
+ {
+ // fdo#61251 sort stuff that starts with the exact rOrigWord before
+ // another ignore-case candidate
+ int n1StartsWithOrig = s1.startsWith(origWord) ? 0 : 1;
+ int n2StartsWithOrig = s2.startsWith(origWord) ? 0 : 1;
+ return n1StartsWithOrig < n2StartsWithOrig;
+ }
+ return nRet < 0;
+ });
+ // All suggestions must be accompanied with length of the text they would replace
+ std::transform(aResults[i].begin(), aResults[i].end(), std::back_inserter(aAllResults),
+ [nLen = sal_uInt16(rBeginCandidates[i].getLength())](const OUString& s) {
+ return std::make_pair(s, nLen);
+ });
+ }
+
+ const auto& it = std::unique(
+ aAllResults.begin(), aAllResults.end(),
+ [](const std::pair<OUString, sal_uInt16>& s1, const std::pair<OUString, sal_uInt16>& s2) {
+ return s1.first.equalsIgnoreAsciiCase(s2.first);
+ });
+ if (const auto nCount = std::min<size_t>(std::distance(aAllResults.begin(), it), FIND_MAX_GLOS))
+ {
+ rLongNames.insert(rLongNames.end(), aAllResults.begin(), aAllResults.begin() + nCount);
+ }
}
void SwGlossaryList::ClearGroups()