(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) | (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; } } } /* не найдено */ if(more && found){ /* нет БОЛЬШЕ подходящих, но ВООБЩЕ - есть */ beep(); more = found = from = 0; goto More; } out: beep(); return 0; } /* Выбор в таблице */ int TblUsualSelect( Table *tbl ){ int c, want; tbl->key = (-1); if( tbl->items == NULL || tbl->nitems <= 0 ) return TOTAL_NOSEL; TblChk( tbl ); if( tbl->showMe ) if((*tbl->showMe)(tbl) <= 0 ) return (-1); if( !tbl->win ) return TOTAL_NOSEL; if( tbl->exposed == NO ){ TblDraw ( tbl ); } /* Указать текущий элемент */ TblPoint( tbl, tbl->current, NO); TblReport( tbl ); for( ;; ){ c = WinGetch(tbl->win); INP: if( tbl->hitkeys && tbl->handler ){ HandlerReply reply; if( is_in(c, tbl->hitkeys)){ c = (*tbl->handler)(tbl, c, &reply); TblReport( tbl ); /* restore scroll bar */ switch( reply ){ case HANDLER_CONTINUE: continue; case HANDLER_NEWCHAR: goto INP; case HANDLER_OUT: goto out; case HANDLER_SWITCH: default: break; /* goto switch(c) */ } } } sw: switch( c ){ case KEY_LEFT: want = tbl->current - tbl->height; goto mv; case KEY_RIGHT: want = tbl->current + tbl->height; goto mv; case KEY_UP: want = tbl->current - 1; goto mv; case KEY_DOWN: next: want = tbl->current + 1; goto mv; case KEY_HOME: want = 0; goto mv; case KEY_END: want = tbl->nitems - 1; goto mv; case KEY_NPAGE: want = tbl->current + tbl->elems; goto mv; case KEY_PPAGE: want = tbl->current - tbl->elems; goto mv; case KEY_IC: if( T_TST(tbl, tbl->current, T_LABEL )) T_CLR(tbl, tbl->current, T_LABEL ); else T_SET(tbl, tbl->current, T_LABEL); if( tbl->current == tbl->nitems - 1 /* LAST */){ TblPoint(tbl, tbl->current, NO ); break; } TblPointAt(tbl, tbl->current ); /* if not goto next; * but break; * then use * TblPoint(tbl, tbl->current, NO); * here */ goto next; case KEY_DC: if( T_TST(tbl, tbl->current, T_HATCH )) T_CLR(tbl, tbl->current, T_HATCH ); else T_SET(tbl, tbl->current, T_HATCH); if( tbl->current == tbl->nitems - 1 /* LAST */){ TblPoint(tbl, tbl->current, NO ); break; } TblPointAt(tbl, tbl->current ); goto next; case ESC: case '\r': case '\n': goto out; case 0: break; default: c = TblTrack(tbl, c); if( c ) goto INP; break; } continue; mv: TblPointAt( tbl, want ); } out: wnoutrefresh( tbl->win ); if( tbl->hideMe ) (*tbl->hideMe)(tbl); return ((tbl->key = c) == ESC ? -1 : tbl->current ); } # Пример 23 - simple visual shell. # UNIX commander ######################################################################### # Это файл Makefile для проекта uxcom - простого меню-ориентированного # экранного интерфейса для переходов по файловой системе. # Ключ -Iкаталог указывает из какого каталога должны браться # include-файлы, подключаемые по #include "имяФайла". # Проект состоит из нескольких файлов: # Пример 17, Пример 18, Пример 19, Пример 21, Пример 23 и других. # # + Left Right _Commands Tools Sorttype + # | /usr/a+---------------------008/013-+ | # +-----------------| Главное меню |---+--+ # | .. +--------------------------+--+ | | # | .BAD | Current directory | | | | # | .contents.m| Root directory | | |##| # | DUMP | Menus | | | | # | Makefile +--------------------------+ | | | # | PLAN | Help | | | | # | _points | Unimplemented | | | | # | table | Change sorttype |##| | | # | #unbold | _Look directory history | | | | # | #uxcom +--------------------------+ | | | # | x.++ | Quit | | | | # | 00 +--------------------------+ | | | # | 11 | Redraw screen | | | | # | LOOP_p +--------------------------+--+ | | # | LOOP_q .c | etc | | # | LOOP_strt .c | install | | # +-------------------------+-------------------------+ | # | points 165 -r--r-- | .cshrc 2509 -rw-r--r-- | | # +-------------------------+-------------------------+ | # | История путешествий | | # +---------------------------------------------------+--+ # SHELL=/bin/sh SRCS = glob.c w.c menu.c pull.c match.c pwd.c hist.c line.c table.c \ main.c treemk.c OBJS = glob.o w.o menu.o pull.o match.o pwd.o hist.o line.o table.o \ main.o treemk.o # INCLUDE = /usr/include # LIB = -lncurses INCLUDE = -I../../src/curses LIB = ../../src/curses/libncurses.a DEFINES = -DUSG -DTERMIOS CC = cc -O # стандартный C-compiler + оптимизация #CC = gcc -O # GNU C-compiler uxcom: $(OBJS) $(CC) $(OBJS) -o $@ $(LIB) sync; ls -l $@; size $@ glob.o: glob.c glob.h # это файл "Пример 18" $(CC) -c glob.c w.o: w.c w.h # это файл "Пример 17" $(CC) -c $(INCLUDE) $(DEFINES) w.c menu.o: menu.c glob.h w.h menu.h # это файл "Пример 19" $(CC) -c $(INCLUDE) $(DEFINES) menu.c pull.o: pull.c glob.h w.h menu.h pull.h # это файл "Пример 20" $(CC) -c $(INCLUDE) $(DEFINES) pull.c match.o: match.c $(CC) -c -DMATCHONLY \ -DMATCH_ERR="TblMatchErr()" match.c pwd.o: pwd.c $(CC) -c -DU42 -DCWDONLY pwd.c treemk.o: treemk.c $(CC) -c $(DEFINES) \ -DERR_CANT_READ=tree_err_cant_read \ -DERR_NAME_TOO_LONG=tree_name_too_long \ -DTREEONLY -DU42 treemk.c hist.o: hist.c hist.h glob.h menu.h w.h # это файл "Пример 21" $(CC) -c $(INCLUDE) $(DEFINES) hist.c line.o: line.c w.h glob.h menu.h hist.h line.h # "Пример 21" $(CC) -c $(INCLUDE) $(DEFINES) line.c table.o: table.c w.h glob.h menu.h table.h # "Пример 22" $(CC) -c $(INCLUDE) $(DEFINES) table.c main.o: main.c glob.h w.h menu.h hist.h line.h pull.h table.h $(CC) -c $(INCLUDE) $(DEFINES) main.c w.h: wcur.h touch w.h /* _______________________ файл main.c __________________________ */ /* Ниже предполагается, что вы раскрасили в /etc/termcap * * выделения A_STANDOUT и A_REVERSE в РАЗНЫЕ цвета ! */ #include "w.h" #include "glob.h" #include "menu.h" #include "hist.h" #include "line.h" #include "table.h" #include "pull.h" #include <signal.h> #include <ustat.h> #include <locale.h> void t_enter(), t_leave(); LineEdit edit; /* редактор строки */ Hist hcwd, hedit, hpat; /* истории: */ /* посещенные каталоги, набранные команды, шаблоны имен */ Menu mwrk, msort; /* должны иметь класс static */ PullMenu pull; typedef enum { SEL_WRK=0, SEL_PANE1, SEL_PANE2, SEL_PULL, SEL_HELP } Sel; Sel current_menu; /* текущее активное меню */ Sel previous_menu; /* предыдущее активное меню */ #define SEL_PANE (current_menu == SEL_PANE1 || current_menu == SEL_PANE2) typedef struct { Table t; /* таблица с именами файлов */ DirContents d; /* содержимое каталогов */ } FileWidget; FileWidget tpane1, tpane2; /* левая и правая панели */ FileWidget *A_pane = &tpane1; /* активная панель */ FileWidget *B_pane = &tpane2; /* противоположная панель */ #define A_tbl (&A_pane->t) #define A_dir (&A_pane->d) #define B_tbl (&B_pane->t) #define B_dir (&B_pane->d) #define TblFW(tbl) ((tbl) == A_tbl ? A_pane : B_pane) void ExchangePanes(){ /* Обменять указатели на панели */ FileWidget *tmp = A_pane; A_pane = B_pane; B_pane = tmp; current_menu = (current_menu == SEL_PANE1 ? SEL_PANE2 : SEL_PANE1); } #define Other_pane(p) ((p) == A_pane ? B_pane : A_pane) #define Other_tbl(t) ((t) == A_tbl ? B_tbl : A_tbl ) WINDOW *panewin; /* окно, содержащее обе панели = stdscr */ typedef enum { NORUN=0, RUNCMD=1, CHDIR=2, TAG=3, FIND=4 } RunType; #define REPEAT_KEY 666 /* псевдоклавиша "повтори выбор в меню" */ #define LEAVE_KEY 777 /* псевдоклавиша "покинь это меню" */ #define NOSELECTED (-1) /* в меню ничего пока не выбрано */ #define CENTER (COLS/2-2) /* линия раздела панелей */ int done; /* закончена ли программа ? */ char CWD[MAXLEN]; /* полное имя текущего каталога */ char SELECTION[MAXLEN]; /* имя выбранного файла */ /*-----------------------------------------------------------------*/ /* Выдать подсказку в строке редактора */ /*-----------------------------------------------------------------*/ #include <stdarg.h> void Message(char *s, ... ){ char msg[80]; va_list args; int field_width; va_start(args, s); vsprintf(msg, s, args); va_end(args); wattrset (panewin, A_tbl->sel_attrib); field_width = A_tbl->width + B_tbl->width - 3; mvwprintw (panewin, LINES-2, tpane1.t.left+1, " %*.*s ", -field_width, field_width, msg); wattrset (panewin, A_tbl->bg_attrib); wnoutrefresh(panewin); } /*-----------------------------------------------------------------* * Меню порядка сортировки имен файлов. * *-----------------------------------------------------------------*/ Info sort_info[] = { { "По возрастанию", 0}, { "По убыванию", 0}, { "По суффиксу", 0}, { "Без сортировки", 0}, { "По размеру", M_HATCH}, { NULL, 0} }; /* При входе в меню сортировки указать текущий тип сортировки */ void sort_show(Menu *m){ MnuPointAt(&msort, (int) sorttype); } /* Выбрать тип сортировки имен файлов */ static void SelectSortType(int sel){ if( sel == NOSELECTED ) sel = MnuUsualSelect(&msort, NO); MnuHide(&msort); current_menu = previous_menu; if(M_REFUSED(&msort)) return; sorttype = (Sort) sel; A_dir->lastRead = B_dir->lastRead = 0L; /* форсировать перечитку */ /* но ничего явно не пересортировывать и не перерисовывать */ } /*-----------------------------------------------------------------* * Отслеживание содержимого каталогов и переинициализация меню. * *-----------------------------------------------------------------*/ #define NON_VALID(d) ((d)->readErrors || (d)->valid == NO) /* Сменить содержимое таблицы и списка файлов */ void InitTblFromDir(FileWidget *wd, int chdired, char *savename){ char *msg, *name; Table *tbl = &(wd->t); DirContents *d = &wd->d; int saveind = tbl->current, saveshift = tbl->shift; char *svname = NULL; if(tbl->nitems > 0 ) svname = strdup(T_ITEMF(tbl, saveind, 0)); /* Несуществующие и нечитаемые каталоги выделить особо */ if( NON_VALID(d)) wattrset(tbl->win, A_REVERSE); TblClear(tbl); if(d->valid == NO){ msg = "Не существует"; name = d->name; goto Report; } else if(d->readErrors){ /* тогда d->files->s == NULL */ msg = "Не читается"; name = d->name; Report: mvwaddstr(tbl->win, tbl->top + tbl->height/2, tbl->left + (tbl->width - strlen(name))/2, name); mvwaddstr(tbl->win, tbl->top + tbl->height/2+1, tbl->left + (tbl->width - strlen(msg))/2, msg); } wattrset(tbl->win, tbl->bg_attrib); tbl->items = d->files; TblInit(tbl, NO); /* Постараться сохранить позицию в таблице */ if( chdired ) TblPlaceByName(tbl, savename); else { if( svname == NULL || TblPlaceByName(tbl, svname) < 0 ){ tbl->shift = saveshift; tbl->current = saveind; TblChk(tbl); } } if(svname) free(svname); } /* Перейти в каталог и запомнить его полное имя */ int mychdir(char *newdir){ int code = chdir(newdir); if( code < 0 ) return code; getwd(CWD); in_the_root = (strcmp(CWD, "/") == 0); HistAdd(&hcwd, CWD, 0); /* запомнить в истории каталогов */ t_enter(&tpane1.t); /* на рамке нарисовать имя текущего каталога */ return code; } /* Изменить текущий каталог и перечитать его содержимое */ int cd(char *newdir, FileWidget *wd, char *oldname){ char oldbase[MAXLEN], *s, *strrchr(char *,char); /* Спасти в oldbase базовое имя старого каталога oldname (обычно CWD) */ if(s = strrchr(oldname, '/')) s++; else s = oldname; strcpy(oldbase, s); if( mychdir(newdir) < 0){ /* не могу перейти в каталог */ Message("Не могу перейти в %s", *newdir ? newdir : "???"); beep(); return (-1); } if( ReadDir(CWD, &wd->d)){ /* содержимое изменилось */ InitTblFromDir (wd, YES, oldbase); return 1; } return 0; } /* Проверить содержимое обеих панелей */ void checkBothPanes(){ /* Случай NON_VALID нужен только для того, чтобы Init... восстановил "аварийную" картинку в панели */ if( ReadDir(tpane1.d.name, &tpane1.d) || NON_VALID(&tpane1.d)) InitTblFromDir(&tpane1, NO, NULL); if( tpane1.t.exposed == NO ) TblDraw(&tpane1.t); if( ReadDir(tpane2.d.name, &tpane2.d) || NON_VALID(&tpane2.d)) InitTblFromDir(&tpane2, NO, NULL); if( tpane2.t.exposed == NO ) TblDraw(&tpane2.t); } /*-----------------------------------------------------------------* * Ввод команд и выдача подсказки. * *-----------------------------------------------------------------*/ /* Особая обработка отдельных клавиш в редакторе строки */ char e_move = NO; /* кнопки со стрелками <- -> двигают курсор по строке/по таблице */ int e_hit[] = { KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_F(0), KEY_IC, ctrl('G'), ctrl('E'), ctrl('L'), ctrl('F'), ctrl('X'), ctrl('Y'), -1 }; int e_handler (LineEdit *le, int c, HandlerReply *reply){ *reply = HANDLER_CONTINUE; switch(c){ /* Перемещение по таблице без выхода из редактора строки */ case KEY_LEFT: if( !SEL_PANE || !e_move){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current - A_tbl->height); break; case KEY_RIGHT: if( !SEL_PANE || !e_move){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current + A_tbl->height); break; case KEY_DOWN: if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current + 1); break; case KEY_UP: if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current - 1); break; case KEY_F(0): /* F10 */ e_move = !e_move; break; case KEY_IC: if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; } TblRetag(A_tbl, A_tbl->current, T_LABEL); TblPointAt(A_tbl, A_tbl->current+1); break; /* Подстановки */ case ctrl('G'): /* подставить полное имя домашнего каталога */ LeInsStr(le, getenv("HOME")); LeInsStr(le, " "); break; case ctrl('E'): /* подставить имя выбранного файла */ if( A_tbl->nitems ) LeInsStr(le, T_ITEMF(A_tbl, A_tbl->current, 0)); LeInsStr(le, " "); break; case ctrl('L'): /* подставить имя выбранного файла из другой панели */ LeInsStr(le, T_ITEMF(B_tbl, B_tbl->current, 0)); LeInsStr(le, " "); break; case ctrl('X'): case ctrl('Y'): /* подстановка имен помеченных файлов */ { int label = (c == ctrl('X') ? T_LABEL : T_HATCH); register i; for(i=0; i < A_tbl->nitems && le->len < le->maxlen; ++i ) if( T_TST(A_tbl, i, label)){ LeInsStr(le, " "); LeInsStr(le, T_ITEMF(A_tbl, i, 0)); } } break; case ctrl('F'): /* подставить имя текущего каталога */ LeInsStr(le, CWD); LeInsStr(le, " "); break; } return c; } /* При начале редактирования ставь курсор в конец строки */ void e_pos (LineEdit *le){ le->pos = le->len; } /* Обозначить, что мы покинули редактор строки */ void e_hide(LineEdit *le){ le->sel_attrib = le->fr_attrib = le->bg_attrib = A_ITALICS; LeDraw(le); } /* Отредактировать строку в предпоследней строке окна */ char *Edit(WINDOW *w, char *src, RunType dorun){ static char CMD[MAXLEN]; /* буфер для строки команды */ int c; if(w != TOPW){ beep(); return NULL; }/* это должно быть верхнее окно */ keypad(w, TRUE); /* Проинициализировать редактор строки */ switch(dorun){ case NORUN: edit.histIn = edit.histOut = NULL; break; case RUNCMD: edit.histIn = edit.histOut = &hedit; break; case FIND: case TAG: edit.histIn = edit.histOut = &hpat; break; case CHDIR: edit.histIn = &hcwd; edit.histOut = NULL; break; } edit.line = CMD; edit.maxlen = sizeof(CMD)-1; edit.top = wlines(w)-2; edit.left = 2; edit.width = wcols (w)-4 - (1+BARWIDTH); edit.insert = YES; edit.nc = YES; edit.win = w; edit.wl_attrib = edit.bg_attrib=A_REVERSE; edit.fr_attrib=A_STANDOUT; edit.sel_attrib = A_NORMAL|A_BLINK; edit.posMe = e_pos; edit.hitkeys = (SEL_PANE ? e_hit : e_hit+5); edit.handler = e_handler; /* edit.hideMe = e_hide; вызывается ЯВНО */ /* остальные поля равны 0, т.к. edit - статическое данное */ for(;;){ strcpy(CMD, src); if(*src){ strcat(CMD, " "); } c = LeEdit( &edit ); if( LE_REFUSED(&edit) || dorun != RUNCMD || !*CMD || c != '\n' ) break; /* курсор в нижнюю строку экрана */ attrset(A_NORMAL); move(LINES-1, 0); refresh(); resetterm(); /* приостановить работу curses-а */ putchar('\n'); /* промотать экран на строку */ system(CMD); /* выполнить команду внешним Шеллом */ fprintf(stderr,"Нажми ENTER чтобы продолжить --- ");gets(CMD); fixterm(); /* возобновить работу curses-а */ RedrawScreen(); /* перерисовать экран */ if(w == panewin){ checkBothPanes(); if(A_tbl->nitems) TblPoint(A_tbl, A_tbl->current, NO); } src = ""; /* во второй раз ничего не подставлять */ } wattrset(w, A_NORMAL); /* ? */ e_hide ( &edit ); return ( *CMD && !LE_REFUSED(&edit)) ? CMD : NULL; } /* Выдача подсказки а также сообщений об ошибках. */ /* В этом же окне можно набирать команды (dorun==RUNCMD). */ char *help(char *msg, RunType dorun){ register i; char *s; static char *helptext[] = { "ESC - выход в главное меню", "F1 - подсказка", "INS - пометить файл", "ctrl/E - подставить имя выбранного файла", "ctrl/L - подставить имя из другой панели", "ctrl/X - подставить помеченные файлы", "ctrl/Y - подставить помеченные курсивом", "ctrl/G - подставить имя домашнего каталога", "ctrl/F - подставить имя текущего каталога", "F4 - история", "F7 - переключить режим вставки/замены", "F10 - переключить перемещения по строке/по панели", }; #define HELPLINES (sizeof(helptext)/sizeof helptext[0]) Sel save_current_menu = current_menu; /* "выскакивающее" POP-UP window */ WINDOW *w = newwin(2+1+HELPLINES+1, 70, 2, (COLS-70)/2); if( w == NULL ) return NULL; current_menu = SEL_HELP; wattrset(w, A_REVERSE); /* это будет инверсное окно */ werase (w); /* заполнить инверсным фоном */ wborder(w); RaiseWin(w); /* окно появляется */ if(*msg){ wattron (w, A_BOLD); mvwaddstr(w, 1+HELPLINES, 2, msg); wattroff(w, A_BOLD); } for(i=0; i < HELPLINES; i++) mvwaddstr(w, 1+i, 2, helptext[i]); s = Edit(w, "", dorun); PopWin(); /* окно исчезает */ current_menu = save_current_menu; return s; } /*-----------------------------------------------------------------* * Управляющее меню. * *-----------------------------------------------------------------*/ int f_left(), f_right(), f_pull(), f_help(), f_sort(), f_dir(), f_bye(), f_redraw(),f_cdroot(); /* Обратите внимание, что можно указывать не все поля структуры, * а только первые. Остальные равны 0 */ #ifndef __GNUC__ Info mwrk_info[] = { /* строки для главного меню */ { "\\Current directory", 0 , f_left }, /* 0 */ { "\\Root directory", M_HATCH , f_right }, /* 1 */ { "\\Menus", 0 , f_pull }, /* 2 */ { "\1", /* гориз. черта */ 0 }, /* 3 */ { "\\Help", 0 , f_help }, /* 4 */ { "Un\\implemented", I_NOSEL }, /* 5 */ { "Change \\sorttype", 0 , f_sort }, /* 6 */ { "Look directory \\history", 0 , f_dir }, /* 7 */ { "\1", /* гориз. черта */ 0 }, /* 8 */ { "\\Quit", M_BOLD , f_bye }, /* 9 */ { "\1", /* гориз. черта */ 0 }, /* 10 */ { "\\Redraw screen", M_HATCH , f_redraw}, /* 11 */ { "Chdir both panels to /", M_HATCH , f_cdroot}, /* 12 */ { NULL, 0 } }; #else /* GNU C-компилятор 1.37 не может инициализировать поля-union-ы */ static char _gnu_[] = "Compiled with GNU C-compiler"; Info mwrk_info[] = { /* строки для главного меню */ { "\\Current directory", 0 }, { "\\Root directory", M_HATCH }, { "\\Menus", 0 }, { "\1", /* гориз. черта */ 0 }, { "\\Help", 0 }, { "Un\\implemented", I_NOSEL }, { "Change \\sorttype", 0 }, { "Look directory \\history", 0 }, { "\1", /* гориз. черта */ 0 }, { "\\Quit", M_BOLD }, { "\1", /* гориз. черта */ 0 }, { "\\Redraw screen", M_HATCH }, { "Chdir both panels to /", M_HATCH }, { NULL, 0 } }; void mwrk_init(){ mwrk_info [0].any.act = f_left; mwrk_info [1].any.act = f_right; mwrk_info [2].any.act = f_pull; mwrk_info [4].any.act = f_help; mwrk_info [6].any.act = f_sort; mwrk_info [7].any.act = f_dir; mwrk_info [9].any.act = f_bye; mwrk_info[11].any.act = f_redraw; mwrk_info[12].any.act = f_cdroot; } #endif char *mwrk_help[] = { "Перейти в левую панель", "Перейти в правую панель", "Перейти в строчное меню", "", "Выдать подсказку", "Не реализовано", "Изменить тип сортировки имен", "История путешествий", "", "Выход", "", "Перерисовка экрана", "Обе панели поставить в корневой каталог", NULL }; void m_help(Menu *m, int n, int among){ Message(mwrk_help[n]); } /* Выбор в рабочем (командном) меню */ void SelectWorkingMenu(int sel){ if(sel == NOSELECTED) sel = MnuUsualSelect( & mwrk, NO); if( M_REFUSED(&mwrk)) help("Выбери Quit", NORUN); else if(mwrk.items[sel].any.act) (*mwrk.items[sel].any.act)(); if( !done) MnuHide( & mwrk ); } f_left () { current_menu = SEL_PANE1; return 0; } f_right() { current_menu = SEL_PANE2; return 0; } f_pull () { current_menu = SEL_PULL; return 0; } f_help () { help("Нажми ENTER или набери команду:", RUNCMD); return 0; } f_sort () { SelectSortType(NOSELECTED); return 0; } f_dir () { Info *idir; if(idir = HistSelect(&hcwd, 20, 3)) cd(idir->s, &tpane2, CWD); current_menu = SEL_PANE2; return 0; } f_bye () { done++; return 0; } f_redraw() { RedrawScreen(); return 0; } f_cdroot() { cd("/", &tpane1, CWD); cd("/", &tpane2, CWD); checkBothPanes(); return 0; } /*-----------------------------------------------------------------* * Выдача информации про файл, редактирование кодов доступа. * *-----------------------------------------------------------------*/ void MYwaddstr(WINDOW *w, int y, int x, int maxwidth, char *s){ register pos; for(pos=0; *s && *s != '\n' && pos < maxwidth; ++s){ wmove(w, y, x+pos); if( *s == '\t') pos += 8 - (pos & 7); else if( *s == '\b'){ if(pos) --pos; } else if( *s == '\r') pos = 0; else { ++pos; waddch(w, isprint(*s) ? *s : '?'); } } } /* Просмотр начала файла в противоположной панели. */ void fastView( char *name, /* имя файла */ unsigned mode, /* некоторые типы файлов не просматривать */ Table *otbl /* противоположная панель */ ){ FILE *fp; register int x, y; char buf[512]; TblClear(otbl); Message("Нажми ENTER для окончания. " "ПРОБЕЛ - изменяет код доступа. " "ESC - откатка."); if( !ISREG(mode)) goto out; if((fp = fopen(name, "r")) == NULL){ Message("Не могу читать %s", name); return; } for(y=0; y < otbl->height && fgets(buf, sizeof buf, fp); y++) MYwaddstr(panewin, otbl->top+y, otbl->left+1, otbl->width-2, buf); fclose(fp); out: wrefresh(otbl->win); /* проявить */ } static struct attrNames{ unsigned mode; char name; char acc; int off; } modes[] = { { S_IREAD, 'r', 'u', 0 }, { S_IWRITE, 'w', 'u', 1 }, { S_IEXEC, 'x', 'u', 2 }, { S_IREAD >> 3, 'r', 'g', 3 }, { S_IWRITE >> 3, 'w', 'g', 4 }, { S_IEXEC >> 3, 'x', 'g', 5 }, { S_IREAD >> 6, 'r', 'o', 6 }, { S_IWRITE >> 6, 'w', 'o', 7 }, { S_IEXEC >> 6, 'x', 'o', 8 }, }; #define NMODES (sizeof(modes)/sizeof(modes[0])) /* Позиция в которой изображать i-ый бит кодов доступа */ #define MODE_X_POS(tbl, i) (tbl->left + DIR_SIZE + 12 + modes[i].off) #define MODE_Y_POS(tbl) (tbl->top + tbl->height + 1) #ifdef FILF /* Изобразить информацию о текущем выбранном файле */ void showMode(Table *tbl, int attr){ Info *inf = & tbl->items[tbl->current]; /* файл */ register i; unsigned mode = inf->mode; /* коды */ int uid = inf->uid, gid = inf->gid; /* хозяин */ /* идентификаторы хозяина и группы процесса-коммандера */ static char first = YES; static int myuid, mygid; WINDOW *win = tbl->win; int xleft = tbl->left + 1, y = MODE_Y_POS(tbl); if( first ){ first = NO; myuid = getuid(); mygid = getgid(); } wattron (win, attr); mvwprintw(win, y, xleft, " %*.*s %8ld ", /* имя файла */ -DIR_SIZE, DIR_SIZE, inf->s ? (!strcmp(inf->s, "..") ? "<UP-DIR>": inf->s) : "(EMPTY)", inf->size); /* тип файла (обычный|каталог|устройство) */ wattron (win, A_ITALICS|A_BOLD); waddch (win, ISDIR(mode) ? 'd': ISDEV(mode) ? '@' : '-'); wattroff(win, A_ITALICS|A_BOLD); /* коды доступа */ for(i=0; i < NMODES; i++){ if((modes[i].acc == 'u' && myuid == uid) || (modes[i].acc == 'g' && mygid == gid) || (modes[i].acc == 'o' && myuid != uid && mygid != gid)) ; else wattron(win, A_ITALICS); mvwaddch(win, y, MODE_X_POS(tbl, i), mode & modes[i].mode ? modes[i].name : '-'); wattroff(win, A_ITALICS); } waddch(win, ' '); wattroff(win, attr); } #define newmode (tbl->items[tbl->current].mode) /* Редактирование кодов доступа к файлам. */ int editAccessModes(FileWidget *wd){ Table *tbl = &wd->t; Table *otbl = &(Other_pane(wd)->t); /* или Other_tbl(tbl); */ unsigned prevmode, oldmode; /* старый код доступа */ char *name; /* имя текущего файла */ WINDOW *win = tbl->win; int position = 0, c; for(;;){ /* Цикл выбора файлов в таблице */ name = T_ITEMF(tbl, tbl->current, 0); oldmode = newmode; /* запомнить */ fastView(name, newmode, otbl); /* показать первые строки файла */ for(;;){ /* Цикл обработки выбранного файла */ wmove(win, MODE_Y_POS(tbl), MODE_X_POS(tbl, position)); switch(c = WinGetch(win)){ /* Некоторые клавиши вызывают перемещение по таблице */ case KEY_BACKTAB: TblPointAt(tbl, tbl->current - tbl->height); goto mv; case '\t': TblPointAt(tbl, tbl->current + tbl->height); goto mv; case KEY_UP: TblPointAt(tbl, tbl->current - 1); goto mv; case KEY_DOWN: TblPointAt(tbl, tbl->current + 1); goto mv; case KEY_HOME: TblPointAt(tbl, 0); goto mv; case KEY_END: TblPointAt(tbl, tbl->nitems-1); goto mv; /* Прочие клавиши предназначены для редактирования кодов доступа */ case KEY_LEFT: if(position) --position; break; case KEY_RIGHT: if(position < NMODES-1) position++; break; default: goto out; case ESC: /* Восстановить старые коды */ prevmode = newmode = oldmode; goto change; case ' ': /* Инвертировать код доступа */ prevmode = newmode; /* запомнить */ newmode ^= modes[position].mode; /* инвертировать */ change: if( chmod(name, newmode) < 0){ beep(); Message("Не могу изменить доступ к %s", name); newmode = prevmode; /* восстановить */ } else /* доступ изменен, показать это */ showMode(tbl, A_REVERSE); break; } } /* Конец цикла обработки выбранного файла */ mv: ; } /* Конец цикла выбора файлов в таблице */ out: /* Очистить противоположную панель после fastView(); */ Message(""); TblClear(otbl); return c; } #undef newmode #else void editAccessModes(FileWidget *wd){} #endif long diskFree(){ struct ustat ust; struct stat st; long freespace; if(stat(".", &st) < 0) return 0; ustat(st.st_dev, &ust); freespace = ust.f_tfree * 512L; freespace /= 1024; Message("В %*.*s свободно %ld Кб.", -sizeof(ust.f_fname), sizeof(ust.f_fname), *ust.f_fname ? ust.f_fname : ".", freespace); doupdate(); /* проявить окно для Message() */ return freespace; } /*-----------------------------------------------------------------* * Специальные команды, использующие обход дерева *-----------------------------------------------------------------*/ /* Выдача сообщений об ошибках (смотри Makefile) */ int tree_err_cant_read(char *name){ Message("Не могу читать \"%s\"", name); return WARNING; } int tree_name_too_long(){ Message("Слишком длинное полное имя"); return WARNING; } char canRun; /* продолжать ли поиск */ /* Прерывание обхода по SIGINT */ void onintr_f(nsig){ canRun = NO; Message("Interrupted"); } /* ==== место, занимаемое поддеревом ==== */ long tu(int *count){ struct stat st; register i; long sum = 0L; *count = 0; for(i=0; i < A_tbl->nitems ;++i ) if( T_TST(A_tbl, i, T_LABEL)){ stat(T_ITEMF(A_tbl, i, 0), &st); #define KB(s) (((s) + 1024L - 1) / 1024L) sum += KB(st.st_size); (*count)++; } return sum; } void diskUsage(){ long du(), size, sizetagged; int n; char msg[512]; Message("Измеряем объем файлов..."); doupdate(); size = du("."); diskFree(); sizetagged = tu(&n); sprintf(msg, "%ld килобайт в %s, %ld кб в %d помеченных файлах", size, CWD, sizetagged, n); help(msg, NORUN); } /* ==== поиск файла ===================== */ extern char *find_PATTERN; /* imported from treemk.c */ extern Info gargv[]; extern int gargc; /* imported from glob.c */ /* Проверить очередное имя и запомнить его, если подходит */ static int findCheck(char *fullname, int level, struct stat *st){ char *basename = strrchr(fullname, '/'); if(basename) basename++; else basename = fullname; if( canRun == NO ) return FAILURE; /* поиск прерван */ if( match(basename, find_PATTERN)){ /* imported from match.c */ gargv[gargc] = NullInfo; /* зачистка */ gargv[gargc].s = strdup(fullname); gargv[gargc++].fl= ISDIR(st->st_mode) ? I_DIR : 0; gargv[gargc] = NullInfo; Message("%s", fullname); doupdate(); } /* Страховка от переполнения gargv[] */ if ( gargc < MAX_ARGV - 1 ) return SUCCESS; else { Message("Найдено слишком много имен."); return FAILURE; } } /* Собрать имена файлов, удовлетворяющие шаблону */ static Info *findAndCollect(char *pattern){ void (*old)() = signal(SIGINT, onintr_f); Sort saveSort; find_PATTERN = pattern; canRun = YES; Message("Ищем %s от %s", pattern, CWD); doupdate(); greset(); /* смотри glob.c, gargc=0; */ walktree(CWD, findCheck, NULL, findCheck); signal(SIGINT, old); saveSort = sorttype; sorttype = SORT_ASC; if(gargc) qsort( gargv, gargc, sizeof(Info), gcmps); sorttype = saveSort; return gargc ? blkcpy(gargv) : NULL; } /* Обработать собранные имена при помощи предъявления меню с ними */ void findFile(FileWidget *wd){ static Info *found; static Menu mfind; int c; Table *tbl = & wd->t; char *pattern = help("Введи образец для поиска, вроде *.c, " "или ENTER для прежнего списка", FIND); if( LE_REFUSED( &edit)) return; /* отказались от поиска */ /* Если набрана пустая строка, help() выдает NULL */ if( pattern ){ /* задан новый образец - ищем */ /* Уничтожить старый список файлов и меню */ if( found ) blkfree( found ); MnuDeinit( &mfind ); found = findAndCollect(pattern); /* поиск */ HistAdd( &hpat, pattern, 0); /* Образуем меню из найденных файлов */ if( found ){ /* если что-нибудь нашли */ mfind.items = found; mfind.title = pattern ? pattern : "Найденные файлы"; mfind.top = 3; mfind.left = COLS/6; mfind.bg_attrib = A_STANDOUT; mfind.sel_attrib = A_REVERSE; MnuInit (&mfind); } } /* else набрана пустая строка - просто вызываем список * найденных ранее файлов. */ if( found == NULL ){ Message("Ничего не найдено"); beep(); return; } c = MnuUsualSelect(&mfind, NO); /* Выбор файла в этом меню вызовет переход в каталог, * в котором содержится этот файл */ if( !M_REFUSED( &mfind )){ char *s = M_ITEM(&mfind, mfind.current); /* пометить выбранный элемен