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