(hatch ? A_ITALICS : 0 ));

/*mvwaddch(PullWin, PYBEG, x-1, reverse ? LEFT_TRIANG : ' ');*/
mvwaddstr(PullWin, PYBEG, x, s);
/*waddch(PullWin, reverse ? RIGHT_TRIANG : ' ');*/
if( pos >= 0 ){ /* Hot key letter */
wattron(PullWin, A_BOLD);
mvwaddch(PullWin, PYBEG, x + pos, s[pos]);
}
wmove (PullWin, PYBEG, x-1); SetPoint(m->savep, PYBEG, x-1);
wattrset(PullWin, m->bg_attrib);
}
int PullPrev(PullMenu *m){
register y;
for( y = m->current - 1; y >= 0; y-- )
if( !PM_TST(m, y, PM_NOSEL )) return y;
return (-1);
}
int PullNext(PullMenu *m){
register y;
for( y = m->current+1; y < m->nitems; y++ )
if( !PM_TST(m, y, PM_NOSEL)) return y;
return (-1);
}
int PullFirst(PullMenu *m){
register y;
for( y = 0; y < m->nitems; y++ )
if( !PM_TST(m, y, PM_NOSEL)) return y;
return (-1);
}
int PullThis(PullMenu *m){
register y;
if( m->current < 0 || m->current >= m->nitems )
return (-1);
if( PM_TST(m, m->current, PM_NOSEL))
return (-1);
return m->current;
}
int PullHot(PullMenu *m, unsigned c){
register y;
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] && !PM_TST(m, y, PM_NOSEL))
return y;
return (-1);
}
/* Указать на элемент n */
void PullPointAt( PullMenu *m, int n){
if( n < 0 || n >= m->nitems ) return ; /* error */
if( n != m->current ){
if( PM_MENU(m, m->current))
MnuHide( PM_MENU(m, m->current));
PullDrawItem( m, m->current, NO, YES );
}
m -> current = n;
PullDrawItem( m, n, YES, YES );
if( m->scrollBar ){
m->scrollBar( m, n, m->nitems );
GetBack(m->savep, PullWin);
}
}
/* Выбор в меню */
int PullUsualSelect(PullMenu *m){
int autogo = NO, c, code, done = 0, snew, sel, reply = (-1);

m->key = (-1);
if((sel = PullThis(m)) < 0 )
if((sel = PullFirst(m)) < 0 ) return TOTAL_NOSEL;
if( PullShow(m) < 0 ) return TOTAL_NOSEL;
PullPointAt(m, sel); /* начальная позиция */
for(;;){
if( autogo ){ /* Автоматическая проявка подменю */
if( PM_MENU(m, m->current) == NULL)
goto ask;
code = MnuUsualSelect(PM_MENU(m, m->current),
PM_TST(m, m->current, PM_LFT) |
PM_TST(m, m->current, PM_RGT));
MnuHide(PM_MENU(m, m->current));
c = PM_MENU(m, m->current)->key;
if(code == (-1)){
reply = (-1); goto out;
}
/* в подменю ничего нельзя выбрать */
if( code == TOTAL_NOSEL) goto ask;
/* MnuUsualSelect выдает специальные коды для
* сдвигов влево и вправо */
if( code == M_LEFT ) goto left;
if( code == M_RIGHT ) goto right;
reply = code; goto out;
} else
ask: c = WinGetch(PullWin);
switch(c){
case KEY_LEFT:
left: if((snew = PullPrev(m)) < 0 ) goto ask;
goto mv;
case KEY_RIGHT:
right: if((snew = PullNext(m)) < 0 ) goto ask;
goto mv;
case ESC:
reply = (-1); goto out;
case '\r': case '\n':
if( PM_MENU(m, m->current) == NULL){ reply = 0; goto out; }
autogo = YES; break;
default:
if((snew = PullHot(m, c)) < 0 ) break;
if( PM_MENU(m, snew) == NULL){ reply=0; done++; }
autogo = YES; goto mv;
}
continue;
mv: PullPointAt(m, sel = snew);
if( done ) break;
}
out: wnoutrefresh(PullWin); PullHide(m); m->key = c;
wattrset(PullWin, A_NORMAL); /* NOT bg_attrib */
return reply; /* номер элемента, выбранного в меню
PM_MENU(m, m->current) */
}

/* Пример 21 */
/* РЕДАКТОР СТРОКИ И ИСТОРИЯ РЕДАКТИРУЕМЫХ СТРОК */
/* _______________________ файл hist.h __________________________ */
/* ИСТОРИЯ. ЗАПОМИНАНИЕ СТРОК И ВЫДАЧА ИХ НАЗАД ПО ТРЕБОВАНИЮ. */
/* ______________________________________________________________ */
typedef struct { /* Паспорт истории */
Info *list; /* запомненные строки */
int sz; /* размер истории (макс.) */
int len; /* текущее число строк */
Menu mnu; /* меню для выборки из истории */
} Hist;
void HistInit(Hist *h, int n);
void HistAdd (Hist *h, char *s, int fl);
Info *HistSelect(Hist *h, int x, int y);

/* _______________________ файл hist.c __________________________ */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "hist.h"
/* Проинициализировать новую "историю" емкостью n строк */
void HistInit(Hist *h, int n){
register i;
if( h->list ){ blkfree( h->list ); h->list = NULL; }
h->len = 0;
h->mnu.title = "History";
h->mnu.bg_attrib = A_NORMAL;
h->mnu.sel_attrib = A_REVERSE;
h->list = (Info *) malloc( (n+1) * sizeof(Info));
if( ! h->list ){
h->sz = 0; return;
}else h->sz = n;
for( i=0; i < n+1 ; i++ )
h->list[i] = NullInfo;
}
/* Добавить строку s с меткой fl в историю */
void HistAdd (Hist *h, char *s, int fl){
register i, j; Info tmp;

if( h->sz == 0 ) return;
/* А нет ли уже такой строки ? */
for( i=0; i < h->len; i++ )
if( !strcmp(s, h->list[i].s )){ /* есть ! */
if( i == 0 ) return; /* первая */
/* сделать ее первой строкой */
tmp = h->list[i];
for( j=i-1; j >= 0; --j )
h->list[j+1] = h->list[j];
h->list[0] = tmp;
return;
}
if( h->len < h->sz ){
for( i=h->len-1; i>= 0; i-- )
h->list[i+1] = h->list[i];
h->len ++ ;
}else{
/* выкинуть самую старую строку из истории */
free( h->list[ h->sz - 1 ].s );
for( i=h->sz - 2; i >= 0; i-- )
h->list[i+1] = h->list[i];
}
(h->list)[0].s = strdup(s); (h->list)[0].fl = fl;
}
/* Выборка строки из истории */
Info *HistSelect(Hist *h, int x, int y){
if( h->len == 0 ) return (Info *) NULL;
h->mnu.top = y;
h->mnu.left = x;
h->mnu.items = h->list;
MnuInit( & h->mnu );
if( h->mnu.hotkeys ){
register i;
for(i=0 ; i < h->mnu.nitems; i++ )
h->mnu.hotkeys[i] = h->list[i].s[0] & 0377;
}
MnuUsualSelect( & h->mnu, 0 );
MnuDeinit ( & h->mnu );
if( M_REFUSED ( & h->mnu ))
return (Info *) NULL;
return & h->list[ h->mnu.current ];
}

/* _______________________ файл line.h __________________________ */
/* РЕДАКТОР ДЛИННЫХ СТРОК (ВОЗМОЖНО ШИРЕ ЭКРАНА) */
/* ______________________________________________________________ */
typedef struct _LineEdit { /* Паспорт редактора строки */
WINDOW *win; /* окно для редактирования */
int width; /* ширина поля редактирования */
int left, top; /* координаты поля редактирования в окне */
int pos; /* позиция в строке */
int shift; /* число символов скрытых левее поля */
char *line; /* строка которая редактируется */
int maxlen; /* максимальная длина строки */
int len; /* текущая длина строки */
int insert; /* 1 - режим вставки; 0 - замены */
int nc; /* 1 - стирать строку по первому нажатию */
int cursorOn; /* курсор включен (для графики) */
int bg_attrib; /* цвет текста */
int fr_attrib; /* цвет пустого места в поле */
int wl_attrib; /* цвет краев строки */
int sel_attrib; /* цвет символа под курсором */
Hist *histIn; /* история для выборки строк */
Hist *histOut; /* история для запоминания строк */
int key; /* кнопка, завершившая редактирование */
Point savep;
/* функции проявки и убирания окна (если надо) */
int (*showMe)(struct _LineEdit *le); /* 1 при успехе */
void (*hideMe)(struct _LineEdit *le);
void (*posMe) (struct _LineEdit *le); /* установка позиции */
/* Функция рисования scroll bar-а (если надо) */
void (*scrollBar)(struct _LineEdit *le, int whichbar, int n, int among);
/* Специальная обработка клавиш (если надо) */
int *hitkeys;
int (*handler)(struct _LineEdit *le, int c, HandlerReply *reply);
} LineEdit;

void LePutChar( LineEdit *le, int at);
void LeCursorHide( LineEdit *le );
void LeCursorShow( LineEdit *le );
void LePointAt( LineEdit *le, int at );
void LePoint( LineEdit *le, int x, int eraseOld );
void LeDraw( LineEdit *le );
void LeReport( LineEdit *le );
void LeDelCh ( LineEdit *le );
void LeInsCh ( LineEdit *le, int c );
void LeRepCh ( LineEdit *le, int c );
int LeInsStr( LineEdit *le, char *s);
int LeWerase( LineEdit *le, char *to );
int LeEdit( LineEdit *le );
#define LINE_DX 1
#define LE_REFUSED(m) ((m)->key < 0 || (m)->key == ESC )

/* _______________________ файл line.c __________________________ */
/* Редактор строки. Эта версия была изначально написана *
* для графики, поэтому здесь не совсем CURSES-ные алгоритмы */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "hist.h"
#include "line.h"

/* Удалить букву из строки */
static char cdelete(register char *s, int at) { char c;
s += at; if((c = *s) == '\0') return c;
while( s[0] = s[1] ) s++; return c;
}
/* Вставить букву в строку */
static void insert(char *s, int at, int c){
register char *p;
s += at; p = s;
while(*p) p++; /* найти конец строки */
p[1] = '\0'; /* закрыть строку */
for( ; p != s; p-- )
p[0] = p[-1];
*s = c;
}
/* Нарисовать видимую часть строки с позиции from */
static void LeDrawLine( LineEdit *le, int from ){
LeCursorHide( le );
for( ; from < le->width; from++ )
LePutChar(le, from);
/* курсор остается спрятанным */
}
/* Выдать символ строки в позиции at */
void LePutChar( LineEdit *le, int at){
int off = le->shift + at;
int bgcolor = le->bg_attrib, wall;
wall = /* символ на краю поля и строка выходит за этот край ? */
( at == 0 && le->shift ||
( at >= le->width - 1 && le->shift + le->width < le->len ));
bgcolor =
( off < le->len ) ? le->bg_attrib :
( at >= le->width || off >= le->maxlen ) ? (le->bg_attrib | A_ITALICS):
/* чистое место в поле */ le->fr_attrib ;
wattrset( le->win, wall? le->wl_attrib|A_BOLD|A_ITALICS: bgcolor);
mvwaddch( le->win, le->top, le->left + at,
off < le->len ? le->line[off] : ' ' );
wattrset( le->win, le->bg_attrib);
}
/* Спрятать курсор. x в интервале 0..le->width */
void LeCursorHide( LineEdit *le ){
int x = le->pos - le->shift;
if( x < 0 || x > le->width || le->cursorOn == NO )
return;
LePutChar( le, x ); le->cursorOn = NO;
}
/* Проявить курсор */
void LeCursorShow( LineEdit *le ){
int x = le->pos - le->shift, saveattr = le->bg_attrib;

if( x < 0 || x > le->width || le->cursorOn == YES ) return;
le->bg_attrib = le->sel_attrib | (le->insert==NO ? A_BOLD : 0);
LePutChar(le, x); le->bg_attrib = saveattr;
wmove(le->win, le->top, le->left + x); le->cursorOn = YES;
SetPoint(le->savep, le->top, le->left+x);
}
/* Функция прокрутки длинной строки через окошко */
static void LeRoll( LineEdit *ptr,
int aid, int *cur, int *shift,
int width, /* ширина окна */
int len, int maxlen,
void (*go) (LineEdit *p, int x, int eraseOld),
void (*draw)(LineEdit *p), /* перерисовщик поля */
int LDX
){
int x = *cur - *shift, oldshift = *shift, newshift = oldshift;
int AID_LFT, AID_RGT, drawn = NO;

if( aid < 0 || aid > len ) return; /* incorrect */
if( x < 0 || x > width ) return; /* incorrect */

AID_LFT = MIN(LDX, maxlen);
AID_RGT = MAX(0, MIN(maxlen, width-1 - LDX));

if( aid < *cur && x <= AID_LFT && oldshift > 0 )
goto Scroll;
else if( aid > *cur && x >= AID_RGT && oldshift + width < maxlen )
goto Scroll;
if( oldshift <= aid && aid < oldshift + width )
/* прокрутка не нужна - символ уже видим */
goto Position;
Scroll:
if( aid >= *cur )
newshift = aid - AID_RGT;
else newshift = aid - AID_LFT;
if( newshift + width > maxlen || (len == maxlen && aid == len))
newshift = maxlen - width;
if( newshift < 0 )
newshift = 0;
if( newshift != oldshift ){
*shift = newshift; (*draw)(ptr); drawn = YES;
}
Position:
if((x = aid - newshift) >= width && len != maxlen )
beep(); /* ERROR */
(*go)(ptr, x, !drawn ); *cur = aid;
}
/* Поставить курсор на at-тый символ строки */
void LePointAt( LineEdit *le, int at ){
/* at == len допустимо */
if( at < 0 || at > le->len ) return;
if( le->pos == at ) return; /* уже на месте */
LeCursorHide( le );
LeRoll( le, at, & le->pos, & le->shift,
le->width, le->len, le->maxlen,
LePoint, LeDraw,
LINE_DX);
le->pos = at;
LeCursorShow( le );
}
/* Нарисовать подходящий scroll bar */
void LePoint( LineEdit *le, int x, int eraseOld ){
if(le->scrollBar)
(*le->scrollBar)(le, BAR_HOR, x + le->shift, le->maxlen+1 );
GetBack( le->savep, le->win);
}
/* Нарисовать подходящий scroll bar */
/* Вызывай это каждый раз, когда len изменится */
void LeReport( LineEdit *le ){
if(le->scrollBar)
le->scrollBar (le, BAR_VER, le->len, le->maxlen+1 );
GetBack( le->savep, le->win);
}
/* Нарисовать видимую часть строки */
void LeDraw( LineEdit *le ){
LeDrawLine( le, 0);
}
/* Удаление буквы из строки */
void LeDelCh( LineEdit *le ){
if( le->len <= 0 || le->pos < 0 || le->pos >= le->len ) return;
LeCursorHide( le );
(void) cdelete( le->line, le->pos );
le->len --;
LeDrawLine( le, le->pos - le->shift );
LeReport( le );
}
/* Вставка буквы в строку */
void LeInsCh( LineEdit *le, int c ){
if( le->len < 0 || le->pos < 0 || le->pos > le->len ) return;
LeCursorHide( le );
insert( le->line, le->pos, c );
le->len++;
LeDrawLine( le, le->pos - le->shift );
LeReport( le );
}
/* Замена буквы в строке */
void LeRepCh( LineEdit *le, int c ){
if( le->len <= 0 || le->pos < 0 || le->pos >= le->len ) return;
LeCursorHide( le );
le->line[ le->pos ] = c;
LePutChar( le, le->pos - le-> shift );
}
/* Вставка подстроки в строку редактирования */
int LeInsStr( LineEdit *le, char *s){
int len = le->len, slen = strlen(s);
register i;

if( len + slen > le->maxlen )
slen = le->maxlen - len;
if( ! slen ) return 0;

for( i=0; i < slen ; i ++ )
insert( le->line, le->pos+i, s[i] );
le->len += slen;
LeCursorHide( le );
LeDrawLine( le, le->pos - le->shift );
LePointAt( le, le->pos + slen );
LeReport( le );
return slen ;
}
/* Стирание слова */
int LeWerase( LineEdit *le, char *to ){
register i;
register char *s = le->line;
char c;

if( to ) *to = '\0';
i = le->pos;
if( s[i] == ' ' || s[i] == '\0' ){
/* найти конец слова */
for( --i; i >= 0 ; i-- )
if( s[i] != ' ' ) break;
if( i < 0 || le->len == 0 ){
beep(); return NO; }
}
/* найти начало слова */
for( ; i >= 0 && s[i] != ' ' ; i-- );
i++; /* i < 0 || s[i] == ' ' */
LeCursorHide( le ); LePointAt( le, i );
while( s[i] != ' ' && s[i] != '\0' ){
c = cdelete( s, i );
if( to ) *to++ = c;
le->len --;
}
/* удалить пробелы после слова */
while( s[i] == ' ' ){
c = cdelete( s, i );
le->len --;
}
if( to ) *to = '\0';
LeDrawLine( le, i - le->shift );
LeReport( le );
return YES;
}
/* Редактор строки
le->line что редактировать.
le->maxlen макс. длина строки.
le->win окно, содержащее поле редактирования.
le->width ширина поля редактирования.
le->top коорд-ты поля редактирования
le->left в окне win.
le->insert = YES режим вставки.
le->nc = YES стирать строку при первом нажатии.
le->histIn входная история или NULL.
le->histOut выходная история или NULL.
le->showMe функция проявки окна или NULL.
le->hideMe функция спрятывания окна или NULL.
le->hitkeys специальные клавиши или NULL.
le->handler обработчик специальных клавиш или NULL.
le->scrollBar рисовалка scroll bar-ов или NULL.
le->posMe установка позиции в строке при входе.
le->bg_attrib цвет поля.
le->fr_attrib цвет незаполненной части поля.
le->wl_attrib цвет краев поля при продолжении.
le->sel_attrib цвет символа под курсором.
*/
int LeEdit( LineEdit *le ){
int c;
int nchar = 0; /* счетчик нажатых клавиш */
Info *inf;

/* проявить окно */
if( le->showMe )
if( (*le->showMe) (le) <= 0 )
return (-1);
if( !le->win ) return (le->key = -1);
Again:
le -> pos = 0;
le -> len = strlen( le->line );
le -> shift = 0;
le -> cursorOn = NO;
le->key = (-1);

LeDraw( le );
if(le->posMe) (*le->posMe)(le);
LePointAt(le, le->pos );
LePoint( le, le->pos - le->shift, NO );
LeReport( le );

for (;;) {
LeCursorShow( le );

c = WinGetch(le->win); /* прочесть символ с клавиатуры */
nchar++; /* число нажатых клавиш */
INP:
if( le->hitkeys && le->handler ){
HandlerReply reply;
if( is_in(c, le->hitkeys)){ /* спецсимвол ? */
c = (*le->handler)(le, c, &reply);
/* Восстановить scroll bars */
LePoint( le, le->pos - le->shift, NO );
LeReport( le );

switch( reply ){
case HANDLER_CONTINUE: continue;
case HANDLER_NEWCHAR: goto INP;
case HANDLER_OUT: goto out;
case HANDLER_AGAIN: /* reset */
LeCursorHide(le); goto Again;
case HANDLER_SWITCH:
default: break; /* goto switch(c) */
}
}
}
sw:
switch (c) {
case KEY_RIGHT: /* курсор вправо */
if (le->pos != le->len && le->len > 0)
LePointAt( le, le->pos + 1);
break;

case KEY_LEFT: /* курсор влево */
if (le->pos > 0)
LePointAt(le, le->pos - 1);
break;

case '\t': /* табуляция вправо */
if (le->pos + 8 > le->len)
LePointAt(le, le->len);
else
LePointAt(le, le->pos + 8);
break;

case KEY_BACKTAB: /* табуляция влево */
case ctrl('X'):
if( le->pos - 8 < 0 )
LePointAt(le, 0);
else LePointAt(le, le->pos - 8 );
break;

case KEY_HOME: /* в начало строки */
LePointAt(le, 0); break;

case KEY_END: /* в конец строки KEY_LL */
if( le->len > 0 )
LePointAt(le, le->len);
break;

case 0177: /* стереть символ перед курсором */
case KEY_BACKSPACE:
case '\b':
if (le->pos == 0) break;
LePointAt(le, le->pos - 1); /* налево */
/* и провалиться в DC ... */

case KEY_F (6): /* стереть символ над курсором */
case KEY_DC:
if (! le->len || le->pos == le->len)
break;
LeDelCh(le);
break;

case KEY_UP: /* вызвать историю */
case KEY_DOWN:
case KEY_NPAGE:
case KEY_PPAGE:
case KEY_F(4):
if( ! le->histIn ) break;
/* иначе позвать историю */
inf = HistSelect( le->histIn,
wbegx(le->win) + le->pos - le->shift + 2, le->top + 1);
if( inf == (Info *) NULL )
break;
LeCursorHide( le );
strncpy( le->line, inf->s, le->maxlen );
goto Again;

out: case '\r': case '\n': case ESC:
/* ввод завершен - выйти */
LeCursorHide( le );
if( c != ESC && le->histOut && *le->line )
/* запомнить строку в историю */
HistAdd( le->histOut, le->line, 0);
if( le->hideMe ) /* спрятать окно */
(*le->hideMe)(le);
return (le->key = c);

case KEY_F (8): /* стереть всю строку */
case ctrl('U'):
le->line[0] = '\0';
le->len = le->pos = le->shift = 0;
LeCursorHide( le );
LeReport( le );
goto REWRITE;

case KEY_F(0): /* F10: стереть до конца строки */
if( le->pos == le->len ) break;
le->line[ le->pos ] = '\0';
le->len = strlen( le->line );
LeCursorHide( le );
LeDrawLine( le, le->pos - le->shift );
LeReport( le );
break;

case ctrl('W'): /* стереть слово */
LeWerase( le, NULL );
break;

case ctrl('A'): /* перерисовка */
LeCursorHide(le); /* RedrawScreen(); */
REWRITE: LeDraw(le);
break;

case KEY_F(7): /* переключить режим вставки/замены */
le->insert = ! le->insert;
LeCursorHide( le );
break;
#ifndef M_UNIX
case ctrl('V'): /* ввод заэкранированного символа */
nchar--;
c = WinGetch(le->win);
nchar++;
if( c >= 0400 ) goto sw;
goto Input;
#endif
case 0: break;
default: /* ввод обычного символа */
if (c >= 0400 || ! isprint(c)) break;
Input: if( le->nc && nchar == 1 && le->insert &&
/*le->pos == 0 &&*/ le->len != 0 ){
/* если это первая нажатая кнопка, то
/* удалить все содержимое строки
/* и заменить ее нажатой буквой */
le->shift = 0;
le->len = le->pos = 1;
le->line[0] = c;
le->line[1] = '\0';
LeCursorHide( le );
LeReport( le );
goto REWRITE;
}
if (!le->insert) {
/* REPLACE - режим замены */
if (le->pos == le->len)
goto AddChar; /* временный INSERT */
LeRepCh( le, c );
LePointAt( le, le->pos + 1 );
} else {
/* INSERT - режим вставки */
AddChar:
if( le->len >= le->maxlen ){
beep(); /* строка переполнена */
break;
}
LeInsCh( le, c );
LePointAt( le, le->pos + 1 );
} /* endif */
} /* endswitch */
} /* endfor */
} /* endfunc */

/* Пример 22 */
/* ______________________________________________________________ */
/* ПРЯМОУГОЛЬНАЯ ТАБЛИЦА-МЕНЮ */
/* _______________________ файл table.h _________________________ */
typedef struct _Table { /* Паспорт таблицы */
int nitems; /* количество элементов в таблице */
Info *items; /* массив элементов */
char *fmt; /* формат вывода */
int key; /* кнопка, завершившая выбор в таблице */

int current; /* номер выбранного элемента */
int shift; /* число элементов перед окном */
WINDOW *win; /* окно в котором размещена таблица */
int left, top, height, width; /* размеры и расположение
таблицы в окне */
int space; /* интервал между колонками */
int elen; /* макс. ширина колонки */
int tcols; /* число колонок в таблице */
int cols; /* число колонок в видимой части таблицы */
int cutpos; /* позиция для обрубания слишком длинных строк */
int scrollok; /* роллируется ? */
int exposed; /* нарисована ? */
int elems; /* текущее число эл-тов в подокне */
int maxelems; /* максимальное число эл-тов в подокне */
int maxshift; /* максимальный сдвиг */
int bg_attrib, sel_attrib; /* цвет фона и выбранного
элемента */
Point savep;
/* Функции проявки/спрятывания окна */
int (*showMe)(struct _Table *tbl);
void (*hideMe)(struct _Table *tbl);
void (*scrollBar)(struct _Table *tbl, int whichbar, int n, int among);
/* Обработчик специальных клавиш */
int *hitkeys;
int (*handler)(struct _Table *tbl, int c, HandlerReply *reply);
} Table;

#define T_BOLD M_BOLD
#define T_NOSEL I_NOSEL
#define T_HATCH M_HATCH
#define T_LABEL M_LABEL

#define T_SET(m, i, flg) (((m)->items)[i]).fl |= (flg)
#define T_CLR(m, i, flg) (((m)->items)[i]).fl &= ~(flg)
#define T_TST(m, i, flg) ((((m)->items)[i]).fl & (flg))

#define T_ITEM(m, i) ((((m)->items)[i]).s)
/* Формат 'd' ниже вставлен лишь для текущего состояния использования
* форматов в нашем проекте: */
#define T_ITEMF(m, i, cut) \
((m)->fmt && *(m)->fmt != 'd' ? \
TblConvert(T_ITEM((m), i), (m)->fmt, (cut)) : T_ITEM((m), i))
#define T_VISIBLE(tbl, new) ((tbl)->exposed == YES && \
(new) >= (tbl)->shift && (new) < (tbl)->shift + (tbl)->elems)
#define TLABSIZE 2 /* ширина поля меток */
#define T_REFUSED(t) ((t)->key < 0 || (t)->key == ESC )

int TblCount( Table *tbl );
void TblInit( Table *tbl, int forcedOneColumn );

void TblChkCur ( Table *tbl );
int TblChkShift( Table *tbl );
void TblChk ( Table *tbl );
char *TblConvert( char *s, char *fmt, int cutpos );

void TblPointAt ( Table *tbl, int snew );
void TblPoint ( Table *tbl, int snew, int eraseOld );
void TblDraw ( Table *tbl );
void TblDrawItem( Table *tbl, int at, int reverse, int selection);
void TblBox ( Table *tbl, int at, int reverse, int hatched,
int width, int axl, int axi, int ay);
int TblClear( Table *tbl );
int TblPlaceByName( Table *tbl, char *p );
void TblReport( Table *tbl );

void TblTag ( Table *tbl, int at, int flag);
void TblUntag( Table *tbl, int at, int flag);
void TblRetag( Table *tbl, int at, int flag);
void TblTagAll( Table *tbl, char *pattern, int flag );
void TblUntagAll( Table *tbl, char *pattern, int flag );

int TblUsualSelect( Table *tbl );

/* _______________________ файл table.c _________________________ */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "table.h"
extern char STRING_BUFFER[MAXLEN]; /* imported from menu.c */
/* надо указать размер, чтоб работал sizeof(STRING_BUFFER) */

/* Переформатировать строку по формату fmt для выдачи в таблицу.
* Пока предложена простейшая интерпретация. */
char *TblConvert( char *s, char *fmt, int cutpos ){
if( fmt && *fmt == 'd'){
register i, j, len; char *p = strrchr(s, '.');
if((len = strlen(s)) < DIR_SIZE && *s != '.' && p ){
int sufxlen = strlen(p);
for(i=0; i < len - sufxlen ; ++i) STRING_BUFFER[i] = s[i];
for(; i < DIR_SIZE - sufxlen; ++i) STRING_BUFFER[i] = ' ';
for(j=0; i < DIR_SIZE; j++, ++i) STRING_BUFFER[i] = p[j];
STRING_BUFFER[i] = '\0';
} else strcpy(STRING_BUFFER, s);
if(cutpos > 0 && cutpos < sizeof(STRING_BUFFER))
STRING_BUFFER[cutpos] = '\0';
} else { /* без формата, только обрубание */
if( cutpos <= 0 ) cutpos = 32000; /* Обрубание выключено */
strncpy(STRING_BUFFER, s, MIN(sizeof(STRING_BUFFER) - 1, cutpos));
}
return STRING_BUFFER;
}
/* Обрубить s до длины cutpos букв */
char *TblCut( char *s, int cutpos ){
if( cutpos <= 0 ) return s;
strncpy(STRING_BUFFER, s, MIN(sizeof(STRING_BUFFER) - 1, cutpos));
return STRING_BUFFER;
}
/* Подсчет элементов таблицы и ширины столбца */
int TblCount( Table *tbl ){
register i, L, LL; char *s;
L = i = 0;
if( tbl->items)
while((s = T_ITEM(tbl, i)) != NULL ){
if( tbl->fmt )
s = TblConvert(s, tbl->fmt, 0);
LL = strlen(s);
if( LL > L ) L = LL;
i++;
}
tbl->nitems = i; return L;
}
/* Разметка таблицы. На входе:
t->items Массив данных, показываемый в меню.
t->exposed = NO Таблица уже нарисована ?
t->fmt Формат строк, выводимых в таблицу.
t->win Окно для размещения таблицы.
t->showMe Функция проявки окна.
t->hideMe Функция упрятывания окна.
t->hitkeys Специальные клавиши []. Конец -1.
t->handler Обработчик или NULL.
t->width Ширина поля таблицы.
t->height Высота поля таблицы.
t->left Левый край таблицы в окне.
t->top Верхний край таблицы в окне.
t->scrollBar Функция рисования scroll-bar-а или NULL.
t->bg_attrib Цвет фона (== цвету фона окна).
t->sel_attrib Цвет выбранного элемента.
forcedOneColumn == YES делает таблицу в 1 колонку.
*/
void TblInit( Table *tbl, int forcedOneColumn ){
int mlen = TblCount( tbl ); /* самый широкий элемент таблицы */
/* усечь до ширины таблицы */
if( mlen > tbl->width || forcedOneColumn )
mlen = tbl->width; /* слишком широко */
/* ширина столбца таблицы = ширина элемента + поле меток + разделитель */
tbl->elen = mlen + TLABSIZE + 1;
/* #####строка_элемент| */
/* метки элемент 1 */
/* число столбцов во всей таблице */
tbl->tcols = (tbl->nitems + tbl->height - 1) / tbl->height;
/* число столбцов, видимых через окно (+1 для ошибок округления) */
tbl->cols = tbl->width / (tbl->elen + 1);
if( tbl->cols == 0 ){ /* слишком широкая таблица */
tbl->cols = 1; /* таблица в одну колонку */
tbl->elen = tbl->width - 2;
mlen = tbl->elen - (TLABSIZE + 1);
tbl->cutpos = mlen; /* и придется обрубать строки */
} else tbl->cutpos = 0; /* без обрубания */
tbl->cols = MIN(tbl->cols, tbl->tcols);
/* интервал между колонками */
tbl->space = (tbl->width - tbl->cols * tbl->elen)/(tbl->cols+1);
if( tbl->space < 0 ){ beep(); tbl->space = 0; }
/* сколько элементов умещается в окно */
tbl->maxelems = tbl-> cols * tbl->height;
tbl->maxshift = (tbl->tcols * tbl->height) - tbl->maxelems;
if( tbl->maxshift < 0 ) tbl->maxshift = 0;
/* требуется ли роллирование таблицы через окно */
tbl->scrollok = (tbl->nitems > tbl->maxelems);
tbl->elems = tbl->shift = tbl->current = 0; /* пока */
tbl->exposed = NO; /* таблица еще не нарисована */
tbl->key = (-1);
}
/* Проверить корректность текущей позиции */
void TblChkCur( Table *tbl ){
if( tbl->current >= tbl->nitems )
tbl->current = tbl->nitems - 1;
if( tbl->current < 0 )
tbl->current = 0;
}
/* Проверить корректность сдвига (числа элементов ПЕРЕД окном) */
int TblChkShift( Table *tbl ){
register int oldshift = tbl->shift;
/* в колонке должно быть видно достаточно много элементов */
if( tbl->cols == 1 && /* таблица в 1 колонку */
tbl->tcols > 1 && /* но всего в ней не одна колонка */
tbl->nitems - tbl->shift < tbl->height / 2 + 1
) tbl->shift = tbl->nitems - (tbl->height/2 + 1);

if( tbl->shift > tbl->maxshift )
tbl->shift = tbl->maxshift;
if( tbl->shift < 0 )
tbl->shift = 0;
return tbl->shift != oldshift; /* скорректировано ? */
}
/* Проверить корректность параметров таблицы */
void TblChk( Table *tbl ){
again:
TblChkCur( tbl ); TblChkShift( tbl );
if( tbl -> maxelems ){
if( tbl -> current >= tbl->shift + tbl->maxelems ){
tbl->shift = tbl->current - (tbl->maxelems - 1);
goto again;
}
if( tbl->current < tbl->shift ){
tbl->shift = tbl->current; goto again;
}
}
}
/* Указать на snew-тый элемент списка, перерисовать картинку */
void TblPointAt( Table *tbl, int snew ){
int curCol; /* текущий столбец всей таблицы (для current) */
int newCol; /* нужный столбец таблицы (для snew) */
int colw; /* нужный столбец ОКНА (для snew) */
int gap; /* зазор */
int newshift = tbl->shift; /* новый сдвиг окна от начала массива */
int drawn = NO; /* таблица целиком перерисована ? */

/* ПРоверить корректность номера желаемого элемента */
if( snew < 0 ) snew = 0;
if( snew >= tbl->nitems ) snew = tbl->nitems - 1;

if( tbl->current == snew && tbl->exposed == YES)
return; /* уже стоим на требуемом элементе */
#define WANTINC 1
#define WANTDEC (tbl->cols-1-WANTINC)
gap = (tbl->height - (tbl->shift % tbl->height)) % tbl->height;
/* gap - это смещение, которое превращает строгую
постолбцовую структуру
--0-- --3--
--1-- --4--
--2-- --5--
в сдвинутую структуру
____ |------
gap=2___/ пусто g0 --1-- g3 | --4-- g6 ....
\____пусто g1 --2-- g4 | --5-- g7
--0-- g2 --3-- g5 | --6-- g8
|------ shift=4
*/
/* операция прокрутки данных через таблицу: TblRoll() _________________*/
/* Элемент уже виден в текущем окне ? */
/* Параметр elems вычисляется в TblDraw() */
if( T_VISIBLE(tbl, snew))
goto ThisWindow;

/* smooth scrolling (гладкое роллирование) */
if( snew == tbl->shift + tbl->elems &&
/* элемент непосредственно следующий ЗА окном */
tbl->current == tbl->shift + tbl->elems - 1
/* курсор стоит в нижнем правом углу окна */
){
newshift++; gap--;
if ( gap < 0 ) gap = tbl->height - 1 ;
goto do_this;
}
if( snew == tbl->shift - 1 &&
/* элемент непосредственно стоящий ПЕРЕД окном */
tbl->current == tbl->shift
/* и курсор стоит в верхнем левом углу окна таблицы */
){
newshift --; gap = (gap + 1) % tbl->height;
goto do_this;
}

/* jump scrolling (прокрутка скачком) */

curCol = (tbl->current+gap) / tbl->height;
newCol = (snew +gap) / tbl->height;
if( tbl->cols > 1 ){
if( newCol > curCol ) colw = WANTINC;
else colw = WANTDEC;
} else colw = 0;
newshift = (newCol - colw) * tbl->height - gap ;

do_this:
if( tbl->shift != newshift || tbl->exposed == NO){
tbl->shift = newshift;
TblChkShift( tbl ); /* >= 0 && <= max */
TblDraw( tbl ); /* перерисовать все окно с нового места */
drawn = YES; /* перерисовано целиком */
}
ThisWindow: /* поставить курсор в текущем окне без перерисовки окна */
TblPoint( tbl, snew, !drawn );
/* tbl->current = snew; сделается в TblPoint() */
}
/* Поставить курсор на элемент в текущем окне */
void TblPoint ( Table *tbl, int snew, int eraseOld ){
if( ! T_VISIBLE(tbl, snew)){
beep(); /* ERROR !!! */ return;
}
if( eraseOld && tbl->current != snew )
TblDrawItem( tbl, tbl->current, NO, YES );
TblDrawItem( tbl, snew, YES, YES );
tbl->current = snew;
TblReport( tbl );
}
/* Нарисовать scroll bar в нужной позиции. Кроме того,
* в эту функцию можно включить и другие действия, например
* выдачу имени T_ITEM(tbl, tbl->current) на рамке окна. */
void TblReport( Table *tbl ){
if ( tbl->scrollBar )
(*tbl->scrollBar)( tbl, BAR_VER|BAR_HOR,
tbl->current, tbl->nitems);
GetBack( tbl->savep, tbl->win ); /* курсор на место ! */
}
/* Перерисовать все окно таблицы */
void TblDraw( Table *tbl ){
register next;
/* число элементов в таблице (может остаться незанятое
* место в правой нижней части окна */
tbl->elems = MIN(tbl->nitems - tbl->shift, tbl->maxelems );
for( next = 0; next < tbl->maxelems; next++ )
TblDrawItem(tbl, next + tbl->shift, NO, tbl->scrollok ? YES : NO);
tbl->exposed = YES; /* окно изображено */
}
/* Нарисовать элемент таблицы */
void TblDrawItem( Table *tbl, int at, int reverse, int selection){
register WINDOW *w = tbl->win;
int pos; char *s; int hatch, bold, label, under;
int ax, axl, ay, column;

if( at >= 0 && at < tbl->nitems ){
s = T_ITEM( tbl, at );
if( tbl->fmt )
s = TblConvert(s, tbl->fmt, tbl->cutpos);
else if( tbl->cutpos > 0 )
s = TblCut(s, tbl->cutpos);
/* выделения */
hatch = T_TST( tbl, at, T_HATCH );
bold = T_TST( tbl, at, T_BOLD );
label = T_TST( tbl, at, T_LABEL );
under = T_TST( tbl, at, I_EXE );
} else { s = "~"; label = hatch = bold = under = NO; }

at -= tbl->shift; /* координату в списке перевести в коорд. окна */
ay = tbl->top + at % tbl->height;
column = at / tbl->height;
/* начало поля меток */
axl = tbl->left + tbl->space + column * (tbl->space + tbl->elen);
/* начало строки-элемента */
ax = axl + TLABSIZE;
if(selection)
TblBox( tbl, at, reverse, reverse && hatch, strlen(s), axl, ax, ay );
wattrset (w, reverse ? tbl->sel_attrib : tbl->bg_attrib);
if( hatch ) wattron(w, A_ITALICS);
if( bold ) wattron(w, A_BOLD);
if( under ) wattron(w, A_UNDERLINE);
mvwaddstr(w, ay, ax, s);
wattrset(w, tbl->bg_attrib | (bold ? A_BOLD:0));
if( label ) mvwaddch(w, ay, axl, LABEL);
if( under ){ wattron(w, A_BOLD); mvwaddch(w, ay, axl+1, BOX_HATCHED);}
wattrset(w, tbl->bg_attrib);
if( column != tbl->cols-1 ) /* не последний столбец */
mvwaddch(w, ay, axl+tbl->elen-1 + (tbl->space+1)/2, VER_LINE);
wmove(w, ay, ax-1); /* курсор перед началом строки */
SetPoint(tbl->savep, ay, ax-1); /* запомнить координаты курсора */
}
/* Зачистить область окна для рисования элемента таблицы */
void TblBox(Table *tbl, int at, int reverse, int hatched,
int width, int axl, int axi, int ay){
register WINDOW *w = tbl->win;
int len = tbl->elen;

wattrset (w, tbl->bg_attrib);
wboxerase(w, axl, ay, axl+len-1, ay);
wattrset (w, reverse ? tbl->sel_attrib : tbl->bg_attrib);
/* если ниже задать axl+len+1, то подсвеченный
* прямоугольник будет фиксированного размера */
wboxerase(w, axi, ay, axl+width-1, ay);
wattrset (w, tbl->bg_attrib);
}
/* Зачистить прямоугольную рабочую область окна tbl->win,
* в которой будет изображаться таблица.
* Эта функция нигде не вызывается ЯВНО, поэтому ВЫ должны
* вызывать ее сами после каждого TblInit() -
* для этого удобно поместить ее в демон (*showMe)();
*/
int TblClear( Table *tbl ){
tbl->exposed = NO;
tbl->elems = 0; /* Это всегда происходит при exposed:= NO */
wboxerase( tbl->win,
tbl->left, tbl->top,
tbl->left + tbl->width - 1,
tbl->top + tbl->height - 1);
return 1;
}
/* Пометить элемент в таблице */
void TblTag( Table *tbl, int at, int flag){
if( T_TST(tbl, at, flag)) return;
T_SET(tbl, at, flag);
if( T_VISIBLE(tbl, at))
TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES );
}
/* Снять пометку с элемента таблицы */
void TblUntag( Table *tbl, int at, int flag){
if( ! T_TST(tbl, at, flag)) return;
T_CLR(tbl, at, flag);
if( T_VISIBLE(tbl, at))
TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES );
}
/* Изменить пометку элемента таблицы */
void TblRetag( Table *tbl, int at, int flag){
if( T_TST(tbl, at, flag)) T_CLR(tbl, at, flag);
else T_SET(tbl, at, flag);
if( T_VISIBLE(tbl, at))
TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES );
}
/* Используется в match() для выдачи сообщения об ошибке */
void TblMatchErr(){}
/* Пометить элементы, чьи имена удовлетворяют шаблону */
void TblTagAll( Table *tbl, char *pattern, int flag ){
register i;
for(i=0; i < tbl->nitems; i++)
if( !T_TST(tbl, i, I_DIR) && match( T_ITEMF(tbl, i, 0), pattern))
TblTag( tbl, i, flag );
}
/* Снять пометки с элементов по шаблону имени */
void TblUntagAll( Table *tbl, char *pattern, int flag ){
register i;
for(i=0; i < tbl->nitems; i++)
if( match( T_ITEMF(tbl, i, 0), pattern))
TblUntag( tbl, i, flag );
}
/* Указать на элемент по шаблону его имени */
int TblPlaceByName( Table *tbl, char *p ){
register i; char *s;

for( i=0; i < tbl->nitems; i++ ){
s = T_ITEMF(tbl, i, 0);
if( match( s, p )){
if( tbl->exposed == NO ){
/* Задать некорректный shift,
* чтобы окно полностью перерисовалось */
tbl->shift = tbl->nitems+1; tbl->elems = 0;
}
TblPointAt( tbl, i );
return i;
}
} return (-1);
}
/* Перемещение по таблице набором первых букв названия элемента */
static int TblTrack( Table *tbl, int c){
char *s; register i;
int from; /* с какого элемента начинать поиск */
int found = 0; /* сколько было найдено */
int plength = 0;
int more = 0;
char pattern[20];

if( c >= 0400 || iscntrl(c)){ beep(); return 0; }
AddCh:
from = 0;
pattern[plength] = c;
pattern[plength+1] = '*';
pattern[plength+2] = '\0';
plength++;
More:
for(i = from; i < tbl->nitems; i++){
s = T_ITEMF(tbl, i, 0);
if( match(s, pattern)){
++found; from = i+1;
TblPointAt( tbl, i );
c = WinGetch( tbl->win );

switch(c){
case '\t': /* find next matching */
more++;
goto More;
case KEY_BACKSPACE: case '\177': case '\b':
if( plength > 1 ){
plength--;
pattern[plength] = '*';
pattern[plength+1] = '\0';
from = 0; more++;
goto More;
} else goto out;
default:
if( c >= 0400 || iscntrl(c)) return c;
if( plength >= sizeof pattern - 2 ) goto out;
goto AddCh;
}
}
}
/* не найдено */