14.5 Текстовый интерфейс — curses |
Предыдущая Содержание Следующая |
curses — древняя библиотека, которая управляет символьно-ориентированными терминалами и скрывает индивидуальные особенности различных терминалов при помощи баз данных termcap или terminfo [Sch90]. Первоначально Кен Арнольд в Беркли извлёк эти функции из редактора vi Билли Джоя. Между тем есть несколько оптимизированных реализаций, даже для DOS; некоторые находятся в свободном доступе. Если мы хотим соединить наш калькулятор с curses, то должны реализовать замены для LineOut и Button и соединить их с объектом Calc. curses предоставляют тип данных WINDOW и оказывается, что лучший выбор состоит в использовании WINDOW для каждого графического объекта. Оригинальная версия curses не использует мышь или даже не предоставляет функций для перемещения курсора под управлением чего-то подобного клавишам со стрелками. Поэтому мы должны будем реализовать другой объект, который контролирует курсор и отправляет кнопкам позиции курсора как события. Похоже, что у нас есть два выбора. Можно определить подкласс Ic для управления курсором и основным циклом приложения, и подклассы Button и LineOut, чтобы предоставить графические объекты. Каждый из этих трёх классов будут владеть его собственным WINDOW. Альтернативно, как показано ниже, можно запустить новую иерархию с помощью подкласса Ic, который владеет WINDOW и может управлять курсором. Дополнительно создаётся ещё два подкласса, которым затем, вероятно, придётся владеть Button и LineOut, соответственно.
Ни одно из решений не выглядит совершенно правильным. Первое кажется, вероятно, ближе к нашему приложению, но мы не инкапсулируем существование типа данных WINDOW в одном классе и не похоже, что мы упаковываем curses способом, который может быть снова использован для следующего проекта. Второе решение, похоже, инкапсулирует curses главным образом в Crt; однако, подклассы должны содержать объекты, которые очень близки данному приложению, то есть, вероятно, мы получим всё же одноразовое решение. Давайте придерживаться второго подхода. В следующем разделе мы увидим, как можно создать лучшую конструкцию с передачей сообщения. Вот новые классы:
Реализация этих классов требует хорошего знания curses; поэтому, мы не будем рассматривать здесь детали кода. Пакет curses должен быть проинициализирован; это выполняется через ловушку в Crt_ctor(): необходимые функции curses вызываются, когда создаётся первый объект Crt. Crt_gate() содержит основной цикл программы. Он игнорирует свое входящее событие и читает данные из клавиатуры, пока это не увидит control-D в качестве индикатора завершения ввода. В этой точке он вернёт reject и основная программа завершится. Для управления курсором используется несколько символов ввода. Если нажат return, Crt_gate() вызывает super_gate(), чтобы отослать событие с kind, равным единице, и с целочисленным массивом с текущей позицией курсора в виде строки и столбца. Все другие символы отсылаются наружу как события с kind, равным нулю, и символом в качестве строки. Интересный класс является CButton. Когда объект сконструирован, на экране появляется прямоугольник с названием кнопки внутри.
% CButton ctor { // new(CButton(), "text", row, col) struct CButton * self = super_ctor(CButton(), _self, app);
self -> button = new(Button(), va_arg(* app, const char *)); self -> y = va_arg(* app, int); self -> x = va_arg(* app, int);
makeWindow(self, 3, strlen(text(self -> button)) + 4, self -> y, self -> x); addStr(self, 1, 2, text(self -> button)); crtBox(self); return self; }
Окно создаётся достаточно большим для текста, окружённого пробелами и фреймом. wire() должен быть перезаписан так, чтобы внутренний объект "кнопка" button получил соединение:
% CButton wire { %casts wire(to, self —> button); }
Наконец, CButton_gate() передаёт текстовые события непосредственно внутренней кнопке. Для события "позиция" мы проверяем, находится ли курсор в нашем прямоугольнике:
% CButton gate { %casts if (kind(item) == 1) { // если kind == 1, это событие щелчок int * v = data(item); // данные в массиве [x, y]
if (v[0] >= self —> x && v[0] < self —> x + cols(self) && v[1] >= self —> y && v[1] < self —> y + rows(self)) return gate(self —> button, 0); return reject; } return gate(self —> button, item); }
Если да, мы отправляем нулевой указатель во внутреннюю кнопку, которая отвечает, отправляя дальше свой текст. И снова мы можем проверить новые классы с помощью простой программой cbutton, прежде чем соберём приложение калькулятора целиком.
int main () { void * crt = new(Crt()); void * lineOut = new(CLineOut(), 5, 10, 40); void * button = new(CButton(), "a", 10, 40);
makeWindow(crt, 0, 0, 0, 0); /* весь экран */ gate(lineOut, "hello, world");
wire(lineOut, button), wire(button, crt); gate(crt, 0); /* главный цикл */
return 0; }
Эта программа выводит на экран hello, world в пятой строке и маленькую кнопку с меткой a вблизи середины нашего окна в терминале. Если мы переместим курсор на кнопку и нажмём return, или если нажмём a, изображение изменится и будет показана a. cbutton завершается, если прерывается или если мы вводим control-D. Как только это заработает, наш калькулятор тоже будет работать. Просто у него больше кнопок и микросхема для вычислений:
int main () { void * calc = new(Calc()); void * crt = new(Crt()); void * lineOut = new(CLineOut(), 1, 1, 12); void * mux = new(Mux());
static const struct tbl { const char * nm; int y, x; } tbl [] = { "C", 0, 15, "1", 3, 0, "2", 3, 5, "3", 3, 10, "+", 3, 15, "4", 6, 0, "5", 6, 5, "6", 6, 10, "-", 6, 15, "7", 9, 0, "8", 9, 5, "9", 9, 10, "*", 9, 15, "Q", 12, 0, "0", 12, 5, "=", 12, 10, "/", 12, 15, 0 }; const struct tbl * tp;
makeWindow(crt, 0, 0, 0, 0); wire(lineOut, calc); wire(mux, crt);
for (tp = tbl; tp -> nm; ++ tp) { void * o = new(CButton(), tp -> nm, tp -> y, tp -> x);
wire(calc, o), wire(o, mux); }
gate(crt, 0); return 0; }
Это решение очень похоже на последнее. Объект CButton нуждается в координатах; поэтому мы расширяем таблицу из которой создаются кнопки. Мы добавляем объект Crt, соединяем его с мультиплексором и позволяем ему выполнять основной цикл. ВыводыПовторное использование класса Calc не должно вызывать большого удивления. Это наименьшее, чего можно ожидать, неважно какая техника проектирования используется для приложения. Однако мы также снова использовали Button, а базовый класс Ic помог нам концентрироваться полностью на взаимодействии с curses, а не на адаптации микросхемы для вычислений к различному множеству типов ввода. Незначительный недостаток в том, что у нас нет чистого разделения между curses и классом Ic. Наша иерархия классов заставляет нас идти на компромисс и (более или менее) использовать в CButton два объекта Ic. Если следующий проект не использует класс Ic, мы не можем снова использовать этот код, разработанный, чтобы скрыть детали curses.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Предыдущая Содержание Следующая |