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