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



 

Текстовый курсор в графическом режиме

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

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

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

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

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

Способ построения курсора

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

Двухадресная команда хоr вычисляет логическую функцию exclusive OR исключающее ИЛИ), ее операндами являются источник и приемник, результат помещается в приемник. При выполнении инструкции хоr микропроцессор запрещает перенос единиц переполнения из младших разрядов старшие и производит поразрядное сложение операндов. Результат выполнения операции для одного бита показан в табл. 5.1.

Таблица 5.1. Схема выполнения операции хоr

Состояние бита источника 0 0 1 1
Состояние бита приемника 0 1 0 1
Состояние бита результата 0 1 1 0

Oбратите внимание на последний столбец таблицы. Если состояние всех разрядов у приемника и источника одинаково, то в результате получится нуль, т. е. приемник будет очищен. Это свойство команды хоr мы неоднократно использовали в примерах для очистки регистров. Здесь нас интересует ее другое свойство.

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

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

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

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

Подпрограмма рассчитана на то, что для вывода символов на экран используется знакогенератор из примера 5.19. Поэтому адрес видеопамяти, соответствующий верхнему левому углу рисунка курсора, выбирается из регистра di, а код цвета фона и высоту символов задают переменные grndcoi и hsymb (см. пример 5.18). При коррекции адреса видеопамяти используется переменная bperiine, описанная в примере 2.11, она равна значению Horsize, умноженному на размер кода точки в байтах (1—4).

Пример 5.24. Подпрограмма изменения состояния текстового курсора

TglCrsr: PushReg <ax,ex,di,Cur_win>; сохранение в стеке
call Setwin ; установка исходного окна
mov al, grndcol ! i al = код цвета фона
mov ex, hsymb ex = высота символов
Tcrsr: xor es:[di], al !! изменяем код первой точки
xor es:[di+1], al !! изменяем код второй точки
add di, bperline коррекция видеоадреса
jnc @F => адрес в пределах сегмента
call Nxtwin установка следующего окна
@@: loop Tcrsr управление циклом
PopReg <Cur_win,di,ex,ax> ; восстановление из стека
ret завершение работы подпрограммы

Выполнение подпрограммы примера 5.24 начинается с сохранения в стеке тех величин, значения которых могут измениться, и установки исходного окна. После этого в регистр al копируется код цвета фона, а в сх — количество строк в символе.

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

Исходный текст примера 5.24 рассчитан на выполнение в видеорежимах PPG. Комментарий к командам, зависящим от видеорежима, начинается с двух восклицательных знаков. При работе в режимах direct color используйте варианты переменных команд, приведенные в табл. 5.2.

Таблица 5.2. Варианты переменных команд для примера 5.24

Режимы PPG
Режимы Hi-Color
Режимы True Color
mov al, grndcol
mov ax, grndcol
mov eax, grndcol
xor es:[di], al
xores:[di], ax
xor es:[di], eax
xor es:[di+1], al
xor es:[di+2], ax
xor es:[di+4], eax

Мигающий курсор

Текстовый курсор обычно мигает, т. е. его изображение периодически появляется и исчезает. Для получения эффекта мигания надо вызывать подпрограмму TglCrsr через равные промежутки времени, например через 0,5 сек, как это делают Windows и ее приложения.

При управлении курсором высокая точность измерения времени не требуется, поэтому можно использовать таймер, который "тикает" через каждые 55 миллисекунд, или 18,2 раза в секунду. Для выдержки паузы надо дождаться пока от таймера поступит нужное количество .тиков с момента начала паузы. Вопрос лишь в том, как узнать, что таймер "тикнул".

В области данных BIOS, начиная с адреса 0000:046с, зарезервировано 4 байта, содержащих 32-разрядный счетчик количества тиков. BIOS очищает счетчик при первоначальной загрузке, после чего его значение увеличивается на 1 с каждым тиком таймера. Для выдержки паузы надо запомнить исходное значение счетчика, в начале паузы, а затем время от времени сравнивать текущее значение с исходным. Пауза закончится, когда их разность достигнет нужного значения.

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

Перехват прерываний от таймера

Каждый тик таймера вызывает так называемое аппаратное прерывание. Текущий процесс вычислений приостанавливается и выполняется специальная процедура BIOS. Она обслуживает процессы, синхронизированные с таймером, и вызывает подпрограмму, адрес начала которой указан в векторе ich. Этот вектор специально выделен для нужд прикладных задач. Сразу после загрузки ПК в нем находится адрес команды iret, расположенной в ROM BIOS. Если в прикладной задаче есть подпрограмма, выполнение которой должно быть синхронизировано с таймером, то ее адрес указывается в векторе ich.

Исходное значение вектора ich надо сохранить в теле задачи. Оно нужно для того, чтобы после вашей подпрограммы можно было начать выполнение той программы (внешней по отношению к задаче), адрес которой находился в векторе ich до изменения его содержимого. Такой трюк в литературе называется "перехват вектора прерывания", он широко используется при работе прикладных задач с внешними устройствами.

Для изменения содержимого вектора надо знать его адрес. Векторы пронумерованы начиная с нуля, каждый из них занимает 4 байта оперативной памяти, а нулевой вектор расположен по адресу 0000:0000. Следовательно, умножив номер вектора на 4, мы получим его адрес в оперативной памяти. В нашем случае 4*ich = 70h (4-28 = 7-16 = 112).

В примере 5.25 приведена группа команд, выполняющих перехват вектора ich, неизвестные вам имена переменных описаны ниже в примере 5.27.

Пример 5.25. Сохранение и изменение содержимого вектора 1Ch

хоr ах, ах ; очистка регистра ах
mov CurStat, al ; запрет построения рисунка курсора
mov fs, ax ; очистка сегментного регистра fs
lea ax, cs:Timeint ах=адрес прерывающей подпрограммы
mov bx, cs bx=сегмент прерывающей подпрограммы
cli запрещаем прерывания
xchg fs:[7Oh], ax перестановка содержимого ах и 7Oh
xchg fs:[72h], bx перестановка содержимого bx и 72h
mov cs:VeclC, ax \/ес1С=исходное значение слова 70h
mov cs:VeclC+2, bx Уес1С+2=исходное значение слова 72h
sti разрешаем прерывания

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

Для доступа к словам вектора прерывания очищается один из сегментных регистров, в примере 5.25 это регистр fs, его очищает третья команда. Затем в регистры ах и bx записываются адрес прерывающей подпрограммы и сегмент, в котором она находится. Прежде чем изменять содержимое вектора, надо запретить прерывания. Это делается потому, что выполнение задачи никак не синхронизировано с таймером и прерывание от последнего может произойти в тот момент, когда задача начала, но еще не завершила изменение и запоминание содержимого вектора ich.

Прерывание запрещает команда cli, после нее производится обмен содержимого (xchg) слов вектора и регистров ах и bx. В результате в словах вектора ich окажется новый, а в регистрах ах и bx старый адрес, который надо запомнить. Следующие две команды пересылают старый адрес в слова vecic и vecic+2, после чего команда sti разрешает прерывания.

Восстановление вектора прерывания

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

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

Пример 5.26. Восстановление исходного содержимого вектора 1Ch

хоr ах, ах ; очистка регистра- ах
mov fs, ax ; очистка сегментного регистра fs
mov ax, cs:VeclC ; ax = содержимое VeclC
mov bx, cs:VeclC+2 ; bx = содержимое VeclC+2
cli ; запрещаем прерывания
mov fs: [70h] , ax ; восстановление 1-го слова вектора
mov fs: [72h] ,bx ; восстановление 2-го слова вектора
sti ; разрешаем прерывания

В примере 5.26 для доступа к словам вектора используется регистр fs, поэтому его содержимое предварительно очищается. Затем в регистры ах и bx копируются первое и второе слово сохраненного ранее вектора ich. После запрещения прерываний содержимое регистров ах и bx копируется в слова 70h и 72h, и разрешаются прерывания. Вектор восстановлен, и можно завершать выполнение задачи.

Замечание
Для работы с векторами прерываний предназначены две специальные функции DOS (прерывания int 2lh). Функция Get Vector (код 35h) читает содержимое вектора, a Set vector (код 25h) записывает в вектор новое содержимое. Однако их применение просто не оправдано, в чем вы можете убедиться самостоятельно.

Прерывающая подпрограмма подсчитывает количество тиков таймера и, как только оно будет равно 9 (примерно через каждые 0,5 сек), изменяет текущее состояние курсора. Рисунок курсора находится на экране, только если задача работает с текстом. Поэтому необходим специальный признак, разрешающий или запрещающий изменение состояния курсора. Кроме того, нужен признак, позволяющий узнать, в каком состоянии находится курсор — включен или погашен (виден или не виден на экране).
Для хранения счетчика тиков и признаков в разделе данных задачи надо зарезервировать две однобайтовые переменные, имеющие следующие имена:
Ntick db 9 ; счетчик тиков таймера изменяется от 0 до 9 CurStat db 0 ; текущее состояние курсора изменяется от 0 до 3.

В байте CurStat используются только два младших бита. Нулевой бит разрешает (1) или запрещает (0) изменение состояния (мигание) курсора, он устанавливается в основной программе. Первый бит отражает текущее состояние курсора на экране: 0 — выключен, 1 — включен. Им управляет прерывающая подпрограмма, текст которой приведен в примере 5.27.

Пример 5.27. Подпрограмма, создающая эффект мигающего курсора

Timeint: PushReg <ax,ds,es>; !! сохранение регистров ах, ds и es
mov ax, data ; ! ! ах = значение сегмента данных
mov ds, ах ; ! ! ds = код сегмента данных
mov es, VBuff ; ! ! es = код сегмента видеобуфера
test CurStat, 01 работа с курсором разрешена ?
jz GoVIC -> нет, завершение подпрограммы
dec Ntick Ntick = Ntick — 1
jnz GoVIC -> пауза продолжается
mov Ntick, 09 Ntick = 9 (примерно 0,5 сек)
xor CurStat, 02 изменение признака состояния курсора
call TglCrsr изменение состояния рисунка курсора
GoVIC: PopReg <es,ds,ax> !! восстановление es, ds и ax
db OEAh код инструкции jmp, на удаленный адрес
VeclC dw 00, 00 старое содержимое вектора ICh

Прежде всего отметим, что подпрограмму примера 5.27 нельзя вызывать командой call Timeint, поскольку ее выполнение завершается не командой ret, а безусловным переходом на тот адрес, который раньше находился в векторе ich. Подпрограмму вызывает процедура BIOS, обрабатывающая задания, адресованные таймеру. Действия, которые надо выполнить для разрешения или запрещения вызовов данной подпрограммы при каждом тике таймера, показаны в примерах 5.25 и 5.26.

При вызове прерывающей подпрограммы в стеке сохраняется содержимое регистров ах, ds и es. В общем случае при входе содержимое регистра ds не определено, и в него надо записать код сегмента данных. Имя сегменту данных присваивается при его описании (см. пример 2.11), обычно это data. Если вы выбрали другое имя, то измените команду mov ax, data. Регистр es используется в подпрограмме TglCrsr, изменяющей текущее состояние рисунка курсора, поэтому он должен содержать код сегмента видеобуфера, который хранится в переменной vbuff.

Основные действия, выполняемые в примере 5.27, достаточно просты. Проверяется состояние младшего разряда байта CurStat, если он очищен, то команда jz GOVIC завершает выполнение подпрограммы, в противном случае работа с рисунком курсора разрешена. Содержимое счетчика тиков уменьшается на 1 и если разность больше нуля, то пауза не закончена и происходит выход из подпрограммы без изменения рисунка курсора. Наконец, если разность равна нулю, то в счетчик тиков записывается число 9, и инвертируются рисунок курсора и признак его состояния.

Перед выходом из подпрограммы восстанавливается сохраненное в стеке содержимое регистров es, ds и ах, а затем выполняется команда jmp, которая передает управление на адрес, сохраненный в двух словах переменной vecic (при выполнении примера 5.25).

В тексте примера 5.27 использован следующий трюк. Имя команды jmp не записано явно, вместо этого в байте, расположенном перед переменной vecic, указан код операции (ОЕАЬ). При ее выполнении микропроцессор интерпретирует содержимое следующих двух слов как адрес, на который производится переход. Это наиболее простой, но не единственно возможный способ вернуться на сохраненное значение вектора прерывания.

Замечание
Если при выполнении вашей задачи содержимое сегментных регистров ds и ез не изменяется после их первоначальной установки, то из текста примера 5.27 можно исключить 5 команд, комментарий к которым начинается с двух восклицательных знаков.

 
Назад Начало Вперед