Skip to content

Instantly share code, notes, and snippets.

@jerstlouis
Created August 13, 2016 03:50
Show Gist options
  • Save jerstlouis/ff216c5c68782bbc7f018c20e466cebf to your computer and use it in GitHub Desktop.
Save jerstlouis/ff216c5c68782bbc7f018c20e466cebf to your computer and use it in GitHub Desktop.
namespace gui::controls;
/*
selectionForeground = white;
disabled: defaultTextColor = Color { 85, 85, 85 };
*/
import "Window"
import "ReplaceDialog"
import "FindDialog"
import "GoToDialog"
import "Array"
char * strchrmax(const char * s, int c, int max)
{
int i;
char ch;
for(i = 0; i < max && (ch = s[i]); i++)
if(ch == c)
return (char *)s + i;
return null;
}
class eCSyntaxState : byte
{
bool inMultiLineComment:1;
bool inPrep:1;
bool escaped:1;
bool continuedSingleLineComment:1;
bool wasInMultiLine:1;
bool continuedString:1;
bool continuedQuotes:1;
bool inString:1;
bool inQuotes:1;
bool inSingleLineComment:1;
bool lastWasStar:1;
bool firstWord:1;
};
public class SyntaxHighlighting
{
eCSyntaxState viewLineState;
eCSyntaxState currentState;
eCSyntaxState backedState;
virtual void InitDraw()
{
currentState = viewLineState;
// Should not really need to do this...
currentState.inString = false;
currentState.inQuotes = false;
currentState.inSingleLineComment = false;
}
virtual bool CanImpactOtherLines(EditLine line)
{
bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
int c;
if(!hadComment)
{
for(c = line.count-1; c >= 0; c--)
{
char ch = line.buffer[c];
if(ch == '\\')
{
hadComment = true;
break;
}
else //if(ch != ' ' && ch != '\t')
break;
}
}
return hadComment;
}
virtual void GotSpace(bool beforeEnd)
{
if(beforeEnd)
currentState.escaped = false;
currentState.lastWasStar = false;
}
virtual void ResetState()
{
currentState = backedState;
}
virtual Color Process(char * word, int wordLen, bool beforeEndOfLine, Color defaultTextColor, SyntaxColorScheme colorScheme)
{
Color newTextColor;
int g, ccc;
currentState.firstWord = false;
backedState = currentState;
currentState.escaped = false;
currentState.lastWasStar = false;
// Determine Syntax Highlighting
newTextColor = defaultTextColor;
if(currentState.inSingleLineComment || currentState.inMultiLineComment)
{
newTextColor = colorScheme.commentColor;
}
else if(currentState.inQuotes)
{
newTextColor = colorScheme.charLiteralColor;
}
else if(currentState.inString)
{
newTextColor = colorScheme.stringLiteralColor;
}
else if(currentState.inPrep)
{
newTextColor = colorScheme.preprocessorColor;
}
if(wordLen == 1 && word[0] == '/')
{
if(!currentState.inSingleLineComment && !currentState.inMultiLineComment && !currentState.inQuotes && !currentState.inString)
{
if(word[1] == '/')
{
currentState.inSingleLineComment = true;
newTextColor = colorScheme.commentColor;
}
else if(word[1] == '*')
{
currentState.inMultiLineComment = true;
newTextColor = colorScheme.commentColor;
}
}
else if(backedState.lastWasStar)
currentState.inMultiLineComment = false;
}
else if(wordLen == 1 && word[0] == '*')
{
if(backedState.wasInMultiLine)
currentState.lastWasStar = true;
}
else if(!currentState.inSingleLineComment && !currentState.inMultiLineComment && !currentState.inQuotes && wordLen == 1 && word[0] == '\"')
{
if(currentState.inString && !currentState.wasEscaped)
{
currentState.inString = false;
}
else
{
currentState.inString = true;
newTextColor = colorScheme.stringLiteralColor;
}
}
else if(!currentState.inSingleLineComment && !currentState.inMultiLineComment && !currentState.inString && wordLen == 1 && word[0] == '\'')
{
if(currentState.inQuotes && !currentState.wasEscaped)
currentState.inQuotes = false;
else
{
currentState.inQuotes = true;
newTextColor = colorScheme.charLiteralColor;
}
}
else if(wordLen == 1 && word[0] == '\\')
{
if(!currentState.wasEscaped)
currentState.escaped = true;
}
else if(beforeEndOfLine && !currentState.inQuotes && !currentState.inString && !currentState.inMultiLineComment && !currentState.inSingleLineComment && (isdigit(word[0]) || (word[0] == '.' && isdigit(word[1]))))
{
char * dot = word[wordLen] == '.' ? word + wordLen : (word[0] == '.' && (word == line.buffer || word[-1] == '-' || isspace(word[-1])) ? word : null);
bool isReal = dot != null;
char * s = null;
if(dot)
isReal = true;
else
{
char * exponent;
bool isHex = (word[0] == '0' && (word[1] == 'x' || word[1] == 'X'));
if(isHex)
{
exponent = strchrmax(word, 'p', wordLen);
if(!exponent) exponent = strchrmax(word, 'P', wordLen);
}
else
{
exponent = strchrmax(word, 'e', wordLen);
if(!exponent) exponent = strchrmax(word, 'E', wordLen);
}
isReal = exponent != null;
}
if(isReal)
strtod(word, &s); // strtod() seems to break on hex floats (e.g. 0x23e3p12, 0x1.fp3)
else
strtol(word, &s, 0);
if(s && s != word)
{
// Check suffixes
char ch;
int i;
int gotF = 0, gotL = 0, gotU = 0, gotI = 0;
bool valid = true;
for(i = 0; valid && i < 5 && (ch = s[i]) && (isalnum(ch) || ch == '_'); i++)
{
switch(ch)
{
case 'f': case 'F': gotF++; if(gotF > 1 || !isReal) valid = false; break;
case 'l': case 'L':
gotL++;
if(gotL > 2 || (isReal && (gotL == 2 || gotF)) || (gotL == 2 && (s[i-1] != ch)))
valid = false;
break;
case 'u': case 'U': gotU++; if(gotU > 1 || isReal) valid = false; break;
case 'i': case 'I': case 'j': case 'J': gotI++; if(gotI > 1) valid = false; break;
default: valid = false;
}
}
// Don't highlight numbers with too many decimal points
if(s[0] == '.' && isdigit(s[1]))
{
int newWordLen;
while(s[0] == '.' && isdigit(s[1]))
{
int newWordLen = s - word;
c += newWordLen - wordLen;
wordLen = newWordLen;
strtod(s, &s);
}
newWordLen = s - word;
c += newWordLen - wordLen;
wordLen = newWordLen;
}
else if(valid)
{
int newWordLen = s + i - word;
newTextColor = colorScheme.numberColor;
c += newWordLen - wordLen;
wordLen = newWordLen;
}
else if(dot && dot > word && dot < s)
newTextColor = colorScheme.numberColor;
}
}
else
{
if(!currentState.inQuotes && !currentState.inString && !currentState.inMultiLineComment && !currentState.inSingleLineComment && word[0] == '#')
{
if(currentState.firstWord)
{
currentState.inPrep = true;
newTextColor = wordLen == 1 ? colorScheme.keywordColors[1] : colorScheme.preprocessorColor;
}
}
if(beforeEndOfLine && !currentState.inQuotes && !currentState.inString && !currentState.inMultiLineComment && !currentState.inSingleLineComment)
{
for(g = 0; g < ((currentState.inPrep && word[0] != '#') ? 2 : 1); g++)
{
const char ** keys = keyWords[g];
int * len = keyLen[g];
for(ccc = 0; keys[ccc]; ccc++)
{
if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
{
newTextColor = colorScheme.keywordColors[g];
break;
}
}
}
}
}
return newTextColor;
}
virtual void StartLine()
{
currentState.lastWasStar = false;
currentState.firstWord = true;
if(!currentState.escaped) currentState.inPrep = false;
currentState.inSingleLineComment = currentState.continuedSingleLineComment;
currentState.escaped = false;
currentState.inString = currentState.continuedString;
currentState.inQuotes = currentState.continuedQuotes;
}
virtual void NextLine(char * buffer, int count)
{
if(count && buffer[count - 1] == '\\')
{
currentState.continuedSingleLineComment = currentState.inSingleLineComment;
currentState.continuedString = currentState.inString;
currentState.continuedQuotes = currentState.inQuotes;
}
else
{
currentState.continuedSingleLineComment = false;
currentState.continuedString = false;
currentState.continuedQuotes = false;
}
}
virtual int ParseWord(char * buffer, int count, int wordLen)
{
int c;
for(c = 0; c < count; c++)
{
unichar ch = buffer[c];
unichar bf = (wordLen == 1) ? buffer[c-1] : 0;
if(CharMatchCategories(ch, separators) || ch == '\t' ||
(wordLen && !CharMatchCategories(ch, numbers|letters|marks|connector) && ch != '#' ) ||
(bf && !CharMatchCategories(bf, numbers|letters|separators|marks|connector) && bf != '#' && bf != '\t'))
break;
wordLen++;
}
return c;
}
virtual void FigureStartSyntaxStates(EditLine viewLine, bool reset, EditLine line)
{
bool inMultiLineComment = reset ? false : viewLineState.inMultiLineComment;
bool wasInMultiLine = reset ? false : viewLineState.wasInMultiLine;
bool inString = false;
bool inQuotes = false;
bool inPrep = reset ? false : viewLineState.inPrep;
bool inSingleLineComment = false;
bool escaped = reset ? false : viewLineState.escaped;
bool continuedSingleLineComment = reset ? false : viewLineState.continuedSingleLineComment;
bool continuedString = reset ? false : viewLineState.continuedString;
bool continuedQuotes = reset ? false : viewLineState.continuedQuotes;
// int maxBackUp = 1000, c;
// for(c = 0, line = viewLine; c < maxBackUp && line && line.prev; line = line.prev);
for(; line != viewLine; line = line.next)
{
char * text = line.buffer;
int c;
char ch;
bool lastWasStar = false;
bool firstWord = true;
if(!escaped) inPrep = false;
inSingleLineComment = continuedSingleLineComment;
escaped = false;
inString = continuedString;
inQuotes = continuedQuotes;
firstWord = true;
for(c = 0; (ch = text[c]); c++)
{
bool wasEscaped = escaped;
bool backLastWasStar = lastWasStar;
bool backWasInMultiLine = wasInMultiLine;
escaped = false;
lastWasStar = false;
wasInMultiLine = inMultiLineComment;
if(ch == '/')
{
if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
{
if(text[c+1] == '/')
{
inSingleLineComment = true;
}
else if(text[c+1] == '*')
{
inMultiLineComment = true;
}
}
else if(backLastWasStar)
inMultiLineComment = false;
}
else if(ch == '*')
{
if(backWasInMultiLine) lastWasStar = true;
}
else if(ch == '\"' && !inSingleLineComment && !inMultiLineComment && !inQuotes)
{
if(inString && !wasEscaped)
{
inString = false;
}
else
{
inString = true;
}
}
else if(ch == '\'' && !inSingleLineComment && !inMultiLineComment && !inString)
{
if(inQuotes && !wasEscaped)
inQuotes = false;
else
{
inQuotes = true;
}
}
else if(ch == '\\')
{
if(!wasEscaped)
escaped = true;
}
else if(ch == '#' && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
{
if(firstWord)
{
inPrep = true;
}
}
else if(ch != ' ' && ch != '\t')
firstWord = false;
}
if(line.count && line.text[line.count - 1] == '\\')
{
continuedSingleLineComment = inSingleLineComment;
continuedString = inString;
continuedQuotes = inQuotes;
}
else
{
continuedSingleLineComment = false;
continuedString = false;
continuedQuotes = false;
}
}
viewLineState.continuedSingleLineComment = continuedSingleLineComment;
viewLineState.continuedString = continuedString;
viewLineState.continuedQuotes = continuedQuotes;
viewLineState.inMultiLineComment = inMultiLineComment;
viewLineState.wasInMultiLine = wasInMultiLine;
viewLineState.inPrep = inPrep;
viewLineState.escaped = escaped;
}
};
public class SyntaxColorScheme
{
public:
Color commentColor;
Color charLiteralColor;
Color stringLiteralColor;
Color preprocessorColor;
Color numberColor;
private Array<Color> keywordColors { };
public property Container<Color> keywordColors
{
set { keywordColors.Copy((void *)value); }
get { return keywordColors; }
}
};
#include <stdarg.h>
#define MAXWORDLEN 64
#define XOFFSET (3 + (/*style.lineNumbers?6 * this.space.w:*/0))
// #define YOFFSET 1
#define YOFFSET (style.multiLine ? 1 : ((clientSize.h + 1 - space.h) / 2))
#define IS_ALUNDER(ch) (/*(ch) == '_' || */CharMatchCategories((ch), letters|numbers|marks|connector))
#define UTF8_IS_FIRST(x) (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || ((b) & 0x40)); }))
#define UTF8_NUM_BYTES(x) (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
#define UTF8_GET_CHAR(string, numBytes) \
__extension__( \
{ \
unichar ch; \
byte b = (string)[0]; \
int i; \
byte mask = 0x7F; \
numBytes = b ? 1 : 0; \
ch = 0; \
if(b & 0x80) \
{ \
if(b & 0x40) \
{ \
mask >>= 2; \
numBytes++; \
if(b & 0x20) \
{ \
numBytes++; \
mask >>= 1; \
if(b & 0x10) \
{ \
if(b & 0x08) { numBytes = 0; } \
numBytes++; \
mask >>= 1; \
} \
} \
} \
else \
numBytes = 0; \
} \
for(i = 0; i<numBytes; i++) \
{ \
ch <<= 6; \
ch |= (b = (string)[i]) & mask; \
mask = 0x3F; \
if(i > 1 && (!(b & 0x80) || (b & 0x40))) \
{ \
numBytes = 0; \
ch = 0; \
} \
} \
if(i < numBytes || \
ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF) || \
(ch < 0x80 && numBytes > 1) || \
(ch < 0x800 && numBytes > 2) || \
(ch < 0x10000 && numBytes > 3))\
{ \
ch = 0; \
numBytes = 0; \
} \
ch; \
})
class EditBoxBits
{
bool autoEmpty:1, readOnly:1, multiLine:1, stuckCaret:1, freeCaret:1, select:1, hScroll:1, vScroll:1, smartHome:1;
bool noCaret:1, noSelect:1, tabKey:1, useTab:1, tabSel:1, allCaps:1, syntax:1, wrap:1;
bool recomputeSyntax:1;
bool cursorFollowsView:1;
// bool lineNumbers:1;
bool autoSize:1;
};
/* TODO:
void UnregisterClass_EditBox()
{
int g;
for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
{
if(keyLen[g])
free(keyLen[g]);
}
}
*/
default:
extern int __ecereVMethodID_class_OnFree;
private:
static class ArrayImpl
{
Class type;
uint size;
byte * array;
};
public class OldArray
{
~OldArray()
{
int c;
void ** array = (void **)((ArrayImpl)this).array;
if(type.type == normalClass || type.type == noHeadClass)
{
for(c = 0; c<size; c++)
((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, array[c]);
}
// TODO: Call OnFree for structClass
delete ((ArrayImpl)this).array;
}
public:
Class type;
property uint size
{
set
{
if(((ArrayImpl)this).array)
{
if(value == size)
return;
((ArrayImpl)this).array = renew0 ((ArrayImpl)this).array byte[type.typeSize * value];
}
else if(value)
((ArrayImpl)this).array = new0 byte[value * type.typeSize];
size = value;
}
get { return size; }
}
property void * data
{
set
{
memcpy(((ArrayImpl)this).array, value, type.typeSize * size);
}
}
private:
uint size;
};
public class UndoAction : struct
{
public:
subclass(UndoAction) type;
bool continued;
virtual void Undo(void * data) { type.Undo(this, data); }
virtual void Redo(void * data) { type.Redo(this, data); }
#ifdef _DEBUG
virtual void Print(void * data) { type.Print(this, data); }
#endif
~UndoAction()
{
if(((Class)type).Destructor)
((void (*)(void *))((Class)type).Destructor)(this);
}
};
public class UndoBuffer
{
Array<UndoAction> actions { size = 8 };
public:
int count;
int curAction;
void * data;
int dontRecord;
bool insideRedo;
bool recordAsOne;
bool firstEvent;
dontRecord = 0;
~UndoBuffer()
{
actions.Free();
}
void Undo()
{
bool continued = true;
while(curAction > 0 && continued)
{
UndoAction action = actions[--curAction];
dontRecord++;
#ifdef _DEBUG
/*Print("Undoing: ");
action.Print(data);*/
#endif
action.Undo(data);
dontRecord--;
continued = curAction > 0 && actions[curAction-1].continued;
}
}
void Redo()
{
bool continued = true;
while(curAction < count && continued)
{
UndoAction action = actions[curAction];
continued = action.continued;
dontRecord++;
insideRedo = true;
curAction++;
#ifdef _DEBUG
/*Print("Redoing: ");
action.Print(data);*/
#endif
action.Redo(data);
insideRedo = false;
dontRecord--;
}
}
void Record(UndoAction action)
{
if(!dontRecord && !insideRedo)
{
if(curAction < count)
{
int c;
for(c = curAction; c < count; c++)
delete actions[c];
}
count = curAction;
if(count >= actions.size)
actions.size += actions.size / 2;
#ifdef _DEBUG
/*Print("Recording: ");
action.Print(data);*/
#endif
if(recordAsOne)
{
if(!firstEvent && count > 0)
actions[count-1].continued = true;
firstEvent = false;
}
actions[count++] = action;
curAction = count;
if(actions.size > count + count / 2 && count + count / 2 >= 8)
actions.size = count + count / 2;
}
else
delete action;
}
};
static class AddCharAction : UndoAction
{
int y, x;
unichar ch;
int addedSpaces, addedTabs, xAdjustment;
type = class(AddCharAction);
void Undo(EditBox editBox)
{
editBox.GoToPosition(null, (ch == '\n') ? (y + 1) : y, (ch == '\n') ? 0 : (x + 1) - xAdjustment);
editBox.BackSpace();
if(addedTabs || addedSpaces)
{
editBox.DelCh(editBox.line, y, x - xAdjustment - (addedSpaces + addedTabs), editBox.line, y, x - xAdjustment, false);
editBox.GoToPosition(editBox.line, y, x);
}
editBox.UpdateDirty();
}
void Redo(EditBox editBox)
{
editBox.GoToPosition(null, y, x);
editBox.PutCh(ch);
editBox.UpdateDirty();
}
#ifdef _DEBUG
void Print(EditBox editBox)
{
PrintLn("AddChar: y = ", y, "x = ", x, ", ch = ", ch, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
}
#endif
};
static class AddTextAction : UndoAction
{
int y1, x1, y2, x2;
char * string;
int addedSpaces, addedTabs, xAdjustment;
type = class(AddTextAction);
#ifdef _DEBUG
void Print(EditBox editBox)
{
PrintLn("AddText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
}
#endif
~AddTextAction()
{
delete string;
}
void Undo(EditBox editBox)
{
EditLine l1, l2;
int c;
editBox.GoToPosition(null, y1, x1);
l1 = editBox.line;
l2 = editBox.lines.first;
for(c = 0; c < y2 && l2; c++, l2 = l2.next);
editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
if(addedTabs || addedSpaces)
editBox.DelCh(editBox.line, y1, x1 - (addedSpaces + addedTabs), editBox.line, y1, x1, false);
editBox.SetViewToCursor(true);
editBox.Modified();
}
void Redo(EditBox editBox)
{
editBox.GoToPosition(null, y1, x1);
editBox.PutS(string);
}
};
static class DelTextAction : UndoAction
{
int y1, x1, y2, x2;
char * string;
bool placeAfter, noHighlight;
int addedSpaces;
type = class(DelTextAction);
#ifdef _DEBUG
void Print(EditBox editBox)
{
PrintLn("DelText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", placeAfter = ", placeAfter);
}
#endif
void Undo(EditBox editBox)
{
editBox.GoToPosition(null, y1, x1);
editBox.PutS(string);
if(!placeAfter)
{
editBox.GoToPosition(null, y1, x1);
if(!noHighlight)
{
editBox.selY = y2;
editBox.selX = x2;
}
{ int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
//editBox.SetViewToCursor(true);
if(addedSpaces)
editBox.DelCh(editBox.line, y1, x1 - addedSpaces, editBox.line, y1, x1, false);
}
else
{
if(!noHighlight)
{
editBox.selY = y1;
editBox.selX = x1;
}
{ int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
//editBox.SetViewToCursor(true);
if(addedSpaces)
editBox.DelCh(editBox.selLine, y1, x1 - addedSpaces, editBox.selLine, y1, x1, false);
}
}
void Redo(EditBox editBox)
{
EditLine l1, l2;
int c;
editBox.GoToPosition(null, y1, x1);
l1 = editBox.line;
l2 = editBox.lines.first;
for(c = 0; c < y2 && l2; c++, l2 = l2.next);
editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
editBox.SetViewToCursor(true);
editBox.Modified();
}
~DelTextAction()
{
delete string;
}
};
static class ReplaceTextAction : UndoAction
{
int y1, x1, y2, x2, y3, x3;
char * oldString;
char * newString;
bool placeAfter;
int addedSpaces, addedTabs, xAdjustment;
type = class(ReplaceTextAction);
#ifdef _DEBUG
void Print(EditBox editBox)
{
PrintLn("ReplaceText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", y3 = ", y3, ", x3 = ", x3, ", oldString = ", oldString, ", newString = ", newString, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs, ", placeAfter = ", placeAfter);
}
#endif
void Undo(EditBox editBox)
{
EditLine l1, l3;
int c;
editBox.GoToPosition(null, y1, x1);
l1 = editBox.line;
l3 = editBox.lines.first;
for(c = 0; c < y3 && l3; c++, l3 = l3.next);
editBox.DelCh(l1, y1, x1, l3, y3, x3, true);
editBox.PutS(oldString);
if(addedSpaces || addedTabs)
editBox.DelCh(l1, y1, x1 - (addedSpaces + addedTabs), l1, y1, x1, false);
//editBox.PutS(oldString);
if(!placeAfter)
{
editBox.GoToPosition(null, y1, x1);
editBox.selY = y2;
editBox.selX = x2;
{ int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
}
else
{
editBox.selY = y1;
editBox.selX = x1;
{ int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
}
}
void Redo(EditBox editBox)
{
EditLine l1, l2;
int c;
editBox.GoToPosition(null, y1, x1);
l1 = editBox.line;
l2 = editBox.lines.first;
for(c = 0; c < y2 && l2; c++, l2 = l2.next);
editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
editBox.PutS(newString);
}
~ReplaceTextAction()
{
delete oldString;
delete newString;
}
};
/*
static class MoveTextAction : UndoAction
{
int fy1, fx1, fy2, fx2;
int ty, tx;
type = class(MoveTextAction);
void Undo(EditBox editBox)
{
}
void Redo(EditBox editBox)
{
}
};
*/
public class EditLine : struct
{
EditLine prev, next;
char * buffer;
uint size, count;
int length;
EditBox editBox;
public:
property const char * text
{
set
{
// Only works for single line edit for now...
EditBox editBox = this.editBox;
editBox.Clear();
editBox.PutS(text);
}
get { return this ? buffer : null; }
};
property EditLine prev { get { return this ? prev : null; } };
property EditLine next { get { return this ? next : null; } };
property int count { get { return this ? count : 0; } };
private:
void Free()
{
delete buffer;
}
// This makes sure the buffer always contains at least count characters
// Keeps a count/2 pad for avoiding always reallocating memory.
bool AdjustBuffer(int count)
{
char * buffer;
int newSize;
// Adds '\0' byte
count = count+1;
newSize = (count + (count >> 1));
// Shrink down the buffer
if(size > newSize)
{
buffer = new char[newSize];
if(!buffer) return false;
if(this.buffer)
{
CopyBytes(buffer, this.buffer, count);
delete this.buffer;
}
this.buffer = buffer;
size = newSize;
return true;
}
// Increase the buffer
else if(size < count)
{
buffer = new char[newSize];
if(!buffer) return false;
if(this.buffer)
{
CopyBytes(buffer, this.buffer, this.count + 1); // size);
delete this.buffer;
}
this.buffer = buffer;
size = newSize;
return true;
}
else
return true;
}
};
public struct BufferLocation
{
EditLine line;
int y, x;
void AdjustDelete(BufferLocation start, BufferLocation end)
{
// Location is before, nothing to do
if(y < start.y || (y == start.y && x < start.x))
return;
// Location is inside deleted bytes, point to the start
if((y >= start.y && (y > start.y || x >= start.x)) &&
(y >= end.y && (y > end.y || x >= end.x)))
{
// Location is after
if(y >= end.y)
{
// Location is on another line
if(y > end.y)
y -= end.y - start.y;
// Location is the last touched line
else
{
if(x >= end.x)
{
line = start.line;
y = start.y;
//if(start.line == end.line)
x -= end.x - start.x;
}
}
}
}
else
this = start;
}
// Assuming no carriage return before first character ???? fixed?
void AdjustAdd(BufferLocation start, BufferLocation end)
{
int numLines = end.y - start.y;
if(y >= start.y)
{
if(y > start.y)
y += numLines;
else
{
if(x > start.x || (x == start.x /*&& (numLines ? true : false)*/))
{
int c;
for(c = 0, line = start.line; c<numLines; c++)
line = line.next;
y += numLines;
//x += numLines ? end.x : (end.x - start.x);
x += end.x - start.x;
}
}
}
}
};
public enum EditBoxFindResult { notFound, found, wrapped };
static const char * keyWords1[] =
{
// C
"return","break","continue","default","switch","case","if","else","for","while", "do","long","short",
"void", "char","int","float","double","signed","unsigned","static", "extern", "struct", "union", "typedef","enum",
"const", "sizeof",
"#include", "#define", "#pragma", "#if", "#else", "#elif", "#ifdef", "#ifndef", "#endif", "#undef", "#line",
"__attribute__", "__stdcall", "_stdcall",
"__declspec", "goto",
"inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
"asm", "__asm", "_asm", "volatile", "#cpu", "__stdcall__",
"__restrict__", "__restrict", "restrict",
// eC
"class", "private", "public",
"property","import",
"delete", "new", "new0", "renew", "renew0", "define",
"get", "set",
"remote",
"dllexport", "dllimport", "stdcall",
"subclass", "__on_register_module", "namespace", "using",
"typed_object", "any_object", "incref", "register", "watch", "stopwatching", "firewatchers", "watchable", "class_designer",
"class_fixed", "class_no_expansion", "isset", "class_default_property", "property_category", "class_data", "class_property",
"virtual", "thisclass","unichar", "dbtable", "dbindex", "database_open", "dbfield",
// Types
"uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "uintptr", "intptr", "intsize", "uintsize",
// Values
"this", "true", "false", "null", "value",
// C++
"protected",
/* "defined" */
null
};
static const char * keyWords2[] =
{
"defined", "warning",
"include", "pragma", "elif", "ifdef", "ifndef", "endif", "undef", "line",
null
};
static const char ** keyWords[] = { keyWords1, keyWords2 };
#define NUM_KEYWORD_GROUPS (sizeof(keyWords) / sizeof(char **))
//static int * keyLen[NUM_KEYWORD_GROUPS];
static int keyLen[NUM_KEYWORD_GROUPS][sizeof(keyWords1)];
static char searchString[1025], replaceString[1025];
static bool matchCase = false, wholeWord = false, searchUp = false;
static GoToDialog goToDialog
{
autoCreate = false, isModal = true, text = $"Go To"
};
public class EditBox : CommonControl
{
class_property(icon) = "<:ecere>controls/editBox.png";
public:
// Notifications
virtual bool Window::NotifyModified(EditBox editBox);
virtual void Window::NotifyCaretMove(EditBox editBox, int line, int charPos);
virtual void Window::NotifyUpdate(EditBox editBox);
virtual bool Window::NotifyDoubleClick(EditBox editBox, EditLine line, Modifiers mods);
virtual void Window::NotifyOvrToggle(EditBox editBox, bool overwrite);
virtual bool Window::NotifyKeyDown(EditBox editBox, Key key, unichar ch);
virtual bool Window::NotifyCharsAdded(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation);
virtual bool Window::NotifyCharsDeleted(EditBox editBox, BufferLocation beforeLoc, BufferLocation after, bool pasteOperation);
virtual bool Window::NotifyDropped(EditBox editBox, int x, int y);
virtual bool Window::NotifyUnsetModified(EditBox editBox);
// Why was this commented out?
// It is required otherwise updating font property from property sheet doesn't immediately reflect in form designer,
// and the scrollArea property isn't compared properly either.
watch(font)
{
font = fontObject;
if(font) ComputeFont();
SetInitSize(initSize);
};
watch(hasVertScroll)
{
if(hasVertScroll)
style.vScroll = true;
};
watch(hasHorzScroll)
{
if(hasHorzScroll)
style.hScroll = true;
};
// Properties
property bool textHorzScroll { property_category $"Behavior" set { style.hScroll = value; } get { return style.hScroll; } }; // Should cut the text on set to false
property bool textVertScroll { property_category $"Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
property bool readOnly
{
property_category $"Behavior"
set
{
style.readOnly = value;
itemEditCut.disabled = value || !selection;
itemEditDelete.disabled = value || !selection;
itemEditPaste.disabled = value;
}
get { return style.readOnly; }
};
property bool multiLine { property_category $"Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
property bool freeCaret { property_category $"Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
property bool tabKey { property_category $"Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
property int tabSize { property_category $"Behavior" set { tabSize = value; } get { return tabSize; } };
property bool tabSelection { property_category $"Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
property bool smartHome { property_category $"Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
property bool autoEmpty { property_category $"Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
property bool noCaret { property_category $"Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
property int maxLineSize { property_category $"Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
property int maxNumLines { property_category $"Behavior" set { maxLines = value; } get { return maxLines; } };
property bool useTab { property_category $"Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
property bool syntaxHighlighting
{
property_category $"Appearance"
set
{
style.syntax = value;
if(value)
{
highlighting = SyntaxHighlighting { };
}
else
delete highlighting;
}
get { return style.syntax; }
};
property bool noSelect { property_category $"Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
property bool allCaps { property_category $"Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
property bool autoSize { property_category $"Behavior" set { style.autoSize = value; } get { return style.autoSize; } };
property bool wrap { set { style.wrap = value; Update(null); } get { return style.wrap; } };
//property bool lineNumbers { set { style.lineNumbers = value; } get { return style.lineNumbers; } };
property int numLines { get { return this ? lineCount : 0; } };
property int lineNumber { get { return y; } }; // TODO: Change to property of EditLine this.line.number
property int column { get { return col; } }; // TODO: Add Set
property int charPos { get { return x; } }; // TODO: Add Set
property EditLine firstLine { get { return lines.first; } }; // Change these to a List<EditLine>... (this.lines[10].text)
property EditLine lastLine { get { return lines.last; } };
property EditLine line { get { return this.line; } }; // TODO: Add Set this.line = this.lines[10]
property const char * contents
{
property_category $"Data"
set
{
if(this)
{
undoBuffer.dontRecord++;
Deselect();
DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
if(value)
AddS(value);
//SetViewToCursor(true);
UpdateDirty();
Home();
undoBuffer.dontRecord--;
}
}
get
{
char * buffer = null;
if(this)
{
/* Can't implement this right now because of memory leak... Need string reference counting...
if(style.multiLine)
{
EditLine line;
int len = 0;
for(line = lines.first; line; line = line.next)
len += strlen(line.buffer);
buffer = new char[len+1];
len = 0;
for(line = lines.first; line; line = line.next)
{
int lineLen = strlen(line.buffer);
memcpy(buffer + len, line.buffer, lineLen);
len += lineLen;
}
buffer[len] = '\0';
else
*/
buffer = this.line ? this.line.buffer : null;
}
return buffer;
}
};
property bool overwrite { get { return overwrite; } };
property bool caretFollowsScrolling { get { return style.cursorFollowsView; } set { style.cursorFollowsView = value; } }
property char * multiLineContents
{
get
{
char * buffer = null;
if(style.multiLine)
{
EditLine line;
int len = 0;
for(line = lines.first; line; line = line.next)
len += strlen(line.buffer)+1;
buffer = new char[len+1];
len = 0;
for(line = lines.first; line; line = line.next)
{
int lineLen = strlen(line.buffer);
memcpy(buffer + len, line.buffer, lineLen);
len += lineLen;
if(line.next) buffer[len++] = '\n';
}
buffer[len] = '\0';
}
return buffer;
}
}
/*
char * GetLineText()
{
if(this)
{
return this.line.buffer;
}
return null;
}
void SetLineText(const char * text)
{
if(this)
{
EditLine_SetText(this.line, text);
}
}
*/
property Color selectionColor { set { selectionColor = value; } get { return selectionColor; } isset { return selectionColor ? true : false; } };
property Color selectionText { set { selectionText = value; } get { return selectionText; } isset { return selectionText ? true : false; } };
property SyntaxColorScheme syntaxColorScheme { set { delete colorScheme; colorScheme = value; incref colorScheme; } }
property bool recordUndoEvent { set { undoBuffer.recordAsOne = value; undoBuffer.firstEvent = true; } get { return undoBuffer.recordAsOne; } };
// selectionStart.line, selectionStart.column (With Set)
// selection.line1, selection.line2, selection.column1, selection.column2 (Read only)
// Methods
private:
Font font;
EditBoxBits style;
int tabSize;
int maxLineSize;
int maxLines;
OldList lines;
int lineCount;
// Font Space size
Size space, large;
// Position of Caret (Not necessarily displayed position)
int x,y;
int col;
// Position of beginning of block (Block ends at (x,y))
int selX, selY;
// line is line at carret, selLine is line at beginning of block
EditLine line, selLine, dropLine;
// Mouse selection Moving virtual caret
int dropX, dropY;
bool selection;
// ViewX is x offset in pixels, ViewY is y offset in lines
int viewX, viewY;
// viewLine is first displayed line
EditLine viewLine;
// start and end of area to redraw
int startY, endY;
// MaxLine is the longest line
EditLine maxLine;
// MaxLength is the longest line's length
int maxLength;
// MouseSelect is true once button is pressed, overwrite is true if mode is on
bool mouseSelect, mouseMove, overwrite, wordSelect;
// Timer is used for mouse selection scrolling
Timer timer
{
window = this, delay = 0.1;
bool DelayExpired()
{
// LineDown();
/*
if(this.viewY > 200)
GoToLineNum(0);
*/
OnMouseMove(mouseX, mouseY, -1);
return true;
}
};
// (mouseX,mouseY) is the position of the mouse in the edit box
int mouseX, mouseY;
bool modified;
void (* FontExtent)(Display display, Font font, const char * text, int len, int * width, int * height, int prevGlyph, int * rPrevGlyph, int * overHang);
Color backColor;
bool rightButtonDown;
bool pasteOperation;
int caretX, caretY;
UndoBuffer undoBuffer { data = this };
int savedAction;
ColorAlpha selectionColor, selectionText;
SyntaxColorScheme colorScheme { };
SyntaxHighlighting highlighting;
menu = Menu { };
// Edit Menu
Menu editMenu { menu, $"Edit", e };
MenuItem itemEditCut
{
editMenu, $"Cut\tCtrl+X", t, disabled = true;
bool NotifySelect(MenuItem item, Modifiers mods)
{
if(!(style.readOnly))
Cut();
return true;
}
};
MenuItem itemEditCopy
{
editMenu, $"Copy\tCtrl+C", c, disabled = true;
bool NotifySelect(MenuItem item, Modifiers mods)
{
Copy();
return true;
}
};
MenuItem itemEditPaste
{
editMenu, $"Paste\tCtrl+V", p;
bool NotifySelect(MenuItem item, Modifiers mods)
{
if(!(style.readOnly))
Paste();
return true;
}
};
MenuItem itemEditDelete
{
editMenu, $"Delete\tDel", d, disabled = true;
bool NotifySelect(MenuItem item, Modifiers mods)
{
if(!(style.readOnly))
DeleteSelection();
return true;
}
};
MenuDivider { editMenu };
MenuItem itemEditSelectAll
{
editMenu, $"Select All\tCtrl+A", a;
bool NotifySelect(MenuItem item, Modifiers mods)
{
SelectAll();
return true;
}
};
MenuDivider { editMenu };
MenuItem itemEditUndo
{
editMenu, $"Undo\tCtrl+Z", u;
disabled = true;
bool NotifySelect(MenuItem item, Modifiers mods)
{
Undo();
return true;
}
};
MenuItem itemEditRedo
{
editMenu, $"Redo\tCtrl+Y", o;
disabled = true;
bool NotifySelect(MenuItem item, Modifiers mods)
{
Redo();
return true;
}
};
MenuDivider { editMenu };
MenuItem
{
editMenu, $"Find Previous\tShift-F3", e, shiftF3;
bool NotifySelect(MenuItem item, Modifiers mods)
{
if(searchString[0])
Find(searchString, wholeWord, matchCase, false);
else
itemEditFind.NotifySelect(this, item, mods);
return true;
}
};
MenuItem
{
editMenu, $"Find Next\tF3", n, f3;
bool NotifySelect(MenuItem item, Modifiers mods)
{
if(searchString[0])
Find(searchString, wholeWord, matchCase, true);
else
itemEditFind.NotifySelect(this, item, mods);
return true;
}
};
MenuItem itemEditFind
{
editMenu, $"Find...\tCtrl+F", f, ctrlF;
bool NotifySelect(MenuItem item, Modifiers mods)
{
FindDialog dialog
{
editBox = this, master = master, isModal = true, searchString = searchString, matchCase = matchCase, wholeWord = wholeWord,
searchUp = searchUp;
// TODO:
// Fix dialog from above shouldn't be visible
// void NotifyDestroyed(FindDialog dialog, DialogResult result)
void NotifyDestroyed(Window window, DialogResult result)
{
FindDialog dialog = (FindDialog) window;
searchUp = dialog.searchUp;
strcpy(searchString, dialog.searchString);
matchCase = dialog.matchCase;
wholeWord = dialog.wholeWord;
}
};
dialog.Create();
return true;
}
};
MenuItem
{
editMenu, $"Replace...\tCtrl+R", r, ctrlR;
bool NotifySelect(MenuItem item, Modifiers mods)
{
ReplaceDialog dialog
{
master = master,
isModal = true,
searchString = searchString,
replaceString = replaceString,
matchCase = matchCase,
wholeWord = wholeWord,
editBox = this;
// TODO:
// void NotifyDestroyed(ReplaceDialog dialog, DialogResult result)
void NotifyDestroyed(Window window, DialogResult result)
{
ReplaceDialog dialog = (ReplaceDialog)window;
const char * replace = dialog.replaceString;
if(replace)
strcpy(replaceString, replace);
strcpy(searchString, dialog.searchString);
matchCase = dialog.matchCase;
wholeWord = dialog.wholeWord;
}
};
dialog.Create();
return true;
}
};
MenuDivider { editMenu };
MenuItem
{
editMenu, $"Go To...\tCtrl+G", g, ctrlG;
bool NotifySelect(MenuItem item, Modifiers mods)
{
goToDialog.editBox = this;
goToDialog.master = master;
goToDialog.Create();
return true;
}
};
MenuDivider { editMenu };
MenuItem itemEditInsertTab
{
editMenu, $"Insert Tabs", i, checkable = true;
bool NotifySelect(MenuItem item, Modifiers mods)
{
style.useTab = item.checked;
return true;
}
};
borderStyle = deep;
snapVertScroll = true;
snapHorzScroll = true;
EditBox()
{
static bool syntaxInit = false;
if(!syntaxInit)
{
int g,c;
syntaxInit = true;
for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
{
for(c = 0; keyWords[g][c]; c++);
//keyLen[g] = new int[c];
for(c = 0; keyWords[g][c]; c++)
{
keyLen[g][c] = strlen(keyWords[g][c]);
}
}
colorScheme.commentColor = dimGray;
colorScheme.charLiteralColor = crimson;
colorScheme.stringLiteralColor = crimson;
colorScheme.preprocessorColor = green;
colorScheme.numberColor = teal;
colorScheme.keywordColors = [ blue, blue ];
}
FontExtent = Display::FontExtent2;
font = fontObject;
lines.offset = (uint)(uintptr)&((EditLine)0).prev;
style = EditBoxBits { hScroll = true };
// cursor = guiApp.GetCursor(IBeam);
// Default Properties
/*maxLines = 65536;
maxLineSize = 1024;*/
maxLines = MAXINT;
maxLineSize = MAXINT;
tabSize = 3;
overwrite = false;
mouseSelect = this.mouseMove = false;
line = null;
lineCount = 0;
x = selX = selY = 0;
col = 0;
y = -1;
line = selLine = null;
viewX = 0;
viewY = 0;
maxLength = 0;
maxLine = null;
startY = 0;
endY = clientSize.h;
wordSelect = false;
// Default space to 8 in case window is not constructed
space = { 8, 8 };
undoBuffer.dontRecord++;
AddCh('\n');
undoBuffer.dontRecord--;
viewLine = lines.first;
style.recomputeSyntax = true;
FindMaxLine();
FixScrollArea();
UpdateCaretPosition(true);
return true;
}
~EditBox()
{
lines.Free(EditLine::Free);
}
void FlushBuffer(Surface surface, EditLine line, int wc, int * renderStart, int * x, int y, int * previousGlyph, int * oh, int numSpaces, bool drawSpaces, Box box)
{
int count = wc - *renderStart;
if(count)
{
if(y + space.h >= box.top && y <= box.bottom)
{
int w;
if(!numSpaces)
{
int coh;
//FontExtent(display, font, line.buffer + *renderStart, count, &w, null);
surface.TextFont(font);
surface.TextExtent2(line.buffer + *renderStart, count, &w, null, *previousGlyph, null, &coh);
if(*x + w + coh + XOFFSET > 0)
{
surface.WriteText2(XOFFSET + *x,y, line.buffer + *renderStart, count, *previousGlyph, previousGlyph);
*oh = coh;
//surface.WriteText(XOFFSET + *x,y, line.buffer + *renderStart, count);
}
*x += w;
}
else
{
w = numSpaces; // * space.w;
if(*x + w + XOFFSET > 0 && (drawSpaces))
surface.Area(XOFFSET + *x /*- 1*/ + *oh, y, XOFFSET + *x + w, y + space.h-1);
// WHATS UP WITH THIS... surface.Area(XOFFSET + *x, y, XOFFSET + *x + w, y + space.h-1);
*x += w;
}
}
*renderStart = wc;
}
}
bool CheckColors(EditLine line, int wc, bool selection, int selX, int editX, bool *selected,
Color selectionForeground, Color selectionBackground, Color textColor, Color *foreground, Color *background, bool *opacity, int *overwrite)
{
bool flush = false;
if(selection)
{
if((wc == selX && line == selLine) || (wc == editX && line == this.line))
{
*selected ^= 1;
*foreground = (*selected) ? selectionForeground : textColor;
*background = selectionBackground;
*opacity = *selected;
flush = true;
}
}
// Overwrite Carret
if(this.overwrite && active)
{
if((style.stuckCaret && wc == line.count && !line.next) ||
(!mouseMove && line == this.line && wc == editX))
{
*overwrite = 1;
flush = true;
}
}
return flush;
}
/*void OnDrawOverChildren(Surface surface)
{
if(style.lineNumbers)
{
int currentLineNumber = this.viewY + 1;
int i;
char lineText[256];
for(i = 0; i * space.h < box.height; i++)
{
// ********* LINE NUMBERING *********
surface.SetForeground(Color{60, 60, 60});
//Highlight current line
if(this.caretY / space.h == currentLineNumber - 1)
surface.SetBackground(Color{220, 220, 220});
else
surface.SetBackground(Color{230, 230, 230});
surface.textOpacity = true;
sprintf(lineText,"%5u ", currentLineNumber % 100000);
if(currentLineNumber > this.lineCount)
surface.WriteText(0,i*space.h+1," ",6);
else
surface.WriteText(0,i*space.h+1,lineText,6);
currentLineNumber++;
}
}
}*/
static inline int countTabsExtraSpaces(char * buffer, int tabSize, int start, int end)
{
// TODO: Handle tabs position properly with UTF-8 (and everywhere else)
int p = 0, i, extra = 0;
for(i = 0; i < end; i++)
{
if(buffer[i] == '\t')
{
int t = tabSize - (p % tabSize);
p += t;
if(i >= start)
extra += t-1;
}
else
p++;
}
return extra;
}
void OnRedraw(Surface surface)
{
EditLine line;
int y = YOFFSET;
bool selected = false, selection = true;
int selX, editX;
Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
Color defaultTextColor = property::foreground;
Color textColor;
Box box;
int maxW = clientSize.w;
Color foreground, background;
bool opacity;
// Overwrite Caret Stuff
int overWrite = 0;
int overWriteX = 0, overWriteY = 0;
char overWriteCh;
// For drawing selection background
EditLine selStartLine = this.y < this.selY ? this.line : this.selLine;
EditLine selEndLine = this.y < this.selY ? this.selLine : this.line;
int selStartX = this.y < this.selY || (this.y == this.selY && this.x < this.selX) ? this.x : this.selX;
int selEndX = this.y > this.selY || (this.y == this.selY && this.x > this.selX) ? this.x : this.selX;
///////////////////////////////////
if(!isEnabled)
defaultTextColor = Color { 85, 85, 85 };
textColor = defaultTextColor;
if(
Abs(selectionBackground.r - property::background.r) +
Abs(selectionBackground.g - property::background.g) +
Abs(selectionBackground.b - property::background.b) < 92)
{
selectionBackground = formColor;
selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
}
surface.TextFont(this.font);
/*
surface.SetBackground(GDefaultPalette()[RND_Get(1, 15)]);
surface.Area(0,0,MAXINT,MAXINT);
*/
if(!isEnabled)
{
surface.SetBackground(formColor);
surface.Area(0,0,clientSize.w, clientSize.h);
}
if(this.selX == this.x && this.selY == this.y)
selection = false;
if(style.freeCaret)
{
editX = this.x;
selX = this.selX;
}
else
{
editX = Min(this.x,this.line.count);
selX = Min(this.selX,this.selLine.count);
}
selected = (this.selY < this.viewY) ^ (this.y < this.viewY);
foreground = selected ? selectionForeground : textColor;
background = selectionBackground;
opacity = selected;
/*
opacity = true;
background = WHITE;
*/
surface.SetForeground(foreground);
surface.SetBackground(background);
surface.TextOpacity(false);
surface.GetBox(box);
for(line = this.viewLine; line; line = line.next)
{
int x = -this.viewX;
int end;
int c;
int start = 0;
Color newTextColor = textColor = defaultTextColor;
bool lineComplete = false;
int overHang = 0;
bool trailingSpace = false;
highlighting.StartLine();
/* === DEBUGGING TOOL FOR MAXLINE ===
if(line == this.maxLine)
{
surface.SetBackground(GREEN|0xFF000000);
surface.TextOpacity(true);
}
else
{
surface.TextOpacity(selected ? true : false);
surface.SetBackground(selected ? SELECTION_COLOR|0xFF000000 : BLACK|0xFF000000);
}
*/
// Draw Selection Background all at once here
if(selected || line == this.line || line == this.selLine)
{
int sx = XOFFSET + x, sy = y;
int tw, th;
int oh = 0;
char * buffer = line.buffer;
if(line != this.line && line != this.selLine)
{
if(style.freeCaret)
{
tw = clientSize.w - sx;
th = space.h;
}
else
{
surface.TextExtent2(buffer, line.count, &tw, &th, 0, null, &oh);
tw += countTabsExtraSpaces(buffer, tabSize, 0, line.count) * space.w;
}
}
else if(line == selStartLine)
{
int prevGlyph = 0;
int start = Min(line.count, selStartX);
int end = Min(line.count, selEndX);
surface.TextExtent2(buffer, start, &tw, &th, 0, &prevGlyph, null);
sx += tw;
sx += countTabsExtraSpaces(buffer, tabSize, 0, start) * space.w;
if(selStartX > start) sx += space.w * (selStartX - start);
if(style.freeCaret && line != selEndLine)
{
tw = clientSize.w - sx;
th = space.h;
}
else if(line != selEndLine)
{
surface.TextExtent2(buffer + start, line.count - start, &tw, &th, prevGlyph, null, &oh);
tw += countTabsExtraSpaces(buffer, tabSize, start, line.count) * space.w;
}
else
{
surface.TextExtent2(buffer + start, end - start, &tw, &th, prevGlyph, null, &oh);
tw += countTabsExtraSpaces(buffer, tabSize, start, end) * space.w;
end = Max(end, selStartX);
if(selEndX > end) { tw += space.w * (selEndX - end); th = space.h; }
}
}
else if(line == selEndLine)
{
int end = Min(line.count, selEndX);
surface.TextExtent2(buffer, end, &tw, &th, 0, null, &oh);
tw += countTabsExtraSpaces(buffer, tabSize, 0, end) * space.w;
if(selEndX - end) { tw += space.w * (selEndX - end); th = space.h; }
}
tw += oh;
surface.Area(sx, sy, sx + tw-1, sy + th-1);
}
if(line == this.selLine && line == this.line)
{
end = Max(line.count, this.x);
end = Max(end, this.selX);
}
else if(line == this.selLine)
end = Max(line.count, this.selX);
else if(line == this.line)
end = Max(line.count, this.x);
else
end = line.count;
for(c=0; c<end; )
{
int bufferLen = 0;
int wordLen = 0;
bool spacing = true;
bool cantHaveWords = false;
int w = 0;
if(lineComplete)
{
x = -viewX;
y += space.h;
lineComplete = false;
}
textColor = newTextColor;
if(!selected)
{
foreground = textColor;
surface.SetForeground(textColor);
}
// Look at words
for(; c <end && !cantHaveWords;)
{
if(wordLen)
{
bufferLen += wordLen;
wordLen = 0;
}
#ifdef _DEBUG
/*if(line.count > 4000)
{
printf("oh");
CheckMemory();
}*/
#endif
// Parse Words
{
int wc = highlighting.ParseWord(line.buffer + c, line.count - c, wordLen);
if(wc)
{
wordLen += wc;
c += wc;
trailingSpace = false;
}
}
if(!wordLen)
{
for(; c<line.count; c++)
{
unichar ch = line.buffer[c];
if(ch == '\t' || ch == ' ')
{
cantHaveWords = true;
if(ch == ' ' && c == line.count-1)
trailingSpace = true;
if(bufferLen)
break;
}
if(ch != ' ' && ch != '\t')
break;
wordLen++;
}
if(c == line.count && c < end)
{
if(bufferLen)
{
c -= wordLen;
break;
}
wordLen += end - c;
c = end;
cantHaveWords = true;
}
highlighting.GotSpace(c < end);
}
else
{
if(isEnabled && style.syntax)
{
newTextColor = highlighting.Process(line.buffer + c - wordLen, wordLen, x < box.right,
defaultTextColor, colorScheme);
// If highlighting for this word is different, break
if(newTextColor != textColor)
{
if(bufferLen)
{
// Better solution than going back?
c -= wordLen;
// Reset syntax flags
highlighting.ResetState();
break;
}
else
{
textColor = newTextColor;
if(!selected)
{
foreground = textColor;
surface.SetForeground(textColor);
}
}
}
}
// If we're not breaking, this can't be rendered as spacing anymore
spacing = false;
// Do word wrapping here
if(style.wrap)
{
//if(!numSpaces)
{
int tw;
int oh;
FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null, 0, null, &oh);
w = tw;
}
/*else
{
w += numSpaces * space.w;
}*/
if(x + viewX + w > maxW)
{
// Avoid an endless loop until we fix wrapping
if(c - wordLen > start)
{
c -= wordLen;
lineComplete = true;
break;
}
}
}
}
bufferLen += wordLen;
wordLen = 0;
}
{
int renderStart = start;
bool flush = false;
bool flagTrailingSpace = false;
int numSpaces = 0;
int wc;
int previousGlyph = 0;
// Render checking if we need to split because of selection or to find where to draw insert caret
for(wc = start; wc < start + bufferLen; wc++)
{
flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
if(overWrite == 1)
{
overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
if(overWriteCh == '\t') overWriteCh = ' ';
}
if(flush)
{
flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
if(flagTrailingSpace) surface.SetBackground(red);
FlushBuffer(surface, line, wc, &renderStart, &x, y, &previousGlyph, &overHang, numSpaces, flagTrailingSpace, box);
if(flagTrailingSpace) surface.SetBackground(background);
if(overWrite == 1)
{
overWriteX = x;
overWriteY = y;
overWrite = 2;
}
numSpaces = 0;
surface.SetForeground(foreground);
flush = false;
}
if(spacing)
{
if(wc < line.count && line.buffer[wc] == '\t')
{
numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
}
else
{
numSpaces += space.w;
}
}
}
flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
if(flagTrailingSpace) surface.SetBackground(red);
FlushBuffer(surface, line, wc, &renderStart, &x, y, &previousGlyph, &overHang, numSpaces, flagTrailingSpace, box);
if(flagTrailingSpace) surface.SetBackground(background);
start += bufferLen;
}
}
if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
{
if(overWrite == 1)
{
overWriteX = x;
overWriteY = y;
overWriteCh = ' ';
overWrite = 2;
}
surface.SetBackground(background);
surface.SetForeground(foreground);
}
highlighting.NextLine(line.buffer, line.count);
y+=this.space.h;
if(y > box.bottom) // >=clientSize.h)
break;
}
if(overWrite)
{
surface.TextOpacity(true);
surface.SetForeground(black);
surface.SetBackground(Color {255,255,85});
surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
}
}
void FixScrollArea()
{
if(style.hScroll || style.vScroll)
{
int width = maxLength + XOFFSET;
int height = lineCount * space.h;
if(style.freeCaret && line)
{
if(x > selX)
{
if(x > line.count)
width = Max(line.length + (x - line.count) * space.w, maxLength + XOFFSET);
}
else
{
if(selX > selLine.count)
width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength + XOFFSET);
}
}
width += space.w;
SetScrollLineStep(8, space.h);
SetScrollArea(width, height, true);
}
}
void ComputeLength(EditLine line)
{
int c;
int x = 0;
for(c = 0; c < line.count; )
{
int len = 1;
int start = c;
int w;
if(c < line.count)
{
byte ch = 0;
int numBytes;
for(len = 0; c < line.count; c += numBytes)
{
ch = line.buffer[c];
numBytes = UTF8_NUM_BYTES(ch);
if(ch == ' ' || ch == '\t')
{
if(!len) c++;
break;
}
len += numBytes;
}
if(!len && ch == ' ')
{
len = 1;
w = space.w;
}
else if(!len && ch == '\t')
{
w = (tabSize * space.w) - (x % (tabSize * space.w));
len = 1;
}
else
{
int oh;
FontExtent(display, font, line.buffer + start, len, &w, null, 0, null, &oh);
}
}
else
{
w = space.w;
c++;
}
x += w;
}
line.length = x;
if(line.length > this.maxLength)
{
this.maxLine = line;
this.maxLength = line.length;
if(style.autoSize) AutoSize();
}
}
void FindMaxLine()
{
EditLine line;
this.maxLength = 0;
this.maxLine = null;
for(line = lines.first; line; line = line.next)
{
if(line.length > this.maxLength)
{
this.maxLength = line.length;
this.maxLine = line;
}
}
if(style.autoSize) AutoSize();
}
void SelDirty()
{
if(this.selY != this.y)
DirtyAll();
else if(this.selX != this.x) // commented out to erase caret: if(this.selX != this.x)
DirtyLine(this.y);
}
void ComputeColumn()
{
// Adjust new edit X position according to tabs
int c, position = 0;
unichar ch;
int nb;
for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
{
// TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
if(ch == '\t')
position += this.tabSize - (position % this.tabSize);
else
position ++;
}
position += this.x - c;
this.col = position;
}
void DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
{
_DelCh(l1, y1, c1, l2, y2, c2, placeAfter, true, null);
}
int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, bool highlight, int * addedSpacesPtr)
{
EditLine line = l1, next;
char * buffer;
int oldCount1, oldCount2;
int y, firstViewY, firstY, firstDropY, firstSelY;
int newLineCount;
int extras = 0;
DelTextAction action = null;
bool hadComment = false;
int start = c1;
if(style.syntax)
hadComment = highlighting.CanImpactOtherLines(line);
if(y2 > y1 || c2 > c1)
{
if(start < l1.count)
{
while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
start--;
}
}
oldCount1 = l1.count;
buffer = l1.buffer;
while(c1 < oldCount1)
{
byte ch = buffer[c1];
if(UTF8_IS_FIRST(ch)) break;
c1--;
extras++;
}
oldCount2 = l2.count;
buffer = l2.buffer;
while(c2 < oldCount2)
{
byte ch = buffer[c2];
if(UTF8_IS_FIRST(ch)) break;
c2++;
extras++;
}
if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
{
int len;
char * string;
len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
string = new char[len];
action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter, noHighlight = !highlight };
GetText(string, l1, y1, start, l2, y2, c2, false, false);
Record(action);
}
//oldCount1 = l1.count;
//oldCount2 = l2.count;
{
BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
}
if(c2 > oldCount2) c2 = oldCount2;
if(!(style.freeCaret))
if(c1 > oldCount1) c1 = oldCount1;
newLineCount = c1 + l2.count-c2;
if(l1 == l2)
{
/*
if(!line.size)
printf("");
buffer = new char[line.size ? line.size : 1];
*/
buffer = new char[line.size];
// TODO: Better handling of these allocation failures
if(!buffer) return extras;
CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
}
else
buffer = l2.buffer;
// TODO: Better handling of these allocation failures
if(!line.AdjustBuffer(newLineCount))
return extras;
#ifdef _DEBUG
/*if(newLineCount > 4000 || newLineCount < 0)
printf("Warning");*/
#endif
line.count = newLineCount;
memmove(l1.buffer+c1, buffer+c2,line.count-c1);
if(c1>oldCount1)
{
if(action)
{
action.addedSpaces = c1-oldCount1;
#ifdef _DEBUG
if(action.addedSpaces > action.x1)
{
printf("bug!");
}
#endif
}
if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
}
line.buffer[line.count] = '\0';
if(l1 == l2)
{
delete buffer;
DirtyLine(y1);
/*
this.dropX -= c2-c1;
this.dropX = Max(this.dropX,0);
this.x -= c2-c1-1;
this.x = Max(this.x,0);
*/
}
else
DirtyEnd(y1);
y = y1;
firstViewY = this.viewY;
firstY = this.y;
firstDropY = this.dropY;
firstSelY = this.selY;
for(line = l1;line;line = next, y++)
{
next = line.next;
if(line!=l1)
{
this.lineCount--;
delete line.buffer;
if(line == this.viewLine)
{
if(this.viewLine.next)
{
this.viewLine = this.viewLine.next;
//this.viewY++;
style.recomputeSyntax = true;
}
else
{
this.viewLine = this.viewLine.prev;
this.viewY--;
style.recomputeSyntax = true;
}
}
else if(y < firstViewY)
this.viewY--;
if(line == this.line)
{
if(this.line.next)
{
this.line = this.line.next;
this.x = this.line.count;
//this.y++;
}
else
{
this.line = this.line.prev;
this.x = this.line.count;
this.y--;
}
ComputeColumn();
}
else if(y < firstY)
this.y--;
if(line == this.dropLine)
{
if(this.dropLine.next)
{
this.dropLine = this.dropLine.next;
this.dropX = this.dropLine.count;
}
else
{
this.dropLine = this.dropLine.prev;
this.dropX = this.dropLine.count;
this.dropY--;
}
}
else if(y < firstDropY)
this.dropY--;
if(line == this.selLine)
{
if(this.selLine.next)
{
this.selLine = this.selLine.next;
this.selX = this.selLine.count;
}
else
{
this.selLine = this.selLine.prev;
this.selX = this.selLine.count;
this.selY--;
}
}
else if(y < firstSelY)
this.selY--;
lines.Delete(line);
}
if(line == l2) break;
}
ComputeLength(l1);
FindMaxLine();
if(style.syntax && (hadComment || highlighting.CanImpactOtherLines(this.line)))
{
DirtyAll();
style.recomputeSyntax = true;
}
return extras;
}
bool DelSel(int * addedSpacesPtr)
{
if(this.line != this.selLine || this.x != this.selX)
{
if(this.selY < this.y)
{
_DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
this.x = this.selX;
this.y = this.selY;
this.line = this.selLine;
}
else if(this.selY > this.y)
{
_DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
this.selX = this.x;
this.selY = this.y;
this.selLine = this.line;
}
else if(this.selX < this.x)
{
_DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
this.x = this.selX;
this.y = this.selY;
this.line = this.selLine;
}
else
{
_DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
this.selX = this.x;
this.selY = this.y;
this.selLine = this.line;
}
ComputeColumn();
return true;
}
return false;
}
bool AddToLine(const char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr, int * xAdjustmentPtr)
{
bool hadComment = false;
// Add the line here
EditLine line = this.line;
// The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
if(!style.hScroll && created)
{
int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
int max;
int x;
int c;
// Lock if no place to display.
if(LFComing)
max = endX;
else if(endX > this.x)
max = Max(this.x, line.count);
else
max = line.count;
for(x = 0, c = 0; c < max+count; )
{
int w;
int numBytes = 1;
const char * string;
if(c < Min(this.x, line.count))
string = line.buffer + c;
else if(c < endX)
string = " ";
else if(c < endX + count)
string = stringLine + c - endX;
else
string = line.buffer + c - endX - count;
if(*string == '\t')
{
w = (tabSize * space.w) - (x % (tabSize * space.w));
}
else
{
int oh;
numBytes = UTF8_NUM_BYTES(*string);
FontExtent(display, this.font, string, numBytes, &w, null, 0, null, &oh);
}
x += w;
if(x >= clientSize.w)
{
count = c - max;
break;
}
c += numBytes; // - 1;
}
}
if(count > 0)
{
int addedSpaces = 0;
int addedTabs = 0;
int xAdjustment = 0;
// Add blank spaces if EES_FREECARET
if(this.x > line.count)
{
if(style.freeCaret)
{
if(style.useTab)
{
int wantedPosition;
int position = 0;
int c;
for(c = 0; c<line.count; c++)
{
if(this.line.buffer[c] == '\t')
position += this.tabSize - (position % this.tabSize);
else
position ++;
}
wantedPosition = position + (this.x - line.count);
// A tab is too much...
if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
addedSpaces = wantedPosition - position;
else
{
xAdjustment = wantedPosition - position;
// Put a first tab
addedTabs = 1;
position += this.tabSize - (position % this.tabSize);
// Add as many tabs as needed
addedTabs += (wantedPosition - position) / this.tabSize;
position += (addedTabs-1) * this.tabSize;
// Finish off with spaces
addedSpaces = wantedPosition - position;
xAdjustment -= addedSpaces + addedTabs;
}
}
else
addedSpaces = this.x - line.count;
}
}
this.x = Min(this.x, line.count);
if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
return false;
DirtyLine(this.y);
if(style.syntax)
hadComment = highlighting.CanImpactOtherLines(line);
// Adjust the size of the line
if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
return false;
{
BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
bool hasComment;
memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
if(addedTabs)
{
FillBytes(line.buffer+line.count,'\t',addedTabs);
#ifdef _DEBUG
if(addedTabs > 4000 || addedTabs < 0)
printf("Warning");
#endif
line.count += addedTabs;
}
if(addedSpaces)
{
FillBytes(line.buffer+line.count,' ',addedSpaces);
#ifdef _DEBUG
if(addedSpaces > 4000 || addedSpaces < 0)
printf("Warning");
#endif
line.count += addedSpaces;
}
if(addedTabsPtr) *addedTabsPtr = addedTabs;
if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
if(xAdjustmentPtr) *xAdjustmentPtr = xAdjustment;
#ifdef _DEBUG
if(count > 4000 || count < 0)
printf("Warning");
#endif
line.count += count;
this.x += count + addedTabs + addedSpaces;
this.selX = this.x;
this.selY = this.y;
this.selLine = this.line;
line.buffer[line.count] = '\0';
ComputeLength(line);
ComputeColumn();
after.x = this.x;
hasComment = highlighting.CanImpactOtherLines(line);
if(!undoBuffer.insideRedo)
{
int backDontRecord = undoBuffer.dontRecord;
undoBuffer.dontRecord = 0;
NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
undoBuffer.dontRecord = backDontRecord;
}
if(style.syntax && (hadComment || hasComment || line != this.line))
{
style.recomputeSyntax = true;
DirtyAll();
}
}
}
return true;
}
void Emptyline(EditLine line, int y)
{
if(line.next)
DelCh(line, y, 0, line.next, y+1, 0, true);
else
DelCh(line, y, 0, line, y, line.count, true);
}
void GoToEnd(bool deselect)
{
if(this.line)
{
this.line = this.lines.last;
if(this.y != this.lineCount-1)
DirtyAll();
else if (this.x != this.line.count)
DirtyLine(this.lineCount-1);
this.y = this.lineCount-1;
this.x = this.line.count;
ComputeColumn();
if(deselect)
_Deselect();
}
}
void GoToHome(bool deselect)
{
if(this.line)
{
this.line = this.lines.first;
if(this.y != 0)
DirtyAll();
else if (this.x !=0)
DirtyLine(this.lineCount-1);
this.y = 0;
this.x = 0;
this.col = 0;
if(deselect)
_Deselect();
}
}
// Returns true if it needs scrolling
bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
{
int c;
int y;
EditLine line;
bool needHScroll = false;
if(py < 0)
{
if(this.viewY > 0)
{
y = this.viewY-1;
line = this.viewLine ? (void *)this.viewLine.prev : null;
}
else
{
y = 0;
line = (void *)this.lines.first;
}
}
else
{
py = Min(py, clientSize.h);
py /= this.space.h;
py = Min(py, this.lineCount);
y = this.viewY;
for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
{
y++;
}
}
if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
needHScroll = true;
px = Max(px,0);
px = Min(px,clientSize.w+this.space.w);
if(tx && line)
{
*tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
}
if(tline) *tline = line;
if(ty) *ty = y;
// Prevent divide by 0 from non valid this.font
if(!this.space.h)
return (y < this.viewY) || needHScroll;
else
return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
return false;
}
// Minimal Update Management Functions
void DirtyAll()
{
this.startY = 0;
this.endY = clientSize.h-1;
// ErrorLog("DirtyAll\n");
}
void DirtyEnd(int y)
{
if((y - this.viewY)*this.space.h < this.startY)
this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
this.endY = clientSize.h-1;
//ErrorLog("DirtyEnd %d\n", y);
}
void DirtyLine(int y)
{
if(y >= this.viewY)
{
if((y - this.viewY)*this.space.h < this.startY)
this.startY = (y - this.viewY)*this.space.h + YOFFSET;
if((y - this.viewY+1)*this.space.h > this.endY)
this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
}
//ErrorLog("DirtyLine %d\n", y);
}
void UpdateDirty()
{
Box box;
if(style.recomputeSyntax && style.syntax)
{
highlighting.FigureStartSyntaxStates(lines.first, true, lines.first);
style.recomputeSyntax = false;
}
box.left = 0;
if(this.startY > this.endY) return;
if(this.startY <= 0 && this.endY >= clientSize.h-1)
{
Update(null);
}
else
{
box.right = clientSize.w-1;
box.top = this.startY;
box.bottom = this.endY;
Update(box);
}
this.startY = clientSize.h;
this.endY = 0;
}
bool IsMouseOnSelection()
{
bool mouseOnSelection = false;
int x, y;
int minY = Min(this.selY, this.y);
int maxY = Max(this.selY, this.y);
int minX = Min(this.selX, this.x);
int maxX = Max(this.selX, this.x);
FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
if(maxX != minX || maxY != minY)
{
if(y > minY && y < maxY)
mouseOnSelection = true;
else if(y == minY && y == maxY)
mouseOnSelection = (x < maxX && x >= minX);
else if(y == minY)
{
if(y == this.selY)
mouseOnSelection = (x >= this.selX);
else if(y == this.y)
mouseOnSelection = (x >= this.x);
}
else if(y == maxY)
{
if(y == this.selY)
mouseOnSelection = (x < this.selX);
else if(y == this.y)
mouseOnSelection = (x < this.x);
}
}
return mouseOnSelection;
}
void UpdateCaretPosition(bool setCaret)
{
if(line)
{
if(mouseMove || !style.noCaret)
{
int max = this.mouseMove ? this.dropX : this.x;
int y = this.mouseMove ? this.dropY : this.y;
EditLine line = this.mouseMove ? this.dropLine : this.line;
int c, x = 0;
if(!(style.freeCaret))
max = Min(max, line.count);
if(FontExtent && display)
{
for(c = 0; c < max; )
{
int len = 1;
int start = c;
int w;
if(c < line.count)
{
byte ch = 0;
int numBytes;
for(len = 0; c < Min(max, line.count); c += numBytes)
{
ch = line.buffer[c];
numBytes = UTF8_NUM_BYTES(ch);
if(ch == ' ' || ch == '\t')
{
if(!len) c++;
break;
}
len += numBytes;
}
if(!len && ch == ' ')
{
w = space.w;
len = 1;
}
else if(!len && ch == '\t')
{
w = (tabSize * space.w) - (x % (tabSize * space.w));
len = 1;
}
else
{
int oh;
FontExtent(display, this.font, line.buffer + start, len, &w, null, 0, null, &oh);
}
}
else
{
w = space.w;
c++;
}
x += w;
}
}
if(setCaret)
caretX = x;
caretY = y * this.space.h;
if(!overwrite)
SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
else
SetCaret(0, 0, 0);
}
else
SetCaret(0, 0, 0);
// TOFIX: Mismatch between NotifyCaretMove() and NotifyDropped() / GoToPosition()
NotifyCaretMove(master, this, y + 1, x + 1);
SelectionEnables();
}
}
void SelectionEnables()
{
if((x != selX || y != selY) && !selection)
{
if(!style.readOnly)
{
itemEditCut.disabled = false;
itemEditDelete.disabled = false;
}
itemEditCopy.disabled = false;
this.selection = true;
}
else if((x == selX && y == selY) && selection)
{
itemEditCut.disabled = true;
itemEditCopy.disabled = true;
itemEditDelete.disabled = true;
this.selection = false;
}
}
void SetSelectCursor()
{
if(!inactive || !style.noSelect)
{
if(this.mouseMove)
cursor = guiApp.GetCursor(arrow);
else if(this.mouseSelect)
cursor = guiApp.GetCursor(iBeam);
else
{
if(IsMouseOnSelection())
cursor = guiApp.GetCursor(arrow);
else
cursor = guiApp.GetCursor(iBeam);
}
}
}
int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
{
int c = sc;
int x = px ? *px : 0;
while(true)
{
int start = c;
int len = 1;
int w;
if(c < Min(max, line.count))
{
byte ch = 0;
int numBytes;
for(len = 0; c < Min(max, line.count); c += numBytes)
{
ch = line.buffer[c];
numBytes = UTF8_NUM_BYTES(ch);
if(ch == ' ' || ch == '\t')
{
if(!len) c++;
break;
}
len += numBytes;
}
if(!len && ch == ' ')
{
w = space.w;
len = 1;
}
else if(!len && ch == '\t')
{
w = (tabSize * space.w) - (x % (tabSize * space.w));
len = 1;
}
else
{
int oh;
FontExtent(display, font, line.buffer + start, len, &w, null, 0, null, &oh);
}
}
else
{
if(style.freeCaret && c < max)
w = space.w;
else
{
if(px) *px = x;
return c;
}
c++;
}
if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
{
int lastW;
while(len > 0)
{
int a = start + len;
lastW = w;
if(a <= line.count)
while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
else
a--;
if(a > start)
{
int oh;
FontExtent(display, font, line.buffer + start, a - start, &w, null, 0, null, &oh);
}
else
w = 0;
if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
len = a - start;
}
if(px) *px = x + w;
return Min(this.maxLineSize - 1, start + len);
}
x += w;
}
}
void SetCursorToViewX()
{
bool selecting = this.x != selX || y != selY;
// Horizontal Adjustment
int x = 0;
int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
if(this.x < c)
this.x = x;
else
{
c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
if(this.x > c)
this.x = c;
}
if(!selecting)
{
selX = this.x;
selY = y;
selLine = line;
}
UpdateCaretPosition(false);
UpdateDirty();
SetSelectCursor();
}
void SetCursorToViewY()
{
int c, numLines;
EditLine oldLine = this.line;
bool selecting = this.x != this.selX || this.y != this.selY;
numLines = clientSize.h / this.space.h;
// Vertical Adjustment
if(this.viewY > this.y)
{
this.y = this.viewY;
this.line = this.viewLine;
}
if(this.viewY + numLines <= this.y)
{
EditLine line;
this.y = this.viewY-1;
for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
{
this.line = line;
this.y++;
}
}
if(this.line != oldLine)
{
this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
ComputeColumn();
}
if(!selecting)
{
this.selX = this.x;
this.selY = this.y;
this.selLine = this.line;
}
UpdateCaretPosition(false);
UpdateDirty();
SetSelectCursor();
}
/*
bool SaveFile(const char * fileName)
{
File f = eFile_Open(fileName, FO_WRITE);
if(f)
{
Save(f, false);
eWindow_SetModified(false);
eInstance_Delete(f);
return true;
}
return false;
}
*/
bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
{
if(line)
{
if(!style.multiLine)
{
x = (active && this.active && !style.readOnly) ? line.count : 0;
selX = 0;
ComputeColumn();
SetViewToCursor(true);
DirtyLine(0);
UpdateDirty();
}
// Update(null);
if(!active && modified)
{
modified = false;
if(!NotifyModified(master, this))
{
modified = true;
*goOnWithActivation = false;
}
}
}
if(!active)
{
ReleaseCapture();
if(timer) timer.Stop();
mouseSelect = false;
wordSelect = false;
}
return true;
}
void AutoSize()
{
int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
}
bool OnResizing(int *w, int *h)
{
if(!*h)
*h = space.h + 2;
if(!*w)
//*w = 80;
*w = space.h * 80 / 14;
return true;
}
void OnResize(int w, int h)
{
/*
if(!hasHorzScroll && !hasVertScroll && viewLine)
SetViewToCursor(true);
*/
//if(!hasHorzScroll && !hasVertScroll && viewLine)
if(viewLine)
SetViewToCursor(true);
//else
// FixScrollArea();
}
bool OnMiddleButtonDown(int x, int y, Modifiers mods)
{
if(style.readOnly) return true;
// We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
// Middle button already serves as a dragger as well.
// Paste();
return true;
}
bool OnRightButtonDown(int x, int y, Modifiers mods)
{
this.rightButtonDown = true;
// Copy();
return true;
}
bool OnRightButtonUp(int x, int y, Modifiers mods)
{
// Context Menu
if(!parent.inactive && rightButtonDown)
{
PopupMenu popup;
Menu contextMenu { };
MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
MenuDivider { contextMenu };
MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
popup = PopupMenu { master = this, menu = contextMenu,
/*
nonClient = true, interim = false, parent = parent,
position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
*/
position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
};
popup.Create();
}
rightButtonDown = false;
return true;
}
bool OnLeftButtonDown(int mx, int my, Modifiers mods)
{
int x,y;
EditLine line;
if(style.noSelect) return true;
// Should we have a separate 'selectOnActivate' style?
if(!mods.isActivate || (style.readOnly && style.multiLine))
{
Capture();
mouseSelect = true;
}
mouseX = mx - XOFFSET;
mouseY = my;
FindMouse(mouseX, mouseY, &x, &y, &line, true);
#ifdef _DEBUG
//PrintLn("OnLeftButtonDown: ", x, ", ", y);
#endif
if(!style.readOnly)
{
if(wordSelect)
mouseMove = false;
else if(IsMouseOnSelection() && !mods.isActivate)
{
DirtyLine(this.y);
mouseMove = true;
dropX = x;
dropY = y;
dropLine = line;
}
}
if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
{
if(mods.shift && !mods.isActivate)
{
this.x = x;
this.y = y;
this.line = line;
DirtyAll();
}
else
{
SelDirty();
DirtyLine(this.y);
this.x = x;
this.y = y;
this.line = line;
DirtyLine(this.y);
_Deselect();
}
ComputeColumn();
}
UpdateDirty();
UpdateCaretPosition(true);
// Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
// ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
return false;
}
bool OnLeftButtonUp(int x, int y, Modifiers mods)
{
timer.Stop();
mouseSelect = false;
wordSelect = false;
x -= XOFFSET;
ReleaseCapture();
if(!style.readOnly)
{
if(mouseMove)
{
EditLine line;
FindMouse(mouseX, mouseY, &x, &y, &line, true);
#ifdef _DEBUG
//PrintLn("MouseMove: ", x, ", ", y);
#endif
dropX = x;
dropY = y;
dropLine = line;
mouseMove = IsMouseOnSelection();
if(!mouseMove)
{
int size = SelSize();
if(size)
{
char * text = new char[size+1];
if(text)
{
int moveX = 0;
GetSel(text, false);
if(Max(selY, this.y) == dropY)
{
if(this.x > selX)
{
if(this.dropX > this.selX)
moveX = this.x - this.selX;
}
else
{
if(this.dropX > this.x)
moveX = this.selX - this.x;
}
}
recordUndoEvent = true;
DelSel(null);
this.dropX -= moveX;
this.selX = this.x = this.dropX;
this.selY = this.y = this.dropY;
this.selLine = this.line = this.dropLine;
AddS(text);
recordUndoEvent = false;
SetViewToCursor(true);
delete text;
Modified();
#ifdef _DEBUG
/* {
byte * c = ((EditBox)this).multiLineContents, * c2;
int l1 = c ? strlen(c) : 0, l2;
Undo();
Redo();
c2 = ((EditBox)this).multiLineContents;
l2 = c2 ? strlen(c2) : 0;
if(l1 != l2 || (l1 && strcmp(c, c2)))
{
PrintLn("Fail!");
}
delete c;
}
*/
#endif
}
}
}
else
{
SelDirty();
DirtyLine(this.y);
this.x = x;
this.y = y;
this.line = line;
ComputeColumn();
DirtyLine(this.y);
_Deselect();
UpdateDirty();
}
}
else
{
EditLine line;
mouseX = x;
mouseY = y;
FindMouse(mouseX, mouseY, &x, &y, &line, true);
#ifdef _DEBUG
//PrintLn("Dropped: ", x, ", ", y);
#endif
NotifyDropped(master, this, x, y);
}
}
mouseMove = false;
// Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
// ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
return false;
}
bool OnMouseMove(int mx, int my, Modifiers mods)
{
int x,y;
EditLine line;
bool needScroll;
if(mods != -1 && mods.isSideEffect)
{
SetSelectCursor();
return true;
}
if(style.noSelect) return true;
if(wordSelect) return true;
mouseX = mx - XOFFSET;
mouseY = my;
needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
if(this.mouseMove || this.mouseSelect)
{
if(!needScroll)
timer.Stop();
else
{
if(needScroll)
timer.Start();
if(mods != -1 &&
((style.hScroll) || (style.vScroll)))
return true;
}
}
if(this.mouseMove)
{
DirtyLine(this.dropY);
this.dropX = x;
this.dropY = y;
DirtyLine(this.dropY);
this.dropLine = line;
SetViewToCursor(true);
#ifdef _DEBUG
//PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
#endif
}
else if(this.mouseSelect)
{
DirtyLine(this.selY);
DirtyLine(this.y);
this.x = x;
this.y = y;
ComputeColumn();
DirtyLine(this.y);
this.line = line;
SetViewToCursor(true);
UpdateDirty();
#ifdef _DEBUG
//PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
#endif
}
SetSelectCursor();
return true;
}
bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
{
int x,y;
EditLine line;
#ifdef _DEBUG
//PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
#endif
mx -= XOFFSET;
if(style.noSelect) return true;
FindMouse(mx, my, &x, &y, &line, false);
if(!NotifyDoubleClick(master, this, line, mods))
return false;
if(x < line.count)
{
int c;
int start = -1;
int numBytes;
for(c = x; c >= 0; c--)
{
unichar ch;
while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
if(!IS_ALUNDER(ch))
break;
start = c;
}
if(start != -1)
{
for(c = start; c<line.count; c += numBytes)
{
unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
if(!ch || !IS_ALUNDER(ch))
break;
}
SelDirty();
DirtyLine(this.y);
this.y = y;
DirtyLine(this.y);
this.selX = start;
this.x = c;
ComputeColumn();
this.line = this.selLine = line;
this.wordSelect = (c != start);
UpdateDirty();
}
}
return true;
}
bool OnPostCreate()
{
/*if(isDocument)
{
Menu fileMenu { menu, "File", F };
saveDialog = fileDialog;
MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
}*/
if(style.autoSize) AutoSize();
return true;
}
void ComputeFont()
{
if(FontExtent)
{
int oh;
FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h, 0, null, &oh);
FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h, 0, null, &oh);
space.w = Max(space.w, 1);
large.w = Max(large.w, 1);
space.h = Max(space.h, 1);
large.h = Max(large.h, 1);
{
EditLine line;
for(line = lines.first; line; line = line.next)
ComputeLength(line);
FindMaxLine();
}
if(viewLine)
SetViewToCursor(true);
FixScrollArea();
Update(null);
}
}
bool OnLoadGraphics()
{
FontExtent = Display::FontExtent2;
font = fontObject;
ComputeFont();
// UpdateCaretPosition(true);
return true;
}
void OnUnloadGraphics()
{
this.font = null;
}
bool OnKeyHit(Key key, unichar ch)
{
bool shift = (key.shift) ? true : false;
#ifdef _DEBUG
//PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
#endif
if(!ch && !key.alt && !key.ctrl)
{
key.code = (SmartKey)key.code;
}
else if(!ch && key.alt)
key.code = 0;
switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
{
case backSpace:
if(style.readOnly) break;
if(style.stuckCaret) GoToEnd(true);
if(!(style.freeCaret))
{
this.x = Min(this.x, this.line.count);
}
if(key.ctrl)
{
int y;
bool done = false;
EditLine line = this.line;
int c = 0;
for(y = this.y; y>= 0; y--)
{
c = (y == this.y) ? (Min(this.x-1, line.count-1)) : line.count-1;
// Slow down when going on lines...
if(y != this.y) break;
for(; c>=0; c--)
{
//if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
if(IS_ALUNDER(line.buffer[c]))
break;
}
for(; c>=0; c--)
{
if(!IS_ALUNDER(line.buffer[c]))
{
//if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
done = true;
break;
}
}
if(done)
break;
if(line.prev)
line = line.prev;
else
break;
}
//if(this.x != 0)
{
_DelCh(line, y, c+1, this.line, this.y, this.x, true, false, null);
this.x = this.selX = Min(c+1, line.count);
this.y = this.selY = y;
this.line = this.selLine = line;
SetViewToCursor(true);
}
ComputeColumn();
}
else
{
BackSpace();
}
return false;
//break;
case del:
if(style.readOnly) break;
if(style.stuckCaret) break;
if(shift)
{
Cut();
}
else
{
// Delete selection
if(this.line != this.selLine || this.x != this.selX)
{
DelSel(null);
SetViewToCursor(true);
Modified();
}
else
{
EditLine line1 = this.line, line2 = this.line;
int x1, y1 = this.y, x2, y2 = this.y;
if(!style.freeCaret)
{
this.selX = this.x = Min(this.x, this.line.count);
ComputeColumn();
}
x1 = this.x;
if(x1 < line1.count)
{
// Delete word
if(key.ctrl)
{
int i;
char * buffer = line1.buffer;
for(i = x1; i < line1.count; i++)
{
if(!IS_ALUNDER(buffer[i]))
break;
}
for(; i < line1.count; i++)
{
// Delete trailing whitespace
if(IS_ALUNDER(buffer[i]))
break;
}
x2 = i;
}
else
x2 = x1 + 1;
}
else
{
// Avoid creating trailing spaces if there is no content on next line
line2 = line1.next;
y2++;
if(line2 && !line2.count)
x1 = line1.count;
x2 = 0;
}
if(line2)
_DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
SetViewToCursor(true);
Modified();
}
}
return false;
//break;
case enter:
case keyPadEnter:
{
if(!key.alt && !key.ctrl)
{
int c;
int position = 0;
bool stuffAfter = false;
char * addString;
int len = 0;
/*bool resetX = false;
int backX;*/
if(style.stuckCaret) GoToEnd(true);
if(style.readOnly) break;
if(!(style.multiLine)) break;
for(c = 0; c<this.line.count && c<this.x; c++)
{
if(this.line.buffer[c] == '\t')
position += this.tabSize - (position % this.tabSize);
else if(this.line.buffer[c] == ' ')
position++;
else
break;
}
// Prevent adding trailing spaces if at the head of a line
/*if(c && c == this.x && c < this.line.count && this.x == this.selX && this.y == this.selY)
{
position = 0;
backX = this.x;
this.x = 0;
this.selX = 0;
resetX = true;
}*/
if(!line.count)
position = x;
if(this.x < this.line.count)
stuffAfter = true;
//If last character is a { indent one tab
c = Min(x, line.count);
if(c > 0 && line.buffer[c - 1] == '{')
{
//Except if the next non space character is a }
bool indent = false;
int i;
for(i = c; i <= this.line.count; i++) // indent will be set to true on nul terminating char
if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
{
if(this.line.buffer[i] != '}')
indent = true;
break;
}
if(indent)
position += this.tabSize;
}
addString = new char[position + 2];
addString[len++] = '\n';
addString[len] = '\0';
if(stuffAfter || !style.freeCaret)
{
for(c = 0; c<position; )
{
if(style.useTab && c + this.tabSize <= position)
{
addString[len++] = '\t';
c += this.tabSize;
}
else
{
addString[len++] = ' ';
c++;
}
}
addString[len] = '\0';
}
recordUndoEvent = true;
if(AddS(addString))
{
EditLine prevLine = this.line.prev;
if(prevLine)
{
// Nuke spaces if that is all that is left on previous line
int i;
char * buffer = prevLine.buffer;
for(i = 0; i < prevLine.count; i++)
if(buffer[i] != ' ' && buffer[i] != '\t')
break;
if(i == prevLine.count)
DelCh(prevLine, this.y - 1, 0, prevLine, this.y - 1, prevLine.count, false);
}
/*if(resetX)
{
this.x = this.selX = backX;
ComputeColumn();
}
else */if(!stuffAfter && style.freeCaret)
{
this.x = this.selX = position;
ComputeColumn();
}
FindMaxLine();
SetViewToCursor(true);
Modified();
}
recordUndoEvent = false;
delete addString;
return false;
}
break;
}
case left:
{
if(style.stuckCaret) break;
if(!(style.freeCaret))
{
this.x = Min(this.x, this.line.count);
this.selX = Min(this.selX, this.selLine.count);
ComputeColumn();
}
if(!shift) SelDirty();
if(key.ctrl)
{
bool foundAlpha = false;
bool found = false;
int y = this.y;
EditLine line, lastLine = null;
int lastC = 0, lastY = 0;
for(line = this.line; (line && !found); line = line.prev, y--)
{
int start;
int c;
if(this.x == 0 && line != this.line)
{
foundAlpha = true;
lastC = line.count;
lastY = y;
lastLine = line;
break;
}
if(line == this.line) start = this.x -1; else start = line.count-1;
start = Min(start, line.count-1);
for(c = start; c >= 0;)
{
int numBytes;
unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
if(IS_ALUNDER(ch))
{
foundAlpha = true;
lastC = c;
lastY = y;
lastLine = line;
}
else
{
if(foundAlpha)
{
found = true;
break;
}
}
while(--c >= 0)
{
byte ch = line.buffer[c];
if(UTF8_IS_FIRST(ch)) break;
}
}
// No next word found,
if(!found && ( this.x > 0 || (!line.count && this.x)))
{
foundAlpha = true;
lastC = 0;
lastY = y;
lastLine = line;
break;
}
}
if(foundAlpha)
{
DirtyLine(this.y);
this.x = lastC;
this.y = lastY;
this.line = lastLine;
DirtyLine(this.y);
ComputeColumn();
}
}
else
{
if(x > 0)
{
if(x <= line.count)
{
byte * buffer = (byte *)line.buffer;
while(--x)
{
byte ch = buffer[x];
if(UTF8_IS_FIRST(ch)) break;
}
}
else
x--;
DirtyLine(y);
}
else if(line.prev)
{
line = line.prev;
DirtyLine(y);
x = line.count;
y--;
DirtyLine(y);
}
ComputeColumn();
}
if(!shift) _Deselect();
SetViewToCursor(true);
//break;
return false;
}
case right:
{
if(style.stuckCaret) break;
if(!(style.freeCaret))
{
this.x = Min(this.x, this.line.count);
this.selX = Min(this.selX, this.selLine.count);
ComputeColumn();
}
if(!shift) SelDirty();
if(!shift && (this.x != this.selX || this.y != this.selY));
else if(key.ctrl)
{
bool onAChar = false;
if(this.selX != this.x || this.selY != this.y)
onAChar = true;
if(this.x<this.line.count)
if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
onAChar = true;
if(key.shift && onAChar &&
((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
{
bool foundAlpha = false;
bool found = false;
EditLine line = null, lastLine = null;
int y = this.y;
int lastC = 0, lastY = 0, lastNumBytes = 0;
for(line = this.line; (line && !found); line = line.next, y++)
{
int start = (line == this.line) ? this.x : 0;
int c;
int numBytes;
unichar ch;
for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
{
if(IS_ALUNDER(ch))
{
foundAlpha = true;
lastC = c;
lastNumBytes = numBytes;
lastY = y;
lastLine = line;
}
else if(foundAlpha)
{
found = true;
break;
}
}
if(!found && (c != this.x || line != this.line))
{
found = true;
lastLine = line;
lastC = line.count;
lastNumBytes = 0;
lastY = y;
break;
}
}
if(found)
{
DirtyLine(this.y);
this.x = lastC + lastNumBytes;
this.y = lastY;
this.line = lastLine;
DirtyLine(this.y);
ComputeColumn();
}
}
else
{
bool foundAlpha = false;
bool found = false;
EditLine line;
int y = this.y;
for(line = this.line; (line && !found); line = line.next, y++)
{
int start = (line == this.line) ? this.x : 0;
int c;
int numBytes;
unichar ch;
for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
{
if(!IS_ALUNDER(ch))
foundAlpha = true;
else if(foundAlpha)
{
found = true;
DirtyLine(this.y);
this.x = c;
this.y = y;
this.line = line;
DirtyLine(this.y);
ComputeColumn();
break;
}
}
// No next word found,
if(!found && (c != this.x || line != this.line))
{
found = true;
DirtyLine(this.y);
this.x = line.count;
this.y = y;
this.line = line;
DirtyLine(this.y);
ComputeColumn();
}
foundAlpha = true;
}
}
}
else
{
if(x < line.count || (style.freeCaret && line.count < maxLineSize))
{
if(x < line.count)
{
byte * buffer = (byte *)line.buffer;
while(++x)
{
byte ch = buffer[x];
if(UTF8_IS_FIRST(ch)) break;
}
}
else
x++;
ComputeColumn();
DirtyLine(y);
}
else
{
if(line.next)
{
DirtyLine(y);
line = line.next;
y++;
x = 0;
col = 0;
DirtyLine(y);
}
}
}
if(!shift) _Deselect();
SetViewToCursor(true);
// break;
return false;
}
case up:
if(key.ctrl)
{
if(!style.vScroll || hasVertScroll) break;
LineUp();
return false;
}
else
{
if(style.stuckCaret) break;
if(!shift) SelDirty();
DirtyLine(this.y);
if(style.wrap)
{
}
else if(line.prev)
{
line = line.prev;
this.y--;
this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
}
DirtyLine(this.y);
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
/*
if(caretY == this.y * space.h)
{
if(line.prev)
{
line = line.prev;
this.y--;
if(!style.freeCaret)
this.x = Min(this.x, line.count);
caretY = MAXINT;
}
else
return false;
}
{
int th = space.h;
int textPos = 0;
int sx = 0, sy = this.y * space.h;
char * text = line.text;
int maxW = clientSize.w - sx;
display.FontExtent(font, " ", 1, null, &th);
do
{
int startPos = textPos;
int width = 0;
int x = 0;
bool lineComplete = false;
if(!style.wrap && caretY == MAXINT)
{
caretY = sy + th;
//textPos = line.count;
//lineComplete = true;
}
for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
{
int w = 0;
int len;
char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
if(nextSpace)
len = (nextSpace - (text + textPos));
else
len = line.count - textPos;
if(textPos < line.count)
{
display.FontExtent(font, text + textPos, len, &w, null);
}
if(nextSpace) { w += space.w; len++; }
if(style.wrap && x + width + w > maxW && x > 0)
{
lineComplete = true;
break;
}
textPos += len;
width += w;
if(nextSpace)
{
x += width;
width = 0;
}
if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
{
x += width;
this.x = textPos;
while(this.x > 0 && x + sx > caretX && this.x > startPos)
{
int len;
if(this.x > line.count)
this.x--;
else
while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
len = this.x - startPos;
display.FontExtent(font, text + startPos, len, &x, null);
}
DirtyLine(this.y);
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
return false;
}
}
if(sy == caretY - th || textPos >= line.count)
{
if(textPos >= line.count)
{
int c = textPos - 1;
while(c > 0 && text[c] == ' ') c--;
this.x = c + 1;
}
else
this.x = line.count;
DirtyLine(this.y);
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
return false;
}
sy += th;
sx = 0;
} while(textPos < line.count);
DirtyLine(this.y);
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
return false;
}
*/
// PREVIOUS CODE
/*
if(this.line.prev)
{
int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
if(!shift) SelDirty();
this.line = this.line.prev;
DirtyLine(this.y);
this.y--;
DirtyLine(this.y);
this.x = x;
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
}
*/
}
// break;
return style.multiLine ? false : true;
case down:
if(key.ctrl)
{
if(!style.vScroll || hasVertScroll)
break;
LineDown();
return false;
}
else
{
if(style.stuckCaret) break;
{
/*
int th = space.h;
int textPos = 0;
int sx = 0, sy = this.y * this.space.h;
int maxW = clientSize.w - sx;
char * text = line.buffer;
*/
if(!shift) SelDirty();
DirtyLine(this.y);
if(style.wrap)
{
/*
if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
{
}
*/
}
else if(line.next)
{
line = line.next;
this.y++;
this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
}
DirtyLine(this.y);
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
/*
while(!textPos || (style.freeCaret || textPos<line.count))
{
int startPos = textPos;
int width = 0;
int x = 0;
bool lineComplete = false;
if(!style.wrap && sy <= caretY)
{
textPos = line.count;
lineComplete = true;
}
for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
{
int w = 0;
int len;
char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
if(nextSpace)
len = (nextSpace - (text + textPos));
else
len = line.count - textPos;
if(textPos < line.count)
{
display.FontExtent(font, text + textPos, len, &w, &th);
}
if(nextSpace) { w += space.w; len++; }
if(style.wrap && x + width + w > maxW && x > 0)
{
lineComplete = true;
break;
}
textPos += len;
width += w;
if(nextSpace)
{
x += width;
width = 0;
}
if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
{
this.x = textPos;
x += width;
while(this.x > 0 && x + sx > caretX && textPos > startPos)
{
int len;
if(this.x > line.count)
this.x--;
else
while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
len = this.x - startPos;
display.FontExtent(font, text + startPos, len, &x, null);
}
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
return false;
}
}
if(sy > caretY)
{
this.x = line.count;
DirtyLine(this.y);
if(!shift) _Deselect();
ComputeColumn();
SetViewToCursor(false);
return false;
}
else if(textPos >= line.count && line.next)
{
startPos = 0;
textPos = 0;
line = line.next;
this.y++;
sy = this.y * this.space.h;
sx = 0; //textBlock.startX;
text = line.buffer;
}
else
{
sy += th;
sx = 0; //textBlock.startX;
}
}
*/
/*
if(line.next)
{
line = line.next;
this.x = Min(this.x, line.count);
//int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
this.y++;
if(!shift) _Deselect();
ComputeColumn();
if(this.selX != this.x || this.selY != this.y)
DirtyLine(this.y);
SetViewToCursor(false);
}
*/
}
/* PREVIOUS CODE
if(this.line.next)
{
int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
if(!shift) SelDirty();
this.line = this.line.next;
DirtyLine(this.y);
this.y++;
this.x = x;
if(!shift) _Deselect();
ComputeColumn();
if(this.selX != this.x || this.selY != this.y)
DirtyLine(this.y);
SetViewToCursor();
}
*/
}
// break;
return style.multiLine ? false : true;
case home:
{
if(style.stuckCaret) break;
if(!style.multiLine && key.ctrl) break;
if(!(style.freeCaret))
this.selX = Min(this.selX, this.selLine.count);
if(!shift) SelDirty();
if(key.ctrl)
{
this.line = this.lines.first;
if(this.y != 0 || this.x != 0)
DirtyAll();
this.y = 0;
this.x = 0;
this.col = 0;
}
else
{
if(style.smartHome)
{
EditLine line = this.line;
int c;
for(c=0; line.buffer[c]; c++)
if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
break;
if(overwrite || (shift && (c != 0 || this.x)))
DirtyLine(this.y);
if(this.x != c)
this.x = c;
else
this.x = 0;
}
else
{
if(overwrite || (shift && this.x != 0))
DirtyLine(this.y);
this.x = 0;
}
ComputeColumn();
}
if(!shift) _Deselect();
SetViewToCursor(true);
//break;
return false;
}
case end:
{
if(style.stuckCaret) break;
if(!style.multiLine && key.ctrl) break;
if(!style.freeCaret)
this.selX = Min(this.selX, this.selLine.count);
if(!shift) SelDirty();
if(key.ctrl)
{
GoToEnd(false);
}
else if(this.x != this.line.count)
{
this.x = this.line.count;
if(overwrite || shift)
DirtyLine(this.y);
ComputeColumn();
}
if(!shift) _Deselect();
SetViewToCursor(true);
//break;
return false;
}
case tab:
if(style.tabKey && !key.ctrl)
{
if(this.selY != this.y && style.tabSel)
{
EditLine firstLine, lastLine;
EditLine line;
int y, x;
// Do multi line selection tabbing here
if(this.selY < this.y)
{
firstLine = this.selLine;
lastLine = this.line;
y = this.selY;
x = this.x;
}
else
{
// Selecting going up
firstLine = this.line;
lastLine = this.selLine;
y = this.y;
x = this.selX;
}
ComputeColumn();
if(shift)
{
for(line = firstLine; line; line = line.next, y++)
{
if(line != lastLine || x)
{
int c;
int lastC = 0;
BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
{
if(line.buffer[c] == '\t')
{
lastC++;
break;
}
else if(line.buffer[c] != ' ')
break;
}
after.x = lastC;
NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
if(lastC)
{
int len = GetText(null, line, y, 0, line, y, lastC, false, false);
char * string = new char[len];
DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
GetText(string, line, y, 0, line, y, lastC, false, false);
Record(action);
}
memmove(line.buffer,line.buffer+lastC,line.size-lastC);
if(!line.AdjustBuffer(line.count-lastC))
break;
line.count-=lastC;
DirtyLine(y);
}
if(line == lastLine) break;
}
}
else
{
for(line = firstLine; line; line = line.next, y++)
{
if(line.count)
{
if(line != lastLine || x)
{
if(style.useTab)
{
if(line.count + 1 <= this.maxLineSize)
{
BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
if(!line.AdjustBuffer(line.count+1))
break;
NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
{
AddCharAction action { ch = '\t', x = 0, y = y };
Record(action);
}
memmove(line.buffer+1,line.buffer,line.size-1);
line.count++;
line.buffer[0] = '\t';
}
}
else
{
if(line.count + this.tabSize <= this.maxLineSize)
{
int c;
BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
if(!line.AdjustBuffer(line.count+this.tabSize))
break;
{
char * string = new char[this.tabSize + 1];
memset(string, ' ', this.tabSize);
string[this.tabSize] = '\0';
Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
}
memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
line.count+=this.tabSize;
for(c=0; c<this.tabSize; c++)
line.buffer[c] = ' ';
}
}
DirtyLine(y);
}
}
if(line == lastLine) break;
}
}
ComputeLength(maxLine);
}
else
{
if(style.useTab)
{
// Insert character
AddCh('\t');
}
else
{
int start, c;
char * addString = new char[this.tabSize + 1];
int len = 0;
if(!(style.freeCaret))
{
this.x = Min(this.x, this.line.count);
ComputeColumn();
}
// Insert spaces
start = Min(this.x, this.selX);
for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
{
addString[len++] = ' ';
}
addString[len] = 0;
AddS(addString);
delete addString;
}
}
Modified();
SetViewToCursor(true);
return false;
}
break;
case pageDown:
if(style.multiLine)
{
if(key.ctrl)
{
if(!(style.hScroll) || hasHorzScroll) break;
if(this.viewX < this.maxLength)
{
//this.viewX+=this.space.w*this.tabSize;
//DirtyAll();
SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
}
}
else
{
PageDown();
DirtyAll();
if(!shift) _Deselect();
SetCursorToViewX();
SetCursorToViewY();
}
return false;
}
break;
case pageUp:
if(style.multiLine)
{
if(key.ctrl)
{
if(!(style.hScroll) || hasHorzScroll) break;
if(this.viewX > 0)
{
//this.viewX-=this.space.w*this.tabSize;
//this.viewX = Max(this.viewX,0);
//DirtyAll();
SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
// SetCursorToView();
}
}
else
{
PageUp();
DirtyAll();
if(!shift) _Deselect();
SetCursorToViewX();
SetCursorToViewY();
}
return false;
}
break;
case insert:
if(key.ctrl)
{
Copy();
return false;
}
else if(!style.readOnly)
{
if(key.shift)
{
if(!(style.readOnly))
Paste();
return false;
}
else
{
this.overwrite ^= 1;
UpdateCaretPosition(true);
if(this.overwrite)
SetCaret(0,0,0);
DirtyLine(this.y);
UpdateDirty();
NotifyOvrToggle(master, this, this.overwrite);
}
}
break;
case hotKey:
break;
default:
switch(key)
{
case ctrlA:
// Select All
if(style.noSelect) break;
{
//Save current view position
int tempX = this.viewX;
int tempY = this.viewY;
this.selX = 0;
this.selY = 0;
this.selLine = this.lines.first;
this.y = this.lineCount-1;
this.line = this.lines.last;
this.x = this.line.count;
ComputeColumn();
DirtyAll();
SetViewToCursor(true);
//Restore previous view position
SetScrollPosition(tempX, tempY * this.space.h);
UpdateDirty();
SetSelectCursor();
SelectionEnables();
}
// TOCHECK: Was there any good reason why we weren't returning false here?
return false; // break;
case ctrlC:
Copy();
return false;
case ctrlV:
if(!(style.readOnly))
{
Paste();
return false;
}
break;
case ctrlX:
{
if(style.readOnly) break;
Cut();
return false;
}
case ctrlL:
{
if(style.readOnly) break;
ClearLine();
return false;
}
case ctrlZ:
if(style.readOnly) break;
Undo();
return false;
case ctrlY:
if(style.readOnly) break;
Redo();
return false;
default:
if(style.readOnly) break;
if(key.shift && key.code == rightBracket)
{
//Only indent back if you are exactly at one tab.
{
//bool whitespace = true;
int i;
char * newline;
int putsize;
int indentwidth = 0;
EditLine line = this.line;
//Only remove one tab if there is nothing else on the line.
for(i = 0; i < this.line.count; i++)
{
if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
{
PutCh(ch);
return false;
}
}
//Find opening {
i = 1;
while(i && line)
{
int pos;
indentwidth = 0;
for(pos = line.count - 1; pos >= 0; pos--)
{
char c = line.buffer[pos];
if(c == ' ')
indentwidth++;
else if(c == '\t')
indentwidth += this.tabSize;
else
//Counting backwards, so when you find a character, indentation is reset
indentwidth = 0;
if(i && c == '}')
i++;
if(i && c == '{')
i--;
}
line = line.prev;
}
//Place the } to get an undo:
PutCh(ch);
this.x = this.line.count;
this.selX = 0;
if(!style.useTab)
putsize = indentwidth;
else
putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
newline = new char[putsize+2];
newline[putsize] = '}';
newline[putsize+1] = '\0';
i = 0;
if(style.useTab)
for(; i < indentwidth / this.tabSize; i++)
newline[i] = '\t';
for(;i < putsize; i++)
newline[i] = ' ';
AddS(newline);
delete newline;
}
return false;
}
else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
{
PutCh(ch);
return false;
}
break;
}
break;
}
return true;
}
void OnHScroll(ScrollBarAction action, int position, Key key)
{
#ifdef _DEBUG
//PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
#endif
this.viewX = position;
if(action != setRange)
{
if(!this.mouseMove && style.cursorFollowsView)
SetCursorToViewX();
}
DirtyAll();
UpdateDirty();
}
void OnVScroll(ScrollBarAction action, int position, Key key)
{
int oldViewY = this.viewY;
#ifdef _DEBUG
//PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
#endif
position /= this.space.h;
if(position < this.viewY)
{
for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
style.recomputeSyntax = true;
}
else if(position > this.viewY)
{
EditLine oldViewLine = viewLine;
for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
if(style.syntax)
highlighting.FigureStartSyntaxStates(oldViewLine, false, firstLine);
}
if(action != setRange)
{
if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
}
if(this.x != this.selX || this.y != this.selY)
DirtyLine(this.y);
{
Scroll(0, (this.viewY - oldViewY) * this.space.h);
/*
int numLines = clientSize.h / this.space.h;
int y;
if(Abs(this.viewY - oldViewY) < numLines)
if(this.viewY > oldViewY)
{
for(y = oldViewY; y <this.viewY; y++)
DirtyLine(y + numLines);
}
else
{
for(y = this.viewY; y <oldViewY; y++)
DirtyLine(y + numLines);
}*/
}
//DirtyAll();
// Fix dirt of stuff before first line...
if(this.viewY - oldViewY > 0)
{
Box box { 0,0, clientSize.w-1, YOFFSET-1 };
Update(box);
}
UpdateDirty();
}
bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr, int * xAdjustmentPtr)
{
EditLine line;
int length, endX = 0;
bool result;
ReplaceTextAction replaceAction = null;
AddCharAction addCharAction = null;
int addedSpaces = 0, addedTabs = 0, xAdjustment = 0;
if(ch == '\r') return true;
if(style.stuckCaret /*|EES_READONLY)*/ )
GoToEnd(true);
if(ch == '\n' && !(style.multiLine) && this.line) return false;
if(!undoBuffer.dontRecord)
{
if(selX != x || selY != y)
{
char buffer[5];
char * newString;
char * oldString;
int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
oldString = new char[len];
UTF32toUTF8Len(&ch, 1, buffer, 4);
newString = CopyString(buffer);
GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
if(selY < y || (selY == y && selX < x))
{
replaceAction.x1 = selX;
replaceAction.y1 = selY;
replaceAction.x2 = x;
replaceAction.y2 = y;
}
else
{
replaceAction.x1 = x;
replaceAction.y1 = y;
replaceAction.x2 = selX;
replaceAction.y2 = selY;
}
Record(replaceAction);
undoBuffer.dontRecord++;
}
else
{
addCharAction = AddCharAction { y = y, x = x, ch = ch };
Record(addCharAction);
}
}
if(ch == '\n')
{
DelSel(&addedSpaces);
if(this.lineCount+1 > this.maxLines)
{
if(style.autoEmpty)
Emptyline(this.lines.first,0);
else
return false;
}
if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
{
// Make sure it fits, but we need a default line is this.font is too big for window
if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
return false;
}
if((this.y >= 0) && this.y < this.viewY)
{
this.viewY++;
DirtyAll();
}
line = EditLine { };
if(!line)
return false;
lines.Insert(this.line, line);
line.editBox = this;
line.buffer = null;
line.count = 0;
line.size = 0;
length = 0;
// If we're displacing the lines from a current line ...
if(this.line)
{
if(this.line.buffer)
{
endX = this.x;
if(this.line.count < endX) endX = this.line.count;
length = this.line.count - endX;
}
}
if(!line.AdjustBuffer(length))
return false;
if(this.line)
{
if(this.line.buffer)
{
CopyBytes(line.buffer,this.line.buffer+endX, length+1);
#ifdef _DEBUG
if(endX > 4000 || endX < 0)
printf("Warning");
#endif
this.line.count = endX;
this.line.buffer[this.line.count] = '\0';
this.line.AdjustBuffer(this.line.count);
ComputeLength(this.line);
}
}
{
BufferLocation before = { this.line, this.y, this.x }, after;
this.line = line;
this.x = 0;
this.col = 0;
line.count = length;
#ifdef _DEBUG
if(length > 4000 || length < 0)
printf("Warning");
#endif
ComputeLength(this.line);
DirtyEnd(this.y);
this.y++;
this.lineCount++;
line.buffer[line.count] = '\0';
result = true;
after.line = this.line, after.y = this.y, after.x = this.x;
NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
}
}
else
{
char string[5];
int count = UTF32toUTF8Len(&ch, 1, string, 5);
DelSel(&addedSpaces);
result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs, &xAdjustment);
if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
if(addedTabsPtr) *addedTabsPtr = addedTabs;
if(xAdjustmentPtr) *xAdjustmentPtr = xAdjustment;
}
this.selX = this.x;
this.selY = this.y;
this.selLine = this.line;
if(replaceAction)
{
replaceAction.x3 = x;
replaceAction.y3 = y;
replaceAction.addedSpaces = addedSpaces;
replaceAction.addedTabs = addedTabs;
undoBuffer.dontRecord--;
}
if(addCharAction)
{
addCharAction.x -= addedTabs * (tabSize-1);
addCharAction.addedSpaces = addedSpaces;
addCharAction.addedTabs = addedTabs;
}
return result;
}
public:
/****************************************************************************
EDIT BOX METHODS
****************************************************************************/
bool AddCh(unichar ch)
{
return _AddCh(ch, null, null, null);
}
void Modified()
{
this.modified = true;
NotifyUpdate(master, this);
modifiedDocument = true;
}
void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
{
Deselect();
_DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
SetViewToCursor(true);
UpdateDirty();
Modified();
}
void Undo()
{
undoBuffer.Undo();
itemEditUndo.disabled = undoBuffer.curAction == 0;
itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
UpdateDirty();
SetSelectCursor();
SelectionEnables();
if(savedAction == undoBuffer.curAction)
{
modifiedDocument = false;
SetModified(false);
NotifyUnsetModified(master, this);
}
}
void Redo()
{
undoBuffer.Redo();
itemEditUndo.disabled = undoBuffer.curAction == 0;
itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
UpdateDirty();
SetSelectCursor();
SelectionEnables();
if(savedAction == undoBuffer.curAction)
{
modifiedDocument = false;
SetModified(false);
NotifyUnsetModified(master, this);
}
}
void Record(UndoAction action)
{
if(!undoBuffer.dontRecord)
{
undoBuffer.Record(action);
itemEditUndo.disabled = undoBuffer.curAction == 0;
itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
}
}
void SelectAll()
{
Select(
this.lines.first, 0,0,
this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
}
void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
{
SelDirty();
this.selX = x1;
this.selY = y1;
this.selLine = line1 ? (EditLine)line1 : this.lines.first;
this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
this.y = line2 ? y2 : (this.lineCount-1);
this.line = line2 ? (EditLine)line2 : this.lines.last;
ComputeColumn();
SelDirty();
SetViewToCursor(true);
UpdateDirty();
}
// TODO: Fix this vs modifiedDocument window property
void SetModified(bool flag)
{
if(this)
{
this.modified = false;
if(flag && !NotifyModified(master, this))
this.modified = true;
}
}
// BASIC OUTPUT
bool AddS(const char * string)
{
if(this)
{
bool ret = true;
const char * line;
int c, count;
int addedSpaces = 0, addedTabs = 0, xAdjustment = 0;
AddTextAction action = null;
ReplaceTextAction replaceAction = null;
this.pasteOperation = true;
if(style.stuckCaret /*|EES_READONLY)*/ )
GoToEnd(true);
if(!undoBuffer.dontRecord)
{
char * placeString = CopyString(string);
if(!style.multiLine)
{
int i;
char ch;
for(i = 0; (ch = placeString[i]); i++)
if(ch == '\n')
{
placeString[i] = '\0';
placeString = renew placeString byte[i+1];
break;
}
}
if(selX != x || selY != y)
{
char * newString;
char * oldString;
int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
oldString = new char[len];
newString = placeString;
GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
if(selY < y || (selY == y && selX < x))
{
replaceAction.x1 = selX;
replaceAction.y1 = selY;
replaceAction.x2 = x;
replaceAction.y2 = y;
}
else
{
replaceAction.x1 = x;
replaceAction.y1 = y;
replaceAction.x2 = selX;
replaceAction.y2 = selY;
}
Record(replaceAction);
}
else if(string[0])
{
if(string[0] == '\n')
action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
else
action = AddTextAction { y1 = y, x1 = x, string = placeString };
Record(action);
}
else
delete placeString;
}
undoBuffer.dontRecord++;
DelSel(&addedSpaces);
count = 0;
line = string;
for(c = 0; string[c]; c++)
{
if(string[c] == '\n' || string[c] == '\r')
{
if(!AddToLine(line, count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs, xAdjustment ? null : &xAdjustment))
{
ret = false;
break;
}
if(string[c] == '\n')
{
if(!AddCh('\n'))
{
count = 0;
ret = false;
break;
}
}
// Reset for next line
count = 0;
line = string+c+1;
/*
if(string[c] == '\r' && *line == '\n')
{
line++;
c++;
}*/
}
else
{
count++;
}
}
// Why was this here?
// FindMaxLine();
// Add the line here
if(ret && count)
if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs, xAdjustment ? null : &xAdjustment))
{
ret = false;
}
FindMaxLine();
undoBuffer.dontRecord--;
if(action)
{
action.y2 = y;
action.x2 = x;
action.addedSpaces = addedSpaces;
action.addedTabs = addedTabs;
action.xAdjustment = xAdjustment;
}
else if(replaceAction)
{
replaceAction.y3 = y;
replaceAction.x3 = x;
replaceAction.addedSpaces = addedSpaces;
replaceAction.addedTabs = addedTabs;
replaceAction.xAdjustment = xAdjustment;
}
UpdateCaretPosition(true);
this.pasteOperation = false;
return ret;
}
return false;
}
// CONSOLE OUTPUT
void Clear()
{
if(this)
{
Deselect();
DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
SetViewToCursor(true);
UpdateDirty();
Modified();
}
}
void PutCh(unichar ch)
{
bool result;
if((ch >= 32 /*&& ch <=126*/) || ch == '\n' || ch == '\t')
//if((ch >= 32) || ch == '\n')
{
int addedSpaces = 0, addedTabs = 0, xAdjustment = 0;
ReplaceTextAction replaceAction = null;
AddCharAction addCharAction = null;
if(style.allCaps)
ch = (ch < 128) ? toupper(ch) : ch; // TODO: UNICODE TO UPPER
if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
{
char buffer[5];
char * newString;
char * oldString;
int len = GetText(null, line, y, x, line, y, x+1, false, false);
oldString = new char[len];
UTF32toUTF8Len(&ch, 1, buffer, 4);
newString = CopyString(buffer);
GetText(oldString, line, y, x, line, y, x+1, false, false);
replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
replaceAction.x1 = x;
replaceAction.y1 = y;
replaceAction.x2 = x+1;
replaceAction.y2 = y;
Record(replaceAction);
undoBuffer.dontRecord++;
DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
undoBuffer.dontRecord--;
}
else if(!undoBuffer.dontRecord)
{
if(selX != x || selY != y)
{
char buffer[5];
char * newString;
char * oldString;
int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
oldString = new char[len];
UTF32toUTF8Len(&ch, 1, buffer, 4);
newString = CopyString(buffer);
GetText(oldString, line, y, x, selLine, selY, selX, false, false);
replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
if(selY < y || (selY == y && selX < x))
{
replaceAction.x1 = selX;
replaceAction.y1 = selY;
replaceAction.x2 = x;
replaceAction.y2 = y;
}
else
{
replaceAction.x1 = x;
replaceAction.y1 = y;
replaceAction.x2 = selX;
replaceAction.y2 = selY;
}
Record(replaceAction);
}
else
{
addCharAction = AddCharAction { y = y, x = x, ch = ch };
Record(addCharAction);
}
}
undoBuffer.dontRecord++;
result = _AddCh(ch, &addedSpaces, &addedTabs, &xAdjustment);
if(replaceAction)
{
replaceAction.x3 = x;
replaceAction.y3 = y;
replaceAction.addedSpaces = addedSpaces;
replaceAction.addedTabs = addedTabs;
replaceAction.addedTabs = xAdjustment;
}
if(addCharAction)
{
addCharAction.addedSpaces = addedSpaces;
addCharAction.addedTabs = addedTabs;
addCharAction.xAdjustment = xAdjustment;
}
undoBuffer.dontRecord--;
if(ch == '\n')
FindMaxLine();
Modified();
if(result) SetViewToCursor(true);
}
}
void PutS(const char * string)
{
if(this)
{
AddS(string);
SetViewToCursor(true);
Modified();
}
}
void Printf(const char * format, ...)
{
if(this)
{
char temp[MAX_F_STRING];
va_list args;
va_start(args, format);
vsnprintf(temp, sizeof(temp), format, args);
temp[sizeof(temp)-1] = 0;
va_end(args);
PutS(temp);
}
}
void SetContents(const char * format, ...)
{
if(this)
{
undoBuffer.dontRecord++;
Deselect();
DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
if(format)
{
char temp[MAX_F_STRING];
va_list args;
va_start(args, format);
vsnprintf(temp, sizeof(temp), format, args);
temp[sizeof(temp)-1] = 0;
va_end(args);
AddS(temp);
}
UpdateDirty();
Home();
undoBuffer.dontRecord--;
}
}
void BackSpace()
{
if(!DelSel(null))
{
if(x > 0)
{
if(x > line.count)
x--;
else
x -= 1 + _DelCh(line, y, x-1, line, y, x, true, false, null);
Modified();
}
else if(this.line.prev)
{
EditLine line = this.line.prev;
int x = line.count;
int y = this.y;
_DelCh(line, this.y-1, x, this.line, this.y, this.x, true, false, null);
this.line = line;
this.y = y-1;
this.x = x;
Modified();
}
this.selX = this.x;
this.selY = this.y;
this.selLine = this.line;
ComputeColumn();
}
else
Modified();
SetViewToCursor(true);
}
void ClearLine()
{
Emptyline(this.line,this.y);
this.selX = this.x = 0;
this.col = 0;
this.selY = this.y;
this.selLine = this.line;
SetViewToCursor(true);
Modified();
}
// CARET CONTROL
void End()
{
if(this)
{
GoToEnd(true);
SetViewToCursor(true);
}
}
void Home()
{
if(this)
{
GoToHome(true);
SetViewToCursor(true);
}
}
bool GoToLineNum(int lineNum)
{
if(this.line)
{
int c;
EditLine line = this.lines.first;
for(c = 0; c < lineNum && line; c++, line = line.next);
if(line)
{
if(this.y != c)
DirtyAll();
else
DirtyLine(c);
this.y = c;
this.line = line;
_Deselect();
SetViewToCursor(true);
return true;
}
}
return false;
}
// NOTE: Mismatch with NotifyCaretMove() for x/y + 1
bool GoToPosition(EditLine line, int y, int x)
{
/*
if(!line)
{
line = this.line;
y = this.y;
}
*/
if(!line)
{
int c;
for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
}
if(line)
{
if(this.y != y)
DirtyAll();
else
DirtyLine(y);
this.x = x;
this.y = y;
this.line = line;
ComputeColumn();
_Deselect();
SetViewToCursor(true);
return true;
}
return false;
}
// VIEW POSITIONING
void SetViewToCursor(bool setCaret)
{
if(created)
{
int numLines;
EditLine line;
int x;
int checkX, checkY;
EditLine checkLine;
bool dontScroll = false;
bool selected;
int viewX, viewY;
FixScrollArea();
selected = selX != this.x || selY != y;
viewX = this.viewX;
viewY = this.viewY;
if(mouseMove)
{
checkLine = dropLine;
checkX = dropX;
checkY = dropY;
}
else
{
checkLine = this.line;
checkX = this.x;
checkY = y;
}
numLines = clientSize.h / space.h;
// This is broken. The EditBox now doesn't do anything different when adding to it,
// regardless of the previous scrolling position. It should be read and then set again
// if one wishes to preserve it.
/* // Don't scroll view to cursor if we're in a EES_NOCARET box
if(style.noCaret && this.viewY < lineCount - numLines - 1)
dontScroll = true;
*/
// Horizontal Adjustment
if(!dontScroll && checkLine)
{
x = 0;
if(mouseMove)
dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
else
{
this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
ComputeColumn();
}
if(style.hScroll)
{
if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
viewX = x - clientSize.w+space.w;
if(x < this.viewX + clientSize.w/2 - space.w)
viewX = Max(0, x - clientSize.w/2 + space.w);
}
}
if(!dontScroll)
{
if(style.vScroll)
{
// Vertical Adjustment
if(viewY > checkY) viewY = checkY;
if(viewY + numLines <= checkY)
{
if(clientSize.h >= space.h)
{
for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
viewY++;
}
else
viewY = checkY;
}
}
else
{
if(mouseMove)
for(;dropLine && dropLine.prev && dropY >= numLines;)
{
dropLine = dropLine.prev;
dropY--;
}
else
for(;this.line && this.line.prev && this.y >= numLines;)
{
this.line = this.line.prev;
y--;
}
}
SetScrollPosition(viewX, viewY * this.space.h);
UpdateCaretPosition(setCaret);
if(!selected)
{
selX = this.x;
selY = this.y;
selLine = this.line;
}
}
UpdateDirty();
SetSelectCursor();
SelectionEnables();
}
}
void CenterOnCursor()
{
int numLines = clientSize.h / this.space.h;
int y = this.y - numLines / 2;
int viewY;
bool figureSyntax = false;
EditLine oldViewLine = viewLine;
if(y > this.lineCount - numLines) y = this.lineCount-numLines;
if(y < 0) y = 0;
viewY = y;
for(;y < this.viewY; y++)
{
this.viewLine = this.viewLine.prev;
style.recomputeSyntax = true;
}
for(;y > this.viewY; y--)
{
this.viewLine = this.viewLine.next;
figureSyntax = true;
}
if(figureSyntax && style.syntax)
highlighting.FigureStartSyntaxStates(oldViewLine, false, firstLine);
this.viewY = viewY;
SetScrollPosition(this.viewX, viewY * this.space.h);
UpdateCaretPosition(true);
UpdateDirty();
}
void SetCursorToView()
{
SetCursorToViewX();
SetCursorToViewY();
}
void PageDown()
{
int c, numLines;
EditLine line;
numLines = clientSize.h / this.space.h;
if(style.noCaret)
{
for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
}
else
{
//EditLine oldLine = this.line;
bool lastOne = false;
EditLine oldViewLine = this.viewLine;
bool figureSyntax = false;
if(this.y >= this.lineCount-1) return;
for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
{
if(this.viewY + numLines < this.lines.count)
{
this.viewLine = this.viewLine.next;
this.viewY++;
figureSyntax = true;
}
else if(c && !lastOne)
break;
else
lastOne = true;
this.line = line;
this.y++;
}
if(figureSyntax && style.syntax)
highlighting.FigureStartSyntaxStates(oldViewLine, false, firstLine);
this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
ComputeColumn();
SetViewToCursor(false);
}
}
void PageUp()
{
int c, numLines;
EditLine line;
if(this.y == 0) return;
numLines = clientSize.h / this.space.h;
if(style.noCaret)
{
for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
}
else
{
for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
{
this.line = line;
this.y--;
if(this.viewLine.prev)
{
this.viewLine = this.viewLine.prev;
this.viewY--;
style.recomputeSyntax = true;
}
}
this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
ComputeColumn();
SetViewToCursor(false);
}
}
void LineUp()
{
if(this.viewLine.prev)
{
//DirtyAll();
// this.viewLine = this.viewLine.prev;
//this.viewY--;
SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
}
}
void LineDown()
{
if(this.viewLine.next)
{
//DirtyAll();
// this.viewLine = this.viewLine.next;
// this.viewY++;
SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
}
}
// Selection
uint SelSize()
{
EditLine l1, l2, line;
int x1, x2, nx1, nx2;
int count;
int start, end;
int size;
if(!this.selLine) return 0;
if(this.selLine == this.line && this.selX == this.x) return 0;
if(this.selY < this.y)
{
l1 = this.selLine;
x1 = this.selX;
l2 = this.line;
x2 = this.x;
}
else if(this.selY > this.y)
{
l1 = this.line;
x1 = this.x;
l2 = this.selLine;
x2 = this.selX;
}
else if(this.selX < this.x)
{
l1 = l2 = this.line;
x1 = this.selX;
x2 = this.x;
}
else
{
l1 = l2 = this.line;
x1 = this.x;
x2 = this.selX;
}
nx1 = Min(x1,l1.count);
nx2 = Min(x2,l2.count);
// Find Number of Bytes Needed
size = 0;
for(line = l1; line; line = line.next)
{
if(line == l1) start = nx1; else start = 0;
if(line == l2) end = nx2; else end = line.count;
size += end-start;
if(style.freeCaret && line == l2)
{
if(l1 == l2)
count = Max(x2-Max(x1,l1.count),0);
else
count = Max(x2-l2.count,0);
size+=count;
}
if(line == l2) break;
// Add Carriage Return / line Feed
size++;
size++;
}
return size;
}
void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
{
if(this)
{
if(!reorder || this.selY < this.y)
{
if(l1) *l1 = this.selLine;
if(y1) *y1 = this.selY;
if(x1) *x1 = this.selX;
if(l2) *l2 = this.line;
if(y2) *y2 = this.y;
if(x2) *x2 = this.x;
}
else if(this.selY > this.y)
{
if(l1) *l1 = this.line;
if(y1) *y1 = this.y;
if(x1) *x1 = this.x;
if(l2) *l2 = this.selLine;
if(y2) *y2 = this.selY;
if(x2) *x2 = this.selX;
}
else if(this.selX < this.x)
{
if(l1) *l1 = this.line;
if(y1) *y1 = this.selY;
if(x1) *x1 = this.selX;
if(l2) *l2 = this.line;
if(y2) *y2 = this.y;
if(x2) *x2 = this.x;
}
else
{
if(l1) *l1 = this.line;
if(y1) *y1 = this.y;
if(x1) *x1 = this.x;
if(l2) *l2 = this.line;
if(y2) *y2 = this.selY;
if(x2) *x2 = this.selX;
}
}
}
void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
{
if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
{
this.selLine = (EditLine)l1;
this.selY = y1;
this.selX = x1;
this.line = (EditLine)l2;
this.y = y2;
this.x = x2;
ComputeColumn();
SetViewToCursor(true);
}
}
int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
{
EditLine l1, l2, line;
int x1, x2, nx1, nx2;
int count;
int start, end;
int numChars = 0;
if(_y2 < _y1)
{
l1 = _l2;
x1 = _x2;
l2 = _l1;
x2 = _x1;
}
else if(_y2 > _y1)
{
l1 = _l1;
x1 = _x1;
l2 = _l2;
x2 = _x2;
}
else if(_x2 < _x1)
{
l1 = l2 = _l1;
x1 = _x2;
x2 = _x1;
}
else
{
l1 = l2 = _l1;
x1 = _x1;
x2 = _x2;
}
nx1 = Min(x1,l1.count);
nx2 = Min(x2,l2.count);
// Copy text
for(line = l1; line; line = line.next)
{
if(line == l1) start = nx1; else start = 0;
if(line == l2) end = nx2; else end = line.count;
if(text)
{
CopyBytes(text, line.buffer + start, end - start);
text += end-start;
}
numChars += end-start;
if(style.freeCaret && line == l2 && addSpaces)
{
if(l1 == l2)
count = Max(x2-Max(x1,l1.count),0);
else
count = Max(x2-l2.count,0);
if(text)
{
FillBytes(text,' ',count);
text += count;
}
else
numChars += count;
}
if(line == l2) break;
// Add line Feed
if(addCr)
{
if(text)
*(text++) = '\r';
numChars++;
}
if(text)
*(text++) = '\n';
numChars++;
}
// '\0' terminate Terminate
if(text)
*(text) = '\0';
numChars++;
return numChars;
}
void GetSel(char * text, bool addCr)
{
GetText(text, line, y, x, selLine, selY, selX, addCr, true);
}
private void _Deselect() // This assumes marking lines as dirty is handled by the caller
{
selLine = line;
selX = x;
selY = y;
}
void Deselect()
{
SelDirty();
_Deselect();
}
// CLIPBOARD
void Copy()
{
if(this)
{
int size = SelSize();
if(size)
{
// Try to allocate memory
ClipBoard clipBoard { };
if(clipBoard.Allocate(size+1))
{
GetSel(clipBoard.memory, true);
// Save clipboard
clipBoard.Save();
}
delete clipBoard;
}
}
}
void Paste()
{
if(this)
{
ClipBoard clipBoard { };
if(clipBoard.Load())
PutS(clipBoard.memory);
delete clipBoard;
}
}
void Cut()
{
if(this)
{
Copy();
if(DelSel(null))
{
SetViewToCursor(true);
Modified();
}
}
}
void DeleteSelection()
{
if(this)
{
DelSel(null);
SetViewToCursor(true);
Modified();
}
}
// FILE INTERFACE
void Save(File f, bool cr)
{
EditLine line;
savedAction = undoBuffer.curAction;
for(line = this.lines.first; line; line = line.next)
{
f.Write(line.buffer, line.count, 1);
if(line.next)
{
if(cr) f.Putc('\r');
f.Putc('\n');
}
}
}
#define BUFFER_SIZE 16384
void Load(File f)
{
undoBuffer.dontRecord++;
if(f)
{
char buffer[BUFFER_SIZE];
for(;;)
{
int count = f.Read(buffer, 1, BUFFER_SIZE-1);
buffer[count] = '\0';
AddS(buffer);
if(!count) break;
}
Home();
}
undoBuffer.dontRecord--;
undoBuffer.count = 0;
undoBuffer.curAction = 0;
itemEditUndo.disabled = undoBuffer.curAction == 0;
itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
}
EditBoxFindResult Find(const char * text, bool matchWord, bool matchCase, bool isSearchDown)
{
EditLine line;
int num;
bool firstPass = true;
EditBoxFindResult result = found;
if(!this.line) return notFound;
num = this.y;
for(line = this.line;;)
{
char * string;
if(!line)
{
if(isSearchDown)
{
line = this.lines.first;
num = 0;
result = wrapped;
}
else
{
line = this.lines.last;
num = this.lineCount - 1;
result = wrapped;
}
}
if(isSearchDown)
string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
else
string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
if(string)
{
Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
return result;
}
if(line == this.line && !firstPass) break;
if(isSearchDown)
{
line = line.next;
num++;
}
else
{
line = line.prev;
num--;
}
firstPass = false;
}
return notFound;
}
EditBoxFindResult FindInSelection(const char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
{
EditLine line;
int y;
int searchLen = strlen(text);
for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
{
char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
{
Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
return found;
}
}
return notFound;
}
bool OnKeyDown(Key key, unichar ch)
{
#ifdef _DEBUG
//PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
#endif
if(!NotifyKeyDown(master, this, key, ch))
return false;
else
{
switch(key)
{
case escape:
{
if(this.mouseMove)
{
this.mouseMove = false;
OnLeftButtonUp(0,0,0);
SetViewToCursor(true);
return false;
}
}
}
}
return true;
}
};
public class EditBoxStream : File
{
EditBox editBox;
BufferLocation start, sel;
uint pos;
byte utf8Bytes[5];
int numBytes;
~EditBoxStream()
{
EditBox editBox = this.editBox;
editBox.x = start.x;
editBox.y = start.y;
editBox.line = start.line;
editBox.selX = sel.x;
editBox.selY = sel.y;
editBox.selLine = sel.line;
editBox.SetViewToCursor(true);
//editBox.ComputeColumn();
}
public:
property EditBox editBox
{
set
{
editBox = value;
start.x = value.x;
start.y = value.y;
start.line = value.line;
sel.x = value.selX;
sel.y = value.selY;
sel.line = value.selLine;
numBytes = 0;
value.GoToHome(true);
}
get { return editBox; }
}
uint Read(byte * buffer, uint size, uint count)
{
uint read = 0;
EditBox editBox = this.editBox;
EditLine line = editBox.line;
int x = editBox.x;
int y = editBox.y;
count *= size;
for(;read < count && line; line = (*&line.next))
{
int numBytes = Min(count - read, (*&line.count) - x);
if(numBytes > 0)
{
memcpy(buffer + read, (*&line.buffer) + x, numBytes);
read += numBytes;
x += numBytes;
}
/*
for(;read < count && x < (*&line.count);)
{
buffer[read++] = (*&line.buffer)[x++];
}
*/
if((*&line.next))
{
if(read < count)
{
buffer[read++] = '\n';
}
else
break;
}
if(x == (*&line.count) && (*&line.next))
{
x = 0;
y++;
}
else
break;
}
editBox.line = editBox.selLine = line;
editBox.x = editBox.selX = x;
editBox.y = editBox.selY = y;
pos += read;
return read / size;
}
bool Seek(int pos, FileSeekMode mode)
{
bool result = true;
EditBox editBox = this.editBox;
EditLine line = editBox.line;
numBytes = 0;
if(mode == FileSeekMode::start)
{
pos = pos - this.pos;
mode = current;
}
if(mode == current)
{
uint read = 0;
int x = editBox.x;
int y = editBox.y;
if(pos > 0)
{
for(;read < pos && line; line = (*&line.next))
{
int numBytes = Min(pos - read, (*&line.count) - x);
if(numBytes > 0)
{
read += numBytes; x += numBytes;
}
/*for(;read < pos && x < (*&line.count);)
{
read++; x++;
}
*/
if((*&line.next))
{
if(read < pos)
{
read++;
x = 0;
}
else
break;
}
else
{
if(read<pos)
result = false;
break;
}
y++;
}
this.pos += read;
}
else if(pos < 0)
{
pos = -pos;
for(;read < pos && line; line = (*&line.prev))
{
int numBytes = Min(pos - read, x);
if(numBytes > 0)
{
read += numBytes;
x -= numBytes;
}
/*for(;read < pos && x > 0;)
{
read++; x--;
}
*/
if((*&line.prev))
{
if(read < pos)
{
read++;
x = (*&(*&line.prev).count);
}
else
break;
}
else
{
if(read<pos)
result = false;
break;
}
y--;
}
this.pos -= read;
}
editBox.line = editBox.selLine = line;
editBox.x = editBox.selX = x;
editBox.y = editBox.selY = y;
}
return result;
}
bool Puts(const char * string)
{
EditBox editBox = this.editBox;
BufferLocation start { editBox.line, editBox.y, editBox.x };
BufferLocation pos;
numBytes = 0;
editBox.AddS(string);
pos.line = editBox.line;
pos.y = editBox.y;
pos.x = editBox.x;
this.start.AdjustAdd(start, pos);
sel.AdjustAdd(start, pos);
return true;
}
// NOTE: BYTE, NOT UNICODE CHARACTER!
bool Putc(char ch)
{
EditBox editBox = this.editBox;
BufferLocation start = { editBox.line, editBox.y, editBox.x };
BufferLocation pos;
if(numBytes < 4)
{
utf8Bytes[numBytes++] = ch;
utf8Bytes[numBytes] = 0;
if(UTF8Validate((char *)utf8Bytes))
{
editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
numBytes = 0;
pos.line = editBox.line;
pos.y = editBox.y;
pos.x = editBox.x;
this.start.AdjustAdd(start, pos);
sel.AdjustAdd(start, pos);
}
return true;
}
return false;
}
bool Getc(char * ch)
{
return Read(ch, 1, 1) ? true : false;
}
void DeleteBytes(uint count)
{
EditBox editBox = this.editBox;
if(count)
{
BufferLocation pos { editBox.line, editBox.y, editBox.x };
BufferLocation end = pos;
uint c = 0;
for(;;)
{
for(;c < count && end.line && end.x < end.line.count;)
{
end.x++;
c++;
}
if(c < count && end.line && end.line.next)
{
end.line = end.line.next;
end.y++;
c++;
end.x = 0;
}
else
break;
}
start.AdjustDelete(pos, end);
sel.AdjustDelete(pos, end);
editBox._DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true, false, null);
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment