Бібліотека Menu

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

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

Основи

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

Загалом, послідовність керування меню-програмою виглядає так:

  1. Ініціалізація curses.
  2. Створення елементів за допомогою new_item(). Ви можете тут вказати назву і опис елементів.
  3. Створення меню за допомогою new_menu(), вказуючи елементи, які приєднуються.
  4. Виставлення меню за допомогою menu_post() і оновлення екрану.
  5. Обробка користувацьких запитів за допомогою циклу і відповідні поновлення меню через menu_driver().
  6. Відклик меню, використовуючи menu_unpost().
  7. Звільнення пам'яті, відведеної під меню за допомогою free_menu().
  8. Звільнення пам'яті, відведеної під елементи за допомогою free_item().
  9. Завершення curses.

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

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

Для того, щоб бути в змозі кориатуватися функціями бібліотеки меню, вам необхідно включити menu.h до вихідного файлу і зв'язати програму з бібліотекою, вказуючи прапорець -lmenu разом із -lcurses, саме в цій послідовності.

#include <menu.h>
    .
    .
    .

Компіляція і зв'язування: gcc <файл програми> -lmenu -lcurses

Приклад 18. Просте меню

#include <curses.h>
#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
    "Choice 1",
    "Choice 2",
    "Choice 3",
    "Choice 4",
    "Exit",
};

int main()
{
    ITEM **my_items;
    int c;
    MENU *my_menu;
    int n_choices, i;
    ITEM *cur_item;

    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **) calloc(n_choices + 1, sizeof(ITEM *));

    for (i = 0; i < n_choices; ++i)
    my_items[i] = new_item(choices[i], choices[i]);
    my_items[n_choices] = (ITEM *) NULL;

    my_menu = new_menu((ITEM **) my_items);
    mvprintw(LINES - 2, 0, "F1 to Exit");
    post_menu(my_menu);
    refresh();

    while ((c = getch()) != KEY_F(1)) {
    switch (c) {
    case KEY_DOWN:
        menu_driver(my_menu, REQ_DOWN_ITEM);
        break;
    case KEY_UP:
        menu_driver(my_menu, REQ_UP_ITEM);
        break;
    }
    }

    free_item(my_items[0]);
    free_item(my_items[1]);
    free_menu(my_menu);
    endwin();
}

Ця програма демонструє основні концепції, використовувані у створенні меню за використанням бібліотеки меню. Спершу ми створили елементи меню за допомогою new_item(), а потім долучили їх до меню функцією new_menu(). Після виставлення меню і оновлення екрану, починається основний цикл обробки запитів. Він читатиме користувацький ввід і реагує відповідно. Функція menu_driver() є основним елементом системи меню. Другий параметр цієї функції вказує, що саме здійснити з меню. Знеченням праметру може бути або звернення з запитом навігації, або знак ASCII, або ж спеціальний ключ KEY_MOUSE, пов'язаний з подією, що надходить від мишки.

Функція menu_driver() розпізнає наступні запити навігації:

REQ_LEFT_ITEM : Переміститися до елементу ліворуч.

REQ_RIGHT_ITEM : Переміститися до елементу праворуч.

REQ_UP_ITEM : Переміститися до елементу звершу.

REQ_DOWN_ITEM : Переміститися до елементу знизу.

REQ_SCR_ULINE : Прокрутити вверх на один рядок.

REQ_SCR_DLINE : Прокрутити вниз на один рядок.

REQ_SCR_DPAGE : Прокрутити вниз на одну сторінку.

REQ_SCR_UPAGE : Прокрутити вверх на одну сторінку.

REQ_FIRST_ITEM : Переміститися до першого елементу.

REQ_LAST_ITEM : Переміститися до останнього елементу.

REQ_NEXT_ITEM : Переміститися до наступного елементу. Move to the next item.

REQ_PREV_ITEM : Переміститися до попереднього елементу.

REQ_TOGGLE_ITEM : Обрати/скасувати вибір елементу.

REQ_CLEAR_PATTERN : Очистити буфер шаблонів меню.

REQ_BACK_PATTERN : Вилучити попередній символ з буферу шаблонів меню.

REQ_NEXT_MATCH : Переміститися до наступного елементу, що співпадає із шаблоном.

REQ_PREV_MATCH : Переміститися до попереднього елементу, що співпадає із шаблоном.

Нехай вас не лякає велика кількість опцій. Ми розглянемо їх поступово одна за одною. Найцікавіші опції у цьому списку - це REQ_UP_ITEM і REQ_DOWN_ITEM. Якщо передати ці дві опції menu_driver(), двигун меню поновить поточний елемент до одного вище або нижче, відповідно.

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

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

REQ_LEFT_ITEM і REQ_RIGHT_ITEM : Меню може містити більше ніж один стовпчик для елементу. Цього можна добитися завдяки функції menu_format(). Під час виводу багатостовпчикового меню, ці запити спричинюють переміщення поточного вибору наліво або направо.

REQ_UP_ITEM і REQ_DOWN_ITEM : Ці дві опції ви вже побачили у прикладі вище. Вони зумовлюють переміщення поточного вибору до елементу вище або нижче.

REQ_SCR_* опції : Чотири опції - REQ_SCR_ULINE, REQ_SCR_DLINE, REQ_SCR_DPAGE, REQ_SCR_UPAGE - пов'язані з прокруткою. Якщо неможливо відобразити всі елементи меню, тоді останнє буде з можливістю прокрутки. Ці запити можна надати двигуну меню menu_driver() для здійснення прокрутки вверх або вниз на один рядок, або ж вверх або вниз на одну сторінку, відповідно.

REQ_FIRST_ITEM, REQ_LAST_ITEM, REQ_NEXT_ITEM and REQ_PREV_ITEM : Сама назва цих опцій пояснює їх: переміститися до першого, останнього, наступного або ж попереднього елементу.

REQ_TOGGLE_ITEM : Цей запит вмикає поточний вибір. Вона використовується у випадку багатозначних меню. Для її використання, опцію O_ONEVALUE необхідно вимкнути за допомогою set_menu_opts().

Запит по шаблону : Кожне меню володіє відповідним буфером шаблонів, який використовується для знаходження найближчого співпадання з ASCII-знаками, введеними користувачем. Як тільки якийсь знак надійде до menu_driver(), функція розмістить його у буфері шаблону. Вона також спробує знайти найближчий збіг із шаблоном серед списку пунктів меню, і перемістить поточний вибір до цього пункту. Запит REQ_CLEAR_PATTERN очищує буфер шаблону. Запит REQ_BACK_PATTERN видаляє попередній символ у буфері шаблону. У випадку, якщо шаблон збігається з більш ніж одним пунктом меню, тоді можна циклічно переходити до пунктів, що зійшлися, за допомогою REQ_NEXT_MATCH і REQ_PREV_MATCH, що переносять поточний вибір до наступного та попереднього збігу, відповідно.

Запити мишкою : У випадку запитів KEY_MOUSE, відбудеться відповідна дія, згідно з положенням мишки. Дія, що відбудеться, описана в сторінці посібника наступним чином: : Якщо другим аргументом являється спеціальна клавіша KEY_MOUSE, відповіну подію мишки перекладено на один з попередньо-визначених запитів. На даний момент, обробляються тільки клацання посеред вікна (тобто всередині ділянки відображення меню, або вікні-облямівці). Якщо ви клацнете вище області відображення меню, буде генеровано REQ_SCR_ULINE, якщо подвійно клацнете - REQ_SCR_UPAGE, і якщо потрійно - REQ_FIRST_ITEM. Якщо клацнете нижче області відображення, буде генеровано REQ_SCR_DLINE, якщо подвійно клацнете - REQ_SCR_DPAGE, і потрійно - REQ_LAST_ITEM. Якщо ви клацнете на пункт всередині ділянки відображення меню, курсор меню переміститься до цього пункту.

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

Вікна меню

Кожне меню створюється і пов'язується з основним і дочірнім вікном. Основне вікно, як правило, відображає титул, або обл'ямівку меню. Дочірнє вікно меню міститиме пункти меню. Але ми не вказали основного та дочірнього вікна у нашому прикладі... Коли вікно не вказано, основним вікном вважатиметься stdscr, і система меню сама обчислює розмір дочірнього вікна для відображення пунктів меню. А тепер, давайте побавимося з цими вікнами, і виведемо меню з облямівкою та титулом.

Приклад 19. Використання вікна меню

#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
    "Choice 1",
    "Choice 2",
    "Choice 3",
    "Choice 4",
    "Exit",
    (char *) NULL,
};
void print_in_middle(WINDOW * win, int starty, int startx, int width,
             char *string, chtype color);

int main()
{
    ITEM **my_items;
    int c;
    MENU *my_menu;
    WINDOW *my_menu_win;
    int n_choices, i;

    /* Initialize curses */
    initscr();
    start_color();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    init_pair(1, COLOR_RED, COLOR_BLACK);

    /* Create items */
    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **) calloc(n_choices, sizeof(ITEM *));
    for (i = 0; i < n_choices; ++i)
    my_items[i] = new_item(choices[i], choices[i]);

    /* Crate menu */
    my_menu = new_menu((ITEM **) my_items);

    /* Create the window to be associated with the menu */
    my_menu_win = newwin(10, 40, 4, 4);
    keypad(my_menu_win, TRUE);

    /* Set main window and sub window */
    set_menu_win(my_menu, my_menu_win);
    set_menu_sub(my_menu, derwin(my_menu_win, 6, 38, 3, 1));

    /* Set menu mark to the string " * " */
    set_menu_mark(my_menu, " * ");

    /* Print a border around the main window and print a title */
    box(my_menu_win, 0, 0);
    print_in_middle(my_menu_win, 1, 0, 40, "My Menu", COLOR_PAIR(1));
    mvwaddch(my_menu_win, 2, 0, ACS_LTEE);
    mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38);
    mvwaddch(my_menu_win, 2, 39, ACS_RTEE);
    mvprintw(LINES - 2, 0, "F1 to exit");
    refresh();

    /* Post the menu */
    post_menu(my_menu);
    wrefresh(my_menu_win);

    while ((c = wgetch(my_menu_win)) != KEY_F(1)) {
    switch (c) {
    case KEY_DOWN:
        menu_driver(my_menu, REQ_DOWN_ITEM);
        break;
    case KEY_UP:
        menu_driver(my_menu, REQ_UP_ITEM);
        break;
    }
    wrefresh(my_menu_win);
    }

    /* Unpost and free all the memory taken up */
    unpost_menu(my_menu);
    free_menu(my_menu);
    for (i = 0; i < n_choices; ++i)
    free_item(my_items[i]);
    endwin();
}

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);
    mvwprintw(win, y, x, "%s", string);
    wattroff(win, color);
    refresh();
}

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

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

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

Приклад 20. Прокрутка меню

#include <curses.h>
#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
                    "Choice 1",
                    "Choice 2",
                    "Choice 3",
                    "Choice 4",
                    "Choice 5",
                    "Choice 6",
                    "Choice 7",
                    "Choice 8",
                    "Choice 9",
                    "Choice 10",
                    "Exit",
                    (char *)NULL,
              };
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);

int main()
{   
    ITEM **my_items;
    int c;
    MENU *my_menu;
    WINDOW *my_menu_win;
    int n_choices, i;

    /* Initialize curses */
    initscr();
    start_color();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_CYAN, COLOR_BLACK);

    /* Create items */
    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
    for(i = 0; i < n_choices; ++i)
            my_items[i] = new_item(choices[i], choices[i]);

    /* Crate menu */
    my_menu = new_menu((ITEM **)my_items);

    /* Create the window to be associated with the menu */
    my_menu_win = newwin(10, 40, 4, 4);
    keypad(my_menu_win, TRUE);

    /* Set main window and sub window */
    set_menu_win(my_menu, my_menu_win);
    set_menu_sub(my_menu, derwin(my_menu_win, 6, 38, 3, 1));
    set_menu_format(my_menu, 5, 1);

    /* Set menu mark to the string " * " */
    set_menu_mark(my_menu, " * ");

    /* Print a border around the main window and print a title */
    box(my_menu_win, 0, 0);
    print_in_middle(my_menu_win, 1, 0, 40, "My Menu", COLOR_PAIR(1));
    mvwaddch(my_menu_win, 2, 0, ACS_LTEE);
    mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38);
    mvwaddch(my_menu_win, 2, 39, ACS_RTEE);

    /* Post the menu */
    post_menu(my_menu);
    wrefresh(my_menu_win);

    attron(COLOR_PAIR(2));
    mvprintw(LINES - 2, 0, "Use PageUp and PageDown to scoll down or up a page of items");
    mvprintw(LINES - 1, 0, "Arrow Keys to navigate (F1 to Exit)");
    attroff(COLOR_PAIR(2));
    refresh();

    while((c = wgetch(my_menu_win)) != KEY_F(1))
    {       switch(c)
            {       case KEY_DOWN:
                            menu_driver(my_menu, REQ_DOWN_ITEM);
                            break;
                    case KEY_UP:
                            menu_driver(my_menu, REQ_UP_ITEM);
                            break;
                    case KEY_NPAGE:
                            menu_driver(my_menu, REQ_SCR_DPAGE);
                            break;
                    case KEY_PPAGE:
                            menu_driver(my_menu, REQ_SCR_UPAGE);
                            break;
            }
            wrefresh(my_menu_win);
    }

    /* Unpost and free all the memory taken up */
    unpost_menu(my_menu);
    free_menu(my_menu);
    for(i = 0; i < n_choices; ++i)
            free_item(my_items[i]);
    endwin();
}

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);
    mvwprintw(win, y, x, "%s", string);
    wattroff(win, color);
    refresh();
}

Ця програма говорить сама за себе. В цьому прикладі, кількість варіантів вибору збільшено до десяти, що перевищує розмір дочірнього вікна, рівного 6-и пунктам. Цю обставину необхідно відверто повідомити системі меню за допомогою функції set_menu_format(). Через неї, ми чітко означимо кількість рядків і стовпчиків, які ми бажаємо вивести як одну сторінку. Ми можемо задати будь-яку кількість пунктів, що буде виведено, якщо вказати відповідну кількість рядків (змінна rows). Якщо користувач притисне або , меню прокрутиться на сторінку завдяки запитам (REQ_SCR_DPAGE and REQ_SCR_UPAGE), наданим menu_driver() (двигуну меню).

Багатостовпчикове меню

У прикладі вище, ви ознайомилися, як використати функцію set_menu_format(). Але я не згадав, що саме здійснює змінна, що відповідає кількосто стовпчиків, cols (третій параметр). Насправді, якщо дочірнє вікно достатньо широке, ви, за бажанням, можете вивести більше одного пункту на тому самому рядкові. Це можна вказати змінною cols. Для спрощення, наступний приклад не включає описів пунктів меню.

Приклад 21. Багатостовпчикове меню

#include <curses.h>
#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
                    "Choice 1", "Choice 2", "Choice 3", "Choice 4", "Choice 5",
                    "Choice 6", "Choice 7", "Choice 8", "Choice 9", "Choice 10",
                    "Choice 11", "Choice 12", "Choice 13", "Choice 14", "Choice 15",
                    "Choice 16", "Choice 17", "Choice 18", "Choice 19", "Choice 20",
                    "Exit",
                    (char *)NULL,
              };

int main()
{   
    ITEM **my_items;
    int c;
    MENU *my_menu;
    WINDOW *my_menu_win;
    int n_choices, i;

    /* Initialize curses */
    initscr();
    start_color();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_CYAN, COLOR_BLACK);

    /* Create items */
    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
    for(i = 0; i < n_choices; ++i)
            my_items[i] = new_item(choices[i], choices[i]);

    /* Crate menu */
    my_menu = new_menu((ITEM **)my_items);

    /* Set menu option not to show the description */
    menu_opts_off(my_menu, O_SHOWDESC);

    /* Create the window to be associated with the menu */
    my_menu_win = newwin(10, 70, 4, 4);
    keypad(my_menu_win, TRUE);

    /* Set main window and sub window */
    set_menu_win(my_menu, my_menu_win);
    set_menu_sub(my_menu, derwin(my_menu_win, 6, 68, 3, 1));
    set_menu_format(my_menu, 5, 3);
    set_menu_mark(my_menu, " * ");

    /* Print a border around the main window and print a title */
    box(my_menu_win, 0, 0);

    attron(COLOR_PAIR(2));
    mvprintw(LINES - 3, 0, "Use PageUp and PageDown to scroll");
    mvprintw(LINES - 2, 0, "Use Arrow Keys to navigate (F1 to Exit)");
    attroff(COLOR_PAIR(2));
    refresh();

    /* Post the menu */
    post_menu(my_menu);
    wrefresh(my_menu_win);

    while((c = wgetch(my_menu_win)) != KEY_F(1))
    {       switch(c)
            {       case KEY_DOWN:
                            menu_driver(my_menu, REQ_DOWN_ITEM);
                            break;
                    case KEY_UP:
                            menu_driver(my_menu, REQ_UP_ITEM);
                            break;
                    case KEY_LEFT:
                            menu_driver(my_menu, REQ_LEFT_ITEM);
                            break;
                    case KEY_RIGHT:
                            menu_driver(my_menu, REQ_RIGHT_ITEM);
                            break;
                    case KEY_NPAGE:
                            menu_driver(my_menu, REQ_SCR_DPAGE);
                            break;
                    case KEY_PPAGE:
                            menu_driver(my_menu, REQ_SCR_UPAGE);
                            break;
            }
            wrefresh(my_menu_win);
    }

    /* Unpost and free all the memory taken up */
    unpost_menu(my_menu);
    free_menu(my_menu);
    for(i = 0; i < n_choices; ++i)
            free_item(my_items[i]);
    endwin();
}

Зверніть увагу на виклик функції set_menu_format(). У ній вказується кількість стовпчиків, рівну 3-ом, виводячи таким чином 3 пункти в одному рядкові. Ми також вимкнули вивід описів, за допомогою функції menu_opts_off(). Існує також декілька функцій, таких як set_menu_opts(), menu_opts_on() та menu_opts(), які також можна використати для керування опціями меню. За їхньої допомоги, можна задати наступні опції меню.

O_ONEVALUE : Це меню дозволяє вибір тільки одного пункту.

O_SHOWDESC : Вивід описів пунктів під час представлення меню (when menu is posted).

O_ROWMAJOR : Вивід меню в row-major послідовності.

O_IGNORECASE : Ігнорування ргістру під час збігу із шаблоном.

O_SHOWMATCH : Переміщатиме курсор посередині назви пункту під час збігу із шаблоном.

O_NONCYCLIC : Не завертати до наступного пункту чи попереднього пункту на іншому кінці меню.

Всі ці опції типово ввімкнені. Ви можете увімкнути, чи вимкнути певний атрибут, якщо викличете menu_opts_on() чи menu_opts_off() із відповідним аргументом. Так само, можете скористартися з set_menu_opts(), щоб безпосередньо вказати якусь опцію. Агрументом цієї функції може служити диз'юнкція (логічне АБО) декількох з вищенаведених констант. Функція menu_opts() вживається для знаходження поточні опції меню.

Багатозначні меню

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

Приклад 22. Багатозначні меню

#include <curses.h>
#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
                    "Choice 1",
                    "Choice 2",
                    "Choice 3",
                    "Choice 4",
                    "Choice 5",
                    "Choice 6",
                    "Choice 7",
                    "Exit",
              };

int main()
{ 
    ITEM **my_items;
    int c;
    MENU *my_menu;
    int n_choices, i;
    ITEM *cur_item;

    /* Initialize curses */
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    /* Initialize items */
    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **)calloc(n_choices + 1, sizeof(ITEM *));
    for(i = 0; i < n_choices; ++i)
            my_items[i] = new_item(choices[i], choices[i]);
    my_items[n_choices] = (ITEM *)NULL;

    my_menu = new_menu((ITEM **)my_items);

    /* Make the menu multi valued */
    menu_opts_off(my_menu, O_ONEVALUE);

    mvprintw(LINES - 3, 0, "Use <SPACE> to select or unselect an item.");
    mvprintw(LINES - 2, 0, "<ENTER> to see presently selected items(F1 to Exit)");
    post_menu(my_menu);
    refresh();

    while((c = getch()) != KEY_F(1)) { 
            switch(c) {       
                    case KEY_DOWN:
                            menu_driver(my_menu, REQ_DOWN_ITEM);
                            break;
                    case KEY_UP:
                            menu_driver(my_menu, REQ_UP_ITEM);
                            break;
                    case ' ':
                            menu_driver(my_menu, REQ_TOGGLE_ITEM);
                            break;
                    case 10:        /* Enter */
                    {       char temp[200];
                            ITEM **items;
                            items = menu_items(my_menu);
                            temp[0] = '\0';
                            for(i = 0; i < item_count(my_menu); ++i)
                                    if(item_value(items[i]) == TRUE)
                                    {       strcat(temp, item_name(items[i]));
                                            strcat(temp, " ");
                                    }
                            move(20, 0);
                            clrtoeol();
                            mvprintw(20, 0, temp);
                            refresh();
                    }
                    break;
            }
    }

    free_item(my_items[0]);
    free_item(my_items[1]);
    free_menu(my_menu);
    endwin();
}

Так, це багато нових функцій. Давайте розглянемо їх одна за одною. Перш за все, REQ_TOGGLE_ITEM. У багатозначному меню, необхідно дозволити користувачу обрати, чи скасувати вибір більш ніж одного пункту меню. Запит REQ_TOGGLE_ITEM вмикає поточний обраний пункт. В такому випадку, коли натиснуто клавішу пробілу, до menu_driver() надійде запит REQ_TOGGLE_ITEM.

Тепер, щойно користувач натисне , ми покажемо пункти, обрані на даний момент. Спочатку, ми визначимо пункти, що належать меню, за допомогою функції menu_items(). Після цього, циклічно пройдемося через ці пункти, щоб з'ясувати, чи обрано пункт, чи ні. Функція item_value() повертає TRUE (істинне значення), якщо пункт обрано. Функція item_count() повертає кількість пунктів меню. Назву пункту можна визначити за допомогою item_name(). Так само, ви можете знайти опис пункту, якщо застосувати item_description().

Опції меню

Тепер ви, напевне, чекаєте хоч якогось урізноманітнення вашого меню. Я знаю, ви хочете кольору... Ви хотіли би створити ті гарні меню, аналогічні іграм у текстовому режимі DOS. Так, функції set_menu_fore() та set_menu_back() можна застосувати, щоб змінити атрибути обраного пункту меню, або вибір якого скасовано. Назви цих функцій трохи вводять в оману. Вони не міняють, насправді, передній та задній план меню.

Для зміни атрибуту відображення пунктів, що не можуть бути обраними, можна використати функцію set_menu_grey(). Це нагадало нам про цікаву і єдину опцію пункту меню, O_SELECTABLE. Ми можемо вимкнути її за допомогою функції item_opts_off(), після чого пункт буде без можливості обрання. Це схоже на ті сірі пункти меню Віндовса. Давайте застосуємо ці ідеї на практиці в наступному прикладі.

Приклад 23. Опції меню

#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
    "Choice 1",
    "Choice 2",
    "Choice 3",
    "Choice 4",
    "Choice 5",
    "Choice 6",
    "Choice 7",
    "Exit",
};

int main()
{
    ITEM **my_items;
    int c;
    MENU *my_menu;
    int n_choices, i;
    ITEM *cur_item;

    /* Initialize curses */
    initscr();
    start_color();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_GREEN, COLOR_BLACK);
    init_pair(3, COLOR_MAGENTA, COLOR_BLACK);

    /* Initialize items */
    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **) calloc(n_choices + 1, sizeof(ITEM *));
    for (i = 0; i < n_choices; ++i)
    my_items[i] = new_item(choices[i], choices[i]);
    my_items[n_choices] = (ITEM *) NULL;
    item_opts_off(my_items[3], O_SELECTABLE);
    item_opts_off(my_items[6], O_SELECTABLE);

    /* Create menu */
    my_menu = new_menu((ITEM **) my_items);

    /* Set fore ground and back ground of the menu */
    set_menu_fore(my_menu, COLOR_PAIR(1) | A_REVERSE);
    set_menu_back(my_menu, COLOR_PAIR(2));
    set_menu_grey(my_menu, COLOR_PAIR(3));

    /* Post the menu */
    mvprintw(LINES - 3, 0, "Press <ENTER> to see the option selected");
    mvprintw(LINES - 2, 0,
         "Up and Down arrow keys to naviage (F1 to Exit)");
    post_menu(my_menu);
    refresh();

    while ((c = getch()) != KEY_F(1)) {
        switch (c) {
        case KEY_DOWN:
            menu_driver(my_menu, REQ_DOWN_ITEM);
            break;
        case KEY_UP:
            menu_driver(my_menu, REQ_UP_ITEM);
            break;
        case 10:            /* Enter */
            move(20, 0);
            clrtoeol();
            mvprintw(20, 0, "Item selected is : %s",
                     item_name(current_item(my_menu)));
            pos_menu_cursor(my_menu);
            break;
        }
    }
    unpost_menu(my_menu);
    for (i = 0; i < n_choices; ++i)
    free_item(my_items[i]);
    free_menu(my_menu);
    endwin();
}

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

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

Приклад 24. Використання користувацького покажчика

#include <curses.h>
#include <menu.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD   4

char *choices[] = {
    "Choice 1",
    "Choice 2",
    "Choice 3",
    "Choice 4",
    "Choice 5",
    "Choice 6",
    "Choice 7",
    "Exit",
};

void func(char *name);

int main()
{
    ITEM **my_items;
    int c;
    MENU *my_menu;
    int n_choices, i;
    ITEM *cur_item;

    /* Initialize curses */
    initscr();
    start_color();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_GREEN, COLOR_BLACK);
    init_pair(3, COLOR_MAGENTA, COLOR_BLACK);

    /* Initialize items */
    n_choices = ARRAY_SIZE(choices);
    my_items = (ITEM **) calloc(n_choices + 1, sizeof(ITEM *));
    for (i = 0; i < n_choices; ++i) {
    my_items[i] = new_item(choices[i], choices[i]);
    /* Set the user pointer */
    set_item_userptr(my_items[i], func);
    }
    my_items[n_choices] = (ITEM *) NULL;

    /* Create menu */
    my_menu = new_menu((ITEM **) my_items);

    /* Post the menu */
    mvprintw(LINES - 3, 0, "Press <ENTER> to see the option selected");
    mvprintw(LINES - 2, 0,
         "Up and Down arrow keys to naviage (F1 to Exit)");
    post_menu(my_menu);
    refresh();

    while ((c = getch()) != KEY_F(1)) {
    switch (c) {
    case KEY_DOWN:
        menu_driver(my_menu, REQ_DOWN_ITEM);
        break;
    case KEY_UP:
        menu_driver(my_menu, REQ_UP_ITEM);
        break;
    case 10:        /* Enter */
        {
        ITEM *cur;
        void (*p) (char *);

        cur = current_item(my_menu);
        p = item_userptr(cur);
        p((char *) item_name(cur));
        pos_menu_cursor(my_menu);
        break;
        }
        break;
    }
    }

    unpost_menu(my_menu);
    for (i = 0; i < n_choices; ++i)
    free_item(my_items[i]);
    free_menu(my_menu);
    endwin();
}

void func(char *name)
{
    move(20, 0);
    clrtoeol();
    mvprintw(20, 0, "Item selected is : %s", name);

}