You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PDCursesMod/pdcurses/color.c

761 lines
23 KiB
C

/* PDCursesMod */
#include <curspriv.h>
/*man-start**************************************************************
color
-----
### Synopsis
bool has_colors(void);
int start_color(void);
int init_pair(short pair, short fg, short bg);
int pair_content(short pair, short *fg, short *bg);
int init_extended_pair(int pair, int fg, int bg);
int extended_pair_content(int pair, int *fg, int *bg);
bool can_change_color(void);
int init_color(short color, short red, short green, short blue);
int color_content(short color, short *red, short *green, short *blue);
int init_extended_color(int color, int red, int green, int blue);
int extended_color_content(int color, int *red, int *green, int *blue);
int alloc_pair( int fg, int bg);
int assume_default_colors(int f, int b);
int find_pair( int fg, int bg);
int free_pair( int pair);
int use_default_colors(void);
void reset_color_pairs(void);
int PDC_set_line_color(short color);
### Description
To use these routines, first, call start_color(). Colors are always
used in pairs, referred to as color-pairs. A color-pair is created by
init_pair(), and consists of a foreground color and a background
color. After initialization, COLOR_PAIR(n) can be used like any other
video attribute.
has_colors() reports whether the terminal supports color.
start_color() initializes eight basic colors (black, red, green,
yellow, blue, magenta, cyan, and white), and two global variables:
COLORS and COLOR_PAIRS (respectively defining the maximum number of
colors and color-pairs the terminal is capable of displaying).
init_pair() changes the definition of a color-pair. It takes three
arguments: the number of the color-pair to be redefined, and the new
values of the foreground and background colors. The pair number must
be between 0 and COLOR_PAIRS - 1, inclusive. The foreground and
background must be between 0 and COLORS - 1, inclusive. If the color
pair was previously initialized, the screen is refreshed, and all
occurrences of that color-pair are changed to the new definition.
pair_content() is used to determine what the colors of a given color-
pair consist of.
init_extended_pair() and extended_pair_content() use ints for the
color pair index and the color values. These allow a larger number
of colors and color pairs to be supported, eliminating the 32767
color and color pair limits.
can_change_color() indicates if the terminal has the capability to
change the definition of its colors.
init_color() is used to redefine a color, if possible. Each of the
components -- red, green, and blue -- is specified in a range from 0
to 1000, inclusive.
color_content() reports the current definition of a color in the same
format as used by init_color().
init_extended_color() and extended_color_content() use integers for
the color index. This enables us to have more than 32767 colors.
assume_default_colors() and use_default_colors() emulate the ncurses
extensions of the same names. assume_default_colors(f, b) is
essentially the same as init_pair(0, f, b) (which isn't allowed); it
redefines the default colors. use_default_colors() allows the use of
-1 as a foreground or background color with init_pair(), and calls
assume_default_colors(-1, -1); -1 represents the foreground or
background color that the terminal had at startup. If the environment
variable PDC_ORIGINAL_COLORS is set at the time start_color() is
called, that's equivalent to calling use_default_colors().
alloc_pair(), find_pair() and free_pair() are also from ncurses.
free_pair() marks a pair as unused; find_pair() returns an existing
pair with the specified foreground and background colors, if one
exists. And alloc_pair() returns such a pair whether or not it was
previously set, overwriting the oldest initialized pair if there are
no free pairs.
reset_color_pairs(), also from ncurses, discards all color pair
information that was set with init_pair(). In practice, this means
all color pairs except pair 0 become undefined.
VT and WinCon define 'default' colors to be those inherited from
the terminal; SDLn defines them to be the colors of the background
image, if any. On all other platforms, and on SDLn if there's no
background images, the default background is black; the default
foreground is white.
PDC_set_line_color() is used to set the color, globally, for the
color of the lines drawn for the attributes: A_UNDERLINE, A_LEFT,
A_RIGHT, A_STRIKEOUT, and A_TOP. A value of -1 (the default) indicates
that the current foreground color should be used.
NOTE: COLOR_PAIR() and PAIR_NUMBER() are implemented as macros.
### Return Value
Most functions return OK on success and ERR on error. has_colors()
and can_change_colors() return TRUE or FALSE. alloc_pair() and
find_pair() return a pair number, or -1 on error.
### Portability
X/Open ncurses NetBSD
has_colors Y Y Y
start_color Y Y Y
init_pair Y Y Y
pair_content Y Y Y
can_change_color Y Y Y
init_color Y Y Y
color_content Y Y Y
alloc_pair - Y -
assume_default_colors - Y Y
find_pair - Y -
free_pair - Y -
use_default_colors - Y Y
PDC_set_line_color - - -
**man-end****************************************************************/
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
/* Color pair structure */
typedef struct _pdc_pair
{
int f; /* foreground color */
int b; /* background color */
int prev, next; /* doubly-linked list indices */
} PDC_PAIR;
int COLORS = 0;
int COLOR_PAIRS = 1; /* until start_color() is called */
static void _init_pair_core(int pair, int fg, int bg);
#define UNSET_COLOR_PAIR -2
static void _unlink_color_pair( const int pair_no)
{
PDC_PAIR *p = SP->pairs;
PDC_PAIR *curr = p + pair_no;
p[curr->next].prev = curr->prev;
p[curr->prev].next = curr->next;
}
static void _link_color_pair( const int pair_no, const int head)
{
PDC_PAIR *p = SP->pairs;
PDC_PAIR *curr = p + pair_no;
curr->next = p[head].next;
curr->prev = head;
p[head].next = p[curr->next].prev = pair_no;
}
static int _hash_color_pair( const int fg, const int bg)
{
int rval = (fg * 31469 + bg * 19583);
assert( SP->pair_hash_tbl_size);
rval ^= rval >> 11;
rval ^= rval << 7;
rval &= (SP->pair_hash_tbl_size - 1);
return( rval);
}
/* Linear/triangular-number hybrid hash table probing sequence. See
https://www.projectpluto.com/hashing.htm for details. */
#define GROUP_SIZE 4
#define ADVANCE_HASH_PROBE( idx, iter) \
{ idx++; \
if( iter % GROUP_SIZE == 0) idx += iter - GROUP_SIZE; \
idx &= (SP->pair_hash_tbl_size - 1); }
static void _check_hash_tbl( void)
{
assert( SP && SP->pairs);
if( SP->pair_hash_tbl_used * 5 / 4 >= SP->pair_hash_tbl_size)
{
int i, n_pairs;
PDC_PAIR *p = SP->pairs;
for( i = 1, n_pairs = 0; i < SP->pairs_allocated; i++)
if( p[i].f != UNSET_COLOR_PAIR)
n_pairs++;
SP->pair_hash_tbl_used = n_pairs;
SP->pair_hash_tbl_size = 8; /* minimum table size */
while( n_pairs >= SP->pair_hash_tbl_size * 3 / 4)
SP->pair_hash_tbl_size <<= 1; /* more than 75% of table is full */
if( SP->pair_hash_tbl)
free( SP->pair_hash_tbl);
SP->pair_hash_tbl = (hash_idx_t *)calloc(
SP->pair_hash_tbl_size, sizeof( hash_idx_t));
for( i = 1; i < SP->pairs_allocated; i++)
if( p[i].f != UNSET_COLOR_PAIR)
{
int idx = _hash_color_pair( p[i].f, p[i].b), iter;
for( iter = 0; SP->pair_hash_tbl[idx]; iter++)
ADVANCE_HASH_PROBE( idx, iter);
SP->pair_hash_tbl[idx] = (hash_idx_t)i;
}
}
}
static int _default_foreground_idx = COLOR_WHITE, _default_background_idx = COLOR_BLACK;
int start_color(void)
{
PDC_LOG(("start_color() - called\n"));
assert( SP);
if (!SP || SP->mono)
return ERR;
SP->color_started = TRUE;
PDC_set_blink(FALSE); /* Also sets COLORS */
if (!SP->default_colors && SP->orig_attr && getenv("PDC_ORIGINAL_COLORS"))
SP->default_colors = TRUE;
_init_pair_core( 0, _default_foreground_idx,
_default_background_idx);
if( !SP->_preserve)
curscr->_clear = TRUE;
#if !defined( CHTYPE_32) && !defined(OS2) && !defined(DOS)
if( COLORS >= 1024 && (long)INT_MAX > 1024L * 1024L)
COLOR_PAIRS = 1024 * 1024;
else if( COLORS >= 16)
{
if( (long)COLORS * (long)COLORS < (long)INT_MAX)
COLOR_PAIRS = COLORS * COLORS;
else
COLOR_PAIRS = INT_MAX;
}
#else
COLOR_PAIRS = (1 << PDC_COLOR_BITS);
#endif /* will be 256 (wide-char builds) or 4096 (8-bit chars) */
return OK;
}
void PDC_set_default_colors( const int fg_idx, const int bg_idx)
{
_default_foreground_idx = fg_idx;
_default_background_idx = bg_idx;
}
static void _normalize(int *fg, int *bg)
{
const bool using_defaults = (SP->orig_attr && (SP->default_colors || !SP->color_started));
if (*fg == -1 || *fg == UNSET_COLOR_PAIR)
*fg = using_defaults ? SP->orig_fore : _default_foreground_idx;
if (*bg == -1 || *fg == UNSET_COLOR_PAIR)
*bg = using_defaults ? SP->orig_back : _default_background_idx;
}
/* When a color pair is reset, all cells of that color should be
redrawn. refresh() and doupdate() don't redraw for color pair changes,
so we have to redraw that text specifically. The following test is
equivalent to 'if( pair == (int)PAIR_NUMBER( *line))', but saves a
few cycles by not shifting. */
#define USES_TARGET_PAIR( ch) (!(((ch) ^ mask) & A_COLOR))
static void _set_cells_to_refresh_for_pair_change( const int pair)
{
int x, y;
const chtype mask = ((chtype)pair << PDC_COLOR_SHIFT);
assert( SP->lines);
assert( curscr && curscr->_y);
if( !curscr->_clear)
for( y = 0; y < SP->lines; y++)
{
chtype *line = curscr->_y[y];
assert( line);
for( x = 0; x < SP->cols; x++)
if( USES_TARGET_PAIR( line[x]))
{
const int start_x = x++;
while( x < SP->cols && USES_TARGET_PAIR( line[x]))
x++;
PDC_transform_line_sliced( y, start_x, x - start_x, line + start_x);
}
}
}
/* Similarly, if PDC_set_bold(), PDC_set_blink(), or
PDC_set_line_color() is called (and changes the way in which text
with those attributes is drawn), the corresponding text should be
redrawn. */
void PDC_set_cells_to_refresh_for_attr_change( const attr_t attr)
{
int x, y;
assert( SP->lines);
assert( curscr && curscr->_y);
if( !curscr->_clear)
for( y = 0; y < SP->lines; y++)
{
const chtype *line = curscr->_y[y];
assert( line);
for( x = 0; x < SP->cols; x++)
if( line[x] & attr)
{
const int start_x = x++;
while( x < SP->cols && (line[x] & attr))
x++;
PDC_transform_line_sliced( y, start_x, x - start_x, line + start_x);
}
}
}
static void _init_pair_core(int pair, int fg, int bg)
{
PDC_PAIR *p;
bool refresh_pair;
assert( SP->pairs_allocated);
assert( pair < COLOR_PAIRS);
if( pair >= SP->pairs_allocated)
{
int i, new_size = SP->pairs_allocated * 2;
while( pair >= new_size)
new_size += new_size;
SP->pairs = (PDC_PAIR *)realloc( SP->pairs,
(new_size + 1) * sizeof( PDC_PAIR));
for( i = SP->pairs_allocated + 1; i <= new_size; i++)
{
p = SP->pairs + i;
p->f = UNSET_COLOR_PAIR;
_link_color_pair( i, SP->pairs_allocated);
}
SP->pairs_allocated = new_size;
}
assert( pair >= 0);
assert( pair < SP->pairs_allocated);
p = SP->pairs + pair;
/* To allow the PDC_PRESERVE_SCREEN option to work, we only reset
curscr if this call to init_pair() alters a color pair created by
the user. */
_normalize(&fg, &bg);
refresh_pair = (p->f != UNSET_COLOR_PAIR && (p->f != fg || p->b != bg));
_check_hash_tbl( );
if( pair && p->f != UNSET_COLOR_PAIR)
{
int idx = _hash_color_pair( p->f, p->b), iter;
for( iter = 0; SP->pair_hash_tbl[idx] != pair; iter++)
{
assert( SP->pair_hash_tbl[idx]);
ADVANCE_HASH_PROBE( idx, iter);
}
SP->pair_hash_tbl[idx] = -1; /* mark as freed */
}
if( pair)
_unlink_color_pair( pair);
p->f = fg;
p->b = bg;
if( pair && fg != UNSET_COLOR_PAIR)
{
int idx = _hash_color_pair( fg, bg), iter;
for( iter = 0; SP->pair_hash_tbl[idx] > 0; iter++)
ADVANCE_HASH_PROBE( idx, iter);
if( !SP->pair_hash_tbl[idx]) /* using a new pair */
SP->pair_hash_tbl_used++;
SP->pair_hash_tbl[idx] = (hash_idx_t)pair;
}
if( pair)
_link_color_pair( pair, (p->f == UNSET_COLOR_PAIR ? SP->pairs_allocated : 0));
if( refresh_pair)
_set_cells_to_refresh_for_pair_change( pair);
}
int init_extended_pair(int pair, int fg, int bg)
{
PDC_LOG(("init_pair() - called: pair %d fg %d bg %d\n", pair, fg, bg));
assert( SP);
if (!SP || !SP->color_started || pair < 1 || pair >= COLOR_PAIRS
|| fg < SP->first_col || fg >= COLORS
|| bg < SP->first_col || bg >= COLORS)
return ERR;
_init_pair_core(pair, fg, bg);
return OK;
}
bool has_colors(void)
{
PDC_LOG(("has_colors() - called\n"));
assert( SP);
return SP ? !(SP->mono) : FALSE;
}
int init_extended_color(int color, int red, int green, int blue)
{
PDC_LOG(("init_color() - called\n"));
assert( SP);
if (!SP || color < 0 || color >= COLORS || !PDC_can_change_color() ||
red < -1 || red > 1000 || green < -1 || green > 1000 ||
blue < -1 || blue > 1000)
return ERR;
SP->dirty = TRUE;
curscr->_clear = TRUE;
return PDC_init_color(color, red, green, blue);
}
int extended_color_content(int color, int *red, int *green, int *blue)
{
PDC_LOG(("color_content() - called\n"));
if (color < 0 || color >= COLORS || !red || !green || !blue)
return ERR;
if (PDC_can_change_color())
return PDC_color_content(color, red, green, blue);
else
{
/* Simulated values for platforms that don't support palette
changing */
int maxval = (color & 8) ? 1000 : 680;
*red = (color & COLOR_RED) ? maxval : 0;
*green = (color & COLOR_GREEN) ? maxval : 0;
*blue = (color & COLOR_BLUE) ? maxval : 0;
return OK;
}
}
bool can_change_color(void)
{
PDC_LOG(("can_change_color() - called\n"));
return PDC_can_change_color();
}
int extended_pair_content(int pair, int *fg, int *bg)
{
PDC_PAIR *p = SP->pairs + pair;
PDC_LOG(("pair_content() - called\n"));
if (pair < 0 || pair >= COLOR_PAIRS || !fg || !bg)
return ERR;
if( pair >= SP->pairs_allocated || (pair && p->f == UNSET_COLOR_PAIR))
{
*fg = COLOR_RED; /* signal use of uninitialized pair */
*bg = COLOR_BLUE; /* with visible, but odd, colors */
}
else
{
*fg = p->f;
*bg = p->b;
}
return OK;
}
int assume_default_colors(int f, int b)
{
PDC_LOG(("assume_default_colors() - called: f %d b %d\n", f, b));
if (f < -1 || f >= COLORS || b < -1 || b >= COLORS)
return ERR;
if (SP->color_started)
{
_init_pair_core(0, f, b);
curscr->_clear = TRUE;
}
return OK;
}
int use_default_colors(void)
{
PDC_LOG(("use_default_colors() - called\n"));
SP->default_colors = TRUE;
SP->first_col = -1;
return assume_default_colors(-1, -1);
}
int PDC_set_line_color(short color)
{
PDC_LOG(("PDC_set_line_color() - called: %d\n", color));
assert( SP);
if (!SP || color < -1 || color >= COLORS)
return ERR;
if( SP->line_color != color)
{
SP->line_color = color;
PDC_set_cells_to_refresh_for_attr_change(
WA_TOP | WA_UNDERLINE | WA_LEFT | WA_RIGHT | WA_STRIKEOUT);
}
return OK;
}
static void _init_color_table( SCREEN *sp)
{
PDC_PAIR *p;
sp->pairs_allocated = 1;
sp->pairs = (PDC_PAIR *)calloc( 2, sizeof(PDC_PAIR));
assert( sp->pairs);
if( !sp->pairs)
return;
p = (PDC_PAIR *)sp->pairs;
p[0].f = p[1].f = UNSET_COLOR_PAIR;
p[0].prev = p[0].next = 0;
p[1].prev = p[1].next = 1;
sp->default_colors = FALSE;
PDC_set_default_colors( _default_foreground_idx, _default_background_idx);
}
int PDC_init_atrtab(void)
{
assert( SP);
_init_color_table( SP);
_init_pair_core( 0,
(SP->orig_attr ? SP->orig_fore : _default_foreground_idx),
(SP->orig_attr ? SP->orig_back : _default_background_idx));
return( 0);
}
void PDC_free_atrtab(void)
{
assert( SP);
assert( SP->pairs);
if( SP->pair_hash_tbl)
free( SP->pair_hash_tbl);
SP->pair_hash_tbl = NULL;
SP->pair_hash_tbl_size = SP->pair_hash_tbl_used = 0;
if( SP->pairs)
free( SP->pairs);
}
int init_pair( short pair, short fg, short bg)
{
return( init_extended_pair( (int)pair, (int)fg, (int)bg));
}
int pair_content( short pair, short *fg, short *bg)
{
int i_fg, i_bg;
const int rval = extended_pair_content( (int)pair, &i_fg, &i_bg);
if( rval != ERR)
{
*fg = (short)i_fg;
*bg = (short)i_bg;
}
return( rval);
}
int init_color( short color, short red, short green, short blue)
{
return( init_extended_color( (int)color, (int)red, (int)green, (int)blue));
}
int color_content( short color, short *red, short *green, short *blue)
{
int i_red, i_green, i_blue;
const int rval = extended_color_content( (int)color, &i_red, &i_green, &i_blue);
if( rval != ERR)
{
*red = (short)i_red;
*green = (short)i_green;
*blue = (short)i_blue;
}
return( rval);
}
int find_pair( int fg, int bg)
{
int idx = _hash_color_pair( fg, bg), iter;
assert( SP);
assert( SP->pairs_allocated);
for( iter = 0; SP->pair_hash_tbl[idx]; iter++)
{
int i;
if( (i = SP->pair_hash_tbl[idx]) > 0)
{
PDC_PAIR *p = SP->pairs;
if( p[i].f == fg && p[i].b == bg)
{
_unlink_color_pair( i); /* unlink it and relink it */
_link_color_pair( i, 0); /* to make it the 'head' node */
return( i); /* we found the color */
}
}
ADVANCE_HASH_PROBE( idx, iter);
}
return( -1);
}
/* alloc_pair() first simply looks to see if the desired pair is
already allocated. If it has been, we're done.
If it hasn't been, the doubly-linked list of free color
pairs (see 'pairs.txt') will indicate an available node. If
we've actually run out of free color pairs, the doubly-linked
list of used color pairs will link to the oldest inserted node.
*/
int alloc_pair( int fg, int bg)
{
int rval = find_pair( fg, bg);
if( -1 == rval) /* pair isn't already allocated. First, look */
{ /* for an unset color pair. */
PDC_PAIR *p = SP->pairs;
rval = p[SP->pairs_allocated].prev;
assert( rval);
if( COLOR_PAIRS == rval) /* all color pairs are in use; */
rval = p[0].prev; /* 'repurpose' the oldest pair */
if( ERR == init_extended_pair( rval, fg, bg))
rval = -1;
assert( rval != -1);
}
return( rval);
}
int free_pair( int pair)
{
PDC_PAIR *p;
assert( SP && SP->pairs);
assert( pair >= 1 && pair < SP->pairs_allocated);
p = SP->pairs + pair;
assert( p->f != UNSET_COLOR_PAIR);
if (!SP || !SP->color_started || pair < 1 || pair >= SP->pairs_allocated
|| p->f == UNSET_COLOR_PAIR)
return ERR;
_init_pair_core(pair, UNSET_COLOR_PAIR, 0);
return OK;
}
void reset_color_pairs( void)
{
assert( SP && SP->pairs);
if( SP->pair_hash_tbl)
free( SP->pair_hash_tbl);
if( SP->pairs)
free( SP->pairs);
SP->pair_hash_tbl = NULL;
SP->pair_hash_tbl_size = SP->pair_hash_tbl_used = 0;
_init_color_table( SP);
_init_pair_core( 0,
(SP->orig_attr ? SP->orig_fore : _default_foreground_idx),
(SP->orig_attr ? SP->orig_back : _default_background_idx));
curscr->_clear = TRUE;
}
#ifdef PDC_COLOR_PAIR_DEBUGGING_FUNCTIONS
/* The following is solely for testing the color pair table, and
specifically its two doubly-linked lists (one of 'used' pairs, one of
'free' pairs). The elements in both lists are counted. The total should
equal the number of allocated pairs. All pairs in the first linked list
are checked to make sure they're really used; all in the second to make
sure they're really free. We also check that the links are consistent.
The return value is 0 if the table checks out, -1 if it does not.
'results' contains the number of used pairs, the number of free pairs,
and the number of allocated pairs (which should be the sum of the first
two numbers.) It also returns some data on the hash table size and
usage. See 'pairs.txt' for more details. */
int PDC_check_color_pair_table( int *results)
{
int idx, n_used = 1, n_free = 1;
PDC_PAIR *p;
assert( SP && SP->pairs);
p = (PDC_PAIR *)SP->pairs;
idx = 0;
while( n_used < SP->pairs_allocated + 10 && p[idx].next)
{ /* loop through all _used_ color pairs */
const int next = p[idx].next;
assert( p[idx].f != UNSET_COLOR_PAIR);
assert( next >= 0 && next < SP->pairs_allocated);
assert( p[next].prev == idx);
idx = p[idx].next;
n_used++;
}
idx = SP->pairs_allocated;
while( n_free < SP->pairs_allocated + 10 && p[idx].next != SP->pairs_allocated)
{ /* loop through all _free_ color pairs */
const int next = p[idx].next;
assert( p[idx].f == UNSET_COLOR_PAIR);
assert( next > 0 && next <= SP->pairs_allocated);
assert( p[next].prev == idx);
idx = p[idx].next;
n_free++;
}
if( results)
{
results[0] = n_used;
results[1] = n_free;
results[2] = SP->pairs_allocated + 1; /* include the 'dummy' pair */
results[3] = SP->pair_hash_tbl_size;
results[4] = SP->pair_hash_tbl_used;
}
return( (n_used + n_free == SP->pairs_allocated + 1) ? 0 : -1);
}
#endif /* #ifdef PDC_COLOR_PAIR_DEBUGGING_FUNCTIONS */