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