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