Пример 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);
/* пометить выбранный элемент */
M_SET(&mfind, mfind.current, M_LABEL);
/* если это каталог - войти в него */
if( M_TST(&mfind, mfind.current, I_DIR))
cd(s, wd, CWD);
/* иначе войти в каталог, содержащий этот файл */
else { char *p; struct savech svch; /* смотри glob.h */
SAVE( svch, strrchr(s, '/')); *svch.s = '\0';
p = strdup(s); RESTORE(svch);
if( !strcmp(CWD, p)) /* мы уже здесь */
TblPlaceByName(tbl, svch.s+1); /* указать курсором */
else /* изменить каталог и указать курсором на файл s */
cd(p, wd, s);
free(p);
}
}
MnuHide(&mfind); /* спрятать меню, не уничтожая его */
}
/*-----------------------------------------------------------------*
* Работа с панелями, содержащими имена файлов двух каталогов. *
*-----------------------------------------------------------------*/
/* Восстановить элементы, затертые рамкой WinBorder */
void t_restore_corners(){
mvwaddch(panewin, LINES-3, 0, LEFT_JOIN);
mvwaddch(panewin, LINES-3, COLS-2-BARWIDTH, RIGHT_JOIN);
mvwaddch(panewin, LINES-5, 0, LEFT_JOIN);
mvwaddch(panewin, LINES-5, COLS-2-BARWIDTH, RIGHT_JOIN);
mvwaddch(panewin, 2, CENTER, TOP_JOIN);
wattron (panewin, A_BOLD);
mvwaddch(panewin, LINES-3, CENTER, BOTTOM_JOIN);
mvwaddch(panewin, LINES-5, CENTER, MIDDLE_CROSS);
wattroff(panewin, A_BOLD);
}
/* Нарисовать нечто при входе в панель. Здесь изменяется
* заголовок окна: он становится равным имени каталога,
* просматриваемого в панели */
void t_enter(Table *tbl){
WinBorder(tbl->win, tbl->bg_attrib, tbl->sel_attrib,
CWD, BAR_VER|BAR_HOR, NO);
t_restore_corners();
}
/* Стереть подсветку при выходе из панели */
void t_leave(Table *tbl){ TblDrawItem( tbl, tbl->current, NO, YES ); }
/* Рисует недостающую часть рамки, которая не изменяется впоследствии */
void t_border_common(){
WinBorder(panewin, A_tbl->bg_attrib, A_tbl->sel_attrib,
A_dir->name, BAR_VER|BAR_HOR, NO);
wattron (panewin, A_BOLD);
whorline(panewin, LINES-3, 1, COLS-1-BARWIDTH-1);
whorline(panewin, LINES-5, 1, COLS-1-BARWIDTH-1);
wverline(panewin, CENTER, A_tbl->top, A_tbl->top + A_tbl->height+2);
wattroff(panewin, A_BOLD);
t_restore_corners();
}
/* Функция, изображающая недостающие части панели при входе в нее */
int t_show(Table *tbl){
#ifdef FILF
showMode(A_tbl, A_STANDOUT); showMode(B_tbl, A_STANDOUT);
#endif
return 1;
}
void t_scrollbar(Table *tbl, int whichbar, int n, int among){
WinScrollBar(tbl->win, BAR_VER|BAR_HOR, n, among,
"Yes", tbl->bg_attrib);
#ifdef FILF
showMode(tbl, A_REVERSE);
#endif
}
/* Особая обработка клавиш при выборе в таблице */
int t_hit[] = {
'\t', KEY_F(1), KEY_F(2), KEY_F(3),
KEY_F(4), KEY_F(8), ' ', '+',
'-', ctrl('R'), ctrl('L'), ctrl('F'),
-1 };
Info t_info[] = {
{ "TAB Перейти в другую панель", 0},
{ "F1 Выдать подсказку", 0},
{ "F2 Ввести команду", 0},
{ "F3 Перейти в родительский каталог", 0},
{ "F4 Перейти в каталог по имени", 0},
{ "F8 Удалить помеченные файлы", 0},
{ "ПРОБЕЛ Редактировать коды доступа", 0},
{ "+ Пометить файлы", 0},
{ "- Снять пометки", 0},
{ "ctrl/R Перечитать каталог", 0},
{ "ctrl/L Выдать размер файлов в каталоге",0},
{ "ctrl/F Поиск файла", 0},
{ NULL, 0}
};
int t_help(){
static Menu mth; int c = 0;
if( mth.items == NULL ){
mth.items = t_info;
mth.title = "Команды в панели";
mth.top = 3; mth.left = COLS/6;
mth.bg_attrib = A_STANDOUT; mth.sel_attrib = A_REVERSE;
MnuInit (&mth);
mth.hotkeys = t_hit;
}
c = MnuUsualSelect(&mth, 0);
/* Спрятать меню, не уничтожая его. Уничтожение выглядело бы так:
* mth.hotkeys = NULL; (т.к. они не выделялись malloc()-ом)
* MnuDeinit(&mth);
*/
MnuHide(&mth);
if( M_REFUSED(&mth)) return 0; /* ничего не делать */
return t_hit[c]; /* клавиша, соответствующая выбранной строке */
}
int t_handler (Table *tbl, int c, HandlerReply *reply){
int i, cnt=0; extern int unlink(), rmdir(); char *answer;
FileWidget *wd = TblFW (tbl);
switch(c){
case '\t': /* перейти в соседнюю панель */
ExchangePanes();
*reply = HANDLER_OUT; return LEAVE_KEY; /* покинуть эту панель */
case KEY_F(1): *reply = HANDLER_NEWCHAR; return t_help();
case KEY_F(2):
(void) Edit(tbl->win, T_ITEMF(tbl, tbl->current, 0), RUNCMD);
break;
case KEY_F(3): cd(".." , wd, CWD); break;
case KEY_F(4):
if(answer = help("Введи имя каталога, в который надо перейти",CHDIR))
cd(answer , wd, CWD);
break;
case ctrl('R'): break;
case KEY_F(8):
for(i=0; i < tbl->nitems; i++)
if(T_TST(tbl, i, M_LABEL)){ int code; cnt++;
if((code = (T_TST(tbl, i, I_DIR) ? rmdir : unlink) (T_ITEMF(tbl, i,0))) < 0)
T_SET(tbl, i, M_HATCH);
}
if(cnt==0) help("Нет помеченных файлов", NORUN);
break;
case '+':
if(answer = help("Шаблон для пометки", TAG))
TblTagAll(tbl, answer, T_LABEL);
break;
case '-':
if(answer = help("Шаблон для снятия пометок", TAG))
TblUntagAll(tbl, answer, T_LABEL);
break;
case ctrl('L'): /* команда "disk usage" */
diskUsage(); break;
case ctrl('F'): /* поиск файла */
findFile(wd); break;
case ' ': /* редактирование кодов доступа */
editAccessModes(wd); break;
}
*reply = HANDLER_OUT; return REPEAT_KEY;
/* вернуться в эту же панель */
}
/* Выбор в одной из панелей. */
int SelectPane(FileWidget *wd){
Table *tbl = & wd->t;
DirContents *d = & wd->d;
int sel, retcode = 0;
RaiseWin( tbl->win );
/* войти в указанный каталог, поправить CWD */
if(mychdir( d->name ) < 0) checkBothPanes();
/* t_enter( tbl ); /* войти в указанную панель, поправить рамку */
for(;;){
/* Проверить, не устарело ли содержимое таблиц */
checkBothPanes();
if((sel = TblUsualSelect( tbl )) == TOTAL_NOSEL ){
current_menu = SEL_PULL; goto out; }
if( T_REFUSED(tbl)) break; /* нажат ESC */
if( tbl->key == LEAVE_KEY ){ retcode=1; break; }
strcpy(SELECTION, T_ITEMF(tbl, sel, 0));
if( tbl->key == REPEAT_KEY ) continue;
if(T_TST(tbl, sel, I_DIR)){ /* это каталог */
/* попытаться перейти в этот каталог */
cd(SELECTION, wd, CWD);
} else if(T_TST(tbl, sel, I_EXE)){ /* выполняемый файл */
(void) Edit(tbl->win, SELECTION, RUNCMD);
} else {
editAccessModes(wd);
/* На самом деле надо производить подбор команды по
* типу файла (набор соответствий должен программироваться
* вами в специальном файле, считываемом при запуске коммандера).
* runCommand( classify(SELECTION));
* где классификация в простейшем случае - по имени и суффиксу,
* а в более развитом - еще и по кодам доступа (включая тип файла)
* и по первой строке файла (или "магическому числу").
*/
}
} /* end for */
t_leave( tbl );
out:
if( !retcode ) current_menu = SEL_PULL; /* выход по ESC */
return retcode;
}
/*-----------------------------------------------------------------*
* Горизонтальное командное меню (вызывается по ESC). *
*-----------------------------------------------------------------*/
PullInfo pm_items [] = { /* подсказка */
{{ " \\Left ", 0 }, NULL, "Left pane" }, /* 0 */
{{ " \\Commands ", 0 }, &mwrk, "Do some commands"}, /* 1 */
{{ " \\Tools ", PM_NOSEL }, NULL, "" }, /* 2 */
{{ " \\Sorttype ", 0 }, &msort, "Change sort type"}, /* 3 */
{{ " \\Right ", 0 }, NULL, "Right pane" }, /* 4 */
{{ NULL, 0 }, NULL, NULL }
};
void p_help(PullMenu *p, int n, int among){ Message( PM_NOTE(p, n)); }
/* Выбор в меню-строке */
void SelectPullMenu(){
int c, sel; Menu *m;
for(;current_menu == SEL_PULL;){
c = PullUsualSelect(&pull);
sel = pull.current;
if( PM_REFUSED(&pull)){ current_menu = previous_menu; return;}
switch(sel){
case 0: current_menu = SEL_PANE1; return;
case 1: SelectWorkingMenu(c); return;
case 2: return; /* не бывает */
case 3: SelectSortType(c); return;
case 4: current_menu = SEL_PANE2; return;
}
}
}
/*-----------------------------------------------------------------*
* Инициализация и завершение. *
*-----------------------------------------------------------------*/
void die(int sig){
echo(); nocbreak(); mvcur(-1,-1,LINES-1,0);
refresh(); endwin (); putchar('\n');
if(sig) printf("Signal %d\n", sig);
if(sig == SIGSEGV) abort(); else exit(sig);
}
void main (void) {
setlocale(LC_ALL, ""); /* получить информацию о языке диагностик */
initscr (); /* включить curses */
signal(SIGINT, die); /* по сигналу вызывать die(); */
signal(SIGBUS, die); /* по нарушению защиты памяти */
signal(SIGSEGV,die);
refresh(); /* обновить экран: это очистит его */
noecho(); cbreak(); /* выключить эхо, включить прозрачный ввод */
/* Проинициализировать истории */
HistInit(&hcwd, 20); hcwd. mnu.title = "История пути";
HistInit(&hedit, 20); hedit.mnu.title = "История команд";
HistInit(&hpat, 8); hpat. mnu.title = "Шаблоны имен";
/* Разметить меню сортировки */
msort.items = sort_info;
msort.title = "Вид сортировки каталога";
msort.top = 1; msort.left = 2;
msort.showMe = sort_show;
msort.bg_attrib = A_NORMAL; msort.sel_attrib = A_STANDOUT;
/* MnuInit (&msort); инициализируется в pull-menu */
/* Разметить рабочее меню */
mwrk.items = mwrk_info;
mwrk.title = "Главное меню";
mwrk.top = 1; mwrk.left = COLS/3;
mwrk.handler = NULL; mwrk.hitkeys = NULL;
mwrk.bg_attrib = A_STANDOUT; mwrk.sel_attrib = A_REVERSE;
mwrk.scrollBar = m_help;
#ifdef __GNUC__
mwrk_init();
#endif
/* MnuInit (&mwrk); инициализируется в pull-menu */
/* Разметить левую и правую панели */
tpane1.t.width = CENTER - 1;
tpane2.t.width = COLS - tpane1.t.width - 2 - (2 + BARWIDTH);
tpane1.t.height = tpane2.t.height = (LINES - 8);
tpane1.t.win = tpane2.t.win = panewin = stdscr;
tpane1.t.left = 1;
tpane2.t.left = CENTER+1;
tpane1.t.top = tpane2.t.top = 3;
tpane1.t.bg_attrib = tpane2.t.bg_attrib = A_NORMAL;
tpane1.t.sel_attrib = tpane2.t.sel_attrib = A_STANDOUT;
tpane1.t.scrollBar = tpane2.t.scrollBar = t_scrollbar;
tpane1.t.hitkeys = tpane2.t.hitkeys = t_hit;
tpane1.t.handler = tpane2.t.handler = t_handler;
tpane1.t.showMe = tpane2.t.showMe = t_show;
tpane1.t.hideMe = tpane2.t.hideMe = NULL;
/* Разметить имена для файловых объектов */
tpane1.d.name = strdup("Текущий каталог");
tpane2.d.name = strdup("Корневой каталог");
/* Изобразить рамки (но пока не проявлять их)
* Это надо сделать до первого cd(), т.к. иначе при неудаче будет выдано
* сообщение, которое проявит НЕЗАВЕРШЕННУЮ картинку */
t_border_common(); t_restore_corners();
/* Доразметить левую панель */
mychdir("."); /* узнать полное имя текущего каталога в CWD[] */
/* прочитать содержимое каталога CWD в tpane1.d */
cd( CWD , &tpane1, CWD);
tpane1.t.fmt = "directory";
InitTblFromDir(&tpane1, NO, NULL);
/* Доразметить правую панель */
tpane2.t.fmt = NULL;
/* прочитать содержимое каталога "/" в tpane2.d */
cd( "/", &tpane2, CWD); /* теперь стоим в корне */
/* Вернуться в рабочий каталог */
cd( tpane1.d.name, &tpane1, CWD);
/* Нарисовать обе панели */
TblDraw(A_tbl); TblDraw(B_tbl);
/* Разметить pulldown меню */
pull.bg_attrib = A_REVERSE; pull.sel_attrib = A_NORMAL;
pull.items = pm_items; pull.scrollBar = p_help;
PullInit(&pull);
/* Основной цикл */
for(done=NO, current_menu=SEL_PANE1, A_pane= &tpane1, B_pane= &tpane2;
done == NO; ){
Message("");
if(SEL_PANE) previous_menu = current_menu;
switch(current_menu){
case SEL_WRK : SelectWorkingMenu(NOSELECTED); break;
case SEL_PULL: SelectPullMenu(); break;
case SEL_PANE1: if( SelectPane(&tpane1) < 0)
M_SET(&mwrk, 0, I_NOSEL); break;
case SEL_PANE2: if( SelectPane(&tpane2) < 0)
M_SET(&mwrk, 0, I_NOSEL); break;
}
}
die(0); /* Завершить работу */
}
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед