Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
WordProcessor.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <cctype>
7
8#if qStroika_Platform_Windows
9#include <Windows.h>
10#include <commdlg.h>
11#include <shellapi.h>
12#endif
13
20
21#include "Stroika/Frameworks/Led/Config.h"
22#include "Stroika/Frameworks/Led/SimpleTextStore.h"
23#include "Stroika/Frameworks/Led/StyledTextEmbeddedObjects.h"
24
25#include "WordProcessor.h"
26
27using namespace Stroika::Foundation;
29
30using Memory::MakeSharedPtr;
31
32using namespace Stroika::Frameworks;
33using namespace Stroika::Frameworks::Led;
34using namespace Stroika::Frameworks::Led::StyledTextIO;
35
36#if qStroika_Frameworks_Led_SupportGDI
37using InteractiveModeUpdater = WordProcessor::InteractiveModeUpdater;
38using UndoableContextHelper = WordProcessor::UndoableContextHelper;
39using Tablet_Acquirer = WordProcessor::Tablet_Acquirer;
40#endif
41
42/*
43 ********************************************************************************
44 **************************** ParagraphDatabaseRep ******************************
45 ********************************************************************************
46 */
47ParagraphDatabaseRep::ParagraphDatabaseRep (TextStore& textStore)
48 : inheritedMC{textStore, GetStaticDefaultParagraphInfo ()}
49{
50#if qStroika_Frameworks_Led_SupportGDI
51 //tmphack test - see if this fixes SPR#1129
52 // LGP 2002-10-19 - didnt appear to work so probably get rid of it - but test some more!!!
53 SetPartition (MakeSharedPtr<WordProcessor::WPPartition> (GetTextStore (), *this));
54#else
55 SetPartition (MakeSharedPtr<LineBasedPartition> (GetTextStore ()));
56#endif
57}
58
59void ParagraphDatabaseRep::SetPartition (const shared_ptr<Partition>& partitionPtr)
60{
61 if (fPartition != partitionPtr) {
62 fPartition = partitionPtr;
63 // Re-align all the partition elts as close as possible to those appropriate for the new partition.
64 CheckMarkerBounaryConstraints (0, GetTextStore ().GetEnd ());
65 }
66}
67
68/*
69@METHOD: WordProcessor::ParagraphDatabaseRep::GetStaticDefaultParagraphInfo
70@DESCRIPTION:
71 <p>Return the default @'ParagraphInfo' object used when the paragraph database object is created.</p>
72*/
73ParagraphInfo ParagraphDatabaseRep::GetStaticDefaultParagraphInfo ()
74{
75 ParagraphInfo defaultPi;
76 const int kDefaultInches = 6;
77 defaultPi.SetMargins (TWIPS{0}, TWIPS (kDefaultInches * 1440));
78 defaultPi.SetJustification (eLeftJustify);
79 return defaultPi;
80}
81
82const ParagraphInfo& ParagraphDatabaseRep::GetParagraphInfo (size_t charAfterPos) const
83{
84 return GetInfo (charAfterPos);
85}
86
87vector<pair<ParagraphInfo, size_t>> ParagraphDatabaseRep::GetParagraphInfo (size_t charAfterPos, size_t nTCharsFollowing) const
88{
89 return GetInfo (charAfterPos, nTCharsFollowing);
90}
91
92void ParagraphDatabaseRep::SetParagraphInfo (size_t charAfterPos, size_t nTCharsFollowing, const IncrementalParagraphInfo& infoForMarkers)
93{
94 if (infoForMarkers.GetMargins_Valid ()) {
95 fCachedFarthestRightMarginInDocument = kBadCachedFarthestRightMarginInDocument;
96 }
97 SetInfo (charAfterPos, nTCharsFollowing, infoForMarkers);
98}
99
100void ParagraphDatabaseRep::SetParagraphInfo (size_t charAfterPos, const vector<pair<IncrementalParagraphInfo, size_t>>& infoForMarkers)
101{
102 for (auto i = infoForMarkers.begin (); i != infoForMarkers.end (); ++i) {
103 if ((*i).first.GetMargins_Valid ()) {
104 fCachedFarthestRightMarginInDocument = kBadCachedFarthestRightMarginInDocument;
105 break;
106 }
107 }
108 SetInfos (charAfterPos, infoForMarkers);
109}
110
111void ParagraphDatabaseRep::SetParagraphInfo (size_t charAfterPos, const vector<pair<ParagraphInfo, size_t>>& infoForMarkers)
112{
113 fCachedFarthestRightMarginInDocument = kBadCachedFarthestRightMarginInDocument;
114 SetInfos2 (charAfterPos, infoForMarkers);
115}
116
117/*
118@METHOD: WordProcessor::ParagraphDatabaseRep::SetInfo
119@DESCRIPTION: <p>Override @'MarkerCover<MARKER,MARKERINFO,INCREMENTALMARKERINFO>::SetInfos' to assure we have a partition
120 associated with us before allowing changes. This partition can be changed later.</p>
121*/
122void ParagraphDatabaseRep::SetInfo (size_t charAfterPos, size_t nTCharsFollowing, const IncrementalParagraphInfo& infoForMarkers)
123{
124 Assert (fPartition.get () != nullptr);
125 inheritedMC::SetInfo (charAfterPos, nTCharsFollowing, infoForMarkers);
126}
127
128void ParagraphDatabaseRep::SetInfos (size_t charAfterPos, const vector<pair<IncrementalParagraphInfo, size_t>>& infoForMarkers)
129{
130 Assert (fPartition.get () != nullptr);
131 inheritedMC::SetInfos (charAfterPos, infoForMarkers);
132}
133
134/*
135@METHOD: WordProcessor::ParagraphDatabaseRep::NoteCoverRangeDirtied
136@ACCESS: protected
137@DESCRIPTION: <p>Override @'MarkerCover<MARKER,MARKERINFO,INCREMENTALMARKERINFO>::NoteCoverRangeDirtied'
138 to assure we check boundary contraints (assure paragraph cover markers end on paragraph boundaries,
139 as defined by our partition).</p>
140*/
141void ParagraphDatabaseRep::NoteCoverRangeDirtied (size_t from, size_t to, const MarkerVector& rangeAndSurroundingsMarkers)
142{
143 inheritedMC::NoteCoverRangeDirtied (from, to, rangeAndSurroundingsMarkers);
144 CheckMarkerBounaryConstraints (rangeAndSurroundingsMarkers);
145}
146
147void ParagraphDatabaseRep::ConstrainSetInfoArgs (size_t* charAfterPos, size_t* nTCharsFollowing)
148{
149 RequireNotNull (charAfterPos);
150 RequireNotNull (nTCharsFollowing);
151
152 size_t from = *charAfterPos;
153 size_t to = from + *nTCharsFollowing;
154 PartitionMarker* startPara = fPartition->GetPartitionMarkerContainingPosition (from);
155 PartitionMarker* endPara = (to <= startPara->GetEnd ()) ? startPara : fPartition->GetPartitionMarkerContainingPosition (to);
156 // If the 'to' position happens to be the exact start of a paritition, it would have also been the end of the previous
157 // parition. Take it to be the end of the previous one (unless that would imply no selection)
158 if (startPara != endPara and endPara->GetStart () == to) {
159 endPara = endPara->GetPrevious ();
160 }
161 size_t bigFrom = startPara->GetStart ();
162 size_t bigEnd = min (endPara->GetEnd (), GetTextStore ().GetEnd () + 1);
163
164 *charAfterPos = bigFrom;
165 *nTCharsFollowing = bigEnd - bigFrom;
166}
167
168/*
169@METHOD: WordProcessor::ParagraphDatabaseRep::CheckMarkerBounaryConstraints
170@ACCESS: private
171@DESCRIPTION: <p>Called internally to check that all the paragraph info records in the MarkerCover
172 respect the contraint that they start and end on paragraph boundaries.</p>
173*/
174void ParagraphDatabaseRep::CheckMarkerBounaryConstraints (size_t from, size_t to) noexcept
175{
176 if (fPartition.get () != nullptr) {
177 MarkerVector markers = CollectAllInRange_OrSurroundings (from, to);
178 sort (markers.begin (), markers.end (), LessThan<ParagraphInfoMarker> ());
179 CheckMarkerBounaryConstraints (markers);
180 }
181}
182
183void ParagraphDatabaseRep::CheckMarkerBounaryConstraints (const MarkerVector& rangeAndSurroundingsMarkers) noexcept
184{
185 /*
186 * For each paragraph style run, check if its edges fall on paragraph (as specified by the partition) boundaries.
187 * If not - then adjust the style runs so they do.
188 */
189 if (fPartition.get () != nullptr) {
190 for (auto i = rangeAndSurroundingsMarkers.begin (); i != rangeAndSurroundingsMarkers.end (); ++i) {
191 ParagraphInfoMarker* m = *i;
192 AssertNotNull (m);
193 size_t m_start; // Use these temporaries as speed tweeks -LGP990310
194 size_t m_end;
195 m->GetRange (&m_start, &m_end);
196 size_t m_length = m_end - m_start;
197
198 // Ignore zero-length ParagraphInfoMarkers - they will soon be culled...
199 Assert (m->GetLength () == m_length);
200 if (m_length != 0) {
201 Assert (m_start == m->GetStart ());
202 PartitionMarker* partitionElt = fPartition->GetPartitionMarkerContainingPosition (m_start);
203 Assert (partitionElt->GetStart () == m->GetStart ()); // cuz we fix these up in order, and can only adjust ends of
204 // cur marker, and start of following one
205 Assert (m->GetEnd () == m_end);
206 size_t partitionElt_end;
207 for (; (partitionElt_end = partitionElt->GetEnd ()) < m_end;) {
208 partitionElt = partitionElt->GetNext ();
209 AssertNotNull (partitionElt); // must get to end by markerpos before last marker
210 }
211 Assert (partitionElt_end == partitionElt->GetEnd ());
212
213 /*
214 * We've now walked all the pms in this ParagraphInfoMarker, and if the last pm end was the same as
215 * our ParagraphInfoMarker end, were all set. If they are unequal (cuz of how we walked the list), then
216 * the PM extends PAST our ParagraphInfoMarker end. If so, we must adjust the boundaries between the
217 * current ParagraphInfoMarker and the following one.
218 *
219 * Now we have an arbitrary choice to make. Do we move the ParagraphInfoMarker to the right, or to
220 * the left. This is completely arbitrary, and the only significance is that if we move to the right
221 * then we lose ParagraphInfo styling of the following sentence when merging them together, and
222 * if we go the other way, we get the opposite effect.
223 *
224 * For now, chose to move the ParagraphInfoMarker to the RIGHT.
225 *
226 * Note - this arbitrary choice has some UI consequences. I've tried (as part of SPR#1072 (2001-11-08)) to
227 * compare what Led does with MSWord2k, and WordPad. WordPad and MSWord2k behave identically, but somewhat inconsistently
228 * within themselves. If you have two paras with different style info, if you select the SINGLE NL between the two and delete
229 * it, the paras are merged with the info from the first paragraph. If you select TWO chars - then the paras are merged
230 * with the info from the second. VERY strange.
231 *
232 * Anyhow - for now - with Led - I've decided to adopt the principle that the info resides (logically) in the
233 * paragraph-termitating character. That implies that when the paragraphs are merged - we copy the info from the
234 * FOLLOWING paragraph. -- LGP 2001-11-08- SPR#1072.
235 */
236 Assert (m->GetEnd () == m_end);
237 if (partitionElt_end != m_end) {
238 MarkerVector markers = CollectAllNonEmptyInRange (m_end, m_end + 1);
239 Assert (markers.size () == 1); // Better be exactly ONE following marker
240 ParagraphInfoMarker* followingMarker = markers[0];
241 Assert (m_end == followingMarker->GetStart ()); // Assure these markers I'm operating on are already continguous
242 Assert (partitionElt_end == partitionElt->GetEnd ());
243 GetTextStore ().SetMarkerEnd (m, partitionElt_end);
244 Assert (followingMarker->GetEnd () >= partitionElt->GetEnd ()); // Cannot set negative length
245 Assert (partitionElt_end == partitionElt->GetEnd ());
246 GetTextStore ().SetMarkerStart (followingMarker, partitionElt_end);
247 IncrementalParagraphInfo followingInfo = IncrementalParagraphInfo (followingMarker->GetInfo ());
248 if (followingMarker->GetLength () == 0) {
249 fMarkersToBeDeleted.AccumulateMarkerForDeletion (followingMarker);
250 Assert (partitionElt_end == m->GetEnd ());
251 CheckForMerges (partitionElt_end);
252 }
253 /*
254 * When we combine two paragraph markers, we must choose which paragraphs info to keep.
255 * We choose to keep the marker info from the second marker. SPR#1038.
256 *
257 * But, do this carefully - just to the last partition element's worth of text - not to the last
258 * paragraph style run element (which could be multiple paragraphs). This was the crux of the fix
259 * in SPR#1072 (2001-11-08).
260 */
261 SetInfoInnerLoop (partitionElt->GetStart (), partitionElt->GetEnd (), followingInfo,
262 UpdateInfo (partitionElt->GetStart (), partitionElt->GetEnd (), LED_TCHAR_OF (""), 0, false, false), nullptr);
263 CullZerod (partitionElt->GetStart ());
264 CullZerod (partitionElt->GetEnd ());
265 }
266 }
267 }
268 }
269}
270
271#if qStroika_Foundation_Debug_AssertionsChecked
272void ParagraphDatabaseRep::Invariant_ () const
273{
274 inheritedMC::Invariant_ ();
275
276 // Check partition in-sync with our marker alignments
277 if (fPartition.get () != nullptr) {
278 // all effected text is diff if we did a replace or not - if no, then from-to,
279 // else from to from+textInserted (cuz from-to deleted)
280 MarkerVector markers = CollectAllInRange_OrSurroundings (0, GetTextStore ().GetLength () + 1);
281 sort (markers.begin (), markers.end (), LessThan<ParagraphInfoMarker> ());
282 PartitionMarker* lastPartitionElt = nullptr;
283 for (auto i = markers.begin (); i != markers.end (); ++i) {
284 ParagraphInfoMarker* m = *i;
285 Assert (m->GetLength () != 0);
286 PartitionMarker* curPartitionElt = fPartition->GetPartitionMarkerContainingPosition (m->GetStart ());
287 Assert (curPartitionElt != lastPartitionElt); // cuz then we would have multiple ParagraphInfos in a single PartitionElt
288 Assert (curPartitionElt->GetStart () == m->GetStart ()); // partElt boundary must always match start
289 Assert (curPartitionElt->GetEnd () <= m->GetEnd ()); // ParagraphInfo contains either one or more (but not less
290 // or partial) partition elts
291 lastPartitionElt = curPartitionElt;
292 }
293 }
294}
295#endif
296
297/*
298 ********************************************************************************
299 ***************************** WordProcessorTable *******************************
300 ********************************************************************************
301 */
302#if qStroika_Frameworks_Led_SupportGDI
303class WordProcessorTable::TableCMD : public InteractiveReplaceCommand, public Memory::UseBlockAllocationIfAppropriate<TableCMD> {
304private:
305 using inherited = InteractiveReplaceCommand;
306
307public:
308 TableCMD (size_t tableAt, size_t tRow, size_t tCol, SavedTextRep* beforeRegion, SavedTextRep* afterRegion, size_t at, const SDKString& cmdName)
309 : inherited (beforeRegion, afterRegion, at, cmdName)
310 , fTableAt (tableAt)
311 , fTableRow (tRow)
312 , fTableColumn (tCol)
313 {
314 }
315
316public:
317 virtual void Do (TextInteractor& interactor) override
318 {
319 WordProcessor& owningWP = dynamic_cast<WordProcessor&> (interactor);
320 WordProcessorTable* aT = owningWP.GetTableAt (fTableAt);
321 AssertNotNull (aT);
322 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*aT, owningWP);
323 WordProcessorTable::TemporarilyAllocateCellWithTablet wp (*aT, fTableRow, fTableColumn);
324 inherited::Do (*wp);
325 }
326 virtual void UnDo (TextInteractor& interactor) override
327 {
328 WordProcessor& owningWP = dynamic_cast<WordProcessor&> (interactor);
329 WordProcessorTable* aT = owningWP.GetTableAt (fTableAt);
330 AssertNotNull (aT);
331 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*aT, owningWP);
332 WordProcessorTable::TemporarilyAllocateCellWithTablet wp (*aT, fTableRow, fTableColumn);
333 inherited::UnDo (*wp);
334 }
335 virtual void ReDo (TextInteractor& interactor) override
336 {
337 WordProcessor& owningWP = dynamic_cast<WordProcessor&> (interactor);
338 WordProcessorTable* aT = owningWP.GetTableAt (fTableAt);
339 AssertNotNull (aT);
340 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*aT, owningWP);
341 WordProcessorTable::TemporarilyAllocateCellWithTablet wp (*aT, fTableRow, fTableColumn);
342 inherited::ReDo (*wp);
343 }
344
345protected:
346 size_t fTableAt;
347 size_t fTableRow;
348 size_t fTableColumn;
349};
350#endif
351
352/*
353 * can only be called inside the context of WordProcessorTable::TemporarilySetOwningWP
354 * since that is what provides our external (window) coordinate system
355 */
356#define Led_Require_CurrentOwningWP() RequireNotNull (fCurrentOwningWP)
357
358/*
359@METHOD: WordProcessorTable::WordProcessorTable
360@DESCRIPTION: <p>You generally don't construct a table object directly, but rather using
361 @'WordProcessor::InsertTable'.</p>
362*/
363WordProcessorTable::WordProcessorTable (AbstractParagraphDatabaseRep* tableOwner, size_t addAt)
364{
365 FinalizeAddition (tableOwner, addAt);
366}
367
368WordProcessorTable::~WordProcessorTable ()
369{
370#if qStroika_Frameworks_Led_SupportGDI
371 if (fCellUpdatePropationUpdater != nullptr) {
372 Assert (false); // This should only happen if an earlier update was aborted (throw). NOT really a bug
373 // if this gets triggered. Just for informational purposes (debugging) only
374 fCellUpdatePropationUpdater->Cancel ();
375 delete fCellUpdatePropationUpdater;
376 }
377 Assert (fCurrentOwningWP == nullptr);
378#endif
379}
380
381void WordProcessorTable::FinalizeAddition (AbstractParagraphDatabaseRep* o, size_t addAt)
382{
383 RequireNotNull (o);
384#if qStroika_Frameworks_Led_SupportGDI
385 TextStore& ts = o->GetTextStore ();
386 TextStore::SimpleUpdater updater (ts, addAt, addAt + 1);
387 ts.ReplaceWithoutUpdate (addAt, addAt, &kEmbeddingSentinelChar, 1);
388 ts.AddMarker (this, addAt, 1, o);
389#endif
390}
391
392Color WordProcessorTable::GetTableBorderColor () const
393{
394 return fBorderColor;
395}
396
397void WordProcessorTable::SetTableBorderColor (Color c)
398{
399 fBorderColor = c;
400}
401
402TWIPS WordProcessorTable::GetTableBorderWidth () const
403{
404 return fBorderWidth;
405}
406
407void WordProcessorTable::SetTableBorderWidth (TWIPS w)
408{
409 fBorderWidth = w;
410}
411
412TWIPS WordProcessorTable::GetColumnWidth (size_t row, size_t column) const
413{
414 if (GetCellFlags (row, column) != ePlainCell) {
415 return TWIPS{0}; // NOT REALLY SURE WHAT THIS SHOULD DO!!!
416 }
417
418 return GetCell (row, column).GetCellXWidth ();
419}
420
421void WordProcessorTable::SetColumnWidth (size_t row, size_t column, TWIPS colWidth)
422{
423 GetCell (row, column).SetCellXWidth (colWidth);
424#if qStroika_Frameworks_Led_SupportGDI
425 InvalidateLayout ();
426#endif
427}
428
429Color WordProcessorTable::GetCellColor (size_t row, size_t column) const
430{
431 return GetCell (row, column).GetBackColor ();
432}
433
434void WordProcessorTable::SetCellColor (size_t row, size_t column, const Color& c)
435{
436 Cell cell = GetCell (row, column);
437 cell.SetBackColor (c);
438}
439
440size_t WordProcessorTable::GetColumnCount (size_t row) const
441{
442 Require (row < GetRowCount ());
443 return fRows[row].fCells.size ();
444}
445
446size_t WordProcessorTable::GetColumnCount (size_t rowStart, size_t rowEnd) const
447{
448 Require (rowStart <= GetRowCount ());
449 Require (rowEnd <= GetRowCount ());
450 size_t colCount = 0;
451 for (size_t ri = rowStart; ri < rowEnd; ++ri) {
452 colCount = max (colCount, fRows[ri].fCells.size ());
453 }
454 return colCount;
455}
456
457void WordProcessorTable::SetColumnCount (size_t row, size_t columns)
458{
459 Require (row < GetRowCount ());
460 size_t curColCount = fRows[row].fCells.size ();
461 if (curColCount != columns) {
462 vector<Cell>& rowCells = fRows[row].fCells;
463 while (curColCount < columns) {
464 Cell cell (*this, ePlainCell);
465 rowCells.push_back (cell);
466 ++curColCount;
467 }
468 while (curColCount > columns) {
469 --curColCount;
470 rowCells.erase (rowCells.begin () + curColCount);
471 }
472 }
473}
474
475/*
476@METHOD: WordProcessorTable::GetCellWordProcessorDatabases
477@ACCESS: public
478@DESCRIPTION: <p>Retrieve the various databases (textstore, style etc) associated
479 with the given cell. Arguments CAN be null. Only non-null pointer values
480 are filled in.</p>
481*/
482void WordProcessorTable::GetCellWordProcessorDatabases (size_t row, size_t column, TextStore** ts, shared_ptr<AbstractStyleDatabaseRep>* styleDatabase,
483 shared_ptr<AbstractParagraphDatabaseRep>* paragraphDatabase,
484 shared_ptr<HidableTextMarkerOwner>* hidableTextDatabase)
485{
486 Require (row < GetRowCount ());
487 Require (column < GetColumnCount (row));
488 const Cell& c = GetCell (row, column);
489 if (ts != nullptr) {
490 *ts = &c.GetTextStore ();
491 }
492 if (styleDatabase != nullptr) {
493 *styleDatabase = c.GetStyleDatabase ();
494 }
495 if (paragraphDatabase != nullptr) {
496 *paragraphDatabase = c.GetParagraphDatabase ();
497 }
498 if (hidableTextDatabase != nullptr) {
499 *hidableTextDatabase = c.GetHidableTextDatabase ();
500 }
501}
502
503/*
504 ********************************************************************************
505 ************************** WordProcessorTable::Cell ****************************
506 ********************************************************************************
507 */
508WordProcessorTable::Cell::Cell (WordProcessorTable& forTable, CellMergeFlags mergeFlags)
509 : fCellMergeFlags{mergeFlags}
510 , fCellRep{mergeFlags == ePlainCell ? (new CellRep (forTable)) : nullptr}
511{
512}
513
514/*
515@METHOD: WordProcessorTable::Cell::GetCellWordProcessorDatabases
516@ACCESS: public
517@DESCRIPTION: <p>Retrieve the various databases (textstore, style etc) associated with the given cell. Arguments
518 CAN be null. Only non-null pointer valeus
519 are filled in.</p>
520*/
521void WordProcessorTable::Cell::GetCellWordProcessorDatabases (TextStore** ts, shared_ptr<AbstractStyleDatabaseRep>* styleDatabase,
522 shared_ptr<AbstractParagraphDatabaseRep>* paragraphDatabase,
523 shared_ptr<HidableTextMarkerOwner>* hidableTextDatabase)
524{
525 Require (fCellMergeFlags == ePlainCell);
526 if (ts != nullptr) {
527 *ts = &GetTextStore ();
528 }
529 if (styleDatabase != nullptr) {
530 *styleDatabase = GetStyleDatabase ();
531 }
532 if (paragraphDatabase != nullptr) {
533 *paragraphDatabase = GetParagraphDatabase ();
534 }
535 if (hidableTextDatabase != nullptr) {
536 *hidableTextDatabase = GetHidableTextDatabase ();
537 }
538}
539
540TextStore& WordProcessorTable::Cell::GetTextStore () const
541{
542 Require (fCellMergeFlags == ePlainCell);
543 EnsureNotNull (fCellRep->fTextStore);
544 return *fCellRep->fTextStore;
545}
546
547shared_ptr<AbstractStyleDatabaseRep> WordProcessorTable::Cell::GetStyleDatabase () const
548{
549 Require (fCellMergeFlags == ePlainCell);
550 return fCellRep->fStyleDatabase;
551}
552
553shared_ptr<AbstractParagraphDatabaseRep> WordProcessorTable::Cell::GetParagraphDatabase () const
554{
555 Require (fCellMergeFlags == ePlainCell);
556 return fCellRep->fParagraphDatabase;
557}
558
559shared_ptr<HidableTextMarkerOwner> WordProcessorTable::Cell::GetHidableTextDatabase () const
560{
561 Require (fCellMergeFlags == ePlainCell);
562 return fCellRep->fHidableTextDatabase;
563}
564
565Color WordProcessorTable::Cell::GetBackColor () const
566{
567 Require (fCellMergeFlags == ePlainCell);
568 return fCellRep->fBackColor;
569}
570
571void WordProcessorTable::Cell::SetBackColor (Color c)
572{
573 Require (fCellMergeFlags == ePlainCell);
574 fCellRep->fBackColor = c;
575}
576
577/*
578 ********************************************************************************
579 ************************ WordProcessorTable::CellRep ***************************
580 ********************************************************************************
581 */
582WordProcessorTable::CellRep::CellRep (WordProcessorTable& forTable)
583 : fForTable (forTable)
584 , fTextStore (nullptr)
585 , fStyleDatabase ()
586 , fParagraphDatabase ()
587 , fHidableTextDatabase ()
588 , fBackColor (Color::kWhite)
589 , fCachedBoundsRect (Led_Rect (0, 0, 0, 0))
590#if qStroika_Frameworks_Led_SupportGDI
591 , fCellXWidth (Led_CvtScreenPixelsToTWIPSH (75)) // This should be overridden someplace - depending on how the table is constructed - but
592// in case its not, and until it is, leave in a vaguely reasonable number - LGP 2003-04-17
593#else
594 , fCellXWidth{75}
595#endif
596{
597 fTextStore = new SimpleTextStore ();
598 fTextStore->AddMarkerOwner (this);
599 fStyleDatabase = MakeSharedPtr<StyleDatabaseRep> (*fTextStore);
600 fParagraphDatabase = MakeSharedPtr<ParagraphDatabaseRep> (*fTextStore);
601 fHidableTextDatabase = MakeSharedPtr<UniformHidableTextMarkerOwner> (*fTextStore);
602}
603
604WordProcessorTable::CellRep::~CellRep ()
605{
606 Require (fStyleDatabase.use_count () == 1); // hack to debug SPR#1465
607 Require (fParagraphDatabase.use_count () == 1); // ''
608 Require (fHidableTextDatabase.use_count () == 1); // ''
609 fStyleDatabase.reset ();
610 fParagraphDatabase.reset ();
611 fHidableTextDatabase.reset ();
612 if (fTextStore != nullptr) {
613 fTextStore->RemoveMarkerOwner (this);
614 }
615 delete fTextStore;
616}
617
618TextStore* WordProcessorTable::CellRep::PeekAtTextStore () const
619{
620 return fTextStore;
621}
622
623void WordProcessorTable::CellRep::AboutToUpdateText (const UpdateInfo& updateInfo)
624{
625 inherited::AboutToUpdateText (updateInfo);
626 if (fForTable.fAllowUpdateInfoPropagationContext and updateInfo.fRealContentUpdate) {
627 if (fForTable.fCellUpdatePropationUpdater != nullptr) {
628 Assert (false); // This should only happen if an earlier update was aborted (throw). NOT really a bug
629 // if this gets triggered. Just for informational purposes (debugging) only
630 fForTable.fCellUpdatePropationUpdater->Cancel ();
631 delete fForTable.fCellUpdatePropationUpdater;
632 fForTable.fCellUpdatePropationUpdater = nullptr;
633 }
634 fForTable.fCellUpdatePropationUpdater =
635 new TextStore::SimpleUpdater (fForTable.GetOwner ()->GetTextStore (), fForTable.GetStart (), fForTable.GetEnd ());
636 }
637}
638
639void WordProcessorTable::CellRep::DidUpdateText (const UpdateInfo& updateInfo) noexcept
640{
641 inherited::DidUpdateText (updateInfo);
642 if (not fForTable.fSuppressCellUpdatePropagationContext) {
643 fForTable.InvalidateLayout ();
644 }
645 if (fForTable.fAllowUpdateInfoPropagationContext and updateInfo.fRealContentUpdate) {
646 AssertNotNull (fForTable.fCellUpdatePropationUpdater);
647 delete fForTable.fCellUpdatePropationUpdater; // NB: This calls the DidUpdate calls for the table itself and its owners...
648 fForTable.fCellUpdatePropationUpdater = nullptr;
649 }
650}
651
652/*
653 ********************************************************************************
654 *************************** WordProcessorTextIOSinkStream **********************
655 ********************************************************************************
656 */
657namespace {
658 inline TWIPS CalcDefaultRHSMargin_ ()
659 {
660 const int kRTF_SPEC_DefaultInches = 6; // HACK - see comments in SinkStreamDestination::SetRightMargin ()
661 int rhsTWIPS = kRTF_SPEC_DefaultInches * 1440;
662 return TWIPS{rhsTWIPS};
663 }
664}
665
666WordProcessorTextIOSinkStream::WordProcessorTextIOSinkStream (TextStore* textStore, const shared_ptr<AbstractStyleDatabaseRep>& textStyleDatabase,
667 const shared_ptr<AbstractParagraphDatabaseRep>& paragraphDatabase,
668 const shared_ptr<HidableTextMarkerOwner>& hidableTextDatabase, size_t insertionStart)
669 : inherited{textStore, textStyleDatabase, insertionStart}
670 , fOverwriteTableMode{false}
671 ,
672#if !qStroika_Frameworks_Led_NestedTablesSupported
673 fNoTablesAllowed{false}
674 ,
675#endif
676 fSavedContexts{}
677 , fParagraphDatabase{paragraphDatabase}
678 , fHidableTextDatabase{hidableTextDatabase}
679 , fSavedParaInfo{}
680 , fNewParagraphInfo{}
681 , fTextHidden{false}
682 , fHidableTextRuns{}
683 , fEndOfBuffer{false}
684 , fIgnoreLastParaAttributes{false}
685 , fCurrentTable{nullptr}
686 , fCurrentTableCellWidths{}
687 , fCurrentTableCellColor{Color::kWhite}
688 , fCurrentTableColSpanArray{}
689 , fTableStack{}
690 , fNextTableRow{0}
691 , fNextTableCell{0}
692 , fCurrentTableCell{size_t (-1)}
694 , fTableOpenLevel{0}
695 , fTableRowOpen{false}
696 , fTableCellOpen{false}
697#endif
698{
699 CTOR_COMMON ();
700}
701
702#if qStroika_Frameworks_Led_SupportGDI
703WordProcessorTextIOSinkStream::WordProcessorTextIOSinkStream (WordProcessor* wp, size_t insertionStart)
704 : inherited{&wp->GetTextStore (), wp->GetStyleDatabase (), insertionStart}
705 , fOverwriteTableMode{false}
706 ,
707#if !qStroika_Frameworks_Led_NestedTablesSupported
708 fNoTablesAllowed{false}
709 ,
710#endif
711 fSavedContexts{}
712 , fParagraphDatabase{wp->GetParagraphDatabase ()}
713 , fHidableTextDatabase{wp->GetHidableTextDatabase ()}
714 , fSavedParaInfo{}
715 , fNewParagraphInfo{}
716 , fTextHidden{false}
717 , fHidableTextRuns{}
718 , fEndOfBuffer{false}
719 , fIgnoreLastParaAttributes{false}
720 , fCurrentTable{nullptr}
721 , fCurrentTableCellWidths{}
722 , fCurrentTableCellColor{Color::kWhite}
723 , fCurrentTableColSpanArray{}
724 , fTableStack{}
725 , fNextTableRow{0}
726 , fNextTableCell{0}
727 , fCurrentTableCell{size_t (-1)}
729 , fTableOpenLevel{0}
730 , fTableRowOpen{false}
731 , fTableCellOpen{false}
732#endif
733{
734 CTOR_COMMON ();
735}
736#endif
737
738WordProcessorTextIOSinkStream::~WordProcessorTextIOSinkStream ()
739{
740#if qStroika_Foundation_Debug_AssertionsChecked
741 Assert (fTableOpenLevel == 0);
742 Assert (not fTableRowOpen);
743 Assert (not fTableCellOpen);
744#endif
745 try {
746 Flush ();
747 Ensure (GetCachedTextSize () == 0); // If flush succeeds, then these must be zero
748 Ensure (fSavedParaInfo.size () == 0);
749 }
750 catch (...) {
751 // ignore, cuz cannot fail out of DTOR
752 }
753}
754
755void WordProcessorTextIOSinkStream::CTOR_COMMON ()
756{
757 fNewParagraphInfo.SetJustification (eLeftJustify);
758 fNewParagraphInfo.SetTabStopList (StandardTabStopList{});
759 fNewParagraphInfo.SetFirstIndent (TWIPS{0});
760 fNewParagraphInfo.SetMargins (TWIPS{0}, CalcDefaultRHSMargin_ ());
761 fNewParagraphInfo.SetListStyle (eListStyle_None);
762 fNewParagraphInfo.SetSpaceBefore (TWIPS{0});
763 fNewParagraphInfo.SetSpaceAfter (TWIPS{0});
764}
765
766void WordProcessorTextIOSinkStream::AppendText (const Led_tChar* text, size_t nTChars, const FontSpecification* fontSpec)
767{
768 RequireNotNull (text);
769 inherited::AppendText (text, nTChars, fontSpec);
770 if (fSavedParaInfo.size () == 0) {
771 fSavedParaInfo.push_back (ParaInfoNSize (fNewParagraphInfo, nTChars));
772 }
773 else if (fSavedParaInfo.back ().first == fNewParagraphInfo) {
774 fSavedParaInfo.back ().second += nTChars;
775 }
776 else {
777 fSavedParaInfo.push_back (ParaInfoNSize (fNewParagraphInfo, nTChars));
778 }
779 if (fHidableTextRuns.size () != 0 and fHidableTextRuns.back ().fData == fTextHidden) {
780 fHidableTextRuns.back ().fElementLength += nTChars;
781 }
782 else {
783 fHidableTextRuns.push_back (DiscontiguousRunElement<bool> (0, nTChars, fTextHidden));
784 }
785}
786
787#if qStroika_Frameworks_Led_SupportGDI
788void WordProcessorTextIOSinkStream::AppendEmbedding (SimpleEmbeddedObjectStyleMarker* embedding)
789{
790 RequireNotNull (embedding);
791 if (GetCachedTextSize () != 0) {
792 Flush ();
793 }
794 size_t whereToStartHiddenArea = GetInsertionStart ();
795 inherited::AppendEmbedding (embedding);
796 if (fTextHidden) {
797 fHidableTextDatabase->MakeRegionHidable (whereToStartHiddenArea, whereToStartHiddenArea + 1);
798 }
799}
800#endif
801
802void WordProcessorTextIOSinkStream::AppendSoftLineBreak ()
803{
804 AppendText (&kSoftLineBreakChar, 1, nullptr);
805}
806
807void WordProcessorTextIOSinkStream::SetJustification (Justification justification)
808{
809 fNewParagraphInfo.SetJustification (justification);
810}
811
812void WordProcessorTextIOSinkStream::SetStandardTabStopList (const StandardTabStopList& tabStops)
813{
814 fNewParagraphInfo.SetTabStopList (tabStops);
815}
816
817void WordProcessorTextIOSinkStream::SetFirstIndent (TWIPS tx)
818{
819 fNewParagraphInfo.SetFirstIndent (tx);
820}
821
822void WordProcessorTextIOSinkStream::SetLeftMargin (TWIPS lhs)
823{
824 fNewParagraphInfo.SetMargins (lhs, max (TWIPS (lhs + 1), fNewParagraphInfo.GetRightMargin ()));
825}
826
827void WordProcessorTextIOSinkStream::SetRightMargin (TWIPS rhs)
828{
829 fNewParagraphInfo.SetMargins (fNewParagraphInfo.GetLeftMargin (), max (TWIPS (fNewParagraphInfo.GetLeftMargin () + 1), rhs));
830}
831
832void WordProcessorTextIOSinkStream::SetSpaceBefore (TWIPS sb)
833{
834 fNewParagraphInfo.SetSpaceBefore (sb);
835}
836
837void WordProcessorTextIOSinkStream::SetSpaceAfter (TWIPS sa)
838{
839 fNewParagraphInfo.SetSpaceAfter (sa);
840}
841
842void WordProcessorTextIOSinkStream::SetLineSpacing (LineSpacing sl)
843{
844 fNewParagraphInfo.SetLineSpacing (sl);
845}
846
847void WordProcessorTextIOSinkStream::SetTextHidden (bool hidden)
848{
849 fTextHidden = hidden;
850}
851
852void WordProcessorTextIOSinkStream::StartTable ()
853{
854#if qStroika_Foundation_Debug_AssertionsChecked
855 // NB: because of nested tables - we COULD be starting a table inside another table.
856 // If we are - then we must be inside a table cell (so row/cell must be open). Otherwise, they
857 // must both be closed
858 if (fTableOpenLevel == 0) {
859 // we are NOT in a non-nested case, and so NONE should be open
860 Assert (fTableOpenLevel == 0);
861 Assert (not fTableRowOpen);
862 Assert (not fTableCellOpen);
863 }
864 else {
865 // we must be in a nested case, and so BOTH should be open
866 Assert (fTableRowOpen);
867 Assert (fTableCellOpen);
868 }
869 // either way - one we start a new table - they must both be closed
870 fTableRowOpen = false;
871 fTableCellOpen = false;
872 ++fTableOpenLevel;
873#endif
874#if !qStroika_Frameworks_Led_NestedTablesSupported
875 if (GetNoTablesAllowed ()) {
876 return;
877 }
878#endif
879
880 if (GetCachedTextSize () != 0) {
881 // Because we address directly into the textstore here - we need any pending writes to actually go in so the right stuff
882 // is already in the textstore.
883 Flush ();
884 }
885
886#if qStroika_Frameworks_Led_NestedTablesSupported
887 if (fCurrentTable != nullptr) {
888 // when we support nested tables for REAL - we need to also save other context like fCurrentTableCellWidths / fCurrentTableColSpanArray
889 fTableStack.push_back (fCurrentTable);
890 }
891 fCurrentTable = nullptr;
892#endif
893
894 if (GetOverwriteTableMode ()) {
895 TextStore& ts = GetTextStore ();
896 size_t realCoordStart = GetInsertionStart ();
897 MarkerOfATypeMarkerSink<WordProcessorTable> maybeTable;
898 Assert (realCoordStart <= ts.GetEnd ());
899 ts.CollectAllMarkersInRangeInto (ts.FindPreviousCharacter (realCoordStart), realCoordStart, fParagraphDatabase.get (), maybeTable);
900 if (maybeTable.fResult != nullptr) {
901 fCurrentTable = maybeTable.fResult;
902 size_t rowSelStart = 0;
903 size_t colSelStart = 0;
904 fCurrentTable->GetCellSelection (&rowSelStart, nullptr, &colSelStart, nullptr);
905 fNextTableRow = rowSelStart;
906 fNextTableCell = colSelStart;
907 fCurrentTableCell = size_t (-1);
908 }
909 }
910
911 if (fCurrentTable == nullptr) {
912 fCurrentTable = new WordProcessorTable (fParagraphDatabase.get (), current_offset () + GetOriginalStart ());
913 SetInsertionStart (GetInsertionStart () + 1); // cuz we added a row which adds a sentinel
914 fNextTableRow = 0;
915 fNextTableCell = 0;
916 fCurrentTableCell = size_t (-1);
917 }
918}
919
920void WordProcessorTextIOSinkStream::EndTable ()
921{
922#if qStroika_Foundation_Debug_AssertionsChecked
923 Require (fTableOpenLevel >= 1);
924 Require (not fTableRowOpen); // caller must close row/cell before closing table
925 Require (not fTableCellOpen);
926 // if this is a nested table, then the parent scope must have had an open cell/row, and otherwise,
927 // NO
928 --fTableOpenLevel;
929 if (fTableOpenLevel > 0) {
930 fTableRowOpen = true;
931 fTableCellOpen = true;
932 }
933 else {
934 fTableRowOpen = false;
935 fTableCellOpen = false;
936 }
937#endif
938#if !qStroika_Frameworks_Led_NestedTablesSupported
939 if (GetNoTablesAllowed ()) {
940 AppendText (LED_TCHAR_OF ("\n"), 1, nullptr);
941 return;
942 }
943#endif
944 if (fCurrentTable != nullptr) {
945 // Be careful to protect against unbalanced start/ends cuz of bad StyledTextIO input data
946 fCurrentTable = nullptr;
947#if qStroika_Frameworks_Led_NestedTablesSupported
948 if (not fTableStack.empty ()) {
949 fCurrentTable = fTableStack.back ();
950 fTableStack.pop_back ();
951 }
952#endif
953 }
954}
955
956void WordProcessorTextIOSinkStream::StartTableRow ()
957{
958#if qStroika_Foundation_Debug_AssertionsChecked
959 Require (fTableOpenLevel >= 1);
960 Require (not fTableRowOpen);
961 Require (not fTableCellOpen);
962 fTableRowOpen = true;
963#endif
964#if !qStroika_Frameworks_Led_NestedTablesSupported
965 if (GetNoTablesAllowed ()) {
966 fNextTableCell = 0;
967 AppendText (LED_TCHAR_OF ("\n"), 1, nullptr);
968 return;
969 }
970#endif
971 RequireNotNull (fCurrentTable);
972 ++fNextTableRow;
973 if (GetOverwriteTableMode ()) {
974 if (fNextTableRow > fCurrentTable->GetRowCount ()) {
975 fCurrentTable->InsertRow (fNextTableRow - 1, 1);
976 }
977 size_t colSelStart = 0;
978 fCurrentTable->GetCellSelection (nullptr, nullptr, &colSelStart, nullptr);
979 fNextTableCell = min (fCurrentTable->GetColumnCount (fNextTableRow - 1) - 1, colSelStart);
980 }
981 else {
982 fNextTableCell = 0;
983 fCurrentTable->InsertRow (fNextTableRow - 1, 1);
984 }
985 fCurrentTableColSpanArray.clear ();
986}
987
988void WordProcessorTextIOSinkStream::EndTableRow ()
989{
990#if qStroika_Foundation_Debug_AssertionsChecked
991 Require (fTableOpenLevel >= 1);
992 Require (fTableRowOpen);
993 Require (not fTableCellOpen);
994 fTableRowOpen = false;
995#endif
996#if !qStroika_Frameworks_Led_NestedTablesSupported
997 if (GetNoTablesAllowed ()) {
998 return;
999 }
1000#endif
1001 AssertNotNull (fCurrentTable);
1002 size_t nCellsInThisRow = fCurrentTableCellWidths.size ();
1003
1004 while (nCellsInThisRow > fCurrentTableColSpanArray.size ()) {
1005 // treat missing startcell/endcell pairs as just colWidth = 1
1006 fCurrentTableColSpanArray.push_back (1);
1007 }
1008
1009 if (nCellsInThisRow != fCurrentTableColSpanArray.size ()) {
1010 // For HTML readers - they don't (generally) call SetCellWidths - and so
1011 // we don't get extra cells generated in that way. -- LGP 2003-05-22
1012 nCellsInThisRow = min (nCellsInThisRow, fCurrentTableColSpanArray.size ());
1013 }
1014 fCurrentTable->SetColumnCount (fNextTableRow - 1, max (nCellsInThisRow, fCurrentTable->GetColumnCount (fNextTableRow - 1)));
1015
1016 size_t col = 0;
1017 for (size_t cellIdx = 0; cellIdx < nCellsInThisRow; ++cellIdx) {
1018 size_t nColsInThisCell = fCurrentTableColSpanArray[cellIdx];
1019 Assert (nColsInThisCell >= 1);
1020 Assert (col < fCurrentTable->GetColumnCount ());
1021
1022 TWIPS thisCellWidth = fCurrentTableCellWidths[cellIdx];
1023 if (nColsInThisCell == 1) {
1024 Assert (fNextTableRow > 0);
1025 fCurrentTable->SetColumnWidth (fNextTableRow - 1, col, thisCellWidth);
1026 }
1027 else {
1028 // not sure what to do in this case. All we know is the width of some SET of columns. We don't know how to apportion it between
1029 // columns. Assume that if it matters - it was specified elsewhere. I COULD use the info based on the existing colwidths
1030 // to at least set properly the last colwidth...
1031 TWIPS prevColWidths = TWIPS{0};
1032 for (size_t i = col; i < col + nColsInThisCell; ++i) {
1033 Assert (fNextTableRow > 0);
1034 prevColWidths += fCurrentTable->GetColumnWidth (fNextTableRow - 1, i);
1035 }
1036 if (prevColWidths < thisCellWidth) {
1037 Assert (fNextTableRow > 0);
1038 fCurrentTable->SetColumnWidth (fNextTableRow - 1, col, thisCellWidth - prevColWidths);
1039 }
1040 }
1041
1042 col += nColsInThisCell;
1043 }
1044}
1045
1046void WordProcessorTextIOSinkStream::StartTableCell (size_t colSpan)
1047{
1048#if qStroika_Foundation_Debug_AssertionsChecked
1049
1050 Require (fTableOpenLevel >= 1);
1051 Require (fTableRowOpen);
1052 Require (not fTableCellOpen);
1053 fTableCellOpen = true;
1054#endif
1055
1056#if !qStroika_Frameworks_Led_NestedTablesSupported
1057 if (GetNoTablesAllowed ()) {
1058 if (fNextTableCell >= 1) {
1059 AppendText (LED_TCHAR_OF ("\t"), 1, nullptr);
1060 }
1061 ++fNextTableCell;
1062 return;
1063 }
1064#endif
1065 Require (colSpan >= 1);
1066
1067 fCurrentTableCell = fNextTableCell;
1068 fCurrentTableColSpanArray.push_back (colSpan);
1069 fNextTableCell += colSpan;
1070
1071 AssertNotNull (fCurrentTable);
1072 fCurrentTable->SetColumnCount (fNextTableRow - 1, max (fNextTableCell, fCurrentTable->GetColumnCount (fNextTableRow - 1)));
1073
1074 Assert (fNextTableRow > 0);
1075 Assert (fNextTableCell > 0);
1076
1077 TextStore* ts = nullptr;
1078 shared_ptr<AbstractStyleDatabaseRep> styleDatabase;
1079 shared_ptr<AbstractParagraphDatabaseRep> paragraphDatabase;
1080 shared_ptr<HidableTextMarkerOwner> hidableTextDatabase;
1081 fCurrentTable->GetCellWordProcessorDatabases (fNextTableRow - 1, fCurrentTableCell, &ts, &styleDatabase, &paragraphDatabase, &hidableTextDatabase);
1082 PushContext (ts, styleDatabase, paragraphDatabase, hidableTextDatabase, 0);
1083 if (GetOverwriteTableMode ()) {
1084 ts->Replace (0, ts->GetLength (), nullptr, 0);
1085 }
1086}
1087
1088void WordProcessorTextIOSinkStream::EndTableCell ()
1089{
1090#if qStroika_Foundation_Debug_AssertionsChecked
1091 Require (fTableOpenLevel >= 1);
1092 Require (fTableRowOpen);
1093 Require (fTableCellOpen);
1094 fTableCellOpen = false;
1095#endif
1096#if !qStroika_Frameworks_Led_NestedTablesSupported
1097 if (GetNoTablesAllowed ()) {
1098 return;
1099 }
1100#endif
1101 Flush ();
1102 fCurrentTable->SetCellColor (fNextTableRow - 1, fCurrentTableCell, fCurrentTableCellColor);
1103 PopContext ();
1104}
1105
1106void WordProcessorTextIOSinkStream::SetListStyle (ListStyle listStyle)
1107{
1108 fNewParagraphInfo.SetListStyle (listStyle);
1109}
1110
1111void WordProcessorTextIOSinkStream::SetListIndentLevel (unsigned char indentLevel)
1112{
1113 fNewParagraphInfo.SetListIndentLevel (indentLevel);
1114}
1115
1116void WordProcessorTextIOSinkStream::SetIgnoreLastParaAttributes (bool ignoreLastParaAttributes)
1117{
1118 fIgnoreLastParaAttributes = ignoreLastParaAttributes;
1119}
1120
1121void WordProcessorTextIOSinkStream::SetTableBorderColor (Color c)
1122{
1123 if (fCurrentTable != nullptr) {
1124 fCurrentTable->SetTableBorderColor (c);
1125 }
1126}
1127
1128void WordProcessorTextIOSinkStream::SetTableBorderWidth (TWIPS bWidth)
1129{
1130 if (fCurrentTable != nullptr) {
1131 fCurrentTable->SetTableBorderWidth (bWidth);
1132 }
1133}
1134
1135void WordProcessorTextIOSinkStream::SetCellWidths (const vector<TWIPS>& cellWidths)
1136{
1137 if (fCurrentTable != nullptr) {
1138 fCurrentTableCellWidths = cellWidths;
1139 }
1140}
1141
1142void WordProcessorTextIOSinkStream::SetCellBackColor (const Color c)
1143{
1144 if (fCurrentTable != nullptr) {
1145 fCurrentTableCellColor = c;
1146 }
1147}
1148
1149void WordProcessorTextIOSinkStream::SetDefaultCellMarginsForCurrentRow (TWIPS top, TWIPS left, TWIPS bottom, TWIPS right)
1150{
1151 // RTF spec seems to indicate that default cell margins can be specified on
1152 // a per-row basis, whereas the MSWord XP UI seems to do it on a per-table basis. Anyhow, no
1153 // matter - as for now - all WE support is on a per table basis, so just update that
1154 // -- LGP 2003-04-30
1155 if (fCurrentTable != nullptr) {
1156 fCurrentTable->SetDefaultCellMargins (top, left, bottom, right);
1157 }
1158}
1159
1160void WordProcessorTextIOSinkStream::SetDefaultCellSpacingForCurrentRow (TWIPS top, TWIPS left, TWIPS bottom, TWIPS right)
1161{
1162 // RTF spec seems to indicate that default cell spacing can be specified on
1163 // a per-row basis, whereas the MSWord XP UI seems to do it on a per-table basis. Anyhow, no
1164 // matter - as for now - all WE support is on a per table basis, so just update that
1165 // Also, the spec allows for a separate value for top/left/bottom/right. Our WP table layout
1166 // class just uses a single spacing value, so compress them into one for now... (see SPR#1396)
1167 // -- LGP 2003-04-30
1168 if (fCurrentTable != nullptr) {
1169 TWIPS aveSpacing = TWIPS ((top + left + bottom + right) / 4);
1170 fCurrentTable->SetCellSpacing (aveSpacing);
1171 }
1172}
1173
1174void WordProcessorTextIOSinkStream::PushContext (TextStore* ts, const shared_ptr<AbstractStyleDatabaseRep>& textStyleDatabase,
1175 const shared_ptr<AbstractParagraphDatabaseRep>& paragraphDatabase,
1176 const shared_ptr<HidableTextMarkerOwner>& hidableTextDatabase, size_t insertionStart)
1177{
1178 if (GetCachedTextSize () != 0) { // must flush before setting/popping context
1179 Flush ();
1180 }
1181 inherited::PushContext (ts, textStyleDatabase, insertionStart);
1182 Context c;
1183 c.fHidableTextDatabase = fHidableTextDatabase;
1184 c.fParagraphDatabase = fParagraphDatabase;
1185 fSavedContexts.push_back (c);
1186 fHidableTextDatabase = hidableTextDatabase;
1187 fParagraphDatabase = paragraphDatabase;
1188}
1189
1190void WordProcessorTextIOSinkStream::PopContext ()
1191{
1192 Require (GetCachedTextSize () == 0); // must flush before setting/popping context
1193 Require (not fSavedContexts.empty ());
1194 inherited::PopContext ();
1195 fHidableTextDatabase = fSavedContexts.back ().fHidableTextDatabase;
1196 fParagraphDatabase = fSavedContexts.back ().fParagraphDatabase;
1197 fSavedContexts.pop_back ();
1198}
1199
1200void WordProcessorTextIOSinkStream::EndOfBuffer ()
1201{
1202 fEndOfBuffer = true;
1203}
1204
1205void WordProcessorTextIOSinkStream::Flush ()
1206{
1207 size_t stripParaCharCount = 0;
1208 if (fEndOfBuffer and fIgnoreLastParaAttributes) {
1209 const vector<Led_tChar>& t = GetCachedText ();
1210 {
1211 for (auto i = t.rbegin (); i != t.rend (); ++i) {
1212 if (*i == '\n') {
1213 break;
1214 }
1215 else {
1216 ++stripParaCharCount;
1217 }
1218 }
1219 }
1220 }
1221
1222 size_t dataSize = GetCachedTextSize ();
1223 size_t whereToInsert = GetInsertionStart () - dataSize;
1224 inherited::Flush ();
1225
1226 // Flush the cached paragraph info
1227 {
1229 [[maybe_unused]] size_t curInsert = whereToInsert;
1230 for (auto i = fSavedParaInfo.begin (); i != fSavedParaInfo.end (); ++i) {
1231 curInsert += (*i).second;
1232 }
1233 Assert (curInsert == GetInsertionStart ());
1234 }
1235 if (stripParaCharCount != 0) {
1236 Assert (fSavedParaInfo.size () > 0);
1237 vector<ParaInfoNSize>::iterator i = fSavedParaInfo.end () - 1;
1238 if ((*i).second > stripParaCharCount) {
1239 (*i).second -= stripParaCharCount;
1240 }
1241 else {
1242 fSavedParaInfo.resize (fSavedParaInfo.size () - 1);
1243 }
1244 }
1245 fParagraphDatabase->SetParagraphInfo (whereToInsert, fSavedParaInfo);
1246 fSavedParaInfo.clear ();
1247 }
1248
1249 /*
1250 * Somewhat of an inelegant hack to work around SPR#1074. The issue is that when we read in an RTF (or other)
1251 * document, we don't SET the region just past the end of the document. Still - we use this internally to
1252 * store the paragraph info of the last paragraph if it has zero characters (really just if it has
1253 * no terminating NL).
1254 *
1255 * For a future release, consider finding a more elegant solution to this. This is the most we can hope
1256 * todo so late in the development cycle.
1257 *
1258 * LGP 2001-11-09 - SPR#1074.
1259 */
1260 if (fEndOfBuffer and not fIgnoreLastParaAttributes and whereToInsert == fParagraphDatabase->GetTextStore ().GetEnd ()) {
1261 fParagraphDatabase->SetParagraphInfo (whereToInsert, 1,
1262 IncrementalParagraphInfo (fParagraphDatabase->GetParagraphInfo (
1263 fParagraphDatabase->GetTextStore ().FindPreviousCharacter (whereToInsert))));
1264 }
1265
1266 // Flush the cached hidable text info
1267 {
1268 vector<pair<size_t, size_t>> hidePairs;
1269 size_t curInsert = whereToInsert;
1270 for (auto i = fHidableTextRuns.begin (); i != fHidableTextRuns.end (); ++i) {
1271 if ((*i).fData) {
1272 hidePairs.push_back (pair<size_t, size_t> (curInsert, curInsert + (*i).fElementLength));
1273 }
1274 curInsert += (*i).fElementLength;
1275 }
1276 for (auto i = hidePairs.rbegin (); i != hidePairs.rend (); ++i) {
1277 fHidableTextDatabase->MakeRegionHidable ((*i).first, (*i).second);
1278 }
1279 fHidableTextRuns.clear ();
1280 }
1281}
1282
1283/*
1284 ********************************************************************************
1285 *************************** WordProcessorTextIOSrcStream ***********************
1286 ********************************************************************************
1287 */
1288WordProcessorTextIOSrcStream::WordProcessorTextIOSrcStream (TextStore* textStore, const shared_ptr<AbstractStyleDatabaseRep>& textStyleDatabase,
1289 const shared_ptr<AbstractParagraphDatabaseRep>& paragraphDatabase,
1290 const shared_ptr<HidableTextMarkerOwner>& hidableTextDatabase,
1291 size_t selectionStart, size_t selectionEnd)
1292 : inherited{textStore, textStyleDatabase, selectionStart, selectionEnd}
1293 , fParagraphDatabase{paragraphDatabase}
1294 , fHidableTextRuns{}
1295{
1296
1297 if (hidableTextDatabase.get () != nullptr) {
1298 fHidableTextRuns = hidableTextDatabase->GetHidableRegions (selectionStart, selectionEnd);
1299 }
1300}
1301
1302#if qStroika_Frameworks_Led_SupportGDI
1303WordProcessorTextIOSrcStream::WordProcessorTextIOSrcStream (WordProcessor* textImager, size_t selectionStart, size_t selectionEnd)
1304 : inherited (textImager, selectionStart, selectionEnd)
1305 , fParagraphDatabase (textImager->GetParagraphDatabase ())
1306 , fHidableTextRuns ()
1307{
1308 shared_ptr<HidableTextMarkerOwner> hidableTextDatabase = textImager->GetHidableTextDatabase ();
1309 if (hidableTextDatabase.get () != nullptr) {
1310 fHidableTextRuns = hidableTextDatabase->GetHidableRegions (selectionStart, selectionEnd);
1311 }
1312}
1313#endif
1314
1315Justification WordProcessorTextIOSrcStream::GetJustification () const
1316{
1317 if (fParagraphDatabase.get () == nullptr) {
1318 return inherited::GetJustification ();
1319 }
1320 else {
1321 return fParagraphDatabase->GetParagraphInfo (GetCurOffset ()).GetJustification ();
1322 }
1323}
1324
1325StandardTabStopList WordProcessorTextIOSrcStream::GetStandardTabStopList () const
1326{
1327 if (fParagraphDatabase.get () == nullptr) {
1328 return inherited::GetStandardTabStopList ();
1329 }
1330 else {
1331 return fParagraphDatabase->GetParagraphInfo (GetCurOffset ()).GetTabStopList ();
1332 }
1333}
1334
1335TWIPS WordProcessorTextIOSrcStream::GetFirstIndent () const
1336{
1337 if (fParagraphDatabase.get () == nullptr) {
1338 return inherited::GetFirstIndent ();
1339 }
1340 else {
1341 return fParagraphDatabase->GetParagraphInfo (GetCurOffset ()).GetFirstIndent ();
1342 }
1343}
1344
1345void WordProcessorTextIOSrcStream::GetMargins (TWIPS* lhs, TWIPS* rhs) const
1346{
1347 RequireNotNull (lhs);
1348 RequireNotNull (rhs);
1349 if (fParagraphDatabase.get () == nullptr) {
1350 inherited::GetMargins (lhs, rhs);
1351 }
1352 else {
1353 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (GetCurOffset ());
1354 *lhs = pi.GetLeftMargin ();
1355 *rhs = pi.GetRightMargin ();
1356 }
1357}
1358
1359/*
1360@METHOD: WordProcessor::WordProcessorTextIOSrcStream::GetSpaceBefore
1361@DESCRIPTION:
1362*/
1363TWIPS WordProcessorTextIOSrcStream::GetSpaceBefore () const
1364{
1365 if (fParagraphDatabase.get () == nullptr) {
1366 return inherited::GetSpaceBefore ();
1367 }
1368 else {
1369 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (GetCurOffset ());
1370 return pi.GetSpaceBefore ();
1371 }
1372}
1373
1374/*
1375@METHOD: WordProcessor::WordProcessorTextIOSrcStream::GetSpaceAfter
1376@DESCRIPTION:
1377*/
1378TWIPS WordProcessorTextIOSrcStream::GetSpaceAfter () const
1379{
1380 if (fParagraphDatabase.get () == nullptr) {
1381 return inherited::GetSpaceAfter ();
1382 }
1383 else {
1384 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (GetCurOffset ());
1385 return pi.GetSpaceAfter ();
1386 }
1387}
1388
1389/*
1390@METHOD: WordProcessor::WordProcessorTextIOSrcStream::GetLineSpacing
1391@DESCRIPTION:
1392*/
1393LineSpacing WordProcessorTextIOSrcStream::GetLineSpacing () const
1394{
1395 if (fParagraphDatabase.get () == nullptr) {
1396 return inherited::GetLineSpacing ();
1397 }
1398 else {
1399 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (GetCurOffset ());
1400 return pi.GetLineSpacing ();
1401 }
1402}
1403
1404/*
1405@METHOD: WordProcessor::WordProcessorTextIOSrcStream::GetListStyleInfo
1406@DESCRIPTION:
1407*/
1408void WordProcessorTextIOSrcStream::GetListStyleInfo (ListStyle* listStyle, unsigned char* indentLevel) const
1409{
1410 RequireNotNull (listStyle);
1411 RequireNotNull (indentLevel);
1412 if (fParagraphDatabase.get () == nullptr) {
1413 inherited::GetListStyleInfo (listStyle, indentLevel);
1414 }
1415 else {
1416 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (GetCurOffset ());
1417 *listStyle = pi.GetListStyle ();
1418 *indentLevel = pi.GetListIndentLevel ();
1419 }
1420}
1421
1422Led_tChar WordProcessorTextIOSrcStream::GetSoftLineBreakCharacter () const
1423{
1424 return kSoftLineBreakChar;
1425}
1426
1427DiscontiguousRun<bool> WordProcessorTextIOSrcStream::GetHidableTextRuns () const
1428{
1429 return fHidableTextRuns;
1430}
1431
1432#if qStroika_Frameworks_Led_SupportTables
1433WordProcessorTextIOSrcStream::Table* WordProcessorTextIOSrcStream::GetTableAt (size_t at) const
1434{
1435 Require (fParagraphDatabase.get () != nullptr);
1436 TextStore& ts = fParagraphDatabase->GetTextStore ();
1437 size_t realCoordStart = GetEmbeddingMarkerPosOffset () + at;
1438 MarkerOfATypeMarkerSink<WordProcessorTable> maybeTable;
1439 ts.CollectAllMarkersInRangeInto (realCoordStart, realCoordStart + 1, fParagraphDatabase.get (), maybeTable);
1440 if (maybeTable.fResult == nullptr) {
1441 return nullptr;
1442 }
1443 else {
1444 /*
1445 * Make sure we create a TableIOMapper for just the subset of the document selected. For now, this just
1446 * applies to ROWS (no support yet for selecting columns).
1447 */
1448 [[maybe_unused]] size_t realCoordEnd = min (maybeTable.fResult->GetEnd (), GetSelEnd ());
1449 Assert (realCoordStart < realCoordEnd);
1450 if (fUseTableSelection) {
1451 size_t rowSelStart = 0;
1452 size_t rowSelEnd = 0;
1453 size_t colSelStart = 0;
1454 size_t colSelEnd = 0;
1455 maybeTable.fResult->GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
1456 return new TableIOMapper{*maybeTable.fResult, rowSelStart, rowSelEnd, colSelStart, colSelEnd};
1457 }
1458 else {
1459 return new TableIOMapper{*maybeTable.fResult};
1460 }
1461 }
1462}
1463#endif
1464
1465void WordProcessorTextIOSrcStream::SummarizeFontAndColorTable (set<SDKString>* fontNames, set<Color>* colorsUsed) const
1466{
1467 inherited::SummarizeFontAndColorTable (fontNames, colorsUsed);
1468
1469 {
1470 TextStore& ts = fParagraphDatabase->GetTextStore ();
1471 MarkersOfATypeMarkerSink2Vector<WordProcessorTable> tables;
1472 ts.CollectAllMarkersInRangeInto (GetSelStart (), GetSelEnd (), fParagraphDatabase.get (), tables);
1473 for (auto i = tables.fResult.begin (); i != tables.fResult.end (); ++i) {
1474 TableIOMapper tiom (**i);
1475 size_t rows = tiom.GetRows ();
1476 for (size_t r = 0; r < rows; ++r) {
1477 size_t columns = tiom.GetColumns (r);
1478 for (size_t c = 0; c < columns; ++c) {
1479 unique_ptr<StyledTextIOWriter::SrcStream> subSrcStream (tiom.MakeCellSubSrcStream (r, c));
1480 if (subSrcStream.get () != nullptr) {
1481 subSrcStream.get ()->SummarizeFontAndColorTable (fontNames, colorsUsed);
1482 }
1483 }
1484 if (colorsUsed != nullptr) {
1485 using CellInfo = StyledTextIOWriter::SrcStream::Table::CellInfo;
1486 vector<CellInfo> cellInfos;
1487 tiom.GetRowInfo (r, &cellInfos);
1488 for (auto ci = cellInfos.begin (); ci != cellInfos.end (); ++ci) {
1489 colorsUsed->insert ((*ci).f_clcbpat);
1490 }
1491 }
1492 }
1493 }
1494 }
1495}
1496
1497/*
1498 ********************************************************************************
1499 *********** WordProcessor::WordProcessorTextIOSrcStream::TableIOMapper *********
1500 ********************************************************************************
1501 */
1502WordProcessorTextIOSrcStream::TableIOMapper::TableIOMapper (WordProcessorTable& realTable, size_t startRow, size_t endRow, size_t startCol, size_t endCol)
1503 : fRealTable{realTable}
1504 , fStartRow{startRow}
1505 , fEndRow{endRow}
1506 , fStartCol{startCol}
1507 , fEndCol{endCol}
1508{
1509 if (fEndRow == static_cast<size_t> (-1)) {
1510 fEndRow = fRealTable.GetRowCount ();
1511 }
1512 if (fEndCol == static_cast<size_t> (-1)) {
1513 fEndCol = fRealTable.GetColumnCount ();
1514 }
1515
1516 Ensure (fStartRow < fEndRow); // must be at least one row
1517 Ensure (fStartCol < fEndCol); // ditto for columns
1518 Ensure (fEndRow <= fRealTable.GetRowCount ());
1519 Ensure (fEndCol <= fRealTable.GetColumnCount ());
1520}
1521
1522size_t WordProcessorTextIOSrcStream::TableIOMapper::GetRows () const
1523{
1524 return fEndRow - fStartRow;
1525}
1526
1527size_t WordProcessorTextIOSrcStream::TableIOMapper::GetColumns (size_t row) const
1528{
1529 size_t vRow = row + fStartRow;
1530 size_t realColCount = fRealTable.GetColumnCount (vRow);
1531 size_t pinnedColEnd = min (realColCount, fEndCol);
1532
1533 Assert (pinnedColEnd > fStartCol); // not sure how to deal with this failing - can it?
1534 // I guess we just pin result at zero??? - LGP 2003-04-11
1535 return pinnedColEnd - fStartCol;
1536}
1537
1538void WordProcessorTextIOSrcStream::TableIOMapper::GetRowInfo (size_t row, vector<CellInfo>* cellInfos)
1539{
1540 Require (row < GetRows ());
1541
1542 size_t vRow = row + fStartRow;
1543
1544 RequireNotNull (cellInfos);
1545 size_t columns = GetColumns (row);
1546 cellInfos->clear ();
1547 for (size_t c = 0; c < columns; ++c) {
1548 size_t vCol = c + fStartCol;
1549 if (fRealTable.GetCellFlags (vRow, vCol) == WordProcessorTable::ePlainCell) {
1550 CellInfo cellInfo;
1551 cellInfo.f_clcbpat = fRealTable.GetCellColor (vRow, vCol);
1552 cellInfo.f_cellx = fRealTable.GetColumnWidth (vRow, vCol);
1553 cellInfos->push_back (cellInfo);
1554 }
1555 else {
1556 // if any previous real cell, append this guys colwidth to him...
1557 if (not cellInfos->empty ()) {
1558 cellInfos->back ().f_cellx += fRealTable.GetColumnWidth (vRow, vCol);
1559 }
1560 }
1561 }
1562}
1563
1564StyledTextIOWriter::SrcStream* WordProcessorTextIOSrcStream::TableIOMapper::MakeCellSubSrcStream (size_t row, size_t column)
1565{
1566 Require (row < GetRows ());
1567 Require (column < GetColumns (row));
1568
1569 size_t vRow = row + fStartRow;
1570 size_t vCol = column + fStartCol;
1571
1572 if (fRealTable.GetCellFlags (vRow, vCol) == WordProcessorTable::ePlainCell) {
1573 TextStore* ts = nullptr;
1574 shared_ptr<AbstractStyleDatabaseRep> styleDatabase;
1575 shared_ptr<AbstractParagraphDatabaseRep> paragraphDatabase;
1576 shared_ptr<HidableTextMarkerOwner> hidableTextDatabase;
1577 fRealTable.GetCellWordProcessorDatabases (vRow, vCol, &ts, &styleDatabase, &paragraphDatabase, &hidableTextDatabase);
1578 return new WordProcessorTextIOSrcStream (ts, styleDatabase, paragraphDatabase, hidableTextDatabase);
1579 }
1580 else {
1581 return nullptr;
1582 }
1583}
1584
1585size_t WordProcessorTextIOSrcStream::TableIOMapper::GetOffsetEnd () const
1586{
1587 // The current implementation of tables uses a single embedding object with a single sentinel character
1588 // for the entire table (no matter how many rows)
1589 return 1;
1590}
1591
1592TWIPS_Rect WordProcessorTextIOSrcStream::TableIOMapper::GetDefaultCellMarginsForRow (size_t /*row*/) const
1593{
1594 // Right now - our table implementation just has ONE value for the entire table
1595 TWIPS_Rect cellMargins = TWIPS_Rect (TWIPS{0}, TWIPS{0}, TWIPS{0}, TWIPS{0});
1596 fRealTable.GetDefaultCellMargins (&cellMargins.top, &cellMargins.left, &cellMargins.bottom, &cellMargins.right);
1597 return cellMargins;
1598}
1599
1600TWIPS_Rect WordProcessorTextIOSrcStream::TableIOMapper::GetDefaultCellSpacingForRow (size_t /*row*/) const
1601{
1602 // Right now - our table implementation just has ONE value for the entire table
1603 TWIPS cellSpacing = fRealTable.GetCellSpacing ();
1604 return TWIPS_Rect (cellSpacing, cellSpacing, TWIPS{0}, TWIPS{0}); // carefull - TLBR sb cellSpacing and last 2 args to TWIPS_Rect::CTOR are height/width!
1605}
1606
1607#if qStroika_Frameworks_Led_SupportGDI
1608
1609class ParagraphInfoChangeTextRep : public InteractiveReplaceCommand::SavedTextRep {
1610private:
1611 using inherited = InteractiveReplaceCommand::SavedTextRep;
1612
1613public:
1614 using ParaInfoNSize = pair<ParagraphInfo, size_t>;
1615
1616public:
1617 ParagraphInfoChangeTextRep (WordProcessor* interactor, size_t from, size_t to)
1618 : inherited (from, to)
1619 , fSavedInfo ()
1620 {
1621 fSavedInfo = interactor->GetParagraphDatabase ()->GetParagraphInfo (from, to - from);
1622 Assert (GetLength () == to - from);
1623 }
1624 virtual size_t GetLength () const override
1625 {
1626 size_t len = 0;
1627 for (auto i = fSavedInfo.begin (); i != fSavedInfo.end (); ++i) {
1628 len += (*i).second;
1629 }
1630 return len;
1631 }
1632 virtual void InsertSelf (TextInteractor* interactor, size_t at, [[maybe_unused]] size_t nBytesToOverwrite) override
1633 {
1634 RequireNotNull (dynamic_cast<WordProcessor*> (interactor));
1635 WordProcessor* wp = dynamic_cast<WordProcessor*> (interactor);
1636 RequireNotNull (wp);
1637 Assert (nBytesToOverwrite == GetLength ()); // For THIS particular kind of update, the length cannot change since we don't save the text
1638 shared_ptr<AbstractParagraphDatabaseRep> paraDBase = wp->GetParagraphDatabase ();
1639 paraDBase->SetParagraphInfo (at, fSavedInfo);
1640 }
1641
1642private:
1643 vector<ParaInfoNSize> fSavedInfo;
1644};
1645
1646// Somewhat kludgy way to share code. Tried a member template, but that caused MSVC60SP1 to crash.
1647template <typename SPECIALIZER, typename T1>
1648void InteractiveWPHelper1 (WordProcessor* wp, T1 arg1)
1649{
1650 TextInteractor::InteractiveModeUpdater iuMode (*wp);
1651 using SavedTextRep = InteractiveReplaceCommand::SavedTextRep;
1652 wp->BreakInGroupedCommands ();
1653 size_t selStart = wp->GetSelectionStart ();
1654 size_t selEnd = wp->GetSelectionEnd ();
1655 SavedTextRep* before = nullptr;
1656 SavedTextRep* after = nullptr;
1657 try {
1658 if (wp->GetCommandHandler () != nullptr) {
1659 before = new ParagraphInfoChangeTextRep (wp, selStart, selEnd);
1660 }
1661 SPECIALIZER::DoIt (wp, selStart, selEnd, arg1);
1662 if (wp->GetCommandHandler () != nullptr) {
1663 after = new ParagraphInfoChangeTextRep (wp, selStart, selEnd);
1664 wp->PostInteractiveUndoPostHelper (&before, &after, selStart, SPECIALIZER::GetName (wp));
1665 }
1666 }
1667 catch (...) {
1668 delete before;
1669 delete after;
1670 throw;
1671 }
1672 wp->BreakInGroupedCommands ();
1673}
1674struct DoIt_SetJustification {
1675 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, Justification justification)
1676 {
1677 wp->SetJustification (selStart, selEnd, justification);
1678 }
1679 static SDKString GetName (WordProcessor* wp)
1680 {
1681 return wp->GetCommandNames ().fJustificationCommandName;
1682 }
1683};
1684struct DoIt_SetStandardTabStopList {
1685 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, StandardTabStopList tabStops)
1686 {
1687 wp->SetStandardTabStopList (selStart, selEnd, tabStops);
1688 }
1689 static SDKString GetName (WordProcessor* wp)
1690 {
1691 return wp->GetCommandNames ().fStandardTabStopListCommandName;
1692 }
1693};
1694struct DoIt_SetMargins {
1695 struct Margins {
1696 TWIPS fLHS;
1697 TWIPS fRHS;
1698 Margins (TWIPS l, TWIPS r)
1699 : fLHS (l)
1700 , fRHS (r)
1701 {
1702 }
1703 };
1704 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, Margins margins)
1705 {
1706 wp->SetMargins (selStart, selEnd, margins.fLHS, margins.fRHS);
1707 }
1708 static SDKString GetName (WordProcessor* wp)
1709 {
1710 return wp->GetCommandNames ().fMarginsCommandName;
1711 }
1712};
1713struct DoIt_SetFirstIndent {
1714 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, TWIPS firstIndent)
1715 {
1716 wp->SetFirstIndent (selStart, selEnd, firstIndent);
1717 }
1718 static SDKString GetName (WordProcessor* wp)
1719 {
1720 return wp->GetCommandNames ().fFirstIndentCommandName;
1721 }
1722};
1723struct DoIt_SetMarginsAndFirstIndent {
1724 struct MarginsAndFirstIndent {
1725 TWIPS fLHS;
1726 TWIPS fRHS;
1727 TWIPS fFirstIndent;
1728 MarginsAndFirstIndent (TWIPS l, TWIPS r, TWIPS firstIndent)
1729 : fLHS (l)
1730 , fRHS (r)
1731 , fFirstIndent (firstIndent)
1732 {
1733 }
1734 };
1735 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, MarginsAndFirstIndent marginsEtc)
1736 {
1737 wp->SetMargins (selStart, selEnd, marginsEtc.fLHS, marginsEtc.fRHS);
1738 wp->SetFirstIndent (selStart, selEnd, marginsEtc.fFirstIndent);
1739 }
1740 static SDKString GetName (WordProcessor* wp)
1741 {
1742 return wp->GetCommandNames ().fMarginsAndFirstIndentCommandName;
1743 }
1744};
1745struct DoIt_SetParagraphSpacing {
1746 struct AllSpacingArgs {
1747 TWIPS fSpaceBefore;
1748 TWIPS fSpaceAfter;
1749 LineSpacing fLineSpacing;
1750 bool fSBValid, fSAValid, fSLValid;
1751 AllSpacingArgs (TWIPS sb, bool sbValid, TWIPS sa, bool saValid, LineSpacing sl, bool slValid)
1752 : fSpaceBefore (sb)
1753 , fSBValid (sbValid)
1754 , fSpaceAfter (sa)
1755 , fSAValid (saValid)
1756 , fLineSpacing (sl)
1757 , fSLValid (slValid)
1758 {
1759 }
1760 };
1761 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, AllSpacingArgs spacingArgs)
1762 {
1763 if (spacingArgs.fSBValid) {
1764 wp->SetSpaceBefore (selStart, selEnd, spacingArgs.fSpaceBefore);
1765 }
1766 if (spacingArgs.fSAValid) {
1767 wp->SetSpaceAfter (selStart, selEnd, spacingArgs.fSpaceAfter);
1768 }
1769 if (spacingArgs.fSLValid) {
1770 wp->SetLineSpacing (selStart, selEnd, spacingArgs.fLineSpacing);
1771 }
1772 }
1773 static SDKString GetName (WordProcessor* wp)
1774 {
1775 return wp->GetCommandNames ().fParagraphSpacingCommandName;
1776 }
1777};
1778struct DoIt_SetListStyle {
1779 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, ListStyle listStyle)
1780 {
1781 wp->SetListStyle (selStart, selEnd, listStyle, true);
1782 }
1783 static SDKString GetName (WordProcessor* wp)
1784 {
1785 return wp->GetCommandNames ().fSetListStyleCommandName;
1786 }
1787};
1788struct DoIt_IndentUnIndentList {
1789 static void DoIt (WordProcessor* wp, size_t selStart, size_t selEnd, bool indent)
1790 {
1791 unsigned char indentLevel = wp->GetListIndentLevel (selStart);
1792 if (indent) {
1793 if (indentLevel < 8) {
1794 ++indentLevel;
1795 }
1796 else {
1797 Led_BeepNotify ();
1798 }
1799 }
1800 else {
1801 if (indentLevel > 0) {
1802 --indentLevel;
1803 }
1804 else {
1805 Led_BeepNotify ();
1806 }
1807 }
1808 wp->SetListIndentLevel (selStart, selEnd, indentLevel, true);
1809 }
1810 static SDKString GetName (WordProcessor* wp)
1811 {
1812 return wp->GetCommandNames ().fIndentLevelChangeCommandName;
1813 }
1814};
1815
1816/*
1817 ********************************************************************************
1818 ********************************* WordProcessor ********************************
1819 ********************************************************************************
1820 */
1821WordProcessor::WPIdler::WPIdler ()
1822 : fWP (nullptr)
1823{
1824}
1825
1826void WordProcessor::WPIdler::SpendIdleTime ()
1827{
1828 /*
1829 * Just randomly grab tables, and lay them out. Don't spend more than kMaxTime per
1830 * idle time call. First check if 'fSomeInvalidTables' as a performance hack (SPR#1365).
1831 */
1832 AbstractParagraphDatabaseRep* pdbRep = fWP->GetParagraphDatabase ().get ();
1833 AssertNotNull (pdbRep);
1834 if (pdbRep->fSomeInvalidTables) {
1835 constexpr Foundation::Time::DurationSeconds kMaxTime = 0.2s;
1836 Foundation::Time::TimePointSeconds startTime = Time::GetTickCount ();
1837 Foundation::Time::TimePointSeconds endTime = startTime + kMaxTime;
1838 AssertNotNull (fWP);
1839 vector<WordProcessorTable*> tables = fWP->GetTablesInRange (0, fWP->GetEnd ());
1840 bool maybeMoreTables = false;
1841 for (auto i = tables.begin (); i != tables.end (); ++i) {
1842 WordProcessorTable* t = *i;
1843 if (t->fNeedLayout != WordProcessorTable::eDone) {
1844 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*t, *fWP);
1845 t->PerformLayout ();
1846 if (endTime < Time::GetTickCount ()) {
1847 maybeMoreTables = true;
1848 break;
1849 }
1850 }
1851 }
1852 pdbRep->fSomeInvalidTables = maybeMoreTables;
1853 }
1854}
1855
1856/*
1857 ********************************************************************************
1858 ******************* WordProcessor::DialogSupport *******************************
1859 ********************************************************************************
1860 */
1861WordProcessor::DialogSupport::FontNameSpecifier WordProcessor::DialogSupport::CmdNumToFontName (CommandNumber /*cmdNum*/)
1862{
1863
1864 Assert (false); // must be overriden - or don't include / enable commands that refer to this method.
1865 return FontNameSpecifier ();
1866}
1867
1868bool WordProcessor::DialogSupport::IsPredefinedFontSize (DistanceType fontSize)
1869{
1870 switch (fontSize) {
1871 case 9:
1872 return true;
1873 case 10:
1874 return true;
1875 case 12:
1876 return true;
1877 case 14:
1878 return true;
1879 case 18:
1880 return true;
1881 case 24:
1882 return true;
1883 case 36:
1884 return true;
1885 case 48:
1886 return true;
1887 case 72:
1888 return true;
1889 default:
1890 return false;
1891 }
1892}
1893
1894DistanceType WordProcessor::DialogSupport::FontCmdToSize (CommandNumber commandNum)
1895{
1896 switch (commandNum) {
1897 case kFontSize9_CmdID:
1898 return 9;
1899 case kFontSize10_CmdID:
1900 return 10;
1901 case kFontSize12_CmdID:
1902 return 12;
1903 case kFontSize14_CmdID:
1904 return 14;
1905 case kFontSize18_CmdID:
1906 return 18;
1907 case kFontSize24_CmdID:
1908 return 24;
1909 case kFontSize36_CmdID:
1910 return 36;
1911 case kFontSize48_CmdID:
1912 return 48;
1913 case kFontSize72_CmdID:
1914 return 72;
1915 }
1916 return 0;
1917}
1918
1919DistanceType WordProcessor::DialogSupport::PickOtherFontHeight (DistanceType /*origHeight*/)
1920{
1921 Assert (false); // You must implement this yourself in your own subclass - or don't enable commands that call it.
1922 return 0;
1923}
1924
1925bool WordProcessor::DialogSupport::PickNewParagraphLineSpacing (TWIPS* /*spaceBefore*/, bool* /*spaceBeforeValid*/, TWIPS* /*spaceAfter*/,
1926 bool* /*spaceAfterValid*/, LineSpacing* /*lineSpacing*/, bool* /*lineSpacingValid*/)
1927{
1928 Assert (false); // You must implement this yourself in your own subclass - or don't enable commands that call it.
1929 return false;
1930}
1931
1932bool WordProcessor::DialogSupport::PickNewParagraphMarginsAndFirstIndent (TWIPS* /*leftMargin*/, bool* /*leftMarginValid*/, TWIPS* /*rightMargin*/,
1933 bool* /*rightMarginValid*/, TWIPS* /*firstIndent*/, bool* /*firstIndentValid*/)
1934{
1935 Assert (false); // You must implement this yourself in your own subclass - or don't enable commands that call it.
1936 return false;
1937}
1938
1939Color WordProcessor::DialogSupport::FontCmdToColor (CommandNumber cmd)
1940{
1941 switch (cmd) {
1942 case kFontColorBlack_CmdID:
1943 return Color::kBlack;
1944 case kFontColorMaroon_CmdID:
1945 return Color::kMaroon;
1946 case kFontColorGreen_CmdID:
1947 return Color::kGreen;
1948 case kFontColorOlive_CmdID:
1949 return Color::kOlive;
1950 case kFontColorNavy_CmdID:
1951 return Color::kNavyBlue;
1952 case kFontColorPurple_CmdID:
1953 return Color::kPurple;
1954 case kFontColorTeal_CmdID:
1955 return Color::kTeal;
1956 case kFontColorGray_CmdID:
1957 return Color::kGray;
1958 case kFontColorSilver_CmdID:
1959 return Color::kSilver;
1960 case kFontColorRed_CmdID:
1961 return Color::kRed;
1962 case kFontColorLime_CmdID:
1963 return Color::kLimeGreen;
1964 case kFontColorYellow_CmdID:
1965 return Color::kYellow;
1966 case kFontColorBlue_CmdID:
1967 return Color::kBlue;
1968 case kFontColorFuchsia_CmdID:
1969 return Color::kFuchsia;
1970 case kFontColorAqua_CmdID:
1971 return Color::kAqua;
1972 case kFontColorWhite_CmdID:
1973 return Color::kWhite;
1974 }
1975 Assert (false);
1976 return Color::kBlack;
1977}
1978
1979WordProcessor::DialogSupport::CommandNumber WordProcessor::DialogSupport::FontColorToCmd (Color color)
1980{
1981 if (color == Color::kBlack) {
1982 return kFontColorBlack_CmdID;
1983 }
1984 else if (color == Color::kMaroon) {
1985 return kFontColorMaroon_CmdID;
1986 }
1987 else if (color == Color::kGreen) {
1988 return kFontColorGreen_CmdID;
1989 }
1990 else if (color == Color::kOlive) {
1991 return kFontColorOlive_CmdID;
1992 }
1993 else if (color == Color::kNavyBlue) {
1994 return kFontColorNavy_CmdID;
1995 }
1996 else if (color == Color::kPurple) {
1997 return kFontColorPurple_CmdID;
1998 }
1999 else if (color == Color::kTeal) {
2000 return kFontColorTeal_CmdID;
2001 }
2002 else if (color == Color::kGray) {
2003 return kFontColorGray_CmdID;
2004 }
2005 else if (color == Color::kSilver) {
2006 return kFontColorSilver_CmdID;
2007 }
2008 else if (color == Color::kRed) {
2009 return kFontColorRed_CmdID;
2010 }
2011 else if (color == Color::kGreen) {
2012 return kFontColorLime_CmdID;
2013 }
2014 else if (color == Color::kYellow) {
2015 return kFontColorYellow_CmdID;
2016 }
2017 else if (color == Color::kBlue) {
2018 return kFontColorBlue_CmdID;
2019 }
2020 else if (color == Color::kFuchsia) {
2021 return kFontColorFuchsia_CmdID;
2022 }
2023 else if (color == Color::kAqua) {
2024 return kFontColorAqua_CmdID;
2025 }
2026 else if (color == Color::kWhite) {
2027 return kFontColorWhite_CmdID;
2028 }
2029 else {
2030 // Now - double check. User could have overridden our FontCmdToColor () method to specify new commands.
2031 // catch those as well (at a slight performance cost)
2032 for (CommandNumber i = kBaseFontColor_CmdID; i <= kLastNamedFontColor_CmdID; ++i) {
2033 if (FontCmdToColor (i) == color) {
2034 return i;
2035 }
2036 }
2037 return kFontColorOther_CmdID;
2038 }
2039}
2040
2041bool WordProcessor::DialogSupport::PickOtherFontColor (Color* color)
2042{
2043 RequireNotNull (color);
2044
2045#if qStroika_Platform_Windows
2046 CHOOSECOLOR cc;
2047 memset (&cc, 0, sizeof (cc));
2048 cc.lStructSize = sizeof (cc);
2049 cc.Flags |= CC_ANYCOLOR;
2050 cc.rgbResult = color->GetOSRep ();
2051 cc.Flags |= CC_RGBINIT;
2052 cc.Flags |= CC_FULLOPEN;
2053
2054 cc.Flags |= CC_ENABLEHOOK;
2055 cc.lpfnHook = ColorPickerINITPROC;
2056
2057 static COLORREF sCustomColors[16];
2058 cc.lpCustColors = sCustomColors;
2059
2060 cc.hwndOwner = ::GetActiveWindow (); // Not a great choice - but the best I can come up with from here...
2061
2062 if (::ChooseColor (&cc)) {
2063 *color = Color (cc.rgbResult);
2064 return true;
2065 }
2066#endif
2067 return false;
2068}
2069
2070#if qStroika_Platform_Windows
2071UINT_PTR CALLBACK WordProcessor::DialogSupport::ColorPickerINITPROC (HWND hWnd, UINT message, [[maybe_unused]] WPARAM wParam, [[maybe_unused]] LPARAM lParam)
2072{
2073 if (hWnd != nullptr and message == WM_INITDIALOG) {
2074 Led_CenterWindowInParent (hWnd);
2075 }
2076 return 0;
2077}
2078#endif
2079
2080bool WordProcessor::DialogSupport::ChooseFont ([[maybe_unused]] IncrementalFontSpecification* font)
2081{
2082 RequireNotNull (font);
2083
2084#if qStroika_Platform_Windows
2085 // Copy each valid attribute into the LOGFONT to initialize the CFontDialog
2086 LOGFONT lf;
2087 (void)::memset (&lf, 0, sizeof (lf));
2088 if (font->GetFontNameSpecifier_Valid ()) {
2089 Characters::CString::Copy (lf.lfFaceName, std::size (lf.lfFaceName), font->GetFontNameSpecifier ().fName);
2090 Assert (::_tcslen (lf.lfFaceName) < std::size (lf.lfFaceName)); // cuz our cached entry - if valid - always short enuf...
2091 }
2092 lf.lfWeight = (font->GetStyle_Bold_Valid () and font->GetStyle_Bold ()) ? FW_BOLD : FW_NORMAL;
2093 lf.lfItalic = (font->GetStyle_Italic_Valid () and font->GetStyle_Italic ());
2094 lf.lfUnderline = (font->GetStyle_Underline_Valid () and font->GetStyle_Underline ());
2095 lf.lfStrikeOut = (font->GetStyle_Strikeout_Valid () and font->GetStyle_Strikeout ());
2096
2097 if (font->GetPointSize_Valid ()) {
2098 lf.lfHeight = font->PeekAtTMHeight ();
2099 }
2100
2101 CHOOSEFONT cc;
2102 memset (&cc, 0, sizeof (cc));
2103 cc.lStructSize = sizeof (cc);
2104 cc.Flags |= CF_SCREENFONTS | CF_NOVERTFONTS | CF_EFFECTS | CF_SCALABLEONLY;
2105
2106 cc.hwndOwner = ::GetActiveWindow (); // Not a great choice - but the best I can come up with from here...
2107
2108 cc.lpLogFont = &lf;
2109 cc.Flags |= CF_INITTOLOGFONTSTRUCT;
2110
2111 if (font->GetTextColor_Valid ()) {
2112 cc.rgbColors = font->GetTextColor ().GetOSRep ();
2113 }
2114
2115 if (::ChooseFont (&cc)) {
2116 *font = FontSpecification (*cc.lpLogFont);
2117 font->SetTextColor (Color (cc.rgbColors));
2118 return true;
2119 }
2120#endif
2121 return false;
2122}
2123
2124void WordProcessor::DialogSupport::ShowSimpleEmbeddingInfoDialog (const SDKString& /*embeddingTypeName*/)
2125{
2126 Assert (false); // You must implement this yourself in your own subclass - or don't enable commands that call it.
2127}
2128
2129bool WordProcessor::DialogSupport::ShowURLEmbeddingInfoDialog (const SDKString& /*embeddingTypeName*/, SDKString* /*urlTitle*/, SDKString* /*urlValue*/)
2130{
2131 Assert (false); // You must implement this yourself in your own subclass - or don't enable commands that call it.
2132 return false;
2133}
2134
2135bool WordProcessor::DialogSupport::ShowAddURLEmbeddingInfoDialog (SDKString* /*urlTitle*/, SDKString* /*urlValue*/)
2136{
2137 Assert (false); // You must implement this yourself in your own subclass - or don't enable commands that call it.
2138 return false;
2139}
2140
2141bool WordProcessor::DialogSupport::AddNewTableDialog (size_t* nRows, size_t* nCols)
2142{
2143 RequireNotNull (nRows);
2144 RequireNotNull (nCols);
2145 // In case no AddNewTable dialog implemented - just default to simple basic table
2146 *nRows = 3;
2147 *nCols = 4;
2148 return true;
2149}
2150
2151bool WordProcessor::DialogSupport::EditTablePropertiesDialog ([[maybe_unused]] TableSelectionPropertiesInfo* tableProperties)
2152{
2153 RequireNotNull (tableProperties);
2154 return false; // You must implement this yourself in your own subclass - or don't enable commands that call it.
2155}
2156
2157/*
2158 ********************************************************************************
2159 ********************************* WordProcessor ********************************
2160 ********************************************************************************
2161 */
2162WordProcessor::CommandNames WordProcessor::sCommandNames = WordProcessor::MakeDefaultCommandNames ();
2163WordProcessor::DialogSupport* WordProcessor::sDialogSupport = nullptr;
2164
2165template <class T, class EXTRACTOR>
2166bool CheckForCommonParaValue (EXTRACTOR /*IGNORED_BUT_HERE_FOR_OVERLOADING*/, const shared_ptr<AbstractParagraphDatabaseRep>& paraDB,
2167 size_t from, size_t to, T* commonValue)
2168{
2169 RequireNotNull (commonValue);
2170 if (paraDB.get () == nullptr) {
2171 throw WordProcessor::NoParagraphDatabaseAvailable ();
2172 }
2173 vector<pair<ParagraphInfo, size_t>> v = paraDB->GetParagraphInfo (from, to - from);
2174 Assert (v.size () != 0);
2175 if (v.size () >= 1) {
2176 T maybeCommonValue = EXTRACTOR () (v[0].first);
2177 for (auto i = v.begin () + 1; i != v.end (); ++i) {
2178 if (EXTRACTOR () ((*i).first) != maybeCommonValue) {
2179 return false;
2180 }
2181 }
2182 *commonValue = maybeCommonValue;
2183 return true;
2184 }
2185 else {
2186 return false;
2187 }
2188}
2189struct JustificationExtractor {
2190 Justification operator() (const ParagraphInfo& from)
2191 {
2192 return from.GetJustification ();
2193 }
2194};
2195struct TabStopExtractor {
2196 StandardTabStopList operator() (const ParagraphInfo& from)
2197 {
2198 return from.GetTabStopList ();
2199 }
2200};
2201struct FirstIndentExtractor {
2202 TWIPS operator() (const ParagraphInfo& from)
2203 {
2204 return from.GetFirstIndent ();
2205 }
2206};
2207struct MarginsRec {
2208 MarginsRec ()
2209 : fLHS (TWIPS{0})
2210 , fRHS (TWIPS{0})
2211 {
2212 }
2213 MarginsRec (TWIPS lhs, TWIPS rhs)
2214 : fLHS (lhs)
2215 , fRHS (rhs)
2216 {
2217 }
2218
2219 TWIPS fLHS;
2220 TWIPS fRHS;
2221
2222 inline bool operator!= (const MarginsRec& rhs)
2223 {
2224 return fLHS != rhs.fLHS or fRHS != rhs.fRHS;
2225 }
2226};
2227struct MarginsRecExtractor {
2228 MarginsRec operator() (const ParagraphInfo& from)
2229 {
2230 return MarginsRec (from.GetLeftMargin (), from.GetRightMargin ());
2231 }
2232};
2233struct SpaceBeforeExtractor {
2234 TWIPS operator() (const ParagraphInfo& from)
2235 {
2236 return from.GetSpaceBefore ();
2237 }
2238};
2239struct SpaceAfterExtractor {
2240 TWIPS operator() (const ParagraphInfo& from)
2241 {
2242 return from.GetSpaceAfter ();
2243 }
2244};
2245struct LineSpacingExtractor {
2246 LineSpacing operator() (const ParagraphInfo& from)
2247 {
2248 return from.GetLineSpacing ();
2249 }
2250};
2251struct ListStyleExtractor {
2252 ListStyle operator() (const ParagraphInfo& from)
2253 {
2254 return from.GetListStyle ();
2255 }
2256};
2257struct ListIndentLevelExtractor {
2258 unsigned char operator() (const ParagraphInfo& from)
2259 {
2260 return from.GetListIndentLevel ();
2261 }
2262};
2263
2264WordProcessor::WordProcessor ()
2265 : inherited ()
2266 , fSmartQuoteMode (true)
2267 , fParagraphDatabase (nullptr)
2268 , fICreatedParaDB (false)
2269 , fHidableTextDatabase (nullptr)
2270 , fICreatedHidableTextDB (false)
2271 , fWPIdler ()
2272 , fCachedCurSelFontSpec ()
2273 , fCachedCurSelJustification (eLeftJustify)
2274 , fCachedCurSelJustificationUnique (false)
2275 , fCachedCurSelFontSpecValid (false)
2276 , fShowParagraphGlyphs (false)
2277 , fShowTabGlyphs (false)
2278 , fShowSpaceGlyphs (false)
2279{
2280 fWPIdler.fWP = this;
2281 IdleManager::Get ().AddIdler (&fWPIdler);
2282 IdleManager::Get ().SetIdlerFrequncy (&fWPIdler, 0.25s);
2283}
2284
2285WordProcessor::~WordProcessor ()
2286{
2287 IdleManager::Get ().RemoveIdler (&fWPIdler);
2288}
2289
2290void WordProcessor::HookLosingTextStore ()
2291{
2292 HookLosingTextStore_ ();
2293 inherited::HookLosingTextStore ();
2294}
2295
2296void WordProcessor::HookLosingTextStore_ ()
2297{
2298 {
2299 MarkersOfATypeMarkerSink2Vector<WordProcessorTable> tables;
2300 GetTextStore ().CollectAllMarkersInRangeInto (GetTextStore ().GetStart (), GetTextStore ().GetEnd (), this, tables);
2301 GetTextStore ().RemoveAndDeleteMarkers (Containers::Start (tables.fResult), tables.fResult.size ());
2302 }
2303
2304 // NB: We only set the fParagraphDatabase/fHidableTextDatabase to nullptr here if we created it because if the USER
2305 // created it - its up to THEM to properly bind it to the right TextStore. And when we are told to use a particular
2306 // database, someones call to change our TextStore shouldn't make us ignore that earlier request (to use a particular database).
2307 // We bother to delete it here if we HAD created it ourselves - only because the TextStore WE created it with
2308 // could be destroyed after this call returns - and then we'd have a database with a bogus pointer to a TextStore.
2309 if (fICreatedParaDB) {
2310 fICreatedParaDB = false;
2311 if (fParagraphDatabase.get () != nullptr) {
2312 fParagraphDatabase.reset (); // Cannot call WordProcessor::SetParagraphDatabase (nullptr) cuz that might build a NEW one
2313 HookParagraphDatabaseChanged ();
2314 }
2315 }
2316 if (fICreatedHidableTextDB) {
2317 SetHidableTextDatabase (nullptr);
2318 fICreatedHidableTextDB = false;
2319 }
2320 //to try to avoid circular links that cause things to not get freed. - LGP 2000/04/24
2321 if (fHidableTextDatabase.get () != nullptr) {
2322 fHidableTextDatabase->SetInternalizer (nullptr);
2323 fHidableTextDatabase->SetExternalizer (nullptr);
2324 }
2325}
2326
2327void WordProcessor::HookGainedNewTextStore ()
2328{
2329 /*
2330 * Note - we must check if the ParagraphDatabase has already been set - and use its Partition. Do this before
2331 * calling inherited::HookGainedNewTextStore () to avoid redundant creation of a 'default' partition (speed tweek).
2332 */
2333 if (fParagraphDatabase.get () != nullptr) {
2334 SetPartition (fParagraphDatabase->GetPartition ());
2335 }
2336 inherited::HookGainedNewTextStore ();
2337 HookGainedNewTextStore_ ();
2338}
2339
2340void WordProcessor::HookGainedNewTextStore_ ()
2341{
2342 if (fParagraphDatabase.get () == nullptr) {
2343 SetParagraphDatabase (nullptr); // fills in default value since we have a textstore...
2344 }
2345 if (fHidableTextDatabase.get () == nullptr) {
2346 SetHidableTextDatabase (MakeSharedPtr<UniformHidableTextMarkerOwner> (GetTextStore ())); // fills in default value since we have e textstore...
2347 fICreatedHidableTextDB = true; // do this AFTER above call - cuz WordProcessor::SetHidableTextDatabase () sets flag FALSE (so for case when others call it)
2348 }
2349}
2350
2351shared_ptr<Partition> WordProcessor::MakeDefaultPartition () const
2352{
2353 // Probably no point in overriding this anymore - LGP 2002-10-20 -- RETHINK??? Perhaps no harm - either...
2354 RequireNotNull (PeekAtTextStore ());
2355 if (fParagraphDatabase.get () == nullptr) {
2356 return MakeSharedPtr<LineBasedPartition> (GetTextStore ());
2357 }
2358 else {
2359 const MarkerOwner* mo = fParagraphDatabase.get ();
2360 return MakeSharedPtr<WPPartition> (GetTextStore (), *const_cast<MarkerOwner*> (mo));
2361 }
2362}
2363
2364/*
2365@METHOD: WordProcessor::SetParagraphDatabase
2366@DESCRIPTION: <p>This method allows the caller to specify the database of paragraph information associated
2367 with the given word processor. If not called, a default will be used, and automatically deleted.</p>
2368 <p>This API exists so that you can share a single database @'WordProcessor::ParagraphDatabasePtr'
2369 with multiple views. And so you can save it associated with a document (or some such object), and dynamically
2370 create/destroy views using that data. Also - so you can subclass it, and provide your own virtual replacement
2371 database.</p>
2372*/
2373void WordProcessor::SetParagraphDatabase (const shared_ptr<AbstractParagraphDatabaseRep>& paragraphDatabase)
2374{
2375 fParagraphDatabase = paragraphDatabase;
2376 fICreatedParaDB = false;
2377 if (fParagraphDatabase.get () == nullptr and PeekAtTextStore () != nullptr) {
2378 fParagraphDatabase = MakeSharedPtr<ParagraphDatabaseRep> (GetTextStore ());
2379 fICreatedParaDB = true;
2380 }
2381 //Any newly assigned fParagraphDatabase better share the same Partition we do!
2382 HookParagraphDatabaseChanged ();
2383}
2384
2385/*
2386@METHOD: WordProcessor::HookParagraphDatabaseChanged
2387@DESCRIPTION: <p>Called whenever the @'WordProcessor::shared_ptr<AbstractParagraphDatabaseRep>' associated with this @'WordProcessor'
2388 is changed. This means when a new one is provided, created, or disassociated. It does NOT mean that its called when any of the
2389 data in the paragphrase database changes.</p>
2390 <p>Usually called by @'WordProcessor::SetParagraphDatabase'. By default, it calls @'WordProcessor::HookParagraphDatabaseChanged_'.</p>
2391*/
2392void WordProcessor::HookParagraphDatabaseChanged ()
2393{
2394 if (PeekAtTextStore () != nullptr) {
2395 HookParagraphDatabaseChanged_ ();
2396 }
2397}
2398
2399/*
2400@METHOD: WordProcessor::HookParagraphDatabaseChanged_
2401@DESCRIPTION: <p>Default implementation of @'WordProcessor::HookParagraphDatabaseChanged'.</p>
2402*/
2403void WordProcessor::HookParagraphDatabaseChanged_ ()
2404{
2405 /*
2406 * At LEAST by default - we want the paragraphDB's partition to be the same as the one our imager is using. Which
2407 * should be preferred (ie which way do we do the copy?)? Since you can have multiple imagers associated with a single
2408 * ParagraphDatabase, and since you can operate on a paragraphdatabase without a WP/imager (say with a document),
2409 * it makes sense to assume THAT is primary. -- LGP 2002-10-20
2410 */
2411 if (fParagraphDatabase.get () != nullptr) {
2412 SetPartition (fParagraphDatabase->GetPartition ());
2413 }
2414 SetExternalizer (MakeDefaultExternalizer ());
2415 SetInternalizer (MakeDefaultInternalizer ());
2416}
2417
2418/*
2419@METHOD: WordProcessor::SetHidableTextDatabase
2420@DESCRIPTION: <p>This method allows the caller to specify the database of hidden-text information associated
2421 with the given word processor. If not called, a default will be used, and automatically deleted.</p>
2422 <p>This API exists so that you can share a single database @'shared_ptr<HidableTextMarkerOwner>'
2423 with multiple views. And so you can save it associated with a document (or some such object), and dynamically
2424 create/destroy views using that data. Also - so you can subclass it, and provide your own virtual replacement
2425 database, or other subclass of the hidable text API.</p>
2426 <p>To disable hidden text support, just call this method from your @'TextImager::HookGainedNewTextStore ()' OVERRIDE,
2427 and pass nullptr. Do this after the @'WordProcessor::HookGainedNewTextStore ()' OVERRIDE - since that method
2428 will create one of these by default.</p>
2429*/
2430void WordProcessor::SetHidableTextDatabase (const shared_ptr<HidableTextMarkerOwner>& hidableTextDatabase)
2431{
2432 //to try to avoid circular links that cause things to not get freed. - LGP 2000/04/24
2433 if (fHidableTextDatabase.get () != nullptr) {
2434 fHidableTextDatabase->SetInternalizer (shared_ptr<FlavorPackageInternalizer> ());
2435 fHidableTextDatabase->SetExternalizer (shared_ptr<FlavorPackageExternalizer> ());
2436 }
2437
2438 fHidableTextDatabase = hidableTextDatabase;
2439 fICreatedHidableTextDB = false;
2440 HookHidableTextDatabaseChanged ();
2441}
2442
2443/*
2444@METHOD: WordProcessor::HookHidableTextDatabaseChanged
2445@DESCRIPTION: <p>Called whenever the @'WordProcessor::shared_ptr<HidableTextMarkerOwner>' associated with this @'WordProcessor'
2446 is changed. This means when a new one is provided, created, or disassociated. It does NOT mean that its called when any of the
2447 data in the hidable text database changes.</p>
2448 <p>Usually called by @'WordProcessor::SetHidableTextDatabase'. By default, it calls @'WordProcessor::HookHidableTextDatabaseChanged_'.</p>
2449*/
2450void WordProcessor::HookHidableTextDatabaseChanged ()
2451{
2452 HookHidableTextDatabaseChanged_ ();
2453}
2454
2455/*
2456@METHOD: WordProcessor::HookHidableTextDatabaseChanged_
2457@DESCRIPTION: <p>Default implementation of @'WordProcessor::HookHidableTextDatabaseChanged'. Assures that when we change the hidableText database,
2458 we re-create the our internalizer and externalizers (cuz those can depend on the hidden text database). And be sure to notify any
2459 newly created hidable text database of our current internalizer and externalizer (the reverse).</p>
2460*/
2461void WordProcessor::HookHidableTextDatabaseChanged_ ()
2462{
2463 if (PeekAtTextStore () != nullptr) {
2464 SetExternalizer (MakeDefaultExternalizer ());
2465 SetInternalizer (MakeDefaultInternalizer ());
2466 }
2467}
2468
2469shared_ptr<FlavorPackageInternalizer> WordProcessor::MakeDefaultInternalizer ()
2470{
2471 return MakeSharedPtr<WordProcessorFlavorPackageInternalizer> (GetTextStore (), GetStyleDatabase (), GetParagraphDatabase (),
2472 GetHidableTextDatabase ());
2473}
2474
2475shared_ptr<FlavorPackageExternalizer> WordProcessor::MakeDefaultExternalizer ()
2476{
2477 return MakeSharedPtr<WordProcessorFlavorPackageExternalizer> (GetTextStore (), GetStyleDatabase (), GetParagraphDatabase (),
2478 GetHidableTextDatabase ());
2479}
2480
2481/*
2482@METHOD: WordProcessor::HookInternalizerChanged
2483@DESCRIPTION: <p>Override @TextInteractor::HookInternalizerChanged' to sync up with our HidableText database.</p>
2484*/
2485void WordProcessor::HookInternalizerChanged ()
2486{
2487 inherited::HookInternalizerChanged ();
2488 if (fHidableTextDatabase.get () != nullptr) {
2489 fHidableTextDatabase->SetInternalizer (GetInternalizer ());
2490 }
2491}
2492
2493/*
2494@METHOD: WordProcessor::HookExternalizerChanged
2495@DESCRIPTION: <p>Override @TextInteractor::HookExternalizerChanged' to sync up with our HidableText database.</p>
2496*/
2497void WordProcessor::HookExternalizerChanged ()
2498{
2499 inherited::HookExternalizerChanged ();
2500 if (fHidableTextDatabase.get () != nullptr) {
2501 fHidableTextDatabase->SetExternalizer (GetExternalizer ());
2502 }
2503}
2504
2505/*
2506@METHOD: WordProcessor::InternalizeBestFlavor
2507@DESCRIPTION: <p>Override @'TextInteractor::InternalizeBestFlavor' and set the internalizer (and so the source stream)
2508 to overwrite mode.</p>
2509*/
2510void WordProcessor::InternalizeBestFlavor (ReaderFlavorPackage& flavorPackage, bool updateCursorPosition, bool autoScroll, UpdateMode updateMode)
2511{
2512 WordProcessorTable* t = GetActiveTable ();
2513 if (t != nullptr) {
2514 WordProcessorFlavorPackageInternalizer* internalizerRep =
2515 dynamic_cast<WordProcessorFlavorPackageInternalizer*> (static_cast<FlavorPackageInternalizer*> (GetInternalizer ().get ()));
2516 AssertNotNull (internalizerRep);
2517
2518 bool oldFlagVal = internalizerRep->GetOverwriteTableMode ();
2519 internalizerRep->SetOverwriteTableMode (true);
2520 try {
2521 size_t selEnd = GetSelectionEnd ();
2522 Assert (selEnd - GetSelectionStart () == 1); // cuz GetActiveTable should assure this
2523
2524 // pass in ONLY selEnd to selEnd (not selStart to selEnd) because with pastes (or D&D) into a
2525 // selected table - we DONT want to delete first (replace) the table itself
2526 bool good = GetInternalizer ()->InternalizeBestFlavor (flavorPackage, selEnd, selEnd);
2527 if (good) {
2528 if (autoScroll) {
2529 ScrollToSelection ();
2530 }
2531 if (updateMode == eImmediateUpdate) {
2532 Update ();
2533 }
2534 }
2535 else {
2536 OnBadUserInput ();
2537 }
2538 internalizerRep->SetOverwriteTableMode (oldFlagVal);
2539 }
2540 catch (...) {
2541 internalizerRep->SetOverwriteTableMode (oldFlagVal);
2542 throw;
2543 }
2544 }
2545 else {
2546 inherited::InternalizeBestFlavor (flavorPackage, updateCursorPosition, autoScroll, updateMode);
2547 }
2548}
2549
2550/*
2551@METHOD: WordProcessor::ExternalizeFlavors
2552@DESCRIPTION: <p>Override @'TextInteractor::ExternalizeFlavors' but also restrict table externalizing to just
2553 the selected portion.</p>
2554*/
2555void WordProcessor::ExternalizeFlavors (WriterFlavorPackage& flavorPackage)
2556{
2557 WordProcessorFlavorPackageExternalizer* externalizerRep =
2558 dynamic_cast<WordProcessorFlavorPackageExternalizer*> (static_cast<FlavorPackageExternalizer*> (GetExternalizer ().get ()));
2559 AssertNotNull (externalizerRep);
2560
2561 bool oldFlagVal = externalizerRep->GetUseTableSelection ();
2562 externalizerRep->SetUseTableSelection (true);
2563 try {
2564 inherited::ExternalizeFlavors (flavorPackage);
2565 externalizerRep->SetUseTableSelection (oldFlagVal);
2566 }
2567 catch (...) {
2568 externalizerRep->SetUseTableSelection (oldFlagVal);
2569 throw;
2570 }
2571}
2572
2573/*
2574@METHOD: WordProcessor::InterectiveSetRegionHidable
2575@DESCRIPTION: <p>Interactively set the given region to be hidable or not. Interactively means that the action
2576 is considered an undoable command.</p>
2577*/
2578void WordProcessor::InterectiveSetRegionHidable (bool hidable)
2579{
2580 RequireNotNull (PeekAtTextStore ()); // Must specify TextStore before calling this, or any routine that calls it.
2581
2582 BreakInGroupedCommands ();
2583
2584 UndoableContextHelper undoContext (*this, hidable ? GetCommandNames ().fHideCommandName : GetCommandNames ().fUnHideCommandName, false);
2585 {
2586 if (hidable) {
2587 GetHidableTextDatabase ()->MakeRegionHidable (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd ());
2588 }
2589 else {
2590 GetHidableTextDatabase ()->MakeRegionUnHidable (undoContext.GetUndoRegionStart (), undoContext.GetUndoRegionEnd ());
2591 }
2592 }
2593 undoContext.CommandComplete ();
2594}
2595
2596/*
2597@METHOD: WordProcessor::GetJustification
2598@DESCRIPTION:
2599 <p>Return the @'Justification' setting for the paragraph containing the character characterPos</p>
2600*/
2601Justification WordProcessor::GetJustification (size_t characterPos) const
2602{
2603 if (fParagraphDatabase.get () == nullptr) {
2604 throw NoParagraphDatabaseAvailable ();
2605 }
2606 return fParagraphDatabase->GetParagraphInfo (characterPos).GetJustification ();
2607}
2608
2609/*
2610@METHOD: WordProcessor::GetJustification
2611@DESCRIPTION:
2612 <p>Return true iff there is a unique answer, and only then to we set out justification param (@'Justification').</p>
2613*/
2614bool WordProcessor::GetJustification (size_t from, size_t to, Justification* justification) const
2615{
2616 RequireNotNull (justification);
2617 return CheckForCommonParaValue (JustificationExtractor (), fParagraphDatabase, from, to, justification);
2618}
2619
2620/*
2621@METHOD: WordProcessor::SetJustification
2622@DESCRIPTION: <p>Set the justification to <code>justification</code> for all paragraphs
2623 between <code>from</code> and <code>to</code>.</p>
2624*/
2625void WordProcessor::SetJustification (size_t from, size_t to, Justification justification)
2626{
2627 Require (from <= to);
2629 pi.SetJustification (justification);
2630 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2631}
2632
2633StandardTabStopList WordProcessor::GetDefaultStandardTabStopList ()
2634{
2635 return StandardTabStopList{};
2636}
2637
2638/*
2639@METHOD: WordProcessor::GetStandardTabStopList
2640@DESCRIPTION:
2641 <p>Return the tabstops list setting for the paragraph containing the character characterPos</p>
2642*/
2643StandardTabStopList WordProcessor::GetStandardTabStopList (size_t characterPos) const
2644{
2645 return fParagraphDatabase->GetParagraphInfo (characterPos).GetTabStopList ();
2646}
2647
2648/*
2649@METHOD: WordProcessor::GetStandardTabStopList
2650@DESCRIPTION:
2651 <p>Return true iff there is a unique answer, and only then to we set out <code>tabStops</code> param</p>
2652*/
2653bool WordProcessor::GetStandardTabStopList (size_t from, size_t to, StandardTabStopList* tabStops) const
2654{
2655 RequireNotNull (tabStops);
2656 return CheckForCommonParaValue (TabStopExtractor (), fParagraphDatabase, from, to, tabStops);
2657}
2658
2659/*
2660@METHOD: WordProcessor::SetStandardTabStopList
2661@DESCRIPTION: <p>Set the tabstops to <code>tabStops</code> for all paragraphs
2662 between <code>from</code> and <code>to</code>.</p>
2663*/
2664void WordProcessor::SetStandardTabStopList (size_t from, size_t to, StandardTabStopList tabStops)
2665{
2666 Require (from <= to);
2668 pi.SetTabStopList (tabStops);
2669 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2670}
2671
2672/*
2673@METHOD: WordProcessor::GetMargins
2674@DESCRIPTION:
2675 <p>Return the left and right margin settings for the paragraph containing the character characterPos</p>
2676*/
2677void WordProcessor::GetMargins (size_t characterPos, TWIPS* leftMargin, TWIPS* rightMargin) const
2678{
2679 RequireNotNull (leftMargin);
2680 RequireNotNull (rightMargin);
2681 const ParagraphInfo& pi = fParagraphDatabase->GetParagraphInfo (characterPos); // Be careful holding onto reference here.
2682 // I do so cuz its a big optimization in my MacOS
2683 // profiling, as it avoids CTOR/DTOR for vector of
2684 // tabstops (SPR#1029)
2685 *leftMargin = pi.GetLeftMargin ();
2686 *rightMargin = pi.GetRightMargin ();
2687}
2688
2689bool WordProcessor::GetMargins (size_t from, size_t to, TWIPS* leftMargin, TWIPS* rightMargin) const
2690{
2691 RequireNotNull (leftMargin);
2692 RequireNotNull (rightMargin);
2693 MarginsRec mrResult;
2694 bool result = CheckForCommonParaValue (MarginsRecExtractor (), fParagraphDatabase, from, to, &mrResult);
2695 *leftMargin = mrResult.fLHS;
2696 *rightMargin = mrResult.fRHS;
2697 return result;
2698}
2699
2700/*
2701@METHOD: WordProcessor::SetMargins
2702@DESCRIPTION: <p>See @'WordProcessor::GetMargins'.</p>
2703*/
2704void WordProcessor::SetMargins (size_t from, size_t to, TWIPS leftMargin, TWIPS rightMargin)
2705{
2706 Require (from <= to);
2708 pi.SetMargins (leftMargin, rightMargin);
2709 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2710}
2711
2712/*
2713@METHOD: WordProcessor::GetFirstIndent
2714@DESCRIPTION: <p>Get the 'first indent' property for the paragraph containing the
2715 given character position.</p>
2716*/
2717TWIPS WordProcessor::GetFirstIndent (size_t characterPos) const
2718{
2719 return fParagraphDatabase->GetParagraphInfo (characterPos).GetFirstIndent ();
2720}
2721
2722/*
2723@METHOD: WordProcessor::GetFirstIndent
2724@DESCRIPTION: <p>Get the 'first indent' property for the paragraphs bounded by the given range, if it is
2725 unique over that range, and return true. If it is not unqique over that range, return false.</p>
2726*/
2727bool WordProcessor::GetFirstIndent (size_t from, size_t to, TWIPS* firstIndent) const
2728{
2729 RequireNotNull (firstIndent);
2730 return CheckForCommonParaValue (FirstIndentExtractor (), fParagraphDatabase, from, to, firstIndent);
2731}
2732
2733/*
2734@METHOD: WordProcessor::SetFirstIndent
2735@DESCRIPTION:
2736*/
2737void WordProcessor::SetFirstIndent (size_t from, size_t to, TWIPS firstIndent)
2738{
2739 Require (from <= to);
2741 pi.SetFirstIndent (firstIndent);
2742 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2743}
2744
2745/*
2746@METHOD: WordProcessor::GetSpaceBefore
2747@DESCRIPTION:
2748 <p>See @'ParagraphInfo::GetSpaceBefore'</p>
2749*/
2750TWIPS WordProcessor::GetSpaceBefore (size_t characterPos) const
2751{
2752 if (fParagraphDatabase.get () == nullptr) {
2753 throw NoParagraphDatabaseAvailable{};
2754 }
2755 return fParagraphDatabase->GetParagraphInfo (characterPos).GetSpaceBefore ();
2756}
2757
2758/*
2759@METHOD: WordProcessor::GetSpaceBefore
2760@DESCRIPTION:
2761 <p>See @'ParagraphInfo::GetSpaceBefore'</p>
2762*/
2763bool WordProcessor::GetSpaceBefore (size_t from, size_t to, TWIPS* sb) const
2764{
2765 RequireNotNull (sb);
2766 return CheckForCommonParaValue (SpaceBeforeExtractor{}, fParagraphDatabase, from, to, sb);
2767}
2768
2769/*
2770@METHOD: WordProcessor::SetSpaceBefore
2771@DESCRIPTION:
2772 <p>See @'ParagraphInfo::GetSpaceBefore'</p>
2773*/
2774void WordProcessor::SetSpaceBefore (size_t from, size_t to, TWIPS sb)
2775{
2776 Require (from <= to);
2777 if (fParagraphDatabase.get () == nullptr) {
2778 throw NoParagraphDatabaseAvailable{};
2779 }
2781 pi.SetSpaceBefore (sb);
2782 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2783}
2784
2785/*
2786@METHOD: WordProcessor::GetSpaceAfter
2787@DESCRIPTION:
2788 <p>See @'ParagraphInfo::GetSpaceAfter'</p>
2789*/
2790TWIPS WordProcessor::GetSpaceAfter (size_t characterPos) const
2791{
2792 if (fParagraphDatabase.get () == nullptr) {
2793 throw NoParagraphDatabaseAvailable ();
2794 }
2795 return fParagraphDatabase->GetParagraphInfo (characterPos).GetSpaceAfter ();
2796}
2797
2798/*
2799@METHOD: WordProcessor::GetSpaceAfter
2800@DESCRIPTION:
2801 <p>See @'ParagraphInfo::GetSpaceAfter'</p>
2802*/
2803bool WordProcessor::GetSpaceAfter (size_t from, size_t to, TWIPS* sa) const
2804{
2805 RequireNotNull (sa);
2806 return CheckForCommonParaValue (SpaceAfterExtractor (), fParagraphDatabase, from, to, sa);
2807}
2808
2809/*
2810@METHOD: WordProcessor::SetSpaceAfter
2811@DESCRIPTION:
2812 <p>See @'ParagraphInfo::GetSpaceAfter'</p>
2813*/
2814void WordProcessor::SetSpaceAfter (size_t from, size_t to, TWIPS sa)
2815{
2816 Require (from <= to);
2817 if (fParagraphDatabase.get () == nullptr) {
2818 throw NoParagraphDatabaseAvailable ();
2819 }
2821 pi.SetSpaceAfter (sa);
2822 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2823}
2824
2825/*
2826@METHOD: WordProcessor::GetLineSpacing
2827@DESCRIPTION:
2828 <p>See @'ParagraphInfo::GetLineSpacing'</p>
2829*/
2830LineSpacing WordProcessor::GetLineSpacing (size_t characterPos) const
2831{
2832 if (fParagraphDatabase.get () == nullptr) {
2833 throw NoParagraphDatabaseAvailable ();
2834 }
2835 return fParagraphDatabase->GetParagraphInfo (characterPos).GetLineSpacing ();
2836}
2837
2838/*
2839@METHOD: WordProcessor::GetLineSpacing
2840@DESCRIPTION:
2841 <p>See @'ParagraphInfo::GetLineSpacing'</p>
2842*/
2843bool WordProcessor::GetLineSpacing (size_t from, size_t to, LineSpacing* sl) const
2844{
2845 RequireNotNull (sl);
2846 return CheckForCommonParaValue (LineSpacingExtractor (), fParagraphDatabase, from, to, sl);
2847}
2848
2849/*
2850@METHOD: WordProcessor::SetLineSpacing
2851@DESCRIPTION:
2852 <p>See @'ParagraphInfo::SetLineSpacing'</p>
2853*/
2854void WordProcessor::SetLineSpacing (size_t from, size_t to, LineSpacing sl)
2855{
2856 Require (from <= to);
2858 pi.SetLineSpacing (sl);
2859 if (fParagraphDatabase.get () == nullptr) {
2860 throw NoParagraphDatabaseAvailable ();
2861 }
2862 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2863}
2864
2865/*
2866@METHOD: WordProcessor::GetListStyle
2867@DESCRIPTION:
2868 <p>See @'ParagraphInfo::GetListStyle'</p>
2869*/
2870ListStyle WordProcessor::GetListStyle (size_t characterPos) const
2871{
2872 if (fParagraphDatabase.get () == nullptr) {
2873 throw NoParagraphDatabaseAvailable ();
2874 }
2875 return fParagraphDatabase->GetParagraphInfo (characterPos).GetListStyle ();
2876}
2877
2878/*
2879@METHOD: WordProcessor::GetListStyle
2880@DESCRIPTION:
2881 <p>See @'ParagraphInfo::GetListStyle'</p>
2882*/
2883bool WordProcessor::GetListStyle (size_t from, size_t to, ListStyle* listStyle) const
2884{
2885 RequireNotNull (listStyle);
2886 return CheckForCommonParaValue (ListStyleExtractor (), fParagraphDatabase, from, to, listStyle);
2887}
2888
2889/*
2890@METHOD: WordProcessor::SetListStyle
2891@DESCRIPTION:
2892 <p>See @'ParagraphInfo::SetListStyle'</p>
2893*/
2894void WordProcessor::SetListStyle (size_t from, size_t to, ListStyle listStyle, bool autoFormat)
2895{
2896 Require (from <= to);
2898 pi.SetListStyle (listStyle);
2899 if (fParagraphDatabase.get () == nullptr) {
2900 throw NoParagraphDatabaseAvailable ();
2901 }
2902 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2903
2904 if (autoFormat) {
2905 AutoFormatIndentedText (from, to);
2906 }
2907}
2908
2909/*
2910@METHOD: WordProcessor::GetListIndentLevel
2911@DESCRIPTION:
2912 <p>See @'ParagraphInfo::GetListIndentLevel'</p>
2913*/
2914unsigned char WordProcessor::GetListIndentLevel (size_t characterPos) const
2915{
2916 if (fParagraphDatabase.get () == nullptr) {
2917 throw NoParagraphDatabaseAvailable ();
2918 }
2919 return fParagraphDatabase->GetParagraphInfo (characterPos).GetListIndentLevel ();
2920}
2921
2922/*
2923@METHOD: WordProcessor::GetListIndentLevel
2924@DESCRIPTION:
2925 <p>See @'ParagraphInfo::GetListIndentLevel'</p>
2926*/
2927bool WordProcessor::GetListIndentLevel (size_t from, size_t to, unsigned char* indentLevel) const
2928{
2929 RequireNotNull (indentLevel);
2930 return CheckForCommonParaValue (ListIndentLevelExtractor (), fParagraphDatabase, from, to, indentLevel);
2931}
2932
2933/*
2934@METHOD: WordProcessor::SetListIndentLevel
2935@DESCRIPTION:
2936 <p>See @'ParagraphInfo::SetListIndentLevel'</p>
2937*/
2938void WordProcessor::SetListIndentLevel (size_t from, size_t to, unsigned char indentLevel, bool autoFormat)
2939{
2940 Require (from <= to);
2942 pi.SetListIndentLevel (indentLevel);
2943 if (fParagraphDatabase.get () == nullptr) {
2944 throw NoParagraphDatabaseAvailable ();
2945 }
2946 fParagraphDatabase->SetParagraphInfo (from, to - from, pi);
2947 if (autoFormat) {
2948 AutoFormatIndentedText (from, to);
2949 }
2950}
2951
2952/*
2953@METHOD: WordProcessor::AutoFormatIndentedText
2954@DESCRIPTION:
2955 <p>Examine each paragraph from 'from' to 'to', and set their various paragraph properties to fit their indent
2956 level. If I supported style sheets, this would be a natural place to use them (just applying a predefined
2957 style sheet).</p>
2958*/
2959void WordProcessor::AutoFormatIndentedText (size_t from, size_t to)
2960{
2961 for (PartitionMarker* pm = GetPartitionMarkerContainingPosition (from); pm != nullptr and pm->GetStart () <= to; pm = pm->GetNext ()) {
2962 AssertNotNull (pm);
2963 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (pm->GetStart ());
2965 if (pi.GetListStyle () == eListStyle_None) {
2966 newPI.SetFirstIndent (TWIPS{0});
2967 newPI.SetMargins (TWIPS{0}, pi.GetRightMargin ());
2968 newPI.SetTabStopList (StandardTabStopList ());
2969 }
2970 else {
2971 const int kTWIPSIncrement = 1440 / 4;
2972 TWIPS marginAt = TWIPS (kTWIPSIncrement * 2 * (pi.GetListIndentLevel () + 1));
2973 newPI.SetFirstIndent (TWIPS (-kTWIPSIncrement));
2974 newPI.SetMargins (marginAt, pi.GetRightMargin ());
2975 StandardTabStopList tabStops;
2976 tabStops.fTabStops.push_back (marginAt);
2977 newPI.SetTabStopList (tabStops);
2978 }
2979 fParagraphDatabase->SetParagraphInfo (pm->GetStart (), pm->GetLength (), newPI);
2980 }
2981}
2982
2983/*
2984@METHOD: WordProcessor::SetSelection
2985@DESCRIPTION: <p>Override @'TextImager::SetSelection' to handle updating selection of embedded tables.</p>
2986*/
2987void WordProcessor::SetSelection (size_t start, size_t end)
2988{
2989 size_t oldSelStart = 0;
2990 size_t oldSelEnd = 0;
2991 GetSelection (&oldSelStart, &oldSelEnd);
2992 inherited::SetSelection (start, end);
2993
2994 // For the area we have ADDED to the selection region, we must set the embedded tables in that region to be
2995 // fully selected. Note that we need not worry about the selection range within an UNSELECTED table
2996 // because that is ignored, and its forcibly reset upon new selection (or at least should be) - LGP 2003-03-17
2997 if (oldSelStart != start or oldSelEnd != end) {
2998 vector<WordProcessorTable*> tables;
2999 size_t checkRangeStart1 = start;
3000 size_t checkRangeEnd1 = oldSelStart;
3001 if (checkRangeStart1 < checkRangeEnd1) {
3002 tables = GetTablesInRange (checkRangeStart1, checkRangeEnd1);
3003 }
3004
3005 size_t checkRangeStart2 = FindPreviousCharacter (oldSelEnd); // back one to handle the case where we had one char selected
3006 size_t checkRangeEnd2 = end;
3007 if (checkRangeStart2 < checkRangeEnd2) {
3008 vector<WordProcessorTable*> tables2 = GetTablesInRange (checkRangeStart2, checkRangeEnd2);
3009 tables.insert (tables.end (), tables2.begin (), tables2.end ()); // append the vectors
3010 }
3011 for (auto i = tables.begin (); i != tables.end (); ++i) {
3012 WordProcessorTable* t = *i;
3013 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*t, *const_cast<WordProcessor*> (this));
3014 t->SetCellSelection (0, t->GetRowCount (), 0, t->GetColumnCount ());
3015 }
3016 }
3017
3018 fCachedCurSelFontSpecValid = false;
3019}
3020
3021/*
3022@METHOD: WordProcessor::GetCaretShownSituation
3023@DESCRIPTION: <p>Override @'TextInteractor::GetCaretShownSituation' to handle tables.</p>
3024*/
3025bool WordProcessor::GetCaretShownSituation () const
3026{
3027 if (inherited::GetCaretShownSituation ()) {
3028 return true;
3029 }
3030
3031 WordProcessorTable* table = GetActiveTable ();
3032 if (table != nullptr) {
3033 return table->GetCaretShownSituation ();
3034 }
3035 return false;
3036}
3037
3038/*
3039@METHOD: WordProcessor::CalculateCaretRect
3040@DESCRIPTION: <p>Override @'TextInteractor::CalculateCaretRect' to handle tables.</p>
3041*/
3042Led_Rect WordProcessor::CalculateCaretRect () const
3043{
3044 WordProcessorTable* table = GetActiveTable ();
3045 if (table != nullptr) {
3046 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*table, *const_cast<WordProcessor*> (this));
3047 return table->CalculateCaretRect ();
3048 }
3049 return inherited::CalculateCaretRect ();
3050}
3051
3052/*
3053@METHOD: WordProcessor::OnTypedNormalCharacter
3054@DESCRIPTION: <p>Override @'TextInteractor::OnTypedNormalCharacter' to handle smart quotes
3055 (@'WordProcessor::GetSmartQuoteMode'), tab/shift-tab indents (lists) and tables.</p>
3056*/
3057void WordProcessor::OnTypedNormalCharacter (Led_tChar theChar, bool optionPressed, bool shiftPressed, bool commandPressed,
3058 bool controlPressed, bool altKeyPressed)
3059{
3060 WordProcessorTable* table = GetActiveTable ();
3061 if (table != nullptr) {
3062 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*table, *const_cast<WordProcessor*> (this));
3063 if (table->OnTypedNormalCharacter (theChar, optionPressed, shiftPressed, commandPressed, controlPressed, altKeyPressed)) {
3064 return;
3065 }
3066 }
3067
3068 // if the entire region is in the 'list' style - then assume a tab is meant to INDENT or UNINDENT
3069 if (theChar == '\t' and not(optionPressed or commandPressed or controlPressed or altKeyPressed)) {
3070 ListStyle ls = eListStyle_None;
3071 if (GetListStyle (GetSelectionStart (), GetSelectionEnd (), &ls) and ls != eListStyle_None) {
3072 InteractiveDoIndentChange (not shiftPressed);
3073 return;
3074 }
3075 }
3076 if (theChar == '"' and GetSmartQuoteMode () and not(optionPressed or commandPressed or controlPressed or altKeyPressed)) {
3077 const wchar_t kSpecialOpenQuote = 8220;
3078 const wchar_t kSpecialCloseQuote = 8221;
3079 bool isAQuoteToClose = false;
3080 size_t selStart = GetSelectionStart ();
3081 {
3082 // Walk backwards and see if we can find a recent OPEN-quote
3083 const size_t kScanBackSize = 1024;
3084 size_t scanBackTo = static_cast<size_t> (max (0, static_cast<int> (selStart) - static_cast<int> (kScanBackSize)));
3085 Memory::StackBuffer<Led_tChar> buf{Memory::eUninitialized, kScanBackSize};
3086 size_t scanBackCount = selStart - scanBackTo;
3087 CopyOut (scanBackTo, scanBackCount, buf.data ());
3088 for (size_t i = scanBackCount; i != 0; --i) {
3089 if (buf[i - 1] == kSpecialCloseQuote) {
3090 // then last thing was a close quote - so next is OPEN
3091 break;
3092 }
3093 else if (buf[i - 1] == kSpecialOpenQuote) {
3094 isAQuoteToClose = true;
3095 break;
3096 }
3097 }
3098 }
3099 wchar_t quoteChar = isAQuoteToClose ? kSpecialCloseQuote : kSpecialOpenQuote;
3100 inherited::OnTypedNormalCharacter (quoteChar, optionPressed, shiftPressed, commandPressed, controlPressed, altKeyPressed);
3101 }
3102 else {
3103 inherited::OnTypedNormalCharacter (theChar, optionPressed, shiftPressed, commandPressed, controlPressed, altKeyPressed);
3104 }
3105}
3106
3107bool WordProcessor::ProcessSimpleClick (Led_Point clickedAt, unsigned clickCount, bool extendSelection, size_t* dragAnchor)
3108{
3109 RequireNotNull (dragAnchor);
3110 size_t clickedOnChar = GetCharAtWindowLocation (clickedAt);
3111 Led_Rect charRect = GetCharWindowLocation (clickedOnChar);
3112
3113 // Only if click is on an embedding character cell, and fully within it (not in case just past it as at when at
3114 // end of line) - then we look at if it needs special processing
3115 //
3116 // Actually - better to check that the click isn't too near the edges of the embedding,
3117 // cuz then its hard to click and make an insertion point in between two embeddings.
3118 // So only do this click-selects somewhere near the middle of the embedding.
3119 Led_Rect tstClickRect = charRect;
3120 const DistanceType kHMargin = 3;
3121 tstClickRect.left += kHMargin;
3122 tstClickRect.right -= kHMargin;
3123 if (tstClickRect.Contains (clickedAt)) {
3124 vector<WordProcessorTable*> tables = GetTablesInRange (clickedOnChar, clickedOnChar + 1);
3125 Assert (tables.size () == 0 or tables.size () == 1);
3126 if (tables.size () == 1) {
3127 WordProcessorTable* t = tables[0];
3128 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*t, *const_cast<WordProcessor*> (this));
3129 if (clickCount == 1) {
3130 // In this case - it really doesn't matter if we pick the LHS or RHS of the embedding
3131 // as the drag anchor...
3132 *dragAnchor = clickedOnChar;
3133 }
3134 return t->ProcessSimpleClick (clickedAt - charRect.GetOrigin (), clickCount, extendSelection);
3135 }
3136 }
3137 return inherited::ProcessSimpleClick (clickedAt, clickCount, extendSelection, dragAnchor);
3138}
3139
3140/*
3141@METHOD: WordProcessor::WhileSimpleMouseTracking
3142@DESCRIPTION: <p>Override @'TextInteractor::WhileSimpleMouseTracking' to handle tables.</p>
3143*/
3144void WordProcessor::WhileSimpleMouseTracking (Led_Point newMousePos, size_t dragAnchor)
3145{
3146 size_t clickedOnChar = GetCharAtWindowLocation (newMousePos);
3147 size_t oldSelStart = GetSelectionStart ();
3148 size_t oldSelEnd = GetSelectionEnd ();
3149
3150 /*
3151 * If the drag anchor is coincident with the LHS or RHS of the clicked on character and the selection length
3152 * is one (we clicked on an embedding) - then just eat that mousetracking - and prevent the selection from
3153 * changing.
3154 */
3155 if ((clickedOnChar == dragAnchor or clickedOnChar + 1 == dragAnchor) and (oldSelEnd - oldSelStart == 1)) {
3156 vector<WordProcessorTable*> tables = GetTablesInRange (clickedOnChar, clickedOnChar + 1);
3157 if (tables.size () == 1) {
3158 WordProcessorTable* t = tables[0];
3159 Led_Rect charRect = GetCharWindowLocation (t->GetStart ());
3160 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*t, *const_cast<WordProcessor*> (this));
3161 // must adjust/be careful about charRect.GetOrigin () in case out of range... not sure what args to REALLY pass in !
3162 t->WhileSimpleMouseTracking (newMousePos - charRect.GetOrigin ());
3163 return;
3164 }
3165 }
3166 inherited::WhileSimpleMouseTracking (newMousePos, dragAnchor);
3167#if 0
3168 DbgTrace ("WordProcessor::WhileSimpleMouseTracking (tickCount=%f, newMousePos=(%d,%d), clickedOnChar=%d, dragAnchor=%d)\n",
3169 Time::GetTickCount (), newMousePos.v, newMousePos.h, clickedOnChar, dragAnchor
3170 );
3171#endif
3172}
3173
3174/*
3175@METHOD: WordProcessor::InsertTable
3176@DESCRIPTION: <p>Create a @'WordProcessorTable' object at the given location in the document. The table object is returned you that you can then
3177 call specific methods to add rows and columns etc.</p>
3178*/
3179WordProcessorTable* WordProcessor::InsertTable (size_t at)
3180{
3181 WordProcessorTable* t = new WordProcessorTable (fParagraphDatabase.get (), at);
3182 t->SetDimensions (1, 1); //tmphack so we have at least one sentinel - auto-delete table when it becomes empty?
3183 //like the embeddings it owns!
3184 // LGP 2002-11-15
3185 return t;
3186}
3187
3188/*
3189@METHOD: WordProcessor::GetTablesInRange
3190@DESCRIPTION: <p>Generate a list of all tables obejcts in the given range. Grabs all tables from 'from' to 'to' with from defaulting
3191 to the start of the buffer, and 'to' defaulting to the end of the buffer.</p>
3192*/
3193vector<WordProcessorTable*> WordProcessor::GetTablesInRange (size_t from, size_t to) const
3194{
3195 if (to == static_cast<size_t> (-1)) {
3196 to = GetTextStore ().GetLength ();
3197 }
3198 Require (from <= to);
3199 Require (to <= GetTextStore ().GetLength () + 1);
3200 MarkersOfATypeMarkerSink2Vector<WordProcessorTable> result;
3201 if (fParagraphDatabase.get () != nullptr) {
3202 GetTextStore ().CollectAllMarkersInRangeInto (from, to, fParagraphDatabase.get (), result);
3203 }
3204 return result.fResult;
3205}
3206
3207/*
3208@METHOD: WordProcessor::GetTableAt
3209@DESCRIPTION: <p>Return the table which starts at offset 'from'. If there is no table there - return nullptr.</p>
3210*/
3211WordProcessorTable* WordProcessor::GetTableAt (size_t from) const
3212{
3213 size_t to = from + 1;
3214 Require (to <= GetTextStore ().GetLength () + 1);
3215 MarkerOfATypeMarkerSink<WordProcessorTable> result;
3216 if (fParagraphDatabase.get () != nullptr) {
3217 GetTextStore ().CollectAllMarkersInRangeInto (from, to, fParagraphDatabase.get (), result);
3218 }
3219 return result.fResult;
3220}
3221
3222WordProcessor::CommandNames WordProcessor::MakeDefaultCommandNames ()
3223{
3224 WordProcessor::CommandNames cmdNames;
3225 cmdNames.fJustificationCommandName = Led_SDK_TCHAROF ("Justification Change");
3226 cmdNames.fStandardTabStopListCommandName = Led_SDK_TCHAROF ("Set Tabs");
3227 cmdNames.fMarginsCommandName = Led_SDK_TCHAROF ("Set Margins");
3228 cmdNames.fFirstIndentCommandName = Led_SDK_TCHAROF ("Set First Indent");
3229 cmdNames.fMarginsAndFirstIndentCommandName = Led_SDK_TCHAROF ("Set Margins and First Indent");
3230 cmdNames.fParagraphSpacingCommandName = Led_SDK_TCHAROF ("Change Paragraph Spacing");
3231 cmdNames.fHideCommandName = Led_SDK_TCHAROF ("Hide");
3232 cmdNames.fUnHideCommandName = Led_SDK_TCHAROF ("UnHide");
3233 cmdNames.fSetListStyleCommandName = Led_SDK_TCHAROF ("Change List Style");
3234 cmdNames.fIndentLevelChangeCommandName = Led_SDK_TCHAROF ("Change Indent Level");
3235 cmdNames.fInsertTableCommandName = Led_SDK_TCHAROF ("Insert Table");
3236 cmdNames.fInsertTableRowAboveCommandName = Led_SDK_TCHAROF ("Insert Table Row Above");
3237 cmdNames.fInsertTableRowBelowCommandName = Led_SDK_TCHAROF ("Insert Table Row Below");
3238 cmdNames.fInsertTableColBeforeCommandName = Led_SDK_TCHAROF ("Insert Table Column Before");
3239 cmdNames.fInsertTableColAfterCommandName = Led_SDK_TCHAROF ("Insert Table Column After");
3240 cmdNames.fInsertURLCommandName = Led_SDK_TCHAROF ("Insert URL");
3241 cmdNames.fRemoveTableRowsCommandName = Led_SDK_TCHAROF ("Remove Rows");
3242 cmdNames.fRemoveTableColumnsCommandName = Led_SDK_TCHAROF ("Remove Columns");
3243 cmdNames.fEmbeddingTypeName_ImageDIB = Led_SDK_TCHAROF ("image (DIB)");
3244 cmdNames.fEmbeddingTypeName_URL = Led_SDK_TCHAROF ("URL");
3245 cmdNames.fEmbeddingTypeName_ImageMacPict = Led_SDK_TCHAROF ("image (MacPICT)");
3246 cmdNames.fEmbeddingTypeName_Table = Led_SDK_TCHAROF ("table");
3247 cmdNames.fEmbeddingTypeName_Unknown = Led_SDK_TCHAROF ("unknown");
3248 cmdNames.fFontSizeChange_Other_NoArg = Led_SDK_TCHAROF ("Other...");
3249 cmdNames.fFontSizeChange_Other_OneArg = Led_SDK_TCHAROF ("Other (%d)...");
3250 cmdNames.fTablePropertiesCommandName = Led_SDK_TCHAROF ("Table Properties...")
3251#if qStroika_Platform_Windows
3252 Led_SDK_TCHAROF ("\tAlt+Enter")
3253#endif
3254 ;
3255 cmdNames.fGenericEmbeddingPropertiesCommandName = Led_SDK_TCHAROF ("Properties")
3256#if qStroika_Platform_Windows
3257 Led_SDK_TCHAROF ("\tAlt+Enter")
3258#endif
3259 ;
3260 cmdNames.fChangeTablePropertiesCommandName = Led_SDK_TCHAROF ("Change table properties");
3261 return cmdNames;
3262}
3263
3264/*
3265@METHOD: WordProcessor::ComputeMaxHScrollPos ()
3266@DESCRIPTION: <p>Override @'TextImager::ComputeMaxHScrollPos' to call
3267 @'WordProcessor::CalculateFarthestRightMargin ()' and
3268 cache the results (for performance reasons).</p>
3269*/
3270DistanceType WordProcessor::ComputeMaxHScrollPos () const
3271{
3272 DistanceType cachedLayoutWidth = 0;
3273 {
3274 /*
3275 * Figure the largest amount we might need to scroll given the current windows contents.
3276 * But take into account where we've scrolled so far, and never invalidate that
3277 * scroll amount. Always leave at least as much layout-width as needed to
3278 * preserve the current scroll-to position.
3279 */
3280 TextInteractor::Tablet_Acquirer tablet_ (this);
3281 Tablet* tablet = tablet_;
3282 DistanceType width = tablet->CvtFromTWIPSH (CalculateFarthestRightMargin ());
3283 if (GetHScrollPos () != 0) {
3284 width = max (width, GetHScrollPos () + GetWindowRect ().GetWidth ());
3285 }
3286 cachedLayoutWidth = max (width, DistanceType (1));
3287 }
3288 DistanceType wWidth = GetWindowRect ().GetWidth ();
3289 if (cachedLayoutWidth > wWidth) {
3290 return (cachedLayoutWidth - wWidth);
3291 }
3292 else {
3293 return 0;
3294 }
3295}
3296
3297/*
3298@METHOD: WordProcessor::CalculateFarthestRightMarginInDocument ()
3299@DESCRIPTION: <p>Calculate how wide an effective margin must be used for specifying the parameters for
3300 a horizontal scrollbar. By default - asks the max row width/margins for all the rows displayed in the
3301 current window (so this value can change when we scroll or edit text).</p>
3302 <p>See also @'WordProcessor::CalculateFarthestRightMarginInWindow'
3303 </p>
3304*/
3305TWIPS WordProcessor::CalculateFarthestRightMarginInDocument () const
3306{
3307 CoordinateType longestRowWidth = 0;
3308 RowReference curRow = RowReference{GetFirstPartitionMarker (), 0};
3309 do {
3310 CoordinateType rhsMargin = 0;
3311 GetLayoutMargins (curRow, nullptr, &rhsMargin);
3312 longestRowWidth = max (longestRowWidth, rhsMargin);
3313 } while (GetNextRowReference (&curRow));
3314 Tablet_Acquirer tablet_ (this);
3315 Tablet* tablet = tablet_;
3316 return tablet->CvtToTWIPSH (longestRowWidth);
3317}
3318
3319/*
3320@METHOD: WordProcessor::GetFarthestRightMarginInDocument ()
3321@DESCRIPTION: <p>See also @'WordProcessor::CalculateFarthestRightMarginInWindow'
3322 </p>
3323*/
3324TWIPS WordProcessor::GetFarthestRightMarginInDocument () const
3325{
3326 AbstractParagraphDatabaseRep* pdbRep = GetParagraphDatabase ().get ();
3327 RequireNotNull (pdbRep); // this shouldn't be called with a null PDB?
3328 if (pdbRep->fCachedFarthestRightMarginInDocument == kBadCachedFarthestRightMarginInDocument) {
3329 pdbRep->fCachedFarthestRightMarginInDocument = CalculateFarthestRightMarginInDocument ();
3330 }
3331 return pdbRep->fCachedFarthestRightMarginInDocument;
3332}
3333
3334/*
3335@METHOD: WordProcessor::CalculateFarthestRightMarginInWindow ()
3336@DESCRIPTION: <p>Calculate how wide an effective margin must be used for specifying the parameters for
3337 a horizontal scrollbar. By default - asks the max row width/margins for all the rows displayed in the
3338 current window (so this value can change when we scroll or edit text).</p>
3339 <p>NB: prior to Led 3.1d8 - this method returned a DistanceType - and it now returns a TWIPS.
3340 </p>
3341*/
3342TWIPS WordProcessor::CalculateFarthestRightMarginInWindow () const
3343{
3344 CoordinateType longestRowWidth = 0;
3345 size_t rowsLeftInWindow = GetTotalRowsInWindow_ ();
3346 RowReference curRow = GetTopRowReferenceInWindow ();
3347 do {
3348 CoordinateType rhsMargin = 0;
3349 GetLayoutMargins (curRow, nullptr, &rhsMargin);
3350 longestRowWidth = max (longestRowWidth, rhsMargin);
3351 } while (rowsLeftInWindow-- > 0 and GetNextRowReference (&curRow));
3352 Tablet_Acquirer tablet_ (this);
3353 Tablet* tablet = tablet_;
3354 return tablet->CvtToTWIPSH (longestRowWidth);
3355}
3356
3357/*
3358@METHOD: WordProcessor::CalculateFarthestRightMargin ()
3359@DESCRIPTION: <p>Typically this will call either @'WordProcessor::GetFarthestRightMarginInDocument' (the default) or
3360 @'WordProcessor::CalculateFarthestRightMarginInWindow'. This is typically called by @'WordProcessor::ComputeMaxHScrollPos'.
3361 </p>
3362*/
3363TWIPS WordProcessor::CalculateFarthestRightMargin () const
3364{
3365 return GetFarthestRightMarginInDocument ();
3366}
3367
3368void WordProcessor::InvalidateAllCaches ()
3369{
3370 inherited::InvalidateAllCaches ();
3371 shared_ptr<AbstractParagraphDatabaseRep> pdb = GetParagraphDatabase ();
3372 if (pdb.get () != nullptr) {
3373 static_cast<AbstractParagraphDatabaseRep*> (pdb.get ())->fCachedFarthestRightMarginInDocument = kBadCachedFarthestRightMarginInDocument;
3374 }
3375}
3376
3377void WordProcessor::TabletChangedMetrics ()
3378{
3379 inherited::TabletChangedMetrics ();
3380 shared_ptr<AbstractParagraphDatabaseRep> pdb = GetParagraphDatabase ();
3381 if (pdb.get () != nullptr) {
3382 static_cast<AbstractParagraphDatabaseRep*> (pdb.get ())->fCachedFarthestRightMarginInDocument = kBadCachedFarthestRightMarginInDocument;
3383 }
3384}
3385
3386void WordProcessor::DidUpdateText (const UpdateInfo& updateInfo) noexcept
3387{
3388 inherited::DidUpdateText (updateInfo);
3389 fCachedCurSelFontSpecValid = false;
3390}
3391
3392void WordProcessor::AssureCurSelFontCacheValid () const
3393{
3394 if (not fCachedCurSelFontSpecValid) {
3395 Assert (GetSelectionEnd () >= GetSelectionStart ());
3396 size_t selectionLength = GetSelectionEnd () - GetSelectionStart ();
3397 WordProcessorTable* aT = GetActiveTable ();
3398 if (aT != nullptr) {
3399 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*aT, *const_cast<WordProcessor*> (this));
3400 aT->AssureCurSelFontCacheValid (&fCachedCurSelFontSpec);
3401 fCachedCurSelJustification = eLeftJustify;
3402 fCachedCurSelJustificationUnique = false;
3403 }
3404 else {
3405 fCachedCurSelFontSpec = GetContinuousStyleInfo (GetSelectionStart (), selectionLength);
3406 fCachedCurSelJustificationUnique = GetJustification (GetSelectionStart (), GetSelectionEnd (), &fCachedCurSelJustification);
3407 }
3408 fCachedCurSelFontSpecValid = true;
3409 }
3410}
3411
3412void WordProcessor::DoSingleCharCursorEdit (CursorMovementDirection direction, CursorMovementUnit movementUnit, CursorMovementAction action,
3413 UpdateMode updateMode, bool scrollToSelection)
3414{
3415 WordProcessorTable* table = GetActiveTable ();
3416 if (table != nullptr) {
3417 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*table, *const_cast<WordProcessor*> (this));
3418 if (table->DoSingleCharCursorEdit (direction, movementUnit, action, updateMode, false)) {
3419 if (scrollToSelection) {
3420 ScrollToSelection ();
3421 }
3422 return;
3423 }
3424 }
3425 inherited::DoSingleCharCursorEdit (direction, movementUnit, action, updateMode, scrollToSelection);
3426}
3427
3428bool WordProcessor::OnUpdateCommand (CommandUpdater* enabler)
3429{
3430 RequireNotNull (enabler);
3431
3432 WordProcessorTable* aT = GetActiveTable ();
3433 if (aT != nullptr) {
3434 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*aT, *const_cast<WordProcessor*> (this));
3435 if (aT->OnUpdateCommand (enabler)) {
3436 return true;
3437 }
3438 }
3439
3440 switch (enabler->GetCmdID ()) {
3441 case kFontStylePlain_CmdID: {
3442 OnUpdateFontStylePlainCommand (enabler);
3443 return true;
3444 }
3445 case kFontStyleBold_CmdID: {
3446 OnUpdateFontStyleBoldCommand (enabler);
3447 return true;
3448 }
3449 case kFontStyleItalic_CmdID: {
3450 OnUpdateFontStyleItalicCommand (enabler);
3451 return true;
3452 }
3453 case kFontStyleUnderline_CmdID: {
3454 OnUpdateFontStyleUnderlineCommand (enabler);
3455 return true;
3456 }
3457#if qStroika_Platform_Windows
3458 case kFontStyleStrikeout_CmdID: {
3459 OnUpdateFontStyleStrikeoutCommand (enabler);
3460 return true;
3461 }
3462#endif
3463 case kSubScriptCommand_CmdID: {
3464 OnUpdateFontStyleSubscriptCommand (enabler);
3465 return true;
3466 }
3467 case kSuperScriptCommand_CmdID: {
3468 OnUpdateFontStyleSuperscriptCommand (enabler);
3469 return true;
3470 }
3471 case kChooseFontCommand_CmdID: {
3472 OnUpdateChooseFontCommand (enabler);
3473 return true;
3474 }
3475 case kInsertTable_CmdID: {
3476 OnUpdateInsertTableCommand (enabler);
3477 return true;
3478 }
3479 case kInsertURL_CmdID: {
3480 OnUpdateInsertURLCommand (enabler);
3481 return true;
3482 }
3483 case kInsertSymbol_CmdID: {
3484 OnUpdateInsertSymbolCommand (enabler);
3485 return true;
3486 }
3487 case kHideSelection_CmdID: {
3488 OnUpdateHideSelectionCommands (enabler);
3489 return true;
3490 }
3491 case kUnHideSelection_CmdID: {
3492 OnUpdateHideSelectionCommands (enabler);
3493 return true;
3494 }
3495 case kParagraphSpacingCommand_CmdID: {
3496 OnUpdateParagraphSpacingChangeCommand (enabler);
3497 return true;
3498 }
3499 case kParagraphIndentsCommand_CmdID: {
3500 OnUpdateParagraphIndentsChangeCommand (enabler);
3501 return true;
3502 }
3503 case kIncreaseIndent_CmdID: {
3504 OnUpdateIndentCommand (enabler);
3505 return true;
3506 }
3507 case kDecreaseIndent_CmdID: {
3508 OnUpdateIndentCommand (enabler);
3509 return true;
3510 }
3511 }
3512
3513 if (kFontMenuFirst_CmdID <= enabler->GetCmdID () and enabler->GetCmdID () <= kFontMenuLast_CmdID) {
3514 OnUpdateFontNameChangeCommand (enabler);
3515 return true;
3516 }
3517 else if (kBaseFontSize_CmdID <= enabler->GetCmdID () and enabler->GetCmdID () <= kLastFontSize_CmdID) {
3518 OnUpdateFontSizeChangeCommand (enabler);
3519 return true;
3520 }
3521 else if (kBaseFontColor_CmdID <= enabler->GetCmdID () and enabler->GetCmdID () <= kLastFontColor_CmdID) {
3522 OnUpdateFontColorChangeCommand (enabler);
3523 return true;
3524 }
3525 else if (kFirstSelectedEmbedding_CmdID <= enabler->GetCmdID () and enabler->GetCmdID () <= kLastSelectedEmbedding_CmdID) {
3526 OnUpdateSelectedEmbeddingExtendedCommand (enabler);
3527 return true;
3528 }
3529 else if (kFirstJustification_CmdID <= enabler->GetCmdID () and enabler->GetCmdID () <= kLastJustification_CmdID) {
3530 OnUpdateParagraphJustificationCommand (enabler);
3531 return true;
3532 }
3533 else if (kFirstShowHideGlyph_CmdID <= enabler->GetCmdID () and enabler->GetCmdID () <= kLastShowHideGlyph_CmdID) {
3534 OnUpdateShowHideGlyphCommand (enabler);
3535 return true;
3536 }
3537 else if (kFirstListStyle_CmdID <= enabler->GetCmdID () and enabler->GetCmdID () <= kLastListStyle_CmdID) {
3538 OnUpdateListStyleChangeCommand (enabler);
3539 return true;
3540 }
3541
3542 return inherited::OnUpdateCommand (enabler);
3543}
3544
3545bool WordProcessor::OnPerformCommand (CommandNumber commandNumber)
3546{
3547 WordProcessorTable* aT = GetActiveTable ();
3548 if (aT != nullptr) {
3549 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*aT, *const_cast<WordProcessor*> (this));
3550 if (aT->OnPerformCommand (commandNumber)) {
3551 return true;
3552 }
3553 }
3554
3555 switch (commandNumber) {
3556 case kFontStylePlain_CmdID: {
3557 OnFontStylePlainCommand ();
3558 return true;
3559 }
3560 case kFontStyleBold_CmdID: {
3561 OnFontStyleBoldCommand ();
3562 return true;
3563 }
3564 case kFontStyleItalic_CmdID: {
3565 OnFontStyleItalicCommand ();
3566 return true;
3567 }
3568 case kFontStyleUnderline_CmdID: {
3569 OnFontStyleUnderlineCommand ();
3570 return true;
3571 }
3572#if qStroika_Platform_Windows
3573 case kFontStyleStrikeout_CmdID: {
3574 OnFontStyleStrikeoutCommand ();
3575 return true;
3576 }
3577#endif
3578 case kSubScriptCommand_CmdID: {
3579 OnFontStyleSubscriptCommand ();
3580 return true;
3581 }
3582 case kSuperScriptCommand_CmdID: {
3583 OnFontStyleSuperscriptCommand ();
3584 return true;
3585 }
3586 case kChooseFontCommand_CmdID: {
3587 OnChooseFontCommand ();
3588 return true;
3589 }
3590 case kInsertTable_CmdID: {
3591 OnInsertTableCommand ();
3592 return true;
3593 }
3594 case kInsertURL_CmdID: {
3595 OnInsertURLCommand ();
3596 return true;
3597 }
3598 case kInsertSymbol_CmdID: {
3599 OnInsertSymbolCommand ();
3600 return true;
3601 }
3602 case kHideSelection_CmdID: {
3603 OnHideSelection ();
3604 return true;
3605 }
3606 case kUnHideSelection_CmdID: {
3607 OnUnHideSelection ();
3608 return true;
3609 }
3610 case kParagraphSpacingCommand_CmdID: {
3611 OnParagraphSpacingChangeCommand ();
3612 return true;
3613 }
3614 case kParagraphIndentsCommand_CmdID: {
3615 OnParagraphIndentsChangeCommand ();
3616 return true;
3617 }
3618 case kIncreaseIndent_CmdID: {
3619 OnIndentCommand (commandNumber);
3620 return true;
3621 }
3622 case kDecreaseIndent_CmdID: {
3623 OnIndentCommand (commandNumber);
3624 return true;
3625 }
3626 }
3627
3628 if (kFontMenuFirst_CmdID <= commandNumber and commandNumber <= kFontMenuLast_CmdID) {
3629 OnFontNameChangeCommand (commandNumber);
3630 return true;
3631 }
3632 else if (kBaseFontSize_CmdID <= commandNumber and commandNumber <= kLastFontSize_CmdID) {
3633 OnFontSizeChangeCommand (commandNumber);
3634 return true;
3635 }
3636 else if (kBaseFontColor_CmdID <= commandNumber and commandNumber <= kLastFontColor_CmdID) {
3637 OnFontColorChangeCommand (commandNumber);
3638 return true;
3639 }
3640 else if (kFirstSelectedEmbedding_CmdID <= commandNumber and commandNumber <= kLastSelectedEmbedding_CmdID) {
3641 return OnSelectedEmbeddingExtendedCommand (commandNumber);
3642 }
3643 else if (kFirstJustification_CmdID <= commandNumber and commandNumber <= kLastJustification_CmdID) {
3644 OnParagraphJustificationCommand (commandNumber);
3645 return true;
3646 }
3647 else if (kFirstShowHideGlyph_CmdID <= commandNumber and commandNumber <= kLastShowHideGlyph_CmdID) {
3648 OnShowHideGlyphCommand (commandNumber);
3649 return true;
3650 }
3651 else if (kFirstListStyle_CmdID <= commandNumber and commandNumber <= kLastListStyle_CmdID) {
3652 OnListStyleChangeCommand (commandNumber);
3653 return true;
3654 }
3655
3656 return inherited::OnPerformCommand (commandNumber);
3657}
3658
3659bool WordProcessor::PassAlongCommandToIntraCellModeTableCell (CommandNumber commandNumber)
3660{
3661 switch (commandNumber) {
3662 // TextInteractor commands
3663 case kCut_CmdID:
3664 return true;
3665 case kCopy_CmdID:
3666 return true;
3667 case kPaste_CmdID:
3668 return true;
3669 case kClear_CmdID:
3670 return true;
3671
3672 case kSelectAll_CmdID: {
3673 /*
3674 * If we get a select-all command, then first select the cell contents. If that
3675 * is already done - then pass on the command to our parent (which will select a wider
3676 * unit). (see SPR#1615).
3677 */
3678 WordProcessorTable* aT = GetActiveTable ();
3679 size_t row = 0;
3680 size_t col = 0;
3681 if (aT != nullptr and aT->GetIntraCellMode (&row, &col)) {
3682 size_t intraCellStart = 0;
3683 size_t intraCellEnd = 0;
3684 aT->GetIntraCellSelection (&intraCellStart, &intraCellEnd);
3685 if (intraCellStart != 0) {
3686 return true;
3687 }
3688 size_t cellEnd = aT->GetCell (row, col).GetTextStore ().GetEnd ();
3689 if (intraCellEnd != cellEnd) {
3690 return true;
3691 }
3692 }
3693 } break;
3694
3695 // WordProcessor commands
3696 case kParagraphSpacingCommand_CmdID:
3697 return true;
3698 case kParagraphIndentsCommand_CmdID:
3699 return true;
3700 case kIncreaseIndent_CmdID:
3701 return true;
3702 case kDecreaseIndent_CmdID:
3703 return true;
3704 case kChooseFontCommand_CmdID:
3705 return true;
3706 case kInsertURL_CmdID:
3707 return true;
3708 case kHideSelection_CmdID:
3709 return true;
3710 case kUnHideSelection_CmdID:
3711 return true;
3712 }
3713
3714 // Ranged WordProcessor commands
3715 if (kBaseFontSize_CmdID <= commandNumber and commandNumber <= kLastFontSize_CmdID) {
3716 return true;
3717 }
3718 if (kBaseFontColor_CmdID <= commandNumber and commandNumber <= kLastFontColor_CmdID) {
3719 return true;
3720 }
3721 if (kFirstJustification_CmdID <= commandNumber and commandNumber <= kLastJustification_CmdID) {
3722 return true;
3723 }
3724 if (kFirstListStyle_CmdID <= commandNumber and commandNumber <= kLastListStyle_CmdID) {
3725 return true;
3726 }
3727 if (kFontMenuFirst_CmdID <= commandNumber and commandNumber <= kFontMenuLast_CmdID) {
3728 return true;
3729 }
3730 if (kFontStyleCommand_FirstCmdId <= commandNumber and commandNumber <= kFontStyleCommand_LastCmdId) {
3731 return true;
3732 }
3733 if (kFirstSelectedEmbedding_CmdID <= commandNumber and commandNumber <= kLastSelectedEmbedding_CmdID) {
3734 return true;
3735 }
3736
3737 return false;
3738}
3739
3740bool WordProcessor::PassAlongCommandToEachSelectedTableCell (CommandNumber commandNumber)
3741{
3742 switch (commandNumber) {
3743 // TextInteractor commands
3744 case kClear_CmdID:
3745 return true;
3746
3747 // WordProcessor commands
3748 case kHideSelection_CmdID:
3749 return true;
3750 case kUnHideSelection_CmdID:
3751 return true;
3752 }
3753
3754 // Ranged WordProcessor commands
3755 if (kBaseFontSize_CmdID <= commandNumber and commandNumber <= kLastFontSize_CmdID) {
3756 return true;
3757 }
3758 if (kBaseFontColor_CmdID <= commandNumber and commandNumber <= kLastFontColor_CmdID) {
3759 return true;
3760 }
3761 if (kFirstJustification_CmdID <= commandNumber and commandNumber <= kLastJustification_CmdID) {
3762 return true;
3763 }
3764 if (kFirstListStyle_CmdID <= commandNumber and commandNumber <= kLastListStyle_CmdID) {
3765 return true;
3766 }
3767 if (kFontMenuFirst_CmdID <= commandNumber and commandNumber <= kFontMenuLast_CmdID) {
3768 return true;
3769 }
3770 if (kFontStyleCommand_FirstCmdId <= commandNumber and commandNumber <= kFontStyleCommand_LastCmdId) {
3771 return true;
3772 }
3773
3774 return false;
3775}
3776
3777/*
3778@METHOD: WordProcessor::OnSelectAllCommand
3779@DESCRIPTION: <p>Override @'TextInteractor::OnSelectAllCommand' () to also make sure we've selected all the cells
3780 in a table - if its the only thing in the document (if we aren't changing the selection - and
3781 have a table selected - then its cells don't automatically get selected otherwise).</p>
3782*/
3783void WordProcessor::OnSelectAllCommand ()
3784{
3785 inherited::OnSelectAllCommand ();
3786 if (GetLength () == 1) {
3787 (void)OnPerformCommand (kSelectTable_CmdID);
3788 }
3789}
3790
3791void WordProcessor::OnUpdateFontNameChangeCommand (CommandUpdater* enabler)
3792{
3793 RequireNotNull (enabler);
3794 IncrementalFontSpecification fontSpec = GetCurSelFontSpec ();
3795 // check the item iff it is the currently selected font.
3796 // But always enable them...
3797 enabler->SetChecked (fontSpec.GetFontNameSpecifier_Valid () and
3798 (GetDialogSupport ().CmdNumToFontName (enabler->GetCmdID ()) == fontSpec.GetFontNameSpecifier ()));
3799 enabler->SetEnabled (true);
3800}
3801
3802void WordProcessor::OnFontNameChangeCommand (CommandNumber cmdNum)
3803{
3804 IncrementalFontSpecification applyFontSpec;
3805 applyFontSpec.SetFontNameSpecifier (GetDialogSupport ().CmdNumToFontName (cmdNum));
3806 InteractiveSetFont (applyFontSpec);
3807}
3808
3809void WordProcessor::OnUpdateFontStylePlainCommand (CommandUpdater* enabler)
3810{
3811 RequireNotNull (enabler);
3812 IncrementalFontSpecification fontSpec = GetCurSelFontSpec ();
3813 enabler->SetChecked (fontSpec.GetStyle_Plain_Valid () and fontSpec.GetStyle_Plain ());
3814 enabler->SetEnabled (true);
3815}
3816
3817void WordProcessor::OnFontStylePlainCommand ()
3818{
3819 IncrementalFontSpecification applyFontSpec;
3820 applyFontSpec.SetStyle_Plain ();
3821 InteractiveSetFont (applyFontSpec);
3822}
3823
3824void WordProcessor::OnUpdateFontStyleBoldCommand (CommandUpdater* enabler)
3825{
3826 RequireNotNull (enabler);
3827 IncrementalFontSpecification fontSpec = GetCurSelFontSpec ();
3828 enabler->SetChecked (fontSpec.GetStyle_Bold_Valid () and fontSpec.GetStyle_Bold ());
3829 enabler->SetEnabled (true);
3830}
3831
3832void WordProcessor::OnFontStyleBoldCommand ()
3833{
3834 IncrementalFontSpecification applyFontSpec;
3835 IncrementalFontSpecification fontSpec = GetCurSelFontSpec ();
3836 applyFontSpec.SetStyle_Bold (not(fontSpec.GetStyle_Bold_Valid () and fontSpec.GetStyle_Bold ()));
3837 InteractiveSetFont (applyFontSpec);
3838}
3839
3840void WordProcessor::OnUpdateFontStyleItalicCommand (CommandUpdater* enabler)
3841{
3842 RequireNotNull (enabler);
3843 IncrementalFontSpecification fontSpec = GetCurSelFontSpec ();
3844 enabler->SetChecked (fontSpec.GetStyle_Italic_Valid () and fontSpec.GetStyle_Italic ());
3845 enabler->SetEnabled (true);
3846}
3847
3848void WordProcessor::OnFontStyleItalicCommand ()
3849{
3850 IncrementalFontSpecification applyFontSpec;
3851 IncrementalFontSpecification fontSpec = GetCurSelFontSpec ();
3852 applyFontSpec.SetStyle_Italic (not(fontSpec.GetStyle_Italic_Valid () and fontSpec.GetStyle_Italic ()));
3853 InteractiveSetFont (applyFontSpec);
3854}
3855
3856void WordProcessor::OnUpdateFontStyleUnderlineCommand (CommandUpdater* enabler)
3857{
3858 RequireNotNull (enabler);
3859 AssureCurSelFontCacheValid ();
3860 enabler->SetChecked (fCachedCurSelFontSpec.GetStyle_Underline_Valid () and fCachedCurSelFontSpec.GetStyle_Underline ());
3861 enabler->SetEnabled (true);
3862}
3863
3864void WordProcessor::OnFontStyleUnderlineCommand ()
3865{
3866 AssureCurSelFontCacheValid ();
3867 IncrementalFontSpecification applyFontSpec;
3868 applyFontSpec.SetStyle_Underline (not(fCachedCurSelFontSpec.GetStyle_Underline_Valid () and fCachedCurSelFontSpec.GetStyle_Underline ()));
3869 InteractiveSetFont (applyFontSpec);
3870}
3871
3872#if qStroika_Platform_Windows
3873
3874void WordProcessor::OnUpdateFontStyleStrikeoutCommand (CommandUpdater* enabler)
3875{
3876 RequireNotNull (enabler);
3877 AssureCurSelFontCacheValid ();
3878 enabler->SetChecked (fCachedCurSelFontSpec.GetStyle_Strikeout_Valid () and fCachedCurSelFontSpec.GetStyle_Strikeout ());
3879 enabler->SetEnabled (true);
3880}
3881
3882void WordProcessor::OnFontStyleStrikeoutCommand ()
3883{
3884 AssureCurSelFontCacheValid ();
3885 IncrementalFontSpecification applyFontSpec;
3886 applyFontSpec.SetStyle_Strikeout (not(fCachedCurSelFontSpec.GetStyle_Strikeout_Valid () and fCachedCurSelFontSpec.GetStyle_Strikeout ()));
3887 InteractiveSetFont (applyFontSpec);
3888}
3889
3890#endif
3891
3892void WordProcessor::OnUpdateFontStyleSubscriptCommand (CommandUpdater* enabler)
3893{
3894 RequireNotNull (enabler);
3895 AssureCurSelFontCacheValid ();
3896 enabler->SetChecked (fCachedCurSelFontSpec.GetStyle_SubOrSuperScript_Valid () and
3897 fCachedCurSelFontSpec.GetStyle_SubOrSuperScript () == FontSpecification::eSubscript);
3898 enabler->SetEnabled (true);
3899}
3900
3901void WordProcessor::OnFontStyleSubscriptCommand ()
3902{
3903 AssureCurSelFontCacheValid ();
3904 IncrementalFontSpecification applyFontSpec;
3905 applyFontSpec.SetStyle_SubOrSuperScript ((fCachedCurSelFontSpec.GetStyle_SubOrSuperScript_Valid () and
3906 fCachedCurSelFontSpec.GetStyle_SubOrSuperScript () == FontSpecification::eSubscript)
3907 ? FontSpecification::eNoSubOrSuperscript
3908 : FontSpecification::eSubscript);
3909 InteractiveSetFont (applyFontSpec);
3910}
3911
3912void WordProcessor::OnUpdateFontStyleSuperscriptCommand (CommandUpdater* enabler)
3913{
3914 RequireNotNull (enabler);
3915 AssureCurSelFontCacheValid ();
3916 enabler->SetChecked (fCachedCurSelFontSpec.GetStyle_SubOrSuperScript_Valid () and
3917 fCachedCurSelFontSpec.GetStyle_SubOrSuperScript () == FontSpecification::eSuperscript);
3918 enabler->SetEnabled (true);
3919}
3920
3921void WordProcessor::OnFontStyleSuperscriptCommand ()
3922{
3923 AssureCurSelFontCacheValid ();
3924 IncrementalFontSpecification applyFontSpec;
3925 applyFontSpec.SetStyle_SubOrSuperScript ((fCachedCurSelFontSpec.GetStyle_SubOrSuperScript_Valid () and
3926 fCachedCurSelFontSpec.GetStyle_SubOrSuperScript () == FontSpecification::eSuperscript)
3927 ? FontSpecification::eNoSubOrSuperscript
3928 : FontSpecification::eSuperscript);
3929 InteractiveSetFont (applyFontSpec);
3930}
3931
3932void WordProcessor::OnUpdateChooseFontCommand (CommandUpdater* enabler)
3933{
3934 RequireNotNull (enabler);
3935 enabler->SetEnabled (true);
3936}
3937
3938void WordProcessor::OnChooseFontCommand ()
3939{
3940 IncrementalFontSpecification curSelFontSpec = GetCurSelFontSpec ();
3941 if (GetDialogSupport ().ChooseFont (&curSelFontSpec)) {
3942 InteractiveSetFont (curSelFontSpec);
3943 }
3944}
3945
3946void WordProcessor::OnUpdateFontSizeChangeCommand (CommandUpdater* enabler)
3947{
3948 RequireNotNull (enabler);
3949 DistanceType chosenFontSize = GetDialogSupport ().FontCmdToSize (enabler->GetCmdID ());
3950
3951 AssureCurSelFontCacheValid ();
3952 if (chosenFontSize == 0) {
3953 switch (enabler->GetCmdID ()) {
3954 case kFontSizeSmaller_CmdID:
3955 case kFontSizeLarger_CmdID: {
3956 enabler->SetEnabled (true);
3957 } break;
3958
3959 case kFontSizeOther_CmdID: {
3960 enabler->SetEnabled (true);
3961 if (fCachedCurSelFontSpec.GetPointSize_Valid ()) {
3962 int pointSize = fCachedCurSelFontSpec.GetPointSize ();
3963 if (not GetDialogSupport ().IsPredefinedFontSize (pointSize)) {
3964 enabler->SetChecked (true);
3965 enabler->SetText (Characters::CString::Format (GetCommandNames ().fFontSizeChange_Other_OneArg.c_str (), pointSize).c_str ());
3966 return;
3967 }
3968 }
3969 enabler->SetChecked (false);
3970 enabler->SetText (GetCommandNames ().fFontSizeChange_Other_NoArg.c_str ());
3971 } break;
3972 }
3973 }
3974 else {
3975 enabler->SetChecked (fCachedCurSelFontSpec.GetPointSize_Valid () and fCachedCurSelFontSpec.GetPointSize () == chosenFontSize);
3976 enabler->SetEnabled (true);
3977 }
3978}
3979
3980void WordProcessor::OnFontSizeChangeCommand (CommandNumber cmdNum)
3981{
3982 DistanceType chosenFontSize = GetDialogSupport ().FontCmdToSize (cmdNum);
3983 if (chosenFontSize == 0) {
3984 switch (cmdNum) {
3985 case kFontSizeSmaller_CmdID: {
3986 IncrementalFontSpecification applyFontSpec;
3987 applyFontSpec.SetPointSizeIncrement (-1);
3988 InteractiveSetFont (applyFontSpec);
3989 return;
3990 } break;
3991 case kFontSizeLarger_CmdID: {
3992 IncrementalFontSpecification applyFontSpec;
3993 applyFontSpec.SetPointSizeIncrement (1);
3994 InteractiveSetFont (applyFontSpec);
3995 return;
3996 } break;
3997 case kFontSizeOther_CmdID: {
3998 DistanceType oldSize = fCachedCurSelFontSpec.GetPointSize_Valid () ? fCachedCurSelFontSpec.GetPointSize () : 0;
3999 chosenFontSize = GetDialogSupport ().PickOtherFontHeight (oldSize);
4000 } break;
4001 }
4002 }
4003 if (chosenFontSize != 0) {
4004 IncrementalFontSpecification applyFontSpec;
4005 applyFontSpec.SetPointSize (static_cast<unsigned short> (chosenFontSize));
4006 InteractiveSetFont (applyFontSpec);
4007 }
4008}
4009
4010void WordProcessor::OnUpdateFontColorChangeCommand (CommandUpdater* enabler)
4011{
4012 RequireNotNull (enabler);
4013 Require (enabler->GetCmdID () >= kBaseFontColor_CmdID and enabler->GetCmdID () <= kLastFontColor_CmdID);
4014 AssureCurSelFontCacheValid ();
4015 if (fCachedCurSelFontSpec.GetTextColor_Valid ()) {
4016 if (enabler->GetCmdID () == kFontColorOther_CmdID) {
4017 // should check all other predefined colors, but for now, just don't check it...
4018 enabler->SetChecked (GetDialogSupport ().FontColorToCmd (fCachedCurSelFontSpec.GetTextColor ()) == kFontColorOther_CmdID);
4019 }
4020 else {
4021 enabler->SetChecked (fCachedCurSelFontSpec.GetTextColor () == GetDialogSupport ().FontCmdToColor (enabler->GetCmdID ()));
4022 }
4023 }
4024 else {
4025 enabler->SetChecked (false);
4026 }
4027 enabler->SetEnabled (true);
4028}
4029
4030void WordProcessor::OnFontColorChangeCommand (CommandNumber cmdNum)
4031{
4032 Require (cmdNum >= kBaseFontColor_CmdID and cmdNum <= kLastFontColor_CmdID);
4033
4034 AssureCurSelFontCacheValid ();
4035 IncrementalFontSpecification applyFontSpec;
4036 if (cmdNum == kFontColorOther_CmdID) {
4037 Color originalColor = GetDefaultFont ().GetTextColor ();
4038 if (fCachedCurSelFontSpec.GetTextColor_Valid ()) {
4039 originalColor = fCachedCurSelFontSpec.GetTextColor ();
4040 }
4041 Color chosenColor = originalColor;
4042 if (GetDialogSupport ().PickOtherFontColor (&chosenColor)) {
4043 applyFontSpec.SetTextColor (chosenColor);
4044 }
4045 }
4046 else {
4047 // Treat color selection like style selection. That is, if text is already red, and you select
4048 // Red, then treat that as 'turning off red' (ie go to black). Otherwise - just treat the command
4049 // as setting the whole wange to that color.
4050 Color chosenColor = GetDialogSupport ().FontCmdToColor (cmdNum);
4051 if (fCachedCurSelFontSpec.GetTextColor_Valid () and fCachedCurSelFontSpec.GetTextColor () == chosenColor) {
4052 applyFontSpec.SetTextColor (Color::kBlack);
4053 }
4054 else {
4055 applyFontSpec.SetTextColor (chosenColor);
4056 }
4057 }
4058 InteractiveSetFont (applyFontSpec);
4059}
4060
4061void WordProcessor::OnUpdateInsertTableCommand (CommandUpdater* enabler)
4062{
4063 RequireNotNull (enabler);
4064 // only allow insert table on empty selection (for now)... Maybe in the future - if there
4065 // is text in the selection - create a table smartly around it???? Looking for tabs etc??
4066 enabler->SetEnabled (GetSelectionStart () == GetSelectionEnd ());
4067}
4068
4069void WordProcessor::OnInsertTableCommand ()
4070{
4071 size_t rows = 2; // 2x3 default row/col count....
4072 size_t cols = 3;
4073 if (GetDialogSupport ().AddNewTableDialog (&rows, &cols)) {
4074 Assert (rows > 0);
4075 Assert (cols > 0);
4076 Assert (rows < 1000); // not strictly required - but would be rediculous...
4077 Assert (cols < 100); // ''
4078
4079 InteractiveModeUpdater iuMode (*this);
4080
4081 // force early failure in abouttoupdate if read-only... before we constrct any objects or bring up and dialogs...
4082 TextStore::SimpleUpdater updater1 (GetTextStore (), GetSelectionStart (), GetSelectionEnd ());
4083
4084 BreakInGroupedCommands ();
4085 UndoableContextHelper context (*this, GetCommandNames ().fInsertTableCommandName, false);
4086 {
4087 // Put up dialog ROW/COL and then insert columns...
4088 WordProcessorTable* t = InsertTable (GetSelectionStart ()); // ignore return result cuz kept track of internally and automatically deleted...
4089 //tmphack - SHOULD come from dialog we launch in here to let user specify
4090 t->SetDimensions (rows, cols);
4091 {
4092 for (size_t r = 0; r < rows; ++r) {
4093 for (size_t c = 0; c < cols; ++c) {
4094 TWIPS targetWidth = TWIPS{0};
4095 {
4096 CoordinateType lhs = 0;
4097 CoordinateType rhs = 0;
4098 GetLayoutMargins (GetRowReferenceContainingPosition (t->GetStart ()), &lhs, &rhs);
4099 Assert (lhs < rhs);
4100 targetWidth = Led_CvtScreenPixelsToTWIPSH (rhs - lhs);
4101 targetWidth = TWIPS ((targetWidth / 5) * 4); // just make it a bit smaller than total possible width
4102 }
4103 t->SetColumnWidth (r, c, TWIPS (static_cast<long> (targetWidth / cols)));
4104
4105 TextStore* ts = nullptr;
4106 t->GetCellWordProcessorDatabases (r, c, &ts);
4107 }
4108 }
4109 }
4110 TextStore::SimpleUpdater updater (GetTextStore (), t->GetStart (), t->GetEnd ());
4111 SetSelection (t->GetEnd (), t->GetEnd ());
4112 }
4113 context.CommandComplete ();
4114 BreakInGroupedCommands ();
4115 }
4116}
4117
4118void WordProcessor::OnUpdateInsertURLCommand (CommandUpdater* enabler)
4119{
4120 RequireNotNull (enabler);
4121 enabler->SetEnabled (GetSelectionStart () == GetSelectionEnd ()); // for now only support on empty selection
4122 // later revise so that if we do this to a non-empty selection, we grab that text and try to make
4123 // it into a URL???
4124}
4125
4126void WordProcessor::OnInsertURLCommand ()
4127{
4128 InteractiveModeUpdater iuMode (*this);
4129 BreakInGroupedCommands ();
4130 SDKString title;
4131 SDKString url;
4132 if (GetDialogSupport ().ShowAddURLEmbeddingInfoDialog (&title, &url)) {
4133 UndoableContextHelper context{*this, GetCommandNames ().fInsertURLCommandName, false};
4134 {
4135 SimpleEmbeddedObjectStyleMarker* e = new StandardURLStyleMarker{
4136 Led_URLD{String::FromSDKString (url).AsNarrowSDKString ().c_str (), String::FromSDKString (title).AsNarrowSDKString ().c_str ()}};
4137 AddEmbedding (e, GetTextStore (), GetSelectionStart (), GetStyleDatabase ().get ());
4138 SetSelection (e->GetEnd (), e->GetEnd ());
4139 }
4140 context.CommandComplete ();
4141 }
4142 BreakInGroupedCommands ();
4143}
4144
4145void WordProcessor::OnUpdateInsertSymbolCommand ([[maybe_unused]] CommandUpdater* enabler)
4146{
4147 RequireNotNull (enabler);
4148#if qStroika_Platform_Windows
4149 enabler->SetEnabled (true);
4150#else
4151 Assert (false); //NYI
4152#endif
4153}
4154
4155void WordProcessor::OnInsertSymbolCommand ()
4156{
4157#if qStroika_Platform_Windows
4158 (void)::ShellExecute (nullptr, Led_SDK_TCHAROF ("open"), Led_SDK_TCHAROF ("CHARMAP.EXE"), nullptr, Led_SDK_TCHAROF (""), SW_SHOWNORMAL);
4159#else
4160 Assert (false); //NYI
4161#endif
4162}
4163
4164void WordProcessor::OnUpdateSelectedEmbeddingExtendedCommand (CommandUpdater* enabler)
4165{
4166 RequireNotNull (enabler);
4167 SimpleEmbeddedObjectStyleMarker* embedding = GetSoleSelectedEmbedding ();
4168 if (enabler->GetCmdID () == kSelectedEmbeddingProperties_CmdID) {
4169 enabler->SetEnabled (embedding != nullptr);
4170 WordProcessorTable* t = GetActiveTable ();
4171 enabler->SetText (t == nullptr ? GetCommandNames ().fGenericEmbeddingPropertiesCommandName.c_str ()
4172 : GetCommandNames ().fTablePropertiesCommandName.c_str ());
4173 }
4174 else {
4175 using PrivateCmdNumber = SimpleEmbeddedObjectStyleMarker::PrivateCmdNumber;
4176 PrivateCmdNumber cmdNum =
4177 static_cast<PrivateCmdNumber> (enabler->GetCmdID () - kFirstPrivateEmbedding_CmdID + SimpleEmbeddedObjectStyleMarker::eMinPrivateCmdNum);
4178 enabler->SetEnabled (embedding != nullptr and embedding->IsCmdEnabled (cmdNum));
4179 if (embedding != nullptr) {
4180 enabler->SetText (embedding->GetCmdText (cmdNum).c_str ());
4181 }
4182 }
4183}
4184
4185bool WordProcessor::OnSelectedEmbeddingExtendedCommand (CommandNumber cmdNum)
4186{
4187 Require (cmdNum >= kFirstSelectedEmbedding_CmdID and cmdNum <= kLastSelectedEmbedding_CmdID);
4188 SimpleEmbeddedObjectStyleMarker* embedding = GetSoleSelectedEmbedding ();
4189 if (embedding == nullptr) {
4190 return false;
4191 }
4192 if (cmdNum == kSelectedEmbeddingProperties_CmdID) {
4193 if (dynamic_cast<StandardURLStyleMarker*> (embedding) != nullptr) {
4194 StandardURLStyleMarker* e = dynamic_cast<StandardURLStyleMarker*> (embedding);
4195 SDKString title = String::FromNarrowSDKString (e->GetURLData ().GetTitle ()).AsSDKString ();
4196 SDKString url = String::FromNarrowSDKString (e->GetURLData ().GetURL ()).AsSDKString ();
4197 if (GetDialogSupport ().ShowURLEmbeddingInfoDialog (GetPrettyTypeName (embedding), &title, &url)) {
4198 // Change URL contents...
4199 {
4200 TextStore::SimpleUpdater updater (GetTextStore (), e->GetStart (), e->GetEnd ());
4201 e->SetURLData (Led_URLD{String::FromSDKString (url).AsNarrowSDKString ().c_str (),
4202 String::FromSDKString (title).AsNarrowSDKString ().c_str ()});
4203 }
4204 }
4205 }
4206 else if (dynamic_cast<WordProcessorTable*> (embedding) != nullptr) {
4207 OnEditTablePropertiesDialog ();
4208 }
4209 else {
4210 // unknown embedding...
4211 GetDialogSupport ().ShowSimpleEmbeddingInfoDialog (GetPrettyTypeName (embedding));
4212 }
4213 }
4214 else {
4215 using PrivateCmdNumber = SimpleEmbeddedObjectStyleMarker::PrivateCmdNumber;
4216 PrivateCmdNumber pCmdNum =
4217 static_cast<PrivateCmdNumber> (cmdNum - kFirstPrivateEmbedding_CmdID + SimpleEmbeddedObjectStyleMarker::eMinPrivateCmdNum);
4218 embedding->DoCommand (pCmdNum);
4219 }
4220 return true;
4221}
4222
4223void WordProcessor::OnEditTablePropertiesDialog ()
4224{
4225 WordProcessorTable* t = GetActiveTable ();
4226 AssertNotNull (t);
4227
4228 /*
4229 * Fill in the selected cells/table properties, and then invoke the dialog.
4230 * Then if the dialog returns TRUE, apply the changes.
4231 */
4232 DialogSupport::TableSelectionPropertiesInfo info;
4233 info.fTableBorderWidth = t->GetTableBorderWidth ();
4234 info.fTableBorderColor = t->GetTableBorderColor ();
4235 t->GetDefaultCellMargins (&info.fDefaultCellMargins.top, &info.fDefaultCellMargins.left, &info.fDefaultCellMargins.bottom,
4236 &info.fDefaultCellMargins.right);
4237 info.fCellSpacing = t->GetCellSpacing ();
4238 {
4239 size_t rowSelStart = 0;
4240 size_t rowSelEnd = 0;
4241 size_t colSelStart = 0;
4242 size_t colSelEnd = 0;
4243 t->GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
4244 info.fCellWidth_Common = false;
4245 info.fCellBackgroundColor_Common = false;
4246 for (size_t r = rowSelStart; r < rowSelEnd; ++r) {
4247 for (size_t c = colSelStart; c < colSelEnd; ++c) {
4248 if (c < t->GetColumnCount (r)) {
4249 TWIPS cellWidth = t->GetColumnWidth (r, c);
4250 Color cellBkgrnd = t->GetCellColor (r, c);
4251 if (r == rowSelStart and c == colSelStart) {
4252 info.fCellWidth_Common = true;
4253 info.fCellWidth = cellWidth;
4254 info.fCellBackgroundColor_Common = true;
4255 info.fCellBackgroundColor = cellBkgrnd;
4256 }
4257 else {
4258 if (info.fCellWidth_Common) {
4259 if (info.fCellWidth != cellWidth) {
4260 info.fCellWidth_Common = false;
4261 }
4262 }
4263 if (info.fCellBackgroundColor_Common) {
4264 if (info.fCellBackgroundColor != cellBkgrnd) {
4265 info.fCellBackgroundColor_Common = false;
4266 }
4267 }
4268 }
4269 }
4270 }
4271 }
4272 }
4273 if (GetDialogSupport ().EditTablePropertiesDialog (&info)) {
4274 UndoableContextHelper context (*this, GetCommandNames ().fChangeTablePropertiesCommandName, false);
4275 {
4276 t->SetTableBorderWidth (info.fTableBorderWidth);
4277 t->SetTableBorderColor (info.fTableBorderColor);
4278 t->SetDefaultCellMargins (info.fDefaultCellMargins.top, info.fDefaultCellMargins.left, info.fDefaultCellMargins.bottom,
4279 info.fDefaultCellMargins.right);
4280 t->SetCellSpacing (info.fCellSpacing);
4281 {
4282 size_t rowSelStart = 0;
4283 size_t rowSelEnd = 0;
4284 size_t colSelStart = 0;
4285 size_t colSelEnd = 0;
4286 t->GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
4287 for (size_t r = rowSelStart; r < rowSelEnd; ++r) {
4288 for (size_t c = colSelStart; c < colSelEnd; ++c) {
4289 if (c < t->GetColumnCount (r)) {
4290 if (info.fCellWidth_Common) {
4291 t->SetColumnWidth (r, c, info.fCellWidth);
4292 }
4293 if (info.fCellBackgroundColor_Common) {
4294 t->SetCellColor (r, c, info.fCellBackgroundColor);
4295 }
4296 }
4297 }
4298 }
4299 }
4300 }
4301 context.CommandComplete ();
4302 }
4303}
4304
4305void WordProcessor::OnUpdateHideSelectionCommands (CommandUpdater* enabler)
4306{
4307 RequireNotNull (enabler);
4308 bool enabled = GetSelectionStart () != GetSelectionEnd ();
4309 if (enabled) {
4310 // check if range already had hidden etc - like GetContiguous for STYLE support....
4311 if (enabler->GetCmdID () == kHideSelection_CmdID) {
4312 enabled = not GetHidableTextDatabase ()->GetHidableRegionsContiguous (GetSelectionStart (), GetSelectionEnd (), true);
4313 }
4314 else if (enabler->GetCmdID () == kUnHideSelection_CmdID) {
4315 enabled = not GetHidableTextDatabase ()->GetHidableRegionsContiguous (GetSelectionStart (), GetSelectionEnd (), false);
4316 }
4317 }
4318 enabler->SetEnabled (enabled);
4319}
4320
4321void WordProcessor::OnHideSelection ()
4322{
4323 InterectiveSetRegionHidable (true);
4324}
4325
4326void WordProcessor::OnUnHideSelection ()
4327{
4328 InterectiveSetRegionHidable (false);
4329}
4330
4331void WordProcessor::OnUpdateParagraphJustificationCommand (CommandUpdater* enabler)
4332{
4333 RequireNotNull (enabler);
4334
4335 AssureCurSelFontCacheValid ();
4336
4337 switch (enabler->GetCmdID ()) {
4338 case kJustifyLeft_CmdID: {
4339 enabler->SetChecked (fCachedCurSelJustificationUnique and (fCachedCurSelJustification == eLeftJustify));
4340 } break;
4341 case kJustifyCenter_CmdID: {
4342 enabler->SetChecked (fCachedCurSelJustificationUnique and (fCachedCurSelJustification == eCenterJustify));
4343 } break;
4344 case kJustifyRight_CmdID: {
4345 enabler->SetChecked (fCachedCurSelJustificationUnique and (fCachedCurSelJustification == eRightJustify));
4346 } break;
4347 case kJustifyFull_CmdID: {
4348 enabler->SetChecked (fCachedCurSelJustificationUnique and (fCachedCurSelJustification == eFullyJustify));
4349 } break;
4350 }
4351 enabler->SetEnabled (true);
4352}
4353
4354void WordProcessor::OnParagraphJustificationCommand (CommandNumber cmdNum)
4355{
4356 switch (cmdNum) {
4357 case kJustifyLeft_CmdID: {
4358 InteractiveSetJustification (eLeftJustify);
4359 } break;
4360 case kJustifyCenter_CmdID: {
4361 InteractiveSetJustification (eCenterJustify);
4362 } break;
4363 case kJustifyRight_CmdID: {
4364 InteractiveSetJustification (eRightJustify);
4365 } break;
4366 case kJustifyFull_CmdID: {
4367 InteractiveSetJustification (eFullyJustify);
4368 } break;
4369 default: {
4370 Assert (false); // shouldn't have been mapped to this function
4371 } break;
4372 }
4373}
4374
4375void WordProcessor::OnUpdateParagraphSpacingChangeCommand (CommandUpdater* enabler)
4376{
4377 RequireNotNull (enabler);
4378 enabler->SetEnabled (true);
4379}
4380
4381void WordProcessor::OnParagraphSpacingChangeCommand ()
4382{
4383 TWIPS spaceBefore = TWIPS{0};
4384 TWIPS spaceAfter = TWIPS{0};
4385 LineSpacing lineSpacing;
4386
4387 bool spaceBeforeValid = GetSpaceBefore (GetSelectionStart (), GetSelectionEnd (), &spaceBefore);
4388 bool spaceAfterValid = GetSpaceAfter (GetSelectionStart (), GetSelectionEnd (), &spaceAfter);
4389 bool lineSpacingValid = GetLineSpacing (GetSelectionStart (), GetSelectionEnd (), &lineSpacing);
4390
4391 if (GetDialogSupport ().PickNewParagraphLineSpacing (&spaceBefore, &spaceBeforeValid, &spaceAfter, &spaceAfterValid, &lineSpacing, &lineSpacingValid)) {
4392 if (spaceBeforeValid) {
4393 }
4394 InteractiveSetParagraphSpacing (spaceBefore, spaceBeforeValid, spaceAfter, spaceAfterValid, lineSpacing, lineSpacingValid);
4395 }
4396}
4397
4398void WordProcessor::OnUpdateParagraphIndentsChangeCommand (CommandUpdater* enabler)
4399{
4400 RequireNotNull (enabler);
4401 enabler->SetEnabled (true);
4402}
4403
4404void WordProcessor::OnParagraphIndentsChangeCommand ()
4405{
4406 TWIPS leftMargin = TWIPS{0};
4407 TWIPS rightMargin = TWIPS{0};
4408 TWIPS firstIndent = TWIPS{0};
4409
4410 // Should retrieve the 'isvalid' flags for margins separately so if we update one we aren't forced to update the other!
4411 bool leftMarginValid = GetMargins (GetSelectionStart (), GetSelectionEnd (), &leftMargin, &rightMargin);
4412 bool rightMarginValid = leftMarginValid;
4413 bool firstIndentValid = GetFirstIndent (GetSelectionStart (), GetSelectionEnd (), &firstIndent);
4414
4415 if (GetDialogSupport ().PickNewParagraphMarginsAndFirstIndent (&leftMargin, &leftMarginValid, &rightMargin, &rightMarginValid,
4416 &firstIndent, &firstIndentValid)) {
4417 // Similarly (as above - we should pass in a flag saying whether or not to change each of the given margins/etc. Maybe pass in by
4418 // pointer, and treat nullptr ptr as meaning no change?
4419 // for now just call if ANYTHING has changed
4420 InteractiveSetMarginsAndFirstIndent (leftMargin, rightMargin, firstIndent);
4421 }
4422}
4423
4424void WordProcessor::OnUpdateListStyleChangeCommand (CommandUpdater* enabler)
4425{
4426 RequireNotNull (enabler);
4427 ListStyle listStyle = eListStyle_None;
4428 bool listStyleValid = GetListStyle (GetSelectionStart (), GetSelectionEnd (), &listStyle);
4429 if (enabler->GetCmdID () == kListStyle_None_CmdID) {
4430 enabler->SetChecked (listStyleValid and listStyle == eListStyle_None);
4431 }
4432 else if (enabler->GetCmdID () == kListStyle_Bullet_CmdID) {
4433 enabler->SetChecked (listStyleValid and listStyle == eListStyle_Bullet);
4434 }
4435 else {
4436 Assert (false); // shouldn't have been mapped to this function
4437 }
4438 enabler->SetEnabled (true);
4439}
4440
4441void WordProcessor::OnListStyleChangeCommand (CommandNumber cmdNum)
4442{
4443 if (cmdNum == kListStyle_None_CmdID) {
4444 InteractiveSetListStyle (eListStyle_None);
4445 }
4446 else if (cmdNum == kListStyle_Bullet_CmdID) {
4447 InteractiveSetListStyle (eListStyle_Bullet);
4448 }
4449 else {
4450 Assert (false); // shouldn't have been mapped to this function
4451 }
4452}
4453
4454void WordProcessor::OnUpdateIndentCommand (CommandUpdater* enabler)
4455{
4456 RequireNotNull (enabler);
4457 ListStyle listStyle = eListStyle_None;
4458 bool listStyleValid = GetListStyle (GetSelectionStart (), GetSelectionEnd (), &listStyle);
4459 if (enabler->GetCmdID () == kIncreaseIndent_CmdID) {
4460 enabler->SetEnabled (listStyleValid and listStyle != eListStyle_None);
4461 }
4462 else if (enabler->GetCmdID () == kDecreaseIndent_CmdID) {
4463 enabler->SetEnabled (listStyleValid and listStyle != eListStyle_None);
4464 }
4465 else {
4466 Assert (false); // shouldn't have been mapped to this function
4467 }
4468}
4469
4470void WordProcessor::OnIndentCommand (CommandNumber cmdNum)
4471{
4472 if (cmdNum == kIncreaseIndent_CmdID) {
4473 InteractiveDoIndentChange (true);
4474 }
4475 else if (cmdNum == kDecreaseIndent_CmdID) {
4476 InteractiveDoIndentChange (false);
4477 }
4478 else {
4479 Assert (false); // shouldn't have been mapped to this function
4480 }
4481}
4482
4483void WordProcessor::OnUpdateShowHideGlyphCommand (CommandUpdater* enabler)
4484{
4485 RequireNotNull (enabler);
4486 Require (enabler->GetCmdID () >= kFirstShowHideGlyph_CmdID);
4487 Require (enabler->GetCmdID () <= kLastShowHideGlyph_CmdID);
4488 // NB: we use an if/else skip-chain instead of switch statement so that X constants
4489 // can be set to magic numbers like zero - to indicate the commands will not be supported.
4490 if (enabler->GetCmdID () == kShowHideParagraphGlyphs_CmdID) {
4491 enabler->SetChecked (GetShowParagraphGlyphs ());
4492 }
4493 else if (enabler->GetCmdID () == kShowHideTabGlyphs_CmdID) {
4494 enabler->SetChecked (GetShowTabGlyphs ());
4495 }
4496 else if (enabler->GetCmdID () == kShowHideSpaceGlyphs_CmdID) {
4497 enabler->SetChecked (GetShowSpaceGlyphs ());
4498 }
4499 else {
4500 Assert (false); // shouldn't have been mapped to this function
4501 }
4502 enabler->SetEnabled (true);
4503}
4504
4505void WordProcessor::OnShowHideGlyphCommand (CommandNumber cmdNum)
4506{
4507 Require (cmdNum >= kFirstShowHideGlyph_CmdID);
4508 Require (cmdNum <= kLastShowHideGlyph_CmdID);
4509 // NB: we use an if/else skip-chain instead of switch statement so that X constants
4510 // can be set to magic numbers like zero - to indicate the commands will not be supported.
4511 if (cmdNum == kShowHideParagraphGlyphs_CmdID) {
4512 SetShowParagraphGlyphs (not GetShowParagraphGlyphs ());
4513 }
4514 else if (cmdNum == kShowHideTabGlyphs_CmdID) {
4515 SetShowTabGlyphs (not GetShowTabGlyphs ());
4516 }
4517 else if (cmdNum == kShowHideSpaceGlyphs_CmdID) {
4518 SetShowSpaceGlyphs (not GetShowSpaceGlyphs ());
4519 }
4520 else {
4521 Assert (false); // shouldn't have been mapped to this function
4522 }
4523}
4524
4525SDKString WordProcessor::GetPrettyTypeName (SimpleEmbeddedObjectStyleMarker* m)
4526{
4527 if (dynamic_cast<StandardDIBStyleMarker*> (m) != nullptr) {
4528 return GetCommandNames ().fEmbeddingTypeName_ImageDIB;
4529 }
4530 else if (dynamic_cast<StandardURLStyleMarker*> (m) != nullptr) {
4531 return GetCommandNames ().fEmbeddingTypeName_URL;
4532 }
4533#if qStroika_Platform_Windows
4534 else if (dynamic_cast<StandardMacPictureStyleMarker*> (m) != nullptr) {
4535 return GetCommandNames ().fEmbeddingTypeName_ImageMacPict;
4536 }
4537#endif
4538 else if (dynamic_cast<WordProcessorTable*> (m) != nullptr) {
4539 return GetCommandNames ().fEmbeddingTypeName_Table;
4540 }
4541 else {
4542 return GetCommandNames ().fEmbeddingTypeName_Unknown;
4543 }
4544}
4545
4546SimpleEmbeddedObjectStyleMarker* WordProcessor::GetSoleSelectedEmbedding () const
4547{
4548 size_t selStart = GetSelectionStart ();
4549 size_t selEnd = GetSelectionEnd ();
4550 Require (selStart <= selEnd);
4551 if (selEnd - selStart == 1) {
4552 vector<SimpleEmbeddedObjectStyleMarker*> embeddings = CollectAllEmbeddingMarkersInRange (selStart, selEnd);
4553 if (embeddings.size () == 1) {
4554 return (dynamic_cast<SimpleEmbeddedObjectStyleMarker*> (embeddings[0]));
4555 }
4556 else {
4557 return nullptr;
4558 }
4559 }
4560 else {
4561 return nullptr;
4562 }
4563}
4564
4565/*
4566@METHOD: WordProcessor::GetListLeader
4567@DESCRIPTION:
4568 <p>Return the string (computed often, as with roman numeral lists) which gets inserted to designate the
4569 list marker.</p>
4570*/
4571Led_tString WordProcessor::GetListLeader (size_t paragraphMarkerPos) const
4572{
4573 if (fParagraphDatabase.get () == nullptr) {
4574 throw NoParagraphDatabaseAvailable ();
4575 }
4576 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (paragraphMarkerPos);
4577 if (pi.GetListStyle () == eListStyle_None) {
4578 return Led_tString{};
4579 }
4580 else {
4581 const Led_tChar kBulletChar = 0x2022;
4582 // In a future release, pay attention to RTF \levelfollowN (0 tab, 1 space, 2 nothing)
4583 // For now, sample RTF docs with MSWord 2k appear to write out 0 (tab) by default.
4584 return Led_tString{&kBulletChar, 1} + LED_TCHAR_OF ("\t");
4585 }
4586}
4587
4588/*
4589@METHOD: WordProcessor::GetListLeaderLength
4590@DESCRIPTION:
4591 <p>Return the width (DistanceType) of the leader. Based on result from @'WordProcessor::GetListLeader'.</p>
4592*/
4593DistanceType WordProcessor::GetListLeaderLength (size_t paragraphMarkerPos) const
4594{
4595 if (fParagraphDatabase.get () == nullptr) {
4596 throw NoParagraphDatabaseAvailable ();
4597 }
4598
4599 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (paragraphMarkerPos);
4600 TWIPS lhsTWIPS = pi.GetLeftMargin ();
4601 lhsTWIPS += pi.GetFirstIndent ();
4602
4603 Tablet_Acquirer tablet_ (this);
4604 Tablet* tablet = tablet_;
4605 CoordinateType lhs = tablet->CvtFromTWIPSH (lhsTWIPS);
4606 Led_tString leader = GetListLeader (paragraphMarkerPos);
4607
4608 size_t len = leader.length ();
4609 if (len == 0) {
4610 return 0;
4611 }
4612 else {
4613 Memory::StackBuffer<DistanceType> distanceResults{Memory::eUninitialized, len};
4614 FontSpecification nextCharsFontStyle = GetStyleInfo (paragraphMarkerPos);
4615 FontSpecification useBulletFont = GetStaticDefaultFont ();
4616 useBulletFont.SetPointSize (max (static_cast<unsigned short> (14), nextCharsFontStyle.GetPointSize ()));
4617 MeasureSegmentWidth_ (useBulletFont, paragraphMarkerPos, paragraphMarkerPos + len, leader.c_str (), distanceResults.data ());
4618 (void)ResetTabStopsWithMargin (lhs, paragraphMarkerPos, leader.c_str (), len, distanceResults.data (), 0);
4619 DistanceType result = distanceResults[len - 1];
4620 return result;
4621 }
4622}
4623
4624InteractiveReplaceCommand::SavedTextRep* WordProcessor::InteractiveUndoHelperMakeTextRep (size_t regionStart, size_t regionEnd, size_t selStart, size_t selEnd)
4625{
4626 using SavedTextRep = InteractiveReplaceCommand::SavedTextRep;
4627 if (regionStart == regionEnd) {
4628 return new EmptySelectionParagraphSavedTextRep (this, selStart, selEnd, regionStart);
4629 }
4630 else {
4631 SavedTextRep* basicRep = inherited::InteractiveUndoHelperMakeTextRep (regionStart, regionEnd, selStart, selEnd);
4632 WordProcessorTable* aT = GetActiveTable ();
4633 if (aT != nullptr) {
4634 return new WordProcessorTable::SavedTextRepWSel (basicRep, *aT, WordProcessorTable::SavedTextRepWSel::eWPAbove);
4635 }
4636 return basicRep;
4637 }
4638}
4639
4640/*
4641@METHOD: WordProcessor::InteractiveSetFont
4642@DESCRIPTION: <p>Override @'StandardStyledTextInteractor::InteractiveSetFont' to handle embedded tables.</p>
4643*/
4644void WordProcessor::InteractiveSetFont (const IncrementalFontSpecification& defaultFont)
4645{
4646 WordProcessorTable* aT = GetActiveTable ();
4647 if (aT != nullptr) {
4648 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*aT, *this);
4649 aT->InteractiveSetFont (defaultFont);
4650 return;
4651 }
4652 inherited::InteractiveSetFont (defaultFont);
4653}
4654
4655/*
4656@METHOD: WordProcessor::InteractiveSetJustification
4657@DESCRIPTION:
4658*/
4659void WordProcessor::InteractiveSetJustification (Justification justification)
4660{
4661 Justification oldJustification;
4662 if (not GetJustification (GetSelectionStart (), GetSelectionEnd (), &oldJustification) or (oldJustification != justification)) {
4663 InteractiveWPHelper1<DoIt_SetJustification> (this, justification);
4664 }
4665}
4666
4667/*
4668@METHOD: WordProcessor::InteractiveSetStandardTabStopList
4669@DESCRIPTION:
4670*/
4671void WordProcessor::InteractiveSetStandardTabStopList (StandardTabStopList tabStops)
4672{
4673 StandardTabStopList oldTabs;
4674 if (not GetStandardTabStopList (GetSelectionStart (), GetSelectionEnd (), &oldTabs) or (oldTabs != tabStops)) {
4675 InteractiveWPHelper1<DoIt_SetStandardTabStopList> (this, tabStops);
4676 }
4677}
4678
4679/*
4680@METHOD: WordProcessor::InteractiveSetMargins
4681@DESCRIPTION:
4682*/
4683void WordProcessor::InteractiveSetMargins (TWIPS leftMargin, TWIPS rightMargin)
4684{
4685 TWIPS oldLeftMargin = TWIPS{0};
4686 TWIPS oldRightMargin = TWIPS{0};
4687 if (not GetMargins (GetSelectionStart (), GetSelectionEnd (), &oldLeftMargin, &oldRightMargin) or
4688 ((oldLeftMargin != leftMargin) or (oldRightMargin != rightMargin))) {
4689 InteractiveWPHelper1<DoIt_SetMargins> (this, DoIt_SetMargins::Margins (leftMargin, rightMargin));
4690 }
4691}
4692
4693/*
4694@METHOD: WordProcessor::InteractiveSetFirstIndent
4695@DESCRIPTION:
4696*/
4697void WordProcessor::InteractiveSetFirstIndent (TWIPS firstIndent)
4698{
4699 TWIPS oldFirstIndent = TWIPS{0};
4700 if (not GetFirstIndent (GetSelectionStart (), GetSelectionEnd (), &oldFirstIndent) or (oldFirstIndent != firstIndent)) {
4701 InteractiveWPHelper1<DoIt_SetFirstIndent> (this, firstIndent);
4702 }
4703}
4704
4705/*
4706@METHOD: WordProcessor::InteractiveSetMarginsAndFirstIndent
4707@DESCRIPTION: <p>Roughly equivalent to @'WordProcessor::InteractiveSetMargins' followed by @'WordProcessor::InteractiveSetFirstIndent'
4708 except that they are bundled together into one action, as far as UNDO is concerned.</p>
4709*/
4710void WordProcessor::InteractiveSetMarginsAndFirstIndent (TWIPS leftMargin, TWIPS rightMargin, TWIPS firstIndent)
4711{
4712 TWIPS oldLeftMargin = TWIPS{0};
4713 TWIPS oldRightMargin = TWIPS{0};
4714 TWIPS oldFirstIndent = TWIPS{0};
4715 if (not GetMargins (GetSelectionStart (), GetSelectionEnd (), &oldLeftMargin, &oldRightMargin) or
4716 ((oldLeftMargin != leftMargin) or (oldRightMargin != rightMargin)) or
4717 not GetFirstIndent (GetSelectionStart (), GetSelectionEnd (), &oldFirstIndent) or (oldFirstIndent != firstIndent)) {
4718 InteractiveWPHelper1<DoIt_SetMarginsAndFirstIndent> (
4719 this, DoIt_SetMarginsAndFirstIndent::MarginsAndFirstIndent (leftMargin, rightMargin, firstIndent));
4720 }
4721}
4722
4723/*
4724@METHOD: WordProcessor::InteractiveSetParagraphSpacing
4725@DESCRIPTION:
4726*/
4727void WordProcessor::InteractiveSetParagraphSpacing (TWIPS spaceBefore, bool spaceBeforeValid, TWIPS spaceAfter, bool spaceAfterValid,
4728 LineSpacing lineSpacing, bool lineSpacingValid)
4729{
4730 /*
4731 * If any of these things have changed - do the command.
4732 */
4733 TWIPS oldSpaceBefore = TWIPS{0};
4734 TWIPS oldSpaceAfter = TWIPS{0};
4735 LineSpacing oldLineSpacing;
4736 if (not GetSpaceBefore (GetSelectionStart (), GetSelectionEnd (), &oldSpaceBefore) or (oldSpaceBefore != spaceBefore) or
4737 not GetSpaceAfter (GetSelectionStart (), GetSelectionEnd (), &oldSpaceAfter) or (oldSpaceAfter != spaceAfter) or
4738 not GetLineSpacing (GetSelectionStart (), GetSelectionEnd (), &oldLineSpacing) or (oldLineSpacing != lineSpacing)) {
4739 InteractiveWPHelper1<DoIt_SetParagraphSpacing> (
4740 this, DoIt_SetParagraphSpacing::AllSpacingArgs (spaceBefore, spaceBeforeValid, spaceAfter, spaceAfterValid, lineSpacing, lineSpacingValid));
4741 }
4742}
4743
4744/*
4745@METHOD: WordProcessor::InteractiveSetListStyle
4746@DESCRIPTION:
4747*/
4748void WordProcessor::InteractiveSetListStyle (ListStyle listStyle)
4749{
4750 /*
4751 * If any of these things have changed - do the command.
4752 */
4753 ListStyle oldListStyle = eListStyle_None;
4754 if (not GetListStyle (GetSelectionStart (), GetSelectionEnd (), &oldListStyle) or oldListStyle != listStyle) {
4755 InteractiveWPHelper1<DoIt_SetListStyle> (this, listStyle);
4756 }
4757}
4758
4759/*
4760@METHOD: WordProcessor::InteractiveDoIndentChange
4761@DESCRIPTION:
4762*/
4763void WordProcessor::InteractiveDoIndentChange (bool increase)
4764{
4765 InteractiveWPHelper1<DoIt_IndentUnIndentList> (this, increase);
4766}
4767
4768/*
4769@METHOD: WordProcessor::GetTabStopList
4770@DESCRIPTION: <p>Override @'TextImager::GetTabStopList' - to return the tabstoplist associated with this paragraph.</p>
4771*/
4772const TabStopList& WordProcessor::GetTabStopList (size_t containingPos) const
4773{
4774 return fParagraphDatabase->GetParagraphInfo (containingPos).GetTabStopList ();
4775}
4776
4777void WordProcessor::DrawBefore (const Led_Rect& subsetToDraw, bool printing)
4778{
4779 size_t winStart = GetMarkerPositionOfStartOfWindow ();
4780 size_t winEnd = GetMarkerPositionOfEndOfWindow (); // note that this COULD cause lots of word-wrapping
4781 // and computation
4782
4783 // Check the current window-display region has no unprocessed tables, and process any if needed
4784 vector<WordProcessorTable*> tables = GetTablesInRange (winStart, winEnd);
4785 for (auto i = tables.begin (); i != tables.end (); ++i) {
4786 WordProcessorTable* t = *i;
4787 if (t->fNeedLayout != WordProcessorTable::eDone) {
4788 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*t, *this);
4789 t->PerformLayout ();
4790 }
4791 }
4792 inherited::DrawBefore (subsetToDraw, printing);
4793}
4794
4795/*
4796@METHOD: WordProcessor::DrawRowSegments
4797@DESCRIPTION: <p>Override @'TextImager::DrawRowSegments' to support things like indents, and justification.</p>
4798*/
4799void WordProcessor::DrawRowSegments (Tablet* tablet, const Led_Rect& currentRowRect, const Led_Rect& invalidRowRect,
4800 const TextLayoutBlock& text, size_t rowStart, size_t rowEnd)
4801{
4802 if (fParagraphDatabase.get () == nullptr) {
4803 throw NoParagraphDatabaseAvailable ();
4804 }
4805 RowReference row = GetRowReferenceContainingPosition (rowStart);
4806 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (rowStart);
4807
4808 Led_Rect adjustedDrawInto = currentRowRect;
4809 {
4810 CoordinateType lhsMargin = 0;
4811 GetLayoutMargins (row, &lhsMargin, nullptr);
4812 adjustedDrawInto += Led_Point (0, lhsMargin);
4813 }
4814
4815 {
4816 if (row.GetSubRow () == 0) {
4817 ListStyle ls = pi.GetListStyle ();
4818 if (ls != eListStyle_None) {
4819 /*
4820 * For tables - ignore the list style - SPR#1394.
4821 */
4822 bool allowLists = true;
4823 if (rowEnd - rowStart == 1) {
4824 vector<WordProcessorTable*> tables = GetTablesInRange (rowStart, rowEnd);
4825 if (tables.size () == 1) {
4826 allowLists = false;
4827 }
4828 }
4829 if (allowLists) {
4830 // For now - treat ALL lists as bullet lists...
4831 Led_Rect xxx = adjustedDrawInto;
4832 Led_tString listLeader = GetListLeader (rowStart);
4833 xxx.SetLeft (xxx.GetLeft () - GetListLeaderLength (rowStart));
4834 FontSpecification nextCharsFontStyle = GetStyleInfo (rowStart);
4835 FontSpecification useBulletFont = GetStaticDefaultFont ();
4836 useBulletFont.SetPointSize (max (static_cast<unsigned short> (14), nextCharsFontStyle.GetPointSize ()));
4837 CoordinateType baseLine = xxx.GetTop () + MeasureSegmentBaseLine (rowStart, rowStart);
4838 DrawSegment_ (tablet, useBulletFont, rowStart, rowStart + listLeader.length (),
4839 TextLayoutBlock_Basic (listLeader.c_str (), listLeader.c_str () + listLeader.length ()), xxx, baseLine, nullptr);
4840 }
4841 }
4842 }
4843 }
4844
4845 switch (pi.GetJustification ()) {
4846 case eCenterJustify: {
4847 adjustedDrawInto.SetLeft (adjustedDrawInto.GetLeft () + CalcSpaceToEat (rowStart) / 2);
4848 } break;
4849 case eRightJustify: {
4850 adjustedDrawInto.SetLeft (adjustedDrawInto.GetLeft () + CalcSpaceToEat (rowStart));
4851 } break;
4852 }
4853 if (not adjustedDrawInto.IsEmpty ()) {
4854 inherited::DrawRowSegments (tablet, adjustedDrawInto, invalidRowRect, text, rowStart, rowEnd);
4855 if (GetShowParagraphGlyphs ()) {
4856 // check for the last row of a partitionelement, and if we hit it - patch the text and rowEnd guys..
4857 PartitionElementCacheInfo pmCacheInfo = GetPartitionElementCacheInfo (GetPartitionMarkerContainingPosition (rowStart));
4858 if (row.GetSubRow () + 1 == pmCacheInfo.GetRowCount () and rowEnd < GetEnd ()) {
4859 const Led_tChar newline = '\n';
4860 FontSpecification nextCharsFontStyle = GetStyleInfo (rowEnd);
4861 FontSpecification useFont = GetStaticDefaultFont ();
4862 useFont.SetPointSize (max (static_cast<unsigned short> (14), nextCharsFontStyle.GetPointSize ()));
4863 useFont.SetTextColor (Color::kGray);
4864 Led_Rect yyy = adjustedDrawInto;
4865 DistanceType segmentWidth = CalcSegmentSize (rowStart, rowEnd);
4866 yyy.left += segmentWidth;
4867 CoordinateType baseLine = yyy.GetTop () + MeasureSegmentBaseLine (rowStart, rowStart);
4868 DrawSegment_ (tablet, useFont, rowEnd, rowEnd + 1, TextLayoutBlock_Basic (&newline, &newline + 1), yyy, baseLine, nullptr);
4869 }
4870 }
4871 }
4872}
4873
4874/*
4875@METHOD: WordProcessor::GetRowHilightRects
4876@DESCRIPTION: <p>Override @'TextImager::GetRowHilightRects' to support tables.</p>
4877*/
4878vector<Led_Rect> WordProcessor::GetRowHilightRects (const TextLayoutBlock& text, size_t rowStart, size_t rowEnd, size_t hilightStart, size_t hilightEnd) const
4879{
4880 size_t len = rowEnd - rowStart;
4881 if (len == 1 and text.PeekAtRealText ()[0] == kEmbeddingSentinelChar) {
4882 vector<WordProcessorTable*> tables = GetTablesInRange (rowStart, rowEnd);
4883 Assert (tables.size () <= 1);
4884 if (not tables.empty ()) {
4885 WordProcessorTable* table = tables[0];
4886 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*table, *const_cast<WordProcessor*> (this));
4887 return table->GetRowHilightRects ();
4888 }
4889 }
4890 return inherited::GetRowHilightRects (text, rowStart, rowEnd, hilightStart, hilightEnd);
4891}
4892
4893void WordProcessor::DrawSegment (Tablet* tablet, size_t from, size_t to, const TextLayoutBlock& text, const Led_Rect& drawInto,
4894 const Led_Rect& invalidRect, CoordinateType useBaseLine, DistanceType* pixelsDrawn)
4895{
4896 DistanceType pixelsDrawn_;
4897 if (GetShowTabGlyphs ()) {
4898 if (pixelsDrawn == nullptr) {
4899 pixelsDrawn_ = 0;
4900 pixelsDrawn = &pixelsDrawn_;
4901 }
4902 }
4903
4904 inherited::DrawSegment (tablet, from, to, text, drawInto, invalidRect, useBaseLine, pixelsDrawn);
4905
4906 // Find all tabs in the segment, and draw them
4907 if (GetShowTabGlyphs () and GetJustification (from) == eLeftJustify) {
4908 for (size_t i = from; i < to; ++i) {
4909 if (text.PeekAtVirtualText ()[i - from] == '\t') {
4910 // Then I need to find distance from last char-pos to this one to draw my glyph
4911 DistanceType beforeTabPos = drawInto.GetLeft () + CalcSegmentSize (from, i);
4912 AssertNotNull (pixelsDrawn);
4913 DistanceType afterTabPos = drawInto.GetLeft () + CalcSegmentSize (from, i + 1);
4914 Assert (beforeTabPos < afterTabPos);
4915 Led_Rect tabRect = drawInto;
4916 tabRect.SetLeft (beforeTabPos);
4917 tabRect.SetRight (afterTabPos);
4918 Assert (tabRect.GetLeft () < tabRect.GetRight ());
4919#if 1
4920 {
4921 Color arrowColor = GetEffectiveDefaultTextColor (eDefaultTextColor);
4922 // Draw a line down the middle (with a couple pixel sluff on either side)
4923 Led_Rect arrowBody = tabRect;
4924 arrowBody.left += 2;
4925 arrowBody.right -= 2;
4926 arrowBody.top += arrowBody.GetHeight () / 2;
4927 arrowBody.bottom = arrowBody.top + 1;
4928 arrowBody.bottom = min (arrowBody.bottom, drawInto.bottom); // just in case...
4929 tablet->EraseBackground_SolidHelper (arrowBody, arrowColor);
4930
4931 // Create the arrow head. Make a list of points. Then create a region from those points, and fill it.
4932 const CoordinateType kArrowHSize = 8;
4933 const CoordinateType kArrowVSize = 8;
4934 Led_Point tip = arrowBody.GetTopRight ();
4935 CoordinateType hTriangleBase = max (arrowBody.right - kArrowHSize, arrowBody.left);
4936 Led_Point topPt = Led_Point (max (arrowBody.GetTop () - kArrowVSize / 2, tabRect.top), hTriangleBase);
4937 Led_Point botPt = Led_Point (min (arrowBody.GetTop () + kArrowVSize / 2, tabRect.bottom), hTriangleBase);
4938#if qStroika_Platform_Windows
4939 Brush backgroundBrush (arrowColor.GetOSRep ());
4940 GDI_Obj_Selector pen (tablet, ::GetStockObject (NULL_PEN));
4941 GDI_Obj_Selector brush (tablet, backgroundBrush);
4942 POINT pts[3];
4943 pts[0] = AsPOINT (tip);
4944 pts[1] = AsPOINT (topPt);
4945 pts[2] = AsPOINT (botPt);
4946 Verify (::Polygon (*tablet, pts, static_cast<int> (std::size (pts))));
4947#else
4948//NYI - but not too serious - cuz looks pretty reasonable without arrow head - just the line...
4949#endif
4950 }
4951#else
4952 //TMPHACK SO WE CAN SEE WEVE GOT RIGHT AREA:
4953 tablet->EraseBackground_SolidHelper (tabRect, Color::kRed);
4954#endif
4955 }
4956 }
4957 }
4958}
4959
4960DistanceType WordProcessor::MeasureSegmentHeight (size_t from, size_t to) const
4961{
4962 Tablet_Acquirer tablet_ (this);
4963 Tablet* tablet = tablet_;
4964 DistanceType d = inherited::MeasureSegmentHeight (from, to);
4965
4966 if (d == 0) {
4967 shared_ptr<HidableTextMarkerOwner> hdb = GetHidableTextDatabase ();
4968 if (hdb.get () != nullptr) {
4969 DiscontiguousRun<bool> regions = hdb->GetHidableRegions (from, to);
4970 if (not regions.empty ()) {
4971 if (not regions[0].fData) {
4972 return 0;
4973 }
4974 }
4975 }
4976 }
4977
4978 PartitionMarker* pm = GetPartitionMarkerContainingPosition (from);
4979 size_t pmStart = pm->GetStart ();
4980 size_t pmEnd = pm->GetEnd ();
4981
4982 /*
4983 * For tables - ignore the line spacing and row spacing parameters. Check stuff like pmStart/End to avoid
4984 * expensive call to GetTablesInRange () except when needed. See SPR#1336.
4985 */
4986 if (pmEnd - pmStart == 1) {
4987 vector<WordProcessorTable*> tables = GetTablesInRange (pmStart, pmEnd);
4988 if (tables.size () == 1) {
4990 WordProcessorTable* t = tables[0];
4991 Assert (t->GetStart () == pmStart);
4992 Assert (t->GetEnd () == pmEnd);
4993 }
4994 return d;
4995 }
4996 }
4997
4998 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (from);
4999 {
5000 LineSpacing sl = pi.GetLineSpacing ();
5001 switch (sl.fRule) {
5002 case LineSpacing::eOnePointFiveSpace:
5003 d *= 3;
5004 d /= 2;
5005 break;
5006 case LineSpacing::eDoubleSpace:
5007 d *= 2;
5008 break;
5009 case LineSpacing::eAtLeastTWIPSSpacing:
5010 d = max (DistanceType (tablet->CvtFromTWIPSV (TWIPS (sl.fArg))), d);
5011 break;
5012 case LineSpacing::eExactTWIPSSpacing:
5013 d = tablet->CvtFromTWIPSV (TWIPS (sl.fArg));
5014 break;
5015 case LineSpacing::eExactLinesSpacing:
5016 d *= sl.fArg;
5017 d /= 20;
5018 break;
5019
5020 default: // Treat as Single space
5021 case LineSpacing::eSingleSpace:
5022 break;
5023 }
5024 }
5025 {
5026 bool isStartOfPara = pmStart == from; // Not a great way to check - but hopefully good enough? LGP 2000/05/30
5027 if (isStartOfPara) {
5028 d += tablet->CvtFromTWIPSV (pi.GetSpaceBefore ());
5029 }
5030 }
5031 {
5032 bool isEndOfPara = pmEnd <= to + 1; // Not a great way to check - but hopefully good enough? LGP 2000/05/30
5033 if (isEndOfPara) {
5034 d += tablet->CvtFromTWIPSV (pi.GetSpaceAfter ());
5035 }
5036 }
5037 return d;
5038}
5039
5040DistanceType WordProcessor::MeasureMinSegDescent (size_t from, size_t to) const
5041{
5042 /*
5043 * See StyledTextImager::MeasureSegmentHeight () for something similar - that this code is paterned after.
5044 */
5045
5046 Require (from <= to);
5047 if (from == to) { // HACK/TMP? SO WE GET AT LEAST ONE SUMMARY RECORD?? LGP 951018
5048 to = from + 1;
5049 }
5050
5051 vector<StyleRunElement> outputSummary = SummarizeStyleMarkers (from, to);
5052
5053 size_t outputSummaryLength = outputSummary.size ();
5054 Assert (outputSummaryLength != 0);
5055 DistanceType minHeightBelow = 0;
5056 size_t indexIntoText = 0;
5057 for (size_t i = 0; i < outputSummaryLength; ++i) {
5058 const StyleRunElement& re = outputSummary[i];
5059 size_t reFrom = indexIntoText + from;
5060 size_t reLength = re.fLength;
5061 size_t reTo = reFrom + reLength;
5062 Assert (indexIntoText <= to - from);
5063 DistanceType itsBaseline;
5064 DistanceType itsHeight;
5065 if (re.fMarker == nullptr) {
5066 itsBaseline = MeasureSegmentBaseLine_ (GetDefaultFont (), reFrom, reTo);
5067 itsHeight = MeasureSegmentHeight_ (GetDefaultFont (), reFrom, reTo);
5068 }
5069 else {
5070 itsBaseline = re.fMarker->MeasureSegmentBaseLine (this, re, reFrom, reTo);
5071 itsHeight = re.fMarker->MeasureSegmentHeight (this, re, reFrom, reTo);
5072 }
5073 minHeightBelow = min (minHeightBelow, (itsHeight - itsBaseline));
5074 indexIntoText += reLength;
5075 }
5076 return minHeightBelow;
5077}
5078
5079void WordProcessor::AdjustBestRowLength (size_t textStart, const Led_tChar* text, const Led_tChar* end, size_t* rowLength)
5080{
5081 RequireNotNull (text);
5082 RequireNotNull (end);
5083 Require (text < end);
5084 RequireNotNull (rowLength);
5085 Require (*rowLength > 0);
5086 inherited::AdjustBestRowLength (textStart, text, end, rowLength);
5087 for (const Led_tChar* cur = &text[0]; cur < end; cur = Led_NextChar (cur)) {
5088 if (*cur == kEmbeddingSentinelChar) {
5089 // Check if its inside a table - and if yes - then rowLength=1
5090 vector<WordProcessorTable*> tables = GetTablesInRange (textStart + cur - text, textStart + cur - text + 1);
5091 if (not tables.empty ()) {
5092 Assert (cur == text);
5093 size_t newBestRowLength = (cur - text) + 1;
5094 Assert (newBestRowLength <= *rowLength + 1); // Assure newBestRowLength is less than it would have been without the
5095 // softlinebreak character, EXCEPT if the softlinebreak char is already
5096 // at the spot we would have broken - then the row gets bigger by the
5097 // one softlinebreak char length...
5098 // LGP 2001-05-09 (see SPR707 test file-SimpleAlignDivTest.html)
5099 Assert (newBestRowLength >= 1);
5100 *rowLength = newBestRowLength;
5101 break;
5102 }
5103 }
5104 // SINCE THE WHOLE PM MUST BE A TABLE OR NOT IN THE PM AT ALL, WE CAN DO THIS...
5105 break;
5106 }
5107}
5108
5109DistanceType WordProcessor::MeasureSegmentBaseLine (size_t from, size_t to) const
5110{
5111 /*
5112 * The default algorithm will ask each display marker for its baseline - and that will
5113 * return - basicly - the MAX of the asents of all the segments.
5114 */
5115 DistanceType d = inherited::MeasureSegmentBaseLine (from, to);
5116
5117 if (d == 0) {
5118 shared_ptr<HidableTextMarkerOwner> hdb = GetHidableTextDatabase ();
5119 if (hdb.get () != nullptr) {
5120 DiscontiguousRun<bool> regions = hdb->GetHidableRegions (from, to);
5121 if (not regions.empty ()) {
5122 if (not regions[0].fData) {
5123 return 0;
5124 }
5125 }
5126 }
5127 }
5128
5129 PartitionMarker* pm = GetPartitionMarkerContainingPosition (from);
5130 size_t pmStart = pm->GetStart ();
5131 size_t pmEnd = pm->GetEnd ();
5132
5133 /*
5134 * For tables - ignore the line spacing and row spacing parameters. Check stuff like pmStart/End to avoid
5135 * expensive call to GetTablesInRange () except when needed. See SPR#1336.
5136 */
5137 if (pmEnd - pmStart == 1) {
5138 vector<WordProcessorTable*> tables = GetTablesInRange (pmStart, pmEnd);
5139 if (tables.size () == 1) {
5141 WordProcessorTable* t = tables[0];
5142 Assert (t->GetStart () == pmStart);
5143 Assert (t->GetEnd () == pmEnd);
5144 }
5145 return d;
5146 }
5147 }
5148
5149 /*
5150 * Then - in one special case of line spacing - we can actually REDUCE the amount of line height.
5151 * In that case - we will make a corresponding reduction in the baseline (so we trim the TOP of the
5152 * text - not the bottom).
5153 * The question is exactly how? Where do we steal the pixels from.
5154 * Again - following what MSWord 2000 does - it seems to compute the MINIMAL decent for all the text in the region. And use that.
5155 * Then - it takes what height it has been allowed (by the LineSpacing::eExactTWIPSSpacing or whatever) - and then computes
5156 * the baseline based on the given height and the MINIMAL (rather than the normal maximal) decent.
5157 */
5158 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (from);
5159 {
5160 LineSpacing sl = pi.GetLineSpacing ();
5161 switch (sl.fRule) {
5162 case LineSpacing::eExactTWIPSSpacing: {
5163 Tablet_Acquirer tablet_ (this);
5164 Tablet* tablet = tablet_;
5165 DistanceType revisedSegHeight = tablet->CvtFromTWIPSV (TWIPS (sl.fArg));
5166 DistanceType mhb = MeasureMinSegDescent (from, to); // aka decent
5167 d = revisedSegHeight - mhb;
5168 } break;
5169 case LineSpacing::eExactLinesSpacing: {
5170 if (sl.fArg < 20) {
5171 Tablet_Acquirer tablet_ (this);
5172 DistanceType normalSegHeight = inherited::MeasureSegmentHeight (from, to);
5173 DistanceType revisedSegHeight = normalSegHeight * sl.fArg / 20;
5174 DistanceType mhb = MeasureMinSegDescent (from, to); // aka decent
5175 d = revisedSegHeight - mhb;
5176 }
5177 } break;
5178 }
5179 }
5180
5181 /*
5182 * Then - we must add in any SPACE - BEFORE.
5183 */
5184 bool isStartOfPara = pmStart == from; // Not a great way to check - but hopefully good enough? LGP 2000/05/30
5185 if (isStartOfPara) {
5186 TWIPS sb = pi.GetSpaceBefore ();
5187 if (sb != 0) {
5188 Tablet_Acquirer tablet_ (this);
5189 Tablet* tablet = tablet_;
5190 d += tablet->CvtFromTWIPSV (sb);
5191 }
5192 }
5193 return d;
5194}
5195
5196/*
5197@METHOD: WordProcessor::SetShowParagraphGlyphs
5198@DESCRIPTION: <p>The 'ShowParagraphGlyphs' property determines whether or not a glyph (image)
5199 is shown on the screen where the end of a paragraph (newline character) would be if it were visible.</p>
5200 <p>This defaults to <em>off</em></p>
5201 <p>See also @'WordProcessor::GetShowParagraphGlyphs'.</p>
5202*/
5203void WordProcessor::SetShowParagraphGlyphs (bool showParagraphGlyphs)
5204{
5205 if (fShowParagraphGlyphs != showParagraphGlyphs) {
5206 fShowParagraphGlyphs = showParagraphGlyphs;
5207 InvalidateAllCaches (); // Can change font metrics for some segments (as well as screen display)
5208 Refresh ();
5209 }
5210}
5211
5212/*
5213@METHOD: WordProcessor::SetShowTabGlyphs
5214@DESCRIPTION: <p>The 'ShowTabGlyphs' property determines whether or not a glyph (image)
5215 is shown on the screen where the tab characters would be if they were visible.</p>
5216 <p>This defaults to <em>off</em></p>
5217 <p>See also @'WordProcessor::GetShowTabGlyphs'.</p>
5218*/
5219void WordProcessor::SetShowTabGlyphs (bool showTabGlyphs)
5220{
5221 if (fShowTabGlyphs != showTabGlyphs) {
5222 fShowTabGlyphs = showTabGlyphs;
5223 Refresh (); // just invalidates the screen display - but not the cached font metrics...
5224 }
5225}
5226
5227/*
5228@METHOD: WordProcessor::SetShowSpaceGlyphs
5229@DESCRIPTION: <p>The 'ShowSpaceGlyphs' property determines whether or not a glyph (image)
5230 is shown on the screen where the space characters would be if they were visible.</p>
5231 <p>This defaults to <em>off</em></p>
5232 <p>See also @'WordProcessor::GetShowSpaceGlyphs'.</p>
5233*/
5234void WordProcessor::SetShowSpaceGlyphs (bool showSpaceGlyphs)
5235{
5236 if (fShowSpaceGlyphs != showSpaceGlyphs) {
5237 fShowSpaceGlyphs = showSpaceGlyphs;
5238 InvalidateAllCaches (); // Can change font metrics for some segments (as well as screen display)
5239 Refresh ();
5240 }
5241}
5242
5243size_t WordProcessor::ComputeRelativePosition (size_t fromPos, CursorMovementDirection direction, CursorMovementUnit movementUnit)
5244{
5245 size_t result = inherited::ComputeRelativePosition (fromPos, direction, movementUnit);
5246
5247 /*
5248 * Quicky implementation to skip over hidable text when using arrow keys.
5249 * Note sure this is right - esp about to-end/to-start stuff?? But seems to work
5250 * halfway decently. -- LGP 2000-08-07
5251 */
5252 shared_ptr<HidableTextMarkerOwner> hdb = GetHidableTextDatabase ();
5253 if (hdb.get () == nullptr) {
5254 Again:
5255 if (direction == eCursorBack or direction == eCursorToStart) {
5256 DiscontiguousRun<bool> regions = hdb->GetHidableRegions (FindPreviousCharacter (result), result);
5257 if (not regions.empty ()) {
5258 //should walk list???
5259
5260 if (not regions[0].fData) {
5261 result = FindPreviousCharacter (result);
5262 goto Again;
5263 }
5264 }
5265 }
5266 else {
5267 DiscontiguousRun<bool> regions = hdb->GetHidableRegions (result, FindNextCharacter (result));
5268 if (not regions.empty ()) {
5269 //should walk list???
5270
5271 if (not regions[0].fData) {
5272 result = FindNextCharacter (result);
5273 goto Again;
5274 }
5275 }
5276 }
5277 }
5278
5279 return result;
5280}
5281
5282/*
5283@METHOD: WordProcessor::ContainsMappedDisplayCharacters
5284@DESCRIPTION: Override @'TextImager::ContainsMappedDisplayCharacters' to optionally map '\n' etc characters.
5285*/
5286bool WordProcessor::ContainsMappedDisplayCharacters (const Led_tChar* text, size_t nTChars) const
5287{
5288 if (fShowParagraphGlyphs and nTChars > 0 and text[nTChars - 1] == '\n') {
5289 return true;
5290 }
5291 if (fShowSpaceGlyphs and nTChars > 0 and ContainsMappedDisplayCharacters_HelperForChar (text, nTChars, ' ')) {
5292 return true;
5293 }
5294 return inherited::ContainsMappedDisplayCharacters (text, nTChars);
5295}
5296
5297/*
5298@METHOD: WordProcessor::ReplaceMappedDisplayCharacters
5299@DESCRIPTION: Override @'TextImager::ContainsMappedDisplayCharacters' to optionally map '\n' etc characters.
5300*/
5301void WordProcessor::ReplaceMappedDisplayCharacters (const Led_tChar* srcText, Led_tChar* copyText, size_t nTChars) const
5302{
5303 inherited::ReplaceMappedDisplayCharacters (srcText, copyText, nTChars);
5304 if (fShowParagraphGlyphs and nTChars > 0 and srcText[nTChars - 1] == '\n') {
5305 // Windoze-specific char - whats equiv on Mac?
5306 const Led_tChar kReplacementChar = 0x00b6;
5307 copyText[nTChars - 1] = kReplacementChar;
5308 }
5309 if (fShowSpaceGlyphs) {
5310 // NOT SURE WHAT CHAR (on any platform) to replace with. Maybe can do best with UNICODE?
5311 const Led_tChar kReplacementChar = 0x00b7;
5312 ReplaceMappedDisplayCharacters_HelperForChar (copyText, nTChars, ' ', kReplacementChar);
5313 }
5314}
5315
5316/*
5317@METHOD: WordProcessor::GetCharLocationRowRelative
5318@DESCRIPTION: Override @'TextImager::GetCharLocationRowRelative' to adjust calculations for
5319 things like indents, and justification.
5320*/
5321Led_Rect WordProcessor::GetCharLocationRowRelative (size_t afterPosition, RowReference topRow, size_t maxRowsToCheck) const
5322{
5323 Led_Rect r = inherited::GetCharLocationRowRelative (afterPosition, topRow, maxRowsToCheck);
5324
5325 {
5326 CoordinateType lhsMargin = 0;
5327 RowReference row = GetRowReferenceContainingPosition (afterPosition);
5328 GetLayoutMargins (row, &lhsMargin, nullptr);
5329 r.left += lhsMargin;
5330 r.right += lhsMargin;
5331 }
5332
5333 Justification justification = GetJustification (afterPosition);
5334 if (justification == eCenterJustify or justification == eRightJustify) {
5335 switch (justification) {
5336 case eCenterJustify: {
5337 DistanceType d = CalcSpaceToEat (afterPosition) / 2;
5338 r.left += d;
5339 r.right += d;
5340 } break;
5341 case eRightJustify: {
5342 DistanceType d = CalcSpaceToEat (afterPosition);
5343 r.left += d;
5344 r.right += d;
5345 } break;
5346 }
5347 }
5348 return r;
5349}
5350
5351/*
5352@METHOD: WordProcessor::GetCharAtLocationRowRelative
5353@DESCRIPTION: <p>Override @'TextImager::GetCharAtLocationRowRelative' to adjust calculations for
5354 things like indents, and justification.</p>
5355*/
5356size_t WordProcessor::GetCharAtLocationRowRelative (const Led_Point& where, RowReference topRow, size_t maxRowsToCheck) const
5357{
5358 // First find the right row. Justification etc will only effect where we are in the row.
5359 size_t posInRow = inherited::GetCharAtLocationRowRelative (where, topRow, maxRowsToCheck);
5360 RowReference row = GetRowReferenceContainingPosition (posInRow);
5361 Led_Point adjustedWhere = where;
5362 {
5363 CoordinateType lhsMargin = 0;
5364 GetLayoutMargins (row, &lhsMargin, nullptr);
5365 adjustedWhere.h -= lhsMargin;
5366 }
5367 Justification justification = GetJustification (posInRow);
5368 switch (justification) {
5369 case eCenterJustify: {
5370 adjustedWhere.h -= CalcSpaceToEat (posInRow) / 2;
5371 } break;
5372 case eRightJustify: {
5373 adjustedWhere.h -= CalcSpaceToEat (posInRow);
5374 } break;
5375 }
5376 return inherited::GetCharAtLocationRowRelative (adjustedWhere, topRow, maxRowsToCheck);
5377 Assert (false); /*NotReached*/
5378 return 0;
5379}
5380
5381/*
5382@METHOD: WordProcessor::ResetTabStops
5383@DESCRIPTION: Override @'PartitioningTextImager::ResetTabStops' to adjust the tabstop computation to take
5384 into account the left hand side margin.
5385*/
5386size_t WordProcessor::ResetTabStops (size_t from, const Led_tChar* text, size_t nTChars, DistanceType* charLocations, size_t startSoFar) const
5387{
5388 CoordinateType lhsMargin = 0;
5389 {
5390 RowReference row = GetRowReferenceContainingPosition (from);
5391 GetLayoutMargins (row, &lhsMargin, nullptr);
5392 }
5393 return ResetTabStopsWithMargin (lhsMargin, from, text, nTChars, charLocations, startSoFar);
5394}
5395
5396size_t WordProcessor::ResetTabStopsWithMargin (DistanceType lhsMargin, size_t from, const Led_tChar* text, size_t nTChars,
5397 DistanceType* charLocations, size_t startSoFar) const
5398{
5399 RequireNotNull (charLocations);
5400 size_t lastTabIndex = 0;
5401 CoordinateType tabAdjust = 0;
5402 DistanceType widthAtStart = (startSoFar == 0 ? 0 : charLocations[startSoFar - 1]);
5403 for (size_t i = startSoFar; i < startSoFar + nTChars; ++i) {
5404 if (text[i] == '\t') {
5405 DistanceType widthSoFar = (i == 0 ? 0 : charLocations[i - 1]);
5406 tabAdjust = widthAtStart +
5407 GetTabStopList (from).ComputeTabStopAfterPosition (Tablet_Acquirer (this), widthSoFar - widthAtStart + lhsMargin) -
5408 lhsMargin - charLocations[i];
5409 lastTabIndex = i;
5410 }
5411 charLocations[i] += tabAdjust;
5412 }
5413 return (lastTabIndex);
5414}
5415
5416/*
5417@METHOD: WordProcessor::GetLayoutMargins
5418@DESCRIPTION: Override @'WordWrappedTextImager::GetLayoutMargins' to take into account paragraph info, like
5419 margins, etc.
5420*/
5421void WordProcessor::GetLayoutMargins (RowReference row, CoordinateType* lhs, CoordinateType* rhs) const
5422{
5423 if (fParagraphDatabase.get () == nullptr) {
5424 throw NoParagraphDatabaseAvailable ();
5425 }
5426
5427 PartitionMarker* pm = row.GetPartitionMarker ();
5428 size_t pmStart = pm->GetStart ();
5429
5430 Tablet_Acquirer tablet_ (this);
5431 Tablet* tablet = tablet_;
5432
5433 ParagraphInfo pi = fParagraphDatabase->GetParagraphInfo (pmStart);
5434 if (lhs != nullptr) {
5435 TWIPS lhsTWIPS = pi.GetLeftMargin ();
5436 if (row.GetSubRow () == 0) {
5437 lhsTWIPS += pi.GetFirstIndent ();
5438 }
5439 *lhs = tablet->CvtFromTWIPSH (lhsTWIPS);
5440 if (row.GetSubRow () == 0) {
5441 ListStyle ls = pi.GetListStyle ();
5442 if (ls != eListStyle_None) {
5443 /*
5444 * For tables - ignore the list style - SPR#1394.
5445 */
5446 bool allowLists = true;
5447 size_t pmEnd = pm->GetEnd ();
5448 if (pmEnd - pmStart == 1) {
5449 vector<WordProcessorTable*> tables = GetTablesInRange (pmStart, pmEnd);
5450 if (tables.size () == 1) {
5451 allowLists = false;
5452 }
5453 }
5454 if (allowLists) {
5455 *lhs += GetListLeaderLength (pmStart);
5456 }
5457 }
5458 }
5459 }
5460 if (rhs != nullptr) {
5461 *rhs = tablet->CvtFromTWIPSH (pi.GetRightMargin ());
5462 }
5463}
5464
5465void WordProcessor::ExpandedFromAndToInPostReplace (size_t from, size_t newTo, size_t stableTypingRegionStart, size_t stableTypingRegionEnd,
5466 size_t startPositionOfRowWhereReplaceBegins, size_t startPositionOfRowAfterReplaceEnds,
5467 size_t* expandedFrom, size_t* expandedTo)
5468{
5469 Justification justification = GetJustification (from);
5470 if (justification != eLeftJustify) {
5471 from = GetStartOfRowContainingPosition (from);
5472 }
5473 inherited::ExpandedFromAndToInPostReplace (from, newTo, stableTypingRegionStart, stableTypingRegionEnd, startPositionOfRowWhereReplaceBegins,
5474 startPositionOfRowAfterReplaceEnds, expandedFrom, expandedTo);
5475}
5476
5477void WordProcessor::PostReplace (PreReplaceInfo& preReplaceInfo)
5478{
5479 inherited::PostReplace (preReplaceInfo);
5480 UpdateMode updateMode = preReplaceInfo.GetUpdateMode ();
5481 if (updateMode != eNoUpdate) {
5482 if (preReplaceInfo.GetTo () + 2 >= GetEnd ()) {
5483 /*
5484 * Normal invalidation mechanism only invalidates where text could be drawn. But we draw the bullet/list
5485 * markers just outside of this region. If there are any such list attributes - assure we invalidated
5486 * all those markers on the side. This only seems to come up when typing into a list (or deleting text from a list)
5487 * at the end of the text buffer. -- LGP 2001-07-12 (SPR#0906)
5488 */
5489 ListStyle ls = eListStyle_None;
5490 if (not WordProcessor::GetListStyle (preReplaceInfo.GetFrom (), GetEnd (), &ls) or ls != eListStyle_None) {
5491 Refresh (updateMode);
5492 }
5493 }
5494 }
5495}
5496
5497DistanceType WordProcessor::CalcSpaceToEat (size_t rowContainingCharPos) const
5498{
5499 size_t rowStart = GetStartOfRowContainingPosition (rowContainingCharPos);
5500 size_t rowEnd = GetEndOfRowContainingPosition (rowStart);
5501 Assert (rowEnd == GetEndOfRowContainingPosition (rowContainingCharPos));
5502
5503 {
5504 size_t lenOfText = rowEnd - rowStart;
5505 Memory::StackBuffer<Led_tChar> buf{Memory::eUninitialized, lenOfText};
5506 CopyOut (rowStart, lenOfText, buf.data ());
5507 // Throw away trailing space characters
5508 while (rowStart < rowEnd) {
5509 size_t i = rowEnd - rowStart - 1;
5510 if (Character{buf[i]}.IsWhitespace ()) {
5511 --rowEnd;
5512 }
5513 else {
5514 break;
5515 }
5516 }
5517 }
5518
5519 DistanceType segmentWidth = CalcSegmentSize (rowStart, rowEnd);
5520 DistanceType layoutWidth;
5521 {
5522 CoordinateType lhsMargin = 0;
5523 CoordinateType rhsMargin = 0;
5524 GetLayoutMargins (GetRowReferenceContainingPosition (rowStart), &lhsMargin, &rhsMargin);
5525 Assert (lhsMargin < rhsMargin);
5526 layoutWidth = rhsMargin - lhsMargin;
5527 }
5528
5529 //HACK WORKAROUND FOR SPR#0565
5530 if (layoutWidth < segmentWidth) {
5531 return 0;
5532 }
5533
5534 Assert (layoutWidth >= segmentWidth); // is this always so? Maybe not with trailing whitespace???? Should that be ignored for center justification? Etc?
5535 DistanceType xtra = layoutWidth - segmentWidth;
5536 return (xtra);
5537}
5538
5539/*
5540 ********************************************************************************
5541 ********************* WordProcessorFlavorPackageInternalizer *******************
5542 ********************************************************************************
5543 */
5544using WordProcessorFlavorPackageInternalizer = WordProcessor::WordProcessorFlavorPackageInternalizer;
5545
5546WordProcessorFlavorPackageInternalizer::WordProcessorFlavorPackageInternalizer (TextStore& ts, const shared_ptr<AbstractStyleDatabaseRep>& styleDatabase,
5547 const shared_ptr<AbstractParagraphDatabaseRep>& paragraphDatabase,
5548 const shared_ptr<HidableTextMarkerOwner>& hidableTextDatabase)
5550 , inherited (ts, styleDatabase)
5551 , fOverwriteTableMode (false)
5552 ,
5553#if !qStroika_Frameworks_Led_NestedTablesSupported
5554 fNoTablesAllowed (false)
5555 ,
5556#endif
5557 fParagraphDatabase (paragraphDatabase)
5558 , fHidableTextDatabase (hidableTextDatabase)
5559{
5560}
5561
5562StandardStyledTextIOSinkStream* WordProcessorFlavorPackageInternalizer::mkStandardStyledTextIOSinkStream (size_t insertionStart)
5563{
5564 WordProcessorTextIOSinkStream* sinkStream =
5565 new WordProcessorTextIOSinkStream (PeekAtTextStore (), fStyleDatabase, fParagraphDatabase, fHidableTextDatabase, insertionStart);
5566 sinkStream->SetIgnoreLastParaAttributes (true);
5567 sinkStream->SetOverwriteTableMode (GetOverwriteTableMode ());
5568#if !qStroika_Frameworks_Led_NestedTablesSupported
5569 sinkStream->SetNoTablesAllowed (GetNoTablesAllowed ());
5570#endif
5571
5572 return sinkStream;
5573}
5574
5575/*
5576 ********************************************************************************
5577 **************************** WordProcessor::WPPartition ************************
5578 ********************************************************************************
5579 */
5580WordProcessor::WPPartition::WPPartition (TextStore& textStore, MarkerOwner& tableMarkerOwner)
5581 : inherited{textStore, eSpecialHackToDisableInit}
5582 , fTableMarkerOwner{tableMarkerOwner}
5583{
5584 FinalConstruct ();
5585 Invariant ();
5586}
5587
5588vector<WordProcessorTable*> WordProcessor::WPPartition::GetTablesInRange (size_t from, size_t to) const
5589{
5590 if (to == static_cast<size_t> (-1)) {
5591 to = GetTextStore ().GetLength ();
5592 }
5593 Require (from <= to);
5594 Require (to <= GetTextStore ().GetLength () + 1);
5595 MarkersOfATypeMarkerSink2Vector<WordProcessorTable> result;
5596 GetTextStore ().CollectAllMarkersInRangeInto (from, to, &fTableMarkerOwner, result);
5597 return result.fResult;
5598}
5599
5600WordProcessorTable* WordProcessor::GetActiveTable () const
5601{
5602 size_t selStart = 0;
5603 size_t selEnd = 0;
5604 GetSelection (&selStart, &selEnd);
5605 if (selEnd - selStart == 1) {
5606 vector<WordProcessorTable*> tables = GetTablesInRange (selStart, selEnd);
5607 Assert (tables.size () <= 1);
5608 if (tables.size () == 1) {
5609 EnsureNotNull (tables[0]);
5610 return tables[0];
5611 }
5612 }
5613 return nullptr;
5614}
5615
5616void WordProcessor::WPPartition::FinalConstruct ()
5617{
5618 // MUST FIX SO WE DO SOMETHING HERE (old dohandleupdate code maybe eliminated)
5619 inherited::FinalConstruct ();
5620 DoHandleUpdateForTableRangeCheck (0, GetTextStore ().GetLength ());
5621}
5622
5623void WordProcessor::WPPartition::DidUpdateText (const UpdateInfo& updateInfo) noexcept
5624{
5625 // cuz random ordering of whether table DidUpdateText() gets called first or PartitionElt::DidUpdateText () - so we msut
5626 // do our checks HERE - to make sure size of table has been adjusted.
5627 {
5628 DoHandleUpdateForTableRangeCheck (updateInfo.fReplaceFrom, updateInfo.GetResultingRHS ());
5629 }
5630
5631 inherited::DidUpdateText (updateInfo);
5632}
5633
5634void WordProcessor::WPPartition::DoHandleUpdateForTableRangeCheck (size_t from, size_t to) noexcept
5635{
5636 TextStore& ts = GetTextStore ();
5637
5638 // must go one forward/back to make sure we get new chars inserted BEFORE a table or just after one
5639 vector<WordProcessorTable*> tables = GetTablesInRange (ts.FindPreviousCharacter (from), ts.FindNextCharacter (to));
5640 for (auto i = tables.begin (); i != tables.end (); ++i) {
5641 WordProcessorTable* t = *i;
5642 if (t->GetLength () != 0) {
5643 size_t tableEnd = t->GetEnd ();
5644 // may need logic similar to that below
5645 // maybe try this:
5646 size_t tableStart = t->GetStart ();
5647 PartitionMarker* pm = GetPartitionMarkerContainingPosition (tableStart);
5648 /*
5649 * Since Partition::Split always leaves 'pm' pointing to the BEGINNING of the range, its OK to do two splits in a row
5650 * so long as we do the one further to the right first.
5651 */
5652 if (tableEnd < pm->GetEnd () and tableEnd > pm->GetStart ()) {
5653 Split (pm, tableEnd);
5654 }
5655 if (tableStart > pm->GetStart () and tableStart < pm->GetEnd ()) {
5656 Split (pm, tableStart);
5657 }
5658
5659 // See if after insertion of that text this PM needs to be coalesed with the next
5660 bool coalesce = NeedToCoalesce (pm);
5661 if (coalesce) {
5662 Coalece (pm); // 'pm' is DELETED BY THIS SO DO NOTHING to it AFTERWARDS!!!
5663 }
5664 pm = pm->GetPrevious ();
5665 if (pm != nullptr) {
5666 coalesce = NeedToCoalesce (pm);
5667 if (coalesce) {
5668 Coalece (pm); // 'pm' is DELETED BY THIS SO DO NOTHING to it AFTERWARDS!!!
5669 }
5670 pm = pm->GetPrevious ();
5671 if (pm != nullptr) {
5672 coalesce = NeedToCoalesce (pm);
5673 if (coalesce) {
5674 Coalece (pm); // 'pm' is DELETED BY THIS SO DO NOTHING to it AFTERWARDS!!!
5675 }
5676 }
5677 }
5678 }
5679 }
5680
5681 PartitionMarker* pm = GetPartitionMarkerContainingPosition (from);
5682 // See if after insertion of that text this PM needs to be coalesed with the next
5683 bool coalesce = NeedToCoalesce (pm);
5684 if (coalesce) {
5685 Coalece (pm); // 'pm' is DELETED BY THIS SO DO NOTHING to it AFTERWARDS!!!
5686 }
5687 pm = pm->GetPrevious ();
5688 if (pm != nullptr) {
5689 coalesce = NeedToCoalesce (pm);
5690 if (coalesce) {
5691 Coalece (pm); // 'pm' is DELETED BY THIS SO DO NOTHING to it AFTERWARDS!!!
5692 }
5693 pm = pm->GetPrevious ();
5694 if (pm != nullptr) {
5695 coalesce = NeedToCoalesce (pm);
5696 if (coalesce) {
5697 Coalece (pm); // 'pm' is DELETED BY THIS SO DO NOTHING to it AFTERWARDS!!!
5698 }
5699 }
5700 }
5701}
5702
5703bool WordProcessor::WPPartition::NeedToCoalesce (PartitionMarker* pm) noexcept
5704{
5705 RequireNotNull (pm);
5706
5707 bool coalesce = inherited::NeedToCoalesce (pm);
5708 if (coalesce) {
5709 /*
5710 * If default implementation said to coalese - it could have been for good reasons, or bad. One good reason would be
5711 * an empty marker. Another would be if this marker didn't end with a table (and some other conditions were met).
5712 * We just need to make sure it wasn't mistaken becasue the PM either IS the end of a table or comes just before one.
5713 * In those cases, we negate the decision of the default code.
5714 */
5715 if (pm->GetLength () != 0) {
5716 size_t end = pm->GetEnd ();
5717 size_t trStart = end - 1;
5718 size_t trEnd = end;
5719 if (pm->GetNext () != nullptr) {
5720 ++trEnd;
5721 }
5722
5723 vector<WordProcessorTable*> tables = GetTablesInRange (trStart, trEnd);
5724 if (not tables.empty ()) {
5725 if (tables.size () == 2) {
5726 // then we must split between the two and so NO need to coalese
5727 return false;
5728 }
5729 else if (tables.size () == 1) {
5730 WordProcessorTable* table = tables[0];
5731 // If table contains this point - then coalese - otherwise don't
5732 if (table->GetStart () == pm->GetEnd ()) {
5733 return false;
5734 }
5735 else if (table->GetEnd () == pm->GetEnd ()) {
5736 return false;
5737 }
5738 }
5739 }
5740 }
5741 }
5742 return coalesce;
5743}
5744
5745#if qStroika_Foundation_Debug_AssertionsChecked
5746void WordProcessor::WPPartition::Invariant_ () const
5747{
5748 Partition::Invariant_ (); // Cannot call LineBasedPartition::Invariant_ () - AKA inherited::Invariant_ () because
5749 // that assumes when a PM ends its cuz of a newline. BUT - it COULD be because of a
5750 // table instead.
5751
5752 /*
5753 * Assure that for ALL PMs, there are no tables in the middle, and no newlines, and that the ends
5754 * of PMs are marked by either the EOB, or a newline or a table.
5755 */
5756 for (PartitionMarker* cur = GetFirstPartitionMarker (); cur != nullptr; cur = cur->GetNext ()) {
5757 AssertNotNull (cur);
5758 size_t start = cur->GetStart ();
5759 size_t end = cur->GetEnd ();
5760 size_t len = end - start;
5761
5762 if (end > GetEnd ()) {
5763 --len; // Last partition extends past end of text
5764 }
5765 Memory::StackBuffer<Led_tChar> buf{Memory::eUninitialized, len};
5766 CopyOut (start, len, buf.data ());
5767 for (size_t i = 1; i < len; ++i) {
5768 Assert (buf[i - 1] != '\n');
5769 vector<WordProcessorTable*> tables = GetTablesInRange (start + i - 1, start + i);
5770 if (not tables.empty ()) {
5771 Assert (tables.size () == 1);
5772 WordProcessorTable* t = tables[0];
5773 if (t->GetLength () != 0) {
5774 Assert (t->GetStart () == start);
5775 Assert (t->GetLength () == len);
5776 }
5777 }
5778 }
5779 if (cur->GetNext () != nullptr) { // All but the last partition must be NL terminated...
5780 Assert (buf[len - 1] == '\n' or (not GetTablesInRange (start + len - 1, start + len).empty ()) or
5781 (start + len + 1 <= GetEnd () and not GetTablesInRange (start + len, start + len + 1).empty ()));
5782 }
5783 }
5784 /*
5785 * Assure that for ALL existing tables, their starts and ends correspond to PM start/ends.
5786 */
5787 vector<WordProcessorTable*> tables = GetTablesInRange (0, GetTextStore ().GetLength ());
5788 for (auto i = tables.begin (); i != tables.end (); ++i) {
5789 WordProcessorTable* t = *i;
5790 if (t->GetLength () != 0) {
5791 PartitionMarker* pm = GetPartitionMarkerContainingPosition (t->GetStart ());
5792 Assert (t->GetStart () == pm->GetStart ());
5793 Assert (t->GetEnd () == pm->GetEnd ());
5794 }
5795 }
5796}
5797#endif
5798
5799/*
5800 ********************************************************************************
5801 ********************* WordProcessorFlavorPackageExternalizer *******************
5802 ********************************************************************************
5803 */
5804using WordProcessorFlavorPackageExternalizer = WordProcessor::WordProcessorFlavorPackageExternalizer;
5805
5806WordProcessorFlavorPackageExternalizer::WordProcessorFlavorPackageExternalizer (TextStore& ts, const shared_ptr<AbstractStyleDatabaseRep>& styleDatabase,
5807 const shared_ptr<AbstractParagraphDatabaseRep>& paragraphDatabase,
5808 const shared_ptr<HidableTextMarkerOwner>& hidableTextDatabase)
5810 , inherited (ts, styleDatabase)
5811 , fParagraphDatabase (paragraphDatabase)
5812 , fHidableTextDatabase (hidableTextDatabase)
5813{
5814}
5815
5816StandardStyledTextIOSrcStream* WordProcessorFlavorPackageExternalizer::mkStandardStyledTextIOSrcStream (size_t selectionStart, size_t selectionEnd)
5817{
5818 WordProcessorTextIOSrcStream* stream = new WordProcessorTextIOSrcStream (PeekAtTextStore (), fStyleDatabase, fParagraphDatabase,
5819 fHidableTextDatabase, selectionStart, selectionEnd);
5820 stream->SetUseTableSelection (GetUseTableSelection ());
5821 return stream;
5822}
5823
5824void WordProcessorTable::DrawSegment (const StyledTextImager* imager, const StyleRunElement& /*runElement*/, Tablet* tablet,
5825 [[maybe_unused]] size_t from, [[maybe_unused]] size_t to, [[maybe_unused]] const TextLayoutBlock& text,
5826 const Led_Rect& drawInto, const Led_Rect& invalidRect, CoordinateType /*useBaseLine*/, DistanceType* pixelsDrawn)
5827{
5828 RequireMember (const_cast<StyledTextImager*> (imager), WordProcessor);
5829 Assert (from + 1 == to);
5830 RequireNotNull (text.PeekAtVirtualText ());
5831 Require (text.PeekAtVirtualText ()[0] == kEmbeddingSentinelChar);
5832
5833 using TemporarilyUseTablet = EmbeddedTableWordProcessor::TemporarilyUseTablet;
5834
5835 WordProcessor& owningWP = *dynamic_cast<WordProcessor*> (const_cast<StyledTextImager*> (imager));
5836
5837 WordProcessorTable::TemporarilySetOwningWP owningWPSetter (*this, owningWP);
5838
5839 DistanceType bwv = Led_CvtScreenPixelsFromTWIPSV (fBorderWidth);
5840 Led_Rect rowRect = drawInto;
5841
5842 size_t nRows = fRows.size ();
5843 for (size_t ri = 0; ri < nRows; ++ri) {
5844 // MUST FIX THIS FOR MULTI-ROW CELLS!!! -- maybe????
5845 // vertical merge cells will NOT be supported for Led 3.1 -- LGP 2003-04-17
5846 size_t nCols = GetColumnCount (ri);
5847 rowRect.bottom = rowRect.top + fRows[ri].fHeight;
5848 for (size_t ci = 0; ci < nCols; ++ci) {
5849 if (GetCellFlags (ri, ci) == ePlainCell) {
5850 Led_Rect scrolledCBWR = TableCoordinates2Window (GetCellBounds (ri, ci));
5851 if (Intersect (scrolledCBWR, invalidRect)) {
5852 Led_Rect scrolledEditorCBWR = TableCoordinates2Window (GetCellEditorBounds (ri, ci));
5853
5854 // SPR#1485: erase the cell margins as well as the cell editor rectangle...
5855 // We can erase the whole rect - cuz we do it before we draw the cell editor portion itself...
5856 if (scrolledCBWR != scrolledEditorCBWR) {
5857 tablet->EraseBackground_SolidHelper (scrolledCBWR, GetCellColor (ri, ci));
5858 }
5859
5860 TemporarilyAllocateCellWP wp (*this, owningWP, ri, ci, scrolledEditorCBWR);
5861 TemporarilyUseTablet tmpUseTablet (*wp, tablet, TemporarilyUseTablet::eDontDoTextMetricsChangedCall);
5862 wp->Draw (scrolledEditorCBWR, false);
5863 DrawCellBorders (tablet, ri, ci, scrolledCBWR);
5864 }
5865 else {
5866 // If the table elements bottom is above the invalid region or
5867 // the tables top is BELOW the invalid region, we can skip this entire row. If
5868 // its BELOW - we can even skip any successive rows
5869 if (scrolledCBWR.bottom < invalidRect.top) {
5870 break;
5871 }
5872 if (scrolledCBWR.top > invalidRect.bottom) {
5873 goto Done;
5874 }
5875 }
5876 }
5877 }
5878 rowRect.top = rowRect.bottom + bwv;
5879 }
5880
5881Done:
5882 DrawTableBorders (owningWP, tablet, drawInto);
5883
5884 if (pixelsDrawn != nullptr) {
5885 *pixelsDrawn = fTotalWidth;
5886 }
5887}
5888
5889void WordProcessorTable::MeasureSegmentWidth ([[maybe_unused]] const StyledTextImager* imager, const StyleRunElement& /*runElement*/,
5890 [[maybe_unused]] size_t from, [[maybe_unused]] size_t to,
5891 [[maybe_unused]] const Led_tChar* text, DistanceType* distanceResults) const
5892{
5893 RequireMember (const_cast<StyledTextImager*> (imager), WordProcessor);
5894 Assert (from + 1 == to);
5895 RequireNotNull (text);
5896 distanceResults[0] = fTotalWidth;
5897}
5898
5899DistanceType WordProcessorTable::MeasureSegmentHeight ([[maybe_unused]] const StyledTextImager* imager, const StyleRunElement& /*runElement*/,
5900 [[maybe_unused]] size_t from, [[maybe_unused]] size_t to) const
5901{
5902 RequireMember (const_cast<StyledTextImager*> (imager), WordProcessor);
5903 Assert (from + 1 == to);
5904 // don't return zero-height as that could cause problems... even if not layed out yet...
5905 // LGP 2003-03-17 - not sure - maybe its OK to return zero if not layed out yet??
5906 return fTotalHeight == 0 ? 1 : fTotalHeight;
5907}
5908
5909/*
5910@METHOD: WordProcessorTable::GetRowHilightRects
5911@DESCRIPTION: <p>Provide table-specific selection hilight behavior (so only the selected cells
5912 or rows or columns are hilighted)</p>
5913*/
5914vector<Led_Rect> WordProcessorTable::GetRowHilightRects () const
5915{
5916 Led_Require_CurrentOwningWP ();
5917
5918 vector<Led_Rect> result;
5919
5920 size_t rowStart = GetStart ();
5921 size_t rowEnd = GetEnd ();
5922 size_t hilightStart = fCurrentOwningWP->GetSelectionStart ();
5923 size_t hilightEnd = fCurrentOwningWP->GetSelectionEnd ();
5924 bool segmentHilighted = max (rowStart, hilightStart) < min (rowEnd, hilightEnd);
5925
5926 if (segmentHilighted) {
5927 Led_Rect tableRect = fCurrentOwningWP->GetIntraRowTextWindowBoundingRect (rowStart, rowEnd);
5928 vector<Led_Rect> hilightRects = fCurrentOwningWP->TextImager::GetRowHilightRects (
5929 TextLayoutBlock_Basic{&kEmbeddingSentinelChar, &kEmbeddingSentinelChar + 1}, rowStart, rowEnd, hilightStart, hilightEnd);
5930
5931 // If all the WHOLE table is hilighted, then display that selection as the entire table hilighted.
5932 // No need to walk through just the cells etc...
5933 if (rowStart != hilightStart or rowEnd != hilightEnd) {
5934 return hilightRects;
5935 }
5936
5937 /*
5938 * Add the hilight rect BEFORE and AFTER (if needed) the table - but leave out the
5939 * table rect itself to handle separately.
5940 */
5941 for (auto i = hilightRects.begin (); i != hilightRects.end (); ++i) {
5942 if (tableRect != *i) {
5943 if (not(*i).IsEmpty ()) {
5944 result.push_back (*i);
5945 }
5946 }
5947 }
5948
5949 /*
5950 * Add the actual table's hilight.
5951 */
5952 {
5953 size_t rowSelStart = 0;
5954 size_t rowSelEnd = 0;
5955 size_t colSelStart = 0;
5956 size_t colSelEnd = 0;
5957 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
5958
5959 // If all the cells hilighted, then display that selection as the entire table hilighted.
5960 if (rowSelStart == 0 and rowSelEnd == GetRowCount () and colSelStart == 0 and colSelEnd == GetColumnCount ()) {
5961 return hilightRects;
5962 }
5963
5964 if (rowSelEnd - rowSelStart == 1 and colSelEnd - colSelStart == 1 and GetIntraCellMode ()) {
5965 TemporarilyAllocateCellWithTablet wp (*const_cast<WordProcessorTable*> (this), rowSelStart, colSelStart);
5966 vector<Led_Rect> cellHilightRegions = wp->GetSelectionWindowRects (wp->GetSelectionStart (), wp->GetSelectionEnd ());
5967 for (auto i = cellHilightRegions.begin (); i != cellHilightRegions.end (); ++i) {
5968 result.push_back (*i);
5969 }
5970 }
5971 else {
5972 for (size_t ri = rowSelStart; ri < rowSelEnd; ++ri) {
5973 size_t thisRowEnd = min (colSelEnd, GetColumnCount (ri));
5974 for (size_t ci = colSelStart; ci < thisRowEnd; ++ci) {
5975 if (GetCellFlags (ri, ci) == ePlainCell) {
5976 // doesn't include cell margins/borders...
5977 Led_Rect wRelCellRect = TableCoordinates2Window (GetCellBounds (ri, ci));
5978 if (not wRelCellRect.IsEmpty ()) {
5979 result.push_back (wRelCellRect);
5980 }
5981 }
5982 }
5983 }
5984 }
5985 }
5986 }
5987
5989 // Make sure rectangles don't overlap with one another (can share an edge) -- SPR#1226
5990 for (auto orit = result.begin (); orit != result.end (); ++orit) {
5991 Ensure ((*orit).GetWidth () > 0);
5992 Ensure ((*orit).GetHeight () > 0);
5993 for (auto irit = orit + 1; irit != result.end (); ++irit) {
5994 Led_Rect hr = *irit;
5995 Ensure (hr.GetWidth () > 0);
5996 Ensure (hr.GetHeight () > 0);
5997 Ensure (not Intersect (hr, *orit));
5998 }
5999 }
6000 }
6001
6002 return result;
6003}
6004
6005/*
6006@METHOD: WordProcessorTable::DrawTableBorders
6007@ACCESS: protected
6008@DESCRIPTION: <p></p>
6009*/
6010void WordProcessorTable::DrawTableBorders (WordProcessor& owningWP, Tablet* tablet, const Led_Rect& drawInto)
6011{
6012#if 0
6013 //Don't delete this code - cuz we MAY want to display (somehow) the border for the table as
6014 // a whole in some special color when it is SELECTED!
6015
6016 // Also - this is REALLY needed - when we have spacing (and in the future we may have other stuff drawn to adorn the
6017 // table - like I said above - like a funny color for hilight state or something???
6018 return;
6019#endif
6020 DistanceType bwh = Led_CvtScreenPixelsFromTWIPSH (fBorderWidth);
6021 //DistanceType bwv = Led_CvtScreenPixelsFromTWIPSV (fBorderWidth);
6022
6023 Led_Rect bounds = drawInto - Led_Point (0, owningWP.GetHScrollPos ());
6024 bounds.right = bounds.left + fTotalWidth;
6025 bounds.bottom = bounds.top + fTotalHeight;
6026 tablet->FrameRectangle (bounds, fBorderColor, bwh);
6027}
6028
6029/*
6030@METHOD: WordProcessorTable::DrawCellBorders
6031@ACCESS: protected
6032@DESCRIPTION: <p>Draw the borders on the given cell with bounds (in tablet drawing coordinates - corrected for scrolling
6033 and the location of the table of the given cell). Note it is assumed the cellBounds argument does NOT take
6034 into account space for the border itself. We draw the border just OUTSIDE the cell.</p>
6035*/
6036void WordProcessorTable::DrawCellBorders (Tablet* tablet, size_t /*row*/, size_t /*column*/, const Led_Rect& cellBounds)
6037{
6038 CoordinateType bw = Led_CvtScreenPixelsFromTWIPSH (fBorderWidth);
6039 // Draw outside of the frame of the cell.
6040 tablet->FrameRectangle (InsetRect (cellBounds, -bw, -bw), fBorderColor, bw);
6041}
6042
6043/*
6044@METHOD: WordProcessorTable::GetCellBounds
6045@ACCESS: public
6046@DESCRIPTION: <p>Retrieve the bounding rectangle for the given cell, NOT including its border.
6047 The rectange is relative to the table itself. Note that the border is drawn
6048 just outside the cell bounds.</p>
6049 <p>See also @'WordProcessorTable::GetCellEditorBounds'</p>
6050*/
6051Led_Rect WordProcessorTable::GetCellBounds (size_t row, size_t column) const
6052{
6053 Require (GetCellFlags (row, column) == ePlainCell);
6054 return GetCell (row, column).GetCachedBoundsRect ();
6055}
6056
6057/*
6058@METHOD: WordProcessorTable::GetCellEditorBounds
6059@ACCESS: public
6060@DESCRIPTION: <p>Similar to @'WordProcessorTable::GetCellBounds' but it takes into account the cell margin,
6061 and insets the cell bounds to return just where the embedded WP bounds lie.</p>
6062*/
6063Led_Rect WordProcessorTable::GetCellEditorBounds (size_t row, size_t column) const
6064{
6065 Require (GetCellFlags (row, column) == ePlainCell);
6066 Led_Rect cellBounds = GetCellBounds (row, column);
6067 Led_Rect cellEditBounds = cellBounds;
6068 TWIPS_Rect defaultCellMarginTWIPS;
6069 GetDefaultCellMargins (&defaultCellMarginTWIPS.top, &defaultCellMarginTWIPS.left, &defaultCellMarginTWIPS.bottom,
6070 &defaultCellMarginTWIPS.right);
6071 cellEditBounds.top += Led_CvtScreenPixelsFromTWIPSV (defaultCellMarginTWIPS.top);
6072 cellEditBounds.left += Led_CvtScreenPixelsFromTWIPSH (defaultCellMarginTWIPS.left);
6073 cellEditBounds.bottom -= Led_CvtScreenPixelsFromTWIPSV (defaultCellMarginTWIPS.bottom);
6074 cellEditBounds.right -= Led_CvtScreenPixelsFromTWIPSH (defaultCellMarginTWIPS.right);
6075 // now assure bounds not empty...
6076 cellEditBounds.bottom = max (cellEditBounds.bottom, cellEditBounds.top + 1);
6077 cellEditBounds.right = max (cellEditBounds.right, cellEditBounds.left + 1);
6078 return cellEditBounds;
6079}
6080
6081/*
6082@METHOD: WordProcessorTable::GetClosestCell
6083@ACCESS: public
6084@DESCRIPTION: <p>Point 'p' must be relative to the table bounds itself.</p>
6085*/
6086void WordProcessorTable::GetClosestCell (const Led_Point& p, size_t* row, size_t* col) const
6087{
6088 RequireNotNull (row);
6089 RequireNotNull (col);
6090
6091 Led_Size border = Led_Size (Led_CvtScreenPixelsFromTWIPSV (fBorderWidth), Led_CvtScreenPixelsFromTWIPSH (fBorderWidth));
6092 DistanceType spacing = Led_CvtScreenPixelsFromTWIPSV (GetCellSpacing ());
6093
6094 // find row...
6095 size_t rowCount = GetRowCount ();
6096 Assert (rowCount > 0);
6097 CoordinateType top = spacing + border.v;
6098 size_t ri = 0;
6099 for (; ri < rowCount; ++ri) {
6100 DistanceType h = fRows[ri].fHeight;
6101 CoordinateType bottom = top + h;
6102 // Treat special case of above entire table as being row zero..
6103 if (p.v < bottom) {
6104 break;
6105 }
6106 top += h;
6107 top += spacing + border.v;
6108 }
6109 if (ri >= rowCount) { // if PAST end of table - then treat that as the last row
6110 ri = rowCount - 1;
6111 }
6112 *row = ri;
6113
6114 // Now find the right column (cell)
6115 size_t colCount = GetColumnCount (ri);
6116 Assert (colCount > 0);
6117 size_t ci = 0;
6118 for (; ci < colCount; ++ci) {
6119 size_t rri = ri;
6120 size_t cci = ci;
6121 GetRealCell (&rri, &cci);
6122 Led_Rect bounds = GetCellBounds (rri, cci);
6123 // Treat special case of above entire table as being row zero..
6124 if (p.h < bounds.GetRight ()) {
6125 break;
6126 }
6127 }
6128 if (ci >= colCount) {
6129 ci = colCount - 1;
6130 }
6131 *col = ci;
6132}
6133
6134Led_Point WordProcessorTable::TableCoordinates2Window (const Led_Point& p) const
6135{
6136 Led_Require_CurrentOwningWP ();
6137 Led_Point tableWROrigin = fCurrentOwningWP->GetCharWindowLocation (GetStart ()).GetTopLeft ();
6138 return p + tableWROrigin;
6139}
6140
6141Led_Rect WordProcessorTable::TableCoordinates2Window (const Led_Rect& r) const
6142{
6143 return Led_Rect (TableCoordinates2Window (r.GetOrigin ()), r.GetSize ());
6144}
6145
6146Led_Point WordProcessorTable::WindowCoordinates2Table (const Led_Point& p) const
6147{
6148 Led_Require_CurrentOwningWP ();
6149 Led_Point tableWROrigin = fCurrentOwningWP->GetCharWindowLocation (GetStart ()).GetTopLeft ();
6150 return p - tableWROrigin;
6151}
6152
6153Led_Rect WordProcessorTable::WindowCoordinates2Table (const Led_Rect& r) const
6154{
6155 return Led_Rect (WindowCoordinates2Table (r.GetOrigin ()), r.GetSize ());
6156}
6157
6158bool WordProcessorTable::GetCaretShownSituation () const
6159{
6160 if (GetIntraCellMode ()) {
6161 size_t selStart = 0;
6162 size_t selEnd = 0;
6163 GetIntraCellSelection (&selStart, &selEnd);
6164 return selStart == selEnd;
6165 }
6166 return false;
6167}
6168
6169/*
6170@METHOD: WordProcessorTable::CalculateCaretRect
6171@DESCRIPTION: <p></p>
6172*/
6173Led_Rect WordProcessorTable::CalculateCaretRect () const
6174{
6175 Led_Require_CurrentOwningWP ();
6176 if (GetIntraCellMode ()) {
6177 size_t selStart = 0;
6178 size_t selEnd = 0;
6179 GetIntraCellSelection (&selStart, &selEnd);
6180 if (selStart == selEnd) {
6181 size_t row = 0;
6182 size_t col = 0;
6183 (void)GetIntraCellMode (&row, &col);
6184 TemporarilyAllocateCellWithTablet wp (*const_cast<WordProcessorTable*> (this), row, col);
6185 return wp->CalculateCaretRect ();
6186 }
6187 }
6188 return (Led_Rect (0, 0, 0, 0));
6189}
6190
6191bool WordProcessorTable::OnTypedNormalCharacter (Led_tChar theChar, bool optionPressed, bool shiftPressed, bool commandPressed,
6192 bool controlPressed, bool altKeyPressed)
6193{
6194 using InteractiveModeUpdater = WordProcessor::InteractiveModeUpdater;
6195 using UndoableContextHelper = WordProcessor::UndoableContextHelper;
6196 Led_Require_CurrentOwningWP ();
6197
6198 size_t rowSelStart = 0;
6199 size_t rowSelEnd = 0;
6200 size_t colSelStart = 0;
6201 size_t colSelEnd = 0;
6202 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6203
6204 if (theChar == '\b') {
6205 // Treat a selection of the entire table and a hackspace as deleting the entire table...
6206 if (rowSelStart == 0 and rowSelEnd == GetRowCount () and colSelStart == 0 and colSelEnd == GetColumnCount () and not fIntraCellMode) {
6207 return false; // so will be handled by higher level - deleting the entire table.
6208 }
6209 }
6210
6211 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6212 AllowUpdateInfoPropagationContext AUIPC (*this);
6213 if (not fIntraCellMode) {
6214 // save all the cleared text for all the selected cells in one command object, but give it the
6215 // typingCommand name so it will be lumped with the typeing command generated by the below wp->OnTypedNormalCharacter ().
6216 UndoableContextHelper undoContext (*fCurrentOwningWP, TextInteractor::GetCommandNames ().fTypingCommandName, false);
6217 {
6218 (void)OnPerformCommand_ApplyToEachSelectedCell (TextInteractor::kClear_CmdID, false);
6219 }
6220 undoContext.CommandComplete ();
6221 SetIntraCellMode (rowSelStart, colSelStart);
6222 }
6223
6224 Assert (fIntraCellMode);
6225 TemporarilyAllocateCellWithTablet wp (*this, rowSelStart, colSelStart);
6226 wp->OnTypedNormalCharacter (theChar, optionPressed, shiftPressed, commandPressed, controlPressed, altKeyPressed);
6227
6228 return true; // handled
6229}
6230
6231bool WordProcessorTable::DoSingleCharCursorEdit (TextInteractor::CursorMovementDirection direction,
6232 TextInteractor::CursorMovementUnit movementUnit, TextInteractor::CursorMovementAction action,
6233 TextInteractor::UpdateMode updateMode, bool scrollToSelection)
6234{
6235 size_t row = 0;
6236 size_t col = 0;
6237 if (GetIntraCellMode (&row, &col)) {
6238 // VERY PRELIMINARY!!!
6239 AllowUpdateInfoPropagationContext AUIPC (*this);
6240 TemporarilyAllocateCellWithTablet wp (*this, row, col);
6241 wp->DoSingleCharCursorEdit (direction, movementUnit, action, updateMode, scrollToSelection);
6242 return true; // handled
6243 }
6244 return false;
6245}
6246
6247bool WordProcessorTable::OnUpdateCommand (TextInteractor::CommandUpdater* enabler)
6248{
6249 Led_Require_CurrentOwningWP ();
6250 RequireNotNull (enabler);
6251
6252 size_t row = 0;
6253 size_t col = 0;
6254 if (GetIntraCellMode (&row, &col)) {
6255 if (fCurrentOwningWP->PassAlongCommandToIntraCellModeTableCell (enabler->GetCmdID ())) {
6256 TemporarilyAllocateCellWithTablet wp (*this, row, col);
6257 [[maybe_unused]] bool result = wp->OnUpdateCommand (enabler);
6258 if (enabler->GetCmdID () == WordProcessor::kSelectedEmbeddingProperties_CmdID and not enabler->GetEnabled ()) {
6259 // SPR#1487: so default command handling will take care of it and we'll see the properties command
6260 return false;
6261 }
6262 return true; // if in a table cell - say the command was eaten here regardless- cut off other commands
6263 }
6264 }
6265
6266 switch (enabler->GetCmdID ()) {
6267 case WordProcessor::kCut_CmdID: {
6268 OnUpdateCutCommand (enabler);
6269 return true;
6270 }
6271 case WordProcessor::kInsertTableRowAbove_CmdID: {
6272 OnUpdateInsertTableRowAboveCommand (enabler);
6273 return true;
6274 }
6275 case WordProcessor::kInsertTableRowBelow_CmdID: {
6276 OnUpdateInsertTableRowBelowCommand (enabler);
6277 return true;
6278 }
6279 case WordProcessor::kInsertTableColBefore_CmdID: {
6280 OnUpdateInsertTableColBeforeCommand (enabler);
6281 return true;
6282 }
6283 case WordProcessor::kInsertTableColAfter_CmdID: {
6284 OnUpdateInsertTableColAfterCommand (enabler);
6285 return true;
6286 }
6287 case WordProcessor::kRemoveTableColumns_CmdID: {
6288 OnUpdateRemoveTableColumnsCommand (enabler);
6289 return true;
6290 }
6291 case WordProcessor::kRemoveTableRows_CmdID: {
6292 OnUpdateRemoveTableRowsCommand (enabler);
6293 return true;
6294 }
6295 case WordProcessor::kSelectTableIntraCellAll_CmdID:
6296 case WordProcessor::kSelectTableCell_CmdID:
6297 case WordProcessor::kSelectTableRow_CmdID:
6298 case WordProcessor::kSelectTableColumn_CmdID:
6299 case WordProcessor::kSelectTable_CmdID: {
6300 OnUpdateSelectTablePartsCommand (enabler);
6301 return true;
6302 }
6303 }
6304
6305 if (fCurrentOwningWP->PassAlongCommandToEachSelectedTableCell (enabler->GetCmdID ())) {
6306 return OnUpdateCommand_ApplyToEachSelectedCell (enabler);
6307 }
6308
6309 return false;
6310}
6311
6312bool WordProcessorTable::OnPerformCommand (TextInteractor::CommandNumber commandNumber)
6313{
6314 Led_Require_CurrentOwningWP ();
6315
6316 AllowUpdateInfoPropagationContext AUIPC (*this);
6317
6318 size_t row = 0;
6319 size_t col = 0;
6320 if (GetIntraCellMode (&row, &col)) {
6321 if (fCurrentOwningWP->PassAlongCommandToIntraCellModeTableCell (commandNumber)) {
6322 TemporarilyAllocateCellWithTablet wp (*this, row, col);
6323 return wp->OnPerformCommand (commandNumber);
6324 }
6325 }
6326
6327 switch (commandNumber) {
6328 case WordProcessor::kCut_CmdID: {
6329 OnCutCommand ();
6330 return true;
6331 }
6332 case WordProcessor::kInsertTableRowAbove_CmdID: {
6333 OnInsertTableRowAboveCommand ();
6334 return true;
6335 }
6336 case WordProcessor::kInsertTableRowBelow_CmdID: {
6337 OnInsertTableRowBelowCommand ();
6338 return true;
6339 }
6340 case WordProcessor::kInsertTableColBefore_CmdID: {
6341 OnInsertTableColBeforeCommand ();
6342 return true;
6343 }
6344 case WordProcessor::kInsertTableColAfter_CmdID: {
6345 OnInsertTableColAfterCommand ();
6346 return true;
6347 }
6348 case WordProcessor::kRemoveTableColumns_CmdID: {
6349 OnRemoveTableColumnsCommand ();
6350 return true;
6351 }
6352 case WordProcessor::kRemoveTableRows_CmdID: {
6353 OnRemoveTableRowsCommand ();
6354 return true;
6355 }
6356 case WordProcessor::kSelectTableIntraCellAll_CmdID:
6357 case WordProcessor::kSelectTableCell_CmdID:
6358 case WordProcessor::kSelectTableRow_CmdID:
6359 case WordProcessor::kSelectTableColumn_CmdID:
6360 case WordProcessor::kSelectTable_CmdID: {
6361 OnPerformTablePartsCommand (commandNumber);
6362 return true;
6363 }
6364 }
6365
6366 if (fCurrentOwningWP->PassAlongCommandToEachSelectedTableCell (commandNumber)) {
6367 return OnPerformCommand_ApplyToEachSelectedCell (commandNumber);
6368 }
6369
6370 return false;
6371}
6372
6373void WordProcessorTable::BreakInGroupedCommands ()
6374{
6375 Led_Require_CurrentOwningWP ();
6376 fCurrentOwningWP->BreakInGroupedCommands ();
6377}
6378
6379bool WordProcessorTable::OnUpdateCommand_ApplyToEachSelectedCell (TextInteractor::CommandUpdater* enabler)
6380{
6381 RequireNotNull (enabler);
6382 Led_Require_CurrentOwningWP ();
6383
6384 bool result = false;
6385 size_t rowSelStart = 0;
6386 size_t rowSelEnd = 0;
6387 size_t colSelStart = 0;
6388 size_t colSelEnd = 0;
6389 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6390 for (size_t ri = rowSelStart; ri < rowSelEnd; ++ri) {
6391 size_t thisRowEnd = min (colSelEnd, GetColumnCount (ri));
6392 for (size_t ci = colSelStart; ci < thisRowEnd; ++ci) {
6393 TemporarilyAllocateCellWithTablet wp (*this, ri, ci);
6394 wp->SetSelection (0, wp->GetEnd (), TextInteractor::eNoUpdate);
6395 result = result or wp->OnUpdateCommand (enabler);
6396 }
6397 }
6398 return result;
6399}
6400
6401bool WordProcessorTable::OnPerformCommand_ApplyToEachSelectedCell (TextInteractor::CommandNumber commandNumber, bool captureChangesForUndo)
6402{
6403 Led_Require_CurrentOwningWP ();
6404 if (captureChangesForUndo) {
6405 fCurrentOwningWP->BreakInGroupedCommands ();
6406 }
6407 bool result = false;
6408 size_t rowSelStart = 0;
6409 size_t rowSelEnd = 0;
6410 size_t colSelStart = 0;
6411 size_t colSelEnd = 0;
6412 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6413 for (size_t ri = rowSelStart; ri < rowSelEnd; ++ri) {
6414 size_t thisRowEnd = min (colSelEnd, GetColumnCount (ri));
6415 for (size_t ci = colSelStart; ci < thisRowEnd; ++ci) {
6416 TemporarilyAllocateCellWithTablet wp (*this, ri, ci, captureChangesForUndo);
6417 wp->SetSelection (0, wp->GetEnd (), TextInteractor::eNoUpdate);
6418 TextInteractor::SuppressCommandBreaksContext SCBC (*wp);
6419 wp->OnPerformCommand (commandNumber);
6420 result = true;
6421 }
6422 }
6423 if (captureChangesForUndo) {
6424 fCurrentOwningWP->BreakInGroupedCommands ();
6425 }
6426 return result;
6427}
6428
6429void WordProcessorTable::OnUpdateCutCommand (TextInteractor::CommandUpdater* enabler)
6430{
6431 RequireNotNull (enabler);
6432 Led_Require_CurrentOwningWP ();
6433 size_t rowSelStart = 0;
6434 size_t rowSelEnd = 0;
6435 size_t colSelStart = 0;
6436 size_t colSelEnd = 0;
6437 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6438 enabler->SetEnabled (rowSelStart != rowSelEnd and colSelStart != colSelEnd);
6439}
6440
6441void WordProcessorTable::OnCutCommand ()
6442{
6443 Led_Require_CurrentOwningWP ();
6444 AllowUpdateInfoPropagationContext AUIPC (*this);
6445 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6446 Assert (fCurrentOwningWP->GetSelectionEnd () - fCurrentOwningWP->GetSelectionStart () == 1);
6447 fCurrentOwningWP->OnCopyCommand ();
6448 UndoableContextHelper undoContext (*fCurrentOwningWP, TextInteractor::GetCommandNames ().fCutCommandName, true);
6449 {
6450 (void)OnPerformCommand_ApplyToEachSelectedCell (TextInteractor::kClear_CmdID, false);
6451 }
6452 undoContext.CommandComplete ();
6453}
6454
6455void WordProcessorTable::OnUpdateInsertTableRowAboveCommand (TextInteractor::CommandUpdater* enabler)
6456{
6457 RequireNotNull (enabler);
6458 Led_Require_CurrentOwningWP ();
6459 enabler->SetEnabled (true);
6460}
6461
6462void WordProcessorTable::OnInsertTableRowAboveCommand ()
6463{
6464 Led_Require_CurrentOwningWP ();
6465 AllowUpdateInfoPropagationContext AUIPC (*this);
6466 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6467 BreakInGroupedCommands ();
6468 UndoableContextHelper context (*fCurrentOwningWP, WordProcessor::GetCommandNames ().fInsertTableRowAboveCommandName, false);
6469 {
6470 // See our current row
6471 size_t curRow = 0;
6472 GetCellSelection (&curRow, nullptr, nullptr, nullptr);
6473
6474 Assert (curRow <= GetRowCount ());
6475 InsertRow (curRow);
6476 }
6477 context.CommandComplete ();
6478 BreakInGroupedCommands ();
6479}
6480
6481void WordProcessorTable::OnUpdateInsertTableRowBelowCommand (TextInteractor::CommandUpdater* enabler)
6482{
6483 RequireNotNull (enabler);
6484 Led_Require_CurrentOwningWP ();
6485 enabler->SetEnabled (true);
6486}
6487
6488void WordProcessorTable::OnInsertTableRowBelowCommand ()
6489{
6490 Led_Require_CurrentOwningWP ();
6491 AllowUpdateInfoPropagationContext AUIPC (*this);
6492 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6493 BreakInGroupedCommands ();
6494 UndoableContextHelper context (*fCurrentOwningWP, WordProcessor::GetCommandNames ().fInsertTableRowAboveCommandName, false);
6495 {
6496 // See our current row
6497 size_t curRow = 0;
6498 GetCellSelection (nullptr, &curRow, nullptr, nullptr);
6499 Assert (curRow <= GetRowCount ());
6500 InsertRow (curRow);
6501 }
6502 context.CommandComplete ();
6503 BreakInGroupedCommands ();
6504}
6505
6506void WordProcessorTable::OnUpdateInsertTableColBeforeCommand (TextInteractor::CommandUpdater* enabler)
6507{
6508 Led_Require_CurrentOwningWP ();
6509 RequireNotNull (enabler);
6510 enabler->SetEnabled (true);
6511}
6512
6513void WordProcessorTable::OnInsertTableColBeforeCommand ()
6514{
6515 Led_Require_CurrentOwningWP ();
6516 AllowUpdateInfoPropagationContext AUIPC (*this);
6517 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6518 BreakInGroupedCommands ();
6519 UndoableContextHelper context (*fCurrentOwningWP, WordProcessor::GetCommandNames ().fInsertTableColBeforeCommandName, false);
6520 {
6521 size_t curCol = 0;
6522 GetCellSelection (nullptr, nullptr, &curCol, nullptr);
6523 Assert (curCol <= GetColumnCount ());
6524 InsertColumn (curCol);
6525 }
6526 context.CommandComplete ();
6527 BreakInGroupedCommands ();
6528}
6529
6530void WordProcessorTable::OnUpdateInsertTableColAfterCommand (TextInteractor::CommandUpdater* enabler)
6531{
6532 Led_Require_CurrentOwningWP ();
6533 RequireNotNull (enabler);
6534 enabler->SetEnabled (true);
6535}
6536
6537void WordProcessorTable::OnInsertTableColAfterCommand ()
6538{
6539 Led_Require_CurrentOwningWP ();
6540 AllowUpdateInfoPropagationContext AUIPC (*this);
6541 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6542 BreakInGroupedCommands ();
6543 UndoableContextHelper context (*fCurrentOwningWP, WordProcessor::GetCommandNames ().fInsertTableColAfterCommandName, false);
6544 {
6545 size_t curCol = 0;
6546 GetCellSelection (nullptr, nullptr, nullptr, &curCol);
6547 Assert (curCol <= GetColumnCount ());
6548 InsertColumn (curCol);
6549 }
6550 context.CommandComplete ();
6551 BreakInGroupedCommands ();
6552}
6553
6554void WordProcessorTable::OnUpdateRemoveTableRowsCommand (TextInteractor::CommandUpdater* pCmdUI)
6555{
6556 Led_Require_CurrentOwningWP ();
6557 RequireNotNull (pCmdUI);
6558 size_t rowSelStart = 0;
6559 size_t rowSelEnd = 0;
6560 size_t colSelStart = 0;
6561 size_t colSelEnd = 0;
6562 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6563 pCmdUI->SetEnabled (colSelStart == 0 and colSelEnd == GetColumnCount (rowSelStart, rowSelEnd));
6564}
6565
6566void WordProcessorTable::OnRemoveTableRowsCommand ()
6567{
6568 Led_Require_CurrentOwningWP ();
6569 AllowUpdateInfoPropagationContext AUIPC (*this);
6570 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6571 BreakInGroupedCommands ();
6572 UndoableContextHelper context (*fCurrentOwningWP, WordProcessor::GetCommandNames ().fRemoveTableRowsCommandName, false);
6573 {
6574 size_t rowSelStart = 0;
6575 size_t rowSelEnd = 0;
6576 size_t colSelStart = 0;
6577 size_t colSelEnd = 0;
6578 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6579 if (colSelStart == 0 and colSelEnd == GetColumnCount (rowSelStart, rowSelEnd)) {
6580 if (rowSelStart == 0 and rowSelEnd == GetRowCount ()) {
6581 fCurrentOwningWP->OnClearCommand (); // handled by TextInteractor (will delete whole table)
6582 return; // aborts command we'd started here...
6583 }
6584 size_t nRowsToDelete = rowSelEnd - rowSelStart;
6585 while (nRowsToDelete > 0) {
6586 DeleteRow (rowSelStart);
6587 --nRowsToDelete;
6588 }
6589 }
6590 else {
6591 fCurrentOwningWP->OnBadUserInput ();
6592 }
6593 }
6594 context.CommandComplete ();
6595}
6596
6597void WordProcessorTable::OnUpdateRemoveTableColumnsCommand (TextInteractor::CommandUpdater* pCmdUI)
6598{
6599 Led_Require_CurrentOwningWP ();
6600 RequireNotNull (pCmdUI);
6601 size_t rowSelStart = 0;
6602 size_t rowSelEnd = 0;
6603 size_t colSelStart = 0;
6604 size_t colSelEnd = 0;
6605 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6606 pCmdUI->SetEnabled (rowSelStart == 0 and rowSelEnd == GetRowCount ());
6607}
6608
6609void WordProcessorTable::OnRemoveTableColumnsCommand ()
6610{
6611 Led_Require_CurrentOwningWP ();
6612 AllowUpdateInfoPropagationContext AUIPC (*this);
6613 InteractiveModeUpdater iuMode (*fCurrentOwningWP);
6614 BreakInGroupedCommands ();
6615 UndoableContextHelper context (*fCurrentOwningWP, WordProcessor::GetCommandNames ().fRemoveTableColumnsCommandName, false);
6616 {
6617 size_t rowSelStart = 0;
6618 size_t rowSelEnd = 0;
6619 size_t colSelStart = 0;
6620 size_t colSelEnd = 0;
6621 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6622 if (rowSelStart == 0 and rowSelEnd == GetRowCount ()) {
6623 if (colSelStart == 0 and colSelEnd == GetColumnCount ()) {
6624 fCurrentOwningWP->OnClearCommand (); // handled by TextInteractor (will delete whole table)
6625 return; // aborts command we'd started here...
6626 }
6627 size_t nColsToDelete = colSelEnd - colSelStart;
6628 while (nColsToDelete > 0) {
6629 DeleteColumn (colSelStart);
6630 --nColsToDelete;
6631 }
6632 }
6633 else {
6634 fCurrentOwningWP->OnBadUserInput ();
6635 }
6636 }
6637 context.CommandComplete ();
6638 BreakInGroupedCommands ();
6639}
6640
6641void WordProcessorTable::OnUpdateSelectTablePartsCommand (TextInteractor::CommandUpdater* enabler)
6642{
6643 switch (enabler->GetCmdID ()) {
6644 case WordProcessor::kSelectTableIntraCellAll_CmdID: {
6645 enabler->SetEnabled (GetIntraCellMode ());
6646 } break;
6647 case WordProcessor::kSelectTableCell_CmdID: {
6648 enabler->SetEnabled (GetIntraCellMode ());
6649 } break;
6650 case WordProcessor::kSelectTableRow_CmdID: {
6651 size_t rowSelStart = 0;
6652 size_t rowSelEnd = 0;
6653 size_t colSelStart = 0;
6654 size_t colSelEnd = 0;
6655 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6656 size_t maxColSelEnd = GetColumnCount (rowSelStart, rowSelEnd);
6657 enabler->SetEnabled (colSelStart != 0 or colSelEnd != maxColSelEnd);
6658 } break;
6659 case WordProcessor::kSelectTableColumn_CmdID: {
6660 size_t rowSelStart = 0;
6661 size_t rowSelEnd = 0;
6662 size_t colSelStart = 0;
6663 size_t colSelEnd = 0;
6664 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6665 enabler->SetEnabled (rowSelStart != 0 or rowSelEnd != GetRowCount ());
6666 } break;
6667 case WordProcessor::kSelectTable_CmdID: {
6668 size_t rowSelStart = 0;
6669 size_t rowSelEnd = 0;
6670 size_t colSelStart = 0;
6671 size_t colSelEnd = 0;
6672 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6673 enabler->SetEnabled (rowSelStart != 0 or colSelStart != 0 or rowSelEnd != GetRowCount () or colSelEnd != GetColumnCount ());
6674 } break;
6675 }
6676}
6677
6678void WordProcessorTable::OnPerformTablePartsCommand (TextInteractor::CommandNumber commandNumber)
6679{
6680 switch (commandNumber) {
6681 case WordProcessor::kSelectTableIntraCellAll_CmdID: {
6682 size_t row = 0;
6683 size_t col = 0;
6684 if (GetIntraCellMode (&row, &col)) {
6685 TemporarilyAllocateCellWithTablet wp (*this, row, col);
6686 wp->OnPerformCommand (TextInteractor::kSelectAll_CmdID);
6687 }
6688 else {
6689 Led_BeepNotify ();
6690 }
6691 } break;
6692 case WordProcessor::kSelectTableCell_CmdID: {
6693 UnSetIntraCellMode ();
6694 } break;
6695 case WordProcessor::kSelectTableRow_CmdID: {
6696 size_t rowSelStart = 0;
6697 size_t rowSelEnd = 0;
6698 size_t colSelStart = 0;
6699 size_t colSelEnd = 0;
6700 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6701 colSelStart = 0;
6702 colSelEnd = GetColumnCount (rowSelStart, rowSelEnd);
6703 SetCellSelection (rowSelStart, rowSelEnd, colSelStart, colSelEnd);
6704 } break;
6705 case WordProcessor::kSelectTableColumn_CmdID: {
6706 size_t rowSelStart = 0;
6707 size_t rowSelEnd = 0;
6708 size_t colSelStart = 0;
6709 size_t colSelEnd = 0;
6710 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6711 rowSelStart = 0;
6712 rowSelEnd = GetRowCount ();
6713 SetCellSelection (rowSelStart, rowSelEnd, colSelStart, colSelEnd);
6714 } break;
6715 case WordProcessor::kSelectTable_CmdID: {
6716 SetCellSelection (0, GetRowCount (), 0, GetColumnCount ());
6717 } break;
6718 }
6719}
6720
6721void WordProcessorTable::AssureCurSelFontCacheValid (IncrementalFontSpecification* curSelFontSpec)
6722{
6723 RequireNotNull (curSelFontSpec);
6724 {
6725 size_t row = 0;
6726 size_t col = 0;
6727 if (GetIntraCellMode (&row, &col)) {
6728 TemporarilyAllocateCellWithTablet wp (*this, row, col);
6729 *curSelFontSpec = wp->GetCurSelFontSpec ();
6730 return;
6731 }
6732 }
6733
6734 size_t rowSelStart = 0;
6735 size_t rowSelEnd = 0;
6736 size_t colSelStart = 0;
6737 size_t colSelEnd = 0;
6738 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6739 for (size_t ri = rowSelStart; ri < rowSelEnd; ++ri) {
6740 size_t thisRowEnd = min (colSelEnd, GetColumnCount (ri));
6741 for (size_t ci = colSelStart; ci < thisRowEnd; ++ci) {
6742 TemporarilyAllocateCellWithTablet wp (*this, ri, ci);
6743 wp->SetSelection (0, wp->GetEnd (), TextInteractor::eNoUpdate);
6744 IncrementalFontSpecification iSpec = wp->GetCurSelFontSpec ();
6745 if (ri == rowSelStart and ci == colSelStart) {
6746 *curSelFontSpec = iSpec;
6747 }
6748 else {
6749 *curSelFontSpec = Intersection (*curSelFontSpec, iSpec);
6750 }
6751 }
6752 }
6753}
6754
6755/*
6756@METHOD: WordProcessorTable::InteractiveSetFont
6757@DESCRIPTION: <p>Apply the given font specification to the selectable table cells.</p>
6758*/
6759void WordProcessorTable::InteractiveSetFont (const IncrementalFontSpecification& defaultFont)
6760{
6761 Led_Require_CurrentOwningWP ();
6762
6763 {
6764 // Must fix to handle UNDO support...
6765 size_t row = 0;
6766 size_t col = 0;
6767 if (GetIntraCellMode (&row, &col)) {
6768 TemporarilyAllocateCellWithTablet wp (*this, row, col);
6769 wp->InteractiveSetFont (defaultFont);
6770 return;
6771 }
6772 }
6773
6774 AllowUpdateInfoPropagationContext AUIPC (*this);
6775
6776 fCurrentOwningWP->BreakInGroupedCommands ();
6777 UndoableContextHelper undoContext (*fCurrentOwningWP, StandardStyledTextInteractor::GetCommandNames ().fFontChangeCommandName, false);
6778 {
6779 size_t rowSelStart = 0;
6780 size_t rowSelEnd = 0;
6781 size_t colSelStart = 0;
6782 size_t colSelEnd = 0;
6783 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6784 for (size_t ri = rowSelStart; ri < rowSelEnd; ++ri) {
6785 size_t thisRowEnd = min (colSelEnd, GetColumnCount (ri));
6786 for (size_t ci = colSelStart; ci < thisRowEnd; ++ci) {
6787 TemporarilyAllocateCellWithTablet wp (*this, ri, ci);
6788 wp->SetStyleInfo (0, wp->GetEnd (), defaultFont);
6789 }
6790 }
6791 }
6792 undoContext.CommandComplete ();
6793}
6794
6795void WordProcessorTable::Write ([[maybe_unused]] SinkStream& sink)
6796{
6797 // sink.write (fData, fLength);
6798}
6799
6800void WordProcessorTable::ExternalizeFlavors ([[maybe_unused]] WriterFlavorPackage& flavorPackage)
6801{
6802 // save done another way - AS RTF - not sure why this is never called - but
6803 // probably lose the whole SimpleEmbedding guy for tables - and just handle directly what
6804 // is done through them now...
6805 // flavorPackage.AddFlavorData (fFormat, fLength, fData);
6806}
6807
6808const char* WordProcessorTable::GetTag () const
6809{
6810 //tmphack
6811 return "table";
6812 // return fEmbeddingTag;
6813}
6814
6815/*
6816@METHOD: WordProcessorTable::ProcessSimpleClick
6817@DESCRIPTION: <p></p>
6818*/
6819bool WordProcessorTable::ProcessSimpleClick (Led_Point clickedAt, unsigned clickCount, bool extendSelection)
6820{
6821#if 0
6822 DbgTrace ("ENTERING WordProcessorTable::ProcessSimpleClick (this= 0x%x, clickedAt=(%d,%d), clickCount=%d, rowSelStart=%d, rowSelEnd=%d, colSelStart=%d, colSelEnd=%d, intraCellMode=%d intraCellStart=%d, intraCellEnd=%d)\n",
6823 this, clickedAt.v, clickedAt.h, clickCount, fRowSelStart, fRowSelEnd, fColSelStart, fColSelEnd, fIntraCellMode, fIntraSelStart, fIntraSelStart
6824 );
6825#endif
6826 Led_Require_CurrentOwningWP ();
6827
6828 size_t clickRow = 0;
6829 size_t clickCol = 0;
6830 GetClosestCell (clickedAt, &clickRow, &clickCol);
6831
6832 fTrackingAnchor_Row = clickRow;
6833 fTrackingAnchor_Col = clickCol;
6834
6835 bool forceSelectAllCells = false;
6836 if (extendSelection) {
6837 size_t selStart = fCurrentOwningWP->GetSelectionStart ();
6838 size_t selEnd = fCurrentOwningWP->GetSelectionEnd ();
6839 selStart = min (selStart, GetStart ());
6840 selEnd = max (selEnd, GetEnd ());
6841 forceSelectAllCells = (selEnd - selStart != 1);
6842 fCurrentOwningWP->SetSelection (selStart, selEnd);
6843 }
6844 else {
6845 fCurrentOwningWP->SetSelection (GetStart (), GetEnd ());
6846 }
6847
6848 if (forceSelectAllCells) {
6849 SetCellSelection (0, GetRowCount (), 0, GetColumnCount ());
6850 }
6851 else if (extendSelection) {
6852 size_t rowSelStart = 0;
6853 size_t rowSelEnd = 0;
6854 size_t colSelStart = 0;
6855 size_t colSelEnd = 0;
6856 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6857 rowSelStart = min (rowSelStart, clickRow);
6858 rowSelEnd = max (rowSelEnd, clickRow + 1);
6859 colSelStart = min (colSelStart, clickCol);
6860 colSelEnd = max (colSelEnd, clickCol + 1);
6861 SetCellSelection (rowSelStart, rowSelEnd, colSelStart, colSelEnd);
6862 }
6863 else {
6864 SetCellSelection (clickRow, clickRow + 1, clickCol, clickCol + 1);
6865 }
6866
6867 {
6868 size_t rowSelStart = 0;
6869 size_t rowSelEnd = 0;
6870 size_t colSelStart = 0;
6871 size_t colSelEnd = 0;
6872 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6873 if (rowSelEnd - rowSelStart == 1 and colSelEnd - colSelStart == 1) {
6874 Led_Rect cellBounds = GetCellBounds (rowSelStart, colSelStart);
6875 Led_Rect cellEditorBounds = GetCellEditorBounds (rowSelStart, colSelStart);
6876
6877 // Only if we click inside the margins do we treat this as intra-cell activation. Otherwise, the user
6878 // just selects the entire cell.
6879 if (cellEditorBounds.Contains (clickedAt)) {
6880 SetIntraCellMode ();
6881
6882 // pass along click to embedded WP
6883 TemporarilyAllocateCellWithTablet wp (*this, rowSelStart, colSelStart);
6884 wp->SetCurClickCount (fCurrentOwningWP->GetCurClickCount (), Time::GetTickCount ());
6885 Assert (fCurrentOwningWP->GetCurClickCount () == clickCount);
6886 wp->ProcessSimpleClick (TableCoordinates2Window (clickedAt), clickCount, extendSelection, &fIntraCellDragAnchor);
6887 }
6888 else {
6889 UnSetIntraCellMode ();
6890 }
6891 }
6892 }
6893#if 0
6894 DbgTrace ("EXITING WordProcessorTable::ProcessSimpleClick (this= 0x%x, rowSelStart=%d, rowSelEnd=%d, colSelStart=%d, colSelEnd=%d, intraCellMode=%d intraCellStart=%d, intraCellEnd=%d)\n",
6895 this, fRowSelStart, fRowSelEnd, fColSelStart, fColSelEnd, fIntraCellMode, fIntraSelStart, fIntraSelStart
6896 );
6897#endif
6898 return true;
6899}
6900
6901void WordProcessorTable::WhileSimpleMouseTracking (Led_Point newMousePos)
6902{
6903#if 0
6904 DbgTrace ("ENTERING WordProcessorTable::WhileSimpleMouseTracking (this= 0x%x, rowSelStart=%d, rowSelEnd=%d, colSelStart=%d, colSelEnd=%d, intraCellMode=%d intraCellStart=%d, intraCellEnd=%d)\n",
6905 this, fRowSelStart, fRowSelEnd, fColSelStart, fColSelEnd, fIntraCellMode, fIntraSelStart, fIntraSelStart
6906 );
6907#endif
6908 Led_Require_CurrentOwningWP ();
6909
6910 if (fCurrentOwningWP->GetSelectionEnd () - fCurrentOwningWP->GetSelectionStart () == 1) {
6911 /*
6912 * If dragging WITHIN a table - then EXTEND the selection to include the area selected.
6913 */
6914 size_t clickRow = 0;
6915 size_t clickCol = 0;
6916 GetClosestCell (newMousePos, &clickRow, &clickCol);
6917
6918 size_t rowSelStart = min (fTrackingAnchor_Row, clickRow);
6919 size_t rowSelEnd = max (fTrackingAnchor_Row + 1, clickRow + 1);
6920 size_t colSelStart = min (fTrackingAnchor_Col, clickCol);
6921 size_t colSelEnd = max (fTrackingAnchor_Col + 1, clickCol + 1);
6922 SetCellSelection (rowSelStart, rowSelEnd, colSelStart, colSelEnd);
6923 }
6924 else if (fCurrentOwningWP->GetSelectionEnd () - fCurrentOwningWP->GetSelectionStart () > 1) {
6925 SetCellSelection (0, GetRowCount (), 0, GetColumnCount ());
6926 }
6927
6928 {
6929 size_t rowSelStart = 0;
6930 size_t rowSelEnd = 0;
6931 size_t colSelStart = 0;
6932 size_t colSelEnd = 0;
6933 GetCellSelection (&rowSelStart, &rowSelEnd, &colSelStart, &colSelEnd);
6934 if (rowSelEnd - rowSelStart == 1 and colSelEnd - colSelStart == 1) {
6935 Led_Rect cellBounds = GetCellBounds (rowSelStart, colSelStart);
6936 Led_Rect cellEditorBounds = GetCellEditorBounds (rowSelStart, colSelStart);
6937
6938 // Only if we click inside the margins do we treat this as intra-cell activation. Otherwise, the user
6939 // just selects the entire cell.
6940 if (cellEditorBounds.Contains (newMousePos)) {
6941 if (fTrackingAnchor_Row == rowSelStart and rowSelStart + 1 == rowSelEnd and fTrackingAnchor_Col == colSelStart and
6942 colSelStart + 1 == colSelEnd) {
6943 // Don't reset to IntraCell mode when tracking if the selected cell is other than the original
6944 // clicked in one. The fIntraCellDragAnchor value would be invalid, and the UI wouldn't make
6945 // much sense anyhow...
6946 SetIntraCellMode ();
6947 }
6948 // pass along click to embedded WP
6949 TemporarilyAllocateCellWithTablet wp (*this, rowSelStart, colSelStart);
6950 wp->SetCurClickCount (fCurrentOwningWP->GetCurClickCount (), Time::GetTickCount ());
6951 wp->WhileSimpleMouseTracking (TableCoordinates2Window (newMousePos), fIntraCellDragAnchor);
6952 }
6953 }
6954 }
6955#if 0
6956 DbgTrace ("EXITING WordProcessorTable::WhileSimpleMouseTracking (this= 0x%x, rowSelStart=%d, rowSelEnd=%d, colSelStart=%d, colSelEnd=%d, intraCellMode=%d intraCellStart=%d, intraCellEnd=%d)\n",
6957 this, fRowSelStart, fRowSelEnd, fColSelStart, fColSelEnd, fIntraCellMode, fIntraSelStart, fIntraSelStart
6958 );
6959#endif
6960}
6961
6962/*
6963@METHOD: WordProcessorTable::GetRealCell
6964@DESCRIPTION: <p>Take the given row/column and modify them in place to assure they refer to the appropriate REAL cell.
6965 If they refer to a merge-cell, refer back to the owning REAL cell. </p>
6966*/
6967void WordProcessorTable::GetRealCell (size_t* row, size_t* column) const
6968{
6969 RequireNotNull (row);
6970 RequireNotNull (column);
6971 size_t r = *row;
6972 size_t c = *column;
6973 for (;;) {
6974 CellMergeFlags flags = GetCell (r, c).GetCellMergeFlags ();
6975 if (flags == ePlainCell) {
6976 *row = r;
6977 *column = c;
6978 return;
6979 }
6980 else {
6981#if qStroika_Foundation_Debug_AssertionsChecked
6982 bool changed = false;
6983#endif
6984 if (flags & eMergeCellLeft) {
6985 Assert (c > 0);
6986 --c;
6987#if qStroika_Foundation_Debug_AssertionsChecked
6988 changed = true;
6989#endif
6990 }
6991 if (flags & eMergeCellUp) {
6992 Assert (r > 0);
6993 --r;
6994#if qStroika_Foundation_Debug_AssertionsChecked
6995 changed = true;
6996#endif
6997 }
6998#if qStroika_Foundation_Debug_AssertionsChecked
6999 Assert (changed);
7000#endif
7001 }
7002 }
7003}
7004
7005const WordProcessorTable::Cell& WordProcessorTable::GetRealCell (size_t row, size_t column) const
7006{
7007 GetRealCell (&row, &column);
7008 return GetCell (row, column);
7009}
7010
7011bool WordProcessorTable::CanMergeCells (size_t fromRow, size_t fromCol, [[maybe_unused]] size_t toRow, [[maybe_unused]] size_t toCol)
7012{
7013 Require (fromRow <= toRow);
7014 Require (fromCol <= toCol);
7015 Require (toRow <= GetRowCount ());
7016 Require (toCol <= GetColumnCount ());
7017 // for now - our only requirements are that the region to merge is square (and this doesn't need to be tested
7018 // for because of my API), and that the top-left is a plain cell, and not already merged into something else.
7019 return GetCellFlags (fromRow, fromCol) == ePlainCell;
7020}
7021
7022/*
7023@METHOD: WordProcessorTable::MergeCells
7024@ACCESS: public
7025@DESCRIPTION: <p>Merge all the cells in the given range. The row positions bound a range of cells, so for example,
7026 if <code>toCol-fromCol==2</code> then 2 columns are being merged, and if <code>toRow-fromRow==1</code>
7027 then we are operating on a single row (all thats really fully implemented at this time - Led 3.1a4).
7028 </p>
7029 <p>Note - though the low-level support for merge-cells is mostly in place - I found it was not needed
7030 to implement most of the table functionality I wanted for 3.1. I was going to use it to handle different
7031 width columns - but since I've now revised the code to directly support having different width columns
7032 (and different numbers of columns) per row, its no longer needed. At some point in the future - this will
7033 probably be more fully supported.
7034 </p>
7035*/
7036void WordProcessorTable::MergeCells (size_t fromRow, size_t fromCol, size_t toRow, size_t toCol)
7037{
7038 Require (fromRow <= toRow);
7039 Require (fromCol <= toCol);
7040 Require (toRow <= GetRowCount ());
7041 Require (toCol <= GetColumnCount ());
7042 Require (CanMergeCells (fromRow, fromCol, toRow, toCol));
7043 bool madeChange = false;
7044 for (size_t r = fromRow; r < toRow; ++r) {
7045 for (size_t c = fromCol; c < toCol; ++c) {
7046 // All but the first cell get merged (into the first)
7047 if (not(r == fromRow and c == fromCol)) {
7048 fRows[r].fCells[c] =
7049 Cell (*this, static_cast<CellMergeFlags> (((r > fromRow) ? eMergeCellUp : 0) | ((c > fromCol) ? eMergeCellLeft : 0)));
7050 madeChange = true;
7051 }
7052 }
7053 }
7054 if (madeChange) {
7055 InvalidateLayout ();
7056 }
7057}
7058
7059void WordProcessorTable::UnMergeCells (size_t fromRow, size_t fromCol, size_t toRow, size_t toCol)
7060{
7061 Require (fromRow <= toRow);
7062 Require (fromCol <= toCol);
7063 Require (toRow < GetRowCount ());
7064 Require (toCol < GetColumnCount ());
7065 bool madeChange = false;
7066 for (size_t r = fromRow; r < toRow; ++r) {
7067 for (size_t c = fromCol; c < toCol; ++c) {
7068 // don't overwrite the cell object (losing all its data) if its already a plain cell)
7069 if (GetCellFlags (r, c) != ePlainCell) {
7070 fRows[r].fCells[c] = Cell (*this, ePlainCell);
7071 madeChange = true;
7072 }
7073 }
7074 }
7075 if (madeChange) {
7076 InvalidateLayout ();
7077 }
7078}
7079
7080/*
7081@METHOD: WordProcessorTable::SetCellSelection
7082@DESCRIPTION: <p>See @'WordProcessorTable::GetCellSelection'.</p>
7083*/
7084void WordProcessorTable::SetCellSelection (size_t rowSelStart, size_t rowSelEnd, size_t colSelStart, size_t colSelEnd)
7085{
7086 Ensure (rowSelStart <= rowSelEnd);
7087 Ensure (rowSelEnd <= GetRowCount ());
7088 Ensure (colSelStart <= colSelEnd);
7089 Ensure (colSelEnd <= GetColumnCount ());
7090 bool changed = (fRowSelStart != rowSelStart) or (fRowSelEnd != rowSelEnd) or (fColSelStart != colSelStart) or (fColSelEnd != colSelEnd);
7091 if (changed) {
7092 fRowSelStart = rowSelStart;
7093 fRowSelEnd = rowSelEnd;
7094 fColSelStart = colSelStart;
7095 fColSelEnd = colSelEnd;
7096 fIntraCellMode = false;
7097 InvalidateIntraCellContextInfo ();
7098 if (fCurrentOwningWP != nullptr) {
7099 fCurrentOwningWP->fCachedCurSelFontSpecValid = false;
7100 fCurrentOwningWP->Refresh (GetStart (), GetEnd ());
7101 }
7102 }
7103#if 0
7104 DbgTrace ("WordProcessorTable::SetCellSelection (table=0x%x, tickCount=%f, rs=%d, re=%d, cs=%d, ce=%d, changed=%d)\n",
7105 this, Time::GetTickCount (), rowSelStart, rowSelEnd, colSelStart, colSelEnd, changed
7106 );
7107#endif
7108}
7109
7110void WordProcessorTable::SetIntraCellMode ()
7111{
7112 Require (fRowSelEnd - fRowSelStart == 1);
7113 Require (fColSelEnd - fColSelStart == 1);
7114 if (not fIntraCellMode) {
7115 TextStore* ts = nullptr;
7116 GetCellWordProcessorDatabases (fRowSelStart, fColSelStart, &ts);
7117 AssertNotNull (ts);
7118 SetIntraCellSelection (0, ts->GetLength ());
7119 fIntraCellMode = true;
7120 if (fCurrentOwningWP != nullptr) {
7121 fCurrentOwningWP->Refresh (GetStart (), GetEnd ());
7122 }
7123 }
7124}
7125
7126void WordProcessorTable::SetIntraCellMode (size_t row, size_t col)
7127{
7128 if (not fIntraCellMode) {
7129 SetCellSelection (row, row + 1, col, col + 1);
7130 SetIntraCellMode ();
7131 }
7132}
7133
7134void WordProcessorTable::UnSetIntraCellMode ()
7135{
7136 if (fIntraCellMode) {
7137 fIntraCellMode = false;
7138 if (fCurrentOwningWP != nullptr) {
7139 fCurrentOwningWP->Refresh (GetStart (), GetEnd ());
7140 }
7141 }
7142}
7143
7144void WordProcessorTable::SetIntraCellSelection (size_t selStart, size_t selEnd)
7145{
7146 if (fIntraSelStart != selStart or fIntraSelEnd != selEnd) {
7147#if 0
7148 DbgTrace ("WordProcessorTable::SetIntraCellSelection (selStart = %d, selEnd = %d)- oldSel=(%d,%d), tickcount=%f\n", selStart, selEnd, fIntraSelStart, fIntraSelEnd, Time::GetTickCount ());
7149#endif
7150 if (fCurrentOwningWP != nullptr) {
7151 fCurrentOwningWP->fCachedCurSelFontSpecValid = false;
7152 }
7153 fIntraSelStart = selStart;
7154 fIntraSelEnd = selEnd;
7155 }
7156}
7157
7158/*
7159@METHOD: WordProcessorTable::ConstructEmbeddedTableWordProcessor
7160@ACCESS: protected
7161@DESCRIPTION: <p>Called internally by the @'WordProcessorTable' code to instantiate mini embedded
7162 word processor objects (@'EmbeddedTableWordProcessor') to be in each cell. Objects created
7163 with this method should be released with a call to
7164 @'WordProcessorTable::ReleaseEmbeddedTableWordProcessor'.</p>
7165*/
7166WordProcessorTable::EmbeddedTableWordProcessor* WordProcessorTable::ConstructEmbeddedTableWordProcessor (WordProcessor& forWordProcessor,
7167 size_t forRow, size_t forColumn,
7168 const Led_Rect& cellWindowRect,
7169 bool captureChangesForUndo)
7170{
7171 size_t cellModeRow = 0;
7172 size_t cellModeCol = 0;
7173 bool activeFocusedCell = GetIntraCellMode (&cellModeRow, &cellModeCol) and cellModeRow == forRow and cellModeCol == forColumn;
7174 EmbeddedTableWordProcessor* e = new EmbeddedTableWordProcessor (forWordProcessor, *this, forRow, forColumn, activeFocusedCell);
7175 try {
7176 TextStore* ts = nullptr;
7177 shared_ptr<AbstractStyleDatabaseRep> styleDatabase;
7178 shared_ptr<AbstractParagraphDatabaseRep> paragraphDatabase;
7179 shared_ptr<HidableTextMarkerOwner> hidableTextDatabase;
7180 GetCellWordProcessorDatabases (forRow, forColumn, &ts, &styleDatabase, &paragraphDatabase, &hidableTextDatabase);
7181 e->SetStyleDatabase (styleDatabase);
7182 e->SetParagraphDatabase (paragraphDatabase);
7183 e->SetHidableTextDatabase (hidableTextDatabase);
7184 e->SpecifyTextStore (ts);
7185 e->SetWindowRect (cellWindowRect);
7186 e->SetDefaultTextColor (WordProcessor::eDefaultBackgroundColor, GetCellColor (forRow, forColumn));
7187 if (captureChangesForUndo) {
7188 e->SetCommandHandler (forWordProcessor.GetCommandHandler ());
7189 }
7190
7191 if (activeFocusedCell) {
7192 using TemporarilyUseTablet = EmbeddedTableWordProcessor::TemporarilyUseTablet;
7193
7194 AssertNotNull (fCurrentOwningWP);
7195 WordProcessor::Tablet_Acquirer tablet (fCurrentOwningWP);
7196 TemporarilyUseTablet tmpUseTablet (*e, tablet, TemporarilyUseTablet::eDontDoTextMetricsChangedCall);
7197
7198 e->SetSelectionShown (true, TextInteractor::eNoUpdate); // set TRUE so stuff that changes the selection does the proper invalidation
7199 e->RestoreMiscActiveFocusInfo ();
7200 }
7201 }
7202 catch (...) {
7203 e->SpecifyTextStore (nullptr);
7204 delete e;
7205 }
7206 return e;
7207}
7208
7209/*
7210@METHOD: WordProcessorTable::ReleaseEmbeddedTableWordProcessor
7211@ACCESS: protected
7212@DESCRIPTION: <p>Release word @'WordProcessorTable::EmbeddedTableWordProcessor' objects
7213 allocated with @'WordProcessorTable::ConstructEmbeddedTableWordProcessor'.
7214 This may not neccesarily DELETE them as they could
7215 be cached (for example - if they are the currently active cell, and are blinking the caret etc...</p>
7216*/
7217void WordProcessorTable::ReleaseEmbeddedTableWordProcessor (EmbeddedTableWordProcessor* e)
7218{
7219 RequireNotNull (e);
7220 e->SaveMiscActiveFocusInfo ();
7221 e->SetCommandHandler (nullptr);
7222 e->SpecifyTextStore (nullptr);
7223 delete e;
7224}
7225
7226/*
7227@METHOD: WordProcessorTable::PerformLayout
7228@ACCESS: protected
7229@DESCRIPTION: <p></p>
7230*/
7231void WordProcessorTable::PerformLayout ()
7232{
7233 Require (fNeedLayout != eDone);
7234
7235 if (fCurrentOwningWP != nullptr) {
7236 TextStore& ts = GetOwner ()->GetTextStore ();
7237 TextStore::SimpleUpdater updater (ts, GetStart (), GetEnd (), false);
7238
7239 Led_Size border = Led_Size (Led_CvtScreenPixelsFromTWIPSV (fBorderWidth), Led_CvtScreenPixelsFromTWIPSH (fBorderWidth));
7240 DistanceType spacing = Led_CvtScreenPixelsFromTWIPSV (GetCellSpacing ());
7241 Led_Rect defaultCellMargin;
7242 {
7243 TWIPS_Rect defaultCellMarginTWIPS;
7244 GetDefaultCellMargins (&defaultCellMarginTWIPS.top, &defaultCellMarginTWIPS.left, &defaultCellMarginTWIPS.bottom,
7245 &defaultCellMarginTWIPS.right);
7246 defaultCellMargin.top = Led_CvtScreenPixelsFromTWIPSV (defaultCellMarginTWIPS.top);
7247 defaultCellMargin.left = Led_CvtScreenPixelsFromTWIPSH (defaultCellMarginTWIPS.left);
7248 defaultCellMargin.bottom = Led_CvtScreenPixelsFromTWIPSV (defaultCellMarginTWIPS.bottom);
7249 defaultCellMargin.right = Led_CvtScreenPixelsFromTWIPSH (defaultCellMarginTWIPS.right);
7250 }
7251
7252 // Need to be more careful her about updating the row heights records. We COULD not be able to do this, and then we want to set a flag
7253 // saying to re-layout when we are RE-associated with a word-processor object (temporarily)
7254
7255 size_t rows = GetRowCount ();
7256
7257 Tablet_Acquirer tablet (fCurrentOwningWP);
7258
7259 DistanceType maxTableWidth = 0;
7260
7261 DistanceType runningHeight = 0;
7262 for (size_t r = 0; r < rows; ++r) {
7263 /*
7264 * Compute REAL (non-merge) cells widths.
7265 */
7266 size_t cols = GetColumnCount (r);
7267 Memory::StackBuffer<DistanceType> realCellWidths{cols}; // cell widths for this row - only for REAL (plain - non-merge) cells
7268 DistanceType rowWidth = 0;
7269 {
7270 size_t lastRealCellIdx = 0;
7271 for (size_t c = 0; c < cols; ++c) {
7272 DistanceType thisColWidth = Led_CvtScreenPixelsFromTWIPSH (GetColumnWidth (r, c));
7273 if (GetCellFlags (r, c) == ePlainCell) {
7274 realCellWidths[c] = thisColWidth;
7275 lastRealCellIdx = c;
7276 rowWidth += thisColWidth;
7277 }
7278 else {
7279 // and ... if its a 'merge left' cell - then add this width to the width of the REAL rect
7280 realCellWidths[lastRealCellIdx] += thisColWidth;
7281 }
7282 }
7283 }
7284
7285 /*
7286 * Compute their row (cell) heights.
7287 */
7288 DistanceType rowHeight = 0;
7289 for (size_t c = 0; c < cols; ++c) {
7290 if (GetCellFlags (r, c) == ePlainCell) {
7291 using TemporarilyUseTablet = EmbeddedTableWordProcessor::TemporarilyUseTablet;
7292
7293 DistanceType totalCellMargin = defaultCellMargin.left + defaultCellMargin.right;
7294 DistanceType wpWidth = (totalCellMargin < realCellWidths[c]) ? realCellWidths[c] - totalCellMargin : 1;
7295 TemporarilyAllocateCellWP wp (*this, *fCurrentOwningWP, r, c, Led_Rect (0, 0, 1000, wpWidth));
7296 TemporarilyUseTablet tmpUseTablet (*wp, tablet, TemporarilyUseTablet::eDontDoTextMetricsChangedCall);
7297 rowHeight = max (rowHeight, wp->GetDesiredHeight ());
7298 }
7299 }
7300 /*
7301 * Note - since cell width is usaully pre-specified - we don't let the margin determine that.
7302 * But - since we're COMPUTING the appropriate cell HEIGHT - we must add in the top/bottom cell margins.
7303 */
7304 rowHeight += defaultCellMargin.top + defaultCellMargin.bottom;
7305 rowHeight = max (rowHeight, DistanceType (5)); // assure some min height
7306 fRows[r].fHeight = rowHeight;
7307 runningHeight += rowHeight;
7308
7309 DistanceType rowWidthWithSpacingNBorders = rowWidth + static_cast<DistanceType> ((cols + 1) * (spacing + border.h));
7310 maxTableWidth = max (maxTableWidth, rowWidthWithSpacingNBorders);
7311
7312 /*
7313 * Store away the resulting cell rectangles (again - only for REAL - non-merge cells).
7314 */
7315 {
7316 DistanceType runningWidth = 0; // not including spacing - cuz that added separately...
7317 size_t lastRealCellIdx = 0;
7318 for (size_t c = 0; c < cols; ++c) {
7319 if (GetCellFlags (r, c) == ePlainCell) {
7320 Cell cell = GetCell (r, c);
7321 Led_Rect cellRect = Led_Rect (runningHeight - rowHeight, runningWidth, rowHeight, realCellWidths[c]);
7322
7323 cellRect +=
7324 Led_Point (static_cast<CoordinateType> ((r + 1) * border.v), static_cast<CoordinateType> ((c + 1) * border.h));
7325
7326 // add in cell spacing
7327 cellRect += Led_Point (static_cast<CoordinateType> ((r + 1) * spacing), static_cast<CoordinateType> ((c + 1) * spacing));
7328
7329 cell.SetCachedBoundsRect (cellRect);
7330 lastRealCellIdx = c;
7331 runningWidth += realCellWidths[c];
7332 }
7333 }
7334 }
7335 }
7336
7337 {
7338 DistanceType totalHeight = 0;
7339 for (auto i = fRows.begin (); i != fRows.end (); ++i) {
7340 totalHeight += (*i).fHeight;
7341 }
7342 fTotalHeight = totalHeight + static_cast<DistanceType> ((spacing + border.v) * (fRows.size () + 1));
7343 }
7344
7345 fTotalWidth = maxTableWidth;
7346
7347 fNeedLayout = eDone;
7348 }
7349}
7350
7351#endif
7352
7353/*
7354@METHOD: WordProcessorTable::GetDimensions
7355@DESCRIPTION: <p>Return the number of rows and columns in the given table. Pointer parameters CAN be null,
7356 and that value is just not returned.</p>
7357*/
7358void WordProcessorTable::GetDimensions (size_t* rows, size_t* columns) const
7359{
7360 if (rows != nullptr) {
7361 *rows = fRows.size ();
7362 }
7363 if (columns != nullptr) {
7364 size_t maxCols = 0;
7365 for (size_t ri = 0; ri < fRows.size (); ++ri) {
7366 maxCols = max (maxCols, fRows[ri].fCells.size ());
7367 }
7368 *columns = maxCols;
7369 }
7370}
7371
7372/*
7373@METHOD: WordProcessorTable::SetDimensions
7374@DESCRIPTION: <p>Specifies the number of rows and columns desired. If rows or columns need to be created, they will be
7375 appended. If rows/columns need to be destroyed - they will be desroyed from the end (right/bottom). If you need
7376 more specific control, then call @'WordProcessorTable::InsertRow', @'WordProcessorTable::DeleteRow',
7377 @'WordProcessorTable::InsertColumn', or @'WordProcessorTable::DeleteColumn' directly.</p>
7378*/
7379void WordProcessorTable::SetDimensions (size_t rows, size_t columns)
7380{
7381 size_t oldRows = 0;
7382 size_t oldColumns = 0;
7383 GetDimensions (&oldRows, &oldColumns);
7384
7385 // delete first
7386 while (oldRows > rows) {
7387 DeleteRow (oldRows - 1);
7388 --oldRows;
7389 }
7390 while (oldColumns > columns) {
7391 DeleteColumn (oldColumns - 1);
7392 --oldColumns;
7393 }
7394
7395 // then append any needed rows/columns
7396 while (oldRows < rows) {
7397 InsertRow (oldRows);
7398 ++oldRows;
7399 }
7400 while (oldColumns < columns) {
7401 InsertColumn (oldColumns);
7402 ++oldColumns;
7403 }
7405 GetDimensions (&oldRows, &oldColumns);
7406 Assert (oldRows == rows);
7407 Assert (oldColumns == columns);
7408 }
7409}
7410
7411/*
7412@METHOD: WordProcessorTable::SetDimensionsAtLeast
7413@DESCRIPTION: <p>Calls @'WordProcessorTable::GetDimensions' and
7414 @'WordProcessorTable::SetDimensions' to assure
7415 there are <em>at least</em> the given number of rows and columns.</p>
7416*/
7417void WordProcessorTable::SetDimensionsAtLeast (size_t rows, size_t columns)
7418{
7419 size_t r = 0;
7420 size_t c = 0;
7421 GetDimensions (&r, &c);
7422 r = max (rows, r);
7423 c = max (columns, c);
7424 SetDimensions (r, c);
7425}
7426
7427/*
7428@METHOD: WordProcessorTable::InsertRow
7429@DESCRIPTION: <p>Insert a new blank row at the given 'at' offset. Must be
7430 between 0 and the current number of rows (inclusive). Get the
7431 number of columns and the size of those columns from the adjacent
7432 row, but copying a maximum of 'maxRowCopyCount' columns.</p>
7433*/
7434void WordProcessorTable::InsertRow (size_t at, size_t maxRowCopyCount)
7435{
7436 Require (at <= GetRowCount ());
7437 Require (maxRowCopyCount >= 1);
7438#if qStroika_Frameworks_Led_SupportGDI
7439
7440 TextStore& ts = GetOwner ()->GetTextStore ();
7441 TextStore::SimpleUpdater updater (ts, GetStart (), GetEnd ());
7442#endif
7443 RowInfo newRow;
7444
7445 // Copy row count and row width from adjoining row. COULD be smarter about this - taking a HINT as
7446 // to whether to copy from LHS or RHS - but this is pretty reasonable...
7447 size_t rowToCopy = at > 0 ? at - 1 : at;
7448 size_t colCount = 0;
7449 if (rowToCopy < GetRowCount ()) {
7450 colCount = GetColumnCount (rowToCopy);
7451 }
7452 colCount = min (colCount, maxRowCopyCount);
7453
7454 for (size_t c = 0; c < colCount; ++c) {
7455 Cell cell (*this, ePlainCell);
7456 cell.SetCellXWidth (GetColumnWidth (rowToCopy, c));
7457 newRow.fCells.push_back (cell);
7458 }
7459
7460 fRows.insert (fRows.begin () + at, newRow);
7461#if qStroika_Frameworks_Led_SupportGDI
7462
7463 InvalidateIntraCellContextInfo ();
7464 InvalidateLayout ();
7465#endif
7466}
7467
7468/*
7469@METHOD: WordProcessorTable::DeleteRow
7470@DESCRIPTION: <p>Delete the given row at the given 'at' offset.</p>
7471*/
7472void WordProcessorTable::DeleteRow (size_t at)
7473{
7474 Require (at < GetRowCount ());
7475#if qStroika_Frameworks_Led_SupportGDI
7476
7477 TextStore& ts = GetOwner ()->GetTextStore ();
7478 TextStore::SimpleUpdater updater (ts, GetStart (), GetEnd ());
7479#endif
7480
7481 fRows.erase (fRows.begin () + at);
7482#if qStroika_Frameworks_Led_SupportGDI
7483 InvalidateIntraCellContextInfo ();
7484 InvalidateLayout ();
7485 ReValidateSelection ();
7486#endif
7487}
7488
7489/*
7490@METHOD: WordProcessorTable::InsertColumn
7491@DESCRIPTION: <p>Insert a new blank column at the given 'at' offset. Must be between
7492 0 and the current number of columns (inclusive).</p>
7493*/
7494void WordProcessorTable::InsertColumn (size_t at)
7495{
7496 Require (at <= GetColumnCount ());
7497#if qStroika_Frameworks_Led_SupportGDI
7498 TextStore& ts = GetOwner ()->GetTextStore ();
7499 TextStore::SimpleUpdater updater (ts, GetStart (), GetEnd ());
7500 TWIPS newColWidth = Led_CvtScreenPixelsToTWIPSH (100);
7501#else
7502 TWIPS newColWidth = TWIPS (100);
7503#endif
7504
7505 // Grab default size for new column from first row/prev (or next) col
7506 if (fRows.size () > 0) {
7507 size_t nColsInRow = GetColumnCount (0);
7508 //size_t useCol = at;
7509 if (at == nColsInRow and at > 0) {
7510 newColWidth = GetColumnWidth (0, at - 1);
7511 }
7512 else if (at < nColsInRow) {
7513 newColWidth = GetColumnWidth (0, at);
7514 }
7515 }
7516
7517 vector<Cell> newCol;
7518 for (size_t r = 0; r < fRows.size (); ++r) {
7519 Cell cell (*this, ePlainCell);
7520 cell.SetCellXWidth (newColWidth);
7521 newCol.push_back (cell);
7522 }
7523
7524 size_t rowCount = fRows.size ();
7525 for (size_t ri = 0; ri < rowCount; ++ri) {
7526 vector<Cell>& rowCells = fRows[ri].fCells;
7527 rowCells.insert (rowCells.begin () + at, newCol[ri]);
7528 }
7529#if qStroika_Frameworks_Led_SupportGDI
7530
7531 InvalidateIntraCellContextInfo ();
7532 InvalidateLayout ();
7533#endif
7534}
7535
7536/*
7537@METHOD: WordProcessorTable::DeleteColumn
7538@DESCRIPTION: <p>Delete the given column at the given 'at' offset.</p>
7539*/
7540void WordProcessorTable::DeleteColumn (size_t at)
7541{
7542 Require (at < GetColumnCount ());
7543 // BUT - allow for the fact that some rows may have fewer columns than GetColumnCount ()...
7544
7545#if qStroika_Frameworks_Led_SupportGDI
7546 TextStore& ts = GetOwner ()->GetTextStore ();
7547 TextStore::SimpleUpdater updater (ts, GetStart (), GetEnd ());
7548#endif
7549
7550 size_t rowCount = fRows.size ();
7551 for (size_t ri = 0; ri < rowCount; ++ri) {
7552 vector<Cell>& rowCells = fRows[ri].fCells;
7553 if (at < rowCells.size ()) {
7554 rowCells.erase (rowCells.begin () + at);
7555 }
7556 }
7557#if qStroika_Frameworks_Led_SupportGDI
7558 InvalidateIntraCellContextInfo ();
7559 InvalidateLayout ();
7560 ReValidateSelection ();
7561#endif
7562}
7563#if qStroika_Frameworks_Led_SupportGDI
7564
7565/*
7566@METHOD: WordProcessorTable::ReValidateSelection
7567@ACCESS: protected
7568@DESCRIPTION: <p>Called internally when something happens that could invalidate the selection. Assure its
7569 still valid.</p>
7570*/
7571void WordProcessorTable::ReValidateSelection ()
7572{
7573 size_t rowCount = GetRowCount ();
7574
7575 size_t rowSelStart = fRowSelStart; // Cannot call GetCellSelection () cuz it asserts selection valid
7576 size_t rowSelEnd = fRowSelEnd;
7577 size_t colSelStart = fColSelStart;
7578 size_t colSelEnd = fColSelEnd;
7579
7580 if (rowSelStart >= rowCount) {
7581 rowSelStart = 0;
7582 rowSelEnd = 0;
7583 }
7584 if (rowSelEnd > rowCount) {
7585 rowSelEnd = rowCount;
7586 }
7587 size_t colCount = GetColumnCount (rowSelStart, rowSelEnd);
7588 if (colSelStart >= colCount) {
7589 colSelStart = 0;
7590 colSelEnd = 0;
7591 }
7592 if (colSelEnd >= colCount) {
7593 colSelEnd = colCount;
7594 }
7595 SetCellSelection (rowSelStart, rowSelEnd, colSelStart, colSelEnd);
7596}
7597
7598/*
7599 ********************************************************************************
7600 ************************** EmbeddedTableWordProcessor **************************
7601 ********************************************************************************
7602 */
7603
7604/*
7605@METHOD: WordProcessorTable::EmbeddedTableWordProcessor::EmbeddedTableWordProcessor
7606@DESCRIPTION: <p></p>
7607*/
7608WordProcessorTable::EmbeddedTableWordProcessor::EmbeddedTableWordProcessor (WordProcessor& owningWordProcessor, WordProcessorTable& owningTable,
7609 size_t tRow, size_t tCol, bool activeEditCell)
7610 : inherited ()
7611 , fOwningWordProcessor (owningWordProcessor)
7612 , fOwningTable (owningTable)
7613 , fTableRow (tRow)
7614 , fTableColumn (tCol)
7615 , fUpdateTablet (nullptr)
7616 , fDesiredHeight (0)
7617 , fDesiredHeightValid (false)
7618 , fActiveEditCell (activeEditCell)
7619 , fSuppressRefreshCalls (false)
7620{
7621 SetImageUsingOffscreenBitmaps (false);
7622}
7623
7624WordProcessorTable& WordProcessorTable::EmbeddedTableWordProcessor::GetOwningTable () const
7625{
7626 return fOwningTable;
7627}
7628
7629WordProcessor& WordProcessorTable::EmbeddedTableWordProcessor::GetOwningWordProcessor () const
7630{
7631 return fOwningWordProcessor;
7632}
7633
7634void WordProcessorTable::EmbeddedTableWordProcessor::SaveMiscActiveFocusInfo ()
7635{
7636 if (fActiveEditCell) {
7637 fOwningTable.SetIntraCellSelection (GetSelectionStart (), GetSelectionEnd ());
7638 fOwningTable.SaveIntraCellContextInfo (fLeftSideOfSelectionInteresting, GetEmptySelectionStyle ());
7639 }
7640}
7641
7642void WordProcessorTable::EmbeddedTableWordProcessor::RestoreMiscActiveFocusInfo ()
7643{
7644 if (fActiveEditCell) {
7645 DisableRefreshContext DFR (*this);
7646 SuppressCellUpdatePropagationContext SCUP (fOwningTable);
7647
7648 FontSpecification emptySelFont;
7649 bool leftSideOfSelectionInteresting = false;
7650 if (fOwningTable.RestoreIntraCellContextInfo (&leftSideOfSelectionInteresting, &emptySelFont)) {
7651 size_t intraCellSelStart = 0;
7652 size_t intraCellSelEnd = 0;
7653 fOwningTable.GetIntraCellSelection (&intraCellSelStart, &intraCellSelEnd);
7654 SetSelection (intraCellSelStart, intraCellSelEnd, TextInteractor::eNoUpdate);
7655 fLeftSideOfSelectionInteresting = leftSideOfSelectionInteresting;
7656 SetEmptySelectionStyle (emptySelFont);
7657 }
7658 else {
7659 SetEmptySelectionStyle ();
7660 }
7661 }
7662}
7663
7664#if !qStroika_Frameworks_Led_NestedTablesSupported
7665void WordProcessorTable::EmbeddedTableWordProcessor::HookInternalizerChanged ()
7666{
7667 inherited::HookInternalizerChanged ();
7668 WordProcessorFlavorPackageInternalizer* internalizerRep =
7669 dynamic_cast<WordProcessorFlavorPackageInternalizer*> (static_cast<FlavorPackageInternalizer*> (GetInternalizer ().get ()));
7670 AssertNotNull (internalizerRep);
7671 internalizerRep->SetNoTablesAllowed (true);
7672}
7673#endif
7674
7675bool WordProcessorTable::EmbeddedTableWordProcessor::OnCopyCommand_Before ()
7676{
7677 return fOwningWordProcessor.OnCopyCommand_Before ();
7678}
7679
7680void WordProcessorTable::EmbeddedTableWordProcessor::OnCopyCommand_After ()
7681{
7682 fOwningWordProcessor.OnCopyCommand_After ();
7683}
7684
7685bool WordProcessorTable::EmbeddedTableWordProcessor::OnPasteCommand_Before ()
7686{
7687 return fOwningWordProcessor.OnPasteCommand_Before ();
7688}
7689
7690void WordProcessorTable::EmbeddedTableWordProcessor::OnPasteCommand_After ()
7691{
7692 fOwningWordProcessor.OnPasteCommand_After ();
7693}
7694
7695void WordProcessorTable::EmbeddedTableWordProcessor::DrawRowHilight (Tablet* /*tablet*/, const Led_Rect& /*currentRowRect*/, const Led_Rect& /*invalidRowRect*/,
7696 const TextLayoutBlock& /*text*/, size_t /*rowStart*/, size_t /*rowEnd*/
7697)
7698{
7699 // Do nothing... - taken care if via owning WordProcessorTable and OVERRIDE of GetRowHilightRects
7700}
7701
7702Tablet* WordProcessorTable::EmbeddedTableWordProcessor::AcquireTablet () const
7703{
7704 if (fUpdateTablet != nullptr) {
7705 return fUpdateTablet;
7706 }
7707 Assert (false); // we shouldn't need this anymore - get rid... LGP 2003-03-18
7708 //tmphack - probably needs to be slightly differnet
7709 return fOwningWordProcessor.AcquireTablet ();
7710}
7711
7712void WordProcessorTable::EmbeddedTableWordProcessor::ReleaseTablet (Tablet* tablet) const
7713{
7714 if (tablet == fUpdateTablet) {
7715 return;
7716 }
7717 Assert (false); // we shouldn't need this anymore - get rid... LGP 2003-03-18
7718 //tmphack - probably needs to be slightly differnet
7719 fOwningWordProcessor.ReleaseTablet (tablet);
7720}
7721
7722void WordProcessorTable::EmbeddedTableWordProcessor::RefreshWindowRect_ (const Led_Rect& windowRectArea, UpdateMode updateMode) const
7723{
7724 if (not fSuppressRefreshCalls) {
7725 // See SPR#1455- really never a need to do IMMEDIATE update and for these embedded WP's can get into a
7726 // deadly embrace. This may not be the BEST solution to that deadly embrace, but it appears to be a
7727 // safe and adequate one... LGP 2003-05-01
7728 UpdateMode useUpdateMode = (updateMode == eImmediateUpdate) ? eDelayedUpdate : updateMode;
7729 fOwningWordProcessor.RefreshWindowRect_ (windowRectArea, useUpdateMode);
7730 }
7731}
7732
7733void WordProcessorTable::EmbeddedTableWordProcessor::UpdateWindowRect_ (const Led_Rect& /*windowRectArea*/) const
7734{
7735 throw CannotUpdateNow ();
7736}
7737
7738bool WordProcessorTable::EmbeddedTableWordProcessor::QueryInputKeyStrokesPending () const
7739{
7740 return true; // a bit of a hack to encourage display code to do a refresh instead of an Update () call
7741 // (which would be OK - but it generates a needless exception) - LGP 2003-05-02
7742 //return fOwningWordProcessor.QueryInputKeyStrokesPending ();
7743}
7744
7745void WordProcessorTable::EmbeddedTableWordProcessor::SetDefaultUpdateMode (UpdateMode defaultUpdateMode)
7746{
7747 if (defaultUpdateMode == eImmediateUpdate) {
7748 defaultUpdateMode = eDelayedUpdate;
7749 }
7750 inherited::SetDefaultUpdateMode (defaultUpdateMode);
7751}
7752
7753void WordProcessorTable::EmbeddedTableWordProcessor::GetLayoutMargins (RowReference row, CoordinateType* lhs, CoordinateType* rhs) const
7754{
7755 if (rhs != nullptr) {
7756 inherited::GetLayoutMargins (row, lhs, nullptr);
7757 }
7758
7759 // MAYBE replace this logic with changing the LHS/RHS according to the cell padding (or what is the term?)
7760 // Anyhow - this hack should work MUCH better than the current display!
7761
7762 // Make the layout width of each line (paragraph) equal to the windowrect. Ie, wrap to the
7763 // edge of the window. NB: because of this choice, we must 'InvalidateAllCaches' when the
7764 // WindowRect changes in our SetWindowRect() OVERRIDE.
7765 if (rhs != nullptr) {
7766 *rhs = (max (CoordinateType (GetWindowRect ().GetWidth ()), CoordinateType (1)));
7767 }
7768}
7769
7770void WordProcessorTable::EmbeddedTableWordProcessor::PostInteractiveUndoPostHelper (InteractiveReplaceCommand::SavedTextRep** beforeRep,
7771 InteractiveReplaceCommand::SavedTextRep** afterRep,
7772 size_t startOfInsert, const SDKString& cmdName)
7773{
7774 RequireNotNull (beforeRep);
7775 RequireNotNull (afterRep);
7776 CommandHandler* ch = GetCommandHandler ();
7777 RequireNotNull (ch);
7778 try {
7779 if (*beforeRep != nullptr and *afterRep != nullptr) {
7780 // We are careful to set things to nullptr at each stage to prevent double
7781 // deletes in the event of a badly timed exception
7782 InteractiveReplaceCommand* cmd =
7783 new TableCMD (fOwningTable.GetStart (), fTableRow, fTableColumn, *beforeRep, *afterRep, startOfInsert, cmdName);
7784 *beforeRep = nullptr;
7785 *afterRep = nullptr;
7786 ch->Post (cmd);
7787 }
7788 }
7789 catch (...) {
7790 delete *beforeRep;
7791 *beforeRep = nullptr;
7792 delete *afterRep;
7793 *afterRep = nullptr;
7794 throw; // safe at this point to throw - but perhaps better to silently eat the throw?
7795 }
7796}
7797
7798InteractiveReplaceCommand::SavedTextRep*
7799WordProcessorTable::EmbeddedTableWordProcessor::InteractiveUndoHelperMakeTextRep (size_t regionStart, size_t regionEnd, size_t selStart, size_t selEnd)
7800{
7801 InteractiveReplaceCommand::SavedTextRep* tableStateRep = inherited::InteractiveUndoHelperMakeTextRep (regionStart, regionEnd, selStart, selEnd);
7802 return new SavedTextRepWSel (tableStateRep, fOwningTable, SavedTextRepWSel::eWPDirect);
7803}
7804
7805DistanceType WordProcessorTable::EmbeddedTableWordProcessor::GetDesiredHeight () const
7806{
7807 if (not fDesiredHeightValid) {
7808 RowReference startingRow = GetRowReferenceContainingPosition (0);
7809 RowReference endingRow = GetRowReferenceContainingPosition (GetEnd ());
7810 /*
7811 * Always take one more row than they asked for, since they will expect if you start and end on a given row - you'll get
7812 * the height of that row.
7813 */
7814 fDesiredHeightValid = true;
7815 fDesiredHeight = GetHeightOfRows (startingRow, CountRowDifference (startingRow, endingRow) + 1);
7816 }
7817 return fDesiredHeight;
7818}
7819
7820/*
7821 ********************************************************************************
7822 ****************** WordProcessorTable::SavedTextRepWSel **********************
7823 ********************************************************************************
7824 */
7825WordProcessorTable::SavedTextRepWSel::SavedTextRepWSel (SavedTextRep* delegateTo, WordProcessorTable& table, WPRelativeFlag wPRelativeFlag)
7826 : inherited (table.GetStart (), table.GetEnd ())
7827 , fWPRelativeFlag (wPRelativeFlag)
7828 , fRealRep (delegateTo)
7829 , fRowSelStart (0)
7830 , fRowSelEnd (0)
7831 , fColSelStart (0)
7832 , fColSelEnd (0)
7833 , fIntraCellMode (false)
7834 , fIntraCellSelStart (0)
7835 , fIntraCellSelEnd (0)
7836{
7837 RequireNotNull (delegateTo);
7838 table.GetCellSelection (&fRowSelStart, &fRowSelEnd, &fColSelStart, &fColSelEnd);
7839 fIntraCellMode = table.GetIntraCellMode ();
7840 if (fIntraCellMode) {
7841 table.GetIntraCellSelection (&fIntraCellSelStart, &fIntraCellSelEnd);
7842 }
7843}
7844
7845size_t WordProcessorTable::SavedTextRepWSel::GetLength () const
7846{
7847 return fRealRep->GetLength ();
7848}
7849
7850void WordProcessorTable::SavedTextRepWSel::InsertSelf (TextInteractor* interactor, size_t at, size_t nBytesToOverwrite)
7851{
7852 fRealRep->InsertSelf (interactor, at, nBytesToOverwrite);
7853}
7854
7855void WordProcessorTable::SavedTextRepWSel::ApplySelection (TextInteractor* interactor)
7856{
7857 fRealRep->ApplySelection (interactor);
7858
7859 WordProcessorTable* aT = nullptr;
7860 if (fWPRelativeFlag == eWPDirect) {
7861 EmbeddedTableWordProcessor* wp = dynamic_cast<EmbeddedTableWordProcessor*> (interactor);
7862 aT = &wp->GetOwningTable ();
7863 }
7864 else {
7865 WordProcessor* wp = dynamic_cast<WordProcessor*> (interactor);
7866 AssertNotNull (wp);
7867 aT = wp->GetActiveTable (); // above fRealRep->ApplySelection should have selected just the right table...
7868 }
7869 AssertNotNull (aT);
7870
7871 AssertNotNull (aT);
7872 aT->SetCellSelection (fRowSelStart, fRowSelEnd, fColSelStart, fColSelEnd);
7873 if (fIntraCellMode) {
7874 aT->SetIntraCellMode ();
7875 aT->SetIntraCellSelection (fIntraCellSelStart, fIntraCellSelEnd);
7876 }
7877 else {
7878 aT->UnSetIntraCellMode ();
7879 }
7880}
7881
7882/*
7883 ********************************************************************************
7884 ************************ EmptySelectionParagraphSavedTextRep *******************
7885 ********************************************************************************
7886 */
7887using EmptySelectionParagraphSavedTextRep = WordProcessor::EmptySelectionParagraphSavedTextRep;
7888
7889EmptySelectionParagraphSavedTextRep::EmptySelectionParagraphSavedTextRep (WordProcessor* interactor, size_t selStart, size_t selEnd, size_t at)
7890 : inherited (interactor, selStart, selEnd)
7891 , fSavedStyleInfo (interactor->GetParagraphDatabase ()->GetParagraphInfo (at))
7892{
7893}
7894
7895void EmptySelectionParagraphSavedTextRep::InsertSelf (TextInteractor* interactor, size_t at, size_t nBytesToOverwrite)
7896{
7897 inherited::InsertSelf (interactor, at, nBytesToOverwrite);
7898 WordProcessor* wp = dynamic_cast<WordProcessor*> (interactor);
7899 RequireNotNull (wp);
7900 wp->GetParagraphDatabase ()->SetParagraphInfo (at, 1, IncrementalParagraphInfo (fSavedStyleInfo));
7901}
7902#endif
#define AssertNotNull(p)
Definition Assertions.h:336
#define EnsureNotNull(p)
Definition Assertions.h:343
#define RequireMember(p, c)
Definition Assertions.h:329
#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 Verify(c)
Definition Assertions.h:422
conditional_t< qStroika_Foundation_Memory_PreferBlockAllocation and andTrueCheck, BlockAllocationUseHelper< T >, Common::Empty > UseBlockAllocationIfAppropriate
Use this to enable block allocation for a particular class. Beware of subclassing.
set< T > Intersection(const set< T > &s1, const set< T > &s2)
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:327
constexpr bool IsWhitespace() const noexcept
Is this blank, and a place text may break (between words, tokens, lines ...)? The C standard white-sp...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
basic_string< SDKChar > SDKString
Definition SDKString.h:38
CONTAINER::value_type * Start(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the start of the ...