Графические устройства

         

Курсор и мышь

Манипулятор "мышь" (далее просто мышь) является основным инструментом для поддержки диалога пользователя с задачей при работе в графических видеорежимах. С помощью мыши выбираются и активизируются диалоговые окна, меню или значки на панелях инструментов, выполняются различные манипуляции с рисунками и прочие действия.

На экране монитора текущее расположение мыши указывает специальный рисунок, который принято называть графическим курсором (graphics cursor) или указателем мыши (mouse pointer). Он удаляется с одного места и появляется на другом при каждом перемещении мыши. Текущие координаты курсора нужны задаче для выполнения различных действий.

В данной главе мы рассмотрим наиболее распространенные варианты построения рисунка курсора и обсудим способы организации взаимодействия задачи с манипулятором "мышь".

Построение рисунка курсора

При работе в текстовых или графических режимах IBM драйвер мыши самостоятельно определяет установленный видеорежим и в зависимости от этого выбирает способ построения или удаления рисунка курсора, задача только разрешает или запрещает ему выполнять эти действия. Драйверы мыши предназначены для работы в среде DOS, они различают только стандартные режимы IBM. Поэтому после установки режимов VESA строить и перемещать рисунок курсора должна задача. В отличие от DOS, операционные системы семейства Windows и OS/2 поддерживают управление курсором, что упрощает действия прикладных задач.

Замечание
Напомним, что код текущего режима хранится в байте, расположенном в области данных BIOS, по адресу 0000:0449. Трехзначные коды режимов VESA непомещаются в байте, и их заменяют кодами OEM, которые уникальны для каждой модели видеокарты. Именно отсутствие стандартов на коды OEM не позволяет разрабатывать драйверы, выполняющие построение рисунка курсора во всех без исключения видеорежимах.

Изображение курсора отличается от обычных рисунков тем, что постоянно перемещается по экрану, следуя за перемещениями манипулятора "мышь". При этом оно должно быть четко видно на любом окружающем фоне и не должно оставлять следов от своего перемещения, за исключением тех случаев, когда такой след создается специально. На видимость и расположение курсора не должны влиять вывод новых рисунков на экран или удаление существующих. В некоторых случаях форма рисунка курсора может изменяться в зависимости от его местонахождения на экране или действий, выполняемых задачей в данный момент времени.

Поэтому при работе с изображением курсора выполняются специфические действия, которые не требовались при построении обычных рисунков. Прежде чем рассматривать эти действия, давайте разберемся, где можно взять и как подготовить рисунок курсора для его использования в задаче.


Курсоры для Windows

Наиболее доступными являются файлы, содержащие рисунки курсоров, подготовленные в стандарте Windows. Операционные системы Windows используют курсоры различной формы: стрелка, вертикальная черта, рука, песочные часы и пр. Конкретный рисунок курсора зависит от выполняемых действий и выбирается системой автоматически. Windows 9X позволяет изменять рисунки курсора при выборе "темы рабочего стола".
Windows 3.X работают с черно-белыми курсорами, заготовки рисунков которых хранятся в специальном файле и извлечь их из него не так просто. Однако существует специальное приложение MouseWarp, которое позволяет оператору выбирать рисунок курсора по своему усмотрению. В комплект этого приложения входит 19 файлов с заготовками рисунков курсоров, которые можно использовать для наших целей.
Windows 9X не только сама изменяет форму курсора, но и позволяет это сделать оператору. Прилагаемые к ней заготовки рисунков курсоров хранятся в отдельном каталоге (cursors) и вы можете их использовать.

Структура файлов Icon.

Семейство Windows использует один общий стандарт icon для хранения файлов с заготовками рисунков курсоров и пиктограмм (значков). Спецификации файлов имеют тип (расширение) cur для курсоров и ico для пиктограмм.

К сожалению, автор не встречал точного описания структуры таких файлов, даже в справочнике Борна содержатся явные неточности. Если вам попадется описание стандарта icon для Windows, то ему не следует слепо доверять. Обязательно распечатайте дамп одного из доступных вам файлов и сравните распечатку с вариантом описания. В качестве эталона можно взять файл nc.ico, входящий в комплект Norton Commander. Для версии NC 5.0 он содержит заготовку рисунка капитанской фуражки с красными цифрами 5.0.

Стандартный файл формата icon состоит из четырех основных частей: заголовка, палитры цветов, заготовки рисунка и маски.

Первые восемь байтов заголовка содержат следующие данные:

слово со смещением 0 всегда очищено (пустое), это признак формата
icon; слово со смещением 2 содержит тип рисунка: 1 — пиктограмма, 2 — курсор; слово со смещением 4 содержит количество хранящихся в файле рисунков (обычно 1); байт со смещением 6 содержит количество точек в строке (обычно 20h); байт со смещением 7 содержит количество строк в рисунке (обычно 20h).

Из других полей заголовка следует отметить слово с адресом 36 (24h), содержащее размер точки рисунка, выраженный в битах. Он равен 1 для черно-белых и 4 для цветных рисунков. Эта величина указывает способ распаковки рисунка и размер палитры.

Палитра используемых цветов располагается в файле, начиная с адреса ЗЕf. Она содержит 2 или 16 строк, в которых хранятся коды цветов в формате b, g, r, 0. Заметим, что в таком формате хранится палитра в вмр-файлах для Windows (см. приложение А). В зависимости от количества цветов палитра занимает 8 (2 цвета) или 64 (16 цветов) байта.

Сразу после палитры размещается образ рисунка. Адрес его начала зависит от размера палитры и равен 46h для черно-белых рисунков или 7Eh для 16-цветных. Количество точек в рисунке фиксировано и составляет 32-32 = = 1024 точки. Черно-белые рисунки упакованы по 8 точек в байте, а цветные — по 2 точки в байте. Соответственно, образ рисунка занимает в файле 128 или 512 байтов.

После образа рисунка располагается маска. Адрес ее начала сбь для черно-белых рисунков или 27Eh для цветных. Маска — это черно-белый рисунок, упакованный по 8 точек в байте и занимающий 128 байтов. Адрес ее начала отстоит от конца файла на 128 байтов.

Образ рисунка и маска хранятся в перевернутом виде: первой записана последняя строка, второй — предпоследняя и т. д., последней в файле хранится первая строка рисунка или маски. Такой способ хранения данных используется в файлах формата BMP (см. приложение А).

Дамп файла с рисунком курсора. В примере 6.1 приведена распечатка (дамп) файла ieft_00.cur, входящего в комплект Mouse Warp. Он содержит рисунок стрелки, наклоненной вправо (обычно стрелка наклонена влево). Распечатка приведена в общепринятой шестнадцатеричной форме, каждой строке предшествует адрес ее начала в файле.

Пример 6.1. Распечатка (dump) файла Left_00.cur

Заголовок файла

000 00 00 02 00 01 00 20 20 00 00 ОЕ 00 04 00 30 01
010 00 00 16 00 00 00 28 00 00 00 20 00 00 00 40 00
020 00 00 01 00 01 00 00 00 00 00 00 01 00 00 00 00
030 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Палитра, содержащая описание черного и белого цветов

ОЗЕ 00 00 00 00 FF FF FF 00

Рисунок курсора, упакованный по 8 точек в байте

046 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
056 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
066 06 00 00 00 06 00 00 00 03 00 00 00 03 00 00 00
076 01 80 00 00 01 84 00 00 00 СС 00 00 00 DC 00 00
086 00 FC 00 00 07 FC 00 00 03 FC 00 00 01 FC 00 00
096 00 FC 00 00 00 7С 00 00 00 ЗС 00 00 00 1C 00 00
ОА6 00 ОС 00 00 00 04 00 00 00 00 00 00 00 00 000 00
ОВ6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00* 00

Маска курсора, упакованная по 8 точек в байте

ОС6 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
OD6 FF FF FF FF FF FF FF FF FF FF FF FF F9 FF FF FF
ОЕ6 FO FF FF FF FO FF FF FF F8 7F FF FF F8 7D FF FF
OF6 FC 39 FF FF FC 31 FF FF fE 01 FF FF FE 01 FF FF
106 ЕО 01 FF FF FO 01 FF FF F8 01 FF FF FC 01 FF FF
116 FE 01 FF FF FF 01 FF FF FF 81 FF FF FF Cl FF FF
126 FF El FF FF FF Fl FF FF FF F9 FF FF FF FD FF FF
136 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

Для того чтобы лучше понять, как хранятся и кодируются рисунок и маска, советуем вам нарисовать их на бумаге в клетку. В рассматриваемом примере они упакованы одинаково, по восемь точек в байте. Единица в разряде означает наличие точки (заштрихованная клетка на бумаге), а нуль — ее отсутствие (пустая клетка на бумаге). После построения вы увидите, что маска похожа на негативное изображение рисунка, но если их совместить, то окажется, что рисунок стрелки в маске оконтурен белой линией.


Предварительная подготовка рисунка

В исходном виде рисунок курсора и маска не удобны для многократного использования. Их надо распаковать, перевернуть, по возможности сократить и хранить в оперативной памяти до конца выполнения задачи.

Перед построением изображения курсора, как и любого рисунка, должна быть установлена палитра используемых цветов (см. главу 4). В данном случае палитра хранится в формате BMP для Windows, который описан в приложении А.

Замечание
Напомним, что в зависимости от способа установки палитры могут измениться коды точек рисунка курсора (но не маски).

Поворот рисунка и маски. Рисунок на экране проще строить в естественном порядке, т. е. в направлении слева направо и сверху вниз. В исходном виде рисунок и маска хранятся "вверх ногами", поэтому перед распаковкой их надо повернуть. При повороте переставляют 16 пар строк: первую строку с последней, вторую — с предпоследней и т. д.

В примере 6.2 показано, как можно программно переставить строки маски или черно-белого рисунка. Перед выполнением примера исходный файл должен быть прочитан в буфер, сегмент которого указывается в регистре fs, а смещение (адрес) рисунка или маски в буфере помещается в регистр di. Перевернутое изображение записывается на место исходного.

Пример 6.2. Поворот черно-белого рисунка или маски

mov si, di копируем адрес первой строки
add si, 124 получаем адрес последней строки
mov ex, 16 количество пар строк
turn : mov eax, fs : [di] еах = строка 1
xchg eax, f s : [si j еах = строка 2; fs: [si] = строка 1
mov fs : [di] , eax fs : [di] = строка 2
add di, 04 адрес следующей строки
sub si, 04 адрес предыдущей строки
loop turn управление повторами цикла

Выполнение примера 6.2 начинается с подготовки адреса последней строки и задания количества переставляемых пар. Перестановку выполняет цикл, его первая команда имеет метку turn. Она копирует в регистр еах первую строку пары. Следующая команда переставляет содержимое еах и второй строки пары. Третья команда копирует содержимое еах в первую строку. В результате переставлена пара строк. Затем корректируются адреса строк, и команда loop повторяет выполнение цикла 16 раз.
При перестановке расположение байтов в строке не изменяется, поскольку пересылку выполняет одна команда. Для переворота цветного рисунка этот пример не подходит, т. к. для перестановки 16-ти байтов пары строк надо организовать внутренний цикл, а во внешнем подготавливать адреса переставляемой пары.

Распаковка рисунка и маски

Повернутые рисунок и маску надо распаковать и сохранить в оперативной памяти. Для их хранения выделяется два массива, размером по 1024 байта (один байт на точку). В дальнейшем мы будем называть их pntimage и pntmask, первый содержит распакованный рисунок курсора, а второй — маску.

При распаковке черно-белого рисунка содержимое каждого байта обрабатывается, начиная со старшего разряда, и значение каждого бита (0 пли I) помешается в соответствующий байт массива pntimage.

При распаковке 16-цветного рисунка в байты массива pntimage записываются сначала старшая, а затем младшая тетрада каждого байта упакованного рисунка. Коды распакованных точек изменяются от о до огь.

Подпрограммы распаковки строк 16- и 2-цветных рисунков приведены в примерах 3.17 и 3.18, но они записывают результат в видеопамять. Применительно к данному случаю их надо изменить так, чтобы результат записывался в оперативную память, и организовать цикл для распаковки всего рисунка или маски.

После распаковки рисунка будут получены коды цветов, являющиеся адресами строк прилагаемой палитры. Маловероятно, чтобы они совпали с кодами цветов системной палитры, с которой работает задача. Едва ли в ней код белого цвета будет равен 1 или OFh, как в прилагаемой к рисунку палитре. Поэтому у вас есть две возможности: либо преобразовать распакованные коды рисунка так, чтобы они соответствовали системной палитре, либо в системной палитре зарезервировать 2 или 16 первых регистров цвета для работы с курсором. Второй способ используется в Windows при работе в режимах PPG.

Последовательность действий при распаковке маски та же, что и при распаковке черно-белого рисунка. Если текущий бит маски содержит 0, то соответствующий байт массива pntmask очищается, но если текущий бит маски содержит 1, то устанавливаются все разряды соответствующего байта массива pntmask (в него записывается код OFFh). Это объясняется специфическим назначением маски — при ее наложении байты видеопамяти либо полностью очищаются, либо остаются без изменения.

Сокращение рисунка и маски.

При выполнении графических задач курсор перемещается достаточно часто, поэтому желательно сократить до минимума действия, связанные с его построением и перемещением. Для этого, в частности, можно исключить из исходного рисунка не используемые (пустые) строки и столбцы.

Как правило, размеры рисунка меньше стандартного поля 32x32 точки. Например, изображение стрелки, хранящейся в файле Left_00.cur (см. пример 6.1) помещается в прямоугольнике шириной в 14 и высотой в 21 точку. Следовательно, для его хранения в памяти достаточно выделить не 1024, а всего 294 байта. Очевидно, что при сокращении рисунка не только уменьшается занимаемое им пространство оперативной памяти, но и ускоряется процесс его построения и удаления. Рисунок и маска взаимосвязаны, поэтому при исключении строки пли столбца рисунка надо исключить соответствующую строку или столбец маски.

Пример описания рисунка и маски. В примере 6.3 заготовка рисунка и маска описаны на языке ассемблера. Это распакованный файл из примера 6.1, в котором переставлены не только строки, но и столбцы, для того чтобы стрелка курсора была наклонена влево, а не вправо.

Пример 6.3. Описание рисунка и маски курсора

Pnt image db 00, 00, 00,00, 00, 00, 00 00,00, no, nn nn no oo
db 00, 00, 00,00, 00, 00, 00, 00,00, 00, 00, 00, 00,00
db 00, 00, FF, 00, 00, 00, 00, 00,00, 00, 00, 00, 00,00
db 00, 00, FF, FF, 00, 00, 00, 00,00, 00, 00, 00, 00,00
db 00, 00, FF,FF, FF, 00, 00, 00,00, 00, 00, 00, 00,00
db 00, 00, FF,FF, FF, FF, 00, 00,00, 00, 00, 00, 00,00
db 00, 00, FF, FF, FF, FF, FF, 00,00, 00, 00, 00, 00,00
db 00, 00, FF, FF, FF, FF, FF, FF, 00, 00, 00, 00, 00,00
db 00, 00, FF, FF, FF, FF, FF, FF, FF, 00, 00, 00, 00,00
db 00, 00, FF, FF, FF, FF, FF, FF, FF, FF, 00, 00, 00,00
db 00, 00, FF,FF, FF, FF, FF, FF, FF, FF, FF, 00, 00,00
db 00, 00, FF, FF, FF, FF, FF, FF, 00, 00, 00, 00, 00,00
db 00, 00, FF, FF, FF, 00, FF, FF, 00, 00, 00, 00, 00,00
db 00, 00, FF, FF, 00, 00, FF, FF, 00, 00, 00, 00, 00,00
db 00, 00, FF, 00, 00, 00, 00 FF, FF, nn 00 nn ПП ПО
db 00, 00, 00,00, 00, 00, 00, FF,FF, 00, 00, 00, 00,00
db 00, 00, 00,00, 00, 00, 00, 00, FF, FF, 00, 00, 00,00
db 00, 00, 00,00, 00, 00, 00, 00, FF, FF, 00, 00, 00,00
db 00, 00, 00, 00, 00, 00, 00, 00,00, FF, FF, 00, 00,00
db 00, 00, oo on nn nn nn 00,00, FF, FF, 00, 00,00
db 00, 00, 00,00, 00, 00, 00, 00,00, 00, 00, 00, 00,00
pntmask db FF, 00, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF
db FF, 00, 00, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF
db FF, 00, 00,00, FF, FF, FF, FF, FF, FF, FF, FF, FF,FF
db FF, 00, 00,00, 00, FF, FF, FF,FF, FF, FF, FF, FF,FF
db FF, 00, 00,00, 00, 00, FF, FF, FF, FF, FF, FF, FF,FF
db FF, 00, 00,00, 00, 00, 00, FF, FF, FF, FF, FF, FF, FF
db FF, 00, 00,00, 00, 00, 00, 00, FF, FF, FF, FF, FF,FF
db FF, 00, 00,00, 00, 00, 00, 00,00, FF, FF, FF, FF,FF
db FF nn 00,00, nn nn nn 00,00, on FF FF FF FF
db FF, 00, 00,00, 00, 00, 00, 00,00, 00, 00, FF, FF, FF
db FF, 00, 00,00, 00, 00, 00, 00,00, 00, 00, 00, FF, FF
db FF, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, FF
db FF, 00, 00, 00, 00, 00, 00, 00, 00, FF, FF, FF, FF, FF
dc FF, 00, 00, 00, 00, 00, 00, 00, 00, :-:-, FF, FF, FF, FF
db FF, 00, 00, 00, FF, FF, 00, 00, 00, 00, FF, FF, FF, FF
db FF, ou, 00, FF, FF, FF, 00, 00, 00, 00, FF, FF, FF, FF
db FF, 00, FF, FF, FF, FF, t F, 00, 00, 00, 00, FF, FF, FF
db FF, FF, FF, FF, FF, FF, FF, 00, 00, 00, 00, FF, FF, FF
db FF, FF, FF, FF, FF, FF, FF, FF, 00, 00, 00, 00, FF, FF
db FF, FF, F"F FF, FF, FF, FF, FF, 00, 00, 00, 00, FF, Ь г
db FF, FF, FF, FF, FF, FF, FF, FF, FF, 00, 00, FF, FF, FF

В примере 6.3 метки pntimage и pntmask предшествуют директиве db, поэтому двоеточие после них не ставится. Если вы будете включать текст примера в свою программу, то все коды FF надо заменить на OFFh или на десятичное число -1. Здесь это не сделано только из соображений наглядности, чтобы можно было увидеть образованный из цифр рисунок. Текст примера лучше всего включить в сегмент данных вашей программы. Для того чтобы рисунок курсора был черно-белым, нулевой регистр цвета видеокарты должен быть очищен, а в последнем (255-м) регистре должен находиться код белого цвета (3F,3F,3F) (см. раздел).

Подведем итог всему сказанному в данном разделе. Курсор, хранящийся в файле формата icon, не удобно использовать без предварительного преобразования рисунка и маски и установки палитры используемых цветов. Если вы хотите, чтобы ваша задача могла работать с файлами формата icon, то в нее придется включить специальную процедуру, выполняющие описанные в данном разделе действия. Если же нужен только один рисунок курсора, то его преобразование проще выполнить вне задачи, а в ее исходный текст включить результат, как это сделано в примере 6.3.

Преобразования выполняются вручную или с помощью специально составленной программы, поскольку стандартные графические редакторы не работают с файлами формата icon. Преобразование вручную занимает сравнительно немного времени. Сначала исходный файл преобразуется в символьную форму, т. е. в файл, содержащий шестнадцатеричные коды (дамп). А затем символьный файл редактируют с помощью любого текстового редактора, например, входящего в Norton Commander и вызываемого нажатием функциональной клавиши <F4>.


Немаскируемый курсор

При построении обычных рисунков их образы копируются в видеопамять, но если таким способом построить рисунок, образ которого приведен в примере 6.3, то изображение белой стрелки будет расположено на фоне черного прямоугольника. Очевидно, что работать с подобным изображением

курсора неудобно и черный фон, окружающий стрелку, надо убрать. Для исключения окружающего фона применяются маскировка, или специальные способы построения изображения курсора.

Один из таких способов мы уже использовали при построении текстового курсора, он описан в разделе, пример 5.24. Здесь нас интересует более универсальный вариант подобной подпрограммы, позволяющий строить изображение курсора произвольного размера и формы. Для записи кодов точек в видеопамять, по-прежнему, будет использоваться логическая операция XOR, вычисляющая функцию "исключающее ИЛИ" (exclusive OR).

Предварительные замечания

Образ рисунка курсора можно хранить в любом сегменте оперативной памяти. Учитывая его небольшой размер (294 байта). мы будем считать, что он расположен в сегменте данных (см. пример 6.3) и имеет имя pntimage. Маска при построении не используется, поэтому массив pntmask нас в данном случае не интересует.

Учитывая, что размеры рисунка не фиксированы и зависят от его формы, в разделе данных задачи надо описать две следующие переменные:

PntXsize dw 14 ; количество точек в строке рисунка курсора
pntYsize dw 21 ; количество строк в рисунке курсора

В приведенном описании значения переменных соответствуют размерам рисунка, показанного в примере 6.3.

Курсор является особым рисунком, его координаты в видеопамяти могут использоваться в различных целях. Поэтому они хранятся в специальных переменных, значение которых может изменяться только при перемещении манипулятора "мышь". В примере 6.8 будет описано несколько переменных, используемых при работе с курсором. Здесь нас интересуют только две из них. Переменная winpnt содержит текущее окно видеопамяти, a offspnt — адрес (смешение) точки левого верхнего угла рисунка курсора в этом окне.

Подпрограмма Tglpntr

Текст подпрограммы, изменяющей состояние курсора на противоположное, приведен в примере 6.4. При каждом нечетном вызове Tglpntr рисунок курсора появляется на экране, а при каждом четном на ею месте восстанавливается исходный фон. Явно задаваемые входные параметры отсутствуют. Регистр es должен содержать код видеосегмента.

Пример 6.4. Подпрограмма переключения состояния курсора

Tglpntr: pusha сохранение содержимого регистров
push Cur_win сохранение исходного окна
ir.ov ax, Winpnt ax = окно с рисунком курсора
mov Cur_win, ax Cur_win = Winpnt
call setwin установка исходного окна
lea si, pntimage si = адрес массива pntimage
mov di, Offspnt di = адрес Е сегменте видеопамяти
mov ex, pntYsize ex = кол-во повторов внешнего цикла
mov bx, horsize вычисляем константу для
sub bx, pntXsize коррекции адресов строк
Displ 1 : push ex сохраняем счетчик строк
mov ex, pntXsize сх = количество точек в строке рисунка
Displ 2: lodsb ! ! al = код очередной точки рисунка
xor es : [di] , al ! ! корректируем байт видеопамяти
inc di ! ! увеличение адреса видеопамяти
jnz @F -> адрес в пределах текущего сегмента
call nxtwin конец сегмента, смена окна
@@: loop Displ 2 управление повторами цикла
pop ex восстанавливаем счетчик строк
add di , bx адрес начала следующей строки
jnc @F -> адрес в пределах текущего сегмента
call nxtwin конец сегмента, смена окна
@@: loop Displ 1 управление повторами цикла
pop Cur win восстановление Cur win
popa восстановление содержимого регистров
call setwin восстановление исходного окна
ret возврат из подпрограммы

В подпрограмме примера 6.4 используется только 5 регистров — ах, bх, сх, si и di, но для сокращения ее текста первая команда pusna сохраняет в стеке содержимое всех регистров. Затем в стек помещается исходное значение переменной Cur win, а ей присваивается новое значение и устанавливается соответствующее окно видеопамяти. В регистры si, di записываются адреса оперативной и видеопамяти, а в сх — количество строк в рисунке курсора. В конце подготовки в регистре bх формируется разность horsize - pntxsize, используемая в цикле построения для коррекции адресов строк видеопамяти.

Построение рисунка выполняют два вложенных цикла. Внешний имеет метку oispi_i. Он начинается с сохранения в стеке и изменения содержимого регистра сх, после чего выполняется внутренний цикл.

Цикл построения строки имеет метку Dispi_2. Его первая команда lodsb считывает в регистр al байт, адрес которого находится в dsrsi, и увеличивает содержимое регистра si на 1. Затем логическая операция XOR записывает содержимое регистра al в видеопамять. Регистр-посредник al нужен потому, что у команды хог (как и у команды mov) оба операнда не могут находиться в памяти.

После вывода очередной точки адрес видеопамяти увеличивается на 1, и если его значение осталось в пределах сегмента, то команда jnz @F обходит call nxtwin. В противном случае команда call nxtwin выполняется и устанавливается следующее окно. Последняя команда (loop Disp_2) повторяет выполнение цикла до тех пор, пока не будет нарисована вся строка.

При возврате во внешний цикл из стека выталкивается содержимое счетчика повторов и вычисляется адрес начала в видеопамяти следующей строки рисунка. Если при этом происходит переполнение, то устанавливается следующее окно видеопамяти. Команда loop Disp_i повторяет выполнение внешнего цикла до тех пор, пока не будет построен весь рисунок курсора.

После построения (или удаления) курсора из стека выталкивается содержимое переменной cur_wir. и всех сохраненных регистров, восстанавливается исходное окно видеопамяти и происходит возврат на вызывающий модуль.

Недостатки немаскируемого курсора

чевидными преимуществами работы с немаскируемым курсором являются следующие:

для построения и удаления курсора нужна одна подпрограмма; подпрограмма выполняется сравнительно быстро; в оперативной памяти хранится только образ рисунка.

Однако такой способ построения имеет один существенный недостаток, сводящий на нет перечисленные преимущества.

Идея использования немаскируемого курсора основана на том, что при определенных значениях операнда-источника команда хог инвертирует код операнда-приемника или не изменяет его (см. раздел). В описанной подпрограмме источником являются точки заготовки рисунка, а приемником — точки видеопамяти. Образ рисунка черно-белый, коды его точек имеют значения либо 00, либо 0FFh. Поэтому при построении рисунка цвета точек экрана, расположенных под стрелкой, инвертируются, а окружающих стрелку не изменяются. Таким образом, цвет рисунка немаскируемого курсора на экране зависит от исходного цвета точек в том месте экрана, на котором он создается.

Вспомним табл. 4.1 из главы 4. При ее описании говорилось, что два цвета являются дополнительными, если при их наложении получается белый цвет. В частности, дополнением к черному цвету является белый, к синему — желтый, к красному — циан, к зеленому — мажента. Исходя из этого, можно представить, как изменяется цвет курсора в зависимости от исходного цвета точек экрана. Если же на экране находится какая-то картинка, т. е. цвет экрана не однороден, то и изображение курсора будет неоднородным. На пестром фоне оно может "потеряться" — стать трудно различимым для глаза.

При работе в режимах PPG описанная подпрограмма инвертирует не код цвета, а номер регистра видеокарты. Полученный при инверсии цвет будет зависеть от установленной (системной) палитры. Эта особенность успешно использовалась, например, в ранних версиях Windows — системная палитра подбиралась так, чтобы можно было использовать немаскируемый курсор.

В заключение заметим, что после описания маскируемого курсора в разделе 6.1.5 мы продолжим обсуждение некоторых вопросов, связанных с построением изображения курсора.


Маскируемый курсор

Маскировка является одним из способов исключения ненужных элементов изображения в процессе построения рисунка. Она применяется не только при выводе на экран курсоров и пиктограмм, но и во многих других случаях, например, при сборке рисунков из отдельных частей. Маска может быть подготовлена заранее с учетом особенностей рисунка или сформирована динамически, на основании анализа цветов строящегося рисунка. В данном разделе рассмотрена работа с готовой маской.

Как производится маскировка

В предыдущем разделе мы использовали тот факт, что при определенных условиях команда хог инвертирует значение операнда-приемника. Заметим также, что у этой команды есть еще одно полезное свойство. Вспомним таблицу истинности логической функции "исключающее ИЛИ" (табл. 5.1). Из нее, в частности, следует, что если один из двух операндов очищен, то результат выполнения команды хог будет равен значению другого операнда. Следовательно, при наложении двух цветов с помощью операции хог черный цвет становится прозрачным.

При построении маскируемого курсора та часть экрана, которую займет его изображение, предварительно окрашивается в черный цвет (очищается). С помощью команды хог (или or) на чистом месте можно построить рисунок любого цвета. Чтобы не портить окружающий фон в образе рисунка, точки, дополняющие его основную часть до прямоугольника, должны иметь черный цвет. Это условие обязательно выполняется у стандартных рисунков курсоров и пиктограмм (см. пример 6.3).

Для закрашивания в черный цвет на место расположения выводимого рисунка накладывается маска. Как говорилось в

Замечания к описанным подпрограммам

В двух предыдущих разделах описаны действия, которые надо выполнить для построения или удаления изображения курсора. Здесь мы рассмотрим, как изменяются команды, выполняющие эти действия в зависимости от тех или иных дополнительных условий. Нас будут интересовать способы ускорения работы с курсором и построения изображения в режимах direct color, когда цвет указывается непосредственно в коде точки.

Ускорение работы с курсором. Курсор является основным средством управления процессом выполнения графических задач. Чем меньше времени затрачивается на перемещение его рисунка, тем больше времени остается на выполнение основных действий. Поэтому ускорение манипуляций с курсором представляет определенный практический интерес.

Для ускорения работы трех описанных подпрограмм надо сократить количество действий, выполняемых в их внутренних циклах.

В подпрограмме Hidepnt (пример 6.6) основное действие выполняет одна команда n\ovsb (ее метка hid_2), поэтому в этом случае применимы способы ускорения построения строк, описанные в разделе. В частности, вместо внутреннего цикла можно использовать подпрограмму примера 3.16, внеся в нее незначительные изменения.

К подпрограммам Tgipntr (см. пример 6.4) и showpnt (см. пример 6.5) описанные в разделе способы ускорения не применимы, поскольку в них основные действия выполняют несколько (2 или 5) команд.

На первый взгляд достаточно просто изменить основные команды так, чтобы они оперировали не с байтами, а со словами или двойными словами, т. е. обрабатывали коды сразу двух или четырех точек. В первом случае количество повторов внутреннего цикла сократится в 2, а во втором — . в 4 раза. Перед входом в цикл содержимое регистра ex (pntxsize) надо уменьшить, соответственно, в 2 или в 4 раза.

Такая замена дает нужный результат, но необходимы дополнительные меры защиты от возможной аварийной ситуации. Давайте разберемся в причине ее возникновения.

Курсор может находиться в любом месте экрана, поэтому вполне вероятно, что одна из строк его изображения расположится в смежных окнах видеопамяти. Если при этом первая точка строки имеет нечетный адрес в видеопамяти, то обрабатывать одной командой сразу две точки такой строки нельзя. При чтении или записи одной из пар точек первый байт операнда окажется в пределах, а второй за пределами текущего сегмента. Это одна из типичных аварийных ситуаций, чаще всего она приводит к тому, что на программистском жаргоне называется "компьютер завис", т. е. он не реагирует ни на какие внешние события, кроме выключения питания.

Для исключения аварийной ситуации можно, например, сделать так, чтобы при работе с курсором адрес его начала в видеопамяти всегда был четным. Изображение курсора следует за манипулятором "мышь". Если при опросе состояния последнего окажется, что он находится в столбце с нечетным номером, то этот номер принудительно делается четным. Такой трюк уменьшает точность позиционирования курсора на экране, поэтому вам придется выбирать меньшее из двух зол. К вопросу о точности позиционирования мы вернемся при описании программирования работы с мышью.

Таким образом, при некотором ограничении точности позиционирования выполнение подпрограмм Tgipntr и showpnt можно ускорить в 2 раза, внеся в них описанные выше изменения. Более существенное ускорение связано со значительным увеличением размеров текстов подпрограмм и едва ли целесообразно.

Изменения для режимов direct color. Подробному описанию особенностей программирования для видеорежимов с указанием цвета в коде точки посвящена главы 7. Здесь мы только покажем, какие изменения надо внести в описанные подпрограммы для их использования при работе в режимах direct color. Это позволит в дальнейшем не повторять описание способов построения курсора. Вы можете пропустить эту часть раздела и вернуться к ней после прочтения главы 7.

В режимах PPG код точки является номером регистра цвета видеокарты, а в режимах direct color он является кодом конкретного цвета и занимает 16 разрядов в режиме Hi-Color и 32 разряда в режиме True color.

Прежде всего, вам придется изменить заготовку рисунка и маску, которые хранятся в массивах pntimage и pntmask (см. пример 6.3).

Проще всего изменить описание маски и черно-белого рисунка курсора. В пояснениях к примеру 6.3 мы советовали при его использовании в конкретной программе заменить все коды OFF десятичным числом -1. Если вы это сделали, то остается только заменить все директивы db на dw для режима Hi-coior или на dd для True color. Макроассемблер зарезервирует требуемое пространство памяти и заменит число — 1 кодами OFFFFh или OFFFFFFFFII, в зависимости от указанной директивы (dw или dd).

Изменить описание цветного рисунка курсора сложнее, в этом случае недостаточно простой замены директив db на dw или dd. У заготовок цветных рисунков распакованный код точки является порядковым номером строки палитры, хранящейся вместе с рисунком. По коду точки надо выбрать из палитры код цвета, преобразовать его в нужную форму и поместить в описание рисунка. Такое преобразование делается программно, а не вручную. В главе 7 описаны способы преобразования рисунков из формата PPG в форматы direct color, их и можно использовать. Однако на первое время лучше ограничиться черно-белым курсором, а к работе с цветным перейти позже, по мере накопления опыта программирования графики.

В режимах direct color размеры массивов pntimage и pntmask увеличивают-ся в 2 или в 4 раза, во столько же раз надо увеличить размер массива pntbuff. Это можно сделать одним из двух способов: заменить в его описании директиву db на dw или dd, либо оставить директиву db, а количество резервируемых байтов умножить на 2 или на 4.

Изменения в текстах подпрограмм примеров 6.4, 6.5 и 6.6 связаны только с увеличением размера кода точки в 2 (режим Hi-Color) или 4 (режим True color) раза. Прежде всего, нужно увеличить значение константы, которая используется для коррекции адресов строк. Во всех примерах ее вычисляют две следующие команды:

mov bx, horsize ; вычисляем константу для sub bx,
pntXsize ; коррекции адресов строк

После них надо записать третью команду, сдвигающую содержимое регистра bх на один (shi bx, i) или на 2 (shi bx, 2) разряда влево. Значение константы увеличится, соответственно, в 2 или в 4 раза.

Остальные изменяемые команды расположены во внутренних циклах. Комментарий к ним начинается с двух восклицательных знаков. Изменения этих команд делятся на следующие три категории:

у строковых команд lodsb, stosb и movsb последняя буква (b) заменяется буквами w (Hi-Color) или d (True Color); если один из операндов команды находится в регистре ai, то имя регистра надо изменить на ах (Hi-Color) ИЛИ на еах (True Color); команды inc di и inc si увеличивают значение адреса на 1. Они заменяются командой сложения (add), которая прибавляет к регистру число 2 (Hi-Color) или 4 (True Color).

Перечисленные изменения делают возможным применение описанных подпрограмм при работе в видеорежимах с указанием цвета в коде точки.

Промежуточные итоги

При программировании конкретной задачи важно не только составить нужную подпрограмму, но и корректно ее использовать. Применительно к нашему случаю это означает следующее:

1. При выводе изображения курсора на экран вы должны быть уверены в том, что его там уже нет, в противном случае на экране может оказаться несколько изображений курсоров или будет испорчен фон, сохраненный при выводе предыдущего изображения. Такая уверенность есть при первом выводе курсора в начале выполнения задачи, но после этого задача должна контролировать его текущее состояние. 2. Перед удалением курсора также надо убедиться в том, что он находится на экране, а исходный фон сохранен в массиве pntbuff. В противном случае при попытке удалить курсор на экране появится прямоугольник, цвет и узор которого не соответствует ожидаемым.

При организации работы с текстовым курсором в разделах Текстовый курсор в графическом режиме и Ввод символов с клавиатуры мы использовали специальный признак, указывающий текущее состояние текстового курсора. Работа с графическим курсором имеет специфические особенности, а способы определения его текущего состояния зависят от того, как задача получает данные от манипулятора "мышь".

Если текущее положение манипулятора задача определяет в режиме опроса, то специальный признак состояния курсора не нужен,- оно определяется логикой выполняемых действий. Однако если задача взаимодействует с манипулятором в режиме прерываний, то без указанного признака не обойтись. Подробное обсуждение этих вопросов будет производиться в процессе описания программирования работы с манипулятором "мышь", к которому мы и переходим.


Подготовка к работе с манипулятором "мышь"

Манипулятор "мышь" преобразует свое перемещение в электрические сигналы и посылает их в компьютер. Наибольшее распространение получили электромеханические устройства, у которых датчиком перемещений является металлический шарик. Его вращение разлагается на два направления по осям х и у, преобразуется в электрические сигналы и поступает в компьютер по соединительному кабелю. На верхней части манипулятора расположены две или три кнопки, данные об их состоянии также передаются в компьютер.

Отдельные модели манипуляторов различаются не только по устройству и внешнему оформлению, но и по расположенному в них электронному блоку, формирующему электрические сигналы при перемещении мыши и нажатии на ее кнопки. Кроме того, могут различаться кабели и разъемы, с помощью которых мышь подключается к компьютеру.

Указанные различия не влияют на способы программирования работы с манипулятором. Конкретные особенности устройства управления мышью "спрятаны" в драйвере, который поставляется в комплекте с устройством. Поэтому при покупке вы можете выбирать ту модель манипулятора, которая вам больше нравится, например, по оформлению.


Общее описание драйвера мыши

От манипулятора в компьютер поступает первичная информация, которая не пригодна для непосредственного использования в прикладных задачах. Предварительную обработку этой информации выполняет специальная программа — драйвер. При перемещении мыши или при нажатии на одну из ее кнопок возникает аппаратное прерывание, в результате которого приостанавливается выполнение текущего процесса и происходит обращение к драйверу. Он обрабатывает поступившие данные и сохраняет результат в своих внутренних переменных, после чего может быть выполнена специальная прерывающая подпрограмма или завершена работа драйвера и продолжено выполнение приостановленного процесса.

Подчеркнем, драйвер только фиксирует наступление события — нажатие на одну из кнопок или перемещение мыши. Реагировать на само событие должна прикладная задача или одна из компонент операционной системы. Если в данный момент с мышью не работает ни одна задача, то событие останется не востребованным. Поэтому наличие драйвера необходимое, но не достаточное условие для организации взаимодействия с мышью.

Установка драйвера

Драйвер является резидентной, т. е. постоянно находящейся в памяти задачей. В процессе загрузки DOS находит файл, содержащий эту задачу, помещает его содержимое в оперативную память и выполняет первый запуск. При этом драйвер настраивается на дальнейшую работу, после чего продолжается процесс загрузки DOS.

Файл, содержащий драйвер, должен находиться в одном из каталогов жесткого диска. Часто, но не всегда, он имеет имя mouse, а его тип может быть com или sys. Тип влияет только на способ первоначальной установки драйвера и не влияет на дальнейшую работу с ним. Если файл имеет тип com (например, mouse.com), то его полная спецификация (путь поиска, имя и тип файла) указывается в системном файле autoexec.bat. А если он имеет тип sys (например, mouse.sys), то спецификация указывается в файле config.sys.
Обычно при продаже к мыши прилагается дискета, содержащая программу для установки драйвера и текстовый файл (его имя readme, или нечто подобное), с рекомендациями по установке. Чаще всего установка сводится к копированию нужных файлов в один из каталогов жесткого диска и включения имени файла драйвера в autoexec.bat ИЛИ config.sys.

Если по каким-то причинам у вас есть мышь без установочной дискеты, попытайтесь использовать любой доступный драйвер, скорее всего вам это
удастся. Современные модели манипуляторов, как правило, соответствуют стандарту Microsoft Mouse, поэтому обслуживать их могут все драйверы, при разработке которых были учтены требования этого стандарта.

Основные функции драйвера выполняются независимо от вычислительной среды. Поэтому Windows ЗХ может использовать установленный в DOS или свой собственный драйвер, загружаемый вместе с системой. Windows 9X являются самостоятельными операционными системами, не зависящими от DOS, поэтому они обязательно загружают драйвер.

Доступ к драйверу

Если драйвер установлен, то при работе в среде DOS адрес его точки входа хранится в векторе ззь. Поэтому для обращения к нему прикладные задачи должны использовать командное прерывание int ззь. Предварительно в регистре ах указывается код запрашиваемой функции, который может изменяться в пределах от о до 24п. Если для выполнения функции нужны входные параметры, то их значения передаются в регистрах общего назначения. В тех же регистрах драйвер возвращает выходные параметры (запрашиваемые данные), если таковые имеются.
Например, для приведения драйвера в первоначальное состояние (сброс или инициализация), в задаче надо выполнить две следующие команды:

mov ах, 0 ; ах = код запроса, в данном случае 0
int 33h ; обращение к драйверу для исполнения запроса.

В результате внутренние переменные драйвера принимают те значения, которые они имели при первоначальной загрузке. Мы еще раз вернемся к рассмотрению данного запроса в следующем разделе.

Сводка функций драйвера. Основной набор функций, выполняемых всеми драйверами, устоялся. Он описан, например, в разделе Mouse Support электронной справочной системы Tech Help. Краткое описание функций, выполняемых конкретным драйвером, как правило, находится на установочной дискете, прилагаемой к манипулятору.

В табл. 6.1 перечислены функции, входящие в основной набор. Указанные в первом столбце таблицы коды являются шестнадцатеричными числами. Обратите внимание на отсутствие кодов 11h, 12h, 1Ch, 22h и 23h. Конкретный драйвер может выполнять дополнительные функции, с этими или другими кодами. Однако они мало что добавляют к основному набору, и дополнительные функции лучше не использовать, исходя из соображений совместимости задачи с любыми моделями драйверов.

Таблица 6.1. Список основных функций драйвера мыши

Код

Запрашиваемое (исполняемое) действие

00

Инициализация драйвера (настройка на работу с мышью)

*01*

Включить (нарисовать на экране) изображение курсора

*02*

Выключить (удалить с экрана) изображение курсора

03

Опрос текущих координат курсора и состояния всех кнопок

04

Установить текущие координаты курсора

05

Опрос счетчика нажатий указанной кнопки и координат

06

Опрос счетчика отпусканий указанной кнопки и координат

07

Установить пределы перемещения курсора по горизонтали

08

Установить пределы перемещения курсора по вертикали

*09*

Установить форму курсора в графическом режиме

*0а*

Установить форму курсора в текстовом режиме

0b

Определить расстояние последнего перемещения в mickeys

0c

Установить подпрограмму для обработки событий

*0d*

Разрешить эмуляцию светового пера

*0е*

Запретить эмуляцию светового пера

0f

Установить шаг курсора при медленном перемещении мыши

*10*

Установить область, в которой курсор не виден

13

Установить шаг курсора при быстром перемещении мыши

14

Изменить подпрограмму, установленную по коду Ос

15

Получить размер внутреннего буфера состояния драйвера

16

Сохранить в памяти внутренний буфер состояния драйвера

17

Восстановить ранее сохраненный буфер состояния драйвера

18

Установить адрес специальной подпрограммы обработки событий

19

Определить адрес подпрограммы, установленной по коду 18

Установить чувствительность мыши в процентах (0—100)

Определить чувствительность мыши в процентах (0—100)

1d

Установить страницу, на которой должен находиться курсор

Определить страницу, на которой находится курсор

1f

Дезактивация драйвера (программное отключение от мыши)

20

Восстановление работы дезактивированного драйвера

21

Программный сброс драйвера (неполный аналог кода 00)

24

Определить тип мыши, драйвера и используемый порт

По назначению выполняемых действий функции, перечисленные в табл. 6.1. можно разделить на несколько групп.

Управление курсором

При установке стандартных текстовых или графических режимов IBM драйвер самостоятельно рисует, удаляет и перемещает указатель мыши, что существенно упрощает структуру прикладных задач, работающих с мышью. Тем не менее, задача должна иметь возможность влиять на выполнение драйвером указанных действий. Для этого в базовый набор команд включено 9 функций, коды которых в табл. 6.1 начинаются и заканчиваются символом "*".

Они позволяют задаче в нужные моменты времени включать и выключать курсор и изменять его форму. По умолчанию драйвер выбирает изображение указателя мыши (курсора) в зависимости от установленного видеорежима. В графических режимах оно имеет форму наклоненной влево стрелки, а в текстовых — прямоугольника. В текстовых режимах курсор перемещается не плавно, а скачками из одного знакоместа в другое. Задача может задавать размер этого скачка при обычном и быстром перемещении мыши.

Остается только сожалеть о том, что эти полезные функции нельзя использовать при работе в графических режимах VESA. Как уже говорилось в предисловии к разделу 6.1, драйвер не может определить характеристики этих режимов, необходимые для построения изображения курсора.

Установочные команды

Наиболее важной функцией драйвера является увязка перемещений мыши с позицией курсора на экране. При выполнении этой функции используются внутренние переменные и счетчики позиций, которые должны иметь определенные значения. Часть из них зависит от характеристик мыши и формируется при установке или инициализации драйвера. Другая часть значений зависит от установленного видеорежима, их должна определять задача.

Функции с кодами 00, 04, 07, 08, OFh, 13h, lAh, IBh, 21h позволяют изменять текущие настройки драйвера. С их помощью задача может инициализировать драйвер, установить пределы и скорость перемещения курсора, изменить чувствительность драйвера к перемещениям мыши. Подробное описание этих функций приведено в следующем разделе.

Информационные команды

Для определения текущих координат мыши и состояния ее кнопок предназначены функции с кодами оз, 05, об и овь. Примеры использования этих функций мы рассмотрим при описании работы с мышью в режиме опроса ее состояния.

Обслуживание прерываний

Альтернативой режиму опроса состояния является режим прерываний, при котором задача получает информацию от драйвера только при наступлении конкретного события — изменения позиции мыши или состояния ее кнопок. В теле задачи должны быть предусмотрены подпрограммы, выполняющие действия, связанные с данным событием (или событиями), например перемещение рисунка курсора на новое место. С помощью функций с кодами ось, I4h, I8h и I9h задача может сообщить драйверу адрес прерывающей подпрограммы и событие, на которое она реагирует. Примеры использования этих функций мы рассмотрим при описании работы с мышью в режиме прерываний.

Специальные функции

Пять команд с кодами I5h, I6h, I7h, iFh и 20h выполняют специфические действия, которые нужны только в особых случаях. В первую очередь к ним относится смена драйвера при выполнении задачи (как правило, системной, а не прикладной). Простой замены содержимого вектора 33h в этом случае недостаточно, поскольку при первоначальной установке драйвера настраивается контроллер прерываний и изменить эти настройки можно только с помощью специальной функции iFh, которая выполняет полную дезактивацию драйвера и возвращает в регистрах es:bx (возможно es:dx) содержимое вектора 33h. Дезактивированный драйвер остается в оперативной памяти. После этого задача может устанавливать свой драйвер или использовать вектор ззь для других целей. Перед выходом из задачи работа драйвера восстанавливается с помощью функции 20Ь, которая не требует задания входных параметров.

В некоторых случаях может понадобиться сохранить текущие настройки драйвера перед их изменением и спустя некоторое время восстановить первоначальные значения. Все внутренние переменные и счетчики хранятся в специальном буфере состояния драйвера. Порядок действий при сохранении и восстановлении содержимого этого буфера следующий. С помощью функции 15Ь задача определяет размер буфера состояния, выделяет соответствующее пространство оперативной памяти и помещает адрес его начала в регистры es:dx. После этого она запрашивает выполнение функции 1бb, которая сохраняет текущее состояние. Теперь можно изменять текущие установки драйвера мыши.

Для восстановления исходного состояния адрес буфера, в котором оно записано, надо поместить в регистры es:dx и обратиться к драйверу с запросом 17b.

Такова общая характеристика базового набора функций, выполняемых драйвером мыши. Теперь мы переходим к рассмотрению способов программирования работы с ним в режимах VESA.

 

Предварительные действия

В данном разделе описана настройка драйвера и задачи на совместную работу. Выполняемые при этом действия не зависят от того, как задача взаимодействует с драйвером, — периодически обращаясь к нему, или в режиме прерываний. Они заключаются в инициализации драйвера, установке границ рабочего поля и исходной позиции курсора. Дополнительно может быть выбрана чувствительность драйвера и курсора к перемещениям мыши. Настройка выполняется после установки видеорежима и получения его характеристик.

Новое макроопределение

В примере 2.12 главы 2 были описаны макроопределения PushReg и PopReg, которые неоднократно использовались в примерах подпрограмм. Добавим к ним новое макроопределение, формирующее команды запроса функций драйвера мыши. Оно почти не сокращает текст программы, но делает его более наглядным и понятным. Описание макроопределения приведено в примере 6.7, оно должно располагаться перед основным текстом программы.

Пример 6.7. Макроопределение для обращений к драйверу мыши

Mouse macro fun ; номер функции задает параметр fun
mov ax, fun&h ; номер функции помещается в ах
int 33h ; обращение к драйверу
endm ; конец макроопределения.

Макровызов этого определения имеет вид Mouse fun, где вместо fun указывается шестнадцатеричный номер вызываемой функции без буквы ь в конце. Обнаружив вызов, Макроассемблер находит одноименное определение, обрабатывает его и включает в задачу две команды. Первая из них пересылает в регистр ах указанный в макровызове код fun, к которому добавляется буква b. Вторая команда int ззь выполняет обращение в драйверу мыши. Например, на месте макровызова Mouse 21 в тексте задачи окажутся две следующие команды:

mov ax, 21h ; запись в ах кода 21h
int 33h ; обращение к драйверу мыши

Замечание
Обратите внимание на то, что макровызов Mouse 2lh ассемблер воспримет как ошибку. Поэтому если вы предпочитаете указывать букву h после кода, то во второй строке примера 6.7 после слова fun надо убрать символы &h, которые вызывают добавление буквы h к коду функции.

Инициализация драйвера нужна для того, чтобы ликвидировать те изменения значений его внутренних переменных, которые могли оставить после себя другие задачи. Если по каким-то причинам эти изменения надо сохранить, то перед инициализацией производится сохранение буфера состояния драйвера, о чем говорилось в конце предыдущего раздела.

Функция Mouse о выполняет инициализацию драйвера и возвращает дополнительные данные в регистрах ах и bх.

Если драйвер мыши отсутствует в оперативной памяти, то регистр ах очищен. Это может означать либо отсутствие соответствующего файла в
autoexec.bat или в config.sys. либо отсутствие или неисправность мыши.

В процессе установки драйвер анализирует наличие и тип мыши, и если работа с ней невозможна, то установка не выполняется.

Если 15 регистре ах находится код OFFFFS-I, то инициализация выполнена успешно. В таком случае в регистре bх указано количество имеющихся у мыши кнопок.

После исполнения запроса Mouse 0 желательно проверить содержимое регистров ах и bх. Если драйвер отсутствует, то дальнейшее выполнение программы невозможно или для управления задачей должна использоваться клавиатура. Аналогично, если задача рассчитана на работу с тремя кнопками, а у мыши их только две, то придется либо прервать выполнение задачи, либо настроить ее на работу только с двумя кнопками.

Функция Mouse 21 аналогична функции Mouse 0, но при ее исполнении не производится аппаратный сброс мыши и не изменяются значения переменных, зависящих от ее технических характеристик. В большинстве случаев это различие не принципиально.

Пределы перемещения и исходная позиция

При работе в режимах VESA основное назначение драйвера заключается в отслеживании текущей позиции мыши и ее преобразовании в координаты курсора на экране. Для того чтобы это преобразование было корректным, после инициализации драйвера надо установить размер рабочего поля, указав его границы по горизонтали и вертикали.

Функции Mouse 7 и Mouse 8 передают драйверу предельные значения координат по горизонтали и вертикали. Миниматьное значение координат х (для Mouse 7) или у (для Mouse б) помещаются в регистр сх, а максимальное значение — в dx. Выходные параметры у обеих функций отсутствуют.

Если рабочее поле занимает весь экран, то минимальные значения обеих координат равны нулю (xmin = Ymin = о), а максимальные зависят от разрешающей способности видеорежима, ПОЭТОМУ Xmax = horsize, a Ymax = versize. Например,для режима VESA 101h Xmax = 640, a Ymax = 480.

После установки границ рабочего поля задается исходная позиция курсора. Его конкретное расположение может быть произвольным, но обычно курсор помещают в центр экрана.

Функция Mouse 4 перемещает курсор в заданную позицию. Перед обращением к драйверу в регистры сх и dx помещаются значения координат х и Y. Выходные параметры у функции отсутствуют.

Здесь имеется в виду тот курсор, который обычно рисует драйвер мыши. Как уже говорилось, при установке режимов VESA драйвер не может
работать с курсором. Поэтому при выполнении данной функции указанная позиция просто фиксируется в счетчиках драйвера, содержащих текущие координаты.

Для перемещения курсора в центр экрана значения координат составляют х = horsize/2, Y = versize/2. После установки этих величин можно нарисовать изображение курсора на экране.

Напомним, что при работе в стандартных видеорежимах IBM драйвер автоматически определяет границы рабочего поля и принудительно помещает изображенис курсора в центр экрана (если курсор включен).

Новые переменные

При выполнении подготовительных действий надо настроить не только драйвер, но и задачу. В процессе выполнения задачи будет неоднократно анализироваться перемещение мыши и состояние ее кнопок. Для того чтобы анализ был возможен, в разделе данных программы должны быть зарезервированы перечисленные в примере 6.8 переменные.

Пример 6.8. Переменные, используемые при работе с мышью

Winpnt dw 2 ; окно видеопамяти, в котором расположен курсор Offspnt dw 22848 смещение изображения курсора в этом окне Xpointer dw 320 текущая X координата курсора (немер столбца)
Ypointer dw 240 текущая Y координата курсора (номер строки)
Mstatus db 0 текущее состояние манипулятора "мышь"
LBevent db 0 изменение состояния левой кнопки
RBevent db 0 изменение состояния правой кнопки

В переменных winpnt и offspnt хранится адрес видеопамяти для левой верхней точки изображения курсора, они уже использовались в примерах 6.4, 6.5 и 6.6 данной главы. Переменные Xpointer и Ypointer содержат тот же адрес, но представленный в виде номеров строки и столбца. В примере 6.8 указаны их исходные значения для режима VESA ioih при условии, что курсор находится в центре экрана. Вычисления значений этих четырех переменных производится в примере 6.9.

Три последние переменные примера 6.8 имеют размер байта. В исходном состоянии они должны быть очищены, что и делается при их описании. Mstatus содержит данные о текущем состоянии манипулятора, a LBevent и RBevent — коды конкретного состояния левой (LB) и правой (RB) кнопок мыши. Как формируются текущие значения этих переменных, показано в примере 6.12.

Выполнение настройки. Способ выполнения всех описанных действий иллюстрирует пример 6.9. Приведенный в нем фрагмент программы должен выполняться в процессе подготовительных действий, но только после установки видеорежима и получения его характеристик.

Пример 6.9. Настройка драйвера, задачи и первый вывод курсора

mouse 0 инициализация драйвера
; !! здесь желательно проверить содержимое регистров АХ и ВХ !!
хог сх, сх СХ = Xmin = Ymin = 0
rnov ax, horsize DX = Xmax = horsize
mouse 7 установка границ по горизонтали
mov dx, versize DX = Ymax = versize
mouse 8 установка границ по вертикали
mov сх, horsize СХ = horsize
shr ex, 01 центр экрана по горизонтали
shr dx, 01 центр экрана по вертикали
mouse 4 установка значений счетчика драйвера
mov Xpointer, сх Xpointer = 0,5 * horsize
mov Ypointer, dx Ypointer = 0,5 * versize
mov ax, versize AX = versize
inc ax AX = versize + i
mul ex DX:AX = (versize + 1) * horsize / 2
; В режимах direct color результат надо умножить на размер кода точки
mov Offspnt, ax сохраняем смещение рисунка курсора
mov ax, GrUnit AX = единица измерения размера окна
mul dl AX = DL * GrUnit (номер видеоокна)
add ax, Base_Win !! учитываем значение базового окна
mov Winpnt, ax сохраняем значение окна видеопамяти
call Showpnt первое построение рисунка курсора

При вычислении адреса видеопамяти по номеру строки и столбца надо учитывать размер кода точки. Пример 6.9 предназначен для выполнения в режимах PPG, когда код точки занимает 1 байт. Если ваша задача работает с режимами direct color, то результат, вычисленный командой mul, надо дополнительно умножить на размер кода точки. Подробнее об этом будет сказано при описании примера 6.13.

Важно
Команда add ax, Base_win нужна только в том случае, если задача поддерживает работу со страницами видеопамяти (см. раздел), в остальных случаях ее надо исключить из текста примера.

В зависимости от способа построения изображения курсора в последней команде примера 6.9 должна вызываться подпрограмма showpnt (см. пример 6.5) или Tgipntr (см. пример 6.4).

Чувствительность курсора и мыши

При установке драйвера по умолчанию выбирается режим работы, при котором перемещение мыши на 1 дюйм по горизонтали или вертикали вызывает перемещение курсора на 640 столбцов. или на 320 строк. Рассмотрим, как драйвер увязывает перемещения мыши и курсора.

Во внутреннем буфере драйвера имеются четыре счетчика, содержащих количество перемещений по вертикали и горизонтали. Два из них связаны с курсором, а два с мышью. Условимся обозначать их как СПК (счетчик перемещений курсора) и СПМ (счетчик перемещений мыши). Прикладные задачи могут изменять значения СПК с помощью команды Mouse 4, но значения СПМ они могут только считывать.

Драйвер пересчитывает значения СПМ в значения СПК, используя для этого специальные коэффициенты. При установке драйвера по умолчанию выбираются такие значения коэффициентов, которые вызывают изменение содержимого СПМ и СПК на 1 при каждом перемещении мыши в горизонтальном направлении, и изменение значений СПМ на 1, а СПК — на 2 при каждом перемещении мыши в вертикальном направлении. Прикладная задача или операционная система могут изменить значения коэффициентов.

Функция Mouse 1A устанавливает, a Mouse IB считывает значение коэффициента, задающего чувствительность СПМ к перемещениям мыши.

Значения коэффициентов указываются или возвращаются драйвером в регистрах bх (горизонтальное направление) и сх (вертикальное направление). Содержимое bх и сх может изменяться от 0 до 100 и интерпретируется как проценты. При задании больших значений они принудительно уменьшаются до 100 (64П).

За единицу принято 50% (код 32h), при котором содержимое СПМ изменяется на 1 при каждом перемещении мыши. Значение 100% вызывает изменение содержимого СПМ на 2 при каждом перемещении мыши. А при коэффициенте 25% оно будет изменяться на 1 при двух перемещениях мыши.

Перемещение мыши принято измерять в mickey. Перевод этого термина автору не известен, но, по сути, это величина обратная количеству точек на дюйм (Dot Per inch или DPI). У современных манипуляторов DPI = 400, соответственно 1 mickey = 1/400 дюйма или примерно 0,06 миллиметра.

Функция Mouse OF устанавливает чувствительность СПК к изменениям СПМ. Перед обращением к драйверу в регистрах сх и dx указываются значения коэффициентов для горизонтального (сх) и вертикального (dx) направлений. Эти коэффициенты указывают, на сколько единиц должно измениться значение СПМ для того, чтобы значение СПК изменилось на 8 единиц. При установке драйвера (по умолчанию) коэффициенты равны 8 для горизонтального и 16 для вертикального направлений. В результате при движении по горизонтали СПМ и СПК изменяются синхронно, а при движении по вертикали СПК изменяется в два раза медленнее, чем СПМ.


Работа в режиме опроса драйвера мыши

С манипулятором "мышь", как с большинством внешних устройств, задача может работать в режиме опроса его текущего состояния, или в режиме прерываний. Принципиальное различие состоит в том, как задача "узнает" об изменении состояния мыши. В первом случае она определяет это самостоятельно, а во втором драйвер "обращает ее внимание" на изменение состояния мыши. Названные режимы обычно дополняют друг друга.

Режим опроса программируется проще, чем режим прерываний, поскольку выполняемые задачей действия не зависят от внешних факторов. Выполнение любого нового действия задача начинает только после завершения предыдущего, что исключает "параллельное" выполнение нескольких разных действий. Именно по этой причине автор выбрал режим опроса для описания способов программирования работы с мышью. Кроме того, приведенные ниже примеры применимы и при работе в режиме прерываний.

В данном разделе описано все, что необходимо для составления завершенной задачи, способной перемешать изображение курсора по экрану и реагировать на нажатие кнопок мыши. Результат можно использовать как основу или как "испытательный полигон" при разработке более сложных и полезных задач и отладке подпрограмм различного назначения.


Управляющий алгоритм для режима опроса

Структура задачи

Прежде всего, давайте уточним некоторые общие вопросы. В структуре задачи, составленной для работы в режиме опроса, можно выделить следующие основные компоненты.

Подготовительные действия. К ним относятся: установка и определение характеристик видеорежима, вычисление значений используемых переменных, резервирование необходимого пространства оперативной памяти, перехват векторов прерываний, настройка драйвера мыши, вывод заставки на экран и пр.

Управляющий алгоритм. В зависимости от конкретных действий оператора инициирует выполнение тех или иных подпрограмм, входящих в состав задачи.

Набор подпрограмм, вызываемых управляющим алгоритмом. Их состав и выполняемые действия зависят от назначения конкретной задачи. Они могут, например, строить рисунки, вводить и выводить текстовые сообщения, перемещать курсор, поддерживать работу с меню и выполнять множество других действий.

Пример управляющего алгоритма. Подготовительные действия и подпрограммы неоднократно обсуждачись и еще будут обсуждаться в тексте книги.

Зцесь нас интересует управляющий алгоритм, который увязывает разрозненные подпрограммы в единое целое. Он представляет собой бесконечно повторяющийся цикл опроса и анализа текущего состояния клавиатуры и драйвера мыши, и вызова подпрограмм, в зависимости от введенных символов или изменения состояния мыши. Для выхода из цикла предназначена специальная команда, исполнение которой приводит к завершению работы задачи и возврату в DOS. Простейший вариант управляющего алгоритма показан в примере 6.10.

Пример 6.10. Управляющий алгоритм для режима опроса

General : mov ah, 01 код функции опроса состояния клавиш
int 16h опрос состояния клавиш
jnz Preskey -> была нажата одна из клавиш
call Statms опрос текущего состояния мыши
xor bh, bh очистка старшего байта регистра bx
Shi bx, 01 удвоение кода состояния
call ChoiceL[bx] обработка состояний левой кнопки
mov Ы, RBevent Ы = код состояния правой кнопки
xor bh, bh очистка старшего байта регистра bx
shl bx, 01 удвоение кода состояния
call ChoiceRfbx] обработка состояний правой кнопки
jmp short General возврат на начало цикла
Preskey: xor ah, ah код функции чтения символа
int 16h чтение введенного символа
cmp ah, 31h введена буква N или п ?
jne Pk_l -> нет
lea si, prmptOl si = адрес подсказки оператору
call Outinf вывод подсказки и ввод ответа
jmp short General возврат на начало цикла
Pk 1: cmp ah, 2Dh введена буква X или х ?
jne Pk_2 -> нет
jmp eoprg переход на завершение задачи
Pk 2: mov bx, 01 bx = 1 (шаг перемещения курсора)
cmp ah, 4Dh символ "стрелка вправо" ?
jne Pk__3 -> нет
movhor : call mothor перемещение курсора по горизонтали
jmp short General возврат на начало цикла
Pk 3: cmp ah, 50h символ "стрелка вниз" ?
jne Pk 4 -> нет
mower: call motver перемещение курсора по вертикали
jmp short General возврат на начало цикла
Pk 4: neg bx bx = -1 (шаг перемещения курсора)
cmp ah, 4Bh символ "стрелка влево" ?
je movhor -> да
сшр ah, 48h ; символ "стрелка вверх" ? je mower ; ~> яэ.
; Здесь можно продолжить анализ введенного символа
jmp General ; ! ! возврат на начало цикла

Текст примера 6.10 делится на две основные части. Первая из них начинается с команды, имеющей метку General, а вторая — с команды, имеющей метку Preskey. Первая часть алгоритма выполняется до тех пор, пока оператор не нажмет на любую клавишу. В этой части производится опрос состояния клавиатуры и драйвера мыши и обработка событий, связанных с изменением состояния мыши. Мы не будем здесь обсуждать, как это делается, поскольку способы опроса и обработки возможных состояний манипулятора "мышь" подробно обсуждаются в следующем разделе. Пока читатель может поверить на слово, что если оператор ничего не делает с клавиатурой и мышью, то задача только опрашивает состояние клавиатуры и мыши, не выполняя никаких других действий.

Роль клавиатуры зависит от формы диалога оператора с задачей. В тех случаях, когда применяются командные строки и не поддерживается работа с меню, клавиатура является основным органом управления. Если же задача поддерживает работу с меню, то клавиатура имеет вспомогательное значение и используется только в специальных случаях, например, для ввода текста или спецификаций создаваемых файлов. Однако и в этих случаях клавиатура не является основным средством для ввода данных.

Техника работы с клавиатурой обсуждалась в разделе, там же описана функция о прерывания int I6h, выполняющая ожидание ввода и чтение кода символа из буфера клавиатуры. В данном случае нам нужна еще одна функция 01 прерывания int 16, которая не ждет ввода символа, а только проверяет состояние буфера клавиатуры. Если буфер пуст, то при возврате из BIOS установлен Z-разряд регистра флагов (признак нуля), а если в буфере находится код символа, то Z-разряд будет очищен.

В примере 6.10 третья команда (jnz Preskey) выполнит переход на метку Preskey, если буфер клавиатуры содержит код символа. Его копия находится в регистре ах, но прежде чем начинать анализ введенного символа, надо учесть следующее обстоятельство. Функция 01 прерывания int I6h оставляет символ в буфере и при следующем опросе клавиатуры он будет прочитан повторно. Поэтому буфер надо принудительно очистить, что и делают первые две команды, расположенные во второй части примера 6.10 (после метки Preskey).

Расшифровка и исполнение команд

После чтения введенного символа в регистре ah находится scan code, а в регистре al — код ASCII, если таковой существует. Обычно для анализа используется scan code, но если надо различать символы верхнего и нижнего (например, А и а), или латинского и русского (например, А и а) регистров, то используется код ASCII. В примере 6.10 анализируется scan code шести разных символов. Вы можете изменить или дополнить набор анализируемых кодов, только не забывайте, что независимо от результата их анализа необходимо вернуться на начало управляющего алгоритма. Для напоминания последняя команда примера выполняет такой переход, а комментарий к ней начинается с двух восклицательных знаков.

Ввод спецификации файла

В примере 6.10 при опознании кода буквы N или п выводится подсказка оператору и вводится спецификация файла. Текст подсказки должен быть описан в разделе данных задачи, например:

prmptOl db 'Введите спецификацию файла >',0

Ее адрес загружается в регистр si, после чего вызывается подпрограмма outinf, описанная в (см. примере 5.29). Введенная спецификация файла находится в буфере Linbuf.

Ввод спецификации это только начало, для работы с файлом его надо открыть, а способ открытия зависит от того, как будет использоваться файл -только для чтения, только для записи или для того и другого. Для открытия файла можно использовать специальную команду (букву) или объединить ввод спецификации и открытие файла в одной подпрограмме. Это удобно потому, что только при открытии проверяется правильность введенной спецификации (существование указанного файла). Для дальнейших манипуляций с файлом так же понадобятся дополнительные команды (буквы), специальные подпрограммы или то и другое. Решение подобных вопросов мы оставляем на усмотрение читателя.

Завершение задачи

Для завершения выполнения задачи и возврата в DOS оператор должен ввести букву х или х. Следует отметить, что для этой цели она используется во многих прикладных задачах для DOS и соответствует команде exit. Иногда для той же цели используется одновременное нажатие (сочетание) клавиш <Alt> и <Х> (<AIt>+<X>). Для опознания такого сочетания в примере 6.10 команду cmp ah, 2Dh надо заменить командой cmp ax, 2D00h.

Расшифровав код 2Dh, управляющий алгоритм выполняет переход на метку
eoprg (end of programm), начиная с которой выполняются заключительные заключительные действия. Они заканчиваются двумя командами, приведенными в примере 6.11.

Пример 6.11. Завершение работы задачи и выход в DOS

eoprg: ; Сначала выполняются заключительные действия, а потом: mov ah, 4Ch ; код запроса "завершение задачи"
int 21h ; обращение к DOS без возврата в задачу

После выполнения последних двух команд примера 6.11 DOS снимает задачу и освобождает занимаемую ей оперативную память общего назначения. Перед выходом надо ликвидировать все внесенные задачей изменения, которые могут нарушить нормальную работу DOS или других задач. В частности, если задача перехватывала векторы прерываний, то надо восстановить их исходные значения. Например, для восстановления исходного состояния вектора ich после метки eoprg надо вставить текст примера 5.26. Если для работы с расширенной памятью использовался драйвер EMM, то надо обратиться к нему для освобождения выделенной памяти. Работа с драйвером EMM будет описана в приложении Б.

Использование стрелок

В примере 6.10 показано, как можно управлять перемещениями указателя мыши с помощью клавиш, на которых нарисованы стрелки, направленные влево, вправо, вверх и вниз. В зависимости от того, какая клавиша нажата, выбирается одна из двух подпрограмм — mother для перемещения указателя мыши по горизонтали или motver для его перемещения по вертикали. Предварительно в регистр bх записывается положительная (перемещение влево или вниз) или отрицательная (перемещение вправо или вверх) единица. Текст обеих подпрограмм приведен в примере 6.13.

Таким образом, в описанном варианте управляющего алгоритма клавиатура используется для управления процессом выполнения задачи. Теперь нам предстоит разобраться в том, какую роль играет мышь.


Формирование кодов событий

В примере 6.10 четвертая команда вызывает подпрограмму statins, текст которой приведен в примере 6.12. Она формирует и передает задаче данные о событиях, связанных с манипулятором "мышь". Выполняемые в ней действия оформлены в виде подпрограммы, для того чтобы их можно было использовать не только в управляющем алгоритме, но и в других случаях, когда нужны данные о состоянии мыши.

Прежде чем описывать подпрограмму statms, обсудим те соображения, которыми руководствовался автор при ее разработке.

Функции драйвера

Драйвер поддерживает три функции, которые позволяют получить разные данные о состоянии мыши.

Функция Mouse 3 возвращает в регистрах bх, сх и dx текущие значения счетчиков координат на экране и состояние кнопок. В сх находится номер столбца (координата х), а в dx — номер строки (координата Y). Три младших разряда регистра bх отражают состояние кнопок. Левой кнопке соответствует нулевой разряд, правой — первый и средней — второй. Если кнопка нажата, то соответствующий ей разряд установлен, а если не нажата, то очищен. Некоторые драйверы позволяют в процессе установки переопределить правую и левую кнопки, это предусмотрено специально для людей, которым удобнее работать левой рукой. В таком случае нулевой разряд регистра bх отражает состояние правой кнопки, а первый разряд — левой.

Функция Mouse 5 возвращает данные о количестве нажатий на одну из кнопок и значение координат в момент последнего нажатия. Перед ее вызовом в регистре bх указывается номер кнопки: bх=о для левой, bx=i для правой и bх=2 для средней. В том же регистре (bх) драйвер возвращает количество нажатий на указанную кнопку, произошедших после последнего опроса ее состояния. Кроме того, в регистре ах возвращается состояние всех кнопок в том же виде, в каком эти данные возвращала функция Mouse 3 в регистре bх. При этом в регистрах сх и dx находятся значения координат в момент последнего нажатия на указанную кнопку.

Функция Mouse 6 отличается от Mouse 5 только тем, что возвращает информацию не о нажатии, а об отпускании указанной кнопки.

Функция Mouse 3 применяется наиболее часто. Функции Mouse 5 и Mouse б нужны в специальных случаях и, вообще говоря, при программировании работы с мышью без них можно обойтись.

Взаимосвязь событий

В технической документации любые изменения в состоянии мыши принято называть событиями (event). Функция Mouse з возвращает данные об элементарных событиях, в общем случае этого не достаточно для выполнения задачей конкретных действий.

Предположим, что в результате опроса драйвера установлен факт нажатия левой кнопки. Сам по себе этот факт мало что говорит, важно знать, изменилось ее состояние или нет. Если кнопка уже была нажата, то ее состояние не изменилось. Аналогичные рассуждения применимы и в случае, если кнопка не нажата. Следовательно, для оценки изменения состояния кнопки надо учитывать результаты предыдущего и текущего опросов драйвера.

Кроме того, кнопка могла быть нажата при неподвижной мыши или при ее перемещении. На практике движение мыши при нажатой левой кнопке обычно используется для перемещения рисунка на экране. А нажатие на левую кнопку при отсутствии перемещения мыши может применяться, например, для выбора элемента меню. Следовательно, при анализе произошедшего события надо учитывать не только текущее и предшествующее состояние кнопок, но и фактор движения мыши.

Каждый из перечисленных факторов может принимать одно из двух взаимоисключающих значений — кнопка нажата или не нажата, мышь движется или неподвижна. Всего возможны восемь событий, перечисленных в табл. 6.2.

В табл. 6.2 перечисленны элементарные события. Из них могут складываться более сложные события, например, во многих случаях применяется быстрое двухкратное нажатие (double-click) на кнопку. Его можно описать как повторное нажатие на одну и ту же кнопку в течение короткого отрезка времени при отсутствии перемещения мыши. Для распознания такого события задача должна спустя заданное время повторно опросить драйвер и убедиться в том, что в обоих случаях был получен код 2.

Таблица 6.2. Перечень событий для одной кнопки мыши

Код события

Движение мыши

Старое состояние

Новое состояние

0

Неподвижна

Не нажата

Не нажата

1

Неподвижна

Не нажата

Нажата

2

Неподвижна

Нажата

Не нажата

3

Неподвижна

Нажата

Нажата

4

Движется

Не нажата

Не нажата

5

Движется

Не нажата

Нажата

6

Движется

Нажата

Не нажата

7

Движется

Нажата

Нажата

Вопрос о том, состояние каких кнопок надо анализировать в задаче, решает программист. На практике основной является левая кнопка, с ней ассоциируется большинство выполняемых действий. Правая используется реже и имеет вспомогательное значение. Одновременное нажатие обеих кнопок обычно не применяется. Средней кнопки у мыши может просто не быть, поэтому она не используется в большинстве программ.

Подпрограмма Statms опрашивает состояние мыши с помощью функции Mouse 3 и формирует коды событий в соответствии с табл. 6.2.

В качестве параметров подпрограмма, приведенная в примере 6.12, использует переменные, описанные в примере 6.8. Значения входных параметров содержат переменные xpointer, YPointer и Mstatus. Выходные параметры помещаются в те же переменные, кроме того, код события для правой кнопки возвращается в переменной RBevent, а для левой — в LBevent.

Пример 6.12. Формирование кодов событий для двух кнопок

Statms : Mouse 3 опрос текущего состояния мыши
xor al, al признак отсутствия движения
стар XPointer, ex координата X изменилась ?
jne SM 1 -> да
cmp YPointer, dx координата Y изменилась ?
je SM_2 -> нет, мышь не перемещалась
^44 / /рограммирование SVUA-графики для IbM
SM 1: or al, 04 признак перемещения мыши
mov XPointer, ex сохраняем новое значение X
mov Ypointer, dx сохраняем новое значение Y
SM 2: mov bh, Ы bh = новое состояние кнопок
xchg Mstatus, bh переставляем байты Ы и Mstatus
push bx сохраняем регистр bx
; Формирование кода события для правой кнопки
and bx, 0202h выделяем разряды состояния кнопки
shr Ы, 01 изменяем код нового состояния
or Ы, bh двумя командами or формируем
or bl, al в Ы код события для правой кнопки
mov RBevent, Ы сохраняем код события в RBevent
; Формирование кода события для левой кнопки
pop bx восстанавливаем регистр bx
and bx, 0101 выделяем разряды состояния кнопки
shl bh, 01 изменяем код старого состояния
or bl, bh и двумя командами or формируем
or bl, al в Ы код события для левой кнопки
mov LBevent, bl сохраняем код события в LBevent
eosub: ret возврат из подпрограммы

После всего сказанного текст примера 6.12 не требует оcобых пояснений. Напомним только, что код состояния формируется в соответствии с табл. 6.2, его значение может изменяться от 0 до 7. Нулевой разряд кода соответствует текущему состоянию кнопки, первый — предыдущему состоянию, а второй разряд указывает перемещение мыши.
Метка eosub, указанная перед командой ret, не имеет отношения к тексту примера. Просто в дальнейшем нам понадобится имя подпрограммы, состоящей из единственной команды ret.

Выбор исполняющей подпрограммы. Вызывающий модуль анализирует полученный код события и выполняет соответствующие действия. В нашем случае вызывающим модулем является управляющий алгоритм.

В управляющем алгоритме выбор подпрограммы, выполняющей нужные действия, осуществляет переключатель (switch). Он применяется во многих языках программирования для выбора одного из нескольких вариантов выполняемых действий. В этом случае код события используется в качестве индекса при выборе одного из адресов, указанных в таблице переходов (transfer table), которая является списком адресов подпрограмм. Преимущество переключателя заключается в том, что количество действий, необходимых для выбора нужного адреса, не зависит от размера списка.

В управляющем алгоритме переключатель используется дважды — сначала для обработки событий, связанных с левой, а затем с правой кнопкой. В обоих случаях код события помещается в регистр bх и удваивается, поскольку списки адресов состоят из слов. После этого команда call вызывает одну из подпрограмм перечисленных в списках choiceL или choiceR. При выполнении этой команды адрес начала списка (значение меток ChoiceL или ChoiceR) суммируется с содержимым регистра bх, в стеке формируется адрес возврата и управление передается нужной подпрограмме.

Обратите внимание на то, что сразу после возвращения из подпрограммы statms код состояния левой кнопки находится в регистре bl. Поэтому при обработке событий левой кнопки нет необходимости копировать его в регистр bl из LBevent. Однако при обработке событий правой кнопки в регистр bl копируется содержимое RBevent.
Списки имен подпрограмм, обрабатывающих события, связанные с левой и правой кнопками, должны быть описаны в разделе данных программы, например, следующим способом:

ChoiceL dw eosub, namel, name2, eosub, motion, паmеЗ, name4, name5
ChoiceR dw eosub, eoprg, патеб, eosub, eosub, name7, nameS, name9

Действия, выполняемые при возникновении каждого из событий, зависят от назначения программы и фантазии программиста. Заведомо очевидны лишь два случая.

Если мышь неподвижна и состояние кнопок не изменилось (коды 0 и 3), то просто ничего не произошло. Этим кодам в обоих списках соответствует имя eosub, которое в примере 6.12 указано перед командой ret.

Если мышь движется и кнопки не нажаты (код 4), то надо просто перемешать курсор. Поэтому на пятом месте в списке choiceL указано имя подпрограммы motion, выполняющей перемещение курсора, ее подробное описание приведено в следующем разделе.

Во втором слове массива choiceR указано имя команды eoprg, начиная с которой выполняются завершающие действия (см. пример 6.11). При нажатии на правую кнопку мыши выполнение задачи прекратится и произойдет возврат в DOS.

Если при подготовке исходного текста программы вы еще не решили, как обрабатывать остальные события, то просто замените имена name1 - name9 именем eosub. Управляющий алгоритм будет игнорировать события, для обработки которых не указаны специальные подпрограммы.

Идентификация графических объектов

Кнопки манипулятора "мышь" используются для управления процессом выполнения задач. Выбор выполняемых действий зависит от объекта, на который указывает курсор. Например, если курсор указывает на элемент меню, то при однократном нажатии на кнопку (обычно левую) этот элемент становится активным и выполняются связанные с ним действия. Нажатие на кнопку во время движения мыши может вызывать перемещение или изменение размеров объекта, на который указывает курсор. При работе в режиме редактирования в этом случае могут изменяться размеры и форма создаваемого объекта.

Перечень действий, выполняемых при нажатии на кнопку мыши, можно продолжать долго. Главное, на что следует обратить внимание, заключается в том, что процедуры, реагирующие на изменение состояния кнопок, как правило, должны идентифицировать расположенный под курсором объект. А как уже говорилось в разделе, для этого задача должна формировать список графических объектов, находящихся на экране в данный момент времени и поддерживать работу с этим списком.

Замечание
Описание процедур, реагирующих на изменение состояния кнопок, выходит за рамки данной книги, поэтому мы ограничимся следующим советом. Начните с составления простой подпрограммы, которая при нажатии левой кнопки проверяет нахождение курсора в прямоугольной области с заданными границами Xmin, Xmax, Ymin, Ymax. Если курсор находится в ней, то подпрограмма завершает выполнение задачи. На экране этим координатам может соответствовать прямоугольник с надписью "Выход" или "Exit". Затем усложните подпрограмму сделав так, чтобы значения указанных границ выбирались из формируемого задачей списка. Следующий шаг — при нахождении объекта в списке выполняется подпрограмма, адрес которой хранится в том же элементе, в котором указаны границы объекта. Так постепенно вы создадите универсальную процедуру для обработки событий, связанных с левой кнопкой.

 

Управление перемещением курсора

Необходимость перемещения курсора возникает при обработке тех событий, которым в табл. 6.2 соответствуют коды от 4 до 7. Перемещение в чистом виде вызывает только событие с кодом 4 — мышь движется, кнопка не нажата и ее состояние не изменялось. Если же мышь движется при нажатой кнопке, то кроме перемещения курсора могут выполняться и другие действия, например "перетаскивание" объекта, на который указывает курсор.

Предварительные замечания

В зависимости от того, какая из кнопок является ведущей (обычно левая), имя подпрограммы, выполняющей перемещение курсора, располагается на пятом месте одного из списков choiceL или choiceR. Указывать это имя на пятом месте обоих списков не имеет смысла. При обработке событий с кодами от 5 до 7 такой проблемы не возникает, поскольку предполагается, что одновременное нажатие обеих кнопок в задачах не используется. Подпрограмма statms не фиксирует этот случай, поскольку состояния кнопок анализируются независимо друг от друга.

Для перемещения изображения курсора надо выполнить следующие действия в такой последовательности: восстановить исходный фон на месте старого изображения, вычислить адрес видеопамяти, соответствующий новому значению координат, и вывести изображение курсора на новом месте (одновременно сохранив исходный фон).

Перечисленные действия выполняет подпрограмма Motion, текст которой приведен в примере 6.13. Кроме нее в текст примера включены еще две подпрограммы, обращение к которым происходит из управляющего алгоритма при нажатии оператором на клавиши с рисунками стрелок, направленных влево, вправо, вверх и вниз. Подпрограмма Mothor перемещает курсор на шаг вправо или влево, a Motver — вверх или вниз. Шаг и направление перемещения задается в регистре bх, для перемещения в сторону уменьшения значений координат его содержимое должно быть отрицательным числом.

Подпрограммы Mother и Motver являются вспомогательными, основные действия выполняет Motion.

Восстановление исходного фона и построение изображения курсора было описано в разделе

Работа в режиме прерываний

Недостаток режима опроса заключается в том, что задача не узнает об изменении состояния мыши до тех пор, пока не обратится к драйверу. В некоторых случаях этот недостаток имеет принципиальное значение, и программист вынужден использовать режим прерываний.

В данном разделе нас будут интересовать те прерывания процесса выполнения задачи, которые вызывает драйвер при изменениях состояния манипулятора "мышь". Задача может разрешить или запретить драйверу прерывать процесс своего выполнения в указанных случаях. Если прерывания разрешены, то момент их возникновения зависит только от действий оператора, работающего с мышью, и никак не связан с действиями, выполняемыми задачей. То есть, как обычно, прерывания происходят по внешним, не зависящим от задачи, причинам.

Указанная особенность режима прерываний требует от программиста определенных навыков и тщательной разработки алгоритма задачи. Кроме внутренних факторов, влияющих на выполнение предусмотренных в задаче действий, приходится учитывать и внешние, а это может существенно изменить конкретную реализацию алгоритма. Мы обсудим этот вопрос на примере работы с курсором в режиме прерываний.

Состояние мыши изменяется не так уж часто, даже если она активно используется оператором. При программировании для режима прерываний, рано или поздно, но вам придется решать, чем занять задачу в паузах между изменениями состояния мыши. Если нет другого занятия, то организуется цикл ожидания действий оператора, а для этого предназначен управляющий алгоритм, описанный в

Функции драйвера

Для того чтобы при изменении состояния мыши драйвер мог прервать выполнение задачи, последняя должна с помощью специальных функций установить прерывающую подпрограмму. В данном разделе описаны варианты установки и удаления прерывающих подпрограмм, требования к их оформлению, условия вызова и данные, передаваемые драйвером.

Обычно термин "прерывающая" указывает на то, что подпрограмма вызывается при возникновении событий, независящих от выполнения основной задачи полностью или частично. В нашем случае момент вызова подпрограммы зависит от действий оператора, работающего с мышью.

Особенность описываемых ниже подпрограмм заключается в том, что в случае необходимости их может вызывать основная задача, но это уже относится к области трюков или искусства программирования.

Установка основной подпрограммы

Драйвер может обслуживать только одну основную прерывающую подпрограмму. Для ее установки задаются адрес точки входа и маска события (или событий), при каждом наступлении которого (которых) будет вызываться установленная подпрограмма.

Функция 0С (set Event Handler) предназначена для установки подпрограммы, реагирующей на события, связанные с изменением состояния мыши. Перед обращением к драйверу полный адрес точки входа указывается в регистрах es:dx, а код маски помещается в младший байт регистра сх. Состояние его разрядов (0 или 1) соответствует наличию или отсутствию следующих событий:

разряд 0 — перемещение мыши;  
разряд 1 — левая кнопка нажата; разряд 2 — левая кнопка отпущена;
разряд 3 — правая кнопка нажата; разряд 4 — правая кнопка отпущена;
разряд 5 — средняя кнопка нажата; разряд 6 — средняя кнопка отпущена.

Допустимо произвольное сочетание указанных признаков, в частности, при записи в регистр сх кода 7Fh подпрограмма будет вызываться при любых изменениях состояния мыши.

Особым случаем является очищенное состояние регистра сх при обращении к драйверу, оно запрещает обслуживание ранее установленной подпрограммы. При изменении состояния мыши драйвер проверяет, указан код произошедшего события в маске или нет. Ни одно из событий не имеет код 0, поэтому при очищенной маске вызов подпрограммы исключается. Для очистки маски достаточно выполнить следующие две команды:

хоr сх, сх ; очистка регистра сх
Mouse ОС ; запрос функции ОС

Если ваша задача устанавливала прерывающую подпрограмму, то не забудьте выполнить эти две команды перед возвратом в DOS. В противном случае при первом же наступлении соответствующего события драйвер обратится к тому участку памяти, в котором подпрограммы уже нет, и возникнет аварийная ситуация.

При исполнении функции ос драйвер просто копирует содержимое регистров сх, dx и es в три слова внутреннего буфера, не выполняя никаких проверок. Таким простым способом исключается возможность установки нескольких подпрограмм. В любой момент времени драйвер обслуживает только ту подпрограмму, которая была установлена последней и "обмануть" его невозможно.

Если задача должна реагировать на несколько разных событий, то в маске надо установить соответствующие разряды, а в прерывающей подпрограмме уточнять причину (или причины) прерывания. Другими словами, иногда приходится сочетать режимы прерываний и опроса состояния драйвера мыши.

Функция 14 (Exchange Event Handier) выполняет те же действия, что и функция ос, кроме того, драйвер возвращает в регистрах es:dx адрес ранее установленной подпрограммы, а в регистре сх маску событий, на которые она реагировала. Эта функция может быть полезна в тех случаях, когда по каким-то причинам надо заменить прерывающую подпрограмму, а через некоторое время восстановить ее работоспособность.

Установка альтернативных подпрограмм

Учитывая, что основная подпрограмма может быть только одна, разработчики предусмотрели возможность установки трех альтернативных подпрограмм. Их принципиальное отличие в том, что в момент изменения состояния мыши должна быть нажата хотя бы одна из трех специальных клавиш.

Функция 18 (set Alternate Event Handier) устанавливает альтернативную подпрограмму, реагирующую на изменения состояний мыши при условии, что нажата одна из трех клавиш — <Alt>, <Ctrl>, <Shift>, или любая их комбинация. Перед обращением к драйверу полный адрес подпрограммы указывается в регистрах es:dx, а код маски помещается в младший байт регистра сх. Состояние его разрядов (0 или 1) соответствует наличию или отсутствию следующих событий:

разряд 0 — перемещение мыши; разряд 1 — левая кнопка нажата;
разряд 2 — левая кнопка отпущена; разряд 3 — правая кнопка нажата;
разряд 4 — правая кнопка отпущена; разряд 5 — нажата клавиша <Shift>;
разряд 6 — нажата клавиша <Ctrl>; разряд 7 — нажата клавиша <Alt>.

Три старших разряда маски отведены для указания одной из клавиш или их сочетания. Драйвер анализирует их не независимо друг от друга, а как трехразрядный код. Поэтому установка любых двух старших разрядов соответствует одновременному нажатию двух клавиш, а установка трех разрядов одновременному нажатию трех клавиш <Alt>+<Ctrl>+<Shift>.

Указание в коде маски, по крайней мере, одной клавиши обязательно. Если очищены все три старших разряда кода маски, то драйвер отвергает попытку установить подпрограмму. При этом он возвращает в регистре ах код OFFFFh.

Коды событий, связанных с изменением состояния мыши, занимают в маске пять младших разрядов, места для средней кнопки не хватает. Если установлены все младшие разряды, то подпрограмма будет вызываться при любом изменении состояния мыши, если одновременно нажата клавиша, указанная в старших разрядах кода маски.

Например, если код маски 9Fh, то подпрограмма будет вызываться, если нажата клавиша <Alt> и мышь перемещается, либо изменяется состояние ее левой или правой кнопок.

Особым случаем является очищенное состояние пяти младших разрядов кода маски, оно запрещает обслуживание установленной альтернативной подпрограммы. Обращаем ваше внимание на то, что должны быть очищены только пять младших разрядов. Например, если подпрограмма работала с клавишей <АН>, то для ее запрета выполняются две следующие команды:

mov сх, 8Oh ; код маски при работе с клавишей <Alt> Mouse 18 ; запрос функции 18

Перед завершением задачи надо обязательно запретить обслуживание как основной, так и альтернативных подпрограмм, если они были установлены.

Функция 19 (Query Alternate Event Handler) проверяет факт установки альтернативной подпрограммы, маска для поиска указывается в регистре сх. Если подпрограмма была установлена, то драйвер возвращает ее адрес в регистрах es:dx, в противном случае он помещает в регистрах код OFFFFh.

Ошибка в драйвере Mitsumi

Автор исследовал три драйвера мыши, разработанные фирмами Microsoft, Genius и Mitsumi. В последнем из них допущена трудно диагностируемая ошибка, она заключается в следующем. Если основная и альтернативная подпрограммы реагируют на одно и то же изменение состояния мыши, то драйвер отдает предпочтение основной и не вызывает альтернативную подпрограмму. В частности, если основная подпрограмма реагирует на любые изменения состояния мыши (код маски 7Fh), то альтернативные подпрограммы не будут вызваны драйвером ни при каких условиях. На этикетке установочной дискеты, прилагаемой к мыши, написано MITSUMI Mouse Driver Version 6.0.

Вызов подпрограммы драйвером

Если произошло событие, код которого указан в маске, то драйвер вызывает установленную подпрограмму. При входе в нее в регистрах bx, сх и dx находятся данные о состоянии кнопок и значениях координат, представленные в том виде, в котором они получаются после выполнения функции 3.

Три младших разряда регистра bх указывают состояние кнопок. Если разряд установлен, то соответствующая ему кнопка нажата, а если очищен, то не нажата. Разряды 0, 1, 2 соответствуют левой, правой и средней кнопкам.

Дополнительно в регистре ах находится код события, явившегося причиной вызова подпрограммы. В этом регистре может быть установлен только один из указанных в коде маски разрядов (см. описание функции ос). Например если подпрограмма вызывается при любом изменении состояния мыши (код маски 7Fh), то будет установлен один из 7 младших разрядов регистра ах.

Предположим, что оператор перемещает мышь при нажатой левой кнопке. В момент начала перемещения произойдут два вызова подпрограммы. В одном случае в регистре ах будет находиться код 1, а во втором 2. Какой из двух вызовов произойдет первым, зависит от того, что раньше сделал оператор — нажал левую кнопку или начал двигать мышь. В дальнейшем состояние левой кнопки не меняется до того момента, пока она не будет отпущена, поэтому при вызовах подпрограммы в регистре ах будет находиться код 1. При отпускании левой кнопки в регистре ах окажется код 4.

Если подпрограмма реагирует только на перемещение курсора (код маски 1), то состояние кнопок указывает код, находящийся в регистре bх.

В конце

Примеры прерывающих подпрограмм

Некоторые особенности программирования для режима прерываний были описаны в разделе на примере обработки аппаратных прерываний от таймера. Программирование работы с мышью имеет свои специфические особенности. Например, при обслуживании двухкнопочной мыши драйвер различает пять событий, но для их обработки он может вызвать только одну подпрограмму. Поэтому мы сначала рассмотрим подпрограмму, реагирующую только на одно событие — перемещение курсора, а затем обсудим, что делать с кнопками.

Способы перемещения курсора

Основные действия, выполняющие перемещение курсора, были описаны в