使用curses管理基于文本的屏幕--(八)

摘要:
现在我们已经理解了curses提供的函数,我们可以继续开发示例程序了。这是一个使用curses库的C语言版本。变量current_ CD用于存储我们当前使用的CD标题。初始化它,使其第一个字符为空,表示“未选择CD”。可变电流_ Cat用于记录当前CD的分类。staticcharcurrent_ cd[MAX_STRING]=“/0”;staticcharcurrent_ cat[MAX_STRING];3现在您需要定义一些文件名。为了简单起见,此版本中使用了固定文件名,包括临时文件名。调用getchoice将向它传递问候和选择,这将指向主菜单或扩展菜单。

CD管理程序

现在我们已经了解了curses所提供了功能,我们可以继续开发我们的例子程序。在这里所展示是一个使用curses库的C语言版本。他提供了一些高级的特性,包括更为清晰的屏幕信息显示以及用于跟踪列表的滚动窗口。
完整的程序共页长,所以我们将其分为几部分,在每一部分中介绍一些函数。
试验--一个新的CD管理程序
1 首先,我们包含所有的头文件以及一些全局常量。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <curses.h>
#define MAX_STRING 80 /* Longest allowed response */
#define MAX_ENTRY 1024 /* Longest allowed database entry */
#define MESSAGE_LINE 6 /* Misc. messages on this line */
#define ERROR_LINE 22 /* Line to use for errors */
#define Q_LINE 20 /* Line for questions */
#define PROMPT_LINE 18 /* Line for prompting on */
2 接下来,我们需要一些全局变量。变量current_cd用于存储当前我们所用的CD标题。对其进行初始化,从而其第一个字符为null表明"没有选中CD"。/0并不是严格必须的,但是他可以保证变量已经进行了初始化,这是一个很好的习惯。变量current_cat用于记录当前CD的分类。
static char current_cd[MAX_STRING] = “/0”;
static char current_cat[MAX_STRING];
3 现在需要定义一些文件名。为了简单,在这个版本中文件名都使用固定的文件名,包括临时文件名。当两个用户在相同的目录中运行这个程序时就会出现问题。
const char *title_file = “title.cdb”;
const char *tracks_file = “tracks.cdb”;
const char *temp_file = “cdb.tmp”;
4 最后,我们定义函数原型。
void clear_all_screen(void);
void get_return(void);
int get_confirm(void);
int getchoice(char *greet, char *choices[]);
void draw_menu(char *options[], int highlight,
int start_row, int start_col);
void insert_title(char *cdtitle);
void get_string(char *string);
void add_record(void);
void count_cds(void);
void find_cd(void);
void list_tracks(void);
void remove_tracks(void);
void remove_cd(void);
void update_cd(void);
5 在我们查看具体的实现之前,我们需要一些菜单结构(实际上为一个菜单选项的数组)。第一个字符是当菜单被选中时所返回的字符;其余的是要显示的字符。当一个CD被选中时会显示扩展菜单。
char *main_menu[] =
{
“add new CD”,
“find CD”,
“count CDs and tracks in the catalog”,
“quit”,
0,
};
char *extended_menu[] =
{
“add new CD”,
“find CD”,
“count CDs and tracks in the catalog”,
“list tracks on current CD”,
“remove current CD”,
“update track information”,
“quit”,
0,
};
这样就完成了所有的初始化工作。现在我们可以进入程序功能了,但是首先,我们需要总结一下函数之间的关系,所有16个函数,其功能可以分为:
绘制菜单
向数据库中添加CD
获取并显示CD数据
试验--主函数
主函数允许我们由菜单中进行选择,直到我们选择退出。
int main()
{
int choice;
initscr();
do {
choice = getchoice(“Options:”,
current_cd[0] ? extended_menu : main_menu);
switch (choice) {
case ‘q’:
break;
case ‘a’:
add_record();
break;
case ‘c’:
count_cds();
break;
case ‘f’:
find_cd();
break;
case ‘l’:
list_tracks();
break;
case ‘r’:
remove_cd();
break;
case ‘u’:
update_cd();
break;
}
} while (choice != ‘q’);
endwin();
exit(EXIT_SUCCESS);
}
试验--菜单
1 main函数所调用的getchoice函数是我们将在这一部介绍的主要函数。调用getchoice会传递给其一个greet以及choices,这会指向主菜单或是扩展菜单(依据是否选择了一个CD)。我们可以在前面的main函数中看到这些函数调用。
int getchoice(char *greet, char *choices[])
{
static int selected_row = 0;
int max_row = 0;
int start_screenrow = MESSAGE_LINE, start_screencol = 10;
char **option;
int selected;
int key = 0;
option = choices;
while (*option) {
max_row++;
option++;
}
/* protect against menu getting shorter when CD deleted */
if (selected_row >= max_row)
selected_row = 0;
clear_all_screen();
mvprintw(start_screenrow - 2, start_screencol, greet);
keypad(stdscr, TRUE);
cbreak();
noecho();
key = 0;
while (key != ‘q’ && key != KEY_ENTER && key != ‘/n’) {
if (key == KEY_UP) {
if (selected_row == 0)
selected_row = max_row - 1;
else
selected_row—;
}
if (key == KEY_DOWN) {
if (selected_row == (max_row - 1))
selected_row = 0;
else
selected_row++;
}
selected = *choices[selected_row];
draw_menu(choices, selected_row, start_screenrow,
start_screencol);
key = getch();
}
keypad(stdscr, FALSE);
nocbreak();
echo();
if (key == ‘q’)
selected = ‘q’;
return (selected);
}
2 在这里我们可以注意到在getchoice函数中调用了两个局部函数:clear_all_screen与draw_menu。我们首先来看一下draw_menu:
void draw_menu(char *options[], int current_highlight,
int start_row, int start_col)
{
int current_row = 0;
char **option_ptr;
char *txt_ptr;
option_ptr = options;
while (*option_ptr) {
if (current_row == current_highlight) attron(A_STANDOUT);
txt_ptr = options[current_row];
txt_ptr++;
mvprintw(start_row + current_row, start_col, “%s”, txt_ptr);
if (current_row == current_highlight) attroff(A_STANDOUT);
current_row++;
option_ptr++;
}
mvprintw(start_row + current_row + 3, start_col,
“Move highlight then press Return “);
refresh();
}
3 clear_all_screen函数用于清除屏幕并重新输出标题。如果选择了一个CD,则会显示其信息。
void clear_all_screen()
{
clear();
mvprintw(2, 20, “%s”, “CD Database Application”);
if (current_cd[0]) {
mvprintw(ERROR_LINE, 0, “Current CD: %s: %s/n”,
current_cat, current_cd);
}
refresh();
}
下面我们来看一下添加和更新CD数据库的函数。由main函数中调用的函数有add_record,update_cd以及remove_cd。这些函数都会调用一些我们在下面部分定义的函数。
试验--数据库文件操作
1 首先,我们如何向数据库中添加一个新的CD记录呢?
void add_record()
{
char catalog_number[MAX_STRING];
char cd_title[MAX_STRING];
char cd_type[MAX_STRING];
char cd_artist[MAX_STRING];
char cd_entry[MAX_STRING];
int screenrow = MESSAGE_LINE;
int screencol = 10;
clear_all_screen();
mvprintw(screenrow, screencol, “Enter new CD details”);
screenrow += 2;
mvprintw(screenrow, screencol, “Catalog Number: “);
get_string(catalog_number);
screenrow++;
mvprintw(screenrow, screencol, “ CD Title: “);
get_string(cd_title);
screenrow++;
mvprintw(screenrow, screencol, “ CD Type: “);
get_string(cd_type);
screenrow++;
mvprintw(screenrow, screencol, “ Artist: “);
get_string(cd_artist);
screenrow++;
mvprintw(PROMPT_LINE-2, 5, “About to add this new entry:”);
sprintf(cd_entry, “%s,%s,%s,%s”,
catalog_number, cd_title, cd_type, cd_artist);
mvprintw(PROMPT_LINE, 5, “%s”, cd_entry);
refresh();
move(PROMPT_LINE, 0);
if (get_confirm()) {
insert_title(cd_entry);
strcpy(current_cd, cd_title);
strcpy(current_cat, catalog_number);
}
}
3 get_confirm函数提示并读取用户的确认信息。他会读取用户的输入字符串并且检测第一个字符是否为Y或是y。如果检测到的为其他字符,则不会给出确认。
int get_confirm()
{
int confirmed = 0;
char first_char;
mvprintw(Q_LINE, 5, “Are you sure? “);
clrtoeol();
refresh();
cbreak();
first_char = getch();
if (first_char == ‘Y’ || first_char == ‘y’) {
confirmed = 1;
}
nocbreak();
if (!confirmed) {
mvprintw(Q_LINE, 1, “ Cancelled”);
clrtoeol();
refresh();
sleep(1);
}
return confirmed;
}
4 最后,我们来看一下insert_title函数。这个函数会通过在标题文件的尾部添加一个标题字符串来向CD数据库中添加一个标题。
void insert_title(char *cdtitle)
{
FILE *fp = fopen(title_file, “a”);
if (!fp) {
mvprintw(ERROR_LINE, 0, “cannot open CD titles database”);
} else {
fprintf(fp, “%s/n”, cdtitle);
fclose(fp);
}
}
5 我们继续来讨论由main所调用的其他的文件操作函数。我们由update_cd函数开始。这个函数使用一个滚动子窗体,并且需要一些所定义的全局内容,因为在后面的list_tracks函数中会需要这些内容。他们是:
#define BOXED_LINES 11
#define BOXED_ROWS 60
#define BOX_LINE_POS 8
#define BOX_ROW_POS 2
update_cd函数允许用户重新输入当前CD的这些音轨信息。在删除以前的音轨记录以后,他会提示输入新的信息。
void update_cd()
{
FILE *tracks_fp;
char track_name[MAX_STRING];
int len;
int track = 1;
int screen_line = 1;
WINDOW *box_window_ptr;
WINDOW *sub_window_ptr;
clear_all_screen();
mvprintw(PROMPT_LINE, 0, “Re-entering tracks for CD. “);
if (!get_confirm())
return;
move(PROMPT_LINE, 0);
clrtoeol();
remove_tracks();
mvprintw(MESSAGE_LINE, 0, “Enter a blank line to finish”);
tracks_fp = fopen(tracks_file, “a”);
我们会在稍后继续列表函数的讨论;在这里我们会做一个简短的小结来强调我们是如何通过滚动窗体输入信息的。技巧就是设置一个子窗体,在边缘绘制一个盒子,然后在这个子窗体中添加一个新的滚动子窗体。
box_window_ptr = subwin(stdscr, BOXED_LINES + 2, BOXED_ROWS + 2,
BOX_LINE_POS - 1, BOX_ROW_POS - 1);
if (!box_window_ptr)
return;
box(box_window_ptr, ACS_VLINE, ACS_HLINE);
sub_window_ptr = subwin(stdscr, BOXED_LINES, BOXED_ROWS,
BOX_LINE_POS, BOX_ROW_POS);
if (!sub_window_ptr)
return;
scrollok(sub_window_ptr, TRUE);
werase(sub_window_ptr);
touchwin(stdscr);
do {
mvwprintw(sub_window_ptr, screen_line++, BOX_ROW_POS + 2,
“Track %d: “, track);
clrtoeol();
refresh();
wgetnstr(sub_window_ptr, track_name, MAX_STRING);
len = strlen(track_name);
if (len > 0 && track_name[len - 1] == ‘/n’)
track_name[len - 1] = ‘/0’;
if (*track_name)
fprintf(tracks_fp, “%s,%d,%s/n”, current_cat, track, track_name);
track++;
if (screen_line > BOXED_LINES - 1) {
/* time to start scrolling */
scroll(sub_window_ptr);
screen_line—;
}
} while (*track_name);
delwin(sub_window_ptr);
fclose(tracks_fp);
}
6 main函数所调用的最后一个函数为remove_cd函数。
void remove_cd()
{
FILE *titles_fp, *temp_fp;
char entry[MAX_ENTRY];
int cat_length;
if (current_cd[0] == ‘/0’)
return;
clear_all_screen();
mvprintw(PROMPT_LINE, 0, “About to remove CD %s: %s. “,
current_cat, current_cd);
if (!get_confirm())
return;
cat_length = strlen(current_cat);
/* Copy the titles file to a temporary, ignoring this CD */
titles_fp = fopen(title_file, “r”);
temp_fp = fopen(temp_file, “w”);
while (fgets(entry, MAX_ENTRY, titles_fp)) {
/* Compare catalog number and copy entry if no match */
if (strncmp(current_cat, entry, cat_length) != 0)
fputs(entry, temp_fp);
}
fclose(titles_fp);
fclose(temp_fp);
/* Delete the titles file, and rename the temporary file */
unlink(title_file);
rename(temp_file, title_file);
/* Now do the same for the tracks file */
remove_tracks();
/* Reset current CD to ‘None’ */
current_cd[0] = ‘/0’;
}
7 我们现在所需要只是列出remove_tracks函数,这个函数会由当前的CD删除音轨信息。他会由update_cd与remove_cd函数所调用。
void remove_tracks()
{
FILE *tracks_fp, *temp_fp;
char entry[MAX_ENTRY];
int cat_length;
if (current_cd[0] == ‘/0’)
return;
cat_length = strlen(current_cat);
tracks_fp = fopen(tracks_file, “r”);
if (tracks_fp == (FILE *)NULL) return;
temp_fp = fopen(temp_file, “w”);
while (fgets(entry, MAX_ENTRY, tracks_fp)) {
/* Compare catalog number and copy entry if no match */
if (strncmp(current_cat, entry, cat_length) != 0)
fputs(entry, temp_fp);
}
fclose(tracks_fp);
fclose(temp_fp);
/* Delete the tracks file, and rename the temporary file */
unlink(tracks_file);
rename(temp_file, tracks_file);
}
试验--查询CD数据库
1 下面这个函数会搜索数据库,计数标题与音轨。
void count_cds()
{
FILE *titles_fp, *tracks_fp;
char entry[MAX_ENTRY];
int titles = 0;
int tracks = 0;
titles_fp = fopen(title_file, “r”);
if (titles_fp) {
while (fgets(entry, MAX_ENTRY, titles_fp))
titles++;
fclose(titles_fp);
}
tracks_fp = fopen(tracks_file, “r”);
if (tracks_fp) {
while (fgets(entry, MAX_ENTRY, tracks_fp))
tracks++;
fclose(tracks_fp);
}
mvprintw(ERROR_LINE, 0,
“Database contains %d titles, with a total of %d tracks.”,
titles, tracks);
get_return();
}
2 我们也许已经不记得我们最喜欢的CD了,不用担心!通过输入一些信息,我们可以通过find_cd来查进行查找。他会提示输入一个子串并且在数据库中进行匹配,并且将全局变量current_cd设置为所查找到的CD标题。
void find_cd()
{
char match[MAX_STRING], entry[MAX_ENTRY];
FILE *titles_fp;
int count = 0;
char *found, *title, *catalog;
mvprintw(Q_LINE, 0, “Enter a string to search for in CD titles: “);
get_string(match);
titles_fp = fopen(title_file, “r”);
if (titles_fp) {
while (fgets(entry, MAX_ENTRY, titles_fp)) {
/* Skip past catalog number */
catalog = entry;
if (found == strstr(catalog, “,”)) {
*found = ‘/0’;
title = found + 1;
/* Zap the next comma in the entry to reduce it to
title only */
if (found == strstr(title, “,”)) {
*found = ‘/0’;
/* Now see if the match substring is present */
if (found == strstr(title, match)) {
count++;
strcpy(current_cd, title);
strcpy(current_cat, catalog);
}
}
}
}
fclose(titles_fp);
}
if (count != 1) {
if (count == 0) {
mvprintw(ERROR_LINE, 0, “Sorry, no matching CD found. “);
}
if (count > 1) {
mvprintw(ERROR_LINE, 0,
“Sorry, match is ambiguous: %d CDs found. “, count);
}
current_cd[0] = ‘/0’;
get_return();
}
}
尽管catalog所向的数组要比current_cat大得多,并且可能会覆写内存,但是fgets中的检测避免了这种可能。
3 最后我们需要在屏幕上列出所选择的CD音轨信息。我们会在最后一部分中利用update_cd中所用的#define内容。
void list_tracks()
{
FILE *tracks_fp;
char entry[MAX_ENTRY];
int cat_length;
int lines_op = 0;
WINDOW *track_pad_ptr;
int tracks = 0;
int key;
int first_line = 0;
if (current_cd[0] == ‘/0’) {
mvprintw(ERROR_LINE, 0, “You must select a CD first. “);
get_return();
return;
}
clear_all_screen();
cat_length = strlen(current_cat);
/* First count the number of tracks for the current CD */
tracks_fp = fopen(tracks_file, “r”);
if (!tracks_fp)
return;
while (fgets(entry, MAX_ENTRY, tracks_fp)) {
if (strncmp(current_cat, entry, cat_length) == 0)
tracks++;
}
fclose(tracks_fp);
/* Make a new pad, ensure that even if there is only a single
track the PAD is large enough so the later prefresh() is always
valid. */
track_pad_ptr = newpad(tracks + 1 + BOXED_LINES, BOXED_ROWS + 1);
if (!track_pad_ptr)
return;
tracks_fp = fopen(tracks_file, “r”);
if (!tracks_fp)
return;
mvprintw(4, 0, “CD Track Listing/n”);
/* write the track information into the pad */
while (fgets(entry, MAX_ENTRY, tracks_fp)) {
/* Compare catalog number and output rest of entry */
if (strncmp(current_cat, entry, cat_length) == 0) {
mvwprintw(track_pad_ptr, lines_op++, 0, “%s”,
entry + cat_length + 1);
}
}
fclose(tracks_fp);
if (lines_op > BOXED_LINES) {
mvprintw(MESSAGE_LINE, 0,
“Cursor keys to scroll, RETURN or q to exit”);
} else {
mvprintw(MESSAGE_LINE, 0, “RETURN or q to exit”);
}
wrefresh(stdscr);
keypad(stdscr, TRUE);
cbreak();
noecho();
key = 0;
while (key != ‘q’ && key != KEY_ENTER && key != ‘/n’) {
if (key == KEY_UP) {
if (first_line > 0)
first_line—;
}
if (key == KEY_DOWN) {
if (first_line + BOXED_LINES + 1 < tracks)
first_line++;
}
/* now draw the appropriate part of the pad on the screen */
prefresh(track_pad_ptr, first_line, 0,
BOX_LINE_POS, BOX_ROW_POS,
BOX_LINE_POS + BOXED_LINES, BOX_ROW_POS + BOXED_ROWS);
key = getch();
}
delwin(track_pad_ptr);
keypad(stdscr, FALSE);
nocbreak();
echo();
}
4 最后两个函数调用get_return,这会提示并且读取一个回车,而忽略其他字符。
void get_return()
{
int ch;
mvprintw(23, 0, “%s”, “ Press return “);
refresh();
while ((ch = getchar()) != ‘/n’ && ch != EOF);
}
小结
在这一章,我们探讨了curses库。curses库为基于文本的程序提供了一个很好的方法来控制屏幕与读取键盘。尽管curses库并没有提供像通用终端接口(GTI)和直接的termios访问那样多的控制,但是他很容易使用。如果我们正在编写一个全屏幕,基于文本的程序,我们应考虑使用curses库来为我们管理屏幕与键盘。

免责声明:文章转载自《使用curses管理基于文本的屏幕--(八)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇全方位掌握 NSIS 的使用[转]JS-Array-新增方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

curses库--libncurses5-dev--游标移动及屏幕的显示

curses是一个在Linux/Unix下广泛应用的图形函数库.,作用是可以绘制在DOS下的用户界面和漂亮的图形。 curses的名字起源于"cursor optimization",即光标优化。它最早由有美国伯克利大学的Bill Joy和Ken Arnold编写的,用来处理一个游戏rogue的屏幕显示。后来贝尔实验室的Mark Horton在System...

ReactNative: 了解相机第三方库react-native-camera的使用

一、简介 在前一篇文章中,初步介绍了RN提供的关于相机功能CameraRoll的使用了。很多时候,这种最基础的API有时很难满足功能需求,此时,如果不想重复造轮子,我们可以选择一个完善好用的第三库。react-native-camera就是一个非常不错的关于相机功能的第三方库,使用这个框架基本能满足大多数的需求,现在来简单研究一下。 二、安装 1、同样地道...

JAVA 上加密算法的实现用例,MessageDigest介绍

第 1 章基础知识 1.1. 单钥密码体制 单钥密码体制是一种传统的加密算法,是指信息的发送方和接收方共同使用同一把密钥进行加解密。 通常 , 使用的加密算法 比较简便高效 , 密钥简短,加解密速度快,破译极其困难。但是加密的安全性依靠密钥保管的安全性 , 在公开的计算机网络上安全地传送和保管密钥是一个严峻的问题,并且如果在多用户的情况下密钥的保管安全性也...

js和java中使用join来进行数组元素的连接

  合理地使用join来进行数组中元素的连接,代码简洁,比自己编码也更为方便。   js中join的用法 var arr = new Array(3) arr[0] = "a" arr[1] = "b" arr[2] = "c" arr.join(".") a.b.c ------------------ arr.join(",") a,b,c   ja...

token解决前后端分离认证和跨域问题和JWT的使用

二、使用token解决前端后端分离用户认证问题 2.1 用户提交帐号和密码到服务器的认证接口 login.html doSubmit:function(){ console.log("~~~~~~~~~~~~~doSubmit"); axios.get("http://localhost:8080/user/login",{...

.NET系统框架

本书是一本讲解.NET技术的书籍,目标读者群也是在.NET框架(.NET Framework)下进行开发的程序员,因此我们无法回避的问题就是:什么是.NET框架?它包含了哪些内容?为开发程序提供了哪些支持?很多朋友对这类个问题的第一反应可能是.NET框架所提供的庞大类库及编写代码所采用的C#语言,实际上远不止这些。 要描述.NET框架,自然会遇到与其相关的...