1 //
2 // "$Id$"
3 //
4 // Copyright 2001-2010 by Bill Spitzak and others.
5 // Original code Copyright Mark Edel. Permission to distribute under
6 // the LGPL for the FLTK library granted by Mark Edel.
7 //
8 // This library is free software. Distribution and use rights are outlined in
9 // the file "COPYING" which should have been included with this file. If this
10 // file is missing or damaged, see the license at:
11 //
12 // http://www.fltk.org/COPYING.php
13 //
14 // Please report all bugs and problems on the following page:
15 //
16 // http://www.fltk.org/str.php
17 //
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <FL/fl_utf8.h>
22 #include "flstring.h"
23 #include <ctype.h>
24 #include <FL/Fl.H>
25 #include <FL/Fl_Text_Buffer.H>
26 #include <FL/fl_ask.H>
27
28
29 /*
30 This file is based on a port of NEdit to FLTK many years ago. NEdit at that
31 point was already stretched beyond the task it was designed for which explains
32 why the source code is sometimes pretty convoluted. It still is a very useful
33 widget for FLTK, and we are thankful that the nedit team allowed us to
34 integrate their code.
35
36 With the introduction of Unicode and UTF-8, Fl_Text_... has to go into a whole
37 new generation of code. Originally designed for monospaced fonts only, many
38 features make less sense in the multibyte and multiwidth world of UTF-8.
39
40 Columns are a good example. There is simply no such thing. The new Fl_Text_...
41 widget converts columns to pixels by multiplying them with the average
42 character width for a given font.
43
44 Rectangular selections were rarely used (if at all) and make little sense when
45 using variable width fonts. They have been removed.
46
47 Using multiple spaces to emulate tab stops has been replaced by pixel counting
48 routines. They are slower, but give the expected result for proportional fonts.
49
50 And constantly recalculating character widths is just much too expensive. Lines
51 of text are now subdivided into blocks of text which are measured at once
52 instead of individual characters.
53 */
54
55
56 #ifndef min
57
max(int i1,int i2)58 static int max(int i1, int i2)
59 {
60 return i1 >= i2 ? i1 : i2;
61 }
62
min(int i1,int i2)63 static int min(int i1, int i2)
64 {
65 return i1 <= i2 ? i1 : i2;
66 }
67
68 #endif
69
70
71 static char *undobuffer;
72 static int undobufferlength;
73 static Fl_Text_Buffer *undowidget;
74 static int undoat; // points after insertion
75 static int undocut; // number of characters deleted there
76 static int undoinsert; // number of characters inserted
77 static int undoyankcut; // length of valid contents of buffer, even if undocut=0
78
79 /*
80 Resize the undo buffer to match at least the requested size.
81 */
undobuffersize(int n)82 static void undobuffersize(int n)
83 {
84 if (n > undobufferlength) {
85 if (undobuffer) {
86 do {
87 undobufferlength *= 2;
88 } while (undobufferlength < n);
89 undobuffer = (char *) realloc(undobuffer, undobufferlength);
90 } else {
91 undobufferlength = n + 9;
92 undobuffer = (char *) malloc(undobufferlength);
93 }
94 }
95 }
96
def_transcoding_warning_action(Fl_Text_Buffer * text)97 static void def_transcoding_warning_action(Fl_Text_Buffer *text)
98 {
99 fl_alert("%s", text->file_encoding_warning_message);
100 }
101
102 /*
103 Initialize all variables.
104 */
Fl_Text_Buffer(int requestedSize,int preferredGapSize)105 Fl_Text_Buffer::Fl_Text_Buffer(int requestedSize, int preferredGapSize)
106 {
107 mLength = 0;
108 mPreferredGapSize = preferredGapSize;
109 mBuf = (char *) malloc(requestedSize + mPreferredGapSize);
110 mGapStart = 0;
111 mGapEnd = requestedSize + mPreferredGapSize;
112 mTabDist = 8;
113 mPrimary.mSelected = 0;
114 mPrimary.mStart = mPrimary.mEnd = 0;
115 mSecondary.mSelected = 0;
116 mSecondary.mStart = mSecondary.mEnd = 0;
117 mHighlight.mSelected = 0;
118 mHighlight.mStart = mHighlight.mEnd = 0;
119 mModifyProcs = NULL;
120 mCbArgs = NULL;
121 mNModifyProcs = 0;
122 mNPredeleteProcs = 0;
123 mPredeleteProcs = NULL;
124 mPredeleteCbArgs = NULL;
125 mCursorPosHint = 0;
126 mCanUndo = 1;
127 input_file_was_transcoded = 0;
128 transcoding_warning_action = def_transcoding_warning_action;
129 }
130
131
132 /*
133 Free all resources.
134 */
~Fl_Text_Buffer()135 Fl_Text_Buffer::~Fl_Text_Buffer()
136 {
137 free(mBuf);
138 if (mNModifyProcs != 0) {
139 delete[]mModifyProcs;
140 delete[]mCbArgs;
141 }
142 if (mNPredeleteProcs > 0) {
143 delete[] mPredeleteProcs;
144 delete[] mPredeleteCbArgs;
145 }
146 }
147
148
149 /*
150 This function copies verbose whatever is in front and after the gap into a
151 single buffer.
152 */
text() const153 char *Fl_Text_Buffer::text() const {
154 char *t = (char *) malloc(mLength + 1);
155 memcpy(t, mBuf, mGapStart);
156 memcpy(t+mGapStart, mBuf+mGapEnd, mLength - mGapStart);
157 t[mLength] = '\0';
158 return t;
159 }
160
161
162 /*
163 Set the text buffer to a new string.
164 */
text(const char * t)165 void Fl_Text_Buffer::text(const char *t)
166 {
167 IS_UTF8_ALIGNED(t)
168
169 // if t is null then substitute it with an empty string
170 // then don't return so that internal cleanup can happen
171 if (!t) t="";
172
173 call_predelete_callbacks(0, length());
174
175 /* Save information for redisplay, and get rid of the old buffer */
176 const char *deletedText = text();
177 int deletedLength = mLength;
178 free((void *) mBuf);
179
180 /* Start a new buffer with a gap of mPreferredGapSize at the end */
181 int insertedLength = (int) strlen(t);
182 mBuf = (char *) malloc(insertedLength + mPreferredGapSize);
183 mLength = insertedLength;
184 mGapStart = insertedLength;
185 mGapEnd = mGapStart + mPreferredGapSize;
186 memcpy(mBuf, t, insertedLength);
187
188 /* Zero all of the existing selections */
189 update_selections(0, deletedLength, 0);
190
191 /* Call the saved display routine(s) to update the screen */
192 call_modify_callbacks(0, deletedLength, insertedLength, 0, deletedText);
193 free((void *) deletedText);
194 }
195
196
197 /*
198 Creates a range of text to a new buffer and copies verbose from around the gap.
199 */
text_range(int start,int end) const200 char *Fl_Text_Buffer::text_range(int start, int end) const {
201 IS_UTF8_ALIGNED2(this, (start))
202 IS_UTF8_ALIGNED2(this, (end))
203
204 char *s = NULL;
205
206 /* Make sure start and end are ok, and allocate memory for returned string.
207 If start is bad, return "", if end is bad, adjust it. */
208 if (start < 0 || start > mLength)
209 {
210 s = (char *) malloc(1);
211 s[0] = '\0';
212 return s;
213 }
214 if (end < start) {
215 int temp = start;
216 start = end;
217 end = temp;
218 }
219 if (end > mLength)
220 end = mLength;
221 int copiedLength = end - start;
222 s = (char *) malloc(copiedLength + 1);
223
224 /* Copy the text from the buffer to the returned string */
225 if (end <= mGapStart) {
226 memcpy(s, mBuf + start, copiedLength);
227 } else if (start >= mGapStart) {
228 memcpy(s, mBuf + start + (mGapEnd - mGapStart), copiedLength);
229 } else {
230 int part1Length = mGapStart - start;
231 memcpy(s, mBuf + start, part1Length);
232 memcpy(s + part1Length, mBuf + mGapEnd, copiedLength - part1Length);
233 }
234 s[copiedLength] = '\0';
235 return s;
236 }
237
238 /*
239 Return a UCS-4 character at the given index.
240 Pos must be at a character boundary.
241 */
char_at(int pos) const242 unsigned int Fl_Text_Buffer::char_at(int pos) const {
243 if (pos < 0 || pos >= mLength)
244 return '\0';
245
246 IS_UTF8_ALIGNED2(this, (pos))
247
248 const char *src = address(pos);
249 return fl_utf8decode(src, 0, 0);
250 }
251
252
253 /*
254 Return the raw byte at the given index.
255 This function ignores all unicode encoding.
256 */
byte_at(int pos) const257 char Fl_Text_Buffer::byte_at(int pos) const {
258 if (pos < 0 || pos >= mLength)
259 return '\0';
260 const char *src = address(pos);
261 return *src;
262 }
263
264
265 /*
266 Insert some text at the given index.
267 Pos must be at a character boundary.
268 */
insert(int pos,const char * text)269 void Fl_Text_Buffer::insert(int pos, const char *text)
270 {
271 IS_UTF8_ALIGNED2(this, (pos))
272 IS_UTF8_ALIGNED(text)
273
274 /* check if there is actually any text */
275 if (!text || !*text)
276 return;
277
278 /* if pos is not contiguous to existing text, make it */
279 if (pos > mLength)
280 pos = mLength;
281 if (pos < 0)
282 pos = 0;
283
284 /* Even if nothing is deleted, we must call these callbacks */
285 call_predelete_callbacks(pos, 0);
286
287 /* insert and redisplay */
288 int nInserted = insert_(pos, text);
289 mCursorPosHint = pos + nInserted;
290 IS_UTF8_ALIGNED2(this, (mCursorPosHint))
291 call_modify_callbacks(pos, 0, nInserted, 0, NULL);
292 }
293
294
295 /*
296 Replace a range of text with new text.
297 Start and end must be at a character boundary.
298 */
replace(int start,int end,const char * text)299 void Fl_Text_Buffer::replace(int start, int end, const char *text)
300 {
301 // Range check...
302 if (!text)
303 return;
304 if (start < 0)
305 start = 0;
306 if (end > mLength)
307 end = mLength;
308
309 IS_UTF8_ALIGNED2(this, (start))
310 IS_UTF8_ALIGNED2(this, (end))
311 IS_UTF8_ALIGNED(text)
312
313 call_predelete_callbacks(start, end - start);
314 const char *deletedText = text_range(start, end);
315 remove_(start, end);
316 int nInserted = insert_(start, text);
317 mCursorPosHint = start + nInserted;
318 call_modify_callbacks(start, end - start, nInserted, 0, deletedText);
319 free((void *) deletedText);
320 }
321
322
323 /*
324 Remove a range of text.
325 Start and End must be at a character boundary.
326 */
remove(int start,int end)327 void Fl_Text_Buffer::remove(int start, int end)
328 {
329 /* Make sure the arguments make sense */
330 if (start > end) {
331 int temp = start;
332 start = end;
333 end = temp;
334 }
335 if (start > mLength)
336 start = mLength;
337 if (start < 0)
338 start = 0;
339 if (end > mLength)
340 end = mLength;
341 if (end < 0)
342 end = 0;
343
344 IS_UTF8_ALIGNED2(this, (start))
345 IS_UTF8_ALIGNED2(this, (end))
346
347 if (start == end)
348 return;
349
350 call_predelete_callbacks(start, end - start);
351 /* Remove and redisplay */
352 const char *deletedText = text_range(start, end);
353 remove_(start, end);
354 mCursorPosHint = start;
355 call_modify_callbacks(start, end - start, 0, 0, deletedText);
356 free((void *) deletedText);
357 }
358
359
360 /*
361 Copy a range of text from another text buffer.
362 fromStart, fromEnd, and toPos must be at a character boundary.
363 */
copy(Fl_Text_Buffer * fromBuf,int fromStart,int fromEnd,int toPos)364 void Fl_Text_Buffer::copy(Fl_Text_Buffer * fromBuf, int fromStart,
365 int fromEnd, int toPos)
366 {
367 IS_UTF8_ALIGNED2(fromBuf, fromStart)
368 IS_UTF8_ALIGNED2(fromBuf, fromEnd)
369 IS_UTF8_ALIGNED2(this, (toPos))
370
371 int copiedLength = fromEnd - fromStart;
372
373 /* Prepare the buffer to receive the new text. If the new text fits in
374 the current buffer, just move the gap (if necessary) to where
375 the text should be inserted. If the new text is too large, reallocate
376 the buffer with a gap large enough to accomodate the new text and a
377 gap of mPreferredGapSize */
378 if (copiedLength > mGapEnd - mGapStart)
379 reallocate_with_gap(toPos, copiedLength + mPreferredGapSize);
380 else if (toPos != mGapStart)
381 move_gap(toPos);
382
383 /* Insert the new text (toPos now corresponds to the start of the gap) */
384 if (fromEnd <= fromBuf->mGapStart) {
385 memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], copiedLength);
386 } else if (fromStart >= fromBuf->mGapStart) {
387 memcpy(&mBuf[toPos],
388 &fromBuf->mBuf[fromStart + (fromBuf->mGapEnd - fromBuf->mGapStart)],
389 copiedLength);
390 } else {
391 int part1Length = fromBuf->mGapStart - fromStart;
392 memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], part1Length);
393 memcpy(&mBuf[toPos + part1Length],
394 &fromBuf->mBuf[fromBuf->mGapEnd], copiedLength - part1Length);
395 }
396 mGapStart += copiedLength;
397 mLength += copiedLength;
398 update_selections(toPos, 0, copiedLength);
399 }
400
401
402 /*
403 Take the previous changes and undo them. Return the previous
404 cursor position in cursorPos. Returns 1 if the undo was applied.
405 CursorPos will be at a character boundary.
406 */
undo(int * cursorPos)407 int Fl_Text_Buffer::undo(int *cursorPos)
408 {
409 if (undowidget != this || (!undocut && !undoinsert && !mCanUndo))
410 return 0;
411
412 int ilen = undocut;
413 int xlen = undoinsert;
414 int b = undoat - xlen;
415
416 if (xlen && undoyankcut && !ilen) {
417 ilen = undoyankcut;
418 }
419
420 if (xlen && ilen) {
421 undobuffersize(ilen + 1);
422 undobuffer[ilen] = 0;
423 char *tmp = strdup(undobuffer);
424 replace(b, undoat, tmp);
425 if (cursorPos)
426 *cursorPos = mCursorPosHint;
427 free(tmp);
428 } else if (xlen) {
429 remove(b, undoat);
430 if (cursorPos)
431 *cursorPos = mCursorPosHint;
432 } else if (ilen) {
433 undobuffersize(ilen + 1);
434 undobuffer[ilen] = 0;
435 insert(undoat, undobuffer);
436 if (cursorPos)
437 *cursorPos = mCursorPosHint;
438 undoyankcut = 0;
439 }
440
441 return 1;
442 }
443
444
445 /*
446 Set a flag if undo function will work.
447 */
canUndo(char flag)448 void Fl_Text_Buffer::canUndo(char flag)
449 {
450 mCanUndo = flag;
451 // disabling undo also clears the last undo operation!
452 if (!mCanUndo && undowidget==this)
453 undowidget = 0;
454 }
455
456
457 /*
458 Change the tab width. This will cause a couple of callbacks and a complete
459 redisplay.
460 Matt: I am not entirely sure why we need to trigger callbacks because
461 tabs are only a graphical hint, not changing any text at all, but I leave
462 this in here for back compatibility.
463 */
tab_distance(int tabDist)464 void Fl_Text_Buffer::tab_distance(int tabDist)
465 {
466 /* First call the pre-delete callbacks with the previous tab setting
467 still active. */
468 call_predelete_callbacks(0, mLength);
469
470 /* Change the tab setting */
471 mTabDist = tabDist;
472
473 /* Force any display routines to redisplay everything (unfortunately,
474 this means copying the whole buffer contents to provide "deletedText" */
475 const char *deletedText = text();
476 call_modify_callbacks(0, mLength, mLength, 0, deletedText);
477 free((void *) deletedText);
478 }
479
480
481 /*
482 Select a range of text.
483 Start and End must be at a character boundary.
484 */
select(int start,int end)485 void Fl_Text_Buffer::select(int start, int end)
486 {
487 IS_UTF8_ALIGNED2(this, (start))
488 IS_UTF8_ALIGNED2(this, (end))
489
490 Fl_Text_Selection oldSelection = mPrimary;
491
492 mPrimary.set(start, end);
493 redisplay_selection(&oldSelection, &mPrimary);
494 }
495
496
497 /*
498 Clear the primary selection.
499 */
unselect()500 void Fl_Text_Buffer::unselect()
501 {
502 Fl_Text_Selection oldSelection = mPrimary;
503
504 mPrimary.mSelected = 0;
505 redisplay_selection(&oldSelection, &mPrimary);
506 }
507
508
509 /*
510 Return the primary selection range.
511 */
selection_position(int * start,int * end)512 int Fl_Text_Buffer::selection_position(int *start, int *end)
513 {
514 return mPrimary.position(start, end);
515 }
516
517
518 /*
519 Return a copy of the selected text.
520 */
selection_text()521 char *Fl_Text_Buffer::selection_text()
522 {
523 return selection_text_(&mPrimary);
524 }
525
526
527 /*
528 Remove the selected text.
529 */
remove_selection()530 void Fl_Text_Buffer::remove_selection()
531 {
532 remove_selection_(&mPrimary);
533 }
534
535
536 /*
537 Replace the selected text.
538 */
replace_selection(const char * text)539 void Fl_Text_Buffer::replace_selection(const char *text)
540 {
541 replace_selection_(&mPrimary, text);
542 }
543
544
545 /*
546 Select text.
547 Start and End must be at a character boundary.
548 */
secondary_select(int start,int end)549 void Fl_Text_Buffer::secondary_select(int start, int end)
550 {
551 Fl_Text_Selection oldSelection = mSecondary;
552
553 mSecondary.set(start, end);
554 redisplay_selection(&oldSelection, &mSecondary);
555 }
556
557
558 /*
559 Deselect text.
560 */
secondary_unselect()561 void Fl_Text_Buffer::secondary_unselect()
562 {
563 Fl_Text_Selection oldSelection = mSecondary;
564
565 mSecondary.mSelected = 0;
566 redisplay_selection(&oldSelection, &mSecondary);
567 }
568
569
570 /*
571 Return the selected range.
572 */
secondary_selection_position(int * start,int * end)573 int Fl_Text_Buffer::secondary_selection_position(int *start, int *end)
574 {
575 return mSecondary.position(start, end);
576 }
577
578
579 /*
580 Return a copy of the text in this selection.
581 */
secondary_selection_text()582 char *Fl_Text_Buffer::secondary_selection_text()
583 {
584 return selection_text_(&mSecondary);
585 }
586
587
588 /*
589 Remove the selected text.
590 */
remove_secondary_selection()591 void Fl_Text_Buffer::remove_secondary_selection()
592 {
593 remove_selection_(&mSecondary);
594 }
595
596
597 /*
598 Replace selected text.
599 */
replace_secondary_selection(const char * text)600 void Fl_Text_Buffer::replace_secondary_selection(const char *text)
601 {
602 replace_selection_(&mSecondary, text);
603 }
604
605
606 /*
607 Highlight a range of text.
608 Start and End must be at a character boundary.
609 */
highlight(int start,int end)610 void Fl_Text_Buffer::highlight(int start, int end)
611 {
612 Fl_Text_Selection oldSelection = mHighlight;
613
614 mHighlight.set(start, end);
615 redisplay_selection(&oldSelection, &mHighlight);
616 }
617
618
619 /*
620 Remove text highlighting.
621 */
unhighlight()622 void Fl_Text_Buffer::unhighlight()
623 {
624 Fl_Text_Selection oldSelection = mHighlight;
625
626 mHighlight.mSelected = 0;
627 redisplay_selection(&oldSelection, &mHighlight);
628 }
629
630
631 /*
632 Return position of highlight.
633 */
highlight_position(int * start,int * end)634 int Fl_Text_Buffer::highlight_position(int *start, int *end)
635 {
636 return mHighlight.position(start, end);
637 }
638
639
640 /*
641 Return a copy of highlighted text.
642 */
highlight_text()643 char *Fl_Text_Buffer::highlight_text()
644 {
645 return selection_text_(&mHighlight);
646 }
647
648
649 /*
650 Add a callback that is called whenever text is modified.
651 */
add_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,void * cbArg)652 void Fl_Text_Buffer::add_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,
653 void *cbArg)
654 {
655 Fl_Text_Modify_Cb *newModifyProcs =
656 new Fl_Text_Modify_Cb[mNModifyProcs + 1];
657 void **newCBArgs = new void *[mNModifyProcs + 1];
658 for (int i = 0; i < mNModifyProcs; i++) {
659 newModifyProcs[i + 1] = mModifyProcs[i];
660 newCBArgs[i + 1] = mCbArgs[i];
661 }
662 if (mNModifyProcs != 0) {
663 delete[]mModifyProcs;
664 delete[]mCbArgs;
665 }
666 newModifyProcs[0] = bufModifiedCB;
667 newCBArgs[0] = cbArg;
668 mNModifyProcs++;
669 mModifyProcs = newModifyProcs;
670 mCbArgs = newCBArgs;
671 }
672
673
674 /*
675 Remove a callback.
676 */
remove_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,void * cbArg)677 void Fl_Text_Buffer::remove_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,
678 void *cbArg)
679 {
680 int i, toRemove = -1;
681
682 /* find the matching callback to remove */
683 for (i = 0; i < mNModifyProcs; i++) {
684 if (mModifyProcs[i] == bufModifiedCB && mCbArgs[i] == cbArg) {
685 toRemove = i;
686 break;
687 }
688 }
689 if (toRemove == -1) {
690 Fl::error
691 ("Fl_Text_Buffer::remove_modify_callback(): Can't find modify CB to remove");
692 return;
693 }
694
695 /* Allocate new lists for remaining callback procs and args (if
696 any are left) */
697 mNModifyProcs--;
698 if (mNModifyProcs == 0) {
699 mNModifyProcs = 0;
700 delete[]mModifyProcs;
701 mModifyProcs = NULL;
702 delete[]mCbArgs;
703 mCbArgs = NULL;
704 return;
705 }
706 Fl_Text_Modify_Cb *newModifyProcs = new Fl_Text_Modify_Cb[mNModifyProcs];
707 void **newCBArgs = new void *[mNModifyProcs];
708
709 /* copy out the remaining members and free the old lists */
710 for (i = 0; i < toRemove; i++) {
711 newModifyProcs[i] = mModifyProcs[i];
712 newCBArgs[i] = mCbArgs[i];
713 }
714 for (; i < mNModifyProcs; i++) {
715 newModifyProcs[i] = mModifyProcs[i + 1];
716 newCBArgs[i] = mCbArgs[i + 1];
717 }
718 delete[]mModifyProcs;
719 delete[]mCbArgs;
720 mModifyProcs = newModifyProcs;
721 mCbArgs = newCBArgs;
722 }
723
724
725 /*
726 Add a callback that is called before deleting text.
727 */
add_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB,void * cbArg)728 void Fl_Text_Buffer::add_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB,
729 void *cbArg)
730 {
731 Fl_Text_Predelete_Cb *newPreDeleteProcs =
732 new Fl_Text_Predelete_Cb[mNPredeleteProcs + 1];
733 void **newCBArgs = new void *[mNPredeleteProcs + 1];
734 for (int i = 0; i < mNPredeleteProcs; i++) {
735 newPreDeleteProcs[i + 1] = mPredeleteProcs[i];
736 newCBArgs[i + 1] = mPredeleteCbArgs[i];
737 }
738 if (mNPredeleteProcs > 0) {
739 delete[] mPredeleteProcs;
740 delete[] mPredeleteCbArgs;
741 }
742 newPreDeleteProcs[0] = bufPreDeleteCB;
743 newCBArgs[0] = cbArg;
744 mNPredeleteProcs++;
745 mPredeleteProcs = newPreDeleteProcs;
746 mPredeleteCbArgs = newCBArgs;
747 }
748
749
750 /*
751 Remove a callback.
752 */
remove_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB,void * cbArg)753 void Fl_Text_Buffer::remove_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB, void *cbArg)
754 {
755 int i, toRemove = -1;
756 /* find the matching callback to remove */
757 for (i = 0; i < mNPredeleteProcs; i++) {
758 if (mPredeleteProcs[i] == bufPreDeleteCB &&
759 mPredeleteCbArgs[i] == cbArg) {
760 toRemove = i;
761 break;
762 }
763 }
764 if (toRemove == -1) {
765 Fl::error
766 ("Fl_Text_Buffer::remove_predelete_callback(): Can't find pre-delete CB to remove");
767 return;
768 }
769
770 /* Allocate new lists for remaining callback procs and args (if any are left) */
771 mNPredeleteProcs--;
772 if (mNPredeleteProcs == 0) {
773 delete[]mPredeleteProcs;
774 mPredeleteProcs = NULL;
775 delete[]mPredeleteCbArgs;
776 mPredeleteCbArgs = NULL;
777 return;
778 }
779 Fl_Text_Predelete_Cb *newPreDeleteProcs = new Fl_Text_Predelete_Cb[mNPredeleteProcs];
780 void **newCBArgs = new void *[mNPredeleteProcs];
781
782 /* copy out the remaining members and free the old lists */
783 for (i = 0; i < toRemove; i++) {
784 newPreDeleteProcs[i] = mPredeleteProcs[i];
785 newCBArgs[i] = mPredeleteCbArgs[i];
786 }
787 for (; i < mNPredeleteProcs; i++) {
788 newPreDeleteProcs[i] = mPredeleteProcs[i + 1];
789 newCBArgs[i] = mPredeleteCbArgs[i + 1];
790 }
791 delete[] mPredeleteProcs;
792 delete[] mPredeleteCbArgs;
793 mPredeleteProcs = newPreDeleteProcs;
794 mPredeleteCbArgs = newCBArgs;
795 }
796
797
798 /*
799 Return a copy of the line that contains a given index.
800 Pos must be at a character boundary.
801 */
line_text(int pos) const802 char *Fl_Text_Buffer::line_text(int pos) const {
803 return text_range(line_start(pos), line_end(pos));
804 }
805
806
807 /*
808 Find the beginning of the line.
809 */
line_start(int pos) const810 int Fl_Text_Buffer::line_start(int pos) const
811 {
812 if (!findchar_backward(pos, '\n', &pos))
813 return 0;
814 return pos + 1;
815 }
816
817
818 /*
819 Find the end of the line.
820 */
line_end(int pos) const821 int Fl_Text_Buffer::line_end(int pos) const {
822 if (!findchar_forward(pos, '\n', &pos))
823 pos = mLength;
824 return pos;
825 }
826
827
828 /*
829 Find the beginning of a word.
830 NOT UNICODE SAFE.
831 */
word_start(int pos) const832 int Fl_Text_Buffer::word_start(int pos) const {
833 // FIXME: character is ucs-4
834 while (pos>0 && (isalnum(char_at(pos)) || char_at(pos) == '_')) {
835 pos = prev_char(pos);
836 }
837 // FIXME: character is ucs-4
838 if (!(isalnum(char_at(pos)) || char_at(pos) == '_'))
839 pos = next_char(pos);
840 return pos;
841 }
842
843
844 /*
845 Find the end of a word.
846 NOT UNICODE SAFE.
847 */
word_end(int pos) const848 int Fl_Text_Buffer::word_end(int pos) const {
849 // FIXME: character is ucs-4
850 while (pos < length() && (isalnum(char_at(pos)) || char_at(pos) == '_'))
851 {
852 pos = next_char(pos);
853 }
854 return pos;
855 }
856
857
858 /*
859 Count the number of characters between two positions.
860 */
count_displayed_characters(int lineStartPos,int targetPos) const861 int Fl_Text_Buffer::count_displayed_characters(int lineStartPos,
862 int targetPos) const
863 {
864 IS_UTF8_ALIGNED2(this, (lineStartPos))
865 IS_UTF8_ALIGNED2(this, (targetPos))
866
867 int charCount = 0;
868
869 int pos = lineStartPos;
870 while (pos < targetPos) {
871 pos = next_char(pos);
872 charCount++;
873 }
874 return charCount;
875 }
876
877
878 /*
879 Skip ahead a number of characters from a given index.
880 This function breaks early if it encounters a newline character.
881 */
skip_displayed_characters(int lineStartPos,int nChars)882 int Fl_Text_Buffer::skip_displayed_characters(int lineStartPos, int nChars)
883 {
884 IS_UTF8_ALIGNED2(this, (lineStartPos))
885
886 int pos = lineStartPos;
887
888 for (int charCount = 0; charCount < nChars && pos < mLength; charCount++) {
889 unsigned int c = char_at(pos);
890 if (c == '\n')
891 return pos;
892 pos = next_char(pos);
893 }
894 return pos;
895 }
896
897
898 /*
899 Count the number of newline characters between start and end.
900 startPos and endPos must be at a character boundary.
901 This function is optimized for speed by not using UTF-8 calls.
902 */
count_lines(int startPos,int endPos) const903 int Fl_Text_Buffer::count_lines(int startPos, int endPos) const {
904 IS_UTF8_ALIGNED2(this, (startPos))
905 IS_UTF8_ALIGNED2(this, (endPos))
906
907 int gapLen = mGapEnd - mGapStart;
908 int lineCount = 0;
909
910 int pos = startPos;
911 while (pos < mGapStart)
912 {
913 if (pos == endPos)
914 return lineCount;
915 if (mBuf[pos++] == '\n')
916 lineCount++;
917 }
918 while (pos < mLength) {
919 if (pos == endPos)
920 return lineCount;
921 if (mBuf[pos++ + gapLen] == '\n')
922 lineCount++;
923 }
924 return lineCount;
925 }
926
927
928 /*
929 Skip to the first character, n lines ahead.
930 StartPos must be at a character boundary.
931 This function is optimized for speed by not using UTF-8 calls.
932 */
skip_lines(int startPos,int nLines)933 int Fl_Text_Buffer::skip_lines(int startPos, int nLines)
934 {
935 IS_UTF8_ALIGNED2(this, (startPos))
936
937 if (nLines == 0)
938 return startPos;
939
940 int gapLen = mGapEnd - mGapStart;
941 int pos = startPos;
942 int lineCount = 0;
943 while (pos < mGapStart) {
944 if (mBuf[pos++] == '\n') {
945 lineCount++;
946 if (lineCount == nLines) {
947 IS_UTF8_ALIGNED2(this, (pos))
948 return pos;
949 }
950 }
951 }
952 while (pos < mLength) {
953 if (mBuf[pos++ + gapLen] == '\n') {
954 lineCount++;
955 if (lineCount >= nLines) {
956 IS_UTF8_ALIGNED2(this, (pos))
957 return pos;
958 }
959 }
960 }
961 IS_UTF8_ALIGNED2(this, (pos))
962 return pos;
963 }
964
965
966 /*
967 Skip to the first character, n lines back.
968 StartPos must be at a character boundary.
969 This function is optimized for speed by not using UTF-8 calls.
970 */
rewind_lines(int startPos,int nLines)971 int Fl_Text_Buffer::rewind_lines(int startPos, int nLines)
972 {
973 IS_UTF8_ALIGNED2(this, (startPos))
974
975 int pos = startPos - 1;
976 if (pos <= 0)
977 return 0;
978
979 int gapLen = mGapEnd - mGapStart;
980 int lineCount = -1;
981 while (pos >= mGapStart) {
982 if (mBuf[pos + gapLen] == '\n') {
983 if (++lineCount >= nLines) {
984 IS_UTF8_ALIGNED2(this, (pos+1))
985 return pos + 1;
986 }
987 }
988 pos--;
989 }
990 while (pos >= 0) {
991 if (mBuf[pos] == '\n') {
992 if (++lineCount >= nLines) {
993 IS_UTF8_ALIGNED2(this, (pos+1))
994 return pos + 1;
995 }
996 }
997 pos--;
998 }
999 return 0;
1000 }
1001
1002
1003 /*
1004 Find a matching string in the buffer.
1005 */
search_forward(int startPos,const char * searchString,int * foundPos,int matchCase) const1006 int Fl_Text_Buffer::search_forward(int startPos, const char *searchString,
1007 int *foundPos, int matchCase) const
1008 {
1009 IS_UTF8_ALIGNED2(this, (startPos))
1010 IS_UTF8_ALIGNED(searchString)
1011
1012 if (!searchString)
1013 return 0;
1014 int bp;
1015 const char *sp;
1016 if (matchCase) {
1017 while (startPos < length()) {
1018 bp = startPos;
1019 sp = searchString;
1020 for (;;) {
1021 char c = *sp;
1022 // we reached the end of the "needle", so we found the string!
1023 if (!c) {
1024 *foundPos = startPos;
1025 return 1;
1026 }
1027 int l = fl_utf8len1(c);
1028 if (memcmp(sp, address(bp), l))
1029 break;
1030 sp += l; bp += l;
1031 }
1032 startPos = next_char(startPos);
1033 }
1034 } else {
1035 while (startPos < length()) {
1036 bp = startPos;
1037 sp = searchString;
1038 for (;;) {
1039 // we reached the end of the "needle", so we found the string!
1040 if (!*sp) {
1041 *foundPos = startPos;
1042 return 1;
1043 }
1044 int l;
1045 unsigned int b = char_at(bp);
1046 unsigned int s = fl_utf8decode(sp, 0, &l);
1047 if (fl_tolower(b)!=fl_tolower(s))
1048 break;
1049 sp += l;
1050 bp = next_char(bp);
1051 }
1052 startPos = next_char(startPos);
1053 }
1054 }
1055 return 0;
1056 }
1057
search_backward(int startPos,const char * searchString,int * foundPos,int matchCase) const1058 int Fl_Text_Buffer::search_backward(int startPos, const char *searchString,
1059 int *foundPos, int matchCase) const
1060 {
1061 IS_UTF8_ALIGNED2(this, (startPos))
1062 IS_UTF8_ALIGNED(searchString)
1063
1064 if (!searchString)
1065 return 0;
1066 int bp;
1067 const char *sp;
1068 if (matchCase) {
1069 while (startPos >= 0) {
1070 bp = startPos;
1071 sp = searchString;
1072 for (;;) {
1073 char c = *sp;
1074 // we reached the end of the "needle", so we found the string!
1075 if (!c) {
1076 *foundPos = startPos;
1077 return 1;
1078 }
1079 int l = fl_utf8len1(c);
1080 if (memcmp(sp, address(bp), l))
1081 break;
1082 sp += l; bp += l;
1083 }
1084 startPos = prev_char(startPos);
1085 }
1086 } else {
1087 while (startPos >= 0) {
1088 bp = startPos;
1089 sp = searchString;
1090 for (;;) {
1091 // we reached the end of the "needle", so we found the string!
1092 if (!*sp) {
1093 *foundPos = startPos;
1094 return 1;
1095 }
1096 int l;
1097 unsigned int b = char_at(bp);
1098 unsigned int s = fl_utf8decode(sp, 0, &l);
1099 if (fl_tolower(b)!=fl_tolower(s))
1100 break;
1101 sp += l;
1102 bp = next_char(bp);
1103 }
1104 startPos = prev_char(startPos);
1105 }
1106 }
1107 return 0;
1108 }
1109
1110
1111
1112 /*
1113 Insert a string into the buffer.
1114 Pos must be at a character boundary. Text must be a correct UTF-8 string.
1115 */
insert_(int pos,const char * text)1116 int Fl_Text_Buffer::insert_(int pos, const char *text)
1117 {
1118 if (!text || !*text)
1119 return 0;
1120
1121 int insertedLength = (int) strlen(text);
1122
1123 /* Prepare the buffer to receive the new text. If the new text fits in
1124 the current buffer, just move the gap (if necessary) to where
1125 the text should be inserted. If the new text is too large, reallocate
1126 the buffer with a gap large enough to accomodate the new text and a
1127 gap of mPreferredGapSize */
1128 if (insertedLength > mGapEnd - mGapStart)
1129 reallocate_with_gap(pos, insertedLength + mPreferredGapSize);
1130 else if (pos != mGapStart)
1131 move_gap(pos);
1132
1133 /* Insert the new text (pos now corresponds to the start of the gap) */
1134 memcpy(&mBuf[pos], text, insertedLength);
1135 mGapStart += insertedLength;
1136 mLength += insertedLength;
1137 update_selections(pos, 0, insertedLength);
1138
1139 if (mCanUndo) {
1140 if (undowidget == this && undoat == pos && undoinsert) {
1141 undoinsert += insertedLength;
1142 } else {
1143 undoinsert = insertedLength;
1144 undoyankcut = (undoat == pos) ? undocut : 0;
1145 }
1146 undoat = pos + insertedLength;
1147 undocut = 0;
1148 undowidget = this;
1149 }
1150
1151 return insertedLength;
1152 }
1153
1154
1155 /*
1156 Remove a string from the buffer.
1157 Unicode safe. Start and end must be at a character boundary.
1158 */
remove_(int start,int end)1159 void Fl_Text_Buffer::remove_(int start, int end)
1160 {
1161 /* if the gap is not contiguous to the area to remove, move it there */
1162
1163 if (mCanUndo) {
1164 if (undowidget == this && undoat == end && undocut) {
1165 undobuffersize(undocut + end - start + 1);
1166 memmove(undobuffer + end - start, undobuffer, undocut);
1167 undocut += end - start;
1168 } else {
1169 undocut = end - start;
1170 undobuffersize(undocut);
1171 }
1172 undoat = start;
1173 undoinsert = 0;
1174 undoyankcut = 0;
1175 undowidget = this;
1176 }
1177
1178 if (start > mGapStart) {
1179 if (mCanUndo)
1180 memcpy(undobuffer, mBuf + (mGapEnd - mGapStart) + start,
1181 end - start);
1182 move_gap(start);
1183 } else if (end < mGapStart) {
1184 if (mCanUndo)
1185 memcpy(undobuffer, mBuf + start, end - start);
1186 move_gap(end);
1187 } else {
1188 int prelen = mGapStart - start;
1189 if (mCanUndo) {
1190 memcpy(undobuffer, mBuf + start, prelen);
1191 memcpy(undobuffer + prelen, mBuf + mGapEnd, end - start - prelen);
1192 }
1193 }
1194
1195 /* expand the gap to encompass the deleted characters */
1196 mGapEnd += end - mGapStart;
1197 mGapStart -= mGapStart - start;
1198
1199 /* update the length */
1200 mLength -= end - start;
1201
1202 /* fix up any selections which might be affected by the change */
1203 update_selections(start, end - start, 0);
1204 }
1205
1206
1207 /*
1208 simple setter.
1209 Unicode safe. Start and end must be at a character boundary.
1210 */
set(int startpos,int endpos)1211 void Fl_Text_Selection::set(int startpos, int endpos)
1212 {
1213 mSelected = startpos != endpos;
1214 mStart = min(startpos, endpos);
1215 mEnd = max(startpos, endpos);
1216 }
1217
1218
1219 /*
1220 simple getter.
1221 Unicode safe. Start and end will be at a character boundary.
1222 */
position(int * startpos,int * endpos) const1223 int Fl_Text_Selection::position(int *startpos, int *endpos) const {
1224 if (!mSelected)
1225 return 0;
1226 *startpos = mStart;
1227 *endpos = mEnd;
1228
1229 return 1;
1230 }
1231
1232
1233 /*
1234 Return if a position is inside the selected area.
1235 Unicode safe. Pos must be at a character boundary.
1236 */
includes(int pos) const1237 int Fl_Text_Selection::includes(int pos) const {
1238 return (selected() && pos >= start() && pos < end() );
1239 }
1240
1241
1242 /*
1243 Return a duplicate of the selected text, or an empty string.
1244 Unicode safe.
1245 */
selection_text_(Fl_Text_Selection * sel) const1246 char *Fl_Text_Buffer::selection_text_(Fl_Text_Selection * sel) const {
1247 int start, end;
1248
1249 /* If there's no selection, return an allocated empty string */
1250 if (!sel->position(&start, &end))
1251 {
1252 char *s = (char *) malloc(1);
1253 *s = '\0';
1254 return s;
1255 }
1256
1257 /* Return the selected range */
1258 return text_range(start, end);
1259 }
1260
1261
1262 /*
1263 Remove the selected text.
1264 Unicode safe.
1265 */
remove_selection_(Fl_Text_Selection * sel)1266 void Fl_Text_Buffer::remove_selection_(Fl_Text_Selection * sel)
1267 {
1268 int start, end;
1269
1270 if (!sel->position(&start, &end))
1271 return;
1272 remove(start, end);
1273 //undoyankcut = undocut;
1274 }
1275
1276
1277 /*
1278 Replace selection with text.
1279 Unicode safe.
1280 */
replace_selection_(Fl_Text_Selection * sel,const char * text)1281 void Fl_Text_Buffer::replace_selection_(Fl_Text_Selection * sel,
1282 const char *text)
1283 {
1284 Fl_Text_Selection oldSelection = *sel;
1285
1286 /* If there's no selection, return */
1287 int start, end;
1288 if (!sel->position(&start, &end))
1289 return;
1290
1291 /* Do the appropriate type of replace */
1292 replace(start, end, text);
1293
1294 /* Unselect (happens automatically in BufReplace, but BufReplaceRect
1295 can't detect when the contents of a selection goes away) */
1296 sel->mSelected = 0;
1297 redisplay_selection(&oldSelection, sel);
1298 }
1299
1300
1301 /*
1302 Call all callbacks.
1303 Unicode safe.
1304 */
call_modify_callbacks(int pos,int nDeleted,int nInserted,int nRestyled,const char * deletedText) const1305 void Fl_Text_Buffer::call_modify_callbacks(int pos, int nDeleted,
1306 int nInserted, int nRestyled,
1307 const char *deletedText) const {
1308 IS_UTF8_ALIGNED2(this, pos)
1309 for (int i = 0; i < mNModifyProcs; i++)
1310 (*mModifyProcs[i]) (pos, nInserted, nDeleted, nRestyled,
1311 deletedText, mCbArgs[i]);
1312 }
1313
1314
1315 /*
1316 Call all callbacks.
1317 Unicode safe.
1318 */
call_predelete_callbacks(int pos,int nDeleted) const1319 void Fl_Text_Buffer::call_predelete_callbacks(int pos, int nDeleted) const {
1320 for (int i = 0; i < mNPredeleteProcs; i++)
1321 (*mPredeleteProcs[i]) (pos, nDeleted, mPredeleteCbArgs[i]);
1322 }
1323
1324
1325 /*
1326 Redisplay a new selected area.
1327 Unicode safe.
1328 */
redisplay_selection(Fl_Text_Selection * oldSelection,Fl_Text_Selection * newSelection) const1329 void Fl_Text_Buffer::redisplay_selection(Fl_Text_Selection *
1330 oldSelection,
1331 Fl_Text_Selection *
1332 newSelection) const
1333 {
1334 int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start,
1335 ch2End;
1336
1337 /* If either selection is rectangular, add an additional character to
1338 the end of the selection to request the redraw routines to wipe out
1339 the parts of the selection beyond the end of the line */
1340 oldStart = oldSelection->mStart;
1341 newStart = newSelection->mStart;
1342 oldEnd = oldSelection->mEnd;
1343 newEnd = newSelection->mEnd;
1344
1345 /* If the old or new selection is unselected, just redisplay the
1346 single area that is (was) selected and return */
1347 if (!oldSelection->mSelected && !newSelection->mSelected)
1348 return;
1349 if (!oldSelection->mSelected)
1350 {
1351 call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
1352 return;
1353 }
1354 if (!newSelection->mSelected) {
1355 call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
1356 return;
1357 }
1358
1359 /* If the selections are non-contiguous, do two separate updates
1360 and return */
1361 if (oldEnd < newStart || newEnd < oldStart) {
1362 call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
1363 call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
1364 return;
1365 }
1366
1367 /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
1368 changed areas), and the unchanged area of their intersection,
1369 and update only the changed area(s) */
1370 ch1Start = min(oldStart, newStart);
1371 ch2End = max(oldEnd, newEnd);
1372 ch1End = max(oldStart, newStart);
1373 ch2Start = min(oldEnd, newEnd);
1374 if (ch1Start != ch1End)
1375 call_modify_callbacks(ch1Start, 0, 0, ch1End - ch1Start, NULL);
1376 if (ch2Start != ch2End)
1377 call_modify_callbacks(ch2Start, 0, 0, ch2End - ch2Start, NULL);
1378 }
1379
1380
1381 /*
1382 Move the gap around without changing buffer content.
1383 Unicode safe. Pos must be at a character boundary.
1384 */
move_gap(int pos)1385 void Fl_Text_Buffer::move_gap(int pos)
1386 {
1387 int gapLen = mGapEnd - mGapStart;
1388
1389 if (pos > mGapStart)
1390 memmove(&mBuf[mGapStart], &mBuf[mGapEnd], pos - mGapStart);
1391 else
1392 memmove(&mBuf[pos + gapLen], &mBuf[pos], mGapStart - pos);
1393 mGapEnd += pos - mGapStart;
1394 mGapStart += pos - mGapStart;
1395 }
1396
1397
1398 /*
1399 Create a larger gap.
1400 Unicode safe. Start must be at a character boundary.
1401 */
reallocate_with_gap(int newGapStart,int newGapLen)1402 void Fl_Text_Buffer::reallocate_with_gap(int newGapStart, int newGapLen)
1403 {
1404 char *newBuf = (char *) malloc(mLength + newGapLen);
1405 int newGapEnd = newGapStart + newGapLen;
1406
1407 if (newGapStart <= mGapStart) {
1408 memcpy(newBuf, mBuf, newGapStart);
1409 memcpy(&newBuf[newGapEnd], &mBuf[newGapStart],
1410 mGapStart - newGapStart);
1411 memcpy(&newBuf[newGapEnd + mGapStart - newGapStart],
1412 &mBuf[mGapEnd], mLength - mGapStart);
1413 } else { /* newGapStart > mGapStart */
1414 memcpy(newBuf, mBuf, mGapStart);
1415 memcpy(&newBuf[mGapStart], &mBuf[mGapEnd], newGapStart - mGapStart);
1416 memcpy(&newBuf[newGapEnd],
1417 &mBuf[mGapEnd + newGapStart - mGapStart],
1418 mLength - newGapStart);
1419 }
1420 free((void *) mBuf);
1421 mBuf = newBuf;
1422 mGapStart = newGapStart;
1423 mGapEnd = newGapEnd;
1424 }
1425
1426
1427 /*
1428 Update selection range if characters were inserted.
1429 Unicode safe. Pos must be at a character boundary.
1430 */
update_selections(int pos,int nDeleted,int nInserted)1431 void Fl_Text_Buffer::update_selections(int pos, int nDeleted,
1432 int nInserted)
1433 {
1434 mPrimary.update(pos, nDeleted, nInserted);
1435 mSecondary.update(pos, nDeleted, nInserted);
1436 mHighlight.update(pos, nDeleted, nInserted);
1437 }
1438
1439
1440 // unicode safe, assuming the arguments are on character boundaries
update(int pos,int nDeleted,int nInserted)1441 void Fl_Text_Selection::update(int pos, int nDeleted, int nInserted)
1442 {
1443 if (!mSelected || pos > mEnd)
1444 return;
1445 if (pos + nDeleted <= mStart) {
1446 mStart += nInserted - nDeleted;
1447 mEnd += nInserted - nDeleted;
1448 } else if (pos <= mStart && pos + nDeleted >= mEnd) {
1449 mStart = pos;
1450 mEnd = pos;
1451 mSelected = 0;
1452 } else if (pos <= mStart && pos + nDeleted < mEnd) {
1453 mStart = pos;
1454 mEnd = nInserted + mEnd - nDeleted;
1455 } else if (pos < mEnd) {
1456 mEnd += nInserted - nDeleted;
1457 if (mEnd <= mStart)
1458 mSelected = 0;
1459 }
1460 }
1461
1462
1463 /*
1464 Find a UCS-4 character.
1465 StartPos must be at a character boundary, searchChar is UCS-4 encoded.
1466 */
findchar_forward(int startPos,unsigned searchChar,int * foundPos) const1467 int Fl_Text_Buffer::findchar_forward(int startPos, unsigned searchChar,
1468 int *foundPos) const
1469 {
1470 if (startPos >= mLength) {
1471 *foundPos = mLength;
1472 return 0;
1473 }
1474
1475 if (startPos<0)
1476 startPos = 0;
1477
1478 for ( ; startPos<mLength; startPos = next_char(startPos)) {
1479 if (searchChar == char_at(startPos)) {
1480 *foundPos = startPos;
1481 return 1;
1482 }
1483 }
1484
1485 *foundPos = mLength;
1486 return 0;
1487 }
1488
1489
1490 /*
1491 Find a UCS-4 character.
1492 StartPos must be at a character boundary, searchChar is UCS-4 encoded.
1493 */
findchar_backward(int startPos,unsigned int searchChar,int * foundPos) const1494 int Fl_Text_Buffer::findchar_backward(int startPos, unsigned int searchChar,
1495 int *foundPos) const {
1496 if (startPos <= 0) {
1497 *foundPos = 0;
1498 return 0;
1499 }
1500
1501 if (startPos > mLength)
1502 startPos = mLength;
1503
1504 for (startPos = prev_char(startPos); startPos>=0; startPos = prev_char(startPos)) {
1505 if (searchChar == char_at(startPos)) {
1506 *foundPos = startPos;
1507 return 1;
1508 }
1509 }
1510
1511 *foundPos = 0;
1512 return 0;
1513 }
1514
1515 //#define EXAMPLE_ENCODING // shows how to process any encoding for which a decoding function exists
1516 #ifdef EXAMPLE_ENCODING
1517
1518 // returns the UCS equivalent of *p in CP1252 and advances p by 1
cp1252toucs(char * & p)1519 unsigned cp1252toucs(char* &p)
1520 {
1521 // Codes 0x80..0x9f from the Microsoft CP1252 character set, translated
1522 // to Unicode
1523 static unsigned cp1252[32] = {
1524 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
1525 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
1526 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
1527 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178
1528 };
1529 unsigned char uc = *(unsigned char*)p;
1530 p++;
1531 return (uc < 0x80 || uc >= 0xa0 ? uc : cp1252[uc - 0x80]);
1532 }
1533
1534 // returns the UCS equivalent of *p in UTF-16 and advances p by 2 (or more for surrogates)
utf16toucs(char * & p)1535 unsigned utf16toucs(char* &p)
1536 {
1537 union {
1538 #if WORDS_BIGENDIAN
1539 struct { unsigned char a, b;} chars;
1540 #else
1541 struct { unsigned char b, a;} chars;
1542 #endif
1543 U16 short_val;
1544 } u;
1545 u.chars.a = *(unsigned char*)p++;
1546 u.chars.b = *(unsigned char*)p++;
1547 return u.short_val;
1548 }
1549
1550 // filter that produces, from an input stream fed by reading from fp,
1551 // a UTF-8-encoded output stream written in buffer.
1552 // Input can be any (e.g., 8-bit, UTF-16) encoding.
1553 // Output is true UTF-8.
1554 // p_trf points to a function that transforms encoded byte(s) into one UCS
1555 // and that increases the pointer by the adequate quantity
general_input_filter(char * buffer,int buflen,char * line,int sline,char * & endline,unsigned (* p_trf)(char * &),FILE * fp)1556 static int general_input_filter(char *buffer, int buflen,
1557 char *line, int sline, char* &endline,
1558 unsigned (*p_trf)(char* &),
1559 FILE *fp)
1560 {
1561 char *p, *q, multibyte[5];
1562 int lq, r, offset;
1563 p = line;
1564 q = buffer;
1565 while (q < buffer + buflen) {
1566 if (p >= endline) {
1567 r = fread(line, 1, sline, fp);
1568 endline = line + r;
1569 if (r == 0) return q - buffer;
1570 p = line;
1571 }
1572 if (q + 4 /*max width of utf-8 char*/ > buffer + buflen) {
1573 memmove(line, p, endline - p);
1574 endline -= (p - line);
1575 return q - buffer;
1576 }
1577 lq = fl_utf8encode( p_trf(p), multibyte );
1578 memcpy(q, multibyte, lq);
1579 q += lq;
1580 }
1581 memmove(line, p, endline - p);
1582 endline -= (p - line);
1583 return q - buffer;
1584 }
1585 #endif // EXAMPLE_ENCODING
1586
1587 /*
1588 filter that produces, from an input stream fed by reading from fp,
1589 a UTF-8-encoded output stream written in buffer.
1590 Returns #bytes read into 'buffer'.
1591
1592 Input can be UTF-8. If it is not, it is decoded with CP1252.
1593 Output is UTF-8.
1594
1595 *input_was_changed returns true if input was not strict UTF-8, so output
1596 differs from input.
1597 */
utf8_input_filter(char * buffer,int buflen,char * line,int sline,char * & endline,FILE * fp,int * input_was_changed)1598 static int utf8_input_filter(char *buffer, // result buffer we fill with utf8 encoded text
1599 int buflen, // max size of buffer from caller
1600 char *line, // file line buffer caller wants us to use
1601 int sline, // max size of line buffer
1602 char* &endline, // keeps track of leftovers in line[] buffer between calls
1603 FILE *fp, // open file we're reading data from
1604 int *input_was_changed) // returned flag: 'true' if buffer[] different from file due to utf8 encoding
1605 {
1606 // p - work pointer to line[]
1607 // q - work pointer to buffer[]
1608 // l - length of utf8 sequence being worked on
1609 // lp - fl_utf8decode() length of utf8 sequence being worked on
1610 // lq - fl_utf8encode() length of utf8 sequence being worked on
1611 // r - bytes read from last fread()
1612 // u - utf8 decoded sequence as a single multibyte unsigned integer
1613 char *p, *q, multibyte[5];
1614 int l, lp, lq, r;
1615 unsigned u;
1616 p = line;
1617 q = buffer;
1618 while (q < buffer + buflen) {
1619 if (p >= endline) { // walked off end of input file's line buffer?
1620 r = (int) fread(line, 1, sline, fp); // read another block of sline bytes from file
1621 endline = line + r;
1622 if (r == 0) return (int) (q - buffer); // EOF? return bytes read into buffer[]
1623 p = line;
1624 }
1625 // Predict length of utf8 sequence
1626 // See if utf8 seq we're working on would extend off end of line buffer,
1627 // and if so, adjust + load more data so that it doesn't.
1628 //
1629 l = fl_utf8len1(*p); // anticipate length of utf8 sequence
1630 if (p + l > endline) { // would walk off end of line buffer?
1631 memmove(line, p, endline - p); // re-jigger line buffer to get some room
1632 endline -= (p - line);
1633 r = (int) fread(endline, 1, sline - (endline - line), fp); // re-fill line buffer
1634 endline += r;
1635 p = line;
1636 if (endline - line < l) break; // sequence *still* extends past end? stop loop
1637 }
1638 while ( l > 0) {
1639 u = fl_utf8decode(p, p+l, &lp); // get single utf8 encoded char as a Unicode value
1640 lq = fl_utf8encode(u, multibyte); // re-encode Unicode value to utf8 in multibyte[]
1641 if (lp != l || lq != l) *input_was_changed = true;
1642
1643 if (q + lq > buffer + buflen) { // encoding would walk off end of buffer[]?
1644 memmove(line, p, endline - p); // re-jigger line[] buffer for next call
1645 endline -= (p - line); // adjust end of line[] buffer for next call
1646 return (int) (q - buffer); // return what's decoded so far, caller will consume buffer
1647 }
1648 memcpy(q, multibyte, lq);
1649 q += lq;
1650 p += lp;
1651 l -= lp;
1652 }
1653 }
1654 memmove(line, p, endline - p);
1655 endline -= (p - line);
1656 return (int) (q - buffer);
1657 }
1658
1659 const char *Fl_Text_Buffer::file_encoding_warning_message =
1660 "Displayed text contains the UTF-8 transcoding\n"
1661 "of the input file which was not UTF-8 encoded.\n"
1662 "Some changes may have occurred.";
1663
1664 /*
1665 Insert text from a file.
1666 Input file can be of various encodings according to what input fiter is used.
1667 utf8_input_filter accepts UTF-8 or CP1252 as input encoding.
1668 Output is always UTF-8.
1669 */
insertfile(const char * file,int pos,int buflen)1670 int Fl_Text_Buffer::insertfile(const char *file, int pos, int buflen)
1671 {
1672 FILE *fp;
1673 if (!(fp = fl_fopen(file, "r")))
1674 return 1;
1675 char *buffer = new char[buflen + 1];
1676 char *endline, line[100];
1677 int l;
1678 input_file_was_transcoded = false;
1679 endline = line;
1680 while (true) {
1681 #ifdef EXAMPLE_ENCODING
1682 // example of 16-bit encoding: UTF-16
1683 l = general_input_filter(buffer, buflen,
1684 line, sizeof(line), endline,
1685 utf16toucs, // use cp1252toucs to read CP1252-encoded files
1686 fp);
1687 input_file_was_transcoded = true;
1688 #else
1689 l = utf8_input_filter(buffer, buflen, line, sizeof(line), endline,
1690 fp, &input_file_was_transcoded);
1691 #endif
1692 if (l == 0) break;
1693 buffer[l] = 0;
1694 insert(pos, buffer);
1695 pos += l;
1696 }
1697 int e = ferror(fp) ? 2 : 0;
1698 fclose(fp);
1699 delete[]buffer;
1700 if ( (!e) && input_file_was_transcoded && transcoding_warning_action) {
1701 transcoding_warning_action(this);
1702 }
1703 return e;
1704 }
1705
1706
1707 /*
1708 Write text to file.
1709 Unicode safe.
1710 */
outputfile(const char * file,int start,int end,int buflen)1711 int Fl_Text_Buffer::outputfile(const char *file,
1712 int start, int end,
1713 int buflen) {
1714 FILE *fp;
1715 if (!(fp = fl_fopen(file, "w")))
1716 return 1;
1717 for (int n; (n = min(end - start, buflen)); start += n) {
1718 const char *p = text_range(start, start + n);
1719 int r = (int) fwrite(p, 1, n, fp);
1720 free((void *) p);
1721 if (r != n)
1722 break;
1723 }
1724
1725 int e = ferror(fp) ? 2 : 0;
1726 fclose(fp);
1727 return e;
1728 }
1729
1730
1731 /*
1732 Return the previous character position.
1733 Unicode safe.
1734 */
prev_char_clipped(int pos) const1735 int Fl_Text_Buffer::prev_char_clipped(int pos) const
1736 {
1737 if (pos<=0)
1738 return 0;
1739
1740 IS_UTF8_ALIGNED2(this, (pos))
1741
1742 char c;
1743 do {
1744 pos--;
1745 if (pos==0)
1746 return 0;
1747 c = byte_at(pos);
1748 } while ( (c&0xc0) == 0x80);
1749
1750 IS_UTF8_ALIGNED2(this, (pos))
1751 return pos;
1752 }
1753
1754
1755 /*
1756 Return the previous character position.
1757 Returns -1 if the beginning of the buffer is reached.
1758 */
prev_char(int pos) const1759 int Fl_Text_Buffer::prev_char(int pos) const
1760 {
1761 if (pos==0) return -1;
1762 return prev_char_clipped(pos);
1763 }
1764
1765
1766 /*
1767 Return the next character position.
1768 Returns length() if the end of the buffer is reached.
1769 */
next_char(int pos) const1770 int Fl_Text_Buffer::next_char(int pos) const
1771 {
1772 IS_UTF8_ALIGNED2(this, (pos))
1773 int n = fl_utf8len1(byte_at(pos));
1774 pos += n;
1775 if (pos>=mLength)
1776 return mLength;
1777 IS_UTF8_ALIGNED2(this, (pos))
1778 return pos;
1779 }
1780
1781
1782 /*
1783 Return the next character position.
1784 If the end of the buffer is reached, it returns the current position.
1785 */
next_char_clipped(int pos) const1786 int Fl_Text_Buffer::next_char_clipped(int pos) const
1787 {
1788 return next_char(pos);
1789 }
1790
1791 /*
1792 Align an index to the current UTF-8 boundary.
1793 */
utf8_align(int pos) const1794 int Fl_Text_Buffer::utf8_align(int pos) const
1795 {
1796 char c = byte_at(pos);
1797 while ( (c&0xc0) == 0x80) {
1798 pos--;
1799 c = byte_at(pos);
1800 }
1801 return pos;
1802 }
1803
1804 //
1805 // End of "$Id$".
1806 //
1807