tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / sc / source / ui / app / inputhdl.cxx
blob0ad8ce05acb1f3c4b6b32877415b1928f1dfc658
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <iterator>
21 #include <memory>
22 #include <string_view>
24 #include <inputhdl.hxx>
25 #include <scitems.hxx>
26 #include <editeng/eeitem.hxx>
28 #include <sfx2/app.hxx>
29 #include <editeng/acorrcfg.hxx>
30 #include <formula/errorcodes.hxx>
31 #include <editeng/adjustitem.hxx>
32 #include <editeng/brushitem.hxx>
33 #include <svtools/colorcfg.hxx>
34 #include <editeng/colritem.hxx>
35 #include <editeng/editobj.hxx>
36 #include <editeng/editstat.hxx>
37 #include <editeng/editview.hxx>
38 #include <editeng/langitem.hxx>
39 #include <editeng/svxacorr.hxx>
40 #include <editeng/unolingu.hxx>
41 #include <editeng/wghtitem.hxx>
42 #include <editeng/justifyitem.hxx>
43 #include <editeng/misspellrange.hxx>
44 #include <sfx2/bindings.hxx>
45 #include <sfx2/viewfrm.hxx>
46 #include <sfx2/docfile.hxx>
47 #include <sfx2/printer.hxx>
48 #include <svl/numformat.hxx>
49 #include <svl/zforlist.hxx>
50 #include <unotools/localedatawrapper.hxx>
51 #include <unotools/charclass.hxx>
52 #include <utility>
53 #include <vcl/help.hxx>
54 #include <vcl/jsdialog/executor.hxx>
55 #include <vcl/commandevent.hxx>
56 #include <vcl/cursor.hxx>
57 #include <vcl/settings.hxx>
58 #include <vcl/svapp.hxx>
59 #include <tools/urlobj.hxx>
60 #include <tools/json_writer.hxx>
61 #include <formula/formulahelper.hxx>
62 #include <formula/funcvarargs.h>
63 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
64 #include <comphelper/lok.hxx>
65 #include <osl/diagnose.h>
67 #include <attrib.hxx>
68 #include <inputwin.hxx>
69 #include <tabvwsh.hxx>
70 #include <docsh.hxx>
71 #include <scmod.hxx>
72 #include <formulaopt.hxx>
73 #include <uiitems.hxx>
74 #include <global.hxx>
75 #include <sc.hrc>
76 #include <globstr.hrc>
77 #include <scresid.hxx>
78 #include <patattr.hxx>
79 #include <viewdata.hxx>
80 #include <document.hxx>
81 #include <docpool.hxx>
82 #include <editutil.hxx>
83 #include <appoptio.hxx>
84 #include <docoptio.hxx>
85 #include <validat.hxx>
86 #include <rfindlst.hxx>
87 #include <inputopt.hxx>
88 #include <simpleformulacalc.hxx>
89 #include <compiler.hxx>
90 #include <editable.hxx>
91 #include <funcdesc.hxx>
92 #include <markdata.hxx>
93 #include <tokenarray.hxx>
94 #include <gridwin.hxx>
95 #include <output.hxx>
96 #include <fillinfo.hxx>
98 // Maximum Ranges in RangeFinder
99 #define RANGEFIND_MAX 128
101 using namespace formula;
103 namespace {
105 ScTypedCaseStrSet::const_iterator findText(
106 const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const & itPos,
107 const OUString& rStart, OUString& rResult, bool bBack)
109 auto lIsMatch = [&rStart](const ScTypedStrData& rData) {
110 return (rData.GetStringType() != ScTypedStrData::Value) && ScGlobal::GetTransliteration().isMatch(rStart, rData.GetString()); };
112 if (bBack) // Backwards
114 ScTypedCaseStrSet::const_reverse_iterator it = rDataSet.rbegin(), itEnd = rDataSet.rend();
115 if (itPos != rDataSet.end())
117 size_t nPos = std::distance(rDataSet.begin(), itPos);
118 size_t nRPos = rDataSet.size() - 1 - nPos;
119 std::advance(it, nRPos);
120 ++it;
123 it = std::find_if(it, itEnd, lIsMatch);
124 if (it != itEnd)
126 rResult = it->GetString();
127 return (++it).base(); // convert the reverse iterator back to iterator.
130 else // Forwards
132 ScTypedCaseStrSet::const_iterator it = rDataSet.begin(), itEnd = rDataSet.end();
133 if (itPos != itEnd)
135 it = std::next(itPos);
138 it = std::find_if(it, itEnd, lIsMatch);
139 if (it != itEnd)
141 rResult = it->GetString();
142 return it;
146 return rDataSet.end(); // no matching text found
149 OUString getExactMatch(const ScTypedCaseStrSet& rDataSet, const OUString& rString)
151 auto it = std::find_if(rDataSet.begin(), rDataSet.end(),
152 [&rString](const ScTypedStrData& rData) {
153 return (rData.GetStringType() != ScTypedStrData::Value)
154 && ScGlobal::GetTransliteration().isEqual(rData.GetString(), rString);
156 if (it != rDataSet.end())
157 return it->GetString();
158 return rString;
161 // This assumes that rResults is a sorted ring w.r.t ScTypedStrData::LessCaseInsensitive() or
162 // in the reverse direction, whose origin is specified by nRingOrigin.
163 sal_Int32 getLongestCommonPrefixLength(const std::vector<OUString>& rResults, std::u16string_view aUserEntry, sal_Int32 nRingOrigin)
165 sal_Int32 nResults = rResults.size();
166 if (!nResults)
167 return 0;
169 if (nResults == 1)
170 return rResults[0].getLength();
172 sal_Int32 nMinLen = aUserEntry.size();
173 sal_Int32 nLastIdx = nRingOrigin ? nRingOrigin - 1 : nResults - 1;
174 const OUString& rFirst = rResults[nRingOrigin];
175 const OUString& rLast = rResults[nLastIdx];
176 const sal_Int32 nMaxLen = std::min(rFirst.getLength(), rLast.getLength());
178 for (sal_Int32 nLen = nMaxLen; nLen > nMinLen; --nLen)
180 if (ScGlobal::GetTransliteration().isMatch(rFirst.copy(0, nLen), rLast))
181 return nLen;
184 return nMinLen;
187 ScTypedCaseStrSet::const_iterator findTextAll(
188 const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const & itPos,
189 const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack, sal_Int32* pLongestPrefixLen = nullptr)
191 rResultVec.clear(); // clear contents
193 if (!rDataSet.size())
194 return rDataSet.end();
196 sal_Int32 nRingOrigin = 0;
197 size_t nCount = 0;
198 ScTypedCaseStrSet::const_iterator retit;
199 if ( bBack ) // Backwards
201 ScTypedCaseStrSet::const_reverse_iterator it, itEnd;
202 if ( itPos == rDataSet.end() )
204 it = rDataSet.rend();
205 --it;
206 itEnd = it;
208 else
210 it = rDataSet.rbegin();
211 size_t nPos = std::distance(rDataSet.begin(), itPos);
212 size_t nRPos = rDataSet.size() - 1 - nPos; // if itPos == rDataSet.end(), then nRPos = -1
213 std::advance(it, nRPos);
214 if ( it == rDataSet.rend() )
215 it = rDataSet.rbegin();
216 itEnd = it;
218 bool bFirstTime = true;
220 while ( it != itEnd || bFirstTime )
222 ++it;
223 if ( it == rDataSet.rend() ) // go to the first if reach the end
225 it = rDataSet.rbegin();
226 nRingOrigin = nCount;
229 if ( bFirstTime )
230 bFirstTime = false;
231 const ScTypedStrData& rData = *it;
232 if ( rData.GetStringType() == ScTypedStrData::Value )
233 // skip values
234 continue;
236 if ( !ScGlobal::GetTransliteration().isMatch(rStart, rData.GetString()) )
237 // not a match
238 continue;
240 rResultVec.push_back(rData.GetString()); // set the match data
241 if ( nCount == 0 ) // convert the reverse iterator back to iterator.
243 // actually we want to do "retit = it;".
244 retit = rDataSet.begin();
245 size_t nRPos = std::distance(rDataSet.rbegin(), it);
246 size_t nPos = rDataSet.size() - 1 - nRPos;
247 std::advance(retit, nPos);
249 ++nCount;
252 else // Forwards
254 ScTypedCaseStrSet::const_iterator it, itEnd;
255 it = itPos;
256 if ( it == rDataSet.end() )
257 it = --rDataSet.end();
258 itEnd = it;
259 bool bFirstTime = true;
261 while ( it != itEnd || bFirstTime )
263 ++it;
264 if ( it == rDataSet.end() ) // go to the first if reach the end
266 it = rDataSet.begin();
267 nRingOrigin = nCount;
270 if ( bFirstTime )
271 bFirstTime = false;
272 const ScTypedStrData& rData = *it;
273 if ( rData.GetStringType() == ScTypedStrData::Value )
274 // skip values
275 continue;
277 if ( !ScGlobal::GetTransliteration().isMatch(rStart, rData.GetString()) )
278 // not a match
279 continue;
281 rResultVec.push_back(rData.GetString()); // set the match data
282 if ( nCount == 0 )
283 retit = it; // remember first match iterator
284 ++nCount;
288 if (pLongestPrefixLen)
290 if (nRingOrigin >= static_cast<sal_Int32>(nCount))
292 // All matches were picked when rDataSet was read in one direction.
293 nRingOrigin = 0;
295 // rResultsVec is a sorted ring with nRingOrigin "origin".
296 // The direction of sorting is not important for getLongestCommonPrefixLength.
297 *pLongestPrefixLen = getLongestCommonPrefixLength(rResultVec, rStart, nRingOrigin);
300 if ( nCount > 0 ) // at least one function has matched
301 return retit;
302 return rDataSet.end(); // no matching text found
307 void ScInputHandler::SendReferenceMarks( const SfxViewShell* pViewShell,
308 const std::vector<ReferenceMark>& rReferenceMarks )
310 if ( !pViewShell )
311 return;
313 bool bSend = false;
315 std::stringstream ss;
317 ss << "{ \"marks\": [ ";
319 for ( size_t i = 0; i < rReferenceMarks.size(); i++ )
321 if ( rReferenceMarks[i].Is() )
323 if ( bSend )
324 ss << ", ";
326 ss << "{ \"rectangle\": \""
327 << rReferenceMarks[i].nX << ", "
328 << rReferenceMarks[i].nY << ", "
329 << rReferenceMarks[i].nWidth << ", "
330 << rReferenceMarks[i].nHeight << "\", "
331 "\"color\": \"" << rReferenceMarks[i].aColor.AsRGBHexString() << "\", "
332 "\"part\": \"" << rReferenceMarks[i].nTab << "\" } ";
334 bSend = true;
338 ss << " ] }";
340 OString aPayload( ss.str() );
341 pViewShell->libreOfficeKitViewCallback(
342 LOK_CALLBACK_REFERENCE_MARKS, aPayload );
345 static inline void incPos( const sal_Unicode c, sal_Int32& rPos, ESelection& rSel )
347 ++rPos;
348 if (c == '\n')
350 ++rSel.end.nPara;
351 rSel.end.nIndex = 0;
353 else
355 ++rSel.end.nIndex;
359 void ScInputHandler::InitRangeFinder( const OUString& rFormula )
361 DeleteRangeFinder();
362 if (!pActiveViewSh || !ScModule::get()->GetInputOptions().GetRangeFinder())
363 return;
364 ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
365 ScDocument& rDoc = pDocSh->GetDocument();
366 const sal_Unicode cSheetSep = rDoc.GetSheetSeparator();
368 OUString aDelimiters = ScEditUtil::ModifyDelimiters(u" !~%\"\t\n"_ustr);
369 // delimiters (in addition to ScEditUtil): only characters that are
370 // allowed in formulas next to references and the quotation mark (so
371 // string constants can be skipped)
373 sal_Int32 nColon = aDelimiters.indexOf( ':' );
374 if ( nColon != -1 )
375 aDelimiters = aDelimiters.replaceAt( nColon, 1, u""); // Delimiter without colon
376 sal_Int32 nDot = aDelimiters.indexOf(cSheetSep);
377 if ( nDot != -1 )
378 aDelimiters = aDelimiters.replaceAt( nDot, 1 , u""); // Delimiter without dot
380 const sal_Unicode* pChar = rFormula.getStr();
381 sal_Int32 nLen = rFormula.getLength();
382 sal_Int32 nPos = 0;
383 sal_Int32 nStart = 0;
384 ESelection aSel;
385 sal_uInt16 nCount = 0;
386 ScRange aRange;
387 while ( nPos < nLen && nCount < RANGEFIND_MAX )
389 // Skip separator
390 while ( nPos<nLen && ScGlobal::UnicodeStrChr( aDelimiters.getStr(), pChar[nPos] ) )
392 if ( pChar[nPos] == '"' ) // String
394 incPos( pChar[nPos], nPos, aSel);
395 while (nPos<nLen && pChar[nPos] != '"') // Skip until end
396 incPos( pChar[nPos], nPos, aSel);
398 incPos( pChar[nPos], nPos, aSel); // Separator or closing quote
401 // Text between separators. We only consider within one line/paragraph.
402 aSel.CollapseToEnd();
403 nStart = nPos;
404 handle_r1c1:
406 bool bSingleQuoted = false;
407 while (nPos < nLen)
409 // tdf#114113: handle addresses with quoted sheet names like "'Sheet 1'.A1"
410 // Literal single quotes in sheet names are masked by another single quote
411 if (pChar[nPos] == '\'')
413 bSingleQuoted = !bSingleQuoted;
415 else if (!bSingleQuoted) // Get everything in single quotes, including separators
417 if (ScGlobal::UnicodeStrChr(aDelimiters.getStr(), pChar[nPos]))
418 break;
420 incPos( pChar[nPos], nPos, aSel);
424 // for R1C1 '-' in R[-]... or C[-]... are not delimiters
425 // Nothing heroic here to ensure that there are '[]' around a negative
426 // integer. we need to clean up this code.
427 if( nPos < nLen && nPos > 0 &&
428 '-' == pChar[nPos] && '[' == pChar[nPos-1] &&
429 formula::FormulaGrammar::CONV_XL_R1C1 == rDoc.GetAddressConvention() )
431 incPos( pChar[nPos], nPos, aSel);
432 goto handle_r1c1;
435 if ( nPos > nStart )
437 OUString aTest = rFormula.copy( nStart, nPos-nStart );
438 const ScAddress::Details aAddrDetails( rDoc, aCursorPos );
439 ScRefFlags nFlags = aRange.ParseAny( aTest, rDoc, aAddrDetails );
440 if ( nFlags & ScRefFlags::VALID )
442 // Set tables if not specified
443 if ( (nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
444 aRange.aStart.SetTab( pActiveViewSh->GetViewData().GetTabNo() );
445 if ( (nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
446 aRange.aEnd.SetTab( aRange.aStart.Tab() );
448 if ( ( nFlags & (ScRefFlags::COL2_VALID|ScRefFlags::ROW2_VALID|ScRefFlags::TAB2_VALID) ) ==
449 ScRefFlags::ZERO )
451 // #i73766# if a single ref was parsed, set the same "abs" flags for ref2,
452 // so Format doesn't output a double ref because of different flags.
453 ScRefFlags nAbsFlags = nFlags & (ScRefFlags::COL_ABS|ScRefFlags::ROW_ABS|ScRefFlags::TAB_ABS);
454 applyStartToEndFlags(nFlags, nAbsFlags);
457 if (!nCount)
459 mpEditEngine->SetUpdateLayout( false );
460 pRangeFindList.reset(new ScRangeFindList( pDocSh->GetTitle() ));
463 Color nColor = pRangeFindList->Insert( ScRangeFindData( aRange, nFlags, aSel));
465 SfxItemSet aSet( mpEditEngine->GetEmptyItemSet() );
466 aSet.Put( SvxColorItem( nColor, EE_CHAR_COLOR ) );
467 mpEditEngine->QuickSetAttribs( aSet, aSel );
468 ++nCount;
472 // Do not skip last separator; could be a quote (?)
475 UpdateLokReferenceMarks();
477 if (nCount)
479 mpEditEngine->SetUpdateLayout( true );
481 pDocSh->Broadcast( SfxHint( SfxHintId::ScShowRangeFinder ) );
485 ReferenceMark ScInputHandler::GetReferenceMark( const ScViewData& rViewData, ScDocShell* pDocSh,
486 tools::Long nX1, tools::Long nX2, tools::Long nY1, tools::Long nY2,
487 tools::Long nTab, const Color& rColor )
489 ScSplitPos eWhich = rViewData.GetActivePart();
491 // This method is LOK specific.
492 if (comphelper::LibreOfficeKit::isCompatFlagSet(
493 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs))
495 SCCOL nCol1 = nX1, nCol2 = nX2;
496 SCROW nRow1 = nY1, nRow2 = nY2;
497 ScDocument& rDoc = pDocSh->GetDocument();
499 PutInOrder(nCol1, nCol2);
500 PutInOrder(nRow1, nRow2);
502 if (nCol1 == nCol2 && nRow1 == nRow2)
503 rDoc.ExtendMerge(nCol1, nRow1, nCol2, nRow2, nTab);
504 else if (rDoc.HasAttrib(nCol2, nRow2, nTab, HasAttrFlags::Merged))
505 rDoc.ExtendMerge(nCol2, nRow2, nCol2, nRow2, nTab);
507 Point aTopLeft = rViewData.GetPrintTwipsPos(nCol1, nRow1);
508 Point aBottomRight = rViewData.GetPrintTwipsPos(nCol2 + 1, nRow2 + 1);
509 tools::Long nSizeX = aBottomRight.X() - aTopLeft.X() - 1;
510 tools::Long nSizeY = aBottomRight.Y() - aTopLeft.Y() - 1;
512 return ReferenceMark(aTopLeft.X(), aTopLeft.Y(), nSizeX, nSizeY, nTab, rColor);
515 Point aScrPos = rViewData.GetScrPos( nX1, nY1, eWhich );
516 tools::Long nScrX = aScrPos.X();
517 tools::Long nScrY = aScrPos.Y();
519 double nPPTX = rViewData.GetPPTX();
520 double nPPTY = rViewData.GetPPTY();
522 Fraction aZoomX = rViewData.GetZoomX();
523 Fraction aZoomY = rViewData.GetZoomY();
525 ScTableInfo aTabInfo(nY1, nY2, true);
526 pDocSh->GetDocument().FillInfo( aTabInfo, nX1, nY1, nX2, nY2,
527 nTab, nPPTX, nPPTY, false, false );
529 ScOutputData aOutputData( nullptr, OUTTYPE_WINDOW, aTabInfo,
530 &( pDocSh->GetDocument() ), nTab,
531 nScrX, nScrY,
532 nX1, nY1, nX2, nY2,
533 nPPTX, nPPTY,
534 &aZoomX, &aZoomY );
536 return aOutputData.FillReferenceMark( nX1, nY1, nX2, nY2,
537 rColor );
540 void ScInputHandler::UpdateLokReferenceMarks()
542 if ( !comphelper::LibreOfficeKit::isActive())
543 return;
545 ScTabViewShell* pShell = pActiveViewSh ? pActiveViewSh
546 : dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
548 if (!pShell)
549 return;
551 ScViewData& rViewData = pShell->GetViewData();
552 ScDocShell* pDocSh = rViewData.GetDocShell();
553 ScRangeFindList* pRangeFinder = GetRangeFindList();
555 if ( !pRangeFinder && !rViewData.IsRefMode() )
556 return;
558 sal_uInt16 nAdditionalMarks = 0;
559 std::vector<ReferenceMark> aReferenceMarks( 1 );
561 if ( rViewData.IsRefMode() )
563 nAdditionalMarks = 1;
565 const svtools::ColorConfig& rColorCfg = ScModule::get()->GetColorConfig();
566 Color aRefColor( rColorCfg.GetColorValue( svtools::CALCREFERENCE ).nColor );
567 tools::Long nX1 = rViewData.GetRefStartX();
568 tools::Long nX2 = rViewData.GetRefEndX();
569 tools::Long nY1 = rViewData.GetRefStartY();
570 tools::Long nY2 = rViewData.GetRefEndY();
571 tools::Long nTab = rViewData.GetRefStartZ();
573 if (rViewData.GetRefEndZ() == rViewData.GetTabNo())
574 nTab = rViewData.GetRefEndZ();
576 PutInOrder(nX1, nX2);
577 PutInOrder(nY1, nY2);
579 aReferenceMarks[0] = ScInputHandler::GetReferenceMark( rViewData, pDocSh,
580 nX1, nX2, nY1, nY2,
581 nTab, aRefColor );
584 sal_uInt16 nCount = pRangeFinder ?
585 ( static_cast<sal_uInt16>( pRangeFinder->Count() ) + nAdditionalMarks ) : nAdditionalMarks;
586 aReferenceMarks.resize( nCount );
588 if ( nCount && pRangeFinder && !pRangeFinder->IsHidden() &&
589 pRangeFinder->GetDocName() == pDocSh->GetTitle() )
591 for (sal_uInt16 i = 0; i < nCount - nAdditionalMarks; i++)
593 ScRangeFindData& rData = pRangeFinder->GetObject( i );
594 ScRange aRef = rData.aRef;
595 aRef.PutInOrder();
597 tools::Long nX1 = aRef.aStart.Col();
598 tools::Long nX2 = aRef.aEnd.Col();
599 tools::Long nY1 = aRef.aStart.Row();
600 tools::Long nY2 = aRef.aEnd.Row();
601 tools::Long nTab = aRef.aStart.Tab();
603 aReferenceMarks[i + nAdditionalMarks] = ScInputHandler::GetReferenceMark( rViewData, pDocSh,
604 nX1, nX2, nY1, nY2,
605 nTab, rData.nColor );
607 ScInputHandler::SendReferenceMarks( pShell, aReferenceMarks );
610 else if ( nCount )
612 ScInputHandler::SendReferenceMarks( pShell, aReferenceMarks );
614 else
616 // Clear
617 aReferenceMarks.clear();
618 ScInputHandler::SendReferenceMarks( pShell, aReferenceMarks );
622 void ScInputHandler::SetDocumentDisposing( bool b )
624 mbDocumentDisposing = b;
627 static void lcl_Replace( EditView* pView, const OUString& rNewStr, const ESelection& rOldSel )
629 if ( !pView )
630 return;
632 ESelection aOldSel = pView->GetSelection();
633 if (aOldSel.HasRange())
634 pView->SetSelection(ESelection(aOldSel.end));
636 EditEngine& rEngine = pView->getEditEngine();
637 rEngine.QuickInsertText( rNewStr, rOldSel );
639 // Dummy InsertText for Update and Paint
640 // To do that we need to cancel the selection from above (before QuickInsertText)
641 pView->InsertText( OUString() );
643 pView->SetSelection(ESelection::AtEnd()); // Set cursor to the end
646 void ScInputHandler::UpdateRange( sal_uInt16 nIndex, const ScRange& rNew )
648 ScTabViewShell* pDocView = pRefViewSh ? pRefViewSh : pActiveViewSh;
649 if ( pDocView && pRangeFindList && nIndex < pRangeFindList->Count() )
651 ScRangeFindData& rData = pRangeFindList->GetObject( nIndex );
652 Color nNewColor = pRangeFindList->FindColor( rNew, nIndex );
654 ScRange aJustified = rNew;
655 aJustified.PutInOrder(); // Always display Ref in the Formula the right way
656 ScDocument& rDoc = pDocView->GetViewData().GetDocument();
657 const ScAddress::Details aAddrDetails( rDoc, aCursorPos );
658 OUString aNewStr(aJustified.Format(rDoc, rData.nFlags, aAddrDetails));
659 SfxItemSet aSet( mpEditEngine->GetEmptyItemSet() );
661 DataChanging();
663 lcl_Replace( pTopView, aNewStr, rData.maSel );
664 lcl_Replace( pTableView, aNewStr, rData.maSel );
666 // We are within one paragraph.
667 const sal_Int32 nDiff = aNewStr.getLength() - (rData.maSel.end.nIndex - rData.maSel.start.nIndex);
668 rData.maSel.end.nIndex += nDiff;
670 aSet.Put( SvxColorItem( nNewColor, EE_CHAR_COLOR ) );
671 mpEditEngine->QuickSetAttribs( aSet, rData.maSel );
673 bInRangeUpdate = true;
674 DataChanged();
675 bInRangeUpdate = false;
677 rData.aRef = rNew;
678 rData.nColor = nNewColor;
680 if (nDiff)
682 const size_t nCount = pRangeFindList->Count();
683 for (size_t i = nIndex + 1; i < nCount; ++i)
685 ScRangeFindData& rNext = pRangeFindList->GetObject( i );
686 if (rNext.maSel.start.nPara != rData.maSel.start.nPara)
687 break;
689 rNext.maSel.start.nIndex += nDiff;
690 rNext.maSel.end.nIndex += nDiff;
694 EditView* pActiveView = pTopView ? pTopView : pTableView;
695 pActiveView->ShowCursor( false );
697 else
699 OSL_FAIL("UpdateRange: we're missing something");
703 void ScInputHandler::DeleteRangeFinder()
705 ScTabViewShell* pPaintView = pRefViewSh ? pRefViewSh : pActiveViewSh;
706 if ( pRangeFindList && pPaintView )
708 ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
709 pRangeFindList->SetHidden(true);
710 pDocSh->Broadcast( SfxHint( SfxHintId::ScShowRangeFinder ) ); // Steal
711 pRangeFindList.reset();
715 static OUString GetEditText(const EditEngine* pEng)
717 return ScEditUtil::GetMultilineString(*pEng);
720 static void lcl_RemoveTabs(OUString& rStr)
722 rStr = rStr.replace('\t', ' ');
725 static void lcl_RemoveLineEnd(OUString& rStr)
727 rStr = convertLineEnd(rStr, LINEEND_LF);
728 rStr = rStr.replace('\n', ' ');
731 static sal_Int32 lcl_MatchParenthesis( const OUString& rStr, sal_Int32 nPos )
733 int nDir;
734 sal_Unicode c1, c2 = 0;
735 c1 = rStr[nPos];
736 switch ( c1 )
738 case '(' :
739 c2 = ')';
740 nDir = 1;
741 break;
742 case ')' :
743 c2 = '(';
744 nDir = -1;
745 break;
746 case '<' :
747 c2 = '>';
748 nDir = 1;
749 break;
750 case '>' :
751 c2 = '<';
752 nDir = -1;
753 break;
754 case '{' :
755 c2 = '}';
756 nDir = 1;
757 break;
758 case '}' :
759 c2 = '{';
760 nDir = -1;
761 break;
762 case '[' :
763 c2 = ']';
764 nDir = 1;
765 break;
766 case ']' :
767 c2 = '[';
768 nDir = -1;
769 break;
770 default:
771 nDir = 0;
773 if ( !nDir )
774 return -1;
775 sal_Int32 nLen = rStr.getLength();
776 const sal_Unicode* p0 = rStr.getStr();
777 const sal_Unicode* p;
778 const sal_Unicode* p1;
779 sal_uInt16 nQuotes = 0;
780 if ( nPos < nLen / 2 )
782 p = p0;
783 p1 = p0 + nPos;
785 else
787 p = p0 + nPos;
788 p1 = p0 + nLen;
790 while ( p < p1 )
792 if ( *p++ == '\"' )
793 nQuotes++;
795 // Odd number of quotes that we find ourselves in a string
796 bool bLookInString = ((nQuotes % 2) != 0);
797 bool bInString = bLookInString;
798 p = p0 + nPos;
799 p1 = (nDir < 0 ? p0 : p0 + nLen) ;
800 sal_uInt16 nLevel = 1;
801 while ( p != p1 && nLevel )
803 p += nDir;
804 if ( *p == '\"' )
806 bInString = !bInString;
807 if ( bLookInString && !bInString )
808 p = p1; // That's it then
810 else if ( bInString == bLookInString )
812 if ( *p == c1 )
813 nLevel++;
814 else if ( *p == c2 )
815 nLevel--;
818 if ( nLevel )
819 return -1;
820 return static_cast<sal_Int32>(p - p0);
823 ScInputHandler::ScInputHandler()
824 : pInputWin( nullptr ),
825 pTableView( nullptr ),
826 pTopView( nullptr ),
827 pTipVisibleParent( nullptr ),
828 nTipVisible( nullptr ),
829 pTipVisibleSecParent( nullptr ),
830 nTipVisibleSec( nullptr ),
831 nFormSelStart( 0 ),
832 nFormSelEnd( 0 ),
833 nCellPercentFormatDecSep( 0 ),
834 nAutoPar( 0 ),
835 eMode( SC_INPUT_NONE ),
836 bUseTab( false ),
837 bTextValid( true ),
838 bModified( false ),
839 bSelIsRef( false ),
840 bFormulaMode( false ),
841 bInRangeUpdate( false ),
842 bParenthesisShown( false ),
843 bCreatingFuncView( false ),
844 bInEnterHandler( false ),
845 bCommandErrorShown( false ),
846 bInOwnChange( false ),
847 bProtected( false ),
848 bLastIsSymbol( false ),
849 mbDocumentDisposing(false),
850 mbPartialPrefix(false),
851 mbEditingExistingContent(false),
852 nValidation( 0 ),
853 eAttrAdjust( SvxCellHorJustify::Standard ),
854 aScaleX( 1,1 ),
855 aScaleY( 1,1 ),
856 pRefViewSh( nullptr ),
857 pLastPattern( nullptr )
859 // The InputHandler is constructed with the view, so SfxViewShell::Current
860 // doesn't have the right view yet. pActiveViewSh is updated in NotifyChange.
861 pActiveViewSh = nullptr;
863 // Bindings (only still used for Invalidate) are retrieved if needed on demand
865 pDelayTimer.reset( new Timer( "ScInputHandlerDelay timer" ) );
866 pDelayTimer->SetTimeout( 500 ); // 500 ms delay
867 pDelayTimer->SetInvokeHandler( LINK( this, ScInputHandler, DelayTimer ) );
870 ScInputHandler::~ScInputHandler()
872 // If this is the application InputHandler, the dtor is called after SfxApplication::Main,
873 // thus we can't rely on any Sfx functions
874 if (!mbDocumentDisposing) // inplace
875 EnterHandler(); // Finish input
877 if (ScModule* mod = ScModule::get(); mod->GetRefInputHdl() == this)
878 mod->SetRefInputHdl(nullptr);
880 if ( pInputWin && pInputWin->GetInputHandler() == this )
881 pInputWin->SetInputHandler( nullptr );
884 void ScInputHandler::SetRefScale( const Fraction& rX, const Fraction& rY )
886 if ( rX != aScaleX || rY != aScaleY )
888 aScaleX = rX;
889 aScaleY = rY;
890 if (mpEditEngine)
892 MapMode aMode( MapUnit::Map100thMM, Point(), aScaleX, aScaleY );
893 mpEditEngine->SetRefMapMode( aMode );
898 void ScInputHandler::UpdateRefDevice()
900 if (!mpEditEngine)
901 return;
903 bool bTextWysiwyg = ScModule::get()->GetInputOptions().GetTextWysiwyg();
904 if ( bTextWysiwyg && pActiveViewSh )
905 mpEditEngine->SetRefDevice( pActiveViewSh->GetViewData().GetDocument().GetPrinter() );
906 else
907 mpEditEngine->SetRefDevice( nullptr );
909 MapMode aMode( MapUnit::Map100thMM, Point(), aScaleX, aScaleY );
910 mpEditEngine->SetRefMapMode( aMode );
912 // SetRefDevice(NULL) uses VirtualDevice, SetRefMapMode forces creation of a local VDev,
913 // so the DigitLanguage can be safely modified (might use an own VDev instead of NULL).
914 if ( !( bTextWysiwyg && pActiveViewSh ) )
916 mpEditEngine->GetRefDevice()->SetDigitLanguage( ScModule::GetOptDigitLanguage() );
920 void ScInputHandler::ImplCreateEditEngine()
922 if ( mpEditEngine )
923 return;
925 // we cannot create a properly initialised EditEngine until we have a document
926 assert( pActiveViewSh );
927 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
928 mpEditEngine = std::make_unique<ScFieldEditEngine>(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool());
929 mpEditEngine->SetWordDelimiters( ScEditUtil::ModifyDelimiters( mpEditEngine->GetWordDelimiters() ) );
930 UpdateRefDevice(); // also sets MapMode
931 mpEditEngine->SetPaperSize( Size( 1000000, 1000000 ) );
932 pEditDefaults.reset( new SfxItemSet( mpEditEngine->GetEmptyItemSet() ) );
934 mpEditEngine->SetControlWord( mpEditEngine->GetControlWord() | EEControlBits::AUTOCORRECT );
935 mpEditEngine->SetReplaceLeadingSingleQuotationMark( false );
936 mpEditEngine->SetModifyHdl( LINK( this, ScInputHandler, ModifyHdl ) );
939 void ScInputHandler::UpdateAutoCorrFlag()
941 EEControlBits nCntrl = mpEditEngine->GetControlWord();
942 EEControlBits nOld = nCntrl;
944 // Don't use pLastPattern here (may be invalid because of AutoStyle)
945 bool bDisable = bLastIsSymbol || bFormulaMode;
946 if ( bDisable )
947 nCntrl &= ~EEControlBits::AUTOCORRECT;
948 else
949 nCntrl |= EEControlBits::AUTOCORRECT;
951 if ( nCntrl != nOld )
952 mpEditEngine->SetControlWord(nCntrl);
955 void ScInputHandler::UpdateSpellSettings( bool bFromStartTab )
957 if ( !pActiveViewSh )
958 return;
960 ScViewData& rViewData = pActiveViewSh->GetViewData();
961 bool bOnlineSpell = pActiveViewSh->IsAutoSpell();
963 // SetDefaultLanguage is independent of the language attributes,
964 // ScGlobal::GetEditDefaultLanguage is always used.
965 // It must be set every time in case the office language was changed.
967 mpEditEngine->SetDefaultLanguage( ScGlobal::GetEditDefaultLanguage() );
969 // if called for changed options, update flags only if already editing
970 // if called from StartTable, always update flags
972 if ( bFromStartTab || eMode != SC_INPUT_NONE )
974 EEControlBits nCntrl = mpEditEngine->GetControlWord();
975 EEControlBits nOld = nCntrl;
976 if( bOnlineSpell )
977 nCntrl |= EEControlBits::ONLINESPELLING;
978 else
979 nCntrl &= ~EEControlBits::ONLINESPELLING;
980 // No AutoCorrect for Symbol Font (EditEngine does no evaluate Default)
981 if ( pLastPattern && pLastPattern->IsSymbolFont() )
982 nCntrl &= ~EEControlBits::AUTOCORRECT;
983 else
984 nCntrl |= EEControlBits::AUTOCORRECT;
985 if ( nCntrl != nOld )
986 mpEditEngine->SetControlWord(nCntrl);
988 ScDocument& rDoc = rViewData.GetDocument();
989 rDoc.ApplyAsianEditSettings( *mpEditEngine );
990 mpEditEngine->SetDefaultHorizontalTextDirection(
991 rDoc.GetEditTextDirection( rViewData.GetTabNo() ) );
992 mpEditEngine->SetFirstWordCapitalization( false );
995 // Language is set separately, so the speller is needed only if online spelling is active
996 if ( bOnlineSpell ) {
997 css::uno::Reference<css::linguistic2::XSpellChecker1> xXSpellChecker1( LinguMgr::GetSpellChecker() );
998 mpEditEngine->SetSpeller( xXSpellChecker1 );
1001 bool bHyphen = pLastPattern && pLastPattern->GetItem(ATTR_HYPHENATE).GetValue();
1002 if ( bHyphen ) {
1003 css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
1004 mpEditEngine->SetHyphenator( xXHyphenator );
1008 // Function/Range names etc. as Tip help
1010 // The other types are defined in ScDocument::GetFormulaEntries
1011 void ScInputHandler::GetFormulaData()
1013 if ( !pActiveViewSh )
1014 return;
1016 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
1018 if ( pFormulaData )
1019 pFormulaData->clear();
1020 else
1022 pFormulaData.reset( new ScTypedCaseStrSet );
1025 if( pFormulaDataPara )
1026 pFormulaDataPara->clear();
1027 else
1028 pFormulaDataPara.reset( new ScTypedCaseStrSet );
1030 const OUString aParenthesesReplacement( cParenthesesReplacement);
1031 const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList();
1032 const sal_uInt32 nListCount = pFuncList->GetCount();
1033 const InputHandlerFunctionNames& rFunctionNames = ScGlobal::GetInputHandlerFunctionNames();
1034 *pFormulaData = rFunctionNames.maFunctionData;
1035 *pFormulaDataPara = rFunctionNames.maFunctionDataPara;
1036 maFormulaChar = rFunctionNames.maFunctionChar;
1038 // Increase suggestion priority of MRU formulas
1039 const ScAppOptions& rOpt = ScModule::get()->GetAppOptions();
1040 const sal_uInt16 nMRUCount = rOpt.GetLRUFuncListCount();
1041 const sal_uInt16* pMRUList = rOpt.GetLRUFuncList();
1042 for (sal_uInt16 i = 0; i < nMRUCount; i++)
1044 const sal_uInt16 nId = pMRUList[i];
1045 for (sal_uInt32 j = 0; j < nListCount; j++)
1047 const ScFuncDesc* pDesc = pFuncList->GetFunction(j);
1048 if (pDesc->nFIndex == nId && pDesc->mxFuncName)
1050 const OUString aEntry = *pDesc->mxFuncName + aParenthesesReplacement;;
1051 const ScTypedStrData aData(aEntry, 0.0, 0.0, ScTypedStrData::Standard);
1052 auto it = pFormulaData->find(aData);
1053 if (it != pFormulaData->end())
1054 pFormulaData->erase(it);
1055 pFormulaData->insert(ScTypedStrData(aEntry, 0.0, 0.0, ScTypedStrData::MRU));
1056 break; // Stop searching
1060 miAutoPosFormula = pFormulaData->end();
1062 // tdf#142031 - collect all the characters for the formula suggestion auto input
1063 ScTypedCaseStrSet aStrSet;
1064 rDoc.GetFormulaEntries( aStrSet );
1065 for (auto iter = aStrSet.begin(); iter != aStrSet.end(); ++iter)
1067 const OUString aFuncName = ScGlobal::getCharClass().uppercase((*iter).GetString());
1068 // fdo#75264 fill maFormulaChar with all characters used in formula names
1069 for (sal_Int32 j = 0; j < aFuncName.getLength(); j++)
1070 maFormulaChar.insert(aFuncName[j]);
1072 pFormulaData->insert(aStrSet.begin(), aStrSet.end());
1073 pFormulaDataPara->insert(aStrSet.begin(), aStrSet.end());
1076 IMPL_LINK( ScInputHandler, ShowHideTipVisibleParentListener, VclWindowEvent&, rEvent, void )
1078 if (rEvent.GetId() == VclEventId::ObjectDying || rEvent.GetId() == VclEventId::WindowHide
1079 || rEvent.GetId() == VclEventId::WindowLoseFocus || rEvent.GetId() == VclEventId::ControlLoseFocus)
1080 HideTip();
1083 IMPL_LINK( ScInputHandler, ShowHideTipVisibleSecParentListener, VclWindowEvent&, rEvent, void )
1085 if (rEvent.GetId() == VclEventId::ObjectDying || rEvent.GetId() == VclEventId::WindowHide
1086 || rEvent.GetId() == VclEventId::WindowLoseFocus || rEvent.GetId() == VclEventId::ControlLoseFocus)
1087 HideTipBelow();
1090 void ScInputHandler::HideTip()
1092 if ( nTipVisible )
1094 pTipVisibleParent->RemoveEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleParentListener ) );
1095 Help::HidePopover(pTipVisibleParent, nTipVisible );
1096 nTipVisible = nullptr;
1097 pTipVisibleParent = nullptr;
1099 aManualTip.clear();
1101 const SfxViewShell* pViewShell = SfxViewShell::Current();
1102 if (comphelper::LibreOfficeKit::isActive() && pViewShell)
1103 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CALC_FUNCTION_LIST, "hidetip"_ostr);
1105 void ScInputHandler::HideTipBelow()
1107 if ( nTipVisibleSec )
1109 pTipVisibleSecParent->RemoveEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleSecParentListener ) );
1110 Help::HidePopover(pTipVisibleSecParent, nTipVisibleSec);
1111 nTipVisibleSec = nullptr;
1112 pTipVisibleSecParent = nullptr;
1114 aManualTip.clear();
1117 namespace
1120 bool lcl_hasSingleToken(std::u16string_view s, sal_Unicode c)
1122 return !s.empty() && s.find(c) == std::u16string_view::npos;
1127 void ScInputHandler::ShowArgumentsTip( OUString& rSelText )
1129 if ( !pActiveViewSh )
1130 return;
1132 ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
1133 const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
1134 const sal_Unicode cSheetSep = pDocSh->GetDocument().GetSheetSeparator();
1135 FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
1136 bool bFound = false;
1137 while( !bFound )
1139 rSelText += ")";
1140 sal_Int32 nLeftParentPos = lcl_MatchParenthesis( rSelText, rSelText.getLength()-1 );
1141 if( nLeftParentPos != -1 )
1143 sal_Int32 nNextFStart = aHelper.GetFunctionStart( rSelText, nLeftParentPos, true);
1144 const IFunctionDescription* ppFDesc;
1145 ::std::vector< OUString> aArgs;
1146 if( aHelper.GetNextFunc( rSelText, false, nNextFStart, nullptr, &ppFDesc, &aArgs ) )
1148 if( !ppFDesc->getFunctionName().isEmpty() )
1150 sal_Int32 nArgPos = aHelper.GetArgStart( rSelText, nNextFStart, 0 );
1151 sal_uInt16 nArgs = static_cast<sal_uInt16>(ppFDesc->getParameterCount());
1152 OUString aFuncName( ppFDesc->getFunctionName() + "(");
1153 OUString aNew;
1154 ScTypedCaseStrSet::const_iterator it =
1155 findText(*pFormulaDataPara, pFormulaDataPara->end(), aFuncName, aNew, false);
1156 if (it != pFormulaDataPara->end())
1158 bool bFlag = false;
1159 sal_uInt16 nActive = 0;
1160 for( sal_uInt16 i=0; i < nArgs; i++ )
1162 sal_Int32 nLength = aArgs[i].getLength();
1163 if( nArgPos <= rSelText.getLength()-1 )
1165 nActive = i+1;
1166 bFlag = true;
1168 nArgPos+=nLength+1;
1170 if( bFlag )
1172 sal_Int32 nStartPosition = 0;
1173 sal_Int32 nEndPosition = 0;
1175 if( lcl_hasSingleToken(aNew, cSep) )
1177 for (sal_Int32 i = 0; i < aNew.getLength(); ++i)
1179 sal_Unicode cNext = aNew[i];
1180 if( cNext == '(' )
1182 nStartPosition = i+1;
1186 else if( lcl_hasSingleToken(aNew, cSheetSep) )
1188 sal_uInt16 nCount = 0;
1189 for (sal_Int32 i = 0; i < aNew.getLength(); ++i)
1191 sal_Unicode cNext = aNew[i];
1192 if( cNext == '(' )
1194 nStartPosition = i+1;
1196 else if( cNext == cSep )
1198 nCount ++;
1199 nEndPosition = i;
1200 if( nCount == nActive )
1202 break;
1204 nStartPosition = nEndPosition+1;
1208 else
1210 sal_uInt16 nCount = 0;
1211 for (sal_Int32 i = 0; i < aNew.getLength(); ++i)
1213 sal_Unicode cNext = aNew[i];
1214 if( cNext == '(' )
1216 nStartPosition = i+1;
1218 else if( cNext == cSep )
1220 nCount ++;
1221 nEndPosition = i;
1222 if( nCount == nActive )
1224 break;
1226 nStartPosition = nEndPosition+1;
1228 else if( cNext == cSheetSep )
1230 continue;
1235 if (nStartPosition > 0)
1237 nArgs = ppFDesc->getParameterCount();
1238 sal_Int16 nVarArgsSet = 0;
1239 if ( nArgs >= PAIRED_VAR_ARGS )
1241 nVarArgsSet = 2;
1242 nArgs -= PAIRED_VAR_ARGS - nVarArgsSet;
1244 else if ( nArgs >= VAR_ARGS )
1246 nVarArgsSet = 1;
1247 nArgs -= VAR_ARGS - nVarArgsSet;
1249 if ( nVarArgsSet > 0 && nActive > nArgs )
1250 nActive = nArgs - (nActive - nArgs) % nVarArgsSet;
1251 aNew = OUString::Concat(aNew.subView(0, nStartPosition)) +
1252 u"\x25BA" +
1253 aNew.subView(nStartPosition) +
1254 " : " +
1255 ppFDesc->getParameterDescription(nActive-1);
1256 if (eMode != SC_INPUT_TOP)
1258 ShowTipBelow( aNew );
1260 else
1262 ShowTip(aNew);
1264 bFound = true;
1267 else
1269 ShowTipBelow( aNew );
1270 bFound = true;
1273 const SfxViewShell* pViewShell = SfxViewShell::Current();
1274 if (comphelper::LibreOfficeKit::isActive() && pViewShell && pViewShell->isLOKDesktop())
1276 tools::JsonWriter writer;
1277 writer.put("type", "formulausage");
1278 writer.put("text", aNew);
1279 OString sFunctionUsageTip = writer.finishAndGetAsOString();
1280 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TOOLTIP, sFunctionUsageTip);
1286 else
1288 break;
1293 void ScInputHandler::ShowTipCursor()
1295 HideTip();
1296 HideTipBelow();
1297 EditView* pActiveView = pTopView ? pTopView : pTableView;
1299 /* TODO: MLFORMULA: this should work also with multi-line formulas. */
1300 if ( !(bFormulaMode && pActiveView && pFormulaDataPara && mpEditEngine->GetParagraphCount() == 1) )
1301 return;
1303 OUString aParagraph = mpEditEngine->GetText( 0 );
1304 ESelection aSel = pActiveView->GetSelection();
1305 aSel.Adjust();
1307 if (aParagraph.getLength() < aSel.end.nIndex)
1308 return;
1310 if (aSel.end.nIndex > 0)
1312 OUString aSelText(aParagraph.copy(0, aSel.end.nIndex));
1314 ShowArgumentsTip( aSelText );
1318 void ScInputHandler::ShowTip( const OUString& rText )
1320 // aManualTip needs to be set afterwards from outside
1322 HideTip();
1323 HideTipBelow();
1325 EditView* pActiveView = pTopView ? pTopView : pTableView;
1326 if (!pActiveView)
1327 return;
1329 Point aPos;
1330 if (pInputWin && pInputWin->GetEditView() == pActiveView)
1332 pTipVisibleParent = pInputWin->GetEditWindow();
1333 aPos = pInputWin->GetCursorScreenPixelPos();
1335 else
1337 pTipVisibleParent = pActiveView->GetWindow();
1338 if (vcl::Cursor* pCur = pActiveView->GetCursor())
1339 aPos = pTipVisibleParent->LogicToPixel( pCur->GetPos() );
1340 aPos = pTipVisibleParent->OutputToScreenPixel( aPos );
1343 tools::Rectangle aRect( aPos, aPos );
1344 QuickHelpFlags const nAlign = QuickHelpFlags::Left|QuickHelpFlags::Bottom;
1345 nTipVisible = Help::ShowPopover(pTipVisibleParent, aRect, rText, nAlign);
1346 pTipVisibleParent->AddEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleParentListener ) );
1349 void ScInputHandler::ShowTipBelow( const OUString& rText )
1351 HideTipBelow();
1353 EditView* pActiveView = pTopView ? pTopView : pTableView;
1354 if ( !pActiveView )
1355 return;
1357 Point aPos;
1358 if (pInputWin && pInputWin->GetEditView() == pActiveView)
1360 pTipVisibleSecParent = pInputWin->GetEditWindow();
1361 aPos = pInputWin->GetCursorScreenPixelPos(true);
1363 else
1365 pTipVisibleSecParent = pActiveView->GetWindow();
1366 if (vcl::Cursor* pCur = pActiveView->GetCursor())
1368 Point aLogicPos = pCur->GetPos();
1369 aLogicPos.AdjustY(pCur->GetHeight() );
1370 aPos = pTipVisibleSecParent->LogicToPixel( aLogicPos );
1372 aPos = pTipVisibleSecParent->OutputToScreenPixel( aPos );
1375 tools::Rectangle aRect( aPos, aPos );
1376 QuickHelpFlags const nAlign = QuickHelpFlags::Left | QuickHelpFlags::Top | QuickHelpFlags::NoEvadePointer;
1377 nTipVisibleSec = Help::ShowPopover(pTipVisibleSecParent, aRect, rText, nAlign);
1378 pTipVisibleSecParent->AddEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleSecParentListener ) );
1381 bool ScInputHandler::GetFuncName( OUString& aStart, OUString& aResult )
1383 if ( aStart.isEmpty() )
1384 return false;
1386 aStart = ScGlobal::getCharClass().uppercase( aStart );
1387 sal_Int32 nPos = aStart.getLength() - 1;
1388 sal_Unicode c = aStart[ nPos ];
1389 // fdo#75264 use maFormulaChar to check if characters are used in function names
1390 ::std::set< sal_Unicode >::const_iterator p = maFormulaChar.find( c );
1391 if ( p == maFormulaChar.end() )
1392 return false; // last character is not part of any function name, quit
1394 ::std::vector<sal_Unicode> aTemp { c };
1395 for(sal_Int32 i = nPos - 1; i >= 0; --i)
1397 c = aStart[ i ];
1398 p = maFormulaChar.find( c );
1400 if (p == maFormulaChar.end())
1401 break;
1403 aTemp.push_back( c );
1406 ::std::vector<sal_Unicode>::reverse_iterator rIt = aTemp.rbegin();
1407 aResult = OUString( *rIt++ );
1408 while ( rIt != aTemp.rend() )
1409 aResult += OUStringChar( *rIt++ );
1411 return true;
1414 namespace {
1415 /// Rid ourselves of unwanted " quoted json characters.
1416 OString escapeJSON(const OUString &aStr)
1418 OUString aEscaped = aStr;
1419 aEscaped = aEscaped.replaceAll("\n", " ");
1420 aEscaped = aEscaped.replaceAll("\"", "'");
1421 return OUStringToOString(aEscaped, RTL_TEXTENCODING_UTF8);
1425 void ScInputHandler::ShowFuncList( const ::std::vector< OUString > & rFuncStrVec )
1427 const SfxViewShell* pViewShell = SfxViewShell::Current();
1428 if (comphelper::LibreOfficeKit::isActive())
1430 if (rFuncStrVec.size() && pViewShell)
1432 auto aPos = pFormulaData->begin();
1433 sal_uInt32 nCurIndex = std::distance(aPos, miAutoPosFormula);
1434 const sal_uInt32 nSize = pFormulaData->size();
1436 OUString aFuncNameStr;
1437 OUString aDescFuncNameStr;
1438 OStringBuffer aPayload("[ ");
1439 for (const OUString& rFunc : rFuncStrVec)
1441 if ( rFunc[rFunc.getLength()-1] == cParenthesesReplacement )
1443 aFuncNameStr = rFunc.copy(0, rFunc.getLength()-1);
1445 else
1447 aFuncNameStr = rFunc;
1450 FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
1451 aDescFuncNameStr = aFuncNameStr + "()";
1452 sal_Int32 nNextFStart = 0;
1453 const IFunctionDescription* ppFDesc;
1454 ::std::vector< OUString > aArgs;
1455 OUString eqPlusFuncName = "=" + aDescFuncNameStr;
1456 if ( aHelper.GetNextFunc( eqPlusFuncName, false, nNextFStart, nullptr, &ppFDesc, &aArgs ) )
1458 if ( !ppFDesc->getFunctionName().isEmpty() )
1460 aPayload.append("{"
1461 "\"index\": "
1462 + OString::number(static_cast<sal_Int64>(nCurIndex))
1463 + ", "
1464 "\"signature\": \""
1465 + escapeJSON(ppFDesc->getSignature())
1466 + "\", "
1467 "\"description\": \""
1468 + escapeJSON(ppFDesc->getDescription())
1469 + "\", \"namedRange\": false }, ");
1471 else
1473 aPayload.append("{"
1474 "\"index\": "
1475 + OString::number(static_cast<sal_Int64>(nCurIndex))
1476 + ", "
1477 "\"signature\": \""
1478 + escapeJSON(aFuncNameStr)
1479 + "\", "
1480 "\"description\": \""
1481 + escapeJSON(OUString())
1482 + "\", \"namedRange\": true }, ");
1485 ++nCurIndex;
1486 if (nCurIndex == nSize)
1487 nCurIndex = 0;
1489 sal_Int32 nLen = aPayload.getLength();
1490 if (nLen <= 2)
1492 aPayload[nLen - 1] = ']';
1494 else
1496 aPayload[nLen - 2] = ' ';
1497 aPayload[nLen - 1] = ']';
1500 OString s = aPayload.makeStringAndClear();
1501 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CALC_FUNCTION_LIST, s);
1503 return;
1506 OUStringBuffer aTipStr;
1507 OUString aFuncNameStr;
1508 OUString aDescFuncNameStr;
1509 ::std::vector<OUString>::const_iterator itStr = rFuncStrVec.begin();
1510 sal_Int32 nMaxFindNumber = 3;
1511 sal_Int32 nRemainFindNumber = nMaxFindNumber;
1512 for ( ; itStr != rFuncStrVec.end(); ++itStr )
1514 const OUString& rFunc = *itStr;
1515 if ( rFunc[rFunc.getLength()-1] == cParenthesesReplacement )
1517 aFuncNameStr = rFunc.copy(0, rFunc.getLength()-1);
1519 else
1521 aFuncNameStr = rFunc;
1523 if ( itStr == rFuncStrVec.begin() )
1525 aTipStr = "[";
1526 aDescFuncNameStr = aFuncNameStr + "()";
1528 else
1530 aTipStr.append(", ");
1532 aTipStr.append(aFuncNameStr);
1533 if ( itStr == rFuncStrVec.begin() )
1534 aTipStr.append("]");
1535 if ( --nRemainFindNumber <= 0 )
1536 break;
1538 sal_Int32 nRemainNumber = rFuncStrVec.size() - nMaxFindNumber;
1539 if ( nRemainFindNumber == 0 && nRemainNumber > 0 )
1541 OUString aMessage( ScResId( STR_FUNCTIONS_FOUND ) );
1542 aMessage = aMessage.replaceFirst("%2", OUString::number(nRemainNumber));
1543 aMessage = aMessage.replaceFirst("%1", aTipStr);
1544 aTipStr = aMessage;
1546 FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
1547 sal_Int32 nNextFStart = 0;
1548 const IFunctionDescription* ppFDesc;
1549 ::std::vector< OUString > aArgs;
1550 OUString eqPlusFuncName = "=" + aDescFuncNameStr;
1551 if ( aHelper.GetNextFunc( eqPlusFuncName, false, nNextFStart, nullptr, &ppFDesc, &aArgs ) )
1553 if ( !ppFDesc->getFunctionName().isEmpty() )
1555 aTipStr.append(" : " + ppFDesc->getDescription());
1558 ShowTip( aTipStr.makeStringAndClear() );
1561 void ScInputHandler::UseFormulaData()
1563 EditView* pActiveView = pTopView ? pTopView : pTableView;
1565 /* TODO: MLFORMULA: this should work also with multi-line formulas. */
1566 if ( !(pActiveView && pFormulaData && mpEditEngine->GetParagraphCount() == 1) )
1567 return;
1569 OUString aParagraph = mpEditEngine->GetText( 0 );
1570 ESelection aSel = pActiveView->GetSelection();
1571 aSel.Adjust();
1573 // Due to differences between table and input cell (e.g clipboard with line breaks),
1574 // the selection may not be in line with the EditEngine anymore.
1575 // Just return without any indication as to why.
1576 if (aSel.end.nIndex > aParagraph.getLength())
1577 return;
1579 if ( aParagraph.getLength() > aSel.end.nIndex &&
1580 ( ScGlobal::getCharClass().isLetterNumeric( aParagraph, aSel.end.nIndex ) ||
1581 aParagraph[ aSel.end.nIndex ] == '_' ||
1582 aParagraph[ aSel.end.nIndex ] == '.' ||
1583 aParagraph[ aSel.end.nIndex ] == '$' ) )
1584 return;
1586 // Is the cursor at the end of a word?
1587 if (aSel.end.nIndex <= 0)
1588 return;
1590 OUString aSelText(aParagraph.copy(0, aSel.end.nIndex));
1592 OUString aText;
1593 if ( GetFuncName( aSelText, aText ) )
1595 // function name is incomplete:
1596 // show matching functions name as tip above cell
1597 ::std::vector<OUString> aNewVec;
1598 miAutoPosFormula = pFormulaData->end();
1599 miAutoPosFormula = findTextAll(*pFormulaData, miAutoPosFormula, aText, aNewVec, false);
1600 if (miAutoPosFormula != pFormulaData->end())
1602 // check if partial function name is not between quotes
1603 sal_Unicode cBetweenQuotes = 0;
1604 for ( int n = 0; n < aSelText.getLength(); n++ )
1606 if (cBetweenQuotes)
1608 if (aSelText[n] == cBetweenQuotes)
1609 cBetweenQuotes = 0;
1611 else if ( aSelText[ n ] == '"' )
1612 cBetweenQuotes = '"';
1613 else if ( aSelText[ n ] == '\'' )
1614 cBetweenQuotes = '\'';
1616 if ( cBetweenQuotes )
1617 return; // we're between quotes
1619 ShowFuncList(aNewVec);
1620 aAutoSearch = aText;
1622 return;
1625 // function name is complete:
1626 // show tip below the cell with function name and arguments of function
1627 ShowArgumentsTip( aSelText );
1630 void ScInputHandler::NextFormulaEntry( bool bBack )
1632 EditView* pActiveView = pTopView ? pTopView : pTableView;
1633 if ( pActiveView && pFormulaData )
1635 ::std::vector<OUString> aNewVec;
1636 ScTypedCaseStrSet::const_iterator itNew = findTextAll(*pFormulaData, miAutoPosFormula, aAutoSearch, aNewVec, bBack);
1637 if (itNew != pFormulaData->end())
1639 miAutoPosFormula = itNew;
1640 ShowFuncList( aNewVec );
1644 // For Tab we always call HideCursor first
1645 if (pActiveView)
1646 pActiveView->ShowCursor();
1649 namespace {
1651 void completeFunction( EditView* pView, const OUString& rInsert, bool& rParInserted )
1653 if (!pView)
1654 return;
1656 ESelection aSel = pView->GetSelection();
1658 bool bNoInitialLetter = false;
1659 OUString aOld = pView->getEditEngine().GetText(0);
1660 // in case we want just insert a function and not completing
1661 if ( comphelper::LibreOfficeKit::isActive() )
1663 ESelection aSelRange = aSel;
1664 --aSelRange.start.nIndex;
1665 --aSelRange.end.nIndex;
1666 pView->SetSelection(aSelRange);
1667 pView->SelectCurrentWord();
1669 if ( aOld == "=" )
1671 bNoInitialLetter = true;
1672 aSelRange.start.nIndex = 1;
1673 aSelRange.end.nIndex = 1;
1674 pView->SetSelection(aSelRange);
1676 else if ( pView->GetSelected().startsWith("()") )
1678 bNoInitialLetter = true;
1679 ++aSelRange.start.nIndex;
1680 ++aSelRange.end.nIndex;
1681 pView->SetSelection(aSelRange);
1685 if(!bNoInitialLetter)
1687 const sal_Int32 nMinLen = std::max(aSel.end.nIndex - aSel.start.nIndex, sal_Int32(1));
1688 // Since transliteration service is used to test for match, the replaced string could be
1689 // longer than rInsert, so in order to find longest match before the cursor, test whole
1690 // string from start to current cursor position (don't limit to length of rInsert)
1691 // Disclaimer: I really don't know if a match longer than rInsert is actually possible,
1692 // so the above is based on assumptions how "transliteration" might possibly work. If
1693 // it's in fact impossible, an optimization would be useful to limit aSel.start.nPos to
1694 // std::max(sal_Int32(0), aSel.end.nIndex - rInsert.getLength()).
1695 aSel.start.nIndex = 0;
1696 pView->SetSelection(aSel);
1697 const OUString aAll = pView->GetSelected();
1698 OUString aMatch;
1699 for (sal_Int32 n = aAll.getLength(); n >= nMinLen && aMatch.isEmpty(); --n)
1701 const OUString aTest = aAll.copy(aAll.getLength() - n); // n trailing chars
1702 if (ScGlobal::GetTransliteration().isMatch(aTest, rInsert))
1703 aMatch = aTest; // Found => break the loop
1706 aSel.start.nIndex = aSel.end.nIndex - aMatch.getLength();
1707 pView->SetSelection(aSel);
1710 OUString aInsStr = rInsert;
1711 sal_Int32 nInsLen = aInsStr.getLength();
1712 bool bDoParen = ( nInsLen > 1 && aInsStr[nInsLen-2] == '('
1713 && aInsStr[nInsLen-1] == ')' );
1714 if ( bDoParen )
1716 // Do not insert parentheses after function names if there already are some
1717 // (e.g. if the function name was edited).
1718 ESelection aWordSel = pView->GetSelection();
1720 // aWordSel.EndPos points one behind string if word at end
1721 if (aWordSel.end.nIndex < aOld.getLength())
1723 sal_Unicode cNext = aOld[aWordSel.end.nIndex];
1724 if ( cNext == '(' )
1726 bDoParen = false;
1727 aInsStr = aInsStr.copy( 0, nInsLen - 2 ); // Skip parentheses
1732 pView->InsertText( aInsStr );
1734 if ( bDoParen ) // Put cursor between parentheses
1736 aSel = pView->GetSelection();
1737 --aSel.start.nIndex;
1738 --aSel.end.nIndex;
1739 pView->SetSelection(aSel);
1741 rParInserted = true;
1747 void ScInputHandler::PasteFunctionData()
1749 if (pFormulaData && miAutoPosFormula != pFormulaData->end())
1751 const ScTypedStrData& rData = *miAutoPosFormula;
1752 OUString aInsert = rData.GetString();
1753 if (aInsert[aInsert.getLength()-1] == cParenthesesReplacement)
1754 aInsert = OUString::Concat(aInsert.subView( 0, aInsert.getLength()-1)) + "()";
1755 bool bParInserted = false;
1757 DataChanging(); // Cannot be new
1758 completeFunction( pTopView, aInsert, bParInserted );
1759 completeFunction( pTableView, aInsert, bParInserted );
1760 DataChanged();
1761 ShowTipCursor();
1763 if (bParInserted)
1764 AutoParAdded();
1767 HideTip();
1769 EditView* pActiveView = pTopView ? pTopView : pTableView;
1770 if (comphelper::LibreOfficeKit::isActive() && pTopView && pInputWin)
1771 pInputWin->TextGrabFocus();
1772 if (pActiveView)
1773 pActiveView->ShowCursor();
1776 void ScInputHandler::LOKPasteFunctionData(const OUString& rFunctionName)
1778 // in case we have no top view try to create it
1779 if (!pTopView && pInputWin)
1781 ScInputMode eCurMode = eMode;
1782 SetMode(SC_INPUT_TOP);
1783 if (!pTopView)
1784 SetMode(eCurMode);
1787 EditView* pEditView = pTopView ? pTopView : pTableView;
1789 if (!pActiveViewSh || !pEditView)
1790 return;
1792 bool bEdit = false;
1793 OUString aFormula;
1794 EditEngine const& rEditEngine = pEditView->getEditEngine();
1796 aFormula = rEditEngine.GetText(0);
1797 /* TODO: LOK: are you sure you want '+' and '-' let start formulas with
1798 * function names? That was meant for "data typist" numeric keyboard
1799 * input. */
1800 bEdit = aFormula.getLength() > 1 && (aFormula[0] == '=' || aFormula[0] == '+' || aFormula[0] == '-');
1803 if ( !bEdit )
1805 OUString aNewFormula('=');
1806 if ( aFormula.startsWith("=") )
1807 aNewFormula = aFormula;
1809 InputReplaceSelection( aNewFormula );
1812 if (pFormulaData)
1814 OUString aNew;
1815 ScTypedCaseStrSet::const_iterator aPos = findText(*pFormulaData, pFormulaData->begin(), rFunctionName, aNew, /* backward = */false);
1817 if (aPos != pFormulaData->end())
1819 miAutoPosFormula = aPos;
1820 PasteFunctionData();
1825 void ScTabViewShell::LOKSendFormulabarUpdate(EditView* pActiveView,
1826 const OUString& rText,
1827 const ESelection& rSelection)
1829 OUString aSelection;
1830 if (pActiveView)
1832 aSelection = OUString::number(pActiveView->GetPosWithField(0, rSelection.start.nIndex)) + ";" +
1833 OUString::number(pActiveView->GetPosWithField(0, rSelection.end.nIndex)) + ";" +
1834 OUString::number(rSelection.start.nPara) + ";" + OUString::number(rSelection.end.nPara);
1836 else
1838 aSelection = OUString::number(rSelection.start.nIndex) + ";" + OUString::number(rSelection.end.nIndex) + ";" +
1839 OUString::number(rSelection.start.nPara) + ";" + OUString::number(rSelection.end.nPara);
1842 sal_uInt64 nCurrentShellId = reinterpret_cast<sal_uInt64>(this);
1844 // We can get three updates per keystroke, StartExtTextInput, ExtTextInput and PostExtTextInput
1845 // Skip duplicate updates. Be conservative and don't skip duplicates that are 5+ seconds
1846 // apart.
1847 std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
1848 if (maSendFormulabarUpdate.m_nShellId == nCurrentShellId &&
1849 maSendFormulabarUpdate.m_aText == rText &&
1850 maSendFormulabarUpdate.m_aSelection == aSelection &&
1851 std::chrono::duration_cast<std::chrono::seconds>(
1852 now - maSendFormulabarUpdate.m_nTimeStamp) < std::chrono::seconds(5))
1854 return;
1857 maSendFormulabarUpdate.m_nShellId = nCurrentShellId;
1858 maSendFormulabarUpdate.m_aText = rText;
1859 maSendFormulabarUpdate.m_aSelection = aSelection;
1860 maSendFormulabarUpdate.m_nTimeStamp = now;
1861 maSendFormulabarUpdate.Send();
1864 void ScTabViewShell::SendFormulabarUpdate::Send()
1866 std::unique_ptr<jsdialog::ActionDataMap> pData = std::make_unique<jsdialog::ActionDataMap>();
1867 (*pData)["action_type"_ostr] = "setText";
1868 (*pData)["text"_ostr] = m_aText;
1869 (*pData)["selection"_ostr] = m_aSelection;
1870 OUString sWindowId = OUString::number(m_nShellId) + "formulabar";
1871 jsdialog::SendAction(sWindowId, u"sc_input_window"_ustr, std::move(pData));
1874 // Calculate selection and display as tip help
1875 static OUString lcl_Calculate( const OUString& rFormula, ScDocument& rDoc, const ScAddress &rPos )
1877 //TODO: Merge with ScFormulaDlg::CalcValue and move into Document!
1878 // Quotation marks for Strings are only inserted here.
1880 if(rFormula.isEmpty())
1881 return OUString();
1883 std::optional<ScSimpleFormulaCalculator> pCalc( std::in_place, rDoc, rPos, rFormula, false );
1885 // FIXME: HACK! In order to not get a #REF! for ColRowNames, if a name is actually inserted as a Range
1886 // into the whole Formula, but is interpreted as a single cell reference when displaying it on its own
1887 bool bColRowName = pCalc->HasColRowName();
1888 if ( bColRowName )
1890 // ColRowName in RPN code?
1891 if ( pCalc->GetCode()->GetCodeLen() <= 1 )
1892 { // ==1: Single one is as a Parameter always a Range
1893 // ==0: It might be one, if ...
1894 OUString aBraced = "(" + rFormula + ")";
1895 pCalc.emplace( rDoc, rPos, aBraced, false );
1897 else
1898 bColRowName = false;
1901 FormulaError nErrCode = pCalc->GetErrCode();
1902 if ( nErrCode != FormulaError::NONE )
1903 return ScGlobal::GetErrorString(nErrCode);
1905 SvNumberFormatter& aFormatter = *rDoc.GetFormatTable();
1906 OUString aValue;
1907 if ( pCalc->IsValue() )
1909 double n = pCalc->GetValue();
1910 sal_uInt32 nFormat = aFormatter.GetStandardFormat( n, 0,
1911 pCalc->GetFormatType(), ScGlobal::eLnge );
1912 aValue = aFormatter.GetInputLineString( n, nFormat );
1913 //! display OutputString but insert InputLineString
1915 else
1917 OUString aStr = pCalc->GetString().getString();
1918 sal_uInt32 nFormat = aFormatter.GetStandardFormat(
1919 pCalc->GetFormatType(), ScGlobal::eLnge);
1921 const Color* pColor;
1922 aFormatter.GetOutputString( aStr, nFormat,
1923 aValue, &pColor );
1926 aValue = "\"" + aValue + "\"";
1927 //! Escape quotation marks in String??
1930 ScRange aTestRange;
1931 if ( bColRowName || (aTestRange.Parse(rFormula, rDoc) & ScRefFlags::VALID) )
1932 aValue += " ...";
1934 return aValue;
1937 void ScInputHandler::FormulaPreview()
1939 OUString aValue;
1940 EditView* pActiveView = pTopView ? pTopView : pTableView;
1941 if ( pActiveView && pActiveViewSh )
1943 OUString aPart = pActiveView->GetSelected();
1944 if (aPart.isEmpty())
1945 aPart = mpEditEngine->GetText(0);
1946 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
1947 aValue = lcl_Calculate( aPart, rDoc, aCursorPos );
1950 if (!aValue.isEmpty())
1952 ShowTip( aValue ); // Display as QuickHelp
1953 aManualTip = aValue; // Set after ShowTip
1954 if (pFormulaData)
1955 miAutoPosFormula = pFormulaData->end();
1956 if (pColumnData)
1957 miAutoPosColumn = pColumnData->end();
1961 void ScInputHandler::PasteManualTip()
1963 // Three dots at the end -> Range reference -> do not insert
1964 // FIXME: Once we have matrix constants, we can change this
1965 sal_Int32 nTipLen = aManualTip.getLength();
1966 sal_uInt32 const nTipLen2(sal::static_int_cast<sal_uInt32>(nTipLen));
1967 if ( nTipLen && ( nTipLen < 3 || aManualTip.subView( nTipLen2-3 ) != u"..." ) )
1969 DataChanging(); // Cannot be new
1971 OUString aInsert = aManualTip;
1972 EditView* pActiveView = pTopView ? pTopView : pTableView;
1973 if (!pActiveView->HasSelection())
1975 // Nothing selected -> select everything
1976 sal_Int32 nOldLen = mpEditEngine->GetTextLen(0);
1977 ESelection aAllSel( 0, 0, 0, nOldLen );
1978 if ( pTopView )
1979 pTopView->SetSelection( aAllSel );
1980 if ( pTableView )
1981 pTableView->SetSelection( aAllSel );
1984 ESelection aSel = pActiveView->GetSelection();
1985 aSel.Adjust();
1986 OSL_ENSURE( !aSel.start.nPara && !aSel.end.nPara, "Too many paragraphs in Formula" );
1987 if ( !aSel.start.nIndex ) // Selection from the start?
1989 if ( aSel.end.nIndex == mpEditEngine->GetTextLen(0) )
1991 // Everything selected -> skip quotation marks
1992 if ( aInsert[0] == '"' )
1993 aInsert = aInsert.copy(1);
1994 sal_Int32 nInsLen = aInsert.getLength();
1995 if ( aInsert.endsWith("\"") )
1996 aInsert = aInsert.copy( 0, nInsLen-1 );
1998 else if ( aSel.end.nIndex )
2000 // Not everything selected -> do not overwrite equality sign
2001 //FIXME: Even double equality signs??
2002 aSel.start.nIndex = 1;
2003 if ( pTopView )
2004 pTopView->SetSelection( aSel );
2005 if ( pTableView )
2006 pTableView->SetSelection( aSel );
2009 if ( pTopView )
2010 pTopView->InsertText( aInsert, true );
2011 if ( pTableView )
2012 pTableView->InsertText( aInsert, true );
2014 DataChanged();
2017 HideTip();
2020 void ScInputHandler::ResetAutoPar()
2022 nAutoPar = 0;
2025 void ScInputHandler::AutoParAdded()
2027 ++nAutoPar; // Closing parenthesis can be overwritten
2030 bool ScInputHandler::CursorAtClosingPar()
2032 // Test if the cursor is before a closing parenthesis
2033 // Selection from SetReference has been removed before
2034 EditView* pActiveView = pTopView ? pTopView : pTableView;
2035 if ( pActiveView && !pActiveView->HasSelection() && bFormulaMode )
2037 ESelection aSel = pActiveView->GetSelection();
2038 sal_Int32 nPos = aSel.start.nIndex;
2039 OUString aFormula = mpEditEngine->GetText(0);
2040 if ( nPos < aFormula.getLength() && aFormula[nPos] == ')' )
2041 return true;
2043 return false;
2046 void ScInputHandler::SkipClosingPar()
2048 // this is called when a ')' is typed and the cursor is before a ')'
2049 // that can be overwritten -> just set the cursor behind the ')'
2051 EditView* pActiveView = pTopView ? pTopView : pTableView;
2052 if (pActiveView)
2054 ESelection aSel = pActiveView->GetSelection();
2055 ++aSel.start.nIndex;
2056 ++aSel.end.nIndex;
2058 // this is in a formula (only one paragraph), so the selection
2059 // can be used directly for the TopView
2061 if ( pTopView )
2062 pTopView->SetSelection( aSel );
2063 if ( pTableView )
2064 pTableView->SetSelection( aSel );
2067 OSL_ENSURE(nAutoPar, "SkipClosingPar: count is wrong");
2068 --nAutoPar;
2071 // Auto input
2073 void ScInputHandler::GetColData()
2075 if ( !pActiveViewSh )
2076 return;
2078 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
2080 if ( pColumnData )
2081 pColumnData->clear();
2082 else
2083 pColumnData.reset( new ScTypedCaseStrSet );
2085 std::vector<ScTypedStrData> aEntries;
2086 rDoc.GetDataEntries(
2087 aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab(), aEntries);
2088 if (!aEntries.empty())
2089 pColumnData->insert(aEntries.begin(), aEntries.end());
2091 miAutoPosColumn = pColumnData->end();
2094 void ScInputHandler::UseColData() // When typing
2096 EditView* pActiveView = pTopView ? pTopView : pTableView;
2097 if ( !(pActiveView && pColumnData) )
2098 return;
2100 // Only change when cursor is at the end
2101 ESelection aSel = pActiveView->GetSelection();
2102 aSel.Adjust();
2104 sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
2105 if (aSel.end.nPara + 1 != nParCnt)
2106 return;
2108 sal_Int32 nParLen = mpEditEngine->GetTextLen(aSel.end.nPara);
2109 if (aSel.end.nIndex != nParLen)
2110 return;
2112 OUString aText = GetEditText(mpEditEngine.get());
2113 if (aText.isEmpty())
2114 return;
2116 std::vector< OUString > aResultVec;
2117 OUString aNew;
2118 sal_Int32 nLongestPrefixLen = 0;
2119 miAutoPosColumn = pColumnData->end();
2120 mbPartialPrefix = false;
2121 miAutoPosColumn = findTextAll(*pColumnData, miAutoPosColumn, aText, aResultVec, false, &nLongestPrefixLen);
2123 if (nLongestPrefixLen <= 0 || aResultVec.empty())
2124 return;
2126 if (aResultVec.size() > 1)
2128 mbPartialPrefix = true;
2129 bUseTab = true; // Allow Ctrl (+ Shift + ) + TAB cycling.
2130 miAutoPosColumn = pColumnData->end();
2132 // Display the rest of longest common prefix as suggestion.
2133 aNew = aResultVec[0].copy(0, nLongestPrefixLen);
2135 else
2137 aNew = aResultVec[0];
2140 // Strings can contain line endings (e.g. due to dBase import),
2141 // which would result in multiple paragraphs here, which is not desirable.
2142 //! Then GetExactMatch doesn't work either
2143 lcl_RemoveLineEnd( aNew );
2145 // Keep paragraph, just append the rest
2146 //! Exact replacement in EnterHandler !!!
2147 // One Space between paragraphs:
2148 sal_Int32 nEdLen = mpEditEngine->GetTextLen() + nParCnt - 1;
2149 OUString aIns = aNew.copy(nEdLen);
2151 // Selection must be "backwards", so the cursor stays behind the last
2152 // typed character
2153 ESelection aSelection( aSel.end.nPara, aSel.end.nIndex + aIns.getLength(),
2154 aSel.end.nPara, aSel.end.nIndex );
2156 // When editing in input line, apply to both edit views
2157 if ( pTableView )
2159 pTableView->InsertText( aIns );
2160 pTableView->SetSelection( aSelection );
2162 if ( pTopView )
2164 pTopView->InsertText( aIns );
2165 pTopView->SetSelection( aSelection );
2168 aAutoSearch = aText; // To keep searching - nAutoPos is set
2171 void ScInputHandler::NextAutoEntry( bool bBack )
2173 EditView* pActiveView = pTopView ? pTopView : pTableView;
2174 if ( pActiveView && pColumnData )
2176 if (!aAutoSearch.isEmpty())
2178 // Is the selection still valid (could be changed via the mouse)?
2179 ESelection aSel = pActiveView->GetSelection();
2180 aSel.Adjust();
2181 sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
2182 if ( aSel.end.nPara+1 == nParCnt && aSel.start.nPara == aSel.end.nPara )
2184 OUString aText = GetEditText(mpEditEngine.get());
2185 sal_Int32 nSelLen = aSel.end.nIndex - aSel.start.nIndex;
2186 sal_Int32 nParLen = mpEditEngine->GetTextLen( aSel.end.nPara );
2187 if ( aSel.end.nIndex == nParLen && aText.getLength() == aAutoSearch.getLength() + nSelLen )
2189 OUString aNew;
2190 ScTypedCaseStrSet::const_iterator itNew =
2191 findText(*pColumnData, miAutoPosColumn, aAutoSearch, aNew, bBack);
2193 if (itNew != pColumnData->end())
2195 // match found!
2196 miAutoPosColumn = itNew;
2197 bInOwnChange = true; // disable ModifyHdl (reset below)
2198 mbPartialPrefix = false;
2200 lcl_RemoveLineEnd( aNew );
2201 OUString aIns = aNew.copy(aAutoSearch.getLength());
2203 // when editing in input line, apply to both edit views
2204 if ( pTableView )
2206 pTableView->DeleteSelected();
2207 pTableView->InsertText( aIns );
2208 pTableView->SetSelection( ESelection(
2209 aSel.end.nPara, aSel.start.nIndex + aIns.getLength(),
2210 aSel.end.nPara, aSel.start.nIndex ) );
2212 if ( pTopView )
2214 pTopView->DeleteSelected();
2215 pTopView->InsertText( aIns );
2216 pTopView->SetSelection( ESelection(
2217 aSel.end.nPara, aSel.start.nIndex + aIns.getLength(),
2218 aSel.end.nPara, aSel.start.nIndex ) );
2221 bInOwnChange = false;
2228 // For Tab, HideCursor was always called first
2229 if (pActiveView)
2230 pActiveView->ShowCursor();
2233 // Highlight parentheses
2234 void ScInputHandler::UpdateParenthesis()
2236 // Find parentheses
2237 //TODO: Can we disable parentheses highlighting per parentheses?
2238 bool bFound = false;
2239 if ( bFormulaMode && eMode != SC_INPUT_TOP )
2241 if ( pTableView && !pTableView->HasSelection() ) // Selection is always at the bottom
2243 ESelection aSel = pTableView->GetSelection();
2244 if (aSel.start.nIndex)
2246 // Examine character left to the cursor
2247 sal_Int32 nPos = aSel.start.nIndex - 1;
2248 OUString aFormula = mpEditEngine->GetText(aSel.start.nPara);
2249 sal_Unicode c = aFormula[nPos];
2250 if ( c == '(' || c == ')' )
2252 // Note this matches only within one paragraph.
2253 sal_Int32 nOther = lcl_MatchParenthesis( aFormula, nPos );
2254 if ( nOther != -1 )
2256 SfxItemSet aSet( mpEditEngine->GetEmptyItemSet() );
2257 aSet.Put( SvxWeightItem( WEIGHT_BOLD, EE_CHAR_WEIGHT ) );
2259 //! Distinguish if cell is already highlighted!!!!
2260 if (bParenthesisShown)
2262 // Remove old highlighting
2263 sal_Int32 nCount = mpEditEngine->GetParagraphCount();
2264 for (sal_Int32 i=0; i<nCount; i++)
2265 mpEditEngine->RemoveCharAttribs( i, EE_CHAR_WEIGHT );
2268 ESelection aSelThis(aSel.start.nPara, nPos, aSel.start.nPara, nPos + 1);
2269 mpEditEngine->QuickSetAttribs( aSet, aSelThis );
2270 ESelection aSelOther(aSel.start.nPara, nOther, aSel.start.nPara, nOther + 1);
2271 mpEditEngine->QuickSetAttribs( aSet, aSelOther );
2273 // Dummy InsertText for Update and Paint (selection is empty)
2274 pTableView->InsertText( OUString() );
2276 bFound = true;
2281 // mark parenthesis right of cursor if it will be overwritten (nAutoPar)
2282 // with different color (COL_LIGHTBLUE) ??
2286 // Remove old highlighting, if no new one is set
2287 if ( bParenthesisShown && !bFound && pTableView )
2289 sal_Int32 nCount = mpEditEngine->GetParagraphCount();
2290 for (sal_Int32 i=0; i<nCount; i++)
2291 pTableView->RemoveCharAttribs( i, EE_CHAR_WEIGHT );
2294 bParenthesisShown = bFound;
2297 void ScInputHandler::ViewShellGone(const ScTabViewShell* pViewSh) // Executed synchronously!
2299 if ( pViewSh == pActiveViewSh )
2301 pLastState.reset();
2302 pLastPattern = nullptr;
2305 ScModule* mod = ScModule::get();
2306 if ( pViewSh == pRefViewSh )
2308 //! The input from the EnterHandler does not arrive anymore
2309 // We end the EditMode anyways
2310 EnterHandler();
2311 bFormulaMode = false;
2312 pRefViewSh = nullptr;
2313 SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
2314 mod->SetRefInputHdl(nullptr);
2315 if (pInputWin)
2316 pInputWin->SetFormulaMode(false);
2317 UpdateAutoCorrFlag();
2320 pActiveViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() );
2322 if ( pActiveViewSh && pActiveViewSh == pViewSh )
2324 OSL_FAIL("pActiveViewSh is gone");
2325 pActiveViewSh = nullptr;
2328 if (mod->GetInputOptions().GetTextWysiwyg())
2329 UpdateRefDevice(); // Don't keep old document's printer as RefDevice
2332 void ScInputHandler::UpdateActiveView()
2334 ImplCreateEditEngine();
2336 // #i20588# Don't rely on focus to find the active edit view. Instead, the
2337 // active pane at the start of editing is now stored (GetEditActivePart).
2338 // GetActiveWin (the currently active pane) fails for ref input across the
2339 // panes of a split view.
2341 vcl::Window* pShellWin = pActiveViewSh ?
2342 pActiveViewSh->GetWindowByPos( pActiveViewSh->GetViewData().GetEditActivePart() ) :
2343 nullptr;
2345 sal_uInt16 nCount = mpEditEngine->GetViewCount();
2346 if (nCount > 0)
2348 pTableView = mpEditEngine->GetView();
2349 for (sal_uInt16 i=1; i<nCount; i++)
2351 EditView* pThis = mpEditEngine->GetView(i);
2352 vcl::Window* pWin = pThis->GetWindow();
2353 if ( pWin==pShellWin )
2354 pTableView = pThis;
2357 else
2358 pTableView = nullptr;
2360 // setup the pTableView editeng for tiled rendering to get cursor and selections
2361 if (pTableView && pActiveViewSh)
2363 if (comphelper::LibreOfficeKit::isActive())
2365 pTableView->RegisterViewShell(pActiveViewSh);
2369 if (pInputWin && (eMode == SC_INPUT_TOP || eMode == SC_INPUT_TABLE))
2371 // tdf#71409: Always create the edit engine instance for the input
2372 // window, in order to properly manage accessibility events.
2373 pTopView = pInputWin->GetEditView();
2374 if (eMode != SC_INPUT_TOP)
2375 pTopView = nullptr;
2377 else
2378 pTopView = nullptr;
2381 void ScInputHandler::SetInputWindow( ScInputWindow* pNew )
2383 pInputWin = pNew;
2386 void ScInputHandler::StopInputWinEngine( bool bAll )
2388 if (pInputWin && !pInputWin->isDisposed())
2389 pInputWin->StopEditEngine( bAll );
2391 pTopView = nullptr; // invalid now
2394 EditView* ScInputHandler::GetActiveView()
2396 UpdateActiveView();
2397 return pTopView ? pTopView : pTableView;
2400 void ScInputHandler::ForgetLastPattern()
2402 pLastPattern = nullptr;
2403 if ( !pLastState && pActiveViewSh )
2404 pActiveViewSh->UpdateInputHandler( true ); // Get status again
2405 else
2406 NotifyChange( pLastState.get(), true );
2409 void ScInputHandler::UpdateAdjust( sal_Unicode cTyped )
2411 SvxAdjust eSvxAdjust;
2412 switch (eAttrAdjust)
2414 case SvxCellHorJustify::Standard:
2416 bool bNumber = false;
2417 if (cTyped) // Restarted
2418 bNumber = (cTyped>='0' && cTyped<='9'); // Only ciphers are numbers
2419 else if ( pActiveViewSh )
2421 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
2422 bNumber = ( rDoc.GetCellType( aCursorPos ) == CELLTYPE_VALUE );
2424 eSvxAdjust = bNumber ? SvxAdjust::Right : SvxAdjust::Left;
2426 break;
2427 case SvxCellHorJustify::Block:
2428 eSvxAdjust = SvxAdjust::Block;
2429 break;
2430 case SvxCellHorJustify::Center:
2431 eSvxAdjust = SvxAdjust::Center;
2432 break;
2433 case SvxCellHorJustify::Right:
2434 eSvxAdjust = SvxAdjust::Right;
2435 break;
2436 default: // SvxCellHorJustify::Left
2437 eSvxAdjust = SvxAdjust::Left;
2438 break;
2441 bool bAsianVertical = pLastPattern &&
2442 pLastPattern->GetItem( ATTR_STACKED ).GetValue() &&
2443 pLastPattern->GetItem( ATTR_VERTICAL_ASIAN ).GetValue();
2444 if ( bAsianVertical )
2446 // Always edit at top of cell -> LEFT when editing vertically
2447 eSvxAdjust = SvxAdjust::Left;
2450 pEditDefaults->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );
2451 mpEditEngine->SetDefaults( *pEditDefaults );
2453 if ( pActiveViewSh )
2455 pActiveViewSh->GetViewData().SetEditAdjust( eSvxAdjust );
2457 mpEditEngine->SetVertical( bAsianVertical );
2460 void ScInputHandler::RemoveAdjust()
2462 // Delete hard alignment attributes
2463 bool bUndo = mpEditEngine->IsUndoEnabled();
2464 if ( bUndo )
2465 mpEditEngine->EnableUndo( false );
2467 // Non-default paragraph attributes (e.g. from clipboard)
2468 // must be turned into character attributes
2469 mpEditEngine->RemoveParaAttribs();
2471 if ( bUndo )
2472 mpEditEngine->EnableUndo( true );
2476 void ScInputHandler::RemoveRangeFinder()
2478 // Delete pRangeFindList and colors
2479 mpEditEngine->SetUpdateLayout(false);
2480 sal_Int32 nCount = mpEditEngine->GetParagraphCount(); // Could just have been inserted
2481 for (sal_Int32 i=0; i<nCount; i++)
2482 mpEditEngine->RemoveCharAttribs( i, EE_CHAR_COLOR );
2483 mpEditEngine->SetUpdateLayout(true);
2485 EditView* pActiveView = pTopView ? pTopView : pTableView;
2486 pActiveView->ShowCursor( false );
2488 DeleteRangeFinder(); // Deletes the list and the labels on the table
2491 bool ScInputHandler::StartTable( sal_Unicode cTyped, bool bFromCommand, bool bInputActivated,
2492 ScEditEngineDefaulter* pTopEngine )
2494 bool bNewTable = false;
2496 if (bModified)
2497 return false;
2499 if (pActiveViewSh)
2501 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
2503 if (!rDoc.ValidCol(aCursorPos.Col()))
2504 return false;
2506 ImplCreateEditEngine();
2507 UpdateActiveView();
2508 SyncViews();
2511 const ScMarkData& rMark = pActiveViewSh->GetViewData().GetMarkData();
2512 ScEditableTester aTester;
2513 if ( rMark.IsMarked() || rMark.IsMultiMarked() )
2514 aTester.TestSelection( rDoc, rMark );
2515 else
2516 aTester.TestSelectedBlock(
2517 rDoc, aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Col(), aCursorPos.Row(), rMark );
2519 bool bStartInputMode = !(pActiveViewSh->GetViewShell() && pActiveViewSh->GetViewShell()->IsLokReadOnlyView());
2521 if (!aTester.IsEditable())
2523 bProtected = true;
2524 // We allow read-only input mode activation regardless
2525 // whether it's part of an array or not or whether explicit cell
2526 // activation is requested (double-click or F2) or a click in input
2527 // line.
2528 bool bShowError = (!bInputActivated || !aTester.GetMessageId() || aTester.GetMessageId() != STR_PROTECTIONERR) &&
2529 !pActiveViewSh->GetViewData().GetDocShell()->IsReadOnly();
2530 if (bShowError)
2532 eMode = SC_INPUT_NONE;
2533 StopInputWinEngine( true );
2534 UpdateFormulaMode();
2535 if ( pActiveViewSh && ( !bFromCommand || !bCommandErrorShown ) )
2537 // Prevent repeated error messages for the same cell from command events
2538 // (for keyboard events, multiple messages are wanted).
2539 // Set the flag before showing the error message because the command handler
2540 // for the next IME command may be called when showing the dialog.
2541 if ( bFromCommand )
2542 bCommandErrorShown = true;
2544 pActiveViewSh->GetActiveWin()->GrabFocus();
2545 pActiveViewSh->ErrorMessage(aTester.GetMessageId());
2547 bStartInputMode = false;
2551 if (bStartInputMode)
2553 // UpdateMode is enabled again in ScViewData::SetEditEngine (and not needed otherwise)
2554 mpEditEngine->SetUpdateLayout( false );
2556 // Take over attributes in EditEngine
2557 const ScPatternAttr* pPattern = rDoc.GetPattern( aCursorPos.Col(),
2558 aCursorPos.Row(),
2559 aCursorPos.Tab() );
2560 if (!ScPatternAttr::areSame(pPattern, pLastPattern))
2562 // Percent format?
2563 const SfxItemSet& rAttrSet = pPattern->GetItemSet();
2565 if ( const SfxUInt32Item* pItem = rAttrSet.GetItemIfSet( ATTR_VALUE_FORMAT ) )
2567 sal_uInt32 nFormat = pItem->GetValue();
2568 if (SvNumFormatType::PERCENT == rDoc.GetFormatTable()->GetType( nFormat ))
2569 nCellPercentFormatDecSep = rDoc.GetFormatTable()->GetFormatDecimalSep( nFormat).toChar();
2570 else
2571 nCellPercentFormatDecSep = 0;
2573 else
2574 nCellPercentFormatDecSep = 0; // Default: no percent
2576 // Validity specified?
2577 if ( const SfxUInt32Item* pItem = rAttrSet.GetItemIfSet( ATTR_VALIDDATA ) )
2578 nValidation = pItem->GetValue();
2579 else
2580 nValidation = 0;
2582 // EditEngine Defaults
2583 // In no case SetParaAttribs, because the EditEngine might already
2584 // be filled (for Edit cells).
2585 // SetParaAttribs would change the content.
2587 //! The SetDefaults is now (since MUST/src602
2588 //! EditEngine changes) implemented as a SetParaAttribs.
2589 //! Any problems?
2591 pPattern->FillEditItemSet( pEditDefaults.get() );
2592 mpEditEngine->SetDefaults( *pEditDefaults );
2593 pLastPattern = pPattern;
2594 bLastIsSymbol = pPattern->IsSymbolFont();
2596 // Background color must be known for automatic font color.
2597 // For transparent cell background, the document background color must be used.
2599 Color aBackCol = pPattern->GetItem( ATTR_BACKGROUND ).GetColor();
2600 ScModule* pScMod = ScModule::get();
2601 if ( aBackCol.IsTransparent() ||
2602 Application::GetSettings().GetStyleSettings().GetHighContrastMode() )
2603 aBackCol = pScMod->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
2604 mpEditEngine->SetBackgroundColor( aBackCol );
2606 // Adjustment
2607 eAttrAdjust = pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue();
2608 if ( eAttrAdjust == SvxCellHorJustify::Repeat &&
2609 pPattern->GetItem(ATTR_LINEBREAK).GetValue() )
2611 // #i31843# "repeat" with "line breaks" is treated as default alignment
2612 eAttrAdjust = SvxCellHorJustify::Standard;
2616 if (pTopEngine)
2618 // Necessary to sync SvxAutoCorrect behavior. This has to be
2619 // done before InitRangeFinder() below.
2620 MergeLanguageAttributes( *pTopEngine);
2623 // UpdateSpellSettings enables online spelling if needed
2624 // -> also call if attributes are unchanged
2625 UpdateSpellSettings( true ); // uses pLastPattern
2627 // Fill EditEngine
2628 OUString aStr;
2629 if (bTextValid)
2631 mpEditEngine->SetTextCurrentDefaults(aCurrentText);
2632 aStr = aCurrentText;
2633 bTextValid = false;
2634 aCurrentText.clear();
2636 else
2637 aStr = GetEditText(mpEditEngine.get());
2639 // cTyped!=0 is overtyping, not editing.
2640 mbEditingExistingContent = !cTyped && !aStr.isEmpty();
2642 if (aStr.startsWith("{=") && aStr.endsWith("}") ) // Matrix formula?
2644 aStr = aStr.copy(1, aStr.getLength() -2);
2645 mpEditEngine->SetTextCurrentDefaults(aStr);
2646 if ( pInputWin )
2647 pInputWin->SetTextString(aStr, true);
2650 UpdateAdjust( cTyped );
2652 if (ScModule::get()->GetAppOptions().GetAutoComplete())
2653 GetColData();
2655 if (!cTyped && !bCreatingFuncView && StartsLikeFormula(aStr))
2656 InitRangeFinder(aStr); // Formula is being edited -> RangeFinder
2658 bNewTable = true; // -> PostEditView Call
2662 if (!bProtected && pInputWin)
2663 pInputWin->SetOkCancelMode();
2665 return bNewTable;
2668 void ScInputHandler::MergeLanguageAttributes( ScEditEngineDefaulter& rDestEngine ) const
2670 const SfxItemSet& rSrcSet = mpEditEngine->GetDefaults();
2671 rDestEngine.SetDefaultItem( rSrcSet.Get( EE_CHAR_LANGUAGE ));
2672 rDestEngine.SetDefaultItem( rSrcSet.Get( EE_CHAR_LANGUAGE_CJK ));
2673 rDestEngine.SetDefaultItem( rSrcSet.Get( EE_CHAR_LANGUAGE_CTL ));
2676 static void lcl_SetTopSelection( EditView* pEditView, ESelection& rSel )
2678 OSL_ENSURE( rSel.start.nPara==0 && rSel.end.nPara==0, "SetTopSelection: Para != 0" );
2680 EditEngine& rEngine = pEditView->getEditEngine();
2681 sal_Int32 nCount = rEngine.GetParagraphCount();
2682 if (nCount > 1)
2684 sal_Int32 nParLen = rEngine.GetTextLen(rSel.start.nPara);
2685 while (rSel.start.nIndex > nParLen && rSel.start.nPara + 1 < nCount)
2687 rSel.start.nIndex -= nParLen + 1; // Including space from line break
2688 nParLen = rEngine.GetTextLen(++rSel.start.nPara);
2691 nParLen = rEngine.GetTextLen(rSel.end.nPara);
2692 while (rSel.end.nIndex > nParLen && rSel.end.nPara + 1 < nCount)
2694 rSel.end.nIndex -= nParLen + 1; // Including space from line break
2695 nParLen = rEngine.GetTextLen(++rSel.end.nPara);
2699 ESelection aSel = pEditView->GetSelection();
2701 if (rSel != aSel)
2702 pEditView->SetSelection( rSel );
2705 void ScInputHandler::SyncViews( const EditView* pSourceView )
2707 if (pSourceView)
2709 bool bSelectionForTopView = false;
2710 if (pTopView && pTopView != pSourceView)
2711 bSelectionForTopView = true;
2712 bool bSelectionForTableView = false;
2713 if (pTableView && pTableView != pSourceView)
2714 bSelectionForTableView = true;
2715 if (bSelectionForTopView || bSelectionForTableView)
2717 ESelection aSel(pSourceView->GetSelection());
2718 if (bSelectionForTopView)
2719 pTopView->SetSelection(aSel);
2720 if (bSelectionForTableView)
2721 lcl_SetTopSelection(pTableView, aSel);
2724 // Only sync selection from topView if we are actually editing there
2725 else if (pTopView && pTableView)
2727 ESelection aSel(pTopView->GetSelection());
2728 lcl_SetTopSelection( pTableView, aSel );
2732 IMPL_LINK_NOARG(ScInputHandler, ModifyHdl, LinkParamNone*, void)
2734 if ( !bInOwnChange && ( eMode==SC_INPUT_TYPE || eMode==SC_INPUT_TABLE ) &&
2735 mpEditEngine && mpEditEngine->IsUpdateLayout() && pInputWin )
2737 // Update input line from ModifyHdl for changes that are not
2738 // wrapped by DataChanging/DataChanged calls (like Drag&Drop)
2739 OUString aText(ScEditUtil::GetMultilineString(*mpEditEngine));
2740 lcl_RemoveTabs(aText);
2741 pInputWin->SetTextString(aText, true);
2746 * @return true means new view created
2748 bool ScInputHandler::DataChanging( sal_Unicode cTyped, bool bFromCommand )
2750 if (pActiveViewSh)
2751 pActiveViewSh->GetViewData().SetPasteMode( ScPasteFlags::NONE );
2752 bInOwnChange = true; // disable ModifyHdl (reset in DataChanged)
2754 if ( eMode == SC_INPUT_NONE )
2755 return StartTable( cTyped, bFromCommand, false, nullptr );
2756 else
2757 return false;
2760 void ScInputHandler::DataChanged( bool bFromTopNotify, bool bSetModified )
2762 ImplCreateEditEngine();
2764 if (eMode==SC_INPUT_NONE)
2765 eMode = SC_INPUT_TYPE;
2767 if ( eMode == SC_INPUT_TOP && pTopView && !bFromTopNotify )
2769 // table EditEngine is formatted below, input line needs formatting after paste
2770 // #i20282# not when called from the input line's modify handler
2771 pTopView->getEditEngine().QuickFormatDoc( true );
2773 // #i23720# QuickFormatDoc hides the cursor, but can't show it again because it
2774 // can't safely access the EditEngine's current view, so the cursor has to be
2775 // shown again here.
2776 pTopView->ShowCursor();
2779 if (bSetModified)
2780 bModified = true;
2781 bSelIsRef = false;
2783 if ( pRangeFindList && !bInRangeUpdate )
2784 RemoveRangeFinder(); // Delete attributes and labels
2786 UpdateParenthesis(); // Highlight parentheses anew
2788 const bool bUpdateKit = comphelper::LibreOfficeKit::isActive() && pActiveViewSh && pInputWin;
2790 if (eMode==SC_INPUT_TYPE || eMode==SC_INPUT_TABLE)
2792 OUString aText;
2793 if (pInputWin)
2794 aText = ScEditUtil::GetMultilineString(*mpEditEngine);
2795 else
2796 aText = GetEditText(mpEditEngine.get());
2797 lcl_RemoveTabs(aText);
2799 if (pInputWin)
2801 // If we will end up updating LoKit at the end, we can skip it here
2802 pInputWin->SetTextString(aText, !bUpdateKit);
2806 // If the cursor is before the end of a paragraph, parts are being pushed to
2807 // the right (independently from the eMode) -> Adapt View!
2808 // If the cursor is at the end, the StatusHandler of the ViewData is sufficient.
2810 // First make sure the status handler is called now if the cursor
2811 // is outside the visible area
2812 mpEditEngine->QuickFormatDoc();
2814 EditView* pActiveView = pTopView ? pTopView : pTableView;
2815 ESelection aSel;
2816 if (pActiveView && pActiveViewSh)
2818 ScViewData& rViewData = pActiveViewSh->GetViewData();
2820 bool bNeedGrow = ( rViewData.GetEditAdjust() != SvxAdjust::Left ); // Always right-aligned
2821 if (!bNeedGrow)
2823 // Cursor before the end?
2824 aSel = pActiveView->GetSelection();
2825 aSel.Adjust();
2826 bNeedGrow = (aSel.end.nIndex != mpEditEngine->GetTextLen(aSel.end.nPara));
2828 if (!bNeedGrow)
2830 bNeedGrow = rViewData.GetDocument().IsLayoutRTL( rViewData.GetTabNo() );
2832 if (bNeedGrow)
2834 // Adjust inplace view
2835 rViewData.EditGrowY();
2836 rViewData.EditGrowX();
2840 if (bUpdateKit)
2842 UpdateActiveView();
2843 if (pActiveView)
2844 aSel = pActiveView->GetSelection();
2846 OUString aText = ScEditUtil::GetMultilineString(*mpEditEngine);
2847 pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_FORMULA, aText.toUtf8());
2848 pActiveViewSh->LOKSendFormulabarUpdate(pActiveView,
2849 aText,
2850 aSel);
2853 UpdateFormulaMode();
2854 bTextValid = false; // Changes only in the EditEngine
2855 bInOwnChange = false;
2858 bool ScInputHandler::StartsLikeFormula( std::u16string_view rStr ) const
2860 // For new input '+' and '-' may start the dreaded "lazy data typist"
2861 // formula input, editing existing formula content can only start with '='.
2862 return !rStr.empty() && (rStr[0] == '=' || (!mbEditingExistingContent && (rStr[0] == '+' || rStr[0] == '-')));
2865 void ScInputHandler::UpdateFormulaMode()
2867 SfxApplication* pSfxApp = SfxGetpApp();
2869 bool bIsFormula = !bProtected;
2870 if (bIsFormula)
2872 const OUString aText = mpEditEngine->GetText(0);
2873 bIsFormula = StartsLikeFormula(aText);
2876 if ( bIsFormula )
2878 if (!bFormulaMode)
2880 pActiveViewSh->GetViewData().SetEditHighlight(true);
2881 bFormulaMode = true;
2882 pRefViewSh = pActiveViewSh;
2883 pSfxApp->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
2884 ScModule* pMod = ScModule::get();
2885 pMod->SetRefInputHdl(this);
2886 if (pInputWin)
2887 pInputWin->SetFormulaMode(true);
2889 // in LOK, we always need to perform the GetFormulaData() call so
2890 // that the formula insertion works
2891 if (comphelper::LibreOfficeKit::isActive() || pMod->GetAppOptions().GetAutoComplete())
2892 GetFormulaData();
2894 UpdateParenthesis();
2895 UpdateAutoCorrFlag();
2898 else // Deactivate
2900 if (bFormulaMode)
2902 pActiveViewSh->GetViewData().SetEditHighlight(false);
2903 ShowRefFrame();
2904 bFormulaMode = false;
2905 pRefViewSh = nullptr;
2906 pSfxApp->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
2907 ScModule::get()->SetRefInputHdl(nullptr);
2908 if (pInputWin)
2909 pInputWin->SetFormulaMode(false);
2910 UpdateAutoCorrFlag();
2915 void ScInputHandler::ShowRefFrame()
2917 // Modifying pActiveViewSh here would interfere with the bInEnterHandler / bRepeat
2918 // checks in NotifyChange, and lead to keeping the wrong value in pActiveViewSh.
2919 // A local variable is used instead.
2920 ScTabViewShell* pVisibleSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() );
2921 if ( !(pRefViewSh && pRefViewSh != pVisibleSh) )
2922 return;
2924 bool bFound = false;
2925 SfxViewFrame& rRefFrame = pRefViewSh->GetViewFrame();
2926 SfxViewFrame* pOneFrame = SfxViewFrame::GetFirst();
2927 while ( pOneFrame && !bFound )
2929 if ( pOneFrame == &rRefFrame )
2930 bFound = true;
2931 pOneFrame = SfxViewFrame::GetNext( *pOneFrame );
2934 if (bFound)
2936 // We count on Activate working synchronously here
2937 // (pActiveViewSh is set while doing so)
2938 pRefViewSh->SetActive(); // Appear and SetViewFrame
2940 // pLastState is set correctly in the NotifyChange from the Activate
2942 else
2944 OSL_FAIL("ViewFrame for reference input is not here anymore");
2948 void ScInputHandler::RemoveSelection()
2950 EditView* pActiveView = pTopView ? pTopView : pTableView;
2951 if (!pActiveView)
2952 return;
2954 ESelection aSel = pActiveView->GetSelection();
2955 aSel.CollapseToEnd();
2956 if (pTableView)
2957 pTableView->SetSelection( aSel );
2958 if (pTopView)
2959 pTopView->SetSelection( aSel );
2962 void ScInputHandler::InvalidateAttribs()
2964 SfxViewFrame* pViewFrm = SfxViewFrame::Current();
2965 if (!pViewFrm)
2966 return;
2968 SfxBindings& rBindings = pViewFrm->GetBindings();
2970 rBindings.Invalidate( SID_ATTR_CHAR_FONT );
2971 rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT );
2972 rBindings.Invalidate( SID_ATTR_CHAR_COLOR );
2974 rBindings.Invalidate( SID_ATTR_CHAR_WEIGHT );
2975 rBindings.Invalidate( SID_ATTR_CHAR_POSTURE );
2976 rBindings.Invalidate( SID_ATTR_CHAR_UNDERLINE );
2977 rBindings.Invalidate( SID_ATTR_CHAR_OVERLINE );
2978 rBindings.Invalidate( SID_ULINE_VAL_NONE );
2979 rBindings.Invalidate( SID_ULINE_VAL_SINGLE );
2980 rBindings.Invalidate( SID_ULINE_VAL_DOUBLE );
2981 rBindings.Invalidate( SID_ULINE_VAL_DOTTED );
2983 rBindings.Invalidate( SID_HYPERLINK_GETLINK );
2985 rBindings.Invalidate( SID_ATTR_CHAR_KERNING );
2986 rBindings.Invalidate( SID_SET_SUPER_SCRIPT );
2987 rBindings.Invalidate( SID_SET_SUB_SCRIPT );
2988 rBindings.Invalidate( SID_ATTR_CHAR_STRIKEOUT );
2989 rBindings.Invalidate( SID_ATTR_CHAR_SHADOWED );
2991 rBindings.Invalidate( SID_SAVEDOC );
2992 rBindings.Invalidate( SID_DOC_MODIFIED );
2995 // --------------- public methods --------------------------------------------
2997 void ScInputHandler::SetMode( ScInputMode eNewMode, const OUString* pInitText, ScEditEngineDefaulter* pTopEngine )
2999 if ( eMode == eNewMode )
3000 return;
3002 ImplCreateEditEngine();
3004 if (bProtected)
3006 eMode = SC_INPUT_NONE;
3007 StopInputWinEngine( true );
3008 if (pActiveViewSh)
3009 pActiveViewSh->GetActiveWin()->GrabFocus();
3010 return;
3013 if (eNewMode != SC_INPUT_NONE && pActiveViewSh)
3014 // Disable paste mode when edit mode starts.
3015 pActiveViewSh->GetViewData().SetPasteMode( ScPasteFlags::NONE );
3017 bInOwnChange = true; // disable ModifyHdl (reset below)
3019 ScInputMode eOldMode = eMode;
3020 eMode = eNewMode;
3021 if (eOldMode == SC_INPUT_TOP && eNewMode != eOldMode)
3022 StopInputWinEngine( false );
3024 if (eMode==SC_INPUT_TOP || eMode==SC_INPUT_TABLE)
3026 if (eOldMode == SC_INPUT_NONE) // not if switching between modes
3028 if (StartTable(0, false, eMode == SC_INPUT_TABLE, pTopEngine))
3030 if (pActiveViewSh)
3031 pActiveViewSh->GetViewData().GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
3035 if (pInitText)
3037 mpEditEngine->SetTextCurrentDefaults(*pInitText);
3038 bModified = true;
3041 sal_Int32 nPara = mpEditEngine->GetParagraphCount()-1;
3042 sal_Int32 nLen = mpEditEngine->GetText(nPara).getLength();
3043 sal_uInt16 nCount = mpEditEngine->GetViewCount();
3045 for (sal_uInt16 i=0; i<nCount; i++)
3047 if ( eMode == SC_INPUT_TABLE && eOldMode == SC_INPUT_TOP )
3049 // Keep Selection
3051 else
3053 mpEditEngine->GetView(i)->SetSelection(ESelection(nPara, nLen));
3055 mpEditEngine->GetView(i)->ShowCursor(false);
3059 UpdateActiveView();
3060 if (eMode==SC_INPUT_TABLE || eMode==SC_INPUT_TYPE)
3062 if (pTableView)
3063 pTableView->SetEditEngineUpdateLayout(true);
3064 pActiveViewSh->GetViewData().SetEditHighlight(true);
3066 else
3068 if (pTopView)
3069 pTopView->SetEditEngineUpdateLayout(true);
3072 if (eNewMode != eOldMode)
3073 UpdateFormulaMode();
3075 bInOwnChange = false;
3079 * @return true if rString only contains digits (no autocorrect then)
3081 static bool lcl_IsNumber(std::u16string_view aString)
3083 size_t nLen = aString.size();
3084 for (size_t i=0; i<nLen; i++)
3086 sal_Unicode c = aString[i];
3087 if ( c < '0' || c > '9' )
3088 return false;
3090 return true;
3093 static void lcl_SelectionToEnd( EditView* pView )
3095 if ( pView )
3096 pView->SetSelection(ESelection::AtEnd());
3099 void ScInputHandler::EnterHandler( ScEnterMode nBlockMode, bool bBeforeSavingInLOK )
3101 if (!mbDocumentDisposing && comphelper::LibreOfficeKit::isActive()
3102 && pActiveViewSh != SfxViewShell::Current())
3103 return;
3105 if (!pActiveViewSh)
3106 return;
3108 // Macro calls for validity can cause a lot of problems, so inhibit
3109 // nested calls of EnterHandler().
3110 if (bInEnterHandler) return;
3111 bInEnterHandler = true;
3112 bInOwnChange = true; // disable ModifyHdl (reset below)
3113 mbPartialPrefix = false;
3115 ImplCreateEditEngine();
3117 bool bMatrix = ( nBlockMode == ScEnterMode::MATRIX );
3119 SfxApplication* pSfxApp = SfxGetpApp();
3120 std::unique_ptr<EditTextObject> pObject;
3121 std::unique_ptr<ScPatternAttr> pCellAttrs;
3122 bool bForget = false; // Remove due to validity?
3124 OUString aString = GetEditText(mpEditEngine.get());
3125 OUString aPreAutoCorrectString(aString);
3126 EditView* pActiveView = pTopView ? pTopView : pTableView;
3127 if (bModified && pActiveView && !aString.isEmpty() && !lcl_IsNumber(aString))
3129 if (pColumnData && miAutoPosColumn != pColumnData->end())
3131 // #i47125# If AutoInput appended something, do the final AutoCorrect
3132 // with the cursor at the end of the input.
3133 lcl_SelectionToEnd(pTopView);
3134 lcl_SelectionToEnd(pTableView);
3137 vcl::Window* pFrameWin = pActiveViewSh->GetFrameWin();
3139 if (pTopView)
3140 pTopView->CompleteAutoCorrect(); // CompleteAutoCorrect for both Views
3141 if (pTableView)
3142 pTableView->CompleteAutoCorrect(pFrameWin);
3143 aString = GetEditText(mpEditEngine.get());
3145 lcl_RemoveTabs(aString);
3146 lcl_RemoveTabs(aPreAutoCorrectString);
3148 // Test if valid (always with simple string)
3149 if (bModified && nValidation)
3151 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocument();
3152 const ScValidationData* pData = rDoc.GetValidationEntry( nValidation );
3153 if (pData)
3155 // #i67990# don't use pLastPattern in EnterHandler
3156 const ScPatternAttr* pPattern = rDoc.GetPattern( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab() );
3158 bool bOk;
3160 if (pData->GetDataMode() == SC_VALID_CUSTOM)
3162 bOk = pData->IsDataValidCustom( aString, *pPattern, aCursorPos, ScValidationData::CustomValidationPrivateAccess() );
3164 else
3166 bOk = pData->IsDataValid( aString, *pPattern, aCursorPos );
3169 if (!bOk)
3171 pActiveViewSh->StopMarking(); // (the InfoBox consumes the MouseButtonUp)
3173 // tdf#125917 Release the grab that a current mouse-down event being handled
3174 // by ScTabView has put on the mouse via its SelectionEngine.
3175 // Otherwise the warning box cannot interact with the mouse
3176 if (ScTabView* pView = pActiveViewSh->GetViewData().GetView())
3178 if (ScViewSelectionEngine* pSelEngine = pView->GetSelEngine())
3179 pSelEngine->ReleaseMouse();
3182 if (bBeforeSavingInLOK)
3184 // Invalid entry but not applied to the document model.
3185 // Exit to complete the "save", leaving the edit view as it is
3186 // for the user to continue after save.
3187 bInOwnChange = false;
3188 bInEnterHandler = false;
3189 return;
3192 if (pData->DoError(pActiveViewSh->GetFrameWeld(), aString, aCursorPos))
3193 bForget = true; // Do not take over input
3199 // Check for input into DataPilot table
3200 if ( bModified && !bForget )
3202 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocument();
3203 ScDPObject* pDPObj = rDoc.GetDPAtCursor( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab() );
3204 if ( pDPObj )
3206 // Any input within the DataPilot table is either a valid renaming
3207 // or an invalid action - normal cell input is always aborted
3208 pActiveViewSh->DataPilotInput( aCursorPos, aString );
3209 bForget = true;
3213 std::vector<editeng::MisspellRanges> aMisspellRanges;
3214 // UpdateLayout must be true during CompleteOnlineSpelling
3215 const bool bUpdateLayout = mpEditEngine->SetUpdateLayout( true );
3216 mpEditEngine->CompleteOnlineSpelling();
3217 bool bSpellErrors = !bFormulaMode && mpEditEngine->HasOnlineSpellErrors();
3218 if ( bSpellErrors )
3220 // #i3820# If the spell checker flags numerical input as error,
3221 // it still has to be treated as number, not EditEngine object.
3222 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocument();
3223 // #i67990# don't use pLastPattern in EnterHandler
3224 const ScPatternAttr* pPattern = rDoc.GetPattern( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab() );
3225 if (pPattern)
3227 SvNumberFormatter* pFormatter = rDoc.GetFormatTable();
3228 // without conditional format, as in ScColumn::SetString
3229 sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
3230 double nVal;
3231 if ( pFormatter->IsNumberFormat( aString, nFormat, nVal ) )
3233 bSpellErrors = false; // ignore the spelling errors
3238 // After RemoveAdjust, the EditView must not be repainted (has wrong font size etc).
3239 // SetUpdateLayout must come after CompleteOnlineSpelling.
3240 // The view is hidden in any case below (Broadcast).
3241 mpEditEngine->SetUpdateLayout( false );
3243 if ( bModified && !bForget ) // What is being entered (text/object)?
3245 sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
3246 if ( nParCnt == 0 )
3247 nParCnt = 1;
3249 bool bUniformAttribs = true;
3250 SfxItemSet aPara1Attribs = mpEditEngine->GetAttribs(0, 0, mpEditEngine->GetTextLen(0));
3251 for (sal_Int32 nPara = 1; nPara < nParCnt; ++nPara)
3253 SfxItemSet aPara2Attribs = mpEditEngine->GetAttribs(nPara, 0, mpEditEngine->GetTextLen(nPara));
3254 if (!(aPara1Attribs == aPara2Attribs))
3256 // Paragraph format different from that of the 1st paragraph.
3257 bUniformAttribs = false;
3258 break;
3262 SfxItemSet aOldAttribs = mpEditEngine->GetAttribs(ESelection::All());
3263 const SfxPoolItem* pItem = nullptr;
3265 // Find common (cell) attributes before RemoveAdjust
3266 if ( bUniformAttribs )
3268 std::optional<SfxItemSet> pCommonAttrs;
3269 for (sal_uInt16 nId = EE_CHAR_START; nId <= EE_CHAR_END; nId++)
3271 SfxItemState eState = aOldAttribs.GetItemState( nId, false, &pItem );
3272 if ( eState == SfxItemState::SET &&
3273 nId != EE_CHAR_ESCAPEMENT && nId != EE_CHAR_PAIRKERNING &&
3274 nId != EE_CHAR_KERNING && nId != EE_CHAR_XMLATTRIBS &&
3275 *pItem != pEditDefaults->Get(nId) )
3277 if ( !pCommonAttrs )
3278 pCommonAttrs.emplace( mpEditEngine->GetEmptyItemSet() );
3279 pCommonAttrs->Put( *pItem );
3283 if ( pCommonAttrs )
3285 ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocument();
3286 pCellAttrs = std::make_unique<ScPatternAttr>(rDoc.getCellAttributeHelper());
3287 pCellAttrs->GetFromEditItemSet( &*pCommonAttrs );
3291 // Clear ParaAttribs (including adjustment)
3292 RemoveAdjust();
3294 bool bAttrib = false; // Formatting present?
3296 // check if EditObject is needed
3297 if (nParCnt > 1)
3298 bAttrib = true;
3299 else
3301 for (sal_uInt16 nId = EE_CHAR_START; nId <= EE_CHAR_END && !bAttrib; nId++)
3303 SfxItemState eState = aOldAttribs.GetItemState( nId, false, &pItem );
3304 if (eState == SfxItemState::INVALID)
3305 bAttrib = true;
3306 else if (eState == SfxItemState::SET)
3308 // Keep same items in EditEngine as in ScEditAttrTester
3309 if ( nId == EE_CHAR_ESCAPEMENT || nId == EE_CHAR_PAIRKERNING ||
3310 nId == EE_CHAR_KERNING || nId == EE_CHAR_XMLATTRIBS )
3312 if ( *pItem != pEditDefaults->Get(nId) )
3313 bAttrib = true;
3318 // Contains fields?
3319 SfxItemState eFieldState = aOldAttribs.GetItemState( EE_FEATURE_FIELD, false );
3320 if ( eFieldState == SfxItemState::INVALID || eFieldState == SfxItemState::SET )
3321 bAttrib = true;
3323 // Not converted characters?
3324 SfxItemState eConvState = aOldAttribs.GetItemState( EE_FEATURE_NOTCONV, false );
3325 if ( eConvState == SfxItemState::INVALID || eConvState == SfxItemState::SET )
3326 bAttrib = true;
3328 // Always recognize formulas as formulas
3329 // We still need the preceding test due to cell attributes
3332 if (bSpellErrors)
3333 mpEditEngine->GetAllMisspellRanges(aMisspellRanges);
3335 if (bMatrix)
3336 bAttrib = false;
3338 if (bAttrib)
3340 mpEditEngine->ClearSpellErrors();
3341 pObject = mpEditEngine->CreateTextObject();
3343 else if (ScModule::get()->GetAppOptions().GetAutoComplete()) // Adjust Upper/Lower case
3345 // Perform case-matching only when the typed text is partial.
3346 if (pColumnData && aAutoSearch.getLength() < aString.getLength())
3347 aString = getExactMatch(*pColumnData, aString);
3351 // Don't rely on ShowRefFrame switching the active view synchronously
3352 // execute the function directly on the correct view's bindings instead
3353 // pRefViewSh is reset in ShowRefFrame - get pointer before ShowRefFrame call
3354 ScTabViewShell* pExecuteSh = pRefViewSh ? pRefViewSh : pActiveViewSh;
3356 if (bFormulaMode)
3358 ShowRefFrame();
3360 if (pExecuteSh)
3362 pExecuteSh->SetTabNo(aCursorPos.Tab());
3363 pExecuteSh->ActiveGrabFocus();
3366 bFormulaMode = false;
3367 pSfxApp->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
3368 ScModule::get()->SetRefInputHdl(nullptr);
3369 if (pInputWin)
3370 pInputWin->SetFormulaMode(false);
3371 UpdateAutoCorrFlag();
3373 pRefViewSh = nullptr; // Also without FormulaMode due to FunctionsAutoPilot
3374 DeleteRangeFinder();
3375 ResetAutoPar();
3377 bool bOldMod = bModified;
3379 bModified = false;
3380 bSelIsRef = false;
3381 eMode = SC_INPUT_NONE;
3382 StopInputWinEngine(true);
3384 // Text input (through number formats) or ApplySelectionPattern modify
3385 // the cell's attributes, so pLastPattern is no longer valid
3386 pLastPattern = nullptr;
3388 if (bOldMod && !bProtected && !bForget)
3390 bool bInsertPreCorrectedString = true;
3391 // No typographic quotes in formulas
3392 if (aString.startsWith("="))
3394 SvxAutoCorrect* pAuto = SvxAutoCorrCfg::Get().GetAutoCorrect();
3395 if ( pAuto )
3397 bInsertPreCorrectedString = false;
3398 OUString aReplace(pAuto->GetStartDoubleQuote());
3399 if( aReplace.isEmpty() )
3400 aReplace = ScGlobal::getLocaleData().getDoubleQuotationMarkStart();
3401 if( aReplace != "\"" )
3402 aString = aString.replaceAll( aReplace, "\"" );
3404 aReplace = OUString(pAuto->GetEndDoubleQuote());
3405 if( aReplace.isEmpty() )
3406 aReplace = ScGlobal::getLocaleData().getDoubleQuotationMarkEnd();
3407 if( aReplace != "\"" )
3408 aString = aString.replaceAll( aReplace, "\"" );
3410 aReplace = OUString(pAuto->GetStartSingleQuote());
3411 if( aReplace.isEmpty() )
3412 aReplace = ScGlobal::getLocaleData().getQuotationMarkStart();
3413 if( aReplace != "'" )
3414 aString = aString.replaceAll( aReplace, "'" );
3416 aReplace = OUString(pAuto->GetEndSingleQuote());
3417 if( aReplace.isEmpty() )
3418 aReplace = ScGlobal::getLocaleData().getQuotationMarkEnd();
3419 if( aReplace != "'" )
3420 aString = aString.replaceAll( aReplace, "'");
3424 pSfxApp->Broadcast( SfxHint( SfxHintId::ScKillEditViewNoPaint ) );
3426 if ( pExecuteSh )
3428 SfxBindings& rBindings = pExecuteSh->GetViewFrame().GetBindings();
3430 sal_uInt16 nId = FID_INPUTLINE_ENTER;
3431 if ( nBlockMode == ScEnterMode::BLOCK )
3432 nId = FID_INPUTLINE_BLOCK;
3433 else if ( nBlockMode == ScEnterMode::MATRIX )
3434 nId = FID_INPUTLINE_MATRIX;
3436 const SfxPoolItem* aArgs[2];
3437 aArgs[1] = nullptr;
3439 if ( bInsertPreCorrectedString && aString != aPreAutoCorrectString )
3441 ScInputStatusItem aItem(FID_INPUTLINE_STATUS,
3442 aCursorPos, aCursorPos, aCursorPos,
3443 aPreAutoCorrectString, pObject.get());
3444 aArgs[0] = &aItem;
3445 rBindings.Execute(nId, aArgs);
3448 ScInputStatusItem aItemCorrected(FID_INPUTLINE_STATUS,
3449 aCursorPos, aCursorPos, aCursorPos,
3450 aString, pObject.get());
3451 if ( !aMisspellRanges.empty() )
3452 aItemCorrected.SetMisspellRanges(&aMisspellRanges);
3454 aArgs[0] = &aItemCorrected;
3455 rBindings.Execute(nId, aArgs);
3458 pLastState.reset(); // pLastState still contains the old text
3460 else
3461 pSfxApp->Broadcast( SfxHint( SfxHintId::ScKillEditView ) );
3463 if ( bOldMod && pExecuteSh && pCellAttrs && !bForget )
3465 // Combine with input?
3466 pExecuteSh->ApplySelectionPattern( *pCellAttrs, true );
3467 pExecuteSh->AdjustBlockHeight();
3470 HideTip();
3471 HideTipBelow();
3473 nFormSelStart = nFormSelEnd = 0;
3474 aFormText.clear();
3476 mbEditingExistingContent = false;
3477 bInOwnChange = false;
3478 bInEnterHandler = false;
3479 if (bUpdateLayout)
3480 mpEditEngine->SetUpdateLayout( true );
3483 void ScInputHandler::CancelHandler()
3485 bInOwnChange = true; // Also without FormulaMode due to FunctionsAutoPilot
3487 ImplCreateEditEngine();
3489 bModified = false;
3490 mbPartialPrefix = false;
3491 mbEditingExistingContent = false;
3493 // Don't rely on ShowRefFrame switching the active view synchronously
3494 // execute the function directly on the correct view's bindings instead
3495 // pRefViewSh is reset in ShowRefFrame - get pointer before ShowRefFrame call
3496 ScTabViewShell* pExecuteSh = pRefViewSh ? pRefViewSh : pActiveViewSh;
3498 if (bFormulaMode)
3500 ShowRefFrame();
3501 if (pExecuteSh)
3503 pExecuteSh->SetTabNo(aCursorPos.Tab());
3504 pExecuteSh->ActiveGrabFocus();
3506 bFormulaMode = false;
3507 SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
3508 ScModule::get()->SetRefInputHdl(nullptr);
3509 if (pInputWin)
3510 pInputWin->SetFormulaMode(false);
3511 UpdateAutoCorrFlag();
3513 pRefViewSh = nullptr; // Also without FormulaMode due to FunctionsAutoPilot
3514 DeleteRangeFinder();
3515 ResetAutoPar();
3517 eMode = SC_INPUT_NONE;
3518 StopInputWinEngine( true );
3519 SCCOL nMaxCol(MAXCOL);
3520 if (pExecuteSh)
3522 pExecuteSh->StopEditShell();
3523 nMaxCol = pExecuteSh->GetViewData().GetDocument().MaxCol();
3526 aCursorPos.Set(nMaxCol+1,0,0); // Invalid flag
3527 mpEditEngine->SetTextCurrentDefaults(OUString());
3529 if ( !pLastState && pExecuteSh )
3530 pExecuteSh->UpdateInputHandler( true ); // Update status again
3531 else
3532 NotifyChange( pLastState.get(), true );
3534 nFormSelStart = nFormSelEnd = 0;
3535 aFormText.clear();
3537 bInOwnChange = false;
3539 if ( comphelper::LibreOfficeKit::isActive() && pExecuteSh )
3541 // Clear
3542 std::vector<ReferenceMark> aReferenceMarks;
3543 ScInputHandler::SendReferenceMarks( pActiveViewSh, aReferenceMarks );
3547 bool ScInputHandler::IsModalMode( const SfxObjectShell* pDocSh )
3549 // References to unnamed document; that doesn't work
3550 return bFormulaMode && pRefViewSh
3551 && pRefViewSh->GetViewData().GetDocument().GetDocumentShell() != pDocSh
3552 && !pDocSh->HasName();
3555 void ScInputHandler::AddRefEntry()
3557 const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
3558 UpdateActiveView();
3559 if (!pTableView && !pTopView)
3560 return; // E.g. FillMode
3562 DataChanging(); // Cannot be new
3564 RemoveSelection();
3565 OUString aText = GetEditText(mpEditEngine.get());
3566 sal_Unicode cLastChar = 0;
3567 sal_Int32 nPos = aText.getLength() - 1;
3568 while (nPos >= 0) //checking space
3570 cLastChar = aText[nPos];
3571 if (cLastChar != ' ')
3572 break;
3573 --nPos;
3576 bool bAppendSeparator = (cLastChar != '(' && cLastChar != cSep && cLastChar != '=');
3577 if (bAppendSeparator)
3579 if (pTableView)
3580 pTableView->InsertText( OUString(cSep) );
3581 if (pTopView)
3582 pTopView->InsertText( OUString(cSep) );
3585 DataChanged();
3588 void ScInputHandler::SetReference( const ScRange& rRef, const ScDocument& rDoc )
3590 HideTip();
3592 const ScDocument* pThisDoc = nullptr;
3593 if (pRefViewSh)
3594 pThisDoc = &pRefViewSh->GetViewData().GetDocument();
3595 bool bOtherDoc = (pThisDoc != &rDoc);
3596 if (bOtherDoc && !rDoc.GetDocumentShell()->HasName())
3598 // References to unnamed document; that doesn't work
3599 // SetReference should not be called, then
3600 return;
3602 if (!pThisDoc)
3603 pThisDoc = &rDoc;
3605 UpdateActiveView();
3606 if (!pTableView && !pTopView)
3607 return; // E.g. FillMode
3609 // Never overwrite the "="!
3610 EditView* pActiveView = pTopView ? pTopView : pTableView;
3611 ESelection aSel = pActiveView->GetSelection();
3612 aSel.Adjust();
3613 if (aSel.start.nPara == 0 && aSel.start.nIndex == 0)
3614 return;
3616 DataChanging(); // Cannot be new
3618 // Turn around selection if backwards.
3619 if (pTableView)
3621 ESelection aTabSel = pTableView->GetSelection();
3622 if (aTabSel.start.nIndex > aTabSel.end.nIndex && aTabSel.start.nPara == aTabSel.end.nPara)
3624 aTabSel.Adjust();
3625 pTableView->SetSelection(aTabSel);
3628 if (pTopView)
3630 ESelection aTopSel = pTopView->GetSelection();
3631 if (aTopSel.start.nIndex > aTopSel.end.nIndex && aTopSel.start.nPara == aTopSel.end.nPara)
3633 aTopSel.Adjust();
3634 pTopView->SetSelection(aTopSel);
3638 // Create string from reference, in the syntax of the document being edited.
3639 OUString aRefStr;
3640 const ScAddress::Details aAddrDetails( *pThisDoc, aCursorPos );
3641 if (bOtherDoc)
3643 // Reference to other document
3644 OSL_ENSURE(rRef.aStart.Tab()==rRef.aEnd.Tab(), "nStartTab!=nEndTab");
3646 // Always 3D and absolute.
3647 OUString aTmp(rRef.Format(rDoc, ScRefFlags::VALID | ScRefFlags::TAB_ABS_3D, aAddrDetails));
3649 ScDocShell* pObjSh = rDoc.GetDocumentShell();
3650 // #i75893# convert escaped URL of the document to something user friendly
3651 OUString aFileName = pObjSh->GetMedium()->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::Unambiguous );
3653 switch(aAddrDetails.eConv)
3655 case formula::FormulaGrammar::CONV_XL_A1 :
3656 case formula::FormulaGrammar::CONV_XL_OOX :
3657 case formula::FormulaGrammar::CONV_XL_R1C1 :
3658 aRefStr = "[\'" + aFileName + "']";
3659 break;
3660 case formula::FormulaGrammar::CONV_OOO :
3661 default:
3662 aRefStr = "\'" + aFileName + "'#";
3663 break;
3665 aRefStr += aTmp;
3667 else
3669 if ( rRef.aStart.Tab() != aCursorPos.Tab() ||
3670 rRef.aStart.Tab() != rRef.aEnd.Tab() )
3671 // pointer-selected => absolute sheet reference
3672 aRefStr = rRef.Format(rDoc, ScRefFlags::VALID | ScRefFlags::TAB_ABS_3D, aAddrDetails);
3673 else
3674 aRefStr = rRef.Format(rDoc, ScRefFlags::VALID, aAddrDetails);
3676 bool bLOKShowSelect = true;
3677 if(comphelper::LibreOfficeKit::isActive() && pRefViewSh->GetViewData().GetRefTabNo() != pRefViewSh->GetViewData().GetTabNo())
3678 bLOKShowSelect = false;
3680 if (pTableView || pTopView)
3682 if (pTableView)
3683 pTableView->InsertText( aRefStr, true, bLOKShowSelect );
3684 if (pTopView)
3685 pTopView->InsertText( aRefStr, true, bLOKShowSelect );
3687 DataChanged();
3690 bSelIsRef = true;
3693 void ScInputHandler::InsertFunction( const OUString& rFuncName, bool bAddPar )
3695 if ( eMode == SC_INPUT_NONE )
3697 OSL_FAIL("InsertFunction, not during input mode");
3698 return;
3701 UpdateActiveView();
3702 if (!pTableView && !pTopView)
3703 return; // E.g. FillMode
3705 DataChanging(); // Cannot be new
3707 OUString aText = rFuncName;
3708 if (bAddPar)
3709 aText += "()";
3711 if (pTableView)
3713 pTableView->InsertText( aText );
3714 if (bAddPar)
3716 ESelection aSel = pTableView->GetSelection();
3717 --aSel.start.nIndex;
3718 --aSel.end.nIndex;
3719 pTableView->SetSelection(aSel);
3722 if (pTopView)
3724 pTopView->InsertText( aText );
3725 if (bAddPar)
3727 ESelection aSel = pTopView->GetSelection();
3728 --aSel.start.nIndex;
3729 --aSel.end.nIndex;
3730 pTopView->SetSelection(aSel);
3734 DataChanged();
3736 if (bAddPar)
3737 AutoParAdded();
3740 void ScInputHandler::ClearText()
3742 if ( eMode == SC_INPUT_NONE )
3744 OSL_FAIL("ClearText, not during input mode");
3745 return;
3748 UpdateActiveView();
3749 if (!pTableView && !pTopView)
3750 return; // E.g. FillMode
3752 DataChanging(); // Cannot be new
3754 if (pTableView)
3756 pTableView->getEditEngine().SetText( u""_ustr );
3757 pTableView->SetSelection(ESelection());
3759 if (pTopView)
3761 pTopView->getEditEngine().SetText( u""_ustr );
3762 pTopView->SetSelection(ESelection());
3765 DataChanged();
3768 bool ScInputHandler::KeyInput( const KeyEvent& rKEvt, bool bStartEdit /* = false */ )
3770 vcl::KeyCode aCode = rKEvt.GetKeyCode();
3771 sal_uInt16 nModi = aCode.GetModifier();
3772 bool bShift = aCode.IsShift();
3773 bool bControl = aCode.IsMod1();
3774 bool bAlt = aCode.IsMod2();
3775 sal_uInt16 nCode = aCode.GetCode();
3776 sal_Unicode nChar = rKEvt.GetCharCode();
3778 if (bAlt && !bControl && nCode != KEY_RETURN)
3779 // Alt-Return and Alt-Ctrl-* are accepted. Everything else with ALT are not.
3780 return false;
3782 // There is a partial autocomplete suggestion.
3783 // Allow its completion with right arrow key (without modifiers).
3784 if (mbPartialPrefix && nCode == KEY_RIGHT && !bControl && !bShift && !bAlt &&
3785 (pTopView || pTableView))
3787 if (pTopView)
3788 pTopView->PostKeyEvent(KeyEvent(0, css::awt::Key::MOVE_TO_END_OF_PARAGRAPH));
3789 if (pTableView)
3790 pTableView->PostKeyEvent(KeyEvent(0, css::awt::Key::MOVE_TO_END_OF_PARAGRAPH));
3792 mbPartialPrefix = false;
3794 // Indicate that this event has been consumed and ScTabViewShell should not act on this.
3795 return true;
3798 if (!bControl && nCode == KEY_TAB)
3800 // Normal TAB moves the cursor right.
3801 EnterHandler();
3803 if (pActiveViewSh)
3804 pActiveViewSh->FindNextUnprot( bShift, true );
3806 ScModule* pScMod = ScModule::get();
3807 const ScInputOptions& rOpt = pScMod->GetInputOptions();
3808 const bool bKit = comphelper::LibreOfficeKit::isActive();
3810 if ( (rOpt.GetMoveKeepEdit() && !bKit)
3811 || (pActiveViewSh && pActiveViewSh->GetMoveKeepEdit() && bKit) )
3812 pScMod->SetInputMode( SC_INPUT_TABLE );
3814 return true;
3817 bool bInputLine = ( eMode==SC_INPUT_TOP );
3819 bool bUsed = false;
3820 bool bSkip = false;
3821 bool bDoEnter = false;
3823 switch ( nCode )
3825 case KEY_RETURN:
3826 // New line when in the input line and Shift/Ctrl-Enter is pressed,
3827 // or when in a cell and Ctrl-Enter is pressed.
3828 if ((pInputWin && bInputLine && bControl != bShift) || (!bInputLine && bControl && !bShift))
3830 bDoEnter = true;
3832 else if (nModi == 0 && nTipVisible && pFormulaData && miAutoPosFormula != pFormulaData->end())
3834 PasteFunctionData();
3835 bUsed = true;
3837 else if ( nModi == 0 && nTipVisible && !aManualTip.isEmpty() )
3839 PasteManualTip();
3840 bUsed = true;
3842 else
3844 ScEnterMode nMode = ScEnterMode::NORMAL;
3845 if ( bShift && bControl )
3846 nMode = ScEnterMode::MATRIX;
3847 else if ( bAlt )
3848 nMode = ScEnterMode::BLOCK;
3849 EnterHandler( nMode );
3851 if (pActiveViewSh)
3852 pActiveViewSh->MoveCursorEnter( bShift && !bControl );
3854 ScModule* pScMod = ScModule::get();
3855 const ScInputOptions& rOpt = pScMod->GetInputOptions();
3856 const bool bKit = comphelper::LibreOfficeKit::isActive();
3858 if ( (rOpt.GetMoveKeepEdit() && !bKit)
3859 || (pActiveViewSh && pActiveViewSh->GetMoveKeepEdit() && bKit) )
3860 pScMod->SetInputMode( SC_INPUT_TABLE );
3862 bUsed = true;
3864 break;
3865 case KEY_TAB:
3866 if (bControl && !bAlt)
3868 if (pFormulaData && nTipVisible && miAutoPosFormula != pFormulaData->end())
3870 // Iterate
3871 NextFormulaEntry( bShift );
3872 bUsed = true;
3874 else if (pColumnData && bUseTab)
3876 // Iterate through AutoInput entries
3877 NextAutoEntry( bShift );
3878 bUsed = true;
3881 break;
3882 case KEY_ESCAPE:
3883 if ( nTipVisible )
3885 HideTip();
3886 bUsed = true;
3888 else if( nTipVisibleSec )
3890 HideTipBelow();
3891 bUsed = true;
3893 else if (eMode != SC_INPUT_NONE)
3895 CancelHandler();
3896 bUsed = true;
3898 else
3899 bSkip = true;
3900 break;
3901 case KEY_F2:
3902 if ( !bShift && !bControl && !bAlt && eMode == SC_INPUT_TABLE )
3904 eMode = SC_INPUT_TYPE;
3905 bUsed = true;
3907 break;
3910 // Only execute cursor keys if already in EditMode
3911 // E.g. due to Shift-Ctrl-PageDn (not defined as an accelerator)
3912 bool bCursorKey = EditEngine::DoesKeyMoveCursor(rKEvt);
3913 bool bInsKey = ( nCode == KEY_INSERT && !nModi ); // Treat Insert like Cursorkeys
3914 if ( !bUsed && !bSkip && ( bDoEnter || EditEngine::DoesKeyChangeText(rKEvt) ||
3915 ( eMode != SC_INPUT_NONE && ( bCursorKey || bInsKey ) ) ) )
3917 HideTip();
3918 HideTipBelow();
3920 if (bSelIsRef)
3922 RemoveSelection();
3923 bSelIsRef = false;
3926 UpdateActiveView();
3927 bool bNewView = DataChanging( nChar );
3929 if (bProtected || (pActiveViewSh && pActiveViewSh->GetViewShell() && pActiveViewSh->GetViewShell()->IsLokReadOnlyView())) // Protected cell?
3930 bUsed = true; // Don't forward KeyEvent
3931 else // Changes allowed
3933 if (bNewView ) // Create anew
3935 if (pActiveViewSh)
3936 pActiveViewSh->GetViewData().GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
3937 UpdateActiveView();
3938 if (eMode==SC_INPUT_NONE)
3939 if (pTableView || pTopView)
3941 OUString aStrLoP;
3943 if (bStartEdit && nCellPercentFormatDecSep != 0 &&
3944 ((nChar >= '0' && nChar <= '9') || nChar == '-' || nChar == nCellPercentFormatDecSep))
3946 aStrLoP = "%";
3949 if (pTableView)
3951 pTableView->getEditEngine().SetText( aStrLoP );
3952 if ( !aStrLoP.isEmpty() )
3953 pTableView->SetSelection(ESelection()); // before the '%'
3955 // Don't call SetSelection if the string is empty anyway,
3956 // to avoid breaking the bInitial handling in ScViewData::EditGrowY
3958 if (pTopView)
3960 pTopView->getEditEngine().SetText( aStrLoP );
3961 if ( !aStrLoP.isEmpty() )
3962 pTopView->SetSelection(ESelection()); // before the '%'
3965 SyncViews();
3968 if (pTableView || pTopView)
3970 if (bDoEnter)
3972 if (pTableView)
3973 if( pTableView->PostKeyEvent( KeyEvent( '\r', vcl::KeyCode(KEY_RETURN) ) ) )
3974 bUsed = true;
3975 if (pTopView)
3976 if( pTopView->PostKeyEvent( KeyEvent( '\r', vcl::KeyCode(KEY_RETURN) ) ) )
3977 bUsed = true;
3979 else if ( nAutoPar && nChar == ')' && CursorAtClosingPar() )
3981 SkipClosingPar();
3982 bUsed = true;
3984 else
3986 if (pTableView)
3988 if (pTopView)
3989 pTableView->SetControlWord(pTableView->GetControlWord() | EVControlBits::SINGLELINEPASTE);
3991 vcl::Window* pFrameWin = pActiveViewSh ? pActiveViewSh->GetFrameWin() : nullptr;
3992 if ( pTableView->PostKeyEvent( rKEvt, pFrameWin ) )
3993 bUsed = true;
3995 pTableView->SetControlWord(pTableView->GetControlWord() & ~EVControlBits::SINGLELINEPASTE);
3997 if (pTopView)
3999 if ( bUsed && rKEvt.GetKeyCode().GetFunction() == KeyFuncType::CUT )
4000 pTopView->DeleteSelected();
4001 else if ( pTopView->PostKeyEvent( rKEvt ) )
4002 bUsed = true;
4006 // AutoInput:
4007 if (bUsed && ScModule::get()->GetAppOptions().GetAutoComplete())
4009 bUseTab = false;
4010 if (pFormulaData)
4011 miAutoPosFormula = pFormulaData->end(); // do not search further
4012 if (pColumnData)
4013 miAutoPosColumn = pColumnData->end();
4015 KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
4016 if ( nChar && nChar != 8 && nChar != 127 && // no 'backspace', no 'delete'
4017 KeyFuncType::CUT != eFunc) // and no 'CTRL-X'
4019 if (bFormulaMode)
4020 UseFormulaData();
4021 else
4022 UseColData();
4026 // When the selection is changed manually or an opening parenthesis
4027 // is typed, stop overwriting parentheses
4028 if ( bUsed && nChar == '(' )
4029 ResetAutoPar();
4031 if ( KEY_INSERT == nCode )
4033 SfxViewFrame* pViewFrm = SfxViewFrame::Current();
4034 if (pViewFrm)
4035 pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT );
4037 if( bUsed && bFormulaMode && ( bCursorKey || bInsKey || nCode == KEY_DELETE || nCode == KEY_BACKSPACE ) )
4039 ShowTipCursor();
4041 if( bUsed && bFormulaMode && nCode == KEY_BACKSPACE )
4043 UseFormulaData();
4048 // #i114511# don't count cursor keys as modification
4049 bool bSetModified = !bCursorKey;
4050 // tdf#81913 - don't delete range finder since cursor keys don't count as modifications
4051 bInRangeUpdate = bCursorKey;
4052 DataChanged(false, bSetModified); // also calls UpdateParenthesis()
4053 bInRangeUpdate = false;
4055 // In the LOK case, we want to set the document modified state
4056 // right away at the start of the edit, so that the content is
4057 // saved even when the user leaves the document before hitting
4058 // Enter
4059 if (comphelper::LibreOfficeKit::isActive() && bSetModified && pActiveViewSh && !pActiveViewSh->GetViewData().GetDocShell()->IsModified())
4060 pActiveViewSh->GetViewData().GetDocShell()->SetModified();
4062 InvalidateAttribs(); //! in DataChanged?
4066 if (pTopView && eMode != SC_INPUT_NONE)
4067 SyncViews();
4069 return bUsed;
4072 OUString ScInputHandler::GetSurroundingText()
4074 if (eMode != SC_INPUT_NONE)
4076 UpdateActiveView();
4077 if (pTableView || pTopView)
4079 if (pTableView)
4080 return pTableView->GetSurroundingText();
4081 else if (pTopView) // call only once
4082 return pTopView->GetSurroundingText();
4085 return OUString();
4088 Selection ScInputHandler::GetSurroundingTextSelection()
4090 if (eMode != SC_INPUT_NONE)
4092 UpdateActiveView();
4093 if (pTableView || pTopView)
4095 if (pTableView)
4096 return pTableView->GetSurroundingTextSelection();
4097 else if (pTopView) // call only once
4098 return pTopView->GetSurroundingTextSelection();
4101 return Selection(0, 0);
4104 bool ScInputHandler::DeleteSurroundingText(const Selection& rSelection)
4106 if (eMode != SC_INPUT_NONE)
4108 UpdateActiveView();
4109 if (pTableView || pTopView)
4111 if (pTableView)
4112 return pTableView->DeleteSurroundingText(rSelection);
4113 else if (pTopView) // call only once
4114 return pTopView->DeleteSurroundingText(rSelection);
4117 return false;
4120 void ScInputHandler::InputCommand( const CommandEvent& rCEvt )
4122 if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
4124 // For CommandEventId::CursorPos, do as little as possible, because
4125 // with remote VCL, even a ShowCursor will generate another event.
4126 if ( eMode != SC_INPUT_NONE )
4128 UpdateActiveView();
4129 if (pTableView || pTopView)
4131 if (pTableView)
4132 pTableView->Command( rCEvt );
4133 else if (pTopView) // call only once
4134 pTopView->Command( rCEvt );
4138 else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
4140 if ( eMode != SC_INPUT_NONE )
4142 UpdateActiveView();
4143 if (pTableView || pTopView)
4145 if (pTableView)
4146 pTableView->Command( rCEvt );
4147 else if (pTopView) // call only once
4148 pTopView->Command( rCEvt );
4152 else
4154 HideTip();
4155 HideTipBelow();
4157 if ( bSelIsRef )
4159 RemoveSelection();
4160 bSelIsRef = false;
4163 UpdateActiveView();
4164 bool bNewView = DataChanging( 0, true );
4166 if (!bProtected && pActiveViewSh && !(pActiveViewSh->GetViewShell() && pActiveViewSh->GetViewShell()->IsLokReadOnlyView())) // changes allowed
4168 if (bNewView) // create new edit view
4170 pActiveViewSh->GetViewData().GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
4171 UpdateActiveView();
4172 if (eMode==SC_INPUT_NONE)
4173 if (pTableView || pTopView)
4175 if (pTableView)
4177 pTableView->getEditEngine().SetText( u""_ustr );
4178 pTableView->SetSelection(ESelection());
4180 if (pTopView)
4182 pTopView->getEditEngine().SetText( u""_ustr );
4183 pTopView->SetSelection(ESelection());
4186 SyncViews();
4189 if (pTableView || pTopView)
4191 if (pTableView)
4192 pTableView->Command( rCEvt );
4193 if (pTopView)
4194 pTopView->Command( rCEvt );
4196 if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
4198 // AutoInput after ext text input
4200 if (pFormulaData)
4201 miAutoPosFormula = pFormulaData->end();
4202 if (pColumnData)
4203 miAutoPosColumn = pColumnData->end();
4205 if (bFormulaMode)
4206 UseFormulaData();
4207 else
4208 UseColData();
4212 DataChanged(); // calls UpdateParenthesis()
4213 InvalidateAttribs(); //! in DataChanged ?
4216 if (pTopView && eMode != SC_INPUT_NONE)
4217 SyncViews();
4221 static ScInputHdlState* getLastState(const ScInputHdlState* pState)
4223 if (!pState)
4224 return nullptr;
4225 return new ScInputHdlState(*pState);
4228 void ScInputHandler::NotifyChange( const ScInputHdlState* pState,
4229 bool bForce, ScTabViewShell* pSourceSh,
4230 bool bStopEditing)
4232 // If the call originates from a macro call in the EnterHandler,
4233 // return immediately and don't mess up the status
4234 if (bInEnterHandler)
4235 return;
4237 bool bRepeat = (pState == pLastState.get());
4238 if (!bRepeat && pState && pLastState)
4239 bRepeat = (*pState == *pLastState);
4240 if (bRepeat && !bForce)
4241 return;
4243 bInOwnChange = true; // disable ModifyHdl (reset below)
4245 if ( pState && !pLastState ) // Enable again
4246 bForce = true;
4248 bool bHadObject = pLastState && pLastState->GetEditData();
4250 //! Before EditEngine gets eventually created (so it gets the right pools)
4251 if ( pSourceSh )
4252 pActiveViewSh = pSourceSh;
4253 else
4254 pActiveViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() );
4256 if (pActiveViewSh)
4257 ImplCreateEditEngine();
4259 if ( pState != pLastState.get() )
4261 pLastState.reset(getLastState(pState));
4264 if ( pState && pActiveViewSh )
4266 ScModule* pScMod = ScModule::get();
4268 ScTabViewShell* pScTabViewShell = dynamic_cast<ScTabViewShell*>(pScMod->GetViewShell());
4270 // Also take foreign reference input into account here (e.g. FunctionsAutoPilot),
4271 // FormEditData, if we're switching from Help to Calc:
4272 if ( !bFormulaMode && !pScMod->IsFormulaMode() &&
4273 ( !pScTabViewShell || !pScTabViewShell->GetFormEditData() ) )
4275 bool bIgnore = false;
4276 if ( bModified )
4278 if (pState->GetPos() != aCursorPos)
4280 if (!bProtected)
4281 EnterHandler();
4283 else
4284 bIgnore = true;
4287 if ( !bIgnore )
4289 const ScAddress& rSPos = pState->GetStartPos();
4290 const ScAddress& rEPos = pState->GetEndPos();
4291 const EditTextObject* pData = pState->GetEditData();
4292 OUString aString = pState->GetString();
4293 bool bTxtMod = false;
4294 ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
4295 ScDocument& rDoc = pDocSh->GetDocument();
4297 aCursorPos = pState->GetPos();
4299 if ( pData )
4300 bTxtMod = true;
4301 else if ( bHadObject )
4302 bTxtMod = true;
4303 else if ( bTextValid )
4304 bTxtMod = ( aString != aCurrentText );
4305 else
4306 bTxtMod = ( aString != GetEditText(mpEditEngine.get()) );
4308 if ( bTxtMod || bForce )
4310 if (pData)
4312 mpEditEngine->SetTextCurrentDefaults( *pData );
4313 if (pInputWin)
4314 aString = ScEditUtil::GetMultilineString(*mpEditEngine);
4315 else
4316 aString = GetEditText(mpEditEngine.get());
4317 lcl_RemoveTabs(aString);
4318 bTextValid = false;
4319 aCurrentText.clear();
4321 else
4323 aCurrentText = aString;
4324 bTextValid = true; //! To begin with remember as a string
4327 const bool bUpdateKit = comphelper::LibreOfficeKit::isActive() && pActiveViewSh;
4329 if (pInputWin)
4331 // If we will end up updating LoKit after this, we can skip it here
4332 pInputWin->SetTextString(aString, !bUpdateKit);
4335 if (bUpdateKit)
4337 UpdateActiveView();
4338 EditView* pActiveView = pTopView ? pTopView : pTableView;
4339 ESelection aSel = pActiveView ? pActiveView->GetSelection() : ESelection();
4341 // if we switched content completely - don't send huge numbers
4342 if (aSel.start.nPara == EE_PARA_MAX)
4343 aSel.start.nPara = 0;
4345 if (aSel.end.nPara == EE_PARA_MAX)
4346 aSel.end.nPara = 0;
4348 pActiveViewSh->LOKSendFormulabarUpdate(pActiveView, aString, aSel);
4349 pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_FORMULA, aString.toUtf8());
4353 if ( pInputWin || comphelper::LibreOfficeKit::isActive()) // Named range input
4355 OUString aPosStr;
4356 bool bSheetLocal = false;
4357 const ScAddress::Details aAddrDetails( rDoc, aCursorPos );
4359 // Is the range a name?
4360 //! Find by Timer?
4361 if ( pActiveViewSh )
4362 pActiveViewSh->GetViewData().GetDocument().
4363 GetRangeAtBlock( ScRange( rSPos, rEPos ), aPosStr, &bSheetLocal);
4365 if ( aPosStr.isEmpty() ) // Not a name -> format
4367 ScRefFlags nFlags = ScRefFlags::ZERO;
4368 if( aAddrDetails.eConv == formula::FormulaGrammar::CONV_XL_R1C1 )
4369 nFlags |= ScRefFlags::COL_ABS | ScRefFlags::ROW_ABS;
4370 if ( rSPos != rEPos )
4372 ScRange r(rSPos, rEPos);
4373 applyStartToEndFlags(nFlags);
4374 aPosStr = r.Format(rDoc, ScRefFlags::VALID | nFlags, aAddrDetails);
4376 else
4377 aPosStr = aCursorPos.Format(ScRefFlags::VALID | nFlags, &rDoc, aAddrDetails);
4379 else if (bSheetLocal)
4381 OUString aName;
4382 if (rDoc.GetName( rSPos.Tab(), aName))
4383 aPosStr = ScPosWnd::createLocalRangeName( aPosStr, aName);
4386 if (pInputWin)
4388 pInputWin->SetPosString(aPosStr);
4389 pInputWin->SetSumAssignMode();
4392 if (comphelper::LibreOfficeKit::isActive() && pActiveViewSh)
4393 pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_ADDRESS, aPosStr.toUtf8());
4396 if (bStopEditing) {
4397 SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScKillEditView ) );
4399 // As long as the content is not edited, turn off online spelling.
4400 // Online spelling is turned back on in StartTable, after setting
4401 // the right language from cell attributes.
4403 EEControlBits nCntrl = mpEditEngine->GetControlWord();
4404 if ( nCntrl & EEControlBits::ONLINESPELLING )
4405 mpEditEngine->SetControlWord( nCntrl & ~EEControlBits::ONLINESPELLING );
4408 bModified = false;
4409 bSelIsRef = false;
4410 bProtected = false;
4411 bCommandErrorShown = false;
4415 if ( pInputWin)
4417 // Do not enable if RefDialog is open
4418 if(!pScMod->IsFormulaMode()&& !pScMod->IsRefDialogOpen())
4420 if ( !pInputWin->IsEnabled())
4422 pDelayTimer->Stop();
4423 pInputWin->Enable();
4426 else if(pScMod->IsRefDialogOpen())
4427 { // Because every document has its own InputWin,
4428 // we should start Timer again, because the input line may
4429 // still be active
4430 if ( !pDelayTimer->IsActive() )
4431 pDelayTimer->Start();
4435 else // !pState || !pActiveViewSh
4437 if ( !pDelayTimer->IsActive() )
4438 pDelayTimer->Start();
4441 // Don't hide function tooltip in LOK, a remote user might be using tip.
4442 if (bStopEditing)
4443 HideTip();
4444 HideTipBelow();
4445 bInOwnChange = false;
4448 void ScInputHandler::UpdateCellAdjust( SvxCellHorJustify eJust )
4450 eAttrAdjust = eJust;
4451 UpdateAdjust( 0 );
4454 void ScInputHandler::ResetDelayTimer()
4456 if( pDelayTimer->IsActive() )
4458 pDelayTimer->Stop();
4459 if ( pInputWin )
4460 pInputWin->Enable();
4464 IMPL_LINK_NOARG( ScInputHandler, DelayTimer, Timer*, void )
4466 if (!(nullptr == pLastState || ScModule::get()->IsFormulaMode() || ScModule::get()->IsRefDialogOpen()))
4467 return;
4469 //! New method at ScModule to query if function autopilot is open
4470 SfxViewFrame* pViewFrm = SfxViewFrame::Current();
4471 if ( pViewFrm && pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) )
4473 if ( pInputWin)
4475 pInputWin->EnableButtons( false );
4476 pInputWin->Disable();
4479 else if ( !bFormulaMode ) // Keep formula e.g. for help
4481 bInOwnChange = true; // disable ModifyHdl (reset below)
4483 pActiveViewSh = nullptr;
4484 mpEditEngine->SetTextCurrentDefaults( OUString() );
4485 if ( pInputWin )
4487 pInputWin->SetPosString( OUString() );
4488 pInputWin->SetTextString(OUString(), true);
4489 pInputWin->Disable();
4492 bInOwnChange = false;
4496 void ScInputHandler::InputSelection( const EditView* pView )
4498 SyncViews( pView );
4499 ShowTipCursor();
4500 UpdateParenthesis(); // Selection changed -> update parentheses highlighting
4502 // When the selection is changed manually, stop overwriting parentheses
4503 ResetAutoPar();
4505 if (comphelper::LibreOfficeKit::isActive() && pActiveViewSh)
4507 EditView* pActiveView = pTopView ? pTopView : pTableView;
4508 ESelection aSel = pActiveView ? pActiveView->GetSelection() : ESelection();
4509 pActiveViewSh->LOKSendFormulabarUpdate(pActiveView, GetEditString(), aSel);
4513 void ScInputHandler::InputChanged( const EditView* pView, bool bFromNotify )
4515 if ( !pView )
4516 return;
4518 UpdateActiveView();
4520 // #i20282# DataChanged needs to know if this is from the input line's modify handler
4521 bool bFromTopNotify = ( bFromNotify && pView == pTopView );
4523 bool bNewView = DataChanging(); //FIXME: Is this at all possible?
4524 aCurrentText = pView->getEditEngine().GetText(); // Also remember the string
4525 mpEditEngine->SetTextCurrentDefaults( aCurrentText );
4526 DataChanged( bFromTopNotify );
4527 bTextValid = true; // Is set to false in DataChanged
4529 if ( pActiveViewSh )
4531 ScViewData& rViewData = pActiveViewSh->GetViewData();
4532 if ( bNewView )
4533 rViewData.GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
4535 rViewData.EditGrowY();
4536 rViewData.EditGrowX();
4539 SyncViews( pView );
4542 const OUString& ScInputHandler::GetEditString()
4544 if (mpEditEngine)
4546 aCurrentText = mpEditEngine->GetText(); // Always new from Engine
4547 bTextValid = true;
4550 return aCurrentText;
4553 Size ScInputHandler::GetTextSize()
4555 Size aSize;
4556 if ( mpEditEngine )
4557 aSize = Size( mpEditEngine->CalcTextWidth(), mpEditEngine->GetTextHeight() );
4559 return aSize;
4562 bool ScInputHandler::GetTextAndFields( ScEditEngineDefaulter& rDestEngine )
4564 bool bRet = false;
4565 if (mpEditEngine)
4567 // Contains field?
4568 SfxItemSet aSet = mpEditEngine->GetAttribs(ESelection::All());
4569 SfxItemState eFieldState = aSet.GetItemState( EE_FEATURE_FIELD, false );
4570 if ( eFieldState == SfxItemState::INVALID || eFieldState == SfxItemState::SET )
4572 // Copy content
4573 std::unique_ptr<EditTextObject> pObj = mpEditEngine->CreateTextObject();
4574 rDestEngine.SetTextCurrentDefaults(*pObj);
4575 pObj.reset();
4577 sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
4578 // Delete attributes
4579 for (sal_Int32 i=0; i<nParCnt; i++)
4580 rDestEngine.RemoveCharAttribs( i );
4582 // Combine paragraphs
4583 while ( nParCnt > 1 )
4585 sal_Int32 nLen = rDestEngine.GetTextLen( 0 );
4586 ESelection aSel( 0,nLen, 1,0 );
4587 rDestEngine.QuickInsertText( OUString(' '), aSel ); // Replace line break with space
4588 --nParCnt;
4591 bRet = true;
4594 return bRet;
4598 * Methods for FunctionAutoPilot:
4599 * InputGetSelection, InputSetSelection, InputReplaceSelection, InputGetFormulaStr
4601 void ScInputHandler::InputGetSelection( sal_Int32& rStart, sal_Int32& rEnd )
4603 rStart = nFormSelStart;
4604 rEnd = nFormSelEnd;
4607 EditView* ScInputHandler::GetFuncEditView()
4609 UpdateActiveView(); // Due to pTableView
4611 EditView* pView = nullptr;
4612 if ( pInputWin )
4614 pInputWin->MakeDialogEditView();
4615 pView = pInputWin->GetEditView();
4617 else
4619 if ( eMode != SC_INPUT_TABLE )
4621 bCreatingFuncView = true; // Don't display RangeFinder
4622 SetMode( SC_INPUT_TABLE );
4623 bCreatingFuncView = false;
4624 if ( pTableView )
4625 pTableView->getEditEngine().SetText( OUString() );
4627 pView = pTableView;
4630 return pView;
4633 void ScInputHandler::InputSetSelection( sal_Int32 nStart, sal_Int32 nEnd )
4635 if ( nStart <= nEnd )
4637 nFormSelStart = nStart;
4638 nFormSelEnd = nEnd;
4640 else
4642 nFormSelEnd = nStart;
4643 nFormSelStart = nEnd;
4646 EditView* pView = GetFuncEditView();
4647 if (pView)
4648 pView->SetSelection( ESelection(0,nStart, 0,nEnd) );
4650 bModified = true;
4653 void ScInputHandler::InputReplaceSelection( std::u16string_view aStr )
4655 if (!pRefViewSh)
4656 pRefViewSh = pActiveViewSh;
4658 OSL_ENSURE(nFormSelEnd>=nFormSelStart,"Selection broken...");
4660 sal_Int32 nOldLen = nFormSelEnd - nFormSelStart;
4661 sal_Int32 nNewLen = aStr.size();
4663 OUStringBuffer aBuf(aFormText);
4664 if (nOldLen)
4665 aBuf.remove(nFormSelStart, nOldLen);
4666 if (nNewLen)
4667 aBuf.insert(nFormSelStart, aStr);
4669 aFormText = aBuf.makeStringAndClear();
4671 nFormSelEnd = nFormSelStart + nNewLen;
4673 EditView* pView = GetFuncEditView();
4674 if (pView)
4676 pView->SetEditEngineUpdateLayout( false );
4677 pView->getEditEngine().SetText( aFormText );
4678 pView->SetSelection( ESelection(0,nFormSelStart, 0,nFormSelEnd) );
4679 pView->SetEditEngineUpdateLayout( true );
4681 bModified = true;
4684 void ScInputHandler::InputTurnOffWinEngine()
4686 bInOwnChange = true; // disable ModifyHdl (reset below)
4688 eMode = SC_INPUT_NONE;
4689 /* TODO: it would be better if there was some way to reset the input bar
4690 * engine instead of deleting and having it recreate through
4691 * GetFuncEditView(), but first least invasively let this fix fdo#71667 and
4692 * fdo#72278 without reintroducing fdo#69971. */
4693 StopInputWinEngine(true);
4695 bInOwnChange = false;
4699 * ScInputHdlState
4701 ScInputHdlState::ScInputHdlState( const ScAddress& rCurPos,
4702 const ScAddress& rStartPos,
4703 const ScAddress& rEndPos,
4704 OUString _aString,
4705 const EditTextObject* pData )
4706 : aCursorPos ( rCurPos ),
4707 aStartPos ( rStartPos ),
4708 aEndPos ( rEndPos ),
4709 aString (std::move( _aString )),
4710 pEditData ( pData ? pData->Clone() : nullptr )
4714 ScInputHdlState::ScInputHdlState( const ScInputHdlState& rCpy )
4716 *this = rCpy;
4719 ScInputHdlState::~ScInputHdlState()
4723 bool ScInputHdlState::operator==( const ScInputHdlState& r ) const
4725 return ( (aStartPos == r.aStartPos)
4726 && (aEndPos == r.aEndPos)
4727 && (aCursorPos == r.aCursorPos)
4728 && (aString == r.aString)
4729 && ScGlobal::EETextObjEqual( pEditData.get(), r.pEditData.get() ) );
4732 ScInputHdlState& ScInputHdlState::operator=( const ScInputHdlState& r )
4734 if (this != &r)
4736 aCursorPos = r.aCursorPos;
4737 aStartPos = r.aStartPos;
4738 aEndPos = r.aEndPos;
4739 aString = r.aString;
4740 pEditData.reset();
4741 if (r.pEditData)
4742 pEditData = r.pEditData->Clone();
4744 return *this;
4747 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */