Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
MultiRowTextImager.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <climits>
7
9#include "Stroika/Frameworks/Led/GDI.h"
10#include "Stroika/Frameworks/Led/Support.h"
11
12#include "MultiRowTextImager.h"
13
14using namespace Stroika::Foundation;
15
16using namespace Stroika::Frameworks::Led;
17
18#if qStroika_Frameworks_Led_SupportGDI
19/*
20 ********************************************************************************
21 ********************************* MultiRowTextImager ***************************
22 ********************************************************************************
23 */
24MultiRowTextImager::~MultiRowTextImager ()
25{
26 Assert (fTopLinePartitionMarkerInWindow == nullptr);
27}
28
29void MultiRowTextImager::HookLosingTextStore ()
30{
31 inherited::HookLosingTextStore ();
32 HookLosingTextStore_ ();
33}
34
35void MultiRowTextImager::HookLosingTextStore_ ()
36{
37 SetPartition (PartitionPtr ());
38}
39
40void MultiRowTextImager::HookGainedNewTextStore ()
41{
42 inherited::HookGainedNewTextStore ();
43 HookGainedNewTextStore_ ();
44}
45
46void MultiRowTextImager::HookGainedNewTextStore_ ()
47{
48 if (GetPartition ().get () == nullptr) {
49 SetPartition (MakeDefaultPartition ());
50 }
51}
52
53void MultiRowTextImager::SetPartition (const PartitionPtr& partitionPtr)
54{
55 fPMCacheMgr.reset ();
56 inherited::SetPartition (partitionPtr);
57 if (partitionPtr.get () == nullptr) {
58 InvalidateTotalRowsInWindow ();
59 fTopLinePartitionMarkerInWindow = nullptr;
60 }
61 else {
62 fPMCacheMgr = unique_ptr<PMInfoCacheMgr> (new PMInfoCacheMgr (*this));
63 fTopLinePartitionMarkerInWindow = GetFirstPartitionMarker ();
64 // ReValidateSubRowInTopLineInWindow ();
65 InvalidateTotalRowsInWindow ();
66 AssureWholeWindowUsedIfNeeded ();
67 InvalidateScrollBarParameters (); // even if we don't change the top row, we might change enuf about the text to change sbar
68 }
69}
70
71PartitioningTextImager::PartitionPtr MultiRowTextImager::MakeDefaultPartition () const
72{
73 return PartitionPtr (new LineBasedPartition (GetTextStore ()));
74}
75
76MultiRowTextImager::PartitionElementCacheInfo MultiRowTextImager::GetPartitionElementCacheInfo (Partition::PartitionMarker* pm) const
77{
78 return fPMCacheMgr->GetPartitionElementCacheInfo (pm);
79}
80
81MultiRowTextImager::PartitionElementCacheInfo MultiRowTextImager::GetPartitionElementCacheInfo (MultiRowTextImager::RowReference row) const
82{
83 return GetPartitionElementCacheInfo (row.GetPartitionMarker ());
84}
85
86bool MultiRowTextImager::GetIthRowReferenceFromHere (RowReference* adjustMeInPlace, ptrdiff_t ith) const
87{
88 for (; ith > 0; --ith) {
89 if (not GetNextRowReference (adjustMeInPlace)) {
90 return false;
91 }
92 }
93 for (; ith < 0; ++ith) {
94 if (not GetPreviousRowReference (adjustMeInPlace)) {
95 return false;
96 }
97 }
98 return true;
99}
100
101size_t MultiRowTextImager::GetRowNumber (RowReference rowRef) const
102{
103 // NB: This routine is VERY EXPENSIVE, if the text above the given row has not yet been wrapped, since
104 // it forces a wrap. This is meant only to be a convenient code-saver in implementing rownumber based
105 // APIs - even though their use is discouraged...
106 size_t rowNumber = rowRef.GetSubRow ();
107 AssertNotNull (rowRef.GetPartitionMarker ());
108 for (PartitionMarker* cur = rowRef.GetPartitionMarker ()->GetPrevious (); cur != nullptr; cur = cur->GetPrevious ()) {
109 rowNumber += GetPartitionElementCacheInfo (cur).GetRowCount ();
110 }
111 return (rowNumber);
112}
113
114/*
115 @METHOD: MultiRowTextImager::CountRowDifference
116 @DESCRIPTION: <p>Count the # of rows from one rowreference to the other (order doesn't matter)
117 <p>See also @'MultiRowTextImager::CountRowDifferenceLimited'</p>
118 */
119size_t MultiRowTextImager::CountRowDifference (RowReference lhs, RowReference rhs) const
120{
121 /*
122 * See which row reference comes before the other, and then can from one TO the
123 * other. Sadly - this forces the wrapping of all that text in between.
124 *
125 * Note - this CAN be expensive if the two row references are far apart, as it requires
126 * wrapping all the text in-between.
127 */
128 PartitionMarker* lhsPM = lhs.GetPartitionMarker ();
129 PartitionMarker* rhsPM = rhs.GetPartitionMarker ();
130 size_t lhsMarkerStart = lhsPM->GetStart ();
131 size_t rhsMarkerStart = rhsPM->GetStart ();
132 bool leftSmaller = ((lhsMarkerStart < rhsMarkerStart) or ((lhsMarkerStart == rhsMarkerStart) and lhs.GetSubRow () <= rhs.GetSubRow ()));
133 RowReference firstRowRef = leftSmaller ? lhs : rhs;
134 RowReference lastRowRef = leftSmaller ? rhs : lhs;
135
136 size_t rowsGoneBy = 0;
137 for (RowReference cur = firstRowRef; cur != lastRowRef; ++rowsGoneBy) {
138 [[maybe_unused]] bool result = GetIthRowReferenceFromHere (&cur, 1);
139 Assert (result);
140 }
141 return rowsGoneBy;
142}
143
144/*
145 @METHOD: MultiRowTextImager::CountRowDifferenceLimited
146 @DESCRIPTION: <p>Count the # of rows from one rowreference to the other (order doesn't matter), but
147 never count a number of rows exceeding 'limit'. Just return 'limit' if there are 'limit' rows or more.</p>
148 <p>The reason you would use this intead of @'MultiRowTextImager::CountRowDifference' is if you are
149 only checking to see the rowcount is 'at least' something, and you dont wnat to count all the rows. That CAN
150 be a pig performance dog - since it tends to force a word-wrap.</p>
151 <p>See also @'MultiRowTextImager::CountRowDifference'</p>
152 */
153size_t MultiRowTextImager::CountRowDifferenceLimited (RowReference lhs, RowReference rhs, size_t limit) const
154{
155 /*
156 * See which row reference comes before the other, and then can from one TO the
157 * other. Sadly - this forces the wrapping of all that text in between.
158 *
159 * Note - this CAN be expensive if the two row references are far apart, as it requires
160 * wrapping all the text in-between.
161 */
162 PartitionMarker* lhsPM = lhs.GetPartitionMarker ();
163 PartitionMarker* rhsPM = rhs.GetPartitionMarker ();
164 size_t lhsMarkerStart = lhsPM->GetStart ();
165 size_t rhsMarkerStart = rhsPM->GetStart ();
166 bool leftSmaller = ((lhsMarkerStart < rhsMarkerStart) or ((lhsMarkerStart == rhsMarkerStart) and lhs.GetSubRow () <= rhs.GetSubRow ()));
167 RowReference firstRowRef = leftSmaller ? lhs : rhs;
168 RowReference lastRowRef = leftSmaller ? rhs : lhs;
169
170 size_t rowsGoneBy = 0;
171 for (RowReference cur = firstRowRef; cur != lastRowRef; ++rowsGoneBy) {
172 [[maybe_unused]] bool result = GetIthRowReferenceFromHere (&cur, 1);
173 Assert (result);
174 if (rowsGoneBy >= limit) {
175 break;
176 }
177 }
178 return rowsGoneBy;
179}
180
181size_t MultiRowTextImager::GetTopRowInWindow () const
182{
183 // NB: Use of this function is discouraged as it is inefficent in the presence of word-wrapping
184 return (GetRowNumber (GetTopRowReferenceInWindow ()));
185}
186
187size_t MultiRowTextImager::GetTotalRowsInWindow () const
188{
189 return GetTotalRowsInWindow_ ();
190}
191
192size_t MultiRowTextImager::GetLastRowInWindow () const
193{
194 // NB: Use of this function is discouraged as it is inefficent in the presence of word-wrapping
195 return (GetRowNumber (GetLastRowReferenceInWindow ()));
196}
197
198void MultiRowTextImager::SetTopRowInWindow (size_t newTopRow)
199{
200// NB: Use of this function is discouraged as it is inefficent in the presence of word-wrapping
201#if 0
202 Assert (newTopRow <= GetRowCount ()); // We require this, but don't call since would cause word-wrapping of entire text...
203#endif
204
205 SetTopRowInWindow (GetIthRowReference (newTopRow));
206
207 Assert (GetTopRowInWindow () == newTopRow); // Since a SetTopRowInWindow() was called - all the
208 // intervening lines have been wrapped anyhow - may
209 // as well check we have our definitions straight...
210}
211
212void MultiRowTextImager::AssureWholeWindowUsedIfNeeded ()
213{
214 SetTopRowInWindow (GetTopRowReferenceInWindow ());
215}
216
217/*
218 @METHOD: MultiRowTextImager::GetMarkerPositionOfStartOfWindow
219 @DESCRIPTION: <p>Efficient implementation of @'TextImager::GetMarkerPositionOfStartOfWindow'</p>
220 */
221size_t MultiRowTextImager::GetMarkerPositionOfStartOfWindow () const
222{
223 return (GetStartOfRow (GetTopRowReferenceInWindow ()));
224}
225
226/*
227 @METHOD: MultiRowTextImager::GetMarkerPositionOfEndOfWindow
228 @DESCRIPTION: <p>Efficient implementation of @'TextImager::GetMarkerPositionOfEndOfWindow'</p>
229 */
230size_t MultiRowTextImager::GetMarkerPositionOfEndOfWindow () const
231{
232 return GetEndOfRow (GetLastRowReferenceInWindow ());
233}
234
235size_t MultiRowTextImager::GetMarkerPositionOfStartOfLastRowOfWindow () const
236{
237 return GetStartOfRow (GetLastRowReferenceInWindow ());
238}
239
240ptrdiff_t MultiRowTextImager::CalculateRowDeltaFromCharDeltaFromTopOfWindow (long deltaChars) const
241{
242 Assert (long (GetMarkerPositionOfStartOfWindow ()) >= 0 - deltaChars);
243 size_t pos = long (GetMarkerPositionOfStartOfWindow ()) + deltaChars;
244 RowReference targetRow = GetRowReferenceContainingPosition (pos);
245 size_t rowDiff = CountRowDifference (targetRow, GetTopRowReferenceInWindow ());
246 return (deltaChars >= 0) ? rowDiff : -long (rowDiff);
247}
248
249ptrdiff_t MultiRowTextImager::CalculateCharDeltaFromRowDeltaFromTopOfWindow (ptrdiff_t deltaRows) const
250{
251 RowReference row = GetIthRowReferenceFromHere (GetTopRowReferenceInWindow (), deltaRows);
252 return (long (GetStartOfRow (row)) - long (GetMarkerPositionOfStartOfWindow ()));
253}
254
255void MultiRowTextImager::ScrollByIfRoom (ptrdiff_t downByRows)
256{
257 RowReference newTopRow = GetTopRowReferenceInWindow ();
258 (void)GetIthRowReferenceFromHere (&newTopRow, downByRows); // ignore result cuz we did say - IF-ROOM!
259 SetTopRowInWindow (newTopRow);
260}
261
262/*
263 @METHOD: MultiRowTextImager::ScrollSoShowing
264 @DESCRIPTION: <p>Implement @'TextImager::ScrollSoShowing' API.</p>
265 */
266void MultiRowTextImager::ScrollSoShowing (size_t markerPos, size_t andTryToShowMarkerPos)
267{
268 Assert (markerPos <= GetLength ()); // Allow any marker position (not just character?)
269 Assert (fTotalRowsInWindow == 0 or fTotalRowsInWindow == ComputeRowsThatWouldFitInWindowWithTopRow (GetTopRowReferenceInWindow ()));
270
271 if (andTryToShowMarkerPos == 0) { // special flag indicating we don't care...
272 andTryToShowMarkerPos = markerPos;
273 }
274 Assert (andTryToShowMarkerPos <= GetLength ()); // Allow any marker position (not just character?)
275
276 /*
277 * First check and see if the given position is within the current window
278 * If so, do nothing. If it isn't, then try making the given selection
279 * the first row (this later strategy is subject to chanage - but its
280 * a plausible, and easy to implement start).
281 */
282 size_t startOfWindow = GetMarkerPositionOfStartOfWindow ();
283 size_t endOfWindow = GetMarkerPositionOfEndOfWindow ();
284 if (markerPos >= startOfWindow and markerPos < endOfWindow and andTryToShowMarkerPos >= startOfWindow and andTryToShowMarkerPos < endOfWindow) {
285 ScrollSoShowingHHelper (markerPos, andTryToShowMarkerPos);
286 return; // nothing (vertical) changed...
287 }
288
289 RowReference originalTop = GetTopRowReferenceInWindow ();
290
291 /*
292 * Now things are a little complicated. We want to show both ends of the
293 * selection - if we can. But - if we must show only one end or the other, we
294 * make sure we show the 'markerPos' end.
295 *
296 * We also would probably (maybe?) like to minimize the amount we scroll-by.
297 * that is - if we only need to show one more line, don't scroll by
298 * more than that.
299 */
300
301 /*
302 * First get us to a RowReference which is close to where we will eventually end up. That way, and calls
303 * we do which will require word-wrapping (stuff to count rows) will only get applied to rows with a good
304 * liklihood of needing to be wrapped anyhow.
305 */
306 RowReference newTop = originalTop;
307 while (markerPos < newTop.GetPartitionMarker ()->GetStart ()) {
308 newTop = RowReference (newTop.GetPartitionMarker ()->GetPrevious (), 0);
309 }
310 // only try scrolling down at all if we don't already fit in the window - cuz otherwise - we could artificially
311 // scroll when not needed.
312 if (not PositionWouldFitInWindowWithThisTopRow (markerPos, newTop)) {
313 while (markerPos > newTop.GetPartitionMarker ()->GetEnd ()) {
314 if (newTop.GetPartitionMarker ()->GetNext () == nullptr) {
315 // could be going to row IN last line
316 break;
317 }
318 newTop = RowReference (newTop.GetPartitionMarker ()->GetNext (), 0); // use row 0 to avoid computing RowCount()
319 }
320 Assert (Contains (markerPos, markerPos, *newTop.GetPartitionMarker ()));
321 }
322
323 /*
324 * At this point, we have a newTop which is CLOSE to where it will end up. We now adjust the
325 * newTop to ASSURE that markerPos is shown.
326 */
327 while (markerPos < GetStartOfRow (newTop) and GetPreviousRowReference (&newTop))
328 ;
329 while (not PositionWouldFitInWindowWithThisTopRow (markerPos, newTop) and GetNextRowReference (&newTop))
330 ;
331
332 // At this point our main desired position should be visible
333 Assert (markerPos >= GetStartOfRow (newTop));
334 Assert (PositionWouldFitInWindowWithThisTopRow (markerPos, newTop));
335
336 /*
337 * Now - try to adjust the newTop so that the 'andTryToShowMarkerPos' is also
338 * shown. But - BE CAREFUL WE PRESERVE VISIBILITY OF 'markerPos'!!!
339 */
340 while (not PositionWouldFitInWindowWithThisTopRow (andTryToShowMarkerPos, newTop)) {
341 RowReference trailNewTop = newTop;
342 if (andTryToShowMarkerPos < GetStartOfRow (trailNewTop)) {
343 if (not GetPreviousRowReference (&trailNewTop)) {
344 break;
345 }
346 }
347 else {
348 if (not GetNextRowReference (&trailNewTop)) {
349 break;
350 }
351 }
352 if (PositionWouldFitInWindowWithThisTopRow (markerPos, trailNewTop)) {
353 newTop = trailNewTop;
354 }
355 else {
356 break;
357 }
358 }
359
360 /*
361 * Now - see if we've moved our 'newTop' by more than a certain threshold. If YES - then we may as well scroll
362 * so that the new region of interest is CENTERED in the window.
363 */
364 const unsigned kRowMoveThreshold = 1;
365 if (CountRowDifferenceLimited (originalTop, newTop, kRowMoveThreshold + 1) > kRowMoveThreshold) {
366 bool mustPreserveSecondPos = PositionWouldFitInWindowWithThisTopRow (andTryToShowMarkerPos, newTop);
367
368 // Now try to center the region of interest. Center by number of rows - not height of pixels. Height of pixels
369 // might be better - but I think this is slightly easier - and probably just as good most of the time.
370 size_t topMarkerPos = min (markerPos, andTryToShowMarkerPos);
371 size_t botMarkerPos = max (markerPos, andTryToShowMarkerPos);
372 size_t numRowsAbove = CountRowDifference (newTop, GetRowReferenceContainingPosition (topMarkerPos));
373 size_t rowsInWindow = ComputeRowsThatWouldFitInWindowWithTopRow (newTop);
374 RowReference lastRowInWindow = GetIthRowReferenceFromHere (newTop, rowsInWindow - 1);
375 size_t numRowsBelow = CountRowDifference (lastRowInWindow, GetRowReferenceContainingPosition (botMarkerPos));
376
377 size_t numRowsToSpare = numRowsAbove + numRowsBelow;
378
379 // to to make numRowsAbove = 1/2 of numRowsToSpare
380 RowReference trailNewTop = newTop;
381 GetIthRowReferenceFromHere (&trailNewTop, int (numRowsAbove) - int (numRowsToSpare / 2));
382 if (PositionWouldFitInWindowWithThisTopRow (markerPos, trailNewTop) and
383 (not mustPreserveSecondPos or PositionWouldFitInWindowWithThisTopRow (andTryToShowMarkerPos, trailNewTop))) {
384 newTop = trailNewTop;
385 }
386 }
387
388 SetTopRowInWindow (newTop); // This handles any notification of scrolling/update of sbars etc...
389
390 Assert (GetMarkerPositionOfStartOfWindow () <= markerPos and markerPos <= GetMarkerPositionOfEndOfWindow ());
391
392 /*
393 * Must call this AFTER we've done some VERTICAL scrolling - cuz the vertical scrolling could have affected the MaxHPOS.
394 */
395 ScrollSoShowingHHelper (markerPos, andTryToShowMarkerPos);
396}
397
398void MultiRowTextImager::SetTopRowInWindow (RowReference row)
399{
400 if (GetForceAllRowsShowing ()) {
401 row = AdjustPotentialTopRowReferenceSoWholeWindowUsed (row);
402 }
403 if (row != GetTopRowReferenceInWindow ()) {
404 SetTopRowInWindow_ (row);
405 InvalidateScrollBarParameters ();
406 }
407}
408
409/*
410 @METHOD: MultiRowTextImager::Draw
411 @DESCRIPTION: <p>Implement the basic drawing of the @'TextImager::Draw' API by breaking the content
412 up into 'rows' of text and drawing each row with @'MultiRowTextImager::DrawRow'. This implementation
413 calls @'TextImager::EraseBackground' to erase the background before drawing the text (drawing the actual
414 text is typically done in TRANSPARENT mode). Draw the space between lines (interline space) with
415 @'MultiRowTextImager::DrawInterLineSpace'.</p>
416 <p>This routine also respecs the @'TextImager::GetImageUsingOffscreenBitmaps' flag, and handles the
417 ofscreen imaging (to reduce flicker). Note that if the 'printing' argument is set- this overrides the offscreen bitmaps
418 flag, and prevents offscreen drawing.</p>
419 */
420void MultiRowTextImager::Draw (const Led_Rect& subsetToDraw, bool printing)
421{
422 Invariant ();
423
424 Led_Rect rowsLeftToDrawRect = GetWindowRect ();
425
426 Tablet_Acquirer tablet_ (this);
427 Tablet* tablet = tablet_;
428 AssertNotNull (tablet);
429
430/*
431 * Save old font/pen/brush info here, and restore - even in the presence of exceptions -
432 * on the way out. That way - the drawsegment code need not worry about restoring
433 * these things.
434 */
435#if qStroika_Foundation_Common_Platform_MacOS
436 tablet->SetPort ();
437 RGBColor oldForeColor = GDI_GetForeColor ();
438 RGBColor oldBackColor = GDI_GetBackColor ();
439#elif qStroika_Foundation_Common_Platform_Windows
440 GDI_Obj_Selector pen (tablet, ::GetStockObject (NULL_PEN));
441 GDI_Obj_Selector brush (tablet, ::GetStockObject (NULL_BRUSH));
442#endif
443
444 /*
445 * Do this AFTER the save of colors above cuz no need in preserving that crap for
446 * offscreen bitmap we cons up here on the fly.
447 */
448 OffscreenTablet thisIsOurNewOST;
449 if (GetImageUsingOffscreenBitmaps () and not printing) {
450 thisIsOurNewOST.Setup (tablet_);
451 }
452
453 try {
454 //size_t rowNumberInWindow = 0;
455 size_t totalRowsInWindow = GetTotalRowsInWindow_ ();
456 RowReference topRowInWindow = GetTopRowReferenceInWindow ();
457 size_t rowsLeftInWindow = totalRowsInWindow;
458 for (PartitionMarker* pm = topRowInWindow.GetPartitionMarker (); rowsLeftInWindow != 0; pm = pm->GetNext ()) {
459 Assert (pm != nullptr);
460 size_t startSubRow = 0;
461 size_t maxSubRow = static_cast<size_t> (-1);
462 if (pm == topRowInWindow.GetPartitionMarker ()) {
463 startSubRow = topRowInWindow.GetSubRow ();
464 }
465 maxSubRow = rowsLeftInWindow - 1 + startSubRow;
466 size_t rowsDrawn = 0;
467 DrawPartitionElement (pm, startSubRow, maxSubRow, tablet, (GetImageUsingOffscreenBitmaps () and not printing) ? &thisIsOurNewOST : nullptr,
468 printing, subsetToDraw, &rowsLeftToDrawRect, &rowsDrawn);
469 Assert (rowsLeftInWindow >= rowsDrawn);
470 rowsLeftInWindow -= rowsDrawn;
471 }
472
473 /*
474 * Now erase to the end of the page.
475 */
476 Assert (tablet == tablet_); // Draw to screen directly past here...
477 {
478 Led_Rect eraser = GetWindowRect ();
479 eraser.top = rowsLeftToDrawRect.top; // only from here down...
480 eraser.bottom = subsetToDraw.bottom; // cuz image rect may not cover what it used to, and never any need to
481
482 if (eraser.top > eraser.bottom) {
483 eraser.bottom = eraser.top;
484 }
485
486 // SEE IF WE CAN TIGHTEN THIS TEST A BIT MORE, SO WHEN NO PIXELS WILL BE DRAWN, WE DON'T BOTHER
487 // IN OTHER WORDS, CHANGE A COUPLE <= to < - LGP 970315
488
489 // QUICKIE INTERSECT TEST
490 if (((eraser.top >= subsetToDraw.top and eraser.top <= subsetToDraw.bottom) or
491 (eraser.bottom >= subsetToDraw.top and eraser.bottom <= subsetToDraw.bottom)) and
492 (eraser.GetHeight () > 0 and eraser.GetWidth () > 0)) {
493 if (GetImageUsingOffscreenBitmaps () and not printing) {
494 tablet = thisIsOurNewOST.PrepareRect (eraser);
495 }
496 EraseBackground (tablet, eraser, printing);
497#if 0
498 // Do we want to hilight the section after the end of the last row displayed if the selection continues onto
499 // the next window? Somehow, I think it ends up looking schlocky. Leave off for now...
500 size_t hilightStart = GetSelectionStart ();
501 size_t hilightEnd = GetSelectionEnd ();
502 size_t end = GetMarkerPositionOfEndOfWindow ();
503 bool segmentHilightedAtEnd = GetSelectionShown () and (hilightStart < end) and (end <= hilightEnd);
504 if (not printing and segmentHilightedAtEnd) {
505 HilightARectangle (tablet, eraser);
506 }
507#endif
508 if (GetImageUsingOffscreenBitmaps () and not printing) {
509 /*
510 * Blast offscreen bitmap onto the screen.
511 */
512 thisIsOurNewOST.BlastBitmapToOrigTablet ();
513 tablet = tablet_; // don't use offscreen tablet past here... Draw to screen directly!!!
514 }
515 }
516 }
517 }
518 catch (...) {
519#if qStroika_Foundation_Common_Platform_MacOS
520 // Probably this code (and below case as well) is buggy. Setting back color in offscreen port (which is current now).
521 // But the code has been in place for quite some time (don't think broken by my offscreen bitmap move to LedGDI) with no
522 // noticable bugs/problems... Reconsider later...
523 // LGP 2001-05-11
524 Assert (*tablet == Led_GetCurrentGDIPort ());
525 GDI_RGBForeColor (oldForeColor);
526 GDI_RGBBackColor (oldBackColor);
527#endif
528 throw;
529 }
530#if qStroika_Foundation_Common_Platform_MacOS
531 Assert (*tablet == Led_GetCurrentGDIPort ());
532 GDI_RGBForeColor (oldForeColor);
533 GDI_RGBBackColor (oldBackColor);
534#endif
535}
536
537/*
538 @METHOD: MultiRowTextImager::DrawPartitionElement
539 @DESCRIPTION: <p></p>
540 */
541void MultiRowTextImager::DrawPartitionElement (PartitionMarker* pm, size_t startSubRow, size_t maxSubRow, Tablet* tablet, OffscreenTablet* offscreenTablet,
542 bool printing, const Led_Rect& subsetToDraw, Led_Rect* remainingDrawArea, size_t* rowsDrawn)
543{
544 RequireNotNull (pm);
545 RequireNotNull (remainingDrawArea);
546 RequireNotNull (rowsDrawn);
547
548 size_t start = pm->GetStart ();
549 size_t end = pm->GetEnd ();
550
551 Assert (end <= GetLength () + 1);
552 if (end == GetLength () + 1) {
553 --end; // don't include bogus char at end of buffer
554 }
555
556 Tablet* savedTablet = tablet;
557 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (pm);
558 size_t endSubRow = min (pmCacheInfo.GetRowCount () - 1, maxSubRow);
559 *rowsDrawn = 0;
560
561 size_t partLen = end - start;
562 Memory::StackBuffer<Led_tChar> partitionBuf{Memory::eUninitialized, partLen};
563 CopyOut (start, partLen, partitionBuf.data ());
564
565 for (size_t subRow = startSubRow; subRow <= endSubRow; ++subRow) {
566 Led_Rect currentRowRect = *remainingDrawArea;
567 currentRowRect.bottom = currentRowRect.top + pmCacheInfo.GetRowHeight (subRow);
568 DistanceType interlineSpace = (subRow == pmCacheInfo.GetLastRow ()) ? pmCacheInfo.GetInterLineSpace () : 0;
569 if ((currentRowRect.bottom + CoordinateType (interlineSpace) > subsetToDraw.top) and (currentRowRect.top < subsetToDraw.bottom)) {
570
571 /*
572 * patch start/end/len to take into account rows...
573 */
574 size_t rowStart = start + pmCacheInfo.PeekAtRowStart (subRow);
575 size_t rowEnd = end;
576 if (subRow < pmCacheInfo.GetLastRow ()) {
577 rowEnd = pm->GetStart () + pmCacheInfo.PeekAtRowStart (subRow + 1); // 'end' points just past last character in row
578 }
579 {
580 if (subRow == pmCacheInfo.GetLastRow ()) {
581 Assert (pm->GetEnd () > 0);
582 size_t markerEnd = pm->GetEnd ();
583 Assert (markerEnd <= GetLength () + 1);
584 if (markerEnd == GetLength () + 1) {
585 rowEnd = GetLength ();
586 }
587 else {
588 size_t prevToEnd = FindPreviousCharacter (markerEnd);
589 if (prevToEnd >= rowStart) {
590 Led_tChar lastChar;
591 CopyOut (prevToEnd, 1, &lastChar);
592 if (RemoveMappedDisplayCharacters (&lastChar, 1) == 0) {
593 rowEnd = (prevToEnd);
594 }
595 }
596 }
597 }
598 Assert (rowEnd == GetEndOfRowContainingPosition (rowStart));
599 }
600
601#if 1
602 TextLayoutBlock_Copy rowText = GetTextLayoutBlock (rowStart, rowEnd);
603#else
604 TextLayoutBlock_Basic rowText (partitionBuf + (rowStart - start), partitionBuf + (rowStart - start) + (rowEnd - rowStart));
605#endif
606
607 if (offscreenTablet != nullptr) {
608 tablet = offscreenTablet->PrepareRect (currentRowRect, interlineSpace);
609 }
610
611 {
612 /*
613 * Not sure why I didn't always do this? But changed from just setting RHS/LHS to subsetToDraw
614 * to this full intersection as part of SPR#1322 - LGP 2003-04-01.
615 */
616 Led_Rect invalidRowRect = Intersection (currentRowRect, subsetToDraw);
617 DrawRow (tablet, currentRowRect, invalidRowRect, rowText, rowStart, rowEnd, printing);
618 }
619
620 /*
621 * Now erase/draw any interline space.
622 */
623 if (interlineSpace != 0) {
624 size_t hilightStart = GetSelectionStart ();
625 size_t hilightEnd = GetSelectionEnd ();
626 bool segmentHilightedAtEnd = GetSelectionShown () and (hilightStart < rowEnd) and (rowEnd <= hilightEnd);
627 if (pm->GetNext () == nullptr and subRow == pmCacheInfo.GetLastRow ()) {
628 segmentHilightedAtEnd = false; // last row always contains no NL - so no invert off to the right...
629 }
630 DrawInterLineSpace (interlineSpace, tablet, currentRowRect.bottom, segmentHilightedAtEnd, printing);
631 }
632
633 if (offscreenTablet != nullptr) {
634 /*
635 * Blast offscreen bitmap onto the screen.
636 */
637 offscreenTablet->BlastBitmapToOrigTablet ();
638 tablet = savedTablet; // don't use offscreen tablet past here... Draw to screen directly!!!
639 }
640 }
641
642 remainingDrawArea->top = currentRowRect.bottom + interlineSpace;
643 ++(*rowsDrawn);
644 }
645}
646
647Led_Rect MultiRowTextImager::GetCharLocation (size_t afterPosition) const
648{
649 return (GetCharLocationRowRelative (afterPosition, RowReference (GetFirstPartitionMarker (), 0)));
650}
651
652Led_Rect MultiRowTextImager::GetCharWindowLocation (size_t afterPosition) const
653{
654 Led_Point windowOrigin = GetWindowRect ().GetOrigin () - Led_Point (0, GetHScrollPos ());
655 return (windowOrigin + GetCharLocationRowRelative (afterPosition, GetTopRowReferenceInWindow (), GetTotalRowsInWindow_ ()));
656}
657
658size_t MultiRowTextImager::GetCharAtLocation (const Led_Point& where) const
659{
660 return (GetCharAtLocationRowRelative (where, RowReference (GetFirstPartitionMarker (), 0)));
661}
662
663size_t MultiRowTextImager::GetCharAtWindowLocation (const Led_Point& where) const
664{
665 Led_Point windowOrigin = GetWindowRect ().GetOrigin () - Led_Point (0, GetHScrollPos ());
666 return (GetCharAtLocationRowRelative (where - windowOrigin, GetTopRowReferenceInWindow (), GetTotalRowsInWindow_ ()));
667}
668
669size_t MultiRowTextImager::GetStartOfRow (size_t rowNumber) const
670{
671 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
672 // Routines using RowReferences often perform MUCH better
673 return (GetStartOfRow (GetIthRowReference (rowNumber)));
674}
675
676size_t MultiRowTextImager::GetStartOfRowContainingPosition (size_t charPosition) const
677{
678 return (GetStartOfRow (GetRowReferenceContainingPosition (charPosition)));
679}
680
681size_t MultiRowTextImager::GetEndOfRow (size_t rowNumber) const
682{
683 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
684 // Routines using RowReferences often perform MUCH better
685 return (GetEndOfRow (GetIthRowReference (rowNumber)));
686}
687
688size_t MultiRowTextImager::GetEndOfRowContainingPosition (size_t charPosition) const
689{
690 return (GetEndOfRow (GetRowReferenceContainingPosition (charPosition)));
691}
692
693size_t MultiRowTextImager::GetRealEndOfRow (size_t rowNumber) const
694{
695 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
696 // Routines using RowReferences often perform MUCH better
697 return (GetRealEndOfRow (GetIthRowReference (rowNumber)));
698}
699
700size_t MultiRowTextImager::GetRealEndOfRowContainingPosition (size_t charPosition) const
701{
702 return (GetRealEndOfRow (GetRowReferenceContainingPosition (charPosition)));
703}
704
705size_t MultiRowTextImager::GetStartOfRow (RowReference row) const
706{
707 PartitionMarker* cur = row.GetPartitionMarker ();
708 size_t subRow = row.GetSubRow ();
709 AssertNotNull (cur);
710 return (cur->GetStart () + (subRow == 0 ? 0 : GetPartitionElementCacheInfo (cur).GetLineRelativeRowStartPosition (subRow)));
711}
712
713size_t MultiRowTextImager::GetEndOfRow (RowReference row) const
714{
715 PartitionMarker* cur = row.GetPartitionMarker ();
716 size_t subRow = row.GetSubRow ();
717 AssertNotNull (cur);
718 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
719 if (subRow == pmCacheInfo.GetLastRow ()) {
720 // Be careful about NL at end. If we end with an NL, then don't count that.
721 // And for the last PM - it contains a bogus empty character. Don't count
722 // that either.
723 Assert (cur->GetEnd () > 0);
724
725 size_t markerEnd = cur->GetEnd ();
726 Assert (markerEnd <= GetLength () + 1);
727 if (markerEnd == GetLength () + 1) {
728 return (GetLength ());
729 }
730
731 size_t prevToEnd = FindPreviousCharacter (markerEnd);
732 if (prevToEnd >= GetStartOfRow (row)) {
733 Led_tChar lastChar;
734 CopyOut (prevToEnd, 1, &lastChar);
735 if (RemoveMappedDisplayCharacters (&lastChar, 1) == 0) {
736 return (prevToEnd);
737 }
738 }
739 return (markerEnd);
740 }
741 else {
742 return (cur->GetStart () + pmCacheInfo.GetLineRelativeRowStartPosition (subRow + 1));
743 }
744}
745
746size_t MultiRowTextImager::GetRealEndOfRow (RowReference row) const
747{
748 PartitionMarker* cur = row.GetPartitionMarker ();
749 size_t subRow = row.GetSubRow ();
750 AssertNotNull (cur);
751 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
752 if (subRow == pmCacheInfo.GetLastRow ()) {
753 Assert (cur->GetEnd () > 0);
754 size_t markerEnd = cur->GetEnd ();
755 return (markerEnd);
756 }
757 else {
758 return (cur->GetStart () + pmCacheInfo.GetLineRelativeRowStartPosition (subRow + 1));
759 }
760}
761
762MultiRowTextImager::RowReference MultiRowTextImager::GetRowReferenceContainingPosition (size_t charPosition) const
763{
764 Require (charPosition <= GetEnd ());
765 PartitionMarker* pm = GetPartitionMarkerContainingPosition (charPosition);
766 AssertNotNull (pm);
767
768 size_t pmStart = pm->GetStart ();
769 if (charPosition == pmStart) { // slight speed tweek
770 return (RowReference (pm, 0));
771 }
772
773 // figure out what subrow the position occurs in, and return that...
774 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (pm);
775 return (RowReference (pm, pmCacheInfo.LineRelativePositionInWhichRow (charPosition - pmStart)));
776}
777
778size_t MultiRowTextImager::GetRowContainingPosition (size_t charPosition) const
779{
780 // Warning: GetRowReferenceContainingPosition () in preference, since
781 // it doesn't require call to pm->GetRowCount () - forcing a word-wrap...
782 return (GetRowNumber (GetRowReferenceContainingPosition (charPosition)));
783}
784
785size_t MultiRowTextImager::GetRowCount () const
786{
787 // NB: This is an expensive routine because it forces a word-wrap on all the text!
788 size_t rowCount = 0;
789 for (PartitionMarker* cur = GetFirstPartitionMarker (); cur != nullptr; cur = cur->GetNext ()) {
790 AssertNotNull (cur);
791 Assert (GetPartitionElementCacheInfo (cur).GetRowCount () >= 1);
792 rowCount += GetPartitionElementCacheInfo (cur).GetRowCount ();
793 }
794 return (rowCount);
795}
796
797Led_Rect MultiRowTextImager::GetCharLocationRowRelativeByPosition (size_t afterPosition, size_t positionOfTopRow, size_t maxRowsToCheck) const
798{
799 return GetCharLocationRowRelative (afterPosition, GetRowReferenceContainingPosition (positionOfTopRow), maxRowsToCheck);
800}
801
802DistanceType MultiRowTextImager::GetRowHeight (size_t rowNumber) const
803{
804 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
805 // Routines using RowReferences often perform MUCH better
806 return (GetRowHeight (GetIthRowReference (rowNumber)));
807}
808
809/*
810 @METHOD: MultiRowTextImager::GetRowRelativeBaselineOfRowContainingPosition
811 @DESCRIPTION: <p>Override/implement @'TextImager::GetRowRelativeBaselineOfRowContainingPosition'.</p>
812 */
813DistanceType MultiRowTextImager::GetRowRelativeBaselineOfRowContainingPosition (size_t charPosition) const
814{
815 RowReference thisRow = GetRowReferenceContainingPosition (charPosition);
816 size_t startOfRow = GetStartOfRow (thisRow);
817 size_t endOfRow = GetEndOfRow (thisRow);
818 return MeasureSegmentBaseLine (startOfRow, endOfRow);
819}
820
821void MultiRowTextImager::GetStableTypingRegionContaingMarkerRange (size_t fromMarkerPos, size_t toMarkerPos, size_t* expandedFromMarkerPos,
822 size_t* expandedToMarkerPos) const
823{
824 AssertNotNull (expandedFromMarkerPos);
825 AssertNotNull (expandedToMarkerPos);
826 Assert (fromMarkerPos <= toMarkerPos);
827 Assert (toMarkerPos <= GetEnd ());
828
829 size_t curTopRowRelativeRowNumber = 0;
830
831 RowReference curRow = GetTopRowReferenceInWindow ();
832 do {
833 PartitionMarker* cur = curRow.GetPartitionMarker ();
834 AssertNotNull (cur);
835 size_t start = cur->GetStart ();
836 size_t end = cur->GetEnd ();
837
838 // For the last partition marker - we are including a BOGUS character past the end of the buffer.
839 // We don't want to return that. But otherwise - it is OK to return the NL at the end of the
840 // other lines (though perhaps that is unnecceary).... LGP 950210
841 if (cur->GetNext () == nullptr) {
842 --end;
843 }
844
845 // If we are strictly before the first row, we won't appear later...
846 if (curTopRowRelativeRowNumber == 0 and (fromMarkerPos < start)) {
847 break;
848 }
849
850 ++curTopRowRelativeRowNumber;
851
852 if (Contains (*cur, fromMarkerPos) and Contains (*cur, toMarkerPos)) {
853 (*expandedFromMarkerPos) = start;
854 (*expandedToMarkerPos) = end;
855 Assert ((*expandedFromMarkerPos) <= (*expandedToMarkerPos));
856 Assert ((*expandedToMarkerPos) <= GetEnd ());
857 return;
858 }
859
860 if (curTopRowRelativeRowNumber >= GetTotalRowsInWindow_ ()) {
861 break; // though this might allow is to go too far - no matter. We'd return
862 // the same result anyhow. And the extra overhead in counter rows
863 // as opposed to lines doesn't offset the overhead in counting a few
864 // extra lines - besides - this is simpler...
865 }
866 } while (GetNextRowReference (&curRow));
867
868 (*expandedFromMarkerPos) = 0;
869 (*expandedToMarkerPos) = GetEnd ();
870}
871
872DistanceType MultiRowTextImager::GetHeightOfRows (size_t startingRow, size_t rowCount) const
873{
874 return (GetHeightOfRows (GetIthRowReference (startingRow), rowCount));
875}
876
877DistanceType MultiRowTextImager::GetHeightOfRows (RowReference startingRow, size_t rowCount) const
878{
879 DistanceType height = 0;
880 for (RowReference curRow = startingRow; rowCount > 0; rowCount--) {
881 PartitionMarker* curPM = curRow.GetPartitionMarker ();
882 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
883 height += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
884 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
885 height += pmCacheInfo.GetInterLineSpace ();
886 }
887 (void)GetNextRowReference (&curRow);
888 }
889 return (height);
890}
891
892void MultiRowTextImager::DidUpdateText (const UpdateInfo& updateInfo) noexcept
893{
894//maynot need this addtion either - since done in PMInfoCache guy...
895#if 1
896 /*
897 * This invalidation is way more aggressive than it needs to be. Really all we need
898 * todo is check if this was one of the rows in the window, and only invalidate then.
899 * But that check would be at least somewhat costly. So this may be best for now.
900 * Maybe later try walking the markers from the start of window by n (number of rows currently
901 * set in total rows in window cache) and see if we are hit. Maybe that wouldn't be
902 * too bad cuz we could do nothing in the common case where the row cache was already
903 * invalid.
904 * --LGP 960912
905 */
906 InvalidateTotalRowsInWindow ();
907#endif
908
909 InvalidateTotalRowsInWindow ();
910 inherited::DidUpdateText (updateInfo);
911 AssertNotNull (fTopLinePartitionMarkerInWindow);
912 // ReValidateSubRowInTopLineInWindow ();
913 AssureWholeWindowUsedIfNeeded ();
914 InvalidateScrollBarParameters (); // even if we don't change the top row, we might change enuf about the text to change sbar
915}
916
917void MultiRowTextImager::SetWindowRect (const Led_Rect& windowRect)
918{
919 /*
920 * NB: We only check that the 'heightChanged' because thats all that can affect the number of rows
921 * shown on the screen. Someone pointed out that the WIDTH of the window rect changing can ALSO
922 * change the number of rows, and invalidate the caches. They were thinking of the subclass
923 * WordWrappedTextImager. And then - in the special case where you have implemented the policy
924 * 'wrap-to-window' (which you would NOT - for example - if you are using a ruler to specify margins).
925 * The point is - it is THERE - where you implement that wrapping policy - e.g. WordWrappedTextImager::GetLayoutMargins() -
926 * that you would have to hook SetWindowRect () and invalidate the cache.
927 */
928 bool heightChanged = GetWindowRect ().GetHeight () != windowRect.GetHeight ();
929 inherited::SetWindowRect (windowRect);
930 if (heightChanged and PeekAtTextStore () != nullptr) {
931 InvalidateTotalRowsInWindow ();
932 AssureWholeWindowUsedIfNeeded ();
933 InvalidateScrollBarParameters ();
934 }
935}
936
937/*
938 @METHOD: MultiRowTextImager::InvalidateAllCaches
939 @DESCRIPTION: <p>Hook @'TextImager::InvalidateAllCaches' method to invalidate additional information.
940 Invalidate cached row-height/etc information for the entire imager. Invalidate rows
941 in a window cached values, etc.</p>
942 */
943void MultiRowTextImager::InvalidateAllCaches ()
944{
945 inherited::InvalidateAllCaches ();
946 if (GetPartition ().get () != nullptr) { // careful that we aren't changing text metrics while we have no textstore attached!!!
947 if (fPMCacheMgr.get () != nullptr) {
948 fPMCacheMgr->ClearCache ();
949 }
950 InvalidateTotalRowsInWindow ();
951 // ReValidateSubRowInTopLineInWindow ();
952 AssureWholeWindowUsedIfNeeded ();
953 InvalidateScrollBarParameters ();
954 }
955}
956
957MultiRowTextImager::RowReference MultiRowTextImager::AdjustPotentialTopRowReferenceSoWholeWindowUsed (const RowReference& potentialTopRow)
958{
959 /*
960 * This check is always safe, but probably not a worthwhile optimization, except that it avoids
961 * some problems about initializing the top-row-reference before we've got a valid
962 * tablet setup to use.
963 */
964 if (potentialTopRow.GetSubRow () == 0 and potentialTopRow.GetPartitionMarker ()->GetPrevious () == nullptr) {
965 return potentialTopRow;
966 }
967
968 CoordinateType windowHeight = GetWindowRect ().GetHeight ();
969 CoordinateType heightUsed = 0;
970
971 for (RowReference curRow = potentialTopRow;;) {
972 PartitionMarker* curPM = curRow.GetPartitionMarker ();
973 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
974 heightUsed += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
975 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
976 heightUsed += pmCacheInfo.GetInterLineSpace ();
977 }
978 if (heightUsed >= windowHeight) {
979 return (potentialTopRow); // Then we used all the space we could have - and that is a good row!
980 }
981 if (not GetNextRowReference (&curRow)) {
982 break;
983 }
984 }
985
986 // If we got here - we ran out of rows before we ran out of height.
987 // That means we should scroll back a smidge...
988 for (RowReference curRow = potentialTopRow;;) {
989 if (not GetPreviousRowReference (&curRow)) {
990 return (curRow); // if we've gone back as far as we can - were done!
991 // Even if we didn't use all the height
992 }
993
994 PartitionMarker* curPM = curRow.GetPartitionMarker ();
995 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
996 heightUsed += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
997 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
998 heightUsed += pmCacheInfo.GetInterLineSpace ();
999 }
1000 if (heightUsed > windowHeight) {
1001 // We went back one too far - forward one and return that.
1002 [[maybe_unused]] bool result = GetNextRowReference (&curRow);
1003 Assert (result);
1004 return curRow;
1005 }
1006 else if (heightUsed == windowHeight) {
1007 return (curRow); // Then we used all the space we could have - and that is a good row!
1008 }
1009 }
1010 Assert (false);
1011 return (potentialTopRow); // NotReached / silence compiler warnings
1012}
1013
1014bool MultiRowTextImager::PositionWouldFitInWindowWithThisTopRow (size_t markerPos, const RowReference& newTopRow)
1015{
1016 if (markerPos < GetStartOfRow (newTopRow)) {
1017 return false;
1018 }
1019
1020 size_t rowCount = ComputeRowsThatWouldFitInWindowWithTopRow (newTopRow);
1021 RowReference lastRow = GetIthRowReferenceFromHere (newTopRow, rowCount - 1);
1022
1023 return (markerPos < GetRealEndOfRow (lastRow));
1024}
1025
1026void MultiRowTextImager::ReValidateSubRowInTopLineInWindow ()
1027{
1028 AssertNotNull (fTopLinePartitionMarkerInWindow);
1029
1030 // don't bother calling GetRowCount () if fSubRowInTopLineInWindow is already ZERO - avoid possible word-wrap
1031 if (fSubRowInTopLineInWindow != 0) {
1032#if 1
1033 size_t lastRow = GetPartitionElementCacheInfo (fTopLinePartitionMarkerInWindow).GetLastRow ();
1034 if (fSubRowInTopLineInWindow > lastRow) {
1035 fSubRowInTopLineInWindow = lastRow;
1036 }
1037#else
1038 bool pmNotWrapped = (fTopLinePartitionMarkerInWindow->fPixelHeightCache == DistanceType (-1));
1039 size_t lastRow = GetPartitionElementCacheInfo (fTopLinePartitionMarkerInWindow).GetLastRow ();
1040 if (fSubRowInTopLineInWindow > lastRow) {
1041 fSubRowInTopLineInWindow = lastRow;
1042 }
1043 if (pmNotWrapped) {
1044 /*
1045 * We invalidated it for this method. But we may have done so prematurely - during
1046 * the context of a marker DidUpdate () method. See SPR#0821
1047 *
1048 * Reset it back to invalid so it will be properly layed out.
1049 */
1050 fTopLinePartitionMarkerInWindow->InvalidateCache ();
1051 }
1052#endif
1053 }
1054}
1055
1056size_t MultiRowTextImager::ComputeRowsThatWouldFitInWindowWithTopRow (const RowReference& newTopRow) const
1057{
1058 /*
1059 * For now, we don't show partial rows at the bottom. We
1060 * might want to reconsider this.
1061 */
1062 CoordinateType windowHeight = GetWindowRect ().GetHeight ();
1063
1064 /*
1065 * Wind out way to the bottom of the window from our current position,
1066 * and count rows.
1067 */
1068 size_t rowCount = 0;
1069 CoordinateType heightUsed = 0;
1070 for (RowReference curRow = newTopRow;;) {
1071 ++rowCount;
1072 PartitionMarker* curPM = curRow.GetPartitionMarker ();
1073 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
1074 heightUsed += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
1075 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
1076 heightUsed += pmCacheInfo.GetInterLineSpace ();
1077 }
1078 if (heightUsed > windowHeight) {
1079 // we went one too far
1080 --rowCount;
1081 break;
1082 }
1083 else if (heightUsed == windowHeight) {
1084 break; // thats all that will fit
1085 }
1086
1087 if (not GetNextRowReference (&curRow)) {
1088 break;
1089 }
1090 }
1091 if (rowCount == 0) { // always for the existence of at least one row...
1092 rowCount = 1;
1093 }
1094
1095 return (rowCount);
1096}
1097
1098/*
1099 @METHOD: MultiRowTextImager::GetCharLocationRowRelative
1100 @DESCRIPTION: <p>Return the row-relative (to get window relative - add top-left of window-rect) bounding rectange of the
1101 given character cell. Compute the vertical position relative to the given argument 'topRow' and check and most
1102 'maxRowsToCheck' before just returning a large 'off-the-end' value result</p>
1103 */
1104Led_Rect MultiRowTextImager::GetCharLocationRowRelative (size_t afterPosition, RowReference topRow, size_t maxRowsToCheck) const
1105{
1106 // MUST FIGURE OUT WHAT TODO HERE BETTER - 10000 not good enough answer always...
1107 const Led_Rect kMagicBeforeRect = Led_Rect (-10000, 0, 0, 0);
1108 const Led_Rect kMagicAfterRect = Led_Rect (10000, 0, 0, 0);
1109
1110 Require (afterPosition <= GetEnd ());
1111
1112 if (afterPosition < GetStartOfRow (topRow)) {
1113 return (kMagicBeforeRect);
1114 }
1115
1116 RowReference curRow = topRow;
1117 size_t curTopRowRelativeRowNumber = 0;
1118 CoordinateType topVPos = 0;
1119 do {
1120 PartitionMarker* cur = curRow.GetPartitionMarker ();
1121 size_t subRow = curRow.GetSubRow ();
1122 AssertNotNull (cur);
1123 size_t start = cur->GetStart ();
1124 size_t end = cur->GetEnd (); // end points JUST PAST LAST VISIBLE/OPERATED ON CHAR
1125
1126 Assert (end <= GetEnd () + 1);
1127
1128 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
1129
1130 /*
1131 * patch start/end/len to take into account rows...
1132 */
1133 start += pmCacheInfo.PeekAtRowStart (subRow);
1134 if (subRow < pmCacheInfo.GetLastRow ()) {
1135 end = cur->GetStart () + pmCacheInfo.PeekAtRowStart (subRow + 1);
1136 Assert (start <= end);
1137 }
1138
1139 ++curTopRowRelativeRowNumber;
1140
1141 /*
1142 * When we've found the right row, then add in the right horizontal offset.
1143 */
1144 if (afterPosition >= start and afterPosition < end) {
1145 Assert (start <= afterPosition);
1146 DistanceType hStart = 0;
1147 DistanceType hEnd = 0;
1148 GetRowRelativeCharLoc (afterPosition, &hStart, &hEnd);
1149 Assert (hStart <= hEnd);
1150 return (Led_Rect (topVPos, hStart, pmCacheInfo.GetRowHeight (subRow), hEnd - hStart));
1151 }
1152
1153 topVPos += pmCacheInfo.GetRowHeight (subRow);
1154
1155 if (pmCacheInfo.GetLastRow () == subRow) {
1156 topVPos += pmCacheInfo.GetInterLineSpace ();
1157 }
1158
1159 if (curTopRowRelativeRowNumber >= maxRowsToCheck) {
1160 break; // return bogus place at the end...
1161 }
1162 } while (GetNextRowReference (&curRow));
1163
1164 return (kMagicAfterRect);
1165}
1166
1167size_t MultiRowTextImager::GetCharAtLocationRowRelative (const Led_Point& where, RowReference topRow, size_t maxRowsToCheck) const
1168{
1169 /*
1170 * Not 100% sure how to deal with points outside our range. For now - we just
1171 * return topMost/bottomMost marker positions. That seems to work decently - at least
1172 * for now... But I worry if it is the right thing when we do
1173 * autoscrolling...
1174 */
1175 if (where.v < 0) {
1176 return 0;
1177 }
1178
1179 RowReference curRow = topRow;
1180 size_t curTopRowRelativeRowNumber = 0;
1181 CoordinateType topVPos = 0;
1182 do {
1183 PartitionMarker* cur = curRow.GetPartitionMarker ();
1184 size_t subRow = curRow.GetSubRow ();
1185 AssertNotNull (cur);
1186 size_t start = cur->GetStart ();
1187
1188 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
1189
1190 /*
1191 * patch start/end/len to take into account rows...
1192 */
1193 start += pmCacheInfo.PeekAtRowStart (subRow);
1194
1195 /*
1196 * Count the interline space as part of the last row of the line for the purpose of hit-testing.
1197 */
1198 DistanceType interLineSpaceIfAny = (pmCacheInfo.GetLastRow () == subRow) ? pmCacheInfo.GetInterLineSpace () : 0;
1199
1200 curTopRowRelativeRowNumber++;
1201 if (where.v >= topVPos and where.v < topVPos + CoordinateType (pmCacheInfo.GetRowHeight (subRow) + interLineSpaceIfAny)) {
1202 return GetRowRelativeCharAtLoc (where.h, start);
1203 }
1204
1205 if (curTopRowRelativeRowNumber >= maxRowsToCheck) {
1206 break; // we've checked enuf...
1207 }
1208
1209 topVPos += pmCacheInfo.GetRowHeight (subRow) + interLineSpaceIfAny;
1210 } while (GetNextRowReference (&curRow));
1211
1212 return GetEnd ();
1213}
1214
1215DistanceType MultiRowTextImager::CalculateInterLineSpace (const PartitionMarker* /*pm*/) const
1216{
1217 return (0); // no interline space by default
1218}
1219
1220/*
1221 @METHOD: MultiRowTextImager::ContainsMappedDisplayCharacters
1222 @DESCRIPTION: <p>Override @'TextImager::ContainsMappedDisplayCharacters' to hide '\n' characters.
1223 See @'qDefaultLedSoftLineBreakChar'.</p>
1224 */
1225bool MultiRowTextImager::ContainsMappedDisplayCharacters (const Led_tChar* text, size_t nTChars) const
1226{
1227 return ContainsMappedDisplayCharacters_HelperForChar (text, nTChars, '\n') or inherited::ContainsMappedDisplayCharacters (text, nTChars);
1228}
1229
1230/*
1231 @METHOD: MultiRowTextImager::RemoveMappedDisplayCharacters
1232 @DESCRIPTION: <p>Override @'TextImager::RemoveMappedDisplayCharacters' to hide '\n' characters.</p>
1233 */
1234size_t MultiRowTextImager::RemoveMappedDisplayCharacters (Led_tChar* copyText, size_t nTChars) const
1235{
1236 size_t newLen = inherited::RemoveMappedDisplayCharacters (copyText, nTChars);
1237 Assert (newLen <= nTChars);
1238 size_t newerLen = RemoveMappedDisplayCharacters_HelperForChar (copyText, newLen, '\n');
1239 Assert (newerLen <= newLen);
1240 Assert (newerLen <= nTChars);
1241 return newerLen;
1242}
1243
1244/*
1245 ********************************************************************************
1246 ******************* MultiRowTextImager::PartitionElementCacheInfo **************
1247 ********************************************************************************
1248 */
1249void MultiRowTextImager::PartitionElementCacheInfo::Clear ()
1250{
1251 fRep = make_shared<Rep> ();
1252}
1253
1254void MultiRowTextImager::PartitionElementCacheInfo::IncrementRowCountAndFixCacheBuffers (size_t newStart, DistanceType newRowsHeight)
1255{
1256 ++fRep->fRowCountCache;
1257
1258 // If rowStart array not big enough then allocate it from the heap...
1259 if (fRep->fRowCountCache > kPackRowStartCount + 1) {
1260 RowStart_* newRowStartArray = new RowStart_[fRep->fRowCountCache - 1];
1261 AssertNotNull (newRowStartArray);
1262 if (fRep->fRowCountCache == kPackRowStartCount + 1 + 1) {
1263 ::memcpy (newRowStartArray, &fRep->fRowStartArray, sizeof (fRep->fRowStartArray));
1264 }
1265 else {
1266 Assert (fRep->fRowCountCache > 2);
1267 if (fRep->fRowCountCache > kPackRowStartCount + 1 + 1) {
1268 AssertNotNull (fRep->fRowStartArray);
1269 (void)memcpy (newRowStartArray, fRep->fRowStartArray, sizeof (newRowStartArray[1]) * (fRep->fRowCountCache - 2));
1270 delete[] fRep->fRowStartArray;
1271 }
1272 }
1273 fRep->fRowStartArray = newRowStartArray;
1274 }
1275
1276 // If rowHeight array not big enough then allocate it from the heap...
1277 if (fRep->fRowCountCache > kPackRowHeightCount) {
1278 RowHeight_* newRowHeightArray = new RowHeight_[fRep->fRowCountCache];
1279 AssertNotNull (newRowHeightArray);
1280 if (fRep->fRowCountCache == kPackRowHeightCount + 1) {
1281 ::memcpy (newRowHeightArray, &fRep->fRowHeightArray, sizeof (fRep->fRowHeightArray));
1282 }
1283 else {
1284 Assert (fRep->fRowCountCache > 1);
1285 if (fRep->fRowCountCache > kPackRowHeightCount + 1) {
1286 AssertNotNull (fRep->fRowHeightArray);
1287 memcpy (newRowHeightArray, fRep->fRowHeightArray, sizeof (newRowHeightArray[1]) * (fRep->fRowCountCache - 1));
1288 delete[] fRep->fRowHeightArray;
1289 }
1290 }
1291 fRep->fRowHeightArray = newRowHeightArray;
1292 }
1293
1294 SetRowStart (fRep->fRowCountCache - 1, newStart);
1295 SetRowHeight (fRep->fRowCountCache - 1, newRowsHeight);
1296 fRep->fPixelHeightCache += newRowsHeight;
1297}
1298
1299/*
1300 ********************************************************************************
1301 *********************** MultiRowTextImager::PMInfoCacheMgr *********************
1302 ********************************************************************************
1303 */
1304MultiRowTextImager::PMInfoCacheMgr::PMInfoCacheMgr (MultiRowTextImager& imager)
1305 : fPMCache ()
1306 , fCurFillCachePM (nullptr)
1307 , fCurFillCacheInfo ()
1308 , fImager (imager)
1309 , fMyMarker ()
1310{
1311 // REDO this class to make IT a MarkerOwner - and use THAT markerowner for MyMarker. Then - store an additional MyMarker for EACH marker
1312 // added to cache (just around the PM its used to wrap). Then remove ONLY that PM from the cache in its DIDUpdate.
1313 PartitionPtr part = imager.GetPartition ();
1314 Assert (part.get () != nullptr);
1315 part->AddPartitionWatcher (this);
1316 fMyMarker = unique_ptr<MyMarker> (new MyMarker (*this));
1317 TextStore& ts = part->GetTextStore ();
1318 ts.AddMarker (fMyMarker.get (), 0, ts.GetLength () + 1, part.get ());
1319}
1320
1321MultiRowTextImager::PMInfoCacheMgr::~PMInfoCacheMgr ()
1322{
1323 PartitionPtr part = fImager.GetPartition ();
1324 part->RemovePartitionWatcher (this);
1325 TextStore& ts = part->GetTextStore ();
1326 ts.RemoveMarker (fMyMarker.get ());
1327}
1328
1329MultiRowTextImager::PartitionElementCacheInfo MultiRowTextImager::PMInfoCacheMgr::GetPartitionElementCacheInfo (Partition::PartitionMarker* pm) const
1330{
1331 if (pm == fCurFillCachePM) {
1332 return fCurFillCacheInfo; // allow recursive call to get PMCacheInfo (so far) DURING context of call to FillCache()
1333 }
1334 using MAP_CACHE = map<Partition::PartitionMarker*, PartitionElementCacheInfo>;
1335 MAP_CACHE::iterator i = fPMCache.find (pm);
1336 if (i == fPMCache.end ()) {
1337 try {
1338 Assert (fCurFillCachePM == nullptr); // can only do one fillcache at a time...
1339 fCurFillCachePM = pm;
1340 fImager.FillCache (pm, fCurFillCacheInfo);
1342 for (size_t t = 0; t < fCurFillCacheInfo.GetRowCount (); ++t) {
1343 Assert (fCurFillCacheInfo.GetLineRelativeRowStartPosition (t) <= pm->GetLength ());
1344 Assert (fCurFillCacheInfo.PeekAtRowStart (t) <= pm->GetLength ());
1345 }
1346 }
1347 i = fPMCache.insert (MAP_CACHE::value_type (pm, fCurFillCacheInfo)).first;
1349 Assert (fCurFillCacheInfo.GetRowCount () == i->second.GetRowCount ());
1350 for (size_t t = 0; t < fCurFillCacheInfo.GetRowCount (); ++t) {
1351 Assert (fCurFillCacheInfo.PeekAtRowHeight (t) == i->second.PeekAtRowHeight (t));
1352 Assert (fCurFillCacheInfo.PeekAtRowStart (t) == i->second.PeekAtRowStart (t));
1353 }
1354 }
1355 Assert (fCurFillCachePM == pm);
1356 fCurFillCachePM = nullptr;
1357 }
1358 catch (...) {
1359 Assert (fCurFillCachePM == pm);
1360 fCurFillCachePM = nullptr;
1361 throw;
1362 }
1363 }
1364 return i->second;
1365}
1366
1367void MultiRowTextImager::PMInfoCacheMgr::ClearCache ()
1368{
1369 fPMCache.clear ();
1370}
1371
1372void MultiRowTextImager::PMInfoCacheMgr::AboutToSplit (PartitionMarker* pm, size_t /*at*/, void** infoRecord) const noexcept
1373{
1374 *infoRecord = pm;
1375}
1376
1377void MultiRowTextImager::PMInfoCacheMgr::DidSplit (void* infoRecord) const noexcept
1378{
1379 PartitionMarker* pm = reinterpret_cast<PartitionMarker*> (infoRecord);
1380 using MAP_CACHE = map<Partition::PartitionMarker*, PartitionElementCacheInfo>;
1381 MAP_CACHE::iterator i = fPMCache.find (pm);
1382 if (i != fPMCache.end ()) {
1383 fPMCache.erase (i);
1384 }
1385 fImager.InvalidateTotalRowsInWindow ();
1386#if 0
1387 if (pm == fImager.fTopLinePartitionMarkerInWindow) {
1388 // if splitting top row - we must revalidate top subrow
1389 fImager.ReValidateSubRowInTopLineInWindow ();
1390 }
1391#endif
1392}
1393
1394void MultiRowTextImager::PMInfoCacheMgr::AboutToCoalece (PartitionMarker* pm, void** infoRecord) const noexcept
1395{
1396 *infoRecord = pm;
1397
1398 PartitionMarker* newTopLine = nullptr;
1399 bool useFirstRow = false; // otherwise use last row...
1400 if (pm == fImager.fTopLinePartitionMarkerInWindow) {
1401 if (pm->GetNext () == nullptr) {
1402 newTopLine = fImager.fTopLinePartitionMarkerInWindow->GetPrevious ();
1403 useFirstRow = false;
1404 }
1405 else {
1406 newTopLine = fImager.fTopLinePartitionMarkerInWindow->GetNext ();
1407 useFirstRow = true;
1408 }
1409 AssertNotNull (newTopLine);
1410 }
1411
1412 if (newTopLine != nullptr) {
1413 //TMPHACK - REALLY should do old comment out code - see old code in MultiRowTextImager::MultiRowPartition::Coalese - trouble is then
1414 // we need to pass MORE info to DID_COALESE trhought eh INFORECORD so we can pass BOTH pm AND the newTopLine (really should do this call there)
1415 // This is a bit of a hack - but AT LEAST should avoid any crashes/flakies we now see - LGP 2002-10-17
1416 // fImager.SetTopRowInWindow_ (RowReference (newTopLine, useFirstRow? 0: fImager.GetPartitionElementCacheInfo (newTopLine).GetLastRow ()));
1417 fImager.SetTopRowInWindow_ (RowReference (newTopLine, 0));
1418 }
1419
1420#if 0
1421 MultiRowPartitionMarker* newTopLine = nullptr;
1422 bool useFirstRow = false; // otherwise use last row...
1423 if (pm == fTextImager.fTopLinePartitionMarkerInWindow) {
1424 if (pm->GetNext () == nullptr) {
1425 newTopLine = fTextImager.fTopLinePartitionMarkerInWindow->GetPreviousMRPM ();
1426 useFirstRow = false;
1427 }
1428 else {
1429 newTopLine = fTextImager.fTopLinePartitionMarkerInWindow->GetNextMRPM ();
1430 useFirstRow = true;
1431 }
1432 AssertNotNull (newTopLine);
1433 }
1434
1435 MultiRowPartitionMarker* successor = (MultiRowPartitionMarker*)pm->GetNext (); // all our pms are subtypes of this type
1436 inherited::Coalece (pm); // deletes pm....
1437 if (successor != nullptr) {
1438 successor->InvalidateCache (); // This is where our xtra text goes - so be sure we invalidate its extent...
1439 }
1440
1441 // If we must change the top-row - then do so NOW - cuz NOW its safe to call GetRowCount ()
1442 if (newTopLine != nullptr) {
1443 fTextImager.SetTopRowInWindow_ (RowReference (newTopLine, useFirstRow ? 0 : fTextImager.GetPartitionElementCacheInfo (newTopLine).GetLastRow ()));
1444 }
1445 AssertNotNull (fTextImager.fTopLinePartitionMarkerInWindow); // can't delete last one...
1446 AssertMember (fTextImager.fTopLinePartitionMarkerInWindow, MultiRowPartitionMarker);
1447 fTextImager.InvalidateTotalRowsInWindow ();
1448#endif
1449}
1450
1451void MultiRowTextImager::PMInfoCacheMgr::DidCoalece (void* infoRecord) const noexcept
1452{
1453 PartitionMarker* pm = reinterpret_cast<PartitionMarker*> (infoRecord);
1454 using MAP_CACHE = map<Partition::PartitionMarker*, PartitionElementCacheInfo>;
1455 MAP_CACHE::iterator i = fPMCache.find (pm);
1456 if (i != fPMCache.end ()) {
1457 fPMCache.erase (i);
1458 }
1459 fImager.InvalidateTotalRowsInWindow ();
1460}
1461
1462void MultiRowTextImager::PMInfoCacheMgr::MyMarkerDidUpdateCallback ()
1463{
1464 fPMCache.clear ();
1465
1466 /*
1467 * This invalidation is way more aggressive than it needs to be. Really all we need
1468 * todo is check if this was one of the rows in the window, and only invalidate then.
1469 * But that check would be at least somewhat costly. So this may be best for now.
1470 * Maybe later try walking the markers from the start of window by n (number of rows currently
1471 * set in total rows in window cache) and see if we are hit. Maybe that wouldn't be
1472 * too bad cuz we could do nothing in the common case where the row cache was already
1473 * invalid.
1474 * --LGP 960912
1475 */
1476 fImager.InvalidateTotalRowsInWindow ();
1477}
1478
1479/*
1480 ********************************************************************************
1481 *************** MultiRowTextImager::PMInfoCacheMgr::MyMarker *******************
1482 ********************************************************************************
1483 */
1484MultiRowTextImager::PMInfoCacheMgr::MyMarker::MyMarker (PMInfoCacheMgr& pmInfoCacheMgr)
1485 : fPMInfoCacheMgr (pmInfoCacheMgr)
1486{
1487}
1488
1489void MultiRowTextImager::PMInfoCacheMgr::MyMarker::DidUpdateText (const UpdateInfo& updateInfo) noexcept
1490{
1491 inherited::DidUpdateText (updateInfo);
1492 fPMInfoCacheMgr.MyMarkerDidUpdateCallback ();
1493}
1494#endif
#define AssertNotNull(p)
Definition Assertions.h:333
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertMember(p, c)
Definition Assertions.h:312
set< T > Intersection(const set< T > &s1, const set< T > &s2)
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...