Ncurses是一個能提供功能鍵定義(快捷鍵),屏幕繪制以及基於文本終端的圖形互動功能的動態庫
您希望您的程序有一個彩色的界面嗎?Ncurses是一個能提供基於文本終端窗口功能的動態庫. Ncurses可以:
- 只要您喜歡,您可以使用整個屏幕
- 創建和管理一個窗口
- 使用8種不同的彩色
- 為您的程序提供鼠標支持
- 使用鍵盤上的功能鍵
Ncurses可以在任何遵循ANSI/POSIX標准的UNIX系統上運行,除此之外,它還可以從系統數據庫中檢測終端的屬性, 並且自動進行調整,提供一個不受終端約束的接口.因此,Ncurses可以在不同的系統平台和不同的終端上工作的非常好.
mc工具集就是一個用ncurses寫的很好的例子,而且在終端上系統核心配置的界面同樣是用ncurses編寫的. 下面就是它們的截圖:
哪里可以下載?
Ncurses是基於GNU/Linux發展的,請訪問 http://www.gnu.org/software/ncurses/以獲得最新的更新版本或者其他詳細信息以及相關鏈接 .
基礎知識
為了能夠使用ncurses庫,您必須在您的源程序中將curses.h包括(include)進來,而且在編譯的需要與它連接起來. 在gcc中您可以使用參數-lcurses進行編譯.
在使用ncurses的時候,您有必要了解它的基礎數據結構.它有一個WINDOW結構,從名字就很容易知道,它是用來描述 您創建的窗體的,所有ncurse庫中的函數都帶有一個WINDOW指針參數.
在ncurses中使用最多的組件是窗體.即使您沒有創建自己的窗體,當前屏幕會認為是您自己的窗體. 如同標准輸入輸出系統提供給屏幕的文件描述符stdout一樣(假設沒有管道轉向),ncurses提供一個 WINDOW指針stdscr做相同工作.除了stdscr外,ncurses還定義了一個WINDOW指針curscr. 和stdscr描述當前屏幕一樣,curscr描述當前在庫中定義的屏幕,您可以帶着"他們有什么區別?"這個問題繼續閱讀.
為了在您的程序中使用ncurses的函數和變量,您必須首先調用initscr函數(初始化工作),它會給一些變量比如 stdscr,curscr等分配內存,並且讓ncurses庫處於准備使用狀態,換句話說,所有ncurses函數必須跟在initscr后面. 同樣的約定,您在結束使用ncurses后,應該使用endwin來釋放所有ncurses使用的內存.在使用endwin后,您將不能在使用 任何ncurses的函數,除非您再一次調用initscr函數.
在initscr和endwin之間,請不要使用標准輸入輸出庫的函數輸出結果到屏幕上,否則,您會看到屏幕會被您的輸出 弄的亂七八糟,這可不是您期望的結果.當ncurses處在激活狀態時,請使用它自己的函數來把結果輸出到屏幕.在調用initscr之前或者 endwin之后,您就可以隨便使用了.
刷新屏幕:refresh
WINDOW結構不會經常保持同一高度寬度以及在窗體中的位置,但是會保持在窗體中的內容.當您向窗體寫入數據時, 會改變窗體中的內容,但並不意味着在屏幕中會立即顯示出來,要更新屏幕內容,必須調用refresh或者wrefres函數.
這里介紹了stdscr和curscr兩者之間的區別.curscr保存着當前屏幕的內容,在調用ncurse的輸出函數后,stdscr和curscr可能會有不同的內容, 如果您想在最近一次屏幕內容改變后讓stdscr和curscr保持一致,您必須使用refresh函數.換句話說, refresh是唯一一個處理curscr的函數.千萬不要弄混淆了curscr和stdscr,應該在refresh函數中更新curscr中的內容
refresh有一個能盡可能快的更新屏幕的機制,當調用refresh時,它只更新窗體中內容改變的行,這節省了CPU的處理時間 ,防止程序往屏幕上寫相同的信息(譯者注:在屏幕的同一位置不用重新顯示同樣的內容.)這種機制就是為什么同時使用ncurses的函數 和標准的輸入輸出函數會造成屏幕內容錯位的原因.當調用ncurses的輸出函數時,它會設置一個標志,能讓refresh 知道是哪一行改變了.但是您調用標准輸入輸出函數時,就不會產生這種結果.
refresh和wrefresh其實做了同樣的事情.wrefresh需要一個WINDOW的指針參數,它僅僅刷新該窗體的內容. refresh()等同於wrefresh(stdscr).我在后面會提到,和wrefresh一樣,ncursers的許多函數都有許多這種為stdscr定義的宏函數.
定義一個新的窗體
下面我們來談談能定義新窗體的subwin和newwin函數.他們都需要一個來定義新窗體的高度,寬度以及 左上角位置的參數,並且返回一個WINDOW指針來指向該窗體.您可以使用它來作為wrefresh的參數或者一些其他我將要 談到的函數.
您可能會問:"如果他們做同樣的事情,為什么要有兩個函數?",您是對的,他們之間有一些細微的差別.subwin創建一個 窗體的子窗體,它將繼承了父窗體的所有屬性.但如果在子窗體中改變了這些屬性的值,它將不會影響父窗體.
除此之外,父窗體和子窗體之間還有一些聯系.父窗體和子窗體中的內容將彼此共享,彼此影響.換句話說, 在父窗口和子窗體重疊的區域的字符會被任意一個窗體改變.如果父窗體寫入了數據到這塊區域,子窗體中這塊區域同樣 改變了,反之也是如此.
和subwin不同的是,newwin創建一個獨有的窗體.這樣的窗體,在沒有他們的子窗體之前,是不會和其他窗體共享 任何文本數據的.使用subwin的好處是可以使用較少的內存就可以方便的共享字符數據了.但是如果您擔心窗體數據會互相影響 那么就應該使用newwin.
您可以創建任意多層的子窗體,每一個子窗體又可以有它自己的子窗體,但是一定要記住,窗體的字符內容是被兩個以上 的窗體共享的.
當您調用完您定義的窗體后,您可以使用delwin函數來刪除該窗體.我建議您使用man pages來得到這些函數的詳細參數.
向窗體寫數據和從窗體讀數據
我們談到了stdscr,curscr,以及刷新屏幕和定義一個新窗體,但是我們怎樣向一個窗體寫入數據?我們怎樣從一個窗體中讀入數據?
實現以上目的函數如同標准輸入輸出庫中的一些函數一樣,我們使用printw來替換printf輸出內容,scanw替換scanf接受輸入, addch替換putc或者putchar,getch替換getc或者getchar.他們用法一樣,僅僅名字不同,類似的,addstr可以用來向窗體 寫入一個字符串,getstr用來從窗體中讀入一個字符串.所有這些函數都是以一個"w"字母開頭,后面再跟上函數的字, 如果需要操作另外一個窗體內容,第一個參數必須是該窗體的WINDOWS結構指針,舉個例子,printw(...)和wprintw(stdscr,...) 是相同的,就如同refresh()和wrefresh(stdscr)一樣.
如果要寫這些函數的詳細說明,這篇文章將會變的很長.要得到他們的述,原型以及返回值或者其他信息,man pages是一個不錯的選擇. 我建議您對照man pages檢查您使用的一個函數.他們提供了詳細和非常有用的信息.在這篇文章的最后一節,我提供了 一個示例程序,可以當作是一個ncurses函數的使用指南.
物理指針和邏輯指針
在講完寫入數據和從窗體讀出數據后,我們需要解釋一下物理指針和邏輯指針 物理指針是一個常用指針,它只有一個,從另一個方面講,邏輯指針屬於ncurses窗體, 每一個窗體都只有一個物理指針,但是他們可以有多個邏輯指針.
當窗體准備寫入和讀出的時候,邏輯指針會指向窗體中將要進行操作的區域.因此, 通過移動邏輯指針,您可以任何時候向窗體中的任意位置寫入數據.這個是區別與標准輸入輸出庫的優勢之處.
移動邏輯指針的函數是move或者另外一個您非常容易猜出來的函數wmove.move是wmove的一個宏函數,專門用來處理 stdscr的.
另外一個需要確認的是物理指針和邏輯指針的協作關系,物理指針的位置將會在一段寫入程序后無效,但是我們通過可以 通過WINDOW結構的_leave標志定位它.如果設置了_leave標志,在寫操作結束后,邏輯指針將會移動到物理指針指向窗體中最后寫入的區域. 如果沒有設置_leave位,在寫操作結束后,物理指針將返回到邏輯指針指向窗體的第一個字符寫入位置._leave標志是由leaveok函數控制的.
移動物理指針的函數是mvcur,不象其他的函數,mvcur在不用等待refresh動作就會立即生效.如果您想隱藏物理指針, 您可以使用curs_set函數,使用man pages來獲得詳細信息.
同樣存在一些宏函數簡化了上述的移動和寫入等函數.您可以在addch,addstr,printw,getch,getstr,scanw等函數 的man pages頁得到更多的解釋.
清除窗體
當我們向窗體寫完內容后,我們怎么樣清除窗體,行和字符?
在ncurses中,清除意味着用空白字符填充整塊區域,整行或者整個窗體的內容. 下面我介紹的函數將會使用空白字符填充必要的區域,達到我們清屏的目的.
首先我們談到能清楚字符和行的函數,delch和wdelch能刪除掉窗體邏輯指針指向的字符,下一個字符和一直到行末的字符都會左移 一個位置.deleteln和wdeleteln能刪除掉邏輯指針指向的行,並且上移下一行.
clrteol和wclrtoeol能清除掉從邏輯指針指向位置右邊字符開始到行末的所有字符.clrtbot和wclrtobot 首先清除掉從邏輯指針所在位置右邊字符開始到行末的所有字符,接着刪除下面所有行.
除了這些,還有一些函數能清除整個屏幕和窗體.有兩種方法可以清除掉整個屏幕.第一個是先用空白字符填充 屏幕所有區域,然后再調用refresh函數.另外一種方法是用固定的終端控制字符清除.第一種方法比較慢,因為它需要重寫 當前屏幕.第二種能迅速清除整個屏幕內容.
erase和werase用空白字符替換窗體的文本字符,在下一次調用refresh后屏幕內容將會被清除掉.但是如果窗體 需要清掉整個屏幕, 這將一個比較苯的辦法.您可以使用上面講的第一種方法來完成.當窗體需要被清除的是一個屏幕那么寬, 您可以使用下面講的函數來非常好的完成您的任務.
在涉及到其他函數之前,我們先來討論一下_clear標志位.如果設置了該標志,那么它會存在WINDOW結構中. 當調用它時,它會用refresh來發送控制代碼到終端,refresh檢查窗體的寬度是否是屏幕的寬度(使用_FULLWIN標志位). 如果是的話,它將用內置的終端方法刷新屏幕,它將寫入除了空白字符外的文本字符到屏幕,這是一種非常快速清屏的方法. 為什么僅僅當窗體的寬度和屏幕寬度相等時才用內置的終端方法清屏呢?那是因為控制終端代碼不僅僅只清除窗體自身 ,它還可以清除當前屏幕._clear標志位由clearok函數控制.
函數clear和wclear被用來清除和屏幕寬度一樣的窗體內容.實際上,這些函數等同與使用werase和clearok. 首先,它用空白字符填充窗體的文本字符.接着,設置_clear標志位,如果窗體寬度和屏幕寬度一樣,就使用內置的終端方法 清屏,如果不一樣就用空白字符填充窗體所有區域再用refresh刷新.
總而言之,如果您知道窗體的寬度和屏幕寬度一樣,就使用clear或者wclear,這個速度將非常快.如果窗體寬度 不是和屏幕寬度一樣,那么使用wclear和werase將沒有任何分別.
使用顏色
您在屏幕上看到的顏色其實都是顏色對,因為每一個區域都有一個背景色和一個前景色.使用ncurses顯示彩色 意味着您定義自己的顏色對並且將這些顏色對寫入到窗體.
如同使用ncurses函數必須先調用initscr一樣,start_color需要首先調用以初始化色素. 您用來定義自己的顏色對的函數是init_pair,當您使用它定義了一個顏色對后,它將會和您在函數中的設置的第一個參數聯系起來. 在程序中,無論您什么時候需要用該顏色對,您只需用COLOR_PAIR調用該參數就可以了.
除了定義顏色對,您還必須使用函數來保證寫入的使用是用不同的顏色對,attron和wattron可以滿足您的要求. 使用這些函數將會用您選擇的顏色對寫入數據到相應的屏幕上,直到調用了attroff或者wattroff函數.
bkgd和wbkgd函數可以改變相應的整個窗體的顏色對,調用時,它將會改變窗體所有區域的前景色和背景色.也就 是說,在下一個刷新動作前,窗體上所有的區域將會使用新的顏色對重寫.
使用剛才提到的那些函數man pages來得到詳細的關於顏色資料和信息.
窗體的邊框
您可以給您的程序里面的窗體一個很好看的邊框,在庫中有一個box宏函數可以替您做到這一點,和其他函數所 不同的是,沒有wbox函數.box需要一個WINDOW指針來作為參數.
您可以在box的man pages頁輕松獲得詳細的幫助,這里有一些需要注意的是,給一個窗體設置邊框其實只是 在窗體的相應邊框區域寫入了一些字符.如果您在邊框區域一下寫如了某些數據,邊框將會被中斷. 解決的辦法就是在您在原始窗體里面再建一個子窗體,將原始窗體放入到邊框里面然后使用里面的子窗體作為需要的輸入數據窗體.
功能鍵
為了能夠使用功能鍵,必須在我們需要接受輸入的窗體中設置_use_keypad標志位,keypad是一個能設置 _use_keypad值的函數,當您設置了_use_keypad后,您就可以使用鍵盤的功能鍵(快捷鍵),如同普通輸入一樣.
在這里,如果您想使用getch來作個簡單接受數據輸入的例子,您需要注意的是要將數據賦給整形變量(int)而不是 字符型(char).這是因為整形變量能容納的功能鍵比字符型更多.您不需要知道這些功能鍵的值,您只需要使用庫中定義的 宏名就可以了,在getch的man page中有這些數值的列表.
范例
我們將來分析一個非常簡單實用的程序.在這個程序中,將使用ncurses定義菜單,菜單中的一個選擇項都會被證明選種. 這個程序比較有意思的一面就是使用了ncurses的窗體來達到菜單效果.您可以看下面的屏幕截圖.
程序開始和普通一樣,包括進去了一個頭文件.接着我們定義了回車鍵和escape鍵的ASCII碼值.
#include <curses.h> #include <stdlib.h> #define ENTER 10 #define ESCAPE 27
當程序的時候,下面的函數會被調用.它首先調用initscr初始化指針接着調用start_color來顯示彩色. 整個程序中所使用的顏色對會在后面定義.調用curs_set(0)會屏蔽掉物理指針.noecho()將終止鍵盤上的輸入會在屏幕上顯示出來. 您可以使用noecho函數控制鍵盤輸入進來的字符,只允許需要的字符顯示.echo()將會屏蔽掉這種效果. 接着的函數keypad設置了可以在stdscr中接受鍵盤的功能鍵(快捷鍵),我們需要在后面的程序中定義F1,F2以及移動的光標鍵.
void init_curses() { initscr(); start_color(); init_pair(1,COLOR_WHITE,COLOR_BLUE); init_pair(2,COLOR_BLUE,COLOR_WHITE); init_pair(3,COLOR_RED,COLOR_WHITE); curs_set(0); noecho(); keypad(stdscr,TRUE); }
下面定義的這個函數定義了一個顯示在屏幕最頂部的菜單欄, 您可以看下面的main段程序,它看上去好象只是屏幕最頂部的一行,其實實際上是stdscr窗體的一個子窗體,該子窗體只有 一行.下面的程序將指向該子窗體的指針作為它的參數,首先改變它的背景色,接着定義菜單的字,我們使用waddstr定義菜單 的字.需要注意的是wattron調用了另外一個不同的顏色對(序號3)以取代缺省的顏色對(序號2).記住2號顏色對在最開始就 由wbkgd設置成缺省的顏色對了.wattroff函數可以讓我們切換到缺省的顏色對狀態.
void draw_menubar(WINDOW *menubar) { wbkgd(menubar,COLOR_PAIR(2)); waddstr(menubar,"Menu1"); wattron(menubar,COLOR_PAIR(3)); waddstr(menubar,"(F1)"); wattroff(menubar,COLOR_PAIR(3)); wmove(menubar,0,20); waddstr(menubar,"Menu2"); wattron(menubar,COLOR_PAIR(3)); waddstr(menubar,"(F2)"); wattroff(menubar,COLOR_PAIR(3)); }
下一個函數顯示了當按下F1或者F2鍵顯示的菜單,定義了一個在藍色背景上 菜單欄顏色一樣的白色背景窗體,我們不希望這個新窗口會被顯示在背景色上的字覆蓋掉.它們應該停留在那里直到 關閉了菜單.這就是為什么菜單窗體不能定義為stdscr的子窗體,下面會提到,窗體items[0]是用newwin函數定義的, 其他8個窗體則都是定義成items[0]窗體的子窗體.這里的items[0]被用來繪制一個圍繞在菜單旁邊的邊框,其他的 窗體則用來顯示菜單中選中的單元.同樣的,他們不會覆蓋掉菜單上的邊框.為了區別選中和沒選中的狀態,有必要讓 選中的單元背景色和其他的不一樣.這就是這個函數中倒數第三句的作用了,菜單中的第一個單元背景色和其他的不一樣, 這是因為菜單彈出來后,第一個單元是選中狀態.
WINDOW **draw_menu(int start_col) { int i; WINDOW **items; items=(WINDOW **)malloc(9*sizeof(WINDOW *)); items[0]=newwin(10,19,1,start_col); wbkgd(items[0],COLOR_PAIR(2)); box(items[0],ACS_VLINE,ACS_HLINE); items[1]=subwin(items[0],1,17,2,start_col+1); items[2]=subwin(items[0],1,17,3,start_col+1); items[3]=subwin(items[0],1,17,4,start_col+1); items[4]=subwin(items[0],1,17,5,start_col+1); items[5]=subwin(items[0],1,17,6,start_col+1); items[6]=subwin(items[0],1,17,7,start_col+1); items[7]=subwin(items[0],1,17,8,start_col+1); items[8]=subwin(items[0],1,17,9,start_col+1); for (i=1;i<9;i++) wprintw(items[i],"Item%d",i); wbkgd(items[1],COLOR_PAIR(1)); wrefresh(items[0]); return items; }
下面這個函數簡單的刪除了上面函數定義的菜單窗體.它首先用delwin函數刪除窗體, 接着釋放items指針的內存單元.
void delete_menu(WINDOW **items,int count) { int i; for (i=0;i<count;i++) delwin(items[i]); free(items); }
scroll_menu函數允許我們在菜單選擇項上上下移動,它通過getch讀取鍵盤上的鍵值,如果按下了鍵盤上的上移或者下移方向鍵, 菜單選擇項的上一個項或者下一個項被選中.回憶一下剛才所講的,選中項的背景色將會和沒選中的不一樣.如果是向左或者向右 的方向鍵,當前菜單將會關閉,另一個菜單打開.如果按下了回車鍵,則返回選中的單元值.如果按下了ESC鍵,菜單將會被關閉,並且沒有任何選擇項 ,下面的函數忽略了其他的輸入鍵.getch能從鍵盤上讀取鍵值,這是因為我們在程序開始使用了keypad(stdscr,TRUE) 並且將返回值賦給一個int型變量而不是char型變量,這是因為int型變量能表示比char型更大的值.
int scroll_menu(WINDOW **items,int count,int menu_start_col) { int key; int selected=0; while (1) { key=getch(); if (key==KEY_DOWN || key==KEY_UP) { wbkgd(items[selected+1],COLOR_PAIR(2)); wnoutrefresh(items[selected+1]); if (key==KEY_DOWN) { selected=(selected+1) % count; } else { selected=(selected+count-1) % count; } wbkgd(items[selected+1],COLOR_PAIR(1)); wnoutrefresh(items[selected+1]); doupdate(); } else if (key==KEY_LEFT || key==KEY_RIGHT) { delete_menu(items,count+1); touchwin(stdscr); refresh(); items=draw_menu(20-menu_start_col); return scroll_menu(items,8,20-menu_start_col); } else if (key==ESCAPE) { return -1; } else if (key==ENTER) { return selected; } } }
最后就是我們的main部分了.它使用了上面所有我們所講述和編寫的函數來使程序合適的工作. 它同樣通過getch讀取鍵值來判斷F1或者F2是否按下了,並且用draw_menu來在相應的菜單窗體上繪制菜單. 接着調用scroll_menu函數讓用戶選擇某一個菜單,當scroll_menu返回后,它刪除菜單窗體並且顯示所選擇的單元內容 在信息欄里.
我必須提到的是函數touchwin.如果在菜單關閉后沒有調用touchwin而立即刷新,那么最后打開的菜單將一直停留在 屏幕上.這是因為在調用refresh時,menu函數根本就沒有完全改變stdscr的內容.它沒有重新寫入數據到stdscr上, 因為它以為窗體內容沒有改變.touchwin函數設置了所有WINDOW結構中的標志位,它通知refresh刷新窗體中所有的行, 值都改變了,這樣在下一次刷新整個窗體時,即使窗體內容沒有改變也要重新寫入一次.在菜單關閉后,選擇的菜單信息會一直停留在 stdscr上面.菜單沒有在stdscr上寫數據,因為它是開了一個新的子窗口.
int main() { int key; WINDOW *menubar,*messagebar; init_curses(); bkgd(COLOR_PAIR(1)); menubar=subwin(stdscr,1,80,0,0); messagebar=subwin(stdscr,1,79,23,1); draw_menubar(menubar); move(2,1); printw("Press F1 or F2 to open the menus. "); printw("ESC quits."); refresh(); do { int selected_item; WINDOW **menu_items; key=getch(); werase(messagebar); wrefresh(messagebar); if (key==KEY_F(1)) { menu_items=draw_menu(0); selected_item=scroll_menu(menu_items,8,0); delete_menu(menu_items,9); if (selected_item<0) wprintw(messagebar,"You haven't selected any item."); else wprintw(messagebar, "You have selected menu item %d.",selected_item+1); touchwin(stdscr); refresh(); } else if (key==KEY_F(2)) { menu_items=draw_menu(20); selected_item=scroll_menu(menu_items,8,20); delete_menu(menu_items,9); if (selected_item<0) wprintw(messagebar,"You haven't selected any item."); else wprintw(messagebar, "You have selected menu item %d.",selected_item+1); touchwin(stdscr); refresh(); } } while (key!=ESCAPE); delwin(menubar); delwin(messagebar); endwin(); return 0; }
如果您拷貝了代碼到一個文件,假設名字是example.c,並且移走了我所有的注釋,您可以用下面這個方法編譯:
gcc -Wall example.c -o example -lcurses
為了測試程序,您可以在參考一章里下載該程序.
總結
我談到了很多關於ncurses的基礎知識,應該足夠用來給您的程序創建一個很好看的界面.還有許多方便的功能 在這里都沒有提及,您可以在我經常問到的幾個函數的man pages里面找到很多有用的信息.讀完了后,您將回明白 我這里提到的東西和內容僅僅是一個介紹而已.
參考程序: example.c
#include <curses.h> #include <stdlib.h> #define ENTER 10 #define ESCAPE 27 void init_curses() { initscr(); start_color(); init_pair(1,COLOR_WHITE,COLOR_BLUE); init_pair(2,COLOR_BLUE,COLOR_WHITE); init_pair(3,COLOR_RED,COLOR_WHITE); curs_set(0); noecho(); keypad(stdscr,TRUE); } void draw_menubar(WINDOW *menubar) { wbkgd(menubar,COLOR_PAIR(2)); waddstr(menubar,"Menu1"); wattron(menubar,COLOR_PAIR(3)); waddstr(menubar,"(F1)"); wattroff(menubar,COLOR_PAIR(3)); wmove(menubar,0,20); waddstr(menubar,"Menu2"); wattron(menubar,COLOR_PAIR(3)); waddstr(menubar,"(F2)"); wattroff(menubar,COLOR_PAIR(3)); } WINDOW **draw_menu(int start_col) { int i; WINDOW **items; items=(WINDOW **)malloc(9*sizeof(WINDOW *)); items[0]=newwin(10,19,1,start_col); wbkgd(items[0],COLOR_PAIR(2)); box(items[0],ACS_VLINE,ACS_HLINE); items[1]=subwin(items[0],1,17,2,start_col+1); items[2]=subwin(items[0],1,17,3,start_col+1); items[3]=subwin(items[0],1,17,4,start_col+1); items[4]=subwin(items[0],1,17,5,start_col+1); items[5]=subwin(items[0],1,17,6,start_col+1); items[6]=subwin(items[0],1,17,7,start_col+1); items[7]=subwin(items[0],1,17,8,start_col+1); items[8]=subwin(items[0],1,17,9,start_col+1); for (i=1;i<9;i++) wprintw(items[i],"Item%d",i); wbkgd(items[1],COLOR_PAIR(1)); wrefresh(items[0]); return items; } void delete_menu(WINDOW **items,int count) { int i; for (i=0;i<count;i++) delwin(items[i]); free(items); } int scroll_menu(WINDOW **items,int count,int menu_start_col) { int key; int selected=0; while (1) { key=getch(); if (key==KEY_DOWN || key==KEY_UP) { wbkgd(items[selected+1],COLOR_PAIR(2)); wnoutrefresh(items[selected+1]); if (key==KEY_DOWN) { selected=(selected+1) % count; } else { selected=(selected+count-1) % count; } wbkgd(items[selected+1],COLOR_PAIR(1)); wnoutrefresh(items[selected+1]); doupdate(); } else if (key==KEY_LEFT || key==KEY_RIGHT) { delete_menu(items,count+1); touchwin(stdscr); refresh(); items=draw_menu(20-menu_start_col); return scroll_menu(items,8,20-menu_start_col); } else if (key==ESCAPE) { return -1; } else if (key==ENTER) { return selected; } } } int main() { int key; WINDOW *menubar,*messagebar; init_curses(); bkgd(COLOR_PAIR(1)); menubar=subwin(stdscr,1,80,0,0); messagebar=subwin(stdscr,1,79,23,1); draw_menubar(menubar); move(2,1); printw("Press F1 or F2 to open the menus. "); printw("ESC quits."); refresh(); do { int selected_item; WINDOW **menu_items; key=getch(); werase(messagebar); wrefresh(messagebar); if (key==KEY_F(1)) { menu_items=draw_menu(0); selected_item=scroll_menu(menu_items,8,0); delete_menu(menu_items,9); if (selected_item<0) wprintw(messagebar,"You haven't selected any item."); else wprintw(messagebar, "You have selected menu item %d.",selected_item+1); touchwin(stdscr); refresh(); } else if (key==KEY_F(2)) { menu_items=draw_menu(20); selected_item=scroll_menu(menu_items,8,20); delete_menu(menu_items,9); if (selected_item<0) wprintw(messagebar,"You haven't selected any item."); else wprintw(messagebar, "You have selected menu item %d.",selected_item+1); touchwin(stdscr); refresh(); } } while (key!=ESCAPE); delwin(menubar); delwin(messagebar); endwin(); return 0; }