if (menuText[n].m_label != 0) {
menuText[n].m_label = 0;
drawItem (n, 20, Y_TOP + n, 0);
nselected--;
prSelects ();
}
goto go_down;
А. Богатырев, 1992-95 - 366 - Си в UNIX
case '\r': /* Select item */
case '\n':
bold ();
drawTitle (menuText[n].m_text, LINES - 2);
/* last but two line */
normal ();
if (menuText[n].m_label == 0) {
menuText[n].m_label = 1;
drawItem (n, 20, Y_TOP + n, 0);
nselected++;
prSelects ();
}
goto go_down;
default:
goto go_down;
}
}
out:
clearScreen ();
gotoXY (COLS / 3, LINES / 2);
bold ();
printf ("Нажми любую кнопку.");
normal ();
/* замусорить экран */
while (!(c = readkey ())) {
/* случайные точки */
gotoXY (rand () % (COLS - 1), rand () % LINES);
putchar ("@.*"[rand () % 3]); /* выдать символ */
fflush (stdout);
}
standout ();
printf ("Нажата кнопка с кодом 0%o\n", c & 0377);
standend ();
if (c == ESC) {
sleep (2); /* подождать 2 секунды */
goto again;
}
die (0); /* успешно завершиться,
* восстановив режимы драйвера */
}
А. Богатырев, 1992-95 - 367 - Си в UNIX
/* Нарисовать строку меню номер i
* в координатах (x,y) с или без выделения
*/
void drawItem (i, x, y, out) {
gotoXY (x, y);
if (out) {
standout ();
bold ();
}
printf ("%c %s ",
menuText[i].m_label ? '-' : ' ', /* помечено или нет */
menuText[i].m_text /* сама строка */
);
if (out) {
standend ();
normal ();
}
}
/* нарисовать центрированную строку в инверсном изображении */
void drawTitle (title, y) char *title; {
register int n;
int length = strlen (title); /* длина строки */
gotoXY (0, y);
/* clearEOL(); */
standout ();
for (n = 0; n < (COLS - length) / 2; n++)
putchar (' ');
printf ("%s", title); n += length;
/* дорисовать инверсией до конца экрана */
for (; n < COLS - 1; n++)
putchar (' ');
standend ();
}
/* выдать общее число выбранных строк */
void prSelects () {
char buffer[30];
if (nselected == 0) {
gotoXY (0, LINES - 1);
clearEOL ();
}
else {
sprintf (buffer, "Выбрано: %d/%d", nselected, nitems);
drawTitle (buffer, LINES - 1);
}
}
А. Богатырев, 1992-95 - 368 - Си в UNIX
/* Работа с будильником -------------------------- */
#define PAUSE 4
int alarmed; /* флаг будильника */
/* реакция на сигнал "будильник" */
void onalarm (nsig) {
alarmed = 1;
}
/* Прочесть символ с клавиатуры, но не позже чем через PAUSE секунд.
* иначе вернуть код 'пробел'.
*/
int getcharacter () {
int c;
fflush(stdout);
/* заказать реакцию на будильник */
signal (SIGALRM, onalarm);
alarmed = 0; /* сбросить флаг */
/* заказать сигнал "будильник" через PAUSE секунд */
alarm (PAUSE);
/* ждать нажатия кнопки.
* Этот оператор завершится либо при нажатии кнопки,
* либо при получении сигнала.
*/
c = getchar ();
/* проверяем флаг */
if (!alarmed) { /* был нажат символ */
alarm (0); /* отменить заказ будильника */
return c;
}
/* был получен сигнал "будильник" */
return ' '; /* продвинуть выбранную строку вниз */
}
/* ---- NDELAY read ----------------------------- */
/* Вернуть 0 если на клавиатуре ничего не нажато,
* иначе вернуть нажатую кнопку
*/
char readkey () {
char c;
int nread;
nread = read (fdtty, &c, 1);
/* обычный read() дожидался бы нажатия кнопки.
* O_NDELAY позволяет не ждать, но вернуть "прочитано 0 символов".
*/
return (nread == 0) ? 0 : c;
}
А. Богатырев, 1992-95 - 369 - Си в UNIX
/* -------- Работа со временем ------------------------ */
void printTime () {
time_t t; /* текущее время */
struct tm *tm;
extern struct tm *localtime ();
char tmbuf[30];
static char *week[7] = { "Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб" };
static char *month[12] = { "Янв", "Фев", "Мар", "Апр", "Май", "Июн",
"Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" };
time (&t); /* узнать текущее время */
tm = localtime (&t); /* разложить его на компоненты */
sprintf (tmbuf, "%2s %02d:%02d:%02d %02d-%3s-%d",
week[tm -> tm_wday], /* день недели (0..6) */
tm -> tm_hour, /* часы (0..23) */
tm -> tm_min , /* минуты (0..59) */
tm -> tm_sec , /* секунды (0..59) */
tm -> tm_mday, /* число месяца (1..31) */
month[tm -> tm_mon], /* месяц (0..11) */
tm -> tm_year + 1900 /* год */
);
gotoXY (COLS / 2, TIMELINE);
clearEOL ();
gotoXY (COLS - strlen (tmbuf) - 1, TIMELINE);
bold ();
printf ("%s", tmbuf);
normal ();
}
8.14. Напишите программу, выдающую файл на экран порциями по 20 строк и ожидающую
нажатия клавиши. Усложнения:
a) добавить клавишу для возврата к началу файла.
b) используя библиотеку termcap, очищать экран перед выдачей очередной порции
текста.
c) напишите эту программу, используя библиотеку curses.
d) используя curses, напишите программу параллельного просмотра 2-х файлов в 2-х
неперекрывающихся окнах.
e) то же в перекрывающихся окнах.
8.15. Напишите функции включения и выключения режима эхо-отображения набираемых на
клавиатуре символов (ECHO).
8.16. То же про "режим немедленного ввода" (CBREAK). В обычном режиме строка, наб-
ранная на клавиатуре, сначала попадает в некоторый буфер в драйвере терминала[*].
____________________
[*] Такие буфера носят название "character lists" - clist. Существуют "сырой" (raw)
clist, в который попадают ВСЕ символы, вводимые с клавиатуры; и "канонический" clist,
в котором хранится отредактированная строка - обработаны забой, отмена строки. Сами
специальные символы (редактирования и генерации сигналов) в каноническую очередь не
попадают (в режиме ICANON).
А. Богатырев, 1992-95 - 370 - Си в UNIX
"Сырая" "Каноническая"
клавиатура--->ОчередьВвода--*-->ОчередьВвода-->read
| файл-устройство
драйвер терминала V эхо /dev/tty??
|
экран<---ОчередьВывода---<--*--<-----------<---write
Этот буфер используется для предчтения - вы можете набирать текст на клавиатуре еще
до того, как программа запросит его read-ом: этот набранный текст сохранится в буфере
и при поступлении запроса будет выдан из буфера. Также, в каноническом режиме ICANON,
буфер ввода используется для редактирования введенной строки: забой отменяет послед-
ний набранный символ, CTRL/U отменяет всю набранную строку; а также он используется
для выполнения некоторых преобразований символов на вводе и выводе[**].
Введенная строка попадает в программу (которая запросила данные с клавиатуры при
помощи read, gets, putchar) только после того, как вы нажмете кнопку с кодом
'\n'. До этого вводимые символы накапливаются в буфере, но в программу не передаются
- программа тем временем "спит" в вызове read. Как только будет нажат символ '\n',
он сам поступит в буфер, а программа будет разбужена и сможет наконец прочесть из
буфера ввода набранный текст.
Для меню, редакторов и других "экранных" программ этот режим неудобен: пришлось
бы слишком часто нажимать . В режиме CBREAK нажатая буква немедленно попадает
в вашу программу (без ожидания нажатия '\n'). В данном случае буфер драйвера исполь-
зуется только для предчтения, но не для редактирования вводимого текста. Редактиро-
вание возлагается на вас - предусмотрите его в своей программе сами!
Заметьте, что код кнопки ("конец ввода") - '\n' - не только "проталки-
вает" текст в программу, но и сам попадает в буфер драйвера, а затем в вашу прог-
рамму. Не забывайте его как-то обрабатывать.
В MS DOS функция чтения кнопки в режиме ~ECHO+CBREAK называется getch(). В UNIX
аналогично ей будет работать обычный getchar(), если перед его использованием устано-
вить нужные режимы драйвера tty вызовом ioctl. По окончании программы режим драйвера
надо восстановить (за вас это никто не сделает). Также следует восстанавливать режим
драйвера при аварийном завершении программы (по любому сигналу[*][*]).
Очереди ввода и вывода используются также для синхронизации скорости работы
программы (скажем, скорости наполнения буфера вывода символами, поступающими из прог-
раммы через вызовы write) и скорости работы устройства (с которой драйвер выбирает
символы с другого конца очереди и выдает их на экран); а также для преобразований
символов на вводе и выводе. Пример управления всеми режимами есть в приложении.
8.17. Функциональные клавиши большинства дисплеев посылают в линию не один, а нес-
колько символов. Например на терминалах, работающих в системе команд стандарта ANSI,
кнопки со стрелками посылают такие последовательности:
стрелка вверх "\033[A" кнопка Home "\033[H"
стрелка вниз "\033[B" кнопка End "\033[F"
стрелка вправо "\033[C" кнопка PgUp "\033[I"
стрелка влево "\033[D" кнопка PgDn "\033[G"
(поскольку первым символом управляющих последовательностей обычно является символ
'\033' (escape), то их называют еще escape-последовательностями). Нам же в программе
удобно воспринимать такую последовательность как единственный код с целым значением
большим 0xFF. Склейка последовательностей символов, поступающих от функциональных
клавиш, в такой внутренний код - также задача экранной библиотеки (учет системы
команд дисплея на вводе).
____________________
[**] Режимы преобразований, символы редактирования, и.т.п. управляются системным вы-
зовом ioctl. Большой пример на эту тему есть в приложении.
[*][*] Если ваша программа завершилась аварийно и моды терминала остались в "странном"
состоянии, то привести терминал в чувство можно командой stty sane
А. Богатырев, 1992-95 - 371 - Си в UNIX
Самым интересным является то, что одиночный символ '\033' тоже может прийти с
клавиатуры - его посылает клавиша Esc. Поэтому если мы строим распознаватель клавиш,