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