Бібліотека Panel
Щойно ви освоїлися з curses, у вас може виникнути бажання написати щось більше. Ви створите багато перекривних вікон, щоб надати програмі професійний вигляд на кшталт Віндовса. Прикро те, що згодом стає важко керувати ними. Багатократні записи й оновлення занурюють вас у справжній кошмар. Накладені вікна утворюють непотріб на екрані, коли ви забудете оновити вікна у належній послідовності.
Але не падайте духом. Існує елегантне рішення, надане бібліотекою панелей. Ось що про неї кажуть самі розробники ncurses:
"Якщо, розроблений вами інтерфейс, включає вікна, що заглиблюються в стос видимості (visibility stack), або з'являються на поверхні під час роботи, підтримка такого в робочому стані може виявитися досить марудною та складною задачею. Тому з'явилася бібліотека панелей."
Тож, якщо у вас багато накладених вікон, тоді бібліотека панелей є саме тим, що вам потрібно. Вона скасовує необхідність виконання ряду wnoutrefresh() та doupdate(), і звільняє від тягаря збереження правильної послідовності оновлення вікон (тобто, від нижнього до верхнього). Бібліотека зберігає інформацію про порядок вікон, їхнього перектиття, й оновлює екран належним чином. Тож, годі чекати. Розгляньмо її ближче.
Основи
Об'єкт панелі являється вікном, яке, насправді, вважається частиною колоди (як в картах), що включає решту об'єктів-панелей. Колоду розглянуто як стос, із повністю видимою верхньою панеллю, тоді як решта панелей можуть бути частково видимими, або зовсім не видно, в залежності від їхнього положення. Тож, основна ідея полягає в створенні стосу накладених панелей, і застосувати бібліотеку панелей для правильного їхнього відображення. Для цього існує окрема функція, подібна до refresh(), яка, якщо її викликати, виведе панелі у правильній послідовності. Так само надано функції для ховання чи виводу панелей, їхнього переміщення, зміни розміру тощо. Під час виклику цих функцій, проблеми з перекриттям автоматично вирішуються бібліотекою панелей.
Загальний потік програми з використанням панелей виглядатиме як наступне:
- Створення вікон (за допомогою newwin()), які буде прикріплено до панелей.
- Створення панелей із певною послідовністю видимості. Тобто, складання стосу, відповідно до бажаної видимості. Панелі започатковано за допомогою функції new_panel().
- Виклик update_panels() для запису панелей до віртуального екрану в правильній подлідовності видимості. Виконання doupdate() для їхнього відображення.
- Керування панелями за допомгою функцій show_panel(), hide_panel(), move_panel() тощо. Можливе застосування допоміжних функцій, таких як panel_hidden() або panel_window(). Можливе використання користувацького покажчика для збереження окремих даних панелей. Функції set_panel_userptr() та panel_userptr() встановлюють і оджержують користувацький покажчик панелі.
- По закінченню, потрібно видалити панель за допомогою del_panel().
Давайте складемо декілька програм для ілюстрації цієї концепції. Трохи далі, проста програма, яка створює 3 накладені панелі, та виводить їх по-черзі на екран.
Компіляція з бібліотекою Panel
Щоб користуватися функціями бібліотеки, вам необхідно включити panel.h у вихідний файл, і під час компіляції зв'язати програму з бібліотекою панелей прапорцями -lpanel і -lcurses, саме в цій послідовності.
#include <panel.h>
.
.
.
компіляція та зв'язка: gcc <файл програми> -lpanel -lncurses
Приклад 14. Основи панелей
#include <panel.h>
int main()
{
WINDOW *my_wins[3];
PANEL *my_panels[3];
int lines = 10, cols = 40, y = 2, x = 4, i;
initscr();
cbreak();
noecho();
/* Create windows for the panels */
my_wins[0] = newwin(lines, cols, y, x);
my_wins[1] = newwin(lines, cols, y + 1, x + 5);
my_wins[2] = newwin(lines, cols, y + 2, x + 10);
/*
* Create borders around the windows so that you can see the effect
* of panels
*/
for(i = 0; i < 3; ++i)
box(my_wins[i], 0, 0);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
doupdate();
getch();
endwin();
}
Як бачите, вищенаведена програма слідує тій самій простій схемі, яку ми вказали. Вікна створено за допомгою newwin(), після чого їх закріплено за панелями завдяки new_panel(). Під час закріпнення до однієї панелі за іншою, виростає стос панелей. Для того, щоб відобразити їх, викликаються update_panels(), а потім doupdate().
Перегляд вікон-панелей
Нижче слідує трохи складніший приклад. Ця програма створює 3 вікна, які можна навідати, якщо натиснути клавішу табуляції. Роздивіться цей код.
Приклад 15. Навігація вікон панелей
#include <panel.h>
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW **wins, int n);
void win_show(WINDOW *win, char *label, int label_color);
void print_in_middle(WINDOW *win, int starty, int startx,
int width, char *string, chtype color);
int main()
{
WINDOW *my_wins[3];
PANEL *my_panels[3];
PANEL *top;
int ch;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_BLUE, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_wins(my_wins, 3);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
/* Set up the user pointers to the next panel */
set_panel_userptr(my_panels[0], my_panels[1]);
set_panel_userptr(my_panels[1], my_panels[2]);
set_panel_userptr(my_panels[2], my_panels[0]);
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
attron(COLOR_PAIR(4));
mvprintw(LINES - 2, 0, "Use tab to browse through the windows (F1 to Exit)");
attroff(COLOR_PAIR(4));
doupdate();
top = my_panels[2];
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case 9:
top = (PANEL *)panel_userptr(top);
top_panel(top);
break;
}
update_panels();
doupdate();
}
endwin();
return 0;
}
/* Put all the windows */
void init_wins(WINDOW **wins, int n)
{
int x, y, i;
char label[80];
y = 2;
x = 10;
for(i = 0; i < n; ++i)
{ wins[i] = newwin(NLINES, NCOLS, y, x);
sprintf(label, "Window Number %d", i + 1);
win_show(wins[i], label, i + 1);
y += 3;
x += 7;
}
}
/* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color)
{
int startx, starty, height, width;
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
box(win, 0, 0);
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
}
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_panel_userptr(). Доступ до ного здійснюється за допомогою функції panel_userptr(), яка поверне користувацький покажчик панелі, вказаної як аргумент. Після знаходження наступної панелі циклу, я вивожу її назовні завдяки top_panel(). Ця функція виводить, вказану їй як аргумент, панель наверх стосу панелей.
Переміщення та зміна розміру панелей
Щоб перемістити панель у бажане місце, можна використати функцію move_panel(). Вона не змінює положення панелі в стосі. Впевніться, що звернулися до move_panel() замість mvwin() стосовно вікна, прив'язаного до панелі.
Зміна розміру панелі представляє дещо складнішу задачу. Безпосередньої функції для зміни розміру вікна, прив'язаного до панелі, не існує. Виходом з цього положення, буде створити нове вікно бажаного розміру, і змінити вікна панелей за допомогою replace_panel() (функції заміни панелей). Не забудьте видалити старе вікно. Назбу вікна, пов'язаного з панеллю можна з'язувати, зскориставшись із panel_window().
Наступна, відносно проста, програма ілюструє ці концепції. Ви можете переходити від одного вікна до іншого за допомогою клавіші , як звичайно. Для зміни розміру діючої панелі, притисніть 'r' (англ. resize), а для переміщення - 'm' (англ. move). Після цього, використайте клавіші-стрілки для модифікації розміру або переміщення, і , для завершення операції. Цей приклад застосовує користувацький покажчик, щоб отримати необхідні дані для цих дій.
Приклад 16. Переміщення та зміна розміру панелей
#include <panel.h>
typedef struct _PANEL_DATA {
int x, y, w, h;
char label[80];
int label_color;
PANEL *next;
}PANEL_DATA;
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW **wins, int n);
void win_show(WINDOW *win, char *label, int label_color);
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);
void set_user_ptrs(PANEL **panels, int n);
int main()
{
WINDOW *my_wins[3];
PANEL *my_panels[3];
PANEL_DATA *top;
PANEL *stack_top;
WINDOW *temp_win, *old_win;
int ch;
int newx, newy, neww, newh;
int size = FALSE, move = FALSE;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_BLUE, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_wins(my_wins, 3);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
set_user_ptrs(my_panels, 3);
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
attron(COLOR_PAIR(4));
mvprintw(LINES - 3, 0, "Use 'm' for moving, 'r' for resizing");
mvprintw(LINES - 2, 0, "Use tab to browse through the windows (F1 to Exit)");
attroff(COLOR_PAIR(4));
doupdate();
stack_top = my_panels[2];
top = (PANEL_DATA *)panel_userptr(stack_top);
newx = top->x;
newy = top->y;
neww = top->w;
newh = top->h;
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case 9: /* Tab */
top = (PANEL_DATA *)panel_userptr(stack_top);
top_panel(top->next);
stack_top = top->next;
top = (PANEL_DATA *)panel_userptr(stack_top);
newx = top->x;
newy = top->y;
neww = top->w;
newh = top->h;
break;
case 'r': /* Re-Size*/
size = TRUE;
attron(COLOR_PAIR(4));
mvprintw(LINES - 4, 0, "Entered Resizing :Use Arrow Keys to resize and press <ENTER> to end resizing");
refresh();
attroff(COLOR_PAIR(4));
break;
case 'm': /* Move */
attron(COLOR_PAIR(4));
mvprintw(LINES - 4, 0, "Entered Moving: Use Arrow Keys to Move and press <ENTER> to end moving");
refresh();
attroff(COLOR_PAIR(4));
move = TRUE;
break;
case KEY_LEFT:
if(size == TRUE)
{ --newx;
++neww;
}
if(move == TRUE)
--newx;
break;
case KEY_RIGHT:
if(size == TRUE)
{ ++newx;
--neww;
}
if(move == TRUE)
++newx;
break;
case KEY_UP:
if(size == TRUE)
{ --newy;
++newh;
}
if(move == TRUE)
--newy;
break;
case KEY_DOWN:
if(size == TRUE)
{ ++newy;
--newh;
}
if(move == TRUE)
++newy;
break;
case 10: /* Enter */
move(LINES - 4, 0);
clrtoeol();
refresh();
if(size == TRUE)
{ old_win = panel_window(stack_top);
temp_win = newwin(newh, neww, newy, newx);
replace_panel(stack_top, temp_win);
win_show(temp_win, top->label, top->label_color);
delwin(old_win);
size = FALSE;
}
if(move == TRUE)
{ move_panel(stack_top, newy, newx);
move = FALSE;
}
break;
}
attron(COLOR_PAIR(4));
mvprintw(LINES - 3, 0, "Use 'm' for moving, 'r' for resizing");
mvprintw(LINES - 2, 0, "Use tab to browse through the windows (F1 to Exit)");
attroff(COLOR_PAIR(4));
refresh();
update_panels();
doupdate();
}
endwin();
return 0;
}
/* Put all the windows */
void init_wins(WINDOW **wins, int n)
{
int x, y, i;
char label[80];
y = 2;
x = 10;
for(i = 0; i < n; ++i)
{ wins[i] = newwin(NLINES, NCOLS, y, x);
sprintf(label, "Window Number %d", i + 1);
win_show(wins[i], label, i + 1);
y += 3;
x += 7;
}
}
/* Set the PANEL_DATA structures for individual panels */
void set_user_ptrs(PANEL **panels, int n)
{
PANEL_DATA *ptrs;
WINDOW *win;
int x, y, w, h, i;
char temp[80];
ptrs = (PANEL_DATA *)calloc(n, sizeof(PANEL_DATA));
for(i = 0;i < n; ++i) {
win = panel_window(panels[i]);
getbegyx(win, y, x);
getmaxyx(win, h, w);
ptrs[i].x = x;
ptrs[i].y = y;
ptrs[i].w = w;
ptrs[i].h = h;
sprintf(temp, "Window Number %d", i + 1);
strcpy(ptrs[i].label, temp);
ptrs[i].label_color = i + 1;
if(i + 1 == n)
ptrs[i].next = panels[0];
else
ptrs[i].next = panels[i + 1];
set_panel_userptr(panels[i], &ptrs[i]);
}
}
/* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color)
{
int startx, starty, height, width;
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
box(win, 0, 0);
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
}
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();
}
Зосередьтися на основному циклові while. Як тільки він знаходить тип
натисненої клавіші, виконується відповідна дія. Якщо притиснуто 'r',
розпочнеться режим зміни розміру. В ньому, буде оновлено розміри вікна,
у відповідності від того, які клавіші-стрілки натиснув користувач.
Після натуску <ENTER>
, зміну розміру припинено, і панель набуде
розмірів, згідно з нашим поясненням вище. Під час режиму зміни розміру,
програма, на жаль, не показує, як саме вікно змінюється. Ми залишемо
це як вправу читачеві, - вивід облямівки з крапок під час зміни позиції
сторін прямокутника вікна.
Якщо притиснуто 'm', розпочнеться режим пересування. Це трохи простіше
за зміну ромірів. Під час натиску стрілок, змінюються координати положення,
тоді як <ENTER>
змусить перемістити панель шляхом викликлику move_panel().
У цій програмі, користувацькі дані, представлені як PANEL_DATA, відіграють дуже важливу роль у знаходженні інформації стосовно панелі. Як вказано в коментарях, PANEL_DATA зберігає розміри панелі, позначку, колір позначки, та покажчик на наступну панель у циклі.
Приховування та показ панелей
Панель можна сховати за допомогою функції hide_panel(). Ця функція просто видаляє вказану панель зі стосу панелей, таким чином прибираючи її з екрану після того, як ви зробите update_panels() і doupdate(). Вона, насправді, не руйнує структуру типу PANEL, що відповідає схованій панелі. Останню можна вивести знову, якщо вжити show_panel().
Наступна програма демонструє приховування панелей. Натисніть 'a', 'b' або 'c', щоб сховати або вивести перше, друге або третє вікно, відповідно. Програма також застосовує користувацькі дані, що включають маленьку змінну hide, яка вказує на те, чи панель є прихованою, чи ні. Невідомо чому, функція panel_hidden(), яка повинна виявляти, чи панель прихована, чи ні, не працює. З цієї причини, цей текст містить також зголошення про ваду, зроблене Майклом Андресом.
Приклад 17. Приховування та вивід панелей
#include <panel.h>
typedef struct _PANEL_DATA {
int hide; /* TRUE if panel is hidden */
} PANEL_DATA;
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW ** wins, int n);
void win_show(WINDOW * win, char *label, int label_color);
void print_in_middle(WINDOW * win, int starty, int startx, int width,
char *string, chtype color);
int main()
{
WINDOW *my_wins[3];
PANEL *my_panels[3];
PANEL_DATA panel_datas[3];
PANEL_DATA *temp;
int ch;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_BLUE, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_wins(my_wins, 3);
/* Attach a panel to each window *//* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
/* Initialize panel datas saying that nothing is hidden */
panel_datas[0].hide = FALSE;
panel_datas[1].hide = FALSE;
panel_datas[2].hide = FALSE;
set_panel_userptr(my_panels[0], &panel_datas[0]);
set_panel_userptr(my_panels[1], &panel_datas[1]);
set_panel_userptr(my_panels[2], &panel_datas[2]);
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
attron(COLOR_PAIR(4));
mvprintw(LINES - 3, 0,
"Show or Hide a window with 'a'(first window) 'b'(Second Window) 'c'(Third Window)");
mvprintw(LINES - 2, 0, "F1 to Exit");
attroff(COLOR_PAIR(4));
doupdate();
while ((ch = getch()) != KEY_F(1)) {
switch (ch) {
case 'a':
temp = (PANEL_DATA *) panel_userptr(my_panels[0]);
if (temp->hide == FALSE) {
hide_panel(my_panels[0]);
temp->hide = TRUE;
} else {
show_panel(my_panels[0]);
temp->hide = FALSE;
}
break;
case 'b':
temp = (PANEL_DATA *) panel_userptr(my_panels[1]);
if (temp->hide == FALSE) {
hide_panel(my_panels[1]);
temp->hide = TRUE;
} else {
show_panel(my_panels[1]);
temp->hide = FALSE;
}
break;
case 'c':
temp = (PANEL_DATA *) panel_userptr(my_panels[2]);
if (temp->hide == FALSE) {
hide_panel(my_panels[2]);
temp->hide = TRUE;
} else {
show_panel(my_panels[2]);
temp->hide = FALSE;
}
break;
}
update_panels();
doupdate();
}
endwin();
return 0;
}
/* Put all the windows */
void init_wins(WINDOW ** wins, int n)
{
int x, y, i;
char label[80];
y = 2;
x = 10;
for (i = 0; i < n; ++i) {
wins[i] = newwin(NLINES, NCOLS, y, x);
sprintf(label, "Window Number %d", i + 1);
win_show(wins[i], label, i + 1);
y += 3;
x += 7;
}
}
/* Show the window with a border and a label */
void win_show(WINDOW * win, char *label, int label_color)
{
int startx, starty, height, width;
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
box(win, 0, 0);
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
}
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();
}
Функції panel_above() і panel_below()
За допомогою функцій panel_above() та panel_below() можна дізнатисям, яка панель знаходиться над і під певною панеллю. Якщо аргументом цієї функції служить NULL, тоді вони повертають покажчики на нижню панель та верхню панель, відповідно.