Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
HiddenText.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <iterator>
7#include <memory>
8
9#include "Config.h"
10
12
13#include "StandardStyledTextImager.h"
14
15#include "HiddenText.h"
16
17using namespace Stroika::Foundation;
18
19using namespace Stroika::Foundation;
20using namespace Stroika::Frameworks;
21using namespace Stroika::Frameworks::Led;
22
23/*
24 ********************************************************************************
25 ****************************** HidableTextMarkerOwner **************************
26 ********************************************************************************
27 */
28HidableTextMarkerOwner::HidableTextMarkerOwner (TextStore& textStore)
29 : fTextStore{textStore}
30{
31 SetInternalizer (nullptr); // sets default
32 SetExternalizer (nullptr); // DITTO
33 fTextStore.AddMarkerOwner (this);
34}
35
36HidableTextMarkerOwner::~HidableTextMarkerOwner ()
37{
38 Require (fMarkersToBeDeleted.IsEmpty ());
39 try {
40 MarkerList markers = CollectAllInRange (0, fTextStore.GetLength () + 1);
41 if (not markers.empty ()) {
42 {
43 vector<Marker*> tmp;
44 copy (markers.begin (), markers.end (), inserter (tmp, tmp.begin ()));
45 GetTextStore ().RemoveMarkers (Traversal::Iterator2Pointer (tmp.begin ()), tmp.size ());
46 }
47 for (auto i = markers.begin (); i != markers.end (); ++i) {
48 delete (*i);
49 }
50 }
51 fTextStore.RemoveMarkerOwner (this);
52 }
53 catch (...) {
54 Assert (false); // someday - handle exceptions here better - should cause no harm but memory leak (frequently - but sometimes worse),
55 // and should be exceedingly rare - LGP 2000/03/30
56 }
57}
58
59/*
60@METHOD: HidableTextMarkerOwner::HideAll
61@DESCRIPTION: <p>Tell all the existing 'hidden text' markers (optionally restricted to those intersecting
62 the 'from' .. 'to' range) to hide themselves. This does <em>not</em> create any new @'HidableTextMarkerOwner::HidableTextMarker's.
63 Call @'HidableTextMarkerOwner::MakeRegionHidable' for that.</p>
64 <p>See @'HidableTextMarkerOwner::ShowAll' to re-show them.</p>
65*/
66void HidableTextMarkerOwner::HideAll ()
67{
68 HideAll (0, fTextStore.GetEnd () + 1);
69}
70
71void HidableTextMarkerOwner::HideAll (size_t from, size_t to)
72{
73 Invariant ();
74 MarkerList markers = CollectAllInRange (from, to);
75 sort (markers.begin (), markers.end (), LessThan<HidableTextMarker> ());
76 // proceed in reverse direction - so that any markers being shown won't affect our text offsets
77 for (auto i = markers.rbegin (); i != markers.rend (); ++i) {
78 if (TextStore::Overlap (**i, from, to)) {
79 (*i)->Hide ();
80 }
81 }
82 Invariant ();
83}
84
85/*
86@METHOD: HidableTextMarkerOwner::ShowAll
87@DESCRIPTION: <p>Tell all the existing 'hidden text' markers (optionally restricted to those intersecting
88 the 'from' .. 'to' range) to show themselves. This does <em>not</em> destroy any new @'HidableTextMarkerOwner::HidableTextMarker's.
89 It merely re-installs their context into the document so that it can be seen and edited.</p>
90 <p>See also @'HidableTextMarkerOwner::HideAll'.</p>
91*/
92void HidableTextMarkerOwner::ShowAll ()
93{
94 ShowAll (0, fTextStore.GetEnd () + 1);
95}
96
97void HidableTextMarkerOwner::ShowAll (size_t from, size_t to)
98{
99 Invariant ();
100 MarkerList markers = CollectAllInRange (from, to);
101 sort (markers.begin (), markers.end (), LessThan<HidableTextMarker> ());
102 // proceed in reverse direction - so that any markers being shown won't affect our text offsets
103 for (auto i = markers.rbegin (); i != markers.rend (); ++i) {
104 if (TextStore::Overlap (**i, from, to)) {
105#if qStroika_Foundation_Debug_AssertionsChecked
106 HidableTextMarkerOwner::Invariant_ (); // don't make virtual call - cuz that might not be fully valid til end of routine...
107#endif
108 (*i)->Show ();
109#if qStroika_Foundation_Debug_AssertionsChecked
110 HidableTextMarkerOwner::Invariant_ (); // don't make virtual call - cuz that might not be fully valid til end of routine...
111#endif
112 }
113 }
114 Invariant ();
115}
116
117/*
118@METHOD: HidableTextMarkerOwner::MakeRegionHidable
119@DESCRIPTION: <p>Mark the region from 'from' to 'to' as hidable. This could involve coalescing adjacent hidden text markers
120 (though even adjacent markers are sometimes <em>NOT</em> coalesced, if they differ in shown/hidden state, or if one is already
121 hidden - cuz then it would be too hard to combine the two).</p>
122 <p>This routine assures that after the call - all text in the given range is encapsulated by a hidden-text
123 marker or markers.</p>
124 <p>Note - this does <em>NOT</em> actually hide the text. You must then call @'HidableTextMarkerOwner::HideAll' and give
125 it the same range given this function to get the text to actually disapear from the screen.</p>
126 <p>See also @'HidableTextMarkerOwner::MakeRegionUnHidable'</p>
127*/
128void HidableTextMarkerOwner::MakeRegionHidable (size_t from, size_t to)
129{
130 Require (from <= to);
131 Invariant ();
132
133 if (from == to) {
134 return; // degenerate behavior
135 }
136
137 MarkerList hidableTextMarkersInRange = CollectAllInRange (from, to);
138
139 // short circuit some code as a performance tweek
140 if (hidableTextMarkersInRange.size () == 1 and hidableTextMarkersInRange[0]->GetStart () <= from and hidableTextMarkersInRange[0]->GetEnd () >= to) {
141 return;
142 }
143
144 sort (hidableTextMarkersInRange.begin (), hidableTextMarkersInRange.end (), LessThan<HidableTextMarker> ());
145
146 HidableTextMarker* prevNonEmptyMarker = nullptr; // used for coalescing...
147
148 if (from > 0) {
149 MarkerList tmp = CollectAllInRange (from - 1, from);
150 // Can sometimes have more than one, if two different hidable region markers didn't get coalesced.
151 for (auto i = tmp.begin (); i != tmp.end (); ++i) {
152 if ((*i)->IsShown () and (*i)->GetEnd () == from) {
153 prevNonEmptyMarker = *i;
154 break;
155 }
156 }
157 }
158
159 // Update other markers and owners - since this change can affect the display
160 {
161 TextStore::SimpleUpdater updater{fTextStore, from, to};
162
163 // iterate through markers, and eliminate all but one of them. The last one - if it exists - we'll enlarge.
164 for (auto i = hidableTextMarkersInRange.begin (); i != hidableTextMarkersInRange.end (); ++i) {
165 AssertNotNull (*i);
166 if (prevNonEmptyMarker == nullptr) {
167 Assert (i == hidableTextMarkersInRange.begin ()); // must have been first marker...
168 prevNonEmptyMarker = *i;
169 AssertNotNull (prevNonEmptyMarker);
170 DISABLE_COMPILER_MSC_WARNING_START (6011)
171 if (from < prevNonEmptyMarker->GetStart ()) {
172 if (prevNonEmptyMarker->IsShown ()) {
173 fTextStore.SetMarkerStart (prevNonEmptyMarker, from);
174 }
175 else {
176 // cannot combine hidden and shown markers, so must create a new one
177 Assert (prevNonEmptyMarker->GetStart () > from);
178 GetTextStore ().AddMarker (MakeHidableTextMarker (), from, prevNonEmptyMarker->GetStart () - from, this);
179 }
180 }
181 DISABLE_COMPILER_MSC_WARNING_END (6011)
182 }
183 else if (prevNonEmptyMarker->IsShown ()) {
184 // If its shown - we can coalesce it - so delete old one
185 fMarkersToBeDeleted.AccumulateMarkerForDeletion (*i);
186 }
187 }
188 if (prevNonEmptyMarker == nullptr) {
189 GetTextStore ().AddMarker (MakeHidableTextMarker (), from, to - from, this);
190 }
191 else {
192 if (prevNonEmptyMarker->GetEnd () < to) {
193 if (prevNonEmptyMarker->IsShown ()) {
194 fTextStore.SetMarkerEnd (prevNonEmptyMarker, to);
195 }
196 else {
197 // cannot combine hidden and shown markers, so must create a new one
198 Assert (to > prevNonEmptyMarker->GetEnd ());
199 GetTextStore ().AddMarker (MakeHidableTextMarker (), prevNonEmptyMarker->GetEnd (), to - prevNonEmptyMarker->GetEnd (), this);
200 }
201 }
202 }
203 }
204
205 Invariant ();
206}
207
208/*
209@METHOD: HidableTextMarkerOwner::MakeRegionUnHidable
210@DESCRIPTION: <p>Remove any hidable-text markers in the given range. The markers could be either hidden or shown
211 at the time. This could involce splitting or coalescing adjacent markers.</p>
212 <p>See also @'HidableTextMarkerOwner::MakeRegionHidable'.</p>
213*/
214void HidableTextMarkerOwner::MakeRegionUnHidable (size_t from, size_t to)
215{
216 Require (from <= to);
217 Invariant ();
218
219 if (from == to) {
220 return; // degenerate behavior
221 }
222
223 MarkerList hidableTextMarkersInRange = CollectAllInRange (from, to);
224 // short circuit some code as a performance tweek
225 if (hidableTextMarkersInRange.empty ()) {
226 return;
227 }
228
229 TempMarker pastSelMarker (GetTextStore (), to + 1, to + 1);
230
231 // Update other markers and owners - since this change can affect the display
232 {
233 TextStore::SimpleUpdater updater{fTextStore, from, to};
234 {
235 sort (hidableTextMarkersInRange.begin (), hidableTextMarkersInRange.end (), LessThan<HidableTextMarker>{});
236
237 // iterate through markers, and eliminate all of them, except maybe on the endpoints - if they have stuff outside
238 // this range
239 for (auto i = hidableTextMarkersInRange.begin (); i != hidableTextMarkersInRange.end (); ++i) {
240 if (i == hidableTextMarkersInRange.begin () and (*i)->GetStart () < from) {
241 // merely adjust its end-point so not overlapping this region. Be careful if he
242 // used to extend past to, and cons up NEW marker for that part.
243 size_t oldEnd = (*i)->GetEnd ();
244 fTextStore.SetMarkerEnd (*i, from);
245 if (oldEnd > to) {
246 fTextStore.AddMarker (MakeHidableTextMarker (), to, oldEnd - to, this);
247 }
248 }
249 else if (i + 1 == hidableTextMarkersInRange.end () and (*i)->GetEnd () > to) {
250 // merely adjust its start-point so not overlapping this region
251 fTextStore.SetMarkerStart (*i, to);
252 }
253 else {
254 fMarkersToBeDeleted.AccumulateMarkerForDeletion (*i);
255 }
256 }
257 }
258 }
259
260 /*
261 * If - as a result of the unhiding - we've expanded our text - we may not have done a 'DIDUPDATE' for enough markers.
262 * So catch the ones we've missed.
263 *
264 * Note 100% sure this is a good enough test - but I hope so - LGP 2000/04/24
265 */
266 {
267 TextStore::SimpleUpdater updater{fTextStore, to, pastSelMarker.GetEnd ()};
268 }
269
270 Invariant ();
271}
272
273/*
274@METHOD: HidableTextMarkerOwner::GetHidableRegions
275@DESCRIPTION: <p>Return a @'DiscontiguousRun<DATA>' list (where DATA=void) of regions of hidable text.
276 Regions returned are relative to offset 'from'. So - for example - if we have hidden text from
277 5..8, and you call GetHidableRegions (2,8) you'll get back the list [[3,3]].</p>
278 <p>NB: this routine only returns the hidable regions which are currently being SHOWN - not any invisible
279 ones. This is because otherwise the run information would be useless, and not convey the
280 actual sizes of the hidden text.</p>
281 <p>See also @'HidableTextMarkerOwner::GetHidableRegionsWithData'.</p>
282*/
283DiscontiguousRun<bool> HidableTextMarkerOwner::GetHidableRegions (size_t from, size_t to) const
284{
285 Invariant ();
286 DiscontiguousRun<bool> result;
287 MarkerList markers = CollectAllInRange (from, to);
288 sort (markers.begin (), markers.end (), LessThan<HidableTextMarker> ());
289 size_t relStartFrom = from;
290 for (auto i = markers.begin (); i != markers.end (); ++i) {
291 size_t mStart;
292 size_t mEnd;
293 (*i)->GetRange (&mStart, &mEnd);
294 {
295 mStart = max (mStart, relStartFrom); // ignore if marker goes back further than our start
296 mEnd = min (mEnd, to); // ignore if end past requested end
297 Assert (mStart < mEnd);
298 result.push_back (DiscontiguousRunElement<bool> (mStart - relStartFrom, mEnd - mStart, (*i)->IsShown ()));
299 relStartFrom = mEnd;
300 }
301 }
302 return result;
303}
304
305DiscontiguousRun<bool> HidableTextMarkerOwner::GetHidableRegions () const
306{
307 return GetHidableRegions (0, fTextStore.GetEnd () + 1);
308}
309
310/*
311@METHOD: HidableTextMarkerOwner::GetHidableRegionsContiguous
312@DESCRIPTION: <p>If 'hidden' is true - then return true - iff the entire region from 'from' to 'to' is hidden.
313 If 'hidden' is false, then return true iff the entire region from 'from' to 'to' contains no hidden elements.</p>
314*/
315bool HidableTextMarkerOwner::GetHidableRegionsContiguous (size_t from, size_t to, bool hidden) const
316{
317 Invariant ();
318 // Sloppy, inefficient implementation. Can be MUCH faster - since we just need to know if there are ANY in this region!
319 DiscontiguousRun<bool> tmpHack = GetHidableRegions (from, to);
320 if (tmpHack.size () == 1) {
321 return tmpHack[0].fData == hidden and tmpHack[0].fOffsetFromPrev == 0 and tmpHack[0].fElementLength >= to - from;
322 }
323 else {
324 if (hidden) {
325 return false;
326 }
327 else {
328 return tmpHack.size () == 0;
329 }
330 }
331}
332
333/*
334@METHOD: HidableTextMarkerOwner::SetInternalizer
335@DESCRIPTION: <p>Sets the internalizer (@'FlavorPackageInternalizer' subclass). to be used with this class.
336 It defaults to @'FlavorPackageInternalizer'.</p>
337*/
338void HidableTextMarkerOwner::SetInternalizer (const shared_ptr<FlavorPackageInternalizer>& i)
339{
340 fInternalizer = i;
341 if (fInternalizer == nullptr) {
342 fInternalizer = make_shared<FlavorPackageInternalizer> (GetTextStore ());
343 }
344}
345
346/*
347@METHOD: HidableTextMarkerOwner::SetExternalizer
348@DESCRIPTION: <p>Sets the externalizer (@'FlavorPackageExternalizer' subclass). to be used with this class.
349 It defaults to @'FlavorPackageExternalizer'.</p>
350*/
351void HidableTextMarkerOwner::SetExternalizer (const shared_ptr<FlavorPackageExternalizer>& e)
352{
353 fExternalizer = e;
354 if (fExternalizer == nullptr) {
355 fExternalizer = make_shared<FlavorPackageExternalizer> (GetTextStore ());
356 }
357}
358
359/*
360@METHOD: HidableTextMarkerOwner::CollapseMarker
361@DESCRIPTION:
362*/
363void HidableTextMarkerOwner::CollapseMarker (HidableTextMarker* m)
364{
365 RequireNotNull (m);
366 Require (m->fShown);
367 size_t start = 0;
368 size_t end = 0;
369 m->GetRange (&start, &end);
370 TextStore::SimpleUpdater updater{fTextStore, start, end, false};
371 m->fShown = false;
372}
373
374/*
375@METHOD: HidableTextMarkerOwner::ReifyMarker
376@DESCRIPTION:
377*/
378void HidableTextMarkerOwner::ReifyMarker (HidableTextMarker* m)
379{
380 RequireNotNull (m);
381 Require (not m->fShown);
382 {
383 size_t start = 0;
384 size_t end = 0;
385 m->GetRange (&start, &end);
386
387 TextStore::SimpleUpdater updater (fTextStore, start, end, false);
388 m->fShown = true;
389 }
390}
391
392/*
393@METHOD: HidableTextMarkerOwner::MakeHidableTextMarker
394@DESCRIPTION: <p>This routine creates the actual marker objects to be used to hide text.</p>
395 <p>By default - it
396 creates @'HidableTextMarkerOwner::FontSpecHidableTextMarker' markers. You can OVERRIDE this to create different
397 style markers, or to set different color etc attributes for use in your @'HidableTextMarkerOwner' subclass.</p>
398*/
399HidableTextMarkerOwner::HidableTextMarker* HidableTextMarkerOwner::MakeHidableTextMarker ()
400{
401 /*
402 * Some alternates you may want to consider in your overrides...
403 *
404
405 IncrementalFontSpecification fontSpec;
406 fontSpec.SetTextColor (Color::kRed);
407 return new LightUnderlineHidableTextMarker (fontSpec);
408
409 or:
410 IncrementalFontSpecification fontSpec;
411 fontSpec.SetTextColor (Color::kRed);
412 #if qStroika_Foundation_Common_Platform_Windows
413 fontSpec.SetStyle_Strikeout (true);
414 #endif
415 return new FontSpecHidableTextMarker (fontSpec);
416
417 */
418 return new LightUnderlineHidableTextMarker{};
419}
420
421TextStore* HidableTextMarkerOwner::PeekAtTextStore () const
422{
423 return &fTextStore;
424}
425
426void HidableTextMarkerOwner::AboutToUpdateText (const UpdateInfo& updateInfo)
427{
428 Invariant ();
429 Assert (fMarkersToBeDeleted.IsEmpty ());
430 inherited::AboutToUpdateText (updateInfo);
431}
432
433void HidableTextMarkerOwner::DidUpdateText (const UpdateInfo& updateInfo) noexcept
434{
435 inherited::DidUpdateText (updateInfo);
436 if (updateInfo.fTextModified) {
437 // cull empty markers
438 MarkerList markers = CollectAllInRange_OrSurroundings (updateInfo.fReplaceFrom, updateInfo.GetResultingRHS ());
439 for (auto i = markers.begin (); i != markers.end (); ++i) {
440 HidableTextMarker* m = *i;
441 if (m->GetLength () == 0) {
442 fMarkersToBeDeleted.AccumulateMarkerForDeletion (m);
443 }
444 }
445 }
446 fMarkersToBeDeleted.FinalizeMarkerDeletions ();
447 Invariant ();
448}
449
450HidableTextMarkerOwner::MarkerList HidableTextMarkerOwner::CollectAllInRange (size_t from, size_t to) const
451{
452 MarkersOfATypeMarkerSink2Vector<HidableTextMarker> result;
453 fTextStore.CollectAllMarkersInRangeInto (from, to, this, result);
454 return result.fResult;
455}
456
457HidableTextMarkerOwner::MarkerList HidableTextMarkerOwner::CollectAllInRange_OrSurroundings (size_t from, size_t to) const
458{
459 MarkersOfATypeMarkerSink2Vector<HidableTextMarker> result;
460 fTextStore.CollectAllMarkersInRangeInto_OrSurroundings (from, to, this, result);
461 return result.fResult;
462}
463
464#if qStroika_Foundation_Debug_AssertionsChecked
465void HidableTextMarkerOwner::Invariant_ () const
466{
467 MarkerList markers = CollectAllInRange (0, fTextStore.GetEnd () + 1);
468
469 sort (markers.begin (), markers.end (), LessThan<HidableTextMarker> ());
470
471 // Walk through - and see we are non-overlapping, and have no empties (unless hidden)
472 // Note - we DON'T require adjacent ones be coalesced, though we try to arrange for that if possible.
473 // We don't always coalece cuz if one already hidden, and we try to make a new one - its too hard to combine the adjacent
474 // stored readwritepackages.
475 size_t lastEnd = 0;
476 for (size_t i = 0; i < markers.size (); i++) {
477 HidableTextMarker* m = markers[i];
478 Assert (m->GetLength () > 0);
479 Assert (m->GetStart () >= lastEnd);
480 lastEnd = m->GetEnd ();
481 }
482 Assert (lastEnd <= fTextStore.GetLength () + 1);
483}
484#endif
485
486/*
487 ********************************************************************************
488 ********* HidableTextMarkerOwner::LightUnderlineHidableTextMarker **************
489 ********************************************************************************
490 */
491HidableTextMarkerOwner::LightUnderlineHidableTextMarker::LightUnderlineHidableTextMarker (const IncrementalFontSpecification& fsp)
492{
493 fFontSpecification = fsp;
494}
495
496Color HidableTextMarkerOwner::LightUnderlineHidableTextMarker::GetUnderlineBaseColor () const
497{
498 if (fFontSpecification.GetTextColor_Valid ()) {
499 return fFontSpecification.GetTextColor ();
500 }
501 else {
502 return inherited::GetUnderlineBaseColor ();
503 }
504}
505
506/*
507 ********************************************************************************
508 *********************** UniformHidableTextMarkerOwner **************************
509 ********************************************************************************
510 */
511UniformHidableTextMarkerOwner::UniformHidableTextMarkerOwner (TextStore& textStore)
512 : inherited{textStore}
513{
514}
515
516void UniformHidableTextMarkerOwner::HideAll ()
517{
518 if (not fHidden) {
519 inherited::HideAll ();
520 fHidden = true;
521 }
522}
523
524void UniformHidableTextMarkerOwner::ShowAll ()
525{
526 if (fHidden) {
527 inherited::ShowAll ();
528 fHidden = false;
529 }
530}
531
532void UniformHidableTextMarkerOwner::MakeRegionHidable (size_t from, size_t to)
533{
534 Require (from <= to);
535
536 //Not so great implementation - could look at particular objects created - and make sure THEY have the hidden bit set...
537 inherited::MakeRegionHidable (from, to);
538 if (fHidden) {
539 inherited::HideAll ();
540 }
541 else {
542 inherited::ShowAll ();
543 }
544}
545
546#if qStroika_Frameworks_Led_SupportGDI
547/*
548 ********************************************************************************
549 ************* HidableTextMarkerOwner::FontSpecHidableTextMarker ****************
550 ********************************************************************************
551 */
552FontSpecification HidableTextMarkerOwner::FontSpecHidableTextMarker::MakeFontSpec (const StyledTextImager* /*imager*/, const StyleRunElement& runElement) const
553{
555 for (auto i = runElement.fSupercededMarkers.begin (); i != runElement.fSupercededMarkers.end (); ++i) {
556 if (StandardStyleMarker* m = dynamic_cast<StandardStyleMarker*> (*i)) {
557 fsp.MergeIn (m->fFontSpecification);
558 }
559 }
560 fsp.MergeIn (fFontSpecification); // give our fontSpec last dibs - so 'deletion' hilighting takes precedence
561 return fsp;
562}
563#endif
564
565/*
566 ********************************************************************************
567 ******************* ColoredUniformHidableTextMarkerOwner ***********************
568 ********************************************************************************
569 */
570void ColoredUniformHidableTextMarkerOwner::FixupSubMarkers ()
571{
572 // Now walk all existing markers, and set their fColor field!!!
573 MarkerList markers = CollectAllInRange_OrSurroundings (0, GetTextStore ().GetEnd () + 1);
574 for (auto i = markers.begin (); i != markers.end (); ++i) {
575 LightUnderlineHidableTextMarker* m = dynamic_cast<LightUnderlineHidableTextMarker*> (*i);
576 AssertNotNull (m);
577 if (fColored) {
578 m->fFontSpecification.SetTextColor (fColor);
579 }
580 else {
581 m->fFontSpecification.InvalidateTextColor ();
582 }
583 }
584}
585
586ColoredUniformHidableTextMarkerOwner::HidableTextMarker* ColoredUniformHidableTextMarkerOwner::MakeHidableTextMarker ()
587{
588 IncrementalFontSpecification fontSpec;
589 if (fColored) {
590 fontSpec.SetTextColor (fColor);
591 }
592 return new LightUnderlineHidableTextMarker (fontSpec);
593}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347