Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 21 (всего у книги 67 страниц)
Экран
Как: вы уже видели, все программы с использованием curses должны начинаться с вызова функции initscr
и заканчиваться вызовом функции endwin
. Далее приведены их описания из заголовочного файла.
#include
WINDOW *initscr(void);
int endwin(void);
Функция initscr
должна вызываться только один раз в каждой программе. В случае успешного завершения она возвращает указатель на структуру stdscr
. Если функция заканчивается аварийно, она просто выводит диагностическое сообщение об ошибке и вызывает завершение программы.
Функция endwin
возвращает константу OK в случае успешного завершения и err в случае неудачи. Вы можете вызвать ее для того, чтобы покинуть curses, а позже возобновить функционирование библиотеки curses, вызвав clearok(stdscr, 1)
и refresh
. Это позволит библиотеке совершенно забыть, как выглядит физический экран, и заставит ее выполнить полное обновление экрана.
Для обновления экрана предоставляется несколько базовых функций.
#include
int addch(const chtype char_to_add);
int addchstr(chtype *const string_to_add);
int printw(char *format, ...);
int refresh(void);
int box(WINDOW *win_ptr, chtype vertical_char, chtype horizontal_char);
int insch(chtype char_to_insert);
int insertln(void);
int delch(void);
int deleteln(void);
int beep(void);
int flash(void);
У библиотеки curses есть свой символьный тип данных chtype
, который может содержать больше разрядов, чем стандартный тип char
. В стандартной версии ncurses для ОС Linux chtype
на самом деле – синоним стандартного типа unsigned long
.
Функции addch
и addchstr
вставляют заданные символ или строку в текущую позицию на экране. Функция printw
форматирует строку так же, как функция printf
, и помещает в текущую позицию на экране. Функция refresh
вызывает обновление физического экрана, возвращая OK
в случае успеха и ERR
при возникновении ошибки. Функция box
позволяет нарисовать рамку вокруг окна.
Примечание
В стандартной библиотеке curses вы можете применять только «обычные» символы для рисования горизонтальных и вертикальных линий. В расширенной версии библиотеки можно использовать два определения,
ASC_VLINE
иACS_HLINE
, для вывода символов вертикальных и горизонтальных линий соответственно, которые позволят нарисовать внешне более привлекательную рамку. Для этого ваш терминал должен поддерживать символы псевдографики. Обычно они лучше отображаются в окне эмулятора xterm, чем на стандартной консоли, но их поддержка полна корректировок или «заплат», поэтому мы полагаем, что вы откажетесь от их применения, если важна переносимость вашей программы.
Функция insch
вставляет символ, сдвигая имеющиеся символы вправо. При этом не определено, что произойдет в конце строки, результат зависит от используемого терминала. Функция insertln
вставляет пустую строку, перемещая имеющиеся строки на одну вниз. Функции delch
и deleteln
аналогичны функциям insert
.
Для получения звука можно вызвать функцию beep
. Немногие терминалы не способны издавать звуки, в этом случае некоторые установки библиотеки curses при вызове beep
заставят экран мигать. Если вы работаете в густонаселенном офисе и звуковые сигналы могут издавать многие компьютеры, возможно, вы сочтете мигание предпочтительным режимом. Как и ожидалось, функция flash
вызывает мигание экрана, если это невозможно, она попробует заставить терминал издать звуковой сигнал взамен.
Вы можете считывать символы с экрана, хотя эта функциональная возможность применяется нечасто, поскольку гораздо легче отслеживать то, что выводится. Если вам все-таки это потребуется, выполняйте считывание с помощью следующих функций:
#include
chtype inch(void);
int instr(char *string);
int innstr(char *string, int number_of_characters);
Функция inch
должна быть всегда доступна, а функции instr
и innstr
не всегда поддерживаются. Функция inch
возвращает символ из текущей позиции курсора на экране и данные о его атрибутах. Обратите внимание на то, что функция возвращает значение не char
, a chtype
, в то время как функции instr
и innstr
пишут в массивы с элементами типа char
.
Существует четыре основных способа очистки области экрана:
#include
int erase (void);
int clear(void);
int clrtobot(void);
int clrtoeol(void);
Функция erase
записывает пробелы во все позиции экрана. Функция clear
, как и erase
, очищает экран, но вызывает перерисовку экрана с помощью внутреннего вызова низкоуровневой функции clearo
k, которая выполняет последовательность очистки экрана и новое отображение экрана при следующем вызове refresh
.
Функция clear
обычно применяет команду терминала, которая очищает весь экран, а не пытается стереть текущие непробельные символы во всех точках экрана. Это делает функцию clear
надежным средством очистки экрана. Сочетание функции clear
с последующей функцией refresh
может обеспечить удобную команду перерисовки экрана в том случае, когда изображение на экране беспорядочно или испорчено каким-либо образом.
Функция clrtobot
очищает экран, начиная с текущей позиции курсора и далее до конца экрана, а функция clrtoeol
очищает экран, начиная с текущей позиции курсора до конца строки, в которой находится курсор.
Для перемещения курсора применяется единственная функция с дополнительной командой, управляющей положением курсора после обновления экрана.
#include
int move(int new_y, int new_x);
int leaveok(WINDOW *window_ptr, bool leave_flag);
Функция move
просто переносит позицию логического курсора в заданное место на экране. Напоминаем о том, что начало экранных координат (0, 0) находится в левом верхнем углу экрана. В большинстве версий библиотеки curses две глобальные целочисленные переменные, LINES
и COLUMNS
, определяют размер физического экрана и могут применяться для определения максимально допустимых значений параметров new_y
и new_x
. Вызов move
сам по себе не приводит к перемещению физического курсора. Он только изменяет позицию на логическом экране, в которой появится следующий вывод. Если вы хотите, чтобы экранный курсор переместился немедленно после вызова функции move, вставьте следом за ним вызов функции refresh
.
Функция leaveok
устанавливает флаг, управляющий положением курсора на физическом экране после его обновления. По умолчанию флаг равен false
, и после вызова refresh
аппаратный курсор остается в той же точке экрана, что и логический курсор. Если флаг равен true
, аппаратный курсор можно оставить в случайно выбранной точке экрана. Как правило, значение, устанавливаемое по умолчанию, предпочтительней, т.к. курсор остается в не лишенной смысла позиции.
У всех символов, обрабатываемых curses, могут быть определенные атрибуты, управляющие способом отображения символа на экране при условии, что оборудование, применяемое для их отображения, поддерживает требуемый атрибут. Определены следующие атрибуты: A_BLINK
, A_BOLD
, A_DIM
, A_REVERSE
, A_STANDOUT
и A_UNDERLINE
. Вы можете использовать перечисленные далее функции для установки атрибутов по одному или все вместе.
#include
int attron(chtype attribute);
int attroff(chtype attribute);
int attrset(chtype attribute);
int standout(void);
int standend(void);
Функция attrset
устанавливает атрибуты curses, функции attron
и attroff
включают и отключают заданные атрибуты, не портя остальные, а функции standout
и standend
обеспечивают более выразительный режим выделения или «лучший из всех» режим. На большинстве терминалов выбирается инверсия.
Выполните упражнение 6.2.
Упражнение 6.2. Перемещение, вставка и атрибуты
Теперь, когда вы знаете больше об управлении экраном, можно испытать более сложный пример moveadd.c. Вы включите несколько вызовов функций refresh
и sleep
в этот пример, чтобы на каждом шаге видеть, как выглядит экран. Обычно программы с использованием библиотеки curses стараются обновлять экран как можно реже, поскольку это не слишком высокопроизводительная операция. Программный код написан с некоторой долей искусственности для обеспечения большей наглядности.
1. Для начала вставьте несколько заголовочных файлов, определите несколько символьных массивов и указатель на них, а затем инициализируйте структуры библиотеки curses:
#include
#include
#include
#include
#include
int main() {
const char witch_one[] = " First Witch ";
const char witch_two[] = " Second Witch ";
const char *scan_ptr;
initscr();
2. Теперь для трех начальных текстовых фрагментов, которые появляются на экране через определенные интервалы, включите и отключите соответствующие флаги атрибутов;
move(5, 15);
attron(A_BOLD);
printw(«%s», «Macbeth»);
attroff(A_BOLD);
refresh();
sleep(1);
move(8, 15);
attron(A_STANDOUT);
printw(«%s», «Thunder and Lightning»);
attroff(A_STANDOUT);
refresh();
sleep(1);
move(10, 10);
printw(«%s», «When shall we three meet again»);
move(11, 23);
printw(«%s», «In thunder, lightning, or in rain ?»);
move(13, 10);
printw(«%s», "When the hurlyburly's done, ");
move(14, 23);
printw(«%s», «When the battle's lost and won.»);
refresh();
sleep(1);
3. Действующие лица идентифицированы, и их имена выводятся посимвольно:
attron(A_DIM);
scan_ptr = witch_one + strlen(witch_one) – 1;
while (scan_ptr != witch_one) {
move(10, 10);
insch(*scan_ptr–);
}
scan_ptr = witch_two + strlen(witch_two) – 1;
while (scan_ptr != witch_two) {
move(13, 10);
insch(*scan_ptr–);
}
attroff(A_DIM);
refresh();
sleep(1);
4. В заключение переместите курсор в правый нижний угол экрана, а затем подготовьте и выполните завершение:
move(LINES – 1, COLS – 1);
refresh();
sleep(1);
endwin();
exit(EXIT_SUCCESS);
}
Когда вы выполните программу, заключительный экран будет выглядеть так, как показано на рис. 6.3. К сожалению, снимок экрана не дает полного впечатления и не показывает курсор, установленный в правом нижнем углу экрана. Эмулятор xterm может быть более подходящей средой для точного отображения программ, чем обычная консоль.
Рис. 6.3
Как это работает
После инициализации некоторых переменных и экрана с помощью библиотеки curses вы применили функции move
для перемещения курсора по экрану. Посредством функций attron
и attroff
вы управляли атрибутами текста, выводимого в заданную точку экрана. Далее перед закрытием библиотеки curses и завершением программа продемонстрировала, как вставлять символы функцией insch
.
Клавиатура
Наряду с предоставлением интерфейса, облегчающего управление экраном, библиотека curses также предлагает средства, облегчающие управление клавиатурой.
Режимы клавиатурыПроцедуры считывания с клавиатуры управляются режимами. Режимы устанавливаются с помощью следующих функций:
#include
int echo(void);
int noecho(void);
int cbreak(void);
int nocbreak(void);
int raw(void);
int noraw(void);
Функции echo
и noecho
просто включают и отключают отображение символов, набираемых на клавиатуре. Оставшиеся четыре функции управляют тем, как символы, набранные на терминале, становятся доступны программе с применением curses. Для того чтобы понять функцию cbreak
, необходимо иметь представление о стандартном режиме ввода. Когда программа, использующая библиотеку curses, стартует с вызова функции initscr
, устанавливается режим ввода, называемый режимом с обработкой (cooked mode). Это означает построчную обработку, т.е. ввод становится доступен программе после нажатия пользователем клавиши cbreak
, программа может установить режим ввода cbreak
, в котором символы становятся доступными программе сразу после их набора, а не помещаются в буфер и передаются программе только после нажатия клавиши
Вызов функции raw
отключает обработку специальных символов, поэтому становится невозможной генерация сигналов или управление потоком с помощью набранных на клавиатуре специальных символьных последовательностей. Вызов функции nocbreak
возвращает режим ввода в режим с обработкой символов, но режим обработки специальных символов не изменяет; вызов noraw
восстанавливает и режим с обработкой, и обработку специальных символов.
Чтение с клавиатуры – очень простая операция. К основным функциям чтения относятся следующие:
#include
int getch(void);
int getstr(char *string);
int getnstr(char *string, int number_of_characters);
int scanw(char *format, ...);
Все они действуют подобно своим аналогам, не входящим в библиотеку curses, getchar
, gets
и scanf
. Обратите внимание на то, что у функции getstr
нет возможности ограничить длину возвращаемой строки, поэтому применять ее следует с большой осторожностью. Если ваша версия библиотеки curses поддерживает функцию getnstr
, позволяющую ограничить количество считываемых символов, всегда применяйте ее вместо функции getstr
. Это очень напоминает поведение функций gets
и fgets
, с которыми вы познакомились в главе 3.
В упражнении 6.3 для демонстрации управления клавиатурой приведен пример короткой программы ipmode.c.
Упражнение 6.3. Режим клавиатуры и ввод
1. Наберите программу и включите в нее начальные вызовы библиотеки curses:
#include
#include
#include
#include
#define PW_LEN 256
#define NAME_LEN 256
int main() {
char name[NAME_LEN];
char password[PW_LEN];
const char *real_password = «xyzzy»;
int i = 0;
initscr();
move(5, 10);
printw(«%s», «Please login:»);
move(7, 10);
printw(«%s», "User name: ");
getstr(name);
move(9, 10);
printw(«%s», "Password: ");
refresh();
2. Когда пользователь вводит свой пароль, необходимо остановить отображение символов на экране. Далее сравните введенный пароль со строкой xyzzy:
cbreak();
noecho();
memset(password, ' ', sizeof(password));
while (i < PW_LEN) {
password[i] = getch();
if (password[i] == 'n') break;
move(8, 20 + i);
addch('*');
refresh();
i++;
}
3. В заключение восстановите отображение символов и выведите сообщение об успешном или неудачном завершении:
echo();
nocbreak();
move(11, 10);
if (strncmp(real_password, password, strlen(real_password)) == 0)
printw(«%s», «Correct»);
else printw(«%s», «Wrong»);
printw(«%s», « password»);
refresh();
sleep(2);
endwin();
exit(EXIT_SUCCESS);
}
Как это работает
Остановив отображение клавиатурного ввода и установив режим cbreak
, вы выделяете область памяти, готовую к приему пароля. Каждый введенный символ пароля немедленно обрабатывается, и на экран выводится *
в следующей позиции курсора. Вам необходимо каждый раз обновлять экран и сравнивать с помощью функции strcmp
две строки: введенный и реальный пароли.
Примечание
Если вы пользуетесь очень старой версией библиотеки curses, вам, возможно, понадобится выполнить дополнительный вызов функции
refresh
перед вызовом функцииgetstr
. В библиотеке ncurses вызовgetstr
обновляет экран автоматически.
Окна
До сих пор вы использовали терминал как средство полноэкранного вывода. Это вполне подходит для маленьких простых программ, но библиотека curses идет гораздо дальше. Вы можете на физическом экране одновременно отображать множество окон разных размеров. Многие из описанных в этом разделе функций поддерживаются в терминах стандарта X/Open так называемой «расширенной» версией curses. Но поскольку они поддерживаются библиотекой ncurses, не велика проблема сделать их доступными на большинстве платформ. Пора идти дальше и применить множественные окна. Вы увидите, как обобщаются до сих пор использовавшиеся команды и применяются в сценариях с множественными окнами.
Структура WINDOWНесмотря на то, что мы уже упоминали стандартный экран stdscr
, пока у вас не было необходимости в его применении, поскольку почти все рассматриваемые до сих пор функции полагали, что они работают на экране stdscr
, и не требовалось передавать его как параметр.
stdscr
– это специальный случай структуры WINDOW
, как stdout – специальный случай файлового потока. Обычно структура WINDOW
объявляется в файле curses.h и, несмотря на то, что ее просмотр может быть очень поучителен, программы никогда не используют эту структуру напрямую, т.к. она может различаться в разных реализациях.
Вы можете создать и уничтожить окно с помощью вызовов функций newwin и delwin:
#include
WINDOW *newwin(int num_of_lines, int num_of_cols, int start_y, int start_x);
int delwin(WINDOW *window_to_delete);
Функция newwin
создает новое окно в позиции экрана (start_y, int start_x
) и с заданным. количеством строк и столбцов. Она возвращает указатель на новое окно или NULL
, если создать окно невозможно. Если вы хотите, чтобы правый нижний угол нового окна совпадал с правым нижним углом экрана, можно задать нулевое количество строк и столбцов. Все окна должны располагаться в пределах экрана. Функция newwin
завершится аварийно, если какая-либо часть окна окажется за пределами экрана. Новое окно, созданное newwin
, абсолютно независимо от всех уже имеющихся окон. По умолчанию оно помещается поверх существующих окон, скрывая (но не изменяя) их содержимое.
Функция delwin
удаляет окно, созданное ранее с помощью функции newwin
. Поскольку при вызове newwin
, по всей вероятности, выделяется память, следует всегда удалять окна, когда в них больше нет нужды.
Примечание
Следите за тем, чтобы никогда не было попыток удалить собственные окна библиотеки curses:
stdscr
иcurscr
!
Когда новое окно создано, как записать в него информацию? У всех уже рассмотренных функций есть универсальные версии, действующие в заданных окнах, и для удобства в них также включено перемещение курсора.
Универсальные функцииВы уже применяли функции addch
и printw
для вставки символов на экран. К этим функциям, как и ко многим другим, может быть добавлен префикс либо w
для окна, либо mv
для перемещения курсора, либо mvw
для перемещения и окна. Если вы посмотрите заголовочный файл большинства версий библиотеки curses, то увидите, что многие функции, применявшиеся до сих пор, – простые макросы (#defines
), вызывающие эти более универсальные функции.
Когда добавляется префикс w
, в начало списка аргументов должен быть вставлен указатель типа WINDOW
. Когда добавляется префикс mv
, в начало списка нужно вставить два дополнительных параметра, координаты y и х. Они задают позицию на экране, в которой выполняется операция, у и х – относительные координаты окна, точка (0, 0) находится в левом верхнем углу окна, а не экрана.
Когда добавляется префикс mvw
, необходимо передавать в функцию три дополнительных параметра: указатель WINDOW
и значения у и х. Как ни странно, указатель WINDOW
всегда в списке предшествует экранным координатам, несмотря на то, что, судя по префиксу, у и х должны быть первыми.
Далее для примера приведен полный набор прототипов для семейств функций addch
и printw
.
#include
int addch(const chtype char);
int waddch(WINDOW *window_pointer, const chtype char);
int mvaddch(int y, int x, const chtype char);
int mvwaddch(WINDOW *window_pointer, int y, int x, const chtype char);
int printw(char *format, ...);
int wprintw(WINDOW *window_pointer, char *format, ...);
int mvprintw(int y, int x, char *format, ...);
int mvwprintw(WINDOW *window_pointer, int y, int x, char *format, ...);
У многих других функций, например inch
, также есть варианты оконные и с перемещением курсора.
Следующие команды позволят вам перемещать и перерисовывать окна:
#include
int mvwin(WINDOW *window_to move, int new_y, int new x);
int wrefresh(WINDOW *window_ptr);
int wclear(WINDOW *window_ptr);
int werase(WINDOW *window_ptr);
int touchwin(WINDOW *window_ptr);
int scrollok(WINDOW *window_ptr, bool scroll_flag);
int scroll(WINDOW *window_ptr);
Функция mvwin
перемещает окно по экрану. Поскольку окно целиком должно располагаться в области экрана, функция mvwin
завершится аварийно, если вы попытаетесь переместить окно так, что какая-то его часть выйдет за пределы экрана.
Функции wrefresh
, wclear
и werase
– просто обобщения функций, с которыми вы встречались ранее; они только принимают указатель WINDOW
, поэтому могут ссылаться на конкретное окно, а не на окно stdscr.
Функция touchwin
довольно специальная. Она информирует библиотеку curses о том, что содержимое окна, на которое указывает ее параметр, было изменено. Это означает, что curses всегда будет перерисовывать такое окно при следующем вызове функции wrefresh
, даже если вы на самом деле не меняли содержимое этого окна. Эта функция очень полезна для определения отображаемого окна при наличии нескольких перекрывающихся окон, загромождающих экран.
Две функции scroll
управляют прокруткой окна. Функция scrollok
при передаче логического значения true (обычно ненулевого) включает прокрутку окна. По умолчанию окна не прокручиваются. Функция scroll
просто прокручивает окно на одну строку вверх. В некоторые реализации библиотеки curses входит и функция wsctl
, которая также принимает количество строк для прокрутки, которое может быть и отрицательным числом. Мы вернемся к прокрутке немного позже в этой главе.
А теперь выполните упражнение 6.4.
Упражнение 6.4. Управление множественными окнами
Теперь, зная, как управлять несколькими окнами, вы можете включить эти новые функции в программу multiw1.c. Для краткости проверка ошибок не приводится.
1. Как обычно, вставьте первыми отсортированные объявления:
#include
#include
#include
int main() {
WINDOW *new_window_ptr;
WINDOW *popup_windov_ptr;
int x loop;
int y_loop;
char a_letter = 'a';
initscr();
2. Заполните базовое окно символами, обновляя физический экран, когда заполнен логический экран:
move(5, 5);
printw(«%s», «Testing multiple windows»);
refresh();
for (y_loop = 0; y_loop < LINES – 1; y_loop++) {
for (x_loop = 0; x_loop < COLS – 1; x_loop++) {
mvwaddch(stdscr, y_loop, x_loop, a_letter);
a_letter++;
if (a_letter > 'z') a_letter = 'a';
}
}
/* Обновление экрана */
refresh();
sleep(2);
3. Теперь создайте окно 10×20 и вставьте в него текст перед прорисовкой окна на экране:
new_window_ptr = newwin(10, 20, 5, 5);
mvwprintw(new_window_ptr, 2, 2, «%s», «Hello World»);
mwwprintw(new_window_ptr, 5, 2, «%s»,
«Notice how very long lines wrap inside the window»);
wrefresh(new_window_ptr);
sleep(2);
4. Измените содержимое фонового окна. Когда вы обновите экран, окно, на которое указывает new_window_ptr
, будет затемнено:
a_letter = '0';
for (y_lоор = 0; y_lоор < LINES – 1; y_lоор++) {
for (х_lоор = 0; xloop < COLS – 1; х_lоор++) {
mvwaddch(stdscr, y_loop, х_lоор, a_letter);
a_letter++;
if (a_letter > '9') a_letter = '0';
}
}
refresh();
sleep(2);
5. Если вы выполните вызов для обновления нового окна, ничего не изменится, поскольку вы не изменяли новое окно:
wrefresh(new_window_ptr);
sleep(2);
6. Но если вы сначала воспользуетесь функцией touchwin
и заставите библиотеку curses думать, что окно было изменено, следующий вызов функции wrefresh снова отобразит новое окно на переднем плане.
touchwin(new_window_ptr);
wrefresh(new_window_ptr);
sleep(2);
7. Добавьте еще одно накладывающееся окно с рамкой вокруг него.
popup_window_ptr = newwin(10, 20, 8, 8);
box(popup_window_ptr, '|', '-');
mvwprintw(popup_window_ptr, 5, 2, «%s», «Pop Up Window!»);
wrefresh(popup_window_ptr);
sleep(2);
8. Поиграйте с новыми всплывающими окнами перед их очисткой и удалением.
touchwin(new_window_ptr);
wrefresh(new_window_ptr);
sleep(2);
wclear(new_window_ptr);
wrefresh(new_window_ptr);
sleep(2);
delwin(new_window_ptr);
touchwin(popup_window_ptr);
wrefresh(popup_window_ptr);
sleep(2);
delwin(popup_window_ptr);
touchwin(stdscr);
refresh();
sleep(2);
endwin();
exit(EXIT_SUCCESS);
}
К сожалению, нет возможности продемонстрировать выполнение этого фрагмента в книге, но на рис. 6.4 показан снимок экрана после отображения первого всплывающего окна.
Рис. 6.4
После того как будет изменен фон и появится новое всплывающее окно, вы увидите экран, показанный на рис. 6.5.
Рис. 6.5
Как это работает
После обычной инициализации программа заполняет стандартный экран цифрами, чтобы легче было увидеть новые окна, вставляемые на передний план. Далее показано, как можно наложить на фон новое окно с включенным в него текстом, разбитым на строки в соответствии с шириной окна. Далее вы видите, как с помощью функции touchwin
заставить curses перерисовать окно, даже если в нем ничего не менялось.
Затем перед закрытием curses и завершением программы вставляется второе окно, перекрывающее первое, чтобы показать, как библиотека curses может управлять перекрывающимися окнами.
Как видно из программного кода примера, при обновлении окон следует быть очень внимательным, чтобы они отображались в нужной очередности. Библиотека curses не хранит никаких сведений об иерархии окон, поэтому если вы попросите curses обновить несколько окон, управлять их иерархией придется вам.
Примечание
Для того чтобы библиотека curses отображала окна в нужном порядке, их следует обновлять в этом порядке. Один из способов добиться этого – сохранять все указатели ваших окон в массиве или списке, в которых поддерживается порядок их размещения, соответствующий порядку их появления на экране.