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