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>
This commit is contained in:
Mike Kaganski 2019-10-07 17:46:36 +03:00
parent cf1b5ae4c5
commit ef2ec07b41
8 changed files with 249 additions and 113 deletions

View File

@ -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 )

View File

@ -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 );

View File

@ -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 );

View File

@ -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()

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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 );

View File

@ -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()