Пример 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(); */
}

© Copyright А. Богатырев, 1992-95 www.abyss-group.narod.ru
Си в UNIX

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

Hosted by uCoz