Background color attribute for Fl_Text_Display. (#378)

* Background color attribute for Fl_Text_Display.

* Adding attributes.

* Avoid clipping horizontal

* Fl_Text_Display underlining.

* Better line positions

* Typos, testing.

* Documentation.
This commit is contained in:
Matthias Melcher 2022-01-27 23:50:06 +01:00 committed by GitHub
parent 84c0b7495b
commit cdb51045dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 243 additions and 144 deletions

View File

@ -142,10 +142,25 @@ public:
\see Fl_Text_Display::highlight_data()
*/
struct Style_Table_Entry {
Fl_Color color; ///< text color
Fl_Font font; ///< text font
Fl_Fontsize size; ///< text font size
unsigned attr; ///< currently unused (this may be changed in the future)
Fl_Color color; ///< text color
Fl_Font font; ///< text font
Fl_Fontsize size; ///< text font size
unsigned attr; ///< further attributes for the text style (see `ATTR_BGCOLOR`, etc.)
Fl_Color bgcolor; ///< text background color if `ATTR_BGCOLOR` or `ATTR_BGCOLOR_EXT` is set
};
/**
attribute flags in `Style_Table_Entry.attr`
*/
enum {
ATTR_BGCOLOR = 0x0001, ///< use the background color in the `bgcolor` field
ATTR_BGCOLOR_EXT_ = 0x0002, ///< (internal use)
ATTR_BGCOLOR_EXT = 0x0003, ///< extend background color to the end of the line
ATTR_UNDERLINE = 0x0004, ///< a single underline, underline types are mutually exclusive
ATTR_GRAMMAR = 0x0008, ///< grammar suggestion (blue dotted underline)
ATTR_SPELLING = 0x000C, ///< spelling suggestion (red dotted underline)
ATTR_STRIKE_THROUGH = 0x0010, ///< line through the middle of the text
ATTR_LINES_MASK = 0x001C, ///< the mask for all underline and strike through types
};
Fl_Text_Display(int X, int Y, int W, int H, const char *l = 0);

View File

@ -2022,141 +2022,148 @@ int Fl_Text_Display::handle_vline(
X = text_area.x - mHorizOffset;
}
startX = X;
startIndex = 0;
if (!lineStr) {
// just clear the background
if (mode==DRAW_LINE) {
style = position_style(lineStartPos, lineLen, -1);
draw_string( style|BG_ONLY_MASK, text_area.x, Y, text_area.x+text_area.w, lineStr, lineLen );
// In DRAW_LINE mode, the first iteration of the loop will draw all
// backgrounds. The second iteration will draw the text, so that text
// overlapping background color changes will not be clipped.
for (int loop=1; loop<=2; loop++) {
int mask = (loop==1) ? BG_ONLY_MASK : TEXT_ONLY_MASK;
startX = X;
startIndex = 0;
if (!lineStr) {
// just clear the background
if (mode==DRAW_LINE) {
style = position_style(lineStartPos, lineLen, -1);
if (loop==1)
draw_string( style|BG_ONLY_MASK, text_area.x, Y, text_area.x+text_area.w, lineStr, lineLen );
}
if (mode==FIND_INDEX) {
IS_UTF8_ALIGNED2(buffer(), lineStartPos)
return lineStartPos;
}
return 0;
}
if (mode==FIND_INDEX) {
IS_UTF8_ALIGNED2(buffer(), lineStartPos)
return lineStartPos;
}
return 0;
}
char currChar = 0, prevChar = 0;
styleX = startX; startStyle = startIndex;
// draw the line
style = position_style(lineStartPos, lineLen, 0);
for (i=0; i<lineLen; ) {
currChar = lineStr[i]; // one byte is enough to handele tabs and other cases
int len = fl_utf8len1(currChar);
if (len<=0) len = 1; // OUCH!
charStyle = position_style(lineStartPos, lineLen, i);
if (charStyle!=style || currChar=='\t' || prevChar=='\t') {
// draw a segment whenever the style changes or a Tab is found
double w = 0;
if (prevChar=='\t') {
// draw a single Tab space
double tab = col_to_x(mBuffer->tab_distance());
double xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
w = ((int(xAbs/tab)+1)*tab) - xAbs;
styleX = startX+w; startStyle = i;
if (mode==DRAW_LINE)
draw_string( style|BG_ONLY_MASK, int(startX), Y, int(startX+w), 0, 0 );
if (mode==FIND_INDEX && startX+w>rightClip) {
// find x pos inside block
free(lineStr);
if (cursor_pos && (startX+w/2<rightClip)) // STR #2788
return lineStartPos + startIndex + len; // STR #2788
return lineStartPos + startIndex;
}
} else {
// draw the text segment from the previous style change up to this point
if ( (style&0xff)==(charStyle&0xff)) {
w = string_width( lineStr+startStyle, i-startStyle, style ) - startX + styleX;
char currChar = 0, prevChar = 0;
styleX = startX; startStyle = startIndex;
// draw the line
style = position_style(lineStartPos, lineLen, 0);
for (i=0; i<lineLen; ) {
currChar = lineStr[i]; // one byte is enough to handele tabs and other cases
int len = fl_utf8len1(currChar);
if (len<=0) len = 1; // OUCH!
charStyle = position_style(lineStartPos, lineLen, i);
if (charStyle!=style || currChar=='\t' || prevChar=='\t') {
// draw a segment whenever the style changes or a Tab is found
double w = 0;
if (prevChar=='\t') {
// draw a single Tab space
double tab = col_to_x(mBuffer->tab_distance());
double xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
w = ((int(xAbs/tab)+1)*tab) - xAbs;
styleX = startX+w; startStyle = i;
if (mode==DRAW_LINE && loop==1)
draw_string( style|BG_ONLY_MASK, int(startX), Y, int(startX+w), 0, 0 );
if (mode==FIND_INDEX && startX+w>rightClip) {
// find x pos inside block
free(lineStr);
if (cursor_pos && (startX+w/2<rightClip)) // STR #2788
return lineStartPos + startIndex + len; // STR #2788
return lineStartPos + startIndex;
}
} else {
w = string_width( lineStr+startIndex, i-startIndex, style );
}
if (mode==DRAW_LINE) {
if (startIndex!=startStyle) {
fl_push_clip(int(startX), Y, int(w)+1, mMaxsize);
draw_string( style, int(styleX), Y, int(startX+w), lineStr+startStyle, i-startStyle );
fl_pop_clip();
// draw the text segment from the previous style change up to this point
if ( (style&0xff)==(charStyle&0xff)) {
w = string_width( lineStr+startStyle, i-startStyle, style ) - startX + styleX;
} else {
draw_string( style, int(startX), Y, int(startX+w), lineStr+startIndex, i-startIndex );
w = string_width( lineStr+startIndex, i-startIndex, style );
}
if (mode==DRAW_LINE) {
if (startIndex!=startStyle) {
fl_push_clip(int(startX), Y, int(w)+1, mMaxsize);
draw_string( style|mask, int(styleX), Y, int(startX+w), lineStr+startStyle, i-startStyle );
fl_pop_clip();
} else {
draw_string( style|mask, int(startX), Y, int(startX+w), lineStr+startIndex, i-startIndex );
}
}
if (mode==FIND_INDEX && startX+w>rightClip) {
// find x pos inside block
int di;
if (startIndex!=startStyle) {
di = find_x(lineStr+startStyle, i-startStyle, style, -int(rightClip-styleX)); // STR #2788
di = lineStartPos + startStyle + di;
} else {
di = find_x(lineStr+startIndex, i-startIndex, style, -int(rightClip-startX)); // STR #2788
di = lineStartPos + startIndex + di;
}
free(lineStr);
IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
return di;
}
if ( (style&0xff)!=(charStyle&0xff)) {
startStyle = i;
styleX = startX+w;
}
}
if (mode==FIND_INDEX && startX+w>rightClip) {
// find x pos inside block
int di;
if (startIndex!=startStyle) {
di = find_x(lineStr+startStyle, i-startStyle, style, -int(rightClip-styleX)); // STR #2788
di = lineStartPos + startStyle + di;
} else {
di = find_x(lineStr+startIndex, i-startIndex, style, -int(rightClip-startX)); // STR #2788
di = lineStartPos + startIndex + di;
}
free(lineStr);
IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
return di;
}
if ( (style&0xff)!=(charStyle&0xff)) {
startStyle = i;
styleX = startX+w;
style = charStyle;
startX += w;
startIndex = i;
}
i += len;
prevChar = currChar;
}
double w = 0;
if (currChar=='\t') {
// draw a single Tab space
double tab = col_to_x(mBuffer->tab_distance());
double xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
w = ((int(xAbs/tab)+1)*tab) - xAbs;
if (mode==DRAW_LINE && loop==1)
draw_string( style|BG_ONLY_MASK, int(startX), Y, int(startX+w), 0, 0 );
if (mode==FIND_INDEX) {
// find x pos inside block
free(lineStr);
if (cursor_pos) // STR #2788
return lineStartPos + startIndex + ( rightClip-startX>w/2 ? 1 : 0 ); // STR #2788
return lineStartPos + startIndex + ( rightClip-startX>w ? 1 : 0 );
}
} else {
w = string_width( lineStr+startIndex, i-startIndex, style );
if (mode==DRAW_LINE) {
// STR 2531
if (startIndex!=startStyle) {
fl_push_clip(int(startX), Y, int(w)+1, mMaxsize);
draw_string( style|mask, int(styleX), Y, int(startX+w), lineStr+startStyle, i-startStyle );
fl_pop_clip();
} else {
draw_string( style|mask, int(startX), Y, int(startX+w), lineStr+startIndex, i-startIndex );
}
}
style = charStyle;
startX += w;
startIndex = i;
}
i += len;
prevChar = currChar;
}
double w = 0;
if (currChar=='\t') {
// draw a single Tab space
double tab = col_to_x(mBuffer->tab_distance());
double xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
w = ((int(xAbs/tab)+1)*tab) - xAbs;
if (mode==DRAW_LINE)
draw_string( style|BG_ONLY_MASK, int(startX), Y, int(startX+w), 0, 0 );
if (mode==FIND_INDEX) {
// find x pos inside block
free(lineStr);
if (cursor_pos) // STR #2788
return lineStartPos + startIndex + ( rightClip-startX>w/2 ? 1 : 0 ); // STR #2788
return lineStartPos + startIndex + ( rightClip-startX>w ? 1 : 0 );
}
} else {
w = string_width( lineStr+startIndex, i-startIndex, style );
if (mode==DRAW_LINE) {
// STR 2531
if (startIndex!=startStyle) {
fl_push_clip(int(startX), Y, int(w)+1, mMaxsize);
draw_string( style, int(styleX), Y, int(startX+w), lineStr+startStyle, i-startStyle );
fl_pop_clip();
} else {
draw_string( style, int(startX), Y, int(startX+w), lineStr+startIndex, i-startIndex );
if (mode==FIND_INDEX) {
// find x pos inside block
int di;
if (startIndex!=startStyle) {
di = find_x(lineStr+startStyle, i-startStyle, style, -int(rightClip-styleX)); // STR #2788
di = lineStartPos + startStyle + di;
} else {
di = find_x(lineStr+startIndex, i-startIndex, style, -int(rightClip-startX)); // STR #2788
di = lineStartPos + startIndex + di;
}
free(lineStr);
IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
return di;
}
}
if (mode==FIND_INDEX) {
// find x pos inside block
int di;
if (startIndex!=startStyle) {
di = find_x(lineStr+startStyle, i-startStyle, style, -int(rightClip-styleX)); // STR #2788
di = lineStartPos + startStyle + di;
} else {
di = find_x(lineStr+startIndex, i-startIndex, style, -int(rightClip-startX)); // STR #2788
di = lineStartPos + startIndex + di;
}
if (mode==GET_WIDTH) {
free(lineStr);
IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
return di;
return int(startX+w);
}
}
if (mode==GET_WIDTH) {
free(lineStr);
return int(startX+w);
}
// clear the rest of the line
startX += w;
style = position_style(lineStartPos, lineLen, i);
if (mode==DRAW_LINE)
draw_string( style|BG_ONLY_MASK, int(startX), Y, text_area.x+text_area.w, lineStr, lineLen );
// clear the rest of the line
startX += w;
style = position_style(lineStartPos, lineLen, i);
if (mode==DRAW_LINE && loop==1)
draw_string( style|BG_ONLY_MASK, int(startX), Y, text_area.x+text_area.w, lineStr, lineLen );
}
free(lineStr);
IS_UTF8_ALIGNED2(buffer(), (lineStartPos+lineLen))
@ -2267,7 +2274,7 @@ void Fl_Text_Display::draw_string(int style,
const char *string, int nChars) const {
IS_UTF8_ALIGNED(string)
const Style_Table_Entry * styleRec;
const Style_Table_Entry *styleRec = NULL;
/* Draw blank area rather than text, if that was the request */
if ( style & FILL_MASK ) {
@ -2284,6 +2291,7 @@ void Fl_Text_Display::draw_string(int style,
int fsize = textsize();
Fl_Color foreground;
Fl_Color background;
Fl_Color bgbasecolor;
if ( style & STYLE_LOOKUP_MASK ) {
int si = (style & STYLE_LOOKUP_MASK) - 'A';
@ -2293,19 +2301,27 @@ void Fl_Text_Display::draw_string(int style,
styleRec = mStyleTable + si;
font = styleRec->font;
fsize = styleRec->size;
bgbasecolor = (styleRec->attr&ATTR_BGCOLOR) ? styleRec->bgcolor : color();
if (style & PRIMARY_MASK) {
if (Fl::focus() == (Fl_Widget*)this) {
if (Fl::screen_driver()->has_marked_text() && Fl::compose_state)
background = color();// Mac OS: underline marked text
else
if (Fl::screen_driver()->has_marked_text() && Fl::compose_state) {
background = bgbasecolor; // Mac OS: underline marked text
} else {
background = selection_color();
}
else background = fl_color_average(color(), selection_color(), 0.4f);
} else {
background = fl_color_average(bgbasecolor, selection_color(), 0.4f);
}
} else if (style & HIGHLIGHT_MASK) {
if (Fl::focus() == (Fl_Widget*)this) background = fl_color_average(color(), selection_color(), 0.5f);
else background = fl_color_average(color(), selection_color(), 0.6f);
} else background = color();
if (Fl::focus() == (Fl_Widget*)this) {
background = fl_color_average(bgbasecolor, selection_color(), 0.5f);
} else {
background = fl_color_average(bgbasecolor, selection_color(), 0.6f);
}
} else {
background = bgbasecolor;
}
foreground = (style & PRIMARY_MASK) ? fl_contrast(styleRec->color, background) : styleRec->color;
} else if (style & PRIMARY_MASK) {
if (Fl::focus() == (Fl_Widget*)this) background = selection_color();
@ -2332,11 +2348,44 @@ void Fl_Text_Display::draw_string(int style,
if (!(style & BG_ONLY_MASK)) {
fl_color( foreground );
fl_font( font, fsize );
int baseline = Y + mMaxsize - fl_descent();
// Make sure antialiased ÄÖÜ do not leak on line above:
// on X11+Xft the antialiased part of characters such as ÄÖÜ leak on the bottom pixel of the line above
static int can_leak = Fl::screen_driver()->text_display_can_leak();
if (can_leak) fl_push_clip(X, Y, toX - X, mMaxsize);
fl_draw( string, nChars, X, Y + mMaxsize - fl_descent());
// Clip top and bottom only. Add margin to avoid clipping horizontally
if (can_leak) fl_push_clip(x(), Y, w(), mMaxsize);
fl_draw( string, nChars, X, baseline);
if (styleRec) {
if (styleRec->attr & ATTR_LINES_MASK) {
int pitch = fsize/7;
int prevAA = fl_antialias();
fl_antialias(1);
switch (styleRec->attr & ATTR_LINES_MASK) {
case ATTR_UNDERLINE:
fl_color(foreground);
fl_line_style(FL_SOLID, pitch);
goto DRAW_UNDERLINE;
break;
case ATTR_GRAMMAR:
fl_color(FL_BLUE);
goto DRAW_DOTTED_UNDERLINE;
case ATTR_SPELLING:
fl_color(FL_RED);
DRAW_DOTTED_UNDERLINE:
fl_line_style(FL_DOT, pitch);
DRAW_UNDERLINE:
fl_xyline(X, baseline + fl_descent()/2, toX);
break;
case ATTR_STRIKE_THROUGH:
fl_color(foreground);
fl_line_style(FL_SOLID, pitch);
fl_xyline(X, baseline - (fl_height()-fl_descent())/3, toX);
break;
}
fl_line_style(FL_SOLID, 1);
fl_antialias(prevAA);
}
}
if (Fl::screen_driver()->has_marked_text() && Fl::compose_state && (style & PRIMARY_MASK)) {
fl_color( fl_color_average(foreground, background, 0.6f) );
fl_line(X, Y + mMaxsize - 1, X + (int)fl_width(string, nChars), Y + mMaxsize - 1);
@ -2379,21 +2428,31 @@ void Fl_Text_Display::clear_rect(int style,
if ( width == 0 )
return;
Fl_Color bgbasecolor = color();
if ( style & STYLE_LOOKUP_MASK ) {
int si = (style & STYLE_LOOKUP_MASK) - 'A';
if (si < 0) si = 0;
else if (si >= mNStyles) si = mNStyles - 1;
const Style_Table_Entry *styleRec = mStyleTable + si;
if (styleRec->attr&ATTR_BGCOLOR_EXT_)
bgbasecolor = styleRec->bgcolor;
}
Fl_Color c;
if (style & PRIMARY_MASK) {
if (Fl::focus()==(Fl_Widget*)this) {
c = selection_color();
} else {
c = fl_color_average(color(), selection_color(), 0.4f);
c = fl_color_average(bgbasecolor, selection_color(), 0.4f);
}
} else if (style & HIGHLIGHT_MASK) {
if (Fl::focus()==(Fl_Widget*)this) {
c = fl_color_average(color(), selection_color(), 0.5f);
c = fl_color_average(bgbasecolor, selection_color(), 0.5f);
} else {
c = fl_color_average(color(), selection_color(), 0.6f);
c = fl_color_average(bgbasecolor, selection_color(), 0.6f);
}
} else {
c = color();
c = bgbasecolor;
}
fl_color(active_r() ? c : fl_inactive(c));
fl_rectf( X, Y, width, height );
@ -2502,6 +2561,10 @@ void Fl_Text_Display::draw_cursor( int X, int Y ) {
Note that style is a somewhat incorrect name, drawing method would
be more appropriate.
If lineIndex is pointing to the last character in a line, and the second
to last character has the ATTR_BGCOLOR_EXT set, the background color will
extend into the remaining line.
\param lineStartPos beginning of this line
\param lineLen number of bytes in line
\param lineIndex position of character within line
@ -2520,9 +2583,21 @@ int Fl_Text_Display::position_style( int lineStartPos, int lineLen, int lineInde
pos = lineStartPos + min( lineIndex, lineLen );
if ( lineIndex >= lineLen )
if ( styleBuf && lineIndex==lineLen && lineLen>0) {
style = ( unsigned char ) styleBuf->byte_at( pos-1 );
if (style == mUnfinishedStyle && mUnfinishedHighlightCB) {
(mUnfinishedHighlightCB)( pos, mHighlightCBArg);
style = (unsigned char) styleBuf->byte_at( pos);
}
int si = (style & STYLE_LOOKUP_MASK) - 'A';
if (si < 0) si = 0;
else if (si >= mNStyles) si = mNStyles - 1;
const Style_Table_Entry *styleRec = mStyleTable + si;
if ((styleRec->attr&ATTR_BGCOLOR_EXT_)==0)
style = FILL_MASK;
} else if ( lineIndex >= lineLen ) {
style = FILL_MASK;
else if ( styleBuf != NULL ) {
} else if ( styleBuf != NULL ) {
style = ( unsigned char ) styleBuf->byte_at( pos );
if (style == mUnfinishedStyle && mUnfinishedHighlightCB) {
/* encountered "unfinished" style, trigger parsing */

View File

@ -57,6 +57,15 @@ const int line_num_width = 75;
Fl_Text_Buffer *stylebuf = 0;
Fl_Text_Display::Style_Table_Entry
styletable[] = { // Style table
#ifdef TESTING_ATTRIBUTES
{ FL_BLACK, FL_COURIER, TS }, // A - Plain
{ FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS, Fl_Text_Display::ATTR_BGCOLOR, FL_LIGHT2 }, // B - Line comments
{ FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS, Fl_Text_Display::ATTR_BGCOLOR_EXT, FL_LIGHT2 }, // C - Block comments
{ FL_BLUE, FL_COURIER, TS, Fl_Text_Display::ATTR_UNDERLINE }, // D - Strings
{ FL_DARK_RED, FL_COURIER, TS, Fl_Text_Display::ATTR_GRAMMAR }, // E - Directives
{ FL_DARK_RED, FL_COURIER_BOLD, TS, Fl_Text_Display::ATTR_STRIKE_THROUGH }, // F - Types
{ FL_BLUE, FL_COURIER_BOLD, TS, Fl_Text_Display::ATTR_SPELLING }, // G - Keywords
#else
{ FL_BLACK, FL_COURIER, TS }, // A - Plain
{ FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS }, // B - Line comments
{ FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS }, // C - Block comments
@ -64,6 +73,7 @@ Fl_Text_Display::Style_Table_Entry
{ FL_DARK_RED, FL_COURIER, TS }, // E - Directives
{ FL_DARK_RED, FL_COURIER_BOLD, TS }, // F - Types
{ FL_BLUE, FL_COURIER_BOLD, TS }, // G - Keywords
#endif
};
const char *code_keywords[] = { // List of known C/C++ keywords...
"and",
@ -188,7 +198,6 @@ style_parse(const char *text,
} else if (strncmp(text, "//", 2) == 0) {
current = 'B';
for (; length > 0 && *text != '\n'; length --, text ++) *style++ = 'B';
if (length == 0) break;
} else if (strncmp(text, "/*", 2) == 0) {
current = 'C';