e5fc1560b0
Part 1, more to come ... git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@7936 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
1639 lines
38 KiB
C++
1639 lines
38 KiB
C++
//
|
|
// "$Id$"
|
|
//
|
|
// Copyright 2001-2010 by Bill Spitzak and others.
|
|
// Original code Copyright Mark Edel. Permission to distribute under
|
|
// the LGPL for the FLTK library granted by Mark Edel.
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Library General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Library General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Library General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
// USA.
|
|
//
|
|
// Please report all bugs and problems on the following page:
|
|
//
|
|
// http://www.fltk.org/str.php
|
|
//
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <FL/fl_utf8.h>
|
|
#include "flstring.h"
|
|
#include <ctype.h>
|
|
#include <FL/Fl.H>
|
|
#include <FL/Fl_Text_Buffer.H>
|
|
|
|
|
|
/*
|
|
This file is based on a port of NEdit to FLTK many years ago. NEdit at that
|
|
point was already stretched beyond the task it was designed for which explains
|
|
why the source code is sometimes pretty convoluted. It still is a very useful
|
|
widget for FLTK, and we are thankful that the nedit team allowed us to
|
|
integrate their code.
|
|
|
|
With the introduction of Unicode and UTF-8, Fl_Text_... has to go into a whole
|
|
new generation of code. Originally designed for monospaced fonts only, many
|
|
features make less sense in the multibyte and multiwidth world of UTF-8.
|
|
|
|
Columns are a good example. There is simply no such thing. The new Fl_Text_...
|
|
widget converts columns to pixels by multiplying them with the average
|
|
character width for a given font.
|
|
|
|
Rectangular selections were rarely used (if at all) and make little sense when
|
|
using variable width fonts. They have been removed.
|
|
|
|
Using multiple spaces to emulate tab stops has been replaced by pixel counting
|
|
routines. They are slower, but give the expected result for proportional fonts.
|
|
|
|
And constantly recalculating character widths is just much too expensive. Lines
|
|
of text are now subdivided into blocks of text which are measured at once
|
|
instead of individual characters.
|
|
*/
|
|
|
|
|
|
#ifndef min
|
|
|
|
static int max(int i1, int i2)
|
|
{
|
|
return i1 >= i2 ? i1 : i2;
|
|
}
|
|
|
|
static int min(int i1, int i2)
|
|
{
|
|
return i1 <= i2 ? i1 : i2;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static char *undobuffer;
|
|
static int undobufferlength;
|
|
static Fl_Text_Buffer *undowidget;
|
|
static int undoat; // points after insertion
|
|
static int undocut; // number of characters deleted there
|
|
static int undoinsert; // number of characters inserted
|
|
static int undoyankcut; // length of valid contents of buffer, even if undocut=0
|
|
|
|
/*
|
|
Resize the undo buffer to match at least the requested size.
|
|
*/
|
|
static void undobuffersize(int n)
|
|
{
|
|
if (n > undobufferlength) {
|
|
if (undobuffer) {
|
|
do {
|
|
undobufferlength *= 2;
|
|
} while (undobufferlength < n);
|
|
undobuffer = (char *) realloc(undobuffer, undobufferlength);
|
|
} else {
|
|
undobufferlength = n + 9;
|
|
undobuffer = (char *) malloc(undobufferlength);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Initialize all variables.
|
|
*/
|
|
Fl_Text_Buffer::Fl_Text_Buffer(int requestedSize, int preferredGapSize)
|
|
{
|
|
mLength = 0;
|
|
mPreferredGapSize = preferredGapSize;
|
|
mBuf = (char *) malloc(requestedSize + mPreferredGapSize);
|
|
mGapStart = 0;
|
|
mGapEnd = mPreferredGapSize;
|
|
mTabDist = 8;
|
|
mPrimary.mSelected = 0;
|
|
mPrimary.mStart = mPrimary.mEnd = 0;
|
|
mSecondary.mSelected = 0;
|
|
mSecondary.mStart = mSecondary.mEnd = 0;
|
|
mHighlight.mSelected = 0;
|
|
mHighlight.mStart = mHighlight.mEnd = 0;
|
|
mModifyProcs = NULL;
|
|
mCbArgs = NULL;
|
|
mNModifyProcs = 0;
|
|
mNPredeleteProcs = 0;
|
|
mPredeleteProcs = NULL;
|
|
mPredeleteCbArgs = NULL;
|
|
mCursorPosHint = 0;
|
|
mCanUndo = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
Free all resources.
|
|
*/
|
|
Fl_Text_Buffer::~Fl_Text_Buffer()
|
|
{
|
|
free(mBuf);
|
|
if (mNModifyProcs != 0) {
|
|
delete[]mModifyProcs;
|
|
delete[]mCbArgs;
|
|
}
|
|
if (mNPredeleteProcs != 0) {
|
|
delete[]mPredeleteProcs;
|
|
delete[]mPredeleteCbArgs;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
This function copies verbose whatever is in front and after the gap into a
|
|
single buffer.
|
|
*/
|
|
char *Fl_Text_Buffer::text() const {
|
|
char *t = (char *) malloc(mLength + 1);
|
|
memcpy(t, mBuf, mGapStart);
|
|
memcpy(t+mGapStart, mBuf+mGapEnd, mLength - mGapStart);
|
|
t[mLength] = '\0';
|
|
return t;
|
|
}
|
|
|
|
|
|
/*
|
|
Set the text buffer to a new string.
|
|
*/
|
|
void Fl_Text_Buffer::text(const char *t)
|
|
{
|
|
IS_UTF8_ALIGNED(t)
|
|
|
|
call_predelete_callbacks(0, length());
|
|
|
|
/* Save information for redisplay, and get rid of the old buffer */
|
|
const char *deletedText = text();
|
|
int deletedLength = mLength;
|
|
free((void *) mBuf);
|
|
|
|
/* Start a new buffer with a gap of mPreferredGapSize at the end */
|
|
int insertedLength = strlen(t);
|
|
mBuf = (char *) malloc(insertedLength + mPreferredGapSize);
|
|
mLength = insertedLength;
|
|
mGapStart = insertedLength;
|
|
mGapEnd = mGapStart + mPreferredGapSize;
|
|
memcpy(mBuf, t, insertedLength);
|
|
|
|
/* Zero all of the existing selections */
|
|
update_selections(0, deletedLength, 0);
|
|
|
|
/* Call the saved display routine(s) to update the screen */
|
|
call_modify_callbacks(0, deletedLength, insertedLength, 0, deletedText);
|
|
free((void *) deletedText);
|
|
}
|
|
|
|
|
|
/*
|
|
Creates a range of text to a new buffer and copies verbose from around the gap.
|
|
*/
|
|
char *Fl_Text_Buffer::text_range(int start, int end) const {
|
|
IS_UTF8_ALIGNED2(this, (start))
|
|
IS_UTF8_ALIGNED2(this, (end))
|
|
|
|
char *s = NULL;
|
|
|
|
/* Make sure start and end are ok, and allocate memory for returned string.
|
|
If start is bad, return "", if end is bad, adjust it. */
|
|
if (start < 0 || start > mLength)
|
|
{
|
|
s = (char *) malloc(1);
|
|
s[0] = '\0';
|
|
return s;
|
|
}
|
|
if (end < start) {
|
|
int temp = start;
|
|
start = end;
|
|
end = temp;
|
|
}
|
|
if (end > mLength)
|
|
end = mLength;
|
|
int copiedLength = end - start;
|
|
s = (char *) malloc(copiedLength + 1);
|
|
|
|
/* Copy the text from the buffer to the returned string */
|
|
if (end <= mGapStart) {
|
|
memcpy(s, mBuf + start, copiedLength);
|
|
} else if (start >= mGapStart) {
|
|
memcpy(s, mBuf + start + (mGapEnd - mGapStart), copiedLength);
|
|
} else {
|
|
int part1Length = mGapStart - start;
|
|
memcpy(s, mBuf + start, part1Length);
|
|
memcpy(s + part1Length, mBuf + mGapEnd, copiedLength - part1Length);
|
|
}
|
|
s[copiedLength] = '\0';
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
Return a UCS-4 character at the given index.
|
|
Pos must be at a character boundary.
|
|
*/
|
|
unsigned int Fl_Text_Buffer::char_at(int pos) const {
|
|
if (pos < 0 || pos >= mLength)
|
|
return '\0';
|
|
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
|
|
const char *src = address(pos);
|
|
return fl_utf8decode(src, 0, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
Return the raw byte at the given index.
|
|
This function ignores all unicode encoding.
|
|
*/
|
|
char Fl_Text_Buffer::byte_at(int pos) const {
|
|
if (pos < 0 || pos >= mLength)
|
|
return '\0';
|
|
const char *src = address(pos);
|
|
return *src;
|
|
}
|
|
|
|
|
|
/*
|
|
Insert some text at the given index.
|
|
Pos must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::insert(int pos, const char *text)
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
IS_UTF8_ALIGNED(text)
|
|
|
|
/* check if there is actually any text */
|
|
if (!text || !*text)
|
|
return;
|
|
|
|
/* if pos is not contiguous to existing text, make it */
|
|
if (pos > mLength)
|
|
pos = mLength;
|
|
if (pos < 0)
|
|
pos = 0;
|
|
|
|
/* Even if nothing is deleted, we must call these callbacks */
|
|
call_predelete_callbacks(pos, 0);
|
|
|
|
/* insert and redisplay */
|
|
int nInserted = insert_(pos, text);
|
|
mCursorPosHint = pos + nInserted;
|
|
IS_UTF8_ALIGNED2(this, (mCursorPosHint))
|
|
call_modify_callbacks(pos, 0, nInserted, 0, NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
Replace a range of text with new text.
|
|
Start and end must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::replace(int start, int end, const char *text)
|
|
{
|
|
// Range check...
|
|
if (!text)
|
|
return;
|
|
if (start < 0)
|
|
start = 0;
|
|
if (end > mLength)
|
|
end = mLength;
|
|
|
|
IS_UTF8_ALIGNED2(this, (start))
|
|
IS_UTF8_ALIGNED2(this, (end))
|
|
IS_UTF8_ALIGNED(text)
|
|
|
|
call_predelete_callbacks(start, end - start);
|
|
const char *deletedText = text_range(start, end);
|
|
remove_(start, end);
|
|
int nInserted = insert_(start, text);
|
|
mCursorPosHint = start + nInserted;
|
|
call_modify_callbacks(start, end - start, nInserted, 0, deletedText);
|
|
free((void *) deletedText);
|
|
}
|
|
|
|
|
|
/*
|
|
Remove a range of text.
|
|
Start and End must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::remove(int start, int end)
|
|
{
|
|
/* Make sure the arguments make sense */
|
|
if (start > end) {
|
|
int temp = start;
|
|
start = end;
|
|
end = temp;
|
|
}
|
|
if (start > mLength)
|
|
start = mLength;
|
|
if (start < 0)
|
|
start = 0;
|
|
if (end > mLength)
|
|
end = mLength;
|
|
if (end < 0)
|
|
end = 0;
|
|
|
|
IS_UTF8_ALIGNED2(this, (start))
|
|
IS_UTF8_ALIGNED2(this, (end))
|
|
|
|
if (start == end)
|
|
return;
|
|
|
|
call_predelete_callbacks(start, end - start);
|
|
/* Remove and redisplay */
|
|
const char *deletedText = text_range(start, end);
|
|
remove_(start, end);
|
|
mCursorPosHint = start;
|
|
call_modify_callbacks(start, end - start, 0, 0, deletedText);
|
|
free((void *) deletedText);
|
|
}
|
|
|
|
|
|
/*
|
|
Copy a range of text from another text buffer.
|
|
fromStart, fromEnd, and toPos must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::copy(Fl_Text_Buffer * fromBuf, int fromStart,
|
|
int fromEnd, int toPos)
|
|
{
|
|
IS_UTF8_ALIGNED2(fromBuf, fromStart)
|
|
IS_UTF8_ALIGNED2(fromBuf, fromEnd)
|
|
IS_UTF8_ALIGNED2(this, (toPos))
|
|
|
|
int copiedLength = fromEnd - fromStart;
|
|
|
|
/* Prepare the buffer to receive the new text. If the new text fits in
|
|
the current buffer, just move the gap (if necessary) to where
|
|
the text should be inserted. If the new text is too large, reallocate
|
|
the buffer with a gap large enough to accomodate the new text and a
|
|
gap of mPreferredGapSize */
|
|
if (copiedLength > mGapEnd - mGapStart)
|
|
reallocate_with_gap(toPos, copiedLength + mPreferredGapSize);
|
|
else if (toPos != mGapStart)
|
|
move_gap(toPos);
|
|
|
|
/* Insert the new text (toPos now corresponds to the start of the gap) */
|
|
if (fromEnd <= fromBuf->mGapStart) {
|
|
memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], copiedLength);
|
|
} else if (fromStart >= fromBuf->mGapStart) {
|
|
memcpy(&mBuf[toPos],
|
|
&fromBuf->mBuf[fromStart + (fromBuf->mGapEnd - fromBuf->mGapStart)],
|
|
copiedLength);
|
|
} else {
|
|
int part1Length = fromBuf->mGapStart - fromStart;
|
|
memcpy(&mBuf[toPos], &fromBuf->mBuf[fromStart], part1Length);
|
|
memcpy(&mBuf[toPos + part1Length],
|
|
&fromBuf->mBuf[fromBuf->mGapEnd], copiedLength - part1Length);
|
|
}
|
|
mGapStart += copiedLength;
|
|
mLength += copiedLength;
|
|
update_selections(toPos, 0, copiedLength);
|
|
}
|
|
|
|
|
|
/*
|
|
Take the previous changes and undo them. Return the previous
|
|
cursor position in cursorPos. Returns 1 if the undo was applied.
|
|
CursorPos will be at a character boundary.
|
|
*/
|
|
int Fl_Text_Buffer::undo(int *cursorPos)
|
|
{
|
|
if (undowidget != this || (!undocut && !undoinsert && !mCanUndo))
|
|
return 0;
|
|
|
|
int ilen = undocut;
|
|
int xlen = undoinsert;
|
|
int b = undoat - xlen;
|
|
|
|
if (xlen && undoyankcut && !ilen) {
|
|
ilen = undoyankcut;
|
|
}
|
|
|
|
if (xlen && ilen) {
|
|
undobuffersize(ilen + 1);
|
|
undobuffer[ilen] = 0;
|
|
char *tmp = strdup(undobuffer);
|
|
replace(b, undoat, tmp);
|
|
if (cursorPos)
|
|
*cursorPos = mCursorPosHint;
|
|
free(tmp);
|
|
} else if (xlen) {
|
|
remove(b, undoat);
|
|
if (cursorPos)
|
|
*cursorPos = mCursorPosHint;
|
|
} else if (ilen) {
|
|
undobuffersize(ilen + 1);
|
|
undobuffer[ilen] = 0;
|
|
insert(undoat, undobuffer);
|
|
if (cursorPos)
|
|
*cursorPos = mCursorPosHint;
|
|
undoyankcut = 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
Set a flag if undo function will work.
|
|
*/
|
|
void Fl_Text_Buffer::canUndo(char flag)
|
|
{
|
|
mCanUndo = flag;
|
|
// disabling undo also clears the last undo operation!
|
|
if (!mCanUndo && undowidget==this)
|
|
undowidget = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
Change the tab width. This will cause a couple of callbacks and a complete
|
|
redisplay.
|
|
Matt: I am not entirely sure why we need to trigger callbacks because
|
|
tabs are only a graphical hint, not changing any text at all, but I leave
|
|
this in here for back compatibility.
|
|
*/
|
|
void Fl_Text_Buffer::tab_distance(int tabDist)
|
|
{
|
|
/* First call the pre-delete callbacks with the previous tab setting
|
|
still active. */
|
|
call_predelete_callbacks(0, mLength);
|
|
|
|
/* Change the tab setting */
|
|
mTabDist = tabDist;
|
|
|
|
/* Force any display routines to redisplay everything (unfortunately,
|
|
this means copying the whole buffer contents to provide "deletedText" */
|
|
const char *deletedText = text();
|
|
call_modify_callbacks(0, mLength, mLength, 0, deletedText);
|
|
free((void *) deletedText);
|
|
}
|
|
|
|
|
|
/*
|
|
Select a range of text.
|
|
Start and End must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::select(int start, int end)
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (start))
|
|
IS_UTF8_ALIGNED2(this, (end))
|
|
|
|
Fl_Text_Selection oldSelection = mPrimary;
|
|
|
|
mPrimary.set(start, end);
|
|
redisplay_selection(&oldSelection, &mPrimary);
|
|
}
|
|
|
|
|
|
/*
|
|
Clear the primary selection.
|
|
*/
|
|
void Fl_Text_Buffer::unselect()
|
|
{
|
|
Fl_Text_Selection oldSelection = mPrimary;
|
|
|
|
mPrimary.mSelected = 0;
|
|
redisplay_selection(&oldSelection, &mPrimary);
|
|
}
|
|
|
|
|
|
/*
|
|
Return the primary selection range.
|
|
*/
|
|
int Fl_Text_Buffer::selection_position(int *start, int *end)
|
|
{
|
|
return mPrimary.position(start, end);
|
|
}
|
|
|
|
|
|
/*
|
|
Return a copy of the selected text.
|
|
*/
|
|
char *Fl_Text_Buffer::selection_text()
|
|
{
|
|
return selection_text_(&mPrimary);
|
|
}
|
|
|
|
|
|
/*
|
|
Remove the selected text.
|
|
*/
|
|
void Fl_Text_Buffer::remove_selection()
|
|
{
|
|
remove_selection_(&mPrimary);
|
|
}
|
|
|
|
|
|
/*
|
|
Replace the selected text.
|
|
*/
|
|
void Fl_Text_Buffer::replace_selection(const char *text)
|
|
{
|
|
replace_selection_(&mPrimary, text);
|
|
}
|
|
|
|
|
|
/*
|
|
Select text.
|
|
Start and End must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::secondary_select(int start, int end)
|
|
{
|
|
Fl_Text_Selection oldSelection = mSecondary;
|
|
|
|
mSecondary.set(start, end);
|
|
redisplay_selection(&oldSelection, &mSecondary);
|
|
}
|
|
|
|
|
|
/*
|
|
Deselect text.
|
|
*/
|
|
void Fl_Text_Buffer::secondary_unselect()
|
|
{
|
|
Fl_Text_Selection oldSelection = mSecondary;
|
|
|
|
mSecondary.mSelected = 0;
|
|
redisplay_selection(&oldSelection, &mSecondary);
|
|
}
|
|
|
|
|
|
/*
|
|
Return the selected range.
|
|
*/
|
|
int Fl_Text_Buffer::secondary_selection_position(int *start, int *end)
|
|
{
|
|
return mSecondary.position(start, end);
|
|
}
|
|
|
|
|
|
/*
|
|
Return a copy of the text in this selection.
|
|
*/
|
|
char *Fl_Text_Buffer::secondary_selection_text()
|
|
{
|
|
return selection_text_(&mSecondary);
|
|
}
|
|
|
|
|
|
/*
|
|
Remove the selected text.
|
|
*/
|
|
void Fl_Text_Buffer::remove_secondary_selection()
|
|
{
|
|
remove_selection_(&mSecondary);
|
|
}
|
|
|
|
|
|
/*
|
|
Replace selected text.
|
|
*/
|
|
void Fl_Text_Buffer::replace_secondary_selection(const char *text)
|
|
{
|
|
replace_selection_(&mSecondary, text);
|
|
}
|
|
|
|
|
|
/*
|
|
Highlight a range of text.
|
|
Start and End must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::highlight(int start, int end)
|
|
{
|
|
Fl_Text_Selection oldSelection = mHighlight;
|
|
|
|
mHighlight.set(start, end);
|
|
redisplay_selection(&oldSelection, &mHighlight);
|
|
}
|
|
|
|
|
|
/*
|
|
Remove text highlighting.
|
|
*/
|
|
void Fl_Text_Buffer::unhighlight()
|
|
{
|
|
Fl_Text_Selection oldSelection = mHighlight;
|
|
|
|
mHighlight.mSelected = 0;
|
|
redisplay_selection(&oldSelection, &mHighlight);
|
|
}
|
|
|
|
|
|
/*
|
|
Return position of highlight.
|
|
*/
|
|
int Fl_Text_Buffer::highlight_position(int *start, int *end)
|
|
{
|
|
return mHighlight.position(start, end);
|
|
}
|
|
|
|
|
|
/*
|
|
Return a copy of highlighted text.
|
|
*/
|
|
char *Fl_Text_Buffer::highlight_text()
|
|
{
|
|
return selection_text_(&mHighlight);
|
|
}
|
|
|
|
|
|
/*
|
|
Add a callback that is called whenever text is modified.
|
|
*/
|
|
void Fl_Text_Buffer::add_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,
|
|
void *cbArg)
|
|
{
|
|
Fl_Text_Modify_Cb *newModifyProcs =
|
|
new Fl_Text_Modify_Cb[mNModifyProcs + 1];
|
|
void **newCBArgs = new void *[mNModifyProcs + 1];
|
|
for (int i = 0; i < mNModifyProcs; i++) {
|
|
newModifyProcs[i + 1] = mModifyProcs[i];
|
|
newCBArgs[i + 1] = mCbArgs[i];
|
|
}
|
|
if (mNModifyProcs != 0) {
|
|
delete[]mModifyProcs;
|
|
delete[]mCbArgs;
|
|
}
|
|
newModifyProcs[0] = bufModifiedCB;
|
|
newCBArgs[0] = cbArg;
|
|
mNModifyProcs++;
|
|
mModifyProcs = newModifyProcs;
|
|
mCbArgs = newCBArgs;
|
|
}
|
|
|
|
|
|
/*
|
|
Remove a callback.
|
|
*/
|
|
void Fl_Text_Buffer::remove_modify_callback(Fl_Text_Modify_Cb bufModifiedCB,
|
|
void *cbArg)
|
|
{
|
|
int i, toRemove = -1;
|
|
|
|
/* find the matching callback to remove */
|
|
for (i = 0; i < mNModifyProcs; i++) {
|
|
if (mModifyProcs[i] == bufModifiedCB && mCbArgs[i] == cbArg) {
|
|
toRemove = i;
|
|
break;
|
|
}
|
|
}
|
|
if (toRemove == -1) {
|
|
Fl::error
|
|
("Fl_Text_Buffer::remove_modify_callback(): Can't find modify CB to remove");
|
|
return;
|
|
}
|
|
|
|
/* Allocate new lists for remaining callback procs and args (if
|
|
any are left) */
|
|
mNModifyProcs--;
|
|
if (mNModifyProcs == 0) {
|
|
mNModifyProcs = 0;
|
|
delete[]mModifyProcs;
|
|
mModifyProcs = NULL;
|
|
delete[]mCbArgs;
|
|
mCbArgs = NULL;
|
|
return;
|
|
}
|
|
Fl_Text_Modify_Cb *newModifyProcs = new Fl_Text_Modify_Cb[mNModifyProcs];
|
|
void **newCBArgs = new void *[mNModifyProcs];
|
|
|
|
/* copy out the remaining members and free the old lists */
|
|
for (i = 0; i < toRemove; i++) {
|
|
newModifyProcs[i] = mModifyProcs[i];
|
|
newCBArgs[i] = mCbArgs[i];
|
|
}
|
|
for (; i < mNModifyProcs; i++) {
|
|
newModifyProcs[i] = mModifyProcs[i + 1];
|
|
newCBArgs[i] = mCbArgs[i + 1];
|
|
}
|
|
delete[]mModifyProcs;
|
|
delete[]mCbArgs;
|
|
mModifyProcs = newModifyProcs;
|
|
mCbArgs = newCBArgs;
|
|
}
|
|
|
|
|
|
/*
|
|
Add a callback that is called before deleting text.
|
|
*/
|
|
void Fl_Text_Buffer::add_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB,
|
|
void *cbArg)
|
|
{
|
|
Fl_Text_Predelete_Cb *newPreDeleteProcs =
|
|
new Fl_Text_Predelete_Cb[mNPredeleteProcs + 1];
|
|
void **newCBArgs = new void *[mNPredeleteProcs + 1];
|
|
for (int i = 0; i < mNPredeleteProcs; i++) {
|
|
newPreDeleteProcs[i + 1] = mPredeleteProcs[i];
|
|
newCBArgs[i + 1] = mPredeleteCbArgs[i];
|
|
}
|
|
if (!mNPredeleteProcs != 0) {
|
|
delete[]mPredeleteProcs;
|
|
delete[]mPredeleteCbArgs;
|
|
}
|
|
newPreDeleteProcs[0] = bufPreDeleteCB;
|
|
newCBArgs[0] = cbArg;
|
|
mNPredeleteProcs++;
|
|
mPredeleteProcs = newPreDeleteProcs;
|
|
mPredeleteCbArgs = newCBArgs;
|
|
}
|
|
|
|
|
|
/*
|
|
Remove a callback.
|
|
*/
|
|
void Fl_Text_Buffer::remove_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB, void *cbArg)
|
|
{
|
|
int i, toRemove = -1;
|
|
/* find the matching callback to remove */
|
|
for (i = 0; i < mNPredeleteProcs; i++) {
|
|
if (mPredeleteProcs[i] == bufPreDeleteCB &&
|
|
mPredeleteCbArgs[i] == cbArg) {
|
|
toRemove = i;
|
|
break;
|
|
}
|
|
}
|
|
if (toRemove == -1) {
|
|
Fl::error
|
|
("Fl_Text_Buffer::remove_predelete_callback(): Can't find pre-delete CB to remove");
|
|
return;
|
|
}
|
|
|
|
/* Allocate new lists for remaining callback procs and args (if
|
|
any are left) */
|
|
mNPredeleteProcs--;
|
|
if (mNPredeleteProcs == 0) {
|
|
mNPredeleteProcs = 0;
|
|
delete[]mPredeleteProcs;
|
|
mPredeleteProcs = NULL;
|
|
delete[]mPredeleteCbArgs;
|
|
mPredeleteCbArgs = NULL;
|
|
return;
|
|
}
|
|
Fl_Text_Predelete_Cb *newPreDeleteProcs =
|
|
new Fl_Text_Predelete_Cb[mNPredeleteProcs];
|
|
void **newCBArgs = new void *[mNPredeleteProcs];
|
|
|
|
/* copy out the remaining members and free the old lists */
|
|
for (i = 0; i < toRemove; i++) {
|
|
newPreDeleteProcs[i] = mPredeleteProcs[i];
|
|
newCBArgs[i] = mPredeleteCbArgs[i];
|
|
}
|
|
for (; i < mNPredeleteProcs; i++) {
|
|
newPreDeleteProcs[i] = mPredeleteProcs[i + 1];
|
|
newCBArgs[i] = mPredeleteCbArgs[i + 1];
|
|
}
|
|
delete[]mPredeleteProcs;
|
|
delete[]mPredeleteCbArgs;
|
|
mPredeleteProcs = newPreDeleteProcs;
|
|
mPredeleteCbArgs = newCBArgs;
|
|
}
|
|
|
|
|
|
/*
|
|
Return a copy of the line that contains a given index.
|
|
Pos must be at a character boundary.
|
|
*/
|
|
char *Fl_Text_Buffer::line_text(int pos) const {
|
|
return text_range(line_start(pos), line_end(pos));
|
|
}
|
|
|
|
|
|
/*
|
|
Find the beginning of the line.
|
|
*/
|
|
int Fl_Text_Buffer::line_start(int pos) const
|
|
{
|
|
if (!findchar_backward(pos, '\n', &pos))
|
|
return 0;
|
|
return pos + 1;
|
|
}
|
|
|
|
|
|
/*
|
|
Find the end of the line.
|
|
*/
|
|
int Fl_Text_Buffer::line_end(int pos) const {
|
|
if (!findchar_forward(pos, '\n', &pos))
|
|
pos = mLength;
|
|
return pos;
|
|
}
|
|
|
|
|
|
/*
|
|
Find the beginning of a word.
|
|
NOT UNICODE SAFE.
|
|
*/
|
|
int Fl_Text_Buffer::word_start(int pos) const {
|
|
// FIXME: character is ucs-4
|
|
while (pos>0 && (isalnum(char_at(pos)) || char_at(pos) == '_')) {
|
|
pos = prev_char(pos);
|
|
}
|
|
// FIXME: character is ucs-4
|
|
if (!(isalnum(char_at(pos)) || char_at(pos) == '_'))
|
|
pos = next_char(pos);
|
|
return pos;
|
|
}
|
|
|
|
|
|
/*
|
|
Find the end of a word.
|
|
NOT UNICODE SAFE.
|
|
*/
|
|
int Fl_Text_Buffer::word_end(int pos) const {
|
|
// FIXME: character is ucs-4
|
|
while (pos < length() && (isalnum(char_at(pos)) || char_at(pos) == '_'))
|
|
{
|
|
pos = next_char(pos);
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
/*
|
|
Count the number of characters between two positions.
|
|
*/
|
|
int Fl_Text_Buffer::count_displayed_characters(int lineStartPos,
|
|
int targetPos) const
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (lineStartPos))
|
|
IS_UTF8_ALIGNED2(this, (targetPos))
|
|
|
|
int charCount = 0;
|
|
|
|
int pos = lineStartPos;
|
|
while (pos < targetPos) {
|
|
pos = next_char(pos);
|
|
charCount++;
|
|
}
|
|
return charCount;
|
|
}
|
|
|
|
|
|
/*
|
|
Skip ahead a number of characters from a given index.
|
|
This function breaks early if it encounters a newline character.
|
|
*/
|
|
int Fl_Text_Buffer::skip_displayed_characters(int lineStartPos, int nChars)
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (lineStartPos))
|
|
|
|
int pos = lineStartPos;
|
|
|
|
for (int charCount = 0; charCount < nChars && pos < mLength; charCount++) {
|
|
unsigned int c = char_at(pos);
|
|
if (c == '\n')
|
|
return pos;
|
|
pos = next_char(pos);
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
/*
|
|
Count the number of newline characters between start and end.
|
|
startPos and endPos must be at a character boundary.
|
|
This function is optimized for speed by not using UTF-8 calls.
|
|
*/
|
|
int Fl_Text_Buffer::count_lines(int startPos, int endPos) const {
|
|
IS_UTF8_ALIGNED2(this, (startPos))
|
|
IS_UTF8_ALIGNED2(this, (endPos))
|
|
|
|
int gapLen = mGapEnd - mGapStart;
|
|
int lineCount = 0;
|
|
|
|
int pos = startPos;
|
|
while (pos < mGapStart)
|
|
{
|
|
if (pos == endPos)
|
|
return lineCount;
|
|
if (mBuf[pos++] == '\n')
|
|
lineCount++;
|
|
}
|
|
while (pos < mLength) {
|
|
if (pos == endPos)
|
|
return lineCount;
|
|
if (mBuf[pos++ + gapLen] == '\n')
|
|
lineCount++;
|
|
}
|
|
return lineCount;
|
|
}
|
|
|
|
|
|
/*
|
|
Skip to the first character, n lines ahead.
|
|
StartPos must be at a character boundary.
|
|
This function is optimized for speed by not using UTF-8 calls.
|
|
*/
|
|
int Fl_Text_Buffer::skip_lines(int startPos, int nLines)
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (startPos))
|
|
|
|
if (nLines == 0)
|
|
return startPos;
|
|
|
|
int gapLen = mGapEnd - mGapStart;
|
|
int pos = startPos;
|
|
int lineCount = 0;
|
|
while (pos < mGapStart) {
|
|
if (mBuf[pos++] == '\n') {
|
|
lineCount++;
|
|
if (lineCount == nLines) {
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
return pos;
|
|
}
|
|
}
|
|
}
|
|
while (pos < mLength) {
|
|
if (mBuf[pos++ + gapLen] == '\n') {
|
|
lineCount++;
|
|
if (lineCount >= nLines) {
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
return pos;
|
|
}
|
|
}
|
|
}
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
return pos;
|
|
}
|
|
|
|
|
|
/*
|
|
Skip to the first character, n lines back.
|
|
StartPos must be at a character boundary.
|
|
This function is optimized for speed by not using UTF-8 calls.
|
|
*/
|
|
int Fl_Text_Buffer::rewind_lines(int startPos, int nLines)
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (startPos))
|
|
|
|
int pos = startPos - 1;
|
|
if (pos <= 0)
|
|
return 0;
|
|
|
|
int gapLen = mGapEnd - mGapStart;
|
|
int lineCount = -1;
|
|
while (pos >= mGapStart) {
|
|
if (mBuf[pos + gapLen] == '\n') {
|
|
if (++lineCount >= nLines) {
|
|
IS_UTF8_ALIGNED2(this, (pos+1))
|
|
return pos + 1;
|
|
}
|
|
}
|
|
pos--;
|
|
}
|
|
while (pos >= 0) {
|
|
if (mBuf[pos] == '\n') {
|
|
if (++lineCount >= nLines) {
|
|
IS_UTF8_ALIGNED2(this, (pos+1))
|
|
return pos + 1;
|
|
}
|
|
}
|
|
pos--;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
Find a matching string in the buffer.
|
|
*/
|
|
int Fl_Text_Buffer::search_forward(int startPos, const char *searchString,
|
|
int *foundPos, int matchCase) const
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (startPos))
|
|
IS_UTF8_ALIGNED(searchString)
|
|
|
|
if (!searchString)
|
|
return 0;
|
|
int bp;
|
|
const char *sp;
|
|
if (matchCase) {
|
|
while (startPos < length()) {
|
|
bp = startPos;
|
|
sp = searchString;
|
|
for (;;) {
|
|
char c = *sp;
|
|
// we reached the end of the "needle", so we found the string!
|
|
if (!c) {
|
|
*foundPos = startPos;
|
|
return 1;
|
|
}
|
|
int l = fl_utf8len(c);
|
|
if (memcmp(sp, address(bp), l))
|
|
break;
|
|
sp += l; bp += l;
|
|
}
|
|
startPos = next_char(startPos);
|
|
}
|
|
} else {
|
|
while (startPos < length()) {
|
|
bp = startPos;
|
|
sp = searchString;
|
|
for (;;) {
|
|
// we reached the end of the "needle", so we found the string!
|
|
if (!*sp) {
|
|
*foundPos = startPos;
|
|
return 1;
|
|
}
|
|
int l;
|
|
unsigned int b = char_at(bp);
|
|
unsigned int s = fl_utf8decode(sp, 0, &l);
|
|
if (fl_tolower(b)!=fl_tolower(s))
|
|
break;
|
|
sp += l;
|
|
bp = next_char(bp);
|
|
}
|
|
startPos = next_char(startPos);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int Fl_Text_Buffer::search_backward(int startPos, const char *searchString,
|
|
int *foundPos, int matchCase) const
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (startPos))
|
|
IS_UTF8_ALIGNED(searchString)
|
|
|
|
if (!searchString)
|
|
return 0;
|
|
int bp;
|
|
const char *sp;
|
|
if (matchCase) {
|
|
while (startPos >= 0) {
|
|
bp = startPos;
|
|
sp = searchString;
|
|
for (;;) {
|
|
char c = *sp;
|
|
// we reached the end of the "needle", so we found the string!
|
|
if (!c) {
|
|
*foundPos = startPos;
|
|
return 1;
|
|
}
|
|
int l = fl_utf8len(c);
|
|
if (memcmp(sp, address(bp), l))
|
|
break;
|
|
sp += l; bp += l;
|
|
}
|
|
startPos = prev_char(startPos);
|
|
}
|
|
} else {
|
|
while (startPos >= 0) {
|
|
bp = startPos;
|
|
sp = searchString;
|
|
for (;;) {
|
|
// we reached the end of the "needle", so we found the string!
|
|
if (!*sp) {
|
|
*foundPos = startPos;
|
|
return 1;
|
|
}
|
|
int l;
|
|
unsigned int b = char_at(bp);
|
|
unsigned int s = fl_utf8decode(sp, 0, &l);
|
|
if (fl_tolower(b)!=fl_tolower(s))
|
|
break;
|
|
sp += l;
|
|
bp = next_char(bp);
|
|
}
|
|
startPos = prev_char(startPos);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Insert a string into the buffer.
|
|
Pos must be at a character boundary. Text must be a correct UTF-8 string.
|
|
*/
|
|
int Fl_Text_Buffer::insert_(int pos, const char *text)
|
|
{
|
|
if (!text || !*text)
|
|
return 0;
|
|
|
|
int insertedLength = strlen(text);
|
|
|
|
/* Prepare the buffer to receive the new text. If the new text fits in
|
|
the current buffer, just move the gap (if necessary) to where
|
|
the text should be inserted. If the new text is too large, reallocate
|
|
the buffer with a gap large enough to accomodate the new text and a
|
|
gap of mPreferredGapSize */
|
|
if (insertedLength > mGapEnd - mGapStart)
|
|
reallocate_with_gap(pos, insertedLength + mPreferredGapSize);
|
|
else if (pos != mGapStart)
|
|
move_gap(pos);
|
|
|
|
/* Insert the new text (pos now corresponds to the start of the gap) */
|
|
memcpy(&mBuf[pos], text, insertedLength);
|
|
mGapStart += insertedLength;
|
|
mLength += insertedLength;
|
|
update_selections(pos, 0, insertedLength);
|
|
|
|
if (mCanUndo) {
|
|
if (undowidget == this && undoat == pos && undoinsert) {
|
|
undoinsert += insertedLength;
|
|
} else {
|
|
undoinsert = insertedLength;
|
|
undoyankcut = (undoat == pos) ? undocut : 0;
|
|
}
|
|
undoat = pos + insertedLength;
|
|
undocut = 0;
|
|
undowidget = this;
|
|
}
|
|
|
|
return insertedLength;
|
|
}
|
|
|
|
|
|
/*
|
|
Remove a string from the buffer.
|
|
Unicode safe. Start and end must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::remove_(int start, int end)
|
|
{
|
|
/* if the gap is not contiguous to the area to remove, move it there */
|
|
|
|
if (mCanUndo) {
|
|
if (undowidget == this && undoat == end && undocut) {
|
|
undobuffersize(undocut + end - start + 1);
|
|
memmove(undobuffer + end - start, undobuffer, undocut);
|
|
undocut += end - start;
|
|
} else {
|
|
undocut = end - start;
|
|
undobuffersize(undocut);
|
|
}
|
|
undoat = start;
|
|
undoinsert = 0;
|
|
undoyankcut = 0;
|
|
undowidget = this;
|
|
}
|
|
|
|
if (start > mGapStart) {
|
|
if (mCanUndo)
|
|
memcpy(undobuffer, mBuf + (mGapEnd - mGapStart) + start,
|
|
end - start);
|
|
move_gap(start);
|
|
} else if (end < mGapStart) {
|
|
if (mCanUndo)
|
|
memcpy(undobuffer, mBuf + start, end - start);
|
|
move_gap(end);
|
|
} else {
|
|
int prelen = mGapStart - start;
|
|
if (mCanUndo) {
|
|
memcpy(undobuffer, mBuf + start, prelen);
|
|
memcpy(undobuffer + prelen, mBuf + mGapEnd, end - start - prelen);
|
|
}
|
|
}
|
|
|
|
/* expand the gap to encompass the deleted characters */
|
|
mGapEnd += end - mGapStart;
|
|
mGapStart -= mGapStart - start;
|
|
|
|
/* update the length */
|
|
mLength -= end - start;
|
|
|
|
/* fix up any selections which might be affected by the change */
|
|
update_selections(start, end - start, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
simple setter.
|
|
Unicode safe. Start and end must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Selection::set(int startpos, int endpos)
|
|
{
|
|
mSelected = startpos != endpos;
|
|
mStart = min(startpos, endpos);
|
|
mEnd = max(startpos, endpos);
|
|
}
|
|
|
|
|
|
/*
|
|
simple getter.
|
|
Unicode safe. Start and end will be at a character boundary.
|
|
*/
|
|
int Fl_Text_Selection::position(int *startpos, int *endpos) const {
|
|
if (!mSelected)
|
|
return 0;
|
|
*startpos = mStart;
|
|
*endpos = mEnd;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
Return if a position is inside the selected area.
|
|
Unicode safe. Pos must be at a character boundary.
|
|
*/
|
|
int Fl_Text_Selection::includes(int pos) const {
|
|
return (selected() && pos >= start() && pos < end() );
|
|
}
|
|
|
|
|
|
/*
|
|
Return a duplicate of the selected text, or an empty string.
|
|
Unicode safe.
|
|
*/
|
|
char *Fl_Text_Buffer::selection_text_(Fl_Text_Selection * sel) const {
|
|
int start, end;
|
|
|
|
/* If there's no selection, return an allocated empty string */
|
|
if (!sel->position(&start, &end))
|
|
{
|
|
char *s = (char *) malloc(1);
|
|
*s = '\0';
|
|
return s;
|
|
}
|
|
|
|
/* Return the selected range */
|
|
return text_range(start, end);
|
|
}
|
|
|
|
|
|
/*
|
|
Remove the selected text.
|
|
Unicode safe.
|
|
*/
|
|
void Fl_Text_Buffer::remove_selection_(Fl_Text_Selection * sel)
|
|
{
|
|
int start, end;
|
|
|
|
if (!sel->position(&start, &end))
|
|
return;
|
|
remove(start, end);
|
|
//undoyankcut = undocut;
|
|
}
|
|
|
|
|
|
/*
|
|
Replace selection with text.
|
|
Unicode safe.
|
|
*/
|
|
void Fl_Text_Buffer::replace_selection_(Fl_Text_Selection * sel,
|
|
const char *text)
|
|
{
|
|
Fl_Text_Selection oldSelection = *sel;
|
|
|
|
/* If there's no selection, return */
|
|
int start, end;
|
|
if (!sel->position(&start, &end))
|
|
return;
|
|
|
|
/* Do the appropriate type of replace */
|
|
replace(start, end, text);
|
|
|
|
/* Unselect (happens automatically in BufReplace, but BufReplaceRect
|
|
can't detect when the contents of a selection goes away) */
|
|
sel->mSelected = 0;
|
|
redisplay_selection(&oldSelection, sel);
|
|
}
|
|
|
|
|
|
/*
|
|
Call all callbacks.
|
|
Unicode safe.
|
|
*/
|
|
void Fl_Text_Buffer::call_modify_callbacks(int pos, int nDeleted,
|
|
int nInserted, int nRestyled,
|
|
const char *deletedText) const {
|
|
IS_UTF8_ALIGNED2(this, pos)
|
|
for (int i = 0; i < mNModifyProcs; i++)
|
|
(*mModifyProcs[i]) (pos, nInserted, nDeleted, nRestyled,
|
|
deletedText, mCbArgs[i]);
|
|
}
|
|
|
|
|
|
/*
|
|
Call all callbacks.
|
|
Unicode safe.
|
|
*/
|
|
void Fl_Text_Buffer::call_predelete_callbacks(int pos, int nDeleted) const {
|
|
for (int i = 0; i < mNPredeleteProcs; i++)
|
|
(*mPredeleteProcs[i]) (pos, nDeleted, mPredeleteCbArgs[i]);
|
|
}
|
|
|
|
|
|
/*
|
|
Redisplay a new selected area.
|
|
Unicode safe.
|
|
*/
|
|
void Fl_Text_Buffer::redisplay_selection(Fl_Text_Selection *
|
|
oldSelection,
|
|
Fl_Text_Selection *
|
|
newSelection) const
|
|
{
|
|
int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start,
|
|
ch2End;
|
|
|
|
/* If either selection is rectangular, add an additional character to
|
|
the end of the selection to request the redraw routines to wipe out
|
|
the parts of the selection beyond the end of the line */
|
|
oldStart = oldSelection->mStart;
|
|
newStart = newSelection->mStart;
|
|
oldEnd = oldSelection->mEnd;
|
|
newEnd = newSelection->mEnd;
|
|
|
|
/* If the old or new selection is unselected, just redisplay the
|
|
single area that is (was) selected and return */
|
|
if (!oldSelection->mSelected && !newSelection->mSelected)
|
|
return;
|
|
if (!oldSelection->mSelected)
|
|
{
|
|
call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
|
|
return;
|
|
}
|
|
if (!newSelection->mSelected) {
|
|
call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
|
|
return;
|
|
}
|
|
|
|
/* If the selections are non-contiguous, do two separate updates
|
|
and return */
|
|
if (oldEnd < newStart || newEnd < oldStart) {
|
|
call_modify_callbacks(oldStart, 0, 0, oldEnd - oldStart, NULL);
|
|
call_modify_callbacks(newStart, 0, 0, newEnd - newStart, NULL);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
|
|
changed areas), and the unchanged area of their intersection,
|
|
and update only the changed area(s) */
|
|
ch1Start = min(oldStart, newStart);
|
|
ch2End = max(oldEnd, newEnd);
|
|
ch1End = max(oldStart, newStart);
|
|
ch2Start = min(oldEnd, newEnd);
|
|
if (ch1Start != ch1End)
|
|
call_modify_callbacks(ch1Start, 0, 0, ch1End - ch1Start, NULL);
|
|
if (ch2Start != ch2End)
|
|
call_modify_callbacks(ch2Start, 0, 0, ch2End - ch2Start, NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
Move the gap around without changing buffer content.
|
|
Unicode safe. Pos must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::move_gap(int pos)
|
|
{
|
|
int gapLen = mGapEnd - mGapStart;
|
|
|
|
if (pos > mGapStart)
|
|
memmove(&mBuf[mGapStart], &mBuf[mGapEnd], pos - mGapStart);
|
|
else
|
|
memmove(&mBuf[pos + gapLen], &mBuf[pos], mGapStart - pos);
|
|
mGapEnd += pos - mGapStart;
|
|
mGapStart += pos - mGapStart;
|
|
}
|
|
|
|
|
|
/*
|
|
Create a larger gap.
|
|
Unicode safe. Start must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::reallocate_with_gap(int newGapStart, int newGapLen)
|
|
{
|
|
char *newBuf = (char *) malloc(mLength + newGapLen);
|
|
int newGapEnd = newGapStart + newGapLen;
|
|
|
|
if (newGapStart <= mGapStart) {
|
|
memcpy(newBuf, mBuf, newGapStart);
|
|
memcpy(&newBuf[newGapEnd], &mBuf[newGapStart],
|
|
mGapStart - newGapStart);
|
|
memcpy(&newBuf[newGapEnd + mGapStart - newGapStart],
|
|
&mBuf[mGapEnd], mLength - mGapStart);
|
|
} else { /* newGapStart > mGapStart */
|
|
memcpy(newBuf, mBuf, mGapStart);
|
|
memcpy(&newBuf[mGapStart], &mBuf[mGapEnd], newGapStart - mGapStart);
|
|
memcpy(&newBuf[newGapEnd],
|
|
&mBuf[mGapEnd + newGapStart - mGapStart],
|
|
mLength - newGapStart);
|
|
}
|
|
free((void *) mBuf);
|
|
mBuf = newBuf;
|
|
mGapStart = newGapStart;
|
|
mGapEnd = newGapEnd;
|
|
}
|
|
|
|
|
|
/*
|
|
Update selection range if characters were inserted.
|
|
Unicode safe. Pos must be at a character boundary.
|
|
*/
|
|
void Fl_Text_Buffer::update_selections(int pos, int nDeleted,
|
|
int nInserted)
|
|
{
|
|
mPrimary.update(pos, nDeleted, nInserted);
|
|
mSecondary.update(pos, nDeleted, nInserted);
|
|
mHighlight.update(pos, nDeleted, nInserted);
|
|
}
|
|
|
|
|
|
// unicode safe, assuming the arguments are on character boundaries
|
|
void Fl_Text_Selection::update(int pos, int nDeleted, int nInserted)
|
|
{
|
|
if (!mSelected || pos > mEnd)
|
|
return;
|
|
if (pos + nDeleted <= mStart) {
|
|
mStart += nInserted - nDeleted;
|
|
mEnd += nInserted - nDeleted;
|
|
} else if (pos <= mStart && pos + nDeleted >= mEnd) {
|
|
mStart = pos;
|
|
mEnd = pos;
|
|
mSelected = 0;
|
|
} else if (pos <= mStart && pos + nDeleted < mEnd) {
|
|
mStart = pos;
|
|
mEnd = nInserted + mEnd - nDeleted;
|
|
} else if (pos < mEnd) {
|
|
mEnd += nInserted - nDeleted;
|
|
if (mEnd <= mStart)
|
|
mSelected = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Find a UCS-4 character.
|
|
StartPos must be at a character boundary, searchChar is UCS-4 encoded.
|
|
*/
|
|
int Fl_Text_Buffer::findchar_forward(int startPos, unsigned searchChar,
|
|
int *foundPos) const
|
|
{
|
|
if (startPos >= mLength) {
|
|
*foundPos = mLength;
|
|
return 0;
|
|
}
|
|
|
|
if (startPos<0)
|
|
startPos = 0;
|
|
|
|
for ( ; startPos<mLength; startPos = next_char(startPos)) {
|
|
if (searchChar == char_at(startPos)) {
|
|
*foundPos = startPos;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
*foundPos = mLength;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
Find a UCS-4 character.
|
|
StartPos must be at a character boundary, searchChar is UCS-4 encoded.
|
|
*/
|
|
int Fl_Text_Buffer::findchar_backward(int startPos, unsigned int searchChar,
|
|
int *foundPos) const {
|
|
if (startPos <= 0) {
|
|
*foundPos = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (startPos > mLength)
|
|
startPos = mLength;
|
|
|
|
for (startPos = prev_char(startPos); startPos>=0; startPos = prev_char(startPos)) {
|
|
if (searchChar == char_at(startPos)) {
|
|
*foundPos = startPos;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
*foundPos = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
Insert text from a file.
|
|
Unicode safe. Inout must be correct UTF-8!
|
|
*/
|
|
int Fl_Text_Buffer::insertfile(const char *file, int pos, int buflen)
|
|
{
|
|
FILE *fp;
|
|
if (!(fp = fl_fopen(file, "r")))
|
|
return 1;
|
|
char *buffer = new char[buflen];
|
|
for (int r; (r = fread(buffer, 1, buflen - 1, fp)) > 0; pos += r) {
|
|
buffer[r] = (char) 0;
|
|
insert(pos, buffer);
|
|
}
|
|
|
|
int e = ferror(fp) ? 2 : 0;
|
|
fclose(fp);
|
|
delete[]buffer;
|
|
return e;
|
|
}
|
|
|
|
|
|
/*
|
|
Write text to file.
|
|
Unicode safe.
|
|
*/
|
|
int Fl_Text_Buffer::outputfile(const char *file, int start, int end,
|
|
int buflen)
|
|
{
|
|
FILE *fp;
|
|
if (!(fp = fl_fopen(file, "wb")))
|
|
return 1;
|
|
for (int n; (n = min(end - start, buflen)); start += n) {
|
|
const char *p = text_range(start, start + n);
|
|
int r = fwrite(p, 1, n, fp);
|
|
free((void *) p);
|
|
if (r != n)
|
|
break;
|
|
}
|
|
|
|
int e = ferror(fp) ? 2 : 0;
|
|
fclose(fp);
|
|
return e;
|
|
}
|
|
|
|
|
|
/*
|
|
Return the previous character position.
|
|
Uncode safe.
|
|
*/
|
|
int Fl_Text_Buffer::prev_char_clipped(int pos) const
|
|
{
|
|
if (pos<=0)
|
|
return 0;
|
|
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
|
|
char c;
|
|
do {
|
|
pos--;
|
|
if (pos==0)
|
|
return 0;
|
|
c = byte_at(pos);
|
|
} while ( (c&0xc0) == 0x80);
|
|
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
return pos;
|
|
}
|
|
|
|
|
|
/*
|
|
Return the previous character position.
|
|
Returns -1 if the beginning of the buffer is reached.
|
|
*/
|
|
int Fl_Text_Buffer::prev_char(int pos) const
|
|
{
|
|
if (pos==0) return -1;
|
|
return prev_char_clipped(pos);
|
|
}
|
|
|
|
|
|
/*
|
|
Return the next character position.
|
|
Returns length() if the end of the buffer is reached.
|
|
*/
|
|
int Fl_Text_Buffer::next_char(int pos) const
|
|
{
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
int n = fl_utf8len(byte_at(pos));
|
|
pos += n;
|
|
if (pos>=mLength)
|
|
return mLength;
|
|
IS_UTF8_ALIGNED2(this, (pos))
|
|
return pos;
|
|
}
|
|
|
|
|
|
/*
|
|
Return the next character position.
|
|
If the end of the buffer is reached, it returns the current position.
|
|
*/
|
|
int Fl_Text_Buffer::next_char_clipped(int pos) const
|
|
{
|
|
return next_char(pos);
|
|
}
|
|
|
|
/*
|
|
Align an index to the current UTF-8 boundary.
|
|
*/
|
|
int Fl_Text_Buffer::utf8_align(int pos) const
|
|
{
|
|
char c = byte_at(pos);
|
|
while ( (c&0xc0) == 0x80) {
|
|
pos--;
|
|
c = byte_at(pos);
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
//
|
|
// End of "$Id$".
|
|
//
|