曲径通幽论坛

 找回密码
 立即注册
搜索
123
返回列表 发新帖
楼主: beyes
打印 上一主题 下一主题

[显示] [专题]通过 curses 管理基于文本的屏幕控制

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
21#
 楼主| 发表于 2009-3-23 12:25:25 | 只看该作者

子窗口应用示例

测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        WINDOW *sub_window_ptr;
        int x_loop;
        int y_loop;
        int counter;
        char a_letter = '1';

        initscr();
        /*以 0-9 来填充屏幕*/
        for(y_loop = 0; y_loop < LINES - 1; y_loop++) {
                for( x_loop = 0; x_loop < COLS - 1; x_loop++) {
                        mvwaddch(stdscr, y_loop, x_loop, a_letter);
                        a_letter++;
                        if(a_letter > '9') a_letter = '1';
                }
        }
        /*创建子窗口,stdscr 是父窗口指针*/
        sub_window_ptr = subwin(stdscr, 10, 20, 10, 10);
        scrollok(sub_window_ptr, 1); /*滚屏设定*/

        touchwin(stdscr);
        refresh();
        sleep(3);

        werase(sub_window_ptr);
        mvwprintw(sub_window_ptr, 2, 0, "%s\\n", "window will scroll");
        wrefresh(sub_window_ptr);
        sleep(3);

        for(counter = 1; counter < 10; counter++) {
                wprintw(sub_window_ptr, "%s\\n", "text is scrolling");
                wrefresh(sub_window_ptr);
                sleep(5);
        }

        delwin(sub_window_ptr);

        touchwin(stdscr);
        refresh();
        sleep(3);

        endwin();
        exit(EXIT_SUCCESS);
}

说明
在子窗口里的内容在 for 循环的控制下会不断滚屏输出。
注意,在子窗口删除及 window(stdscr) 被刷新后,之前在子窗口中输出的内容仍保留在屏幕中,这是因为子窗口中刷新出来的数据正是来自与父窗口 stdscr 。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
22#
 楼主| 发表于 2009-3-23 12:28:50 | 只看该作者

键盘上的数字小键盘(keypad)

在前面已经看到 curses 提供处理键盘的一些工具。许多按键至少都会有光标移动键和功能键。许多键盘还有数字小键盘以及其它按键,如 Insert Home 键。

在绝大部分的终端上,解码这些按键都算是一个难题。因为一般情况下按下这些按键后,它们会发送一个以转义字符打头的字符串。应用程序不仅要设法分辨出 ”单独按下 Escap 按键“ 和 ”按下功能按键爱你发送出来的一连串字符“ 之间的差别,还要对付因终端型号的不同而产生的 “同一逻辑按键使用不同的转义序列” 的情况,这两个问题交织在一起,错综复杂。

幸运的是,curses 为管理功能键提供了一个精巧的工具。对于每一个终端来说,它们的功能键发送出来的转义序列都被保存,通常是在一个 terminfo 结构里,并且所包含的 curses.h 头文件有一套前缀为  KEY_  的逻辑按键 #defines 定义。

当 curses 启动时,转义序列和逻辑按键之间的转换被禁止,这需要 keypad 函数重新开启。如果成功就返回 OK,如果失败就返回 ERR 。

#include <cuurses.h>

int keypad(WINDOW *window_ptr, bool keypad_on);

一旦通过调用 keypad ,并设置 keypad_on 参数为 ture 时, keypad mode 被使能。接着,curses 接管(take over)按键的转义序列的处理工作。这样,读取键盘不但返回被按下的按键,也返回与逻辑按键对应的 KEY_defines 。

在使用 keypad mode 时有 3 个小小的限制:
      第一个问题是识别 escape 转义序列需要一定的时间,而许多网络协议要么把字符打成数据包(这会导致 escpae 转义序列的识别不正确),要么会从某个地方开始分断它们(这将导致功能键的转义序列被识别成 escape 字符和其它彼此没有联系的字符)。在 WAN 网络或其它繁忙的链路上这一情况将更为剧烈。唯一解决的办法是尝试对终端进行编程使之对你所希望用的每一个功能键发送单独的,独一无二的字符,然而这会限制控制字符的数量。
      为了让 curses 能够区分 “单独按下 Escape按键” 和一个以 Escape 字符打头的键盘转义序列,它就必须等待很短的一小段时间。在 keypad 模式被激活后,处理 Escape 按键所造成的非常微小的延迟也能被注意到。
      curses 不能处理 “非独一无二” 的转义序列。如果你的终端有两个都发送同一个转义序列的不同按键,curses 将对此不做处理,因为它不能告诉该返回哪一个逻辑按键。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
23#
 楼主| 发表于 2009-3-24 11:03:10 | 只看该作者

keypad 模式测试

测试代码

#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

#define LOCAL_ESCAPE_KEY 27

int main()
{
        int key;

        initscr();
        crmode();  /*切换到 CBREAK 模式,即字符一输入就被处理*/
        keypad(stdscr, TRUE); /*激活 keypad 模式*/

        noecho();  /*输入不回显*/
        clear();
        mvprintw(5, 5, "Key pad demostration. Press 'q' to quit");
        move(7,5);
        refresh();
        key = getch();  /*检测按键*/

        while(key != ERR && key != 'q') {
                move(7,5);
                clrtoeol();  /*如果没错误则清(7,5)起到其所在的行末*/

                if((key >= 'A' && key <= 'Z') ||
                   (key >= 'a' && key <= 'z')) {
                        printw("Key was %c", (char)key);
                }
                else {
                        switch(key) {
                                case LOCAL_ESCAPE_KEY: printw("%s", "Escape key"); break;
                                case KEY_END: printw("%s", "END key"); break;
                                case KEY_BEG: printw("%s", "BEGINNING key"); break;
                                case KEY_RIGHT: printw("%s", "RIGHT key"); break;
                                case KEY_LEFT: printw("%s", "LEFT key"); break;
                                case KEY_UP: printw("%s", "UP key"); break;
                                case KEY_DOWN: printw("%s", "DOWN key"); break;
                                default: printw("Unmatched - %d", key); break;
                        }/* switch */
                } /* else */

                refresh();
                key = getch();

        }/* while */

        endwin();
        exit(EXIT_SUCCESS);
}

程序说明
在上面程序中,必须关闭回显功能,否则光标在按键按下时会发生移动

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
24#
 楼主| 发表于 2009-3-25 01:21:13 | 只看该作者

使用颜色

刚开始时,非常少的 ”哑“ 终端是支持颜色显示功能的,所以早期版本的 curses 对这方面也不做支持。而在 ncurses 及现在大部分的 curses 实现中都支持颜色显示。不幸的是,curses 的“哑屏幕” 出身已经影响到了 API, curses 在使用颜色方面也有许多严格的限制,这反应了早期的终端在颜色显示方面相当差的兼容能力。

屏幕上的每一个字符都 可以用多种颜色中的一种,字符所在的背景色也是一样可以选择。例如,你可以用绿色来 写文本,而文本所在的背景色可选为红色。

curses 所支持的颜色显示有一点不寻常,在这里,一个字符的颜色并没有定义得与其背景色无关( independently of 与...无关;不取决与...);而是,你必须定义一个字符的前景色和背景色作为一个 “颜色对” (color pair)。

在确定你可以使用 curses 的颜色功能之前,你必须检查当前的中断是否支持颜色显示,然后对 curses 的颜色例程进行初始化。对于此,可以使用两个函数:has_colors 和 start_color :
#include <curses.h>

bool has_colors(void);
int start_color(void);

如果终端支持颜色显示,那么 has_colors 函数返回 ture 。接着,就可以调用 start_color 了,如果颜色功能初始化成功,函数返回 OK 。一旦 start_color  一经调用并且颜色显示功能被初始化成功,变量 COLOR_PAIRS 就被设置为该终端所能支持的 “颜色对” 的最大值。一般情况下限制为 64 个 “颜色对”。变量 COLORS 定义了可选择颜色种类的最大值,此值一般为 8 。在内部,0-63一般作为每种 "颜色对“ 的特定 ID 。

在你可以使用颜色作为属性之前,你必须对你所想用的 ”颜色对“ 进行初始化——用 init_pair 函数来完成此工作。访问颜色属性使用 COLOR_PAIR 函数:
#include <curses.h>

int init_pair(short pair_number, short foreground, short background);
int COLOR_PAIR(int pair_number);
int pair_content(short pair_number, short *foreground, short *background);

curses.h 通常都定义了一些基本的颜色,其命名以 COLOR_ 开始。
pair_content() 函数允许对之前定义的 ”颜色对“ 信息进行检索(retrieve)。

比如,要定义绿色背景上的红色内容为一号 ”颜色对“,那么如下定义:
init_pair(1, COLOR_RED, COLOR_GREEN);

经过上面的定义后,这个颜色组合可以用来作为颜色的属性了,注意一下 COLOR_PAIR 的用法:
wattron(window_ptr, COLOR_PAIR(1));

由于 COLOR_PAIR 已经被定义为一个属性,接下来就可以使用其他的属性与之组合。在 PC 上,通过组合 COLOR_PAIR 属性和附加属性 A_BOLD 属性以达到在屏幕上显示出更浓的颜色效果,组合两个属性的方法为使用 OR 位运算符,如下所示:
wattron(window_ptr, COLOR_PAIR(1)|A_BOLD);

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
25#
 楼主| 发表于 2009-3-26 16:55:03 | 只看该作者

终端颜色显示测试

测试代码

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <curses.h>
int main()
{
int i;
initscr();
if (!has_colors()) { /*终端是否支持颜色显示*/
endwin();
fprintf(stderr, "Error - no color support on this terminal\\n");
exit(1);
}
if (start_color() != OK) { /*初始化颜色功能是否成功*/
endwin();
fprintf(stderr, "Error - could not initialize colors\\n");
exit(2);
}
clear();
mvprintw(5, 5, "There are %d COLORS, and %d COLOR_PAIRS available",
COLORS, COLOR_PAIRS); /* 一般COLORS为8种; COLOR_PAIRS 为 64 */
refresh();

init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_RED, COLOR_GREEN);
init_pair(3, COLOR_GREEN, COLOR_RED);
init_pair(4, COLOR_YELLOW, COLOR_BLUE);
init_pair(5, COLOR_BLACK, COLOR_WHITE);
init_pair(6, COLOR_MAGENTA, COLOR_BLUE);
init_pair(7, COLOR_CYAN, COLOR_WHITE);
for (i = 1; i <= 7; i++) {
attroff(A_BOLD);
attrset(COLOR_PAIR(i)); /* 也可以用 attron 来代替*/
mvprintw(5 + i, 5, "Color pair %d", i);
attrset(COLOR_PAIR(i) | A_BOLD);
mvprintw(5 + i, 25, "Bold color pair %d", i);
refresh();
sleep(1);
}
endwin();
exit(EXIT_SUCCESS);
}
程序说明
在检查了屏幕是否支持彩色显示后,程序对颜色处理进行了初始化以及定义了一些 ”颜色对“ 。
attrset() 函数和 attron() 函数在此可以互换使用。
在颜色属性定义中,COLOR_PAIR() 的返回值亦是其中一种。
在用 init_pair() 函数进行初始化后,在内部就存在了一个 "颜色对" 的特定 ID ,当使用 COLOR_PAIR() 函数时,就会查找到这个 ID ,于是便知道了之前初始化的颜色属性。那么 COLOR_PAIR() 函数的返回值就可以传递给 attron(), attroff(), attrset() 等函数所用。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
26#
 楼主| 发表于 2009-3-26 21:59:33 | 只看该作者

重定义颜色

作为早期的 ”哑终端“ 的遗留问题---在任何一次只能显示非常少的颜色,但允许对颜色进行配置,curses 允许使用  init_color() 函数进行颜色重定义:
include <curses.h>

ini init_color(short color_number, short red, short green, short blue);
这个函数把一个可用色彩的深度(0 ~ COLORS 之间)细调为一个新的值,新值的范围从 0 - 1000 。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
27#
 楼主| 发表于 2009-3-27 00:34:57 | 只看该作者

逻辑屏幕和显示平面

在编写比较高级的 curses 程序时,我们可以先创建一个逻辑屏幕,然后再把它的全部或部分内容输出到物理屏幕上,这个办法简便易行,效果也不错。如果还能有一个尺寸大于物理屏幕的逻辑屏幕,再根据需要一次只显示逻辑屏幕的某个部分,其效果往往会更好。

在前面讲过,所有的窗口不能大于物理屏幕。但实际上,curses 允许逻辑屏幕大于正常窗口,并且专门为尺寸大于正常窗口的逻辑屏幕上的信息处理准备了特殊的数据结构 -- 显示平面(pad)。

创建显示平面和创建正常窗口的原理非常相似:
include <curses.h>

WINDOW *newpad(int number_of_lines, int number_of_columns);

注意,和 newwin() 一样,newpad() 函数的返回值是一个指向 WINDOW 结构的指针。Pad 的删除同样使用 delwin() 函数。

对”显示平面“有其独立的函数。显示平面不受屏幕坐标位置的限制,所以在刷新时必须指定希望把显示平面的哪个区间输出到屏幕上,还必须设定它在屏幕上占据的坐标位置。以下是对显示平面进行刷新的 prefresh 函数的定义:
include <curses.h>

int prefresh(WINDOW *pad_ptr, int pad_row, int pad_column,
             int screen_row_min, int screen_col_min,
             int screen_row_max, int screen_col_max);

这个函数的作用是把显示平面从 (pad_row, pad_column) 开始的区间写到屏幕上的一块区域中,这块区域被定义为坐标 (screen_row_min, screen_col_min) 和 (screen_row_max, screen_col_max) 之间的那一部分。

一个附加的函数是 pnoutrefresh() ,它和 wnoutrefresh() 的作用类似,功能也是使得屏幕的更新变得更为有效。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
28#
 楼主| 发表于 2009-3-27 02:18:29 | 只看该作者

pad 的应用举例

测试代码

#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        WINDOW *pad_ptr;
        int x, y;
        int pad_lines;
        int pad_cols;
        char disp_char;

        initscr();
        /*显示平面比终端的长宽都大 50 个字符宽度*/
        pad_lines = LINES + 50;
        pad_cols = COLS + 50;
        pad_ptr = newpad(pad_lines, pad_cols);

        disp_char = 'a';

        for(x = 0; x < pad_lines; x++) {
                for(y = 0; y < pad_cols; y++) {
                        mvwaddch(pad_ptr, x, y, disp_char);
                        if(disp_char == 'z') disp_char = 'a';
                        else disp_char++;
                }
        }
        mvprintw(0,0, "The MAX LINES On This Terminal Is %d", LINES);
        mvprintw(1,0, "The MAX COLS On This Terminal Is %d", COLS);
        refresh();

        prefresh(pad_ptr, 0, 0, 2, 2, 9, 9);
        sleep(2);
        prefresh(pad_ptr, LINES + 5, COLS + 7, 5, 5, 21, 19);
        sleep(2);
        delwin(pad_ptr);
        endwin();
        exit(EXIT_SUCCESS);
}

说明
上面我的终端可以容纳24行80列的字符。
prefresh(pad_ptr, 0, 0, 2, 2, 9, 9)
可以看到,参数 (0,0) 表示要显示的内容从 (0,0) 坐标起,显示内容的多少限定在(2-9) 行,(2-9) 列这么一块区域里。
prefresh(pad_ptr, LINES + 5, COLS + 7, 5, 5, 21, 19);
上一条语句效果一样,由上可见,{ (LINES+5),(COLS+7) }   表示的字符为 j 。

程序运行如下图所示:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-6 09:00 , Processed in 0.076125 second(s), 19 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表