UNIX 萤幕导向程式的发展利器 - curses
                                                         校园网路策进会
                                                         会长    林建宏
 ■ 前言
    相信您在网路上一定用过如  tin,elm 等工具, 这些软体有项共同的特色,
    即他们能利用上下左右等方向键来控制游标的位置.  除此之外, 这些程式
    的画面也较为美观. 对 Programming 有兴趣的朋友一定对此感到好奇, 也
    许他能在 PC 上用 Turbo C 轻易地写出类似的程式, 然而, 但当他将相同
    的程式一字不变地移到工作站上来编译时, 却出现一堆抓也抓不完的错误.
    其实, 原因很简单, 他使用的函式库可能在 UNIX 上是没有定义的.  有些
    在 Turbo-C 上被广泛使用的一些函式, 可能在 UNIX 上是不被定义的.
    为了因应网路上各式各样的终端机形态  (terminal), UNIX 上特别发展出
    一套函式库, 专门用来处理 UNIX 上游标移动及萤幕的显示.  这就是本篇
    文章要为您介绍的 - curses.h 函式库.  利用这个函式库, 您也可以写出
    像 elm 般利用方向键来移动光棒位置的程式. (CCCA 近来所提供的线上选
    课程式, 及程式服务界面, 即是笔者利用 curses 发展而成的 )
 ■ curses 的历史与版本
    cureses 最早是由柏克莱大学的 Bill Joy 及 Ken Arnold 所发展出来的.
    当时发展此一函式库主要原因是为了提高程式对不同终端机的相容性而设
    计的.  因此, 利用 curses  发展出来的程式将和您所使用的终端机无关.
    也就是说, 您不必担心您的程式因为换了一部终端机而无法使用.  这对程
    式设计师而言,    尤其是网路上程式的撰写,    是件相当重要的一件事.
    curses之所以能对上百种以上的终端机工作,  是因为它将所有终端机的资
    料, 存放在一个叫 termcap 的资料库, ( 而在第二版的 System V 系统中
    , 新版的 curses 以 terminfo 取代原来的 termcap). 有了这些记录, 程
    式就能够知道遇到哪一种终端机时,   须送什麽字元才能移动游标的位置,
    送什麽字元才能清除整个萤幕清除. (* 注一)
    另外, 本文的介绍 以 System V 的 curses 版本为主.
 ■ 如何在您的程式使用 curses ?
    在您的 C 程式的档头将  <curses.h> include 进来.当您引进  curses.h
    这个函式库後, 系统会自动将 <stdio.h> 和 <unctl.h>一并  include 进
    来.另外,  在  System  V  版本中, <terminfo.h>  这个函式库也将一并
    include进来.
     #include <curses.h>
     main()
     {
      :  :
      :  :
     }
    当然, 您的系统内必须放有 curses.h 这个函式库.
 ■ 如何编译(compile)
    当您编辑好您的程式, 在 UNIX 提示符号下键入:
      % /usr/5bin/cc [file.c] -lcurses
                               ^^^^^^^
                               引进 curses.h 这个 library
    或 % /usr/5bin/cc [file.c] -lcurses -ltermlib
      (*注二)
 ■ 如何开始我的第一个 curses 程式?
    在开始使用 curses 的一切命令之前, 您必须先利用  initscr()这个函式
    来开启 curses 模式.
    相对的, 在结束  curses  模式前  ( 通常在您结束程式前  )  也必须以
    endwin()来关闭 curses 模式.
     #include <curses.h>
     main()
     {
       initscr();
       :  :
       :  :
       :  :
       endwin();
     }
    这是一般 curses 程式标准的模式.
    此外, 您可以就您程式所须, 而做不同的设定. 当然, 您可以不做设定,而
    只是呼叫 initscr().
    您可以自己写一个函式来存放所有您所须要的设定.  平常使用时, 只要呼
    叫这个函式即可启动 curses 并完成一切设定.
    下面的例子, 即是笔者将平常较常用的一些设定放在一个叫 initial()的函
    式内.
      void initial()
     {
       initscr();
       cbreak();
       nonl();
       noecho();
       intrflush(stdscr,FALSE);
       keypad(stdscr,TRUE);
       refresh();
      }
   各函式分别介绍如下:
     □ initscr()
          initscr()  是一般 curses 程式必须先呼叫的函数, 一但这个函数
          被呼叫之後, 系统将根据终端机的形态并启动 curses 模式.
     □ endwin()
          curses 通常以呼叫 endwin() 来结束程式.  endwin() 可用来关闭
          curses 模式, 或是暂时的跳离  curses 模式.如果您在程式中须要
          call shell ( 如呼叫 system() 函式 ) 或是需要做 system call,
          就必须先以   endwin()   暂时跳离   curses  模式.   最後再以
          wrefresh() doupdate() 来重返 curses 模式.
     □ cbreak()
        nocbreak()
          当 cbreak  模式被开启後, 除了 DELETE 或 CTRL 等仍被视为特殊
          控制字元外一切输入的字元将立刻被一一读取.当处於 nocbreak 模
          式时, 从键盘输入的字元将被储存在  buffer 里直到输入  RETURN
          或 NEWLINE.在较旧版的 curses 须呼叫 crmode(),nocrmode() 来
          取代 cbreak(),nocbreak()
     □ nl()
        nonl()
          用来决定当输入资料时, 按下 RETURN 键是否被对应为 NEWLINE 字
          元 ( 如 \n ).
          而输出资料时, NEWLINE  字元是否被对应为  RETURN 和 LINDFEED
          系统预设是开启的.

     □ echo()
        noecho()
          此函式用来控制从键盘输入字元时是否将字元显示在终端机上.系统
          预设是开启的.
     □ intrflush(win,bf)
          呼叫 intrflush 时须传入两个值:
          win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr
          bf 为 TRUE 或 FALSE
          当 bf 为 true 时, 当输入中断字元 ( 如 break) 时, 中断的反应
          将较为快速.但可能会造成萤幕的错乱.

      □ keypad(win,bf)
          呼叫 keypad 时须传入两个值:
          win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr
          bf 为 TRUE 或 FALSE
          当开启  keypad 後, 可以使用键盘上的一些特殊字元, 如上下左右
          等方向键, curses 会将这些特殊字元转换成 curses.h 内定义的一
          些特殊键. 这些定义的特殊键通常以 KEY_ 开头.

      □ refresh()
          refresh() 为 curses 最常呼叫的一个函式.
          curses 为了使萤幕输出入达最佳化, 当您呼叫萤幕输出函式企图改
          变萤幕上的画面时, curses  并不会立刻对萤幕做改变,  而是等到
          refresh() 呼叫後, 才将刚才所做的变动一次完成.  其馀的资料将
          维持不变. 以尽可能送最少的字元至萤幕上. 减少萤幕重绘的时间.
          如果是 initscr() 後第一次呼叫  refresh(), curses 将做清除萤
          幕的工作.


 ■ 游标的控制
       move(y,x)       将游标移动至 x,y 的位置
       getyx(win,y,x)  得到目前游标的位置
                       (请注意! 是 y,x 而不是 &y,&x )
 ■ 有关清除萤幕的函式
           clear()
           erase()   将整个萤幕清除
                     (请注意配合refresh() 使用)
 ■ 如何在萤幕上显示字元
     echochar(ch)              显示某个字元
     addch(ch)                 显示某个字元
     mvaddch(y,x,ch)           在(x,y) 上显示某个字元
                               相当於呼叫 move(y,x);addch(ch);
     addstr(str)               显示一串字串
     mvaddstr(y,x,str)         在(x,y) 上显示一串字串
                               相当於呼叫 move(y,x);addstr(str);
     printw(format,str)        类似 printf() , 以一定的格式输出至萤幕
     mvprintw(y,x,format,str)  在(x,y) 位置上做 printw 的工作.
                               相当於呼叫 move(y,x);printw(format,str);
 ■ 如何从键盘上读取字元
      getch()                        从键盘读取一个字元 (注意! 传回的是整数值)
      getstr()                       从键盘读取一串字元
      scanw(format,&arg1,&arg2...)   如同 scanf, 从键盘读取一串字元
      □例:
      int ch;
      char string1[80];   /* 请注意! 不可宣告为 char *string1; */
      char string2[80];
      echo();            /* 开启 echo 模式, 使输入立刻显示在萤幕上 */
      ch=getch();
      string1=getstr();
      scanw("%s",string2);
      mvprintw(10,10,"String1=%s",string1);
      mvprintw(11,10,"String2=%s",string2);
 ■ 如何利用方向键
    curses 将一些如方向键等特殊控制字元, 以 KEY_ 为开头定义在 curses.h
    这个档案里头, 如 KEY_UP  即代表方向键的  " ↑ ".  但, 如果您想使用
    curses.h  所为您定义的这些特殊键的话,  您就必须将   keypad  设定为
    TRUE. 否则, 您就必须自己为所有的特殊键定义了.
   curses.h 为一些特殊键的定义如下:
     KEY_UP          0403           ↑
     KEY_DOWN        0402           ↓
     KEY_LEFT        0404           ←
     KEY_RIGHT       0405           →
     KEY_HOME        0406           Home key (upward+left arrow)
     KEY_BACKSPACE   0407           backspace (unreliable)
     KEY_F0          0410           Function keys.
     KEY_F(n)        (KEY_F0+(n))   Formula for f .
     KEY_NPAGE       0522           Next page
     KEY_PPAGE       0523           Previous page
    以上仅列出笔者较常使用的一些控制键, 至於其他控制键的定义, 请自行参
    阅 man curses (* 注三)
    一并为您列出其他常用的一些特殊字元
     [TAB]                 /t
     [ENTER]               /r
     [ESC]                 27
     [BACKSPACE]           127
  ■ 如何改变萤幕显示字元的属性
    为了使输出的萤幕画面更为生动美丽,  我们常须要在萤幕上做一些如反白,
    闪烁等变化.  curses 定义了一些特殊的属性, 透过这些定义, 我们也可以
    在 curses 程式□控制萤幕的输出变化.
     attron(mod)    开启属性
     attroff(mod)   关闭属性
    curses.h 里头定义了一些属性, 如:
     A_UNDERLINE    加底线
     A_REVERSE      反白
     A_BLINK        闪烁
     A_BOLD         高亮度
     A_NORMAL       标准模式 (只能配合 attrset() 使用)
    当使用 attron() 开启某一种特殊属性模式後, 接下来在萤幕的输出都会以
    该种属性出现. 直到您呼叫 attroff() 将此模式关闭.
    请注意, 当您欲 attron() 开启另一种属性时, 请记得利用 attroff()先关
    闭原来的属性, 或直接以 attrset(A_NORMAL)  将所有特殊属性关闭.否则,
    curses 会将两种属性做重叠处理.
     □例:
        attrset(A_NORMAL);            /* 先将属性设定为正常模式       */
        attron(A_UNDERLINE);          /* 加底线                       */
        mvaddstr(9,10,"加底线");      /* 加底线输出一串字元           */
        attroff(A_UNDERLINE);         /* 关闭加底线模式, 恢复正常模式 */
        attron(A_REVERSE);            /* 开启反白模式                 */
        mvaddstr(10,10,"反白");       /* 输出一串反白字元             */
        attroff(A_REVERSE);           /* 关闭反白模式, 恢复正常模式   */
        attron(A_BLINK);              /* 开启闪烁模式                 */
        mvaddstr(11,10,"闪烁");       /* 输出一串闪烁字元             */
        attroff(A_BLINK);             /* 关闭闪烁模式, 恢复正常模式   */
        attron(A_BOLD);               /* 开启高亮度模式               */
        mvaddstr(12,10,"高亮度");     /* 输出一串高亮度字元           */
        attroff(A_BOLD);              /* 关闭高亮度模式, 恢复正常模式 */
  ■ 其他常用的一些函式
       beep()              发出一声哔声
       box(win,ch1,ch2)    自动画方框  ch1: 画方框时垂直方向所用字元
                                       ch2: 画方框时水平方向所用字元
                                       example: box(stdscr,'|','-');
                                       将以 | 及 - 围成一个方框
  ■ 应用完整□例
    下面所举的例子,  即完全利用刚刚所介绍的含式来完成.这个程式可将从键
    盘上读取的字元显示在萤幕上, 并且可以上下左右方向键来控制游标的位置
    , 当按下 [ESC] 後, 程式即结束.
    您有没有发现, 这不就是一个简单全萤幕编辑器的雏形吗?
   #include <curses.h>                  /* 引进 curses.h , 并自动引进 stdio.h */
   #define StartX  1                    /* 决定游标初始位置 */
   #define StartY  1
   void initial();
   main()
   {
      int x=StartX;                     /* 宣告 x,y 并设定其初值              */
      int y=StartY;
      int ch;                           /* 宣告 ch 为整数,配合 getch() 使用   */
      initial();                        /* 呼叫 initial(), 启动 curses 模式,  */
                                        /* 并完成其它设定                     */
      box(stdscr,'|','-');              /* 画方框                             */
      attron(A_REVERSE);                /* 开启反白模式                       */
      mvaddstr(0,20,"Curses Program");  /* 在 (20,0) 处输出反白字元           */
      attroff(A_REVERSE);               /* 关闭反白模式                       */
      move(x,y);                        /* 将游标移至初始位置                 */
      do {                              /* 以无限回圈不断等待输入             */
       ch=getch();                      /* 等待自键盘输入字元
       switch(ch) {                     /* 判断输入字元为何                   */
          case KEY_UP: --y;             /* 判断是否"↑"键被按下               */
                       break;
          case KEY_DOWN: ++y;           /* 判断是否"↓"键被按下               */
                       break;
          case KEY_RIGHT: ++x;          /* 判断是否"→"键被按下               */
                       break;
          case KEY_LEFT: --x;           /* 判断是否"←"键被按下               */
                       break;
          case '\r':                    /* 判断是否 ENTER 键被按下            */
                    ++y;
                    x=0;
                    break;
          case '\t':                    /* 判断是否 TAB 键被按下              */
                    x+=7;
                    break;
          case 127:                     /* 判断是否 BACKSPACE 键被按下        */
                     mvaddch(y,--x,' ');/* delete 一个字元                    */
                     break;
          case 27: endwin();            /* 判断是否[ESC]键被按下              */
                   exit(1);             /* 结束 curses 模式                   */
                                        /* 结束此程式                         */
          default:
                   addch(ch);           /* 如果不是特殊字元, 将此字元印出     */
                   x++;
                   break;
        }
        move(y,x);                      /* 移动游标至现在位置                 */
      } while (1);
    }
   void initial()                       /* 自定开启 curses 函式               */
   {
       initscr();
       cbreak();
       nonl();
       noecho();
       intrflush(stdscr,FALSE);
       keypad(stdscr,TRUE);
       refresh();
    }

  ■ 後记
    学完了上述的一些命令,  相不相信您已经可以写出一个漂亮的全萤幕编辑
    器了? 事实上, curses 提供的函式不下  200 个, 可是笔者认为, 一切再
    复杂的函式都可以用本文提到的一些组合变化而成,  学了太多的函式, 只
    是徒增自己困扰罢了.  当然,  如果您对其它函式有兴趣,  可以自行参阅
    curses 说明档.  ( 方法: % man curses ) 本文不过行抛砖引玉之效, 也
    希望未来能陆续出现更多同学自行创作的程式.
     * 任何疑问及建议, 欢迎 e-mail 至 ljh@CCCA.NCTU.edu.tw. 谢谢 ! *

 注一:
     请参考 /usr/share/lib/termcup
            /usr/share/lib/terminfo/s/sun
 注二:
     1.如果是 BSD 的版本, 需使用
       cc [file.c] -lcurses -ltermcap 来完成 compile.
     2.计中工作站不知何故将原来的 /usr/5bin/cc 更改为 /usr/5bin/cc.org
       因此, 您若想在计中工作站 compile curses 程式.需以 /usr/5bin/cc.org
       取代 /usr/5bin/cc , 否则 compile 可能发生错误.
     3.较旧版的 curses 需同时引进 curses 和 termlib 这两个 library,
       因此, 您必须使用 /usr/5bin/cc [file.c] -lcurses -ltermlib 来 compile.
 注三:
      根据笔者的经验, 上下左右方向键应可正常使用而不会发生问题, 但其它
      如 PgUp,PgDn,功能键,Home,End 等特殊键, 很容易因机器, 键盘不同而无
      法使用, 因此, 若您的程式须要在不同的机器上使用, 建议您只用方向键来
      控制, 其它的特殊键少用为妙.
      至於 PgUp,PgDn 一些特殊键的控制方法, 由於较为复杂, 有兴趣的同学可参
      考 tin 原始程式 curses.c 内所使用的一些方法.
                 UNIX 萤幕导向程式的发展利器 - curses (二)
                                                          校园网路策进会
                                                          会长    林建宏
      在上期为您介绍完了 curses.h 函式库的一些基本函式呼叫後在, 在本期里
    , 我们将继续为您介绍 curses 有关多视窗处理的函式. 有了这些函式, 我们
    可以在程式里同时处理多个不同的视窗.  如 joe 编辑器内我们可将萤幕切割
    成好几个小萤幕, 并且可以在这些不同的萤幕间做切换并编辑不同的档案, 这
    就是多视处理的应用. 另外, 有关 POP-UP 视窗的制作, 以及视窗的卷动, 在
    本文里, 我们将以简单的例子, 告诉您这些功能是如何做到的. 关於一些较基
    本函式的用法, 我们将不再特别介绍. 如果您尚未熟悉 curses 基本函式使用
    方法, 请参阅上一期 (80 期 ) 通讯.
  ■ 视窗的建立
    视窗的建立, 以 newwin() 这个函式来完成.  同时, 需宣告此视窗为 WINDOW
    结构变数.
    WINDOW *newwin(lines,colums,start_y,start_x);
     WINDOW *win;
     win=newwin(10,20,0,0);
    如此, 将以 (0,0) 为原点, 取一个 10 列 20 行的矩形为一新的视窗.  今後
    我们只要呼叫 win 这个变数, 就可以对这新视窗做处理.
     如: wmove(win,3,2);
  ■ 多视窗处理函式的格式
    这一类函式和一般的基本函式极为类似, 几乎每一个基本函式都有一个对应的
    视窗处理函式.  一般将 'w' 加在函式的里头作为区别, 'w' 乃 'window' 之
    意. 另外, 因为可同时处理多个视窗, 在呼叫使用时, 需特别指定欲处理的视
    窗. 当然, 如果您指定对 stdscr 做处理, 由於是对标准输出入萤幕处理, 其
    作用将相当於一般基本的函式.
     如:
      wmove(win,y,x)     即对 win 这个视窗做 move() 动作.
      wmove(stdscr,y,x)  相当於 move(y,x)
    介绍一些较重要的函式
    wmove(win,y,x)
    touchwin(win)
    wrefresh(win)
    mvwaddstr(win,y,x,str)
    wattron(attr)
    delwin(win)
    subwin(win,ny,nx,y,x)
    其他函式多和基本函式互为对应, 故不全部列出, 详细名称可参考 curses
    的 online manual.
  ■ 视窗内的座标系
    视窗内的座标系, 将以此视窗的起始点为新原点, 并以其相对位置作为新的
    座标. 举例来说
    win=newwin(10,20,5,5);
    wmove(win,2,3);
    将以 (5,5) 为新原点,  y 方向移动 2 单位, x 方向移动 3 单位. 因此实际
    上, 游标将移动至 y=7 x=8 的位置上.
  ■ POP-UP 视窗的建立
    利用 curses 所提供的视窗处理函式, 我们可以做出像  ONLINE HELP 的 POP
    -UP 画面. 当按下某键後, 一个新的视窗将像 " 跳 " 出来一般覆盖原来的画
    面. 当关掉此视窗後, 又不会影响到原来被覆盖的画面.
    下面的例子, 我们及模拟 ONLINE HELP 的形式, 当按下 'h' 键时, 视窗即出现
   #include <curses.h>
   main()
    {
     int ch,x,y;
     WINDOW *win;
     initscr();   ←┐
     cbreak;        │ 启动 curses 模式
     noecho();      │
     nonl();      ←┘
     win=newwin(4,30,LINES/2-3, COLS/2-15);/* 建立一个新视窗, 其中LINES,COLS
*/
     box(win,'|','-');                     /* 为 curses 内定值,
即萤幕行/列数*/
     mvwaddstr(win,1,4,"This is another screen");
     mvwaddstr(win,2,2,"Press anykey to continue..");
     for (y=0;y<LINES;++y)     /* 以'@'填满萤幕 */
      for (x=0;x<COLS;++x)
        mvprintw(y,x,"@");
      for(;;) {
       refresh();
       ch=getch();
       switch(ch) {
         case 'q':                /* 按 'q' 键离开 */
                   endwin();
                   exit(0);
         case '\t':              /* 按 [TAB] 键 呼叫另一视窗   */
           touchwin(win);        /* wrefresh() 前需 touchwin() */
           wrefresh(win);
           getch();              /* 按任意键关闭视窗 */
           touchwin(stdscr);
           break;
         default:break;
        }
      }
    }
   执行结果:
      ┌————————————————————————————┐
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      └————————————————————————————┘
                     ↑ 原来画面被 '@' 填满, 按下[TAB]键後
                     ↓ 出现 POP-UP 画面.
      ┌————————————————————————————┐
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@□---------------------------+@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@|   This is another screen   |@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@| Press anykey to continue.. |@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@□---------------------------+@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      │ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ │
      └————————————————————————————┘
  ■ 视窗的卷动
    视窗的卷动, 掖Q用来配合视窗的处理, 当我们持续对视窗输出直到视窗的游
    标移动至最後一列时, 如果我们再输出一列或是输出一个换行字元时, 视窗可
    整个往上卷动一行. 这对我们撰写一个编辑程式时, 是尤其重要的, 一个画面
    无法卷动的编辑器, 势必无法处理超过一个萤幕大小的档案.
    视窗的卷动是预设为关闭的, 并以 scrollok() 来控制开闭.
    scrollok(win,TRUE);    开启
    scrollok(win,FALSE);   关闭
    下面的例子因为不断地输出 0,1,2.. 故将以一个 40 * 10 的视窗不停的卷动
      #include <curses.h>
      main()
       {
         int i;
         WINDOW *scrwin,*boxwin;
         initscr();     ←┐
         cbreak;          │ 启动 curses 模式
         noecho();        │
         nonl();        ←┘
         scrwin=newwin(10,40,LINES/2-6,COLS/2-25); /* 设定另一视窗大小 */
         boxwin=newwin(12,42,LINES/2-7,COLS/2-26); /* 设定外框视窗大小 */
         scrollok(scrwin,TRUE);  /* 开启视窗卷动功能 */
         box(boxwin,'|','-');
         refresh();
         wrefresh(boxwin);
         for (i=0;;++i)         /* 不断地在视窗内输出 0-8 的数字,使视窗卷动
*/
           {
           wprintw(scrwin,"%d",i%9);
           wrefresh(scrwin);
           }
        }
      执行结果:
            ┌——————————————————————┐
            │         □---------------------□          │
            │         |3456780123456780123412| ↑ 视     │
            │         |3456780123456780123456| │ 窗     │
            │         |7801234567801234567801| │ 不     │
            │         |2345678012345678012345| │ 停     │
            │         |6780123456780123456780| │ 往     │
            │         |1234567801234567801234| │ 上     │
            │         |5678012345678012345678| │ 卷     │
            │         |0123456780123456780123| │ 动     │
            │         □---------------------□          │
            │                                            │
            └——————————————————————┘
  ■ □例 - 模拟 joe 分割画面同时编辑两个档案
     在下面的例子里, 我们应用了多视窗处理的函式, 改良上回介绍的编辑器,
     在这个程式里, 我们可以同时编辑两个画面, 并以 [ESC] 做不同视窗间的
     切换. 同时, 按下 [TAB] 键, 会出现 POP-UP 的 ONLINE HELP.
   #include <curses.h>
   void initial();
   main()
    {
     WINDOW *win[2],*curwin,*helpwin;
     int nowwin;
     int x,y;
     int i;
     int ch;
     initial();
     win[0]=newwin(LINES/2-1,COLS-1,0,0);       /* 设定两个视窗的大小*/
     win[1]=newwin(LINES/2-1,COLS-1,LINES/2,0);
     helpwin=newwin(3,30,2,COLS/2-15 );        /* ONLINE HELP 的大小 */
     box(helpwin,'|','-');
     mvwaddstr(helpwin,0,10,"ONLINE HELP");    /* ONLINE HELP 的内容 */
     mvwaddstr(helpwin,1,4,"Hit any key to continue..");
     for (i=0;i<COLS-1;++i)              /* 画两个视窗间的界限 */
       mvaddch(LINES/2-1,i,'-');
     nowwin=0;                          /* 先指定游标在第一视窗 */
     curwin=win[nowwin];
     getyx(curwin,y,x);
     move(0,0);
     refresh();
     refresh();
     do {
       ch=getch();
       switch(ch) {
             case KEY_UP: --y;             /* 判断是否"↑"键被按下       */
                          break;
             case KEY_DOWN: ++y;           /* 判断是否"↓"键被按下       */
                          break;
             case KEY_RIGHT: ++x;          /* 判断是否"→"键被按下       */
                          break;
             case KEY_LEFT: --x;           /* 判断是否"←"键被按下       */
                          break;
             case '\r':                    /* 判断是否 ENTER 键被按下    */
                       ++y;
                       x=0;
                       break;
             case '\t':                    /* 判断是否 TAB 键被按下      */
                       touchwin(helpwin);
                       wrefresh(helpwin); /* 呼叫 ONLINE HELP */
                       getch();
                       touchwin(win[1-nowwin]);  /* 重画第一,二视窗 */
                       wrefresh(win[1-nowwin]);
                       touchwin(curwin);
                       wrefresh(curwin);
                       break;
             case 127:                     /* 判断是否 BACKSPACE 键被按下 */
                        wmove(curwin,y,--x);/* delete 一个字元            */
                        waddch(curwin,' ');
                        break;
            case 27 : nowwin=1-nowwin;      /* [ESC] 键切换视窗 */
                       curwin=win[nowwin];
                       getyx(curwin,y,x);
                       break;
            default:
                      waddch(curwin,ch);
                      x++;
                      break;
                 }
            wmove(curwin,y,x);
            wrefresh(curwin);
         } while(1);
     }
   void initial()
      {
          initscr();                  ←┐
          cbreak();                     │ 启动 curses 模式
          nonl();                       │
          noecho();                   ←┘
          intrflush(stdscr,FALSE);
          keypad(stdscr,TRUE);
          refresh();
       }

    执行结果:
           ┌—————————————————————————————┐
           │    screen1                                               │
     ┌→  │         this is screen 1, you can press [ESC] to         │
  以 │    │     switch between screen 1 and screen 2.                │
[ESC]│    │                                                          │
  切 │    │                                                          │
  换 │    │----------------------------------------------------------│
  游 │    │     screen 2                                             │
  标 │    │                                                          │
  位 └→  │        _ (游标)                                          │
  置       │                                                          │
           └—————————————————————————————┘
                                   ↑ 按下[TAB] 键,出现 ONLINE HELP
                                   ↓
           ┌—————————————————————————————┐
           │    screen1                                               │
           │         this is screen 1, you can press [ESC] to         │
           │     switch□--------ONLINE HELP--------□                │
           │           |   Hit any key to continue..|                 │
           │           □---------------------------□                │
           │----------------------------------------------------------│
           │     screen 2                                             │
           │                                                          │
           │                                                          │
           │                                                          │
           └—————————————————————————————┘
                                   ↑ 按任意键, ONLINE HELP 关闭
                                   ↓
           ┌—————————————————————————————┐
           │    screen1                                               │
           │         this is screen 1, you can press [ESC] to         │
           │     switch between screen 1 and screen 2.                │
           │                                                          │
           │                                                          │
           │----------------------------------------------------------│
           │     screen 2                                             │
           │                                                          │
           │        _ (游标)                                          │
           │                                                          │
           └—————————————————————————————┘


 ■ 结语
    我们以连续两期来介绍 curses.h 函式库的使用方法, 相信同学对撰写这类的
    程式应该不再陌生. 所谓『戏法人人会变, 巧妙各有不同』. 知道了基本函式
    的呼叫方法, 能不能写出实用的程式, 就靠各位的巧思和创造力了.
    有任何问题建议, 欢迎 E-mail 至  ljh@CCCA.NCTU.edu.tw , 谢谢 !
发信人: Cardinal.bbs@mic.ee.ntu.edu.tw (Cardinal), 信区: unix
标  题: Re: 请问谁会用 curses 显示 ANSI color 字
发信站: 台大电机 Maxwell 站
首先声明,这一封的内容应该属於 programming board,但是现在有不止一个
人问我这个问题,所以在这个版再把详细的方法说明一遍。如果有人看不懂而
仍然有兴趣的,请 mail 给我 (Cardinal.bbs@mic.ee.ntu.edu.tw) ,不要在
这边 reply,我会考虑在私下或在 programming board解决你的问题。
==> 在 Cardinal@Maxwell (Cardinal) 的文章中提到:
:   1.开一个 new window (newwin)
:   2.设定 window 的彩色属性 (wattrset)
==> 在设定彩色属性之前应该先设定颜色的 "pair" ,所谓的 "pair" 是指
    foreground及background的颜色。curses的颜色有下面几种 (type为
    short) :
        COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE,
        COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
    用init_pair(short pair, short f_color, short b_color)来设定 pair,
    for example:
        init_pair(100, COLOR_RED, COLOR_BLUE)
    就设定了编号为 100, 蓝底红字的 color pair 了.
    另外你要是嫌这几种颜色太单调了, 可以用 init_color 来设定色彩, 细节
    这边就不谈了.
    然後就用 wattrset(WINDOW* pwindow, short color_pair)设定你window的
    颜色, for example:
        wattrset(pwindow, 100) 就设定了一个蓝底红字的 window (不要忘记
                               这儿的 100 是刚刚用 init_pair设定的值)
:   3.印在 window 的字就自动变成那个颜色了 (mvwprintw, mvwaddstr, ...)
==> 这句... 该不会有问题吧.
:   4.想要印不同颜色的字,只要把那个字 "OR" (|) 不同的颜色即可 (记住,
:     这种有属性的字要用 int,不能用 char)
==> 其实型别不是用 int, 而是用 chtype (不过没有差别, 去查查 curses.h就
    知道) , 譬如说, 你想要在刚刚设定为蓝底红字的 window印一个别的颜色的
    'A' 字, 可以这麽做 :
    init_pair(another_color_pair, COLOR_随便, COLOR_随便) -->先设定另一
                                                             个 color pair
    char   cascii = 'A';
    chtype cascii_color = cascii | another_color_pair;
    再把 cascii_color 印出来就是一个你想要颜色的 A 了.
--
                                                        ~  Cardinal  ~
From:      Cardinal (Cardinal)
Title:     关於精华区...
Date:      Fri Mar 10 20:36:27 1995
您好:
在 programming 版精华区 unix - curses libraries 中有一篇文章是我写的,
刚刚来这边找资料时翻到的, 真是受宠若惊. 不过原来的文章 (如何用 curses
显示彩色) 有一点忘了提到, 希望您能把下面的说明加进去:
  1.  init_color及init_pair 是 SystemVR3以後的标准, 不适用於 BSD 或
      SunOS.
  2.  在 SunOS 上要达成这样的目的, 我知道的有两种解法
    a.有一款大同的中文工作站有支援 init_color & init_pair 的 library
      这一型的 library 与 SunOS 为 object-code compatible.
    b.ncurses 支援 init_pair & init_color