Пример 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

Назад | Содержание | Вперед