CodeNet / Языки программирования / C / C++ / Руководства и справочные материалы по C/C++ / C для профессиональных программистов
C для профессиональных программистов
ГЛАВА 9 -- 1 --
-------
ИНТЕРФЕЙС С "МЫШЬЮ"
-----------------------------------------------------------------
Наиболее популярным устройством ввода данных после
клавиатуры является "мышь" (mouse). Несмотря на то, что "мышь" и
сходные технологии, такие как "roller ball", получили широкое
распространение лишь в последнее время, популярность "мыши"
берет свое начало с момента выхода на рынок очередной разработки
фирмы Apple компьютера Apple Lisa, в котором впервые была
применена технология "мышь" для работы с пиктограммным (иконным)
интерфейсом операционной системы этого компьютера. Модель Apple
Lisa произвела форменный переворот в фирме Macintosh, которая
пошла по пути использования "мыши" и пиктограммного интерфейса в
своих программных продуктах. Перед выходом на рынок серии IBM
PS/2 "мышь", по существу, была третьим дополнением к РС. Тем не
менее уже при анонсировании системы IBM PS/2 сообщалось, что она
снабжена портом для подключения "мыши", и "мышь" занимает
значительное место среди РС.
Наилучшее использование "мыши" - предмет постоянных
дискуссий. Не все программисты (или пользователи) воспринимают
пиктограмный интерфейс. В связи с тем, что "мышь" впервые была
использована именно с пиктограммным интерфейсом, вопросы
использования "мыши" чаще всего сводятся именно к нему, а это в,
свою очередь, отталкивает от "мыши" большой круг программистов,
негативно относящихся к такому интерфейсу. Однако "мышь" ведь
может использоваться и без такого интерфейса. Например,
практически все согласны с тем, что "мышь" эффективно может
использоваться при работе с интерактивной графикой.
Некоторые модели манипуляторов типа "мышь", равно как и
выполняемые ими функции, могут значительно отличаться друг от
друга. Поэтому заметим, что все программы, приведенные в этой
главе, ориентированы на использование "мыши" фирмы Microsoft,
которая функционально идентична "мыши", используемой в моделях PS
/2. Для обеспечения интерфейса с "мышью" фирмы Microsoft вам
необходимо иметь по крайней мере саму "мышь", руководство
пользователя по программному обеспечению "мыши" (Microsoft Mouse
Programmer's Reference Guide) и поставляемый с этим руководством
диск. На этом диске расположена специальная библиотека с именем
MOUSE.LIB, выполняющая поддержку функционирования "мыши" на самом
нижнем уровне. Мы будем использовать функции из этой библиотеки в
качестве базовых функций при рассмотрении программ, предлагаемых
вам в этой главе. вы должны помнить, что ваш компилятор С должен
быть совместим с подключаемыми на этапе редактирования связей
подпрограммами из библиотеки подпрограмм поставляемых на диске
фирмой Microsoft. вам также надлежит помнить, что обязательно
необходимо наличие драйвера устройства MOUSE.SYS.
После беглого обзора основ использования манипулятора типа
"мышь" в этой главе вы получите информацию о том, как можно
модифицировать ранее рассмотренную программу "рисования" с тем,
чтобы она могла работать с "мышью" (с учетом того, что основные
концепции применения "мыши", а также разработанные в процессе
Глава IX -- 2 --
изложения материала подпрограммы, могут использоваться вами в
дальнейшем при создании различных конкретных приложений).
Глава IX -- 3 --
НЕКОТОРЫЕ НАЧАЛЬНЫЕ СВЕДЕНИЯ О "МЫШИ"
-----------------------------------------------------------------
Для того, чтобы использовать "мышь", прежде всего необходимо
инсталировать соответствующий драйвер. Для "мыши" фирмы Microsoft
в файл CONFIG.SYS должна быть добавлена следующая строка:
device = mouse.sys
Для инсталяции драйвера "мыши" фирмы IBM должна быть
запущена программа MOUSE.COM. С этой целью в файл AUTOEXEC.BAT
может быть добавлена строка вида:
mouse
После того, как драйвер размещен в системе, любые действия
по перемещению "мыши" или нажатию ее клавиш будут вызывать
генерацию прерывания 33Н. Процесс, управляющий "мышью", вызывает
прерывание, затем устанавливает значения соответствующих
внутренних переменных и продолжает свою работу. Вследствие того,
что прерывание генерируется лишь при изменении "мышью" своего
положения, неподвижная "мышь" не вызывает необходимости выделения
на нее ресурсов компьтера.
Точно так же, как с клавиатурой ассоциирован курсор, с
"мышью" также ассоциирован некий маркер на экране (называемый
далее указатель). Подпрограммы в библиотеке в поддержке "мыши"
фирмы Microsoft определяют по умолчанию следующую форму
указателя: в графических режимах - это стрелка, а в текстовых
режимах - прямоугольник в рамках габаритных размеров символа.
Аналогично курсору клавиатуры указатель-курсор "мыши" может
отображаться лишь тогда, когда "мышь" непосредственно
используется. В противном случае указатель-курсор "мыши" не
отображается на экране так, как будто интерфейса с "мышью" вообще
не существует.
Несмотря на то, что "мышь" и экран физически отделены друг
от друга, связь между ними все же имеется, так как драйвер
"мыши" автоматически отслеживает содержимое счетчиков,
индицирующих текущее состояние указателя-курсора "мыши" на
экране. При перемещении "мыши" курсор автоматически перемещается
на экране в точном соответствии с изменением координат "мыши".
Расстояние, на которое была перемещана "мышь", измеряется в
"мышиных" шагах. Один такой шаг равен 1/200 дюйма. Однако в
большинстве случаев знать на сколько переместилась "мышь" совсем
необязательно.
Глава IX -- 4 --
ВИРТУАЛИЗАЦИЯ И РЕАЛЬНЫЙ ЭКРАН
-----------------------------------------------------------------
Библиотека подпрограмм поддержки "мыши" фирмы Microsoft
работает с виртуальным экраном в виде массива точек растра
(массива из единиц минимального изображения, цвет и яркость
которых можно задать независимо от остального изображения),
который может отличаться от реального экрана. При перемещении
"мыши" счетчики местоположения курсора изменяют свое значение.
Перед отображением курсора виртуальные координаты курсора
преобразуются в координаты реального экрана. В видеорежимах 6,
14, 15 и 16 это преобразование осуществляется один к одному. В
режимах 4 и 5 не каждая точка виртуальной горизонтальной позиции
преобразуется в координты реального экрана, а через одну. вы
должны обратить внимание на этот факт, так как программа
рисования, к которой будет добавлен интерфейс с "мышью" и которая
будет рассматриваться в данной главе, работает именно в 4
графическом режиме.
Глава IX -- 5 --
БИБЛИОТЕКА ПОДДЕРЖКИ "МЫШИ"
-----------------------------------------------------------------
Подпрограммы внутри MOUSE.LIB ассоциируются у пользователя с
одной функцией, использующей в качестве входного аргумента число,
специфицирующее номер функции поддержки "мыши". (Этот процесс
некоторым образом сходен с процессом доступа к функциям DOS
посредством прерывания 21Н с указанием номера нужной функции).
Имя этой функции определяется моделью памяти, которая
используется при компиляции вашей программы. Используйте имя
cmouses() для модели маленькой памяти, cmousec() для модели
компактной памяти, cmousem() для модели средней памяти и
cmousel() для модели большой и самой большой (огромной) памяти.
(Заметим, что функция не может работать в модели самой маленькой
памяти). Пример, представленный в этой главе, использует модель
маленькой памяти, однако вы можете изменить тип модели памяти по
своему усмотрению.
Основным форматом функции cmouses() является:
void cmouses(fnum, arg2, arg3, arg4);
int *fnum, *arg2, *arg3, *arg4;
Как видно, fnum является номером функции поддержки "мыши",
которую необходимо вызвать. Другие параметры содержат информацию,
необходимую для спецификации функции. Обратите внимание, что
функции передаются не сами аргументы, а указатели на их значения.
Функция cmouses() возвращает результаты работы в виде параметров
и, следовательно, нуждается в их адресации. Фирма Microsoft
определила тридцать функций поддержки "мыши". Однако в программе
рисования будут использованы лишь пять из них. Ниже приведен
краткий обзор функций поддержки "мыши" фирмы Microsoft, которые
будут использованы нами в этой главе.
Привести в исходное состояние, выдать статус.
---------------------------------------------
Функция 0 приводит "мышь" в начальное состояние (сбрасывает
"мышь") Она перемещает курсор-указатель "мыши" в центр экрана и
"выключает " его. Функция возвращает номер нажатой клавиши
"мыши" в качестве значения arg2. После завершения функции fnum
принимает значение 0, если "мышь" и соответствующее программное
обеспечение не инсталированы, и -1 в противном случае.
Отобразить курсор
-----------------
Функция 1 отображает указатель-курсор "мыши". Она не
возвращает никакого значения.
Переместить курсор
------------------
Глава IX -- 6 --
Функция 2 перемещает курсор по экрану. Она не возвращает
никакого значения.
Выдать статус клавиши и позицию курсора
---------------------------------------
Функция 3 возвращает статус клавиши в arg2, виртуальную
горизонтальную позицию курсора в arg3, а виртуальную вертикальную
позицию курсора в arg4.
Статус клавиши кодируется в битах 0 и 1 байта arg2. Если
значение бита 0 установлено (равно 1), то была нажата левая
клавиша "мыши", если значение бита 1 установлено (равно 1), то
была нажата правая клавиша. Если значения обоих битов не
установлены (равны 0), то никакая клавиша нажата не была.
Установить координаты курсора
------------------------------
Функция 4 устанавливает месторасположение курсора "мыши".
Значение arg3 определяет горизонтальную позицию, а значение arg4
- вертикальную позицию курсора. вы всегда должны помнить, что
значения не должны выходить за пределы виртуального экрана,
который вы используете.
Индикация движения
------------------
Функция 11 возвращает число вертикальных и горизонтальных
"мышиных" шагов, которое "мышь" прошла со времени последнего
обращения к функции 11, другими словами - это изменение
вертикальных и горизонтальных координат "мыши". Функция также
сбрасывает внутренний регистр-счетчик в 0. Значение вертикального
счетчика возвращается в arg3, а горизонтального - в arg4. Это
позволяет, если "мышь" после последнего обращения к функции не
перемещалась на плоскости, получить значения как горизонтального,
так и вертикального счетчиков равными 0. Если значение одного из
счетчиков (или обоих) отлично от 0, то "мышь" перемещалась на
плоскости.
Положительное значение изменения вертикальных "мышиных"
шагов позволяет заключить, что "мышь" двигалась вниз.
Отрицательное же значение свидетельствует о движении "мыши"
вверх.
Положительное значение изменения числа горизонтальных шагов
свидетельствует о том, что "мышь" перемещалась вправо, а
отрицательное значение показывает на то, что "мышь" перемещалась
влево.
Глава IX -- 7 --
Глава IX -- 8 --
ФУНКЦИИ ПОДДЕРЖКИ "МЫШИ" ВЕРХНЕГО УРОВНЯ
-----------------------------------------------------------------
Используя функцию cmouses() вы можете создать набор функций
языка Си высокого уровня, которые значительно облегчат вам
программирование интерфейсов, ориентированных на использованием
"мыши". Посмотрите, как это делается.
Установка "мыши" в исходное состояние.
--------------------------------------
Функция, представленная ниже, mouse_reset() используется для
установки "мыши" в исходное состояние. Заметим, что функция
требует наличия соответствующего программного обеспечения и
аппаратной части компьютера, а также инсталяции двухклавишной
"мыши".
/* Установка "мыши" в исходное состояние */
void mouse_reset()
{
int fnum, arg2, arg3, arg4;
fnum = 0; /* Установка "мыши" в исходное состояние */
cmouses( &fnum, &arg2, &arg3, &arg4);
if(fnum!=-1) {
printf("Аппаратные или программные средства поддержки ");
printf("'мыши' не инсталированы");
exit(1);
}
if(arg2!=2) {
printf("Разрешено использование только двухклавишной 'мыши'");
exit(1);
}
}
Отображение и перемещение курсора "мыши".
-----------------------------------------
Взаимодополняющие друг друга функции cursor_on() и
cursor_off(), представленные ниже, позволяют активизировать и
деактивизировать изображение курсора на экране дисплея.
/* Включение курсора "мыши" */
void cursor_on()
{
int fnum;
fnum = 1; /* отобразить курсор */
cmouses( &fnum, &fnum, &fnum, &fnum);
}
Глава IX -- 9 --
/* Выключение курсора "мыши" */
void cursor_off()
{
int fnum;
fnum = 2; /* стереть курсор */
cmouses( &fnum, &fnum, &fnum, &fnum);
}
Какая из клавиш "мыши" была нажата?
-----------------------------------
Другой парой взаимодополняющих друг друга функций являются
функции rightb_pressed() и leftb_pressed(), представленные ниже.
Эти функции возвращают значение "истина", если нажата правая или
левая клавиши.
/* Возвращает значение "истина", если нажата правая клавиша,
и "ложь" в противном случае */
rightb_pressed()
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* Чтение позиции и статуса клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
return arg2 & 2;
}
/* Возвращает значение "истина", если нажата левая клавиша,
и "ложь" в противном случае */
leftb_pressed()
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* Чтение позиции и статуса клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
return arg2 & 1;
}
Как обнаружить перемещение "мыши"?
----------------------------------
Функция 11, которая возвращает изменение значения счетчика
"мыши" (в "мышиных" шагах) после последнего обращения к ней,
позволяет определить факт перемещения "мыши". Функция
mouse_motion(), представленная ниже, возвращает изменение
Глава IX -- 10 --
местоположения "мыши" в горизонтальном и вертикальном
направлениях в переменных, чьи указатели являются аргументами
функции. Если оба значения deltax и deltay равны 0, то факт
движения "мыши" не регистрируется.
/* Возвращает направление движения */
void mouse_motion(deltax, deltay)
char *deltax, *deltay;
{
int fnum, arg2, arg3, arg4;
fnum = 11; /* получить направление движения */
cmouses( &fnum, &arg2, &arg3, &arg4);
if(arg3>0) *deltax = RIGHT;
else if(arg3<0) *deltax = LEFT;
else *deltax = NOT_MOVED;
if(arg4>0) *deltay = DOWN;
else if(arg4<0) *deltay = UP;
else *deltay = NOT_MOVED;
}
Макросы RIGHT, LEFT, UP, DOWN и NOT_MOVED определены
следующим образом:
#define NOT_MOVED 0
#define RIGHT 1
#define LEFT 2
#define UP 3
#define DOWN 4
Чтение и установка позиции курсора.
-----------------------------------
Функции set_mouse_position() и mouse_position(),
представленные ниже, используются для установки чтения текущей
позиции курсора "мыши".
/* Установить координаты курсора "мыши" */
void set_mouse_position(x, y)
int x, y;
{
int fnum, arg2;
fnum = 4; /* установка позиции */
cmouses(&fnum, &arg2, &x, &y);
}
/* Возвращает координаты курсора "мыши" */
Глава IX -- 11 --
void mouse_position(x, y)
int *x, *y;
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* получить позицию и статус клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
*x = arg3;
*y = arg4;
}
Глава IX -- 12 --
ПРОСТЕЙШАЯ ДЕМОНСТРАЦИОННАЯ ПРОГРАММА
-----------------------------------------------------------------
Программа, представленная ниже, демонстрирует применение
функций поддержки "мыши" высокого уровня. вы можете сразу ввести
ее в ваш компьютер и попробовать ее в деле.
/* Интерфейс с "мышью" Microsoft/IBM */
#include "dos.h"
#define NOT_MOVED 0
#define RIGHT 1
#define LEFT 2
#define UP 3
#define DOWN 4
void mouse_position(), mode(), goto_xy(), mouse_motion();
void cursor_on(), cursor_off(), mouse_reset();
main(argc, argv)
int argc;
char *argv[];
{
char deltax, deltay, x, y;
if(argc!=2) {
printf(" Используйте формат: mouser <видеорежим> ");
exit(1);
}
mode(atoi(argv[1]));
mouse_reset(); /* инициализация "мыши" */
cursor_on(); /* "включение" курсора */
do {
goto_xy(0, 0);
if(leftb_presed()) printf("Левая клавиша");
if(rightb_pressed()) {
printf("Правая клавиша");
mouse_position(&x, &y);
printf("%d %d - ", x, y);
}
/* Отображение местоположения "мыши" */
mouse_motion(&deltax, &deltay);
if(deltax || deltay) {
printf("Перемещение");
switch(deltax) {
case NOT_MOVED: break;
case RIGHT: printf("Вправо");
break;
case LEFT: printf("Влево");
break;
}
Глава IX -- 13 --
switch(deltay) {
case NOT_MOVED: break;
case UP: printf("Вверх");
break;
case DOWN: printf("Вниз");
break;
}
}
/* Цикл выполняется пока обе клавиши нажаты одновременно */
} while(!(leftb_pressed() && rightb_pressed()));
mode(3);
}
/* Установка видеорежима */
void mode(mode_code)
int mode_code;
{
union REGS r;
r.h.al = mode_code;
r.h.ah = 0;
int86(0x10, &r, &r);
}
/* Пересылка курсора в позицию, специфицированную
координатами х и у */
void goto_xy(x, y)
int x, y;
{
union REGS r;
r.h.ah=2; /* функция адресации курсора */
r.h.dl = y; /* координаты столбца */
r.h.dh = x; /* координаты строки */
r.h.bh = 0; /* видеостраница */
int86(0x10, &r, &r);
}
/**********************************************************/
/* Функции, обеспечивающие интерфейс с "мышью" */
/**********************************************************/
/* Включение курсора "мыши" */
void cursor_on()
{
int fnum;
fnum = 1; /* отобразить курсор */
cmouses( &fnum, &fnum, &fnum, &fnum);
}
/* Выключение курсора "мыши" */
void cursor_off()
Глава IX -- 14 --
{
int fnum;
fnum = 2; /* стереть курсор */
cmouses( &fnum, &fnum, &fnum, &fnum);
}
/* Возвращает значение "истина", если нажата правая клавиша,
и "ложь" в противном случае */
rightb_pressed()
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* Чтение позиции и статуса клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
return arg2 & 2;
}
/* Возвращает значение "истина", если нажата левая клавиша,
и "ложь" в противном случае */
leftb_pressed()
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* Чтение позиции и статуса клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
return arg2 & 1;
}
/* Возвращает направление движения */
void mouse_motion(deltax, deltay)
char *deltax, *deltay;
{
int fnum, arg2, arg3, arg4;
fnum = 11; /* получить направление движения */
cmouses( &fnum, &arg2, &arg3, &arg4);
if(arg3>0) *deltax = RIGHT;
else if(arg3<0) *deltax = LEFT;
else *deltax = NOT_MOVED;
if(arg4>0) *deltay = DOWN;
else if(arg4<0) *deltay = UP;
else *deltay = NOT_MOVED;
}
/* Установить координаты курсора "мыши" */
Глава IX -- 15 --
void set_mouse_position(x, y)
int x, y;
{
int fnum, arg2;
fnum = 4; /* установка позиции */
cmouses(&fnum, &arg2, &x, &y);
}
/* Возвращает координаты курсора "мыши" */
void mouse_position(x, y)
int *x, *y;
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* получить позицию и статус клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
*x = arg3;
*y = arg4;
}
/* Установка "мыши" в исходное состояние */
void mouse_reset()
{
int fnum, arg2, arg3, arg4;
fnum = 0; /* Установка "мыши" в исходное состояние */
cmouses( &fnum, &arg2, &arg3, &arg4);
if(fnum!=-1) {
printf("Аппаратные или программные средства поддержки ");
printf("'мыши' не инсталированы");
exit(1);
}
if(arg2!=2) {
printf("Разрешено использование только двухклавишной ");
printf("'мыши'");
exit(1);
}
}
Перед использованием этой программы в командной строке DOS
специфицируйте видеорежим, в котором вы желаете работать.
Программа будет сообщать вам о направлении перемещения "мыши",а
также о нажатых клавишах "мыши". Вдобавок ко всему, нажатие
правой клавиши будет вызывать отображение текущей позиции Х, У.
Нажатие обеих клавиш вызовет завершение программы. Попробуйте
выполнить эту программу в различных видеорежиах и обратите
внимание на возникающие при этом эффекты.
Глава IX -- 16 --
Глава IX -- 17 --
ВВОД ИНФОРМАЦИИ С ПОМОЩЬЮ "МЫШИ" В ПРОГРАММЕ РИСОВАНИЯ
-----------------------------------------------------------------
Итак, вы теперь готовы к тому, чтобы разработать
подпрограммы, позволяющие с помощью "мыши" управлять программой
рисования. Интерфейс с "мышью" может быть добавлен в существующие
подпрограммы управления, что, естественно, будет более
предпочтительно, чем разработка новых подпрограмм или модификация
существующих.
Такой путь выгоден прежде всего тем, что функциональны
возможности клавиш управления курсором сохраняются на все 100
процентов, и пользователь в каждой конкретной ситуации может
выбрать наиболее подходящее устройство для ввода данных
(клавиатура или "мышь").
Прежде, чем "мышь" будет включена как устройство ввода
программу рисования, необходимо разработать две подпрограммы,
учитывающие специфику "мыши". Первая подпрограмма - wait_on()
позволяет реализовать процесс ожидания отпускания (освобождения)
специфицированной клавиши пользователем. Анализ подобного рода
имеет весьма большое значение, так как соответствующие прерывания
генерируются постоянно, пока клавиша не нажата. (Однако
невозможно обеспечить такое мгновенное нажатие на клавишу, в
результате которого сформировалось бы лишь одно прерывание). Во
многих подпрограммах наоборот важно избежать такой ситуации и
поэтому в них каждое нажатие на клавишу генерирует (точнее будет
сказать кажется, что генерирует) только одно прерывание за то
время, пока клавиша нажата. В соответствии с этим, ваша программа
должна обращаться к фунции wait_on(), представленной ниже,
непосредственно перед выполнением и после того, когда нажата
соответствующая клавиша.
/* Возвращает 1, если специфицированная клавиша не нажата */
void wait_on(button)
int button;
{
if(button== LEFTB)
while(leftb_pressed());
else
while(rightb_pressed());
}
Макросы LEFTB и RIGHTB, представленные ниже, используются
при обращении к wait_on().
#define LEFTB 1
#define RIGHTB 2
Второй необходимой вам функцией является mouse_menu(). Эта
функция отображает однострочное меню и позволяет пользователю
осуществлять выбор из него элементов путем перемещения "мыши" на
плоскости (и, соответственно, курсора "мыши" по экрану) и нажатия
любой клавиши "мыши". Эта функция может работать только в 4
Глава IX -- 18 --
графическом режиме. Функции передается двумерный массив символов,
который содержит элементы меню (которые может выбрать
пользователь), значение каждого элемента меню (его код),а также
координаты Х и У отображения меню на экране. Массив символов
определяет максимальную длину каждого элемента меню в 19
символов. Функция возвращает в качестве результата номер
выбранного пользователем элемента меню, начиная с 0, или -1, если
пользователь не выбрал ни один из элементов меню. Когда функция
начинает свою работу, то она вначале вычисляет длину в пикселах
(элементах растра) каждого элемента меню, после чего резервирует
пространство по начальной и конечной точке растра для каждого
элемента меню, одновременно запоминая эту информацию в массиве
len. (В четвертом графическом режиме каждый символ имеет высоту в
8 точек растра и ширину в 16 точек растра.) После этих
вычислений, функция переходит в состояние ожидаения прерывания от
клавиш "мыши". При этом осуществляется анализ нажата или нет
клавиша "мыши" в момент нахождения ее курсора в области меню, и,
если да, то в месте расположения какого элемента меню. Функция
mouse_menu() приведена ниже.
/* Отображает однострочное меню для "мыши" и возвращает
код выбранного пользователем элемента меню */
mouse_menu(count, item, x, y)
int count; /* количество элементов меню */
char item[][20]; /* элементы меню */
int x, y; /* позиции отображения */
{
int i, len[MENU_MAX][2], t;
int mousex, mousey;
goto_xy(x, y);
t = 0;
for(i=0; i<count; i++) {
printf("%s ", item[i]);
len[i][0] = t;
/* каждый символ имеет ширину в 16 точек растра */
len[i][1] = t + strlen(item[i])*16;
t = len[i][1] + 32; /* добавляется два пробела между
элементами меню */
}
/* ожидание выбора пользователем элемента меню */
do {
if(rightb_pressed() || leftb_pressed()) break;
} while(!kbhit());
/* ожидание нажатия клавиши */
while(rightb_pressed() || leftb_pressed());
/* получить текущую позицию курсора "мыши" */
mouse_position(&mousex, &mousey);
/* анализируется, находится ли курсор в пределах меню */
Глава IX -- 19 --
if(mousey>=0 && mousey<8) /* символ имеет высоту
8 точек растра */
for(i=0; i<count; i++) {
if(mousex>len[i][0] && mousex<len[i][1])
return i;
}
return i;
}
returtn -1; /* выбор из меню не осуществлялся */
}
Глава IX -- 20 --
ОСНОВНОЙ ЦИКЛ РАБОТЫ ПРОГРАММЫ
-----------------------------------------------------------------
В отличие от всей остальной части программы рисования,
фрагмент функции main() претерпел наиболее зеначительные
изменения. В функцию main() добавлены, в частности, функции,
обеспечивающие интерфейвс с мышью. Текст функции main приведен
ниже.
main()
{
char done=0;
mode(4); /* переключатель в 4 графический режим для
адаптеров CGA/EGA */
palette(0); /* палитра 0 */
mouse_reset(); /* инсталяция "мыши" */
xhairs(x, y); /* установить графический курсор */
set_mouse_position(y*2, x); /* установить начальную позицию
курсора "мыши" */
do {
/* просматривается, перемещалась ли "мышь" */
mouse_motion(&deltax, &deltay);
if(deltax || deltay) read_mouse();
/* проверка нажатия клавиши */
if(leftb_pressed() || rightb_pressed())
read_mouse();
if(kbhit()) {
done = read_kb();
/* перемещение "мыши" в соответствием с размещением
графического курсора */
set_mouse_position(y*2, x);
}
} while (!done);
mode(2);
}
Как вы можете видеть, функции main(), за исключением функции
done(), не оперируют локальными переменными. Вместо этого все
управляющие и вычисляемые переменные, необходимые для работы
программы, являются глобальными, что позволяет как функциям
работы с клавиатурой, так и функциям работы с "мышью", избавится
от длинного списка аргументов при обращении к ним, а также
упростить их использование в программе. Как вы видите, анализ
типа прерывания (от клавиатуры или от "мыши") осуществляется
внутри цикла. При поступлении прерывания от этих устройств
вызываются соответствующие функции. Заметьте, что курсор "мыши"
при этом не выключается. Взамен этого во время выполнения
рисующей части программы графический курсор используется для
индикации текущей позиции на экране. Курсор "мыши" активизируется
лишь при работе с однострочным меню.
Глава IX -- 21 --
Функция read_kb(), которая обеспечивает процесс ввода с
клавиатуры, представлена ниже. В основном она ничем не отличается
от оригинальной версии и выполняет довольно значительные по своей
роли функции.
/* Чтение и обработка команд, вводимых с клавиатуры */
read_kb()
{
union k{
char c[2];
int i;
} key;
key.i = bioskey(0);
xhairs(x, y); /* стереть графический курсор */
if(!key.c[0]) switch(key.c[1]){
case 75: /* влево */
if(on_flag) line(x, y, x, y-inc, cc);
y -= inc;
break;
case 77: /* вправо */
if(on_flag) line(x, y, x, y+inc, cc);
y += inc;
break;
case 72: /* вверх */
if(on_flag) line(x, y, x-inc, y, cc);
x -= inc;
break;
case 80: /* вниз */
if(on_flag) line(x, y, x+inc, y, cc);
x += inc;
break;
case 71: /* влево вверх */
if(on_flag) line(x, y, x-inc, y-inc, cc);
y -= inc; x -= inc;
break;
case 73: /* вправо вверх */
if(on_flag) line(x, y, x-inc, y+inc, cc);
y += inc; x -= inc;
break;
case 79: /* влево вниз */
if(on_flag) line(x, y, x+inc, y-inc, cc);
y -= inc; x += inc;
break;
case 81: /* вправо вниз */
if(on_flag) line(x, y, x+inc, y+inc, cc);
y += inc; x += inc;
break;
case 59: inc = 1; /* F1 - уменьшить скорость */
break;
case 60: inc = 5; /* F2 - увеличить скорость */
break;
Глава IX -- 22 --
}
else switch(tolower(key.c[0])) {
case 'o': on_flag = !on_flag; /* переключатеоль
шаблона цвета */
break;
case '1': cc = 1; /* цвет 1 */
break;
case '2': cc = 2; /* цвет 2 */
break;
case '3': cc = 3; /* цвет 3 */
break;
case '0': cc = 0; /* цвет 0 */
break;
case 'b': box(startx, starty, endx, endy, cc);
break;
case 'f': fill_box(startx, starty, endx, endy, cc);
break;
case 'l': line(startx, starty, endx, endy, cc);
break;
case 'c': circle(startx, starty, endy-starty, cc);
break;
case 'h': fill_circle(startx, starty, endy-starty, cc);
break;
case 's': save_pic();
break;
case 'r': load_pic();
break;
case 'm': /* переместить область */
move(startx, starty, endx, endy, x, y);
break;
case 'x': /* копировать область */
copy(startx, starty, endx, endy, x, y);
break;
case 'd': /* определить объект для вращения */
sides = define_object(object, x, y);
break;
case 'a': /* вращать объект */
rotate_object(object, 0.05, x, y, sides);
break;
case '\r': /* установить конечную точку для линии,
окружности или прямоугольника */
if(first_point) {
startx = x; starty = y;
}
else {
endx = x; endy = y;
}
first_point = !first_point;
break;
case 'p': pal_num = pal_num==1 ? 2:1;
palette(pal_num);
}
/* восстановить изображение графического курсора */
xhairs(x, y);
Глава IX -- 23 --
if(tolowel(key.c[0])=='q') return 1;
return 0;
}
Функция read_mouse(), которая обрабатывает ввод от "мыши",
представлена ниже.
/* Чтение и обработка команд, вводимых с помощью "мыши" */
read_mouse()
{
int oldx, oldy;
int choice;
oldx = x; oldy = y;
xhairs(x, y); /* стереть с текущей позиции */
/* нажаты обе клавиши для активизации меню */
if(rightb_pressed() && leftb_pressed()) {
choice = menu(); /* получить результат выбора
из меню */
switch(choice) {
case 0: box(startx, starty, endx, endy, cc);
break;
case 1: circle(startx, starty, endy-starty, cc);
break;
case 2: line(startx, starty, endx, endy, cc);
break;
case 3: fill_box(startx, starty, endx, endy, cc);
break;
case 4: fill_circle(startx, starty, endy-starty, cc);
break;
}
}
/* правая клавиша определяет конечную точку фигуры */
else if(rightb_pressed()) {
if(first_point) {
startx = x; starty = y;
}
else {
endx = x; endy = y;
}
first_point = !first_point;
wait_on(RIGHTB); /* ожидание освобождения клавиши */
}
if(deltax || deltay) {
mouse_position(&y, &x);
y = y / 2; /* нормализация координат виртуального
экрана */
/* нажмите левую клавишу для рисования */
Глава IX -- 24 --
if(leftb_pressed()) mouse_on_flag = 1;
else mouse_on_flag = 0;
if(mouse_on_flag) line(oldx, oldy, x, y, cc);
}
/* восстановить изображение графического курсора */
xhairs(x, y);
}
Функция read_mouse() работает следующим образом. Во-первых,
анализируется факт одновременного нажатия двух клавиш. Если факт
имеет место, то путем обращения к функции menu() активизируется
меню, которое устанавливается и управляется путем обращения к
mouse_menu(). Если пользователь осуществил выбор одного из
элементов меню, то инициируется соответствующее действие. Так как
в состав текущего меню входят различные фигуры, из которых
складывается изображение, - прямоугольники, окружности, линии и
действия, позволяющие, например, закрашивать эти фигуры, то с
помощью "мыши" вы можете выбрать один из этих элементов будущего
рисунка или действие. (вы можете также включить в меню средства,
позволяющие пользователю выбирать цвета и устанавливать палитру).
Правая клавиша используется для определения конечной точки
линий, прямоугольников и окружностей, точно так же, как клавиша
<ВВОД> используется для этих же целей при работе с клавиатурой.
Установите "мышь" в первой конечной точке объекта и нажмите
правую клавишу. Затем переместите "мышь" во вторую конечную точку
и опять нажмите клавишу. Так вы получите изображение интересующей
вас фигуры.
Когда левая клавиша не нажата, "мышь" может перемещаться по
экрану, не оставляя за собой никакого следа. Это происходит
потому, что по умолчанию перо "мыши" считается поднятым. Для
того, чтобы получить возможность "писать мышью" (опустить перо),
необходимо нажать левую клавишу. Пока клавиша нажата, перо
считается опущенным.
В конце концов, если "мышь" изменила свои координаты, будут
обновлены значения счетчиков X и Y.
Функция menu(), представленная ниже, использует для
установки и управления меню функцию mouse_menu(), описанную
ранее.
/* Отображение меню */
menu()
{
register int i, j;
char far *ptr = (char far *) 0xB8000000; /* Указатель на
CGA-память */
char far *temp;
unsigned char buf[14][80]; /* для хранения содержимого
экрана */
int x, y, choice;
char items[][20] = {
Глава IX -- 25 --
"BOX",
"CIRCLE",
"LINE",
"FILL BOX",
"FILL CIRCLE"
};
temp = ptr;
/* сохранение верхней части текущего экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
buf[i][j] = *temp; /* четный байт */
buf[i][j+1] = *(temp+8152); /* нечетный байт */
*temp = 0; *(temp+8152) = 0; /* очистка верхней
части экрана */
temp++;
}
goto_xy(0, 0);
/* ожидание, которое будет прервано в результате нажатия на
клавишу */
while(rightb_pressed() || leftb_pressed());
cursor_on();
choice = mouse_menu(5, items, 0, 0);
cursor_off();
temp = ptr;
/* восстановление изображения верхней части экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
*temp = buf[i][j];
*(temp+8152) = buf[i][j+1];
temp++;
}
return choice;
}
В процессе работы функция прежде всего сохраняет изображение
верхней части текущего экрана, не ожидая нажатия какой-либо
клавиши. Затем следует "включение" курсора "мыши" и обращение к
функции mouse_menu(). После передачи управления от функции mouse_
menu() курсор включается, восстанавливается предыдущее
изображение верхней части экрана, и функция возвращает
соответствующий код выбранного элемента меню.
Глава IX -- 26 --
ОПРЕДЕЛЕНИЕ ОБЪЕКТОВ С ПОМОЩЬЮ "МЫШИ"
-----------------------------------------------------------------
Оригинальная версия программы рисования допускает
использование объектов, определяемых с помощью функции
define_object(), путем специфицирования конечных точек каждого
сегмента линии. Каждый определенный ранее объект может быть
подвергнут вращению. Версия функции define_object(),
представленная ниже, также позволяет с помощью "мыши" указывать
конечные точки (габариты) объектов.
/* Определение объекта по его габаритным точкам с помощью
клавиатуры или "мыши" */
define_object(ob, x, y)
double ob[][4];
int x, y;
{
union k{
char c[2];
int i;
} key;
register int i, j;
char far *ptr = (char far *) 0xB8000000; /* Указатель на
CGA-память */
char far *temp;
unsigned char buf[14][80];
int sides=0;
int deltax, deltay, oldx, oldy;
temp = ptr;
/* сохранение верхней части текущего экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
buf[i][j] = *temp; /* четный байт */
buf[i][j+1] = *(temp+8152); /* нечетный байт */
*temp = 0; *(temp+8152) = 0; /* очистка верхней
части экрана */
temp++;
}
i = 0;
key.i = 0;
xhairs(x, y);
do {
goto_xy(0, 0);
printf("Определите сторону %d", sides+1);
if(i==0) printf("Укажите первую габаритную точку");
else printf("Укажите вторую габаритную точку");
do {
/************** Добавочная часть для мыши. *******************/
/* Просматривается, если "мышь" перемещается */
Глава IX -- 27 --
mouse_motion(&deltax, &deltay);
/* Используйте левую клавишу для определения точки*/
if(leftb_pressed()) {
/* стирание графического курсора */
xhairs(x, y);
/* запоминание координат точки */
ob[sides][i++] = (double) x;
ob[sides][i++] = (double) y;
if(i==4) {
i = 0;
sides++;
}
break;
}
} while(!kbhit() && !deltax && ! deltay);
if(leftb_pressed()) wait_on(LEFTB);
if(deltax || deltay) {
/* если "мышь" переместилась, то пересчет координат*/
oldx = x; oldy = y;
mouse_position(&y, &x);
y = y / 2; /* нормализация коодинат виртуального
экрана*/
/* стирание графического курсора */
xhairs(oldx, oldy);
}
/********** Конец добавочной части для "мыши" *****************/
else if(kbhit()) {
key.i = bioskey(0);
/* изображение графического курсора */
xhairs(x, y);
if(key.c[0]==13) {
/* используйте <ВВОД> для определения точки*/
ob[sides][i++] = (double) x;
ob[sides][i++] = (double) y;
if(i==4) {
i = 0;
sides++;
}
}
/* если клавиши управления курсором, то перемещение
графического курсора */
if(!key.c[0]) switch(key.c[1]) {
case 75: /* влево */
y-=1;
break;
case 77: /* вправо */
y+=1;
break;
case 72: /* вверх */
x-=1;
break;
Глава IX -- 28 --
case 80: /* вниз */
x+=1;
break;
case 71: /* влево вверх */
x-=1; y-=1;
break;
case 73: /* вправо вверх */
x-=1; y+=1;
break;
case 79: /* влево вниз */
x+=1; y-=1;
break;
case 81: /* вправо вниз */
x+=1; y+=1;
break;
}
}
if(key.c[1] != 59) xhairs(x, y);
} while(key.c[1] != 59); /* нажмите F1 для остановки */
temp = ptr;
/* востановление изображения в верхней части экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
*temp = buf[i][j];
*(temp+8152) = buf[i][j+1];
temp++;
}
return sides;
}
Как вы можете видеть, левая клавиша используется для
указания габаритных точек. Движение "мыши" кодируется точно так
же, как и в функции read_mouse(). За исключением того, что в
функцию добавлен фрагмент исходного текста для поддержки ввода
информации с помощью "мыши", оригинальная версия функции
define_object() больше никаких изменений не претерпела. Для
указания очередной габаритной точки переместите "мышь" в нужную
позицию и нажмите клавишу.
Глава IX -- 29 --
ПОЛНЫЙ ТЕКСТ МОДИФИЦИРОВАННОЙ ПРОГРАММЫ РИСОВАНИЯ
-----------------------------------------------------------------
Ниже приведен исходный текст программы рисования с учетом
всех изменений и дополнений, обсужденных выше.
/* Эта версия программы рисования для адаптеров CGA/EGA
позволяет использовать "мышь" фирмы Microsoft/IBM,
а также альтернативное устройство ввода (клавиатуру)
*/
#define NUM_SIDES 20 /* Максимальное число сторон фигуры.
По желанию можно увеличить */
#define NOT_MOVED 0
#define RIGHT 1
#define LEFT 2
#define UP 3
#define DOWN 4
#define LEFTB 1
#define RIGHTB 2
#define MENU_MAX 20 /* Максимальное число элементов меню */
#include "dos.h"
#include "stdio.h"
#include "math.h"
void mode(), line(), box(), fill_box();
void mempoint(), palette(), xhairs();
void circle(), plot_circle(), fill_circle();
void rotate_point(), rotate_object(), goto_xy();
void display_object(), copy(), move();
void save_pic(), load_pic();
void set_mouse_position(), mouse_position(), mouse_motion();
void cursor_on(), cursor_off(), wait_on(), mouse_reset();
unsigned char read_point();
/* Это массив предназначен для хранения координат динамически
определяемой фигуры */
double object[NUM_SIDES][4];
double asp_ratio; /* коэффициент сжатия окружностей */
int x=10, y=10; /* текущая позиция экрана */
int cc=2; /* текущий цвет */
int on_flag=1, mouse_on_flag=0; /* перо опущено или
поднято */
int pal_num=1; /* номер палитры */
/* Габаритные точки, определяющие линию, окружность
или прямоугольник */
int startx=0, starty=0, endx=0, endy=0, first_point=1;
int inc=1; /* приращение движения */
Глава IX -- 30 --
int sides=0; /* номер стороны определяемой фигуры */
int deltax, deltay; /* "мышь" изменяет данные в
индицированной позиции */
main()
{
char done=0;
mode(4); /* переключатель в 4 графический режим для
адаптеров CGA/EGA */
palette(0); /* палитра 0 */
mouse_reset(); /* инсталяция "мыши" */
xhairs(x, y); /* установить графический курсор */
set_mouse_position(y*2, x); /* установить начальную позицию
курсора "мыши" */
do {
/* просматривается, перемещалась ли "мышь" */
mouse_motion(&deltax, &deltay);
if(deltax || deltay) read_mouse();
/* проверка нажатия клавиши */
if(leftb_pressed() || rightb_pressed())
read_mouse();
if(kbhit()) {
done = read_kb();
/* перемещение "мыши" в соответствии с размещением
графического курсора */
set_mouse_position(y*2, x);
}
} while (!done);
mode(2);
}
/* Чтение и обработка команд, вводимых с помощью "мыши" */
read_mouse()
{
int oldx, oldy;
int choice;
oldx = x; oldy = y;
xhairs(x, y); /* стереть с текущей позиции */
/* нажаты обе клавиши для активизации меню */
if(rightb_pressed() && leftb_pressed()) {
choice = menu(); /* получить результат выбора
из меню */
switch(choice) {
case 0: box(startx, starty, endx, endy, cc);
break;
case 1: circle(startx, starty, endy-starty, cc);
break;
case 2: line(startx, starty, endx, endy, cc);
Глава IX -- 31 --
break;
case 3: fill_box(startx, starty, endx, endy, cc);
break;
case 4: fill_circle(startx, starty, endy-starty, cc);
break;
}
}
/* правая клавиша определяет конечную точку фигуры */
else if(rightb_pressed()) {
if(first_point) {
startx = x; starty = y;
}
else {
endx = x; endy = y;
}
first_point = !first_point;
wait_on(RIGHTB); /* ожидание освобождения клавиши */
}
if(deltax || deltay) {
mouse_position(&y, &x);
y = y / 2; /* нормализация координат виртуального
экрана */
/* нажмите левую клавишу для рисования */
if(leftb_pressed()) mouse_on_flag = 1;
else mouse_on_flag = 0;
if(mouse_on_flag) line(oldx, oldy, x, y, cc);
}
/* восстановить изображение графического курсора */
xhairs(x, y);
}
/* Чтение и обработка команд, вводимых с клавиатуры */
read_kb()
{
union k{
char c[2];
int i;
} key;
key.i = bioskey(0);
xhairs(x, y); /* стереть графический курсор */
if(!key.c[0]) switch(key.c[1]){
case 75: /* влево */
if(on_flag) line(x, y, x, y-inc, cc);
y -= inc;
break;
case 77: /* вправо */
if(on_flag) line(x, y, x, y+inc, cc);
y += inc;
break;
case 72: /* вверх */
if(on_flag) line(x, y, x-inc, y, cc);
Глава IX -- 32 --
x -= inc;
break;
case 80: /* вниз */
if(on_flag) line(x, y, x+inc, y, cc);
x += inc;
break;
case 71: /* влево вверх */
if(on_flag) line(x, y, x-inc, y-inc, cc);
y -= inc; x -= inc;
break;
case 73: /* вправо вверх */
if(on_flag) line(x, y, x-inc, y+inc, cc);
y += inc; x -= inc;
break;
case 79: /* влево вниз */
if(on_flag) line(x, y, x+inc, y-inc, cc);
y -= inc; x += inc;
break;
case 81: /* вправо вниз */
if(on_flag) line(x, y, x+inc, y+inc, cc);
y += inc; x += inc;
break;
case 59: inc = 1; /* F1 - уменьшить скорость */
break;
case 60: inc = 5; /* F2 - увеличить скорость */
break;
}
else switch(tolower(key.c[0])) {
case 'o': on_flag = !on_flag; /* переключатель
шаблона цвета */
break;
case '1': cc = 1; /* цвет 1 */
break;
case '2': cc = 2; /* цвет 2 */
break;
case '3': cc = 3; /* цвет 3 */
break;
case '0': cc = 0; /* цвет 0 */
break;
case 'b': box(startx, starty, endx, endy, cc);
break;
case 'f': fill_box(startx, starty, endx, endy, cc);
break;
case 'l': line(startx, starty, endx, endy, cc);
break;
case 'c': circle(startx, starty, endy-starty, cc);
break;
case 'h': fill_circle(startx, starty, endy-starty, cc);
break;
case 's': save_pic();
break;
case 'r': load_pic();
break;
case 'm': /* переместить область */
Глава IX -- 33 --
move(startx, starty, endx, endy, x, y);
break;
case 'x': /* копировать область */
copy(startx, starty, endx, endy, x, y);
break;
case 'd': /* определить объект для вращения */
sides = define_object(object, x, y);
break;
case 'a': /* вращать объект */
rotate_object(object, 0.05, x, y, sides);
break;
case '\r': /* установить конечную точку для линии,
окружности или прямоугольника */
if(first_point) {
startx = x; starty = y;
}
else {
endx = x; endy = y;
}
first_point = !first_point;
break;
case 'p': pal_num = pal_num==1 ? 2:1;
palette(pal_num);
}
/* восстановить изображение графического курсора */
xhairs(x, y);
if(tolowel(key.c[0])=='q') return 1;
return 0;
}
/* Установить палитру */
void palette(pnum)
int pnum;
{
union REGS r;
r.h.bh = 1; /* кодирование 4 графического режима */
r.h.bl = pnum;
r.h.ah = 11; /* функцкия установления палитры */
int86(0x10, &r, &r);
}
/* Установить видеорежим */
void mode(mode_code)
int mode_code;
{
union REGS r;
r.h.al = mode_code;
r.h.ah = 0;
int86(0x10, &r, &r);
}
/* Рисование прямоугольника */
Глава IX -- 34 --
void box(startx, starty, endx, endy, color_code)
int startx, starty, endx, endy, color_code;
{
line(startx, starty, endx, starty, color_code);
line(startx, starty, startx, endy, color_code);
line(startx, endy, endx, endy, color_code);
line(endx, starty, endx, endy, color_code);
}
/* Рисование линии в специфицированном цвете с использованием
основного алгоритма Брезенхама */
void line(startx, starty, endx, endy, color)
int startx, starty, endx, endy, color;
{
register int t, distance;
int x=0, y=0, delta_x, delta_y;
int incx, incy;
/* определение расстояния в обоих направлениях */
delta_x = endx-startx;
delta_y = endy-starty;
/* Вычисление направления приращения. Приращение
вычисляется относительно 0 как для горизонтальной,
так и для вертикальной линии
*/
if(delta_x>0) incx = 1;
else if(delta_x==0) incx = 0;
else incx=-1;
if(delta_y>0) incy = 1;
else if(delta_y==0) incy = 0;
else incy=-1;
/* Определяется, какое расстояние больше */
delta_x = abs(delta_x);
delta_y = abs(delta_y);
if(delta_x>delta_y) distance = delta_x;
else distance = delta_y;
/* Вычерчиывние линии */
for(t=0; t<=distance+1; t++) {
mempoint(startx, starty, color);
x+=delta_x;
y+=delta_y;
if(x>distance) {
x-=distance;
startx+=incx;
}
if(y>distance) {
y-=distance;
starty+=incy;
}
Глава IX -- 35 --
}
}
/* Закрашивание прямоугольника специфицированным цветом */
void fill_box(startx, starty, endx, endy, color_code)
int startx, starty, endx, endy, color_code;
{
register int i, begin, end;
begin = startx<endx ? startx : endx;
end = startx>endx ? startx : endx;
for(i=begin; i<=end; i++)
line(i, starty, i, endy, color_code);
}
/* Рисование окружности с использованием целочисленного алгоритма
Брезенхама */
void circle(x_center, y_center, radius, color_code)
int x_center, y_center, radius, color_code;
{
register int x, y, delta;
asp_ratio = 1.0; /* Если Вас не удовлетворяют предлагаемые
пропорции фигуры, измените значение этой
переменной */
y = radius;
delta = 3 - 2 * radius;
for(x=0; x<y; ) {
plot_circle(x, y, x_center, y_center, color_code);
if(delta < 0)
delta += 4*x+6;
else {
delta += 4*(x-y)+10;
y--;
}
x++;
}
x=y;
if(y) plot_circle(x, y, x_center, y_center, color_code);
}
/* Непосредственное вычерчивание окружности по определенным
пользователем точкам */
void plot_circle(x, y, x_center, y_center, color_code)
int x, y, x_center, y_center, color_code;
{
int startx, endx, x1, starty, endy, y1;
starty = y*asp_ratio;
endy = (y+1)*asp_ratio;
Глава IX -- 36 --
startx = x*asp_ratio;
endx = (x+1)*asp_ratio;
for(x1=startx; x1<endx; ++x1) {
mempoint(x1+x_center, y+y_center, color_code);
mempoint(x1+x_center, y_center-y, color_code);
mempoint(x_center-x1, y_center-y, color_code);
mempoint(x_center-x1, y+y_center, color_code);
}
for(y1=starty; y1<endy; ++y1) {
mempoint(y1+x_center, x+y_center, color_code);
mempoint(y1+x_center, y_center-x, color_code);
mempoint(x_center-y1, y_center-x, color_code);
mempoint(x_center-y1, x+y_center, color_code);
}
}
/* Закрашивание окружности путем циклического обращения к функции
circle() с меньшим радиусом */
void fill_circle(x, y, r, c)
int x, y, r, c;
{
while(r) {
circle(x, y, r, c);
r--;
}
}
/* Отображение графического курсора */
void xhairs(x, y)
int x, y;
{
line(x-4, y, x+3, y, 1 | 128);
line(x, y+4, x, y-3, 1 | 128);
}
/* Запись точки непосредственно в CGA/EGA */
void mempoint(x, y, color_code)
int x, y, color_code;
{
union mask {
char c[2];
int i;
} bit_mask;
int i, index, bit_position;
unsigned char t;
char xor; /* xor - цвет изображения записывается или
перезаписывается */
char far *ptr = (char far *) 0xB8000000; /* Указатель
CGA памяти */
Глава IX -- 37 --
bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном
коде */
/* проверка значений для 4 режима */
if(x<0 || x>199 || y<0 || y>319) return;
xor= color_code & 128; /* просматривается, если режим
установлен */
color_code = color_code & 127; /* маска старшего бита */
/* установка маски бита и кода цвета для правой ячейки
памяти */
bit_position = y%4;
color_code<<=2*(3-bit_position);
bit_mask.i>>=2*bit_position;
/* поиск байта для корректировки в видеопамяти */
index = x*40 +(y >> 2);
if(x % 2) index += 8152; /* если дополнительно
использовался 2-й сегмент
памяти */
/* запись цвета */
if(!xor) { /* режим перезаписи */
t = *(ptr+index) & bit_mask.c[0];
*(ptr+index) = t | color_code;
}
else { /* режим записи цвета */
t = *(ptr+index) | (char)0;
*(ptr+index) = t ^ color_code;
}
}
/* Непосредственное чтение байта из видеопамяти CGA/EGA
в режиме 4 */
unsigned char read_point(x, y)
int x, y;
{
union mask {
char c[2];
int i;
} bit_mask;
int i, index, bit_position;
unsigned char t;
char xor; /* xor - цвет изображения записывается или
перезаписывается */
char far *ptr = (char far *) 0xB8000000; /* Указатель
CGA памяти */
bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном коде */
/* проверка значений для 4 режима */
if(x<0 || x>199 || y<0 || y>319) return;
Глава IX -- 38 --
/* установка маски бита и кода цвета для правой ячейки
памяти */
bit_position = y%4;
bit_mask.i<<=2*(3-bit_position);
/* поиск нужного байта в видеопамяти */
index = x*40 +(y >> 2);
if(x % 2) index += 8152; /* если дополнительно
использовался 2-й блок
памяти */
/* чтение цвета */
t = *(ptr+index) & bit_mask.c[0];
t >>=2*(3-bit_position);
return t;
}
/* сохранение видео изображения на экране */
void save_pic()
{
char fname[80];
FILE *fp;
register int i, j;
char far *ptr = (char far *) 0xB8000000; /* Указатель
CGA памяти */
char far *temp;
unsigned char buf[14][80]; /* буфер содержимого экрана */
temp = ptr;
/* сохранение верхней части текущего экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
buf[i][j] = *temp; /* четный байт */
buf[i][j+1] = *(temp+8152); /* нечетный байт */
*temp = 0; *(temp+8152) = 0; /* очистка верхней
части экрана */
temp++;
}
goto_xy(0, 0);
printf("Имя файла : ");
gets(fname);
if(!(fp=fopen(fname, "wb"))) {
printf("Файл не может быть открыт\n");
return;
}
temp = ptr;
/* восстановление изображения верхней части экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
Глава IX -- 39 --
*temp = buf[i][j];
*(temp+8152) = buf[i][j+1];
temp++;
}
/* запись изображения в файл */
for(i=0; i<8152; i++) {
putc(*ptr, fp); /* четный байт */
putc(*(ptr+8152), fp); /* нечетный байт */
ptr++;
}
fclose(fp);
}
/* загрузка в память изображения из файла */
void load_pic()
{
char fname[80];
FILE *fp;
register int i, j;
char far *ptr = (char far *) 0xB8000000; /* Указатель
CGA памяти */
char far *temp;
unsigned char buf[14][80]; /* буфер содержимого экрана */
temp = ptr;
/* сохранение верхней части текущего экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
buf[i][j] = *temp; /* четный байт */
buf[i][j+1] = *(temp+8152); /* нечетный байт */
*temp = 0; *(temp+8152) = 0; /* очистка верхней
части экрана */
temp++;
}
goto_xy(0, 0);
printf("Имя файла : ");
gets(fname);
if(!(fp=fopen(fname, "rb"))) {
goto_xy(0, 0);
printf("Файл не может быть открыт\n");
temp = ptr;
/* восстановление изображения верхней части экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
*temp = buf[i][j];
*(temp+8152) = buf[i][j+1];
temp++;
}
return;
}
Глава IX -- 40 --
/* Загрузка изображения из файла */
for(i=0; i<8152; i++) {
*ptr = getc(fp); /* четный байт */
*(ptr+8152) = getc(fp); /* нечетный байт */
ptr++;
}
fclose(fp);
}
/* Перемещение курсора в специфицированную позицию */
void goto_xy(x, y)
int x, y;
{
union REGS r;
r.h.ah=2; /* функция адресации курсора */
r.h.dl = y; /* координаты столбца */
r.h.dh = x; /* координаты строки */
r.h.bh = 0; /* видеостраница */
int86(0x10, &r, &r);
}
/* Перемещение области в другое место */
void move(startx, starty, endx, endy, x, y)
int startx, starty; /* верхняя левая координата */
int endx, endy; /* нижняя правая координата места, куда будет
перемещена область */
int x, y; /* верхняя левая координата области, откуда будет
перемещено изображение*/
{
int i, j;
unsigned char c;
for(; startx<=endx; startx++, endx++)
for(i=starty, j=y; i<=endy; i++, j++) {
c = read_point(startx, i); /* чтение точки */
mempoint(startx, i, 0); /* стирание предыдущего
изображения */
mempoint(x, j, c); /* запись его в новое место */
}
}
/* Копировать область в другое место */
void copy (startx, starty, endx, endy, x, y)
int startx, starty; /* верхняя левая координата */
int endx, endy; /* нижняя правая координата места, куда будет
скопирована область */
int x, y; /* верхняя левая координата области, откуда будет
Глава IX -- 41 --
перемещено изображение */
{
int i, j;
unsigned char c;
for(; startx<=endx; startx++, endx++)
for(i=starty, j=y; i<=endy; i++, j++) {
c = read_point(startx, i); /* чтение точки */
mempoint(x, j, c); /* запись ее в новое место */
}
}
/* Вращение точки относительно специфицированной пользователем
начальной точки x_org и y_org на угол theta */
void rotate_point(theta, x, y, x_org, y_org)
double theta, *x, *y;
int x_org, y_org;
{
double tx, ty;
/* нормализация координат х и у исходной точки */
tx = *x - x_org;
ty = *y - y_org;
/* вращение */
*x = tx * cos(theta) - ty * sin(theta);
*y = tx * sin(theta) + ty * cos(theta);
/* возврат значений координат */
*x += x_org;
*y += y_org;
}
/* Вращение специфицированного объекта */
void rotate_object(ob, theta, x, y, sides)
double ob[][4]; /* определение объекта */
double theta; /* угол поворота в радианах */
int x, y; /* центр вращения */
int sides;
{
register int i, j;
double tempx, tempy; /* это вспомогательные переменные,
используемые при преобразовании типов */
char ch;
for(;;){
ch = getch(); /* просматривается, если указывается
направление врашения */
switch(tolower(ch)) {
case 'l': /* вращение против часовой стрелки */
theta = theta < 0 ? -theta : theta;
Глава IX -- 42 --
break;
case 'r': /* вращение по часовой стрелке */
theta = theta > 0 ? -theta : theta;
break;
default: return;
}
for(j=0; j<sides; j++) {
/* стирание предыдущей линии */
line((int) ob[j][0], (int) ob[j][1],
(int) ob[j][2], (int) ob[j][3], 0);
rotate_point(theta, &ob[j][0], &ob[j][1], x, y);
rotate_point(theta, &ob[j][2], &ob[j][3], x, y);
line((int) ob[j][0], (int) ob[j][1],
(int) ob[j][2], (int) ob[j][3], 2);
}
}
}
/* отображение объекта */
void display_object(ob, sides)
double ob[][4];
int sides;
{
register int i;
for(i=0; i<sides; i++)
line((int) ob[i][0], (int) ob[i][1],
(int) ob[i][2], (int) ob[i][3], 2);
}
/* Определение объекта по его габаритным точкам с помощью
клавиатуры или "мыши" */
define_object(ob, x, y)
double ob[][4];
int x, y;
{
union k{
char c[2];
int i;
} key;
register int i, j;
char far *ptr = (char far *) 0xB8000000; /* Указатель на
CGA-память */
char far *temp;
unsigned char buf[14][80];
int sides=0;
int deltax, deltay, oldx, oldy;
temp = ptr;
Глава IX -- 43 --
/* сохранение верхней части текущего экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
buf[i][j] = *temp; /* четный байт */
buf[i][j+1] = *(temp+8152); /* нечетный байт */
*temp = 0; *(temp+8152) = 0; /* очистка верхней
части экрана */
temp++;
}
i = 0;
key.i = 0;
xhairs(x, y);
do {
goto_xy(0, 0);
printf("Определите сторону %d", sides+1);
if(i==0) printf("Укажите первую габаритную точку");
else printf("Укажите вторую габаритную точку");
do {
/**************** Добавочная часть для мыши *******************/
/* Просматривается если "мышь" перемещается */
mouse_motion(&deltax, &deltay);
/* используйте левую клавишу для определения точки*/
if(leftb_pressed()) {
xhairs(x, y); /* стирание графического курсора */
/* запоминание координат точки */
ob[sides][i++] = (double) x;
ob[sides][i++] = (double) y;
if(i==4) {
i = 0;
sides++;
}
break;
}
} while(!kbhit() && !deltax && ! deltay);
if(leftb_pressed()) wait_on(LEFTB);
if(deltax || deltay) {
/* если "мышь" переместилась, то пересчет координат*/
oldx = x; oldy = y;
mouse_position(&y, &x);
y = y / 2; /* нормализация координат виртуального
экрана*/
/* стирание графического курсора */
xhairs(oldx, oldy);
}
/*********** Конец добавочной части для "мыши" *****************/
else if(kbhit()) {
key.i = bioskey(0);
/* изображение графического курсора */
xhairs(x, y);
if(key.c[0]==13) {
Глава IX -- 44 --
/* используйте <ВВОД> для определения точки*/
ob[sides][i++] = (double) x;
ob[sides][i++] = (double) y;
if(i==4) {
i = 0;
sides++;
}
}
/* если клавиши управления курсором, то
перемещение графического курсора */
if(!key.c[0]) switch(key.c[1]) {
case 75: /* влево */
y-=1;
break;
case 77: /* вправо */
y+=1;
break;
case 72: /* вверх */
x-=1;
break;
case 80: /* вниз */
x+=1;
break;
case 71: /* влево вверх */
x-=1; y-=1;
break;
case 73: /* вправо вверх */
x-=1; y+=1;
break;
case 79: /* влево вниз */
x+=1; y-=1;
break;
case 81: /* вправо вниз */
x+=1; y+=1;
break;
}
}
if(key.c[1] != 59) xhairs(x, y);
} while(key.c[1] != 59); /* нажмите F1 для остановки */
temp = ptr;
/* восстановление изображения в верхней части экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
*temp = buf[i][j];
*(temp+8152) = buf[i][j+1];
temp++;
}
return sides;
}
/* Отображение меню */
menu()
Глава IX -- 45 --
{
register int i, j;
char far *ptr = (char far *) 0xB8000000; /* Указатель на
CGA-память */
char far *temp;
unsigned char buf[14][80]; /* для хранения содержимого
экрана */
int x, y, choice;
char items[][20] = {
"BOX",
"CIRCLE",
"LINE",
"FILL BOX",
"FILL CIRCLE"
};
temp = ptr;
/* сохранение верхней части текущего экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
buf[i][j] = *temp; /* четный байт */
buf[i][j+1] = *(temp+8152); /* нечетный байт */
*temp = 0; *(temp+8152) = 0; /* очистка верхней
части экрана */
temp++;
}
goto_xy(0, 0);
/* ожидание, которое будет прервано в результате нажатия на
клавишу */
while(rightb_pressed() || leftb_pressed());
cursor_on();
choice = mouse_menu(5, items, 0, 0);
cursor_off();
temp = ptr;
/* восстановление изображения верхней части экрана */
for(i=0; i<14; i++)
for(j=0; j<80; j+=2) {
*temp = buf[i][j];
*(temp+8152) = buf[i][j+1];
temp++;
}
return choice;
}
/**********************************************************/
/* Функции, обеспечивающие интерфейс с "мышью" */
/**********************************************************/
/* Включение курсора "мыши" */
Глава IX -- 46 --
void cursor_on()
{
int fnum;
fnum = 1; /* отобразить курсор */
cmouses( &fnum, &fnum, &fnum, &fnum);
}
/* Выключение курсора "мыши" */
void cursor_off()
{
int fnum;
fnum = 2; /* стереть курсор */
cmouses( &fnum, &fnum, &fnum, &fnum);
}
/* Возвращает значение "истина", если нажата правая клавиша,
и "ложь" в противном случае */
rightb_pressed()
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* Чтение позиции и статуса клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
return arg2 & 2;
}
/* Возвращает значение "истина", если нажата левая клавиша,
и "ложь" в противном случае */
leftb_pressed()
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* Чтение позиции и статуса клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
return arg2 & 1;
}
/* Установить координаты курсора "мыши" */
void set_mouse_position(x, y)
int x, y;
{
int fnum, arg2;
fnum = 4; /* установка позиции */
Глава IX -- 47 --
cmouses(&fnum, &arg2, &x, &y);
}
/* Возвращает координаты курсора "мыши" */
void mouse_position(x, y)
int *x, *y;
{
int fnum, arg2, arg3, arg4;
fnum = 3; /* получить позицию и статус клавиши */
cmouses( &fnum, &arg2, &arg3, &arg4);
*x = arg3;
*y = arg4;
}
/* Возвращает направление движения */
void mouse_motion(deltax, deltay)
char *deltax, *deltay;
{
int fnum, arg2, arg3, arg4;
fnum = 11; /* получить направление движения */
cmouses( &fnum, &arg2, &arg3, &arg4);
if(arg3>0) *deltax = RIGHT;
else if(arg3<0) *deltax = LEFT;
else *deltax = NOT_MOVED;
if(arg4>0) *deltay = DOWN;
else if(arg4<0) *deltay = UP;
else *deltay = NOT_MOVED;
}
/* Отображает однострочное меню для "мыши" и возвращает
код выбранного пользователем элемента меню */
mouse_menu(count, item, x, y)
int count; /* количество элементов меню */
char item[][20]; /* элементы меню */
int x, y; /* позиции отображения */
{
int i, len[MENU_MAX][2], t;
int mousex, mousey;
goto_xy(x, y);
t = 0;
for(i=0; i<count; i++) {
printf("%s ", item[i]);
len[i][0] = t;
/* каждый символ имеет ширину в 16 точек растра */
len[i][1] = t + strlen(item[i])*16;
t = len[i][1] + 32; /* добавляется два пробела между
элементами меню */
}
/* ожидание выбора пользователем элемента меню */
Глава IX -- 48 --
do {
if(rightb_pressed() || leftb_pressed()) break;
} while(!kbhit());
/* ожидание нажатия клавиши */
while(rightb_pressed() || leftb_pressed());
/* получить текущую позицию курсора "мыши" */
mouse_position(&mousex, &mousey);
/* анализируется, находится ли курсор в пределах меню */
if(mousey>=0 && mousey<8) /* символ имеет высоту
8 точек растра */
for(i=0; i<count; i++) {
if(mousex>len[i][0] && mousex<len[i][1])
return i;
}
return i;
}
/* Возвращает 1, если специфицированная клавиша не нажата */
void wait_on(button)
int button;
{
if(button== LEFTB)
while(leftb_pressed());
else
while(rightb_pressed());
}
/* Установка "мыши" в исходное состояние */
void mouse_reset()
{
int fnum, arg2, arg3, arg4;
fnum = 0; /* Установка "мыши" в исходное состояние */
cmouses( &fnum, &arg2, &arg3, &arg4);
if(fnum!=-1) {
printf("Аппаратные или программные средства поддержки ");
printf("'мыши' не инсталированы");
exit(1);
}
if(arg2!=2) {
printf("Разрешено использование только двухклавишной 'мыши'");
exit(1);
}
}
Программа рисования, предложенная вам в этой главе, довольно
удобна в обращении, так как позволяет вам рисовать произвольные
рисунки (естественно используя стандартные линии), двигаясь при
Глава IX -- 49 --
этом в любых произвольных направлениях по экрану. Например,
рисунок 9.1 был нарисован на экране дисплея буквально за
несколько минут. Рис. 9.2 иллюстрирует, что изображается на
экране при вызове меню "мыши" (форма курсора - стрелка принята по
умолчанию).
Глава IX -- 50 --
НЕКОТОРЫЕ ВОЗМОЖНОСТИ РАСШИРЕНИЯ ВЫПОЛНЯЕМЫХ
ФУНКЦИЙ ПРОГРАММЫ
-----------------------------------------------------------------
Во-первых, вам возможно понадобится модифицировать функцию
mouse_menu() с тем, чтобы возможно было обрабатывать меню,
занимающее более одной строки экрана. Фактически для реализации
этого наиболее приемлемым путем будет объединение функции
обработки меню "мыши" с функциями обработки "всплывающего" меню,
рассмотренными в главе 1.
Если вы привыкли работать с иконным интерфейсом, то вы, в
принципе, можете модифицировать функции обработки меню "мыши"
таким образом, чтобы вместо слов в меню фигурировали на
соответствующих местах пиктограммы, обозначающие тот или иной
элемент меню. Возможно также модифицировать функции таким
образом, чтобы обеспечить полиэкраный режим работы "мыши", а
также копирование (а не перемещение) части экрана (или
изображения) с одного места в другое.
