`
helpbs
  • 浏览: 1161602 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

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管理基于文本的屏幕

    Linux或者Windows+Cygwin开发环境下,使用ncerses进行文本方式的图形界面编程的指导手册!

    Linux程序设计 第6章 使用curses函数库管理基于文本的屏幕

    即使是编写基于字符的全屏幕程序,使用curses函数库的方案也更简明,而程序本身也更独立于具体的终端。在编写这类程序时,使用curses函数库更比直接使用escape转义序列容易得多。curses函数库还可以对键盘进行管理,...

    cd管理系统程序 linux

    界面部分:本程序基于Linux下运行,所以暂时用curses函数库来编写其文本屏幕。分为两个文本窗口和pad窗口实现其选项。 逻辑部分:通过unixC系统函数实现对其的输入输出管理,数据更新,用户操作管理,软件运行失败时...

    ncurses-devel-5.5-24.20060715.x86_64.rpm

    Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库。 Ncurses是一个能提供基于文本终端窗口功能的动态库. Ncurses可以: 只要您喜欢,您可以使用整个屏幕 创建和管理一个...

    i9n:快速的cljs + nodejs终端(curses)声明式UI

    对于复杂curses应用程序(例如文件管理器,音乐播放器,基于ascii的游戏,甚至是文本编辑器)的中坚力量,除了对core.async的使用以外,对其余代码几乎没有足够的见解,因此它们始终是一个库,而不是一个框架core....

    ncurses安装包

    ncurses(new curses)是一个提供应用程序编程接口(API)的编程库,允许程序员以独立于终端的方式编写基于文本的用户界面。它是用于开发在终端仿真器下运行的“类似GUI的” 应用程序软件的工具包。它还优化了屏幕...

    Linux程序设计中文第4版.part3

    第6章 使用curses函数库管理基于文本的屏幕 第7章 数据管理 第8章 MySQL 第9章 开发工具 第10章 调试 第11章 进程和信号 第12章 POSIX线程 第13章 进程间通信:管道 第14章 信号量、共享内存和消息...

    Linux程序设计中文第4版.part1

    第6章 使用curses函数库管理基于文本的屏幕 第7章 数据管理 第8章 MySQL 第9章 开发工具 第10章 调试 第11章 进程和信号 第12章 POSIX线程 第13章 进程间通信:管道 第14章 信号量、共享内存和消息队列 第...

    Linux程序设计中文第4版.part2

    第6章 使用curses函数库管理基于文本的屏幕 第7章 数据管理 第8章 MySQL 第9章 开发工具 第10章 调试 第11章 进程和信号 第12章 POSIX线程 第13章 进程间通信:管道 第14章 信号量、共享内存和消息...

    C指针原理教程之Ncurses介绍

    Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库。 Ncurses是一个能提供基于文本终端窗口功能的动态库. Ncurses可以: · 只要您喜欢,您可以使用整个屏幕 · 创建和管理一个...

    UNIX操作系统教程 张红光

    第1章绪论.1 1.1操作系统概述1 1.1.1建立操作系统的目标1 1.1.2操作系统是用户与计算机的接口1 1.1.3操作系统是资源管理器2 1.2UNIX系统的主要特性3 1.3UNIX系统的发展史4 1.4开源软件与UNIX的推广发展6 1.4.1开源...

    Linux程序设计 第4版.haozip01

    第6章 使用curses函数库管理基于文本的屏幕 175 6.1 用curses函数库进行编译 175 6.2 curses术语和概念 176 6.3 屏幕 178 6.3.1 输出到屏幕 179 6.3.2 从屏幕读取 180 6.3.3 清除屏幕 180 6.3.4 移动光标 180...

    Linux程序设计 第4版.haozip02

    第6章 使用curses函数库管理基于文本的屏幕 175 6.1 用curses函数库进行编译 175 6.2 curses术语和概念 176 6.3 屏幕 178 6.3.1 输出到屏幕 179 6.3.2 从屏幕读取 180 6.3.3 清除屏幕 180 6.3.4 移动光标 180...

Global site tag (gtag.js) - Google Analytics