Бібліотека Form

Якщо ви бачили форми веб-сторінок, які дозволяють користувацький ввід і здійснюють різноманітні речі, вас може зацікавити, як можна створити подібні форми у текстовому режимі. Це доволі складне завдання, створення форм за допомогою звичайної бібліотеки ncurses. Саме для цього було створено бібліотеку форм, щоб забезпечити каркасом для легкої побудови та підтримки у робочому стані форм. Ця бібліотека включає велику кількість властивостей (функцій) які керують перевіркою правильності вводу, динамічним розширенням полів і.т.д. Давайте поступово ознайомимося з ними.

Форма - це набір полів; кожне поле може являти собою або етикетку (статичний текст) або місце для вводу даних. Бібліотека форм ткож надає функції для поділу форм на численні сторінки.

Основи

Форми створюються схоже до того, як відбувається у випадку меню. Перш за все, започатковуються поля, зв'язані з формою за допомогою new_field(). Ви можете встановити додаткові опції для полів, тож вони відображатимуться з вишуканими атрибутами, перевірятимуться на правильність перед тим як поле втратить фокус і.т.д. Після цього, поля долучаються до форми, а сама форма виставляється для відображення і готова для отримання вводу. Аналогічно до menu_driver(), керування формами відбувається через form_driver(). Ми можемо надіслати запит form_driver() для переміщення фокусу на інше поле або курсору на кінець поля тощо. Після того, як користувач введе дані у поля і відбудеться перевірка, форму можна відкликати і виділену пам'ять звільнити.

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

  1. Ініціювати бібліотеку curses.
  2. Створити поля, використовуючи new_field(). Ви можете вказати висоту і ширину поля і його місцеположення у формі.
  3. Створити форми за допомогою new_form(), вказуючи поля, що долучаються.
  4. Виставити форму за допомогою form_post() і оновити екран.
  5. Обробити користувацькі запити за допомогою цикла і при необхідності оновити форму за допомогою form_driver().
  6. Відкликати форму за допомогою form_unpost().
  7. Звільнити виділену для форми пам'ять, використовуючи free_form().
  8. Звільнити виділену для об'єктів пам'ять, використовуючи free_field().
  9. Завершити режим curses.

Як ви бачите, робота з бібліотекою форм дуже подібна до оперування бібліотекою меню. Наступні приклади призначені для дослідження різноманітних аспектів керування формами. Давайте розпочнемо з простого прикладу спочатку.

Компіляція з бібліотекою Form

Щоб мати можливість використовувати функції бібліотеки форм, вам необхідно включити файл заголовку form.h у код і зв'язати програму з бібліотекою, вказуючи компілятору -lform разом з -lcurses, саме у цій послідовності.

#include <form.h>
.
.
.

компіляція і зв'язка: gcc <файл програми> -lform -lcurses

Приклад 25. Основи форм

#include <form.h>

int main()
{
    FIELD *field[3];
    FORM *my_form;
    int ch;

    /* ініціалізація curses */
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    /* ініціалізація полів */
    field[0] = new_field(1, 10, 4, 18, 0, 0);
    field[1] = new_field(1, 10, 6, 18, 0, 0);
    field[2] = NULL;

    /* встановити опції полів */
    set_field_back(field[0], A_UNDERLINE); /* атрибут тла: підкреслення  */
    field_opts_off(field[0], O_AUTOSKIP);  /* не переходити на наступне *
                                            * поле, коли це заповнилось */  
    set_field_back(field[1], A_UNDERLINE);
    field_opts_off(field[1], O_AUTOSKIP);

    /* створити форму і виставити її */
    my_form = new_form(field);
    post_form(my_form);
    refresh();

    mvprintw(4, 10, "Value 1:");
    mvprintw(6, 10, "Value 2:");
    refresh();

    /* цикл для отримання користувацьких запитів */
    while((ch = getch()) != KEY_F(1))
    {
        switch(ch)
        {
            case KEY_DOWN:
                /* перейти на інше поле */
                form_driver(my_form, REQ_NEXT_FIELD);
                /* перейти на кінець поточного буферу */
                form_driver(my_form, REQ_END_LINE);
                break;
            case KEY_UP:
                /* перейти до попереднього поля */
                form_driver(my_form, REQ_PREV_FIELD);
                form_driver(my_form, REQ_END_LINE);
                break;
            default:
                /* якщо це звичайний знак, вивести його */
                form_driver(my_form, ch);
                break;
         }
    }
    /* відкликати форму і звільнити пам'ять */
    unpost_form(my_form);
    free_form(my_form);
    free_field(field[0]);
    free_field(field[1]);

    endwin();
    return 0;
}

Приклад зверху є доволі прямолінійним. Він створює два поля, за допомогою new_field(). Функція new_field() візьме висоту, ширину, стартову позицію y, стартову позицію x, число рядків поза екраном і число додаткових робочих буферів. П'ятий аргумент, число рядків поза екраном вказує на те, яку частину поля буде виведено на екрані. Якщо він дорівнює нулю, все поле завжди виводитиметься, у протилежному випадку, форма буде з можливістю прокрутки, коли користувач намагатиметься дістатися до невідображених частин поля. Бібліотека форм виділяє по одному буферу на поле для збереження даних, введених користувачем. Завдяки останньому параметру, ми можемо вказати new_field() виділити додаткові буфери. Вони можуть бути використаними для будь-якої цілі, яку вам завгодно.

Після створення полів, ми встановили атрибут фону у вигляді підкреслення. Опцію AUTOSKIP вимкнено, використовуючи field_opts_off(). Якщо цю опцію ввімкнено, фокус переміститься на наступне поле після того як перше заповниться.

Після долучення полів до форми, остання виставляється. З цього моменту, користувацький ввід опрацьовується через while-цикл, здійснюючи відповідні запити до form_driver(). Подробиці про всі запити до form_driver() оговорено пізніше.

Робота з полями

Кожне поле форми асоційоване з різноманітними атрибутами. Їх можна маніпулювати для отримання бажаного ефекту і просто для забави... Тож приступимо.

Отримання розміру і положення поля

Параметри, задані під час створення поля, можна отримати з допомогою field_info(). Ця функція повертає висоту, ширину, стартову позицію y, стартову позицію x, число невідображених рядків і число додаткових буферів через параметри, надані їй. Це, в своєму роді, зворотня до new_field() функція.

int field_info( FIELD *field,            /* яке поле                  */
                int *height; int width,  /* розмір поля               */
                int *top, int *left,     /* верхній лівий кут         */
                int *offscreen,          /* невідображені рядки       */
                int *nbuff);             /* кількість робочих буферів */

Переміщення поля

Полження поля можна змінити за допомогою move_field().

int move_field( FIELD *field,        /* яке поле                */ 
                int top, int left);  /* новий верхній лівий кут */

Як завжди, нове місцезнаходження можна перевірити за допомогою field_info().

Вирівнювання поля

Вирівнювання поля можна здійснити за допомогою set_field_just().

int set_field_just( FIELD *field,        /* яке поле             */
                    int justmode);       /* режим вирівнювання   */
int field_just( FIELD *field);           /* інформація про режим *
                                          * вирівнювання         */

Режими вирівнювання, що розпізнаються цими функціями, є NO_JUSTIFICATION (без вирівнювання), JUSTIFY_RIGHT (вирівнювання з правої сторони), JUSTIFY_LEFT (вирівнювання з лівої сторони), JUSTIFY_CENTER (вирівнювання по центру).

Атрибути відображення полів

Як ви вже бачили у прикладі вище, атрибути відображення полів можна задати функціями set_field_fore() і set_field_back(). Ці функції встановлюють атрибути переднього і заднього плану поля. Ви можете також вказати символ-заповнювач, який наповнить незайняту частину поля. Символ-заповнювач встановлюється функцією set_field_pad(), стандартно, він має значення пробілу. Функції field_fore(), field_back() і field_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);       /* символ-заповнювач                   */
chtype   field_pad( FIELD, *field); /* яке поле запитати,                  */
                                    /* повертає поточний символ-заповнювач */

Хоч функції вище видаються простими, використання кольорів з set_field_fore() може виявитись заплутаним спочатку. Давайте спочатку розберемося у атрибутах переднього і задьного плану поля. Атрибут переднього плану асоційовано зі знаком. Це означає, що знак буде виведено того кольору, який ви вкажете у set_field_fore(). Атрибут заднього плану використовується для заповнення тла поля, незалежно, чи є там якісь знаки, чи ні. Стосовно кольорів. Оскільки кольори завжди задаються парами, в який саме спосіб правильно відобразити кольорові поля? Ось приклад, який повинен прояснити атрибути кольору.

Приклад 26. Атрибути форми

#include <form.h>

int main()
{
    FIELD *field[3];
    FORM *my_form;
    int ch;

    /* ініціалізація curses */
    initscr();
    start_color();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    /* ініціалізація декількох кольорових пар */
    init_pair(1, COLOR_WHITE, COLOR_BLUE);
    init_pair(2, COLOR_WHITE, COLOR_BLUE);

    /* ініціалізація полів */
    field[0] = new_field(1, 10, 4, 18, 0, 0);
    field[1] = new_field(1, 10, 6, 18, 0, 0);
    field[2] = NULL;

    /* опції полів */
    set_field_fore(field[0], COLOR_PAIR(1));  /* встановити блакитне тло     */
    set_field_back(field[0], COLOR_PAIR(2));  /* і білий передній план       */
    field_opts_off(field[0], O_AUTOSKIP);     /* не переходити до наступного *
                                               * поле, коли це заповнено     */
    set_field_back(field[1], A_UNDERLINE);
    field_opts_off(field[1], O_AUTOSKIP);

    /* створити форму і виставити її */
    my_form = new_form(field);
    post_form(my_form);
    refresh();

    set_current_field(my_form, field[0]);   /* фокус на кольорове поле */
    mvprintw(4, 10, "Value 1:");
    mvprintw(6, 10, "Value 2:");
    mvprintw(LINES - 2, 0, "Use UP, DOWN arrow keys to switch between fields");
    refresh();

    /* цикл обробки запитів користувача */
    while((ch getch()) != KEY_F(1))
    {
        switch(ch)
        {
            case KEY_DOWN:
                /* перейти на наступне поле */
                form_driver(my_form, REQ_NEXT_FIELD);
                /* перейти на кінець поточного буферу */
                form_driver(my_form, REQ_END_LINE);
                break;
            case KEY_UP:
                /* перейти до попереднього поля */
                form_driver(my_form, REQ_PREV_FIELD);
                form_driver(my_form, REQ_END_LINE);
                break;
            default:
                /* якщо це звичайний знак, його буде
                   виведено */
                form driver(my_form, ch);
                break;
        }
    }
    /* відкликати форму і звільнити пам'ять */
    unpost_form(my_form);
    free_form(my_form);
    free_field(field[0]);
    free_field(field[1]);

    endwin();
    return 0;
}

Можете побавитись з кольоровими парами, зрозуміти кольорові атрибути. У моїх програмах з використанням кольору, я, як правило, задаю лише атрибут кольору тла за допомогою set_field_back().

Біти опцій полів

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

int set_field_opts( FIELD *filed,     /* яке поле,               */
                    int attr);        /* встановлюваний атрибут  */
int field_opts_on(  FIELD *field,     /* яке поле,               */
                    int attr);        /* ввімкнути атрибут       */
int field_opts_off( FIELD *field,     /* яке поле,               */
                    int attr);        /* вимкнути атрибут        */
int field_opts( FIELD *field);        /* запит атрибутів поля    */

Функцією set_field_opts() ви можете безпосередньо задати атрибути поля, або вибірково ввімкнути або вимкнути деякі атрибути функціями field_opts_on() або field_opts_off(). Коли завгодно, ви можете здійснити запит атрибутів поля, функцією field_opts(). Наступне є списком наявних опцій. За замовчуванням, усі опції ввімкнено.

O_VISIBLE : Контролює, чи поле видиме на екрані. Може застосовуватись під час обробки форми для приховування чи появи поля, в залежності ві значення інших полів.

O_ACTIVE : Контролює, чи поле активне під час обробки форми (тобто, чи можна його відвідувати, користуючись клавішами навігації). Може використовуватись для створення заголовків або таких полів, чиє значення у буфері змінюється в залежності від обробки даних формою, а не користувачем.

O_PUBLIC : Контролює, чи відображаються дані під час їхнього введення у поле. Якщо цю опцію вимкнено, бібліотека curses дозволить ввід і редагування даних у цьому полі, але вони не відображатимуться і видимий курсор поля не мінятиме положення. Ви можете вимкнути біт O_PUBLICK для створення полів для гасел.

O_EDIT : Контролює, чи дані поля можуть бути зміненими. Коли цю опцію вимкнено, усі запити на редагування поля, за винятком REQ_PREV_CHOICE або REQ_NEXT_CHOICE, зазнають невдачі. Такі поля тільки для читання можуть бути корисними для повідомлень з допомогою.

O_WRAP : Контролює завертання рядка у багаторядкових полях. За звичайних обставин, коли будь-який знак (відокремленого від інших слів побілами) слова досягає кінця поточного рядка, ціле слово переноситься на наступний рядок (за умови, що такий існує). Якщо цю опцію вимкнено, слово буде розбито новим рядком.

O_BLANK : Контролює автоматичне очищення поля. Коли цю опцію ввімкнено, ввід знака у першу позицію поля зітре ціле поле (за винятком щойно введеного знаку).

O_AUTOSKIP : Контролює автоматичний перехід на наступне поле, якщо порточне заповнилось. За звичайних обставин, коли користувач намагається ввести більше даних ніж вміщається, редагування перескакує на наступне поле (якщо таке існує). Якщо цю опцію вимкнено, курсор просто застрягне наприкінці поля. Ця опція ігнорується у динамічних полях, які ще досягли свого обмеження розміру.

O_NULLOK : Контролює, чи перевірка даних відбувається для пустих полів. Звично - ні; користувач може залишити поле порожнім без виклику прийнятої перевірки даних під час виходу. Якщо цю опцію вимкнено, вихід з поля викличе перевірку даних.

O_PASSOK : Контролює, чи перевірка даних відбувається після кожного виходу з поля, або тільки коли зміст поля змінено. За звичайни обставин, останнє є правдою. Встановлення O_PASSOK може бути корисним, у випадку коли функції перевірки даних можуть змінитись під час опрацьовування форми.

O_STATIC : Контролює, чи поле має фіксований розмір. Якщо ви вимкнете цю опцію, поле буде динамічним і розтягуватиметься, щоб вмістити введені дані.

Опції полів не можна змінити, якщо поле є обраним у даний момент, тільки для неактивних полів.

Опції являються бітовими масками і можуть бути складеними через логічне АБО явним способом. Ви вже побачили можливість вимкнення опції O_AUTOSKIP. Наступний приклад проясняє використання деяких з цих опцій. Інші буде пояснено у слушний момент.

Приклад 27. Використання опцій полів

#include <form.h>

#define STARTX 15
#define STARTY 4
#define WIDTH 25

#define N_FIELDS 3

int main()
{
    FIELD *field[N_FIELDS];
    FORM *my_form;
    int ch, i;

    /* ініціалізація curses */
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    /* ініціалізація полів */
    for (i = 0; i < N_FIELDS - 1; ++i)
    field[i] = new_field(1, WIDTH, STARTY + i * 2, STARTX, 0, 0);
    field[N_FIELDS - 1] = NULL;

    /* опції полів */
    set_field_back(field[1], A_UNDERLINE);  /* Print a line for the option  */

    field_opts_off(field[0], O_ACTIVE); /* This field is a static label */
    field_opts_off(field[1], O_PUBLIC); /* This filed is like a password field */
    field_opts_off(field[1], O_AUTOSKIP);   /* To avoid entering the same field */
    /* after last character is entered */

    /* створити форму і виставити її */
    my_form = new_form(field);
    post_form(my_form);
    refresh();

    set_field_just(field[0], JUSTIFY_CENTER);   /* Center Justification */
    set_field_buffer(field[0], 0, "This is a static Field");
    /* Initialize the field  */
    mvprintw(STARTY, STARTX - 10, "Field 1:");
    mvprintw(STARTY + 2, STARTX - 10, "Field 2:");
    refresh();

    /* цикл для отримання запитів користувача */
    while ((ch = getch()) != KEY_F(1)) {
    switch (ch) {
    case KEY_DOWN:
        /* Go to next field */
        form_driver(my_form, REQ_NEXT_FIELD);
        /* Go to the end of the present buffer */
        /* Leaves nicely at the last character */
        form_driver(my_form, REQ_END_LINE);
        break;
    case KEY_UP:
        /* Go to previous field */
        form_driver(my_form, REQ_PREV_FIELD);
        form_driver(my_form, REQ_END_LINE);
        break;
    default:
        /* If this is a normal character, it gets */
        /* Printed                                */
        form_driver(my_form, ch);
        break;
    }
    }

    /* відкликати форму і звільнити пам'ять */
    unpost_form(my_form);
    free_form(my_form);
    free_field(field[0]);
    free_field(field[1]);

    endwin();
    return 0;
}

Цей приклад, хоч і нічого не здійснює, демонструє використання опцій. Якщо вживати їх відповідним чином, вони допоможуть ефективно представляти інформацію форми. Друге поле, маючи скасованим O_PUBLIC, не відображує введених знаків.

Статус поля

Статус поля уточнює, чи поле було редагованим, чи ні. Початково його встановлено до значення FALSE і якщо користувач ввів щось у поле і буфер даних змінено, він отримує значення TRUE. Тож, статус поля можна перевірити, щоб дізнатися, чи редагувалося поле, чи ні. Наступні функції можуть допомогти у цьому.

int set_field_status( FIELD *field,   /* яке поле,              */
                      int status);    /* статус встановити      */
int     field_status( FIELD *field);  /* видобути статус поля   */

Краще перевірити статус поля тільки після виходу з нього, так як буфер даних може ще залишатись не поновленим, так як ще не відбулася перевірка даних. Щоб впевнитись, що повернуто правильний статус, використайте field_status(). Якщо буфер даних поля залишився незмінним, він поверне 0, у протилежному випадку значення буде не-нульовим: 1 - при здійснені рутини перевірки даних поля, 2 - при ініціалізації чи завершенні форми або 3 одразу після обробки запиту REQ_VALIDATION двигуном форми.

Користувацький покажчик на поле

Кожна структура поля містить один покажчик, який може бути використано користувачем для власних цілей, оскільки він не підвласний бібліотеці форм. Наступні функції встановлюють і видобувають користувацький покажчик.

int set_field_userptr( FIELD *field,
                       char *userptr);   /* користувацький покажчик, який  *
                                          * ви хочете асоціювати з полем   */
int    *field_userptr( FIELD *field);    /* добути користувацький покажчик */

Поля зі змінним розміром

Якщо вам потрібні динамічно-змінювані поля різноманітної ширини, це саме той розділ, який вас зацікавить. Динамічні поля дозволяють користувачеві ввести більше даних, ніж початковий розмір поля, даючи змогу його зростання. В залежності від орієнтації поля, воно прокручуватиметься горизонтально або вертикально, щоб вмістити нові дані.

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

field_opts_off(field_pinter, O_STATIC);

Але, звичайно, не радимо дозволяти полю рости нескінчено. Ви можете обмежити зростання поля наступним чином

   int set_max_field( FIELD *field,     /* яке поле, */
                      int max_growth);  /* максимально-дозволене зростання */

Інформацію про динамічно-зростаюче поле можна отримати через

int dynamic_field_info( FIELD *field,    /* яке поле, */
    int *prows,    /* число рядків, які можна заповнити, */
    int *pcols,    /* число стовпчиків, які можна заповнити, */
    int *pmax)     /* максимально-допустиме зростання  */

Хоча, field_info() так само працюватиме, рекомендується використовувати саме dynamic_field_info() для динамічних полів.

Пригадуєте функцію бібліотеки форм new-field()? Нове поле з висотою 1, створене за допомогою цієї функції, стане однорядковим полем. Нове поле, сворене з висотою більшою за один, означатиме багаторядкове поле.

Однорядкове поле із вимкненим O_STATIC (динамічно-зростаюче поле), міститиме лише один рядок, але кількість стовпчиків зростатиме, в залежності від кількості внесених даних. Кількість відображених рядків залишатиметься сталою, додаткові дані прокручуючись вертикально.

Два прараграфи вище, в основновних рисах, стисло описали поводження динамічно-зростаючих полів. Додаткові риси бібліотеки форм описано нище:

  1. Опцію поля O_AUTOSKIP ігноровано, якщо O_STATIC вимкнено і не вказано дозволеного максимального зростання поля. На даний момент, O_AUTOSKIP автоматично здійснює запит REQ_NEXT_FIELD для двигуна форм, коли користувач введе знак до останньої позиції поля. На зростаючих полях, де не вказано обмеження зростання, остання позиція поля відсутня. Якщо максимально-можливе зростання вказано, опція O_AUTOSKIP працюватиме як звичайно, коли поле виросте до свого максимального розміру.
  2. Вирінювання поля ігнорується, коли вимкнено опцію O_STATIC. На сьогодні, set_field_just() може використовуватись для вирівнювання по лівому краю - JUSTIFY_LEFT, вирівнювання по правому краю - JUSTIFY_RIGHT і вирівнювання по центру - JUSTIFY_CENTER вмісту однорядкового поля. Зростаюче однорядкове поле збільшуватиметься горизонтально, з можливістю прокрутки, і може вміщати більше даних, ніж є можливість вирівняти. Повернене значення field_just() залишатиметься незмінним.
  3. Перевантажений запит до двигуна форм REQ_NEW_LINE діятиме так само, незалежно від опції форми O_NL_OVERLOAD, якщо опцію поля O_STATIC вимкено і не вказано дозволеного максимального зростання поля. На даний момент, якщо опцію форми O_NL_OVERLOAD ввімкнено, REQ_NEW_LINE автоматично вмикає REQ_NEW_FIELD, якщо викликано з останнього рядка поля. Якщо поле може зростати без обмежень, останній рядок відсутній, тож REQ_NEW_LINE не викличе REQ_NEW_FIELD. Якщо ж вказано дозволене максимальне зростання, і ввімкнено O_NL_OVERLOAD, REQ_NEW_LINE неявно викличе REQ_NEXT_FIELD, коли поле зросло до свого максимального розміру, і користувач знаходиться на останньому рядку.
  4. Функція бібліотеки dup_field() працюватиме як заведено; вона подвоїть поле, включаюч поточний розмір буферу, та вміст поля. Дозволений максимальний зріст також буде дубльовано.
  5. Функція бібліотеки link_field() працюватиме як звичайно; вона зкопіює всі атрибути поля, і розділить буфер з полем, на яке створено посилання. Якщо опцію O_STATIC згодом змінено одним з полів, що поділяють буфер, те як система реагуватиме на спробу ввести більше даних до поля, ніж буфер спроможний втримати, залежатиме від налагодження цієї опції (O_STATIC) для поточного поля.

Дещо зі сказаного вище має зміст лише після пояснення двигуна форм. Ми займемося цим у наступних розділах.

Вікна форм

Концепція вікон форм дуже схожа до вікон меню. Кожну форму асоційовано з головним вікном і другорядним. Головне вікно відображає заголовок і облямівку. Другорядне вікно містить усі поля і розміщує їх в залежності від іхньої позиції. Це надає гнучкості маніпулюванню складними формами.

Ослільки це дуже подібно на вікна меню, я наведу приклад, навіть без зайвого пояснення. Функції подібні і працюють так само.

Приклад 28. Вікна форми

#include <form.h>

void print_in_middle(WINDOW *win, int starty, int startx, int width,
                     char *string, chtype color);
int main()
{
    FIELD *field[3];
    FORM *my_form;
    WINDOW *my_form_win;
    int ch, rows, cols;

    /* ініціалізація curses */
    initscr();
    start_color();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    /* ініціалізація кольорових пар */
    init_pair(1, COLOR_RED, COLOR_BLACK);

    /* ініціалізація полів */
    field[0] = new_field(1, 10, 6, 1, 0, 0);
    field[1] = new_field(1, 10, 6, 1, 0, 0);
    field[2] = NULL;

    /* опції полів */
    set_field_back(field[0], A_UNDERLINE);
    field_opts_off(field[0], O_AUTOSKIP); /* не переходити на наступне *
                                           * поле, коли це заповнилось */
    set_field_back(field[1], A_UNDERLINE);
    field_opts_off(field[1], O_AUTOSKIP);

    /* створити форму і виставити її */
    my_form = new_form(field);

    /* обчислити площу, яку займе форма */
    scale_form(my_form, &rows, &cols);

    /* створити вікно, пов'язане з формою */
    my_form_win = newwin(rows + 4, cols + 4, 4, 4);
    keypad(my_form_win, TRUE);

    /* заснувати основне і другорядне вікно */
    set_form_win(my_form, my_form_win);
    set_form_sub(my_form, derwin(my_form_win, rows, cols, 2, 2));

    /* вивести облямівку навколо основного вікна і заголовок */
    box(my_form_win, 0, 0);
    print_in_middle(my_form_win, 1, 0, cols + 4, "My Form", COLOR_PAIR(1));

    post_form(my_form);
    wrefresh(my_form_win);

    mvprintw(LINES - 2, 0, "Use UP, DOWN arrow keys to switch between fields");
    refresh();

    /* цикл користувацьких запитів */
    while((ch = wgetch(my_form_win)) != KEY_F(1))
    {
        switch(ch)
        {
            case KEY_DOWN:
                /* перейти на наступне поле */
                form_driver(my_form, REQ_NEXT_FIELD);
                /* перейти на кінець поточного буферу */
                form_driver(my_form, REQ_END_LINE);
                break;
            case KEY_UP:
                /* перейти до попереднього поля */
                form_driver(my_form, REQ_PREV_FIELD);
                form_driver(my_form, REQ_END_LINE);
                break;
            default:
                /* якщо це звичайний знак, вивести його */
                form_driver(my_form, ch);
                break;
        }
    }
    /* відкликати форму і звільнити пам'ять */
    unpost_form(my_form);
    free_form(my_form);
    free_field(field[0]);
    free_field(field[1]);

    endwin();
    return 0;
}

void print_in_middle(WINDOW *win, int starty, int startx, int width, 
                     char *string, chtype color)
{
    int length, x, y;
    float temp;

    if(win == NULL)
        win = stdscr;
    getyx(win, y, x);
    if(startx != 0)
        x = startx;
    if(starty != 0)
        y = starty;
    if(width == 0)
        width = 80;

    length = strlen(string);
    temp = (width - length) / 2;
    x = startx + (int)temp;
    wattron(win, color);
    mvprintw(win, y, x, "%s", string);
    wattroff(win, color);
    refresh();
}

Перевірка справності вікон

Стандартно, дозволяється будь-який ввід у поле. Але є можливість додати перевірку поля. В такому разі, будь-яка спроба залишити поле користувачем, тоді як воно містить дані, що не пройшли перевірки певного типу, зазнає невдачі. Деякі типи перевірки включають навіть контролювання символів під час їхнього введення до поля.

Перевірку поля можна додати за допомогою наступної функції.

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

Після встановлення, тип перевірки поля можна дізнатися за допомогою

FIELDTYPE *field_type(FIELD *field);   /* тип перевірки */

Двигун форм перевіряє дані поля тільки якщо їх введено користувачем. Перевірка не відбувається коли

  • Програма змінює значення, що належить полю, шляхом виклику set_field_buffer().

  • Значення зв'язаних полів можна змінити лише опосередково, шляхом зміни значення поля, з яким вони зв'язані.

Означено наступні типи перевірок. Ви можете також визначити власний тип, хоча це трохи складно та незграбно.

заборонені (перевірка відбувається під час вводу символів). Встановлюється як наступне:

TYPE_ALPHA : Таке поле дозволяє алафавітні дані; пробіли, цифри та спеціальні символи

int set_field_type(FIELD *field,    /* яке саме поле */
                   TYPE_ALPHA,      /* тип (перевірки) поля */
                   int width);      /* найменша ширина поля */

Аргумент width встановлює мінімальну кількість даних. Користувач повинен ввести, щонайменше, width кількість символів до того, як покинути поле. Типово, краще встановити це значення до загальної ширини поля; якщо мінімальна кількість даних виявиться більшою за ширину поля, перевірка завжди зазнаватиме невдачі. Якщо ж width рівна нулю, заповнення поля буде необов'язковим.

заборонені (перевірка відбувається під час вводу символів). Встановлюється як наступне:

TYPE_ALNUM : Таке поле дозволяє алфавітні дані та цифри; пробіли та спеціальні символи

int set_field_type(FIELD *field,          /* яке саме поле */
                   TYPE_ALNUM,            /* тип (перевірки) поля */
                   int width);            /* найменша ширина поля */

Аргумент width встановлює мінімальну кількість даних. Так само, як і у випадку TYPE_ALPHA, краще встановити це значення до загальної ширини поля; якщо мінімальна кількість даних виявиться більшою за ширину поля, перевірка завжди зазнаватиме невдачі. Якщо ж width рівна нулю, заповнення поля буде необов'язковим.

значень (наприклад, до двохсимвольних поштових кодів Американських штатів. Такий тип поля можна встановити як

TYPE_ENUM : Цей тип дозволяє вам обмежити введені дані до певного набору ланцюжкових

int set_field_type(FIELD *field,       /* яке саме поле */
                   TYPE_ENUM,          /* тип (перевірки) поля */
                   char **valuelist;   /* список можливих значень */
                   int checkcase;      /* регістрозалежне порівняння */
                   int checkunique);   /* унікальність вводу */

Параметр valuelist повинен являти собою покажчик на, NULL-завершений, список чинних ланцюжків. Аргумент checkcase, якщо істинний, зробить порівняння з ланцюжком регістрозалежним.

Коли користувач залишає поле типу TYPE_ENUM, процедура перевірки намагається зіставити дані буферу із чинним варіантом. Якщо введені дані повністю зійдуться, перевірка матиме успіх. Можна також ввести тільки префікс чинного ланцюжка, який автоматично доповниться до повного під час перевірки.

Типово, якщо ви ввели такий префікс, і він збігається з більше ніж одним значенням зі списку, префікс доповниться до першого значення, яке збіглося. Зате, якщо істинним є аргумент checkunique, введений префікс повинен бути унікальним, щоб бути чинним.

Запити REQ_NEXT_CHOICE та REQ_PREV_CHOICE можуть виявитися особливо корисними з такими полями.

TYPE_INTEGER : Цей тип поля дозволяє лише одне ціле число. Встановлюється як:

int set_field_type(FIELD *field,          /* яке саме поле */
                   TYPE_INTEGER,          /* тип (перевірки) поля */
                   int padding,           /* скільки заповнити нулем */
                   int vmin, int vmax);   /* чинний діапазон */

Дозволеними символами такого поля являються необов'язковий знак мінусу або плусю та цифри. Перевірка діапазону відбувається під час виходу з поля. Якщо максимальне значення діапазону більше або рівне мінімальному, діапазон ігнорується.

Буфер поля типу TYPE_INTEGER зручно обробляється функцією atoi(3) Стандартної бібліотеки.

TYPE_NUMERIC : Цей тип поля дозволяє десяткове число. Встановлюється як наступне:

int set_field_type(FIELD *field,          /* яке саме поле */
                   TYPE_NUMERIC,          /* тип (перевірки) поля */
                   int padding,           /* скільки заповнити нулем */
                   int vmin, int vmax);   /* чинний діапазон */

Символами такого поля можуть складатися з необов'язкового знаку мінусу або плусю та цифр, із можливою десятковою точкою. Перевірка діапазону відбувається під час виходу з поля. Якщо максимальне значення діапазону більше або рівне мінімальному, діапазон ігнорується.

Якщо введене значення пройде перевірку діапазону, його буде доповнено відповідною кількістю нулів, щоб задовольнити аргумент padding.

Буфер поля типу TYPE_NUMERIC зручно обробляється функцією atof(3) Стандартної бібліотеки.

Встановлюється як наступне:

TYPE_REGEXP : Цей тип поля дозволяє дані, що відповідають певному регулярному виразові.

int set_field_type(FIELD *field,          /* яке саме поле */
                   TYPE_REGEXP,           /* тип (перевірки) поля */
                   char *regexp);         /* регулярний вираз */

Синтаксис регулярного виразу відповідає тому, що вказано в regcomp(3). Перевірка збігу з регулярним виразом відбувається, коли користувач покине поле.

Двигун форм: основа системи форм

Так само, як і з системою меню, form_driver() відіграє дуже важливу роль для системи форм. Всі типи запитів до системи форм потрібно обробляти form_driver().

int form_driver(FORM *form,     /* форма, якої це стосується */
                int request)    /* код запиту до форми */

Як ви вже бачили у попередніх прикладах, необхідно створити цикл, що отримує користувацький ввід, і вирішає, чи це дані поля, чи запит до форми. Запити до форми потім передано form_driver() для здійснення своєї роботи.

Запити можна приблизно поділити на наступні категорії, описані нижче.

Запити навігації сторінки

Такі запити спричиняють пересування всередині форми на сторінковому рівні, викликаючи вивід нового екрану форми. Форму можна скласти з багатьох сторінок. Якщо у вас велика форма, з багатьма полями та логічними розділами, тоді ви можете поділити її на сторінки. Для цього існує функція set_new_page(), яка створить нову сторінку для вказаного поля.

int set_new_page(FIELD *field,  /* поле, для якого встановлено чи скасовано 
                                 * розділювач сторінки */
         bool new_page_flag);   /* TRUE, щоб встановити розділювач */

Наступні запити дозволять вам перейти до інших сторінок

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 - з першого поля на останнє. Порядок полів відповідає послідовності покажчиків на поля в масиві форми (встановлений new_form(), або set_form_fields()).

Можна також міняти поля так, ніби їх сортовано за їхнім положенням на екрані, тобто зліва на право, та зверху вниз. Для цього скористайтеся з другої групи запитів.

І, нарешті, можна пересуватися між полями, використовуючи візуальні напрямки - вверх, вниз, ліворуч, праворуч. Для цього, використайте третю групу, що складається з чотирьох запитів. Запам'ятайте, однак, що початковим положенням форми для такого типу пересування вважається її верхній лівий кут.

Так, наприклад, якщо ви маєте багаторядкове поле B, і два однорядкових поля A та C на тому самому рядку, що й B, але A знаходитиметься ліворуч від B, а C - праворуч. В такому разі, 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 : Переміститися вниз у полі.

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

Запити щодо прокрутки

Динамічні поля, що збільшилися, або поля явно створені з рядками, що виходять поза межі екрану, включатимуть можливість прокрутки. Однорядкові поля можна прокручувати горизонтально, тоді як багаторядкові - вертикально. Прокрутка, звично, викликається редагувальними та переміщеннями всередині поля (бібліотека сама прокручує поле, щоб зберегти курсор видимим). Можна також явно викликати прокрутку за допомогою наступних запитів:

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_CLR_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_DEL_PREV напочатку поля вважатиметься запитом REQ_PREV_FIELD.

Але якщо вимкнено опцію O_BS_OVERLOAD, таке поводження скасовано, і двигун форм поверне просто E_REQUEST_DENIED (відмову запиту).

Порядкові запити

Якщо у вас поле впорядкованого типу, і включає функції, що дозволяють отримати наступне та попереднє значення типу за заданим значенням, наступні запити можуть добути попереднє, розмістивши його до буферу поля:

REQ_NEXT_CHOICE : Розмістить наступне значення (відносно поточного) до буферу.

REQ_PREV_CHOICE : Розмістить попереднє значення (відносно поточного) до буферу.

У випадку вбудованих типів полів, тільки TYPE_ENUM має функції отримання попереднього та наступного значень. Якщо ви визначили власний тип поля (дивіться розділ "Визначення власного типу перевірки"), то можете додати також власні функції впорядкованості.

Команди для додатків

Запити стосовно форм представлені як цілі числа, більші за KEY_MAX, і менші або рівні сталій MAX_COMMAND. Значення в цьому діапазоні ігноруються form_driver(). Тож, їх можна використати для самої програми. Ці значення можна розглянути як притаманні даному додатку, що змушують його здійснювати певні дії.