Введение в программирование с curses

авторы: Eric S. Raymond и Zeyd M. Ben-Halim

Содержание


Введение

Этот документ является введением в программирование с curses. Это не исчерпывающий справочник по Интерфейсу Прикладных Программ(API) для curses; эту роль играют man страницы curses. Скорее, его цель - облегчить понимание пакета программистам на C.

Этот документ предназначен для программистов на C, которые еще плохо знакомы с ncurses. Если ты уже имел опыт программирования с curses, тем не менее должен прочитать разделы Работа с мышью, Отладка, Совместимость со старыми версиями, и Подсказки, советы и трюки. Они помогут тебе увеличить скорость приложений за счет специальных возможностей и особенностей реализации ncurses. Если ты не так опытен, то продолжай читать.

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

curses API может показаться неким архаизмом на UNIX терминалах при возрастающем преобладании X, Motif, и Tcl/Tk. Тем не менее, UNIX еще поддерживает линии tty и X-ы поддерживают xterm(1); curses API имеет преимущества: (а) обратная портируемость на текстовые терминалы, и (б) простота. Для приложений, в которых не требуется растровой графики и разных шрифтов, реализация интерфейса с помощью curses обычно проще и дешевле, чем использование одного X toolkit.

Краткая история Curses

Исторически, первым предком curses были процедуры, написанные для обеспечения работы с экраном в игре rogue; они использовали параметры уже существующей базы данных termcap описаний возможностей терминалов. Эти процедуры были сложены в документированную библиотеку и впервые появились в ранних версиях BSD UNIX.

В System III UNIX от Bell Labs свойства были переписаны и намного улучшена сама библиотека curses. В ней был представлен формат terminfo. Terminfo основывается на базе данных Berkeley termcap, но содержит несколько улучшений и расширений. Были представлены параметризованные возможности строк, стало возможным описать различные видео атрибуты и цвета, и она стала работать с намного большим количеством терминалов, чем это было возможно с termcap. Позднее в выпусках AT&T System V, curses развила использование большинства свойств и предоставила большие возможности, оставив далеко позади BSD curses по мощности и гибкости.

Цель этого документа

Этот документ описывает ncurses, свободную реализацию System V curses API с некоторыми ясно описываемыми расширениями. Она включает в себя следующие свойства System V curses:

  • Поддержку различных выделений светом на экране (BSD curses могла работать только с одним световым `выделением', обычно инверсией цвета).

  • Поддержку рисования линий и прямоугольников с помощью символов псевдографики.

  • Распознавание ввода функциональных клавиш.

  • Поддержку цвета.

  • Поддержку pads (окон, больших чем размер экрана, для которых экран или подокно определены как область вывода).
Также, этот пакет позволяет использовать вставку и удаление строк и свойства символов терминалов, и определяет как оптимально использовать эти свойства без помощи программиста. Он также позволяет отображать произвольные комбинации видео атрибутов, даже на терминалах, которые оставляют ``magic cookies'' на экране, когда происходят изменения в атрибутах.

Пакет ncurses может также перехватывать и использовать сообщения от мыши в некоторых средах (xterm в системе X window). Этот документ включает советы по использованию мыши.

Пакет ncurses создал Pavel Curtis. Первоначальный ведущий пакета Zeyd Ben-Halim <zmbenhal@netcom.com>. Eric S. Raymond <esr@snark.thyrsus.com> написал много новых свойств, которые вошли в версии больше 1.8.1 и написал большую часть введения. JШrgen Pfeifer написал целиком код меню и форм и связующий код Ada95. Дальнейшая работа была выполнена Thomas Dickey и JШrgen Pfeifer. Florian La Roche выступает в роли ведущего для Free Software Foundation, у которого авторское право на ncurses. Связаться с текущими ведущими можно написав по адресу bug-ncurses@gnu.org.

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

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

Терминология

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

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

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

Библиотека Curses

Краткий обзор Curses

Компиляция программ, использующих Curses

Чтобы использовать библиотеку, необходимо иметь определенными некоторые типы и переменные. Поэтому, программист должен иметь строку:
	  #include <curses.h>
в начале исходного кода программы. Экранный пакет использует стандартную I/O библиотеку(ввода/вывода), поэтому <curses.h> включает <stdio.h>. <curses.h> в зависимости от твоей системы также включает <termios.h>, <termio.h>, или <sgtty.h>. Для программиста излишне (но безопасно) еще раз включать эти строки. При линковке с curses тебе нужно иметь -lncurses в LDFLAGS или в командной строке. Нет необходимости в любых других библиотеках.

Обновление экрана

Чтобы оптимально обновлять экран, необходимы процедуры, которым нужно знать как выглядит экран в данный момент и что хочет программист чтобы на нем было в следующий момент. Для этой цели определяется тип данных (структура) называемый WINDOW, который описывает изображение окна для программы, включая начальную позицию на экране (координаты (y, x), левого верхнего угла) и его размер. Один из них (называемый curscr, текущий экран) это образ экрана, который в настоящий момент отображается на терминале. Другой экран (называемый stdscr, стандартный экран) - на нем по умолчанию делаются все изменения.

Окно - исключительно для внутреннего представления. Оно используется для построения и хранения возможного изображения части терминала. Оно ничего не знает относительно реального экрана терминала; оно больше похоже на scratchpad или буфер записи.

Чтобы часть физического экрана, связанная с окном, отразила содержимое структуры окна, вызывается процедура refresh() (или wrefresh(), если окно не stdscr).

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

Стандартные окна и соглашения об именах функций

Как отмечалось выше, программы могут использовать несколько окон, и еще два окна создаются автоматически: curscr, которое знает что на терминале в данный момент, и stdscr, которое отображает, что хочет получить программист на терминале в следующий момент. Пользователь никогда не должен иметь прямого доступа к curscr. Изменения должны делаться через API, а затем вызываться процедура refresh() (или wrefresh()).

Для многих функций определено использование stdscr как экрана по умолчанию. Например, чтобы добавить символ на stdscr, нужен один вызов addch() с желаемым символов в качестве аргумента. Для записи в другое окно используется процедура waddch() (`w' оконно-зависимая addch()). Это соглашение применимо ко всем функциям с именами, начинающимися с "w", когда они должны применяться к определенным окнам. Этому правилу не подчиняются процедуры, для которых окно должно всегда указываться.

Для изменения текущих координат (y, x) используются процедуры move() и wmove(). Однако, часто после изменения желательно выполнить какую-нибудь I/O операцию. Чтобы избежать неуклюжести кода большинство I/O процедур могут предваряться префиксом 'mv' и желаемые (y, x) координаты указываться раньше других аргументов функций. Например, вызовы

	  move(y, x);
	  addch(ch);
можно заменить на
	  mvaddch(y, x, ch);
и
	  wmove(win, y, x);
	  waddch(win, ch);
можно заменить на
	  mvwaddch(win, y, x, ch);
Обратите внимание, что указатель на описатель окна (win) стоит перед добавляемыми (y, x) координатами. В функции необходимый указатель окна передается всегда первым параметром.

Переменные

Библиотека curses устанавливает некоторые переменные, описывающие возможности терминала.
      тип    название  Описание    
      ------------------------------------------------------------------
      int    LINES     число строк в терминале
      int    COLS      число столбцов в терминале
curses.h также вводит некоторые #define константы и типы, полезные всем:
bool
логический тип, на самом деле `char' (например, bool doneit;)
TRUE
флаг логической `правды' (1).
FALSE
флаг логической `лжи' (0).
ERR
флаг ошибки, возвращается процедурами при неудаче (-1).
OK
флаг ошибки, возвращается процедурами когда все выполнено правильно.

Использование библиотеки

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

Здесь пример программы, которая послужит поводом для обсуждения:

#include <curses.h>
#include <signal.h>

static void finish(int sig);

main(int argc, char *argv[])
{
    /* инициализируй свои не относящиеся к curses структуры данных здесь */

    (void) signal(SIGINT, finish);      /* подготовить прерывания для завершения */

    (void) initscr();      /* инициализировать библиотеку curses */
    keypad(stdscr, TRUE);  /* разрешить преобразование кодов клавиатуры */
    (void) nonl();         /* не делать NL->CR/NL при выводе */
    (void) cbreak();       /* читать один символ за раз, не ждать \n */
    (void) noecho();       /* не показывать ввод */

    if (has_colors())
    {
        start_color();

        /*
         * Простое назначение цветов, часто нужное всем.
         */
        init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK);
        init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);
        init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK);
        init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK);
        init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK);
        init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
        init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK);
        init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
    }

    for (;;)
    {
        int c = getch();     /* обновить, принять одиночное нажатие клавиши с ввода */

        /* обработать команду нажатой клавиши */
    }

    finish(0);               /* мы закончили */
}

static void finish(int sig)
{
    endwin();

    /* выполни завершающие не curses команды здесь */

    exit(0);
}

Запуск

Чтобы использовать экранный пакет, программы должны знать характеристики терминала, и должно быть выделено пространство для curscr и stdscr. Функция initscr() заботится об этих вещах. Т.к. под окна выделяется место, то может произойти переполнение памяти при попытке сделать это. Если этот редкий случай произойдет, то initscr() завершит выполнение программы с сообщением об ошибке. initscr() должна всегда вызываться перед любой процедурой, которая воздействует на окна. Если это не сделано, то программа завершится с образованием файла core(дампа памяти), как только попытается обратиться к curscr или stdscr. Однако, обычно лучше всего подождать, пока вызов действительно не понадобится, например после проверки ошибок запуска. Процедуры, меняющие состояние терминала, типа nl() и cbreak() должны также вызываться после initscr().

После того как экна были размещены на экране, ты можешь настроить их под свою программу. Если ты хочешь, скажем, разрешить прокрутку экрана, используй scrollok(). Если ты хочешь, чтобы курсор был слева от места последнего изменения используй leaveok(). Если это не сделано, refresh() будет перемещать курсор в текущие оконные координаты (y, x) после его обновления.

Ты можешь создать новые окна с помощью функций newwin(), derwin(), и subwin(). Процедура delwin() позволяет избавиться от старых окон. Все опции, описанные выше, применимы к любому окну.

Вывод

Теперь, когда мы все настроили, мы бы хотели что-нибудь вывести на терминал. Основные функции, используемые для этого, которые работают и для окон это addch() и move(). addch() вставляет символ в текущие (y, x) координаты. move() изменяет текущие (y, x) координаты на те, которые бы ты хотел. Она возвращает ERR, если ты вышел за пределы окна. Как отмечалось выше, ты можешь объединить две функции в одну mvaddch() чтобы сделать обе эти вещи за один раз.

Другие функции вывода, например addstr() и printw(), все - вызывают addch() для добавления символа в окно.

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

Если вызвать wrefresh() с curscr в качестве аргумента, то это заставит думать curscr, что экран выглядит как curscr. Это полезно для реализации команды, которая перерисовывает экран, в случае если на нём беспорядок.

Ввод

Дополнительная функция к addch() - getch(), которая с установленным эхом, будет вызывать addch() для повторения символа на экране. Поскольку экранному пакету необходимо всё время знать что творится на терминале, то если символы отображаются, то tty должен быть в raw или cbreak режиме. Поскольку изначально эхо на терминал разрешено и он в обычном ``cooked'' режиме, то одна из этих вещей должна быть изменена перед вызовом getch(); иначе вывод программы будет непредсказуем.

Когда тебе нужно ввести строку в окне, то для этого есть функции wgetstr() и подобные. Есть даже функция wscanw(), которая может выполнять в scanf()(3)-стиле разбор строки с несколькими полями в окне ввода. Также есть псевдостроковые функции, которые включают эхо на время своего выполнения.

Пример кода выше использует вызов keypad(stdscr, TRUE) чтобы разрешить поддержку отображения функциональных клавиш. С помощью этого свойства, код getch() отслеживает во входном потоке последовательности символов, которые соответствуют стрелкам и функциональным клавишам. Эти последовательности возвращаются как значения псевдосимволов. Возвращаются #define значения, описанные в curses.h. Перевод последовательностей в #define значения определяются key_ возможностями в терминальных записях terminfo.

Использование символов псевдографики

Функция addch() (и некоторые другие, включая box() и border()) может принимать некоторые псевдосимвольные аргументы, которые специально определены в ncurses. Эти #define значения определены в заголовочном файле curses.h; смотри там полный список(ищи префикс ACS_).

Большинство полезных ACS определяют символы псевдографики. Ты можешь использовать их для рисования прямоугольников и простых графиков на экране. Если терминал не имеет таких символов, то curses.h переводит их в узнаваемый (хотя и некрасивый) набор по умолчанию ASCII.

Атрибуты и цвет символа

Пакет ncurses поддерживает выделения на экране, включающие standout, reverse-video, подчеркивание и мигание. Он также поддерживает цвет, к которому относятся как еще к одному выделению.

Выделения внутренне кодируются с помощью старших битов в псевдосимвольном типе (chtype), который curses.h использует для представления содержимого экранной ячейки. Смотри в заголовочном файле curses.h полный список значений масок выделений (ищи префикс A_).

Существует два пути сделать выделение. Первый - это логически сложить (or) значение выделения с символьным аргументом в вызове addch(), или еще сделать один вызов, который принимает chtype аргумент.

Второй - это установить текущее значение выделения. Оно логически сложится (or) с любым выделением, сделанном первым путем. Ты можешь сделать это с помощью функций attron(), attroff(), и attrset(); подробней смотри на страницах руководства.

Цвет - это специальный вид выделения. На самом деле пакет работает с таким термином как цветовая пара - комбинация цветов переднего и заднего плана. Пример кода выше настраивает восемь цветовых пар, все обязательно существующие цвета на черном фоне. Обратите внимание что каждая цветовая пара, в сущности, отражает название цвета переднего плана. Любые другие другие значения из неконфликтующего диапазона могут использоваться в качестве значений первых аргументов init_pair().

После того как ты закончил с помощью init_pair() создание цветовой пары N, ты можешь использовать COLOR_PAIR(N) в качестве выделения, вызвав особую цветовую комбинацию. Обратите внимание, что в COLOR_PAIR(N), константа N, является константой во время компиляции и может быть использована в инициализаторах.

Работа с мышью

Библиотека ncurses также обеспечивает взаимодействие с мышью. Внимание: это возможно только в ncurses, это не часть ни стандарта XSI Curses, ни System V Release 4, ни BSD curses. Поэтому, мы рекомендуем заключать код работы с мышью в #ifdef, используя свойство макроса NCURSES_MOUSE_VERSION, для того чтобы он не компилировался и не линковался на не-ncurses системах.

В настоящее время, события от мыши работают только под xterm. В будущем, ncurses научится определять присутствие свободного сервера мыши gpm(1), Alessandro Rubini для Linux систем и принимать события от мыши через него.

Работа с мышью очень проста. Чтобы начать работу, используй функцию mousemask(), принимающую в качестве первого аргумента битовую маску, в которой указываешь какие события ты хочешь чтобы твоя программа видела. Она возвращает битовую маску событий которые на самом деле будут видимы, которая может отличаться от аргумента, если устройство мыши не способно выдавать некоторые типы событий, которые ты указал.

Как только мышь стала работать, цикл команд твоего приложения должен отслеживать возвращаемые значения KEY_MOUSE с помощью wgetch(). Когда ты видишь его, то значит событие мыши в очереди. Чтобы вытащить его из очереди, используется функция getmouse() (ты должен сделать это перед следующим wgetch(), иначе может возникнуть другое событие мыши и сделает первое событие недосягаемым).

Каждый вызов getmouse() заполняет структуру (адрес которой ты передал) данными события мыши. Данные события включают координаты указателя мыши в координатах экрана. Они также включают маску события. Биты маски установлены соответственно типу возвращаемого события.

Структура мыши содержит два дополнительных поля, которые могут использоваться в будущем для взаимодействия ncurses с новыми свойствами устройств указателей. В дополнение к x и y координатам, есть область памяти для z координаты; она может быть полезна для сенсорных экранов, которые возвращают параметр давления или время нажатия. Есть также поле ID устройства, которое может использоваться для распознавания различных устройств указателей.

Признак видимости событий может быть изменён в любое время с помощью mousemask(). События, которые можно отслеживать: нажатие, отпускание, однократный-, двойной- и тройной-щелчок (ты можешь установить максимальное время удержания клавишы между щелчками). Если ты не сделаешь щелчки видимыми, то они возвращаются в виде пар нажал-отпустил. В некоторых средах в маску событий могут включаться биты состояния клавиш shift, alt, и ctrl клавиатуры во время события.

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

Т.к. события мыши доступны не во всех средах, то было бы глупо строить приложения ncurses, которым для работы необходима мышь. Предпочтительно использовать мышь как ссылку для команд укажи-и-нажми в приложениях, которые обычно доступны с клавиатуры. Две тестовых игры в поставке ncurses (bs и knight) содержат код, который демонстрирует как это может быть сделано.

Смотри в man страницах curs_mouse(3X) полное описание функций взаимодействия с мышью.

Завершение

Чтобы убрать после процедур ncurses , существует процедура endwin(). Она восстанавливает режимы tty, которые были до первого вызова initscr(), и перемещает курсор в нижний левый угол. Таким образом, всегда после вызова initscr, перед выходом, должна вызываться endwin().

Описания функций

Здесь мы подробно опишем работу некоторых важных функций curses, как дополнение к описанию в man страницах.

Инициализация и завершение

initscr()
Почти всегда первой вызываемой функцией должна быть initscr(). Она определяет тип терминала и инициализирует структуры данных curses. initscr() также все настраивает так, чтобы первый вызов refresh() очистил весь экран. Если произошла ошибка, то записывается сообщение в стандартный поток ошибок и программа завершается. Иначе возвращается указатель на stdscr. Некоторые функции можно вызывать перед initscr (slk_init(), filter(), ripofflines(), use_env(), и если ты используешь различные терминалы, newterm().)

endwin()
Твоя программа всегда должна вызывать endwin() перед тем как выйти или для вызова shell. Эта функция восстанавливает режимы tty, перемещает курсор в нижний левый угол экрана, сбрасывает терминал в правильный не-визуальный режим. Вызов refresh() или doupdate() после временного выхода из программы восстанавливает экран ncurses в первоначальное состояние.

newterm(type, ofp, ifp)
Программа, которая работает более чем с одним терминалом должна использовать newterm() вместо initscr(). newterm() должен вызываться один раз для каждого терминала. Он возвращает переменную типа SCREEN *, которая должна сохраняться как ссылка на этот терминал. Аргументами являются тип терминала (строка) и указатели FILE для вывода и ввода в терминал. Если тип NULL - используется переменная среды $TERM. endwin() должна вызываться единожды во время завершения работы для каждого терминала, открытого этой функцией.

set_term(new)
Эта функция используется для переключения терминала в другой уже открытый newterm() терминал. Указатель на экран передается в качестве параметра. Этой функцией возвращается предыдущий терминал. Все остальные вызовы влияют только на текущий терминал.

delscreen(sp)
Обратная newterm(); освобождает структуры данных, связанные с данной SCREEN ссылкой.

Процесс вывода на терминал

refresh() и wrefresh(win)
Эти функции делают вывод непосредственно на терминал, тогда как другие процедуры просто работают со структурами данных. wrefresh() копирует заданное окно на физический экран терминала , принимая в расчет то что уже на нем есть, чтобы выполнить оптимизацию. refresh() выполняет регенерацию stdscr(). Если leaveok() не был разрешен, то физический курсор терминала располагается слева от курсора окна.

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

Возможности низкоуровневого доступа

setupterm(term, filenum, errret)
Эта процедура вызывается для инициализации описания терминала, без установки экранных структур curses или изменения битов режимов драйвера tty. term - строка символов, обозначающая название используемого терминала. filenum - файловый указатель UNIX на терминал, используемый для вывода. errret - указатель на целое, в которое пишется успешно или нет выполнение. Возвращаемое значение может быть 1 (все нормально), 0 (нет такого терминала), или -1 (проблемы с нахождением базы данных terminfo).

Значение term может быть NULL, что приводит к использованию значения среды TERM. Указатель errret может также быть NULL, что значит код ошибки не нужен. Если errret определен и что-то не так, setupterm() напечатает соответствующее сообщение об ошибке и программа завершится, что лучше чем просто вернуться. Таким образом, простая программа вызывает setupterm(0, 1, 0) и не беспокоится об ошибках инициализации.

После вызова setupterm(), глобальная переменная cur_term устанавливается на текущую структуру параметров терминала. Вызвав setupterm() для каждого терминала и сохранив и восстановить cur_term, то для программы возможно использовать два или более терминала сразу. Setupterm() также сохраняет названия разделов описаний терминала в глобальном символьном массиве ttytype[]. Последующие вызовы setupterm() перезаписывают этот массив, так что тебе, если требуется, надо сохранять их самому.

Отладка

ВНИМАНИЕ: Эти функции не часть стандартного curses API!

trace()
Эта функция используется для точной установки уровня отладки. Если уровень отладки не ноль, во время выполнения твоей программы создастся файл с именем `trace' в текущем рабочем каталоге, в котором содержится отчет о действиях библиотеки. Большие уровни отладки разрешают выводить больше подробностей (и объемнее) -- подробности смотри в комментариях рядом с определениями TRACE_ в файле curses.h. (Также возможно установить уровень отладки, установив его в переменной среды NCURSES_TRACE).
_tracef()
Эта функция используется для вывода твоей собственной отладочной информации. Она доступна только если ты линковал с -lncurses_g. Она может использоваться вместо printf(), только она выводит новую строку после всех аргументов. Вывод осуществляется в файл в именем trace в текущий каталог.
Журнал отладки может быть труден для понимания из-за избыточных объемов выводимых данных в нем. Есть сценарий, называемый tracemunch, включенный в поставку ncurses, который может до какой-то степени облегчить задачу; он группирует длинные последовательности похожих операций в более сжатую одиночную строку псевдооперации. Эти псевдооперации можно отличить по названию из заглавных букв.

Подсказки, советы и трюки

Man страницы ncurses - полное руководство по библиотеке. В оставшейся части этого документа, мы обсудим полезные методы, которые могут быть не так очевидны из описания man страниц.

Некоторые предостерегающие замечания

Если ты думаешь что тебе нужно использовать noraw() или nocbreak(), подумай еще раз и тихонько двигайся дальше. Вероятно лучшим решением будет использовать getstr() или подобную для симуляции cooked режима. Функции noraw() и nocbreak() стараются восстановить cooked режим, но они могут задрать некоторые контрольные биты, установив их перед тем как приложение запустится. Также, они всегда были плохо описаны и вероятно уменьшат используемость твоего приложения с другими библиотеками curses.

Запомни, что refresh() это синоним wrefresh(stdscr), и не пытайся смешивать использование stdscr с использованием окон, объявленных newwin(); вызов refresh() сметет их с экрана. Правильным решением будет использовать subwin(), или не трогать stdscr совсем и уложить черепицей объявленные окна, которые затем wnoutrefresh() где-нибудь в цикле событий программы и одиночным вызовом doupdate() выполнить настоящую перерисовку.

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

Есть свободная библиотека панелей, включенная в поставку ncurses, которая неплохо выполняет работу по усилению способностей перекрывающихся окон.

Постарайся избежать использования глобальных переменных LINES и COLS. Используй вместо них getmaxyx() в контексте stdscr. Причина: твой код может портироваться для запуска в средах с изменяемыми размерами окна, т.е. несколько экранов могут иметь разные размеры.

Временный выход из режима ncurses

Иногда тебе нужно написать программу, которая почти все время находится в экранном режиме, но время от времени возвращается в обычный `cooked' режим. Обычная причина - это поддержка выхода в shell. Это просто устроить в ncurses.

Для выхода из режима ncurses, вызывается endwin() как если бы завершалась работа программы. Это возвратит экран из cooked режима; ты можешь запустить shell. Когда ты захочешь вернуться в режим ncurses, просто вызови refresh() или doupdate(). Они перерисуют экран.

Есть логическая функция, isendwin(), код которой можно использовать для определения активен ли экранный режим ncurses. Она возвращает TRUE в промежутке между вызовом endwin() и следующим refresh(), иначе FALSE .

Вот часть примерного кода для shell:

    addstr("Shelling out...");
    def_prog_mode();           /* сохранить текущие режимы tty */
    endwin();                  /* восстановить первоначальные режимы tty */
    system("sh");              /* запустить shell */
    addstr("returned.\n");     /* подготовить сообщение о возвращении */
    refresh();                 /* восстановить сохраненные режимы, перерисовать экран */

Использование ncurses в xterm

Операция изменения размера в X посылает SIGWINCH в приложения, запущенные в xterm. Библиотека ncurses не перехватывает этот сигнал, потому что в общем-то не знает как ты хочешь перерисовать экран. Тебе придется написать обработчик SIGWINCH самому.

Простейший код обработчика SIGWINCH имеет endwin, за ним refresh и экран перерисовывается сам. refresh берет новый размер экрана из окружения терминала.

Обработка нескольких терминальных экранов

Функция initscr() на самом деле вызывает функцию newterm() для выполнения большей части работы. Если ты пишешь программу, которая отрывает несколько терминалов, используй newterm() напрямую.

Для каждого вызова ты должен указать тип терминала и пару файловых указателей; каждый вызов возвращает экранную ссылку и stdscr устанавливается равным последней. Ты можешь переключаться между терминалами с помощью set_term. Обратите внимание что ты можешь также вызвать def_shell_mode и def_prog_mode на каждый tty.

Проверка возможностей терминала

Иногда нужно написать программу, которая проверяет наличие различных возможностей, перед тем как решить войти ли в ncurses режим. Простое решение проблемы - вызвать setupterm(), затем использовать функции tigetflag(), tigetnum(), и tigetstr() для проверки.

В частности это полезно когда ты хочешь проверить считать ли данный тип терминала как `умный' (с адресуемым курсором) или `глупым'. Правильный путь для проверки - это посмотреть что возвращаемое значение tigetstr("cup") не NULL. Другой способ, это включить файл term.h и проверить значение макроса cursor_address.

Настройка скорости

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

Специальные свойства ncurses

Когда программа работает на PC-совместимом компьютере, ncurses предоставляет расширенную поддержку IBM high-half и ROM символов. Выделение A_ALTCHARSET позволяет отображать обе high-half ACS графику и PC ROM графику 0-31, которая обычно интерпретируется как контрольные символы.

Функция wresize() позволяет изменять размер окна по месту.

Совместимость со старыми версиями

Не смотря на все наши усилия, есть некоторые различия между ncurses и (недокументированным!) поведением старых реализаций curses. Они восстали из двусмысленностей или пропусков в документации API.

Регенерация перекрывающихся окон

Если ты определил два окна, A и B, которые перекрываются, и затем как-то переместил и регенерировал их, то как надо делать изменения в перекрывающихся областях в исторических версиях curses часто точно не описаны.

Чтобы понять почему возникла эта проблема, вспомним что обновления экрана подсчитываются между двумя представлениями всего экрана. В документации сказано, что когда ты регенерируешь окно, во-первых оно копируется в виртуальный экран, а затем подсчитываются изменения для обновления физического экрана (и применяются на терминале). Но как "применяются" точно не указано, и есть неуловимая разница в том какая разница получилась при копировании в случае где два перекрывающихся окна регенерировались в неизвестные промежутки времени.

Что получится в перекрывающихся областях зависит от того что при wnoutrefresh() было с аргументами -- какая часть аргумента окна копируется на виртуальный экран. Некоторые реализации выполняют "измененное копирование", копируя только места в окне, которые были изменены (или были помечены как изменённые с помощью wtouchln() и компанией). Некоторые реализации выполняют "целиковое копирование", копируя всё окно в виртуальный экран были или нет в нём изменения.

Сама библиотека ncurses не всегда согласуется с этими положениями. С версии 1.8.7 по 1.9.8a был баг и они делали целиковое копирование. Версии 1.8.6 и старее и версии 1.9.9 и новее, выполняли изменённое копирование.

Для большинства коммерческих реализаций curses, это не документировано и нельзя знать точно (по крайней мере для ведущих ncurses) выполняют ли они изменённое копирование или целиковое копирование. Мы знаем что в System V release 3 curses логика похожа на попытку выполнить изменённое копирование, но логика среды и представление данных достаточно сложны, и наше познания достаточно поверхносны, и очень трудно узнать насколько это правда. Не все чисто и в том что подразумевают документация SVr4 и стандарт XSI. Стандарт XSI Curses упоминает только wnoutrefresh(); документы SVr4 кажется описывают целиковое копирование, но возможно с некоторыми усилиями и стилем прочитать их совсем по-другому.

Может быть поэтому неблагоразумно полагаться на какое-либо поведение в программах, которые могут линковаться с другими реализациями curses. Вместо этого, ты можешь выяснить с touchwin() перед вызовом wnoutrefresh() для гарантии целостности содержимого везде.

По-настоящему просветленный путь обработки этого это использовать библиотеку панелей. Если ты захочешь обновить экран, выполни update_panels(), которая сделает все необходимые вызовы wnoutrfresh() для всех панелей, в порядке определенной тобой очерёдности. Затем ты можешь сделать один doupdate() и это будет один единственный всплеск физического I/O, который выполнит все твои обновления.

Заполнение при стирании

Если ты использовал очень старые версии ncurses (1.8.7 или старше) тебя может удивить поведение стирающих функций. В старых версиях стираемые зоны окна заполнялись пробелом, изменяемым с помощью текущего атрибута окна (как установили wattrset(), wattron(), wattroff() и компания).

В новых версиях это не так. Вместо этого атрибут стирания пробел - это нормально, хотя и пока изменяется функциями bkgdset() или wbkgdset().

Это изменение в поведение ncurses соответствует System V Release 4 и стандарту XSI Curses.

Соответствие XSI Curses

Библиотека ncurses предполагает что соответствует базовому уровню стандарта XSI Curses от X/Open. Многие свойства расширенного уровня (фактически, почти все свойства не имеющие отношения к широким символам и интернационализации) также поддерживаются.

Один эффект соответствия XSI это изменение в поведении описан в "Заполнение при стирании -- Совместимость с старыми версиями".

Также ncurses удовлетворяет требование XSI, что каждый макрос должен иметь соответствующую функцию, которая линкуется (и будет проверен прототипом) если макроопределение запрещено с помощью #undef.

Библиотека панелей

Сама библиотека ncurses обеспечивает хорошую поддержку для отображения экранов, в которых окна расположены черепицей (не перекрываются). В большинстве случаев, в которых окна могут перекрываться, ты можешь использовать серию вызовов wnoutrefresh() и doupdate(), и позаботиться о порядке выполнения регенерации окон. Порядок должен быть снизу вверх, иначе части окна, которые должны быть закрыты будут торчать наружу.

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

Библиотека панелей впервые появилась в AT&T System V. Версия, описываемая здесь - это свободный код панелей, распространяющийся с ncurses.

Компиляция программ, использующих библиотеку панелей

Твои модули, использующие панели, должны импортировать объявление о библиотеке панелей
	  #include <panel.h>
и должны линковаться с помощью аргумента -lpanel. Обратите внимание, что они также должны линковаться с библиотекой ncurses с помощью -lncurses. Большинство современных линкеров двухпроходные и разрешают любой порядок, но все еще хорошей практикой является писать -lpanel первым и -lncurses вторым.

Краткий обзор панелей

Объект панель - это окно, которое рассматривается как часть стола, включающего все другие объекты панелей. Стол имеет самый верхний порядок видимости. Библиотека панелей включает функцию update (аналог refresh()), которая отображает все панели на столе в правильном порядке, рассчитывая перекрытия. Стандартное окно, stdscr, считается ниже всех панелей.

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

Панель создается из окна вызовом new_panel() с указателем на окно. Она появится наверху стола. Панельное окно доступно как значение panel_window(), вызываемое с указателем панели как аргумента.

Ты можешь удалить панель (убрать со стола) с помощью del_panel. Это не удаляет связанное окно; тебе нужно сделать это самому. Чтобы заместить панельное окно другим окном вызывается replace_window. Новое окон может быть другого размера; код панели пересчитает все перекрытия. Эта операция не изменяет позицию окна на столе.

Для перемещения окна используется move_panel(). Функции mvwin() с панельным окном недостаточно, потому что она не изменяет представление панельной библиотеки о том где теперь находится окно. Эта операция не затрагивает глубину, содержание и размер.

Две функции (top_panel(), bottom_panel()) позволяют переложить по-новому стол. Первая выталкивает окно аргумент наверх стола; вторая опускает его вниз. Обе операции оставляют неизменными расположение панели, содержание и размер.

Функция update_panels() выполняет все необходимые вызовы wnoutrefresh(), подготавливая для doupdate() (который ты должен вызвать потом сам).

Обычно, ты захочешь вызывать update_panels() и doupdate() только перед применением команд ввода, один раз в цикле взаимодействия с пользователем. Если ты вызываешь update_panels() после каждого взаимодействия и даже пишешь в панель, то этим ты создаешь много ненужных регенераций и экран мигает.

Панели, ввод и стандартный экран

Ты не должен смешивать операции wnoutrefresh() или wrefresh() с кодом панелей; это сработает если только окно аргумент находится в верхней панели или оно не закрыто другими панелями.

Окно stsdcr это особенный случай. Считается, что оно ниже всех панелей. Однако ты должен вызывать update_panels() перед doupdate() даже когда ты изменяешь только stdscr, потому что изменения панелей может закрыть часть stdscr.

Обратите внимание, что wgetch автоматически вызывает wrefresh. Поэтому, перед запросом ввода из панельного окна, ты должен быть уверен что что панель не перекрыта полностью.

В настоящее время нет пути отобразить изменения одной закрываемой панели без перерисовки всех панелей.

Сокрытие панелей

Есть возможность временно удалить панель со стола; для этого используется hide_panel. Можно показать панель опять с помощью show_panel(). Вызов функции panel_hidden проверяет скрыто окно или нет.

Код panel_update игнорирует скрытые панели. Ты не можешь выполнить top_panel() или bottom_panel на скрытой panel(). Другие операции с панелями возможны.

Другие различные возможности

Возможно перемещаться по столу с помощью функций panel_above() и panel_below. Взяв указатель на панель, они возвращают панель выше и панель ниже чем эта панель. Если указатель NULL, они возвращают самую нижнюю и самую верхнюю панель.

Каждая панель имеет связанный с ней указатель пользователя, который не используется кодом панели, и к которому ты можешь присоединить данные приложения. Смотри man страницу документации о set_panel_userptr() и panel_userptr.

Библиотека меню

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

Библиотека меню впервые появилась в AT&T System V. Версия, описываемая здесь - это свободный код меню, распространяющийся с ncurses.

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

Твои модули, использующие панели, должны импортировать объявление о библиотеке меню
	  #include <menu.h>
и должны линковаться с помощью аргумента -lmenu. Обратите внимание, что они также должны линковаться с библиотекой ncurses с помощью -lncurses. Большинство современных линкеров двухпроходные и принимают любой порядок, но все еще хорошей практикой является писать -lmenu первым и -lncurses вторым.

Краткий обзор меню

Меню, создаваемое библиотекой, состоит из набора элементов, включающих две части: название и описание. Для создания меню нужно создать группу этих элементов и соединить их с системой объектов меню.

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

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

Общий поток управления программой с меню выглядит так:

  1. Инициализировать curses.
  2. Создать элементы меню, используя new_item().
  3. Создать меню, используя new_menu().
  4. Поместить меню, используя menu_post().
  5. Регенерировать экран.
  6. Обработать пользовательские запросы в цикле ввода.
  7. Убрать меню, используя menu_unpost().
  8. Удалить меню, используя free_menu().
  9. Удалить элементы меню, используя free_item().
  10. Завершить curses.

Выбор элементов

Меню может быть многозначным или (по умолчанию) однозначным (смотри на страницах руководства menu_opts(3x) как изменить установку по умолчанию). Оба типа всегда имеют текущий элемент.

Из однозначных меню ты можешь узнать выбранное значение просто прочитав текущий элемент. Из многозначных меню ты можешь получить выбранный набор с помощью цикла через элементы, применив к ним функцию предикат item_value(). Твой код обработки меню может использовать функцию set_item_value() чтобы добавить элемент в выбранный набор.

Элементы меню могут быть сделаны невыбранными с помощью set_item_opts() или item_opts_off() с аргументом O_SELECTABLE. Это только для одной определенной опции меню, но это является хорошей практикой в коде если быть другие биты опции могли бы быть включены.

Отображение меню

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

  • Число и максимальная длина элементов меню
  • Разрешена ли опция O_ROWMAJOR
  • Разрешено ли отображение описания
  • Задан ли формат меню программистом
  • Длина строки маркера меню, используемой для выделения выбранных элементов
Функция set_menu_format() позволяет установить максимальный размер области вывода или страницы меню, которая будет использоваться для отображения элементов меню. Возможно получить любой формат меню с помощью menu_format(). Формат по умолчанию строк=16, столбцов=1.

Страница меню может быть меньше форматного размера. Это зависит от числа элементов и размера и установлен ли O_ROWMAJOR. Эта опция (по умолчанию включена) заставляет элементы меню отображаться по `растровому' шаблону, так что если есть более чем один элемент, то они будут располагаться горизонтально бок о бок первой паре элементов в верхней строке. Альтернативой является отображение по колонкм, которое пытается поместить несколько первых элементов в первую колонку.

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

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

Каждое меню имеет строку маркер, использующуюся в качестве визуальной отметки выбранных элементов; подробнее смотри страницу руководства menu_mark(3x). Длина строки маркера также влияет на размер страницы меню.

Функция scale_menu() возвращает минимальный отображаемый размер, который код меню вычислил исходя из всех условий. Также, есть другие атрибуты отображения меню, включающие атрибут выбранности, атрибут для выбираемых элементов, атрибут для не выбираемых элементов и символ разделитель, используемый для отделения названия элемента от текста описания. По умолчанию они установлены в приемлемые значения, которые библиотека позволяет изменить (смотри страницу руководства menu_attribs(3x).

Окна меню

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

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

По умолчанию, оба окна stdscr. Ты можешь установить их с помощью функции menu_win(3x).

Когда ты вызываешь menu_post(), ты пишешь меню в подокно. Когда ты вызываешь menu_unpost(), ты стираешь подокно, однако, ни одна из этих операций на самом деле не изменяет экран. Чтобы сделать это вызывается wrefresh() или что-то подобное.

Обработка ввода в меню

Главный цикл обработчика меню должен периодически вызывать menu_driver(). Первый аргумент этой процедуры - указатель меню; второй - это код команды меню. Ты должен написать процедуру, которая транслировала бы вводимые символы в коды команд меню, и передавала их в menu_driver(). Коды команд меню полностью описаны в menu_driver(3x).

Простейшая группа кодов команд REQ_NEXT_ITEM, REQ_PREV_ITEM, REQ_FIRST_ITEM, REQ_LAST_ITEM, REQ_UP_ITEM, REQ_DOWN_ITEM, REQ_LEFT_ITEM, REQ_RIGHT_ITEM. Они изменяют текущий выбранный элемент. Эти запросы могут вызывать прокрутку страницы меню, если отображена только её часть.

Есть запросы для прокрутки, которые также изменяют текущий элемент (потому что место выбора не изменилось, но изменился элемент). Это REQ_SCR_DLINE, REQ_SCR_ULINE, REQ_SCR_DPAGE, и REQ_SCR_UPAGE.

REQ_TOGGLE_ITEM устанавливает или отменяет выбор текущего элемента. Его можно использовать только для многозначных меню; если ты попытаешься использовать его с включенным O_ONEVALUE, то получишь ошибку (E_REQUEST_DENIED).

Каждое меню имеет связанный с ним буфер образов. Логика menu_driver() пытается накопить печатные ASCII символы пропуская их через этот буфер; когда префикс совпадает с именем элемента, то элемент (или следующий совпавший элемент) выбирается. Если добавляемый символ не дает новых совпадений, то символ удаляется из буфера образов и menu_driver() возвращает E_NO_MATCH.

Некоторые запросы изменяют буфер образов напрямую: REQ_CLEAR_PATTERN, REQ_BACK_PATTERN, REQ_NEXT_MATCH, REQ_PREV_MATCH. Последние два полезны когда ввод в буфер образов дает более одного совпавшего элемента в многозначном меню.

Каждая успешная прокрутка или перемещение по элементам вызывает очистку буфера образов. Также возможно точно настроить буфер образов с помощью set_menu_pattern().

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

Другие различные возможности

Различные опции меню могут влиять на процесс и внешний вид или обработку ввода в меню. Подробнее смотри menu_opts(3x).

Возможно изменять текущий элемент из кода приложения; это полезно, если ты хочешь писать свои собственные запросы перемещения. Также возможно точно установить верхнюю строку меню. Смотри mitem_current(3x). Если твоему приложению нужно изменить курсор подокна меню по какой-то причине, pos_menu_cursor() восстановит его в правильное положение для дальнейшей работы драйвера меню.

Возможно установить обработчики, вызываемые во время инициализации и завершения кода меню, и всякий раз когда изменяется выбранный элемент. Смотри menu_hook(3x).

Каждый элемент и каждое меню имеет связанный с ним пользовательский указатель, на который можно повесить данные приложения. Смотри mitem_userptr(3x) и menu_userptr(3x).

Библиотека форм

Библиотека форм это расширение curses для поддержки легкого программирования экранных форм ввода данных и контроля за программой.

Библиотека форм впервые появилась в AT&T System V. Версия, описываемая здесь - это свободный код форм, распространяющийся с ncurses.

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

Твои модули, использующие формы, должны импортировать объявление о библиотеке форм
	  #include <form.h>
и должны линковаться с помощью аргумента -lform. Обратите внимание, что они также должны линковаться с библиотекой ncurses с помощью -lncurses. Большинство современных линкеров двухпроходные и принимают любой порядок, но все еще хорошей практикой является писать -lform первым и -lncurses вторым.

Краткий обзор форм

Формы - это набор полей; каждое поле может быть или ярлыком (пояснительный текст) или полем ввода данных. Длинные формы могут быть разделены на страницы; каждый ввод новой страницы очищает экран.

Чтобы сделать форму, тебе нужно создать группу полей и подсоединить её к объекту форме-рамки; с библиотекой форм это делается относительно просто.

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

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

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

Общий поток управления программой с формой выглядит так:

  1. Инициализировать curses.
  2. Создать поля формы, используя new_field().
  3. Создать форму, используя new_form().
  4. Поместит форму, используя form_post().
  5. Регенерировать экран.
  6. Обработать пользовательские запросы в цикле ввода.
  7. Убрать форму, используя form_unpost().
  8. Освободить форму, используя free_form().
  9. Освободить поля, используя free_field().
  10. Завершить curses.
Обратите внимание, что это выглядит как программа с меню; библиотека форм выполняет задачи, которые во многом похожи, и её интерфейс естественно разрабатывался так чтобы быть похожим на библиотеку меню где только можно.

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

Создание и удаление полей и форм

Основная функция для создания полей это new_field():

FIELD *new_field(int height, int width,   /* габариты нового поля  */ 
                 int top, int left,       /* верхний левый угол    */
                 int offscreen,           /* число offscreen строк */
                 int nbuf);               /* число рабочих буферов */
Элементы меню всегда занимают одну строку, но поля формы могут иметь несколько строк. Поэтому в new_field() тебе необходимо указывать ширину и высоту (первые два аргумента, оба должны быть больше нуля).

Ты должен также указать местоположение левого верхнего угла поля на экране (третий и четвёртый аргументы, должны быть ноль или больше). Обратите внимание, что эти координаты относительно подокна формы, которое по умолчанию совпадает с stdscr, но не должно быть stdscr, если ты вызываешь set_form_window().

Пятый аргумент позволяет тебе указать число off-screen строк. Если он равен нулю, то поле всегда отображается целиком. Если он не ноль, то форму можно будет прокручивать, только с one screen-full (изначально верхняя часть) отображаемом в любое заданное время. Если ты делаешь поле динамическим и увеличиваешь его так что оно больше не помещается в экран, то форма станет прокручиваемой даже если аргумент offscreen был равен нулю.

Библиотека форм выделяет один рабочий буфер на поле; размер каждого буфера равен ((высота + offscreen)*ширина + 1, один символ для каждой позиции в поле плюс конечный NUL. Шестой аргумент это число дополнительных буферов данных, выделяемых для поля; твое приложение может использовать их для своих собственных целей.

FIELD *dup_field(FIELD *field,            /* поле для копирования */
                 int top, int left);      /* позиция новой копии  */
Функция dup_field() копирует существующее поле в новую позицию. Копируется информация о размере и буферах; некоторые флаги атрибутов и биты статуса нет (подробнее смотри в form_field_new(3X)).

FIELD *link_field(FIELD *field,           /* поле для копирования */
                  int top, int left);     /* позиция новой копии  */
Функция link_field() также копирует существующее поле в новую позицию. Отличие от dup_field() в том что она делает буфер нового поля совместным для использования со старым полем.

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

Как и скопированные поля, связанные поля имеют отдельные биты атрибутов.

Как ты уже наверно догадался, все эти создания полей возвращают NULL если выделение поля невозможно из-за нехватки памяти или из-за выходящих за границы аргументов.

Для подключения полей к форме, используй

FORM *new_form(FIELD **fields);
Этой функции необходим массив указателей на поля, оканчивающийся NULL. Если поля подключились к только что созданному объекту форме, то возвращается его адрес (в противном случае NULL, если выделение не удалось осуществить).

Обратите внимание, что new_field() не копирует массив указателей в свое хранилище; если ты изменишь содержимое массива указателей во время работы формы, то могут произойти всевозможные странные вещи. Также заметим, что любое заданное поле может быть подсоединено только к одной форме.

Функции free_field() и free_form существуют для удаления объектов поля и формы. Может появиться ошибка при попытке удалить поле подключенное к форме, но не наоборот; поэтому, для большинства случаев удаляй объект формы первым.

Получение и изменение атрибутов поля

Каждое поле формы имеет связанные с ним атрибуты расположения и размер. Также есть другие атрибуты поля, используемые для контроля за отображением и редактированием поля. Некоторые (например, O_STATIC бит) - достаточно сложные, раскрываются в своих собственных разделах позже. Мы опишем здесь функции, используемые для определения и установки некоторых основных атрибутов.

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

Получение данных о размере и расположении

Ты можешь получить размеры поля и местоположение с помощью:

int field_info(FIELD *field,              /* поле, из которого получаем */
               int *height, *int width,   /* размер поля                */ 
               int *top, int *left,       /* верхний левый угол         */
               int *offscreen,            /* число offscreen строк      */
               int *nbuf);                /* число рабочих буферов      */
Эта функция типа new_field(), но наоборот; вместо установки атрибутов размеров и местоположения нового поля, она возвращает их из существующего.

Изменение расположения поля

Если возможно, можно переместить поле на экране:

int move_field(FIELD *field,              /* изменяемое поле    */
               int top, int left);        /* новый верхний угол */
Конечно, можно узнать текущее местоположение с помощью field_info().

Атрибуты выравнивания

Одностроковые поля могут быть невыровнены, выровнены по правому краю, выровнены по левому краю, или по центру. Вот как работать с этим атрибутом:

int set_field_just(FIELD *field,          /* изменяемое поле       */
                   int justmode);         /* устанавливаемый режим */

int field_just(FIELD *field);             /* получить режим поля   */
Значения режима, принимаемые и возвращаемые этими функциями, определены макросами препроцессора NO_JUSTIFICATION, JUSTIFY_RIGHT, JUSTIFY_LEFT, или JUSTIFY_CENTER.

Атрибуты отображения поля

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

Эта группа из четырех атрибутов поля контролирует визуальное отображение поля на экране, никак не влияя на данные в буфере поля.

int set_field_fore(FIELD *field,          /* изменяемое поле */
                   chtype attr);          /* устанавливаемый атрибут */ 

chtype field_fore(FIELD *field);          /* опрашиваемое поле */

int set_field_back(FIELD *field,          /* изменяемое поле */
                   chtype attr);          /* устанавливаемый атрибут */ 

chtype field_back(FIELD *field);          /* опрашиваемое поле */

int set_field_pad(FIELD *field,           /* изменяемое поле */
                 int pad);                /* устанавливаемый pad символ */ 

chtype field_pad(FIELD *field);

int set_new_page(FIELD *field,            /* изменяемое поле */
                 int flag);               /* TRUE для новой страницы */ 

chtype new_page(FIELD *field);            /* опрашиваемое поле */
Атрибуты, устанавливаемые и возвращаемые первыми четырьмя функциями - обычные значения атрибутов дисплея curses(3x) (A_STANDOUT, A_BOLD, A_REVERSE и т.д.). Бит страницы поля контролирует отображение начала новой экранной формы.

Биты опций поля

Также имеется большой набор битов опций поля, которые ты можешь устанавливать, чтобы контролировать различные аспекты работы формы. Ты можешь манипулировать ими с помощью следующих функций:
int set_field_opts(FIELD *field,          /* изменяемое поле */
                   int attr);             /* устанавливаемый атрибут */ 

int field_opts_on(FIELD *field,           /* изменяемое поле */
                  int attr);              /* включить атрибут */ 

int field_opts_off(FIELD *field,          /* изменяемое поле */
                   int attr);             /* выключить атрибут */

int field_opts(FIELD *field);             /* опрашиваемое поле */
По умолчанию, все опции включены. Вот доступные биты опций:
O_VISIBLE
Управляет видимостью поля на экране. Может использоваться во время работы с формой чтобы скрыть или показать поле в зависимости от значения родительских полей.
O_ACTIVE
Контролирует, будет ли поле активным во время работы с формой (т.е. можно ли в него попасть с помощью клавиш перемещения). Может использоваться для создания ярлыков или производных полей с изменяемым буфером значений с помощью форм приложения, а не с помощью пользователя.
O_PUBLIC
Контролирует, будут ли данные отображаться во время ввода в поле. Если эта опция поля выключена, то библиотека принимает и редактирует данные в этом поле, но не показывает их и видимый в поле курсор не перемещается. Ты можешь выключать O_PUBLIC бит в полях ввода пароля.
O_EDIT
Контролирует, можно ли изменять данные поля. Когда эта опция выключена, все запросы на редактирование кроме REQ_PREV_CHOICE и REQ_NEXT_CHOICE выполняться не будут. Такие только-для-чтения поля могут быть полезны для сообщений помощи.
O_WRAP
Контролирует перенос слов в многостроковых полях. Обычно, когда любой символ (отделённого пробелом) слова достигает конца текущей строки, всё слово переносится на следующую строку (надеюсь, что одно останется). Когда эта опция выключена, слово будет разбито концом строки.
O_BLANK
Контролирует пустоту поля. Когда эта опция включена, вводимый символ в первую позицию поля сотрет все поле (исключая только что введенный символ).
O_AUTOSKIP
Контролирует автоматический переход в следующее поле когда это закончилось. Обычно, когда пользователь формы пытается ввести больше данных в поле чем в него влезает, положение редактирования перескакивает на следующее поле. Когда эта опция выключена, пользовательский курсор останется в конце поля. Эта опция игнорируется в динамических полях, которые не имеют ограничений на размер.
O_NULLOK
Контролирует, будет ли правильность применяться к пустым полям. Обычно нет; пользователь может оставить поле пустым без вызова обычной проверки правильности в конце. Если эта опция поля выключена, выход из поля вызывает проверку правильности.
O_PASSOK
Контролирует, будет ли проверка правильности производиться при каждом выходе или только после модификации поля. Обычно верно последнее. Установка O_PASSOK может быть полезна, если функция проверки проверки правильности может изменяться во время работы формы.
O_STATIC
Контролирует, будет ли поле постоянным по отношению к своим начальным размерам. Если ты это выключишь, поле станет динамическим и будет растягиваться, чтобы вместить вводимые данные.
Опции поля не могут изменяться если поле выбрано в настоящий момент. Однако, опции могут изменяться в помещенных полях, если по не является текущим.

Значения опции это битовые маски и могут быть составлены с помощью логического or.

Статус поля

Каждое поле имеет флаг статуса, которые устанавливается в FALSE, когда поле создано и TRUE когда значение в буфере поля имеет 0 изменений. Этот флаг можно опрашивать и устанавливать напрямую:

int set_field_status(FIELD *field,      /* изменяемое поле */
                   int status);         /* устанавливаемый режим */

int field_status(FIELD *field);         /* получить режим поля */
Установка этого флага с помощью программы может быть полезна если ты периодически используешь одну и ту же форму, ища каждый раз измененные поля.

Вызов field_status() с не выбранным в настоящий момент для ввода полем возвратит правильное значение. Вызов field_status() с полем, которое выбрано в настоящий момент для ввода может не дать необходимого правильного значения статуса поля, потому что вводимые данные еще не скопированы в нулевой буфер т.к. не прошли проверку правильности при выходе. Чтобы гарантировать что возвращаемое значение статуса отражает реальность, вызови field_status() с: (1) в процедуре проверки правильности поля, (2) из обработчиков инициализации или завершения работы поля, или (3) только после обработки запроса REQ_VALIDATION драйвером формы.

Пользовательский указатель поля

Каждая структура поля содержит один символьный указатель, который не используется библиотекой форм. Предполагается, что он будет использован приложением для сохранения своих данных именно для этого поля. Ты можешь работать с ним:
int set_field_userptr(FIELD *field,       /* изменяемое поле */
                   char *userptr);        /* устанавливаемый режим */

char *field_userptr(FIELD *field);        /* получить режим поля */
(Строго говоря, пользовательский указатель в поле должен иметь (void *) тип. Тип (char *) сохранен для совместимости с System V.)

Можно правильно настроить пользовательский указатель в поле по умолчанию (вызовом set_field_userptr() с аргументом поля равным NULL.) Когда создается новое поле, пользовательский указатель поля по умолчанию копируется в пользовательский указатель нового поля.

Поля с изменяемым размером

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

Одностороковое динамическое поле имеет постоянную высоту (1) но переменную ширину, прокручиваемую горизонтально, чтобы показать данные поля не изменяя его размеров и местоположения. Многостроковое динамическое поле имеет постоянную ширину, но переменную высоту (несколько строк), прокручиваемую вертикально, чтобы показать данные поля не изменяя его размеров и местоположения.

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

int set_max_field(FIELD *field,     /* изменяемое поле (не может быть NULL) */
                   int max_size);   /* верхний предел размера поля */ 
Если поле одностроковое, то max_size принимается равным числу колонок; если оно многостроковое, то принимается равным числу строк. Чтобы запретить любое ограничение, используй нулевой аргумент. Ограничительный размер можно изменять независимо установлен или нет O_STATIC бит, но не будет никакого эффекта пока его не установишь.

Следующие свойства поля изменяются, когда оно стало динамическим:

  • Если нет предела роста, то нет окончательной позиции поля; поэтому O_AUTOSKIP и O_NL_OVERLOAD игнорируются.
  • Выравнивание поля будет игнорироваться (хотя любое установленное выравнивание внутренне сохраняется и может быть запрошено).
  • Вызовы dup_field() и link_field() копируют размеры динамических буферов. Если опция O_STATIC установлена в одной из ссылок, изменение размера буфера произойдет только когда поле будет редактироваться через эту ссылку.
  • Вызов field_info() возвратит первоначальный статический размер поля; используй dynamic_field_info() чтобы получить настоящий динамический размер.

Правильность заполнения поля

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

Проверка правильности поля (если есть) не производится когда set_field_buffer() модифицирует буфер ввода, и когда этот буфер изменяется через ссылку на это поле.

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

int set_field_type(FIELD *field,          /* изменяемое поле */
                   FIELDTYPE *ftype,      /* связываемый тип */
                   ...);                  /* дополнительные аргументы */

FIELDTYPE *field_type(FIELD *field);      /* опрашиваемое поле */
Правильный тип поля считается атрибутом поля. Также как и для других атрибутов поля, выполнение set_field_type() с полем NULL изменяет системные установки правильности по умолчанию для вновь создаваемых полей.

Вот предопределенные типы правильности:

TYPE_ALPHA

Этот тип поля принимает алфавитные данные; без пробелов, без цифр, без специальных символов (это проверяется во время ввода символа). Вот как это настраивается:

int set_field_type(FIELD *field,          /* изменяемое поле */
                   TYPE_ALPHA,            /* связываемый тип */
                   int width);            /* максимальная ширина поля */
Аргумент width определяет минимальную ширину данных. Обычно, ты захочешь установить его равным ширине поля; если оно больше ширины поля, то проверка правильности всегда будет терпеть неудачу. Минимальная ширина ноль делает конец поля необязательным.

TYPE_ALNUM

Этот тип поля принимает алфавитные и цифровые данные; без пробелов, без специальных символов (это проверяется во время ввода символа). Вот как это настраивается:

int set_field_type(FIELD *field,          /* изменяемое поле */
                   TYPE_ALNUM,            /* связываемый тип */
                   int width);            /* максимальная ширина поля */
Аргумент width определяет минимальную ширину данных. Как с TYPE_ALPHA, обычно ты захочешь установить его равным ширине поля; если оно больше ширины поля, то проверка правильности всегда будет терпеть неудачу. Минимальная ширина ноль делает конец поля необязательным.

TYPE_ENUM

Этот тип позволяет тебе ограничить значения поля указанным набором строковых значений (например, двухстроковые почтовые коды для U.S. штатов). Вот как это настраивается:

int set_field_type(FIELD *field,          /* изменяемое поле */
                   TYPE_ENUM,             /* связываемый тип */
                   char **valuelist;      /* список возможных значений */
                   int checkcase;         /* регистрозависимо? */
                   int checkunique);      /* должны быть уникальными? */
Параметр valuelist должен указывать на NULL-завершающийся список правильных строк. Аргумент checkcase, если true, делает сравнение строк регистрозависимым.

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

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

Запросы ввода REQ_NEXT_CHOICE и REQ_PREV_CHOICE могут быть особенно полезны этим полям.

TYPE_INTEGER

Поле с этим типом принимает целые числа. Вот как это настраивается:

int set_field_type(FIELD *field,          /* изменяемое поле */
                   TYPE_INTEGER,          /* связываемый тип */
                   int padding,           /* # places to zero-pad to */
                   int vmin, int vmax);   /* допустимый диапазон */
Правильные символы состоят из необязательного предшествующего минуса и цифр. Проверка диапазона выполняется при выходе. Если верхняя граница диапазона меньше или равна нижней, то диапазон игнорируется.

Если значение прошло проверку диапазона, то оно заполняется необходимым количеством ведущих нулей.

При TYPE_INTEGER буфер значения можно удобно интерпретировать с помощью функции библиотеки C atoi(3).

TYPE_NUMERIC

Поле с этим типом принимает дробные числа. Вот как это настраивается:

int set_field_type(FIELD *field,              /* изменяемое поле */
                   TYPE_NUMERIC,              /* связываемый тип */
                   int padding,               /* # places of precision */
                   double vmin, double vmax); /* допустимый диапазон */
Правильные символы состоят из необязательного предшествующего минуса и цифр, возможно включение десятичного разделителя. Если твоя система поддерживает локаль, используемый символ десятичного разделителя может быть определен твоей локалью. Проверка диапазона выполняется при выходе. Если верхняя граница диапазона меньше или равна нижней, то диапазон игнорируется.

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

При TYPE_NUMERIC буфер значения можно удобно интерпретировать с помощью функции библиотеки C atof(3).

TYPE_REGEXP

Поле с этим типом принимает данные соответствующие регулярному выражению. Вот как это настраивается:

int set_field_type(FIELD *field,          /* изменяемое поле */
                   TYPE_REGEXP,           /* связываемый тип */
                   char *regexp);         /* выражения для проверки */
Синтаксис регулярного выражения тот же что и regcomp(3). Проверка на соответствие регулярному выражению выполняется при выходе.

Прямая работа с буфером поля

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

char *field_buffer(FIELD *field,          /* опрашиваемое поле */
                   int bufindex);         /* номер опрашиваемого буфера */
Обычно, состояние нулевого буфера каждого поля устанавливается пользователем во время операции редактирования этого поля. Но иногда полезно установить значение нулевого буфера(или какого-то другого) из приложения:
int set_field_buffer(FIELD *field,        /* изменяемое поле */
                   int bufindex,          /* номер изменяемого буфера */
                   char *value);          /* устанавливаемое строковое значение */
Если размера поля не хватает и оно не может измениться до достаточно большего размера чтобы содержать указанное значение, то значение обрезается до подходящего.

Вызов field_buffer() с null указателем поля вызовет ошибку. Вызов field_buffer() с полем, невыбранным в данный момент для ввода возвратит правильное значение. Вызов field_buffer() с полем, выбранным в данный момент для ввода может необязательно дать правильный размер буфера поля, потому что вводимые данные еще не скопированы в нулевой буфер, т.к. не прошли проверку правильности при выходе. Чтобы гарантировать что возвращаемое значение статуса отражает реальность, вызови field_status() с: (1) в процедуре проверке правильности поля, (2) из хуков инициализации или завершения работы поля, или (3) только после обработки запроса REQ_VALIDATION драйвером формы.

Атрибуты формы

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

Главный атрибут формы - это ее список полей. Ты можешь запросить или или изменить этот список с помощью:

int set_form_fields(FORM *form,           /* изменяемая форма */
                    FIELD **fields);      /* подключаемые поля */

char *form_fields(FORM *form);            /* получить поля формы */

int field_count(FORM *form);              /* количество подключенных полей */
Второй аргумент set_form_fields() может быть массивом указателей полей, заканчивающийся NULL, типа как в new_form(). В этом случае, старые поля формы отключаются, но не удаляются (и их можно подключить к другой форме), а новые поля подключаются.

Он также может быть null, в этом случае старые поля отключаются (и не удаляются), но никаких новых полей подключено не будет.

Функция field_count() просто считает число полей, подключенных к заданной форме. Она возвращает -1, если аргумент указатель формы равен NULL.

Управление отображением формы

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

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

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

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

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

int scale_form(FORM *form,                /* опрашиваемая форма */
               int *rows,                 /* строк в форме */
               int *cols);                /* колонок в форме */
Размеры формы возвращаются в места указанные аргументами. Как только ты получил эту информацию, ты можешь использовать ее для задания окон, используя одну из следующих функций:
int set_form_win(FORM *form,              /* изменяемая форма */
                 WINDOW *win);            /* подключаемое окно рамка */

WINDOW *form_win(FORM *form);             /* получить окно рамку формы */

int set_form_sub(FORM *form,              /* изменяемая форма */
                 WINDOW *win);            /* подключаемое подокно */

WINDOW *form_sub(FORM *form);             /* получить подокно формы */
Заметим, что операции curses, включая refresh(), на форме должны выполняться на окне рамке, а не подокне формы.

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

int data_ahead(FORM *form);               /* опрашиваемая форма */ 

int data_behind(FORM *form);              /* опрашиваемая форма */ 
Функция data_ahead() возвращает TRUE, если (a) текущее поле однострочное и имеет непоказанные данные справа, (b) текущее поле многострочное и в нем есть данные, выходящие за нижний предел.

Функция data_behind() возвращает TRUE, если первая (сверху слева) символьная позиция находится за пределами экрана (не отображена).

Наконец, есть функция, восстанавливающая значение курсора окна формы, ожидаемое драйвером формы:

int pos_form_cursor(FORM *)               /* опрашиваемая форма */
Если приложение изменяет курсор окна формы, вызови эту функцию перед возвратом управления обратно драйверу формы чтобы пересинхронизировать его.

Обработка ввода в драйвере формы

Функция form_driver() обрабатывает виртуальные запросы ввода для перемещения по форме, редактирования, и запросы правильности, так же как menu_driver делает это для меню ( смотри раздел по обработке ввода в меню).

int form_driver(FORM *form,               /* форма для ввода запроса */
                int request);             /* код запроса формы */
Твоей виртуализационной функции ввода необходимо принять ввод и сконвертить его или в алфавитноцифровой символ (который понимается как вводимые данные в выбранное поле в настоящий момент), или в обрабатываемый запрос формы.

Драйвер формы обеспечивает обработчик (через функции правильности ввода и удаления поля) с помощью которого код твоего приложения может проверить какой ввод ожидается из драйвера.

Запросы перемещения страниц

Эти запросы приводят к перемещению по страницам в форме, переключаясь на новые экраны формы.

REQ_NEXT_PAGE
Перейти на следующую страницу формы.
REQ_PREV_PAGE
Перейти на предыдущую страницу формы.
REQ_FIRST_PAGE
Перейти на первую страницу формы.
REQ_LAST_PAGE
Перейти на последнюю страницу формы.
Эти запросы обращаются со списком как с зацикленным; т.е., REQ_NEXT_PAGE с последней страницы перейдет на первую, и REQ_PREV_PAGE с первой страницы перейдет на последнюю.

Запросы перемещения между полями

Эти запросы для перемещения между полями на одной странице.

REQ_NEXT_FIELD
Перейти на следующее поле.
REQ_PREV_FIELD
Перейти на предыдущее поле.
REQ_FIRST_FIELD
Перейти на первое поле.
REQ_LAST_FIELD
Перейти на последнее поле.

REQ_SNEXT_FIELD
Перейти на отсортированное следующее поле.
REQ_SPREV_FIELD
Перейти на отсортированное предыдущее поле.
REQ_SFIRST_FIELD
Перейти на отсортированное первое поле.
REQ_SLAST_FIELD
Перейти на отсортированное последнее поле.

REQ_LEFT_FIELD
Перейти на поле слева.
REQ_RIGHT_FIELD
Перейти на поле справа.
REQ_UP_FIELD
Перейти на поле сверху.
REQ_DOWN_FIELD
Перейти на поле снизу.
Эти запросы обращаются со списком полей на странице как с зацилкенным; т.е., REQ_NEXT_FIELD с последнего поля перейдет на первое, и REQ_PREV_FIELD с первого поля перейдет на последнее. Порядок полей (и для запросов REQ_FIRST_FIELD и REQ_LAST_FIELD) прост - это порядок указателей полей в массиве формы (как было установлено new_form() или set_form_fields()

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

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

Например, предположим, что ты имеешь многостроковое поле B, и два одностроковых поля A и C на одно линии с B, A слева от B и C справа от B. REQ_MOVE_RIGHT от A перейдем в B только если A, B, и C все лежат на одной первой линии; иначе мы пропустим B и попадем в C.

Запросы перемещения внутри поля

Эти запросы перемещают курсор редактирования, находящийся в текущем выбранном поле.

REQ_NEXT_CHAR
Перейти на следующий символ.
REQ_PREV_CHAR
Перейти на предыдущий символ.
REQ_NEXT_LINE
Перейти на следующую строку.
REQ_PREV_LINE
Перейти на предыдущую строку.
REQ_NEXT_WORD
Перейти на следующее слово.
REQ_PREV_WORD
Перейти на предыдущее слово.
REQ_BEG_FIELD
Перейти в начало поля.
REQ_END_FIELD
Перейти в конец поля.
REQ_BEG_LINE
Перейти в начало строки.
REQ_END_LINE
Перейти в конец строки.
REQ_LEFT_CHAR
Перейти левее в поле.
REQ_RIGHT_CHAR
Перейти правее в поле.
REQ_UP_CHAR
Перейти вверх в поле.
REQ_DOWN_CHAR
Перейти вниз в поле.
Каждое слово - это часть строки отделенная с начала и в конце символом пробела. Команды перемещения в начало и в конец строки или поля ищут первый или последний не-pad символ в своих диапазонах.

Запросы прокрутки

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

REQ_SCR_FLINE
Прокрутить вертикально вперед на строку.
REQ_SCR_BLINE
Прокрутить вертикально назад на строку.
REQ_SCR_FPAGE
Прокрутить вертикально вперед на страницу.
REQ_SCR_BPAGE
Прокрутить вертикально назад на страницу.
REQ_SCR_FHPAGE
Прокрутить вертикально вперед на половину страницы.
REQ_SCR_BHPAGE
Прокрутить вертикально назад на половину страницы.
REQ_SCR_FCHAR
Прокрутить горизонтально вперед на символ.
REQ_SCR_BCHAR
Прокрутить горизонтально назад на символ.
REQ_SCR_HFLINE
Прокрутить горизонтально вперед шириной в поле.
REQ_SCR_HBLINE
Прокрутить горизонтально назад шириной в поле.
REQ_SCR_HFHALF
Прокрутить горизонтально вперед шириной на половину поля.
REQ_SCR_HBHALF
Прокрутить горизонтально назад шириной на половину поля.
Для прокрутки, страница поля задается высотой ее видимой части.

Запросы редактирования

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

Следующие запросы поддерживаются при редактировании поля и изменения режима редактирования:

REQ_INS_MODE
Установить режим вставки.
REQ_OVL_MODE
Установить режим замены.
REQ_NEW_LINE
Запрос новой строки (объяснение ниже).
REQ_INS_CHAR
Вставить пробел в место символа.
REQ_INS_LINE
Вставить пустую строку в место символа.
REQ_DEL_CHAR
Удалить символ под курсором.
REQ_DEL_PREV
Удалить предыдущее от курсора слово.
REQ_DEL_LINE
Удалить строку с курсором.
REQ_DEL_WORD
Удалить слово под курсором.
REQ_CLR_EOL
Очистить до конца строки.
REQ_CLR_EOF
Очистить до конца поля.
REQ_CLEAR_FIELD
Очистить все поле.
Работа REQ_NEW_LINE и REQ_DEL_PREV запросов сложна и частично контролируется парой опций форм. Специальные случаи возникают когда курсор находится в начале поля или на последней строке поля.

Во-первых, мы считаем REQ_NEW_LINE:

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

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

Однако, REQ_NEW_LINE в начале поля, или на последней строке поля, вместо этого выполняет REQ_NEXT_FIELD. Если опция O_NL_OVERLOAD выключена, то это специальное действие запрещено.

Теперь, что делается с REQ_DEL_PREV:

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

Однако, REQ_DEL_PREV в начале поле вместо этого выполняется как REQ_PREV_FIELD.

Если опция O_BS_OVERLOAD выключена, то это специальное действие запрещено и драйвер формы только возвращает E_REQUEST_DENIED.

Смотри обсуждение в Опции формы о том как устанавливать и сбрасывать опции перегрузки.

Запросы упорядочивания

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

REQ_NEXT_CHOICE
Поместить следующее значение текущего значения в буфер.
REQ_PREV_CHOICE
Поместить предыдущее значение текущего значения в буфер.
Из встроенных типов данных только TYPE_ENUM имеет встроенные следующие и предыдущие функции. Когда ты определяешь свой тип поля (смотри Правильность типов, определяемая пользователем), ты можешь связать с ним свои функции упорядочивания.

Команды приложения

Запросы формы представляют из себя целые значения выше curses значения больше чем KEY_MAX и меньше или равно константе MAX_COMMAND. Если твоя процедура виртуализации ввода возвращает значение выше MAX_COMMAND, драйвер формы проигнорирует его.

Изменение обработчиков полей

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

typedef void	(*HOOK)();       /* указатель на функцию, возвращающую void */

int set_form_init(FORM *form,    /* изменяемая форма */
                  HOOK hook);    /* обработчик при инициализации */

HOOK form_init(FORM *form);      /* опрашиваемая форма */

int set_form_term(FORM *form,    /* изменяемая форма */
                  HOOK hook);    /* обработчик при удалении */

HOOK form_term(FORM *form);      /* опрашиваемая форма */

int set_field_init(FORM *form,   /* изменяемая форма */
                  HOOK hook);    /* обработчик при инициализации */

HOOK field_init(FORM *form);     /* опрашиваемая форма */

int set_field_term(FORM *form,   /* изменяемая форма */
                  HOOK hook);    /* обработчик при удалении */

HOOK field_term(FORM *form);     /* опрашиваемая форма */
Эти функции позволяют установить или запросить четыре различных обработчика. В каждой функции установки, второй аргумент должен содержать адрес функции обработчика. Эти функции отличаются только временем вызова на выполнение обработчика.

form_init
Этот обработчик вызывается, когда форма помещается; а также, после каждой операции изменения страницы.
field_init
Этот обработчик вызывается, когда форма помещается; а также, после каждого изменения поля.
field_term
Этот обработчик вызывается только после проверки правильности поля; т.е., только перед изменением поля. Также он вызывается когда форма убирается.

form_term
Этот обработчик вызывается когда форма убирается; а также, после каждой операции изменения страницы.
Вызовы этих обработчиков могут сделаны
  1. Когда пользовательский запрос на редактирование обработан драйвером формы
  2. Когда изменилась текущая страница из-за вызова set_current_field()
  3. Когда изменилось текущее поле из-за вызова set_form_page()
Смотри в Команды смены поля обсуждение последних двух случаев.

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

Ты можешь запретить любой из этих обработчиков (пере)установив их в NULL как значение по умолчанию.

Команды смены поля

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

int set_current_field(FORM *form,         /* изменяемая форма */
                      FIELD *field);      /* поле, куда переместиться */

FIELD *current_field(FORM *form);         /* опрашиваемая форма */

int field_index(FORM *form,               /* опрашиваемая форма */
                FIELD *field);            /* получить индекс поля */
Функция field_index() возвращает индекс заданного поля в массиве полей заданной формы (массив, введенный в new_form() или set_form_fields()).

Начальное текущее поле формы - это первое активное поле на первой странице. Функция set_form_fields() сбрасывает это.

Также возможно перемещаться по страницам.

int set_form_page(FORM *form,             /* изменяемая форма */
                  int page);              /* номер нужной страницы (0-начало) */

int form_page(FORM *form);                /* узнать текущую страницу формы */
Начальная страница только что созданной формы - нулевая. Функция set_form_fields() сбрасывает это.

Опции формы

Как и поля, формы могут иметь контрольные биты опций. Они могут изменяться или запрашиваться такими функциями:

int set_form_opts(FORM *form,             /* изменяемая форма */
                  int attr);              /* устанавливаемый атрибут */ 

int form_opts_on(FORM *form,              /* изменяемая форма */
                 int attr);               /* включить атрибуты */ 

int form_opts_off(FORM *form,             /* изменяемая форма */
                  int attr);              /* выключить атрибуты */ 

int form_opts(FORM *form);                /* опрашиваемая форма */
По умолчанию, все опции включены. Вот доступные биты опций:
O_NL_OVERLOAD
Разрешить замену для REQ_NEW_LINE как описано в Запросы редактирования. Значение этой опции игнорируется в динамических полях, которые имеют неограниченный размер; но они не имеют последней строки, поэтому случая возникновения REQ_NEXT_FIELD никогда не будет.
O_BS_OVERLOAD
Разрешить замену для REQ_DEL_PREV как описано в Запросы редактирования.
Значения опций - это битовые маски и они могут объединяться с помощью логического or.

Правильность типов, определяемая пользователем

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

Объединение типов

Простейший путь создать пользовательский тип данных - это собрать его из двух существующих:

FIELD *link_fieldtype(FIELDTYPE *type1, 
                      FIELDTYPE *type2);
Эта функция создает тип поля, который будет принимать любое допустимое значение или одного или другого аргумента типа поля (которые могут быть или предопределены или определены программистом). Если set_field_type() вызывается с последними необходимыми аргументами, то новый составной тип ожидает все аргументы для первого типа, затем все аргументы для второго. Функции упорядочивания (смотри Запросы упорядочивания) связанные с компонентами типа будут работать вместе; что запустит функцию проверки правильности для первого типа, затем для второго.

Новые типы полей

Чтобы создать тип поля с самого начала, тебе нужно указать одну или обе из следующих вещей:

  • Функцию проверки правильности символа, для проверки символа как только он введен.
  • Функцию проверки правильности поля, применяемую при выходе из поля.
Вот как это делается:

typedef int	(*HOOK)();       /* указатель на функцию, возвращающую int */

FIELDTYPE *new_fieldtype(HOOK f_validate, /* field validator */
                         HOOK c_validate) /* character validator */


int free_fieldtype(FIELDTYPE *ftype);     /* освобождаемый тип */
По крайней мере один из аргументов new_fieldtype() не должен быть NULL. Драйвер формы будет автоматически вызывать функции правильности нового типа в соответствующих точках обработки поля нового типа.

Функция free_fieldtype() освобождает аргумент fieldtype, освобождая всё место связанное с ним.

Обычно, a field validator вызывается когда пользователь пытается выйти из поля. Её первый аргумент - указатель поля, из которого берется буфер поля 0 и тестирует его. Если функция возвращает TRUE, то операция прошла успешно; если она вернула FALSE, то курсор редактирования находится в поле.

A character validator берет символ, переданный в качестве первого аргумента. Она тоже возвращает TRUE, если символ правильный, иначе FALSE.

Аргументы функции проверки данных

Твои функции проверки данных поля и символа принимают второй аргумент. Второй аргумент - это адрес структуры (которую мы называем куча), создаваемого из любого типа поля указанного аргумента передаваемого в set_field_type(). Если такой аргумент не определен для типа поля, этот аргумент указатель кучи будет NULL.

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

Вот как сделать такую связь:

typedef char	*(*PTRHOOK)();    /* указатель на функцию, возвращающую (char *) */
typedef void	(*VOIDHOOK)();    /* указатель на функцию, возвращающую void */

int set_fieldtype_arg(FIELDTYPE *type,    /* изменяемый тип */
                      PTRHOOK make_str,   /* делает структуру из args */
                      PTRHOOK copy_str,   /* делает копию структуры */
                      VOIDHOOK free_str); /* освобождает хранилище структуры */
Вот как использовать обработчики управления хранилищем:

make_str
Эта функция вызывается set_field_type(). Она берет один аргумент, va_list типозависимых аргументов, передаваемых в set_field_type(). Она ожидает возврата указатель кучи структуры данных, который энкапсулирует эти аргументы.
copy_str
Эта функция вызывается функциями библиотеки, чтобы разместить новые экземпляры поля. Считается, что она берет указатель кучи, копирует кучу в выделяемое хранилище, и овзвращает адрес этой скопированной кучи.
free_str
Эта функция вызывается библиотечными процедурами удаления поля и типа. Она принимает указатель кучи в качестве аргумента, и счивается, что она освободит место из под этой кучи.
Функции make_str и copy_str могут вернуть NULL, что сигнализирует о неудачном размещении. Процедуры библиотеки, вызывающие их, возвращают ошибку, когда это случается. Таким образом, твое проверочные функции никогда не увидят NULL файлового указателя и им не нужно специально проверять это.

Функции упорядочивания для пользовательских типов

Некоторые пользовательские типы просто упорядочить тем же способом что и TYPE_ENUM. Для этих типов, возможно определить функции следования и предшествования для поддержки запросов REQ_NEXT_CHOICE и REQ_PREV_CHOICE. Вот как:

typedef int	(*INTHOOK)();     /* указатель на функцию, возвращающую int */

int set_fieldtype_arg(FIELDTYPE *type,    /* изменяемый тип */
                      INTHOOK succ,       /* получить следующее значение */
                      INTHOOK pred);      /* получить предыдущее значение*/
Функции следования и предшествования, в каждой, будет приниматься два аргумента; указатель поля, и указатель кучи (как для проверочных функций). Считается, что они используют фунцию field_buffer() для чтения текущего значения, и set_field_buffer() для установки буфера 0 в следующее или предыдущее значение. Любой обработчик может вернуть TRUE, чтобы указать на успешность выполнения (установлено допустимое следующее или предыдущее значение) или FALSE, указывая на неудачу.

Как избежать проблем

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

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

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


Перевод выполнил Юрий Козлов, очень рад замечаниям по адресу off@tsinet.ru.
Оригинальный html находится вместе с исходными текстами ncurses-4.2 в каталоге misc .
06 Окт Сре 06:48:12 MSK 1999
Сергиев-Посадская LUG spslug.sposad.ru