|
|
эта страница доступна на следующих языках: English Castellano ChineseGB Deutsch Francais Italiano Nederlands Portugues Russian Turkce |
автор Reha K. Gerçeker <gerceker(at)itu.edu.tr> Об авторе: Reha is a student of computer engineering in Istanbul, Turkey. He loves the freedom Linux provides as a software development platform. He spends much of his time in front of his computer, writing programms. He wishes to become a smart programmer someday. Перевод на Русский: Иванов Ю.А. <yaivanov(at)mail.ru> Содержание: |
Резюме:
Ncurses это библиотека, которая реализует для текстовых терминалов многооконный интерфейс с большими возможностями.
Вы хотите, чтобы ваша консольная программа имела цветной оконный интерфейс? Если да, то вам нужна ncurses, библиотека, которая реализует оконный интерфейс для текстовых терминалов. С помощью ncurses вы сможете:
Ncurses можно использовать на любой UNIX системе, поддерживающей стандарт ANSI/ POSIX. Кроме того, библиотеку отличает способность определять свойства терминала через системную базу и реализовывать, таким образом, терминально-независимый интерфейс. Для вашей программы это означает, первое, кросс-платформенную переносимость и, второе, независимость от типа используемого терминала.
В качестве живых примеров использования библиотеки можно привести всем известный Midnight Commander и программу, используемую при конфигурации ядра системы. Их "скриншоты" вы можете увидеть ниже
Ncurses разработана по открытой лицензии и поэтому, чтобы скачать ее последний релиз, получить более подробную информацию или найти другие соответствующие ссылки, мы смело посещаем www.gnu.org/software/ncurses/.
Для того, чтобы использовать библиотеку, вам следует включить в исходный код своей программы заголовочный файл curses.h и при "линковке" программы не забыть указать компилятору gcc параметр -lcurses.
При работе с библиотекой необходимо иметь понятие о такой фундаментальной структуре данных, как WINDOW. Как вы можете легко догадаться, она используется для описания создаваемого окна. Почти все функции библиотеки получают в качестве параметра указатель на эту структуру.
Наиболее часто используемыми компонентами ncurses являются окна. Даже если вы в своей программе не создаете окон, сам экран считается окном. Также как и файловый дескриптор stdout стандартной библиотеки I/O представляет экран ( когда нет других перенаправлений ), ncurses имеет свой указатель stdscr, который делает аналогичную работу. Кроме stdscr, библиотека имеет еще один похожий указатель - curscr. В то время как stdscr представляет экран, curscr представляет текущий экран. Вы можете спросить: "Так в чем отличие?". Читайте дальше.
Для того, чтобы использовать функции и переменные ncurses в своих программах, вы должны обратиться к функции initscr. Эта функция выделяет память для таких переменных как stdscr, curscr и делает библиотеку готовой к использованию. Другими словами, все функции библиотеки должны использоваться только после initscr. Очень похожим образом, вам следует обратиться к функции endwin, когда работа с ncurses закончена. Эта функция освобождает память, выделенную для ncurses. После вызова endwin, вы не сможете воспользоваться функциями ncurses до тех пор, пока снова не обратитесь к initscr.
Между обращениями к initscr и endwin, вам следует быть уверенным, что для работы с экраном не используются функции стандарьной библиотеки I/O. В противном случае, вы столкнетесь с нежелательным и, обычно, некорректным выводом на экран. Когда ncurses активна, используйте только ее функции. Перед обращением к initscr или после вызова endwin, вы можете делать что хотите.
Структура WINDOW не толлько хранит высоту, ширину и позицию окна, но также его контекст. Когда вы пишите в окно, его контекст меняется, но это не значит, что это сразу же отражается на экране. Для обновления экрана необходимо вызвать refresh или wrefresh.
Вот здесь и лежит различие между stdscr и curscr. В то время как curscr хранит контекст текущего экрана, stdscr, после обращения к фукнциям вывода библиотеки, может содержать уже другую информацию. Если вы хотите, чтобы последние изменения, отраженные в stdscr были перенесена в curscr, необходимо вызвать функцию refresh. Другими словами, refresh является единственной функцией связанной с curscr. И настоятельно рекомендуется, во избежание недоразумений с curscr, выполнять его обновление только с помощью этой функции.
В refresh реализован механизм обновления экрана с максимально возможной скоростью. При своем вызове, функция обновляет только те строки экрана, которые были изменены. Это бережет процессорное время, поскольку отпадает необходимость в повторном перезаписыании информации на экране. Это механизм как раз и является причиной несовместимости функций ncurses и стандартной библиотеки I/O при совместном использовании; при своем вызове функции библиотеки ncurses устанавливают флаг, который указывает refresh на измененнные строки экрана, при вызове функций стандартной библиотеки I/O ничего подобного не происходит.
refresh и wrefresh, в основном, делают одно и то же. wrefresh получает в качестве параметра указатель на структуру WINDOW и обновляет контекст только укзанного окна. refresh() является эквивалентом wrefresh(stdscr). Как я расскажу позднее, большинство функций ncurses, аналогично wrefresh, имеют подобные макросы, связывающие их с stdscr.
Давайте теперь поговорим о subwin и newwin, функциях, которые создают новые окна. Обе эти функции в качестве параметров получают высоту, ширину и координаты верхнего левого угла нового окна. В свою очередь, они возвращают указатель на структуру WINDOW, представляющую вновь созданное окно. Вы можете использовать этот указатель с wrefresh и другими функциями о которых я расскажу чуть позже.
"Если они делают одно и то же, зачем их дублировать?" можете вы спросить. И будете правы, они отличаются не сильно. subwin создает новое окно, как подокно в другом окне. Окно, созданное таким образом, наследует свойства родительского окна. Эти унаследованные свойства в дальнейшем могут быть изменены без влияния на родительское окно.
Кроме этого, существует еще одна вещь, которая связывает вместе родительское и дочернее окна. Символьный массив, который хранит контекст окна, становится общим и для родительского и для дочернего окон. Другими словами, символы на пересечении двух окон, могут быть изменены любым из них. Если родитель осуществляет запись в этой области, то меняется контекст и дочернего окна. Верно и обратное.
В противоположность subwin, newwin создает совершенно новое окно. Это окно, до тех пор пока в нем не будут созданы подокна, не разделяет свой символьный массив с другим окном. Преимущество использования subwin выражается в том, что общий символьный массив использует меньше памяти. Однако, когда окна накладываются одно на другое, использование newwin имеет свои положительные стороны.
Вы можете создавать подокна любой глубины. Каждое подокно, в свою очередь, может иметь свои подокна, но помните, что один и тот же символьный массив является общим для них всех.
Когда вы сделали все что хотели с созданным окном, вы можете его удалить. В этом вам поможет функция delwin. Я полагаю, что вы сами можете более подробно ознакомиться с вышеперечисленными функциями обратившись к документации.
Мы поговорили о stdscr, curscr, обновлении экрана и создании новых окон. Но как мы можем что-либо записать в окно? Или как мы прочитать данные из окна?
Функции, которые используются для этих целей очень похожи на своих собратьев из стандартной библиотеки I/O. Среди этих функций printw ( аналог printf ), scanw ( аналог scanf ), addch ( аналог putc или putchar ), getch ( аналог getc или getchar ). Они используются как обычно, разница только в именах. Аналогично, addstr можно использовать для записи в, а getstr для чтения строки из окна. Все эти функции с добавленной буквой 'w' впереди имени и указателем на структуру WINDOW в качестве первого передаваемого параметра, делают свою работу с различными окнами из stdscr. Например, printw(...) и wprintw(stdscr, ...) эквивалентны, также как refresh() и wrefresh(stdscr).
Можно бесконечно вникать в детали этих функций. Лучшим подспорьем в этом деле, конечно же, служит документация. Заключительная часть этой статьи, где я представлю пример программы, также может послужить руководством по использованию указанных функций.
После рассказа о записи в и чтении из окон настало время поговорить о физических и логичеких курсорах. Под физическим курсором мы понимаем тот мерцающий курсор, который всегда виден на экране. Он всегда в единственном экземпляре. С другой стороны, логический курсор принадлежит окну, созданному с помощью библиотеки ncurses и, следовательно, каждое окно будет иметь свой курсор. Таким образом, логических курсоров может быть несколько.
Логический курсор находится в области окна, где начнется процедура записи или чтения. Таким образом, имея возможность перемещать логический курсор практически повсюду, вы можете выводить информацию в любом месте экрана или окна в любое время. В этом заключается преимущество ncurses перед стандартной библиотекой I/O.
Функция, которая перемещает логический курсор это move или, как вы можете легко догадаться, wmove. move является макросом wmove, написанным для stdscr.
Теперь мы рассмотрим вопрос координации физического и логического курсоров. Позиция физического курсора, о которой можно говорить после завершения операции записи, определяется флагом _leave ( определен в структуре WINDOW ). Если _leave установлен, после операции записи, логический курсор будет перемещен в позицию физического курсора ( где был записан последний символ ). Если _leave не установлен, после операции записи, физический курсор возвращается в позицию логического курсора ( где был записан первый символ ). Флагом _leave управляет функция leaveok.
Перемещением физического курсора управляет функция mvcur. В отличие от других функций, работа mvcur отражается на экране мгновенно без ожидания следующего обновления. Если вы хотите, чтобы физический курсор стал невидимым, используйте функцию curs_set. Детали смотри в документации.
Также существуют макросы, которые комбинируют функции перемещения и записи, описанные выше, в один простой вызов. С этими функциями можно знакомится так же в документации.
Запись в окно произведена. Но как мы можем очистить окно, строку или символы?
В ncurses, операция очистки означает заполнение области, строки или контекста окна пробелами. Функции, о которых я буду рассказывать ниже, заполняют указанные области пробелами и, таким образом, очищают экран.
Сначала, давайте поговорим о функциях, которые связаны с очисткой символа или строки. Функции delch и wdelch удаляют символ, который находится в позиции логического курсора окна и смещают оставшиеся в той же строке символы влево. deleteln и wdeleteln удаляют строку на которой находится логический курсор и смещают все низлежащие строки вверх.
Функции clrtoeol и wclrtoeol удаляют все символы в одной строке справа от логического курсора. clrtobot и wclrtobot, сначала, вызывают wclrtoeol для удаления всех символов справа от логического курсора, а затем удаляют все последующие строки.
Помимо этих функций существуют функции, которые могут очищать экран или окно целиком. Есть два метода подобной очистки экрана. В первом, позиции экрана заполняются пробелами и, затем, вызывается refresh, а, во втором, используется встроенный код управления терминалом. Второй метод быстрее, так как не требует перезаписи позиций экрана и очищает экран практически мгновенно.
erase и werase заполняют символьный массив окна пробелами. И при следующем вызове refresh, окно очищается. Однако, если окно, которое следует очистить, заполняет весь экран, использование этих функций становится не эффективным. Они используют первый вариант очистки, описанный выше. Когда окно, которое нужно очистить, занимает весь экран, имеет смысл использовать функции, рассмотренные ниже.
Перед переходом к описанию других функций, пришло время рассказать о флаге _clear. Этот флаг определен в структуре WINDOW и если установлен, говорит refresh отправить на терминал управляющий код. При вызове, refresh проверяет занимает окно весь экран или нет ( используется флаг _FULLWIN ), если да, то она очищает экран с помощью управляющего кода. Это делает очистку экрана очень быстрой. Причина почему терминальный метод используется только для окон, занимающих весь экран лежит в том, что с помощью управляющего кода очищается экран, а не само окно. Флагом _clear управляет функция clearok.
Функции clear и wclear используются для очистки окон по ширине соответствующих экрану. Но на самом деле, эти функции эквивалентны вызовам werase и clearok. Сначала, они заполняют символьный массив окна пробелами. Затем, после установки флага _clear, очищают экран с помощью управляющего кода, если окно по ширине соответствует экрану или, же, обновляют окно, заполняя его пробелами.
Так что, если вы знаете, что окно, которое нужно очистить, занимает весь экран используйте clear или wclear. Это будет быстрее и эффективнее. Однако, когда окно не занимает экрана целиком, разницы в использовании wclear или werase нет.
Цвета, которые вы видите на экране следует предсталять в виде пар цветов. Это потому, что каждая область имеет свой "background"- и "foreground"-цвет. Вывод информации в цвете, в ncurses, означает создание своих собственных пар цветов и использовании их в операции записи в окне.
Аналогично тому как initscr необходимо вызывать в начале работы с ncurses, start_color необходимо вызывать для инициализации работы с цветами. Функция, которую необходимо использовать для создания пар цветов это init_pair. Когда вы создаете с помощью init_pair пару цветов, она ассоциируется с числом, которое вы передаете функции в качестве первого параметра. Затем, когда бы вы не захотели использовать эту пару цветов, вы просто сошлетесь на нее с помощью этого числа.
Кроме создания пар цветов, вам необходимы функции, осуществляющие запись с использованием различных цветовых пар. Это осуществляется функциями attron и wattron. Эти функции, пока не будут вызваны attroff или wattroff, заставляют выводиться всю информацию в соответствующее окно выбранной парой цветов.
Также существуют функции bkgd и wbkgd, которые меняют пару цветов, ассоциированную с целым окном. При вызове, они изменяют "background"- и "foreground"-цвета всех областей окна. Это значит, что при следующем вызове refresh, каждая область окна будет перезаписана с помощью новой пары цветов.
Смотрите документацию по поводу доступных цветов и подробной информации о вышеприведенных функциях.
Для придания вашей программе еще лучшего вида, вы можете создать вокруг окон рамки. В библиотеке существует макрос box, который и делает это для вас. В отличие от других функций, дял box wbox не существует; в качестве аргумента box получает указатель на структуру WINDOW.
Более подробную информацию о box вы можете найти в документации. Однако, следует упомянуть вот о чем. Обрамление окна рамкой означает просто запись необходимых символов в символьный массив окна, в его граничные области. Если вы позднее запишите в эти области какую-либо информацию, рамка окна будет испорчена. Для предотвращения этого, с помощью subwin вы создаете в исходном окне внутреннее окно, обрамляя, таким образом, исходное окно рамкой и используя внутреннее окно для записи.
Для того, чтобы использовать функциональные клавиши, для окна с которым вы собираетесь работать необходимо установить флаг _use_keypad. keypad является функцией, которая устанавливает значение _use_keypad. Когда вы устанавливаете _use_keypad, вы можете получить ввод с клавиатуры как обычно, используя функции ввода.
В этом случае, например, при использовании getch для получения данных, вам следует знать, что эти данные нужно хранить в переменной int, а не char. Причина этого заключается в том, что числовые значения функциональных клавиш по величине больше, чем может хранить переменная типа char. Вам не нужно знать этих числовых значений, просто узнайте их символьные имена, обратившись к описанию функции getch.
Сейчас мы собираемся проанализировать небольшую программу. В этой программе будет продемонстрирована работа с меню, созданным с помощью ncurses. "Скриншоты" вы можете посмотреть ниже:
Программа, как обычно, начинается с включения заголовочных файлов. Затем, мы определяем константы, чьими значениями будут ASCII-значения клавиш enter и escape.
#include <curses.h> #include <stdlib.h> #define ENTER 10 #define ESCAPE 27
Функция, приведенная ниже, вызывается в программе первой, при старте. В ней первой вызывается initscr для инициализации curses и, затем, start_color, которая делает возможным использование цветов. Пары цветов будут определены позднее. Вызов curs_set(0) делает физический курсор невидимым. noecho останавливает вывод информации с клавиатуры на экран. Функцию noecho можно также использовать для управления вводом с клавиатуры и отображения на экране только необходимой информации. Функция echo может быть вызвана когда нужно отменить действие noecho. И последней вызывается 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 ( см. ниже ) и посмотреть, что это горизонтальное меню, появляющееся в виде одной строки в верхней части экрана, на самом деле определено как subwindow размером в одну строку для 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. Для создания эффекта меню, поверх синего окна, используемого в качестве подложки, создается новое окно с таким же белым цветом как и menubar. Мы не хотим, чтобы этим новым окном, были перезаписаны символы ранее выведенные на подложку. Они должны остаться на своем месте после закрытия меню. Вот почему, окно меню не может быть создано как subwindow stdscr. Как вы можете видеть ниже, окно items[0] создается с помощью newwin, а другие 8 окон items созданы как subwindows для 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 она определяет нажатые на клавиатуре клавиши. И если были нажаты клавиши со стрелками вверх или вниз, тогда становится выбранным пункт меню, расположенный выше или ниже. Это, если вы можете вспомнить, сделано путем изменения цвета подложки выбранного пункта меню отличным от цвета подложки других пунктов. Если были нажаты клавиши со стрелками влево или вправо, открытое меню закрывается и открывается другое. Если нажата клавиша enter, выбранный пункт меню по прежнему остается выбранным. При нажатии ESC, меню закрывается без выбора какого-либо пункта. Все другие клавиши функция игнорирует. В этой функции с помощью getch можно определять нажатие клавиши со стрелками. Позвольте мне напомнить вам, что все это осуществилось благодаря вызову в первой функции программы ( init_curses ) функции keypad(stdscr,TRUE) и тому, что возвращенное функцией getch значение было сохранено в переменной с типом int, а не char ( значения функциональных клавиш по величине больше величины, хранимой в переменной с типом 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 or F2 нажаты, отрисовывается соответствующее окно меню с помощью draw_menu. После этого, в ней вызывается scroll_menu и пользователю дается возможность выбрать какой-либо пункт меню. После отработки scroll_menu, она удаляет окна меню и выводит выбранный пункт в messagebar.
Еще я хочу упомянуть о функции touchwin. Если refresh, после закрытия меню, была вызвана напрямую без touchwin, то последнее открытое меню останется на экране. Причина этого в том, что функции меню не изменяют stdscr вообще, и при вызове refresh тоже, они не перезаписывают никаких символов в stdscr, поскольку полагают, что окно не изменилось. touchwin устанавливает все флаги в структуре WINDOW, которые говорят refresh обо всех измененных в окне строках и поэтому, при следующем вызове 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, которых должно хватить для создания хорошего интерфейса для вашей программы. Однако, возможности библиотеки не ограничиваются вышеизложенным. Вы сами сможете открыть много интересного углубившись в документацию к библиотеке и поймете, что эта статья только начало.
|
Webpages maintained by the LinuxFocus Editor team
© Reha K. Gerçeker, FDL LinuxFocus.org Click here to report a fault or send a comment to LinuxFocus |
Translation information:
|
2002-03-02, generated by lfparser version 2.19