else{ /* ищем самое верхнее видимое окно */
for(topw = EOW, n=botw; n != EOW; n=wins[n].next)
/* спрятанное окно не может быть верхним */
if( wins[n].busy == W_VISIBLE) topw = n;
/* Может совсем не оказаться видимых окон; тогда
* topw == EOW, хотя botw != EOW. Макрос TOPW предложит
* в качестве верхнего окна окно stdscr */
}
}
/* Виртуально перерисовать окна в списке в порядке снизу вверх */
static void WinRefresh(){ register nw;
/* чистый фон экрана */
touchwin(stdscr); wnoutrefresh(stdscr);
if(botw != EOW) for(nw=botw; nw != EOW; nw=wins[nw].next)
if(wins[nw].busy == W_VISIBLE){
touchwin(wins[nw].w); wnoutrefresh(wins[nw].w);
}
}
/* Исключить окно из списка не уничтожая ячейку */
static int WinDelList(WINDOW *w){ register nw, prev;
if(botw == EOW) return EOW; /* список пуст */
for(prev=EOW, nw=botw; nw != EOW; prev=nw, nw=wins[nw].next)
if(wins[nw].w == w){
if(prev == EOW) botw = wins[nw].next; /* было дно стопки */
else wins[prev].next = wins[nw].next;
return nw; /* номер ячейки в таблице окон */
}
return EOW; /* окна не было в списке */
}
/* Сделать окно верхним, если его еще не было в таблице - занести */
int RaiseWin(WINDOW *w){ int nw, n;
if((nw = WinDelList(w)) == EOW){ /* не было в списке */
for(nw=0; nw < MAXW; nw++) /* занести в таблицу */
if( !iswindow(nw)){ wins[nw].w = w; break; }
if(nw == MAXW){ beep(); return EOW; } /* слишком много окон */
}
/* поместить окно nw на вершину списка */
if(botw == EOW) botw = nw;
else{ for(n = botw; wins[n].next != EOW; n=wins[n].next);
wins[n].next = nw;
}
wins[nw].busy = W_VISIBLE; /* окно видимо, слот занят */
wins[topw = nw].next = EOW; WinRefresh(); return nw;
}
/* Удалить окно из списка и (возможно) уничтожить */
/* Окно при этом исчезнет с экрана */
void DestroyWin(WINDOW *w, int destroy){ int nw;
if((nw = WinDelList(w)) != EOW){ /* окно было в списке */
ComputeTopWin();
wins[nw].busy = W_FREE; /* ячейка свободна */
wins[nw].w = NULL;
}
if(destroy) delwin(w); /* уничтожить curses-ное окно */
WinRefresh();
}
void PopWin(){ KillWin(TOPW); }
/* Спрятать окно, и при этом сделать его самым нижним. */
int HideWin(WINDOW *w){ register nw, prev;
if(botw == EOW) return EOW; /* список пуст */
for(prev = EOW, nw = botw; nw != EOW; prev = nw, nw = wins[nw].next )
if(wins[nw].w == w){
wnoutrefresh(w); /* вместо untouchwin(w); */
wins[nw].busy = W_HIDDEN; /* спрятано */
if( nw != botw ){
wins[prev].next = wins[nw].next; /* удалить из списка */
wins[nw].next = botw; botw = nw; /* на дно стопки */
}
WinRefresh();
ComputeTopWin();
return nw;
}
return EOW; /* нет в списке */
}
/* _______________ ОФОРМИТЕЛЬСКИЕ РАБОТЫ _____________________ */
/* Нарисовать горизонтальную линию */
void whorline(WINDOW *w, int y, int x1, int x2){
for( ; x1 <= x2; x1++) mvwaddch(w, y, x1, HOR_LINE);
}
/* Нарисовать вертикальную линию */
void wverline(WINDOW *w, int x, int y1, int y2){
for( ; y1 <= y2; y1++) mvwaddch(w, y1, x, VER_LINE);
}
/* Нарисовать прямоугольную рамку */
void wbox(WINDOW *w, int x1, int y1, int x2, int y2){
whorline(w, y1, x1+1, x2-1);
whorline(w, y2, x1+1, x2-1);
wverline(w, x1, y1+1, y2-1);
wverline(w, x2, y1+1, y2-1);
/* Углы */
mvwaddch (w, y1, x1, UPPER_LEFT);
mvwaddch (w, y1, x2, UPPER_RIGHT);
mvwaddch (w, y2, x1, LOWER_LEFT);
/* Нижний правый угол нельзя занимать ! */
if(! (wbegx(w) + x2 == COLS-1 && wbegy(w) + y2 == LINES-1))
mvwaddch (w, y2, x2, LOWER_RIGHT);
}
/* Нарисовать рамку вокруг окна */
void wborder(WINDOW *w){ wbox(w, 0, 0, wcols(w)-1, wlines(w)-1); }
/* Очистить прямоугольную область в окне */
void wboxerase(WINDOW *w, int x1, int y1, int x2, int y2){
int x, y; register i, j; getyx(w, y, x);
for(i=y1; i <= y2; ++i) for(j=x1; j <= x2; j++)
mvwaddch(w, i, j, ' ');
wmove(w, y, x);
}
/* Нарисовать рамку и заголовок у окна */
void WinBorder (WINDOW *w, int bgattrib, int titleattrib, char *title,
int scrollok, int clear){
register x, y;

wattrset (w, bgattrib); /* задать цвет окна */
if(clear) werase(w); /* заполнить окно цветными пробелами */
wborder (w); /* нарисовать рамку вокруг окна */
if (title) { /* если есть заголовок ... */
for (x = 1; x < wcols (w) - 1; x++){
wattrset(w, bgattrib); mvwaddch (w, 2, x, HOR_LINE);
/* очистка поля заголовка */
wattrset(w, titleattrib); mvwaddch (w, 1, x, ' ');
}
wattrset(w, bgattrib);
mvwaddch (w, 2, 0, LEFT_JOIN);
mvwaddch (w, 2, wcols (w) - 1, RIGHT_JOIN);
wattrset (w, A_BOLD | titleattrib);
mvwaddstr(w, 1, (wcols(w)-strlen(title))/2, title);
wattrset (w, bgattrib);
}
if (scrollok & BAR_VER) { /* выделить столбец под scroll bar. */
int ystart = WY(title, 0), xend = XEND(w, scrollok);
for (y = ystart; y < wlines (w) - 1; y++)
mvwaddch (w, y, xend, VER_LINE);
mvwaddch (w, wlines (w)-1, xend, BOTTOM_JOIN);
mvwaddch (w, ystart-1, xend, TOP_JOIN);
}
/* затычка */
if(wcols(w)==COLS && wlines(w)==LINES){ wattrset(w, A_NORMAL);
mvwaddch(w, LINES-1, COLS-1, ' ');
}
wattrset (w, bgattrib);
}
/* Нарисовать вертикальный scroll bar (горизонтальный не сделан) */
/* Написано не очень аккуратно */
void WinScrollBar(WINDOW *w, int whichbar, int n, int among,
char *title, int bgattrib){
register y, i;
int starty = WY(title, 0);
int endy = wlines (w) - 1;
int x = XEND(w, whichbar) + 1;
int height = endy - starty ;

if(whichbar & BAR_VER){ /* вертикальный */
wattrset (w, A_NORMAL);
for (y = starty; y < endy; y++)
for (i = 0; i < BARWIDTH; i++)
mvwaddch (w, y, x + i, ' ');
y = starty;
if(among > 1) y += ((long) (height - BARHEIGHT) * n / (among - 1));
wattron(w, A_BOLD);
for (i = 0; i < BARWIDTH; i++)
mvwaddch (w, y, x + i, BOX);
wattrset(w, bgattrib | A_BOLD );
if( wcols(w) >= 10 )
mvwprintw(w, 0, wcols(w)-9, "%03d/%03d", n+1, among);
}
wattrset (w, bgattrib);
}
#ifdef TEST
main(){ WINDOW *w[5]; register i, y;
initscr(); /* запустить curses */
w[0] = newwin(16, 20, 4, 43); /* создать 5 окон */
w[1] = newwin(12, 20, 7, 34);
w[2] = newwin(6, 30, 3, 40);
w[3] = newwin(7, 35, 12, 38);
w[4] = newwin(6, 20, 11, 54);
for(i=0; i < 5; i++){
keypad (w[i], TRUE);
wattrset(w[i], A_REVERSE); werase(w[i]);
wborder (w[i]); mvwprintw(w[i], 1, 2, "Window %d", i);
RaiseWin(w[i]); /* сделать верхним окном */
}
noecho(); cbreak(); /* прозрачный ввод */
for(;botw != EOW;){ int c;
/* нарисовать порядок окон */
for(i=botw, y=0; y < 5; y++, i=(i==EOW ? EOW : wins[i].next))
mvprintw(8 - y, 5, i==EOW ? "~": "%d%c", i,
wins[i].busy == W_HIDDEN ? 'h':' ');
mvprintw(9, 5, "topw=%3d botw=%3d", topw, botw);
wnoutrefresh(stdscr); /* вирт. проявка этих цифр */
c = WinGetch(TOPW);
/* здесь происходит doupdate();
* и только в этот момент картинка проявляется */

switch(c){
case KEY_DC: PopWin(); break;
case KEY_IC: KillWin(BOTW); break;
case '0': case '1': case '2': case '3': case '4': case '5':
c -= '0'; if( !iswindow(c)){ beep(); break; }
RaiseWin(WIN(c)); break;
case 'D': KillWin(w[2]); break;
case 'h': HideWin(BOTW); break;
case 'H': HideWin(TOPW); break;
case ESC: goto out;
default: waddch(TOPW, c & 0377); break;
}
}
mvaddstr(LINES-2, 0, "Больше нет окон"); refresh();
out: echo(); nocbreak(); endwin();
}
#endif

/* Пример 18 */
/* _______________________ файл glob.h ___________________________*/
/* ПОДДЕРЖКА СПИСКА ИМЕН ФАЙЛОВ ЗАДАННОГО КАТАЛОГА */
/* ______________________________________________________________ */
#define FILF

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
# define DIR_SIZE 14
extern char *malloc(unsigned); char *strdup(const char *str);
extern char *getenv();
extern char *strchr(char *, char), *strrchr(char *, char);
#define ISDIR(mode) ((mode & S_IFMT) == S_IFDIR)
#define ISDEV(mode) ((mode & S_IFMT) & (S_IFCHR|S_IFBLK))
#define ISREG(mode) ((mode & S_IFMT) == S_IFREG)
#define ISEXE(mode) ((mode & S_IFMT) == S_IFREG && (mode & 0111))
#define isdir(st) ISDIR(st.st_mode)
#define isdev(st) ISDEV(st.st_mode)
#define isreg(st) ISREG(st.st_mode)
#define isexe(st) ISEXE(st.st_mode)
#define YES 1
#define NO 0
#define I_DIR 0x01 /* это имя каталога */
#define I_EXE 0x02 /* это выполняемый файл */
#define I_NOSEL 0x04 /* строку нельзя выбрать */
#define I_SYS (I_DIR | I_EXE | I_NOSEL)
/* Скопировано из treemk.c
* Лучше просто написать #include "glob.h" в файле treemk.c
*/
#define FAILURE (-1) /* код неудачи */
#define SUCCESS 1 /* код успеха */
#define WARNING 0 /* нефатальная ошибка */

typedef struct _info { /* структура элемента каталога */
char *s; /* имя файла */
short fl; /* флаг */
union _any{
int (*act)(); /* возможно связанное действие */
char *note; /* или комментарий */
unsigned i; /* или еще какой-то параметр */
struct _info *inf;
} any; /* вспомогательное поле */
#ifdef FILF
/* дополнительные необязательные параметры, получаемые из stat(); */
long size;
int uid, gid;
unsigned short mode;
#endif
} Info;
typedef union _any Any;

extern Info NullInfo;
#define MAX_ARGV 256 /* Максимальное число имен в каталоге */
typedef struct { /* Содержимое каталога name */
time_t lastRead; /* время последнего чтения каталога */
Info *files; /* содержимое каталога */
char *name; /* имя каталога */
ino_t ino; dev_t dev; /* I-узел и устройство */
char valid; /* существует ли этот каталог вообще */
short readErrors; /* != 0, если каталог не читается */
} DirContents;
/* Виды сортировки имен в каталоге */
typedef enum { SORT_ASC, SORT_DESC, SORT_SUFX,
SORT_NOSORT, SORT_SIZE } Sort;
extern Sort sorttype; extern int in_the_root;

int gcmps (const void *p1, const void *p2);
Info *blkcpy(Info *v); void blkfree(Info *v);
Info *glob(char **patvec, char *dirname);
Info *glb(char *pattern, char *dirname);
int ReadDir(char *dirname, DirContents *d);

struct savech{ char *s, c; };
#define SAVE(sv, str) (sv).s = (str); (sv).c = *(str)
#define RESTORE(sv) if((sv).s) *(sv).s = (sv).c

/* _______________________ файл glob.c __________________________ */
#include "glob.h"
int in_the_root = NO; /* читаем корневой каталог ? */
Sort sorttype = SORT_SUFX; /* сортировка имен по суффиксу */
Info NullInfo = { NULL, 0 }; /* и прочие поля = 0 (если есть) */

char *strdup(const char *s){
char *p = malloc(strlen(s)+1); if(p)strcpy(p, s); return p; }

/* Содержится ли любой из символов в строке ? */
int any(register char *s, register char *p){
while( *s ){ if( strchr(p, *s)) return YES; s++; }
return NO;
}
/* Найти последнюю точку в имени */
static char *lastpoint (char *s)
{ register char *last; static char no[] = "";
if((last = strchr(s, '.')) == NULL) return no;
/* если имя начинается с точки - не считать ее */
return( last == s ? no : last );
}
/* Сравнение строк с учетом их суффиксов */
int strsfxcmp (register char *s1, register char *s2){
char *p1, *p2, c1, c2; int code;
p1 = lastpoint (s1); p2 = lastpoint (s2);
if (code = strcmp (p1, p2)) return code; /* суффиксы разные */
/* иначе: суффиксы равны. Сортируем по головам */
c1 = *p1; c2 = *p2; *p1 = '\0'; *p2 = '\0'; /* временно */
code = strcmp (s1, s2);
*p1 = c1; *p2 = c2; return code;
}
/* Функция сортировки */
int gcmps(const void *p1, const void *p2){
Info *s1 = (Info *) p1, *s2 = (Info *) p2;
switch( sorttype ){
default:
case SORT_ASC: return strcmp(s1->s, s2->s);
case SORT_DESC: return -strcmp(s1->s, s2->s);
case SORT_SUFX: return strsfxcmp(s1->s, s2->s);
case SORT_NOSORT: return (-1);
#ifdef FILF
case SORT_SIZE: return (s1->size < s2->size ? -1 :
s1->size == s2->size ? 0 : 1 );
#endif
}
}
/* Копирование блока */
Info *blkcpy(Info *v){
register i, len;
Info *vect = (Info *) malloc(((len=blklen(v)) + 1) * sizeof(Info));
for(i=0; i < len; i++ ) vect[i] = v[i];
vect[len] = NullInfo; return vect;
}
/* Измерение длины блока */
int blklen(Info *v){
int i = 0;
while( v->s ) i++, v++;
return i;
}
/* Очистка блока (уничтожение) */
void blkfree(Info *v){
Info *all = v;
while( v->s )
free((char *) v->s ), v++;
free((char *) all );
}
/* Сравнение двух блоков */
int blkcmp( register Info *p, register Info *q ){
while( p->s && q->s && !strcmp(p->s, q->s) &&
(p->fl & I_SYS) == (q->fl & I_SYS)){ p++; q++; }
if( p->s == NULL && q->s == NULL )
return 0; /* совпадают */
return 1; /* различаются */
}
char globchars [] = "*?[";
Info gargv[MAX_ARGV]; int gargc;
static short readErrors;
void greset() { gargc = 0; readErrors = 0; }

/* Расширить шаблон имен файлов в сами имена */
static void globone(char *pattern, char dirname[]){
extern char *strdup(); struct stat st;
DIR *dirf; struct dirent *d;
if( any(pattern, globchars) == NO ){ /* no glob */
gargv[gargc] = NullInfo;
gargv[gargc].s = strdup(pattern);
gargc++;
gargv[gargc] = NullInfo;
return;
}
if((dirf = opendir(dirname)) == NULL){ readErrors++; goto out; }
while(d = readdir(dirf)){
if(match(d->d_name, pattern)){
char fullname[512];
if( sorttype != SORT_NOSORT && !strcmp(d->d_name, "."))
continue;
/* В корневом каталоге имя ".." следует пропускать */
if( in_the_root && !strcmp(d->d_name, "..")) continue;
/* Проверка на переполнение */
if( gargc == MAX_ARGV - 1){
free(gargv[gargc-1].s);
gargv[gargc-1].s = strdup(" Слишком много файлов!!!");
gargv[gargc-1].fl = I_SYS;
break;
}
gargv[gargc] = NullInfo;
gargv[gargc].s = strdup(d->d_name);
sprintf(fullname, "%s/%s", dirname, d->d_name);
if(stat(fullname, &st) < 0) gargv[gargc].fl |= I_NOSEL;
else if(isdir(st)) gargv[gargc].fl |= I_DIR;
else if(isexe(st)) gargv[gargc].fl |= I_EXE;
#ifdef FILF
gargv[gargc].size = st.st_size;
gargv[gargc].uid = st.st_uid;
gargv[gargc].gid = st.st_gid;
gargv[gargc].mode = st.st_mode;
#endif
gargc++;
}
}
closedir(dirf);
out: gargv[ gargc ] = NullInfo;
}
/* Расширить несколько шаблонов */
Info *glob(char **patvec, char *dirname){
greset();
while(*patvec){ globone(*patvec, dirname); patvec++; }
qsort(gargv, gargc, sizeof(Info), gcmps);
return blkcpy(gargv);
}
Info *glb(char *pattern, char *dirname){ char *pv[2];
pv[0] = pattern; pv[1] = NULL; return glob(pv, dirname);
}
/* Прочесть содержимое каталога, если оно изменилось:
* Вернуть: 0 - каталог не менялся;
* 1 - изменился;
* 1000 - изменился рабочий каталог (chdir);
* -1 - каталог не существует;
*/
int ReadDir(char *dirname, DirContents *d){
struct stat st; Info *newFiles;
int save = YES; /* сохранять метки у файлов ? */
int dirchanged = NO; /* сделан chdir() ? */

/* каталог мог быть удален, а мы об этом не извещены */
if( stat(dirname, &st) < 0 ){
d->valid = NO; d->lastRead = 0L;
if(d->files) blkfree(d->files);
d->files = blkcpy( &NullInfo );
return (-1); /* не существует */
} else d->valid = YES;
/* не изменился ли адрес каталога, хранимого в *d ? */
if(d->ino != st.st_ino || d->dev != st.st_dev){ /* изменился */
d->ino = st.st_ino; d->dev = st.st_dev;
save = NO; d->lastRead = 0L; dirchanged = YES;
}
/* не изменилось ли имя каталога ? */
if( !d->name || strcmp(d->name, dirname)){
if(d->name) free(d->name); d->name = strdup(dirname);
/* save=NO; d->lastRead = 0; */
}
/* проверим, был ли модифицирован каталог ? */
if( save==YES && d->files && st.st_mtime == d->lastRead )
return 0; /* содержимое каталога не менялось */
d->lastRead = st.st_mtime;
newFiles = glb("*", d->name); /* прочесть содержимое каталога */
if(save == YES && d->files){
register Info *p, *q;
if( !blkcmp(newFiles, d->files)){
blkfree(newFiles); return 0; /* не изменилось */
} /* иначе сохранить пометки */
for(p= d->files; p->s; p++)
for(q= newFiles; q->s; ++q)
if( !strcmp(p->s, q->s)){
q->fl |= p->fl & ~I_SYS; break;
}
}
if(d->files) blkfree(d->files);
d->files = newFiles; d->readErrors = readErrors;
return 1 + (dirchanged ? 999:0);
/* каталог изменился */
}

/* Пример 19 */
/* ________________________файл menu.h __________________________ */
/* РОЛЛИРУЕМОЕ МЕНЮ */
/* _______________________________________________________________*/
#include <ctype.h>
#include <sys/param.h>
#define M_HOT '\\' /* горячий ключ */
#define M_CTRL '\1' /* признак горизонтальной черты */
#define MXEND(m) XEND((m)->win,(m)->scrollok)
#define NOKEY (-33) /* горячего ключа нет */
#define MAXLEN MAXPATHLEN /* макс. длина имен файлов */
typedef enum { /* Коды, возвращаемые handler-ом (HandlerReply *reply) */
HANDLER_OUT = 0, /* выйти из функции выбора */
HANDLER_CONTINUE = 1, /* читать очередную букву */
HANDLER_NEWCHAR = 2, /* пойти на анализ кода handler-ом. */
HANDLER_SWITCH = 3, /* пойти на switch() */
HANDLER_AGAIN = 4 /* перезапустить всю функцию выбора */
} HandlerReply;
typedef struct _Menu { /* паспорт меню */
int nitems; /* число элементов меню */
Info *items; /* сам массив элементов */
int *hotkeys; /* "горячие" клавиши */
int key; /* клавиша, завершившая выбор */
int current; /* текущая строка списка */
int shift; /* сдвиг окна от начала меню */
int scrollok; /* окно роллируемое ? */
WINDOW *win; /* окно для меню */
int left, top, height, width; /* координаты меню на экране и
размер окна win */
int textwidth, textheight; /* размер подокна выбора */
int bg_attrib; /* атрибут фона окна */
int sel_attrib; /* атрибут выбранной строки */
char *title; /* заголовок меню */
Point savep;
void (*showMe) (struct _Menu *m);
void (*scrollBar) (struct _Menu *m, int n, int among);
int *hitkeys; /* клавиши, обрабатываемые особо */
int (*handler) (struct _Menu *m, int c, HandlerReply *reply);

} Menu;
/* Структура окна с меню:
*--------------* +0
| ЗАГОЛОВОК | +1
*-----------*--* +2
|+ стр1ааа | | +3
| стр2ббб |##| <- scroll bar шириной BARWIDTH
| стр3ввв | |
*___________|__*
|DX| len |DX|BS|
*/
/* Метки у элементов меню */
#define M_BOLD I_DIR /* яркая строка */
#define M_HATCH 0x08 /* строка тусклая */
#define M_LFT 0x10 /* для использования в pulldown menu */
#define M_RGT 0x20 /* для использования в pulldown menu */
#define M_LABEL 0x40 /* строка имеет метку */
#define M_LEFT (-111)
#define M_RIGHT (-112)
#define TOTAL_NOSEL (-I_NOSEL)

#define M_SET(m, i, flg) (((m)->items)[i]). fl |= (flg)
#define M_CLR(m, i, flg) (((m)->items)[i]). fl &= ~(flg)
#define M_TST(m, i, flg) ((((m)->items)[i]).fl & (flg))
#define M_ITEM(m, i) ((((m)->items)[i]).s)
/* Прототипы */
int MnuInit (Menu *m); void MnuDeinit (Menu *m);
void MnuDrawItem (Menu * m, int y, int reverse, int selection);
int MnuNext (Menu *m); int MnuPrev (Menu *m);
int MnuFirst(Menu *m); int MnuLast (Menu *m);
int MnuPgUp (Menu *m); int MnuPgDn (Menu *m);
int MnuThis (Menu *m); int MnuHot (Menu *m, unsigned c);
int MnuName (Menu *m, char *name);
void MnuDraw (Menu *m); void MnuHide(Menu *m);
void MnuPointAt (Menu *m, int y);
void MnuPoint (Menu *m, int line, int eraseOld);
int MnuUsualSelect (Menu *m, int block);
int is_in(register int c, register int s[]);
char *MnuConvert (char *s, int *pos);

#define M_REFUSED(m) ((m)->key < 0 || (m)->key == ESC )
#define MNU_DY 1

/* _______________________ файл menu.c __________________________ */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include <signal.h>
/* ---------------- implementation module ------------------------- */
/* Не входит ли символ в специальный набор? Массив завершается (-1) */
int is_in(register int c, register int s[]){
while (*s >= 0) {
if(*s == c) return YES;
s++;
}
return NO;
}
char STRING_BUFFER[ MAXLEN ]; /* временный буфер */
/* Снять пометку с "горячей" клавиши. */
char *MnuConvert (char *s, int *pos){
int i = 0;
*pos = (-1);
while (*s) {
if (*s == M_HOT) { *pos = i; s++; }
else STRING_BUFFER[i++] = *s++;
}
STRING_BUFFER[i] = '\0'; return STRING_BUFFER;
}
/* Рамка вокруг окна с меню */
static void MnuWin (Menu *m) {
WinBorder(m->win, m->bg_attrib, m->sel_attrib,
m->title, m->scrollok, YES);
}
/* Нарисовать scroll bar в нужной позиции */
static void MnuWinBar (Menu *m) {
WINDOW *w = m -> win; /* окно */
WinScrollBar(m->win, m->scrollok, m->current, m->nitems,
m->title, m->bg_attrib);
if(m->scrollBar) /* может быть еще какие-то наши действия */
m->scrollBar(m, m->current, m->nitems);
}
/* Роллирование меню */
/*
+---+----->+-МАССИВ--+<-----+
| n|всего |;;;;;;;;;| | shift сдвиг до окна
cur| | |;;;;;;;;;| |
текущий| | =ОКНО============<---------|
элемент| | I ;;;;;;;;; I | y строка окна
0..n-1 | | I ;;;;;;;;; I | |
+------>I###:::::::::###I<--+ |h высота окна
| I ;;;;;;;;; I |
| =================<---------+
| |;;;;;;;;;|
+----->|_________|
*/

static void MnuRoll (Menu *ptr,
int aid, /* какой новый элемент выбрать (0..n-1) */
int *cur, int *shift,
int h, /* высота окна (строк) */
int n, /* высота items[] (элементов) */
void (*go) (Menu *p, int y, int eraseOld),
void (*draw) (Menu *p),
int DY
) {
int y = *cur - *shift; /* текущая строка окна */
int newshift; /* новый сдвиг */
int AID_UP, AID_DN;

if (aid < 0 || aid >= n) return; /* incorrect */
if (y < 0 || y >= h) return; /* incorrect */
AID_UP = MIN (DY, n);
AID_DN = MAX (0, MIN (n, h - 1 - DY));

if (aid < *cur && y <= AID_UP && *shift > 0)
goto scroll; /* down */
if (aid > *cur && y >= AID_DN && *shift + h < n)
goto scroll; /* up */

if (*shift <= aid && aid < *shift + h) {
/* роллировать не надо, а просто пойти в нужную строку окна */
(*go) (ptr, aid - *shift, YES);
*cur = aid; /* это надо изменять ПОСЛЕ (*go)() !!! */
return;
}
scroll:
if (aid > *cur) newshift = aid - AID_DN; /* вверх up */
else if (aid < *cur) newshift = aid - AID_UP; /* вниз down */
else newshift = *shift;

if (newshift + h > n) newshift = n - h;
if (newshift < 0) newshift = 0;

*shift = newshift; *cur = aid;
(*draw) (ptr); /* перерисовать окно */
(*go) (ptr, aid - newshift, NO); /* встать в нужную строку окна */
}
/* Инициализация и разметка меню. На входе:
m->items Массив строк.
m->title Заголовок меню.
m->top Верхняя строка окна (y).
m->left Левый край (x).
m->handler Обработчик нажатия клавиш или NULL.
m->hitkeys Специальные клавиши [] или NULL.
m->bg_attrib Цвет фона окна.
m->sel_attrib Цвет селекции.
*/
int MnuInit (Menu *m) {
int len, pos; char *s; register i;

m -> current = m -> shift = 0;
m -> scrollok = m -> key = 0;
if (m -> hotkeys) { /* уничтожить старые "горячие" ключи */
free ((char *) m -> hotkeys); m -> hotkeys = (int *) NULL;
}
/* подсчет элементов меню */
for (i = 0; M_ITEM (m, i) != (char *) NULL; i++);
m -> nitems = i;

/* отвести массив для "горячих" клавиш */
if (m -> hotkeys = (int *) malloc (sizeof (int) * m -> nitems)) {
for (i = 0; i < m -> nitems; i++)
m -> hotkeys[i] = NOKEY;
}
/* подсчитать ширину текста */
len = m -> title ? strlen (m -> title) : 0;
for (i = 0; i < m -> nitems; i++) {
if (*(s = M_ITEM (m, i)) == M_CTRL) continue;
s = MnuConvert (s, &pos);
if (m -> hotkeys && pos >= 0)
m -> hotkeys[i] =
isupper (s[pos]) ? tolower (s[pos]) : s[pos];
if ((pos = strlen (s)) > len)
len = pos;
}
/* сформировать окно */
#define BORDERS_HEIGHT (2 + (m -> title ? 2 : 0))
#define BORDERS_WIDTH (2 + 2*DX + (m -> scrollok ? BARWIDTH + 1 : 0))
m -> height = m->nitems + BORDERS_HEIGHT;
if (m -> height > LINES * 2 / 3) { /* слишком высокое меню */
m -> scrollok = BAR_VER; /* будет роллироваться */
m -> height = LINES * 2 / 3;
}
if((m -> width = len + BORDERS_WIDTH) > COLS ) m->width = COLS;
m -> textheight = m->height - BORDERS_HEIGHT;
m -> textwidth = m->width - BORDERS_WIDTH;
/* окно должно лежать в пределах экрана */
if( m->top + m->height > LINES ) m->top = LINES - m->height;
if( m->left + m->width > COLS ) m->left = COLS - m->width;
if( m->top < 0 ) m->top = 0;
if( m->left < 0 ) m->left = 0;

if( m->win ){ /* уничтожить старое окно */
KillWin( m->win ); m->win = NULL; }
if( m->win == NULL ){ /* создать окно и нарисовать основу */
if((m->win = newwin(m->height, m->width, m->top, m->left))
== NULL) return 0;
keypad(m->win, TRUE); MnuWin(m); MnuDraw(m);
/* но окно пока не вставлено в список активных окон */
}
return ( m->win != NULL );
}
/* Деинициализировать меню */
void MnuDeinit (Menu *m) {
if( m->win ){ KillWin (m->win); m->win = NULL; }
if( m->hotkeys ){
free ((char *) m -> hotkeys); m -> hotkeys = (int *) NULL;
}
}
/* Спрятать меню */
void MnuHide (Menu *m){ if( m->win ) HideWin(m->win); }
/* Зачистить место для line-той строки окна меню */
static void MnuBox (Menu *m, int line, int attr) {
register WINDOW *w = m -> win;
register i, xend = MXEND(m);

wattrset (w, attr);
for (i = 1; i < xend; i++)
mvwaddch (w, line, i, ' ');
/* ликвидировать последствия M_CTRL-линии */
wattrset (w, m->bg_attrib);
mvwaddch (w, line, 0, VER_LINE);
mvwaddch (w, line, xend, VER_LINE);
wattrset (w, m->bg_attrib);
}
/* Нарисовать строку меню в y-ой строке окна выбора */
void MnuDrawItem (Menu *m, int y, int reverse, int selection) {
register WINDOW *w = m -> win;
int pos, l, attr;
int ay = WY (m->title, y), ax = WX (0);
char *s, c;
int hatch, bold, label, cont = NO, under;

if (y + m -> shift >= 0 && y + m -> shift < m -> nitems) {
s = M_ITEM (m, y + m -> shift);
hatch = M_TST (m, y + m -> shift, I_NOSEL) ||
M_TST (m, y + m -> shift, M_HATCH);
bold = M_TST (m, y + m -> shift, M_BOLD);
label = M_TST (m, y + m -> shift, M_LABEL);
under = M_TST (m, y + m -> shift, I_EXE);
}
else { /* строка вне допустимого диапазона */
s = "~"; label = hatch = bold = NO;
}
if (*s == M_CTRL) { /* нарисовать горизонтальную черту */
int x, xend = MXEND(m);
wattrset(w, m->bg_attrib);
for(x=1; x < xend; x++)
mvwaddch(w, ay, x, HOR_LINE);
mvwaddch (w, ay, 0, LEFT_JOIN);
mvwaddch (w, ay, xend, RIGHT_JOIN);
wattrset (w, m->bg_attrib);
return;
}
l = strlen(s = MnuConvert (s, &pos));
c = '\0';
if (l > m -> textwidth) { /* слишком длинная строка */
c = s[m -> textwidth];
s[m -> textwidth] = '\0'; cont = YES;
if (pos > m -> textwidth) pos = (-1);
}
if (selection)
MnuBox (m, ay, reverse ? m->sel_attrib : m->bg_attrib);
wattrset (w, attr = (bold ? A_BOLD : 0) |
(hatch ? A_ITALICS : 0) |
(under ? A_UNDERLINE : 0) |
(reverse ? m->sel_attrib : m->bg_attrib));
mvwaddstr (w, ay, ax, s);
if( cont ) mvwaddch(w, ay, ax+m->textwidth, RIGHT_TRIANG);
/* Hot key letter */
if (pos >= 0) {
wattron (w, bold ? A_ITALICS : A_BOLD);
mvwaddch (w, ay, WX(pos), s[pos]);
}
if (label){ /* строка помечена */
wattrset (w, attr | A_BOLD);
mvwaddch (w, ay, 1, LABEL);
}
if (under){
wattrset (w, A_BOLD);
mvwaddch (w, ay, ax-1, BOX_HATCHED);
}
if (c) s[m->textwidth] = c;
wattrset (w, m->bg_attrib);
SetPoint (m->savep, ay, ax-1); /* курсор поставить перед словом */
}
/* Выбор в меню подходящего элемента */
int MnuNext (Menu *m) {
char *s; register y = m -> current;
for (++y; y < m -> nitems; y++)
if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
return y;
return (-1);
}
int MnuPrev (Menu *m) {
char *s; register y = m -> current;
for (--y; y >= 0; --y)
if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
return y;
return (-1);
}
int MnuPgUp (Menu *m) {
char *s; register n, y = m -> current;
for (--y, n = 0; y >= 0; --y) {
if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
n++;
if (n == m -> textheight) return y;
}
return MnuFirst (m);
}
int MnuPgDn (Menu *m) {
char *s; register n, y = m -> current;
for (++y, n = 0; y < m -> nitems; y++) {
if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
n++;
if (n == m -> textheight) return y;
}
return MnuLast (m);
}
int MnuFirst (Menu *m) {
char *s; register y;
for (y = 0; y < m -> nitems; y++)
if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
return y;
return (-1);
}
int MnuLast (Menu *m) {
char *s; register y;
for (y = m -> nitems - 1; y >= 0; --y)
if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
return y;
return (-1);
}
int MnuThis (Menu *m) {
char *s;
if (m -> current < 0 || m -> current >= m -> nitems)
return (-1); /* error */
if ((s = M_ITEM (m, m -> current)) &&
*s != M_CTRL && !M_TST (m, m -> current, I_NOSEL))
return m -> current;
return (-1);
}
int MnuName (Menu *m, char *name) {
char *s; register y; int pos;
for(y = 0; y < m -> nitems; ++y)
if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL) &&
strcmp(name, MnuConvert(s, &pos)) == 0 ) return y;
return (-1);
}
int MnuHot (Menu *m, unsigned c) {
register y; char *s;
if (m -> hotkeys == (int *) NULL)
return (-1);
if (c < 0400 && isupper (c))
c = tolower (c);
for (y = 0; y < m -> nitems; y++)
if (c == m -> hotkeys[y] &&
(s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
return y;
return (-1);
}
/* Нарисовать содержимое меню для выбора */
void MnuDraw (Menu *m) {
register i, j;
for (i = 0; i < m -> textheight; i++)
MnuDrawItem (m, i, NO, m -> scrollok ? YES : NO);
}
/* Поставить курсор в line-тую строку окна. */
void MnuPoint(Menu *m, int line,
int eraseOld /* стирать старую селекцию? */){
int curline = m->current - m->shift; /* текущая строка окна */
if (line < 0 || line >= m -> textheight) return; /* ошибка */
if (eraseOld && curline != line) /* стереть старый выбор */
MnuDrawItem (m, curline, NO, YES);
MnuDrawItem (m, line, YES, YES); /* подсветить новую строку */
}
/* Перейти к y-той строке массива элементов, изменить картинку */
void MnuPointAt (Menu *m, int y) { char *s;
if (y < 0 || y >= m->nitems) return; /* ошибка! */
if ((s = M_ITEM (m, y)) == NULL || *s == M_CTRL) return;
MnuRoll (m, y, &m -> current, &m -> shift,
m -> textheight, m -> nitems,
MnuPoint, MnuDraw, MNU_DY);
if (m -> scrollok) MnuWinBar(m); /* сдвинуть scroll bar */
GetBack(m->savep, m->win); /* вернуть курсор в начало строки селекции,
* откуда он был сбит MnuWinBar-ом */
}
/* Выбор в меню без участия "мыши". */
int MnuUsualSelect (Menu *m, int block) {
int sel, snew, c, done = 0;

m -> key = (-1);
if( ! m->win ) return TOTAL_NOSEL;
if((sel = MnuThis (m)) < 0)
if((sel = MnuFirst (m)) < 0)
return TOTAL_NOSEL; /* в меню нельзя ничего выбрать */
RaiseWin (m->win); /* сделать окно верхним */
MnuPointAt (m, sel); /* проявить */
if(m->showMe) m->showMe(m); /* может быть изменить позицию ? */

for (;;) {
c = WinGetch (m->win);
INP:
if (m -> hitkeys && m -> handler) {
HandlerReply reply;
if (is_in (c, m -> hitkeys)) {
c = (*m -> handler) (m, c, &reply);
/* восстановить scroll bar */
MnuPointAt (m, m -> current);
switch (reply) {
case HANDLER_CONTINUE: continue;
case HANDLER_NEWCHAR: goto INP;
case HANDLER_OUT: goto out;
case HANDLER_SWITCH: default:
break; /* goto switch(c) */
}
}
}
switch (c) {
case KEY_UP:
if ((snew = MnuPrev (m)) < 0) break;
goto mv;
case KEY_DOWN:
next:
if ((snew = MnuNext (m)) < 0) break;
goto mv;
case KEY_HOME:
if ((snew = MnuFirst (m)) < 0) break;
goto mv;
case KEY_END:
if ((snew = MnuLast (m)) < 0) break;
goto mv;
case KEY_NPAGE:
if ((snew = MnuPgDn (m)) < 0) break;
goto mv;
case KEY_PPAGE:
if ((snew = MnuPgUp (m)) < 0) break;
goto mv;

case KEY_IC: /* поставить/снять пометку */
if (M_TST (m, sel, M_LABEL)) M_CLR (m, sel, M_LABEL);
else M_SET (m, sel, M_LABEL);
MnuPointAt (m, sel);
/* Если вы вычеркнете goto next;
* и оставите просто break;
* то вставьте в это место
* MnuPoint( m, m->current - m->shift, NO ); */
goto next;
case KEY_DC:
if (M_TST (m, sel, M_HATCH)) M_CLR (m, sel, M_HATCH);
else M_SET (m, sel, M_HATCH);
MnuPointAt (m, sel); goto next;

case KEY_LEFT:
if (block & M_LFT) {
sel = M_LEFT; goto out;
} break;
case KEY_RIGHT:
if (block & M_RGT) {
sel = M_RIGHT; goto out;
} break;
case 0: break;
default:
if (c == '\n' || c == '\r' || c == ESC)
goto out;
if ((snew = MnuHot (m, c)) < 0) {
beep(); break;
}
/* иначе найден HOT KEY (горячая клавиша) */
done++; goto mv;
}
continue;
mv:
MnuPointAt (m, sel = snew);
if(done){ wrefresh(m->win); /* проявить новую позицию */ break; }
}
out: wnoutrefresh(m->win);
return((m->key = c) == ESC ? -1 : sel);
/* Меню автоматически НЕ ИСЧЕЗАЕТ: если надо -
* явно делайте MnuHide(m); после MnuUsualSelect(); */
}

/* Пример 20 */
/* ______________________________________________________________ */
/* PULL_DOWN меню (меню-строка) */
/* _______________________ файл pull.h __________________________ */
typedef struct {
Info info; /* строка в меню */
Menu *menu; /* связанное с ней вертикальное меню */
char *note; /* подсказка */
} PullInfo;
typedef struct _Pull { /* Паспорт меню */
int nitems; /* количество элементов в меню */
PullInfo *items;/* элементы меню */
int *hotkeys; /* горячие ключи */
int key; /* клавиша, завершившая выбор */
int current; /* выбранный элемент */
int space; /* интервал между элементами меню */
int bg_attrib; /* цвет фона строки */
int sel_attrib; /* цвет выбранного элемента */
Point savep;
void (*scrollBar) (struct _Pull *m, int n, int among);
} PullMenu;
#define PYBEG 0 /* строка, в которой размещается меню */

#define PM_BOLD I_DIR
#define PM_NOSEL I_NOSEL
#define PM_LFT M_LFT
#define PM_RGT M_RGT

#define PM_SET(m, i, flg) (m)->items[i].info.fl |= (flg)
#define PM_CLR(m, i, flg) (m)->items[i].info.fl &= ~(flg)
#define PM_TST(m, i, flg) ((m)->items[i].info.fl & (flg))
#define PM_ITEM(m, i) ((m)->items[i].info.s)
#define PM_MENU(m, i) ((m)->items[i].menu)
#define PM_NOTE(m, i) ((m)->items[i].note)
#define COORD(m, i) ((m)->space * (i+1) + PullSum(m, i))

int PullInit(PullMenu *m);
int PullSum(PullMenu *m, int n);
void PullDraw(PullMenu *m);
int PullShow(PullMenu *m);
void PullHide(PullMenu *m);
void PullDrawItem(PullMenu *m, int i, int reverse, int selection);
void PullPointAt(PullMenu *m, int y);

int PullHot(PullMenu *m, unsigned c);
int PullPrev(PullMenu *m);
int PullNext(PullMenu *m);
int PullFirst(PullMenu *m);
int PullThis(PullMenu *m);
int PullUsualSelect(PullMenu *m);

#define PullWin stdscr
#define PM_REFUSED(m) ((m)->key < 0 || (m)->key == ESC )

/* _______________________ файл pull.c __________________________ */
#include "glob.h"
#include "w.h"
#include "menu.h"
#include "pull.h"

int PullSum(PullMenu *m, int n){
register i, total; int pos;
for(i=0, total = 0; i < n; i++ )
total += strlen( MnuConvert(PM_ITEM(m, i), &pos ));
return total;
}
/* Разметка меню. На входе:
p->items массив элементов с M_HOT-метками и связанных меню.
p->bg_attrib цвет фона строки.
p->sel_attrib цвет выбранного элемента.
Меню всегда размещается в окне stdscr (PullWin).
*/
int PullInit(PullMenu *m){
/* подменю не должны быть инициализированы,
* т.к. все равно будут сдвинуты в другое место */
int total, pos; char *s; register i;
m->key = m->current = 0;
if(m->hotkeys){
free((char *) m->hotkeys); m->hotkeys = (int *) NULL;
}
/* подсчитать элементы меню */
m->nitems = 0;
for( i=0, total = 0; PM_ITEM(m, i) != NULL; i++ ){
total += strlen(s = MnuConvert(PM_ITEM(m, i), &pos));
m->nitems++;
}
if( total > wcols(PullWin)){ /* меню слишком широкое */
err: beep(); return 0;
}
m->space = (wcols(PullWin) - total - 2) / (m->nitems + 1);
if( m->space <= 0 ) goto err;
/* разметить горячие клавиши */
if( m-> hotkeys = (int *) malloc( sizeof(int) * m->nitems )){
for(i=0; i < m->nitems; i++ )
m->hotkeys[i] = NOKEY;
}
for( i=0; i < m->nitems; i++ ){
if( PM_MENU(m,i)){
PM_MENU(m,i)->left = COORD(m, i) - 1;
PM_MENU(m,i)->top = PYBEG + 1;
PM_MENU(m,i)->bg_attrib = m-> bg_attrib;
PM_MENU(m,i)->sel_attrib = m-> sel_attrib;
if( PM_MENU(m,i)->win )
MnuDeinit( PM_MENU(m,i));
MnuInit( PM_MENU(m,i));
}
if( m->hotkeys ){
s = MnuConvert(PM_ITEM(m, i), &pos);
if( pos >= 0 )
m->hotkeys[i] =
isupper(s[pos]) ? tolower(s[pos]) : s[pos];
}
}
keypad(PullWin, TRUE); return 1;
}
/* Проявить pull-down меню */
int PullShow(PullMenu *m){
register i; int first, last;
first = last = (-1);
for(i=0; i < m->nitems; i++ ){
PM_SET(m, i, PM_LFT | PM_RGT );
if( !PM_TST(m, i, PM_NOSEL)){
if( first < 0 ) first = i;
last = i;
}
}
if( first < 0 ) return (TOTAL_NOSEL);
if(first == last ){
PM_CLR(m, first, PM_LFT | PM_RGT );
}else{
PM_CLR(m, first, PM_LFT);
PM_CLR(m, last, PM_RGT);
}
wmove(PullWin, PYBEG, 0);
wattrset(PullWin, m->bg_attrib);
wclrtoeol(PullWin);
PullDraw(m); return 1;
}
void PullDraw(PullMenu *m){ register i;
for(i=0; i < m->nitems; i++ )
PullDrawItem(m, i, NO, NO);
}
/* Спрятать pull-down меню. Сама строка остается, подменю исчезают */
void PullHide(PullMenu *m){
register i;
for(i=0; i < m->nitems; i++ )
if( PM_MENU(m, i)) MnuHide( PM_MENU(m, i));
PullDraw(m);
}
/* Нарисовать элемент меню */
void PullDrawItem(PullMenu *m, int i, int reverse, int selection){
int x, pos, hatch = PM_TST(m, i, PM_NOSEL );
char *s;

x = COORD(m, i); s = MnuConvert( PM_ITEM(m, i), &pos );
wattrset(PullWin,
(reverse ? m->sel_attrib : m->bg_attrib) |