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