Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
TextInteractor.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <cctype>
7#include <set>
8
14
15#include "Stroika/Frameworks/Led/Command.h"
16#include "Stroika/Frameworks/Led/Config.h"
17#include "Stroika/Frameworks/Led/IdleManager.h"
18#include "Stroika/Frameworks/Led/Marker.h"
19#include "Stroika/Frameworks/Led/TextStore.h"
20
21#include "TextInteractor.h"
22
23using namespace Stroika::Foundation;
25
26using namespace Stroika::Frameworks;
27using namespace Stroika::Frameworks::Led;
28
29#if qStroika_Frameworks_Led_SupportGDI
30using SavedTextRep = InteractiveReplaceCommand::SavedTextRep;
31
32namespace {
33 class FlavorSavorTextRep : public SavedTextRep {
34 private:
35 using inherited = SavedTextRep;
36
37 public:
38 FlavorSavorTextRep (TextInteractor* interactor, size_t regionStart, size_t regionEnd, size_t selStart, size_t selEnd)
39 : inherited (selStart, selEnd)
40 , fSavedText ()
41 , fTextLength (regionEnd - regionStart)
42 {
43 RequireNotNull (interactor);
44 interactor->GetExternalizer ()->ExternalizeBestFlavor (fSavedText, regionStart, regionEnd);
45 }
46 virtual size_t GetLength () const override
47 {
48 return fTextLength;
49 }
50 virtual void InsertSelf (TextInteractor* interactor, size_t at, size_t nBytesToOverwrite) override
51 {
52 RequireNotNull (interactor);
53 interactor->GetInternalizer ()->InternalizeBestFlavor (fSavedText, at, at + nBytesToOverwrite);
54 }
55
56 private:
57 size_t fTextLength;
59 };
60}
61
62namespace {
63 // Only these chars count as whitespce for smart cut/n/paste
64 inline bool IsSmartSpace (Led_tChar c)
65 {
66 return (c == ' ' or c == '\t');
67 }
68 inline bool IsShouldBeSepWithWhitespaceWordChar (Led_tChar c)
69 {
70 // iswalnum vectors to GetStringTypeW (CT_CTYPE1) and then checks for C1_ALPHA, which
71 // matches all alphabetic, numeric, AND Idiogram characters. I THINK the right thing to use
72 // is to directly call GetStringTypeW (CT_CTYPE2, and check for C2_SEGMENTSEPARATOR, or something
73 // like that (will have to read up on UNICODE more, and/or experiemnet). Anyhow - none of that will happen
74 // for this release, and this should be good enuf to prevent smart-copy-paste from happing with
75 // idiogram characters
76 return Character (c).IsAlphaNumeric () and c < 127;
77 }
78}
79
80namespace {
81 class MyCallback : public TextInteractor::DialogSupport::SpellCheckDialogCallback {
82 private:
83 using inherited = TextInteractor::DialogSupport::SpellCheckDialogCallback;
84
85 public:
86 MyCallback (TextInteractor& ti)
87 : fTI{ti}
88 , fIgnoredWords{}
89 {
90 }
91
92 public:
93 DISABLE_COMPILER_MSC_WARNING_START (6262)
94 virtual MisspellingInfo* GetNextMisspelling () override
95 {
96 SpellCheckEngine* sce = fTI.GetSpellCheckEngine ();
97 if (sce != nullptr) {
98 Led_tChar charBuf[10 * 1024]; // buffer size doesn't matter much - but just has to be larger than the largest undef word we ever want to find...
99 bool firstTry = true;
100 size_t startRegion = fTI.GetSelectionEnd ();
101 SecondTry: {
102 // regardless of the startRegion - back up the search to the start of the interesected word. The only
103 // exception is if the size of our charBuf isn't big enough to go past the startRegion position (cuz
104 // then we'd be going backwards, and risk never moving forwards, in case of a large word)
105 size_t wordStart = 0;
106 size_t wordEnd = 0;
107 bool wordReal = false;
108 fTI.GetTextStore ().FindWordBreaks (startRegion, &wordStart, &wordEnd, &wordReal, sce->PeekAtTextBreaksUsed ());
109 if (wordReal and wordStart + std::size (charBuf) > startRegion) {
110 startRegion = wordStart;
111 }
112 }
113 size_t endRegion = min (startRegion + std::size (charBuf), fTI.GetEnd ());
114 fTI.CopyOut (startRegion, endRegion - startRegion, charBuf);
115 const Led_tChar* cursor = nullptr;
116 const Led_tChar* wordStart = nullptr;
117 const Led_tChar* wordEnd = nullptr;
118 if (sce->ScanForUndefinedWord (charBuf, charBuf + (endRegion - startRegion), &cursor, &wordStart, &wordEnd)) {
119 /*
120 * If the undefined word ends at the end of the buffer region we looked at - it might be an artifact of our
121 * chunking. Retry the word (being careful about the special case where the 'word' is as big as our buffer).
122 */
123 if (wordStart != charBuf and wordEnd == charBuf + (endRegion - startRegion)) {
124 startRegion += (wordStart - charBuf); // advance to the start of this word and try again
125 goto SecondTry;
126 }
127
128 Led_tString undefinedWord = Led_tString{wordStart, wordEnd};
129 if (fIgnoredWords.find (undefinedWord) != fIgnoredWords.end ()) {
130 // push startRegion a bit forward over this word, and try again, but don't
131 // set 'second try' flag cuz we haven't wrapped at the end of the document...
132 startRegion += (wordEnd - charBuf);
133 goto SecondTry;
134 }
135 MisspellingInfo* mi = new MisspellingInfo ();
136 mi->fUndefinedWord = undefinedWord;
137 mi->fSuggestions = sce->GenerateSuggestions (mi->fUndefinedWord);
138 size_t selStart = startRegion + wordStart - charBuf;
139 size_t selEnd = selStart + (wordEnd - wordStart);
140 fTI.SetSelection (selStart, selEnd);
141 fTI.ScrollToSelection ();
142 return mi;
143 }
144 else if (endRegion < fTI.GetEnd ()) {
145 // no undefined words in that region, so try the next chunk. Stopping at word boundaries taken
146 // care of with logic already above (for first case)
147 startRegion = endRegion;
148 goto SecondTry;
149 }
150 else if (firstTry) {
151 // Wrap around...
152 startRegion = 0;
153 firstTry = false;
154 goto SecondTry;
155 }
156 }
157 return nullptr;
158 }
159 DISABLE_COMPILER_MSC_WARNING_END (6262)
160 virtual void DoIgnore () override
161 {
162 fTI.SetSelection (fTI.GetSelectionEnd (), fTI.GetSelectionEnd ());
163 fTI.ScrollToSelection ();
164 }
165 virtual void DoIgnoreAll () override
166 {
167 {
168 size_t origSelStart = fTI.GetSelectionStart ();
169 size_t origSelEnd = fTI.GetSelectionEnd ();
170 Memory::StackBuffer<Led_tChar> text{Memory::eUninitialized, origSelEnd - origSelStart + 1};
171 fTI.CopyOut (origSelStart, origSelEnd - origSelStart, text.data ());
172 Led_tString ignoredWord = Led_tString{text.data (), origSelEnd - origSelStart};
173 fIgnoredWords.insert (ignoredWord);
174 }
175 DoIgnore ();
176 }
177 virtual void DoChange (const Led_tString& changeTo) override
178 {
179 size_t origSelStart = fTI.GetSelectionStart ();
180 size_t origSelEnd = fTI.GetSelectionEnd ();
181 TextInteractor::SearchParameters sp;
182 {
183 Memory::StackBuffer<Led_tChar> text{Memory::eUninitialized, origSelEnd - origSelStart + 1};
184 fTI.CopyOut (origSelStart, origSelEnd - origSelStart, text.data ());
185 sp.fMatchString = Led_tString{text.data (), origSelEnd - origSelStart};
186 }
187 fTI.SetSelection (origSelStart, origSelStart); // cuz OnDoReplaceCommand () looks from selectionEND
188 fTI.OnDoReplaceCommand (sp, changeTo);
189 }
190 virtual void DoChangeAll (const Led_tString& changeTo) override
191 {
192 size_t origSelStart = fTI.GetSelectionStart ();
193 size_t origSelEnd = fTI.GetSelectionEnd ();
194 TextInteractor::SearchParameters sp;
195 {
196 Memory::StackBuffer<Led_tChar> text{Memory::eUninitialized, origSelEnd - origSelStart + 1};
197 fTI.CopyOut (origSelStart, origSelEnd - origSelStart, text.data ());
198 sp.fMatchString = Led_tString{text.data (), origSelEnd - origSelStart};
199 }
200 fTI.OnDoReplaceAllCommand (sp, changeTo);
201 }
202 virtual bool AddToDictionaryEnabled () const override
203 {
204 SpellCheckEngine* sce = fTI.GetSpellCheckEngine ();
205 if (sce != nullptr) {
206 SpellCheckEngine::UDInterface* udi = sce->GetUDInterface ();
207 if (udi != nullptr) {
208 return udi->AddWordToUserDictionarySupported ();
209 }
210 }
211 return false;
212 }
213 virtual void AddToDictionary (const Led_tString& newWord) override
214 {
215 SpellCheckEngine* sce = fTI.GetSpellCheckEngine ();
216 if (sce != nullptr) {
217 SpellCheckEngine::UDInterface* udi = sce->GetUDInterface ();
218 if (udi != nullptr) {
219 udi->AddWordToUserDictionary (newWord);
220 }
221 }
222
223 fTI.SetSelection (fTI.GetSelectionEnd (), fTI.GetSelectionEnd ());
224 fTI.ScrollToSelection ();
225 }
226 virtual void LookupOnWeb (const Led_tString& word) override
227 {
228 const char kURLBase[] = "http://dictionary.reference.com/search?q=";
229 Led_URLManager::Get ().Open (kURLBase + Led_tString2ANSIString (word));
230 }
231 virtual bool OptionsDialogEnabled () const override
232 {
233 // cuz no implementation of OptionsDialog () callback...
234 return false;
235 }
236 virtual void OptionsDialog () override
237 {
238 // NYI. When enabled - change result of OptionsDialogEnabled ()
239 }
240
241 private:
242 TextInteractor& fTI;
243 set<Led_tString> fIgnoredWords; // note we intentionally dont keep this around so that the
244 // ignored words list stays around (lives) as long as the dialog...
245 };
246}
247
248/*
249 ********************************************************************************
250 ************************* TextInteractor::DialogSupport ************************
251 ********************************************************************************
252 */
253void TextInteractor::DialogSupport::DisplayFindDialog (Led_tString* /*findText*/, const vector<Led_tString>& /*recentFindSuggestions*/,
254 bool* /*wrapSearch*/, bool* /*wholeWordSearch*/, bool* /*caseSensative*/, bool* /*pressedOK*/)
255{
256 /*
257 * You may want to use code something like this in your OVERRIDE:
258 *
259 Led_StdDialogHelper_FindDialog findDialog (::AfxGetResourceHandle (), nullptr);
260
261 findDialog.fFindText = *findText;
262 findDialog.fRecentFindTextStrings = recentFindSuggestions;
263 findDialog.fWrapSearch = *wrapSearch;
264 findDialog.fWholeWordSearch = *wholeWordSearch;
265 findDialog.fCaseSensativeSearch = *caseSensative;
266
267 findDialog.DoModal ();
268
269 *findText = findDialog.fFindText;
270 *wrapSearch = findDialog.fWrapSearch;
271 *wholeWordSearch = findDialog.fWholeWordSearch;
272 *caseSensative = findDialog.fCaseSensativeSearch;
273 *pressOK = findDialog.fPressedOK;
274 */
275 Assert (false); // to use this - you must OVERRIDE this routine, and provide your own implementation, perhaps akin to the above.
276}
277
278TextInteractor::DialogSupport::ReplaceButtonPressed
279TextInteractor::DialogSupport::DisplayReplaceDialog (Led_tString* /*findText*/, const vector<Led_tString>& /*recentFindSuggestions*/,
280 Led_tString* /*replaceText*/, bool* /*wrapSearch*/, bool* /*wholeWordSearch*/, bool* /*caseSensative*/)
281{
282 Assert (false); // to use this - you must OVERRIDE this routine, and provide your own implementation, perhaps akin to the above.
283 return eReplaceButton_Cancel;
284}
285
286void TextInteractor::DialogSupport::DisplaySpellCheckDialog (SpellCheckDialogCallback& /*callback*/)
287{
288 Assert (false); // to use this - you must OVERRIDE this routine, and provide your own implementation, perhaps akin to the above.
289}
290
291/*
292 ********************************************************************************
293 ******************** TextInteractor::UndoableContextHelper *********************
294 ********************************************************************************
295 */
296TextInteractor::UndoableContextHelper::UndoableContextHelper (TextInteractor& ti, const SDKString& cmdName, bool allowSmartCNPExpansion)
297 : fSimplePlainTextInsertOptimization (false)
298 , fTextInteractor (ti)
299 , fCmdName (cmdName)
300 , fSelStart (ti.GetSelectionStart ())
301 , fSelEnd (ti.GetSelectionEnd ())
302 , fBefore (nullptr)
303 , fCommandComplete (false)
304{
305 if (allowSmartCNPExpansion) {
306 ti.OptionallyExpandSelectionForSmartCutAndPasteModeDeletes (&fSelStart, &fSelEnd);
307 }
308 if (ti.GetCommandHandler () != nullptr) {
309 ti.PreInteractiveUndoHelper (&fBefore, fSelStart, fSelEnd, ti.GetSelectionStart (), ti.GetSelectionEnd ());
310 }
311}
312
313TextInteractor::UndoableContextHelper::UndoableContextHelper (TextInteractor& ti, const SDKString& cmdName, size_t regionAndSelStart,
314 size_t regionAndSelEnd, bool allowSmartCNPExpansion)
315 : fSimplePlainTextInsertOptimization (false)
316 , fTextInteractor (ti)
317 , fCmdName (cmdName)
318 , fSelStart (regionAndSelStart)
319 , fSelEnd (regionAndSelEnd)
320 , fBefore (nullptr)
321 , fCommandComplete (false)
322{
323 if (allowSmartCNPExpansion) {
324 ti.OptionallyExpandSelectionForSmartCutAndPasteModeDeletes (&fSelStart, &fSelEnd);
325 }
326 if (ti.GetCommandHandler () != nullptr) {
327 ti.PreInteractiveUndoHelper (&fBefore, fSelStart, fSelEnd, regionAndSelStart, regionAndSelEnd);
328 }
329}
330
331TextInteractor::UndoableContextHelper::UndoableContextHelper (TextInteractor& ti, const SDKString& cmdName, size_t regionStart,
332 size_t regionEnd, size_t selStart, size_t selEnd, bool allowSmartCNPExpansion)
333 : fSimplePlainTextInsertOptimization (false)
334 , fTextInteractor (ti)
335 , fCmdName (cmdName)
336 , fSelStart (regionStart)
337 , fSelEnd (regionEnd)
338 , fBefore (nullptr)
339 , fCommandComplete (false)
340{
341 if (allowSmartCNPExpansion) {
342 ti.OptionallyExpandSelectionForSmartCutAndPasteModeDeletes (&fSelStart, &fSelEnd);
343 }
344 if (ti.GetCommandHandler () != nullptr) {
345 ti.PreInteractiveUndoHelper (&fBefore, fSelStart, fSelEnd, selStart, selEnd);
346 }
347}
348
349TextInteractor::UndoableContextHelper::~UndoableContextHelper ()
350{
351 if (not fCommandComplete) {
352 delete fBefore;
353 }
354}
355
356/*
357@METHOD: TextInteractor::UndoableContextHelper::CommandComplete
358@ACCESS: public
359@DESCRIPTION: <p>There are two overloaded versions of this function. Both require knowledge of
360 the END of the updated (inserted) region. The one with no arguments assumes the selectionEnd
361 is the end of the inserted region. The second overload allows you to explicitly specify the
362 end of the inserted region.
363 </p>
364 <p>Call this function when a command has been completed (See the @'TextInteractor::UndoableContextHelper' docs
365 for how to structure the CTOR/CommandComplete calls). Failure to call this after constructing a UndoableContextHelper
366 object results in the command not being recorded (as is appropriate if there is an exception thrown and the command
367 is not performed).
368 </p>
369*/
370void TextInteractor::UndoableContextHelper::CommandComplete ()
371{
372 fTextInteractor.ScrollToSelection ();
373 if (fTextInteractor.GetCommandHandler () != nullptr) {
374 if (GetSimplePlainTextInsertOptimization ()) {
375 fTextInteractor.PostInteractiveSimpleCharInsertUndoHelper (&fBefore, fSelStart, fTextInteractor.GetSelectionEnd (), fCmdName);
376 }
377 else {
378 fTextInteractor.PostInteractiveUndoHelper (&fBefore, fSelStart, fTextInteractor.GetSelectionEnd (), fCmdName);
379 }
380 }
381 fCommandComplete = true;
382}
383
384void TextInteractor::UndoableContextHelper::CommandComplete (size_t endOfInsert)
385{
386 fTextInteractor.ScrollToSelection ();
387 if (fTextInteractor.GetCommandHandler () != nullptr) {
388 if (GetSimplePlainTextInsertOptimization ()) {
389 fTextInteractor.PostInteractiveSimpleCharInsertUndoHelper (&fBefore, fSelStart, endOfInsert, fCmdName);
390 }
391 else {
392 fTextInteractor.PostInteractiveUndoHelper (&fBefore, fSelStart, endOfInsert, fCmdName);
393 }
394 }
395 fCommandComplete = true;
396}
397
398/*
399 ********************************************************************************
400 ************************* TextInteractor::PreReplaceInfo ***********************
401 ********************************************************************************
402 */
403TextInteractor::PreReplaceInfo::PreReplaceInfo ()
404 : fTextInteractor (nullptr)
405 , fUpdateMode (TextInteractor::eNoUpdate)
406 , fFrom (0)
407 , fTo (0)
408 , fWithWhatCharCount (0)
409 , fBoundingUpdateMarker ()
410 , fBoundingUpdateHeight (0)
411 , fStableTypingRegionHeight (0)
412{
413}
414
415TextInteractor::PreReplaceInfo::~PreReplaceInfo ()
416{
417 if (fTextInteractor != nullptr) {
418 fTextInteractor->GetTextStore ().RemoveMarker (&fBoundingUpdateMarker);
419 fTextInteractor = nullptr;
420 }
421}
422
423TextInteractor::UpdateMode TextInteractor::PreReplaceInfo::GetUpdateMode () const
424{
425 return fUpdateMode;
426}
427
428size_t TextInteractor::PreReplaceInfo::GetFrom () const
429{
430 return fFrom;
431}
432
433size_t TextInteractor::PreReplaceInfo::GetTo () const
434{
435 return fTo;
436}
437
438/*
439 ********************************************************************************
440 ******************************** TextInteractor ********************************
441 ********************************************************************************
442 */
443
444TextInteractor::CommandNames TextInteractor::sCommandNames = TextInteractor::MakeDefaultCommandNames ();
445TextInteractor::DialogSupport* TextInteractor::sDialogSupport = nullptr;
446TextInteractor::SearchParameters TextInteractor::sSearchParameters;
447TextInteractor::ReplaceParameters TextInteractor::sReplaceParameters;
448
449TextInteractor::TextInteractor ()
450 : fCommandHandler (nullptr)
451 , fSpellCheckEngine (nullptr)
452 , fSuppressCommandBreaksContext (false)
453 , fDefaultUpdateMode (eDelayedUpdate)
454 , fSmartCutAndPasteMode (true)
455 , fClickCount (0)
456 , fLastMouseDownAt (Led_Point (0, 0))
457 , fWholeWindowInvalid (false)
458 , fUseSecondaryHilight (false)
459 , fUseBitmapScrollingOptimization (true)
460 , fSuppressTypedControlCharacters (true)
461 , fInteractiveUpdadeMode (eIndeterminateInteractiveUpdateMode)
462 , fTmpPreReplaceInfo ()
463 , fDoingUpdateModeReplaceOn (nullptr)
464 , fCaretShown (false)
465 , fLeftSideOfSelectionInteresting (false)
466 , fCaretShownAfterPos (true)
467 , fInternalizer ()
468 , fExternalizer ()
469 ,
470 //fScrollBarType (),
471 fScrollBarParamsValid (false)
472{
473 fScrollBarType[h] = eScrollBarNever;
474 fScrollBarType[v] = eScrollBarNever;
475}
476
477TextInteractor::~TextInteractor ()
478{
479 Assert (fCommandHandler == nullptr); // must be set to nullptr before we are destroyed...
480 // just sanity check - no real reason...
481 Assert (fSpellCheckEngine == nullptr); // DITTO
482}
483
484TextInteractor::CommandNames TextInteractor::MakeDefaultCommandNames ()
485{
486 TextInteractor::CommandNames cmdNames;
487 cmdNames.fTypingCommandName = Led_SDK_TCHAROF ("Typing");
488 cmdNames.fCutCommandName = Led_SDK_TCHAROF ("Cut");
489 cmdNames.fClearCommandName = Led_SDK_TCHAROF ("Clear");
490 cmdNames.fPasteCommandName = Led_SDK_TCHAROF ("Paste");
491 cmdNames.fUndoFormatString = Led_SDK_TCHAROF ("Undo %s");
492 cmdNames.fRedoFormatString = Led_SDK_TCHAROF ("ReDo %s");
493#if qStroika_Platform_Windows
494 cmdNames.fUndoFormatString += Led_SDK_TCHAROF ("\tCtrl+Z");
495 cmdNames.fRedoFormatString += Led_SDK_TCHAROF ("\tCtrl+Y");
496#endif
497 cmdNames.fReplaceCommandName = Led_SDK_TCHAROF ("Replace");
498 cmdNames.fReplaceAllCommandName = Led_SDK_TCHAROF ("Replace All");
499 cmdNames.fReplaceAllInSelectionCommandName = Led_SDK_TCHAROF ("Replace All In Selection");
500 return cmdNames;
501}
502
503/*
504@METHOD: TextInteractor::OnUpdateCommand
505@ACCESS: public
506@DESCRIPTION: <p>
507 </p>
508*/
509bool TextInteractor::OnUpdateCommand (CommandUpdater* enabler)
510{
511 RequireNotNull (enabler);
512 switch (enabler->GetCmdID ()) {
513 case kSelectAll_CmdID: {
514 enabler->SetEnabled (true);
515 return true;
516 }
517 case kCut_CmdID: {
518 OnUpdateCutCopyClearCommand (enabler);
519 return true;
520 }
521 case kCopy_CmdID: {
522 OnUpdateCutCopyClearCommand (enabler);
523 return true;
524 }
525 case kPaste_CmdID: {
526 OnUpdatePasteCommand (enabler);
527 return true;
528 }
529 case kClear_CmdID: {
530 OnUpdateCutCopyClearCommand (enabler);
531 return true;
532 }
533 case kUndo_CmdID: {
534 OnUpdateUndoRedoCommand (enabler);
535 return true;
536 }
537 case kRedo_CmdID: {
538 OnUpdateUndoRedoCommand (enabler);
539 return true;
540 }
541 case kFind_CmdID: {
542 OnUpdateFindCommands (enabler);
543 return true;
544 }
545 case kFindAgain_CmdID: {
546 OnUpdateFindCommands (enabler);
547 return true;
548 }
549 case kEnterFindString_CmdID: {
550 OnUpdateFindCommands (enabler);
551 return true;
552 }
553 case kReplace_CmdID: {
554 OnUpdateFindCommands (enabler);
555 return true;
556 }
557 case kReplaceAgain_CmdID: {
558 OnUpdateFindCommands (enabler);
559 return true;
560 }
561 case kSpellCheck_CmdID: {
562 OnUpdateSpellCheckCommand (enabler);
563 return true;
564 }
565 case kSelectWord_CmdID:
566 case kSelectTextRow_CmdID:
567 case kSelectParagraph_CmdID: {
568 OnUpdateSelectTextCommand (enabler);
569 return true;
570 }
571 }
572 return false;
573}
574
575/*
576@METHOD: TextInteractor::OnPerformCommand
577@ACCESS: public
578@DESCRIPTION: <p>
579 </p>
580*/
581bool TextInteractor::OnPerformCommand (CommandNumber commandNumber)
582{
583 switch (commandNumber) {
584 case kSelectAll_CmdID: {
585 OnSelectAllCommand ();
586 return true;
587 }
588 case kCut_CmdID: {
589 OnCutCommand ();
590 return true;
591 }
592 case kCopy_CmdID: {
593 OnCopyCommand ();
594 return true;
595 }
596 case kPaste_CmdID: {
597 OnPasteCommand ();
598 return true;
599 }
600 case kClear_CmdID: {
601 OnClearCommand ();
602 return true;
603 }
604 case kUndo_CmdID: {
605 OnUndoCommand ();
606 return true;
607 }
608 case kRedo_CmdID: {
609 OnRedoCommand ();
610 return true;
611 }
612 case kFind_CmdID: {
613 OnFindCommand ();
614 return true;
615 }
616 case kFindAgain_CmdID: {
617 OnFindAgainCommand ();
618 return true;
619 }
620 case kEnterFindString_CmdID: {
621 OnEnterFindString ();
622 return true;
623 }
624 case kReplace_CmdID: {
625 OnReplaceCommand ();
626 return true;
627 }
628 case kReplaceAgain_CmdID: {
629 OnReplaceAgainCommand ();
630 return true;
631 }
632 case kSpellCheck_CmdID: {
633 OnSpellCheckCommand ();
634 return true;
635 }
636 case kSelectWord_CmdID:
637 case kSelectTextRow_CmdID:
638 case kSelectParagraph_CmdID: {
639 OnPerformSelectTextCommand (commandNumber);
640 return true;
641 }
642 }
643 return false;
644}
645
646void TextInteractor::OnUpdateCutCopyClearCommand (CommandUpdater* enabler)
647{
648 RequireNotNull (enabler);
649 size_t start;
650 size_t end;
651 static_cast<TextImager*> (this)->GetSelection (&start, &end);
652 enabler->SetEnabled (start != end);
653}
654
655void TextInteractor::OnUpdatePasteCommand (CommandUpdater* enabler)
656{
657 RequireNotNull (enabler);
658 //tmphack
659 enabler->SetEnabled (true);
660}
661
662void TextInteractor::OnUpdateUndoRedoCommand (CommandUpdater* enabler)
663{
664 RequireNotNull (enabler);
665 if (GetCommandHandler () == nullptr) {
666 enabler->SetEnabled (false);
667 }
668 else {
669 if (enabler->GetCmdID () == kUndo_CmdID) {
670 enabler->SetEnabled (GetCommandHandler ()->CanUndo ());
671
672 SDKString menuItemText =
673 Characters::CString::Format (GetCommandNames ().fUndoFormatString.c_str (), GetCommandHandler ()->GetUndoCmdName ());
674 enabler->SetText (menuItemText.c_str ());
675 }
676 else if (enabler->GetCmdID () == kRedo_CmdID) {
677 enabler->SetEnabled (GetCommandHandler ()->CanRedo ());
678
679 SDKString menuItemText =
680 Characters::CString::Format (GetCommandNames ().fRedoFormatString.c_str (), GetCommandHandler ()->GetRedoCmdName ());
681 enabler->SetText (menuItemText.c_str ());
682 }
683 }
684}
685
686void TextInteractor::OnUpdateSelectTextCommand (CommandUpdater* enabler)
687{
688 RequireNotNull (enabler);
689 enabler->SetEnabled (true);
690}
691
692void TextInteractor::OnPerformSelectTextCommand (CommandNumber commandNumber)
693{
694 // Note: these are intentionally made not undoable, since they don't modify data
695 size_t oldSelStart = GetSelectionStart ();
696 size_t oldSelEnd = GetSelectionEnd ();
697 size_t newSelStart = oldSelStart;
698 size_t newSelEnd = oldSelEnd;
699 switch (commandNumber) {
700 case kSelectWord_CmdID: {
701 size_t wordStart = 0;
702 size_t wordEnd = 0;
703 bool wordReal = false;
704 GetTextStore ().FindWordBreaks (oldSelStart, &wordStart, &wordEnd, &wordReal);
705 if (wordReal) {
706 Assert (wordStart <= oldSelStart);
707 newSelStart = wordStart;
708 }
709 else {
710 // See if we were just after a word
711 GetTextStore ().FindWordBreaks (FindPreviousCharacter (oldSelStart), &wordStart, &wordEnd, &wordReal);
712 if (wordReal) {
713 Assert (wordStart <= oldSelStart);
714 newSelStart = wordStart;
715 }
716 }
717
718 GetTextStore ().FindWordBreaks (oldSelEnd, &wordStart, &wordEnd, &wordReal);
719 if (wordReal) {
720 newSelEnd = wordEnd;
721 }
722 } break;
723 case kSelectTextRow_CmdID: {
724 newSelStart = GetStartOfRowContainingPosition (oldSelStart);
725 // check if oldSelEnd is ALREADY the end of a row - and if so - don't change it,
726 // but otherwise, do so.
727 if (oldSelStart == oldSelEnd or oldSelEnd != GetStartOfRowContainingPosition (oldSelEnd)) {
728 newSelEnd = GetEndOfRowContainingPosition (oldSelEnd);
729 }
730 } break;
731 case kSelectParagraph_CmdID: {
732 newSelStart = GetTextStore ().GetStartOfLineContainingPosition (oldSelStart);
733
734 // check if oldSelEnd is ALREADY the end of a paragraph - and if so - don't change it,
735 // but otherwise, do so.
736 if (oldSelStart == oldSelEnd or oldSelEnd != GetTextStore ().GetStartOfLineContainingPosition (oldSelEnd)) {
737 newSelEnd = GetTextStore ().GetEndOfLineContainingPosition (oldSelEnd);
738 // grab one past endofline - so we select the NEWLINE at the end...
739 if (newSelEnd < GetEnd ()) {
740 Led_tChar c;
741 GetTextStore ().CopyOut (newSelEnd, 1, &c);
742 if (c == '\n') {
743 newSelEnd = FindNextCharacter (newSelEnd);
744 }
745 }
746 }
747 } break;
748 }
749 SetSelection (newSelStart, newSelEnd);
750}
751
752namespace {
753 vector<Led_tString> MergeRecentFindStrings (const Led_tString& s, const vector<Led_tString>& oldRecents);
754}
755
756void TextInteractor::OnFindCommand ()
757{
758 SearchParameters parameters = GetSearchParameters ();
759 bool pressedOK = false;
760 GetDialogSupport ().DisplayFindDialog (&parameters.fMatchString, parameters.fRecentFindStrings, &parameters.fWrapSearch,
761 &parameters.fWholeWordSearch, &parameters.fCaseSensativeSearch, &pressedOK);
762 parameters.fRecentFindStrings = MergeRecentFindStrings (parameters.fMatchString, parameters.fRecentFindStrings);
763 SetSearchParameters (parameters);
764 if (pressedOK) {
765 OnFindAgainCommand ();
766 }
767}
768
769void TextInteractor::OnReplaceCommand ()
770{
771 SearchParameters parameters = GetSearchParameters ();
772 ReplaceParameters rParameters = GetReplaceParameters ();
773 DialogSupport::ReplaceButtonPressed pressed =
774 GetDialogSupport ().DisplayReplaceDialog (&parameters.fMatchString, parameters.fRecentFindStrings, &rParameters.fReplaceWith,
775 &parameters.fWrapSearch, &parameters.fWholeWordSearch, &parameters.fCaseSensativeSearch);
776 parameters.fRecentFindStrings = MergeRecentFindStrings (parameters.fMatchString, parameters.fRecentFindStrings);
777 SetSearchParameters (parameters);
778 SetReplaceParameters (rParameters);
779 switch (pressed) {
780 case TextInteractor::DialogSupport::eReplaceButton_Find:
781 OnFindAgainCommand ();
782 break;
783 case TextInteractor::DialogSupport::eReplaceButton_Replace:
784 OnDoReplaceCommand (parameters, rParameters.fReplaceWith);
785 break;
786 case TextInteractor::DialogSupport::eReplaceButton_ReplaceAll:
787 OnDoReplaceAllCommand (parameters, rParameters.fReplaceWith);
788 break;
789 case TextInteractor::DialogSupport::eReplaceButton_ReplaceAllInSelection:
790 OnDoReplaceAllInSelectionCommand (parameters, rParameters.fReplaceWith);
791 break;
792 }
793}
794
795void TextInteractor::OnReplaceAgainCommand ()
796{
797 SearchParameters parameters = GetSearchParameters ();
798 ReplaceParameters rParameters = GetReplaceParameters ();
799 OnDoReplaceCommand (parameters, rParameters.fReplaceWith);
800}
801
802void TextInteractor::OnDoReplaceCommand (const SearchParameters& searchFor, const Led_tString& replaceWith)
803{
804 BreakInGroupedCommands ();
805 // search for last text entered into find dialog (could have been a find again).
806 size_t origSelStart = GetSelectionStart ();
807 size_t origSelEnd = GetSelectionEnd ();
808 size_t whereTo = GetTextStore ().Find (searchFor, origSelEnd);
809 if ((whereTo == kBadIndex) or (whereTo == origSelStart and whereTo + searchFor.fMatchString.length () == origSelEnd)) {
810 Led_BeepNotify ();
811 }
812 else {
813 InteractiveModeUpdater iuMode (*this);
814 size_t replaceStart = whereTo;
815 size_t replaceEnd = whereTo + searchFor.fMatchString.length ();
816 UndoableContextHelper undoContext (*this, GetCommandNames ().fReplaceCommandName, replaceStart, replaceEnd, GetSelectionStart (),
817 GetSelectionEnd (), false);
818 {
819 InteractiveReplace_ (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd (), replaceWith.c_str (), replaceWith.length ());
820 SetSelection (whereTo, whereTo + replaceWith.length ());
821 }
822 undoContext.CommandComplete ();
823 ScrollToSelection ();
824 }
825 BreakInGroupedCommands ();
826}
827
828void TextInteractor::OnDoReplaceAllCommand (const SearchParameters& searchFor, const Led_tString& replaceWith)
829{
830 InteractiveModeUpdater iuMode (*this);
831 BreakInGroupedCommands ();
832 size_t startAt = 0;
833 while (true) {
834 size_t whereTo = GetTextStore ().Find (searchFor, startAt, GetTextStore ().GetEnd ());
835 if (whereTo == kBadIndex) {
836 break;
837 }
838 else {
839 size_t replaceStart = whereTo;
840 size_t replaceEnd = whereTo + searchFor.fMatchString.length ();
841 UndoableContextHelper undoContext (*this, GetCommandNames ().fReplaceAllCommandName, replaceStart, replaceEnd,
842 GetSelectionStart (), GetSelectionEnd (), false);
843 {
844 InteractiveReplace_ (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd (), replaceWith.c_str (), replaceWith.length ());
845 SetSelection (whereTo, whereTo + replaceWith.length ());
846 startAt = whereTo + replaceWith.length ();
847 }
848 undoContext.CommandComplete ();
849 }
850 }
851 ScrollToSelection ();
852 BreakInGroupedCommands ();
853}
854
855void TextInteractor::OnDoReplaceAllInSelectionCommand (const SearchParameters& searchFor, const Led_tString& replaceWith)
856{
857 InteractiveModeUpdater iuMode (*this);
858 BreakInGroupedCommands ();
859 size_t startAt = GetSelectionStart ();
860 TempMarker selectionRegion (GetTextStore (), startAt, GetSelectionEnd ());
861 while (true) {
862 Assert (startAt <= selectionRegion.GetEnd ());
863 size_t whereTo = GetTextStore ().Find (searchFor, startAt, selectionRegion.GetEnd ());
864 if (whereTo == kBadIndex) {
865 break;
866 }
867 else {
868 size_t replaceStart = whereTo;
869 size_t replaceEnd = whereTo + searchFor.fMatchString.length ();
870 UndoableContextHelper undoContext (*this, GetCommandNames ().fReplaceAllInSelectionCommandName, replaceStart, replaceEnd,
871 GetSelectionStart (), GetSelectionEnd (), false);
872 {
873 InteractiveReplace_ (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd (), replaceWith.c_str (), replaceWith.length ());
874 SetSelection (whereTo, whereTo + replaceWith.length ());
875 startAt = whereTo + replaceWith.length ();
876 }
877 undoContext.CommandComplete ();
878 }
879 }
880 ScrollToSelection ();
881 BreakInGroupedCommands ();
882}
883
884void TextInteractor::OnFindAgainCommand ()
885{
886 TextStore::SearchParameters parameters = GetSearchParameters ();
887
888 // search for last text entered into find dialog (could have been a find again).
889 size_t origSelStart = GetSelectionStart ();
890 size_t origSelEnd = GetSelectionEnd ();
891 size_t whereTo = GetTextStore ().Find (parameters, origSelEnd);
892 if ((whereTo == kBadIndex) or (whereTo == origSelStart and whereTo + parameters.fMatchString.length () == origSelEnd)) {
893 Led_BeepNotify ();
894 }
895 else {
896 SetSelection (whereTo, whereTo + parameters.fMatchString.length ());
897 ScrollToSelection ();
898 }
899}
900
901void TextInteractor::OnEnterFindString ()
902{
903 SearchParameters parameters = GetSearchParameters ();
904
905 size_t selStart = GetSelectionStart ();
906 size_t selEnd = GetSelectionEnd ();
907 size_t selLength = selEnd - selStart;
908
909 Memory::StackBuffer<Led_tChar> buf{Memory::eUninitialized, selLength};
910 CopyOut (selStart, selLength, buf.data ());
911 parameters.fMatchString = Led_tString{buf.data (), selLength};
912 parameters.fRecentFindStrings = MergeRecentFindStrings (parameters.fMatchString, parameters.fRecentFindStrings);
913 SetSearchParameters (parameters);
914}
915
916void TextInteractor::OnUpdateFindCommands (CommandUpdater* enabler)
917{
918 RequireNotNull (enabler);
919 if (enabler->GetCmdID () == kFind_CmdID) {
920 enabler->SetEnabled (true);
921 }
922 else if (enabler->GetCmdID () == kFindAgain_CmdID) {
923 enabler->SetEnabled (GetSearchParameters ().fMatchString.length () != 0);
924 }
925 else if (enabler->GetCmdID () == kEnterFindString_CmdID) {
926 enabler->SetEnabled (GetSelectionStart () != GetSelectionEnd ());
927 }
928 else if (enabler->GetCmdID () == kReplace_CmdID) {
929 enabler->SetEnabled (true);
930 }
931 else if (enabler->GetCmdID () == kReplaceAgain_CmdID) {
932 enabler->SetEnabled (GetSearchParameters ().fMatchString.length () != 0);
933 }
934}
935
936TextInteractor::SearchParameters TextInteractor::GetSearchParameters () const
937{
938 return sSearchParameters;
939}
940
941void TextInteractor::SetSearchParameters (const SearchParameters& sp)
942{
943 sSearchParameters = sp;
944}
945
946TextInteractor::ReplaceParameters TextInteractor::GetReplaceParameters () const
947{
948 return sReplaceParameters;
949}
950
951void TextInteractor::SetReplaceParameters (const ReplaceParameters& rp)
952{
953 sReplaceParameters = rp;
954}
955
956vector<Led_tString> TextInteractor::MergeRecentFindStrings (const Led_tString& s, const vector<Led_tString>& oldRecents)
957{
958 const unsigned int kMaxEntries = 20;
959 vector<Led_tString> result = oldRecents;
960 // See if the given string appears in the list. If so - erase it (so it only appears once). Prepend the new string
961 // (most recent) to the top of the list. Truncate any entries beyond some magic #.
962 vector<Led_tString>::iterator i = std::find (result.begin (), result.end (), s);
963 if (i != result.end ()) {
964 result.erase (i);
965 }
966 result.insert (result.begin (), s);
967 if (result.size () > kMaxEntries) {
968 result.erase (result.begin () + kMaxEntries, result.end ());
969 }
970 return result;
971}
972
973void TextInteractor::OnSpellCheckCommand ()
974{
975 if (GetSpellCheckEngine () == nullptr) {
976 Led_BeepNotify ();
977 }
978 else {
979 MyCallback cb (*this);
980 GetDialogSupport ().DisplaySpellCheckDialog (cb);
981 }
982}
983
984void TextInteractor::OnUpdateSpellCheckCommand (CommandUpdater* enabler)
985{
986 RequireNotNull (enabler);
987 enabler->SetEnabled (GetSpellCheckEngine () != nullptr);
988}
989
990/*
991@METHOD: TextInteractor::SetDefaultUpdateMode
992@ACCESS: protected
993@DESCRIPTION: <p>TextInteractor's have an associated default UpdateMode. This is the update mode which is
994 used by methods which used the eDefaultUpdateMode argument (most default UpdateMode args in Led are this value).</p>
995 <p>This should <em>not</em> be set directly. Instead, instantiate a @'TextInteractor::TemporarilySetUpdateMode' object on
996 the stack.</p>
997*/
998void TextInteractor::SetDefaultUpdateMode (UpdateMode defaultUpdateMode)
999{
1000 if (defaultUpdateMode != eDefaultUpdate) { // setting update mode to 'eDefaultUpdate' is synonomous with a NO-OP
1001 fDefaultUpdateMode = defaultUpdateMode;
1002 }
1003}
1004
1005/*
1006@METHOD: TextInteractor::LooksLikeSmartPastableText
1007@ACCESS: protected
1008@DESCRIPTION: <p>This is called internally when text is pasted or dropped in a Led text buffer.
1009 Right now - it looks at the selection range to see if the text surrounding the selection boundaries
1010 has space characters and sets this information into @'TextInteractor::SmartCNPInfo'.
1011 </p>
1012 <p>This code is still far from perfect, but now (SPR#1286) appears to work pretty decently.
1013 It may still see significant revision at some point as part of SPR #1040.
1014 </p>
1015*/
1016bool TextInteractor::LooksLikeSmartPastableText ([[maybe_unused]] const Led_tChar* text, size_t /*nTextTChars*/, SmartCNPInfo* smartCNPInfo) const
1017{
1018 RequireNotNull (text);
1019 RequireNotNull (smartCNPInfo);
1020 if (GetSmartCutAndPasteMode ()) {
1021 size_t selStart = GetSelectionStart ();
1022 size_t selEnd = GetSelectionEnd ();
1023
1024 // If both current and prev chars where non-space, insert a space between.
1025 if (selStart == 0 or selStart >= GetEnd ()) {
1026 smartCNPInfo->fWordBreakAtSelStart = true;
1027 }
1028 else {
1029 size_t prev = FindPreviousCharacter (selStart);
1030 Led_tChar prevC;
1031 CopyOut (prev, 1, &prevC);
1032 Led_tChar c;
1033 CopyOut (selStart, 1, &c);
1034 smartCNPInfo->fWordBreakAtSelStart = (IsShouldBeSepWithWhitespaceWordChar (c) != IsShouldBeSepWithWhitespaceWordChar (prevC));
1035 }
1036
1037 if (selEnd == 0 or selEnd >= GetEnd ()) {
1038 smartCNPInfo->fWordBreakAtSelEnd = true;
1039 }
1040 else {
1041 size_t prev = FindPreviousCharacter (selEnd);
1042 Led_tChar prevC;
1043 CopyOut (prev, 1, &prevC);
1044 Led_tChar c;
1045 CopyOut (selEnd, 1, &c);
1046 smartCNPInfo->fWordBreakAtSelEnd = (IsShouldBeSepWithWhitespaceWordChar (c) != IsShouldBeSepWithWhitespaceWordChar (prevC));
1047 }
1048 }
1049 return true;
1050}
1051
1052/*
1053@METHOD: TextInteractor::OptionallyAddExtraSpaceForSmartCutAndPasteModeAdds
1054@ACCESS: protected
1055@DESCRIPTION: <p>See @'TextInteractor::GetSmartCutAndPasteMode'.</p>
1056 <p>The way in which which we handle smart cut-and-paste is twofold. We have sometimes add extra spaces
1057 (which is done in this routine), and sometimes we must delete extra spaces
1058 (which is done in @'TextInteractor::OptionallyExpandSelectionForSmartCutAndPasteModeDeletes').</p>
1059 <p>We must be careful todo our space insertion in such a way that it is friendly to the UNDO system (doesn't
1060 appear as a separate undo event etc).</p>
1061 <p>Note - this routine is called after the update has taken place. And it looks at the text
1062 at the LHS of where the insertion was done, and at the RHS of where it finihsed. The RHS of where
1063 it finished is always assumed to be the result of a call to @'TextImager::GetSelectionEnd'. And the start
1064 is given by the 'selStart' argument.</p>
1065*/
1066void TextInteractor::OptionallyAddExtraSpaceForSmartCutAndPasteModeAdds (size_t selStart, const SmartCNPInfo& smartCNPInfo)
1067{
1068 size_t selEnd = GetSelectionEnd ();
1069
1070 Require (0 <= selStart);
1071 Require (selStart <= selEnd);
1072 Require (selEnd <= GetTextStore ().GetEnd ());
1073
1074 if (GetSmartCutAndPasteMode ()) {
1075 if (selEnd > 0 and selEnd < GetTextStore ().GetEnd ()) {
1076 size_t prev = FindPreviousCharacter (selEnd);
1077 Led_tChar prevC;
1078 CopyOut (prev, 1, &prevC);
1079 Led_tChar c;
1080 CopyOut (selEnd, 1, &c);
1081 if (smartCNPInfo.fWordBreakAtSelEnd and IsShouldBeSepWithWhitespaceWordChar (c) and IsShouldBeSepWithWhitespaceWordChar (prevC)) {
1082 InteractiveReplace_ (selEnd, selEnd, LED_TCHAR_OF (" "), 1, false);
1083 // Cannot pass 'updateSelection' flag in the above InteractiveReplace_ () call because
1084 // that code only works under restrictive circumstances (see require calls in that code).
1085 // Still - we MUST advance the end of the selection to take into account the space added. Not
1086 // just for cosmetic reasons, but because the current (as of Led 2.3b8) - and probably all future versions-
1087 // Undo code uses the selection end at the end of insertions to see how much text needs to be
1088 // saved for undoing purposes.
1089 // LGP990401
1090 ++selEnd;
1091 SetSelection (selEnd, selEnd);
1092 }
1093 }
1094 // If both current and prev chars where non-space, insert a space between.
1095 if (selStart > 0 and selStart < GetTextStore ().GetEnd ()) {
1096 size_t prev = FindPreviousCharacter (selStart);
1097 Led_tChar prevC;
1098 CopyOut (prev, 1, &prevC);
1099 Led_tChar c;
1100 CopyOut (selStart, 1, &c);
1101 if (smartCNPInfo.fWordBreakAtSelStart and IsShouldBeSepWithWhitespaceWordChar (c) and IsShouldBeSepWithWhitespaceWordChar (prevC)) {
1102 // Programming Confusion Note:
1103 //
1104 // Not quite sure why we pass the updateCursorPosition=false param here. Seems like it should
1105 // be true. But that seems to screw up the enclosing undo information.
1106 // Well, I guess if it aint broke, don't fix it. Least not right now... --LGP 970315.
1107 InteractiveReplace_ (selStart, selStart, LED_TCHAR_OF (" "), 1, false);
1108 }
1109 else {
1110 if (IsSmartSpace (c) and IsSmartSpace (prevC)) {
1111 InteractiveReplace_ (selStart, FindNextCharacter (selStart), nullptr, 0, false);
1112 }
1113 }
1114 }
1115 }
1116}
1117
1118/*
1119@METHOD: TextInteractor::OptionallyExpandSelectionForSmartCutAndPasteModeDeletes
1120@DESCRIPTION: <p>See @'TextInteractor::GetSmartCutAndPasteMode'.</p>
1121*/
1122void TextInteractor::OptionallyExpandSelectionForSmartCutAndPasteModeDeletes (size_t* selStart, size_t* selEnd)
1123{
1124 RequireNotNull (selStart);
1125 RequireNotNull (selEnd);
1126 Require (0 <= *selStart);
1127 Require (*selStart <= *selEnd);
1128 Require (*selEnd <= GetTextStore ().GetEnd ());
1129
1130 if (GetSmartCutAndPasteMode ()) {
1131 size_t realStart = *selStart;
1132 size_t realEnd = *selEnd;
1133 size_t newStart = realStart;
1134 size_t newEnd = realEnd;
1135
1136 // if selStart is in whitespace, or in the middle of a word,
1137 // or selSend is just after whitespace or in the middle of a word then don't be smart.
1138 // LGP 970315 (amended LGP990415)
1139 {
1140 if (realStart < GetEnd ()) {
1141 Led_tChar c;
1142 CopyOut (realStart, 1, &c);
1143 if (Character (c).IsWhitespace ()) {
1144 return;
1145 }
1146 if (realStart > 0) {
1147 CopyOut (FindPreviousCharacter (realStart), 1, &c);
1148 if (IsShouldBeSepWithWhitespaceWordChar (c)) {
1149 return;
1150 }
1151 }
1152 }
1153
1154 if (realStart < realEnd and realEnd < GetEnd ()) {
1155 Led_tChar c;
1156 CopyOut (FindPreviousCharacter (realEnd), 1, &c);
1157 if (Character (c).IsWhitespace ()) {
1158 return;
1159 }
1160 if (realEnd < GetEnd ()) {
1161 CopyOut (realEnd, 1, &c);
1162 if (IsShouldBeSepWithWhitespaceWordChar (c)) {
1163 return;
1164 }
1165 }
1166 }
1167 }
1168
1169 // Now see if at the cursor we have extra spaces - scan first backward, and then forward. Should be at most one.
1170 Led_tChar c;
1171 // back
1172 {
1173 if (newStart > 0) {
1174 size_t prev = FindPreviousCharacter (newStart);
1175 CopyOut (prev, 1, &c);
1176 size_t prevprev = FindPreviousCharacter (prev);
1177 Led_tChar prevprevC;
1178 CopyOut (prevprev, 1, &prevprevC);
1179 if (prevprev != prev and IsSmartSpace (c) and not(IsSmartSpace (prevprevC) or prevprevC == '\n')) {
1180 newStart = prev;
1181 }
1182 }
1183 }
1184 // and forth
1185 {
1186 if (newEnd < GetEnd ()) {
1187 CopyOut (newEnd, 1, &c);
1188 Led_tChar charBeforeStart = '\0';
1189 if (newStart != 0) {
1190 CopyOut (FindPreviousCharacter (newStart), 1, &charBeforeStart);
1191 }
1192 if (IsSmartSpace (c)) {
1193 Assert (newEnd < FindNextCharacter (newEnd)); // Assert not looping!
1194 newEnd = FindNextCharacter (newEnd);
1195 if (newEnd < GetEnd ()) {
1196 // As a result of this change - don't put two chars together that should be separated by whitespace
1197 Led_tChar nextChar = '\0';
1198 CopyOut (newEnd, 1, &nextChar);
1199 if (IsShouldBeSepWithWhitespaceWordChar (charBeforeStart) and IsShouldBeSepWithWhitespaceWordChar (nextChar)) {
1200 newEnd = FindPreviousCharacter (newEnd);
1201 }
1202 }
1203 }
1204 }
1205 }
1206
1207 *selStart = newStart;
1208 *selEnd = newEnd;
1209 }
1210}
1211
1212/*
1213@METHOD: TextInteractor::SetSelectionShown
1214@DESCRIPTION: <p>See TextInteractor::GetSelectionShown. Typically this method isn't called directly by user code, but
1215 from within the class library wrappers (e.g. Led_MFC) on gain/lose focus events.</p>
1216*/
1217void TextInteractor::SetSelectionShown (bool shown)
1218{
1219 SetSelectionShown (shown, eDefaultUpdate);
1220}
1221
1222void TextInteractor::SetSelectionShown (bool shown, UpdateMode updateMode)
1223{
1224 if (GetSelectionShown () != shown) {
1225 TextImager::SetSelectionShown (shown);
1226 Refresh (GetSelectionStart (), GetSelectionEnd (), updateMode);
1227 }
1228}
1229
1230/*
1231@METHOD: TextInteractor::SetSelection
1232@DESCRIPTION: <p>See @'TextInteractor::GetSelection'.</p>
1233*/
1234void TextInteractor::SetSelection (size_t start, size_t end)
1235{
1236 Assert (end <= GetEnd ());
1237 UpdateMode updateMode = GetDefaultUpdateMode ();
1238
1239 // This isn't quite perfect for the case of eImmediateUpdate- but should be close enough...
1240 if (start != GetSelectionStart () or end != GetSelectionEnd ()) {
1241 IdleManager::NonIdleContext nonIdleContext;
1242
1243 size_t oldSelectionStart = GetSelectionStart ();
1244 size_t oldSelectionEnd = GetSelectionEnd ();
1245
1246 /*
1247 * Update fLeftSideOfSelectionInteresting. if start changed, we are interested in LHS, and
1248 * if end changed we are interested in RHS. But if BOTH changed then just assume interested
1249 * in RHS.
1250 */
1251 if (start == GetSelectionStart ()) {
1252 fLeftSideOfSelectionInteresting = false;
1253 }
1254 else if (end == GetSelectionEnd ()) {
1255 fLeftSideOfSelectionInteresting = true;
1256 }
1257 else if ((start < GetSelectionStart ()) == (end < GetSelectionEnd ())) {
1258 fLeftSideOfSelectionInteresting = (start < GetSelectionStart ());
1259 }
1260
1261 /*
1262 * Note we must invalidate the caret state before and after changing the selction so
1263 * that we erase the old position, and draw in the new.
1264 */
1265 InvalidateCaretState ();
1266
1267 TextImager::SetSelection (start, end);
1268
1269 if ((GetSelectionShown () or GetUseSecondaryHilight ()) and updateMode != eNoUpdate) {
1270 /*
1271 * What we REALLY want to invalidate is the CHANGE in hilight region. Not the UNION.
1272 *
1273 * The difference (invalid area) is Union (a,b)-Intersection(a,b). Now with the facilities at our
1274 * disposal, there doesn't appear any obvious way to compute this. So we hack it out
1275 * a bit.
1276 */
1277 UpdateMode useUpdateMode = (updateMode == eImmediateUpdate) ? eDelayedUpdate : updateMode;
1278
1279 size_t lhsOuter = min (oldSelectionStart, GetSelectionStart ()); // left of ALL 4
1280 size_t rhsOuter = max (oldSelectionEnd, GetSelectionEnd ()); // right of ALL 4
1281 size_t lhsInner = max (oldSelectionStart, GetSelectionStart ()); // the two inner ones - lhsInner could be >=< rhsInner
1282 size_t rhsInner = min (oldSelectionEnd, GetSelectionEnd ());
1283 Assert (lhsOuter <= rhsOuter); // See!- left of ALL 4
1284 Assert (lhsOuter <= lhsInner);
1285 Assert (lhsOuter <= rhsInner);
1286 Assert (lhsOuter <= rhsOuter); // See!- right of ALL 4
1287 Assert (lhsInner <= rhsOuter);
1288 Assert (rhsInner <= rhsOuter);
1289 /*
1290 * SPR#0973 - added in the FindPrev/FindNext calls to expand the region we update slightly. Reason is cuz
1291 * with new hilight display code - expanding the selection slightly at the end can affect the other end by
1292 * up to one character (rarely - but best to over-invalidate than under-invalidate).
1293 */
1294 Refresh (FindPreviousCharacter (lhsOuter), FindNextCharacter (lhsInner), useUpdateMode);
1295 Refresh (FindPreviousCharacter (rhsInner), FindNextCharacter (rhsOuter), useUpdateMode);
1296 }
1297
1298 InvalidateCaretState ();
1299 }
1300 if (updateMode == eImmediateUpdate) {
1301 Update ();
1302 }
1303 RecomputeSelectionGoalColumn ();
1304}
1305
1306void TextInteractor::SetSelection (size_t start, size_t end, UpdateMode updateMode)
1307{
1308 TemporarilySetUpdateMode updateModeSetter (*this, updateMode);
1309 TextImager* tim = this; // Dynamicly bind to 1-arg version. Direct call would select overloaded version from this class!
1310 tim->SetSelection (start, end);
1311}
1312
1313/*
1314@METHOD: TextInteractor::ScrollToSelection
1315@DESCRIPTION: <p>Scroll the current window, so that the selection is showing. Calls TextInteractor::ScrollSoShowing ().
1316 Since TextInteractor::ScrollSoShowing () requires two marker-positions to try to show, we use the instance variable
1317 fLeftSideOfSelectionInteresting to guess which side of the selection the user is probably most interested in.
1318 This routine also pays attention to @'TextImager::GetCaretShownAfterPos' so that it shows the right part of
1319 the screen in ambiguous cases.</p>
1320 <p>See TextImager::ScrollSoShowing for more details. But basicly this tries to show the entire selection, if
1321 possible. And uses the flag to pick which side of the selection to use if only part of it can be made visible in the
1322 current window at once.</p>
1323*/
1324void TextInteractor::ScrollToSelection (UpdateMode updateMode, bool forceShowSelectionEndpoint)
1325{
1326 size_t selStart = GetSelectionStart ();
1327 size_t selEnd = GetSelectionEnd ();
1328
1329 /*
1330 * Based on SPR#0940. If some of the selection is showing - its not totally clear the user would want us
1331 * to scroll the entire screen to show another part of the selection. Maybe yes - maybe no. But clearly - if the
1332 * ENTIRE SCREEN is selected (as in this SPR - then we don't want to scroll to show a different part of the selection.
1333 * I THINK so anyhow... LGP 2001-05-24
1334 *
1335 * PLUS - fix SPR#1051 - added forceShowSelectionEndpoint flag, so when using cursors, we can force a scroll.
1336 */
1337 if (not forceShowSelectionEndpoint and selStart <= GetMarkerPositionOfStartOfWindow () and selEnd >= GetMarkerPositionOfEndOfWindow ()) {
1338 return;
1339 }
1340
1341 /*
1342 * Why is this so complicated? And is it right?
1343 *
1344 * There are a couple of issues here. One is that the semantics of @'TextImager::ScrollSoShowing'
1345 * are very precise, but perhaps slightly unintuitive. We try to show the CHARACTERS (not marker positions)
1346 * given by the marker-positions specified.
1347 *
1348 * But when you are cursoring around, what you want to see are the CHARACTERS - not the MARKER POSITIONS.
1349 *
1350 * In these ambiguous cases (like at a wrap point), we want to make sure we get the interpretation of which
1351 * character to show correct. And when the user must choose (cannot show whole selection) we want to make sure
1352 * we AT LEAST show the IMPORTANT side of the selection.
1353 *
1354 * The 'lastCharShown = FindPreviousCharacter (selEnd)' bit is cuz we want to show the character BEFORE that
1355 * marker position. And for most APIs the marker pos names the character after that marker position.
1356 *
1357 * -- LGP990212
1358 */
1359 size_t firstCharShown = selStart;
1360 size_t lastCharShown = FindPreviousCharacter (selEnd);
1361 if (fLeftSideOfSelectionInteresting) {
1362 if (firstCharShown != lastCharShown and GetStartOfRowContainingPosition (firstCharShown) != GetStartOfRowContainingPosition (lastCharShown)) {
1363 lastCharShown = firstCharShown;
1364 }
1365 ScrollSoShowing (firstCharShown, lastCharShown, updateMode);
1366 }
1367 else {
1368 if (GetCaretShownAfterPos ()) {
1369 lastCharShown = FindNextCharacter (lastCharShown);
1370 }
1371 if (firstCharShown != lastCharShown and GetStartOfRowContainingPosition (firstCharShown) != GetStartOfRowContainingPosition (lastCharShown)) {
1372 firstCharShown = lastCharShown;
1373 }
1374 ScrollSoShowing (lastCharShown, firstCharShown, updateMode);
1375 }
1376}
1377
1378void TextInteractor::HookLosingTextStore ()
1379{
1380 HookLosingTextStore_ ();
1381 TextImager::HookLosingTextStore ();
1382}
1383
1384void TextInteractor::HookLosingTextStore_ ()
1385{
1386 AbortReplace (fTmpPreReplaceInfo);
1387 fExternalizer.reset ();
1388 fInternalizer.reset ();
1389}
1390
1391void TextInteractor::HookGainedNewTextStore ()
1392{
1393 HookGainedNewTextStore_ ();
1394 TextImager::HookGainedNewTextStore ();
1395}
1396
1397void TextInteractor::HookGainedNewTextStore_ ()
1398{
1399 if (fExternalizer.get () == nullptr) {
1400 SetExternalizer (MakeDefaultExternalizer ());
1401 }
1402 if (fInternalizer.get () == nullptr) {
1403 SetInternalizer (MakeDefaultInternalizer ());
1404 }
1405}
1406
1407/*
1408@METHOD: TextInteractor::ProcessSimpleClick
1409@ACCESS: protected
1410@DESCRIPTION: <p>Bad name. Basicly does a small portion of the click handling code. Vectored out
1411 not only for code sharing, but to provide a hook we can use in the StyledTextInteractor code
1412 to handle embeddings (without massive cut/n/paste duplication of code).</p>
1413 <p>What this does is to convert the click (or double or tripple click into an appropriate
1414 selection. The anchor only has meaning for double clicks with exteneded selection on.
1415 <p>This routine uses the existing selection, and just adjusts that selection.</p>
1416 <p>This routine returns true if all went well, and you should continue with interpretting
1417 click. It returns false if you should assume something clicked on swallowed up the click.</p>
1418 <p>This is not a GREAT line to have drawn for the API boundary. But its a start.</p>
1419 <p>LGP 960303</p>
1420*/
1421bool TextInteractor::ProcessSimpleClick (Led_Point clickedAt, unsigned clickCount, bool extendSelection, size_t* dragAnchor)
1422{
1423 RequireNotNull (dragAnchor);
1424 switch (clickCount) {
1425 case 1: {
1426 size_t newPos = GetCharAtClickLocation (clickedAt);
1427 size_t newSelStart = newPos;
1428 size_t newSelEnd = newPos;
1429 {
1430 WhileTrackingConstrainSelection (&newSelStart, &newSelEnd);
1431 newPos = newSelStart;
1432 }
1433 if (extendSelection) {
1434 newSelStart = min (newSelStart, GetSelectionStart ());
1435 newSelEnd = max (newSelEnd, GetSelectionEnd ());
1436 }
1437
1438 /*
1439 * Set the drag anchor:
1440 *
1441 * Note - we use @'TextInteractor::GetCharAtClickLocation' instead of @'TextImager::GetCharAtWindowLocation'
1442 * so we get the right intended click location between two characters (for an anchor point for drag selection).
1443 *
1444 * Code that is looking to see if the user clicked on an embedding itself will directly call
1445 * @'TextImager::GetCharAtWindowLocation'.
1446 */
1447 if (extendSelection) {
1448 // if we're extending the selection, the anchor should be the OTHER end of the selection (SPR#1364)
1449 if (newPos == newSelStart) {
1450 *dragAnchor = newSelEnd;
1451 }
1452 else {
1453 *dragAnchor = newSelStart;
1454 }
1455 }
1456 else {
1457 *dragAnchor = newPos;
1458 }
1459
1460 // Set flag for how to display caret based on where we clicked
1461 if (not extendSelection) {
1462 SetCaretShownAfterPos (GetCharWindowLocation (newPos).top <= clickedAt.v);
1463 }
1464
1465 WhileTrackingConstrainSelection (&newSelStart, &newSelEnd);
1466 SetSelection (newSelStart, newSelEnd);
1467 } break;
1468
1469 default: {
1470 // others are ignored
1471 } break;
1472 }
1473#if 0
1474 DbgTrace ("TextInteractor::ProcessSimpleClick (tickCount=%f, newMousePos=(%d,%d), clickCount=%d, extendSel=%d, newSelStart=%d, newSelEnd=%d)\n",
1475 Time::GetTickCount (), clickedAt.v, clickedAt.h, clickCount, extendSelection, GetSelectionStart (), GetSelectionEnd ()
1476 );
1477#endif
1478 return true;
1479}
1480
1481/*
1482@METHOD: TextInteractor::UpdateClickCount
1483@DESCRIPTION: <p>Helper to implemented best feeling UI for double click detection.</p>
1484*/
1485void TextInteractor::UpdateClickCount (Time::TimePointSeconds clickAtTime, const Led_Point& clickAtLocation)
1486{
1487 if (ClickTimesAreCloseForDoubleClick (clickAtTime) and PointsAreCloseForDoubleClick (clickAtLocation)) {
1488 IncrementCurClickCount (clickAtTime);
1489 }
1490 else {
1491 SetCurClickCount (1, clickAtTime);
1492 }
1493 fLastMouseDownAt = clickAtLocation;
1494}
1495
1496/*
1497@METHOD: TextInteractor::ClickTimesAreCloseForDoubleClick
1498@DESCRIPTION: <p>Helper to implemented best feeling UI for double click detection. See also @'TextInteractor::UpdateClickCount' ().</p>
1499*/
1500bool TextInteractor::ClickTimesAreCloseForDoubleClick (Time::TimePointSeconds thisClick)
1501{
1502 return (fLastClickedAt + Led_GetDoubleClickTime () >= thisClick);
1503}
1504
1505/*
1506@METHOD: TextInteractor::PointsAreCloseForDoubleClick
1507@DESCRIPTION: <p>Helper to implemented best feeling UI for double click detection. See also @'TextInteractor::UpdateClickCount' ().</p>
1508*/
1509bool TextInteractor::PointsAreCloseForDoubleClick (const Led_Point& p)
1510{
1511 const CoordinateType kMultiClickDistance = 4;
1512 CoordinateType hDelta = p.h - fLastMouseDownAt.h;
1513 if (hDelta < 0) {
1514 hDelta = -hDelta;
1515 }
1516 CoordinateType vDelta = p.v - fLastMouseDownAt.v;
1517 if (vDelta < 0) {
1518 vDelta = -vDelta;
1519 }
1520 return ((hDelta <= kMultiClickDistance) and (vDelta <= kMultiClickDistance));
1521}
1522
1523/*
1524@METHOD: TextInteractor::WhileSimpleMouseTracking
1525@DESCRIPTION: <p>Helper to share code among implementations. Call this while mouse is down to handle autoscrolling,
1526 and selection updating.</p>
1527*/
1528void TextInteractor::WhileSimpleMouseTracking (Led_Point newMousePos, size_t dragAnchor)
1529{
1530#if qStroika_Frameworks_Led_DynamiclyChooseAutoScrollIncrement
1531 Foundation::Time::TimePointSeconds now = Time::GetTickCount ();
1532 static Foundation::Time::TimePointSeconds sLastTimeThrough{};
1533 const Foundation::Time::DurationSeconds kClickThreshold = Led_GetDoubleClickTime () / 3;
1534 bool firstClick = (now - sLastTimeThrough > kClickThreshold);
1535
1536 int increment = firstClick ? 1 : 2;
1537#else
1538 const int increment = 1;
1539#endif
1540
1541 size_t rhsPos = GetCharAtClickLocation (newMousePos);
1542 {
1543 size_t ignored = rhsPos;
1544 WhileTrackingConstrainSelection (&rhsPos, &ignored);
1545 }
1546
1547 /*
1548 * Handle vertical autoscrolling, if necessary.
1549 */
1550 if (rhsPos < GetMarkerPositionOfStartOfWindow ()) {
1551 ScrollByIfRoom (-1, eImmediateUpdate);
1552 rhsPos = GetMarkerPositionOfStartOfWindow ();
1553 }
1554 else if (rhsPos > GetMarkerPositionOfEndOfWindow ()) {
1555 ScrollByIfRoom (1, eImmediateUpdate);
1556 rhsPos = FindNextCharacter (GetMarkerPositionOfEndOfWindow ()); // So we select past to end of window
1557 }
1558
1559 /*
1560 * And horizontal.
1561 */
1562 const int kHScrollIncrementFactor = 4;
1563 if (newMousePos.h < GetWindowRect ().left) {
1564 if (GetHScrollPos () > 0) {
1565 SetHScrollPos (max (0, int (GetHScrollPos ()) - increment * kHScrollIncrementFactor));
1566 }
1567 }
1568 else if (newMousePos.h > GetWindowRect ().right) {
1569 SetHScrollPos (min (static_cast<CoordinateType> (GetHScrollPos () + increment * kHScrollIncrementFactor),
1570 static_cast<CoordinateType> (ComputeMaxHScrollPos ())));
1571 }
1572
1573 size_t newSelStart = min (rhsPos, dragAnchor);
1574 size_t newSelEnd = max (rhsPos, dragAnchor);
1575 WhileTrackingConstrainSelection (&newSelStart, &newSelEnd);
1576 SetSelection (newSelStart, newSelEnd, eImmediateUpdate);
1577
1578#if qStroika_Frameworks_Led_DynamiclyChooseAutoScrollIncrement
1579 sLastTimeThrough = now;
1580#endif
1581}
1582
1583/*
1584@METHOD: TextInteractor::WhileTrackingConstrainSelection
1585@DESCRIPTION: <p>Override this to provide unusual selection behavior during
1586 tracking. For example, if you want to only allow selection of whole lines when
1587 the caps-lock key is down (OK, silly, but its an example), you could check for that
1588 state in this routine, and assure selStart and selEnd are adjusted to meet those
1589 criteria.</p>
1590 <p>By default this calls calls @'TextInteractor::WhileTrackingConstrainSelection_ForWholeWords',
1591 for double clicks and @'TextInteractor::WhileTrackingConstrainSelection_ForWholeRows' for 3 or more clicks.</p>
1592*/
1593void TextInteractor::WhileTrackingConstrainSelection (size_t* selStart, size_t* selEnd)
1594{
1595 RequireNotNull (selStart);
1596 RequireNotNull (selEnd);
1597 Require (GetCurClickCount () > 0);
1598 switch (GetCurClickCount ()) {
1599 case 1: {
1600 // No constraints on a single click
1601 } break;
1602
1603 case 2: {
1604 WhileTrackingConstrainSelection_ForWholeWords (selStart, selEnd);
1605 } break;
1606
1607 default: {
1608 // any more than 3 and we constrain to whole rows
1609 WhileTrackingConstrainSelection_ForWholeRows (selStart, selEnd);
1610 } break;
1611 }
1612}
1613
1614/*
1615@METHOD: TextInteractor::WhileTrackingConstrainSelection_ForWholeWords
1616@DESCRIPTION: <p>See @'TextInteractor::WhileTrackingConstrainSelection'</p>
1617*/
1618void TextInteractor::WhileTrackingConstrainSelection_ForWholeWords (size_t* selStart, size_t* selEnd)
1619{
1620 RequireNotNull (selStart);
1621 RequireNotNull (selEnd);
1622
1623 size_t wordStart = 0;
1624 size_t wordEnd = 0;
1625 bool wordReal = false;
1626 GetTextStore ().FindWordBreaks (*selStart, &wordStart, &wordEnd, &wordReal);
1627 *selStart = wordStart;
1628
1629 GetTextStore ().FindWordBreaks (*selEnd, &wordStart, &wordEnd, &wordReal);
1630#if qStroika_Frameworks_Led_DoubleClickSelectsSpaceAfterWord
1631 if (wordReal) {
1632 // select the space forward...
1633 size_t xWordStart = 0;
1634 size_t xWordEnd = 0;
1635 bool xWordReal = false;
1636 GetTextStore ().FindWordBreaks (wordEnd, &xWordStart, &xWordEnd, &xWordReal);
1637 if (not xWordReal) {
1638 wordEnd = xWordEnd;
1639 }
1640 }
1641#endif
1642 *selEnd = wordEnd;
1643}
1644
1645/*
1646@METHOD: TextInteractor::WhileTrackingConstrainSelection_ForWholeRows
1647@DESCRIPTION: <p>See @'TextInteractor::WhileTrackingConstrainSelection'</p>
1648*/
1649void TextInteractor::WhileTrackingConstrainSelection_ForWholeRows (size_t* selStart, size_t* selEnd)
1650{
1651 RequireNotNull (selStart);
1652 RequireNotNull (selEnd);
1653 Require (*selStart <= *selEnd);
1654
1655 size_t origSelStart = *selStart;
1656 size_t origSelEnd = *selEnd;
1657
1658 *selStart = GetStartOfRowContainingPosition (origSelStart);
1659 *selEnd = GetStartOfNextRowFromRowContainingPosition (*selEnd);
1660 // if at end of buffer, go to end of buffer. Else, select to start of next row
1661 if (*selEnd <= origSelEnd) {
1662 *selEnd = GetEndOfRowContainingPosition (origSelEnd);
1663 }
1664}
1665
1666/*
1667@METHOD: TextInteractor::GetCharAtClickLocation
1668@DESCRIPTION: <p>Just like @'TextImager::GetCharAtWindowLocation'(), but doesn't try to
1669 find the character which encloses the particular point. Rather - we try to
1670 find the character the user was probably trying
1671 to click at to position the cursor. This is typically either the same, or the character
1672 just just following. If we are clicking towards the end of the character - we probably
1673 wanted to click just <em>before</em> the next character. Note this works for BIDI characters.</p>
1674*/
1675size_t TextInteractor::GetCharAtClickLocation (const Led_Point& where) const
1676{
1677 size_t clickedOnChar = GetCharAtWindowLocation (where);
1678 size_t endOfClickedRow = GetEndOfRowContainingPosition (clickedOnChar);
1679
1680 /*
1681 * SPR#1597 (was originally SPR#1232). A click past the end of the wrap point in a wrapped
1682 * line has a value EQUAL to the end of the row. However - its also EQUAL to the start of
1683 * the following row. The trouble is that GetEndOfRowContainingPosition (THAT_POSITION)
1684 * will return the end of the FOLLOWING row in that case.
1685 *
1686 * Since in order for this to happen there must be 1 or more characters in the row (to be a wrap point),
1687 * we can look at the PRECEEDING character, and compute ITS End-of-Row. If thats the same as the
1688 * returned click-position - we must have clicked on the end-of-row, and so we correct
1689 * the value of 'endOfClickedRow'.
1690 */
1691 if (GetEndOfRowContainingPosition (FindPreviousCharacter (clickedOnChar)) == clickedOnChar) {
1692 endOfClickedRow = clickedOnChar;
1693 }
1694
1695 Assert (GetStartOfRowContainingPosition (clickedOnChar) <= clickedOnChar);
1696 Assert (clickedOnChar <= endOfClickedRow);
1697
1698 if (clickedOnChar < endOfClickedRow) { // Don't wrap cuz click past end - LGP 950424
1699 Led_Rect charRect = GetCharWindowLocation (clickedOnChar);
1700 bool clickedToLHSOfChar = (where.h <= charRect.left + CoordinateType (charRect.GetWidth ()) / 2 and charRect.GetWidth () != 0);
1701 if (GetTextDirection (clickedOnChar) == eLeftToRight) {
1702 if (not clickedToLHSOfChar) {
1703 clickedOnChar = FindNextCharacter (clickedOnChar);
1704 }
1705 }
1706 else {
1707 if (clickedToLHSOfChar) {
1708 clickedOnChar = FindNextCharacter (clickedOnChar);
1709 }
1710 }
1711 }
1712 return (clickedOnChar);
1713}
1714
1715void TextInteractor::Draw (const Led_Rect& /*subsetToDraw*/, bool /*printing*/)
1716{
1717 NoteWindowPartiallyUpdated ();
1718 // don't call cuz there is none defined - pure virtual...TextImager::Draw (subsetToDraw, printing);
1719}
1720
1721void TextInteractor::DrawBefore (const Led_Rect& /*subsetToDraw*/, bool /*printing*/)
1722{
1723 // LGP 2003-03-20 - not 100% sure this is necessary or a good idea - but done to keep backward
1724 // compat with old behavior. Once new idle code in place - test without this. See SPR#1366.
1725 if (not fScrollBarParamsValid) {
1726 UpdateScrollBars ();
1727 }
1728}
1729
1730void TextInteractor::DrawAfter (const Led_Rect& /*subsetToDraw*/, bool printing)
1731{
1732 if (GetUseSecondaryHilight () and not GetSelectionShown () and not printing) {
1733 Region r;
1734 GetSelectionWindowRegion (&r, GetSelectionStart (), GetSelectionEnd ());
1735 Tablet_Acquirer tablet_ (this);
1736 Tablet* tablet = tablet_;
1737 tablet->FrameRegion (r, Led_GetSelectedTextBackgroundColor ());
1738 }
1739}
1740
1741void TextInteractor::SetTopRowInWindow (size_t newTopRow, UpdateMode updateMode)
1742{
1743 TemporarilySetUpdateMode updateModeSetter (*this, updateMode);
1744 TextImager* tim = this; // Dynamicly bind to 1-arg version. Direct call would select overloaded version from this class!
1745 tim->SetTopRowInWindow (newTopRow);
1746}
1747
1748void TextInteractor::ScrollByIfRoom (ptrdiff_t downBy, UpdateMode updateMode)
1749{
1750 TemporarilySetUpdateMode updateModeSetter (*this, updateMode);
1751 TextImager* tim = this; // Dynamicly bind to 1-arg version. Direct call would select overloaded version from this class!
1752 tim->ScrollByIfRoom (downBy);
1753}
1754
1755void TextInteractor::ScrollSoShowing (size_t markerPos, size_t andTryToShowMarkerPos, UpdateMode updateMode)
1756{
1757 TemporarilySetUpdateMode updateModeSetter (*this, updateMode);
1758 TextImager* tim = this; // Dynamicly bind to 2-arg version. Direct call would select overloaded version from this class!
1759 tim->ScrollSoShowing (markerPos, andTryToShowMarkerPos);
1760}
1761
1762void TextInteractor::SetDefaultFont (const IncrementalFontSpecification& defaultFont, UpdateMode updateMode)
1763{
1764 TemporarilySetUpdateMode updateModeSetter{*this, updateMode};
1765 TextImager* tim = this; // Dynamicly bind to 1-arg version. Direct call would select overloaded version from this class!
1766 tim->SetDefaultFont (defaultFont);
1767 Refresh ();
1768}
1769
1770void TextInteractor::SetWindowRect (const Led_Rect& windowRect, UpdateMode updateMode)
1771{
1772 TemporarilySetUpdateMode updateModeSetter (*this, updateMode);
1773 TextImager* tim = this; // Dynamicly bind to 1-arg version. Direct call would select overloaded version from this class!
1774 tim->SetWindowRect (windowRect);
1775}
1776
1777void TextInteractor::SetHScrollPos (CoordinateType hScrollPos)
1778{
1779 PreScrollInfo preScrollInfo;
1780 PreScrollHelper (eDefaultUpdate, &preScrollInfo);
1781 TextImager::SetHScrollPos (hScrollPos);
1782 PostScrollHelper (preScrollInfo);
1783}
1784
1785void TextInteractor::SetHScrollPos (CoordinateType hScrollPos, UpdateMode updateMode)
1786{
1787 TemporarilySetUpdateMode updateModeSetter (*this, updateMode);
1788 TextImager* tim = this; // Dynamicly bind to 1-arg version. Direct call would select overloaded version from this class!
1789 tim->SetHScrollPos (hScrollPos);
1790}
1791
1792void TextInteractor::PreScrollHelper (UpdateMode updateMode, PreScrollInfo* preScrollInfo)
1793{
1794 UpdateMode realUpdateMode = RealUpdateMode (updateMode);
1795 preScrollInfo->fUpdateMode = realUpdateMode;
1796 if (realUpdateMode != eNoUpdate) {
1797 preScrollInfo->fOldWindowStart = GetMarkerPositionOfStartOfWindow ();
1798 preScrollInfo->fOldHScrollPos = GetHScrollPos ();
1799 preScrollInfo->fTryTodoScrollbits = GetUseBitmapScrollingOptimization () and bool (realUpdateMode == eImmediateUpdate);
1800 if (preScrollInfo->fTryTodoScrollbits) {
1801 preScrollInfo->fOldLastRowStart = GetMarkerPositionOfStartOfLastRowOfWindow ();
1802 try {
1803 Update (false); // so the stuff we scroll-bits is up to date...
1804 // Wouldn't want to scroll stale bits :-)
1805 }
1806 catch (...) {
1807 preScrollInfo->fTryTodoScrollbits = false;
1808 }
1809 }
1810 }
1811}
1812
1813void TextInteractor::PostScrollHelper (PreScrollInfo preScrollInfo)
1814{
1815 /*
1816 * Only if we scrolled do we need to refresh screen.
1817 */
1818 size_t newStartOfWindow = GetMarkerPositionOfStartOfWindow ();
1819 preScrollInfo.fUpdateMode = RealUpdateMode (preScrollInfo.fUpdateMode);
1820 if (preScrollInfo.fUpdateMode != eNoUpdate and
1821 ((preScrollInfo.fOldWindowStart != newStartOfWindow) or (preScrollInfo.fOldHScrollPos != GetHScrollPos ()))) {
1822
1823 // Don't try this except if we only got vertical scrolling - at least for now...
1824 if (preScrollInfo.fTryTodoScrollbits and preScrollInfo.fOldHScrollPos == GetHScrollPos ()) {
1825 Led_Rect windowRect = GetWindowRect ();
1826
1827 Tablet_Acquirer tablet_{this};
1828 Tablet* tablet = tablet_;
1829 if (preScrollInfo.fOldWindowStart > newStartOfWindow) {
1830 /* *
1831 * Move text (bits) DOWN screen (UP /|\ ARROW).
1832 * |
1833 * |
1834 */
1835 CoordinateType newPos = GetCharWindowLocation (GetStartOfRowContainingPosition (preScrollInfo.fOldWindowStart)).top;
1836 if (newPos > 0) {
1837 try {
1838 tablet->ScrollBitsAndInvalRevealed (windowRect, newPos - windowRect.top);
1839 }
1840 catch (...) {
1841 // Ignore any errors - just don't do scrollbits then...
1842 preScrollInfo.fTryTodoScrollbits = false;
1843 }
1844 }
1845 else {
1846 preScrollInfo.fTryTodoScrollbits = false; // can happen, for example, if only one row fits in window...
1847 }
1848 if (preScrollInfo.fTryTodoScrollbits) {
1849 /*
1850 * Very subtle speed hack. It turns out, in this case, we may need to update a sliver on the top
1851 * of the window (exposed by scrollbits), and the bottom of the window (cuz we don't show partial
1852 * rows). So the BOUNDING RECTANLGE of the update region is the whole window. Why is this
1853 * a problem? In our update code, we use the bounding rectangle of the update region for
1854 * logical clipping. This means we end up drawing (though clipped out) every row of text.
1855 * This can make the scroll operation needlessly slow. By simply doing the update of the
1856 * top sliver first, we assure we always have a nice rectangular update region, so our
1857 * later optimizations work better. -- LGP 961030
1858 */
1859 Update ();
1860
1861 // Now we may not want to allow the partial line to be displayed. Leave that choice to the
1862 // logic in the imager, and repaint the area past the end of the last row
1863 CoordinateType lastRowBottom = GetCharWindowLocation (GetMarkerPositionOfStartOfLastRowOfWindow ()).bottom;
1864 Led_Rect eraser = windowRect;
1865 eraser.top = lastRowBottom;
1866 RefreshWindowRect (eraser, preScrollInfo.fUpdateMode);
1867 }
1868 }
1869 else {
1870 /*
1871 * Move text (bits) UP screen (DOWN | ARROW).
1872 * |
1873 * \|/
1874 * *
1875 */
1876 CoordinateType newPos = GetCharLocationRowRelativeByPosition (newStartOfWindow, preScrollInfo.fOldWindowStart, 5).top;
1877
1878 if (newPos > 0) {
1879 try {
1880 tablet->ScrollBitsAndInvalRevealed (windowRect, -newPos);
1881 }
1882 catch (...) {
1883 // Ignore any errors - just don't do scrollbits then...
1884 preScrollInfo.fTryTodoScrollbits = false;
1885 }
1886 }
1887 else {
1888 preScrollInfo.fTryTodoScrollbits = false; // can happen, for example, if only one row fits in window...
1889 }
1890
1891 if (preScrollInfo.fTryTodoScrollbits) {
1892 // now refresh the exposed portion at the bottom. Note that much of it may have been exposed
1893 // already by the InvalRgn/ScrollWindow calls. But we may have to invalidate even more cuz we don't
1894 // show entire bottom lines.
1895 CoordinateType lastRowBottom = GetCharWindowLocation (preScrollInfo.fOldLastRowStart).bottom;
1896 Led_Rect eraser = windowRect;
1897 eraser.top = lastRowBottom;
1898 RefreshWindowRect (eraser, eDelayedUpdate);
1899 }
1900
1901 if (preScrollInfo.fUpdateMode == eImmediateUpdate) {
1902 Update ();
1903 }
1904 }
1905
1906 // ScrollBitsing succeeded, so we can return now, and avoid the below REFRESH.
1907 // Otherwise, fall through, and handle things the old-fashioned way...
1908 if (preScrollInfo.fTryTodoScrollbits) {
1909 return;
1910 }
1911 }
1912
1913 Refresh (preScrollInfo.fUpdateMode);
1914 }
1915}
1916
1917void TextInteractor::Replace (size_t from, size_t to, const Led_tChar* withWhat, size_t withWhatCharCount, UpdateMode updateMode)
1918{
1919 if (from != to or withWhatCharCount != 0) {
1920 Assert (fDoingUpdateModeReplaceOn == nullptr);
1921 fDoingUpdateModeReplaceOn = this;
1922 try {
1923 PreReplaceInfo preReplaceInfo;
1924 PreReplace (from, to, withWhatCharCount, updateMode, &preReplaceInfo);
1925 GetTextStore ().Replace (from, to, withWhat, withWhatCharCount);
1926 PostReplace (preReplaceInfo);
1927 Assert (fDoingUpdateModeReplaceOn == this);
1928 }
1929 catch (...) {
1930 Assert (fDoingUpdateModeReplaceOn == this);
1931 fDoingUpdateModeReplaceOn = nullptr;
1932 throw;
1933 }
1934 fDoingUpdateModeReplaceOn = nullptr;
1935 }
1936}
1937
1938void TextInteractor::AboutToUpdateText (const MarkerOwner::UpdateInfo& updateInfo)
1939{
1940 TextImager::AboutToUpdateText (updateInfo);
1941 if (fDoingUpdateModeReplaceOn != this) {
1942 // Sometimes a textstore sends notifications about updates beyond the end of the text (e.g. when setting
1943 // styles which apply to newly typed characters). We only pay attention up to the end of the text
1944 // (since that is all we display)
1945 PreReplace (updateInfo.fReplaceFrom, min (updateInfo.fReplaceTo, GetEnd ()), updateInfo.fTextLength, eDefaultUpdate, &fTmpPreReplaceInfo);
1946 }
1947}
1948
1949void TextInteractor::DidUpdateText (const UpdateInfo& updateInfo) noexcept
1950{
1951 TextImager::DidUpdateText (updateInfo);
1952 if (fDoingUpdateModeReplaceOn != this) {
1953 try {
1954 PostReplace (fTmpPreReplaceInfo);
1955 }
1956 catch (...) {
1957 Refresh (); // shouldn't happen? But if it does - this is probably the best we can do to handle it...
1958 }
1959 }
1960}
1961
1962void TextInteractor::PreReplace (size_t from, size_t to, size_t withWhatCharCount, UpdateMode updateMode, PreReplaceInfo* preReplaceInfo)
1963{
1964 RequireNotNull (preReplaceInfo);
1965
1966 if (preReplaceInfo->fTextInteractor != nullptr) { // used as flag to indicate cleaned up state (marker removed)
1967 AbortReplace (*preReplaceInfo);
1968 }
1969
1970 updateMode = RealUpdateMode (updateMode);
1971
1972 preReplaceInfo->fFrom = from;
1973 preReplaceInfo->fTo = to;
1974 preReplaceInfo->fWithWhatCharCount = withWhatCharCount;
1975 preReplaceInfo->fUpdateMode = updateMode;
1976
1977 if (updateMode == eNoUpdate) {
1978 return;
1979 }
1980
1981 if (updateMode == eDelayedUpdate and IsWholeWindowInvalid ()) {
1982 preReplaceInfo->fUpdateMode = eNoUpdate;
1983 return;
1984 }
1985
1986 try {
1987 /*
1988 * Every once in a while, somebody will try todo an edit operation outside of
1989 * the visible window. Though this is rare, when it DOES happen, there are frequently
1990 * several of them (as in reading in a file). So we attempt to specially tweek this
1991 * case. Do a preliminary and then secondary test so we don't slow needlessly waste time
1992 * for the more common case.
1993 *
1994 * Note - we could also just as reasonably tweek the case where we are inserting BEFORE
1995 * the start of the window, but I've found no cases where that comes up, so I don't bother
1996 * for now.
1997 *
1998 * LGP 960515.
1999 */
2000 {
2001 size_t endOfWindow = GetMarkerPositionOfEndOfWindow ();
2002 if (from > endOfWindow) {
2003 /*
2004 * Adding text JUST after the end of the window could - in principle - affect
2005 * the word-breaks of the end of window, so we must be a little more careful.
2006 */
2007 size_t endOfNextRow = GetEndOfRowContainingPosition (FindNextCharacter (endOfWindow));
2008 if (from > endOfNextRow) {
2009 updateMode = eNoUpdate;
2010 preReplaceInfo->fUpdateMode = eNoUpdate;
2011 return;
2012 }
2013 }
2014 }
2015
2016 Assert (updateMode != eNoUpdate);
2017 /*
2018 * Grab a range that includes totally all the rows between the start selection and the end.
2019 *
2020 * Save this region in a Marker (so it gets its bounds adjusted for the edit). Then compute
2021 * its pixelBounds (really we just need its HEIGHT). We will use this later to see
2022 * if the edit has caused a change in the pixel-height of the region, which would mean we
2023 * have to draw not just that region, but all the way to the end of the window.
2024 *
2025 * Also we will use it later to decide if the starting or ending row (word-breaks) have
2026 * changed.
2027 */
2028 size_t startPositionOfRowWhereReplaceBegins = GetStartOfRowContainingPosition (from);
2029 size_t startPositionOfRowAfterReplaceEnds = GetEndOfRowContainingPosition (to);
2030 if (startPositionOfRowAfterReplaceEnds < GetTextStore ().GetEnd ()) {
2031 startPositionOfRowAfterReplaceEnds = GetStartOfRowContainingPosition (FindNextCharacter (startPositionOfRowAfterReplaceEnds));
2032 Assert (GetEndOfRowContainingPosition (to) <= startPositionOfRowAfterReplaceEnds);
2033 }
2034 Assert (startPositionOfRowWhereReplaceBegins <= startPositionOfRowAfterReplaceEnds);
2035
2036 preReplaceInfo->fBoundingUpdateHeight =
2037 GetTextWindowBoundingRect (startPositionOfRowWhereReplaceBegins, startPositionOfRowAfterReplaceEnds).GetHeight ();
2038
2039 /*
2040 * In case the above changes, we may be able to get away with only updating the stable
2041 * typing region, assuming its height hasn't changed.
2042 */
2043 {
2044 size_t expandedFromMarkerPos = 0;
2045 size_t expandedToMarkerPos = 0;
2046 GetStableTypingRegionContaingMarkerRange (from, to, &expandedFromMarkerPos, &expandedToMarkerPos);
2047 if (expandedFromMarkerPos == startPositionOfRowWhereReplaceBegins and expandedToMarkerPos == startPositionOfRowAfterReplaceEnds) {
2048 // Speed tweek. Avoid expensive call to GetTextWindowBoundingRect () - and use old value...
2049 preReplaceInfo->fStableTypingRegionHeight = preReplaceInfo->fBoundingUpdateHeight;
2050 Assert (preReplaceInfo->fStableTypingRegionHeight == GetTextWindowBoundingRect (expandedFromMarkerPos, expandedToMarkerPos).GetHeight ());
2051 }
2052 else {
2053 preReplaceInfo->fStableTypingRegionHeight = GetTextWindowBoundingRect (expandedFromMarkerPos, expandedToMarkerPos).GetHeight ();
2054 }
2055 }
2056
2057 /*
2058 * The marker is one past the end of the final row so any typing just after the end of the row
2059 * gets included.
2060 */
2061 GetTextStore ().AddMarker (&preReplaceInfo->fBoundingUpdateMarker, startPositionOfRowWhereReplaceBegins,
2062 (startPositionOfRowAfterReplaceEnds - startPositionOfRowWhereReplaceBegins) + 1, this);
2063 preReplaceInfo->fTextInteractor = this; // assign after add, cuz this var serves as flag to indicate addMarker call done...
2064 }
2065 catch (NotFullyInitialized&) {
2066 // Fine - we can ignore that..
2067 preReplaceInfo->fUpdateMode = eNoUpdate;
2068 return;
2069 }
2070 catch (...) {
2071 throw;
2072 }
2073}
2074
2075void TextInteractor::PostReplace (PreReplaceInfo& preReplaceInfo)
2076{
2077 UpdateMode updateMode = preReplaceInfo.fUpdateMode;
2078 if (updateMode != eNoUpdate) {
2079 size_t from = preReplaceInfo.fFrom;
2080 size_t withWhatCharCount = preReplaceInfo.fWithWhatCharCount;
2081
2082 size_t newTo = from + withWhatCharCount;
2083
2084 // Subtract one from end cuz we added one earlier so chars appended would grow marker...
2085 size_t startPositionOfRowWhereReplaceBegins = preReplaceInfo.fBoundingUpdateMarker.GetStart ();
2086 size_t startPositionOfRowAfterReplaceEnds = preReplaceInfo.fBoundingUpdateMarker.GetEnd () - 1;
2087 Assert (startPositionOfRowWhereReplaceBegins <= startPositionOfRowAfterReplaceEnds);
2088
2089 size_t stableTypingRegionStart = 0;
2090 size_t stableTypingRegionEnd = 0;
2091 GetStableTypingRegionContaingMarkerRange (from, newTo, &stableTypingRegionStart, &stableTypingRegionEnd);
2092
2093 size_t expandedFromMarkerPos = 0;
2094 size_t expandedToMarkerPos = 0;
2095 ExpandedFromAndToInPostReplace (from, newTo, stableTypingRegionStart, stableTypingRegionEnd, startPositionOfRowWhereReplaceBegins,
2096 startPositionOfRowAfterReplaceEnds, &expandedFromMarkerPos, &expandedToMarkerPos);
2097
2098 Led_Rect windowRect = GetWindowRect ();
2099 Led_Rect expandedFromToMarkerRect = GetTextWindowBoundingRect (expandedFromMarkerPos, expandedToMarkerPos);
2100 Led_Rect updateRect = expandedFromToMarkerRect;
2101
2102 // we must ALWAYS draw to the end of the row (including space after last character)
2103 updateRect.right = windowRect.right;
2104
2105 // Now if we've changed the height of the bounding rows region, we need to repaint to end.
2106 {
2107 Led_Rect revisedRect = expandedFromToMarkerRect; // Speed tweek. Avoid expensive call to GetTextWindowBoundingRect () - and use old value...
2108 if (expandedFromMarkerPos != startPositionOfRowWhereReplaceBegins or expandedToMarkerPos != startPositionOfRowAfterReplaceEnds) {
2109 revisedRect = GetTextWindowBoundingRect (startPositionOfRowWhereReplaceBegins, startPositionOfRowAfterReplaceEnds);
2110 }
2111 Assert (revisedRect == GetTextWindowBoundingRect (startPositionOfRowWhereReplaceBegins, startPositionOfRowAfterReplaceEnds));
2112
2113 if (preReplaceInfo.fBoundingUpdateHeight != revisedRect.GetHeight ()) {
2114 updateRect = Led_Rect (updateRect.top, windowRect.left, windowRect.bottom - updateRect.top, windowRect.GetWidth ());
2115 }
2116 }
2117
2118 // Now if we've changed the height of the stable region, we need to repaint to end of screen.
2119 {
2120 Led_Rect revisedRect = expandedFromToMarkerRect; // Speed tweek. Avoid expensive call to GetTextWindowBoundingRect () - and use old value...
2121 if (expandedFromMarkerPos != stableTypingRegionStart or expandedToMarkerPos != stableTypingRegionEnd) {
2122 revisedRect = GetTextWindowBoundingRect (stableTypingRegionStart, stableTypingRegionEnd);
2123 }
2124 Assert (revisedRect == GetTextWindowBoundingRect (stableTypingRegionStart, stableTypingRegionEnd));
2125 if (preReplaceInfo.fStableTypingRegionHeight != revisedRect.GetHeight ()) {
2126 updateRect = Led_Rect (updateRect.top, windowRect.left, windowRect.bottom - updateRect.top, windowRect.GetWidth ());
2127 }
2128 }
2129
2130 RefreshWindowRect (updateRect, updateMode);
2131 }
2132}
2133
2134void TextInteractor::AbortReplace (PreReplaceInfo& preReplaceInfo)
2135{
2136 if (preReplaceInfo.fTextInteractor != nullptr) {
2137 // remove marker, and set to nullptr to indicate cleaned up.
2138 preReplaceInfo.fTextInteractor->GetTextStore ().RemoveMarker (&preReplaceInfo.fBoundingUpdateMarker);
2139 preReplaceInfo.fTextInteractor = nullptr;
2140 }
2141}
2142
2143void TextInteractor::ExpandedFromAndToInPostReplace (size_t from, size_t newTo, size_t stableTypingRegionStart,
2144 size_t stableTypingRegionEnd, size_t startPositionOfRowWhereReplaceBegins,
2145 size_t startPositionOfRowAfterReplaceEnds, size_t* expandedFrom, size_t* expandedTo)
2146{
2147 RequireNotNull (expandedFrom);
2148 RequireNotNull (expandedTo);
2149
2150 // Edits in a row practically never change the word-break from the previous line, but they CAN - in
2151 // principle - (eg. in the second row there is a very long word, and you insert a space near the
2152 // beginning, the little word-let created might fit on the previous row, in which case you would
2153 // need to redisplay BOTH rows. But we only need to update previous rows or earlier text in this
2154 // row if the row-break point changes - thats what our marker-test here measures.
2155 size_t expandedFromMarkerPos = 0;
2156 if (GetStartOfRowContainingPosition (from) == startPositionOfRowWhereReplaceBegins) {
2157 expandedFromMarkerPos = from;
2158
2159 // On windows - at least temporarily - editing one char can change the WIDTH/MeasureText
2160 // of the preceeding character, since we don't need to add on the overhang anymore.
2161 // So be sure to draw that previous character in that subtle case
2162 // - See SPR# 0340 - LGP 960516
2163 if (expandedFromMarkerPos > startPositionOfRowWhereReplaceBegins) {
2164 expandedFromMarkerPos = FindPreviousCharacter (expandedFromMarkerPos);
2165 }
2166 }
2167 else {
2168 // practically never happens....
2169 expandedFromMarkerPos = stableTypingRegionStart;
2170 }
2171
2172 // Edits much more commonly (though still only a few percent of edits) can change the ending
2173 // row word-break line. A change here means we must redraw to the end of the stable-region
2174 // (typically line - aka paragraph).
2175 size_t expandedToMarkerPos = GetEndOfRowContainingPosition (newTo);
2176 {
2177 size_t nowStartOfNextRow = expandedToMarkerPos;
2178 if (nowStartOfNextRow < GetTextStore ().GetEnd ()) {
2179 nowStartOfNextRow = GetStartOfRowContainingPosition (FindNextCharacter (nowStartOfNextRow));
2180 Assert (expandedToMarkerPos <= nowStartOfNextRow);
2181 }
2182
2183 if (nowStartOfNextRow != startPositionOfRowAfterReplaceEnds) {
2184 // then we changed word-breaks! Must go all the way to the end of the stable region.
2185 expandedToMarkerPos = stableTypingRegionEnd;
2186 }
2187 }
2188
2189 *expandedFrom = expandedFromMarkerPos;
2190 *expandedTo = expandedToMarkerPos;
2191}
2192
2193void TextInteractor::InteractiveReplace (const Led_tChar* withWhat, size_t withWhatCharCount, UpdateMode updateMode)
2194{
2195 BreakInGroupedCommandsIfDifferentCommand (GetCommandNames ().fTypingCommandName);
2196 InteractiveModeUpdater iuMode{*this};
2197 UndoableContextHelper undoContext{*this, GetCommandNames ().fTypingCommandName, withWhatCharCount == 0};
2198 {
2199 InteractiveReplace_ (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd (), withWhat, withWhatCharCount, true, true, updateMode);
2200 bool anyChanges = InteractiveReplaceEarlyPostReplaceHook (withWhatCharCount);
2201 if (withWhatCharCount == 1 and not anyChanges) {
2202 // need other tests as well???? Like same start location ?? Maybe done inside command-handler stuff????
2203 undoContext.SetSimplePlainTextInsertOptimization (true);
2204 }
2205 }
2206 undoContext.CommandComplete ();
2207}
2208
2209/*
2210@METHOD: TextInteractor::InteractiveReplace_
2211@DESCRIPTION:
2212*/
2213void TextInteractor::InteractiveReplace_ (size_t from, size_t to, const Led_tChar* withWhat, size_t withWhatCharCount,
2214 bool updateCursorPosition, bool validateTextForCharsetConformance, UpdateMode updateMode)
2215{
2216 // Assert selection bounardaries valid Led_tChar boundaries
2217 if (validateTextForCharsetConformance) {
2218 // Then check the GIVEN text - no assert here, just bad_input if text bad...
2219 if (not ValidateTextForCharsetConformance (withWhat, withWhatCharCount)) {
2220 OnBadUserInput ();
2221 return; // in case OnBadUserInput () doesn't throw
2222 }
2223 }
2224
2225 TempMarker newSel{GetTextStore (), to + 1, to + 1}; // NB: This marker pos MAY split a character - but that should cause
2226 // no problems.
2227 // We are only keeping this temporarily and we subtract one at the
2228 // end. Just want to make it past point where we do the insertion
2229 // so we get the right adjustment on
2230
2231 Tablet_Acquirer performanceHackTablet{this}; // sometimes acquiring/releaseing the tablet can be expensive,
2232 // and as a result of stuff later in this call, it happens
2233 // several times. By acquiring it here, we make the other calls
2234 // much cheaper (cuz its basicly free to acquire when already
2235 // acuired).
2236
2237 SetCaretShownAfterPos (true); // by default show pos after - this is what works best for NL's and is what we
2238 // want most of the time...
2239 Replace (from, to, withWhat, withWhatCharCount, updateMode);
2240 if (updateCursorPosition) {
2241 size_t newSelection = newSel.GetStart ();
2242 if (newSelection > 0) {
2243 --newSelection;
2244 }
2245
2246 SetSelection (newSelection, newSelection);
2247 }
2248}
2249
2250/*
2251@METHOD: TextInteractor::InteractiveReplaceEarlyPostReplaceHook
2252@DESCRIPTION: <p>Hook function called AFTER the @'TextInteractor::InteractiveReplace_' in @'TextInteractor::InteractiveReplace', but
2253 <em>before</em> the @'TextInteractor::PreInteractiveUndoHelper' call. The need to get into this interval is if you need to update
2254 the text to <em>augment</em> the effect of the user typing, and what that effect to be reflected in the UNDO information.</p>
2255 <p>This happens, for example, with @'StandardStyledTextInteractor::InteractiveReplaceEarlyPostReplaceHook' where
2256 we want to set the font of the newly typed character according to the 'fEmptySelection' style. See spr#0604 for more details.</p>
2257 <p><em>OLD-CODE-NOTE</em>NB: This routine was changed in Led 3.1a8 to return a boolean result.</p>
2258 </p>The boolean return value must be true if any changes were made that could affect undo processing (i.e. that could affect
2259 what infomration must be saved for proper undo processing).</p>
2260*/
2261bool TextInteractor::InteractiveReplaceEarlyPostReplaceHook (size_t /*withWhatCharCount*/)
2262{
2263 return false;
2264}
2265
2266/*
2267@METHOD: TextInteractor::PreInteractiveUndoHelper
2268@DESCRIPTION: <p>This is called early on - before any change happens - to preserve the contents of a region about to be updated - so
2269 that the region can be restored upon an UNDO command. The argument selStart/selEnd are NOT the actual selection regions - but
2270 rather the region (which maybe slightly larger) that needs to be preserved (perhaps the arg names should change?).</p>
2271 <p>Note - this can differ from the selection region because of SmartCutAndPaste - where we expand the selection slightly for
2272 a command to do funky stuff with whitespace.</p>
2273 <p>Note that the handed in selStart/selEnd arguments can be modified by this routine (due to a call to
2274 @'TextInteractor::PreInteractiveUndoHelperHook').</p>
2275 <p>We then preserve the ACTUAL selection at the time this was called in the resulting 'beforeRep' object.</p>
2276 <p>NB: new in Led 3.1a6 - we require that fCommandHandler != nullptr to call this.</p>
2277*/
2278void TextInteractor::PreInteractiveUndoHelper (InteractiveReplaceCommand::SavedTextRep** beforeRep, size_t regionStart, size_t regionEnd,
2279 size_t selStart, size_t selEnd)
2280{
2281 Require (regionStart <= regionEnd);
2282 Require (selStart <= selEnd);
2283 RequireNotNull (beforeRep);
2284 Require ((*beforeRep) == nullptr);
2285 RequireNotNull (fCommandHandler);
2286
2287 try {
2288 (*beforeRep) = InteractiveUndoHelperMakeTextRep (regionStart, regionEnd, selStart, selEnd);
2289 }
2290 catch (...) {
2291 // any exceptions in here are cuz we don't have enuf memory to make this
2292 // command undoable. No matter. Proceed anyhow, ignoring the exception...
2293 // And commit any existing commands to make it more likely real code
2294 // succeeds, and cuz we don't want any funny undo behavior where some
2295 // commands in sequence might get skipped.
2296 AssertNotNull (fCommandHandler);
2297 fCommandHandler->Commit ();
2298 }
2299}
2300
2301/*
2302@METHOD: TextInteractor::PostInteractiveUndoHelper
2303@DESCRIPTION: <p>This routine is called after a user action has taken place which is to be recorded for UNDOing.
2304 The 'startOfInsert' / 'endOfInsert' passed here refer to the region of text which must be preserved.
2305 The actual selection saved will be the currently selected text at the time this method is called.</p>
2306 <p>Note - the startOfInsert/endOfInsert can differ from the selection region because things like
2307 SmartCutAndPaste can expand the affected area of text to BEYOND what was actually selected by the user.</p>
2308 <p>This method operatates by calling @'TextInteractor::PostInteractiveUndoPostHelper' with the
2309 beforeRep argument given this function and an afterRep computed herein.</p>
2310 <p>NB: As of Led 3.1a6 - we require that fCommandHandler != nullptr to call this.</p>
2311*/
2312void TextInteractor::PostInteractiveUndoHelper (InteractiveReplaceCommand::SavedTextRep** beforeRep, size_t startOfInsert,
2313 size_t endOfInsert, const SDKString& cmdName)
2314{
2315 RequireNotNull (beforeRep);
2316 RequireNotNull (*beforeRep); // This shouldn't be called if there was a problem creating beforeRep (exception)
2317 RequireNotNull (fCommandHandler);
2318 SavedTextRep* afterRep = nullptr;
2319 try {
2320 afterRep = InteractiveUndoHelperMakeTextRep (startOfInsert, endOfInsert, GetSelectionStart (), GetSelectionEnd ());
2321 PostInteractiveUndoPostHelper (beforeRep, &afterRep, startOfInsert, cmdName);
2322 }
2323 catch (...) {
2324 delete (*beforeRep);
2325 (*beforeRep) = nullptr;
2326 delete afterRep;
2327 afterRep = nullptr;
2328 throw; // safe at this point to throw - but perhaps better to silently eat the throw?
2329 }
2330}
2331
2332/*
2333@METHOD: TextInteractor::PostInteractiveSimpleCharInsertUndoHelper
2334@DESCRIPTION: <p>Utility function for optimized undo support - keeping smaller objects in the undo buffer, and trying re-use/tweek
2335 an existing one in the common case of multiple consecutive characters typed.</p>
2336*/
2337void TextInteractor::PostInteractiveSimpleCharInsertUndoHelper (InteractiveReplaceCommand::SavedTextRep** beforeRep, size_t startOfInsert,
2338 size_t endOfInsert, const SDKString& cmdName)
2339{
2340 RequireNotNull (beforeRep);
2341 RequireNotNull (*beforeRep); // This shouldn't be called if there was a problem creating beforeRep (exception)
2342 RequireNotNull (fCommandHandler);
2343 if (endOfInsert - startOfInsert == 1) {
2344 Led_tChar c;
2345 CopyOut (startOfInsert, 1, &c);
2346 if (fCommandHandler->PostUpdateSimpleTextInsert (startOfInsert, c)) {
2347 delete *beforeRep;
2348 *beforeRep = nullptr;
2349 return;
2350 }
2351 // at least create a plain-text guy if we cannot update current one...
2352 SavedTextRep* afterRep = new InteractiveReplaceCommand::PlainTextRep (GetSelectionStart (), GetSelectionEnd (), &c, 1);
2353 PostInteractiveUndoPostHelper (beforeRep, &afterRep, startOfInsert, cmdName);
2354 Assert (afterRep == nullptr); // cleared out by PostInteractiveUndoPostHelper ()
2355 return;
2356 }
2357
2358 PostInteractiveUndoHelper (beforeRep, startOfInsert, endOfInsert, cmdName);
2359}
2360
2361/*
2362@METHOD: TextInteractor::PostInteractiveUndoPostHelper
2363@DESCRIPTION: <p>This routine is called after a user action has taken place which is to be recorded for UNDOing.
2364 The routine simply posts an @'InteractiveReplaceCommand' (with the already saved before/after reps) to the current
2365 command handler.</p>
2366 <p>This method is typically called by @'TextInteractor::PostInteractiveUndoHelper'.</p>
2367 <p>NB: As of Led 3.1a6 - we require that fCommandHandler != nullptr to call this.</p>
2368*/
2369void TextInteractor::PostInteractiveUndoPostHelper (InteractiveReplaceCommand::SavedTextRep** beforeRep,
2370 InteractiveReplaceCommand::SavedTextRep** afterRep, size_t startOfInsert, const SDKString& cmdName)
2371{
2372 RequireNotNull (beforeRep);
2373 RequireNotNull (afterRep);
2374 RequireNotNull (fCommandHandler);
2375 try {
2376 if (*beforeRep != nullptr and *afterRep != nullptr) {
2377 // We declare temporaries here, and be careful to set things to nullptr at each stage to prevent double
2378 // deletes in the event of a badly timed exception
2379 InteractiveReplaceCommand* cmd = new InteractiveReplaceCommand (*beforeRep, *afterRep, startOfInsert, cmdName);
2380 *beforeRep = nullptr;
2381 *afterRep = nullptr;
2382 fCommandHandler->Post (cmd);
2383 }
2384 }
2385 catch (...) {
2386 delete *beforeRep;
2387 *beforeRep = nullptr;
2388 delete *afterRep;
2389 *afterRep = nullptr;
2390 throw; // safe at this point to throw - but perhaps better to silently eat the throw?
2391 }
2392}
2393
2394InteractiveReplaceCommand::SavedTextRep* TextInteractor::InteractiveUndoHelperMakeTextRep (size_t regionStart, size_t regionEnd,
2395 size_t selStart, size_t selEnd)
2396{
2397 if (regionStart == regionEnd) {
2398 // optimization, cuz these are smaller
2399 return new InteractiveReplaceCommand::PlainTextRep (selStart, selEnd, nullptr, 0);
2400 }
2401 else {
2402 return new FlavorSavorTextRep (this, regionStart, regionEnd, selStart, selEnd);
2403 }
2404}
2405
2406void TextInteractor::OnUndoCommand ()
2407{
2408 InteractiveModeUpdater iuMode (*this);
2409 if (GetCommandHandler () != nullptr and GetCommandHandler ()->CanUndo ()) {
2410 GetCommandHandler ()->DoUndo (*this);
2411 ScrollToSelection ();
2412 }
2413 else {
2414 Led_BeepNotify ();
2415 }
2416}
2417
2418void TextInteractor::OnRedoCommand ()
2419{
2420 InteractiveModeUpdater iuMode (*this);
2421 if (GetCommandHandler () != nullptr and GetCommandHandler ()->CanRedo ()) {
2422 GetCommandHandler ()->DoRedo (*this);
2423 ScrollToSelection ();
2424 }
2425 else {
2426 Led_BeepNotify ();
2427 }
2428}
2429
2430void TextInteractor::Refresh (size_t from, size_t to, UpdateMode updateMode) const
2431{
2432 Require (from <= to);
2433 updateMode = RealUpdateMode (updateMode);
2434 if ((updateMode != eNoUpdate) and (from != to)) {
2435 if (updateMode == eDelayedUpdate and IsWholeWindowInvalid ()) {
2436 return;
2437 }
2438 // we could be more precise - but no need. Just take the box bounding the two
2439 // endpoints.
2440 // I'd be more inclined to worry about optimizing this, but the most relevant plausible
2441 // usage of this routine - during typing - doesn't use it. So I believe we can get away with
2442 // being sloppy here - LGP 960516
2443 Led_Rect refreshRect = GetTextWindowBoundingRect (from, to);
2444 RefreshWindowRect_ (refreshRect, updateMode);
2445 }
2446}
2447
2448void TextInteractor::Refresh (const Marker* range, UpdateMode updateMode) const
2449{
2450 RequireNotNull (range);
2451 updateMode = RealUpdateMode (updateMode);
2452 if (updateMode != eNoUpdate) {
2453 if (updateMode == eDelayedUpdate and IsWholeWindowInvalid ()) {
2454 return;
2455 }
2456 Refresh (range->GetStart (), range->GetEnd (), updateMode);
2457 }
2458}
2459
2460/*
2461@METHOD: TextInteractor::DoSingleCharCursorEdit
2462@ACCESS: protected
2463@DESCRIPTION: <p>Helper routine for handling cursoring done by user.</p>
2464 */
2465void TextInteractor::DoSingleCharCursorEdit (CursorMovementDirection direction, CursorMovementUnit movementUnit,
2466 CursorMovementAction action, UpdateMode updateMode, bool scrollToSelection)
2467{
2468 IdleManager::NonIdleContext nonIdleContext;
2469
2470 size_t oldStartSel = GetSelectionStart ();
2471 size_t oldEndSel = GetSelectionEnd ();
2472 Assert (GetSelectionEnd () <= GetLength () + 1);
2473
2474 size_t newStartSel = oldStartSel;
2475 size_t newEndSel = oldEndSel;
2476
2477 UpdateMode useUpdateMode = (updateMode == eImmediateUpdate) ? eDelayedUpdate : updateMode;
2478
2479 GoalColumnRecomputerControlContext skipRecompute (*this, action == eCursorMoving and movementUnit == eCursorByRow and
2480 (direction == eCursorBack or direction == eCursorForward));
2481
2482 // In a couple of cases, we get burned by the ambiguity of location-specification at start and end
2483 // of row. This only matters when the users says navigate to start/end of row, and we happen to already
2484 // be there. In that special case, we will navigate to the start/end of the next row.
2485 //
2486 // This situation only occurs when we are at the end of a row which has been word-wrapped. This is because
2487 // in that special case, there is a flag - CaretShownAfterPos - which says if you show the caret before or
2488 // after the marker-pos of the selection.
2489 //
2490 // If we are in this situation, OVERRIDE the usual navigation logic.
2491 if (movementUnit == eCursorByRow and GetStartOfRowContainingPosition (newStartSel) == newStartSel) {
2492 if (GetCaretShownAfterPos ()) {
2493 if (direction == eCursorToEnd and GetEndOfRowContainingPosition (newStartSel) == newStartSel) {
2494 goto SkipNavigation;
2495 }
2496 }
2497 else {
2498 if (direction == eCursorToStart) {
2499 // fall through with usual 'to start of row' logic, but backup one characater within the row so
2500 // that code doesn't get fooled about which row we are on.
2501 newStartSel = FindPreviousCharacter (newStartSel);
2502 }
2503 if (direction == eCursorToEnd) {
2504 goto SkipNavigation;
2505 }
2506 }
2507 }
2508
2509 // Generally don't want to mess with the caret shown pos. But in these cases few cases,
2510 // the user is indicating which he'd prefer.
2511 // Perhaps there are more cases? Can we generalize/simplify?
2512 if (movementUnit == eCursorByChar) {
2513 SetCaretShownAfterPos (true);
2514 }
2515 if (movementUnit == eCursorByRow) {
2516 switch (direction) {
2517 case eCursorToStart:
2518 SetCaretShownAfterPos (true);
2519 break;
2520 case eCursorToEnd:
2521 SetCaretShownAfterPos (false);
2522 break;
2523 }
2524 }
2525
2526 if (action != eCursorExtendingSelection or (oldStartSel == oldEndSel)) {
2527 fLeftSideOfSelectionInteresting = (direction == eCursorBack or direction == eCursorToStart);
2528 }
2529 if (fLeftSideOfSelectionInteresting) {
2530 newStartSel = ComputeRelativePosition (newStartSel, direction, movementUnit);
2531 if (action == eCursorMoving) { // only case where we do this - destroy/extend we keep track of start
2532 newEndSel = newStartSel;
2533 }
2534 }
2535 else {
2536 newEndSel = ComputeRelativePosition (newEndSel, direction, movementUnit);
2537 if (action == eCursorMoving) { // only case where we do this - destroy/extend we keep track of start
2538 newStartSel = newEndSel;
2539 }
2540 }
2541
2542SkipNavigation:
2543
2544 // The above can reverse start/end, so make sure that doesn't happen...
2545 if (newEndSel < newStartSel) {
2546 size_t tmp = newStartSel;
2547 newStartSel = newEndSel;
2548 newEndSel = tmp;
2549 }
2550 Assert (newStartSel <= newEndSel);
2551
2552 /*
2553 * Now that we know the new and old selection region, we can perform the action
2554 */
2555 switch (action) {
2556 case eCursorDestroying: {
2557 if (oldStartSel == oldEndSel) {
2558 /*
2559 * In this case, then the computations above for the NEW selection
2560 * can be considered valid.
2561 */
2562 Assert (newEndSel >= newStartSel);
2563 size_t howMany = newEndSel - newStartSel;
2564 if (howMany >= 1) { // might be zero if we were backed up against the start of the buffer.
2565 bool oldCutAndPaste = GetSmartCutAndPasteMode ();
2566 try {
2567 SetSmartCutAndPasteMode (false); // See SPR#1044 - when user hits backspace and has empty selection, dont use smart cut and paste...
2568 {
2569 BreakInGroupedCommandsIfDifferentCommand (GetCommandNames ().fClearCommandName);
2570 InteractiveModeUpdater iuMode (*this);
2571 UndoableContextHelper undoContext (*this, GetCommandNames ().fClearCommandName, newStartSel, newEndSel,
2572 GetSelectionStart (), GetSelectionEnd (), true);
2573 {
2574 InteractiveReplace_ (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd (), LED_TCHAR_OF (""),
2575 0, true, true, useUpdateMode);
2576 }
2577 undoContext.CommandComplete ();
2578 }
2579 // After the above deletion, we already end up the the selection adjustment being handled for us (and more correctly
2580 // than we can easily due to smart cut and paste). So just grab the current (already correct) selection, to prevent
2581 // our later call to SetSelection () from doing any harm.
2582 GetSelection (&newStartSel, &newEndSel);
2583 SetSmartCutAndPasteMode (oldCutAndPaste);
2584 }
2585 catch (...) {
2586 SetSmartCutAndPasteMode (oldCutAndPaste);
2587 throw;
2588 }
2589 }
2590 }
2591 else {
2592 /*
2593 * Otherwise, if there WAS some selection, and we get any kind of
2594 * delete key, we REALLY just want to delete the selection, and
2595 * ignore whatever computation was done above for where to put
2596 * the new selection - it goes right to the old startSel.
2597 */
2598 Assert (oldEndSel >= oldStartSel);
2599 Assert (oldStartSel == GetSelectionStart ());
2600 Assert (oldEndSel == GetSelectionEnd ());
2601 OnClearCommand ();
2602 // After the above deletion, we already end up the the selection adjustment being handled for us (and more correctly
2603 // than we can easily due to smart cut and paste). So just grab the current (already correct) selection, to prevent
2604 // our later call to SetSelection () from doing any harm.
2605 GetSelection (&newStartSel, &newEndSel);
2606 }
2607 } break;
2608
2609 case eCursorMoving: {
2610 // Nothing todo (actual setting of selection done at the end)
2611 } break;
2612
2613 case eCursorExtendingSelection: {
2614 // Nothing todo (actual setting of selection done at the end)
2615 } break;
2616
2617 default:
2618 Assert (false); // bad direction argument
2619 }
2620
2621 /*
2622 * Buy this point, we've computed where we should be, and performed
2623 * any actions on the text that needed to be taken (e.g. deleting).
2624 * Nothing should have yet been redisplayed. Now we update the selection,
2625 * perform any needed scrolling, and only then - display - if prescribed.
2626 */
2627 SetSelection (newStartSel, newEndSel, useUpdateMode);
2628
2629 if (scrollToSelection) {
2630 ScrollToSelection (useUpdateMode, true);
2631 }
2632
2633 if (updateMode == eImmediateUpdate) {
2634 Update ();
2635 }
2636}
2637
2638void TextInteractor::OnCutCommand ()
2639{
2640 InteractiveModeUpdater iuMode (*this);
2641 BreakInGroupedCommands ();
2642 if (GetSelectionStart () != GetSelectionEnd ()) {
2643 OnCopyCommand ();
2644 UndoableContextHelper undoContext (*this, GetCommandNames ().fCutCommandName, true);
2645 {
2646 InteractiveReplace_ (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd (), LED_TCHAR_OF (""), 0);
2647 }
2648 undoContext.CommandComplete ();
2649 }
2650 BreakInGroupedCommands ();
2651}
2652
2653void TextInteractor::OnCopyCommand ()
2654{
2655 size_t start = GetSelectionStart ();
2656 size_t end = GetSelectionEnd ();
2657 Assert (start <= end);
2658 if (start < end) {
2659 BreakInGroupedCommands ();
2660
2661 if (OnCopyCommand_Before ()) {
2662 try {
2663 OnCopyCommand_CopyFlavors ();
2664 }
2665 catch (...) {
2666 OnCopyCommand_After ();
2667 throw;
2668 }
2669 OnCopyCommand_After ();
2670 }
2671 }
2672}
2673
2674void TextInteractor::OnPasteCommand ()
2675{
2676 InteractiveModeUpdater iuMode (*this);
2677 BreakInGroupedCommands ();
2678
2679 if (OnPasteCommand_Before ()) {
2680 try {
2681 UndoableContextHelper undoContext (*this, GetCommandNames ().fPasteCommandName, false);
2682 {
2683 OnPasteCommand_PasteBestFlavor ();
2684 }
2685 undoContext.CommandComplete ();
2686 }
2687 catch (...) {
2688 OnPasteCommand_After ();
2689 throw;
2690 }
2691 OnPasteCommand_After ();
2692 }
2693 BreakInGroupedCommands ();
2694}
2695
2696void TextInteractor::OnClearCommand ()
2697{
2698 InteractiveModeUpdater iuMode (*this);
2699 BreakInGroupedCommands ();
2700 UndoableContextHelper undoContext (*this, GetCommandNames ().fClearCommandName, true);
2701 {
2702 InteractiveReplace_ (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd (), LED_TCHAR_OF (""), 0);
2703 }
2704 undoContext.CommandComplete ();
2705 BreakInGroupedCommands ();
2706}
2707
2708/*
2709@METHOD: TextInteractor::OnCopyCommand_Before
2710@ACCESS: protected
2711@DESCRIPTION: <p>Hook overriden by SDK-specific classes or templates which does special things in the before
2712 a clipboard <em>copy</em> operation can begin (like opening a clipboard object). Generally should not be
2713 called directly or overridden, except when implementing new SDK wrappers.</p>
2714 <p>Return false or throw if fail</p>
2715 <p>See also @'TextInteractor::OnCopyCommand_After',
2716 @'TextInteractor::OnPasteCommand_Before',
2717 and
2718 @'TextInteractor::OnPasteCommand_After'
2719 .</p>
2720 */
2721bool TextInteractor::OnCopyCommand_Before ()
2722{
2723 return true;
2724}
2725
2726/*
2727@METHOD: TextInteractor::OnCopyCommand_After
2728@ACCESS: protected
2729@DESCRIPTION: <p>See also @'TextInteractor::OnCopyCommand_Before'.</p>
2730 */
2731void TextInteractor::OnCopyCommand_After ()
2732{
2733}
2734
2735/*
2736@METHOD: TextInteractor::OnCopyCommand_CopyFlavors
2737@ACCESS: protected
2738@DESCRIPTION: <p></p>
2739 */
2740void TextInteractor::OnCopyCommand_CopyFlavors ()
2741{
2742 WriterClipboardFlavorPackage writer;
2743 ExternalizeFlavors (writer);
2744}
2745
2746bool TextInteractor::ShouldEnablePasteCommand () const
2747{
2748 return Led_ClipboardObjectAcquire::FormatAvailable_TEXT ();
2749}
2750
2751/*
2752@METHOD: TextInteractor::OnPasteCommand_Before
2753@DESCRIPTION: <p>Hook overriden by SDK-specific classes or templates which does special things in the before
2754 a clipboard <em>paste</em> operation can begin (like opening a clipboard object). Generally should not be
2755 called directly or overridden, except when implementing new SDK wrappers.</p>
2756 <p>Return false or throw if fail</p>
2757 <p>See also @'TextInteractor::OnPasteCommand_After',
2758 @'TextInteractor::OnCopyCommand_Before',
2759 and
2760 @'TextInteractor::OnCopyCommand_After'
2761 .</p>
2762 */
2763bool TextInteractor::OnPasteCommand_Before ()
2764{
2765 return true;
2766}
2767
2768/*
2769@METHOD: TextInteractor::OnPasteCommand_After
2770@ACCESS: protected
2771@DESCRIPTION: <p>See also @'TextInteractor::OnPasteCommand_Before'.</p>
2772 */
2773void TextInteractor::OnPasteCommand_After ()
2774{
2775}
2776
2777/*
2778@METHOD: TextInteractor::OnPasteCommand_PasteBestFlavor
2779@ACCESS: protected
2780@DESCRIPTION: <p></p>
2781 */
2782void TextInteractor::OnPasteCommand_PasteBestFlavor ()
2783{
2784#if qStroika_Platform_Windows && 0
2785 // A little debugging hack for windows - sometimes helpful to turn this on
2786 // to peek in the debugger at what is on the clipboard - LGP 960430
2787
2788 long clipFormat = 0;
2789 while ((clipFormat = ::EnumClipboardFormats (clipFormat)) != 0) {
2790 TCHAR buf[1024];
2791 int nChars = ::GetClipboardFormatName (clipFormat, buf, std::size (buf));
2792 int breakHere = 0;
2793 }
2794#endif
2795
2796 ReaderClipboardFlavorPackage clipData;
2797
2798 SmartCNPInfo smartCNPInfo;
2799 bool doSmartCNP = PasteLooksLikeSmartCNP (&smartCNPInfo);
2800 size_t savedSelStart = GetSelectionStart (); // save cuz InternalizeBestFlavor () will tend to adjust selStart
2801 InternalizeBestFlavor (clipData);
2802 Assert (savedSelStart <= GetSelectionStart ()); // InternalizeBestFlavor can only adjust it FORWARD - not backward...
2803 if (doSmartCNP) {
2804 OptionallyAddExtraSpaceForSmartCutAndPasteModeAdds (savedSelStart, smartCNPInfo);
2805 }
2806}
2807
2808/*
2809@METHOD: TextInteractor::OnPasteCommand_PasteFlavor_Specific
2810@ACCESS: protected
2811@DESCRIPTION: <p></p>
2812 */
2813void TextInteractor::OnPasteCommand_PasteFlavor_Specific (Led_ClipFormat format)
2814{
2815 ReaderClipboardFlavorPackage clipData;
2816 SmartCNPInfo smartCNPInfo;
2817 bool doSmartCNP = PasteLooksLikeSmartCNP (&smartCNPInfo);
2818 size_t savedSelStart = GetSelectionStart (); // save cuz InternalizeBestFlavor () will tend to adjust selStart
2819 InternalizeFlavor_Specific (clipData, format);
2820 Assert (savedSelStart <= GetSelectionStart ()); // InternalizeBestFlavor can only adjust it FORWARD - not backward...
2821 if (doSmartCNP) {
2822 OptionallyAddExtraSpaceForSmartCutAndPasteModeAdds (savedSelStart, smartCNPInfo);
2823 }
2824}
2825
2826/*
2827@METHOD: TextInteractor::PasteLooksLikeSmartCNP
2828@ACCESS: protected
2829@DESCRIPTION: <p></p>
2830 */
2831bool TextInteractor::PasteLooksLikeSmartCNP (SmartCNPInfo* scnpInfo) const
2832{
2833 RequireNotNull (scnpInfo);
2834 ReaderClipboardFlavorPackage clipData;
2835 bool doSmartCNP = GetSmartCutAndPasteMode () and clipData.GetFlavorAvailable_TEXT ();
2836 if (doSmartCNP) {
2837 /*
2838 * Check if this REALLY looks like a good opportunity todo a smart-cut-and-paste whitespace adjustment.
2839 */
2840 size_t length = clipData.GetFlavorSize (kTEXTClipFormat);
2841 Led_ClipFormat textFormat = kTEXTClipFormat;
2842
2843 Memory::StackBuffer<char> buf{Memory::eUninitialized, length}; // could use bufsize=(len+1)/sizeof (Led_tChar)
2844 length = clipData.ReadFlavorData (textFormat, length, buf.data ());
2845 if (doSmartCNP) {
2846 Led_tChar* buffp = reinterpret_cast<Led_tChar*> (static_cast<char*> (buf));
2847 size_t nTChars = length / sizeof (Led_tChar);
2848 doSmartCNP = LooksLikeSmartPastableText (buffp, nTChars, scnpInfo);
2849 }
2850 }
2851 return doSmartCNP;
2852}
2853
2854/*
2855@METHOD: TextInteractor::OnSelectAllCommand
2856@DESCRIPTION: <p>Command to implement the "Select All" UI. Trivial implementation, but nearly all UI's want it, so why
2857 write it each time?</p>
2858*/
2859void TextInteractor::OnSelectAllCommand ()
2860{
2861 SetSelection (0, GetLength ());
2862}
2863
2864bool TextInteractor::CanAcceptFlavor (Led_ClipFormat clipFormat) const
2865{
2866 return (kTEXTClipFormat == clipFormat or kFILEClipFormat == clipFormat);
2867}
2868
2869void TextInteractor::InternalizeBestFlavor (ReaderFlavorPackage& flavorPackage, bool updateCursorPosition, bool autoScroll, UpdateMode updateMode)
2870{
2871 size_t start = GetSelectionStart ();
2872 size_t end = GetSelectionEnd ();
2873
2874 bool good = false;
2875 {
2876 TempMarker newSel{GetTextStore (), end + 1, end + 1}; // NB: This marker pos MAY split a multibyte character - but that should cause
2877 // no problems.
2878 // We are only keeping this temporarily and we subtract one at the
2879 // end. Just want to make it past point where we do the insertion
2880 // so we get the right adjustment on
2881 good = fInternalizer->InternalizeBestFlavor (flavorPackage, start, end);
2882 if (good and updateCursorPosition) {
2883 SetCaretShownAfterPos (true); // by default show pos after - this is what works best for NL's and is what we
2884 // want most of the time...
2885
2886 // We placed a marker one past the end of the selection. Then we replaced the given selection.
2887 // So the marker now points one past where it really should. Backward adjust it, and set the selection there.
2888 size_t newSelection = newSel.GetStart ();
2889 if (newSelection > start) {
2890 --newSelection;
2891 }
2892
2893 SetSelection (newSelection, newSelection);
2894 }
2895 }
2896
2897 if (good) {
2898 if (autoScroll) {
2899 ScrollToSelection ();
2900 }
2901 if (updateMode == eImmediateUpdate) {
2902 Update ();
2903 }
2904 }
2905 else {
2906 OnBadUserInput ();
2907 }
2908}
2909
2910/*
2911@METHOD: TextInteractor::InternalizeFlavor_Specific
2912@DESCRIPTION: <p></p>
2913 */
2914void TextInteractor::InternalizeFlavor_Specific (ReaderFlavorPackage& flavorPackage, Led_ClipFormat format, bool updateCursorPosition,
2915 bool autoScroll, UpdateMode updateMode)
2916{
2917 size_t start = GetSelectionStart ();
2918 size_t end = GetSelectionEnd ();
2919
2920 bool good = false;
2921 {
2922 TempMarker newSel{GetTextStore (), end + 1, end + 1}; // NB: This marker pos MAY split a multibyte character - but that should cause
2923 // no problems.
2924 // We are only keeping this temporarily and we subtract one at the
2925 // end. Just want to make it past point where we do the insertion
2926 // so we get the right adjustment on
2927
2928 if (format == kTEXTClipFormat) {
2929 good = fInternalizer->InternalizeFlavor_TEXT (flavorPackage, start, end);
2930 }
2931 else if (format == kFILEClipFormat) {
2932 good = fInternalizer->InternalizeFlavor_FILE (flavorPackage, start, end);
2933 }
2934 else {
2935 good = fInternalizer->InternalizeBestFlavor (flavorPackage, start, end);
2936 }
2937
2938 if (good and updateCursorPosition) {
2939 SetCaretShownAfterPos (true); // by default show pos after - this is what works best for NL's and is what we
2940 // want most of the time...
2941
2942 // We placed a marker one past the end of the selection. Then we replaced the given selection.
2943 // So the marker now points one past where it really should. Backward adjust it, and set the selection there.
2944 size_t newSelection = newSel.GetStart ();
2945 if (newSelection > start) {
2946 --newSelection;
2947 }
2948
2949 SetSelection (newSelection, newSelection);
2950 }
2951 }
2952
2953 if (good) {
2954 if (autoScroll) {
2955 ScrollToSelection ();
2956 }
2957 if (updateMode == eImmediateUpdate) {
2958 Update ();
2959 }
2960 }
2961 else {
2962 OnBadUserInput ();
2963 }
2964}
2965
2966/*
2967@METHOD: TextInteractor::MakeDefaultInternalizer
2968@DESCRIPTION: <p>Make a @'FlavorPackageInternalizer' which is appropriate for this text interactor. Override this
2969 to make a different subclass, which supports different style and file formats from the (simple) default.</p>
2970 <p>By default, this creates a @'FlavorPackageInternalizer'.</p>
2971*/
2972shared_ptr<FlavorPackageInternalizer> TextInteractor::MakeDefaultInternalizer ()
2973{
2974 return Memory::MakeSharedPtr<FlavorPackageInternalizer> (GetTextStore ());
2975}
2976
2977/*
2978@METHOD: TextInteractor::HookInternalizerChanged
2979@DESCRIPTION: <p>Called by @'TextInteractor::SetInternalizer' whenever there is a new internalizer specified.</p>
2980*/
2981void TextInteractor::HookInternalizerChanged ()
2982{
2983}
2984
2985/*
2986@METHOD: TextInteractor::ExternalizeFlavors
2987@DESCRIPTION: <p>Use the associated externalizer (see @'TextInteractor::SetExternalizer') to call
2988 ExternalizeFlavors applied to the current selection.</p>
2989*/
2990void TextInteractor::ExternalizeFlavors (WriterFlavorPackage& flavorPackage)
2991{
2992 fExternalizer->ExternalizeFlavors (flavorPackage, GetSelectionStart (), GetSelectionEnd ());
2993}
2994
2995/*
2996@METHOD: TextInteractor::ExternalizeBestFlavor
2997@DESCRIPTION: <p>Use the associated externalizer (see @'TextInteractor::SetExternalizer') to call
2998 ExternalizeBestFlavor applied to the current selection.</p>
2999*/
3000void TextInteractor::ExternalizeBestFlavor (WriterFlavorPackage& flavorPackage)
3001{
3002 fExternalizer->ExternalizeBestFlavor (flavorPackage, GetSelectionStart (), GetSelectionEnd ());
3003}
3004
3005/*
3006@METHOD: TextInteractor::MakeDefaultExternalizer
3007@DESCRIPTION: <p>Make a @'FlavorPackageExternalize' which is appropriate for this text interactor. Override this
3008 to make a different subclass, which supports different style and file formats from the (simple) default.</p>
3009 <p>By default, this creates a @'FlavorPackageExternalizer'.</p>
3010*/
3011shared_ptr<FlavorPackageExternalizer> TextInteractor::MakeDefaultExternalizer ()
3012{
3013 return Memory::MakeSharedPtr<FlavorPackageExternalizer> (GetTextStore ());
3014}
3015
3016/*
3017@METHOD: TextInteractor::HookExternalizerChanged
3018@DESCRIPTION: <p>Called by @'TextInteractor::SetExternalizer' whenever there is a new externalizer specified.</p>
3019*/
3020void TextInteractor::HookExternalizerChanged ()
3021{
3022}
3023
3024/*
3025@METHOD: TextInteractor::OnBadUserInput
3026@DESCRIPTION: <p>By default this throws @'TextInteractor::BadUserInput' but it can be overriden to
3027 NOT throw anything, and just beep or something. BEWARE then when calling this that it may or
3028 may not throw, and may or may not return.</p>
3029*/
3030void TextInteractor::OnBadUserInput ()
3031{
3032 // you may want to OVERRIDE this to do a staged alert, or to throw an exception???
3033 //Led_BeepNotify ();
3034 throw BadUserInput{}; // default catcher should call Led_BeepNotify ()
3035}
3036
3037/*
3038@METHOD: TextInteractor::SetScrollBarType
3039@DESCRIPTION: <p>Specify whether or not the interactor will display / manage scrollbars. This really is handled in OS/ClassLib specific subclasses.
3040 But the API is here to keep it uniform across the platforms.</p>
3041 <p>The default settings for each direction (v/h) are 'TextInteractor::eScrollBarNever'
3042 (except than for the Windows platform, @'Led_Win32_Helper<BASE_INTERACTOR>::OnCreate_Msg'
3043 and @'Led_MFC_Helper<MFC_BASE_CLASS,BASE_INTERACTOR>::OnCreate' will set the initial value according to
3044 the windows style passed into the WM_CREATE message).</p>
3045 <p>On windows I recall that it USED to be standard that an edit text would suddenly
3046 spawn a scrollbar when needed. This doesn't appear to be the case any longer.
3047 I always hated this behavior, but since I didn't know better, I implemented it.</p>
3048 <p>Even though this behavior is no longer particularly standard or common, it is sometimes
3049 desired, and so supported.</p>
3050 <p>NB: You need not turn on the WS_V/HSCROLL styles to make the scrollbars appear/disapear.
3051 When this is on, it is handled automagically.</p>
3052 <p>NB: This USED to be controlled by a compile-time variable @'qMakeWindowsScrollbarAppearOnlyIfNeeded',
3053 which is now obsolete.</p>
3054 <p>See also 'TextInteractor::GetScrollBarType' and @'TextInteractor::InvalidateScrollBarParameters'.</p>
3055*/
3056void TextInteractor::SetScrollBarType (VHSelect vh, ScrollBarType scrollBarType)
3057{
3058 if (GetScrollBarType (vh) != scrollBarType) {
3059 InvalidateScrollBarParameters ();
3060 SetScrollBarType_ (vh, scrollBarType);
3061 }
3062}
3063
3064/*
3065@METHOD: TextInteractor::InvalidateScrollBarParameters
3066@DESCRIPTION: <p>Mark the contents of the scrollbars as invalid. Someone, sometime later soon will call
3067 @'TextInteractor::UpdateScrollBars' to fix them up again.</p>
3068*/
3069void TextInteractor::InvalidateScrollBarParameters ()
3070{
3071 InvalidateScrollBarParameters_ ();
3072}
3073
3074/*
3075@METHOD: TextInteractor::UpdateScrollBars
3076@DESCRIPTION: <p>Override this to handle any update of the scrollbars you may need to. Something has happened to
3077 invalidate what they now display (new text added, scrolled, or whatever - someone called @'TextInteractor::InvalidateScrollBarParameters').
3078 This is usually taken care of in any class library wrapper code, such as @'Led_MFC_Helper<MFC_BASE_CLASS,BASE_INTERACTOR>'
3079 or @'Led_Win32_Helper<BASE_INTERACTOR>'.</p>
3080*/
3081void TextInteractor::UpdateScrollBars ()
3082{
3083 UpdateScrollBars_ ();
3084}
3085
3086/*
3087@METHOD: TextInteractor::SetCaretShown
3088@DESCRIPTION: <p>See also @'TextInteractor::GetCaretShownSituation' and @'TextInteractor::GetCaretShown'.</p>
3089*/
3090void TextInteractor::SetCaretShown (bool shown)
3091{
3092 if (GetCaretShown () != shown) {
3093 fCaretShown = shown;
3094 InvalidateCaretState ();
3095 }
3096}
3097
3098/*
3099@METHOD: TextInteractor::GetCaretShownSituation
3100@DESCRIPTION: <p>To decide if its appropriate for the editor to display a blinking caret, we check an
3101 overall state variable @'TextInteractor::GetCaretShown' which is logically tied to
3102 whether or not the edit widget has the focus. But - thats not all that needs to be
3103 considered to decide if you should display the caret. You also - typically - need
3104 to have an empty selection. This virtual method does that particular check.</p>
3105*/
3106bool TextInteractor::GetCaretShownSituation () const
3107{
3108 size_t selStart = 0;
3109 size_t selEnd = 0;
3110 GetSelection (&selStart, &selEnd);
3111 return selStart == selEnd;
3112}
3113
3114/*
3115@METHOD: TextInteractor::SetCaretShownAfterPos
3116@DESCRIPTION: <p>See @'TextInteractor::GetCaretShownAfterPos' for meaning of this flag.</p>
3117 */
3118void TextInteractor::SetCaretShownAfterPos (bool shownAfterPos)
3119{
3120 if (GetCaretShownAfterPos () != shownAfterPos) {
3121 InvalidateCaretState (); // before and after so we draw/erase in new and old places
3122 fCaretShownAfterPos = shownAfterPos;
3123 InvalidateCaretState ();
3124 }
3125}
3126
3127/*
3128@METHOD: TextInteractor::CalculateCaretRect
3129@ACCESS: protected
3130@DESCRIPTION: <p>This is complicated due to worrying about bidirectional editing, and due to the fact
3131 that two adjacent characters in logical order, maybe in very different parts of the screen
3132 (e.g. the marker position between the end of a row and the start of the next row maybe identical, or
3133 the marker position between one character and another WITHIN a row at a directional boundary may
3134 indicate two very different caret locations).</p>
3135 <p>The first issue to resolve is - <em>which character</em> to we care most about - the one <em>preceeding</em>
3136 the marker position or the one <em>following</em>. This question is arbitrated by the <em>@'TextInteractor::GetCaretShownAfterPos'</em>
3137 API.</p>
3138 <p>The second question - as to which side of the character to display the caret - is decided
3139 by the text direction. But WHICH text direction? The one of the preceeding or following character? Easy - we use
3140 the same choice as chosen by the first question.
3141 </p>
3142 */
3143Led_Rect TextInteractor::CalculateCaretRect () const
3144{
3145 size_t selEnd = GetSelectionEnd ();
3146 if (GetSelectionStart () == selEnd) {
3147 bool showAfter = GetCaretShownAfterPos ();
3148 if (selEnd == 0) {
3149 showAfter = true;
3150 }
3151
3152 size_t charAfterPos = showAfter ? selEnd : FindPreviousCharacter (selEnd);
3153 TextDirection textDirection = GetTextDirection (charAfterPos);
3154 Led_Rect caretRect = GetCharWindowLocation (charAfterPos);
3155
3156 if (caretRect.GetBottom () < GetWindowRect ().GetTop () or caretRect.GetTop () > GetWindowRect ().GetBottom ()) {
3157 return Led_Rect{0, 0, 0, 0};
3158 }
3159
3160 Led_Rect origCaretRect = caretRect;
3161 FontMetrics fontMetrics = GetFontMetricsAt (charAfterPos);
3162
3163 // WE NEED THE WHOLE ROW BASELINE!!! THEN CAN COMPUTE caretrect from that!!!
3164 DistanceType baseLineFromTop = GetRowRelativeBaselineOfRowContainingPosition (charAfterPos);
3165 CoordinateType realBaseLine = baseLineFromTop + caretRect.top;
3166
3167 // now adjust caretrect to be font-metrics from the base line
3168 caretRect.top = realBaseLine - fontMetrics.GetAscent ();
3169 caretRect.bottom = realBaseLine + fontMetrics.GetDescent ();
3170
3171 // Before pinning CaretRect to full rowRect, first try to adjust it so its inside.
3172 // It can come outside the rowRect if the baseLineFromTop is zero - for example - when
3173 // the preceeding text is hidden.
3174 if (caretRect.top < origCaretRect.top) {
3175 DistanceType diff = origCaretRect.GetTop () - caretRect.GetTop ();
3176 caretRect += Led_Point (diff, 0);
3177 }
3178
3179 // pin CaretRect to full rowRect
3180 caretRect.SetTop (max (caretRect.GetTop (), origCaretRect.GetTop ()));
3181 caretRect.bottom = min (caretRect.GetBottom (), origCaretRect.GetBottom ());
3182
3183 if (textDirection == eLeftToRight) {
3184 if (not showAfter) {
3185 caretRect.left = caretRect.right;
3186 }
3187 }
3188 else {
3189 caretRect.left = caretRect.right;
3190 }
3191
3192 const CoordinateType kCaretWidth = 1;
3193 // quickie hack to be sure caret doesn't go off right side of window!!!
3194 const CoordinateType kSluff = kCaretWidth + 1;
3195 if (caretRect.GetLeft () + kSluff > GetWindowRect ().GetRight ()) {
3196 caretRect.SetLeft (GetWindowRect ().GetRight () - kSluff);
3197 }
3198 caretRect.SetRight (caretRect.GetLeft () + kCaretWidth);
3199
3200 Ensure (not caretRect.IsEmpty ());
3201 return caretRect;
3202 }
3203 else {
3204 return (Led_Rect{0, 0, 0, 0});
3205 }
3206}
3207
3208void TextInteractor::InvalidateCaretState ()
3209{
3210 if (IsWholeWindowInvalid ()) {
3211 return;
3212 }
3213 if (GetCaretShown () and (GetSelectionStart () == GetSelectionEnd ())) {
3214 RefreshWindowRect (CalculateCaretRect ());
3215 }
3216}
3217
3218/*
3219@METHOD: TextInteractor::OnTypedNormalCharacter
3220@DESCRIPTION: <p>High level handling that does basically all of the portable support for a typed character.
3221 This is typically what you would call (the class library integration classes call this directly) to handle
3222 keyboard input. You might also plausibly OVERRIDE this method to provide special handling for
3223 particular key sequences (e.g. LedLineIt! overrides this to map shift-tab when there is a selection
3224 to an indent command rather than an insertion of text).</p>
3225 */
3226void TextInteractor::OnTypedNormalCharacter (Led_tChar theChar, bool /*optionPressed*/, bool /*shiftPressed*/, bool /*commandPressed*/,
3227 bool controlPressed, bool /*altKeyPressed*/)
3228{
3229 IdleManager::NonIdleContext nonIdleContext;
3230
3231 Assert (GetSelectionEnd () <= GetLength () + 1);
3232
3233 if (GetSuppressTypedControlCharacters ()) {
3234 bool controlChar = Character (theChar).IsControl ();
3235 if (controlChar && (theChar == '\r' || theChar == '\n' || theChar == ' ' || theChar == '\t' || theChar == '\b')) {
3236 controlChar = false;
3237 }
3238 if (controlChar) {
3239 OnBadUserInput ();
3240 return;
3241 }
3242 }
3243
3244 switch (theChar) {
3245 case '\b': {
3246 CursorMovementDirection dir = eCursorBack;
3247 CursorMovementUnit unit = controlPressed ? eCursorByWord : eCursorByChar;
3248 CursorMovementAction action = eCursorDestroying;
3249 DoSingleCharCursorEdit (dir, unit, action, eDefaultUpdate);
3250 } break;
3251
3252 default: {
3253 if (theChar == '\n') {
3254 BreakInGroupedCommands ();
3255 }
3256 InteractiveReplace (&theChar, 1, eDefaultUpdate);
3257 } break;
3258 }
3259
3260 ScrollToSelection ();
3261#if qStroika_Frameworks_Led_PeekForMoreCharsOnUserTyping
3262 UpdateIfNoKeysPending ();
3263#else
3264 Update ();
3265#endif
3266}
3267
3268bool TextInteractor::DelaySomeForScrollBarClick ()
3269{
3270 const Time::DurationSeconds kDelayAfterFirstTicks = 0.20s; // maybe should use ::GetDblClickTime()???
3271 const Time::DurationSeconds kDelayAfterOtherTicks = 0.02s; // This delay is so on really fast computers, text doesn't scroll too quickly
3272 const int kTimesForFirstClick = 2;
3273 const Time::DurationSeconds kLongTime = 1.0s; // any click after this time deemed we start again with first-tick
3274 static short sTimesThruBeforeReset;
3275
3276 Foundation::Time::TimePointSeconds now = Time::GetTickCount ();
3277 if (fLastScrolledAt == Time::TimePointSeconds{0s} or fLastScrolledAt + kLongTime < now) {
3278 fLastScrolledAt = now + kDelayAfterFirstTicks;
3279 sTimesThruBeforeReset = 1;
3280 return true; // first time through - handle click immediately
3281 }
3282 else if (fLastScrolledAt < now) {
3283 ++sTimesThruBeforeReset;
3284 fLastScrolledAt = now + (sTimesThruBeforeReset <= kTimesForFirstClick ? kDelayAfterFirstTicks : kDelayAfterOtherTicks);
3285 return true; // enuf time has elapsed
3286 }
3287 else {
3288 return false; // not enough time has elapsed
3289 }
3290}
3291#endif
#define AssertNotNull(p)
Definition Assertions.h:336
#define RequireNotNull(p)
Definition Assertions.h:350
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:327
constexpr bool IsControl() const noexcept
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual pointer data() noexcept
returns a (possibly const) pointer to the start of the live buffer data. This return value can be inv...
basic_string< SDKChar > SDKString
Definition SDKString.h:38