]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/stc/scintilla/src/Document.cxx
7d832241fc1a65716b3a4bc74237ce736173c705
[wxWidgets.git] / contrib / src / stc / scintilla / src / Document.cxx
1 // Scintilla source code edit control
2 // Document.cxx - text document that handles notifications, DBCS, styling, words and end of line
3 // Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org>
4 // The License.txt file describes the conditions under which this software may be distributed.
5
6 #include <stdlib.h>
7 #include <string.h>
8 #include <stdio.h>
9 #include <ctype.h>
10
11 #include "Platform.h"
12
13 #include "Scintilla.h"
14 #include "SVector.h"
15 #include "CellBuffer.h"
16 #include "Document.h"
17
18 Document::Document() {
19 refCount = 0;
20 #ifdef unix
21 eolMode = SC_EOL_LF;
22 #else
23 eolMode = SC_EOL_CRLF;
24 #endif
25 dbcsCodePage = 0;
26 stylingBits = 5;
27 stylingBitsMask = 0x1F;
28 stylingPos = 0;
29 stylingMask = 0;
30 for (int ch = 0; ch < 256; ch++) {
31 wordchars[ch] = isalnum(ch) || ch == '_';
32 }
33 endStyled = 0;
34 enteredCount = 0;
35 tabInChars = 8;
36 watchers = 0;
37 lenWatchers = 0;
38 }
39
40 Document::~Document() {
41 for (int i = 0; i < lenWatchers; i++) {
42 watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
43 }
44 delete []watchers;
45 watchers = 0;
46 lenWatchers = 0;
47 }
48
49 // Increase reference count and return its previous value.
50 int Document::AddRef() {
51 return refCount++;
52 }
53
54 // Decrease reference count and return its provius value.
55 // Delete the document if reference count reaches zero.
56 int Document::Release() {
57 int curRefCount = --refCount;
58 if (curRefCount == 0)
59 delete this;
60 return curRefCount;
61 }
62
63 void Document::SetSavePoint() {
64 cb.SetSavePoint();
65 NotifySavePoint(true);
66 }
67
68 int Document::LineStart(int line) {
69 return cb.LineStart(line);
70 }
71
72 int Document::LineFromPosition(int pos) {
73 return cb.LineFromPosition(pos);
74 }
75
76 int Document::LineEndPosition(int position) {
77 int line = LineFromPosition(position);
78 if (line == LinesTotal() - 1) {
79 position = LineStart(line + 1);
80 } else {
81 position = LineStart(line + 1) - 1;
82 // When line terminator is CR+LF, may need to go back one more
83 if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
84 position--;
85 }
86 }
87 return position;
88 }
89
90 int Document::VCHomePosition(int position) {
91 int line = LineFromPosition(position);
92 int startPosition = LineStart(line);
93 int endLine = LineStart(line + 1) - 1;
94 int startText = startPosition;
95 while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )
96 startText++;
97 if (position == startText)
98 return startPosition;
99 else
100 return startText;
101 }
102
103 int Document::SetLevel(int line, int level) {
104 int prev = cb.SetLevel(line, level);
105 if (prev != level) {
106 DocModification mh(SC_MOD_CHANGEFOLD, LineStart(line), 0, 0, 0);
107 mh.line = line;
108 mh.foldLevelNow = level;
109 mh.foldLevelPrev = prev;
110 NotifyModified(mh);
111 }
112 return prev;
113 }
114
115 static bool IsSubordinate(int levelStart, int levelTry) {
116 if (levelTry & SC_FOLDLEVELWHITEFLAG)
117 return true;
118 else
119 return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
120 }
121
122 int Document::GetLastChild(int lineParent, int level) {
123 if (level == -1)
124 level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
125 int maxLine = LinesTotal();
126 int lineMaxSubord = lineParent;
127 while ((lineMaxSubord < maxLine-1) && IsSubordinate(level, GetLevel(lineMaxSubord+1))) {
128 lineMaxSubord++;
129 }
130 if (lineMaxSubord > lineParent) {
131 if (level > (GetLevel(lineMaxSubord+1) & SC_FOLDLEVELNUMBERMASK)) {
132 // Have chewed up some whitespace that belongs to a parent so seek back
133 if ((lineMaxSubord > lineParent) && (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG)) {
134 lineMaxSubord--;
135 }
136 }
137 }
138 return lineMaxSubord;
139 }
140
141 int Document::GetFoldParent(int line) {
142 int level = GetLevel(line);
143 int lineLook = line-1;
144 while ((lineLook > 0) && (
145 (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
146 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
147 ) {
148 lineLook--;
149 }
150 if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
151 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
152 return lineLook;
153 } else {
154 return -1;
155 }
156 }
157
158 int Document::ClampPositionIntoDocument(int pos) {
159 return Platform::Clamp(pos, 0, Length());
160 }
161
162 bool Document::IsCrLf(int pos) {
163 if (pos < 0)
164 return false;
165 if (pos >= (Length() - 1))
166 return false;
167 return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
168 }
169
170 bool Document::IsDBCS(int pos) {
171 #if PLAT_WIN
172 if (dbcsCodePage) {
173 // Anchor DBCS calculations at start of line because start of line can
174 // not be a DBCS trail byte.
175 int startLine = pos;
176 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
177 startLine--;
178 while (startLine <= pos) {
179 if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) {
180 startLine++;
181 if (startLine >= pos)
182 return true;
183 }
184 startLine++;
185 }
186 }
187 return false;
188 #else
189 return false;
190 #endif
191 }
192
193 // Normalise a position so that it is not halfway through a two byte character.
194 // This can occur in two situations -
195 // When lines are terminated with \r\n pairs which should be treated as one character.
196 // When displaying DBCS text such as Japanese.
197 // If moving, move the position in the indicated direction.
198 int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
199 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
200 // If out of range, just return value - should be fixed up after
201 if (pos < 0)
202 return pos;
203 if (pos > Length())
204 return pos;
205
206 // Position 0 and Length() can not be between any two characters
207 if (pos == 0)
208 return pos;
209 if (pos == Length())
210 return pos;
211
212 // assert pos > 0 && pos < Length()
213 if (checkLineEnd && IsCrLf(pos - 1)) {
214 if (moveDir > 0)
215 return pos + 1;
216 else
217 return pos - 1;
218 }
219
220 // Not between CR and LF
221
222 #if PLAT_WIN
223 if (dbcsCodePage) {
224 // Anchor DBCS calculations at start of line because start of line can
225 // not be a DBCS trail byte.
226 int startLine = pos;
227 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
228 startLine--;
229 bool atLeadByte = false;
230 while (startLine < pos) {
231 if (atLeadByte)
232 atLeadByte = false;
233 else if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine)))
234 atLeadByte = true;
235 else
236 atLeadByte = false;
237 startLine++;
238 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
239 }
240
241 if (atLeadByte) {
242 // Position is between a lead byte and a trail byte
243 if (moveDir > 0)
244 return pos + 1;
245 else
246 return pos - 1;
247 }
248 }
249 #endif
250
251 return pos;
252 }
253
254 void Document::ModifiedAt(int pos) {
255 if (endStyled > pos)
256 endStyled = pos;
257 }
258
259 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
260 // SetStyleAt does not change the persistent state of a document
261
262 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
263 void Document::DeleteChars(int pos, int len) {
264 if (enteredCount == 0) {
265 enteredCount++;
266 if (cb.IsReadOnly())
267 NotifyModifyAttempt();
268 if (!cb.IsReadOnly()) {
269 int prevLinesTotal = LinesTotal();
270 bool startSavePoint = cb.IsSavePoint();
271 const char *text = cb.DeleteChars(pos*2, len * 2);
272 if (startSavePoint && cb.IsCollectingUndo())
273 NotifySavePoint(!startSavePoint);
274 ModifiedAt(pos);
275 int modFlags = SC_MOD_DELETETEXT | SC_PERFORMED_USER;
276 DocModification mh(modFlags, pos, len, LinesTotal() - prevLinesTotal, text);
277 NotifyModified(mh);
278 }
279 enteredCount--;
280 }
281 }
282
283 void Document::InsertStyledString(int position, char *s, int insertLength) {
284 if (enteredCount == 0) {
285 enteredCount++;
286 if (cb.IsReadOnly())
287 NotifyModifyAttempt();
288 if (!cb.IsReadOnly()) {
289 int prevLinesTotal = LinesTotal();
290 bool startSavePoint = cb.IsSavePoint();
291 const char *text = cb.InsertString(position, s, insertLength);
292 if (startSavePoint && cb.IsCollectingUndo())
293 NotifySavePoint(!startSavePoint);
294 ModifiedAt(position / 2);
295
296 int modFlags = SC_MOD_INSERTTEXT | SC_PERFORMED_USER;
297 DocModification mh(modFlags, position / 2, insertLength / 2, LinesTotal() - prevLinesTotal, text);
298 NotifyModified(mh);
299 }
300 enteredCount--;
301 }
302 }
303
304 int Document::Undo() {
305 int newPos = 0;
306 if (enteredCount == 0) {
307 enteredCount++;
308 bool startSavePoint = cb.IsSavePoint();
309 int steps = cb.StartUndo();
310 for (int step=0; step<steps; step++) {
311 int prevLinesTotal = LinesTotal();
312 const Action &action = cb.UndoStep();
313 int cellPosition = action.position / 2;
314 ModifiedAt(cellPosition);
315 newPos = cellPosition;
316
317 int modFlags = SC_PERFORMED_UNDO;
318 // With undo, an insertion action becomes a deletion notification
319 if (action.at == removeAction) {
320 newPos += action.lenData;
321 modFlags |= SC_MOD_INSERTTEXT;
322 } else {
323 modFlags |= SC_MOD_DELETETEXT;
324 }
325 if (step == steps-1)
326 modFlags |= SC_LASTSTEPINUNDOREDO;
327 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
328 LinesTotal() - prevLinesTotal, action.data));
329 }
330
331 bool endSavePoint = cb.IsSavePoint();
332 if (startSavePoint != endSavePoint)
333 NotifySavePoint(endSavePoint);
334 enteredCount--;
335 }
336 return newPos;
337 }
338
339 int Document::Redo() {
340 int newPos = 0;
341 if (enteredCount == 0) {
342 enteredCount++;
343 bool startSavePoint = cb.IsSavePoint();
344 int steps = cb.StartRedo();
345 for (int step=0; step<steps; step++) {
346 int prevLinesTotal = LinesTotal();
347 const Action &action = cb.RedoStep();
348 int cellPosition = action.position / 2;
349 ModifiedAt(cellPosition);
350 newPos = cellPosition;
351
352 int modFlags = SC_PERFORMED_REDO;
353 if (action.at == insertAction) {
354 newPos += action.lenData;
355 modFlags |= SC_MOD_INSERTTEXT;
356 } else {
357 modFlags |= SC_MOD_DELETETEXT;
358 }
359 if (step == steps-1)
360 modFlags |= SC_LASTSTEPINUNDOREDO;
361 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
362 LinesTotal() - prevLinesTotal, action.data));
363 }
364
365 bool endSavePoint = cb.IsSavePoint();
366 if (startSavePoint != endSavePoint)
367 NotifySavePoint(endSavePoint);
368 enteredCount--;
369 }
370 return newPos;
371 }
372
373 void Document::InsertChar(int pos, char ch) {
374 char chs[2];
375 chs[0] = ch;
376 chs[1] = 0;
377 InsertStyledString(pos*2, chs, 2);
378 }
379
380 // Insert a null terminated string
381 void Document::InsertString(int position, const char *s) {
382 InsertString(position, s, strlen(s));
383 }
384
385 // Insert a string with a length
386 void Document::InsertString(int position, const char *s, int insertLength) {
387 char *sWithStyle = new char[insertLength * 2];
388 if (sWithStyle) {
389 for (int i = 0; i < insertLength; i++) {
390 sWithStyle[i*2] = s[i];
391 sWithStyle[i*2 + 1] = 0;
392 }
393 InsertStyledString(position*2, sWithStyle, insertLength*2);
394 delete []sWithStyle;
395 }
396 }
397
398 void Document::DelChar(int pos) {
399 if (IsCrLf(pos)) {
400 DeleteChars(pos, 2);
401 } else if (IsDBCS(pos)) {
402 DeleteChars(pos, 2);
403 } else if (pos < Length()) {
404 DeleteChars(pos, 1);
405 }
406 }
407
408 int Document::DelCharBack(int pos) {
409 if (pos <= 0) {
410 return pos;
411 } else if (IsCrLf(pos - 2)) {
412 DeleteChars(pos - 2, 2);
413 return pos - 2;
414 } else if (IsDBCS(pos - 1)) {
415 DeleteChars(pos - 2, 2);
416 return pos - 2;
417 } else {
418 DeleteChars(pos - 1, 1);
419 return pos - 1;
420 }
421 }
422
423 void Document::Indent(bool forwards, int lineBottom, int lineTop) {
424 if (forwards) {
425 // Indent by a tab
426 for (int line = lineBottom; line >= lineTop; line--) {
427 InsertChar(LineStart(line), '\t');
428 }
429 } else {
430 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
431 for (int line = lineBottom; line >= lineTop; line--) {
432 int ispc = 0;
433 while (ispc < tabInChars && cb.CharAt(LineStart(line) + ispc) == ' ')
434 ispc++;
435 int posStartLine = LineStart(line);
436 if (ispc == tabInChars) {
437 DeleteChars(posStartLine, ispc);
438 } else if (cb.CharAt(posStartLine + ispc) == '\t') {
439 DeleteChars(posStartLine, ispc + 1);
440 } else { // Hit a non-white
441 DeleteChars(posStartLine, ispc);
442 }
443 }
444 }
445 }
446
447 void Document::ConvertLineEnds(int eolModeSet) {
448 BeginUndoAction();
449 for (int pos = 0; pos < Length(); pos++) {
450 if (cb.CharAt(pos) == '\r') {
451 if (cb.CharAt(pos+1) == '\n') {
452 if (eolModeSet != SC_EOL_CRLF) {
453 DeleteChars(pos, 2);
454 if (eolModeSet == SC_EOL_CR)
455 InsertString(pos, "\r", 1);
456 else
457 InsertString(pos, "\n", 1);
458 } else {
459 pos++;
460 }
461 } else {
462 if (eolModeSet != SC_EOL_CR) {
463 DeleteChars(pos, 1);
464 if (eolModeSet == SC_EOL_CRLF) {
465 InsertString(pos, "\r\n", 2);
466 pos++;
467 } else {
468 InsertString(pos, "\n", 1);
469 }
470 }
471 }
472 } else if (cb.CharAt(pos) == '\n') {
473 if (eolModeSet != SC_EOL_LF) {
474 DeleteChars(pos, 1);
475 if (eolModeSet == SC_EOL_CRLF) {
476 InsertString(pos, "\r\n", 2);
477 pos++;
478 } else {
479 InsertString(pos, "\r", 1);
480 }
481 }
482 }
483 }
484 EndUndoAction();
485 }
486
487 bool Document::IsWordChar(unsigned char ch) {
488 return wordchars[ch];
489 }
490
491 int Document::ExtendWordSelect(int pos, int delta) {
492 if (delta < 0) {
493 while (pos > 0 && IsWordChar(cb.CharAt(pos - 1)))
494 pos--;
495 } else {
496 while (pos < (Length()) && IsWordChar(cb.CharAt(pos)))
497 pos++;
498 }
499 return pos;
500 }
501
502 int Document::NextWordStart(int pos, int delta) {
503 if (delta < 0) {
504 while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t'))
505 pos--;
506 if (isspace(cb.CharAt(pos - 1))) { // Back up to previous line
507 while (pos > 0 && isspace(cb.CharAt(pos - 1)))
508 pos--;
509 } else {
510 bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1));
511 while (pos > 0 && !isspace(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1))))
512 pos--;
513 }
514 } else {
515 bool startAtWordChar = IsWordChar(cb.CharAt(pos));
516 while (pos < (Length()) && isspace(cb.CharAt(pos)))
517 pos++;
518 while (pos < (Length()) && !isspace(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos))))
519 pos++;
520 while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t'))
521 pos++;
522 }
523 return pos;
524 }
525
526 bool Document::IsWordAt(int start, int end) {
527 int lengthDoc = Length();
528 if (start > 0) {
529 char ch = CharAt(start - 1);
530 if (IsWordChar(ch))
531 return false;
532 }
533 if (end < lengthDoc - 1) {
534 char ch = CharAt(end);
535 if (IsWordChar(ch))
536 return false;
537 }
538 return true;
539 }
540
541 // Find text in document, supporting both forward and backward
542 // searches (just pass minPos > maxPos to do a backward search)
543 // Has not been tested with backwards DBCS searches yet.
544 long Document::FindText(int minPos, int maxPos, const char *s, bool caseSensitive, bool word) {
545 bool forward = minPos <= maxPos;
546 int increment = forward ? 1 : -1;
547
548 // Range endpoints should not be inside DBCS characters, but just in case, move them.
549 int startPos = MovePositionOutsideChar(minPos, increment, false);
550 int endPos = MovePositionOutsideChar(maxPos, increment, false);
551
552 // Compute actual search ranges needed
553 int lengthFind = strlen(s);
554 int endSearch = 0;
555 if (startPos <= endPos) {
556 endSearch = endPos - lengthFind + 1;
557 } else {
558 endSearch = endPos;
559 }
560 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
561 char firstChar = s[0];
562 if (!caseSensitive)
563 firstChar = toupper(firstChar);
564 int pos = startPos;
565 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
566 char ch = CharAt(pos);
567 if (caseSensitive) {
568 if (ch == firstChar) {
569 bool found = true;
570 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
571 ch = CharAt(pos + posMatch);
572 if (ch != s[posMatch])
573 found = false;
574 }
575 if (found) {
576 if ((!word) || IsWordAt(pos, pos + lengthFind))
577 return pos;
578 }
579 }
580 } else {
581 if (toupper(ch) == firstChar) {
582 bool found = true;
583 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
584 ch = CharAt(pos + posMatch);
585 if (toupper(ch) != toupper(s[posMatch]))
586 found = false;
587 }
588 if (found) {
589 if ((!word) || IsWordAt(pos, pos + lengthFind))
590 return pos;
591 }
592 }
593 }
594 pos += increment;
595 if (dbcsCodePage) {
596 // Ensure trying to match from start of character
597 pos = MovePositionOutsideChar(pos, increment, false);
598 }
599 }
600 //Platform::DebugPrintf("Not found\n");
601 return - 1;
602 }
603
604 int Document::LinesTotal() {
605 return cb.Lines();
606 }
607
608 void Document::SetWordChars(unsigned char *chars) {
609 int ch;
610 for (ch = 0; ch < 256; ch++) {
611 wordchars[ch] = false;
612 }
613 if (chars) {
614 while (*chars) {
615 wordchars[*chars] = true;
616 chars++;
617 }
618 } else {
619 for (ch = 0; ch < 256; ch++) {
620 wordchars[ch] = isalnum(ch) || ch == '_';
621 }
622 }
623 }
624
625 void Document::SetStylingBits(int bits) {
626 stylingBits = bits;
627 stylingBitsMask = 0;
628 for (int bit=0; bit<stylingBits; bit++) {
629 stylingBitsMask <<= 1;
630 stylingBitsMask |= 1;
631 }
632 }
633
634 void Document::StartStyling(int position, char mask) {
635 stylingPos = position;
636 stylingMask = mask;
637 }
638
639 void Document::SetStyleFor(int length, char style) {
640 if (enteredCount == 0) {
641 enteredCount++;
642 int prevEndStyled = endStyled;
643 if (cb.SetStyleFor(stylingPos, length, style, stylingMask)) {
644 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
645 prevEndStyled, length);
646 NotifyModified(mh);
647 }
648 stylingPos += length;
649 endStyled = stylingPos;
650 enteredCount--;
651 }
652 }
653
654 void Document::SetStyles(int length, char *styles) {
655 if (enteredCount == 0) {
656 enteredCount++;
657 int prevEndStyled = endStyled;
658 bool didChange = false;
659 for (int iPos = 0; iPos < length; iPos++, stylingPos++) {
660 if (cb.SetStyleAt(stylingPos, styles[iPos], stylingMask)) {
661 didChange = true;
662 }
663 }
664 endStyled = stylingPos;
665 if (didChange) {
666 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
667 prevEndStyled, endStyled - prevEndStyled);
668 NotifyModified(mh);
669 }
670 enteredCount--;
671 }
672 }
673
674 bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
675 for (int i = 0; i < lenWatchers; i++) {
676 if ((watchers[i].watcher == watcher) &&
677 (watchers[i].userData == userData))
678 return false;
679 }
680 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
681 if (!pwNew)
682 return false;
683 for (int j = 0; j < lenWatchers; j++)
684 pwNew[j] = watchers[j];
685 pwNew[lenWatchers].watcher = watcher;
686 pwNew[lenWatchers].userData = userData;
687 delete []watchers;
688 watchers = pwNew;
689 lenWatchers++;
690 return true;
691 }
692
693 bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
694 for (int i = 0; i < lenWatchers; i++) {
695 if ((watchers[i].watcher == watcher) &&
696 (watchers[i].userData == userData)) {
697 if (lenWatchers == 1) {
698 delete []watchers;
699 watchers = 0;
700 lenWatchers = 0;
701 } else {
702 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
703 if (!pwNew)
704 return false;
705 for (int j = 0; j < lenWatchers - 1; j++) {
706 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
707 }
708 delete []watchers;
709 watchers = pwNew;
710 lenWatchers--;
711 }
712 return true;
713 }
714 }
715 return false;
716 }
717
718 void Document::NotifyModifyAttempt() {
719 for (int i = 0; i < lenWatchers; i++) {
720 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
721 }
722 }
723
724 void Document::NotifySavePoint(bool atSavePoint) {
725 for (int i = 0; i < lenWatchers; i++) {
726 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
727 }
728 }
729
730 void Document::NotifyModified(DocModification mh) {
731 for (int i = 0; i < lenWatchers; i++) {
732 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
733 }
734 }