summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editeng/source/misc/svxacorr.cxx57
-rw-r--r--include/editeng/svxacorr.hxx10
-rw-r--r--sw/inc/editsh.hxx6
-rw-r--r--sw/source/core/edit/edws.cxx26
-rw-r--r--sw/source/uibase/docvw/edtwin.cxx202
-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.cxx63
8 files changed, 254 insertions, 118 deletions
diff --git a/editeng/source/misc/svxacorr.cxx b/editeng/source/misc/svxacorr.cxx
index bf52a5cf2a69..922f0b97973f 100644
--- a/editeng/source/misc/svxacorr.cxx
+++ b/editeng/source/misc/svxacorr.cxx
@@ -1526,12 +1526,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 ) const
+OUString SvxAutoCorrect::GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt,
+ sal_Int32 nPos)
{
+ OUString sRet;
if( !nPos )
- return false;
+ return sRet;
sal_Int32 nEnde = nPos;
@@ -1539,7 +1539,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 ]))
;
@@ -1552,21 +1552,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 );
- SvxAutoCorrect* pThis = const_cast<SvxAutoCorrect*>(this);
- CharClass& rCC = pThis->GetCharClass( eLang );
+ 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 46c2b0e9dadf..69920089b50f 100644
--- a/include/editeng/svxacorr.hxx
+++ b/include/editeng/svxacorr.hxx
@@ -287,8 +287,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 ) const;
+ 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
@@ -327,6 +332,7 @@ public:
// Query/Set the current settings of AutoCorrect
long GetFlags() const { return nFlags; }
SvxSwAutoFormatFlags& GetSwFlags() { return aSwFlags;}
+ const SvxSwAutoFormatFlags& GetSwFlags() const { return aSwFlags; }
bool IsAutoCorrFlag( long nFlag ) const
{ return (nFlags & nFlag) != 0; }
void SetAutoCorrFlag( long nFlag, bool bOn = true );
diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx
index 01eee9f968ad..59fedaeedaf8 100644
--- a/sw/inc/editsh.hxx
+++ b/sw/inc/editsh.hxx
@@ -824,7 +824,11 @@ public:
/// Call AutoCorrect
void AutoCorrect( SvxAutoCorrect& rACorr, bool bInsertMode,
sal_Unicode cChar );
- bool GetPrevAutoCorrWord( SvxAutoCorrect const & 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 eae129280c10..ac8775d3d78c 100644
--- a/sw/source/core/edit/edws.cxx
+++ b/sw/source/core/edit/edws.cxx
@@ -276,23 +276,35 @@ void SwEditShell::SetNewDoc()
GetDoc()->getIDocumentState().SetNewDoc(true);
}
-bool SwEditShell::GetPrevAutoCorrWord( SvxAutoCorrect const & rACorr, OUString& rWord )
+OUString SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr)
{
SET_CURR_SHELL( this );
- bool bRet;
+ OUString sRet;
SwPaM* pCursor = getShellCursor( true );
const sal_Int32 nPos = pCursor->GetPoint()->nContent.GetIndex();
SwTextNode* pTNd = pCursor->GetNode().GetTextNode();
if( pTNd && nPos )
{
SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, 0 );
- bRet = rACorr.GetPrevAutoCorrWord( aSwAutoCorrDoc,
- pTNd->GetText(), nPos, rWord );
+ sRet = rACorr.GetPrevAutoCorrWord(aSwAutoCorrDoc, pTNd->GetText(), 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);
+ const sal_Int32 nPos = pCursor->GetPoint()->nContent.GetIndex();
+ SwTextNode* pTNd = pCursor->GetNode().GetTextNode();
+ if (pTNd && nPos)
+ {
+ aRet = SvxAutoCorrect::GetChunkForAutoText(pTNd->GetText(), nPos);
+ }
+ return aRet;
}
SwAutoCompleteWord& SwEditShell::GetAutoCompleteWords()
diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx
index 3e605f318c70..9af4c7a15541 100644
--- a/sw/source/uibase/docvw/edtwin.cxx
+++ b/sw/source/uibase/docvw/edtwin.cxx
@@ -254,12 +254,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;
@@ -277,10 +276,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 )
@@ -2588,7 +2589,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();
@@ -2597,7 +2598,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);
@@ -2606,7 +2607,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;
@@ -2617,7 +2618,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:
@@ -2693,13 +2694,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);
}
}
@@ -5437,23 +5437,18 @@ void SwEditWin::Command( const CommandEvent& rCEvt )
rSh.SetExtTextInputData( *pData );
}
}
- uno::Reference< frame::XDispatchRecorder > xRecorder =
- m_rView.GetViewFrame()->GetBindings().GetRecorder();
- if(!xRecorder.is())
+ uno::Reference< frame::XDispatchRecorder > xRecorder =
+ m_rView.GetViewFrame()->GetBindings().GetRecorder();
+ if(!xRecorder.is())
+ {
+ SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get();
+ if (!rACfg.IsAutoTextTip() || !ShowAutoText(rSh.GetChunkForAutoText()))
{
- SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get();
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);
}
+ }
}
}
break;
@@ -5929,7 +5924,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;
@@ -5938,7 +5932,7 @@ void QuickHelpData::Move( QuickHelpData& rCpy )
void QuickHelpData::ClearContent()
{
- nLen = nCurArrPos = 0;
+ nCurArrPos = nNoPos;
m_bIsDisplayed = m_bAppendSpace = false;
nTipId = 0;
m_aHelpStrings.clear();
@@ -5946,11 +5940,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;
@@ -5962,13 +5955,13 @@ void QuickHelpData::Start( SwWrtShell& rSh, sal_uInt16 nWrdLen )
rSh.GetCharRect().Pos() )));
aPt.Y() -= 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;
@@ -6047,23 +6040,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());
}
}
// Data for second loop iteration
@@ -6091,7 +6085,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
@@ -6108,22 +6102,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());
}
}
@@ -6140,15 +6137,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;
@@ -6157,9 +6155,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);
}
};
@@ -6172,33 +6171,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 );
@@ -6207,7 +6247,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 71e3f9a7b34d..3822a3ca6856 100644
--- a/sw/source/uibase/inc/edtwin.hxx
+++ b/sw/source/uibase/inc/edtwin.hxx
@@ -193,8 +193,9 @@ class 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 b25e63fb2f48..e60710b5b306 100644
--- a/sw/source/uibase/inc/gloslst.hxx
+++ b/sw/source/uibase/inc/gloslst.hxx
@@ -56,7 +56,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 96d7212643a1..e327b719c664 100644
--- a/sw/source/uibase/utlui/gloslst.cxx
+++ b/sw/source/uibase/utlui/gloslst.cxx
@@ -388,31 +388,70 @@ 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(size_t i = 0; i < aGroupArr.size(); ++i)
{
AutoTextGroup* pGroup = aGroupArr[i];
+ sal_Int32 nIdx{ 0 };
for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
{
- OUString sBlock = pGroup->sLongNames.getToken(j, STRING_DELIM);
- if( nBeginLen + 1 < sBlock.getLength() &&
- rSCmp.isEqual( sBlock.copy(0, nBeginLen), rBegin ))
+ OUString sBlock = pGroup->sLongNames.getToken(0, STRING_DELIM, nIdx);
+ for (size_t k = 0; k < rBeginCandidates.size(); ++k)
{
- pLongNames->push_back( sBlock );
- nFound++;
- if(FIND_MAX_GLOS == nFound)
- break;
+ const OUString& s = rBeginCandidates[k];
+ if (s.getLength() + 1 < sBlock.getLength()
+ && rSCmp.isEqual(sBlock.copy(0, s.getLength()), s))
+ {
+ aResults[k].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()