Stroika Library 3.0d22
 
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
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_Foundation_Common_Platform_MacOS
440 tablet->SetPort ();
441 RGBColor oldForeColor = GDI_GetForeColor ();
442 RGBColor oldBackColor = GDI_GetBackColor ();
443#elif qStroika_Foundation_Common_Platform_Windows
444 GDI_Obj_Selector pen (tablet, ::GetStockObject (NULL_PEN));
445 GDI_Obj_Selector brush (tablet, ::GetStockObject (NULL_BRUSH));
446#endif
447
448 /*
449 * Do this AFTER the save of colors above cuz no need in preserving that crap for
450 * offscreen bitmap we cons up here on the fly.
451 */
452 OffscreenTablet thisIsOurNewOST;
453 if (GetImageUsingOffscreenBitmaps () and not printing) {
454 thisIsOurNewOST.Setup (tablet_);
455 }
456
457 try {
458 //size_t rowNumberInWindow = 0;
459 size_t totalRowsInWindow = GetTotalRowsInWindow_ ();
460 RowReference topRowInWindow = GetTopRowReferenceInWindow ();
461 size_t rowsLeftInWindow = totalRowsInWindow;
462 for (PartitionMarker* pm = topRowInWindow.GetPartitionMarker (); rowsLeftInWindow != 0; pm = pm->GetNext ()) {
463 Assert (pm != nullptr);
464 size_t startSubRow = 0;
465 size_t maxSubRow = static_cast<size_t> (-1);
466 if (pm == topRowInWindow.GetPartitionMarker ()) {
467 startSubRow = topRowInWindow.GetSubRow ();
468 }
469 maxSubRow = rowsLeftInWindow - 1 + startSubRow;
470 size_t rowsDrawn = 0;
471 DrawPartitionElement (pm, startSubRow, maxSubRow, tablet, (GetImageUsingOffscreenBitmaps () and not printing) ? &thisIsOurNewOST : nullptr,
472 printing, subsetToDraw, &rowsLeftToDrawRect, &rowsDrawn);
473 Assert (rowsLeftInWindow >= rowsDrawn);
474 rowsLeftInWindow -= rowsDrawn;
475 }
476
477 /*
478 * Now erase to the end of the page.
479 */
480 Assert (tablet == tablet_); // Draw to screen directly past here...
481 {
482 Led_Rect eraser = GetWindowRect ();
483 eraser.top = rowsLeftToDrawRect.top; // only from here down...
484 eraser.bottom = subsetToDraw.bottom; // cuz image rect may not cover what it used to, and never any need to
485
486 if (eraser.top > eraser.bottom) {
487 eraser.bottom = eraser.top;
488 }
489
490 // SEE IF WE CAN TIGHTEN THIS TEST A BIT MORE, SO WHEN NO PIXELS WILL BE DRAWN, WE DON'T BOTHER
491 // IN OTHER WORDS, CHANGE A COUPLE <= to < - LGP 970315
492
493 // QUICKIE INTERSECT TEST
494 if (((eraser.top >= subsetToDraw.top and eraser.top <= subsetToDraw.bottom) or
495 (eraser.bottom >= subsetToDraw.top and eraser.bottom <= subsetToDraw.bottom)) and
496 (eraser.GetHeight () > 0 and eraser.GetWidth () > 0)) {
497 if (GetImageUsingOffscreenBitmaps () and not printing) {
498 tablet = thisIsOurNewOST.PrepareRect (eraser);
499 }
500 EraseBackground (tablet, eraser, printing);
501#if 0
502 // Do we want to hilight the section after the end of the last row displayed if the selection continues onto
503 // the next window? Somehow, I think it ends up looking schlocky. Leave off for now...
504 size_t hilightStart = GetSelectionStart ();
505 size_t hilightEnd = GetSelectionEnd ();
506 size_t end = GetMarkerPositionOfEndOfWindow ();
507 bool segmentHilightedAtEnd = GetSelectionShown () and (hilightStart < end) and (end <= hilightEnd);
508 if (not printing and segmentHilightedAtEnd) {
509 HilightARectangle (tablet, eraser);
510 }
511#endif
512 if (GetImageUsingOffscreenBitmaps () and not printing) {
513 /*
514 * Blast offscreen bitmap onto the screen.
515 */
516 thisIsOurNewOST.BlastBitmapToOrigTablet ();
517 tablet = tablet_; // don't use offscreen tablet past here... Draw to screen directly!!!
518 }
519 }
520 }
521 }
522 catch (...) {
523#if qStroika_Foundation_Common_Platform_MacOS
524 // Probably this code (and below case as well) is buggy. Setting back color in offscreen port (which is current now).
525 // But the code has been in place for quite some time (don't think broken by my offscreen bitmap move to LedGDI) with no
526 // noticable bugs/problems... Reconsider later...
527 // LGP 2001-05-11
528 Assert (*tablet == Led_GetCurrentGDIPort ());
529 GDI_RGBForeColor (oldForeColor);
530 GDI_RGBBackColor (oldBackColor);
531#endif
532 throw;
533 }
534#if qStroika_Foundation_Common_Platform_MacOS
535 Assert (*tablet == Led_GetCurrentGDIPort ());
536 GDI_RGBForeColor (oldForeColor);
537 GDI_RGBBackColor (oldBackColor);
538#endif
539}
540
541/*
542 @METHOD: MultiRowTextImager::DrawPartitionElement
543 @DESCRIPTION: <p></p>
544 */
545void MultiRowTextImager::DrawPartitionElement (PartitionMarker* pm, size_t startSubRow, size_t maxSubRow, Tablet* tablet, OffscreenTablet* offscreenTablet,
546 bool printing, const Led_Rect& subsetToDraw, Led_Rect* remainingDrawArea, size_t* rowsDrawn)
547{
548 RequireNotNull (pm);
549 RequireNotNull (remainingDrawArea);
550 RequireNotNull (rowsDrawn);
551
552 size_t start = pm->GetStart ();
553 size_t end = pm->GetEnd ();
554
555 Assert (end <= GetLength () + 1);
556 if (end == GetLength () + 1) {
557 --end; // don't include bogus char at end of buffer
558 }
559
560 Tablet* savedTablet = tablet;
561 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (pm);
562 size_t endSubRow = min (pmCacheInfo.GetRowCount () - 1, maxSubRow);
563 *rowsDrawn = 0;
564
565 size_t partLen = end - start;
566 Memory::StackBuffer<Led_tChar> partitionBuf{Memory::eUninitialized, partLen};
567 CopyOut (start, partLen, partitionBuf.data ());
568
569 for (size_t subRow = startSubRow; subRow <= endSubRow; ++subRow) {
570 Led_Rect currentRowRect = *remainingDrawArea;
571 currentRowRect.bottom = currentRowRect.top + pmCacheInfo.GetRowHeight (subRow);
572 DistanceType interlineSpace = (subRow == pmCacheInfo.GetLastRow ()) ? pmCacheInfo.GetInterLineSpace () : 0;
573 if ((currentRowRect.bottom + CoordinateType (interlineSpace) > subsetToDraw.top) and (currentRowRect.top < subsetToDraw.bottom)) {
574
575 /*
576 * patch start/end/len to take into account rows...
577 */
578 size_t rowStart = start + pmCacheInfo.PeekAtRowStart (subRow);
579 size_t rowEnd = end;
580 if (subRow < pmCacheInfo.GetLastRow ()) {
581 rowEnd = pm->GetStart () + pmCacheInfo.PeekAtRowStart (subRow + 1); // 'end' points just past last character in row
582 }
583 {
584 if (subRow == pmCacheInfo.GetLastRow ()) {
585 Assert (pm->GetEnd () > 0);
586 size_t markerEnd = pm->GetEnd ();
587 Assert (markerEnd <= GetLength () + 1);
588 if (markerEnd == GetLength () + 1) {
589 rowEnd = GetLength ();
590 }
591 else {
592 size_t prevToEnd = FindPreviousCharacter (markerEnd);
593 if (prevToEnd >= rowStart) {
594 Led_tChar lastChar;
595 CopyOut (prevToEnd, 1, &lastChar);
596 if (RemoveMappedDisplayCharacters (&lastChar, 1) == 0) {
597 rowEnd = (prevToEnd);
598 }
599 }
600 }
601 }
602 Assert (rowEnd == GetEndOfRowContainingPosition (rowStart));
603 }
604
605#if 1
606 TextLayoutBlock_Copy rowText = GetTextLayoutBlock (rowStart, rowEnd);
607#else
608 TextLayoutBlock_Basic rowText (partitionBuf + (rowStart - start), partitionBuf + (rowStart - start) + (rowEnd - rowStart));
609#endif
610
611 if (offscreenTablet != nullptr) {
612 tablet = offscreenTablet->PrepareRect (currentRowRect, interlineSpace);
613 }
614
615 {
616 /*
617 * Not sure why I didn't always do this? But changed from just setting RHS/LHS to subsetToDraw
618 * to this full intersection as part of SPR#1322 - LGP 2003-04-01.
619 */
620 Led_Rect invalidRowRect = Intersection (currentRowRect, subsetToDraw);
621 DrawRow (tablet, currentRowRect, invalidRowRect, rowText, rowStart, rowEnd, printing);
622 }
623
624 /*
625 * Now erase/draw any interline space.
626 */
627 if (interlineSpace != 0) {
628 size_t hilightStart = GetSelectionStart ();
629 size_t hilightEnd = GetSelectionEnd ();
630 bool segmentHilightedAtEnd = GetSelectionShown () and (hilightStart < rowEnd) and (rowEnd <= hilightEnd);
631 if (pm->GetNext () == nullptr and subRow == pmCacheInfo.GetLastRow ()) {
632 segmentHilightedAtEnd = false; // last row always contains no NL - so no invert off to the right...
633 }
634 DrawInterLineSpace (interlineSpace, tablet, currentRowRect.bottom, segmentHilightedAtEnd, printing);
635 }
636
637 if (offscreenTablet != nullptr) {
638 /*
639 * Blast offscreen bitmap onto the screen.
640 */
641 offscreenTablet->BlastBitmapToOrigTablet ();
642 tablet = savedTablet; // don't use offscreen tablet past here... Draw to screen directly!!!
643 }
644 }
645
646 remainingDrawArea->top = currentRowRect.bottom + interlineSpace;
647 ++(*rowsDrawn);
648 }
649}
650
651Led_Rect MultiRowTextImager::GetCharLocation (size_t afterPosition) const
652{
653 return (GetCharLocationRowRelative (afterPosition, RowReference (GetFirstPartitionMarker (), 0)));
654}
655
656Led_Rect MultiRowTextImager::GetCharWindowLocation (size_t afterPosition) const
657{
658 Led_Point windowOrigin = GetWindowRect ().GetOrigin () - Led_Point (0, GetHScrollPos ());
659 return (windowOrigin + GetCharLocationRowRelative (afterPosition, GetTopRowReferenceInWindow (), GetTotalRowsInWindow_ ()));
660}
661
662size_t MultiRowTextImager::GetCharAtLocation (const Led_Point& where) const
663{
664 return (GetCharAtLocationRowRelative (where, RowReference (GetFirstPartitionMarker (), 0)));
665}
666
667size_t MultiRowTextImager::GetCharAtWindowLocation (const Led_Point& where) const
668{
669 Led_Point windowOrigin = GetWindowRect ().GetOrigin () - Led_Point (0, GetHScrollPos ());
670 return (GetCharAtLocationRowRelative (where - windowOrigin, GetTopRowReferenceInWindow (), GetTotalRowsInWindow_ ()));
671}
672
673size_t MultiRowTextImager::GetStartOfRow (size_t rowNumber) const
674{
675 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
676 // Routines using RowReferences often perform MUCH better
677 return (GetStartOfRow (GetIthRowReference (rowNumber)));
678}
679
680size_t MultiRowTextImager::GetStartOfRowContainingPosition (size_t charPosition) const
681{
682 return (GetStartOfRow (GetRowReferenceContainingPosition (charPosition)));
683}
684
685size_t MultiRowTextImager::GetEndOfRow (size_t rowNumber) const
686{
687 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
688 // Routines using RowReferences often perform MUCH better
689 return (GetEndOfRow (GetIthRowReference (rowNumber)));
690}
691
692size_t MultiRowTextImager::GetEndOfRowContainingPosition (size_t charPosition) const
693{
694 return (GetEndOfRow (GetRowReferenceContainingPosition (charPosition)));
695}
696
697size_t MultiRowTextImager::GetRealEndOfRow (size_t rowNumber) const
698{
699 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
700 // Routines using RowReferences often perform MUCH better
701 return (GetRealEndOfRow (GetIthRowReference (rowNumber)));
702}
703
704size_t MultiRowTextImager::GetRealEndOfRowContainingPosition (size_t charPosition) const
705{
706 return (GetRealEndOfRow (GetRowReferenceContainingPosition (charPosition)));
707}
708
709size_t MultiRowTextImager::GetStartOfRow (RowReference row) const
710{
711 PartitionMarker* cur = row.GetPartitionMarker ();
712 size_t subRow = row.GetSubRow ();
713 AssertNotNull (cur);
714 return (cur->GetStart () + (subRow == 0 ? 0 : GetPartitionElementCacheInfo (cur).GetLineRelativeRowStartPosition (subRow)));
715}
716
717size_t MultiRowTextImager::GetEndOfRow (RowReference row) const
718{
719 PartitionMarker* cur = row.GetPartitionMarker ();
720 size_t subRow = row.GetSubRow ();
721 AssertNotNull (cur);
722 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
723 if (subRow == pmCacheInfo.GetLastRow ()) {
724 // Be careful about NL at end. If we end with an NL, then don't count that.
725 // And for the last PM - it contains a bogus empty character. Don't count
726 // that either.
727 Assert (cur->GetEnd () > 0);
728
729 size_t markerEnd = cur->GetEnd ();
730 Assert (markerEnd <= GetLength () + 1);
731 if (markerEnd == GetLength () + 1) {
732 return (GetLength ());
733 }
734
735 size_t prevToEnd = FindPreviousCharacter (markerEnd);
736 if (prevToEnd >= GetStartOfRow (row)) {
737 Led_tChar lastChar;
738 CopyOut (prevToEnd, 1, &lastChar);
739 if (RemoveMappedDisplayCharacters (&lastChar, 1) == 0) {
740 return (prevToEnd);
741 }
742 }
743 return (markerEnd);
744 }
745 else {
746 return (cur->GetStart () + pmCacheInfo.GetLineRelativeRowStartPosition (subRow + 1));
747 }
748}
749
750size_t MultiRowTextImager::GetRealEndOfRow (RowReference row) const
751{
752 PartitionMarker* cur = row.GetPartitionMarker ();
753 size_t subRow = row.GetSubRow ();
754 AssertNotNull (cur);
755 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
756 if (subRow == pmCacheInfo.GetLastRow ()) {
757 Assert (cur->GetEnd () > 0);
758 size_t markerEnd = cur->GetEnd ();
759 return (markerEnd);
760 }
761 else {
762 return (cur->GetStart () + pmCacheInfo.GetLineRelativeRowStartPosition (subRow + 1));
763 }
764}
765
766MultiRowTextImager::RowReference MultiRowTextImager::GetRowReferenceContainingPosition (size_t charPosition) const
767{
768 Require (charPosition <= GetEnd ());
769 PartitionMarker* pm = GetPartitionMarkerContainingPosition (charPosition);
770 AssertNotNull (pm);
771
772 size_t pmStart = pm->GetStart ();
773 if (charPosition == pmStart) { // slight speed tweek
774 return (RowReference (pm, 0));
775 }
776
777 // figure out what subrow the position occurs in, and return that...
778 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (pm);
779 return (RowReference (pm, pmCacheInfo.LineRelativePositionInWhichRow (charPosition - pmStart)));
780}
781
782size_t MultiRowTextImager::GetRowContainingPosition (size_t charPosition) const
783{
784 // Warning: GetRowReferenceContainingPosition () in preference, since
785 // it doesn't require call to pm->GetRowCount () - forcing a word-wrap...
786 return (GetRowNumber (GetRowReferenceContainingPosition (charPosition)));
787}
788
789size_t MultiRowTextImager::GetRowCount () const
790{
791 // NB: This is an expensive routine because it forces a word-wrap on all the text!
792 size_t rowCount = 0;
793 for (PartitionMarker* cur = GetFirstPartitionMarker (); cur != nullptr; cur = cur->GetNext ()) {
794 AssertNotNull (cur);
795 Assert (GetPartitionElementCacheInfo (cur).GetRowCount () >= 1);
796 rowCount += GetPartitionElementCacheInfo (cur).GetRowCount ();
797 }
798 return (rowCount);
799}
800
801Led_Rect MultiRowTextImager::GetCharLocationRowRelativeByPosition (size_t afterPosition, size_t positionOfTopRow, size_t maxRowsToCheck) const
802{
803 return GetCharLocationRowRelative (afterPosition, GetRowReferenceContainingPosition (positionOfTopRow), maxRowsToCheck);
804}
805
806DistanceType MultiRowTextImager::GetRowHeight (size_t rowNumber) const
807{
808 // NB: Use of routines using rowNumbers force word-wrap, and so can be quite slow.
809 // Routines using RowReferences often perform MUCH better
810 return (GetRowHeight (GetIthRowReference (rowNumber)));
811}
812
813/*
814 @METHOD: MultiRowTextImager::GetRowRelativeBaselineOfRowContainingPosition
815 @DESCRIPTION: <p>Override/implement @'TextImager::GetRowRelativeBaselineOfRowContainingPosition'.</p>
816 */
817DistanceType MultiRowTextImager::GetRowRelativeBaselineOfRowContainingPosition (size_t charPosition) const
818{
819 RowReference thisRow = GetRowReferenceContainingPosition (charPosition);
820 size_t startOfRow = GetStartOfRow (thisRow);
821 size_t endOfRow = GetEndOfRow (thisRow);
822 return MeasureSegmentBaseLine (startOfRow, endOfRow);
823}
824
825void MultiRowTextImager::GetStableTypingRegionContaingMarkerRange (size_t fromMarkerPos, size_t toMarkerPos, size_t* expandedFromMarkerPos,
826 size_t* expandedToMarkerPos) const
827{
828 AssertNotNull (expandedFromMarkerPos);
829 AssertNotNull (expandedToMarkerPos);
830 Assert (fromMarkerPos <= toMarkerPos);
831 Assert (toMarkerPos <= GetEnd ());
832
833 size_t curTopRowRelativeRowNumber = 0;
834
835 RowReference curRow = GetTopRowReferenceInWindow ();
836 do {
837 PartitionMarker* cur = curRow.GetPartitionMarker ();
838 AssertNotNull (cur);
839 size_t start = cur->GetStart ();
840 size_t end = cur->GetEnd ();
841
842 // For the last partition marker - we are including a BOGUS character past the end of the buffer.
843 // We don't want to return that. But otherwise - it is OK to return the NL at the end of the
844 // other lines (though perhaps that is unnecceary).... LGP 950210
845 if (cur->GetNext () == nullptr) {
846 --end;
847 }
848
849 // If we are strictly before the first row, we won't appear later...
850 if (curTopRowRelativeRowNumber == 0 and (fromMarkerPos < start)) {
851 break;
852 }
853
854 ++curTopRowRelativeRowNumber;
855
856 if (Contains (*cur, fromMarkerPos) and Contains (*cur, toMarkerPos)) {
857 (*expandedFromMarkerPos) = start;
858 (*expandedToMarkerPos) = end;
859 Assert ((*expandedFromMarkerPos) <= (*expandedToMarkerPos));
860 Assert ((*expandedToMarkerPos) <= GetEnd ());
861 return;
862 }
863
864 if (curTopRowRelativeRowNumber >= GetTotalRowsInWindow_ ()) {
865 break; // though this might allow is to go too far - no matter. We'd return
866 // the same result anyhow. And the extra overhead in counter rows
867 // as opposed to lines doesn't offset the overhead in counting a few
868 // extra lines - besides - this is simpler...
869 }
870 } while (GetNextRowReference (&curRow));
871
872 (*expandedFromMarkerPos) = 0;
873 (*expandedToMarkerPos) = GetEnd ();
874}
875
876DistanceType MultiRowTextImager::GetHeightOfRows (size_t startingRow, size_t rowCount) const
877{
878 return (GetHeightOfRows (GetIthRowReference (startingRow), rowCount));
879}
880
881DistanceType MultiRowTextImager::GetHeightOfRows (RowReference startingRow, size_t rowCount) const
882{
883 DistanceType height = 0;
884 for (RowReference curRow = startingRow; rowCount > 0; rowCount--) {
885 PartitionMarker* curPM = curRow.GetPartitionMarker ();
886 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
887 height += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
888 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
889 height += pmCacheInfo.GetInterLineSpace ();
890 }
891 (void)GetNextRowReference (&curRow);
892 }
893 return (height);
894}
895
896void MultiRowTextImager::DidUpdateText (const UpdateInfo& updateInfo) noexcept
897{
898//maynot need this addtion either - since done in PMInfoCache guy...
899#if 1
900 /*
901 * This invalidation is way more aggressive than it needs to be. Really all we need
902 * todo is check if this was one of the rows in the window, and only invalidate then.
903 * But that check would be at least somewhat costly. So this may be best for now.
904 * Maybe later try walking the markers from the start of window by n (number of rows currently
905 * set in total rows in window cache) and see if we are hit. Maybe that wouldn't be
906 * too bad cuz we could do nothing in the common case where the row cache was already
907 * invalid.
908 * --LGP 960912
909 */
910 InvalidateTotalRowsInWindow ();
911#endif
912
913 InvalidateTotalRowsInWindow ();
914 inherited::DidUpdateText (updateInfo);
915 AssertNotNull (fTopLinePartitionMarkerInWindow);
916 // ReValidateSubRowInTopLineInWindow ();
917 AssureWholeWindowUsedIfNeeded ();
918 InvalidateScrollBarParameters (); // even if we don't change the top row, we might change enuf about the text to change sbar
919}
920
921void MultiRowTextImager::SetWindowRect (const Led_Rect& windowRect)
922{
923 /*
924 * NB: We only check that the 'heightChanged' because thats all that can affect the number of rows
925 * shown on the screen. Someone pointed out that the WIDTH of the window rect changing can ALSO
926 * change the number of rows, and invalidate the caches. They were thinking of the subclass
927 * WordWrappedTextImager. And then - in the special case where you have implemented the policy
928 * 'wrap-to-window' (which you would NOT - for example - if you are using a ruler to specify margins).
929 * The point is - it is THERE - where you implement that wrapping policy - e.g. WordWrappedTextImager::GetLayoutMargins() -
930 * that you would have to hook SetWindowRect () and invalidate the cache.
931 */
932 bool heightChanged = GetWindowRect ().GetHeight () != windowRect.GetHeight ();
933 inherited::SetWindowRect (windowRect);
934 if (heightChanged and PeekAtTextStore () != nullptr) {
935 InvalidateTotalRowsInWindow ();
936 AssureWholeWindowUsedIfNeeded ();
937 InvalidateScrollBarParameters ();
938 }
939}
940
941/*
942 @METHOD: MultiRowTextImager::InvalidateAllCaches
943 @DESCRIPTION: <p>Hook @'TextImager::InvalidateAllCaches' method to invalidate additional information.
944 Invalidate cached row-height/etc information for the entire imager. Invalidate rows
945 in a window cached values, etc.</p>
946 */
947void MultiRowTextImager::InvalidateAllCaches ()
948{
949 inherited::InvalidateAllCaches ();
950 if (GetPartition ().get () != nullptr) { // careful that we aren't changing text metrics while we have no textstore attached!!!
951 if (fPMCacheMgr.get () != nullptr) {
952 fPMCacheMgr->ClearCache ();
953 }
954 InvalidateTotalRowsInWindow ();
955 // ReValidateSubRowInTopLineInWindow ();
956 AssureWholeWindowUsedIfNeeded ();
957 InvalidateScrollBarParameters ();
958 }
959}
960
961MultiRowTextImager::RowReference MultiRowTextImager::AdjustPotentialTopRowReferenceSoWholeWindowUsed (const RowReference& potentialTopRow)
962{
963 /*
964 * This check is always safe, but probably not a worthwhile optimization, except that it avoids
965 * some problems about initializing the top-row-reference before we've got a valid
966 * tablet setup to use.
967 */
968 if (potentialTopRow.GetSubRow () == 0 and potentialTopRow.GetPartitionMarker ()->GetPrevious () == nullptr) {
969 return potentialTopRow;
970 }
971
972 CoordinateType windowHeight = GetWindowRect ().GetHeight ();
973 CoordinateType heightUsed = 0;
974
975 for (RowReference curRow = potentialTopRow;;) {
976 PartitionMarker* curPM = curRow.GetPartitionMarker ();
977 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
978 heightUsed += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
979 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
980 heightUsed += pmCacheInfo.GetInterLineSpace ();
981 }
982 if (heightUsed >= windowHeight) {
983 return (potentialTopRow); // Then we used all the space we could have - and that is a good row!
984 }
985 if (not GetNextRowReference (&curRow)) {
986 break;
987 }
988 }
989
990 // If we got here - we ran out of rows before we ran out of height.
991 // That means we should scroll back a smidge...
992 for (RowReference curRow = potentialTopRow;;) {
993 if (not GetPreviousRowReference (&curRow)) {
994 return (curRow); // if we've gone back as far as we can - were done!
995 // Even if we didn't use all the height
996 }
997
998 PartitionMarker* curPM = curRow.GetPartitionMarker ();
999 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
1000 heightUsed += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
1001 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
1002 heightUsed += pmCacheInfo.GetInterLineSpace ();
1003 }
1004 if (heightUsed > windowHeight) {
1005 // We went back one too far - forward one and return that.
1006 [[maybe_unused]] bool result = GetNextRowReference (&curRow);
1007 Assert (result);
1008 return curRow;
1009 }
1010 else if (heightUsed == windowHeight) {
1011 return (curRow); // Then we used all the space we could have - and that is a good row!
1012 }
1013 }
1014 Assert (false);
1015 return (potentialTopRow); // NotReached / silence compiler warnings
1016}
1017
1018bool MultiRowTextImager::PositionWouldFitInWindowWithThisTopRow (size_t markerPos, const RowReference& newTopRow)
1019{
1020 if (markerPos < GetStartOfRow (newTopRow)) {
1021 return false;
1022 }
1023
1024 size_t rowCount = ComputeRowsThatWouldFitInWindowWithTopRow (newTopRow);
1025 RowReference lastRow = GetIthRowReferenceFromHere (newTopRow, rowCount - 1);
1026
1027 return (markerPos < GetRealEndOfRow (lastRow));
1028}
1029
1030void MultiRowTextImager::ReValidateSubRowInTopLineInWindow ()
1031{
1032 AssertNotNull (fTopLinePartitionMarkerInWindow);
1033
1034 // don't bother calling GetRowCount () if fSubRowInTopLineInWindow is already ZERO - avoid possible word-wrap
1035 if (fSubRowInTopLineInWindow != 0) {
1036#if 1
1037 size_t lastRow = GetPartitionElementCacheInfo (fTopLinePartitionMarkerInWindow).GetLastRow ();
1038 if (fSubRowInTopLineInWindow > lastRow) {
1039 fSubRowInTopLineInWindow = lastRow;
1040 }
1041#else
1042 bool pmNotWrapped = (fTopLinePartitionMarkerInWindow->fPixelHeightCache == DistanceType (-1));
1043 size_t lastRow = GetPartitionElementCacheInfo (fTopLinePartitionMarkerInWindow).GetLastRow ();
1044 if (fSubRowInTopLineInWindow > lastRow) {
1045 fSubRowInTopLineInWindow = lastRow;
1046 }
1047 if (pmNotWrapped) {
1048 /*
1049 * We invalidated it for this method. But we may have done so prematurely - during
1050 * the context of a marker DidUpdate () method. See SPR#0821
1051 *
1052 * Reset it back to invalid so it will be properly layed out.
1053 */
1054 fTopLinePartitionMarkerInWindow->InvalidateCache ();
1055 }
1056#endif
1057 }
1058}
1059
1060size_t MultiRowTextImager::ComputeRowsThatWouldFitInWindowWithTopRow (const RowReference& newTopRow) const
1061{
1062 /*
1063 * For now, we don't show partial rows at the bottom. We
1064 * might want to reconsider this.
1065 */
1066 CoordinateType windowHeight = GetWindowRect ().GetHeight ();
1067
1068 /*
1069 * Wind out way to the bottom of the window from our current position,
1070 * and count rows.
1071 */
1072 size_t rowCount = 0;
1073 CoordinateType heightUsed = 0;
1074 for (RowReference curRow = newTopRow;;) {
1075 ++rowCount;
1076 PartitionMarker* curPM = curRow.GetPartitionMarker ();
1077 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (curPM);
1078 heightUsed += pmCacheInfo.GetRowHeight (curRow.GetSubRow ());
1079 if (curRow.GetSubRow () == pmCacheInfo.GetLastRow ()) {
1080 heightUsed += pmCacheInfo.GetInterLineSpace ();
1081 }
1082 if (heightUsed > windowHeight) {
1083 // we went one too far
1084 --rowCount;
1085 break;
1086 }
1087 else if (heightUsed == windowHeight) {
1088 break; // thats all that will fit
1089 }
1090
1091 if (not GetNextRowReference (&curRow)) {
1092 break;
1093 }
1094 }
1095 if (rowCount == 0) { // always for the existence of at least one row...
1096 rowCount = 1;
1097 }
1098
1099 return (rowCount);
1100}
1101
1102/*
1103 @METHOD: MultiRowTextImager::GetCharLocationRowRelative
1104 @DESCRIPTION: <p>Return the row-relative (to get window relative - add top-left of window-rect) bounding rectange of the
1105 given character cell. Compute the vertical position relative to the given argument 'topRow' and check and most
1106 'maxRowsToCheck' before just returning a large 'off-the-end' value result</p>
1107 */
1108Led_Rect MultiRowTextImager::GetCharLocationRowRelative (size_t afterPosition, RowReference topRow, size_t maxRowsToCheck) const
1109{
1110 // MUST FIGURE OUT WHAT TODO HERE BETTER - 10000 not good enough answer always...
1111 const Led_Rect kMagicBeforeRect = Led_Rect (-10000, 0, 0, 0);
1112 const Led_Rect kMagicAfterRect = Led_Rect (10000, 0, 0, 0);
1113
1114 Require (afterPosition <= GetEnd ());
1115
1116 if (afterPosition < GetStartOfRow (topRow)) {
1117 return (kMagicBeforeRect);
1118 }
1119
1120 RowReference curRow = topRow;
1121 size_t curTopRowRelativeRowNumber = 0;
1122 CoordinateType topVPos = 0;
1123 do {
1124 PartitionMarker* cur = curRow.GetPartitionMarker ();
1125 size_t subRow = curRow.GetSubRow ();
1126 AssertNotNull (cur);
1127 size_t start = cur->GetStart ();
1128 size_t end = cur->GetEnd (); // end points JUST PAST LAST VISIBLE/OPERATED ON CHAR
1129
1130 Assert (end <= GetEnd () + 1);
1131
1132 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
1133
1134 /*
1135 * patch start/end/len to take into account rows...
1136 */
1137 start += pmCacheInfo.PeekAtRowStart (subRow);
1138 if (subRow < pmCacheInfo.GetLastRow ()) {
1139 end = cur->GetStart () + pmCacheInfo.PeekAtRowStart (subRow + 1);
1140 Assert (start <= end);
1141 }
1142
1143 ++curTopRowRelativeRowNumber;
1144
1145 /*
1146 * When we've found the right row, then add in the right horizontal offset.
1147 */
1148 if (afterPosition >= start and afterPosition < end) {
1149 Assert (start <= afterPosition);
1150 DistanceType hStart = 0;
1151 DistanceType hEnd = 0;
1152 GetRowRelativeCharLoc (afterPosition, &hStart, &hEnd);
1153 Assert (hStart <= hEnd);
1154 return (Led_Rect (topVPos, hStart, pmCacheInfo.GetRowHeight (subRow), hEnd - hStart));
1155 }
1156
1157 topVPos += pmCacheInfo.GetRowHeight (subRow);
1158
1159 if (pmCacheInfo.GetLastRow () == subRow) {
1160 topVPos += pmCacheInfo.GetInterLineSpace ();
1161 }
1162
1163 if (curTopRowRelativeRowNumber >= maxRowsToCheck) {
1164 break; // return bogus place at the end...
1165 }
1166 } while (GetNextRowReference (&curRow));
1167
1168 return (kMagicAfterRect);
1169}
1170
1171size_t MultiRowTextImager::GetCharAtLocationRowRelative (const Led_Point& where, RowReference topRow, size_t maxRowsToCheck) const
1172{
1173 /*
1174 * Not 100% sure how to deal with points outside our range. For now - we just
1175 * return topMost/bottomMost marker positions. That seems to work decently - at least
1176 * for now... But I worry if it is the right thing when we do
1177 * autoscrolling...
1178 */
1179 if (where.v < 0) {
1180 return 0;
1181 }
1182
1183 RowReference curRow = topRow;
1184 size_t curTopRowRelativeRowNumber = 0;
1185 CoordinateType topVPos = 0;
1186 do {
1187 PartitionMarker* cur = curRow.GetPartitionMarker ();
1188 size_t subRow = curRow.GetSubRow ();
1189 AssertNotNull (cur);
1190 size_t start = cur->GetStart ();
1191
1192 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (cur);
1193
1194 /*
1195 * patch start/end/len to take into account rows...
1196 */
1197 start += pmCacheInfo.PeekAtRowStart (subRow);
1198
1199 /*
1200 * Count the interline space as part of the last row of the line for the purpose of hit-testing.
1201 */
1202 DistanceType interLineSpaceIfAny = (pmCacheInfo.GetLastRow () == subRow) ? pmCacheInfo.GetInterLineSpace () : 0;
1203
1204 curTopRowRelativeRowNumber++;
1205 if (where.v >= topVPos and where.v < topVPos + CoordinateType (pmCacheInfo.GetRowHeight (subRow) + interLineSpaceIfAny)) {
1206 return GetRowRelativeCharAtLoc (where.h, start);
1207 }
1208
1209 if (curTopRowRelativeRowNumber >= maxRowsToCheck) {
1210 break; // we've checked enuf...
1211 }
1212
1213 topVPos += pmCacheInfo.GetRowHeight (subRow) + interLineSpaceIfAny;
1214 } while (GetNextRowReference (&curRow));
1215
1216 return GetEnd ();
1217}
1218
1219DistanceType MultiRowTextImager::CalculateInterLineSpace (const PartitionMarker* /*pm*/) const
1220{
1221 return (0); // no interline space by default
1222}
1223
1224/*
1225 @METHOD: MultiRowTextImager::ContainsMappedDisplayCharacters
1226 @DESCRIPTION: <p>Override @'TextImager::ContainsMappedDisplayCharacters' to hide '\n' characters.
1227 See @'qDefaultLedSoftLineBreakChar'.</p>
1228 */
1229bool MultiRowTextImager::ContainsMappedDisplayCharacters (const Led_tChar* text, size_t nTChars) const
1230{
1231 return ContainsMappedDisplayCharacters_HelperForChar (text, nTChars, '\n') or inherited::ContainsMappedDisplayCharacters (text, nTChars);
1232}
1233
1234/*
1235 @METHOD: MultiRowTextImager::RemoveMappedDisplayCharacters
1236 @DESCRIPTION: <p>Override @'TextImager::RemoveMappedDisplayCharacters' to hide '\n' characters.</p>
1237 */
1238size_t MultiRowTextImager::RemoveMappedDisplayCharacters (Led_tChar* copyText, size_t nTChars) const
1239{
1240 size_t newLen = inherited::RemoveMappedDisplayCharacters (copyText, nTChars);
1241 Assert (newLen <= nTChars);
1242 size_t newerLen = RemoveMappedDisplayCharacters_HelperForChar (copyText, newLen, '\n');
1243 Assert (newerLen <= newLen);
1244 Assert (newerLen <= nTChars);
1245 return newerLen;
1246}
1247
1248/*
1249 ********************************************************************************
1250 ******************* MultiRowTextImager::PartitionElementCacheInfo **************
1251 ********************************************************************************
1252 */
1253void MultiRowTextImager::PartitionElementCacheInfo::Clear ()
1254{
1255 fRep = MakeSharedPtr<Rep> ();
1256}
1257
1258void MultiRowTextImager::PartitionElementCacheInfo::IncrementRowCountAndFixCacheBuffers (size_t newStart, DistanceType newRowsHeight)
1259{
1260 ++fRep->fRowCountCache;
1261
1262 // If rowStart array not big enough then allocate it from the heap...
1263 if (fRep->fRowCountCache > kPackRowStartCount + 1) {
1264 RowStart_* newRowStartArray = new RowStart_[fRep->fRowCountCache - 1];
1265 AssertNotNull (newRowStartArray);
1266 if (fRep->fRowCountCache == kPackRowStartCount + 1 + 1) {
1267 ::memcpy (newRowStartArray, &fRep->fRowStartArray, sizeof (fRep->fRowStartArray));
1268 }
1269 else {
1270 Assert (fRep->fRowCountCache > 2);
1271 if (fRep->fRowCountCache > kPackRowStartCount + 1 + 1) {
1272 AssertNotNull (fRep->fRowStartArray);
1273 (void)memcpy (newRowStartArray, fRep->fRowStartArray, sizeof (newRowStartArray[1]) * (fRep->fRowCountCache - 2));
1274 delete[] fRep->fRowStartArray;
1275 }
1276 }
1277 fRep->fRowStartArray = newRowStartArray;
1278 }
1279
1280 // If rowHeight array not big enough then allocate it from the heap...
1281 if (fRep->fRowCountCache > kPackRowHeightCount) {
1282 RowHeight_* newRowHeightArray = new RowHeight_[fRep->fRowCountCache];
1283 AssertNotNull (newRowHeightArray);
1284 if (fRep->fRowCountCache == kPackRowHeightCount + 1) {
1285 ::memcpy (newRowHeightArray, &fRep->fRowHeightArray, sizeof (fRep->fRowHeightArray));
1286 }
1287 else {
1288 Assert (fRep->fRowCountCache > 1);
1289 if (fRep->fRowCountCache > kPackRowHeightCount + 1) {
1290 AssertNotNull (fRep->fRowHeightArray);
1291 memcpy (newRowHeightArray, fRep->fRowHeightArray, sizeof (newRowHeightArray[1]) * (fRep->fRowCountCache - 1));
1292 delete[] fRep->fRowHeightArray;
1293 }
1294 }
1295 fRep->fRowHeightArray = newRowHeightArray;
1296 }
1297
1298 SetRowStart (fRep->fRowCountCache - 1, newStart);
1299 SetRowHeight (fRep->fRowCountCache - 1, newRowsHeight);
1300 fRep->fPixelHeightCache += newRowsHeight;
1301}
1302
1303/*
1304 ********************************************************************************
1305 *********************** MultiRowTextImager::PMInfoCacheMgr *********************
1306 ********************************************************************************
1307 */
1308MultiRowTextImager::PMInfoCacheMgr::PMInfoCacheMgr (MultiRowTextImager& imager)
1309 : fPMCache ()
1310 , fCurFillCachePM (nullptr)
1311 , fCurFillCacheInfo ()
1312 , fImager (imager)
1313 , fMyMarker ()
1314{
1315 // REDO this class to make IT a MarkerOwner - and use THAT markerowner for MyMarker. Then - store an additional MyMarker for EACH marker
1316 // added to cache (just around the PM its used to wrap). Then remove ONLY that PM from the cache in its DIDUpdate.
1317 PartitionPtr part = imager.GetPartition ();
1318 Assert (part.get () != nullptr);
1319 part->AddPartitionWatcher (this);
1320 fMyMarker = unique_ptr<MyMarker> (new MyMarker (*this));
1321 TextStore& ts = part->GetTextStore ();
1322 ts.AddMarker (fMyMarker.get (), 0, ts.GetLength () + 1, part.get ());
1323}
1324
1325MultiRowTextImager::PMInfoCacheMgr::~PMInfoCacheMgr ()
1326{
1327 PartitionPtr part = fImager.GetPartition ();
1328 part->RemovePartitionWatcher (this);
1329 TextStore& ts = part->GetTextStore ();
1330 ts.RemoveMarker (fMyMarker.get ());
1331}
1332
1333MultiRowTextImager::PartitionElementCacheInfo MultiRowTextImager::PMInfoCacheMgr::GetPartitionElementCacheInfo (Partition::PartitionMarker* pm) const
1334{
1335 if (pm == fCurFillCachePM) {
1336 return fCurFillCacheInfo; // allow recursive call to get PMCacheInfo (so far) DURING context of call to FillCache()
1337 }
1338 using MAP_CACHE = map<Partition::PartitionMarker*, PartitionElementCacheInfo>;
1339 MAP_CACHE::iterator i = fPMCache.find (pm);
1340 if (i == fPMCache.end ()) {
1341 try {
1342 Assert (fCurFillCachePM == nullptr); // can only do one fillcache at a time...
1343 fCurFillCachePM = pm;
1344 fImager.FillCache (pm, fCurFillCacheInfo);
1346 for (size_t t = 0; t < fCurFillCacheInfo.GetRowCount (); ++t) {
1347 Assert (fCurFillCacheInfo.GetLineRelativeRowStartPosition (t) <= pm->GetLength ());
1348 Assert (fCurFillCacheInfo.PeekAtRowStart (t) <= pm->GetLength ());
1349 }
1350 }
1351 i = fPMCache.insert (MAP_CACHE::value_type (pm, fCurFillCacheInfo)).first;
1353 Assert (fCurFillCacheInfo.GetRowCount () == i->second.GetRowCount ());
1354 for (size_t t = 0; t < fCurFillCacheInfo.GetRowCount (); ++t) {
1355 Assert (fCurFillCacheInfo.PeekAtRowHeight (t) == i->second.PeekAtRowHeight (t));
1356 Assert (fCurFillCacheInfo.PeekAtRowStart (t) == i->second.PeekAtRowStart (t));
1357 }
1358 }
1359 Assert (fCurFillCachePM == pm);
1360 fCurFillCachePM = nullptr;
1361 }
1362 catch (...) {
1363 Assert (fCurFillCachePM == pm);
1364 fCurFillCachePM = nullptr;
1365 throw;
1366 }
1367 }
1368 return i->second;
1369}
1370
1371void MultiRowTextImager::PMInfoCacheMgr::ClearCache ()
1372{
1373 fPMCache.clear ();
1374}
1375
1376void MultiRowTextImager::PMInfoCacheMgr::AboutToSplit (PartitionMarker* pm, size_t /*at*/, void** infoRecord) const noexcept
1377{
1378 *infoRecord = pm;
1379}
1380
1381void MultiRowTextImager::PMInfoCacheMgr::DidSplit (void* infoRecord) const noexcept
1382{
1383 PartitionMarker* pm = reinterpret_cast<PartitionMarker*> (infoRecord);
1384 using MAP_CACHE = map<Partition::PartitionMarker*, PartitionElementCacheInfo>;
1385 MAP_CACHE::iterator i = fPMCache.find (pm);
1386 if (i != fPMCache.end ()) {
1387 fPMCache.erase (i);
1388 }
1389 fImager.InvalidateTotalRowsInWindow ();
1390#if 0
1391 if (pm == fImager.fTopLinePartitionMarkerInWindow) {
1392 // if splitting top row - we must revalidate top subrow
1393 fImager.ReValidateSubRowInTopLineInWindow ();
1394 }
1395#endif
1396}
1397
1398void MultiRowTextImager::PMInfoCacheMgr::AboutToCoalece (PartitionMarker* pm, void** infoRecord) const noexcept
1399{
1400 *infoRecord = pm;
1401
1402 PartitionMarker* newTopLine = nullptr;
1403 bool useFirstRow = false; // otherwise use last row...
1404 if (pm == fImager.fTopLinePartitionMarkerInWindow) {
1405 if (pm->GetNext () == nullptr) {
1406 newTopLine = fImager.fTopLinePartitionMarkerInWindow->GetPrevious ();
1407 useFirstRow = false;
1408 }
1409 else {
1410 newTopLine = fImager.fTopLinePartitionMarkerInWindow->GetNext ();
1411 useFirstRow = true;
1412 }
1413 AssertNotNull (newTopLine);
1414 }
1415
1416 if (newTopLine != nullptr) {
1417 //TMPHACK - REALLY should do old comment out code - see old code in MultiRowTextImager::MultiRowPartition::Coalese - trouble is then
1418 // 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)
1419 // This is a bit of a hack - but AT LEAST should avoid any crashes/flakies we now see - LGP 2002-10-17
1420 // fImager.SetTopRowInWindow_ (RowReference (newTopLine, useFirstRow? 0: fImager.GetPartitionElementCacheInfo (newTopLine).GetLastRow ()));
1421 fImager.SetTopRowInWindow_ (RowReference (newTopLine, 0));
1422 }
1423
1424#if 0
1425 MultiRowPartitionMarker* newTopLine = nullptr;
1426 bool useFirstRow = false; // otherwise use last row...
1427 if (pm == fTextImager.fTopLinePartitionMarkerInWindow) {
1428 if (pm->GetNext () == nullptr) {
1429 newTopLine = fTextImager.fTopLinePartitionMarkerInWindow->GetPreviousMRPM ();
1430 useFirstRow = false;
1431 }
1432 else {
1433 newTopLine = fTextImager.fTopLinePartitionMarkerInWindow->GetNextMRPM ();
1434 useFirstRow = true;
1435 }
1436 AssertNotNull (newTopLine);
1437 }
1438
1439 MultiRowPartitionMarker* successor = (MultiRowPartitionMarker*)pm->GetNext (); // all our pms are subtypes of this type
1440 inherited::Coalece (pm); // deletes pm....
1441 if (successor != nullptr) {
1442 successor->InvalidateCache (); // This is where our xtra text goes - so be sure we invalidate its extent...
1443 }
1444
1445 // If we must change the top-row - then do so NOW - cuz NOW its safe to call GetRowCount ()
1446 if (newTopLine != nullptr) {
1447 fTextImager.SetTopRowInWindow_ (RowReference (newTopLine, useFirstRow ? 0 : fTextImager.GetPartitionElementCacheInfo (newTopLine).GetLastRow ()));
1448 }
1449 AssertNotNull (fTextImager.fTopLinePartitionMarkerInWindow); // can't delete last one...
1450 AssertMember (fTextImager.fTopLinePartitionMarkerInWindow, MultiRowPartitionMarker);
1451 fTextImager.InvalidateTotalRowsInWindow ();
1452#endif
1453}
1454
1455void MultiRowTextImager::PMInfoCacheMgr::DidCoalece (void* infoRecord) const noexcept
1456{
1457 PartitionMarker* pm = reinterpret_cast<PartitionMarker*> (infoRecord);
1458 using MAP_CACHE = map<Partition::PartitionMarker*, PartitionElementCacheInfo>;
1459 MAP_CACHE::iterator i = fPMCache.find (pm);
1460 if (i != fPMCache.end ()) {
1461 fPMCache.erase (i);
1462 }
1463 fImager.InvalidateTotalRowsInWindow ();
1464}
1465
1466void MultiRowTextImager::PMInfoCacheMgr::MyMarkerDidUpdateCallback ()
1467{
1468 fPMCache.clear ();
1469
1470 /*
1471 * This invalidation is way more aggressive than it needs to be. Really all we need
1472 * todo is check if this was one of the rows in the window, and only invalidate then.
1473 * But that check would be at least somewhat costly. So this may be best for now.
1474 * Maybe later try walking the markers from the start of window by n (number of rows currently
1475 * set in total rows in window cache) and see if we are hit. Maybe that wouldn't be
1476 * too bad cuz we could do nothing in the common case where the row cache was already
1477 * invalid.
1478 * --LGP 960912
1479 */
1480 fImager.InvalidateTotalRowsInWindow ();
1481}
1482
1483/*
1484 ********************************************************************************
1485 *************** MultiRowTextImager::PMInfoCacheMgr::MyMarker *******************
1486 ********************************************************************************
1487 */
1488MultiRowTextImager::PMInfoCacheMgr::MyMarker::MyMarker (PMInfoCacheMgr& pmInfoCacheMgr)
1489 : fPMInfoCacheMgr (pmInfoCacheMgr)
1490{
1491}
1492
1493void MultiRowTextImager::PMInfoCacheMgr::MyMarker::DidUpdateText (const UpdateInfo& updateInfo) noexcept
1494{
1495 inherited::DidUpdateText (updateInfo);
1496 fPMInfoCacheMgr.MyMarkerDidUpdateCallback ();
1497}
1498#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...