Для того, чтобы понять, как можно использовать окна, необходимо знать, какие действия с окнами можно выполнять. Помните, что окном является прямоугольная область на экране дисплея. Окна имеют и другие свойства, но главное, что отличает их от других типов экранных изображений, - это их способность как бы всплывать и погружаться относительно остального изображения. Информация, которая содержалась в соответствующей прямоугольной области экрана до появления нового окна, должна сохраняться. Окно накладывается на предшествующее изображение, как бы всплывает. Когда окно уничтожается (погружается), то информация, которая была до его появления, должна быть восстановлена. Та часть изображения, на которую накладывается новое окно, также может содержать окна. На рис. 5.3 представлено изображение на экране дисплея, которое содержит несколько окон, причем каждое последующее окно накладывается на часть предыдущего. +-----------------------------------------------------------+ | +----------------------------+ | | | | | | | | | | | +---------+------+ | | | | | | | +-----------+ | Окно D | | | | | Окно B | | | | | | | | | | | | +--------------+ +--+ | | | | | Окно C | | | | | | Окн| | +--------+----------------+-+| | | | | | | || | | | | | | || | | | +---+ | Окно E || | | | | | || | | +---------------+ | || | | | +--------+----------------+-+| | | | +----------------+ | | | +----------------------------------+ | - 2 - | | | | +-----------------------------------------------------------+ Рис. 5.2 Наложение нескольких окон Если бы каждая программа, работающая с окнами, должна была управлять размещением окон и восстанавливать содержимое экрана, которое было до их появления, то создание и сопровождение таких программ было бы очень трудным делом. К счастью, в этом нет необходимости. Поскольку функции и свойства окон являются общими для разных применений, то можно использовать библиотеку функций общего назначения для работы с окнами. Настоящая книга содержит такую библиотеку, ее функции описаны в разделе 6. Основных операций по работе с окнами, использующихся в большинстве программ, не так уж много, и они не слишком сложны. При работе с окнами вам необходимо установить окно, определив его размеры и местоположение. Вы имеете возможность также устанавливать его цвета, границу и заголовок. Вы можете вписать свой текст внутрь окна, а также при необходимости изменять местоположение окна. Наконец, вы можете удалить окно с экрана, восстановив при этом изображение, которое было на экране до появления окна. Основываясь на этих операциях, вы можете создавать программы, которые используют чарующие многоцветьем красок окна в качестве пользовательского интерфейса. (Следует помнить, что использование эффективной библиотеки само по себе не гарантирует высокое качество пользовательских характеристик разрабатываемой программы. Программист должен использовать вместе с окнами также звуковые эффекты и другие инструментальные программные средства). Чтобы понять, как можно управлять изображением на экране, вы должны разобраться в организации видеопамяти. Приводимые ниже сведения представляют собой введение в архитектуру видеопамяти IBM PC. Для получения исчерпывающей информации по этому вопросу - 3 - обращайтесь к "Руководству Питера Нортона для программистов по IBM PC" (Питер Нортон, Мicrosoft Press, 1985). Архитектура видеопамяти ----------------------------------------------------------------- Система формирования изображения является неотъемлемой частью ПЭВМ IBM PC. В более ранних моделях персональных ЭВМ видеотерминалы подключались через последовательные порты ввода/вывода, но аппаратная архитектура IBM PC включает в себя и видеосистему. Предназначенное для вывода на экран изображение создается в видеопамяти. В IBM PC в качестве видеопамяти используется часть оперативной памяти. Видеопамять доступна для чтения и записи процессору и, следовательно, вашим программам. Видеопроцессор, входящий в состав видеоконтроллера, по содержимому видеопамяти постоянно формирует изображение на экране дисплея. Поэтому каждый новый символ, записанный в видеопамять, почти немедленно появляется на экране. Поскольку видеопамять доступна для микропроцессора, то скорость формирования изображения соответствует скорости пересылки содержимого памяти, которая превышает скорость передачи данных при подключении видеотерминала через последовательные порты ввода/вывода. Адреса видеопамяти и ее характеристики являются стандартными для всех ПЭВМ линии IBM PC, а также совместимых с ними. ПЭВМ типа IBM PC может иметь одну из трех видеосистем, использующих различные типы видеомониторов и, следовательно, различные типы видеоконтроллеров. Видеоконтроллер первого типа называется Монохромным Адаптером (МА), он обеспечивает работу только монохромного видеомонитора в символьном режиме и не поддерживает графического режима. Видеоконтроллер второго типа называется Цветным Графическим Адаптером (CGA). С помощью - 4 - контроллера CGA подключается цветной монитор, который может работать в двух различных режимах. В символьном режиме имеется возможность выбирать цвет фона и цвет символа из восьми возможных цветов, а также один из двух уровней интенсивности цвета для символа. В графическом режиме низкого разрешения (640x200 растровых точек) можно работать только с одним цветом. Третий видеоконтроллер называется Усовершенствованным Графическим Адаптером (EGA), который поддерживает такой же символьный режим, что и CGA, и многоцветный графический режим более высокого разрешения. Приведенные в данной книге программы работают с любым из этих контроллеров в символьном режиме. Поскольку в этом режиме контроллеры CGA и EGA функционально эквивалентны, то нижеследующие рассуждения для CGA относятся к обоим этим контроллерам. Видеопамять организована в виде двумерного массива символов, состоящего из рядов и колонок. Ее можно рассматривать и как набор следующих друг за другом 16-разрядных слов, по одному на каждый символ. Каждый ряд содержит 80 следующих друг за другом символов, все они образуют 25 следующих друг за другом колонок. Слово содержит информацию об одном символе и состоит из двух восьмибитных байт: один - для ASCII-кода символа, второй - для атрибутов символа, определяющих его изображение. Код ASCII записан в младшем (правом) байте слова. Видеопамять контроллера МА организована в виде одной страницы, а контроллера CGA - в виде четырех страниц. Программы из этой книги используют только первую страницу видеопамяти контроллера CGA. Видеопамять расположена в верхних областях доступного процессору адресного пространства. Разработчики IBM PC, столкнувшись с ограничением на максимальный объем адресуемого адресного пространства в 1 мегабайт, решили разместить видеопамять и ПЗУ Базовой Системы Ввода-Вывода (ROM BIOS) в - 5 - верхних областях этого пространства. Для того, чтобы позволить контроллерам МА и CGA работать совместно на одной ПЭВМ, разработчики назначили различные адреса сегментов видеопамяти для разных контроллеров. Память контроллера МА начинается в сегменте 0xB000, а память контроллера CGA - в сегменте 0xB800. Программа может определить, какой из контроллеров используется, путем вызова соответствующей функции ROM BIOS, и настроиться таким образом на соответствующий адрес сегмента видеопамяти. Поскольку архитектура видеопамяти для того и другого случая в основном одинакова, то необходимые действия по настройке программы будут минимальными. Байт атрибутов символа содержит 2 трехразрядных поля кодирования цвета (одно для цвета фона символа и одно для цвета самого символа), разряд для задания уровня интенсивности цвета символа и разряд для установки режима мерцания символа при его отображении. На рис. 5.4 представлена конфигурация байта атрибутов. +----+----+----+----+----+----+----+----+ | | | | | | | | | | B | R | G | B | I | R | G | B | | | | | | | | | | +-+--+----+----+----+--+-+----+----+----+ | +-------+------+ | +-------+------+ | | | | | | | +------- цвет символа | | | | | +----------------- интенсивность цвета | | символа | +--------------------------- цвет фона | +-------------------------------------- мерцание символа Рис. 5.4 Байт атрибутов символа - 6 - Комбинации красной, зеленой и голубой цветовых составляющих позволяют получить набор из восьми отчетливо различаемых цветов: белого, красного, зеленого, голубого, синего, малинового, желтого и черного. Использование разряда интенсивности для цвета символа позволяет получить еще восемь дополнительных оттенков цвета. В случае использования Монохромного Адаптера значение байта атрибутов немного отличается от описанного выше. Поскольку возможность использования различных цветов не поддерживается контроллером МА, то разрешены только комбинации белого символа на черном фоне или черного символа на белом фоне. Другие коды цвета дают комбинации черного на черном или белого на белом. Хотя следует отметить, что комбинация голубого символа и черного фона формирует изображение подчеркнутого символа, что не поддерживается уже контроллером CGA. Монохромный Адаптер, как и CGA, поддерживает возможности задания интенсивности цвета и режима мерцания символа. Поскольку вы теперь знаете, где размещена видеопамять и что в нее может быть записано, то можете использовать программу pokes из библиотеки Турбо Си для формирования изображения на экране дисплея. Ниже приводится листинг 5.1 маленькой программы под названием vidpoke.c, которая записывает строку непосредственно в видеопамять. Исходя из предположения, что на вашей машине работает контроллер CGA, программа формирует для каждого символа байт атрибута со значением 7, что соответствут отображению строки в виде белых символов на черном фоне. Если вы используете Монохромный Адаптер, измените в строке # define VSEG значение с 0xB800 на 0xB000. Листинг 5.1: vidpoke.c /* vidpoke.c */ - 7 - #define VSEG 0x6800 char vdata [] = "Что сделал Кан?"; main() { char *vp; int v; for (v=0, vp = vdata; *vp; v +=2, vp++) poke ( VSEG, v, 0x700| *vp); } "Снег" и обратный ход луча развертки ----------------------------------------------------------------- Если вы запустите программу vidpoke.exe в цикле или значительно увеличите длину выводимой строки, то сможете увидеть на экране так называемый "снег", который появляется при выполнении программы. Вы будете наблюдать "снег" только в случае использования контроллера CGA или его аналога. Контроллеры ЕGA и МА не дают подобного эффекта. Из нижеследующих объяснений вы узнаете, почему возникает "снег" и как его можно устранить программнми средствами. "Снег" возникает при использовании контроллера CGA из-за особенностей аппаратной архитектуры видеосистемы. Поскольку и микропроцессор, и видеоконтроллер обращаются к одной видеопамяти, то в случае одновременного обращения возникает необходимость в координировании доступа к памяти. Работа видеоконтроллера синхронизирована по времени с работой схемы развертки. Во время формирования элемента изображения на экране видеоконтроллер должен иметь доступ к ячейке памяти, которая содержит бит, соответствующий этому элементу изображения. Видеопроцессор при этом не может ждать, поскольку он жестко синхронизирован с устройством формирования растра. Следовательно, если видеоконтроллер и микропроцессор пытаются одновременно обратиться к одной и той же ячейке памяти, то видеоконтроллер - 8 - должен иметь при этом более высокий приоритет. В хорошо разработанной системе микропроцессор будет находиться в состоянии ожидания, при котором запросы микропроцессора к видеопамяти не поступают. В контроллерах МА и EGA использован этот принцип, и вы можете не беспокоиться о конфликтных ситуациях между микропроцессором и видеоконтроллером при доступе к видеопамяти. Но в контроллере CGA в символьном режиме этого не выполняется, поэтому вы должны сами предпринимать некоторые действия. В случае использования контроллера CGA, если микропроцессору нужно обратиться к видеопамяти для чтения или записи, то это ему разрешается, а видеоконтроллеру доступ на это время запрещается. А поскольку работа видеоконтроллера жестко привязана по времени к формированию горизонтальной и вертикальной разверток на экране ЭЛТ, то вместо извлеченных из видеопамяти данных видеоконтроллер при формировании изображения в это время вынужден использовать случайные данные. Поэтому, когда ваша программа изменяет содержимое памяти, на экране случайным образом возникают мерцающие светлые пятна, называемые "снегом". К счастью, контроллер CGA позволяет избавиться от этого раздражающего мерцания. При формировании изображения на экране ЭЛТ электронная пушка бомбардирует электронами пятна фосфора на поверхности экрана. Если энергия электронов будет достаточно высока, то пятна фосфора будут светиться, в противном случае они будут темными. Изображение на экране ЭЛТ формируется в виде последовательности горизонтальных линий, называемых растровыми линиями. Контроллер CGA формирует 200 таких линий. Развертка осуществляется электронным лучом слева направо и сверху вниз. (На самом деле используются три электронных пушки, по одной для каждого из основных цветов: красного, зеленого и голубого). При достижении конца растровой линии электронный луч должен переместиться к левому краю экрана и на одну линию вниз. Это перемещение называется горизонтальным обратным ходом луча. После формирования самой нижней растровой линии электронный луч должен вернуться к самой верхней растровой линии экрана. Это перемещение называется вертикальным обратным ходом луча. Во время - 9 - этих двух перемещений энергия электронного луча низкая, и он не подсвечивает растровые точки. В это время не происходит отображения символов на экране, и видеоконтроллер не осуществляет чтения из видеопамяти. Следовательно, в промежутки времени, соответствующие обратному ходу луча, микропроцессор имеет беспрепятственный доступ к видеопамяти. Контроллер CGA имеет аппаратный регистр, содержимое которого указывает на состояние схем обратного хода луча. Проверяя содержимое этого регистра, программа может осуществлять доступ к видеопамяти только в промежутки времени, соответствующие обратному ходу луча, что позволяет избежать возникновения "снега". Проверка содержимого этого регистра должна осуществляться за достаточно короткий промежуток времени. Если вы попытаетесь осуществить ее из программы на языке высокого уровня (например, Си), то она работать не будет, поскольку время на проверку превысит в этом случае время обратного хода луча. И от "снега" вы не избавитесь. Проблема может быть решена только с помощью языка ассемблера. Написанная на языке ассемблера программа может дожидаться периода обратного хода луча, продолжительность которого достоточна для чтения или записи символа в память. Язык ассемблера позволяет создать программу, которая будет выполнять эти действия достаточно быстро, чтобы соответствовать тем ограничениям по времени, которые накладываются видеосистемой. Запомните, что контроллеры МА и EGA избавляют от необходимости использования такого рода ассемблерной программы. Операции чтения символов из видеопамяти и записи в видеопамять будут выполняться быстрее при использовании этих контроллеров. Но эта ассемблерная программа является необходимой при использовании контроллера CGA в символьном режиме (если, конечно, вы не имеете желания наблюдать "снег" на экране). Вам необходимо знать, что некоторые так называемые "совместимые видеоконтроллеры" являются недостаточно эффективными - 10 - для IBM РС вследствие проблемы с сигналом обратного хода луча. Тактовая частота 4,77 МГц процессора 8088 является слишком низкой, и продолжительность сигнала, соответствующего состоянию обратного хода луча, является слишком короткой. При использовании этого сигнала в программах на ассемблере уничтожить весь "снег" на экране не удается. Поэтому такие видеоконтроллеры применяются обычно в ПЭВМ с более высокой тактовой частотой процессора, таких, как IBM AT, или использующих акселератор для ускорения выполнения машинных команд. Ниже приводится описание двух функций из исходного модуля ibmpc.c, полная информация о котором содержится в разделе 4. Эти функции написаны на языке Си, но включают фрагменты на языке ассемблера, что допускается компилятором Турбо Си. Для того чтобы использовать эти функции, необходимо иметь транслятор с языка ассемблера MASM. Эти функции работают корректно с любым видеоконтроллером и уничтожают "снег" в случае использования контроллера CGA. Вам нет необходимости вызывать эти функции из своих программ, они используются программами для работы с "окнами" из этого раздела. Вы можете однако захотеть модифицировать эти функции, если используете контроллеры МА и EGA или если у вас нет программы МАSM. Если вы пишете программы и при этом неизвестно, кто и на какой аппаратной конфигурации будет их использовать, то должны оставить эти функции без изменений и использовать трансляторы Турбо Си и MASM для их трансляции. vpoke(unsigned vseg, unsigned adr, unsigned chr) ------------------------------------------------ Эта функция заносит байт с кодом символа и байт с его атрибутами в видеопамять. Параметр adr представляет собой смещение в байтах от начала сегмента видеопамяти: 0 соответствует первому символу, 2 - второму и т.д. Параметр chr содержит байт атрибутов символа (старший) и ASCII-код символа. Параметр vseg - 11 - определяет адрес сегмента видеопамяти (0xB800 для CGA, 0xB000 для МА). Эта функция осуществляет вывод символа с проверкой на обратный ход луча, там, где это необходимо. int vpeak(unsigned vseg,unsigned adr) ------------------------------------- Эта функция возвращает код символа и его атрибуты (старший байт), размещенные в видеопамяти по адресу vseg:adr, то есть выполняют чтение символа с проверкой на обратный ход луча, где это необходимо. Заключение ----------------------------------------------------------------- Изложенные сведения об архитектуре видеосистемы IBM PC и основных принципах работы с экранными "окнами" позволяют понять представленные в разделе 6 исходные тексты библиотеки функций управления "окнами", удовлетворяющие требованиям компилятора Турбо Си. Использование этих функций облегчит вам программирование видеоизображений. Вы можете смело применять эти функции, и сконцентрировать свое внимание на проблемах, которые специфичны для вашей прикладной задачи. Глава 6 ------- Библиотека оконных функций ----------------------------------------------------------------- Программы, описанные в этой и нескольких последующих главах, представляют библиотеку оконных функций, которая поддерживает широкий диапазон экранных оконных операций. Функции подразделены - 12 - на подсистемы, использование которых позволяет организовать меню, контекстно-управляемые подсказки, редактирование текста и форматирование данных в прикладных системах. Эти подсистемы поддерживаются общецелевой оконной библиотекой, которая может использоваться в прикладных программах так же, как и подсистемы. В этой главе описывается общецелевая библиотека оконных функций. Оконные функции, описанные в данной главе, могут применяться для организации окон в одной из двух конфигураций: стековой и слоеной; одна конфигурация является с точки зрения использующей их программы подмножеством другой. Слоеные окна обладают большими возможностями, чем стековые, однако стековые более эффективны, то есть выдача на экран и уничтожение их происходит быстрее. Программа может быть связана либо со стековыми, либо со слоеными оконными функциями, но не с теми и другими одновременно. При компиляции оконных функций, вы должны принять решение о том, какую именно оконную конфигурацию применить. Для стековых окон определяется переменная времени компиляции FASTWINDOWS, для слоеных окон она удаляется. Прикладная программа может быть связана с любой библиотекой до тех пор, пока она не использует возможности, поддерживаемые только для слоеных окон. Те прикладные программы, которые используют возможности исключительно слоеных окон, должны быть связаны с библиотекой оконных функций, которая была компилирована без определения FASTWINDOWS. Стековые окна ----------------------------------------------------------------- Конфигурация стековых окон предполагает, что любая выполняемая вами с окном операция (запись в него текста, изменение цвета, уничтожение его и т.д.) производится, когда окно является полностью видимым пользователю. Полная видимость означает, что ни одна часть окна не накрыта другим окном и что - 13 - окно не скрыто функцией hide_window (о которой будет сказано ниже). Когда установлено стековое окно, оконное программное обеспечение строит буфер для хранения прежнего содержимого видеопамяти, которую будет занимать окно. Видеопамять сохраняется в буфере, а окно записывается в видеопамять. При выполнении любых операций, модифицирующих окно, все изменения выполняются непосредственно в видеопамяти, а программное обеспечение предполагает, что окно является полностью видимым. Когда окно уничтожается, содержимое хранящего его буфера записывается обратно в видеопамять, восстанавливая таким образом видеообраз памяти к состоянию до образования окна. Вы будете обычно обращаться только к стековому окну, образованному последним. Если вы сначала создали окно А, а затем окно В, которое закрывает часть окна А, то при записи текста в окно А может случиться, что часть текста попадет в часть окна В, закрывающую окно А. Далее, если вы уничтожите окно A до уничтожения окна В, то часть буфера сохранения окна А запишется в начало части окна В. Большинство коммерческих оконных пакетов поддерживает только стековые окна, поскольку большинство приложений, использующих окна, могут успешно функционировать в среде стековых окон. Обычной практикой в приложениях является использование окна, открытого последним, и уничтожение окон в порядке, обратном их созданию. Такие приложения должны использовать стековые окна из-за преимуществ их функционирования. Оконные операции, описанные в данной книге, позволяют вам создавать одно или более окон и затем относить различные операции к одному из созданных окон. Вы можете обращаться к конкретному окну или использовать пустую спецификацию для сообщения вызванной функции о своем намерении выполнить операцию в окне, созданном последним. Это соглашение используется как для стековых, так и для слоеных окон, однако пользователи стековых оконных функций должны быть уверены, что любое окно, к которому они обращаются, либо создано последним, либо полностью видимо на экране. - 14 - Слоеные окна ----------------------------------------------------------------- Слоеные окна обладают гораздо большей гибкостью, чем стековые, к тому же они предоставляют пользователю гораздо больше возможностей по созданию различных оконных интерфейсов. Когда слоеное окно создано, любая оконная операция может быть адресована ему, независимо от его видимости или близости к другим окнам. В дополнение к обычному набору оконных операций слоеные окна могут перемещаться в двумерной плоскости экрана и могут выдвигаться на передний или убираться на задний планы в слоях созданных окон. Когда создано слоеное окно, распределяется буфер сохранения видеосодержимого, но окно не отображается. Буфер сохранения инициализируется видеозначениями, которые окно могло бы содержать, если бы оно было видимым. Любые последующие операции, производимые в этом окне, пока оно невидимо, производятся в буфере сохранения. Когда слоеное окно отображается, видеопамять и буфер сохранения обмениваются содержимым. Затем, до тех пор пока окно является полностью видимым (не закрытым полностью или частично другим окном), любые операции производятся с видеопамятью, а не с буфером сохранения. Когда одно и более других окон покрывают адресуемое окно полностью или частично, определение области изменения является более сложным. Для тех частей окна, которые являются видимыми, изменение производится в видеопамяти. Однако для областей, закрытых другими окнами, изменение производится в буфере сохранения покрывающего окна. Поскольку окно может иметь различные части, закрытые несколькими другими окнами, то алгоритм определения места, где должно быть сделано изменение, обязан сначала просмотреть все окна, созданные позже данного, с целью установления факта выполнения изменения в области, покрытой - 15 - следующим окном последовательности. Если такое окно найдено, то изменение записывается в его буфер сохранения. Если ни одно окно не закрывает модифицируемый участок, то изменение производится в видеопамяти. Рассмотрим рисунок 6.1. Три окна расположены так, что часть окна А видима, часть закрыта окном В и часть - окном С. В буферах сохранения каждого окна вы можете видеть границы частей других окон, которые закрыты. +---------------------------------------------------------------+ | | | .+-----------+ | | . | | | | . +-----------+ Буфер | | . | | сохранения В | | . | | | | . | | | | . +-----------+ | | . . | | . | | . . | | +----------+ | | +----+ B +----+ . | | | A | | | . | | | | +----+----+---+. . | | | | | C |. . | | | +-----| | . | | +----------| | . | | | | . | | | | +------+----------+---+ | | +-------------+ | | | | | | . | | | | | | . | | | | | | . +------+ | | | | . +-----------------+ | | | . +---------------------+ | - 16 - | Буфер сохранения С | | | +---------------------------------------------------------------+ Рисунок 6.1. Три слоеных перекрывающихся окна. Если вы запишите текстовую строку "now is the time" в окно А, текст будет направлен в три различных места. Результат показан на рисунке 6.2. Так как часть окна А, где записано "now", является видимой, то слово записывается непосредственно в видеопамять и может быть прочитано пользователем. Слова "is the" являются частью окна А, которая закрыта окном В, поэтому эти слова записываются в буфер сохранения окна В. Слово "time" оказывается в той части окна А, которая покрыта окном С, следовательно "time" записывается в буфер сохранения окна С +---------------------------------------------------------------+ | | | | | | | | | . +---------+ | | . +---------+ | | . | is the | Буфер сохранения В | | . | | | | +-------+ +---------+ | | +-------+ B +------+ . | | | A | +--+------+-+. . | | | now | | C |. . | | | +----+ | . | | +------------+ | +-----+--------+--+ | | +-----------+ | | time | | | | . +-----+ | | | | . +--------------+ | | | . +-----------------+ | - 17 - | Буфер сохранения С | | | +---------------------------------------------------------------+ Рисунок 6.2. Слоеные окна с текстом. Рисунок 6.3. показывает, что происходит при уничтожении окна В. Часть окна А из его буфера сохранения поступает в видеопамять, и слово "is" может теперь быть прочитано пользователем. Однако, поскольку часть окна В была покрыта окном С, то часть буфера сохранения окна В копируется в буфер сохранения окна С, следовательно, буфер сохранения окна С теперь содержит слова "the time". +---------------------------------------------------------------+ | | | | | | | | | +----------------------+ | | | A +---------+------+ . | | | | C | . | | | now is | | . | | | | | . | | +------------+ | +-------------------+--+ | | +----------------+ | the time | | | | . | | | | | . | | | | | . +-------------------+ | | | . +----------------------+ | | Буфер сохранения С | | | +---------------------------------------------------------------+ Рисунок 6.3. Уничтожение слоеного окна. - 18 - Независимо от использования стековых или слоеных окон, работа их будет зависеть от требований вашей системы. Каждый подход имеет свои преимущества и недостатки. Если вы начали со стековых окон и обнаружили позже, что вам нужны дополнительные свойства слоеной оконной архитектуры, вы можете выполнить изменение путем перекомпиляции оконных функций без определения FASTWINDOWS и перередактировать связи ваших программ. Оконные функции ----------------------------------------------------------------- Эти функции включены в оконную библиотеку. Для каждой функции описаны ее назначение и способ применения. Далее приводятся примеры использования этих функций. WINDOW *establish_window(x,y,h,w) --------------------------------- Эта функция создает окно, но не отображает его. (Для показа окна воспользуйтесь функцией display_window). Параметры x и y являются координатами верхнего левого угла окна. Эти параметры выражаются в символьных позициях экрана, где координаты левого верхнего угла самого экрана равны (0,0). Параметры h и w являются высотой и шириной окна в символьных позициях. Эта функция не вызовет никакого изменения экрана. Если вы создадите окно, позиция и размеры которого не позволяют ему быть полностью видимым, программное обеспечение установит позицию, при которой оно полностью поместится на экране. Если ширина больше 80 или высота больше 25, функция преобразует соответствующий размер до максимально допустимого значения. Окно создается с умалчиваемыми атрибутами. Его рамка образуется одинарными линиями, цвет содержимого - ярко-белый на - 19 - черном фоне, а заголовок отсутствует. Эти атрибуты могут быть изменены соответствующими вызовами других функций, описанных ниже. Окна располагаются в обратном порядке по отношению к их созданию. Окно, созданное самым последним, является верхним окном экрана и будет (при выдаче) закрывать окна, созданные ранее. Эта иерархия зависит от порядка, в котором окна образуются, а не от порядка их показа. Функция establish_window возвращает указатель на структуру WINDOW, которая определена во включаемом файле twindow.h. Этот указатель используется при последующих вызовах оконных функций для идентификации окна, к которому относится вызов. Передавайте указатель NULL другим оконным функциям, если хотите работать с окном, образованным последним. void set_border(WINDOW *wnd, int btype) --------------------------------------- Эта функция устанавливает тип оконной рамки. Целочисленный параметр btype должен принимать одно из следующих значений: . 0 - одинарные линии (по умолчанию); . 1 - двойные линии; . 2 - одинарные верх и низ, двойные боковые; . 3 - двойные верх и низ, одинарные боковые; . 4 - специальное проталкиваемое вниз окно меню с одинарными линиями и t-блоком от верхнего левого до верхнего правого угла. void set_colors(WINDOW *wnd, int area, int bg, int fg, int inten) ----------------------------------------------------------------- - 20 - Эта функция устанавливает цвета для окна. Параметр area может принимать следующие значения: . ALL . BORDER . TITLE . ACCENT . NORMAL Этот параметр определяет части окна, на которые распространяется действие. ALL соответствует всем частям. BORDER устанавливает цвета рамки окна, которая занимает односимвольный ряд позиций вокруг него. TITLE устанавливает цвета заголовка, размещаемого на верхней границе окна. ACCENT - это область, используемая для блоков меню и другого выделенного текста, который появляется в качестве временно подсвечиваемых частей области NORMAL, где отображается весь текст. Целые числа bg и fg задают цвета для фона и переднего плана различных частей окна. Допускаются следующие цвета: . RED - алый; . GREEN - зеленый; . BLUE - голубой; . WHITE - белый; . YELLOW - желтый; . AQUA - аквамариновый; . MAGENTA - красный; . BLACK - черный. Целое число inten определяет интенсивность символов переднего плана и может принимать два значения: . BRIGHT - яркая . DIM - обычная Все значения определены в исходном файле twindow.h, который - 21 - рассматривается позже в этой главе. void set_title(WINDOW *wnd, char *title) ---------------------------------------- Эта функция устанавливает значение заголовка окна. Передаваемая вами строка должна сохраняться в течение всего существования окна, поэтому используйте литеральную константу или внешний массив символов. void set_intensity(WINDOW *wnd, int inten) ------------------------------------------ Эта функция устанавливает интенсивность фона для всех частей окна. Значениями inten могут быть BRIGHT или DIM. void display_window(WINDOW *wnd) -------------------------------- Эта функция отображает окно, которое было ранее создано. Чтобы избежать неудачного отображения, вызывайте эту функцию после установки всех атрибутов и, если возможно, после того, как окно заполнено текстом, который оно должно выдавать. void delete_window(WINDOW *wnd) ------------------------------- Эта функция уничтожает созданное ранее окно. Если окно является видимым (выдано с помощью display_window), то оно - 22 - очищается, и экран восстанавливается к его предыдущему образу. void clear_window(WINDOW *wnd) ------------------------------ Эта функция заполняет текстовую область окна пробелами. void hide_window(WINDOW *wnd) ----------------------------- Эта функция очищает выданное окно, восстанавливая на экране его предыдущее содержимое. Окно остается существующим и может модифицироваться любым способом. Последующий вызов display_window восстановит его на экране в соответствующем расположении относительно других окон. void wcursor(WINDOW *wnd, int x, int y) --------------------------------------- Каждое окно обладает логической позицией курсора, которая изменяется от 0,0 (верхний левый угол текстовой области окна) до граничных размеров окна. Эта функция переставляет курсор в окне. Функции wputehar и wprintf выдают текст относительно данной позиции курсора. Они изменяют позицию курсора точно также, как это происходило бы, если бы управление курсором осуществлялось с помощью клавиатуры дисплея. Символ новой строки (\n) устанавливает курсор в нулевой столбец следующей строки, перемещая текст в окне вверх на одну строку, если курсор уже находится в нижней строке окна. Символ табуляции (\t) перемещает курсор к следующей позиции табуляции в окне. Табуляции - 23 - располагаются с интервалом в четыре символа. void wprintf(WINDOW *wnd, char *fmt, ...) ----------------------------------------- Эта функция является оконной версией стандартной функции printf языка Си. Она использует стандартную функцию sprintf для построения строки, выдаваемой в окно. Убедитесь, что результирующая строка не длиннее 100 символов, или измените длину в массиве dlin функции wprintf в исходном файле twindow.c. void wputchar(WINDOW *wnd, int ch) ---------------------------------- Эта функция является оконной версией putchar. Она записывает символ из ch в окно в текущую позицию курсора. Курсор при этом продвигается дальше. Если символ является символом новой строки (\n), курсор переставляется в нулевую позицию следующей строки. Если символ является табуляцией (\t), курсор продвигается к следующей позиции табуляции окна. Оконные позиции табуляции располагаются в каждой четвертой позиции окна. void reverse_video(WINDOW *wnd) ------------------------------- После обращения к этой функции все вызовы wprintf и wputchar будут при отображении использовать цвета ACCENT вместо NORMAL. void normal_video(WINDOW *wnd) - 24 - ------------------------------ После обращения к этой функции все вызовы wprintf и wputchar будут при отображении использовать цвета NORMAL. Эта функция используется для возврата к нормальному отображению после вызова reverse_video. void close_all() ---------------- Эта функция уничтожает все образованные окна. void move_windom(WINDOW *wnd, int x, int y) ------------------------------------------- Эта функция перемещает окно таким образом, что его верхний левый угол устанавливается в символьных координатах, заданных x и у. Эта функция может быть использована только для слоеных окон. void rmove_window(WINDOW *wnd, int x, int y) -------------------------------------------- Эта функция перемещает окно путем добавления к значениям x и y текущих координат левого верхнего угла окна. Используйте эту функцию только для слоеных окон. void forefront(WINDOW *wnd) --------------------------- - 25 - Эта функция перемещает окно в самое переднее положение относительно других окон. Окно, если оно видимо, отображается поверх остальных. Используйте эту функцию только для слоеных окон. void rear_window(WINDOW *wnd) ----------------------------- Эта функция перемещает окно в самое заднее положение относительно других окон. Окно, если оно видимо, отображается под всеми остальными. Используйте эту функцию только для слоеных окон. int get_selection(WINDOW *wnd, int sel, char *keys) --------------------------------------------------- Эта функция позволяет использовать окно в качестве меню. Вы должны создать окно и записать в него несколько строк текста, скажем, функцией wprintf. Вы можете использовать set_colors для установки значений цветов ACCENT в окне (по умолчанию - черные буквы на белом фоне). Затем вы вызываете get_selection. Функция использует окно в качестве меню, каждая строка которого представляет альтернативу выбора. Альтернативы подсвечиваются цветом ACCENT. Целочисленное значение sel используется для первоначального размещения яркого блока курсора меню на одной из альтернатив. Значение 1 соответствует первой альтернативе, 2 - второй и т.д. Пользователь может перемещать блок меню вверх и вниз клавишами управления курсором и производить выбор в меню клавишей <Ввод>. Клавиша <Ключ> применяется для выхода из этого процесса. Cсылка keys указывает на строку значений клавиш, которые - 26 - могут использоваться для выбора из меню. Некоторые системы меню разрешают пользователю применять нажатия клавиш наряду с перемещением блока курсора. Для выключения этой возможности передайте параметру keys ссылку NULL. Эта функция возвращает целочисленное значение, равное номеру выбранной в меню альтернативы. Значение обычно равняется единице и более, однако, если пользователь нажимает клавишу <Ключ>, функция возвращает нуль. Функция будет также возвращать значения FWD или BS, если пользователь нажимает клавиши управления курсором вправо или влево. Эти значения определены в keys.h. void error_message(char *s) --------------------------- Эта функция выдает сообщение об ошибке, указываемое ссылкой s, и включает звуковой сигнал. Сообщение отображается в окне в нижнем правом квадранте экрана. Сообщение остается на экране после завершения функции. void clear_message() -------------------- Эта функция удаляет сообщение об ошибке, если оно было выдано функцией error_message. Листинги оконных функций ----------------------------------------------------------------- Данные листинги содержат исходные файлы функций поддержки окна. Приводятся два листинга: twindow.h и twindow.c. Каждый - 27 - листинг сопровождается описанием его содержания. Исходный листинг: twindow.h --------------------------- Листинг 6.1, twindow.h, определяет оконные структуры и содержит прототипы для функций. Вы должны включать их в любую исходную программу, которая использует оконные функции. Листинг 6.1: twindow.h /* twindow.h */ /* Выделите это определение из комментария для стековых окон, * но не для слоеных окон. * * #define FASTWINDOWS * */ /* window colors */ #define RED 4 #define GREEN 2 #define BLUE 1 #define WHITE (RED+GREEN+BLUE) #define YELLOW (RED+GREEN) #define AQUA (GREEN+BLUE) #define MAGENTA (RED+BLUE) #define BLACK 0 #define BRIGHT 8 #define DIM 0 #define BORDER 0 #define TITLE 1 #define ACCENT 2 #define NORMAL 3 #define ALL 4 - 28 - #define TRUE 1 #define FALSE 0 #define ERROR -1 #define OK 0 /* оконные управляющие структуры */ typedef struct field { /* описание поля ввода данных */ char *fmask; /* маска поля ввода данных */ int fprot; /* защита поля */ char *fbuff; /* буфер поля */ int ftype; /* тип поля */ int from; /* строка поля */ int fcol; /* столбец поля */ void (*fhelp)(); /* функция подсказки поля */ char *fhwin; /* функция подсказки окна */ int flx, fly; /* расположение подсказки окна */ int (*fvalid)(); /* функция заполнения поля */ struct field *fnxt; /* следующее поле выдачи */ struct field *fprv; /* предыдущее поле выдачи */ } FIELD; typedef struct _wnd { int _wv; /* истина, если окно видимо */ int _hd; /* истина, если окно скрыто */ char *_ws; /* указывает на блок сохранения окна */ char *_tl; /* указывает на заголовок окна */ int _wx; /* nv x координата */ int _wy; /* nv y координата */ int _ww; /* ширина окна */ int _wh; /* высота окна */ int _wsp; /* указатель прокрутки */ int _sp; /* указатель выбора */ int _cr; /* позиция х курсора */ int btype; /* тип рамки */ int wcolor[4]; /* цвета окна */ int _pn; /* предыдущий нормальный цвет */ - 29 - struct _wnd *_nx; /* указывает на следующее окно */ struct _wnd *_pv; /* указывает на предыдущее окно */ FIELD *_fh; /* указывает на 1-е поле ввода данных */ FIELD *_ft; /* указывает на последнее поле ввода данных */ } WINDOW; typedef struct w_menu { char *mname; char **mselcs; void (**func)(); } MENU; #define SAV (wnd->_ws) #define WTITLE (wnd->_tl) #define COL (wnd->_wx) #define ROW (wnd->_wy) #define WIDTH (wnd->_ww) #define HEIGHT (wnd->_wh) #define SCROLL (wnd->_wsp) #define SELECT (wnd->_sp) #define WCURS (wnd->_cr) #define WBORDER (wnd->wcolor[BORDER]) #define WTITLEC (wnd->wcolor[TITLE]) #define WACCENT (wnd->wcolor[ACCENT]) #define WNORMAL (wnd->wcolor[NORMAL]) #define PNORMAL (wnd->_pn) #define BTYPE (wnd->btype) #define NEXT (wnd->_nx) #define PREV (wnd->_pv) #define WCOLOR (wnd->wcolor) #define VISIBLE (wnd->_wv) #define HIDDEN (wnd->_hd) #define FHEAD (wnd->_fh) #define FTAIL (wnd->_ft) #define NW (wcs[wnd->btype].nw) #define NE (wcs[wnd->btype].ne) #define SE (wcs[wnd->btype].se) #define SW (wcs[wnd->btype].sw) - 30 - #define SIDE (wcs[wnd->btype].side) #define LINE (wcs[wnd->btype].line) /* ПРОТОТИПЫ ФУНКЦИЙ И МАКРОСЫ */ /* общецелевые функции и макросы */ void clear_screen(void); int vmode(void); void cursor(int, int); void curr_cursor(int *, int *); int cursor_type(void); void set_cursor_type(int); int get_char(void); int scroll_lock(void); void vpoke(unsigned, unsigned, unsigned); int vpeek(unsigned, unsigned); /* оконные функции и макросы */ WINDOW *establish_window(int, int, int, int); void set_border(WINDOW *, int); void set_colors(WINDOW *, int, int, int, int); void set_intensity(WINDOW *, int); void set_title(WINDOW *, char *); void display_window(WINDOW *); void delete_window(WINDOW *); void clear_window(WINDOW *); void hide_window(WINDOW *); void wprintf(WINDOW *, char *, ...); void wputchar(WINDOW *, int); void close_all(void); void wcursor(WINDOW *, int x, int y); void error_message(char *); void clear_message(void); int get_selection(WINDOW *, int, char *); - 31 - #define reverse_video(wnd) wnd->wcolor[3]=wnd->wcolor[2] #define normal_video(wnd) wnd->wcolor[3]=wnd->_pn #define rmove_window(wnd,x,y) repos_wnd(wnd, x, y, 0) #define move_window(wnd,x,y) repos_wnd(wnd, COL-x, ROW-y, 0) #define forefront(wnd) repos_wnd(wnd, 0, 0, 1) #define rear_window(wnd) repos_wnd(wnd, 0, 0, -1) /* внутренние для оконных процессов */ void accent(WINDOW *); void deaccent(WINDOW *); void scroll(WINDOW *, int); void repos_wnd(WINDOW *, int, int, int); void acline(WINDOW *, int); #define accent(wnd) acline(wnd, WACCENT) #define deaccent(wnd) acline(wnd, WNORMAL) #define clr(bg,fg,in) ((fg)|(bg<<4)|(in)) #define vad(x,y) ((y)*160+(x)*2) #ifdef FASTWINDOWS #define cht(ch,at) (((ch)&255)|((at)<<8)) #define displ(w,x,y,c,a) vpoke(VSG,vad(x+COL,y+ROW),cht(c,a)) #define dget(w,x,y) vpeek(VSG,vad(x+COL,y+ROW)) #define verify_wnd(w) (*(w)=listtail)!=0 #else void displ(WINDOW *wnd, int x, int y, int ch, int at); #endif /* функция редактора */ void text_editor(WINDOW *, char *, unsigned); /* функция меню */ void menu_select(char *name, MENU *mn); /* функция подсказки */ void load_help(char *); void set_help(char *, int, int); /* функция ввода данных */ - 32 - void init_template(WINDOW *); FIELD *establish_field(WINDOW *, int, int, char *, char *, int); void clear_template(WINDOW *); void field_tally(WINDOW *); int data_entry(WINDOW *); void wprompt(WINDOW *, int, int, char *); void error_message(char *); void clear_notice(void); void field_window(FIELD *, char *, int, int); #define field_protect(f,s) f->fprot=s #define field_help(f,h) f->fhelp=h #define field_validate(f,v) f->fvalid=v Описание программы: twindow.h ----------------------------------------------------------------- Глобальная переменная FASTWINDOWS определена внутри комментария в представленной программе. Включение переменной рассчитано на применение стековой оконной конфигурации. Без изменений при компиляции будет принята слоеная оконная конфигурация. Для компиляции стековой оконной системы необходимо выделить оператор #define из комментария. Структура FIELD используется для определения полей ввода данных внутри области данных в окнах. Этот процесс описан в Главе 8. Структура WINDOW описывает окно для системы . Каждому окну назначается одна структура этого типа. Структура MENU используется программным обеспечением оконных меню в Главе 10. Должен быть массив структур MENU с одним элементом для каждого проталкиваемого вниз меню. Список операторов #define используется для придания - 33 - операторам в twindow.c лучшей читаемости. Мнемонические имена соответствуют элементам структуры WINDOW, указанной ссылкой wnd. Все функции в twindow.c используют имя этой ссылки по соглашению. twindow.h содержит прототипы для всех оконных функций, которые будут вызываться прикладными программами. Исходный листинг: twindow.c. ---------------------------- Листинг 6.2 - это twindow.c. Он содержит все описанные ранее в этой главе функции. Вы должны откомпилировать его и связывать его объектный модуль с любой программой, которая использует окна. Поскольку он вызывает функции из ibmpc.c, его объектный модуль должен быть также включен в редактирование связей. Листинг 6-2: twindow.c /* twindow.c */ #include #include #include #include #include #include #include #include "twindow.h" #include "keys.h" #define TABS 4 #define SCRNHT 25 #define SCRNWIDTH 80 #define ON 1 #define OFF 0 - 34 - #define ERROR -1 /* локальные прототипы */ redraw(WINDOW *wnd); wframe(WINDOW *wnd); dtitle(WINDOW *wnd); int *waddr(WINDOW *wnd, int x, int y); vswap(WINDOW *wnd); vsave(WINDOW *wnd); vrstr(WINDOW *wnd); add_list(WINDOW *wnd); beg_list(WINDOW *wnd); remove_list(WINDOW *wnd); insert_list(WINDOW *wl, WINDOW *w2); #ifndef FASTWINDOWS int dget(WINDOW *wnd, int x, int y); verify_wnd(WINDOW **wl); #endif /* массив наборов символов рамки */ struct { int nw, ne, se, sw, side, line; } wcs[] = { {218,191,217,192,179,196}, /* одинарная линия */ {201,187,188,200,186,205}, /* двойная линия */ {214,183,189,211,186,196}, /* одинарный верх, двойные бока */ {213,184,190,212,179,205}, /* двойной верх, одинарные бока */ {194,194,217,192,179,196}, /* выталкиваемое вниз меню */ }; /* голова и хвост связанного списка оконных структур */ WINDOW *listhead = NULL; WINDOW *listtail = NULL; int VSG; /* адрес видеосегмента */ /* создание нового окна */ - 35 - WINDOW *establish_window(x, y, h, w) { WINDOW *wnd; VSG = (vmode() == 7 ? 0xb000 : 0xb800); if ((wnd = (WINDOW *) malloc(sizeof (WINDOW))) == NULL) return NULL; /* параметры ограничений */ WTITLE = ""; HEIGHT = min(h, SCRNHT); WIDTH = min(w, SCRNWIDTH); COL = max(0, min(x, SCRNWIDTH-WIDTH)); ROW = max(0, min(y, SCRNHT-HEIGHT)); WCURS = 0; SCROLL = 0; SELECT = 1; BTYPE = 0; VISIBLE = HIDDEN = 0; PREV = NEXT = NULL; FHEAD = FTAIL = NULL; WBORDER=WNORMAL=PNORMAL=WTITLEC = clr(BLACK, WHITE, BRIGHT); WACCENT = clr(WHITE, BLACK, DIM); if ((SAV = malloc(WIDTH * HEIGHT * 2)) == (char *) 0) return NULL; add_list(wnd); #ifndef FASTWINDOWS clear_window(wnd); wframe(wnd); #endif return wnd; } /* установить рамку окна */ void set_border(WINDOW *wnd, int btype) { if (verify_wnd(&wnd)) { - 36 - BTYPE = btype; redraw(wnd); } } /* установить цвета */ void set_colors(WINDOW *wnd,int area, int bg, int fg, int inten) { if (vmode() == 7) { if (bg != WHITE && bg != BLACK) return; if (fg != WHITE && fg != BLACK) return; } if (verify_wnd(&wnd)) { if (area == ALL) while (area) WCOLOR [--area] = clr(bg, fg, inten); else WCOLOR [area] = clr(bg, fg, inten); redraw(wnd); } } /* установить яркость окна */ void set_intensity(WINDOW *wnd, int inten) { int area = ALL; if (verify_wnd(&wnd)) { while (area) { WCOLOR [--area] &= ~BRIGHT; WCOLOR [area] |= inten; } redraw(wnd); } } - 37 - /* установить заголовок */ void set_title(WINDOW *wnd, char *title) { if (verify_wnd(&wnd)) { WTITLE = title; redraw(wnd); } } /* перевыдать окно при изменении атрибута */ static redraw(WINDOW *wnd) { #ifndef FASTWINDOWS int x, y, chat, atr; for (y = 1; y < HEIGHT-1; y++) for (x = 1; x < WIDTH-1; x++) { chat = dget(wnd, x, y); atr = (((chat>>8)&255) == PNORMAL ? WNORMAL : WACCENT); displ(wnd, x, y, chat&255, atr); } wframe(wnd); #endif PNORMAL = WNORMAL; } /* выдать созданное окно */ void display_window(WINDOW *wnd) { if (verify_wnd(&wnd) && !VISIBLE) { VISIBLE = 1; #ifdef FASTWINDOWS if (HIDDEN) { HIDDEN = 0; vrstr(wnd); - 38 - } else { vsave(wnd); clear_window(wnd); wframe(wnd); } #else vswap(wnd); #endif } } /* закрыть все окна */ void close_all() { WINDOW *sav, *wnd = listtail; while (wnd) { sav = PREV; delete_window(wnd); wnd = sav; } } /* удалить окно */ void delete_window(WINDOW *wnd) { if (verify_wnd(&wnd)) { hide_window(wnd); free(SAV); remove_list(wnd); /* удалить окно из списка */ free(wnd); } } /* скрыть окно */ void hide_window(WINDOW *wnd) - 39 - { if (verify_wnd(&wnd) && VISIBLE) { #ifndef FASTWINDOWS vswap(wnd); #else vrstr(wnd); #endif HIDDEN = 1; VISIBLE = 0; } } #ifndef FASTWINDOWS /* перемещение окна в его 3-х мерном плане */ void repos_wnd(WINDOW *wnd, int x, int y, int z) { WINDOW *twnd; int x1, y1, chat; if (!verify_wnd(&wnd)) return; twnd = establish_window(x+COL, y+ROW, HEIGHT, WIDTH); twnd -> _tl = WTITLE; twnd -> btype = BTYPE; twnd -> wcolor[BORDER] = WBORDER; twnd -> wcolor[TITLE] = WTITLEC; twnd -> wcolor[ACCENT] = WACCENT; twnd -> wcolor[NORMAL] = WNORMAL; twnd -> _wsp = SCROLL; twnd -> _cr = WCURS; if (z != 1) { remove_list(twnd); if (z == 0) insert_list(twnd, wnd); else beg_list(twnd); } for (y1 = 0; y1 < twnd->_wh; y1++) - 40 - for (x1 = 0; x1 < twnd->_ww; x1++) { chat = dget(wnd, x1, y1); displ(twnd, x1, y1, chat&255, (chat>>8)&255); } twnd->_wv = 1; vswap(twnd); hide_window(wnd); free(SAV); remove_list(wnd); *wnd = *twnd; insert_list(wnd, twnd); remove_list(twnd); free(twnd); } #endif /* очистить область окна */ void clear_window(WINDOW *wnd) { register int x1, y1; if (verify_wnd(&wnd)) for (y1 = 1; y1 < HEIGHT-1; y1++) for (x1 = 1; x1 < WIDTH-1; x1++) displ(wnd,x1, y1, ' ', WNORMAL); } /* изобразить окно */ static wframe(WINDOW *wnd) { register int x1, y1; if (!verify_wnd(&wnd)) return; /* заголовок окна */ displ(wnd,0, 0, NW, WBORDER); dtitle(wnd); - 41 - displ(wnd,WIDTH-1, 0, NE, WBORDER); /* боковые стороны окна */ for (y1 = 1; y1 < HEIGHT-1; y1++) { displ(wnd,0, y1, SIDE, WBORDER); displ(wnd,WIDTH-1, y1, SIDE, WBORDER); } /* низ окна */ displ(wnd,0, y1, SW, WBORDER); for (x1 = 1; x1 < WIDTH-1; x1++) displ(wnd,x1, y1, LINE, WBORDER); displ(wnd,x1, y1, SE, WBORDER); } /* выдать заголовок окна */ static dtitle(WINDOW *wnd) { int x1 = 1, i, ln; char *s = WTITLE; if (!verify_wnd(&wnd)) return; if (s) { ln = strlen(s); if (ln > WIDTH-2) i = 0; else i = ((WIDTH-2-ln) / 2); if (i > 0) while (i--) displ(wnd, x1++, 0, LINE, WBORDER); while (*s && x1 < WIDTH-1) displ(wnd, x1++, 0, *s++, WTITLEC); } while (x1 < WIDTH-1) displ(wnd, x1++, 0, LINE, WBORDER); } - 42 - /* оконно-ориентированная printf */ void wprintf(WINDOW *wnd, char *ln, ...) { char dlin [100], *dl = dlin; if (verify_wnd(&wnd)) { va_list ap; va_start(ap, ln); vsprintf(dlin, ln, ap); va_end(ap); while (*dl) wputchar(wnd, *dl++); } } /* записать символ в окно */ void wputchar(WINDOW *wnd, int c) { if (!verify_wnd(&wnd)) return; switch (c) { case '\n': if (SCROLL == HEIGHT-3) scroll(wnd, UP); else SCROLL++; WCURS = 0; break; case '\t': do displ(wnd,(WCURS++)+3,SCROLL+1,'',WNORMAL); while ((WCURS%TABS) && (WCURS+1) < WIDTH-1); break; default: ; if ((WCURS+1) < WIDTH-1) { displ(wnd, WCURS+1, SCROLL+1, c, WNORMAL); WCURS++; } - 43 - break; } } /* установить курсор окна */ void wcursor(WINDOW *wnd, int x, int y) { if (verify_wnd(&wnd) && x < WIDTH-1 && y < HEIGHT-1) { WCURS = x; SCROLL = y; cursor(COL+x+1, ROW+y+1); } } /* позволяет пользователю произвести оконный выбор */ int get_selections(WINDOW *wnd, int s, char *keys) { int c = 0, ky; if (!verify_wnd(&wnd)) return 0; SELECT = s; while (c != ESC && c != '\r' && c != BS && c != FWD) { accent(wnd); c = get_char(); deaccent(wnd); switch (c) { case UP: if (SELECT > 1) SELECT--; else SELECT = SCROLL+1; break; case DN: if (SELECT < SCROLL+1) SELECT++; else SELECT = 1; break; case '\r': - 44 - case ESC: case FWD: case BS: break; default: if (keys) { ky = 0; while (*(keys + ky)) { if (*(keys+ky)==toupper(c) || *(keys+ky)==tolower(c)) return ky + 1; ky++; } } break; } } return c == '\r' ? SELECT : c == ESC ? 0 : c; } union REGS rg; /* прокручивает содержимое окна вверх или вниз */ void scroll(WINDOW *wnd, int dir) { int row = HEIGHT-1, col, chat; if (!verify_wnd(&wnd)) return; if (NEXT == NULL && HEIGHT > 3 && VISIBLE) { rg.h.ah = dir == UP ? 6 : 7; rg.h.al = 1; rg.h.bh = WNORMAL; rg.h.cl = COL + 1; rg.h.ch = ROW + 1; rg.h.dl = COL + WIDTH - 2; rg.h.dh = ROW + HEIGHT - 2; int86(16, &rg, &rg); - 45 - return; } if (dir == UP) { for (row = 2; row < HEIGHT-1; row++) for (col = 1; col < WIDTH-1; col++) { chat = dget(wnd, col, row); displ(wnd,col,row-1,chat&255,(chat>>8)&255); } for (col = 1; col < WIDTH-1; col++) displ(wnd, col, row-1, ' ', WNORMAL); } else { for (row = HEIGHT-2; row > 1; --row) for (col = 1; col < WIDTH-1; col++) { chat = dget(wnd, col, row-1); displ(wnd,col,row,chat&255,(chat>>8)&255); } for (col = 1; col < WIDTH-1; col++) displ(wnd, col, row, '', WNORMAL); } } #ifndef FASTWINDOWS /* вычисляет абрис отображаемого символа окна */ static int *waddr(WINDOW *wnd, int x, int y) { WINDOW *nxt = NEXT; int *vp; if (!VISIBLE) return (int *) (SAV+y*(WIDTH*2)+x*2); x += COL; y += ROW; while (nxt) { if (nxt->_wv) if (x >= nxt->_wx && x <= nxt->_wx + nxt->_ww-1) - 46 - if (y >= nxt->_wy && y <= nxt->_wy + nxt->_wh-1) { x -= nxt->_wx; y -= nxt->_wy; vp = (int *) ((nxt->_ws) +y*(nxt->_ww*2)+x*2); return vp; } nxt = nxt->_nx; } return NULL; } /* выдать символ в окно */ void displ(WINDOW *wnd, int x, int y, int ch, int at) { int *vp; int vch = (ch&255)|(at<<8); if ((vp = waddr(wnd, x, y)) != NULL) *vp = vch; else vpoke(VSG,vad(x+COL,y+ROW),vch); } /* получить отображенный символ из окна */ static int dget(WINDOW *wnd, int x, int y) { int *vp; if ((vp = waddr(wnd, x, y)) != NULL) return *vp; return vpeek(VSG,vad(x+COL,y+ROW)); } /* видеофункции низкого уровня */ - 47 - /* обменивает содержимое видеообраза и буфера сохранения */ static vswap(WINDOW *wnd) { int x, y, chat; int *bf = (int *) SAV; for (y = 0; y < HEIGHT; y++) for (x = 0; x < WIDTH; x++) { chat = *bf; *bf++ = dget(wnd, x, y); displ(wnd, x, y, chat&255, (chat>>8)&255); } } #else /* сохраняет видеопамять в буфере сохранения */ static vsave(WINDOW *wnd) { int x, y; int *bf = (int *) SAV; for (y = 0; y < HEIGHT; y++) for (x = 0; x < WIDTH; x++) *bf++ = vpeek(VSG, vad(x+COL, y+ROW)); } /* восстанавливает видеопамять из буфера сохранения */ static vrstr(WINDOW *wnd) { int x, y; int *bf = (int *) SAV; for (y = 0; y < HEIGHT; y++) for (x = 0; x < WIDTH; x++) vpoke(VSG,vad(x+COL, y+ROW), *bf++); - 48 - } #endif /* заменяет яркость строки, указываемой SELECT */ void acline(WINDOW *wnd, int set) { int x, ch; if (!verify_wnd(&wnd)) return; for (x = 1; x < WIDTH - 1; x++) { ch = dget(wnd, x, SELECT) & 255; displ(wnd, x, SELECT, ch, set); } } /* ФУНКЦИИ ОБРАБОТКИ СПИСКА */ /* добавляет окно в конец списка */ static add_list(WINDOW *wnd) { if (listtail) { PREV = listtail; listtail->_nx = wnd; } listtail = wnd; if (!listhead) listhead = wnd; } /* добавляет окно в начало списка */ static beg_list(WINDOW *wnd) { if (listhead) { NEXT = listhead; listhead->_pv = wnd; } - 49 - listhead = wnd; if (!listtail) listtail = wnd; } /* удаляет окно из списка */ static remove_list(WINDOW *wnd) { if (NEXT) NEXT->_pv = PREV; if (PREV) PREV->_nx = NEXT; if (listhead == wnd) listhead = NEXT; if (listtail == wnd) listtail = PREV; NEXT = PREV = NULL; } /* вставляет w 1 после w 2 */ static insert_list(WINDOW *w1, WINDOW *w2) { w1->_pv = w2; w1->_nx = w2->_nx; w2->_nx = w1; if (w1->_nx == NULL) listtail = w1; else w1->_nx->_pv = w1; } #ifndef FASTWINDOWS /* проверяет наличие окна в списке */ static verify_wnd(WINDOW **w1) { WINDOW *wnd; wnd = listhead; - 50 - if (*w1 == NULL) *w1 = listtail; else { while (wnd != NULL) { if (*w1 == wnd) break; wnd = NEXT; } } return wnd != NULL; } #endif WINDOW *ewnd = NULL; /* сообщение об ошибках */ void error_message(char *s) { ewnd = establish_window(50, 22, 3, max(10, strlen(s)+2)); set_colors(ewnd, ALL, RED, YELLOW, BRIGHT); set_title(ewnd, " ERROR! "); display_window(ewnd); wprintf(ewnd, s); putchar(BELL); } void clear_message() { if (ewnd) delete_window(ewnd); ewnd = NULL; } Описание программы: twindow.c ----------------------------- - 51 - Далее описывается исходная программа twindow.c. Для каждой функции описывается, что она делает и как работает. Программист может использовать эти описания для понимания текста программы. Объявления внешних данных в twindow.c включают прототипы для каждой функции, локальные в исходном файле, массив структур для определения пяти типов рамки окна, головной и хвостовой указатели для списка структур WINDOW. Рамка окна управляется элементом структуры WINDOW, которая устанавливает окно. Этот элемент является целочисленным смещением в таблице типов рамки. Вход, на который указывает смещение, содержит шесть значений, каждое из которых представляет одну из сторон или узлов окна. Первое значение определяет верхний левый или северо-западный угол. Имя переменной (nw, ne, se, sw) сообщает вам, какой угол определяется. Целое число side относится к вертикальным сторонам рамки; целое число line соответствует верхней и нижней горизонтальным линиям рамки. Значения относятся к символам из набора графических символов ПЭВМ. Две WINDOW-ссылки listhead и listtail являются головным и хвостовым указателями для списка окон. Когда создаются окна, они добавляются к этому списку. Первоначально эти два указателя равны NULL. Когда создается первое окно, выделяется память для структуры WINDOW, и ее адрес копируется в оба указателя. У списка имеется голова, указывающая на первое окно списка, и хвост, указывающий на последнее. Когда создается второе окно, его адрес копируется в хвостовой указатель. Кроме того, адрес второго окна записывается в указатель _nx в первой структуре WINDOW, а адрес первой записывается в указатель _pv второй. Голова списка указывает на первое окно, которое указывает на второе и т.д. Хвост списка указывает на последнее окно. Каждое окно также указывает на своего предшественника в цепи, следовательно, список является двунаправленной структурой данных, называемой двусвязным списком. (Для знакомства со списковыми структурами данных см.: - 52 - Brady. С Development Tools for the IBM PC. - 1986.). Функция establish_window инициализирует переменную VSG адресом сегмента видеопамяти. Функция распределяет память для структуры WINDOW и инициализирует эту структуру оконными характеристиками, принимаемыми по умолчанию, а также размером и координатами, заданными при вызове функции. Она выделяет память для буфера сохранения видеопамяти и записывает адрес буфера в структуру WINDOW. После инициализации структуры функция вызывает add_list для добавления структуры к списку окон. Текстовая область окна очищается, и образ окна выделяется, если обрабатываются слоеные окна. Эти функции оперируют в буфере сохранения, поэтому окно пока не изображается. Функция establish_ window возвращает адрес структуры WINDOW в точку вызова. Функции set_border, set_colors, set_intensity и set_title модифицируют характеристики созданного окна. Сначала они вызывают verify_wnd для проверки того, что при вызове передан адрес созданного окна. Затем они модифицируют заданный атрибут. В конце они вызывают функцию redraw для записи изменений на экран. Функция redraw перевыдает окно, если обрабатываются слоеные окна.