TUI库newt和snack简要介绍

栏目: 服务器 · 发布时间: 6年前

内容简介:大多数商业化软件产品一般会通过实现GUI(Graphical User Interface)或者TUI(Text-based User Interface/Textual User Interface/Terminal User Interface)来降低软件的使用难度。本文简要介绍一个TUI库:因为

大多数商业化软件产品一般会通过实现GUI(Graphical User Interface)或者TUI(Text-based User Interface/Textual User Interface/Terminal User Interface)来降低软件的使用难度。本文简要介绍一个TUI库: newt

newt 是由RedHat开发,主要用在RatHat的 Linux 发行版本(RHEL, Fedora和CentOS)的安装程序项目 Anaconda 中。 NEWT 的全称是: Not Erik’s Windowing Toolkit ,它基于 S-Lang 库实现。

因为 newt 是用于Linux发行版本的安装程序中,因而它的运行空间非常有限,需要保证程序尽可能小,所以在设计时选择使用 C语言 进行开发,也尽量不支持多余特性。于是在设计之初就不支持事件驱动。在支持事件驱动的窗口库中,有一个事件循环监听事件发生,根据发生的事件展示不同的窗口。在 newt 中,窗口创建和销毁基于栈模式,新创建的窗口位于之前的窗口之上,且只能展示最上层窗口,当最上层窗口销毁之后才能展示下边的窗口。这也就是说所有窗口都为模态窗口( model windows )。这种特性限制了 newt 库本身只适合用于完成顺序的程序流程,一个典型的场景就是程序的安装向导。SHELL程序往往也是顺序的流程,从 SHELL 程序转换为 newt 窗口程序完全不涉及程序流程的改变,实现非常简单。

newt 中的主要结构为组件( components ), 它和其他窗口库中所称的 widget 为相同概念。 newt 组件有许多, 常用的包括:

  • Button
  • CompactButton
  • EntryBox
  • CheckBox
  • RadioButton
  • Textbox
  • Scrollbars
  • Listbox

具体可以参考这篇 tutorail文章 。这篇文章写于2003年, 当时 newt 版本为v0.31,略有些老,但内容上在现在的版本(v0.52)上依然适用。

Form 是一种特殊的组件,它将其他组件组合在一起。 newt 应用程序使用 form 做为与用户交互的媒介。 newt 应用程序与用户交互的的简要逻辑为: 当 newt 应用需要从用户获取输入时,它运行一个 form 。此时, newt 应用开始等待用户输入。用户输入信息到 form 中所包含的组件后,将控制权交还 newt 应用(如通过触发按钮等行为), newt 应用再从组件中获取到用户输入的内容继续执行。 form 可以包含任意组件,也包括其他 formform 嵌套可以用于改变 TAB 键触发的移动顺序、控制窗口不同区域的背景色,滚动特定窗口区域等。

newt 代码中将所有的组件都用一种变量类型: newtComponent 来表示。我们可以给组件注册回调函数,回调函数何时被触发取决于组件类型。回调函数原型和注册函数原型如下:

typedef void (*newtCallback)(newtComponent, void *);
void newtComponentAddCallback(newtComponent co, newtCallback f, void * data);

newt 所有组件都绘制在窗口( window )上, newt 默认创建了一个特殊的背景窗口,叫做 root 窗口。我们能够在该窗口上放置组件,也可以使用API创建新的窗口。 newt 库的坐标系统原点坐于屏幕左上角,函数原型中, left 参数表示 x 坐标,从原点出发从左至右增大, top 参数表示 y 坐标,从上到下增大。 left 可以对应为列数, top 可以对应为行数,两个坐标都可以为负数,表示从反方向开始计算。如:

newtDrawRootText(10, -5, "Hello NEWT");

表示在距屏幕左上角的第10列,倒数第5行的位置输出字符串 Hello NEWT

下边我们以一个实例来说明 newt 的使用。

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <newt.h>

void show_message_window(void) {
    char message[] = "This is a pretty long message. It will be displayed "
                     "in a newt textbox, and illustrates how to construct "
                     "a textbox from arbitrary text which may not have "
                     "very good line breaks.\n\n"
                     "Notice how literal \\n characters are respected, and "
                     "may be used to force line breaks and blank lines.";
    newtComponent text, button, form;

    newtCls();
    text = newtTextboxReflowed(1, 1, message, 30, 5, 5, 0);
    button = newtButton(12, newtTextboxGetNumLines(text) + 2, "Exit");
    newtCenteredWindow(37, newtTextboxGetNumLines(text) + 7, "MESSAGE");
    form = newtForm(NULL, NULL, 0);
    newtFormAddComponents(form, text, button, NULL);
    newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();
}

void show_login_failed_window(void) {
    newtComponent label, button, form;

    newtCls();
    newtCenteredWindow(60, 6, "NOTE");
    label = newtLabel(10, 2, "Login failed!");

    button = newtCompactButton(30, 4, "Exit");

    form = newtForm(NULL, NULL, 0);
    newtFormAddComponents(form, label, button, NULL);
    newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();
}

int main(void) {
    int cols, rows;

    newtInit();

    do {
        newtCls();
        newtPushHelpLine(NULL);

        newtGetScreenSize(&cols, &rows);
        newtOpenWindow(1, 1, cols - 2, rows - 4, "LOGIN");

        newtComponent form, btn_ok, btn_cancel, label_username, label_password,
                      entry_username, entry_password, result;
        const char *username, *password;

        label_username = newtLabel(10, 3, "Username:");
        entry_username = newtEntry(20, 3, "root", 20, &username,
                                   NEWT_FLAG_SCROLL);
        label_password = newtLabel(10, 5, "Password:");
        entry_password = newtEntry(20, 5, "", 20, &password,
                                   NEWT_FLAG_PASSWORD | NEWT_FLAG_SCROLL);

        result = newtLabel(10, 10, "");

        btn_ok = newtButton(10, 7, "OK");
        btn_cancel = newtButton(20, 7, "Cancel");

        form = newtForm(NULL, NULL, 0);

        newtFormAddComponents(form, label_username, entry_username,
                              label_password, entry_password,
                              btn_ok, btn_cancel, result,
                              NULL);
        struct newtExitStruct exit_status;
        newtFormRun(form, &exit_status);

        if (exit_status.reason == NEWT_EXIT_COMPONENT) {
            if (exit_status.u.co == btn_ok) {
                if ((strcmp(username, "root") == 0)
                    && (strcmp(password, "123456") == 0))
                {
                    show_message_window();
                    break;
                } else {
                    show_login_failed_window();
                }
            } else if (exit_status.u.co == btn_cancel) {
                break;
            }
        }

        newtRefresh();
        newtFormDestroy(form);
        newtPopWindow();
        newtPopHelpLine();

    } while (1);

    newtFinished();
    return 0;
}

CentOS发行版默认已带有 newt 库,我们需要安装 newt-devel 库:

yum install newt-devel

编译该程序:

gcc -o newtdemo newtdemo.c -lnewt

执行 newtdemo , 界面如下图:

TUI库newt和snack简要介绍

在密码框中输入 123456 后,移动焦点选择 OK 按钮,回车后显示另一个窗口:

TUI库newt和snack简要介绍

我们简单说明下源代码。 main 函数中首先调用了:

newtInit();

newtInit() 用于初始化 newt 库的内部数据结构,并将终端设置为 raw 模式。接着调用 newtCls() 来清空屏幕。 newt 窗口的最后一行用于显示帮助信息,如每个快捷键所对应的功能。代码中调用 newtPushHelpLine(NULL) 显示默认的帮助信息。接下来通过调用 newtOpenWindow() 创建了一个新窗口,后续的组件将绘制在该窗口中。接下来,在窗口上创建了 UsernamePassword 两个输入框用于接收用户输入,以及两个按钮。

从组件定义的代码行中可以看到所有的组件都是 newtComponent 类型:

    newtComponent form, btn_ok, btn_cancel, label_username, label_password,
                  entry_username, entry_password, result;

我们使用一个 Form 表单将这些组件组合起来:

    newtFormAddComponents(form, label_username, entry_username,
                          label_password, entry_password,
                          btn_ok, btn_cancel, result,
                          NULL);

当执行 newtFormRun() 时, newt 应用将等待用户操作。我们通过 exit_status 结构体接收用户的操作信息来判断哪个按钮被触发。如果用户触发了 Cancel 按钮则程序结束。如果触发的为 OK 按钮,则检测输入的密码是否为 123456 。如果输入错误,则显示一个错误信息窗口。密码验证通过则显示一个长消息 Textbox 窗口。

这里我们也可以通过注册回调函数给两个按钮组件,根据哪个回调函数被执行来判断哪个按钮被触发。

newt 整体使用非常简单,然而比较遗憾的是,它基本没有文档,最好的参考资料为官方源码中的几个示例:

官方 newt 库中还提供了 Python 封装库,名称为 snack 。具体用法和C库类似,官方也提供了两个示例:

除此之外, newt 库中还提供了一个 whiptail 的命令行程序,用在SHELL脚本中显示对话框。比如,我们在BASH中执行:

whiptail --yesno "Continue?" 10 50

这将显示如下窗口:

TUI库newt和snack简要介绍

当选择 YES 按钮时,程序退出码为 0 ,选择 NO 按钮,程序退出码为 1 。我们选择 YES 之后,查看退出码:

[root@centos2 newt]# echo $?
0

具体信息可以参考:

类似的TUI工具还有 CDKdialog 。CDK是一个程序库,能够链接进我们自己开发的应用程序, dialog 为一个命令行程序,与 whiptail 类似,可以在SHELL脚本中实现TUI界面。它们都是基于 ncurses 开发的, ncurses 是目前最为广泛使用的TUI库。不过它的API过于底层,我们直接使用相对繁琐,对于简单的界面实现,还是推荐使用更为上层的封装库。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Design Patterns

Head First Design Patterns

Elisabeth Freeman、Eric Freeman、Bert Bates、Kathy Sierra、Elisabeth Robson / O'Reilly Media / 2004-11-1 / USD 49.99

You're not alone. At any given moment, somewhere in the world someone struggles with the same software design problems you have. You know you don't want to reinvent the wheel (or worse, a flat tire),......一起来看看 《Head First Design Patterns》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具