Вікна

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

Основи

Вікно можна створити за допомогою функції newwin(). Ця функція не відображає, насправді, нічого на екрані. Вона виділяє пам'ять для структури вікна і заповнює цю структуру даними, стосовно розміру вікна, початкової координати y, початкової координати x тощо. Таким чином, у curses вікно - це просто абстракція уявного вікна, яким можна керувати, незалежно від інших частин екрану.

Функція newwin() повертає покажчик на структуру типу WINDOW, яку можна передати віконним функціям на зразок wprintw() тощо. І, нарешті, вікно можна знищити за допомогою delwin(). Остання перерозподілить пам'ять, виділену під структуру вікна.

Хай живе вікно!

Що за втіха, якщо вікно створене, але ми не бачимо його. Тож, сама цікава частина полягає саме у відображенні вікна. Можна також застосувати функцію box() для виводу облямівки навколо вікна. Давайте розглянемо функції, пов'язані з відображенням вікна.

Приклад 7. Облямівка вікна

#include <ncurses.h>

WINDOW *create_newwin(int height, int width, int starty, int startx);
void destroy_win(WINDOW *local_win);

int main(int argc, char *argv[])
{       
    WINDOW *my_win;
    int startx, starty, width, height;
    int ch;

    initscr();                      /* Розпочне режим curses      */
    cbreak();                       /* Вимкне буферування рядків, *
                                     * передає все нам            */
    keypad(stdscr, TRUE);           /* Нам потрібна клавіша F1    */

    height = 3;
    width = 10;
    starty = (LINES - height) / 2;  /* Обчислює положення вікна   */
    startx = (COLS - width) / 2;    /* (по центру)                */
    printw("Press F1 to exit");
    refresh();
    my_win = create_newwin(height, width, starty, startx);

    while((ch = getch()) != KEY_F(1)) {       
        switch(ch) {       
        case KEY_LEFT:
            destroy_win(my_win);
            my_win = create_newwin(height, width, starty, --startx);
            break;
        case KEY_RIGHT:
            destroy_win(my_win);
            my_win = create_newwin(height, width, starty, ++startx);
            break;
        case KEY_UP:
            destroy_win(my_win);
            my_win = create_newwin(height, width, --starty, startx);
            break;
        case KEY_DOWN:
            destroy_win(my_win);
            my_win = create_newwin(height, width, ++starty, startx);
            break;
       }
    }

    endwin();                       /* Завершує режим curses      */
    return 0;
}

WINDOW *create_newwin(int height, int width, int starty, int startx)
{       
    WINDOW *local_win;

    local_win = newwin(height, width, starty, startx);
    box(local_win, 0 , 0);         /* 0, 0 встановлює стандартні  *
                                    * символи вертикальних і      *
                                    * горизонтальних ліній        */
    wrefresh(local_win);           /* Виводить рамку              */

    return local_win;
}

void destroy_win(WINDOW *local_win)
{
    /* box(local_win, ' ', ' '); не призведе до бажаного наслідку
     * стирання вікна. Збережуться чотири кути вікна, як залишок
     * вікна.
     */
    wborder(local_win, ' ', ' ', ' ',' ',' ',' ',' ',' ');
    /* Використано наступні параметри:
     * 1. win: вікно, на яке розповсюджуватиметься дія
     * 2. ls: символ лівого краю вікна
     * 3. rs: символ правого краю вікна
     * 4. ts: символ верхнього краю вікна
     * 5. bs: символ нижнього краю вікна
     * 6. tl: символ верхьнього лівого кута вікна
     * 7. tr: character to be used for the top right corner of the window
     * 8. bl: character to be used for the bottom left corner of the window
     * 9. br: character to be used for the bottom right corner of the window
     */
    wrefresh(local_win);
    delwin(local_win);
}

Пояснення

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

Функція create_newwin() створює нове вікно за допомогою newwin() і виводить облямівку за допомогою функції box(). Функція destroy_win() спершу стирає вікно шляхом перезапису облямівки символом ' ', після чого викликає delwin() для звільнення, відведеної під вікно, пам'яті. В залежності від клавіші, на яку натисне користувач, starty або startx зміниться і утвориться нове вікно.

У процедурі destroy_win(), як ви можливо зауважили, я використав wborder() замість box(). Чому саме пояснено у коментарі до коду (ви пропустили його, я знаю. Прочитайте код :-)). Функція wborder() виводить рамку навколо вікна, використовуючи символи, вказані їй як 4 кути і 4 лінії. Щоб стало зрозуміліше, якщо би ви викликали wborder() як вказано нижче:

wborder(win, '|', '|', '-', '-', '+', '+', '+', '+');

це спричинило би до вікна, подібного до наступного:

     +------------+
     |            |
     |            |
     |            |
     |            |
     |            |
     |            |
     +------------+

Інші речі з прикладу

Ви також побачили у вищенаведеному прикладі, що я використав змінні COLS і LINES, які ініціалізуються до розміру екрану після initscr(). Вони можуть виявитись корисними, щоб визначити розмір екрану або знаходження координат його центру, як у прикладі вище. Функція getch(), як звичайно, отримує символ, що відповідає натиснутій клавіші, і в залежності ві того що натиснуто, відповідна дія. Конструкція switch дуже типова для для програм, що взаємодіють з клавіатурою.

Додаткові функції облямівки вікна

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

Наступна програма застосує mvhline() і mvvline() для досягнення подібного ефекту. Це дві, досить прості функції. Вони створюють горизонтальну або вертикальну лінії вказаної довжини у вказаному місці.

Приклад 8. Додаткові функції облямівки

#include <ncurses.h>

typedef struct _win_border_struct {
    chtype ls, rs, ts, bs, tl, tr, bl, br;
} WIN_BORDER;

typedef struct _WIN_struct {
    int startx, starty;
    int height, width;
    WIN_BORDER border;
} WIN;

void init_win_params(WIN * p_win);
void print_win_params(WIN * p_win);
void create_box(WIN * win, bool flag);

int main(int argc, char *argv[])
{
    WIN win;
    int ch;

    initscr();          /* Start curses mode            */
    start_color();      /* Start the color functionality */
    cbreak();           /* Line buffering disabled, Pass on
                 * everty thing to me           */
    keypad(stdscr, TRUE);   /* I need that nifty F1         */
    noecho();
    init_pair(1, COLOR_CYAN, COLOR_BLACK);

    /* Initialize the window parameters */
    init_win_params(&win);
    print_win_params(&win);

    attron(COLOR_PAIR(1));
    printw("Press F1 to exit");
    refresh();
    attroff(COLOR_PAIR(1));

    create_box(&win, TRUE);
    while ((ch = getch()) != KEY_F(1)) {
    switch (ch) {
    case KEY_LEFT:
        create_box(&win, FALSE);
        --win.startx;
        create_box(&win, TRUE);
        break;
    case KEY_RIGHT:
        create_box(&win, FALSE);
        ++win.startx;
        create_box(&win, TRUE);
        break;
    case KEY_UP:
        create_box(&win, FALSE);
        --win.starty;
        create_box(&win, TRUE);
        break;
    case KEY_DOWN:
        create_box(&win, FALSE);
        ++win.starty;
        create_box(&win, TRUE);
        break;
    }
    }
    endwin();           /* End curses mode */
    return 0;
}

void init_win_params(WIN * p_win)
{
    p_win->height = 3;
    p_win->width = 10;
    p_win->starty = (LINES - p_win->height) / 2;
    p_win->startx = (COLS - p_win->width) / 2;

    p_win->border.ls = '|';
    p_win->border.rs = '|';
    p_win->border.ts = '-';
    p_win->border.bs = '-';
    p_win->border.tl = '+';
    p_win->border.tr = '+';
    p_win->border.bl = '+';
    p_win->border.br = '+';

}

void print_win_params(WIN * p_win)
{
#ifdef _DEBUG
    mvprintw(25, 0, "%d %d %d %d", p_win->startx, p_win->starty,
         p_win->width, p_win->height);
    refresh();
#endif
}

void create_box(WIN * p_win, bool flag)
{
    int i, j;
    int x, y, w, h;

    x = p_win->startx;
    y = p_win->starty;
    w = p_win->width;
    h = p_win->height;

    if (flag == TRUE) {
    mvaddch(y, x, p_win->border.tl);
    mvaddch(y, x + w, p_win->border.tr);
    mvaddch(y + h, x, p_win->border.bl);
    mvaddch(y + h, x + w, p_win->border.br);
    mvhline(y, x + 1, p_win->border.ts, w - 1);
    mvhline(y + h, x + 1, p_win->border.bs, w - 1);
    mvvline(y + 1, x, p_win->border.ls, h - 1);
    mvvline(y + 1, x + w, p_win->border.rs, h - 1);

    } else
    for (j = y; j <= y + h; ++j)
        for (i = x; i <= x + w; ++i)
        mvaddch(j, i, ' ');

    refresh();

}