• Компиляция с библиотекой curses
  • Терминология библиотеки curses и общие представления
  • Экран
  • Вывод на экран
  • Считывание с экрана
  • Очистка экрана
  • Перемещение курсора
  • Атрибуты символов
  • Клавиатура
  • Режимы клавиатуры
  • Клавиатурный ввод
  • Окна
  • Структура WINDOW
  • Универсальные функции
  • Перемещение и обновление окна
  • Оптимизация обновлений экрана
  • Вложенные окна
  • Дополнительная клавиатура
  • Применение цвета
  • Переопределение цветов
  • Панели 
  • Приложение, управляющее коллекцией компакт-дисков
  • Начало нового приложения для работы с коллекцией компакт-дисков
  • Взгляд на функцию main
  • Формирование меню
  • Управление базой данных
  • Запросы к базе данных компакт-дисков
  • Резюме 
  • Глава 6

    Управление текстовыми экранами с помощью библиотеки curses

    В главе 5 вы узнали, как улучшить управление вводом символов и как обеспечить вывод символов способом, не зависящим от особенностей конкретного терминала. Проблема использования общего терминального интерфейса (GTI или termios) и манипулирование escape-последовательностями с помощью tparm и родственных функций заключается в необходимости применения большого объема программного кода низкого уровня. Для многих программ предпочтительней интерфейс высокого уровня. Мы хотели бы иметь возможность просто рисовать на экране и применять библиотеку функций для автоматического отслеживания аппаратных характеристик терминала.

    В этой главе вы узнаете именно о такой библиотеке, называемой curses. Стандарт curses очень важен как компромисс между простыми "строковыми" программами и полностью графическими (которые обычно труднее программировать) программами в графической оболочке X Window System, такими как GTK+/GNOME и Qt/KDE, В ОС Linux есть библиотека svgatib (Super VGA Library, библиотека низкоуровневой графики), но она не является стандартной библиотекой UNIX, поэтому обычно не доступна в других UNIX-подобных операционных системах.

    Библиотека curses применяется во многих полноэкранных приложениях как довольно легкий и аппаратно-независимый способ разработки полноэкранных, хотя и символьных программ. Такие программы почти всегда легче писать с помощью библиотеки curses, чем непосредственно применять escape-последовательности. Эта библиотека также может управлять клавиатурой, обеспечивая легкий в использовании, не блокирующий режим ввода символов.

    Вы можете столкнуться с тем, что несколько примеров из этой главы не всегда будут отображаться на простой консоли Linux так, как вы ожидали. Бывают случаи, когда сочетание библиотеки curses и определения консоли терминала получается немного не согласованным и приводит в результате к несколько странным компоновкам при использовании curses. Но если для отображения вывода применить графическую оболочку X Window System и окно xterm, все встанет на свои места.

    В этой главе обсуждаются следующие темы:

    □ применение библиотеки curses:

    □ основные идеи curses;

    □ управление базовыми вводом и выводом;

    □ использование множественных окон;

    □ применение режима дополнительной клавиатуры (keypad mode);

    □ добавление цвета.

    Мы закончим главу переработкой на языке С программы, управляющей коллекцией компакт-дисков, подытожив все, чему вы научились к этому моменту.

    Компиляция с библиотекой curses

    Библиотека curses получила свое название благодаря способности оптимизировать перемещение курсора и минимизировать необходимые обновления экрана, а следовательно, уменьшить количество символов, нуждающихся в пересылке на текстовый терминал. Несмотря на то, что сейчас количество символов вывода гораздо менее важно, чем во времена неинтеллектуальных терминалов и низкоскоростных модемов, библиотека curses выжила как полезное дополнение к набору инструментов программиста.

    Поскольку curses — это библиотека, для ее применения необходимо включить в программу заголовочный файл, объявления функций и макросы из соответствующей системной библиотеки. Существует несколько разных реализаций библиотеки curses. Первоначальная версия появилась в системе BSD UNIX и затем была включена в разновидности UNIX стиля System V прежде, чем была стандартизована группой X/Open. Система Linux использует вариант ncurses ("new curses") — свободно распространяемую версию System V Release 4.0 curses, разработанную для Linux. Эта реализация хорошо переносится на другие версии UNIX, хотя и содержит несколько непереносимых дополнительных функций. Есть даже версии библиотеки для MS-DOS и Windows. Если вы увидите, что библиотека curses, поставляемая с вашей версией системы UNIX, не поддерживает некоторые функции, попытайтесь получить копию альтернативной библиотеки ncurses. Обычно пользователи ОС Linux обнаруживают уже установленную библиотеку ncurses или, по крайней мере, ее компоненты, необходимые для выполнения программ на базе библиотеки curses. Если инструментальные библиотеки для нее заранее не установлены в вашем дистрибутиве (нет файла curses.h или файла библиотеки curses для редактирования связей), для большинства основных дистрибутивов их всегда можно найти в виде стандартного пакета с именем наподобие ibncurses5-dev.

    Примечание

    В стандарте X/Open определены два варианта curses: базовый и расширенный. Расширенный вариант библиотеки curses содержит разнородную кучу дополнительных функций, включая ряд функций для обработки многостолбцовых символов и подпрограммы управления цветом. Кроме приведенного далее в этой главе описания способов управления цветом, мы будем в основном привязаны к функциям базовой версии библиотеки.

    При компиляции программ, использующих curses, следует подключить заголовочный файл curses.h и на этапе редактирования связей саму библиотеку с помощью аргумента -lcurses. Во многих системах Linux вы можете применять просто библиотеку curses, а потом обнаружить, что на самом деле вы пользуетесь усовершенствованной, более новой реализацией ncurses.

    Для того чтобы проверить, как установлена библиотека curses в вашей системе, выполните команду

    ls -l /usr/include/*curses.h

    для просмотра заголовочных файлов и

    ls -l /usr/lib/lib*curses*

    для проверки библиотечных файлов. Если вы увидите, что curses.h и ncurses.h — прямо связанные файлы, и существует файл библиотеки ncurses, то у вас есть возможность компилировать файлы из этой главы с помощью следующей команды:

    $ gcc program. с -о program -lcurses

    Если установка curses в вашей системе не использует автоматически ncurses, вы сможете явно задать использование ncurses, включив файл ncurses.h вместо файла curses.h и выполнив следующую команду:

    $ gcc -I/usr/include/ncurses program.с -о program -lncurses

    в которой опция

    -I
    задает каталог для поиска заголовочного файла.

    Примечание

    В загружаемом коде сценария Makefile предполагается, что в установленной у вас системе по умолчанию применяется библиотека curses, поэтому вы должны заменить его или откомпилировать вручную, если в вашей системе это не так.

    Если вы точно не знаете, как установлена библиотека curses в вашей системе, обратитесь к страницам интерактивного справочного руководства, посвященным ncurses, или просмотрите другую интерактивную документацию; обычное место ее хранения — каталог /usr/share/doc/, в котором вы найдете каталог curses или ncurses часто с присоединенным в конце номером версии.

    Терминология библиотеки curses и общие представления

    Подпрограммы curses действуют на экранах, в окнах и вложенных окнах или подокнах. Экран — это устройство (обычно экран терминала, но может быть и экран эмулятора терминала xterm), на который вы записываете информацию. Он занимает все доступное пространство дисплея этого устройства, Если экран — окно терминала в графическом окне, то он представляет собой совокупность всех доступных символьных позиций в окне терминала. Всегда существует, по крайней мере, одно окно curses с именем

    stdscr
    , совпадающее по размеру с физическим экраном. Вы можете создавать дополнительные окна с размером, меньшим, чем размер экрана. Окна могут накладываться друг на друга и иметь много вложенных окон, но каждое из них всегда должно находиться внутри родительского окна.

    Библиотека curses поддерживает две структуры данных, действующие как отображение экрана терминала:

    stdscr
    и
    curscr
    . Структура
    stdscr
    , наиболее важная из двух, обновляется, когда функции curses формируют вывод. Структура данных
    stdscr
    — "стандартный экран". Она действует во многом так же, как стандартный вывод stdout из библиотеки stdio. Эта структура — стандартное окно вывода в программах, использующих библиотеку curses. Структура
    curscr
    похожа на
    stdscr
    , но хранит внешний вид отображаемого в текущий момент экрана. Вывод, записанный в структуру
    stdscr
    , не появляется на экране до тех пор, пока программа не вызовет функцию
    refresh
    , в которой библиотека curses сравнивает содержимое
    stdscr
    (как должен выглядеть экран) со второй структурой
    curscr
    (как выглядит экран в данный момент). Затем
    curses
    использует различия между этими двумя структурами для обновления экрана.

    Некоторым программам с использованием curses нужно знать, что библиотека поддерживает структуру

    stdscr
    , которая применяется в нескольких функциях curses как параметр. Однако действительная структура
    stdscr
    реализуется по-разному, и к ней никогда не следует обращаться напрямую. У программ с использованием curses практически нет нужды в применении структуры
    curscr
    .

    Таким образом, процесс вывода символов в программе с применением curses выглядит следующим образом:

    1. Используется функция библиотеки curses для обновления логического экрана.

    2. Запрашивается у библиотеки curses обновление физического экрана с помощью функции

    refresh
    .

    Преимущество двухшагового подхода помимо большей легкости при программировании — очень эффективный вариант обновления экрана curses. Это может быть не столь важно для экрана консоли, но становится существенным показателем, если программа выполняется через медленное сетевое соединение.

    Программа с использованием curses выполнит множество вызовов функций вывода на логический экран, возможно, перемещая курсор по всему экрану для того, чтобы достичь нужной позиции вывода текста или рисования линий и рамок. На каком-то этапе пользователю потребуется увидеть весь этот вывод. Когда это произойдет (обычно во время вызова функции

    refresh
    ), библиотека curses найдет оптимальный способ формирования физического экрана, соответствующего логическому. Применяя нужные характеристики терминала и оптимизируя перемещения курсора, curses часто обновляет экран, выводя гораздо меньше символов по сравнению со всеми операциями записи на экран, если бы они выполнялись немедленно.

    Макет логического экрана — это символьный массив, упорядоченный по строкам и столбцам, с начальной позицией экрана (0, 0) в левом верхнем углу (рис. 6.1).

    Рис. 6.1


    Во всех функциях библиотеки curses применяются координаты со значением у (строки) перед значением х (столбцы). Каждая позиция хранит не только символ, расположенный в этом месте экрана, но и его атрибуты. Атрибуты, которые можно отобразить, зависят от физических характеристик терминала, но, как правило, они включают жирное начертание и подчеркивание символа. На консолях Linux вам также доступны негативное изображение и цвет, о которых речь пойдет далее в этой главе.

    Поскольку библиотека curses нуждается в создании и удалении некоторых временных структур данных, все программы с использованием curses должны инициализировать библиотеку перед применением и затем разрешить ей восстановить первоначальные установки после ее применения. Делается это с помощью вызовов пары функций:

    initscr
    и
    endwin
    (упражнение 6.1).

    Упражнение 6.1. Программа с использованием curses, выводящая приветствие

    В этом примере вы напишите очень простую использующую curses программу screen1.c, чтобы показать эти и другие базовые функции в действии. Далее будут описаны их прототипы.

    1. Вставьте заголовочный файл curses.h и в функцию

    main
    , включите вызовы для инициализации и возврата в исходное состояние библиотеки curses:

    #include <unistd.h>

    #include <stdlib.h>

    #include <curses.h>


    int main() {

     initscr();

     ...

     endwin();

     exit(EXIT_SUCCESS);

    }

    2. Внутрь поместите код для перемещения курсора в точку (5, 15) на логическом экране, выведите приветствие "Hello World" и обновите реальный экран. В заключение примените вызов

    sleep(2
    ) для того, чтобы приостановить выполнение программы на две секунды и просмотреть вывод на экран перед ее завершением:

    move(5, 15);

    printw("%s", "Hello World");

    refresh();

    sleep(2);

    Пока программа выполняется, вы видите фразу "Hello World" в левом верхнем квадранте пустого экрана (рис. 6.2).

    Рис. 6.2


    Как это работает

    Эта программа инициализирует библиотеку curses, перемещает курсор в заданную точку экрана и отображает некоторый текст. После короткой паузы она закрывает библиотеку и завершается.

    Экран

    Как: вы уже видели, все программы с использованием curses должны начинаться с вызова функции

    initscr
    и заканчиваться вызовом функции
    endwin
    . Далее приведены их описания из заголовочного файла.

    #include <curses.h>

    WINDOW *initscr(void);

    int endwin(void);

    Функция

    initscr
    должна вызываться только один раз в каждой программе. В случае успешного завершения она возвращает указатель на структуру
    stdscr
    . Если функция заканчивается аварийно, она просто выводит диагностическое сообщение об ошибке и вызывает завершение программы.

    Функция

    endwin
    возвращает константу OK в случае успешного завершения и err в случае неудачи. Вы можете вызвать ее для того, чтобы покинуть curses, а позже возобновить функционирование библиотеки curses, вызвав
    clearok(stdscr, 1)
    и
    refresh
    . Это позволит библиотеке совершенно забыть, как выглядит физический экран, и заставит ее выполнить полное обновление экрана.

    Вывод на экран

    Для обновления экрана предоставляется несколько базовых функций.

    #include <curses.h>

    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 <curses.h>

    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 <curses.h>

    int erase (void);

    int clear(void);

    int clrtobot(void);

    int clrtoeol(void);

    Функция

    erase
    записывает пробелы во все позиции экрана. Функция
    clear
    , как и
    erase
    , очищает экран, но вызывает перерисовку экрана с помощью внутреннего вызова низкоуровневой функции clear
    o
    k, которая выполняет последовательность очистки экрана и новое отображение экрана при следующем вызове
    refresh
    .

    Функция

    clear
    обычно применяет команду терминала, которая очищает весь экран, а не пытается стереть текущие непробельные символы во всех точках экрана. Это делает функцию
    clear
    надежным средством очистки экрана. Сочетание функции
    clear
    с последующей функцией
    refresh
    может обеспечить удобную команду перерисовки экрана в том случае, когда изображение на экране беспорядочно или испорчено каким-либо образом.

    Функция

    clrtobot
    очищает экран, начиная с текущей позиции курсора и далее до конца экрана, а функция
    clrtoeol
    очищает экран, начиная с текущей позиции курсора до конца строки, в которой находится курсор.

    Перемещение курсора

    Для перемещения курсора применяется единственная функция с дополнительной командой, управляющей положением курсора после обновления экрана.

    #include <curses.h>

    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 <curses.h>

    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 <stdio.h>

    #include <unistd.h>

    #include <stdlib.h>

    #include <string.h>

    #include <curses.h>


    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 <curses.h>

    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). Это означает построчную обработку, т.е. ввод становится доступен программе после нажатия пользователем клавиши <Enter> (или <Return> на некоторых клавиатурах). Специальные символы на клавиатуре включены, поэтому набор соответствующих клавиатурных последовательностей может сгенерировать сигнал в программе. Управление потоком, если терминал запускается с терминала, также включено. Вызывая функцию
    cbreak
    , программа может установить режим ввода
    cbreak
    , в котором символы становятся доступными программе сразу после их набора, а не помещаются в буфер и передаются программе только после нажатия клавиши <Enter>. Как и в режиме с обработкой, специальные символы клавиатуры действуют, а простые клавиши, например <Backspace>, передаются для обработки непосредственно в программу, поэтому если вы хотите, чтобы нажатие клавиши <Backspace> приводило к привычным действиям, то вы должны запрограммировать их самостоятельно.

    Вызов функции

    raw
    отключает обработку специальных символов, поэтому становится невозможной генерация сигналов или управление потоком с помощью набранных на клавиатуре специальных символьных последовательностей. Вызов функции
    nocbreak
    возвращает режим ввода в режим с обработкой символов, но режим обработки специальных символов не изменяет; вызов
    noraw
    восстанавливает и режим с обработкой, и обработку специальных символов.

    Клавиатурный ввод

    Чтение с клавиатуры — очень простая операция. К основным функциям чтения относятся следующие:

    #include <curses.h>

    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 <unistd.h>

    #include <stdlib.h>

    #include <curses.h>

    #include <string.h>

    #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, '\0', 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 <curses.h>

    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 <curses.h>

    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 <curses.h>

    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 <unistd.h>

    #include <stdlib.h>

    #include <curses.h>


    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 отображала окна в нужном порядке, их следует обновлять в этом порядке. Один из способов добиться этого — сохранять все указатели ваших окон в массиве или списке, в которых поддерживается порядок их размещения, соответствующий порядку их появления на экране.

    Оптимизация обновлений экрана

    Как вы видели в примере из упражнения 6.4, обновление множественных окон требует некоторой ловкости, но не слишком обременительно. Но может возникнуть более серьезная проблема, если нуждающийся в обновлении терминал подключен через медленное сетевое соединение. К счастью, в наши дни с ней сталкиваются очень редко, но ее обработка настолько легка, что мы рассмотрим ее просто для полноты картины.

    Задача состоит в минимизации количества символов, прорисовываемых на экране, поскольку при наличии медленных линий связи рисование на экране может оказаться утомительно долгим. Библиотека curses предлагает специальный метод обновления экрана с помощью пары функций

    wnoutrefresh
    и
    doupdate
    :

    #include <curses.h>

    int wnoutrefresh(WINDOW *window_ptr);

    int doupdate(void);

    Функция

    wnoutrefresh
    определяет, какие символы необходимо отправить на экран, но не отправляет их на самом деле. Функция
    doupdate
    действительно отправляет изменения на терминал. Если вы просто вызовите
    wnoutrefresh
    , а за ней тут же функцию
    doupdate
    , эффект будет такой же, как при вызове функции
    wrefresh
    . Однако если вы хотите перерисовать ряд окон, то можете вызвать функцию
    wnoutrefresh
    для каждого окна (конечно, в нужном порядке) и затем вызвать
    doupdate
    только после последнего вызова
    wnoutrefresh
    . Это позволит библиотеке curses выполнить расчеты, связанные с обновлением экрана, по очереди для каждого окна и только после этого вывести обновленный экран. Такой подход почти всегда позволяет curses минимизировать количество символов, нуждающихся в пересылке.

    Вложенные окна

    Теперь, когда мы рассмотрели множественные окна, остановимся на специальном случае множественных окон, называемом вложенными окнами или подокнами. Создаются и уничтожаются вложенные окна с помощью следующих вызовов:

    #include <curses.h>

    WINDOW *subwin(WINDOW *parent, int num_of_lines, int num_of_cols,

     int start_y, int start_x);

    int delwin(WINDOW *window_to_delete);

    У функции

    subwin
    почти такой же список параметров, как у функции
    newwin
    , и удаляются вложенные окна так же, как другие окна с помощью вызова
    delwin
    . Для записи во вложенные окна, как и в новые окна, вы можете применять ряд функций
    mvw
    . На самом деле большую часть времени вложенные окна ведут себя почти так же, как новые окна, но есть одно важное отличие: подокна самостоятельно не хранят отдельный набор экранных символов; они используют ту же область хранения символов, что и родительское окно, заданное при создании вложенного окна. Это означает, что любые изменения, сделанные во вложенном окне, вносятся и в лежащее в основании родительское окно, поэтому, когда подокно удаляется, экран не меняется.

    На первый взгляд вложенные окна кажутся бесполезным экспериментом. Почему не изменять просто родительское окно? Основная сфера их применения — предоставление простого способа прокрутки другого окна. Потребность в прокрутке небольшой области экрана удивительно часто возникает при написании программ с использованием curses. Создав вложенное окно и прокручивая его, вы добьетесь желаемого результата.

    Примечание

    Одно ограничение, накладываемое на применение вложенных окон, заключается в необходимости перед обновлением экрана вызвать в приложении функцию

    touchwin
    для родительского окна.

    Выполните упражнение 6.5.

    Упражнение 6.5. Вложенные окна

    Теперь, когда вы познакомились с новыми функциями, этот короткий пример покажет, как они действуют и чем отличаются от функций окна, применявшихся ранее.

    1. Начальная секция кода программы subscl.c инициализирует отображение базового окна с некоторым текстом:

    #include <unistd.h>

    #include <stdlib.h>

    #include <curses.h>


    int main() {

     WINDOW *sub_window_ptr;

     int x_loop;

     int y_loop;

     int counter;

     char a_letter = '1';

     initscr();

     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 > '9') a_letter = '1';

      }

     }

    2. Теперь создайте новое подокно с прокруткой. Как рекомендовалось, вам следует перед обновлением экрана "коснуться" родительского окна:

     ub_window_ptr = subwin(stdscr, 10, 20, 10, 10);

     scrollok(sub_window_ptr, 1);

     touchwin(stdscr);

     refresh();

     sleep(1);

    3. Сотрите содержимое вложенного окна, выведите в нем текст и обновите его. Прокрутка текста обеспечивается циклом:

     werase(sub_window_ptr);

     mvwprintw(sub_window_ptr, 2, 0, "%s", "This window will now scroll");

     wrefresh(sub_window_ptr);

     sleep(1);

     for (counter = 1; counter < 10; counter++) {

      wprintw(sub_window_ptr, "%s", "This text is both wrapping and \

       scrolling.");

      wrefresh(sub_window_ptr);

      sleep(1);

     }

    4. Завершив цикл, удалите вложенное окно и обновите основной экран:

     delwin(sub_window_ptr);

     touchwin(stdscr);

     refresh();

     sleep(1);

     endwin();

     exit(EXIT_SUCCESS);

    }

    К концу программы вы увидите вывод, показанный на рис. 6.6.

    Рис. 6.6 


    Как это работает

    После присвоения указателю

    sub_window_ptr
    результата вызова
    subwin
    вы включаете прокрутку вложенного окна. Даже после удаления вложенного окна и обновления базового окна (
    strdcr
    ) текст на экране не меняется, поскольку вложенное окно на самом деле откорректировало символьные данные экрана
    strdcr
    .

    Дополнительная клавиатура

    Вы уже познакомились с некоторыми средствами библиотеки curses для обработки клавиатурного ввода. У многих клавиатур, как минимум, есть клавиши управления курсором и функциональные клавиши. Кроме того, у многих клавиатур есть дополнительная клавиатура и другие клавиши, например, <Insert> и <Home>.

    Для большинства терминалов расшифровка этих клавиш — серьезная проблема, потому что они посылают строку символов, начинающуюся с escape-символа. Дело не только в том, что приложению трудно отличить одиночное нажатие клавиши <Esc> от строки символов, появившейся в результате нажатия функциональной клавиши, оно еще должно справляться с терминалами разных типов, применяющими разные управляющие последовательности для одних и тех же логических клавиш.

    К счастью, библиотека curses предоставляет элегантное решение для управления функциональными клавишами. Обычно в структуре

    terminfo
    для каждого терминала хранится последовательность, отправляемая каждой функциональной клавишей, и во включенном в программу файле curses.h для логических клавиш есть набор определений, начинающихся с префикса
    KEY_
    .

    Когда curses стартует, преобразование последовательностей в логические клавиши отключено, и его следует включить вызовом функции

    keypad
    . Если вызов успешен, функция вернет
    OK
    , в противном случае
    ERR
    .

    #include <curses.h>

    int keypad(WINDOW *window_ptr, bool keypad_on);

    Когда режим дополнительной клавиатуры включен с помощью вызова функции

    keypad
    с параметром
    keypad_on
    , равным
    true
    , библиотека curses принимает на себя обработку клавиатурных последовательностей, так что чтение с клавиатуры может вернуть не только нажатую клавишу, но и одно из определений вида
    KEY_
    для логических клавиш.

    Отметьте три незначительных ограничения, налагаемых при использовании режима дополнительной клавиатуры.

    □ Распознавание escape-последовательностей требует разного времени, и многие сетевые протоколы сгруппируют символы в пакеты (что приведет к неверному распознаванию escape-последовательностей) или разделят их (что приведет к распознаванию последовательностей функциональных клавиш, как клавиши <Esc> и отдельных символов). Такое поведение чаще всего наблюдается в региональных сетях (Wide-Area Network, WAN) и других медленных линиях связи. Единственный выход — попытаться запрограммировать терминалы так, чтобы они отправляли единичные уникальные символы в ответ на нажатие каждой функциональной клавиши, используемой вами, хотя это ограничит количество управляющих символов.

    □ Для того чтобы библиотека curses могла отличить нажатие клавиши <Esc> от клавиатурной последовательности, начинающейся с символа

    Esc
    , ей требуется ожидание в течение короткого промежутка времени. Иногда при включенном режиме дополнительной клавиатуры можно заметить легкую задержку при обработке клавиши <Esc>.

    □ Библиотека curses не может обрабатывать неуникальные escape-последовательности. Если у вашего терминала есть две разные клавиши, отправляющие одну и ту же последовательность, библиотека просто не будет ее обрабатывать, поскольку не может решить, какую логическую клавишу следует вернуть.

    Выполните упражнение 6.6.

    Упражнение 6.6. Применение дополнительной клавиатуры

    Далее приведена короткая программа keypad.c, демонстрирующая применение режима дополнительной клавиатуры. После запуска программы нажмите клавишу <Esc> и отметьте незначительную задержку, в течение которой программа пытается понять: Esc — это начало управляющей последовательности или просто нажатие одной клавиши,

    1. Инициализировав программу и библиотеку curses, включите режим дополнительной клавиатуры:

    #include <unistd.h>

    #include <stdlib.h>

    #include <curses.h>

    #define LOCAL_ESCAPE_KEY 27


    int main() {

     int key;

     initscr();

     crmode();

     keypad(stdscr, TRUE);

    2. Отключите отображение символов, чтобы помешать перемещению курсора при нажатии клавиш управления курсором. Экран очищается, и выводится некоторый текст. Программа ждет нажатия клавиши и до тех пор, пока не нажата клавиша <Q> или не возникла ошибка. Символ нажатой клавиши выводится на экран. Если нажатые клавиши соответствуют одной из последовательностей для дополнительной клавиатуры терминала, вместо символа выводится эта последовательность.

     noecho();

     clear();

     mvprintw(5, 5, "Key pad demonstration. Press 'q' to quit");

     move(7, 5);

     refresh();

     key = getch();

     while (key != ERR && key i= 'q') {

      move(7, 5);

      clrtoeol();

      if ((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z')) {

       printw("Key was%c", (char)key);

      } else {

       switch(key) {

       case LOCAL_ESCAPE_KEY:

        printw("%s", "Escape key");

        break;

       case KEY_END:

        printw("%s", "END key");

        break;

       case KEY_BEG:

        printw("%s", "BEGINNING key");

        break;

       case KEY_RIGHT:

        printw("%s", "RIGHT key");

        break;

       case KEY_LEFT:

        printw("%s", "LEFT key");

        break;

       case KEY_UP:

        printw("%s", "UP key");

        break;

       case KEY_DOWN:

        printw("%s", "DOWN key");

        break;

       default:

        printw("Unmatched — %d", key);

        break;

       } /* switch */

      } /* else */

      refresh();

      key = getch();

     } /* while */

     endwin();

     exit(EXIT_SUCCESS);

    }

    Как это работает

    Включив режим дополнительной клавиатуры, вы увидите, как можно распознать различные функциональные клавиши на дополнительной клавиатуре, генерирующие escape-последовательности. Вы, возможно, сумеете заметить, что распознавание клавиши <Esc> немного медленнее, чем других клавиш.

    Применение цвета

    В прошлом очень немногие терминалы ввода/вывода поддерживали цвета, поэтому у большей части самых старых версий библиотеки curses не было поддержки цветов. Цвета появились в библиотеке ncurses и других современных реализациях curses. К сожалению, на "неинтеллектуальный экран", первооснову библиотеки curses, повлиял API, и curses используют цвета очень ограниченным способом, отражающим слабые характеристики старых цветных терминалов.

    Каждая символьная ячейка на экране может быть записана одним цветом из набора разных цветов на фоне одного цвета из набора различных цветов фона. Например, можно вывести зеленый текст на красном фоне.

    Цветовая поддержка в библиотеке curses немного необычна, в том смысле, что цвет символа не определяется независимо от цвета фона. Вы должны задать цвет переднего плана и фона как пару, именуемую, что неудивительно, цветовой парой.

    Прежде чем применять цвета в curses, нужно убедиться в том, что текущий терминал поддерживает цвета, и инициализировать подпрограммы управления цветом библиотеки curses. Для этого примените две функции:

    has_colors
    и
    start_color
    .

    #include <curses.h>

    bool has_colors(void);

    int start_color(void);

    Функция

    has_colors
    возвращает
    true
    , если терминал поддерживает цвета. Далее следует вызвать функцию
    start_color
    , которая вернет
    OK
    , если цветовая поддержка успешно инициализирована. После вызова
    start_color
    и инициализации цветов переменная
    COLOR_PAIRS
    принимает значение, равное максимальному количеству цветовых пар, которые может поддерживать терминал. Переменная
    COLORS
    определяет максимальное число доступных цветов, которых, как правило, восемь. Внутри компьютера числа от 0 до 63 действуют как уникальные ID для каждого из доступных цветов.

    Прежде чем применять цвета как атрибуты, вы должны инициализировать цветовые пары, которые хотите использовать. Делается это с помощью функции

    init_pair
    . Обратиться к атрибутам, задающим цвет, можно с помощью функции
    COLOR_PAIR
    .

    #include <curses.h>

    int init_pair(short pair_number, short foreground, short background);

    int COLOR_PAIR(int pair_number);

    int pair_content(short pair_number, short *foreground, short *background);

    В файле curses.h обычно определены некоторые базовые цвета, начинающиеся с префикса

    COLOR_
    . Дополнительная функция
    pair_content
    позволяет извлечь сведения о ранее определенной цветовой паре.

    Для определения цветовой пары номер 1, как красный на зеленом, примените следующую строку:

    init_pair(1, COLOR_RED, COLOR_GREEN);

    Затем вы сможете получить доступ к этой цветовой паре, применив функцию

    COLOR_PAIR
    следующим образом:

    wattron(window_ptr, COLOR_PAIR(1));

    Она установит вывод в будущем на экран красных символов на зеленом фоне.

    Поскольку

    COLOR_PAIR
    — это атрибут, вы можете комбинировать его с другими атрибутами. На ПК часто можно добиться на экране цветов повышенной яркости, объединив с помощью поразрядной операции
    OR
    атрибут
    COLOR_PAIR
    с дополнительным атрибутом
    A_BOLD
    :

    wattron(window_ptr, COLOR_PAIR(1) | A_BOLD);

    Давайте проверим эти функции в примере color.c (упражнение 6.7).

    Упражнение 6.7. Цвета

    1. Сначала проверьте, поддерживает ли цвета терминал, используемый программой. Если да, то инициализируйте отображение цветов:

    #include <unistd.h>

    #include <stdlib.h>

    #include <stdio.h>

    #include <curses.h>


    int main() {

     int i;

     initscr();

     if (!has_colors()) {

      endwin();

      fprintf(stderr, "Error — no color support on this terminal\n");

      exit(1);

     }

     if (start_color() != OK) {

      endwin();

      fprintf(stderr, "Error — could not initialize colors\n");

      exit(2);

     }

    2. Теперь можно вывести допустимое количество цветов и цветовые пары. Создайте семь цветовых пар и выведите их по очереди на экран:

     clear();

     mvprintw(5, 5, "There are %d COLORS, and %d COLOR_PAIRS available", COLORS, COLOR_PAIRS);

     refresh();

     init_pair(1, COLOR_RED, COLOR_BLACK);

     init_pair(2, COLOR_RED, COLOR_GREEN);

     init_pair(3, COLOR_GREEN, COLOR_RED);

     init_pair(4, COLOR_YELLOW, COLOR_BLUE);

     init_pair(5, COLOR_BLACK, COLOR_WHITE);

     init_pair(6, COLOR_MAGENTA, COLOR_BLUE);

     init_pair(7, COLOR_CYAN, COLOR_WHITE);

     for (i = 1; i <= 7; i++) {

      attroff(A_BOLD);

      attrset(COLOR_PAIR(i));

      mvprintw(5 + i, 5, "Color pair %d", i);

      attrset(COLOR_PAIR(i) | A_BOLD);

      mwprintw(5 + i, 25, "Bold color pair %d", i);

      refresh();

      sleep(1);

     }

     endwin();

     exit(EXIT_SUCCESS);

    }

    Выполнение примера приведет к выводу, показанному на рис. 6.7, за вычетом реальных цветов, которые не отображаются на черно-белом снимке экрана.

    Рис. 6.7


    Как это работает

    После проверки того, что экран поддерживает управление цветами, программа инициализирует цветовую обработку и определяет ряд цветовых пар. Далее на экран выводится текст с использованием цветовых пар для того, чтобы продемонстрировать комбинации разных цветов на экране.

    Переопределение цветов

    Как пережиток, оставшийся от старых неинтеллектуальных терминалов, которые могли отображать очень немного цветов в каждый момент времени, но позволяли настраивать текущую цветовую палитру, в библиотеке curses сохранилась возможность переопределения цветов с помощью функции

    init_color
    :

    #include <curses.h>

    int init_color(short color_number, short red, short green, short blue);

    Она позволяет переопределить существующий цвет (в диапазоне от 0 до

    COLORS
    ) новыми значениями яркости цвета из диапазона от 0 до 1000. Такой подход немного напоминает определение цветовых характеристик в графических файлах формата GIF.

    Панели 

    При написании более сложных программ с использованием curses порой бывает легче построить логический экран и затем позже вывести весь или часть экрана на физический экран. В некоторых случаях лучше иметь логический экран большего размера, чем физический экран и отображать только часть логического экрана в любой конкретный момент времени.

    Это нелегко сделать с помощью функций библиотеки curses, с которыми вы познакомились к этому моменту, т.к. все окна должны быть не больше физического экрана. Библиотека curses предоставляет специальную структуру данных, панель (pad), для манипулирования данными логического экрана, которые не умещаются в стандартном окне.

    Структура панели похожа на структуру

    WINDOW
    , и все функции библиотеки curses, написанные для работы с окнами, можно применять и к панелям. Но у панелей есть и собственные функции для создания и обновления.

    Панели создаются во многом так же, как и обычные окна.

    #include <curses.h>

    WINDOW *newpad(int number_of_lines, int number_of_columns);

    Обратите внимание на то, что возвращаемое значение — указатель на структуру типа

    WINDOW
    , такое же, как у функции
    newwin
    . Удаляются панели, как и окна, функцией
    delwin
    .

    Но к панелям применяются другие подпрограммы обновления. Поскольку панель не привязана к конкретной точке экрана, вы должны задать область панели, которую хотите поместить на экран, и ее положение на экране. Делайте это с помощью функции prefresh.

    #include <сurses.h>

    int prefresh(WINDOW *pad_ptr, int pad_row, int pad_column, int screen_row_min, int screen_col_min, int screen_row_max, int screen_соl_max);

    Функция выполняет запись области панели, начинающейся в точке (

    pad_row
    ,
    pad_column
    ), в область экрана, определенную от (
    screen_row_min
    ,
    screen_col_min
    ) до (
    screen_row_max
    ,
    screen_col_max
    ).

    Есть и дополнительная подпрограмма

    pnoutrefresh
    . Она действует так же, как функция
    wnoutrefresh
    , обеспечивая более производительное обновление экрана.

    Давайте проверим это на практике с помощью программы pad.с (упражнение 6.8).

    Упражнение 6.8. Применение панели

    1. В начале этой программы вы инициализируете структуру панели и затем формируете панель с помощью функции, которая возвращает указатель на нее. Вставьте символы, заполняющие структуру панели (панель на 50 символов шире и выше экрана терминала):

    #include <unistd.h>

    #include <stdlib.h>

    #include <curses.h>


    int main() {

     WINDOW *pad_ptr;

     int x, y;

     int pad_lines;

     int pad_cols;

     char disp_char;

     initscr();

     pad_lines = LINES + 50;

     pad_cols = COLS + 50;

     pad_ptr = newpad(pad_lines, padcols);

     disp_char = 'a';

     for (x = 0; x < pad_lines; x++) {

      for (у = 0; у < pad_cols; y++) {

       mvwaddch(pad_ptr, x, y, disp_char);

       if (disp_char == 'z') disp_char = 'a';

       else disp_char++;

      }

     }

    2. Теперь перед завершением программы нарисуйте разные области панели в разных местах экрана:

     prefresh(pad_ptr, 5, 7, 2, 2, 9, 9);

     sleep(1);

     prefresh(pad_ptr, LINES + 5, COLS + 7, -5, 5, 21, 19);

     sleep(1);

     delwin(pad_ptr);

     endwin();

     exit(EXIT_SUCCESS);

    }

    Выполнив эту программу, вы увидите нечто подобное показанному на рис. 6.8.

    Рис. 6.8


    Приложение, управляющее коллекцией компакт-дисков

    Теперь, когда вы узнали о средствах, которые предлагает библиотека curses, можно разработать типовое приложение. Далее приведена его версия, написанная на языке С и использующая библиотеку curses. К достоинствам приложения относятся более четкое отображение информации на экране и применение окна с прокруткой для просмотра списков.

    Все приложение занимает восемь страниц, поэтому мы разделили его на секции и отдельные функции внутри секций. Исходный код программы curses_app.c можно получить на Web-сайте издательства Wrox (http://www.wrox.com/WileyCDA/). Как и все программы из этой книги, оно подчиняется требованиям Общедоступной лицензии проекта GNU.

    Примечание

    Мы написали эту версию приложения для работы с базой данных компакт-дисков, используя информацию из предыдущих глав. Данное приложение — потомок оригинального сценария командной оболочки, приведенного в главе 2. Оно не перепроектировалось для написания на языке С, поэтому вы увидите в этой версии многие подходы, заимствованные из сценария. Учтите, что в этой реализации есть существенные ограничения, которые мы устраним в последующих модификациях.

    Мы разбили программный код этого приложения на несколько отдельных секций, которые обозначены заголовками последующих разделов. Соглашения, принятые для оформления этого программного кода, немного отличаются от оформления большинства программ в этой книге; здесь выделение цветом применяется только для обозначения вызовов других функций приложения.

    Начало нового приложения для работы с коллекцией компакт-дисков

    Первая секция программного кода просто связана с объявлениями переменных и функций, которые вы будете применять позже, и инициализацией некоторых структур данных.

    1. Включите в программу все приведенные заголовочные файлы и несколько глобальных переменных:

    #include <unistd.h>

    #include <stdlib.h>

    #include <stdio.h>

    #include <string.h>

    #include <curses.h>

    #define MAX_STRING 80 /* Самый длинный допустимый ответ */

    #define MAX_ENTRY 1024 /* Самый длинный допустимый элемент БД */

    #define MESSAGE_LINE 6 /* В этой строке разные сообщения */

    #define ERROR LINE 22 /* Строка для вывода ошибок */

    #define Q_LINE 20 /* Строка для вопросов */

    #define PROMPT_LINE 18 /* Строка для вывода приглашения */

    2. Теперь вам нужны глобальные переменные. Переменная

    current_cdis
    применяется для хранения названия текущего компакт-диска, с которым вы работаете в данный момент. Она инициализируется так, что первый символ равен
    NULL
    , чтобы показать, что компакт-диск не выбран. Символ завершения
    \0
    , строго говоря, не обязателен, но он гарантирует инициализацию переменной, что само по себе хорошая вещь. Переменная
    current_cat
    применяется для записи номера текущего компакт-диска в каталоге:

    static char current_cd[MAX_STRING] = "\0";

    static char current_cat[MAX_STRING];

    3. Теперь объявите имена файлов. Для простоты в этой версии имена файлов фиксированные, как и имя временного файла.

    Это может вызвать проблемы, если программа выполняется двумя пользователями в одном и том же каталоге. Лучше получать имена файлов базы данных как аргументы программы или из переменных окружения. Нам также потребуется улучшенный метод генерации уникального имени временного файла, для чего мы могли бы использовать функцию

    tmpnam
    из стандарта POSIX. Мы обратимся к решению многих из этих проблем в главе 8, когда применим СУРБД MySQL для хранения данных.

    const char *title_file = "title.cdb";

    const char *tracks_file = "tracks.cdb";

    const char *temp_file = "cdb.tmp";

    4. И наконец, прототипы функций:

    void clear_all_screen(void);

    void get_return(void);

    int get_confirm(void);

    int getchoice(char *greet, char *choices[]);

    void draw_menu(char *options[], int highlight,

     int start_row, int start_col);

    void insert_title(char *cdtitle);

    void get_string(char *string);

    void add_record(void);

    void count_cds(void);

    void find_cd(void);

    void list_tracks(void);

    void remove_tracks(void);

    void remove_cd(void);

    void update_cd(void);

    5. Прежде чем рассматривать их реализацию, введем некоторые структуры (на самом деле массив пунктов меню) для хранения меню. Когда выбирается пункт меню, возвращается первый символ выбранного пункта. Например, если это пункт меню add new CD (добавить новый CD), при его выборе будет возвращен символ

    а
    . Когда компакт-диск выбран, будет отображаться расширенное меню.

    char *main_menu[] = {

     "add new CD",

     "find CD",

     "count CDs and tracks in the catalog",

     "quit",

     0,

    };

    char *extended_menu[] = {

     "add new CD",

     "find CD",

     "count CDs and tracks in the catalog",

     "list tracks on current CD";

     "remove current CD",

     "update track information",

     "quit",

     0,

    };

    На этом инициализация закончена. Теперь можно переходить к функциям программы, но сначала необходимо составить общее представление о взаимосвязях всех 16 функций. Функции разделены на три программных секции:

    □ отображение меню;

    □ добавление компакт-дисков в базу данных;

    □ извлечение и отображение данных компакт-диска.

    Визуальное представление дано на рис. 6.9.

    Рис. 6.9

    Взгляд на функцию main

    Функция

    main
    позволяет выбирать пункты меню, пока не выбран вариант выхода из меню (quit). Далее приведен соответствующий код.

    int main() {

     int choice;

     initscr();

     do {

      choice = getchoice("Options:", current_cd[0] ? extended_menu : main_menu);

      switch (choice) {

      case 'q':

       break;

      case 'a':

       add_record();

       break;

      case 'c':

       count_cds();

       break;

      case 'f':

       find_cd();

       break;

      case 'l':

       list_tracks();

       break;

      case 'r':

       remove_cd();

       break;

      case 'u':

       update_cd();

       break;

      }

     } while (choice != 'q');

     endwin();

     exit(EXIT_SUCCESS);

    }

    Теперь давайте подробно рассмотрим функции, связанные с тремя секциями программы.

    Формирование меню

    В этой секции рассматриваются три функции, относящиеся к пользовательскому интерфейсу программы.

    1. Функция

    getchoice
    , вызываемая из функции
    main
    , — это основная функция данной секции. В функцию
    getchoice
    передается приглашение
    greet
    и указатель
    choices
    на базовое или расширенное меню (в зависимости от того, выбран ли компакт-диск). Вы также увидите, как
    main_menu
    или
    extended_menu
    передаются как параметры в описанную ранее функцию
    main
    .

    int get_choice(char *greet, char* choises[]) {

     static int selected_row = 0;

     int max_row = 0;

     int start_screenrow = MESSAGE_LINE, start_screencol = 10;

     char **option;

     int selected;

     int key = 0;

     option = choices;

     while (*option) {

      max_row++;

      option++;

     }

     if (selected_row >= max_row)

      selected_row = 0;

     clear_all_screen();

     mvprintw(start_screenrow - 2, start_screencol, greet);

     keypad(stdscr, TRUE);

     cbreak();

     noecho();

     key = 0;

     while (key != 'q' && key != KEY_ENTER && key != '\n') {

      if (key == KEY_UP) {

       if (selected_row == 0) selected_row = max_row - 1;

       else selected_row--;

      }

      if (key == KEY_DOWN) {

       if (selected_row == (max_row - 1)) selected_row = 0;

       else selected_row++;

      }

      selected = *choices[selected_row];

      draw_menu(choices, selected_row, start_screen_row, start_screencol);

      key = getch();

     }

     keypad(stdscr, FALSE);

     nocbreak();

     echo();

     if (key == 'q') selected = 'q';

     return(selected);

    }

    2. Обратите внимание на то, как две локальные функции

    clear_all_screen
    и
    draw_menu
    вызываются внутри функции
    getchoice
    . Первой рассмотрим функцию
    draw_menu
    :

    void draw_menu(char* options[], int current_highlight, int start_row, int start_col) {

     int current_row = 0;

     char **option_ptr;

     char *txt_ptr;

     option_ptr = options;

     while (*option_ptr) {

      if (current_row == current_highlight) attron(A_STANDOUT);

      txt_ptr = options[current_row];

      txt_ptr++;

      mvprintw(start_row + current_row, start_col, "%s", txt_ptr);

      if (current_row == current_highlight) attroff(A_STANDOUT);

      current_row++;

      option_ptr++;

     }

     mvprintw(start_row + current_row + 3, start_col,

      "Move highlight then press Return ");

     refresh();

    }

    3. Далее рассмотрим функцию

    clear_all_screen
    , которая, как ни странно, очищает экран и перезаписывает заголовок. Если компакт-диск выбран, отображаются его данные:

    void clear all_screen() {

     clear();

     mvprintw(2, 20, "%s", "CD Database Application");

     if (current_cd[0]) {

      mvprintw(ERROR_LINE, 0, "Current CD: %s: %s\n", current_cat, current_cd);

     }

     refresh();

    }

    Управление базой данных

    В этом разделе описаны функции пополнения или обновления базы данных компакт-дисков. Функции

    add_record
    ,
    update_cd
    и
    remove_cd
    вызываются из функции
    main
    .

    Добавление записей

    1. Добавьте сведения о новом компакт-диске в базу данных.

    void add_record {

     char catalog_number[MAX_STRING];

     char cd_title[MAX_STRING];

     char cd_type[MAX_STRING];

     char cd_artist[MAX_STRING];

     char cd_entry[MAX_STRING];

     int screenrow = MESSAGE_LINE;

     int screencol = 10;

     clear_all_screen();

     mvprintw(screenrow, screencol, "Enter new CD details");

     screenrow += 2;

     mvprintw(screenrow, screencol, "Catalog Number: " );

     get_string(catalog_number);

     screenrow++;

     mvprintw(screenrow, screencol, " CD Title: ");

     get_string(cd_title);

     screenrow++;

     mvprintw(screenrow, screencol, " CD Type: ");

     get_string(cd_type);

     screenrow++;

     mvprintw(screenrow, screencol, " Artist: ");

     get_string(cd_artist);

     screenrow++;

     mvprintw(PROMPT_LINE-2, 5, "About to add this new entry:");

     sprintf(cd_entry, "%s, %s, %s, %s",

      catalog_number, cd_title, cd_type, cd_artist);

     mvprintw(PROMPT_LINE, 5, "%s", cd_entry);

     refresh();

     move(PROMPT_LINE, 0);

     if (get_confirm()) {

      insert_title(cd_entry);

      strcpy(current_cd, cd_title);

      strcpy(current_cat, catalog_number);

     }

    }

    2. Функция

    get_string
    приглашает к вводу и считывает строку из текущей позиции экрана. Она также удаляет завершающую новую пустую строку:

    void get_string(char* string) {

     int len;

     wgetnstr(stdscr, string, MAX_STRING);

     len = strlen(string);

     if (len > 0 && string[len - 1] == '\n') string[len - 1] = '\0';

    }

    3. Функция

    get_confirm
    запрашивает и считывает пользовательское подтверждение. Она читает введенную пользователем строку и проверяет, первый символ —
    Y
    или
    у
    . Если она обнаруживает другой символ, то не дает подтверждения.

    int get_confirm() {

     int confirmed = 0;

     char first_char;

     mvprintw(Q_LINE, 5, "Are you sure? ");

     clrtoeol();

     refresh();

     cbreak();

     first_char = getch();

     if (first_char == 'Y' || first_char == 'y') {

      confirmed = 1;

     }

     nocbreak();

     if (!confirmed) {

      mvprintw(Q_LINE, 1, " Cancelled");

      clrtoeol();

      refresh();

      sleep(1);

     }

     return confirmed;

    }

    4. Последней рассмотрим функцию

    insert_title
    . Она вставляет в базу данных компакт-дисков заголовок, добавляя строку с заголовком в конец файла заголовков:

    void insert_title(char* cdtitle) {

     FILE *fp = fopen(title_file, "a");

     if (!fp) {

      mvprintw(ERROR_LINE, 0, "cannot open CD titles database");

     } else {

      fprintf(fp, "%s\n", cdtitle);

      fclose(fp);

     }

    }

    Обновление записей

    1. Продолжим рассмотрение других управляющих функций, вызываемых из функции

    main
    . Следующая из них — функция
    update_cd
    . Эта функция использует обведенное рамкой вложенное окно с прокруткой и нуждается в нескольких константах, которые объявляются как глобальные, поскольку они позже потребуются функции
    list_tracks
    .

    #define BOXED_LINES  11

    #define BOXED_ROWS   60

    #define BOX_LINE_POS 8

    #define BOX_ROW_POS  2

    2. Функция

    update_cd
    позволяет пользователю заново ввести сведения о дорожках текущего компакт-диска. Удалив предыдущие записи о дорожках, она приглашает ввести новую информацию.

    void update_cd() {

     FILE *tracks_fp;

     char track_name[MAX_STRING];

     int len;

     int track = 1;

     int screen_line = 1;

     WINDOW *box_window_ptr;

     WINDOW *sub_window_ptr;

     clear_all_screen();

     mvprintw(PROMPT_LINE, 0, "Re-entering tracks for CD. ");

     if (!get_confirm())

    return;

     move(PROMP_TLINE, 0);

     clrtoeol();

     remove_tracks();

     mvprintw(MESSAGE_LINE, 0, "Enter a blank line to finish");

     tracks_fp = fopen(tracks_file, "a");

    Примечание

    Листинг будет продолжен через минуту; мы хотим сделать краткую паузу, чтобы обратить ваше внимание на ввод данных в обрамленное окно с прокруткой. Хитрость заключается в формировании вложенного окна, рисовании рамки по его краю и создании внутри этого окна нового вложенного окна с прокруткой.

     box_window_ptr = subwin(stdscr, BOXED_LINES + 2, BOXED_ROWS + 2,

      BOX_LINE_POS - 1, BOX_ROW_POS - 1);

     if (!box_window_ptr) return;

     box(box_window_ptr, ACS_VLINE, ACS_HLINE);

     sub_window_ptr = subwin(stdscr, BOXED_LINES, BOXED_ROWS,

      BOX_LINE_POS, BOX_ROW_POS);

     if (!sub_window_ptr) return;

     scrollok(sub_window_ptr, TRUE);

     werase(sub_window_ptr);

     touchwin(stdscr);

     do {

      mvwprintw(sub_window_ptr, screen_line++, BOX_ROW_POS + 2,

       "Track %d: ", track);

      clrtoeol();

      refresh();

      wgetnstr(sub_window_ptr, track_name, MAX_STRING);

      len = strlen(track_name);

      if (len > 0 && track_name[len - 1] = '\n')

       track_name[len - 1] = '\0';

      if (*track_name)

       fprintf(tracks_fp, "%s, %d, %s\n", current_cat, track, track_name);

      track++;

      if (screen_line > BOXED__LINES - 1) {

       /* время начать прокрутку */

       scroll(sub_window_ptr);

       screen_line--;

      }

     } while (*track_name);

     delwin(sub_window_ptr);

     fclose(tracks_fp);

    }

    Удаление записей

    1.

    remove_cd
    — последняя функция, вызываемая из функции
    main
    .

    void remove_cd() {

     FILE *titles_fp, *temp_fp;

     char entry[MAX_ENTRY];

     int cat_length;

     if (current_cd[0] == '\0') return;

     clear_all_screen();

     mvprintw(PROMPT_LINE, 0, "About to remove CD %s: %s. ", current_cat, current_cd);

     if (!get_confirm())

      return;

     cat_length = strlen(current_cat);

     /* Файл заголовков копируется во временный, игнорируя данный CD */

     titles_fp = fopen(title_file, "r");

     temp_fp = fopen(temp_flie, "w");

     while(fgets(entry, MAX_ENTRY, titles_fp)) {

      /* Сравнивает номер в каталоге и копирует элемент, если не

         найдено совпадение */

      if (strncmp(current_cat, entry, cat_length) != 0)

       fputs(entry, temp_fp);

     }

     fclose(titles_fp);

     fclose(temp_fp);

     /* Удаляет файл заголовков и переименовывает временный файл */

     unlink(title_file);

     rename(temp_file, title_file);

     /* Теперь делает то же самое для файла дорожек */

     remove_tracks();

     /* Устанавливает 'None' для текущего CD */

     current_cd[0] = '\0';

    }

    2. Теперь вам только нужен программный код функции

    remove_tracks
    , удаляющей дорожки текущего компакт-диска. Она вызывается двумя функциями —
    update_cd
    и
    remove_cd
    .

    void remove_tracks() {

     FILE *tracks_fp, *temp_fp;

     char entry[MAX_ENTRY];

     int cat_length;

     if (current_cd[0] == '\0') return;

     cat_length = strlen(current_cat);

     tracks_fp = fopen(tracks_file, "r");

     if (tracks_fp == (FILE *)NULL) return;

     temp_fp = fopen(temp_file, "w");

     while (fgets(entry, MAX_ENTRY, tracks_fp)) {

      /* Сравнивает номер в каталоге и копирует элемент, если не

         найдено совпадение */

      if (strncmp(current_cat, entry, cat_length) != 0)

       fputs(entry, temp_fp);

     }

     fclose(tracks_fp);

     fclose(temp_fp);

     /* Удаляет файл дорожек и переименовывает временный файл */

     unlink(tracks_file);

     rename(temp_file, tracks_file);

    }

    Запросы к базе данных компакт-дисков

    Теперь рассмотрим функции для доступа к данным, которые для упрощения доступа хранятся в паре простых файлов как поля, разделенные запятыми.

    1. Страстным коллекционерам важно знать, каким богатством они обладают или сколько собрано. Следующая функция делает это превосходно; она просматривает базу данных, подсчитывая заголовки и дорожки.

    void count_cds() {

     FILE *titles_fp, *tracks_fp;

     char entry[MAX_ENTRY];

     int titles = 0;

     int tracks = 0;

     titles_fp = fopen(title_file, "r");

     if (titles_fp) {

      while (fgets(entry, MAX_ENTRY, titles_fp))

       titles++;

      fclose(titles_fp);

     }

     tracks_fp = fopen(tracks_file, "r");

     if (tracks_fp) {

      while (fgets(entry, MAX_ENTRY, tracks_fp))

       tracks++;

      fclose(tracks_fp);

     }

     mvprintw(ERROR_LINE, 0,

      "Database contains %d titles, with a total of %d tracks.", titles, tracks);

     get_return();

    }

    2. Вы потеряли аннотацию к вашему любимому компакт-диску? Не волнуйтесь! Если вы аккуратно ввели подробную информацию в базу данных, теперь можно найти перечень дорожек с помощью функции

    find_cd
    . Она предлагает ввести подстроку, совпадение с которой нужно искать в базе данных, и устанавливает в глобальную переменную
    current_cd
    заголовок найденного компакт-диска.

    void find_cd() {

     char match[MAX_STRING], entry[MAX_ENTRY];

     FILE *titles_fp;

     int count = 0;

     char *found, *title, *catalog;

     mvprintw(Q_LINE, 0, "Enter a string to search for in CD titles: ");

     get_string(match);

     titles_fp = fopen(title_file, "r");

     if (titles_fp) {

      while (fgets(entry, MAX_ENTRY, titles_fp)) {

       /* Пропускает прежний номер в каталоге */

       catalog = entry;

       if (found == strstr(catalog, ", ")) {

        *found = '\0';

        title = found + 1;

        /* Стирает следующую запятую в элементе, укорачивая его

           только до заголовка */

        if (found == strstr(title, ", ")) {

         *found = '\0';

         /* Теперь проверяет, есть ли совпадающая строка */

         if (found == strstr(title, match)) {

          count++;

          strcpy(current_cd, title);

          strcpy(current_cat, catalog);

         }

        }

       }

      }

      fclose(titles_fp);

     }

     if (count != 1) {

      if (count == 0) {

       mvprintw(ERROR_LINE, 0, "Sorry, no matching CD found. ");

      }

      if (count > 1) {

       mvprintw(ERROR_LINE, 0,

        "Sorry, match is ambiguous: CDs found. ", count);

      }

      current_cd[0] = '\0';

      get_return();

     }

    }

    Хотя переменная

    catalog
    указывает на массив, больший чем
    current_cat
    , и могла бы переписать память, проверка в функции
    fgets
    препятствует этому.

    3. Вам также нужно иметь возможность перечислить на экране дорожки выбранного компакт-диска. Для вложенных окон можно использовать директивы

    #define
    , применявшиеся в функции
    update_cd
    в предыдущем разделе.

    void list_tracks() {

     FILE *tracks_fp;

     char entry[MAX_ENTRY];

     int cat_length;

     int lines_op = 0;

     WINDOW *track_pad_ptr;

     int tracks = 0;

     int key;

     int first_line = 0;

     if (current_cd[0] == '\0') {

      mvprintw(ERROR_LINE, 0, "You must select a CD first. ");

      get_return();

      return;

     }

     clear_all_screen();

     cat_length = strlen(current_cat);

     /* Сначала считает количество дорожек у текущего CD */

     tracks_fp = fopen(tracks_file, "r");

     if (!tracks_fp) return;

     while (fgets(entry, MAX_ENTRY, tracks_fp)) {

      if (strncmp(current_cat, entry, cat_length) == 0) tracks++;

     }

     fclose(tracks_fp);

     /* Создает новую панель, гарантируя, что даже при наличии одной

        дорожки панель достаточна большая, поэтому последующий вызов

        prefresh() всегда будет допустим. */

     track_pad_ptr = newpad(tracks + 1 + ВОХЕD_LINES, BOXED_ROWS + 1);

     if (!track_pad_ptr) return;

     tracks_fp = fopen(tracks_file, "r");

     if (!tracks_fp) return;

     mvprintw(4, 0, "CD Track Listing\n");

     /* Записывает сведения о дорожке на панель */

     while (fgets(entry, MAX_ENTRY, tracks_fp)) {

      /* Сравнивает номер каталога и оставшийся вывод элемента */

      if (strncmp(current_cat, entry, cat_length) == 0) {

       mvwprintw(track_pad_ptr, lines_op++, 0, "%s", entry + cat_length + 1);

      }

     }

     fclose(tracks_fp);

     if (lines_op > BOXED_LINES) {

      mvprintw(MESSAGE_LINE, 0,

       "Cursor keys to scroll, RETURN or q to exit");

     } else {

      mvprintw(MESSAGE_LINE, 0, "RETURN or q to exit");

     }

     wrefresh(stdscr);

     keypad(stdscr, TRUE);

     cbreak();

     noecho();

     key = 0;

     while (key != "q" && key != KEY_ENTER && key != '\n') {

      if (key == KEY_UP) {

       if (first_line > 0) first_line--;

      }

      if (key == KEY_DOWN) {

       if (first_line + BOXED_LINES + 1 < tracks) first_line++;

      }

      /* Теперь рисует соответствующую часть панели на экране */

      prefresh(track_pad_ptr, first_line, 0, BOX_LINE_POS, BOX_ROW_POS,

       BOX_LINE_POS + BOXED_LINES, BOX_ROW_POS + BOXED_ROWS);

      key = getch();

     }

     delwin(track_pad_ptr);

     keypad(stdsсr, FALSE);

     nocbreak();

     echo();

    }

    4. В последних двух функциях вызывается функция

    get_return
    , которая приглашает к вводу и считывает символ возврата каретки, игнорируя другие символы.

    void get_return() {

     int ch;

     mvprintw(23, 0, "is", " Press return ");

     refresh();

     while ((ch = getchar()) != '\n' && ch != EOF);

    }

    Если вы выполните эту программу, то увидите на экране нечто похожее на рис. 6.10.

    Рис. 6.10 

    Резюме 

    В этой главе вы изучили библиотеку curses. Она предлагает текстовым программам удобный способ управления экраном и считывания данных с клавиатуры. Хотя библиотека curses не обеспечивает такого уровня управления, как общий терминальный интерфейс (GTI) и прямой доступ к структуре

    terminfo
    , ею гораздо легче пользоваться. Если вы пишете полноэкранное текстовое приложение, стоит рассмотреть возможность применения в нем библиотеки curses для управления экраном и чтения данных с клавиатуры.








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх