2.1 curses库简介
curses库是curses开发包中最重要的一个库,其中提供了一些基本的屏幕操作函数,包括输入/输出,屏幕初始化,屏幕处理中断以及窗口的创建和操作等操作。curses窗口在第三章我们单独讨论。curses库中提供的函数一共将近200个,在本章中我们不可能每个函数都涉及到,我们涉及到仅是最常用的一部分。剩下的一些函数可以通过unix中的帮助命令man curses进行了解。
2.1.1如何在程序中使用curses
为了能够在程序中使用curses库函数,在程序中我们必须引用curses库的头文件curses.h,即下面的一行:
#include <curses.h>
由于curses使用标准I/O,因此这个库一旦被引用,系统自动的将<stdio.h>和<unclt.h>一起包含进来,如果对于System V系统,<terminfo.h>也会包含进来,另外还可能包括<termios.h>、<termio.h>、<sgtty.h>,这具体的由系统本身决定。程序编写结束后我们使用下面的命令进行编译和链接,生成可执行文件:
cc [flags] file –lcurses
-lcurses参数用来在链接的时候提示链接程序将curses库链接进去。
2.1.2 curses中的常量定义
头文件curses.h中定义了四个整型常量,两个常量是大多数的curses函数的返回结果,另外两个是布尔类型的值,一旦我们包含了curses头文件,我们就可以在程序中直接使用它们,这四个常量是是:
OK curses函数运行成功的返回值,系统定义为0
ERR curses函数运行发生错误时候的返回值,系统定义为-1
TRUE 布尔值,表示为真,系统定义为1
FALSE 布尔值,表示为假,系统定义为0
2.1.3标准屏幕与当前屏幕
在第二章我们谈到refresh()的时候曾谈到了标准屏幕和当前屏幕的概念。如果程序中使用了curses库,那么在程序初始化的时候,系统将自动产生两个默认屏幕,第一个我们即是标准屏幕,系统定义为stdscr,它代表终端的整个屏幕区域。第二个就是当前屏幕,系统定义为curscr。为了了解当前屏幕的概念,我们首先必须对curses库的屏幕刷新机制有所了解。
通常终端的刷新频率都不是很高,因此频繁的刷新可能导致显示速度非常的慢。因此curses库对终端屏幕刷新进行了一些优化处理。在curses库中我们对标准屏幕的任何更新并不会立即反映到屏幕上,而是必须等我们调用了函数refresh()或者wrefresh()之后,改变的效果才会在屏幕上真正显示出来。当前屏幕就是我们能够看到的屏幕。如果我们对当前屏幕进行更改而还没有调用刷新函数,那么标准屏幕即就是当前屏幕已经进行了更新但是还没有调用refresh()函数时候即没有显示出来时候的屏幕,它仅是一个虚拟屏幕。在屏幕刷新的时候curses库系统将标准屏幕与当前屏幕进行比较,然后仅仅刷新物理终端屏幕上的它们之间的不同之处,一旦进行了刷新,当前屏幕将与标准屏幕相同。同时一旦当前屏幕进行了改动,标准屏幕将发生变化直到下一次刷新。这种刷新效率最低的时候就是标准屏幕与当前屏幕完全不同,不过在大部分情况下都能够加快显示。
2.1.4 curses命名规范
在curses 中我们使用最多的就是标准屏幕stdscr,因此很多的函数都是默认直接作用于stdscr上。但很多的情况下,我们需要的不是标准屏幕,而是程序自定义窗口。由于标准屏幕也是一种特殊的窗口,因此原来的那些函数应该只要改动作用对象就可以直接应用在程序自定义窗口上。通常情况下,如果函数是针对所有的窗口应用而不是标准屏幕,那么这些函数前面按照curses命令规范就必须具有一个“w”作为前缀,同时它的第一个参数是一个WINDOW结构的窗口指针。这个窗口指针用来确定函数操作的自定义窗口。
比如按照命名规范,move(y,x)函数应该是作用于stdscr,而wmove(win,y,x)应该作用任意窗口,其中包括stdscr。 wmove(stdscr,y,x)等同于move(y,x)。而且事实上curses中很多的类似与move(y,x)的函数都不是真正的函数,它们是一些宏定义,比如move(y,x)的可能如下:
#define move(y,x) wmove(win,y,x)
curses中的另一个命名规范就是移动操作与输入/输出操作在同一个函数里面的结合。这些函数在名称前面加上mv,然后将需要移动的坐标(y,x)增加到参数列表中。比如下面的代码:
move(y,x);
addch(ch);
按照curses命令规范,可以合并为函数mvaddch(y,x,ch)。同样对于下面的代码
wmove(y,x) ;
waddch(ch);
按照命名规范也可以使用函数mvwaddch(y,x,ch);
另外对于不同组件的函数都会在前面加上组件名作为前缀,比如面板函数通常形如panel_x形式,而菜单函数则形如menu_x形式。
2.2终端模式
在了解curses库函数之前我觉得有必要先介绍一下终端的模式设置,终端模式实际上是一系列的开关,它规定了终端的一些属性,比如如何显示字符,如何解释读取字符等等,它的设置与程序的运行有直接的影响。
当我们在UNIX系统上进行输入或者显示一个字符的时候,UNIX其实在幕后进行了很多重要的处理,比如:
■ 当我们敲入字符的时候,系统将根据模式属性设置来判断字符是否需要立即在屏幕上回显。
■ 系统根据模式设置决定读取字符后的处理方法,是立即一一读取还是暂时先存放在字符缓冲区中。
■ 系统判断是否需要将输入<Ctrl>D(control D)解释为文件结束符。
■ 系统判断如何处理功能键F1、F2或者方向键等等,决定是将它们作为普通的键读取还是作为功能键读取。
curses库程序维护了所有屏幕上的控制,因此curses 在UNIX系统上关闭了回显功能即UNIX系统本身对字符显示不进行处理,而由它自己进行回显。一些curses函数比如noecho() 和cbreak()都被设计来改变标准字符处理。在程序中通过这些函数来控制如何解释输入字符。
下面我们对终端的一些模式属性进行解释,这些模式属性在后面的部分会经常遇到和使用。
2.2.1 ECHO模式
ECHO模式即回显模式,ECHO模式用来决定用户的输入是否立即回显。当ECHO模式设置后,它使得在键盘上输入的每一个字符都在终端屏幕上当前光标处显示出来,在调用某些函数如addch()的时候字符显示后光标的位置将自动的向后移动一个位置。在非回显模式下,字符的显示必须由程序本身来完成,否则字符不会显示。非回显模式下按键通常用来控制屏幕的操作而不是用来进行字符输入。函数语法如表2.1所示。
表2.1 echo()模式函数概述
头文件
curses.h
概述
int echo()
int noecho()
返回
成功
失败
OK
ERR
echo()用来设置回显模式,noecho()关闭回显模式。默认情况下回显模式是打开的。
2.2.2 CBREAK模式
CBREAK模式也称之为立即输入模式。字符输入在一般的情况下必须加回车键才能完成,这时候退格键是起作用的,输入的字符可以通过BackSpace键删除掉。但是这种操作并不适合交互操作,所以程序有时候把终端模式设置为CBREAK模式。在CBREAK模式下,除了DELETE或者CTRL等仍然被视为特殊控制字符以外,所有的输入字符都被一一立即读取出来。如果没有设置CBREAK模式,从键盘输入的字符都将被存储在缓冲区里面直到输入回车键或者行结束符。中断字符和流控制字符并不受这个模式的影响。函数语法如表2.2所示。
表2.2 立即输入模式函数概述
头文件
curses.h
概述
int cbreak()
int nocbreak()
返回
成功
失败
OK
ERR
默认情况下CBREAK模式是打开的。在旧版本的curses中,必须使用crmode()和nocrmode()取代cbreak()和nocbreak()。
2.2.3 NEWLINE模式
NEWLINE模式用来决定当输入资料时回车键是否被对应为新行产生符(NEWLINE),比如‘/n’;而输出时NEWLINE字符是否对应于回车键。默认情况下,NEWLINE模式是打开的。nl()和nonl()函数用来为终端设置和取消NEWLINE模式。
表2.3 NEWLINE模式函数概述
头文件
curses.h
概述
int nl()
int nonl()
返回
成功
失败
OK
ERR
如果终端设置成NEWLINE模式,那么在输入字符的时候,按下回车键将会产生新的一行,同时在输出字符的时候换行符将对应成回车键。
2.2.4 功能键模式
通常情况下功能键比如左移方向键‘←’是不会被读取转换的,因此即使这时候我们调用wgetch()之类的函数也不能将它们读取出来。为了读取这些特殊键,我们必须设置功能键模式。一旦功能键模式开启,键盘上的一些特殊字符比如上下左右以及F1,F2等等按键都可以转换为curses.h内部定义的一些特殊键,这些键通常以“KEY_”开始,比如KEY_LEFT、KEY_DOWN、KEY_F0等等。功能键开启函数为keypad(),语法如表2.4所示。
表2.4 功能键模式函数概述
头文件
curses.h
概述
int keypad(win,buf)
WINDOW *win;
int buf;
返回
成功
失败
OK
ERR
win为一个WINDOW类型的指针,它指向需要设置功能键模式的窗口,buf为TRUE或者FALSE,用来指定模式的开启和关闭。
curses.h定义的特殊功能键现列举如表2.5所示。
表2.5功能键
功能键定义
控制码
描述
KEY_MIN
0401
Curses中定义的最小的键值
KEY_BREAK
0401
Break按键
KEY_DOWN
0402
↓
KEY_UP
0403
↑
KEY_LEFT
0404
←
KEY_RIGHT
0405
→
KEY_HOME
0406
Home键
KEY_BACKSPACE
0407
退格键backspace
KEY_F0
0410
功能键F0
KEY_F(n)
KEY_F0+n
功能键Fn
KEY_DL
0510
行删除键
KEY_IL
0511
行插入键
KEY_DC
0512
字符删除键
KEY_IC
0513
字符插入键
KEY_NPAGE
0522
下一页
KEY_PPAGE
0523
上一页
KEY_END
0550
end按键
KEY_MAX
0777
最大的curses键值
上面列出的仅是最常用的,完整的资料可以在文件<tinfo.h>找到,通常它的目录为/usr/include/目录下。
2.2.5 RAW模式
RAW模式也称之为原始模式,它与CBREAK模式类似,用户的输入会立即被接受,但是某些中断字符,转义字符以及挂起和流控制字符将不再起作用,取而代之的是产生一个相应的信号。raw()和noraw()可以用来设置原始模式,CBREAK模式是覆盖RAW模式的,如果同时设置CBREAK和RAW模式,将仅有RAW模式起作用。
表2.6 原始模式函数概述
头文件
curses.h
概述
int raw();
int noraw();
返回
成功
失败
OK
ERR
2.2.6延迟模式
延迟模式包括半延迟模式和无延迟模式。
半延迟模式与CBREAK模式也非常的类似,这种模式下的所有的用户输入都是立即被接受的,但是如果在一段时间内没有用户输入,则输入函数立即返回ERR。这段时间间隔可以自己指定,单位通常为1/10秒,范围为1-255。
无延迟模式主要用来控制终端的字符输入。一般情况下,终端输入函数比如getch()是阻塞调用,即一直等待直到键盘输入才返回。而无延迟模式可以将这种调用更改为非阻塞调用,即一旦getch()发现键盘没有任何输入它就返回错误ERR。
半延迟和无延迟模式的函数语法如表2.7。
表2.7 延迟函数概述
头文件
curses.h
概述
int halfdelay( tenth)
int tenth;
int nodelay(win,bf)
WINDOW *win;
bool bf;
返回
成功
失败
OK
ERR
halfdelay()设置半延迟模式,参数tenth指定半延迟的时间间隔,单位是10毫秒。使用函数nocbreak() 函数可以取消终端的半延迟模式。
nodelay()函数设置终端无延迟模式。win是指向需要设置无延迟模式的窗口的指针,bf用来决定开启或者关闭该模式。如果bf为TRUE,则设置无延迟模式。
2.3字符以及字符串操作
在确保终端进行了正确的设置之后我们就可以对终端进行一些操作。字符和字符串操作是应用程序中使用频率最高的, curses库中的一些基本函数允许我们从键盘接受输入,并且将结果输出到指定窗口上或者在指定窗口上读写、删除字符和字符串、定位光标位置或者控制字符色彩等等。为了防止重复,下面我们将描述最常使用的标准屏幕上的字符和字符串操作函数,指定窗口上的函数它们命名符合curses函数命名规范,请自行参考帮助文件确定。
2.3.1字符、字符串输出
curses中提供了addch()、addstr()函数分别用来在标准屏幕上输出字符和字符串。同时提供了printw()函数进行格式化屏幕输出。
2.3.1.1 addch()函数
addch()函数将给定的字符输出到标准屏幕上的当前位置,它的参数是一个chtype类型的字符,函数语法如表2.8所示。
表2.8 增加字符函数概述
头文件
curses.h
概述
int addch(ch);
chtype ch;
int echochar(ch);
chtype ch;
返回
成功
失败
OK
ERR
addch()函数是curses中的核心输出函数,许多其余的函数都是从它演变过去的。如果函数执行成功,将返回OK常量,否则返回ERR。
函数在当前光标处输出一个字符,同时光标将向右移动一个位置。如果移动后光标将超出了当前屏幕的范围,光标将自动换行到下一行的开始位置。例如如果光标的当前位置为(0,0),下面的代码将在(0,0) 处输出字符‘a’,并且将光标移动到位置(0,1)处:
addch(‘a’);
假设当前终端屏幕的COLS为80,那么如果光标位置为(0,79),addch(‘a’)将在光标处输出‘a’,同时光标移动到(1,0)处。
在这里我们可能会有个疑惑,那就是为什么参数的类型不是char而是chtype?chtype类型是什么类型?curses中将chtype声明为无符号长整型,它的低位可以包含字符本身的信息,这部分与char字符类型相似,它的高位部分存储了字符的额外的属性信息,比如字符是否高亮度显示,是否反显以及什么色彩等等,这在对字符操作的时候非常有用,而char类型却做不到这一点。关于字符属性的更多的内容在这章的”字符属性中有更多的描述。
除了常用的字符参数以外,addch()函数还可以使用的C语言中的转义字符:
/n 换行。删除从当前位置到行尾的所有的字符,并且将字符指针向下移动一 行。
如果设置了newline标志,addch函数将删除光标后的所有字符并将字符指针置
于下一行的开始处。
/r 回车,将字符指针置于当前行的开始处。
/t 制表符,将字符指针移动到下一个制表符处。
按照命令规范,由addch()衍生出来的函数还有“waddch()”、“mvaddch()”、“mvwaddch()”,它们的用法不再赘述。
除了上面的这些常用字符之外,curses中还定义了一些特殊的行图形字符,它们通常用来绘图或者制表。curses中的字符实际上就是由它们中的部分组成。对于这些特殊的符号,curses中定义了一些常量与之对应,这些常量都以ACS_开始,通常称之为ACS_常量,目前常用的的有如表2.9所示。
表2.9 常用ACS_常量
变量名称
符号
ACS_ULCORNER
┏
ACS_LLCORNER
┗
ACS_URCORNER
┓
ACS_LRCORNER
┗
ACS_RTEE
┫
ACS_LTEE
┣
ACS_BTEE
┻
ACS_TTEE
┳
ACS_HLINE
━
ACS_VLINE
┃
ACS_PLUS
╋
ACS_S1
▁
ACS_S9
▔
ACS_DIAMOND
◆
ACS_CKBOARD
▓
ACS_DEGREE
o
ACS_PLMINUS
±
ACS_BULLET
·
ACS_LARROW
<
ACS_RARROW
>
ACS_UARROW
∧
ACS_DARROW
∨
ACS_BOARD
#
ACS_LANTERN
#
BLOCK
#
addch()函数输出后并不执行屏幕刷新,因此为了能够显示输出结果,我们就必须调用refresh()函数。curses中提供了另外一个函数echochar()函数可以在完成字符输出的同时完成刷新,这在使用非控制字符的时候非常方便。它的函数语法见表2.8所示。
2.3.1.2 addstr()函数
addch()函数只能在当前位置增加一个字符,如果需要增加字符串,那就需要使用addstr()函数。字符串的首字符将在当前光标处输出。它的参数是一个字符指针,语法如表2.10所示。
表2.10 增加字符串函数概述
头文件
curses.h
概述
int addstr(str);
char *str;
返回
成功
失败
OK
ERR
如果当前光标位置是(0,0),我们需要在当前位置处输出字符串“line”,代码可以如下:
addstr(“line”);
addstr()函数将首字符‘l’置于(0,0)处,同时将光标移动到(0,4)处。如果字符串中包含了/n,/r,/t等字符,addstr()将象addch()中一样执行相应的操作。如果字符串的长度超出了屏幕的大小,字符串将被截取掉。
curses中与addstr()类似的函数还包括waddstr()、mvwaddstr()、mvaddstr()。
2.3.1.3 printw()函数
printw()函数在屏幕上格式化输出一个或者多个值,这些值包括字符串、字符、十进制数、八进制数、以及二进制数,函数的语法如表2.11所示。
表2.11 格式化输出函数概述
头文件
curses.h
概述
int printw(fmt [,arg…])
char *fmt;
返回
成功
失败
OK
ERR
这个函数主要用来在标准屏幕上格式化输出。它与C语言中的printf()类似,也是变参函数,而且用法可以说是完全相同。它的参数解释如下:
fmt 是一个字符串指针,用来表示打印的格式,比如对于字符串格式为%s,字符为%c,十进制整数为%d,八进制整数为%o等等。
arg 是需要打印的值,如果给出的arg不止一个,每一个都必须用逗号隔开,它们必须与fmt的格式相适应。如果fmt为%s格式,则相应的arg参数必须为一个字符串指针,如果fmt为%d格式,则相应的arg参数必须为整数。
例如如果我们需要在屏幕(0,0)处输出字符串name和整数15,那么代码如下:
printw(“%s %d”,name,15);
与它类似的函数包括mvprintw(),mvwprintw(),wprintw()。
2.3.1.4字符输出示例
程序2-1是一个简单的字符和字符串输出示例。
程序2-1 字符输出示例
#include <curses.h>
int main(void)
{
initscr();
noecho();
addch(‘x’);
waddch(stdscr,‘y’|A_UNDERLINE|A_BOLD);
mvaddch(2,1,‘z’|A_BOLD);
refresh();
sleep(3);
clear();
addstr(“字符串输出使用示例”);
waddstr(stdscr,”/n字符串输出使用示例”);
mvwaddstr(stdscr,2,1,”字符串使用示例”);
refresh();
sleep(3);
clear();
printw(“%s%d %c”,”name”,10,‘m’);
refresh();
endwin();
}
2.3.2字符、字符串输入
curses中提供了getch()、getstr()函数分别用来接受字符和字符串输入,同时也提供了scanfw()函数进行格式化屏幕输入。
2.3.2.1 getch()函数
curses库中的getch()函 数可以从终端键盘读入一个字符,并且返回字符的整数值,语法如表2.12所示。
表2.12 从键盘输入字符函数概述
头文件
curses.h
概述
int getch();
返回
成功
失败
返回读入的字符整数值
ERR
这个函数通常用来从键盘接受输入。例如,在下面的程序片断中,读入字符并且进行存储直到接收到/n或者文件结束符或者接受缓冲区已满。
int c,p[max];
int i;
i =0;
while((c=getch())!=(int)’/n’&& i<max)
p[i++]=c;
正如前面所说,为了保证刷新速度,从键盘输入的字符不一定会立即显示在屏幕上,这由终端的输入模式决定。输入模式决定了在程序接受字符之前内核进行处理的过程。如果终端被设置成ECHO 模式,getch()接受的字符将立即在屏幕上显示,否则屏幕将保持不变化直到刷新后才显示出来。通常情况下,内核会缓存输入文本,如果你不需要输入缓存,你就必须设置CBRREAK或者raw模式。在raw模式下,内核并不进行缓存处理;而在CBREAK 模式下,除了^S、^Q、^C、^Y 等控制字符以外,其余的字符都原封不动的发送到系统中被处理。如果终端没有设置成RAW或者NOECHO模式,getch()将自动的将终端设置成CBREAK模式。
对于普通的字符,getch() 将返回与字符本身相同的整数值。但如果想获取功能键和方向键等我们则必须设置功能键模式。一旦进行设置,getch()将返回curses.h中定义的与这些功能键对应的整数。它们通常以KEY_开始,比如KEY_F1、KEY_LEFT、KEY_HOME等等。比如下面的代码就是用来判断按键是否为左移方向键:
ch=getch();
if(ch==KEY_LEFT)
printf(“KEY_LEFT键被按下”);
2.3.2.2 getstr()
getstr()函数从终端键盘接受字符串,并且显示在指定的位置上。
表2.13 从键盘输入字符串函数概述
头文件
curses.h
概述
int getstr(str)
char* str
返回
成功
失败
OK
ERR
str是一个字符指针指向字符串变量或者存储字符串的位置。从键盘输入时候字符串必须以‘/n’结束,当字符串被存储的时候,‘/n’被空字符所代替。str声明的时候必须有足够的空间来存放输入字符。当读入字符串成功的时候返回OK,否则返回ERR错误。
getstr()通常用来在从键盘读入文本一行。在下面的代码中,getstr()从键盘接受字符串并且把它存放在变量name中。
char name[20];
getstr(name);
与getch()相同,如果终端模式被设置为ECHO,getstr()将终端屏幕上立即更新显示字符。如果终端模式没有设置为RAW或者NOECHO模式,函数将自动将终端设置为CBREAK模式,并在读入字符串以后自动恢复到以前的模式。
2.3.2.3 scanw()函数
scanw()函数可以格式化输入数据,并把它们拷贝到指定的位置,这个值可以是字符串、字符、十进制、八进制或者二进制数,函数语法如表2.14所示。
表2.14 格式化输入函数概述
头文件
curses.h
概述
int scanw(fmt [,argptr…])
char * fmt;
返回
成功
失败
OK
ERR
scanw()与scanf()函数类似,是一个变参函数,而且两者用法基本相同。它的参数解释如下:
fmt 是一个字符串指针,用来表示打印的格式,比如对于字符串格式为%s,字符为%c,十进制整数为%d,八进制整数为%o等等。
arg 是需要打印的值,如果给出的arg不止一个,每一个都必须用逗号隔开,它们必须与fmt的格式相适应。如果fmt为%s格式,则相应的arg参数必须为一个字符串指针。
scanw()主要用来从键盘读入一些字符串,数字或者字符的组合,比如学生信息,既包括字符串类型的姓名,浮点数类型的学分,也包括A,B,C…之类的等级信息。这时候如果输入一个学生的信息时候就必须使用scanw()函数。例如,下面的代码输入一个学生的姓名,学分以及操行等级。
char name[20];
float id;
char c;
scanw(“%s %f %c”,name,&id,c);
与前面几个函数一样,如果终端模式被设置为ECHO,getstr()将终端屏幕上立即更新显示字符。如果终端模式没有设置为RAW或者NOECHO模式,函数将自动将终端设置为CBREAK模式,并在读入字符串以后自动恢复到以前的模式。
2.3.2.4 字符、字符串输入示例程序
程序2-3 字符、字符串输入示例
#include <curses.h>
main(void)
{
char str[20];
char *pstr;
initscr();
crmode();
printw(“File to Open: “);
refresh();
getstr(str);
printw(“You typed is :%s/n “,str);
refresh();
sleep(3);
pstr=malloc(sizeof(char)*20);
printw(“Enter you name:”);
refresh();
getnstr(pstr,20);
refresh();
printw(“you enter is:%s/n”,pstr);
refresh();
sleep(1);
free(pstr);
endwin();
}
2.3.3字符插入和删除
curses库中提供的insch()函数可以用来在当前位置上插入一个字符,它的语法如表2.15所示。
表2.15 插入字符和删除字符函数概述
头文件
curses.h
概述
int insch(ch)
chtype ch;
int delch();
返回
成功
失败
OK
ERR
字符插入以后光标将自动的向右移动一个位置,如果最右边的字符超出了终端屏幕的范围,它将被截取掉。例如下面的程序片断中,insch()函数在标准屏幕的当前位置插入由整数cnt指定的数字和字符:
int cnt;
chtype *string;
while(cnt!=0){
insch(string[cnt]);
cnt--;
}
与insch()匹配,curses库中同样也提供了删除当前光标处字符的函数delch()。delch函数从当前位置删除一个字符,并且将删除字符右边的所有字符向左移动一个位置。当前行最右边的字符由空格代替。
delch()函数没有任何参数,如果函数执行成功返回OK,否则返回ERR。在下面的程序片断中,delch()从当前位置删除字符直到整数cnt变为0为止。
int cnt;
while(cnt!=0){
delch();
cnt--;
}
2.3.4行插入和删除
除了在当前位置插入一个字符或者删除一个字符以外我们有时候还需要在当前位置插入一行或者删除一行。curses库中提供了函数insertln()和deleteln()来插入和删除一行。insertln()函数的语法如表2.16所示。
表2.16 行操作函数概述
头文件
curses.h
概述
int insertln();
int deleteln();
返回
成功
失败
OK
ERR
在下面的例子中,当整数cnt为79时候,insertln函数将在屏幕上插入一空白行。
int cnt;
if(cnt == 79){
insertln();
}
deleteln()函数删除当前行,并且将当前行下的所有的行向上移动一行,最后一行则用空格代替。
deleteln()函数没有任何的参数,如果函数执行正确,返回OK,否则返回ERR。在下面的程序片断中,如果整数cnt的值为79,则函数deleteln()将从标准屏幕上删除一行。
int cnt;
if(cnt==79)
deleteln();
2.4字符属性
2.4.1字符属性简介
前面我们在谈到addch()函数的时候曾谈到chtype类型, chtype类型包括两部分信息:一部分包含字符本身的信息,另一部分包括与字符相关联的一些属性信息。这些属性允许字符用不同的方式显示,包括反显,加粗,变色,加下划线等等。通过这些方式可以将需要重点突出的文字与其余的文字区分开来。
事实上在标准屏幕上每输出一个字符的时候,这些字符总会有一些默认属性与它们关联在一起,比如一般的字符属性为A_NORMAL。然而,使用函数attrset()我们可以更改当前字符的属性。目前curses中支持的一些属性列表如下:
■ A_NORMAL:标准的显示模式
■ A_BLINK:闪烁属性
■ A_BOLD:加粗属性
■ A_DIM: 半透明属性
■ A_REVERSE:反显属性
■ A_STANDOUT:高亮度显示属性
■ A_UNDERLINE:加下划线
■ A_ALTCHARSET:可代替字符集
■ COLOR_PAIR(n):字符的背景和前景属性
这些属性一般作为参数传递给函数attrset()以及其余的一些对属性进行操作的函数。当字符作为参数传递给函数的时候我们可以将这些属性直接赋给字符。如果一个字符具有两个或者两个以上的属性,那么这些属性之间使用‘|’来对这些属性进行组合,例如,“A_UNDERLINE|A_STANDOUT”属性使得显示字符高亮度显示的同时带有下划线。
但是不是所有的终端都能够显示上面列出的所有的属性的,比如单色终端就不可能显示色彩属性。如果某个终端不能显示指定的属性,程序首先会尽量选择一个与它相似的属性来代替它,如果没有这样的可代替属性的话,这个属性会被忽略掉。
除了这儿列出的一些属性之外,还有两种属性A_CHARTEXT和A_ATTRIBUTES。你可以通过curses的inch()函数和逻辑与 ‘&’ 操作符来使用这两个属性,从而来提取终端屏幕上某一位置的字符以及它的属性。至于inch()的用法我们在后面会继续讲解。第三个属性是A_COLOR,可以用来提取终端屏幕上的某一位置的字符的颜色的信息。
2.4.2设置和取消字符属性
表2.17所示的三个函数可以用来设置和取消文本字符的属性:
表2.17 字符属性设置函数概述
头文件
curses.h
概述
int attron(attrs)
chtype attrs;
int attrset(attrs)
chtype attrs;
int attroff(attrs)
chtype attrs;
返回
成功
失败
OK
ERR
我们前面列出的那些属性可以单独的设置和关闭,某个属性的设置和关闭对其余的属性不会产生任何影响。
attron()和attrset()都可以用来设置指定的属性,但是它们的用法却有所不同。attron()设置attrs参数指定的属性,它的设置不影响任何现在已经存在的属性,但是一旦它设置,之后输出的所有的文本字符都将受该属性的影响。attrset()与attron()不同,它的设置将影响到当前的所有的属性,因为它是用attrs指定的参数属性代替当前的所有属性,如果某个属性在设置之前是打开的,在attrs中却没有设置,那么设置后这个属性将被关闭。如果某个属性已经存在,那么可以通过attroff()关闭这个属性。另外我们也可以通过attrset(0)的特殊用法关闭所有的属性。我们可以从下面的一个程序片断中看到这几个函数的用法:
………
printw(“A word in”);
attrset(A_BOLD);
printw(“boldface”);
attrset(0);//关闭所有的属性
printw(“really stands out./n”;
………
refresh();
2.4.3高亮度显示模式
有的时候我们有必要对文本的某一部分进行高亮度显示以将它与其余的部分区分开来表示其重要性,但是不同的终端之间表示方法不一定相同,有的是将显示部分反显,有的是将显示部分加粗。前面的A_STANDOUT属性则使我们不需要考虑这些内容,它会根据终端的性能做出相应的显示。虽然通过attrset()和attron()可以设置和关闭A_STANDOUT属性,但是curses库中还提供了两个更方便的函数standout()和standend()来实现这个目的,它们的语法如表2.18所示。
表2.18 字符属性设置函数概述
头文件
curses.h
概述
int standout()
int standend()
返回
成功
失败
OK
ERR
standout()函数将在当前屏幕上打开高亮度显示属性A_STANDOUT。standend()函数的作用和attrset(0)的作用相同,将关闭所有的属性。
2.4.4字符属性示例
这一节我们给出一个完成的使用字符属性的例子,我们用一个文本文件来记录文本的属性信息,我们在需要设置属性的数据之前加上转义符从而将信息提供给程序,转义规则如下:
/N 表示正常显示
/B 表示高亮度显示
/U 表示下划线显示
/R 表示反色显示
/D 表示半高亮显示
文本文件示例如下:
/N Normal Text/Ddim Text /R reverse Text/Uunderline Text
程序2-4 字符属性设置示例
程序名称 attrset.c
编译命令 cc –o attrset attrset.c –lcurses
#include <stdio.h>
#include <curses.h>
main(int argc,char **argv)
{
FILE *fd;
int c,c2;
void exit(),perror();
if(argc!=2)
{
fprintf(stderr,”Usage:highlight file/n”);
exit(1);
}
fd=fopen(argv[1],”r”);
if(fd==NULL)
{
perror(argv[1]);
exit(0);
}
initscr();
scrollok(stdscr,TRUE);
nonl();
while((c=getc(fd))!=EOF)
{
if(c==‘//’)
{
c2=getc(fd);
switch(c2)
{
case ‘B’:
attrset(A_BOLD);
continue;
case ‘D’:
attrset(A_DIM);
continue;
case ‘U’:
attrset(A_UNDERLINE);
continue;
case ‘L’:
attrset(A_BLINK);
continue;
case ‘R’:
attrset(A_REVERSE);
continue;
case ‘N’:
attrset(0);
continue;
}
addch(c);
addch(c2);
}
else
addch(c);
}
fclose(fd);
refresh();
endwin();
exit(0);
}
2.5光标操作
2.5.1移动光标
在讲述移动光标之前有必要了解光标的概念。Curses库中光标分为物理光标和逻辑光标。物理光标是最常用的光标,它只有一个;而逻辑光标属于curses窗体,每个窗体只有一个物理光标,但却可能有多个逻辑光标。
当我们在窗体(包括标准屏幕stdscr)中进行输入输出的时候,逻辑光标会不断的定位于窗体中要进行操作的区域。因此通过移动逻辑光标,你可以在任何时候在窗体的任何部分进行输入输出。
curses库中提供的move()函数可以将逻辑光标移动到给定的位置,这个位置是相对与屏幕的左上角而言,左上角的坐标为(0,0)。函数语法如表2.19所示。
表2.19 光标移动函数概述
头文件
curses.h
概述
int move(y,x);
int y,x;
返回
成功
失败
OK
ERR
参数y是移动后位置的所在的行数,x为新位置所在的的列数。如果移动的目标位置超出了屏幕的范围,则会导致错误。屏幕的行宽和列宽在curses库中定义为(LINES-1,COLS-1)。如果我们需要将光标移动到第五行第四列,则函数代码如下:
move(4,3);
需要注意的是行和列都是从0开始计数。我们进行的大部分操作在操作之前都需要移动光标到一定的位置,如果这样的话我们需要分两步进行。首先移动光标然后进行相关操作。为了更方便,一些函数将移动光标与显示字符结合起来执行。这种函数的格式一般如下:
mvfunc(y , x, [arg,…])
func是原有的普通的函数,只是在func的前面增加mv表示移动位置。函数参数如下:
■ func一般为操作函数的名字,比如addch,addstr等等。
■ y为操作进行时候光标所在的行数,通常也是移动之后的新的光标位置。
■ x为操作进行时候光标所在的列数。
arg是函数包含的可选参数,不是每一个函数都用到。这类函数的返回值与没有‘mv’前缀时候的返回值是一样的。比如我们需要将光标移动到(10,5)处然后输出字符‘X’,那么我们就可以使用move()函数与addch()函数结合形成的mvaddch()函数来实现。代码可以写为:
mvaddch(10,5,‘X’);
另一方面,我们有必要了解逻辑光标和物理光标的相互协作关系。物理光标的位置将会在一段输入程序后无效,但是我们通过可以通过WINDOW结构的_leave标志重新定位它。如果设置了_leave标志,在输入操作结束后,逻辑光标将会移动到物理光标指向的窗体中最后写入的区域。如果没有设置_leave标志,在输入操作结束后,物理光标将返回到逻辑光标指向的窗体的第一个字符输入位置。_leave标志是由leaveok()函数控制的。
除了move可以用来移动逻辑光标之外,curses库中还提供了mvcur()函数来移动物理光标。它的语法如表2.20所示。
表2.20 光标移动函数概述
头文件
curses.h
概述
void mvcur(last_y,last_x,new_y,new_x)
int last_y,last_x,new_y,new_x);
返回
成功
失败
OK
ERR
mvcur()函数的参数如下:
■ last_y和last_x给出移动之前光标所位于的行和列的位置。
■ new_y和new_x给出的是移动后光标所位于的行和列的位置。
例如,如果需要将光标从(10,5)移动到(3,0)处,mvcur()的调用示例如下:
mvcur(10,5,3,0);
mvcur()是一个较低层次的函数,因此与move()相比不怎么常用。与其它函数相比,mvcur()不用等待屏幕刷新就会立即生效。如果您想隐藏物理指针,您可以使用curs_set()函数。它仅有一个整型参数,可以为0、1、2,分别表示光标的状态为隐藏、正常、高亮显示。如果系统支持设定的光标显示属性,则函数将返回以前设置的光标属性,否则返回ERR。
2.5.2清除屏幕
清除屏幕包括清除整个屏幕和清除部分屏幕。清除整个屏幕通常有两种方法,第一种是先用空白字符填充屏幕所有区域,然后再调用refresh()函数刷新屏幕。另外一种方法是用固定的终端控制字符清除屏幕。第一种方法比较慢,因为它需要重写当前屏幕。第二种能迅速清除整个屏幕内容。
curses库清除整个屏幕可以使用clear()和erase()函数来进行,同时使用wclear()和werase()清除指定窗口。这四个函数在标准屏幕上使用空格来代替当前字母从而达到清屏的效果。它们的语法如表2.21所示。
表2.21 屏幕清除函数概述
头文件
curses.h
概述
int clear();
int erase();
返回
成功
失败
OK
ERR
clear()还是erase()都不带有参数,如果执行成功,它们都返回OK,否则返回ERR。而且两者实际上都是宏定义。虽然这个函数都能达到清屏的效果,但是它们之间还是有细微的差别。clear()清除屏幕上的所有的字符并且将光标移动到屏幕的原点(0,0),继而自动调用clearok()函数,这使得屏幕在下次调用refresh()刷新的时候能够完全被清除。因此clear()函数能够清除物理屏幕上的那些curses无法识别的“垃圾”,这样下次输出将是基于一个完全“干净”的屏幕进行的。在下面的例子中,如果输入的值为’c’,则程序清除屏幕。
int c;
if((c=getch())==’c’)
{
clear();
refresh();
}
erase()函数同样可以用来清除屏幕,但是它不会自动调用clearok()函数,因此与clear()相比,它并不是一种最彻底的清除方式。
curses库中同样也提供了两个函数来进行部分屏幕清除,它们是clrtobot()和clrtoeol()。这两个函数跟clear()函数的原理相同,都是用空格代替当前的需要清除部分的现有字符。这两个函数的语法如表2.22所示。
表2.22 屏幕局部清除函数概述
头文件
curses.h
概述
int clrtoeol()
int clrtobot()
返回
成功
失败
OK
ERR
clrtoeol()函数和clrtobot()函数都没有任何的参数,如果函数执行成功返回OK,否则返回ERR。clrtobot()清除从当前光标位置到屏幕底端的所有内容,例如,如果当前的位置为(10,0),clrtobot()清除从第十行开始(包括第十行)的所有字符。clrtoeol()清除屏幕上从当前光标位置到该行末尾的所有字符。例如对于一个行字符数为80的屏幕来说,如果当前的位置为(10,10),那么clrtoeol()将清除从(10,10)到(10,79)的所有字符,而行开始的字符包括从(10,0)到(10,9)保持不变。上面的函数也必须调用refresh()以后才开始生效。不管clrtobot()还是clrtoeol()都会改变当前光标的位置。
程序2-5 屏幕清除函数使用示例
程序名称 scr_clear.c
编译命令 cc –o scr_clear scr_clear.c –lcurses
#include <curses.h>
main()
{
int c;
initscr();
addstr("Press ‘l’ to delete from here to the end of the line and on.");
addstr("/nPress ‘b’ to delete from here to the end of screen.");
move(0,30);
refresh();
c=getch();
if (c==’b’)
clrtobot();
else if(c==’l’)
clrtoeol();
refresh();
endwin();
}
2.6颜色属性
除了能够在终端屏幕上进行字符操作以外,有的时候我们希望能够使用颜色属性。因此curses库中也提供了对颜色的支持,但是关于颜色的一些函数只有在终端描述数据库为terminfo的时候才被支持,如果终端描述数据库为termcap时候则不支持。
在使用颜色属性之前我们必须对curses库中的颜色机制有所了解,为此我们必须了解颜色表(Color Table)和颜色配对表(Color Pair Table)的概念。对颜色的操作都是以这两个表为基础进行的。因此这部分我们首先介绍一些基本的颜色特征描述,然后着重介绍颜色表以及颜色配对表的概念,接着介绍如何使用颜色属性。由于不是所有的终端都支持颜色属性,因此不同终端上的程序的移植问题必须有所考虑,最后部分则是介绍如果保证使用颜色属性的程序在不同终端上移植的参考指南以及一些使用的示例。
2.6.1 颜色表定义
curses库中颜色通常都是配对使用,包括前景色和背景色。为了能够在程序中使用颜色属性,我们首先必须定义使用的颜色,使用它们进行相关初始化工作,并且使用这些颜色创建颜色配对。最后这些颜色配对作为颜色属性供使用。
当curses库初始化的时候系统将自动的创建一张颜色表产生默认颜色定义。这个颜色定义表中有很多的条目,条目的数量和当前终端一次能够显示的颜色的数量是相匹配的。表格中的每一个条目包含三个部分:分别表示红色,绿色和蓝色的亮度。自然界中的所有的颜色都是由这三种颜色组以不同的亮度合成而来。
curses库中使用RGB方法来表示一种颜色,它允许使用红色,绿色和蓝色的亮度直接表示某一种颜色。另外一些终端使用HSL的表示法代替RGB来描述一种颜色,H表示色度(Hue) ,S表示饱和度(Saturation),L表示亮度(Luminosity)。使用HSL表示法的终端在terminfo数据库中可以辨别出来,系统会自动的完成从HSL到RGB的转换。
如果程序中使用到颜色属性,那么在程序的开始我们必须使用八种基本的颜色对颜色表的各个条目进行默认初始化。这八种默认的基本颜色以及RGB分量如表2.23所示。
表 2.23 默认的颜色表
(R)ed (G)reen (B)lue
黑色:0
COLOR_BLACK
0
0
0
红色:1
COLOR_RED
1000
0
0
绿色:2
COLOR_GREEN
0
1000
0
黄色:3
COLOR_YELLOW
1000
1000
0
蓝色:4
COLOR_BLUE
0
0
1000
紫红:5
COLOR_MAGENTA
1000
0
1000
青色:6
COLOR_CYAN
0
1000
1000
白色:7
COLOR_WHITE
1000
1000
1000
大部分的彩色终端在同一时刻仅能显示八种颜色,因此颜色表的默认条目数为八条。如果终端能够显示超过八种颜色,那么颜色表的条目数目相应的也将超过八个。如果终端只能显示少于八种颜色,那么颜色表的条目也将保持为八个,但只有颜色表的前n个颜色能被使用。
表2.23中定义的是八种基本颜色,也是系统初始化时候的默认颜色。如果我们不想使用默认颜色定义颜色,那么我们可以使用函数init_color()来改变颜色的定义。如果终端不能显示这些颜色将返回ERR。
在curses.h中我们定义了下面的一些颜色有关的宏,这些值与默认颜色表相应颜色的位置对应。
COLOR_BLACK 0
COLOR_RED 1
COLOR_GREEN 2
COLOR_YELLOW 3
COLOR_BLUE 4
COLOR_MAGENTA 5
COLOR_CYAN 6
COLOR_WHITE 7
2.6.2 颜色配对表
颜色配对表维护的是一定数量的能够在终端屏幕上显示的颜色配对,主要指前景色和背景色的配对。这张表由程序本身而不是由系统进行维护。与颜色表不一样,颜色配对表中没有默认的条目值,它的条目以及条目数目由程序员决定。程序使用的每一个颜色配对条目都必须由函数init_pair()进行初始化。颜色配对表中每一个条目包含两个字段:前景颜色和背景颜色。对于每一个已经初始化过的颜色配对表来说,这两个字段包含的内容是当前使用的颜色在颜色表中的条目索引号。
表2.24演示了函数init_pair()如何用来初始化颜色配对表的第一项,使它的前景色为蓝色(默认颜色表中蓝色的条目索引为4),背景色为黄色(默认颜色表中的条目索引为3)。颜色配对表的第二项初始化的结果为前景色为青色(颜色表中的条目索引号为6),背景色为紫红色(颜色表中的默认索引号为5)。颜色配对表中的没有被初始化的条目全部被初始化为0,即默认的颜色为黑色。
表 2.24 颜色配对表示例
序号
前景色
背景色
0
0
0
1
4
3
2
6
5
3
0
0
4
0
0
5
0
0
6
0
0
如果在程序中需要进行颜色属性的设置,那么我们可能会用到两个全局变量COLORS和COLORS_PAIRS。这两个变量都是在头文件<curses.h>中定义。COLORS变量包含了终端能够支持的最大的颜色的数目。COLOR_PAIRS变量包含了终端能够支持的颜色配对表的最大数目。它们都通过函数start_color()进行初始化,start_color()从terminfo数据库中获取相应终端的对应信息并将其值赋给这两个变量。
有的时候我们可能会发现同一种颜色在不同的终端上显示效果不一样,这一般都是由机器的显卡有关。
2.6.3 使用COLOR_PAIR(n)属性
COLOR_PAIR(n)属性指定当前的前景色和背景色分别为颜色配对表中索引为n的条目中的对应值。如果我们选择使用系统默认的颜色定义,那么我们在使用COLOR_PAIR(n)属性之前只有两件事情需要做:一是调用start_color()函数,这样,颜色配对表就会使用函数init_pair(pair,f,b)进行初始化工作。init_pair(pair,f,b)的第一个参数pair是颜色配对表中需要初始化的条目的索引号。它必须介于0和COLOR_PAIRS-1之间。参数f和b是整数值,它们代表前景色和背景色在颜色表中的条目索引值。它的大小介于0和COLORS-1之间。上面我们谈到的示例程序初始化的过程如下:
init_pair(1,COLOR_BLUE,COLOR_YELLOW);
init_pair(2,COLOR_CYAN,COLOR_MAGENTA);
或者
init_pair(1,4,3);
init_pair(2,6,5);
一旦一个颜色配对表的条目被初始化,COLOR_PAIR(n)就可以象其余的属性一下使用了。通过函数attron()我们可以象设置其余的属性一样设置属性COLOR_PAIR(n)。比如下面的代码:
attron(COLOR_PAIR(1));
一旦进行了上面的设置,那么以后所有显示的字符都是为蓝色前景,黄色背景,直到我们将颜色属性关闭。既然颜色属性是属性的一种,那么我们就可以将COLOR_PAIR(n)属性和其余的属性一起组合起来使用。例如设置颜色属性的同时我们可以设置闪烁属性。
attrset(A_BLINK|COLOR_PAIR(1));
2.6.4 更改颜色表定义
到目前为止我们使用的颜色表都是系统提供的默认颜色表,有的时候我们不希望使用默认颜色表而是重新定义颜色表,那么我们就必须使用curses库中提供的函数init_color(color,r,g,b)。第一个参数color是需要改变的条目在颜色表中的索引号。后面的三个参数分别是更改后的颜色中红色,绿色,蓝色的颜色分量。颜色分量的值在0到1000之间。一旦某个颜色的定义发生了改变,屏幕上所有的相关颜色立即改变。颜色表中条目索引值为1的颜色值默认为蓝色(COLOR_BLUE),现在我们可以进行改变将该蓝色进一步加亮:
init_color(COLOR_BLUE,0,700,1000);
这样我们以后一旦调用COLOR_PAIR(n),所有的默认蓝色都更改为设定后的加亮蓝色。
2.6.5 程序移植
跟curses的其余的部分一样,有关颜色操作的函数当初设计的时候都是考虑到可移植性的。因此它也是与终端独立的。但是不同中终端的显示能力是不同的。例如,某个终端支持64色,而且为它编写的程序可以支持64中颜色,那么程序在这个终端上会运行的非常好,而且能够获取预料的效果,但是如果终端仅仅支持16种颜色,那么程序将不可能取得同样的效果。因此如果要保证使用颜色属性的程序在不同的终端上运行就必须考虑到颜色移植会遇到的问题。我们通常遵循下面的几个步骤来保证程序的移植性:
■ 用最基本的八种颜色生成的颜色配对表,最多只使用其中的七个条目。这样程序可以在大多数的终端上运行。记住即使终端能够支持八种颜色,我们也只能使用其中的七种,因为curses将索引为0的条目保留给自己使用。
■ 不要使用条目0中的颜色作为背景色,在一些终端上这是比较推荐的做法。当我们使用颜色0作为背景时候,即使我们将它重新定义为其余的颜色,它也总是会自动的转换为黑色。
■ 最好将颜色属性和其余的一些属性组合起来使用,颜色属性从根本上来说也是高亮度显示的一种,这样即使终端是单色的,那么也可以显示高亮效果。在彩色终端上,上面列出的都是可以使用的,但是在单色终端上只有视频属性是可以使用的,彩色属性则被忽略掉。
■ 如果我们需要确定程序中能够使用的颜色和颜色配对的最多数量,那么应该尽量的使用系统定义的全局变量COLORS和COLOR_PAIRS而不是自定义的一些常量。
2.6.6 颜色操作宏以及函数
2.6.5.1宏定义
在<curses.h>头文件中我们还定义了其余的两个宏:A_COLOR和PAIR_NUMBER。
A_COLOR 是一个掩码位,用来提取颜色配对表的一些信息。它能够清除颜色配对表的字段,以及判断是否有颜色配对在使用中。
PAIR_NUMBER(attrs)的作用与COLOR_PAIR(n)的作用相反,它返回与指定属性attrs相关联的颜色配对表的索引号。
另外curses库中还提供了两个函数来获取当前程序运行的终端的一些信息。has_colors()函数返回一个布尔类型的值。如果终端支持彩色显示,返回TRUE,否则返回FALSE。函数can_change_colors()同样返回一个布尔类型的值。如果终端支持彩色并且能够改变它们的默认颜色表,则返回TRUE,否则返回FALSE。
同样我们还可以通过两个函数来决定与当前终端相关联的颜色表和颜色配对表的一些信息。color_content()函数使得我们能够在初始化的颜色中获取相应的RGB分量。如果函数指定的颜色不存在或者终端不能更改默认颜色定义则返回ERR,否则返回 OK。另外一个函数是pair_content(),通过它我们可以获取指定的颜色配对表中的颜色信息。如果颜色配对表没有初始化则返回ERR,否则返回OK。
下面的部分我们主要来讨论颜色属性中使用到的一些非常重要的函数,start_color(),init_pair(),init_color()等等。再讨论它们的同时我们会给它相应的示例程序或者代码以用来演示它们的用法。
2.6.5.2 sart_color()
sart_color()将终端的颜色恢复到终端打开时候的状态。函数语法如表2.25所示。
表2.25 startcolor()函数概述
头文件
curses.h
概述
init start_color()
返回
成功
失败
OK
ERR
如果终端不支持彩色则返回ERR ,否则返回OK。当你决定在程序中使用颜色的时候,你首先必须调用这个函数。这个操作必须在所有的色彩函数之前调用,否则程序将出错。一般在调用了函数initscr()函数之后就调用这个函数。它在颜色表中初始化八种默认的颜色(黑,红,绿,黄,蓝,紫,青,白),同时初始化变量COLORS和COLOR_PAIRS。如果根据终端数据库获取的COLOR_PAIRS的数目大于64则COLOR_PAIRS设置为64。
2.6.5.3 init_pair()
函数init_pair()用来改变指定的颜色配对表条目中的颜色定义。函数语法如表2.26所示。
表2.26 init_pair函数概述
头文件
curses.h
概述
int init_pair (pair,f,b)
short pair,f,b;
返回
成功
失败
OK
ERR
如果能够重新设置颜色配对表中的指定条目,函数返回OK,否则返回ERR。颜色配对表的条目索引在作为宏COLOR_PAIR(n)的参数使用之前它们必须通过这个函数进行初始化。函数的第一个参数pair是需要改变的颜色配对表的条目索引,值在0到COLOR_PAIRS-1之间。参数f为整数,指定前景色在颜色表中的索引,b则为背景色的索引。它们的值都必须在0到COLORS-1之间。
下面的示例程序颜色了如果使用这个函数,在这段代码中我们将颜色配对表的索引为1的条目的前景色设置为红色,将背景色设置为绿色,然后输出字符“Red on Green”。
程序2-6 颜色使用示例
程序名称 initpair.c
编译命令 cc –o initpair initpair.c –lcurses
#include <stdio.h>
#include <curses.h>
main()
{
initscr();
if(start_color()==OK)
{
init_pair(1,COLOR_RED,COLOR_ GREEN);
attron(COLOR_PAIR(1));
addstr(“Red on Green”);
refresh();
}
else
{
printw(“can’t support color”);
refresh();
}
endwin();
}
2.6.5.4 init_color()
程序中一旦调用了start_color()函数,那么curses将用默认的颜色填充颜色表。如果我们需要更改颜色定义的话,就必须使用函数init_color()。
表2.27 init_pair函数概述
头文件
curses.h
概述
int init_color(color, r, g , b )
short color, r, g, b;
返回
成功
失败
OK
ERR
函数的第一个参数color是需要改变的颜色在颜色表中的条目索引号,值介于0到COLORS-1之间,剩下的三个参数r,g,b分别是新颜色RGB分量,它们的值介于0到1000之间。下面的例子演示了如果使用这个函数。
程序2-7 init_color()函数使用示例
程序名称 initcolor.c
编译命令 cc –o initcolor initcolor.c –lcurses
#include <stdio.h>
#include <curses.h>
main()
{
initscr();
if(start_color()==OK)
{
init_pair(1,COLOR_RED,COLOR_GREEN);
attron(COLOR_PAIR(1));
if(init_color(COLOR_RED,0,0,1000)==OK)
addstr(“BLUE ON GREEN”);
else
addstr(“RED ON GREEN”);
refresh();
}
endwin();
}
在上面的代码中我们首先将颜色配对表的索引为1的条目初始化为红色前景,绿色背景。然后我们把颜色表中索引为COLOR_RED的颜色分量更改为(0,0,1000),实际上是蓝色。这样我们所有输出红色的地方都被代替为蓝色。一旦我们通过init_color()函数改变了颜色表中某个条目的颜色定义,屏幕上使用原有颜色的地方立即会更新到新的颜色,而不需要等到wrefresh()函数刷新。
对上面的几个例子,有几点说明。在使用彩色之前必须调用start_color()进行相关初始化,但如果初始化失败,程序不一定要退出,因为我们使用彩色的程序在单色终端上也可以运行,只是所有彩色的地方都变成了高亮显示。