Содержание
Введение
Версии Tcl и Тк
Расширения Tcl и Тк
Tcl в World Wide Web
Ftp-архивы
Группы новостей
На кого рассчитана эта книга
Как работать с данной книгой
Примеры программ
Соглашения о представлении материала
Пиктограммы
Как организована данная книга
Особенности четвертого издания книги
Благодарности за помощь в подготовке первого издания книги
Благодарности за помощь в подготовке второго издания книги
Благодарности за помощь в подготовке третьего издания книги
Благодарности за помощь в подготовке четвертого издания книги
Как связаться с автором книги
Ждем ваших отзывов
Часть I. Основы Tcl
Hello, World!
Переменные
Подстановка команд
Математические выражения
Подстановка символов, представленных с помощью обратной косой черты
Группировка с помощью фигурных скобок и двойных кавычек
Группировка перед подстановкой
Группировка математических выражений с помощью фигурных скобок
Примеры подстановки
Процедуры
Пример вычисления факториала
Дополнительные сведения о переменных
Команда unset
Проверка наличия переменных
Дополнительные сведения о математических выражениях
Комментарии
Правила подстановки и группировки
Особенности группировки и подстановки
Справочная информация
Арифметические операции
Встроенные математические функции
Основные Tcl-команды
Глава 2. Первые шаги
Tcl-сценарии в системе Unix
Главное меню Windows
Macintosh OS 8/9 и ResEdit
Команда console
Параметры командной строки
Предопределенные переменные
Глава 3. Простой CGI-сценарий
Стандарт CGI и динамическое создание Web-страниц
Сценарий guestbook.cgi
Формирование начала HTML-документа
Данные, генерируемые CGI-сценарием
Использование массива Tcl в качестве базы данных
Создание HTML-форм и обработка данных, введенных пользователем
Пакеты ncgi и cgi.tcl
Сценарий newguest.cgi
Сохранение данных с помощью Тсl-сценариев
Обработка ошибок в CGI-сценариях
Усовершенствование сценария
Глава 4. Обработка строк
Строки в составе выражений
Сравнение строк с шаблонами
Классы символов
Преобразование с помощью карты символов
Команда append
Команда format
Команда scan
Команда binary
Примеры
Двоичные данные и обмен с файлами
Источники дополнительных сведений
Глава 5. Списки
Формирование списков
Команда lappend
Команда lset
Команда concat
Извлечение элементов списков: команды llength, lindex и Irange
Изменение списков: команды linsert и lreplace
Поиск в списке: команда lsearch
Сортировка списков: команда lsort
Команда split
Команда join
Источники дополнительных сведений
Глава 6. Управляющие структуры
Команда switch
Команда while
Команда foreach
Работа с несколькими списками
Команда for
Команды break и continue
Команда catch
Команда error
Команда return
Глава 7. Процедуры и область видимости
Изменение имен команд с помощью команды rename
Область видимости
Команда global
Передача имени с помощью команды upvar
Создание псевдонимов с помощью команды upvar
Пространства имен и команда upvar
Команды, работающие с именами переменных
Глава 8. Массивы Tcl
Переменные массивов
Команда array
Передача имен массивов
Создание структур данных на базе массивов
Стек
Список массивов
Простая база данных
Альтернативы использованию массивов
Глава 9. Работа с программами и файлами
Особенности выполнения команды exec в системе Windows
AppleScript в системе Macintosh
Команда file
Имена файлов на различных платформах
Выделение компонентов пути: команды split, dirname, tail
Действия с файлами и каталогами
Создание каталогов
Фиксированные и символьные ссылки
Удаление файлов
Переименование файлов и каталогов
Атрибуты файлов
Использование команд ввода-вывода
Открытие файлов
Расширение Expect
Чтение и запись данных
Команда read
Окончание строки символов
Произвольный доступ к данным
Закрытие каналов ввода-вывода
Текущий каталог: команды cd и pwd
Действия с файлами с использованием команды glob
Команды exit и pid
Переменные окружения
Команда registry
Часть II. Расширенные средства Tcl
Команды, выполняющие конкатенацию параметров
Команды, обеспечивающие обратный вызов
Командный префикс
Динамическое формирование процедур
Выполнение конкатенации в команде eval
Проблемы с цитированием в команде eval
Команда uplevel
Команда subst
Глава 11. Регулярные выражения
Правила записи регулярных выражений
Наборы символов
Итераторы
Оператор выбора
Использование якорей
Использование обратной косой черты
Порядок установления соответствия
Выделение подшаблонов
Расширенные регулярные выражения
Последовательности, начинающиеся с обратной косой черты
Классы символов
\
Итераторы с ограниченным числом повторений
Обратные ссылки
Упреждающее сравнение
Коды символов
Элементы замены
Классы эквивалентности
Сравнение, чувствительное к переводу строки
Встроенные опции
Открытый синтаксис
Синтаксис регулярных выражений
Команда regexp
Проблемы, возникающие при использовании итераторов
Примеры регулярных выражений
Команда regsub
Преобразование данных в программу с помощью regsub
Разбор CGI-параметров
Декодирование HTML-примитивов
Простая программа разбора HTML-кода
Удаление HTML-комментариев
Команды, использующие регулярные выражения
Глава 12. Библиотеки сценариев и пакеты
Использование пакетов
Пакеты, реализованные в виде С-программ
Порядок загрузки пакетов
Команда package
Создание библиотек с помощью файла tcllndex
Команда unknown
Запрет использования библиотеки: auto_noload
Соглашения, действующие при интерактивной работе
Предыстория вызова команд
Сокращения имен команд
Среда оболочки Tcl
Процедура tcl_findLibrary
Стиль программирования
Глобальные массивы и представление переменных состояния
Официальные руководства
Глава 13. Сведения об интерпретаторе и средства отладки
Команда info
Процедуры
Стек вызова
Выполнение команд
Сценарии и библиотеки
Номера версий
Среда выполнения
Выполнение программ на различных платформах
Контроль переменных и команд
Переменные, предназначенные только для чтения
Создание элементов массива с помощью команды trace
Предыстория вызова команд
Обращение к предыстории в Tcl и С shell
Отладка
Tcl Dev Kit
Инструмент Checker
Инструмент Compiler
Инструмент TclApp
Инструмент Service Manager
Инструмент Inspector
Прочие инструменты
Critcl
Команда bgerror
Команда tkerror
Контроль производительности программ
Tcl-компилятор
Глава 14. Пространства имен
Переменные в пространствах имен
Поиск команд
Вложенные пространства имен
Импортирование и экспортирование процедур
Пространства имен и обратный вызов
Интроспекция
Команда namespace
Преобразование существующих пакетов для работы с пространствами имен
Объектная система [incr Tcl]
Объектная система xotcl
Замечания
Команда variable в глобальной области видимости
Автозагрузка и процедура auto_import
Пространства имен и команда uplevel
Особенности именования пространств имен
Дополнительные операции
Глава 15. Интернационализация
Кодирование файлов и команда fconfigure
Сценарии, представленные в различных кодировках
Unicode и UTF-8
Двоичная кодировка
Преобразование кодировок
Команда encoding
Каталоги сообщений
Управление файлами каталогов сообщений
Каталоги сообщений и пространства имен
Пакет msgcat
Глава 16. Программы, управляемые событиями
Команда after
Команда fileevent
Команда vwait
Команда fconfigure
Команда fblocked
Буферизация
Преобразование символа конца строки
Обработка символа конца файла
Последовательные устройства
Кодировки
Настройка каналов ввода-вывода
Глава 17. Использование сетевых гнезд
Стандартная библиотека Tcl
HTTP
Гнезда на стороне клиента
Серверные гнезда
Служба echo
Получение данных по протоколу HTTP
Запрос HEAD
Запросы GET и POST
Команда fcopy
Пакет http
Команда http::geturl
Команда http::formatQuery
Команды http::register и http::unregister
Команда http::reset
Команда http::cleanup
Basic Authentication
Глава 18. Web-сервер TclHttpd
Добавление кода к TclHttpd
Модификация главной программы
Обработчики доменов
Пакеты html и ncgi
Передача клиенту результатов обработки запроса
Application Direct
Работа с MIME-типами
Типы документов
Шаблоны HTML + Tcl
Шаблоны и структура Web-узла
Использование переменных для хранения информации о Web-узле
Обработчики данных форм
Шаблоны для обработчиков данных форм
Формы самопроверки
Пакет html
Назначение процедур
Стандартные модули Application Direct
Отладка
Передача почтовых сообщений
Дистрибутивный пакет TclHttpd
Состав дистрибутивного пакета
Настройка сервера
Имя сервера и номер порта
Идентификатор пользователя и группы
Адрес администратора Web-узла
Корневой каталог документов
Различные установки для работы с документами
Шаблоны документов
Файлы протоколов
Каталоги CGI
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl
Создание интерпретаторов
Имя интерпретатора в роли команды
Использование команды list в составе interp eval
Защищенные интерпретаторы
Псевдонимы команд
Скрытые команды
Подстановка
Поддержка ввода-вывода защищенными интерпретаторами
Защищенная база
Политики безопасности
Ограничения на использование временных файлов
Защищенная команда after
Глава 20. Safe-Tk и дополнительный модуль браузера
Ограничения Safe-Tk
Дополнительный модуль браузера
Пример дополнительного модуля
Установка дополнительного модуля
Политики безопасности и дополнительный модуль
Настройка политик безопасности
Конфигурационные файлы для политик
Наборы возможностей
Создание новой политики безопасности
Глава 21. Многопотоковые Тсl-сценарии
Поддержка потоков в Tcl
Использование расширений с многопотоковыми сценариями
Использование расширения Thread
Создание соединяемых потоков
Передача сообщений потокам
Передача асинхронных сообщений
Сохранение и освобождение потоков
Обработка ошибок
Разделяемые ресурсы
Управление каналами ввода-вывода
Передача каналов между потоками
Разделяемые переменные
Мютексы и переменные условий
Переменные условий
Пулы потоков
Команды пакета Thread
Пространство имен tsv
Пространство имен tpool
Глава 22. Tclkit и Starkit
Доставка приложений в виде файлов Starkit
Виртуальные файловые системы
Использование sdx для сборки приложений
Просмотр содержимого Starkit-файла
Стандартная организация пакета
Создание файла Starpack
Использование виртуальной файловой системы пакета Starkit
Создание разделяемых файлов Starkit
Metakit
Обращение к базе данных Metakit
Создание просмотров Metakit
Сохранение данных в Starkit-файле
Wikit и Wiki
Особенности применения Starkit-файлов
Самообновляющиеся приложения
Простые инсталляторы
Часть III. Основы Тк
Именование компонентов Тк
Настройка компонентов Тк
Атрибуты компонентов Тк и база ресурсов
Команды Тк
Команды для выполнения действий с компонентами
Процедуры поддержки
Наборы компонентов
Tix
[incr Tk] и [incr Widgets]
BWidgets
TkTable
Глава 24. Tk в примерах
Фрейм для кнопок
Командные кнопки
Текстовая метка и элемент ввода
Обработка нажатий клавиш и фокус ввода
Размеры текста и полоса прокрутки
Процедура Run
Процедура Log
Процедура Stop
Работа на различных платформах
Браузер
Управление состоянием
Поиск файлов
Каскадное меню
Текстовый компонент, предназначенный только для чтения
Тсl-оболочка
Использование нескольких интерпретаторов
Внешний вид окон
Глава 25. Диспетчер компоновки pack
Горизонтальное и вертикальное размещение
Модель полостей
Пространство компоновки и пространство отображения
Внутреннее дополнение, задаваемое с помощью опций -ipadx и -ipady
Внешнее дополнение, задаваемое с помощью опций -padx и-pady
Изменение размеров окон и опция -expand
Фиксация
Очередь компоновки
Особенности компоновки полос прокрутки
Выбор родительского компонента при компоновке
Исключение компонента из очереди компоновки
Правила компоновки
Стек окон
Глава 26. Диспетчер компоновки grid
Внешнее дополнение, реализуемое с помощью опций -padx и-pady
Внутреннее дополнение, реализуемое с помощью опций -ipadx и -ipady
Размещение нескольких компонентов в одной ячейке
Объединение строк и столбцов
Атрибуты строк и столбцов
Минимальный размер ячейки
Поддержка окон с изменяемыми размерами
Опция -uniform
Команда grid
Глава 27. Диспетчер компоновки place
Управление панелями
Выравнивание фреймов
Связывание событий
Управление размещением фреймов
Команда place
Глава 28. Компонент panedwindow
Программирование компонента panedwindow
Атрибуты panedwindow
Глава 29. Связывание команд с событиями
Команда bindtags
Использование команд break и continue
Определение новых дескрипторов связывания
Описание событий
События мыши
Прочие события
Связывания для окон верхнего уровня
Модификаторы
Последовательности событий
Виртуальные события
Генерация событий
Информация о событиях
Ключевые слова событий
Часть IV. Компоненты Тк
Кнопки, связанные с Тс1-переменными
Атрибуты кнопок
Операции с кнопками
Меню и menubutton
Системные меню
Контекстные меню
Меню опций
Расположение пунктов меню в несколько столбцов
События меню
Виртуальные события меню
Выполнение действий с меню
Атрибуты меню
Пакет для работы с меню по именам
Глава 31. База данных ресурсов
Загрузка файла базы данных
Включение записей в базу данных
Обращение к базе данных
Кнопки, определяемые пользователем
Меню, определяемые пользователями
Особенности подстановки переменных
Глава 32. Простые компоненты Тк
Использование компонента labelframe
Включение других приложений
Стили окон верхнего уровня
Компонент label
Атрибуты компонента label
Компонент message
Выравнивание текста, содержащегося в компонентах label и message
Компонент scale
Атрибуты компонента scale
Программирование линейных регуляторов
Команда bell
Глава 33. Полосы прокрутки
Протокол взаимодействия с полосами прокрутки
Операции xview и yview
Компонент scrollbar
Атрибуты компонента scrollbar
Программирование полос прокрутки
Глава 34. Поля редактирования и инкрементные регуляторы
Советы по использованию полей редактирования
Использование компонента spinbox
Связывания для компонентов entry и spinbox
Атрибуты компонентов entry и spinbox
Программирование полей редактирования и инкрементных регуляторов
Глава 35. Окна списков
Программирование компонента listbox
Компонент listbox
Связывания для компонента listbox
Режим выделения single
Режим выделения extended
Режим выделения multiple
Связывания для прокрутки
Виртуальные события для компонента listbox
Атрибуты компонента listbox
Глава 36. Текстовый компонент
Индексные выражения
Сравнение индексов
Текстовые маркеры
Дескрипторы
Использование атрибутов нескольких дескрипторов
Междустрочный интервал и выравнивание
Табуляторы
Выделение
Связывания для дескрипторов
Поиск текста
Встроенные компоненты
Встроенные изображения
Чтение содержимого текстового компонента
Получение сведений о маркерах
Дамп содержимого компонента
Отмена выполненных действий
Связывания и события
Виртуальные события
Операции с текстом
Атрибуты текстового компонента
Глава 37. Компонент canvas
Программа Hello, World!
Линейный регулятор для определения минимального и максимального значения
Объекты холста
Штриховые линии
Дуги
Битовые карты
Изображения
Линии
Овалы
Многоугольники
Прямоугольники
Текстовые объекты
Окна
Операции над компонентом canvas
Генерация Postscript-описаний
Атрибуты компонента canvas
Советы
Представление координат
Масштабирование и вращение
Работа с ресурсами
Объекты, определяемые посредством большого количества точек
Выбор объектов холста
Часть V. Особенности работы Тк
Команда selection
Команда clipboard
Обработчики выделений
Глава 39. Диалоговые окна и фокус ввода
Диалоговые окна для работы с файлами и каталогами
Диалоговые окна для выбора цвета
Диалоговые окна, определяемые разработчиком
Команда focus
Передача фокуса ввода
Команда tkwait
Удаление компонентов
Совместное использование команд focus, grab и tkwait
Диалоговое окно для ввода строки
Комбинации клавиш и фокус ввода
Анимация и команда update
Глава 40. Атрибуты компонентов Тк
Размеры компонента
Обрамление и рельеф
Подсветка компонентов, обладающих фокусом ввода
Дополнение и точки фиксации
Глава 41. Цвет, изображения и курсоры
Цветовые значения
Карты отображения цвета и визуальные классы
Битовые карты и изображения
Битовые карты
Атрибут bitmap
Изображения photo
Текстовый курсор
Курсор мыши
Глава 42. Шрифты и текстовые атрибуты
Системные шрифты
Шрифты Unicode
Имена шрифтов X Window
Метрика шрифта
Команда font
Текстовые атрибуты
Атрибуты, управляющие выделенным текстом
Использование сетки и изменение размеров
Программа, реализующая выбор шрифтов
Глава 43. Команда send
Сценарий, осуществляющий передачу данных
Взаимодействие процессов
Удаленное выполнение eval с использованием сетевых гнезд
Глава 44. Диспетчеры окон
Пиктограммы
Состояние сеанса
Прочие операции с диспетчером окон
Команда winfo
Взаимосвязь между компонентами одного семейства
Размер компонента
Расположение компонентов
Виртуальное корневое окно
Работа с атомами и идентификаторами
Карты отображения цвета и визуальные классы
Команда tk
Глава 45. Поддержка пользовательских установок
Определение пользовательских установок
Интерфейс пользовательских установок
Управление файлом пользовательских установок
Отслеживание изменений в переменных пользовательских установок
Доработка пакета
Глава 46. Интерфейс для определения связываний
Средства редактирования связываний
Сохранение и загрузка связываний
Часть VI. Программирование на языке С
Командные процедуры С и объекты данных
SWIG
Инициализация Tcl
Вызов Тсl-сценариев
Использование библиотеки Tcl С
Создание загружаемых пакетов
Процедура инициализации пакета
Использование Tcl_PkgProvide
Командная процедура С
Коды завершения командных процедур
Управление результирующими строками
Командный интерфейс Tcl_Obj
Управление счетчиком ссылок Tcl_Obj
Модификация значений Tcl_Obj
Проблемы, связанные с использованием разделяемых значений Tcl_Obj
Команда blob
Тсl_Аllос, ckalloc и malloc
Обработка параметров и использование Tcl_GetIndexFromObj
Создание и удаление элементов хэш-таблицы
Формирование списка
Поддержка ссылок на значения Tcl_Obj
Использование Tcl_ Preserve и Tcl_ Release для защиты данных
Макрос CONST в Tcl 8.4 API
Действия со строками и интернационализация
Преобразование наборов символов
Tcl_Main и Tcl_AppInit
Цикл обработки событий
Вызов сценариев из С-программ
Отказ от Tcl_Eval
Глава 48. Компиляция Tcl и программных расширений
Структура инсталляционного каталога
Построение Tcl из исходных кодов
Стандартные опции программы configure
Инсталляция
Использование библиотек-заглушек
Использование autoconf
Создание шаблонов
Пример расширения
Файл Makefile.in
Стандартные файлы заголовков
Использование расширения
Глава 49. Создание компонентов Тк на языке С
Структура данных компонента
Команда класса компонента
Команда экземпляра компонента
Установка и изменение значений атрибутов
Определение атрибутов компонента
Отображение часов
Поддержка оконных событий
Освобождение ресурсов
Глава 50. Библиотеки С
Создание и удаление интерпретаторов
Создание и удаление команд
Пакеты и динамическая загрузка
Управление результирующими строками
Распределение памяти
Работа со списками
Разбор команд
Конвейерная обработка
Отслеживание действий интерпретатора
Выполнение Тсl-команд
Информация об ошибках
Действия с Тсl-переменными
Обработка выражений
Преобразование чисел
Объекты Tcl
Основные типы объектов
Строковые объекты
ByteArray для двоичных данных
Динамические строки
Наборы символов
AssocData и структуры данных интерпретатора
Хэш-таблицы
Обработка опций
Регулярные выражения и проверка строк
Реализация цикла обработки событий
Работа с файлами
События таймера
Обратные вызовы времени бездействия
Ввод-вывод
Драйверы каналов ввода-вывода
Обработка имен файлов
Получение информации о файловой системе
Реализация виртуальной файловой системы
Поддержка потоков
Работа с сигналами
Нормальное завершение программы
Macintosh
Аварийное завершение
Прочие процедуры
Общие сведения о С-библиотеке Тк
Создание окон
Имя приложения для команды send
Настройка окон
Опции командной строки
Координаты окон
Стек окон
Информация об окнах
Установка атрибутов компонента
Выделение данных и буфер обмена
Интерфейс цикла обработки событий
Обработка оконных событий
Связывание событий
Захват событий клавиатуры
Обработка ошибок графического протокола
Использование базы данных ресурсов
Управление битовыми картами
Создание новых типов изображений
Использование изображений в составе компонентов
Изображения photo
Поддержка объектов холста
Диспетчеры компоновки
Идентификаторы строк
Карты отображения цвета и визуальные классы
Обрамления с имитацией трехмерных эффектов
Курсоры мыши
Шрифты и отображение текста
Графический контекст
Выделение памяти для карты пикселей
Экранные единицы измерения
Использование рельефа
Позиция фиксации
Стили концов линий
Стили соединения линий
Штриховые линии
Стили выравнивания текста
Атомы
Управление идентификаторами ресурсов
Дескрипторы приложений Windows
Часть VII. Изменения в составе Tcl и Тк
Средства, поддержка которых была прекращена
Операция cget
Подсветка при наличии фокуса ввода
Связывания
Полосы прокрутки
Команда pack
Поддержка фокуса ввода
Команда send
Внутреннее дополнение
Значения переключателей опций
Поле редактирования
Меню
Окна списков
Атрибут geometry
Текстовый компонент
Атрибуты управления цветом
Работа с цветом и команда tk colormodel
Атрибут scrollincrement
Выделение
Команда bell
Глава 52. Tcl 7.5/Tk 4.1
Преобразование символа новой строки
Переменная tcl_platform
Команда console
Команда clock
Команда load
Команда package
Использование нескольких переменных цикла
Перенос цикла обработки событий из Тк в Tcl
Сетевые гнезда
Команда fconfigure
Использование нескольких интерпретаторов и Safe-Tcl
Диспетчер компоновки grid
Текстовый компонент
Поле редактирования
Глава 53. Tcl 7.6/Tk 4.2
Виртуальные события
Стандартные диалоговые окна
Диспетчер компоновки grid
Команда unsupported! в системе Macintosh
Глава 54. Tcl/Tk 8.0
Поддержка двоичных строк
Пространства имен
Safe-Tcl
Новый вариант lsort
Переменная tcl_precision
Соглашения 2000
Пакет http
Обмен через последовательные линии связи
Платформенно-независимые шрифты
Команда tk scaling
Включение приложений
Платформенно-ориентированные меню
Толщина обрамления
Платформенно-ориентированные кнопки и полосы прокрутки
Изображения в составе текстового компонента
Команда destroy
Команда grid
Модификации версии 8.0
Процедура tcl_findLibrary
Процедура auto_mkindex_old
Символы клавиш Windows для работы с меню
Событие MouseWheel
Атрибут fill для текста на холсте
Процедура safe::loadTk
Глава 55. Tcl/Tk 8.1
Команда encoding
Пакет msgcat
С API для работы с UTF-8 и Unicode
Поддержка потоков
Расширенные регулярные выражения
Работа со строками
Расширение DDE
Дополнительные возможности
Глава 56. Tcl/Tk 8.2
Эффективные операции со строками
Пустые имена массивов
Особенности создания дополнительных модулей для браузера
Управление последовательными портами в системе Windows
Синтаксис расширенных регулярных выражений
Глава 57. Tcl/Tk 8.3
Новые опции команды glob
Команды для работы с регулярными выражениями
Результаты команды scan
Удаление повторяющихся элементов списка с помощью lsort
Удаление элементов массива
Модификация команды clock
Поддержка отложенной загрузки пакетов
Дополнение Img
Шаблон штриховых линий
Скрытый текст
Управление курсором мыши
Проверка содержимого поля редактирования
Прочие средства Тк
Диалоговое окно для выбора каталогов
Взаимодействие оконного диспетчера с окнами верхнего уровня
Поддержка системных курсоров Windows
Поддержка колесика прокрутки в системе Unix
Новый модификатор Quadruple
Модификации версии 8.3
Диалоговое окно выбора файлов в системе Macintosh
Атрибут state для текстовых меток
Поддержка пиктограмм в системе Windows
Новые страницы интерактивной справочной системы
Глава 58. Tcl/Tk 8.4
Преобразование 64-битовых значений
Поддержка 64-битовой файловой системы
Размер машинного слова
Дополнительные средства для работы с файловой системой
Команды file и glob
Работа со списками
Поиск в массивах
Расширенные средства поддержки обмена через последовательные линии
Новые операторы сравнения строк
Отслеживание выполнения команд
Интроспекция
Прочие изменения в составе Tcl
Данные, возвращаемые командой regsub
Повышенное разрешение таймера в системе Windows
Модифицированная команда fcopy
Новые компоненты Тк
Отмена действий в текстовом компоненте и другие дополнения
Новые возможности диспетчеров компоновки pack и grid
Размеры строк и столбцов в диспетчере компоновки grid
Отображение текста и изображений в составе компонента
Новые атрибуты, определяющие рельеф кнопок
Управление состоянием полей редактирования и окон списков
Работа с оконным диспетчером
Прочие изменения в составе Tk
Поддержка прозрачных изображений
Выбор нескольких файлов с помощью tk_getOpenFile
Поддержка кнопок фиксированной ширины
Доступ к содержимому буфера обмена
Информация об использовании изображения
Новые события для диспетчеров окон
Управление текстовым курсором
Новая опция команды bell
Генерация Postscript-описаний для встроенных окон
Глава 59. Содержимое компакт-диска
Предметный указатель
Текст
                    Практическое
программирование на
Tcl и Тк
ЧЕТВЕРТОЕ ИЗДАНИЕ


Practical Programming in Tcl and Tk FOURTH EDITION Brent B. Welch Ken Jones with Jeffrey Hobbs PRENTICE HALL PTR PRENTICE HALL PTR Upper Saddle River, NJ 07458 www.phptr.com
Практическое программирование на Tcl и Тк ЧЕТВЕРТОЕ ИЗДАНИЕ Брент Уэлш Кен Джонс при участии Джеффри Хоббса Москва • Санкт-Петербург • Киев 2004
ББК 32.973.26-018.2.75 У98 УДК 681.3.07 Издательский дом "Вильяме" Зав. редакцией С.Н. Тригуб Перевод с английского и редакция В. В. Вейшмана По общим вопросам обращайтесь в Издательский дом "Вильяме" по адресу: info@williamspublishing.com, http://www.williamspublishing.com Уэлш, Брент Б., Джонс, Кен, Хоббс, Джеффри У98 Практическое программирование на Tcl и Тк, 4-е издание. : Пер. с англ. — М. : Издательский дом "Вильяме", 2004, — 1136 с. : ил. — Парал. тит. англ. ISBN 5-8459-0661-Х (рус.) В этой книге рассматривается широкий спектр вопросов, связанных с применением языка Tcl и расширения Тк. Здесь обсуждаются базовые средства составления Tcl-программ, динамическая генерация команд, использование регулярных выражений, ввод-вывод, управляемый событиями, компоненты Тк, работа с изображениями и цветом, установление соответствия между событиями и Tcl-командами средства конфигурирования и другие важные вопросы. Авторы также уделили внимание программированию Тс1-расширений на языке С. Данная книга будет полезна как опытным разработчикам, так и начинающим программистам. ББК 32.973.26-018.2.75 Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм. Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то пи было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Prentice Hall PTR. Authorized translation from the English language edition published by Prentice Hall PTR, Copyright © 2003 by Pearson Education, Inc. All rights reserved. No part of this book may be reproduced, stored in retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise without either the prior written permission о the Publisher. Russian language edition published by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2004 ISBN 5-8459-0661-Х (рус.) © Издательский дом "Вильяме", 2004 ISBN 0-13-038560-3 (англ.) © Pearson Education, Inc., 2003
Оглавление Часть I. Основы Tcl 55 Глава 1. Общие сведения о языке Tcl 56 Глава 2. Первые шаги 85 Глава 3. Простой CGI-сценарий 93 Глава 4. Обработка строк 114 Глава 5. Списки 133 Глава 6. Управляющие структуры 147 Глава 7. Процедуры и область видимости 163 Глава 8. Массивы Tcl 172 Глава 9. Работа с программами и файлами 186 Часть П. Расширенные средства Tcl 215 Глава 10. Цитирование и использование команды eval 217 Глава 11. Регулярные выражения 234 Глава 12. Библиотеки сценариев и пакеты 274 Глава 13. Сведения об интерпретаторе и средства отладки 291 Глава 14. Пространства имен 320 Глава 15. Интернационализация 337 Глава 16. Программы, управляемые событиями 350 Глава 17. Использование сетевых гнезд 362 Глава 18. Web-сервер TclHttpd 388 Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 432 Глава 20. Safe-Tk и дополнительный модуль браузера 459 Глава 21. Многопотоковые Тс1-сценарии 472 Глава 22. Tclkit и Starkit 508 Часть III. Основы Тк 529 Глава 23. Общие сведения о Тк 530 Глава 24. Тк в примерах 543 Глава 25. Диспетчер компоновки раек 566 Глава 26. Диспетчер компоновки grid 587 Глава 27. Диспетчер компоновки place 599
б Оглавление Глава 28. Компонент panedwindow 608 Глава 29. Связывание команд с событиями 615 Часть IV. Компоненты Тк 637 Глава 30. Кнопки и меню 639 Глава 31. База данных ресурсов 667 Глава 32. Простые компоненты Тк 681 Глава 33. Полосы прокрутки 698 Глава 34. Поля редактирования и инкрементные регуляторы 708 Глава 35. Окна списков 724 Глава 36. Текстовый компонент 738 Глава 37. Компонент canvas 771 Часть V. Особенности работы Тк 813 Глава 38. Выделение данных и буфер обмена 815 Глава 39. Диалоговые окна и фокус ввода 825 Глава 40. Атрибуты компонентов Тк 840 Глава 41. Цвет, изображения и курсоры 850 Глава 42. Шрифты и текстовые атрибуты 869 Глава 43. Команда send 885 Глава 44. Диспетчеры окон 896 Глава 45. Поддержка пользовательских установок 915 Глава 46. Интерфейс для определения связываний 929 Часть VI. Программирование на языке С 937 Глава 47. С-программы и язык Tcl 938 Глава 48. Компиляция Tcl и программных расширений 985 Глава 49. Создание компонентов Тк на языке С 1002 Глава 50. Библиотеки С 1033 Часть VII. Изменения в составе Tcl и Тк 1059 Глава 51. Tcl 7.4/Тк 4.0 1060 Глава 52. Tcl 7.5/Тк 4.1 1069 Глава 53. Tcl 7.6/Тк 4.2 1074 Глава 54. Тс1/Тк 8.0 1077 Глава 55. Тс1/Тк 8.1 1086 Глава 56. Тс1/Тк 8.2 1091 Глава 57. Тс1/Тк 8.3 1094 Глава 58. Тс1/Тк 8.4 1101 Глава 59. Содержимое компакт-диска 1112 Предметный указатель 1115
Содержание Введение 38 Некоторые аргументы в пользу выбора Tcl 39 Версии Tcl и Тк 42 Расширения Tcl и Тк 44 Tcl в World Wide Web 44 Ftp-архивы 45 Группы новостей 45 На кого рассчитана эта книга 45 Как работать с данной книгой 46 Примеры программ 46 Соглашения о представлении материала 46 Пиктограммы 47 Как организована данная книга 47 Особенности четвертого издания книги 49 Благодарности за помощь в подготовке первого издания книги 49 Благодарности за помощь в подготовке второго издания книги 51 Благодарности за помощь в подготовке третьего издания книги 52 Благодарности за помощь в подготовке четвертого издания книги 52 Как связаться с автором книги 53 Ждем ваших отзывов 53 Часть I. Основы Tcl 55 Глава 1. Общие сведения о языке Tcl 56 Команды Tcl 57 Hello, World! 57 Переменные 58
8 Содержа н Подстановка команд 59 Математические выражения 60 Подстановка символов, представленных с помощью обратной косой черты 61 Группировка с помощью фигурных скобок и двойных кавычек 63 Особенности использования квадратных скобок 64 Группировка перед подстановкой 64 Группировка математических выражений с помощью фигурных скобок 65 Примеры подстановки 66 Процедуры 66 Пример вычисления факториала 68 Дополнительные сведения о переменных 70 Особенности применения различных символов в именах переменных 71 Команда unset 72 Проверка наличия переменных 72 Дополнительные сведения о математических выражениях 72 Комментарии 74 Правила подстановки и группировки 75 Особенности группировки и подстановки 77 Справочная информация 79 Последовательности символов, начинающиеся с обратной косой черты 79 Арифметические операции 80 Встроенные математические функции 80 Основные Tcl-команды 81 Глава 2. Первые шаги 85 Кохманда source 86 Tcl-сценарии в системе Unix 86 Главное меню Windows 88 Macintosh OS 8/9 и ResEdit 88 Macintosh OS X 89 Команда console 90 Параметры командной строки 90 Опции кохмандной строки, отображаемые программой wish 91 Предопределенные переменные 92
Содержание 9 Глава 3. Простой CGI-сценарий 93 Общие сведения о языке HTML 94 Стандарт CGI и динамическое создание Web-страниц 96 Сценарий guestbook.cgi 97 Использование библиотечного файла 98 Формирование начала HTML-документа 99 Данные, генерируемые CGI-сценарием 101 Использование массива Tcl в качестве базы данных 103 Создание HTML-форм и обработка данных, введенных пользователем 105 Форма newguest.html 105 Пакеты ncgi и cgi.tcl 107 Сценарий newguest.cgi 107 Сохранение данных с помощью Тс1-сценариев 109 Обработка ошибок в CGI-сценариях ПО Усовершенствование сценария 113 Глава 4. Обработка строк 114 Команда string 114 Индексы 117 Строки в составе выражений 118 Сравнение строк с шаблонами 119 Классы символов 121 Преобразование с помощью карты символов 122 Команда append 123 Команда format 123 Команда scan 127 Команда binary 127 Шаблоны форматирования 128 Примеры 130 Двоичные данные и обмен с файлами 132 Источники дополнительных сведений 132 Глава 5. Списки 133 Списки в языке Tcl 134 Формирование списков 136 Команда list 136 Команда lappend 137 Команда lset 138 Команда concat 138 Извлечение элементов списков: команды llength, lindex и Irange 139 Изменение списков: команды linsert и lreplace 140
10 Содержание Поиск в списке: команда lsearch 141 Сортировка списков: команда lsort 143 Команда split 144 Команда join 146 Источники дополнительных сведений 146 Глава 6. Управляющие структуры 147 Выражение if then else 148 Команда switch 150 Комментарии в составе команды switch 152 Команда while 152 Команда foreach 153 Использование нескольких переменных цикла 155 Работа с несколькими списками 156 Команда for 156 Команды break и continue 157 Команда catch 157 Обработка ситуаций, не являющихся ошибочными 159 Команда error 159 Команда return 161 Глава 7. Процедуры и область видимости 163 Команда ргос 163 Изменение имен команд с помощью команды rename 165 Область видимости 166 Команда global 167 Передача имени с помощью команды upvar 168 Создание псевдонимов с помощью команды upvar 169 Использование команды upvar для поддержки состояния объектов 170 Пространства имен и команда upvar 170 Команды, работающие с именами переменных 171 Глава 8. Массивы Tcl 172 Языковые средства для работы с массивами 172 Индексы 173 Переменные массивов 174 Команда array 175 Преобразование массивов в списки 176 Передача имен массивов 177 Создание структур данных на базе массивов 178 Простые записи 178
Содержание 11 Стек 180 Список массивов 181 Простая база данных 183 Альтернативы использованию массивов 184 Глава 9. Работа с программами и файлами 186 Запуск программ с помощью команды exec 186 Переменная auto_noexec 189 Особенности выполнения команды exec в системе Windows 189 AppleScript в системе Macintosh 189 Команда file 190 Имена файлов на различных платформах 192 Формирование пути: команда file join 193 Выделение компонентов пути: команды split, dirname, tail 195 Действия с файлами и каталогами 196 Копирование файлов 196 Создание каталогов 196 Фиксированные и символьные ссылки 197 Удаление файлов 197 Переименование файлов и каталогов 198 Атрибуты файлов 198 Использование команд ввода-вывода 201 Открытие файлов 202 Создание канала связи с процессом 204 Расширение Expect 205 Чтение и запись данных 205 Команды puts и gets 206 Команда read 206 Окончание строки символов 207 Произвольный доступ к данным 208 Закрытие каналов ввода-вывода 209 Текущий каталог: команды cd и pwd 209 Действия с файлами с использованием команды glob 209 Расширение символа ~ 211 Команды exit и pid 212 Переменные окружения 212 Команда registry 213 Часть II. Расширенные средства Tcl 215 Глава 10. Цитирование и использование команды eval 217 Формирование кода с помощью команды list 218
12 Содержание Команда eval 218 Команды, выполняющие конкатенацию параметров 220 Команды, обеспечивающие обратный вызов 221 Командный префикс 221 Динамическое формирование процедур 222 Выполнение конкатенации в команде eval 224 Использование команды eval в процедуре оболочки 224 Проблемы с цитированием в команде eval 227 Команда uplevel 229 Команда subst 232 Обработка строк с помощью команды subst 233 Глава 11. Регулярные выражения 234 В каких случаях целесообразно использовать регулярные выражения 235 Решение часто возникающих проблем 236 Правила записи регулярных выражений 237 Сравнение символов 237 Наборы символов 237 Итераторы 238 Оператор выбора 239 Использование якорей 240 Использование обратной косой черты 240 Порядок установления соответствия 241 Выделение подшаблонов 242 Расширенные регулярные выражения 242 Совместимость с шаблонами, поддерживаемыми в Tcl 8.0 243 Последовательности, начинающиеся с обратной косой черты 244 Классы символов 244 "Экономное" сопоставление 245 Итераторы с ограниченным числом повторений 246 Обратные ссылки 247 Упреждающее сравнение 247 Коды символов 248 Элементы замены 249 Классы эквивалентности 249 Сравнение, чувствительное к переводу строки 249 Встроенные опции 250 Открытый синтаксис 250 Синтаксис регулярных выражений 251 Команда regexp 255
Содержание 13 Шаблон для разбора URL 257 Проблемы, возникающие при использовании итераторов 260 Примеры регулярных выражений 260 Команда regsub 262 Преобразование данных в программу с помощью regsub 263 Декодирование URL 263 Разбор CGI-параметров 265 Декодирование HTML-примитивов 267 Простая программа разбора HTML-кода 269 Удаление HTML-комментариев 272 Команды, использующие регулярные выражения 272 Глава 12. Библиотеки сценариев и пакеты 274 Доступ к пакетам: переменная auto_path 275 Использование пакетов 276 Автоматическая загрузка пакетов 277 Пакеты, реализованные в виде С-программ 278 Порядок загрузки пакетов 279 Команда package 280 Создание библиотек с помощью файла tcllndex 281 Команда unknown 283 Автозагрузка 283 Запрет использования библиотеки: auto_noload 284 Соглашения, действующие при интерактивной работе 285 Автоматический вызов программ 285 Предыстория вызова команд 285 Сокращения имен команд 285 Среда оболочки Tcl 285 Расположение библиотеки сценариев Tcl 286 Процедура tcl_findLibrary 287 Стиль программирования 288 Префиксы имен процедур 288 Глобальные массивы и представление переменных состояния 289 Официальные руководства 289 Глава 13. Сведения об интерпретаторе и средства отладки 291 Команда clock 292 Команда info 296 Переменные 297 Процедуры 297 Стек вызова 300 Выполнение команд 301
14 Содержание Сценарии и библиотеки 302 Номера версий 303 Среда выполнения 303 Выполнение программ на различных платформах 304 Контроль переменных и команд 305 Контроль выполнения команд 306 Переменные, предназначенные только для чтения 307 Создание элементов массива с помощью команды trace 308 Предыстория вызова команд 309 Вызов команды history 310 Обращение к предыстории в Tcl и С shell 311 Отладка 312 Tcl Dev Kit 313 Отладчик с расширенными возможностями 314 Инструмент Checker 314 Инструмент Compiler 314 Инструмент TclApp 315 Инструмент Service Manager 315 Инструмент Inspector 315 Прочие инструменты 315 Консоль tkcon 316 Critcl 316 Команда bgerror 316 Команда tkerror 316 Контроль производительности программ 317 Информация о времени в файле протокола 317 Tcl-компилятор 318 Глава 14. Пространства имен 320 Использование пространств имен 321 Переменные в пространствах имен 323 Составные имена 324 Поиск команд 325 Вложенные пространства имен 326 Импортирование и экспортирование процедур 327 Пространства имен и обратный вызов 329 Интроспекция 330 Команда namespace 331 Преобразование существующих пакетов для работы с пространствами имен 332 Объектная система [incr Tcl] 333
Содержание 15 Объектная система xotcl 333 Замечания 334 Имена компонентов, изображений и интерпретаторов 334 Команда variable в глобальной области видимости 334 Автозагрузка и процедура auto_import 334 Пространства имен и команда uplevel 335 Особенности именования пространств имен 335 Дополнительные операции 336 Глава 15. Интернационализация 337 Наборы символов pi кодировки 338 Системная кодировка 339 Кодирование файлов и команда fconfigure 340 Сценарии, представленные в различных кодировках 341 Unicode и UTF-8 341 Двоичная кодировка 342 Преобразование кодировок 343 Команда encoding 343 Каталоги сообщений 344 Определение локального языка 345 Управление файлами каталогов сообщений 346 Каталоги сообщений и пространства имен 347 Пакет msgcat 349 Глава 16. Программы, управляемые событиями 350 Цикл обработки событий Tcl 351 Команда after 351 Команда fileevent 352 Команда vwait 354 Команда fconfigure 356 Неблокирующий режим ввода-вывода 357 Команда fblocked 359 Буферизация 359 Преобразование символа конца строки 359 Обработка символа конца файла 360 Последовательные устройства 360 Кодировки 361 Настройка каналов ввода-вывода 361
16 Содержание Глава 17. Использование сетевых гнезд 362 Сетевые расширения Tcl 363 Scotty 363 Стандартная библиотека Tcl 363 HTTP 364 Гнезда на стороне клиента 364 Опции, используемые при создании клиентских гнезд 365 Серверные гнезда 366 Опции, используемые при создании серверных гнезд 367 Служба echo 367 Получение данных по протоколу HTTP 370 Proxy-серверы 371 Запрос HEAD 373 Запросы GET и POST 375 Команда fcopy 379 Пакет http 380 Команда http::config 381 Команда http::geturl 381 Команда http::formatQuery 385 Команды http::register и http::unregister 385 Команда http::reset 385 Команда http::cleanup 386 Basic Authentication 386 Глава 18. Web-сервер TclHttpd 388 Интеграция TclHttpd с прикладными программами 389 Архитектура TclHttpd 390 Добавление кода к TclHttpd 391 Модификация главной программы 392 Обработчики доменов 392 Состояние соединения и данные запроса 393 Пакеты html и ncgi 394 Передача клиенту результатов обработки запроса 394 Application Direct 394 Обработка данных, содержащихся в запросе 396 Работа с МШЕ-типами 398 Типы документов 398 Шаблоны HTML + Tcl 400 Размещение Tcl-кода 401 Шаблоны и структура Web-узла 402
Содержание 17 Использование переменных для хранения информации о Web-узле 406 Обработчики данных форм 407 Обработчики Application Direct 408 Шаблоны для обработчиков данных форм 411 Формы самопроверки 411 Пакет html 413 Назначение процедур 415 Стандартные модули Application Direct 419 Информация о состоянии сервера 420 Отладка 420 Передача почтовых сообщений 422 Дистрибутивный пакет TclHttpd 424 Запуск сервера 424 Состав дистрибутивного пакета 425 Настройка сервера 426 Опции командной строки 426 Имя сервера и номер порта 427 Идентификатор пользователя и группы 428 Адрес администратора Web-узла 428 Корневой каталог документов 428 Различные установки для работы с документами 429 Шаблоны документов 430 Файлы протоколов 430 Каталоги CGI 431 Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 432 Команда interp 433 Создание интерпретаторов 435 Иерархия интерпретаторов 436 Имя интерпретатора в роли команды 437 Использование команды list в составе interp eval 437 Защищенные интерпретаторы 438 Псевдонимы команд 439 Интроспекция псевдонимов 440 Скрытые команды 441 Подстановка 442 Поддержка ввода-вывода защищенными интерпретаторами 444 Защищенная база 446 Политики безопасности 448
18 Содержание Ограничение доступа к гнездам 448 Ограничения на использование временных файлов 451 Защищенная команда after 456 Глава 20. Safe-Tk и дополнительный модуль браузера 459 Тк в ведомых интерпретаторах 460 Встроенные окна Тк 460 Ограничения Safe-Tk 461 Дополнительный модуль браузера 462 Переменные embed_args и plugin 463 Пример дополнительного модуля 464 Установка дополнительного модуля 464 Политики безопасности и дополнительный модуль 465 Пакет browser 467 Настройка политик безопасности 468 Файл config/plugin.cfg 469 Конфигурационные файлы для политик 470 Наборы возможностей 470 Создание новой политики безопасности 471 Глава 21. Многопотоковые Тс1-сценарии 472 Что такое поток 473 Поддержка потоков в Tcl 474 Подготовка Tcl-интерпретатора с поддержкой потоков 474 Использование расширений с многопотоковыми сценариями 475 Использование расширения Thread 476 Создание потоков 476 Создание соединяемых потоков 478 Передача сообщений потокам 481 Передача синхронных сообщений 481 Передача асинхронных сообщений 483 Сохранение и освобождение потоков 484 Обработка ошибок 485 Разделяемые ресурсы 486 Управление каналами ввода-вывода 487 Доступ к файлам из различных потоков 487 Передача каналов между потоками 488 Разделяемые переменные 493 Мютексы и переменные условий 495 Мютексы 495 Переменные условий 496 Пулы потоков 499
Содержание 19 Команды пакета Thread 501 Пространство имен thread 501 Пространство имен tsv 504 Пространство имен tpool 506 Глава 22. Tclkit и Starkit 508 Использование Tclkit 509 Структура файла Starkit 510 Доставка приложений в виде файлов Starkit 510 Виртуальные файловые системы 511 Обращение к содержимому ZIP-файла с помощью VFS 512 Использование sdx для сборки приложений 513 Создание простого файла Starkit 513 Просмотр содержимого Starkit-файла 514 Стандартная организация пакета 514 Создание файла Starpack 515 Использование виртуальной файловой системы пакета Starkit 515 Создание tclhttpd.kit 517 Создание разделяемых файлов Starkit 519 Metakit 521 Модель данных Metakit 522 Обращение к базе данных Metakit 522 Создание просмотров Metakit 525 Сохранение данных в Starkit-файле 526 Wikit и Wiki 527 Особенности применения Starkit-файлов 527 Наборы документов 527 Самообновляющиеся приложения 528 Простые инсталляторы 528 Часть III. Основы Тк 529 Глава 23. Общие сведения о Тк 530 Тк-программа "Hello, World!" 532 Именование компонентов Тк 534 Настройка компонентов Тк 535 Атрибуты компонентов Тк и база ресурсов 536 Справочная информация Тк 537 Команды Тк 538 Команды для создания компонентов 538 Команды для выполнения действий с компонентами 539 Процедуры поддержки 540
20 Содержание Наборы компонентов 541 BLT 541 Tix 542 [incr Tk] и [incr Widgets] 542 BWidgets 542 TkTable 542 Глава 24. Tk в примерах 543 ExecLog 543 Заголовок окна 546 Фрейм для кнопок 546 Командные кнопки 547 Текстовая метка и элемент ввода 547 Обработка нажатий клавиш и фокус ввода 547 Размеры текста и полоса прокрутки 548 Процедура Run 549 Процедура Log 550 Процедура Stop 550 Работа на различных платформах 551 Браузер 552 Изменение размеров окон 556 Управление состоянием 556 Поиск файлов 556 Каскадное меню 558 Текстовый компонент, предназначенный только для чтения 559 Тс1-оболочка 559 Текстовые маркеры, дескрипторы и связывание 563 Использование нескольких интерпретаторов 563 Внешний вид окон 564 Глава 25. Диспетчер компоновки pack 566 Размещение относительно направления 567 Размеры окон и команда pack propagate 568 Горизонтальное и вертикальное размещение 568 Модель полостей 570 Пространство компоновки и пространство отображения 572 Опция -fill 572 Внутреннее дополнение, задаваемое с помощью опций -ipadx и -ipady 573 Внешнее дополнение, задаваемое с помощью опций -padx и-pady 575 Изменение размеров окон и опция -expand 576
Содержание 21 Фиксация 578 Очередь компоновки 580 Интроспекция 581 Особенности компоновки полос прокрутки 581 Выбор родительского компонента при компоновке 582 Исключение компонента из очереди компоновки 583 Правила компоновки 583 Команда pack 584 Стек окон 586 Глава 26. Диспетчер компоновки grid 587 Размещение компонентов в виде таблицы 587 Опция -sticky 588 Внешнее дополнение, реализуемое с помощью опций -padx и-pady 590 Внутреннее дополнение, реализуемое с помощью опций -ipadx и -ipady 590 Размещение нескольких компонентов в одной ячейке 591 Объединение строк и столбцов 592 Атрибуты строк и столбцов 593 Дополнение строк и столбцов 593 Минимальный размер ячейки 594 Поддержка окон с изменяемыми размерами 594 Опция -uniform 596 Команда grid 597 Глава 27. Диспетчер компоновки place 599 Общие сведения о диспетчере компоновки place 599 Управление панелями 601 Разбор параметров и поддержка состояния 603 Выравнивание фреймов 603 Связывание событий 604 Управление размещением фреймов 605 Команда place 606 Глава 28. Компонент panedwindow 608 Использование panedwindow 608 Управление содержимым панелей 609 Программирование компонента panedwindow 611 Атрибуты panedwindow 613
22 Содержание Глава 29. Связывание команд с событиями 615 Команда bind 616 Команда bindtags 617 Обработка нажатий клавиш и фокус ввода 619 Использование команд break и continue 620 Определение новых дескрипторов связывания 620 Описание событий 621 События клавиатуры 623 События мыши 624 Прочие события 625 Связывания для окон верхнего уровня 626 Модификаторы 626 Последовательности событий 629 Виртуальные события 631 Генерация событий 632 Информация о событиях 633 Синтаксис команды event 633 Ключевые слова событий 634 Часть IV. Компоненты Тк 637 Глава 30. Кнопки и меню 639 Команды, вызываемые посредством кнопок, и области видимости 640 Кнопки, связанные с Тс1-переменными 645 Атрибуты кнопок 647 Операции с кнопками 650 Меню и menubutton 651 Строка меню 653 Системные меню 654 Контекстные меню 654 Меню опций 654 Расположение пунктов меню в несколько столбцов 655 События меню 655 Выбор пунктов меню с помощью клавиатуры 655 Виртуальные события меню 656 Выполнение действий с меню 656 Атрибуты меню 658 Пакет для работы с меню по именам 661 Комбинации клавиш для доступа к меню 665
Содержание 23 Глава 31. База данных ресурсов 667 Общие сведения о ресурсах 667 Шаблоны ресурсов 668 Загрузка файла базы данных 670 Включение записей в базу данных 671 Обращение к базе данных 672 Кнопки, определяемые пользователем 672 Меню, определяемые пользователями 674 Приложение и пользовательские ресурсы 677 Особенности подстановки переменных 679 Глава 32. Простые компоненты Тк 681 Фреймы и окна верхнего уровня 682 Атрибуты компонентов frame, labelframe и toplevel 682 Использование компонента labelframe 684 Включение других приложений 685 Стили окон верхнего уровня 686 Компонент label 687 Атрибуты width и wrapLength 688 Атрибуты компонента label 689 Компонент message 690 Атрибуты компонента message 692 Выравнивание текста, содержащегося в компонентах label и message 693 Компонент scale 693 Связывания для компонента scale 694 Атрибуты компонента scale 695 Программирование линейных регуляторов 696 Команда bell 697 Глава 33. Полосы прокрутки 698 Использование полос прокрутки 698 Протокол взаимодействия с полосами прокрутки 700 Операция set полосы прокрутки 701 Операции xview и yview 702 Компонент scrollbar 703 Связывания для полос прокрутки 704 Атрибуты компонента scrollbar 705 Программирование полос прокрутки 706
24 Содержание Глава 34. Поля редактирования и инкрементные регуляторы 708 Использование полей редактирования 709 Проверка содержимого полей редактирования 710 Советы по использованию полей редактирования 712 Использование компонента spinbox 713 Связывания для компонентов entry и spinbox 716 Атрибуты компонентов entry и spinbox 718 Программирование полей редактирования и инкрементных регуляторов 721 Глава 35. Окна списков 724 Использование окон списков 724 Выполнение действий с содержимым компонента listbox 724 Программирование компонента listbox 725 Компонент listbox 728 Связывания для компонента listbox 731 Режим выделения Browse 731 Режим выделения single 732 Режим выделения extended 732 Режим выделения multiple 734 Связывания для прокрутки 734 Виртуальные события для компонента listbox 735 Атрибуты компонента listbox 735 Режим сетки 737 Глава 36. Текстовый компонент 738 Индексы 739 Вставка и удаление текста 740 Индексные выражения 740 Сравнение индексов 741 Текстовые маркеры 742 Направление маркера 743 Дескрипторы 743 Атрибуты дескрипторов 744 Использование атрибутов нескольких дескрипторов 746 Междустрочный интервал и выравнивание 747 Табуляторы 750 Выделение 750 Связывания для дескрипторов 751 Поиск текста 753 Встроенные компоненты 754 Встроенные изображения 756
Содержание 25 Чтение содержимого текстового компонента 757 Получение информации о дескрипторах 758 Получение сведений о маркерах 759 Дамп содержимого компонента 759 Отмена выполненных действий 760 Связывания и события 762 Связывания для текстового компонента 762 Виртуальные события 764 Операции с текстом 765 Атрибуты текстового компонента 769 Глава 37. Компонент canvas 771 Координаты холста 771 Программа Hello, World! 773 Дескрипторы холста 774 Линейный регулятор для определения минимального и максимального значения 776 Объекты холста 782 Состояние холста и его объектов 783 Штриховые линии 784 Дуги 785 Битовые карты 786 Изображения 787 Линии 788 Овалы 791 Многоугольники 791 Прямоугольники 792 Текстовые объекты 793 Окна 799 Операции над компонентом canvas 802 Генерация Postscript-описаний 805 Атрибуты компонента canvas 808 Советы 810 Экранные координаты и координаты холста 810 Представление координат 810 Масштабирование и вращение 810 Работа с ресурсами 811 Объекты, определяемые посредством большого количества точек 811 Выбор объектов холста 811
26 Содержание Часть V. Особенности работы Тк 813 Глава 38. Выделение данных и буфер обмена 815 Модель выделения 815 Команда selection 818 Команда clipboard 819 Обработчики выделений 820 Обработчик выделения для холста 820 Глава 39. Диалоговые окна и фокус ввода 825 Стандартные диалоговые окна 826 Окна сообщений 826 Диалоговые окна для работы с файлами и каталогами 827 Диалоговые окна для выбора цвета 829 Диалоговые окна, определяемые разработчиком 829 Фокус ввода 830 Команда focus 831 Передача фокуса ввода 831 Захват ввода 832 Команда tkwait 833 Удаление компонентов 834 Совместное использование команд focus, grab и tkwait 834 Диалоговое окно для ввода строки 836 Комбинации клавиш и фокус ввода 838 Анимация и команда update 838 Глава 40. Атрибуты компонентов Тк 840 Установка значения атрибутов 840 Размеры компонента 842 Обрамление и рельеф 845 Подсветка компонентов, обладающих фокусом ввода 846 Дополнение и точки фиксации 847 Глава 41. Цвет, изображения и курсоры 850 Работа с цветом 851 Цветовые палитры 853 Цветовые значения 853 Карты отображения цвета и визуальные классы 856 Битовые карты и изображения 858 Команда image 859 Битовые карты 860 Атрибут bitmap 860 Изображения photo 861
Содержание 27 Текстовый курсор 865 Курсор мыши 866 Глава 42. Шрифты и текстовые атрибуты 869 Именование шрифтов 870 Именованные шрифты 871 Системные шрифты 872 Шрифты Unicode 872 Имена шрифтов X Window 873 Особенности работы со шрифтами в ранних версиях Тк 874 Метрика шрифта 875 Команда font 876 Текстовые атрибуты 877 Размещение 877 Атрибуты, управляющие выделенным текстом 878 Использование сетки и изменение размеров 878 Программа, реализующая выбор шрифтов 879 Глава 43. Команда send 885 Возможности команды send 886 Команда send и авторизация X Window 887 Сценарий, осуществляющий передачу данных 888 Взаимодействие процессов 890 Удаленное выполнение eval с использованием сетевых гнезд 892 Глава 44. Диспетчеры окон 896 Команда win 896 Размеры, расположение и оформление окон верхнего уровня 897 Пиктограммы 900 Состояние сеанса 901 Прочие операции с диспетчером окон 903 Команда winfo 904 Передача команд между приложениями 905 Взаимосвязь между компонентами одного семейства 906 Размер компонента 906 Расположение компонентов 908 Виртуальное корневое окно 909 Работа с атомами и идентификаторами 910 Карты отображения цвета и визуальные классы 911 Команда tk 912
28 Содержание Глава 45. Поддержка пользовательских установок 915 Файлы, используемые приложением по умолчанию 915 Определение пользовательских установок 917 Интерфейс пользовательских установок 920 Управление файлом пользовательских установок 924 Отслеживание изменений в переменных пользовательских установок 926 Доработка пакета 927 Глава 46. Интерфейс для определения связываний 929 Совместная работа окон списков 932 Средства редактирования связываний 934 Сохранение и загрузка связываний 935 Часть VI. Программирование на языке С 937 Глава 47. С-программы и язык Tcl 938 Основные понятия 939 Основные подходы к написанию С-кода для Tcl-приложения 939 Командные процедуры С и объекты данных 940 SWIG 940 Инициализация Tcl 941 Вызов Тс1-сценариев 942 Использование библиотеки Tcl С 942 Создание загружаемых пакетов 944 Команда load 944 Процедура инициализации пакета 945 Использование Tcl_PkgProvide 946 Командная процедура С 947 Строковый командный интерфейс 948 Коды завершения командных процедур 950 Управление результирующими строками 950 Командный интерфейс Tcl_Obj 951 Управление счетчиком ссылок Tcl_Obj 953 Модификация значений Tcl_Obj 955 Проблемы, связанные с использованием разделяемых значений Tcl_Obj 956 Команда blob 958 Создание и удаление хэш-таблиц 958 Тс1_А11ос, ckalloc и malloc 961 Обработка параметров и использование Tcl_GetIndexFromObj 961
Содержание 29 Создание и удаление элементов хэш-таблицы 964 Формирование списка 965 Поддержка ссылок на значения Tcl_Obj 966 Использование Tcl_ Preserve и Tcl_ Release для защиты данных 967 Макрос CONST в Tcl 8.4 API 969 Действия со строками и интернационализация 970 Интерфейс DString 970 Преобразование наборов символов 971 Tcl_Main и Tcl_AppInit 972 Tk_Main 975 Цикл обработки событий 977 Вызов сценариев из С-программ 978 Разновидности Tcl_Eval 979 Отказ от Tcl_Eval 980 Глава 48. Компиляция Tcl и программных расширений 985 Стандартная структура каталогов 986 Дистрибутивный пакет 986 Структура инсталляционного каталога 987 Построение Tcl из исходных кодов 988 Инструменты configure и autoconf 989 Стандартные опции программы configure 991 Инсталляция 993 Использование библиотек-заглушек 993 Использование autoconf 995 Файл tcl.m4 995 Создание шаблонов 996 Пример расширения 996 Файл configure.in 997 Файл Makefile.in 998 Стандартные файлы заголовков 1000 Использование расширения 1000 Глава 49. Создание компонентов Тк на языке С 1002 Инициализация расширения 1003 Структура данных компонента 1004 Команда класса компонента 1005 Команда экземпляра компонента 1010 Установка и изменение значений атрибутов 1014 Определение атрибутов компонента 1019 Отображение часов 1025
30 Содержа н Поддержка оконных событий 1029 Освобождение ресурсов 1031 Глава 50. Библиотеки С 1033 Общие сведения о С-библиотеке Tcl 1034 Инициализация приложения 1034 Создание и удаление интерпретаторов 1034 Создание и удаление команд 1035 Пакеты и динамическая загрузка 1035 Управление результирующими строками 1035 Распределение памяти 1035 Работа со списками 1036 Разбор команд 1036 Конвейерная обработка 1037 Отслеживание действий интерпретатора 1037 Выполнение Тс1-команд 1037 Информация об ошибках 1038 Действия с Тс 1-переменными 1038 Обработка выражений 1039 Преобразование чисел 1039 Объекты Tcl 1039 Основные типы объектов 1039 Строковые объекты 1040 ByteArray для двоичных данных 1041 Динамические строки 1041 Наборы символов 1041 AssocData и структуры данных интерпретатора 1042 Хэш-таблицы 1042 Обработка опций 1043 Регулярные выражения и проверка строк 1043 Реализация цикла обработки событий 1043 Работа с файлами 1044 События таймера 1044 Обратные вызовы времени бездействия 1044 Ввод-вывод 1044 Драйверы каналов ввода-вывода 1045 Обработка имен файлов 1046 Получение информации о файловой системе 1046 Реализация виртуальной файловой системы 1047 Поддержка потоков 1047 Работа с сигналами 1048
Содержание 31 Нормальное завершение программы 1048 Macintosh 1048 Аварийное завершение 1048 Прочие процедуры 1048 Общие сведения о С-библиотеке Тк 1049 Главная программа и параметры командной строки 1049 Создание окон 1049 Имя приложения для команды send 1050 Настройка окон 1050 Опции командной строки 1050 Координаты окон 1050 Стек окон 1051 Информация об окнах 1051 Установка атрибутов компонента 1051 Выделение данных и буфер обмена 1051 Интерфейс цикла обработки событий 1051 Обработка оконных событий 1052 Связывание событий 1052 Захват событий клавиатуры 1052 Обработка ошибок графического протокола 1053 Использование базы данных ресурсов 1053 Управление битовыми картами 1053 Создание новых типов изображений 1053 Использование изображений в составе компонентов 1053 Изображения photo 1054 Поддержка объектов холста 1054 Диспетчеры компоновки 1054 Идентификаторы строк 1055 Карты отображения цвета и визуальные классы 1055 Обрамления с имитацией трехмерных эффектов 1056 Курсоры мыши 1056 Шрифты и отображение текста 1056 Графический контекст 1056 Выделение памяти для карты пикселей 1057 Экранные единицы измерения 1057 Использование рельефа 1057 Позиция фиксации 1057 Стили концов линий 1057 Стили соединения линий 1057 Штриховые линии 1058 Стили выравнивания текста 1058
32 Содержание Атомы 1058 Управление идентификаторами ресурсов 1058 Дескрипторы приложений Windows 1058 Часть VII. Изменения в составе Tcl и Тк 1059 Глава 51. Tcl 7.4/Тк 4.0 1060 Оболочка wish 1060 Средства, поддержка которых была прекращена 1060 Операция cget 1061 Подсветка при наличии фокуса ввода 1061 Связывания 1061 Полосы прокрутки 1062 Команда pack 1062 Поддержка фокуса ввода 1063 Команда send 1063 Внутреннее дополнение 1064 Значения переключателей опций 1064 Поле редактирования 1064 Меню 1065 Окна списков 1065 Атрибут geometry 1066 Текстовый компонент 1066 Атрибуты управления цветом 1067 Работа с цветом и команда tk colormodel 1067 Атрибут scrollincrement 1067 Выделение 1068 Команда bell 1068 Глава 52. Tcl 7.5/Tk 4.1 1069 Выполнение сценариев на нескольких платформах 1069 Обработка имен файлов 1069 Преобразование символа новой строки 1070 Переменная tcl_platform 1070 Команда console 1070 Команда clock 1070 Команда load 1071 Команда package 1071 Использование нескольких переменных цикла 1071 Перенос цикла обработки событий из Тк в Tcl 1072 Сетевые гнезда 1072 Команда info hostname 1072
Содержание 33 Команда fconfigure 1072 Использование нескольких интерпретаторов и Safe-Tcl 1073 Диспетчер компоновки grid 1073 Текстовый компонент 1073 Поле редактирования 1073 Глава 53. Tcl 7.6/Tk 4.2 1074 Дополнительные операции file 1074 Виртуальные события 1074 Стандартные диалоговые окна 1075 Диспетчер компоновки grid 1075 Команда unsupported! в системе Macintosh 1076 Глава 54. Tcl/Tk 8.0 1077 Тс1-компилятор 1078 Выявление ошибок при компиляции 1078 Поддержка двоичных строк 1078 Пространства имен 1079 Safe-Tcl 1079 Новый вариант lsort 1079 Переменная tcl_precision 1080 Соглашения 2000 1080 Пакет http 1080 Обмен через последовательные линии связи 1080 Платформенно-независимые шрифты 1081 Команда tk scaling 1081 Включение приложений 1081 Платформенно-ориентированные меню 1081 Толщина обрамления 1082 Платформенно-ориентированные кнопки и полосы прокрутки 1082 Изображения в составе текстового компонента 1082 Команда destroy 1083 Команда grid 1083 Модификации версии 8.0 1083 Опция -error команды fconfigure 1083 Элемент tcl_platform(debug) 1083 Процедура tcl_findLibrary 1084 Процедура auto_mkindex_old 1084 Символы клавиш Windows для работы с меню 1085 Событие MouseWheel 1085 Атрибут fill для текста на холсте 1085 Процедура safe::loadTk 1085
34 Содержание Глава 55. Tcl/Tk 8.1 1086 Unicode и интернационализация приложений 1086 Опция -encoding команды fconfigure 1086 Команда encoding 1087 Пакет msgcat 1087 С API для работы с UTF-8 и Unicode 1087 Поддержка потоков 1087 Команда testthread 1088 Расширенные регулярные выражения 1089 Работа со строками 1089 Расширение DDE 1089 Дополнительные возможности 1090 Обмен через последовательные линии связи 1090 Элемент tcl_platform(user) 1090 Глава 56. Tcl/Tk 8.2 1091 Модификация Trf 1091 Эффективные операции со строками 1092 Пустые имена массивов 1092 Особенности создания дополнительных модулей для браузера 1092 Управление последовательными портами в системе Windows 1093 Синтаксис расширенных регулярных выражений 1093 Глава 57. Tcl/Tk 8.3 1094 Новые команды и опции для работы с файлами 1094 Новые опции команды glob 1095 Команды для работы с регулярными выражениями 1095 Результаты команды scan 1095 Удаление повторяющихся элементов списка с помощью lsort 1095 Удаление элементов массива 1096 Модификация команды clock 1096 Поддержка отложенной загрузки пакетов 1096 Дополнение Img 1096 Шаблон штриховых линий 1096 Особенности работы с холстом 1096 Скрытый текст 1097 Управление курсором мыши 1098 Проверка содержимого поля редактирования 1098 Прочие средства Тк 1098 Модификация окон списков 1098 Диалоговое окно для выбора каталогов 1098
Содержание 35 Взаимодействие оконного диспетчера с окнами верхнего уровня 1099 Поддержка системных курсоров Windows 1099 Поддержка колесика прокрутки в системе Unix 1099 Новый модификатор Quadruple 1099 X Input Method (XIM) 1099 Модификации версии 8.3 1099 Определение типа проверки 1100 Диалоговое окно выбора файлов в системе Macintosh 1100 Атрибут state для текстовых меток 1100 Поддержка пиктограмм в системе Windows 1100 Новые страницы интерактивной справочной системы 1100 Глава 58. Tcl/Tk 8.4 1101 Поддержка 64-битового кода 1102 64-битовая арифметика 1102 Преобразование 64-битовых значений 1102 Поддержка 64-битовой файловой системы 1103 Размер машинного слова 1103 Дополнительные средства для работы с файловой системой 1103 Виртуальные файловые системы 1103 Команды file и glob 1103 Работа со списками 1104 Поиск в массивах 1104 Расширенные средства поддержки обмена через последовательные линии 1104 Новые операторы сравнения строк 1105 Отслеживание выполнения команд 1105 Интроспекция 1105 Прочие изменения в составе Tcl 1106 Удаление неиспользуемых переменных 1106 Данные, возвращаемые командой regsub 1106 Повышенное разрешение таймера в системе Windows 1106 Модифицированная команда fcopy 1106 Новые компоненты Тк 1106 Отмена действий в текстовом компоненте и другие дополнения 1107 Новые возможности диспетчеров компоновки pack и grid 1107 Несимметричное дополнение 1107 Размеры строк и столбцов в диспетчере компоновки grid 1108 Отображение текста и изображений в составе компонента 1108 Новые атрибуты, определяющие рельеф кнопок 1108
36 Содержание Управление состоянием полей редактирования и окон списков 1108 Работа с оконным диспетчером 1109 Прочие изменения в составе Tk 1109 Автоповтор 1109 Поддержка прозрачных изображений 1110 Выбор нескольких файлов с помощью tk_getOpenFile 1110 Поддержка кнопок фиксированной ширины 1110 Доступ к содержимому буфера обмена 1110 Информация об использовании изображения 1110 Новые события для диспетчеров окон 1110 Управление текстовым курсором 1111 Новая опция команды bell 1111 Генерация Postscript-описаний для встроенных окон 1111 Глава 59. Содержимое компакт-диска 1112 Техническая подаержка 1114 Предметный указатель 1115
Посвящается Джоди, Кристоферу, Дэниэлу и Майклу. Б рент Посвящается Дину за поддержку и терпение. Кен
Введение Tcl — это аббревиатура от Tool Command Language. Tcl означает как язык программирования, так и интерпретатор с этого языка. Интерпретатор Tcl можно без труда включать в состав прикладных программ. Tcl и используемый с ним набор инструментов создания графических пользовательских интерфейсов, называемый Тк, были разработаны профессором Джоном Осте- раутом (John Ousterhout) из калифорнийского университета. Tcl и Тк распространяются свободно, и их можно найти на многих узлах Internet и использовать для создания любых, в том числе коммерческих приложений. Интерпретатор Tcl был перенесен из Unix в системы DOS, PalmOS, VMS, Windows, OS/2, NT и Macintosh. Набор инструментов Tk может выполняться лишь в средах X Window, Windows и Macintosh. О Tcl я впервые услышал в 1988 году, когда учился в калифорнийском университете. В то время мы занимались созданием сетевой операционной системы Sprite. Мы, студенты, работали над новым ядром, а Джон Остераут написал редактор и эмулятор терминала. В качестве языка он использовал Tcl, в результате чего пользователи получили возможность определять меню и выполнять другие действия по настройке своих программ. Мы работали во времена системы Х10, и профессор Остераут планировал создать средства X Window, базирующиеся на Tcl. Это позволило бы взаимодействовать различным программам путем обмена Tcl-командами. На мой взгляд, возможность подобного взаимодействия инструментальных программ является одним из основных преимуществ Tcl. Первоначально языку Tcl отводилась роль инструмента для настройки программ и организации высокоуровневого взаимодействия между ними. Основную же часть приложения, как нам казалось, должен был составлять скомпилированный код. Редактор тх и эмулятор терминала tx, написанные Остераутом, соответствовали этой модели. Однако впоследствии оказалось, что можно создавать приложения полностью на Tcl. Это стало возможным благодаря тому, что оболочка wish обеспечивала доступ к другим программам, к файловой системе и сетевым гнездам и в дополнение к этому позволяла создавать графический пользовательский интерфейс. Теперь нередко можно встретить приложения, содержащие тысячи строк Тс1-кода. По мере накопления опыта работы с Tcl и Тк я все больше убеждался в их преимуществах, однако осознанию возможностей, предоставляемых данными средствами, предшествовали периоды разочарования в них. Это одна из причин, по которой была написана данная книга. Сотрудничая со специалистами Xerox PARC, я убеждался в преимуществах Tcl и Тк, но в то же время получал информацию о многих их недостатках. Мои коллеги по рабо-
39 те с удовольствием использовали Tcl и Тк в своих проектах и, обнаруживая изъяны, всегда ставили меня в известность. Я, в свою очередь, создал набор программных средств, позволяющих использовать возможности Tcl и Тк и избежать возникновения проблем. Данная книга задумана как практическое руководство, которое могло бы помочь читателю использовать Tcl и Тк в своей работе и в то же время избежать разочарований, которые в свое время испытал я. С того момента, как я познакомился с Tcl, прошло около 14 лет, а первое издание данной книги было выпущено около восьми лет назад. Несколько лет я работал под руководством Джона Остераута, сначала в Sun Microsystems, а затем в Scriptics Corporation. В основном я писал программы на Tcl, в то время как остальные участники рабочей группы занимались реализацией средств Tcl на языке С. Я создавал такие приложения, как HTML-редакторы, программы для работы с электронной почтой, Web-серверы и базы данных. Опыт, полученный при этом, я попытался отразить в данной книге. Основная часть книги посвящена созданию Tcl-сценариев, однако внимание уделяется также написанию Tcl-расширений на языке С. В настоящее время я продолжаю работать с языком Tcl и получаю все большее удовольствие от этого. Надеюсь, что в данной книге мне удастся передать мое отношение к Tcl и Тк. Некоторые аргументы в пользу выбора Tcl Tcl, как и почти каждый язык сценариев, несколько напоминает такие языки оболочек Unix, как Bourne Shell (sh), С Shell (csh), Korn Shell (ksh) и Perl. Программы, написанные на языках оболочек, позволяют запускать другие программы. Они предоставляют разработчику выразительные средства (переменные, управляющие потоки и процедуры), достаточные для создания сложных сценариев, включающих в качестве составных частей существующие программы. Сценарии оболочки — незаменимое средство для автоматизации часто выполняемых рутинных операций. Несмотря на некоторые общие черты, объединяющие Tcl с другими языками сценариев, он существенно отличается от них. Дело в том, что Тс1-ин- терпретатор может быть без труда включен в состав приложения. Tcl успешно справляется с ролью языка, используемого для настройки приложений. Используя Tcl, нет надобности придумывать новый формат файлов или командный язык для приложений. Включив в программу Тс1-интерпретатор, вы получаете возможность выполнять простые операции и объединять их в сценарий, полностью удовлетворяющий потребностям пользователей. Tcl также позволяет управлять приложениями из других программ, существенно упрощая тем самым их интеграцию.
40 Библиотека Tcl С предоставляет несложный для понимания интерфейс и проста в использовании. Данная библиотека реализует основные возможности интерпретатора и поддерживает набор базовых команд, позволяющих работать с переменными, формировать потоки управления и использовать процедуры. Богатый набор API предоставляет доступ к средствам операционных систем, позволяя запускать другие программы, работать с файловой системой и использовать сетевые гнезда. Тк добавляет команды, посредством которых можно создавать графические пользовательские интерфейсы. Tcl и Тк С API реализуют виртуальную машину, обеспечивающую работу в операционных средах Unix, Windows и Macintosh. Виртуальная машина Tcl допускает расширение, поскольку приложения имеют возможность определять новые Tcl-команды. Эти команды можно связать с процедурами приложения, написанными на С или С+-Ь В результате применения подобного подхода появляется возможность создать приложение как набор относительно простых элементов, написанных на компилируемом языке и оформленных как Tcl-команды. Для объединения простых элементов, или программных примитивов, в приложение используется Тс1-сцена- рий. Средствами уровня сценариев можно запускать другие программы, обращаться к файловой системе и непосредственно вызывать скомпилированные компоненты приложения посредством Tcl-команд, определенных разработчиком. На уровне С-программ вы можете вызвать Tcl-сценарии, читать значения Tcl-переменных и присваивать им новые значения и даже отслеживать работу Тс1-интерпретатора. Многие Tcl-расширения свободно распространяются через Internet. Большинство из них включает библиотеку С, обеспечивающую новые функциональные возможности и Tcl-интерфейс к ней. В качестве примеров предлагаются системы управления базами данных, программы организации телефонных справочников, средства доступа к MIDI-контроллеру и другие программы. Tcl-команды, предназначенные для управления интерактивными программами, реализованы в продукте expect. Наиболее популярным Tcl-расширением является Тк — набор инструментальных средств разработки графических пользовательских интерфейсов. В Тк определены Tcl-команды. которые позволяют создавать компоненты интерфейса и выполнять с ними различные действия. Применение сценариев для программирования пользовательских интерфейсов имеет следующие преимущества. • Поскольку компиляция отсутствует, от редактирования исходного кода до запуска его на выполнение проходит совсем немного времени. • Tcl-команды реализуют интерфейс гораздо более высокого уровня по сравнению со стандартными средствами создания пользовательских ин-
41 терфейсов, содержащихся в библиотеке С. Для реализации простого интерфейса достаточно нескольких команд. В то же время, если вам необходимо изменить ту или иную характеристику компонента, вы можете сделать это в любой момент. • Пользовательский интерфейс может быть отделен от остальной части приложения. Разработчик имеет возможность сосредоточить все внимание на реализации основных функциональных возможностей приложения, а затем, не затрачивая больших усилий, создать необходимые интерфейсные средства. Базовый набор компонентов Тк, как правило, оказывается достаточным для формирования интерфейсов для большинства программ. По необходимости вы можете также разработать новые компоненты Тк на языке С, кроме того, в вашем распоряжении имеется большой набор компонентов, распространяемых по сети. Для создания расширений могут использоваться самые различные языки, например Visual Basic, Scheme, Elisp, Perl, Python, Ruby или Javascript. Выбор конкретного языка зависит от вкусов разработчика. Tcl предоставляет простые языковые конструкции, похожие на средства языка С. Тс1-примитивы несложно создать путем написания процедур на С. Tcl прост в изучении. Благодаря его использованию некоторым разработчикам удавалось реализовать сложные проекты за очень короткое время (порядка нескольких недель). Такие результаты получались несмотря на то, что многие из них никогда ранее не писали программы на Tcl. Приблизительно в то же время, когда вышло в свет первое издание данной книги, появился язык Java. Этот язык в короткие сроки приобрел большую популярность. При решении некоторых задач он с успехом заменил языки С и C-iг. Несмотря на то что Tcl был ориентирован на совместную работу с С-программами, его удалось быстро адаптировать для взаимодействия с р.иртуальной машиной Java. Там, где раньше шла речь о С или С+ +, теперь можно смело говорить "С, C+ + или Java". Следует лишь учитывать некоторые детали, специфические для Java-программ. В данной книге интерфейс Tcl/Java не описывается, но на компакт-диске вы можете найти средства TclBlend. TclBlend загружает виртуальную машину Java в Tcl-приложение и позволяет вызывать Java-методы. Он также позволяет реализовывать Tcl- команды на Java вместо использования С или C++. Существует Тс1-интер- претатор, написанный на Java, который называется Jacl. Он имеет ряд ограничений по сравнению с Tcl-интерпретатором, написанным на С, но в тех случаях, когда вы не можете применить традиционный интерпретатор, Jacl оказывается незаменимым. Язык Javascript, разработанный Netscape, предназначен для организации взаимодействия с элементами Web-страниц. Он часто используется при со-
42 здании интерфейсов на базе HTML. Однако Tcl позволяет реализовать более универсальные решения, применимые в самых различных приложениях. Дополнительный модуль Web-браузера Tcl/Tk позволяет выполнять Тс1-сцена- рии в среде браузера. В результате Tcl становится альтернативным решением не только для JavaScript, но и для Java. Дополнительный модуль, организующий выполнение Tcl-сценариев в среде браузера, описан в главе 20. Версии Tcl и Тк Tcl и Тк продолжают развиваться. Информацию о новых реализациях Tcl можно найти по адресу http://www.beedub.com/book/. Реализации Tcl и Тк выпускаются парами, предназначенными для совместной работы. В первом издании этой книги описывались средства Tcl 7.4 и Тк 4.0, но в ней также можно было встретить ссылки на Tk 3.6. В четвертом издании освещаются новые средства, добавленные в Tcl/Tk 8.4. • Окончательная реализация Tcl 7.5 и Тк 4.1 была выпущена в мае 1996 года. В этих версиях был осуществлен перенос Тк в операционные среды Windows и Macintosh. В них же появился механизм защиты Safe-Tcl, предназначенный для выполнения аплетов, распространяемых по сети. Кроме того, в данных версиях была реализована поддержка сетевых гнезд и новой подсистемы ввода-вывода с поддержкой высокопроизводительного обмена, управляемого событиями. • Реализации Tcl 7.6 и Тк 4.2 появились в октябре 1996 года. В них были модернизированы средства Safe-Tcl и обновлен диспетчер компоновки grid, впервые появившийся в Тк 4.1. Здесь же были реализованы виртуальные события (например, событие <<Сору» может заменить <Control-c>), стандартное диалоговое окно и некоторые дополнительные команды работы с файлами. • Tcl 7.7 и Тк 4.3 представляли собой внутренние реализации, предназначенные для разработки дополнительных модулей Tcl/Tk для Web-браузеров Netscape Navigator и Microsoft Internet Explorer. Их разработка реально осуществлялась параллельно с работой над версиями 7.6 и Тк 4.2. Дополнительный модуль был создан для различных платформ, включая Solaris/SPARC, Solaris/INTEL, SunOS, Linux, Digital Unix, IRIX, HP/UX, Windows 95, Windows NT и Macintosh. Этот модуль поддерживает Tcl-аилеты в составе Web-страниц; в нем используется механизм защиты на базе Safe-Tcl. • К основным особенностям Tcl 8.0 относится динамический компилятор Tcl-кода, применение которого позволяет многократно повысить эффективность выполнения Tcl-сценариев. Tcl 8.0 поддерживает строки, ко-
43 торые могут содержать символы с нулевым кодом. Для Tcl-сценариев компилятор прозрачен, однако разработчики, которые занимаются созданием расширений, должны знать некоторые новые С API, в противном случае они не смогут воспользоваться новыми возможностями. Процесс создания Tcl 8.0 несколько затянулся. Это было связано с переходом Джона Остераута из Sun Microsystems в Scriptics Corporation. Реализация 8.0р2 была готова осенью 1997 года и вскоре приобрела популярность. Однако выход окончательной реализации, 8.0.5, задержался до весны 1999 года. • Новая версия Тк была создана лишь для того, чтобы обеспечить наилучшее соответствие Tcl 8.O. Тк 8.0 включает механизм поддержки шрифтов, не зависящий от конкретной платформы, платформенно-ониенти- рованные меню и панели инструментов, а также платформенно-ориен- тированные компоненты, внешний вид которых согласуется с другими приложениями, выполняющимися в системах Windows и Macintosh. • Tcl/Tk 8.1 обеспечивают поддержку Unicode и содержат новые средства обработки регулярных выражений, которые предоставляют возможности, не уступающие Perl 5. Кроме того, эти версии поддерживают работу потоков, что позволяет без труда включать Tcl в состав многопотоковых приложений. Разработчики Тк проделали огромную работу, чтобы найти подходящие шрифты для представления символов Unicode, а каталоги сообщений существенно упрощают написание приложений с интерфейсом на различных языках. В работе над Tcl/Tk 8.1 также сказался переход Остераута из Sun в Scriptics. Первая альфа-реализация была закончена осенью 1997 года, а окончательная реализация, 8.1.1, была готова лишь в мае 1999 года. • Реализация Tcl/Tk 8.2 задумывалась в основном для того, чтобы исправить замеченные ошибки и повысить надежность работы системы. Вместе с этим API библиотеки Tcl С был дополнен некоторыми средствами, увеличивающими набор доступных расширений. Работа над Tcl/Tk 8.2 закончилась быстро, и данные реализации вышли в свет летом 1999 года. • Tcl/Tk 8.3 была существенно расширена по сравнению с предыдущими реализациями. В Тк были приняты шаблоны Dash и Image, предложенные Дженом Найтменсом (Jan Nijtmans). Реализация 8.3.0 была выпущена в феврале 2000 года, а последний вариант, 8.3.5, был реализован в октябре 2002 года. • При разработке Tcl/Tk 8.4 основное внимание уделялось производительности, кроме того, были добавлены интерфейс виртуальной файловой системы и три компонента Tk: spinbox, labeledf rame и panedwindow.
44 Работа над этой реализацией продолжалась достаточно долго. Первая бета-версия была выпущена в июне 2000 года, а реализация 8.4.2 вышла в мае 2003 года. Расширения Tcl и Тк Tcl разработан так, что дополнения создаются в виде расширений, при этом базовые средства Tcl не затрагиваются. В настоящее время доступны многочисленные расширения. Информацию о них можно получить в Web по следующему адресу: http://www.tcl.tk/resource/ Однако для внесения некоторых изменений приходится модифицировать базовый комплект Tcl/Tk. Если вы хотите приложить свои усилия к разработке новых вариантов Tcl/Tk, ваша помощь будет с благодарностью принята. Необходимую информацию о деятельности группы разработчиков Tcl и Тк вы можете найти по следующему адресу: http://www.tcl.tk/cgi-bin/tct/tip/ Исходные коды Tcl и Тк поддерживаются в рамках проекта SourceForge. http://www.sourceforge.net/proj ects/tcl http://www.sourceforge.net/proj ects/tktoolkit Сообщения о замеченных ошибках и предложения о доработках регистрируются в базе данных. Наибольшие шансы быть принятыми имеют дополнения к исходному коду, сделанные в соответствии с руководствами Tcl Engineering Manual В руководствах описаны правила оформления кода, требования к тестированию и документации. Tcl в World Wide Web Знакомство с материалами, посвященными Tcl, можно начать со следующих Web-страниц: http://www.tcl.tk/ http://tcl.activestate.com/ http://www.purl.org/NET/Tcl-FAQ/ Узел Wiki часто обновляется по инициативе пользователей. На нем публикуются многочисленные материалы о Tcl и его расширениях (в качестве инициатора публикации новой информации можете выступить вы). http://wiki.tcl.tk/
45 На Web-странице данной книги содержатся сведения об ошибках для всех изданий. За материалами, публикуемыми на этом узле, слежу лично я и планирую делать это и в дальнейшем. http://www.beedub.com/book/ Кроме того, полезную информацию вы можете найти на Web-узле Prentice Hall. http://www.prenhall.com/ Ftp-архивы Ниже перечислены некоторые из FTP-узлов, содержащих информацию oTcl. ftp://ftp.tcl.tk/pub/tcl ftp://src.doc.ic.ac.uk/packages/tcl/ ftp://ftp.luth.se/pub/unix/tcl/ ftp://ftp.sunet.se/pub/lang/tcl ftp://ftp.cs.Columbia.edu/archives/tcl ftp://ftp.funet.fi/pub/languages/tcl Для доступа к этим узлам можно использовать любые браузеры, например Mozilla, Netscape, Internet Explorer и Lynx. Группы новостей Группа сотр.lang.tcl отличается высокой активностью. В ее рамках действует форум, посвященный обсуждению Tcl. Сообщения о Тс1-расширениях и приложениях поступают в группу сотр. lang.tcl .announce. Для доступа к материалам групп новостей можно воспользоваться специальными Web- службами. Для этого достаточно ввести сотр.lang.tcl в иоле редактирования на следующей Web-странице: http://groups.google.com На кого рассчитана эта книга Данная книга может быть полезна как программистам, впервые приступающим к работе с Tcl, так и квалифицированным специалистам. Всем категориям читателей я рекомендую внимательно прочитать главу 1. Программная модель Tcl достаточно проста, но она существенно отличается от многих других языков программирования. Данная модель основана на подстановке строк, поэтому, внимательно разобравшись с ней, вы избежите многих проблем в работе. В книге приведено большое количество примеров, посвященных эффективному использованию средств Tcl и Тк. Практически в каждой
46 главе содержатся таблицы, в которых приводится информация о Тс1-коман- дах и компонентах. Предполагается, что читатель данной книги имеет некоторый опыт программирования, однако даже новичок, читая эту книгу, сможет достаточно быстро приобрести необходимые навыки. Знание языков оболочек Unix поможет вам, но оно не является обязательным требованием. Там, где изложение материала тесно связано с принципами работы оконных систем, приводится соответствующая информация. В главе 2 описываются детали использования Tcl и Тк в системах Unix, Windows и Macintosh. Как работать с данной книгой Лучше всего в процессе чтения книги выполнять на компьютере приведенные в ней примеры. Часто программисты испытывают затруднения при изучении Tcl. Дело в том, что в справочной информации по Tcl и Тк отсутствуют примеры использования соответствующих языковых конструкций, а реальные программы оказываются плохо документированы. Данная книга призвана предоставить читателю недостающие сведения. Для изучения конкретных команд Tcl и Тк я рекомендую использовать интерактивную справочную информацию. В ней приводится детальное описание соответствующих языковых конструкций. В данной книге содержатся многие данные из справочной системы, но в ней отсутствуют детали, которые могут изменяться в зависимости от реализации Tcl и Тк. HTML-версии страниц интерактивной справочной системы можно найти на компакт-диске, прилагаемом к данной книге. Примеры программ Данная книга поставляется с компакт-диском, на котором записаны коды всех рассмотренных в книге примеров. Диск может читаться в системах Unix, Windows и Macintosh. Кроме программ на нем находятся версии Tcl и Тк, которые были доступны на момент выхода данной книги, а также многие из программ, свободно распространяемых по Internet. Исходные коды примеров можно также найти в Web по следующему адресу: http://www.beedub.com/book/ Соглашения о представлении материала Примеры кода представлены в книге по-разному. Наиболее важные из них отделены от остального текста и оформлены в виде листингов. Коды
47 программ на Tcl и С представлены моноширинным шрифтом. Результаты выполнения Tcl-команд, кроме того, выделены курсивом. Следует заметить, что символы => не являются частью данных, возвращаемых командой. expr 5+8 => 13 Моноширинный шрифт также применяется для представления имен Tcl- команд и С-процедур в тексте. В книге часто встречаются правила использования Tcl-команд. Имя команды и неизменяемые ключевые слова отображаются моноширинным шрифтом. Указания на то, что в вызове команд присутствуют значения переменных, опций или другие данные, выделены курсивом. Если параметр не является обязательным, слева и справа от него помещаются знаки вопроса. Пример, объясняющий использование команды set, приведен ниже. set имя_переменной ?значение? Пиктограммы Информация, которой желательно уделить дополнительное внимание, помечается пиктограммами. Как организована данная книга Материал книги разделен на семь частей. В первой главе описаны основные средства Tcl. В первой главе рассматриваются механизмы, лежащие в основе языка. Этот материал очень важен. Вам обязательно надо ознакомиться с ним, если вы хотите эффективно использовать Tcl. Если вы уже программировали на Tcl, вам все равно следует хотя бы бегло просмотреть главу 1. В главе 2 описываются детали использования Tcl и Тк в системах Unix, Windows и Macintosh. В главе 3 представлен пример приложения (CGI- сценарий), который иллюстрирует основные особенности составления программ на языке Tcl. Остальные главы части I посвящены детальному рассмотрению основных Tcl-команд. В частности, здесь обсуждаются работа со строками, типы данных, использование потока управления и процедур, а также области видимости. Завершается часть I рассмотрением средств обмена с файлами и запуска других программ. В части II описаны расширенные средства составления Tcl-программ. Вначале обсуждается команда eval, позволяющая динамически генерировать Tcl-программы. Дополнительные возможности по обработке строк предоставляют регулярные выражения. Если обработка данных составляет существенную часть вашего приложения и если приложение работает медленно, то вам, возможно, удастся повысить его производительность за счет использования
48 средств поддержки регулярных выражений. Пространства имен позволяют разделить глобальную область видимости процедур и переменных на отдельные области. Unicode и каталоги сообщений существенно упрощают создание многоязыковых приложений. Библиотеки и пакеты позволяют организовать код так, чтобы он мог использоваться в различных проектах. Средства интроспекции предоставляют важную информацию о внутреннем состоянии Tcl. Средства ввода-вывода, управляемого событиями, позволяют создавать приложения, работающие одновременно с несколькими клиентами. Для реализации HTTP-протокола, позволяющего получать Web-страницы, служат сетевые гнезда. В последних нескольких главах части II описываются инструментальные средства разработки приложений и особенности их использования на различных платформах. Средства Safe-Tcl служат для организации защищенной среды, в которой можно выполнять Tcl-аплеты. Для работы в Web может использоваться расширяемый Web-сервер TclHttpd, написанный полностью на Tcl. На базе TclHttpd легко создавать прикладные программы; можно также интегрировать сервер с существующими приложениями, создавая тем самыхм для них Web-интерфейс. Starkit — новое эффективное средство, позволяющее создавать пакеты и выполнять доставку приложений Tcl/Tk. Для организации файловой системы в пакете используются средства поддержки виртуальной файловой системы (VFS). В части III содержатся общие сведения о Тк. Возможности Тк проиллюстрированы рядом примеров. В этой же части описывается связывание — установление соответствия между Tcl-командой и событием, например щелчком мышью или нажатием клавиши. Три последних главы части III посвящены диспетчерам компоновки Тк, которые предоставляют мощные средства по организации пользовательского интерфейса. В части IV данной книги описываются компоненты Тк. С помощью компонентов реализуются кнопки, меню, полосы прокрутки, текстовые метки, поля редактирования, текстовые области, поддерживающие различные шрифты, холст для рисования, окна списков и линейные регуляторы. Для каждого компонента имеются средства, позволяющие изменять его конфигурацию и организовывать взаимодействие с прикладными программами, однако поведение компонента, реализованное по умолчанию, чаще всего удовлетворяет требованиям разработчиков. Для упрощения настройки компонентов и обеспечения согласованности элементов интерфейса предусмотрены базы данных ресурсов. В части V описываются те инструменты Тк, которым не было уделено достаточного внимания в предыдущих частях. В частности, здесь рассматриваются выделения, вопросы передачи фокуса ввода и стандартные диалоговые окна. Кроме того, детально рассматривается работа со шрифтами, с цветом
49 и изображениями — элементами, общими для всех компонентов Тк. Оканчивается данная часть несколькими примерами программ достаточно большого объема. Часть VI представляет собой введение в программирование Тс1-расшире- ний на языке С. Данная часть включена в книгу для того, чтобы читатель мог составить правильное представление о том, как можно дополнять Tcl командами, написанными на С, и интегрировать Tcl с существующими приложениями. Каждая глава части VII посвящена одной из реализаций Tcl/Tk, упоминаемых в данной книге. В этих главах описываются новые возможности, а также характеристики, которые могут изменяться от версии к версии. Эти характеристики приходится учитывать, адаптируя программы для новых версий Tcl/Tk. Особенности четвертого издания книги В четвертом издании изложение материала ведется с использованием версии Tcl/Tk 8.4, в которой были реализованы многие новые возможности. В Tcl появилась виртуальная файловая система, позволяющая учитывать структуру каталогов при доставке приложений, а также работать с удаленными ресурсами на FTP- и Web-узлах посредством интерфейса с обычной файловой системой. В четвертое издание добавлена глава 22, посвященная Tclkit и Starkit и использованию базы данных Metakit для хранения сценариев и других файлов. Благодаря VFS эти файлы размещаются в приватной файловой системе. Starkit — новое эффективное средство, позволяющее создавать пакеты и выполнять доставку приложений Tcl/Tk. В четвертое издание добавлена также глава 21, посвященная средствам поддержки многопотоковых программ. Эти средства оказываются очень полезными при включении Tcl в многопотоковые приложения. В данном издании также рассматриваются новые компоненты Тк. Инкрементный регулятор похож на поле редактирования, однако в нем реализованы дополнительные возможности. Фреймы с метками предоставляют новые возможности по оформлению окон. Компонент panedwindow представляет собой специализированный диспетчер компоновки, реализующий новые возможности по организации пользовательских интерфейсов. Благодарности за помощь в подготовке первого издания книги Я благодарю сотрудников Xerox PARC за помощь и понимание. Многие советы, приведенные в книге, стали результатом не только моей личной ра-
50 боты, но и опыта моих коллег. Вопросы, заданные Дейвом Николсом (Dave Nichols), воодушевили меня на изучение базовых механизмов работы интерпретатора Tcl. Ден Свайнхат (Dan Swinehart) и Лоренс Батчер (Lawrence Butcher) своими замечаниями поддерживали во мне постоянное желание продолжать работу. Рон Фредерик (Ron Frederick) и Берри Керчивел (Berry Kerchival) применили Tk для разработки графических интерфейсов своих систем. Полученные при этом результаты еще раз подтвердили высокую эффективность данного инструмента. Беки Барвел (Becky Burwell), Рич Голд (Rich Gold), Карл Хаусер (Carl Hauser), Джон Максвелл (John Maxwell), Кен Пайер (Ken Pier), Марвин Теймер (Marvin Theimer) и Моэн Вишвонет (Mohan Vishwanath) ознакомились с моими первыми набросками. Их комментарии и замечания оказали неоценимую помощь в дальнейшей работе над текстом. Керин Петерсен (Karin Petersen), Билл Шилит (Bill Schilit) и Терри Ватсон (Terri Watson) все время искали способы нестандартного применения Tcl, чем вносили приятное разнообразие в работу. Я хочу особо поблагодарить Марка Вейсера (Mark Weiser) и Дуга Терри (Doug Terry) за помощь и поддержку. Выражаю благодарность автору Tcl и Tk Джону Остерауту. Разработанные им системы -- самые замечательные из всех, которые мне когда-либо приходилось встречать. Джон снабжал меня последними реализациями Tk 4.0, благодаря чему я имел возможность изучить их новые возможности еще до выпуска первой бета-версии. Благодарю программистов, с которыми мне приходилось общаться. От них я узнал много нового. Я считаю Джона ЛоВерсо (John LoVerso) и Сте- фена Алера (Stephen Uhler) самыми талантливыми разработчиками Тс1-про- грамм из всех, кого я когда-либо знал. Выражаю искреннюю благодарность тем, кто прочитал мои первые наброски и высказал ценные замечания. Это Пайери Девид (Pierre David), Клиф Флинт (Clif Flynt), Симон Кенион (Simon Kenyon), Эджен Ли (Eugene Lee), Дон Лайбе (Don Libes), Ли Мур (Lee Moore), Джо Мосс (Joe Moss), Хэдор Шемтов (Hador Shemtov), Френк Стаджано (Prank Stajano), Чарльс Тейер (Charles Thayer) и Джим Торнтон (Jim Thornton). Многие присылали мне предложения и замечания по почте. Здесь я привожу имена лишь некоторых из них. Это Мигель Энджел (Miguel Angel), Сте- фен Бенсен (Stephen Bensen), Джеф Блейн (Jeff Blaine), Том Чарнок (Тот Charnock), Брайан Купер (Brian Cooper), Патрик Д'Круз (Patrick D'Cruze), Бенойт Десросайерс (Benoit Desrosiers), Тед Даннинг (Ted Dunning), Марк Ей- чин (Mark Eichin), Пол Фриберг (Paul Priberg), Карл Гаутиер (Carl Gauthier), Дэвид Гердес (David Gerdes), Клаус Хекенберг (Klaus Hackenberg), Торкл Хэсл (Torkle Hasle), Марти Херст (Marti Hearst), Джин-Пайери Герберт (Jean- Pierre Herbert), Джейми Хонан (Jamie Honan), Норман Клейн (Norman Klein), Джо Констан (Joe Konstan), Сьюзен Ларсон (Susan Larson), Хекен Лилдже-
51 грен (Некап Liljegren), Лайонел Маллет (Lionel Mallet), Деджен Милоджичич Dejan Milojicic), Грэг Миншелл (Greg Minshall), Бернд Mop (Bernd Mohr), Вилл Морс (Will Morse), Хейко Нердменн (Heiko Nardmann), Герд Неджебау- эр (Gerd Neugebauer), Кери Рензема (Сагу Renzema), Роб Рипел (Rob Riepel), Дэн Шенк (Dan Schenk), Джин-Гай Шнейдер (Jean-Guy Schneider), Элизабет Шолл (Elizabeth Scholl), Карл Швамб (Karl Schwamb), Рони Шапиро (Rony Shapiro), Питер Симани (Peter Simanyi), Вине Скехен (Vince Skahan), Билл Стамбо (Bill Stumbo), Глен Вандербург (Glen Vanderburg), Лэрри Вирден (Larry Virden), Рид Вейд (Reed Wade) и Джим Вайт (Jim Wight). К сожалению, я не смог ответить на все письма. Выражаю признательность редакторам и всем сотрудникам Prentice Hall. Высококвалифицированные профессионалы Марк Тауб (Mark Taub), Линн Шнейдер (Lynn Schneider) и Керри Кидон (Kerry Reardon) очень помогли мне в работе над книгой. Благодарности за помощь в подготовке второго издания книги Я снова хочу поблагодарить Джона Остераута за поддержку во время работы в группе Tcl/Tk компании Sun Microsystems. Остальные участники группы делали все возможное для того, чтобы превратить Tcl и Тк в настоящие инструменты для создания кроссплатформенных программ. Скотт Стентон (Scott Stanton) занимался переносом Тк на PC, а Рей Джонсон (Ray Johnson) обеспечивал работу Тк в среде Macintosh. Джейкоб Леви (Jacob Levy) реализовал систему ввода-вывода, управляемую событиями, Safe-Tcl, и дополнительный модуль для браузера. Брайан Левис (Brian Lewis) создал компилятор Tcl. Кен Кори (Ken Corey) занимался интеграцией Tcl и Java, а также помогал в работе над SpecTcl. Сид Полк (Syd Polk) обобщил систему меню для работы с платформенно-ориентированными компонентами в средах Macintosh и Windows. Колин Стивене (Colin Stevens) работал над поддержкой шрифтов и средствами интернационализации для Тк. Стефен Алер (Stephen Uhler) заслужил мою искреннюю благодарность за интересные примеры, которые я использовал в свой книге. Он руководил группой разработчиков SpecTcl. Стефен создал библиотеку HTML, пользуясь которой я написал редактор. Мы вместе работали над первыми версиями TclHttpd. Он научил меня писать компактный эффективный Tcl-код и использовать регулярные выражения при решении многих задач. Я многому обязан Стефену и надеюсь, что хоть в чем-то смог быть полезен ему. Я снова благодарю Марка Тауба, а также Эйлин Кларк (Eileen Clark) и Марту Вильяме (Martha Williams) из Prentice Hall. Джордж Вильяме (George Williams) помог мне собрать файлы для размещения на компакт-диске.
52 Благодарности за помощь в подготовке третьего издания книги Джон Остераут продолжает играть важную роль в развитии и распространении Tcl, на этот раз уже как основатель Scriptics Corporation. Я хочу поблагодарить всех сотрудников Scriptics, в особенности Сару Дениэлс (Sarah Daniels), Скотта Стентона (Scott Stanton), Рея Джонсона (Ray Johnson), Брайана Серлеса (Bryan Surles), Мелиссу Чавла (Melissa Chawla), Ли Бернхарда (Lee Bernhard), Суреша Шастри (Suresh Sastry), Эмиля Скеффо- на (Emil Scaffon), Пэт P. (Pat P.), Скотта Редмана (Scott Redman) и Берри Керчивела (Berry Kerchival). Все сотрудники Scriptics заслужили искреннюю благодарность за то, что создали в компании атмосферу, благоприятствующую творческой работе. Автор многих публикаций Джерри Пик (Jerry Peek) помог мне полезными советами. Кен Джонс (Ken Jones) рассказал об эффективных инструментах индексации. Большое спасибо всем читателям, приславшим свои отзывы и предложения. Сведения о новых способах использования Tcl всегда интересовали меня. Я благодарю также редакторов Prentice Hall Марка Тауба, Джоан Мак- намара (Joan McNamara) и Джоан Юрел (Joan Eurell). Они помогли мне вовремя окончить работу над третьим изданием книги. Благодарности за помощь в подготовке четвертого издания книги Благодаря Джефу Хоббсу (Jeff Hobbs) и Кену Джонсу (Ken Jones) работа над данным проектом была успешно завершена. Джефф широко известен среди программистов, использующих Tcl. Новые реализации Tcl/Tk выходили во многом благодаря его руководящим усилиям и интенсивной работе. Кен — известный преподаватель Tcl, и благодаря моему сотрудничеству с ним данная книга пополнилась новым интересным материалом. Я хочу поблагодарить участников Tcl Core Team; усилиями этой группы, Tcl становится все более мощным инструментом и приобретает новые возможности. Я хотел бы поблагодарить Джин-Клод Випплер (Jean-Claude Wippler) и Стива Лендерса (Steve Landers) за Metakit, Tclkit и Starkits. Эти инструменты предоставляют уникальные возможности по созданию пакетов и доставке Tcl-приложений. Я надеюсь, что данные технологии будут развиваться и дальше. Информацию о Starkit я получал также от некоторых пользователей. Сообщения мне прислали Роберт Течентин (Robert Techentin), Стив Блинкхорн (Steve Blinkhorn), Френк Серджент (Frank Sergeant), Ap- джен Маркус (Arjen Markus), Уве Колоска (Uwe Koloska), Лэрри Вирдсн
53 (Larry Virden), Том Крехбиел (Tom Krehbiel) и Дональд Портер (Donald Porter). Выражаю свою благодарность руководству и сотрудникам Prentice Hall за постоянную поддержку. Марк Тауб продолжает быть "крестным отцом" этой книги. Важную роль в подготовке четвертого издания к выпуску сыграла Кетлин Керен (Kathleen Caren). И, наконец, я благодарю свою жену Джоди за любовь, доброту, терпение и понимание в долгие часы работы над книгой. У меня три сына, Кристофер, Дэниел и Майкл. Мне приходится постоянно отвечать на их вопросы, поэтому я уверен, что умственная деградация мне не грозит. Как связаться с автором книги Я всегда готов выслушать мнение читателей о книге. Мой адрес: welch® acm.org. Если вы включите в поле subject письма слово "book" либо название данной книги, это поможет мне правильно сортировать приходящие письма. Новую информацию о данной книге и других сферах моей деятельности вы можете получить по адресу http://www.beedub.com/. На Web-узле я веду учет замеченных ошибок, поэтому ваши комментарии будут приняты с благодарностью. Ждем ваших отзывов Вы, читатель этой книги, и есть главный ее критик и комментатор. Мы ценим ваше мнение и хотим знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услышать и любые другие замечания, которые вам хотелось бы высказать в наш адрес. Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бумажное или электронное письмо, либо просто посетить наш Web-сервер и оставить свои замечания там. Одним словом, любым удобным для вас способом дайте нам знать, нравится или нет вам эта книга, а также выскажите свое мнение о том, как сделать наши книги более интересными для вас. Посылая письмо или сообщение, не забудьте указать название книги и ее авторов, а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением и обязательно учтем его при отборе и подготовке к изданию последующих книг. Наши координаты: E-mail: inf oQwilliamspublishing. com WWW: http: //www. williamspublishing. com Информация для писем из: России: 115419, Москва, а/я 783 Украины: 03150, Киев, а/я 152
ЧАСТЬ Основы Tcl В части I приводятся основные сведения о языке Tcl. Главу 1 следует прочитать каждому читателю, независимо от того, какие задачи он собирается решать на практике. В этой главе описаны основные свойства языка. Tcl достаточно прост, поэтому освоить его не составит труда даже для начинающих программистов. Квалифицированным специалистам также необходимо просмотреть главу 1, чтобы избежать подходов, неприменимых при работе с TCL. В главе 2 приведено краткое руководство по запуску Tcl и Тк в операционных средах Unix, Windows и Macintosh. Возможно, что главу 2 вам придется прочитать в первую очередь, чтобы иметь возможность выполнить примеры, приведенные в главе 1. В главе 3 представлена типичная TCL-программа — CGI-сценарий, реализующий гостевую книгу на Web-узле. При создании этой программы применены средства, подробно описанные в последующих главах. Основная цель главы — ознакомить читателя с реальным примером, демонстрирующим возможности Tcl. Остальные главы части I посвящены непосредственно программированию на Tcl. Обработка строк описана в главе 4. О работе со списками рассказывается в главе 5. Управляющие структуры, например циклы и выражения if, описаны в главе 6. Глава 7 посвящена рассмотрению процедур Tcl, посредством которых можно реализовывать новые Tcl-команды. В главе 8 обсуждаются массивы Tcl. Массивы — одна из наиболее полезных структур в Tcl; они обеспечивают гибкость создаваемых приложений. В главе 9 рассказывается об операциях ввода-вывода и о запуске других программ. Этими средствами создаются Tcl-сценарии, которые позволяют объединять различные программы и обрабатывать данные, содержащиеся в файлах. Прочитав часть I. вы получите знания, достаточные для того, чтобы разрабатывать простые Tcl-программы, а также читать и понимать код, написанный другими программистами.
Глава 1 Общие сведения о языке Tcl В данной главе описывается синтаксис языка сценариев Tcl. В частности, здесь рассматриваются подстановка и группировка, выполняемые в процессе интерпретации Tcl-ирограмм. В процессе обсуждения будут рассмотрены следующие команды Tcl: puts, format, set, expr, string, while, incr и рrос. Tcl представляет собой командный язык, ориентированный на обработку строк. В нем используется лишь несколько основных языковых конструкций. Синтаксис языка крайне прост, что позволяет быстро изучить его. Язык Tcl в основном ориентирован на решение задач, предполагающих объединение набора "строительных блоков" в готовое приложение. Tcl — интерпретируемый язык; в процессе выполнения приложения его код обрабатывается интерпретатором. Благодаря такому подходу существенно упрощается процесс разработки и модернизации приложения; необходимые для этого операции могут выполняться в интерактивном режиме. Возможность выполнения команд в интерактивном режиме существенно упрощает изучение Tcl. Если вы еще не умеете запускать Tcl-интерпретатор в вашей системе, вам следует прочитать главу 2, в которой рассматривается выполнение Tcl в операционных средах Unix, Windows и Macintosh. В этой главе излагаются общие сведения о языке Tcl. Даже если вы считаете себя опытным программистом, постарайтесь выделить время на то, чтобы прочитать данную главу. Это поможет вам убедиться, что вы действительно знаете язык Tcl, и позволит избежать в дальнейшем многих ошибок. Основные механизмы, лежащие в основе Tcl, связаны с обработкой и подстановкой строк, что позволяет без труда проследить действия интерпретатора. Модель Tcl несколько отличается от других языков программирования, и в этом вы убедитесь, прочитав данную главу.
Глава 1. Общие сведения о языке Tcl 57 Команды Tcl Tcl —■ это аббревиатура от Tool Command Language. Команды выполняют различные действия, например: вывод строки, вычисление значения арифметического выражения или отображение компонентов (widget) на экране. В Tcl в виде команд представляются все действия, даже присвоение значений переменным и определение процедур. Для вызова команд в языке Tcl предусмотрены достаточно простые синтаксические конструкции. Все сложные действия выполняет интерпретатор, реализующий команды. Команда Tcl записывается в следующем виде: команда параметр1 параметр2 параметрЗ. . . Команда может представлять собой либо встроенную команду Tcl, либо процедуру. Имя команды отделяется от параметров с помощью одного или нескольких пробелов или знаков табуляции; те же символы используются для разделения параметров. Символ конца строки или точка с запятой завершают команду. В процессе интерпретации Tcl осуществляет группировку (объединение нескольких слов в один параметр) и подстановку (замену переменных и вызовов вложенных процедур). Действия интерпретатора Tcl по обработке команд можно разделить на три этапа. • Группировка параметров. • Подстановка вложенных команд, переменных и символов, которым предшествует обратная косая черта. • Вызов команды. Интерпретация параметров самой командой. (Этот вопрос будет рассмотрен далее в настоящей главе.) Hello, World! Листинг 1.1. Программа "Hello, World!" puts stdout {Hello, World!} => Hello, World! В данном примере команде puts передаются два параметра: идентификатор потока ввода-вывода и строка. Команда puts записывает строку в поток, добавляя символ новой строки. Данный простой пример демонстрирует две следующие особенности Tcl. • Параметры интерпретируются командой. В данном случае поток ввода- вывода идентифицируется с помощью имени stdout. Данное имя потока используется puts и другими командами ввода-вывода. Имя stderr
58 Часть I. Основы Tcf определяет стандартный поток ошибок, а имя stdin — стандартный поток ввода. Обмен данными с файлами подробно рассматривается в главе 9. Фигурные скобки применяются для группировки нескольких слов в один параметр. В результате команда puts воспринимает слова Hello, World! как второй параметр. Скобки не являются частью значения. Фигурные скобки предназначены для предоставления дополнительной информации интерпретатору. Перед тем как значение будет передано команде, скобки удаляются. Посредством скобок группируются все символы, включая переводы строк и вложенные скобки. Группировка оканчивается при появлении закрывающей фигурной скобки. Для группировки в Tcl также могут использоваться двойные кавычки. Группировка будет более подробно рассмотрена далее в этой главе. Переменные Для присвоения значений переменной используется команда set. Этой команде передаются два параметра: имя переменной и значение. Имя переменной может быть любой длины; регистр символов учитывается. В составе имени допустимы любые символы. При написании Tcl-программ нет необходимости объявлять переменные перед их использованием. Интерпретатор создает переменную в тот момент, когда ей необходимо впервые присвоить значение. Для того чтобы обратиться к значению переменной, надо указать перед ее именем символ $, как показано в листинге 1.2. Листинг 1.2. Использование переменных Tcl set var 5 => 5 set b $var => 5 Вторая команда set, содержащаяся в листинге, присваивает переменной b значение переменной var. В данном примере вы впервые встречаетесь с под-
Глава 1. Общие сведения о языке Tcl 59 становкой. Чтобы лучше понять, как выполняется вторая команда set, ее можно переписать, заменив $var значением переменной var: set b 5 В действительности подстановка осуществляется несколько другими, более эффективными способами, что очень важно в том случае, когда значением переменной является длинная строка. Подстановка команд Помимо подстановки переменных, в Tcl используется подстановка команд. Вложенные команды помещаются в квадратные скобки. Интерпретатор Tcl воспринимает любую последовательность символов, находящуюся между открывающей и закрывающей квадратной скобкой, как команду. В процессе интерпретации осуществляется замена выражения в квадратных скобках (включая сами скобки) результатом выполнения вложенной команды. Квадратные скобки выполняют те же функции, что и одинарные кавычки в некоторых оболочках, кроме того, такой подход позволяет работать с командами любого уровня вложенности. Листинг 1.3. Подстановка команд set len [string length foobar] => 6 В листинге 1.3 приведенное ниже выражение представляет собой вложенную команду. string length foobar Данная команда возвращает длину строки "foobar". Команда string будет подробно описана в главе 4. При выполнении выражения в первую очередь выполняется вложенная команда. Затем осуществляется подстановка, в результате чего внешняя команда приобретает следующий вид: set len б Если в составе внешней команды находится несколько вложенных команд, интерпретатор обрабатывает их слева направо. Закрывающая скобка является признаком конца очередной команды. Встретив ее, интерпретатор выполняет команду. Это надо учитывать в тех случаях, когда результат одной команды может влиять на выполнение другой.
60 Часть I. Основы Tcl Математические выражения Сам по себе интерпретатор Tcl не вычисляет значения математических выражений. Он лишь выполняет группировку, подстановку и вызов команд. Для разбора и вычисления значений математических выражений используется команда expr. Листинг 1.4. Простое арифметическое выражение expr 7.2/4 => 1.8 Синтаксис выражений, передаваемых команде expr в качестве параметров, такой же, как и синтаксис выражений в языке С. Команда expr обрабатывает целые числа, числа с плавающей точкой и логические значения. Результатом выполнения логических операций является значение 0 (false) либо 1 (true). По необходимости целые числа преобразуются в значения с плавающей точкой. Восьмеричные числа начинаются с нуля (например, значение 033 равно целому числу 27). Шестнадцатеричные значения начинаются с символов Ох. В Tcl поддерживается представление чисел с плавающей точкой. Сведения о приоритете операций приведены в конце данной главы. В математических выражениях могут присутствовать ссылки на переменные и вложенные команды. В примере, приведенном в листинге 1.5, посредством команды expr выполняется сложение значения переменной х с числом, равным длине строки "f oobar". В результате подстановки команде expr передается 6 + 7 и при выполнении команды set переменной len присваивается значение 13. Листинг 1.5. Вложенные команды set x 7 set len [expr [string length foobar] + $x] => 13 Средства обработки выражений поддерживают ряд встроенных функций, применяемых при проведении математических вычислений. (Перечень таких функций приведен в конце данной главы.) В примере, показанном в листинге 1.6, вычисляется число pi. Листинг 1.6. Использование встроенных математических функций set pi [expr 2*asin(1.0)] => 3.1415926535897931
Глава 1. Общие сведения о языке Tcl 61 При реализации функции expr были приняты меры для обеспечения корректности значений и предотвращения нежелательных преобразований числовых значений в строковые. Разработчики программ, со своей стороны, могут повысить эффективность выполнения операции expr, применяя фигурные скобки для группировки выражений. Это связано с особенностями работы компилятора, преобразующего исходный текст в байтовый код. Более подробно данный вопрос будет рассмотрен далее в этой главе. Пример использования фигурных скобок для повышения быстродействия вычислений приведен в листинге 1.7. Все выражения, содержащиеся в этом листинге, составлены корректно. Листинг 1.7. Использование фигурных скобок для группировки выражений expr {7.2 / 4} set len [expr {[string length foobar] + $x}] set pi [expr {2*asin(l.0)}] Подстановка символов, представленных с помощью обратной косой черты Еще один тип подстановки, выполняемый интерпретатором Tcl, связан с использованием обратной косой черты. Таким способом в состав параметров можно включать символы, которые в обычных условиях имеют специальные значения. Предположим, например, что вы собираетесь использовать в составе параметра символ $ или какую-либо скобку. Перед таким символом надо указать обратную косую черту. Если для составления требуемого выражения вам надо использовать большое число символов обратной косой черты, следует помнить, что существует более простой способ, позволяющий получить тот же результат. В частности, команда list, которая будет описана в главе 5, автоматически отменяет специальные значения символов. В листинге 1.8 приведен пример использования обратной косой черты для получения литерального значения символа $. Листинг 1.8. Отмена специального значения символа с помощью обратной косой черты set dollar \$foo => $foo set x $dollar => $foo
62 Часть I. Основы Тс! Интерпретация выполняется в один проход. Вторая команда set в приведенном выше примере иллюстрирует важную особенность Tcl. Значение переменной dollar не испытывает на себе влияния операции подстановки. Другими словами, при подстановке разбор значений переменных не производится. В данном примере значением как переменной dollar, так и переменной х является строка "$f oo". Общее правило таково: при использовании eval нет необходимости заботиться о значениях переменных. Подробнее этот вопрос будет обсуждаться в главе 10. С помощью последовательностей знаков, начинающихся с обратной косой черты, можно записывать шестнадцатеричные, восьмеричные значения символов, а также представлять их в формате Unicode. set escape \u001b set escape \0xlb set escape \033 В каждом из этих выражений переменной escape присваивается символ ASCII ESC, код которого в десятичном представлении равен 27. Правила представления символов последовательностями, начинающимися с обратной косой черты, приведены в табл. 1.1. Часто символ обратной косой черты используется для записи длинной команды в нескольких строках. Как было сказано ранее, перевод строки завершает команду. В примере, представленном в листинге 1.9, обратная косая черта обязательно должна присутствовать, иначе последним символом в записи команды expr будет знак +. Листинг 1.9. Запись длинной команды в двух строках с использованием обратной косой черты set totalLength [expr [string length $one] + \ [string length $two]] Отменить специальное значение перевода строки можно двумя способами. Первый способ состоит в формировании параметра посредством группировки. В этом случае никаких действий для отмены специального значения перевода строки не требуется; соответствующий символ становится частью группы и не завершает команду. Второй способ — включить в конец строки обратную косую черту. В этом случае последний символ в строке преобразуется в пробел, а все пробелы в начале следующей строки удаляются. Другими словами, символ обратной косой черты не только позволяет продолжить команду в следующей строке, но и удаляет ненужные пробелы, включаемые в начало строки для форматирования.
Глава 1. Общие сведения о языке Tcl 63 Группировка с помощью фигурных скобок и двойных кавычек Двойные кавычки и фигурные скобки используются для группировки нескольких слов в один параметр. Различие между ними состоит в том, что кавычки допускают подстановку в группе, а фигурные скобки запрещают ее. Это правило действует по отношению к командам, переменным и последовательностям, начинающимся с обратной косой черты. Листинг 1.10. Группировка с помощью двойных кавычек и фигурных скобок set s Hello => Hello puts stdout "The length of $s is [string length $s]." => The length of Hello is 5. puts stdout {The length of $s is [string length $s].} => The length of $s is [string length $s]. При выполнении второй команды, приведенной в данном примере, интерпретатор Tcl, обрабатывая второй параметр puts, выполняет подстановку как значения переменной, так и команды. В третьей команде подстановка запрещена, поэтому строка выводится в том виде, в котором она указана в записи параметра. На практике группировка с помощью фигурных скобок в основном выполняется тогда, когда подстановка параметра должна быть отложена на более позднее время либо вовсе не должна выполняться. В качестве примеров можно привести циклы, условные выражения и объявления процедур. Двойные кавычки чаще всего применяются в командах, подобных puts. Кавычки также часто используются в команде format. Эта команда выполняет те же функции, что и функция printf в языке С. Первый параметр команды format определяет формат вывода. В нем часто присутствуют специальные символы, например перевод строки, знаки табуляции и пробелы. Проще всего указать эти символы с помощью последовательностей, начинающихся с обратной косой черты (например, перевод строки можно представить как \п, а знак табуляции — как \t). Перед выполнением команды format последовательности, начинающиеся с обратной косой черты, должны быть преобразованы, поэтому для формирования параметра, определяющего формат, целесообразно использовать двойные кавычки. puts [format "Item: °/0s\ t°/05.3f" $name $value] В данном случае команда format используется для выравнивания при выводе значений переменных name и value с помощью символа табуляции.
64 Часть I. Основы Tcl Выражения °/es и e/e5.3f задают формат вывода остальных параметров команды. Заметьте, что символ \п, обычно указываемый при вызове команды printf языка С, здесь отсутствует. Он не нужен, так как команда puts сама добавляет символ перевода строки. Подробно команда format будет описана в главе 4. Особенности использования квадратных скобок Квадратные скобки используются для подстановки команд и не выполняют группировку. Вместо этого вложенная команда, определяемая с помощью квадратных скобок, рассматривается как часть текущей группы. В приведенной ниже команде двойные кавычки формируют последний параметр, выполняя группировки, а результаты выполнения вложенной команды включаются в группу. puts stdout "The length of $s is [string length $s]." Если в состав параметра входит только вложенная команда, вам нет необходимости группировать элементы с помощью двойных кавычек, поскольку средства разбора Tcl рассматривают вложенную команду как единое целое. puts stdout [string length $s] В приведенном ниже выражении кавычки излишни. puts stdout " [expr $x + $у] " Группировка перед подстановкой Интерпретатор Tcl выполняет разбор команды за один проход. В течение единственного прохода он принимает решение о группировке и подстановке. Группировка осуществляется перед подстановкой. Эту особенность интерпретатора Tcl необходимо иметь в виду при написании программ. Значения, полученные в результате подстановки, не влияют на группировку, так как решение о группировке было принято ранее. Приведенный ниже пример демонстрирует влияние'вложенных команд на группировку. Вложенная команда рассматривается как неразрывная последовательность символов, причем внутренняя структура этих символов не учитывается. При формировании параметров основной команды результаты подстановки включаются в текущую группу символов. Листинг 1.11. Вложенная команда и подстановка переменных set х 7; set у 9 puts stdout $x+$y=[expr $x + $y] => 7+9=16
Глава 1. Общие сведения о языке Tcl 65 В листинге 1.11 второй параметр команды puts имеет следующий вид: $х+$у=[expr $х + $у] Пробелы в составе вложенной команды при группировке игнорируются. В тот момент, когда интерпретатор Tcl встречает левую квадратную скобку, подстановка некоторых переменных уже выполнена и сформирована следующая строка: 7+9= При появлении левой квадратной скобки осуществляется рекурсивный вызов интерпретатора для выполнения вложенной команды. Перед выполнением ехрr подстановка переменных $х и $у уже выполнена. Результат выполнения команды ехрr включается вместо всей последовательности символов, находящихся между левой и правой квадратной скобкой. Поэтому команда puts получает в качестве второго параметра следующее значение: 7+9=16 Группировка, выполняется перед подстановкой В рассмотренном выше примере при обработке второго параметра команды puts решение о группировке принимается перед решением о подстановке. Даже если результат выполнения вложенной команды содержит пробелы или другие специальные символы, аргумент будет передан команде без учета специальных значений. Группировка и подстановка переменных соотносятся между собой так же, как группировка и подстановка команд. Пробелы или другие специальные символы в составе значений переменных не оказывают влияние на решение о группировке, так как это решение принимается еще тогда, когда значения переменных недоступны. Если вы хотите, чтобы при выводе результатов выполнения команды символы + и = были отделены от чисел пробелами, вам надо явным образом сгруппировать параметр, используя двойные кавычки. При этом команда puts примет следующий вид: puts stdout "$х + $у = [expr $х + $у]" В данном случае для группировки используются кавычки. Это важно, так как в составе группы должна быть разрешена подстановка значений переменных и команды. Группировка математических выражений с помощью фигурных скобок Команда expr выполняет некоторые действия по подстановке внутри фигурных скобок. Более подробно этот вопрос будет рассмотрен далее в этой
66 Часть I. Основы Tcl главе. Таким образом, оказывается, что в приведенной ниже команде выполняется подстановка значений переменных в выражении, помещенном в фигурные скобки. puts stdout "$x + $у = [expr {$х + $у}]" Примеры подстановки Если в составе команды присутствует несколько подстановок, не разделенных пробелами или знаками табуляции, группировка выполняется по умолчанию. Использовать двойные кавычки в этом случае не обязательно. В приведенной ниже команде осуществляется конкатенация значений переменных а, b и с. set concat $a$b$c Если же вам необходимо разделить значения переменных пробелами, необходимо использовать кавычки. set concat "$а $b $с" В общем случае команды, помещенные в квадратные скобки, и ссылки на переменные можно указывать в любой позиции строки. В приведенном ниже примере имя команды определяется в результате выполнения другой команды. [findCommand $x] параметр параметр При работе с Тк имя компонента часто используется как имя команды. $text insert end "Hello, World!" Процедуры Для определения процедур в Tcl используется команда ргос. Единожды определенная Tcl-процедура может быть использована многократно, причем вызывается она точно так же, как и встроенная команда Tcl. Определение процедуры осуществляется с помощью следующего выражения: ргос имя список_параметров тело_процедуры Первый параметр — это имя определяемой процедуры. В качестве второго параметра команды задается список параметров процедуры. Третий параметр — тело процедуры, включающее одну или несколько Тс1-команд. В имени процедуры могут содержаться практически любые символы; регистр символов учитывается. В данной книге принято соглашение об именовании, согласно которому имена процедур начинаются с символа верхнего
Глава 1. Общие сведения о языке Тс! 67 регистра, а имена переменных — с символа нижнего регистра. По мере развития Tcl все большее значение приобретает стиль программирования. Этому вопросу посвящена глава 12. Листинг 1.12. Определение процедуры proc Diag {а Ъ} { set с [expr {sqrt($a * $а + $Ь * $Ъ)}] return $c } puts "The diagonal of a 3, 4 right triangle is [Diag 3 4]" => The diagonal of a 3, 4 right triangle is 5.0 Процедура Diag, определенная в данном примере, вычисляет длину гипотенузы прямоугольного треугольника. В качестве параметров процедуре передаются длины катетов. Функция sqrt — одна из функций, поддерживаемых командой expr. Переменная с — это локальная переменная процедуры; она существует только при выполнении Diag. Области видимости переменных будут обсуждаться в главе 7. Переменная с в данном примере не обязательна; при необходимости можно было бы составить код процедуры, не используя переменных. В этом случае процедура выглядела бы следующим образом: proc Diag {a b} { return [expr {sqrt($a * $а + $b * $Ъ)}] } Команда return возвращает результаты выполнения процедуры. В данном случае она не обязательна, так как интерпретатор Tcl по умолчанию возвращает значение последней команды в теле процедуры. Таким образом, процедура Dial может выглядеть так: proc Diag {а Ъ} { expr {sqrt($a * $а + $Ь * $Ь)} } Обратите внимание на использование фигурных скобок. Скобка в конце первой строки является началом третьего параметра команды proc; этот же параметр является телом процедуры. Поскольку при разборе команды интерпретатор Tcl встречает открывающую фигурную скобку, он игнорирует специальное значение символов новой строки и читает текст до появления закрывающей скобки. Использование двойных кавычек для группировки даст тот же результат. Интерпретатор будет группировать символы, включая переводы строк, до появления второй двойной кавычки. В результате группировки
68 Часть I. Основы Tcl формируется третий параметр ргос, представляющий собой последовательность команд. В дальнейшем при выполнении процедуры символы перевода строки будут завершать команды. Помещение тела процедуры в фигурные скобки дает еще один эффект: подстановка откладывается до тех пор, пока не начнется выполнение процедуры. Это очень важно, так как в данном примере переменные a, b и с будут определены лишь при вызове процедуры, поэтому в определении Diag выполнить подстановку этих переменных невозможно. Команда ргос предоставляет также дополнительные возможности, например, поддерживает различное число параметров и позволяет использовать значения параметров по умолчанию. Данный вопрос будет подробно обсуждаться в главе 7. Пример вычисления факториала Чтобы закрепить полученные знания, рассмотрим пример, в котором для вычисления факториала применяется цикл. Листинг 1.13. Использование цикла while для вычисления факториала ргос Factorial {x} { set i 1; set product 1 while {$i <= $x} { set product [expr {$product * $i}] incr i } return $product } Factorial 10 => 3628800 Точка с запятой в первой строке используется для разделения команд. Этот символ выполняет те же функции, что и перевод строки. Цикл while применяется для того, чтобы перемножить числа от единицы до значения х. Первый параметр while представляет собой логическое выражение, а второй параметр является телом цикла. Команда while и другие управляющие структуры описаны в главе 6. Для вычисления значения логического выражения применяются те же средства, которые использует команда expr. По этой причине в первый параметр команды while не включается команда expr. Даже если вычисляется сложное выражение, в использовании expr нет необходимости.
Глава 1. Общие сведения о языке Tcl 69 Как и тело процедуры, тело цикла формируется с помощью фигурных скобок. Открывающая фигурная скобка, предназначенная для формирования тела процедуры, должна находиться в той же строке, что и команда ргос, а если скобка формирует тело цикла, ее следует располагать в той же строке, что и команду while. Если же вы хотите поместить фигурную скобку в следующей строке, то текущая строка обязательно должна оканчиваться символом обратной косой черты. Как вы уже знаете, обратная косая черта отменяет специальное значение перевода строки. while {$i < $х} \ { set product ... } Для группировки логических выражений и команд, составляющих тело цикла, всегда надо использовать фигурные скобки. При группировке логического выражения фигурные скобки нельзя заменять двойными кавычками, поскольку подстановка переменных должна быть отложена до того момента, когда интерпретатор начнет вычислять выражение. Ниже приведен пример бесконечного цикла, set i l; while $i<=10 {incr i} Этот цикл никогда не будет завершен1. Дело в том, что интерпретатор Tcl выполнит подстановку $i перед вызовом команды while, в результате логическое выражение примет вид 1<=10. Значение такого выражения всегда равно true. Чтобы устранить подобные ошибки, надо соблюдать рекомендуемый стиль составления кода, в частности всегда применять для группировки выражений фигурные скобки. set i l; while {$i<=10} {incr i} Для увеличения значения переменной i в теле цикла используется команда incr. Она очень удобна, так как заменяет более длинную команду. set i [expr {$i + 1}] Команде incr может передаваться дополнительный параметр: положительное или отрицательное целое число, указывающее, на какую величину должно изменяться значение переменной. Используя данную форму команды, можно отказаться от применения переменной цикла и ограничиться переменной х. В этом случае цикл примет следующий вид: !Это может показаться смешным, но при использовании Tcl 8.0 данное утверждение неверно. В версии 8.0 был представлен компилятор байтового кода, при реализации которого была допущена ошибка, приводящая к тому, что цикл завершается. Данная ошибка была исправлена в реализации 8.0.5. — Прим. авт.
70 Часть I. Основы Tcl while {$x > 1} { set product [expr {$product * $x}] incr x -1 } В листинге 1.14 приведена модифицированная процедура вычисления факториала, в которой используется рекурсивный вызов. Рекурсивной называется такая функция, которая в процессе выполнения вызывает саму себя. При каждом рекурсивном вызове значение х уменьшается на единицу, а когда оно станет равным 1, рекурсивные обращения прекращаются. Листинг 1.14. Рекурсивная функция, вычисляющая факториал proc Factorial {x} { if {$х <= 1} { return 1 } else { return [expr {$x * [Factorial [expr {$x - 1}]]}] } } Дополнительные сведения о переменных Если команде set передается один параметр, она возвращает значение переменной. Данная команда интерпретирует параметр как имя переменной и возвращает ее текущее значение. Символ $, который указывается перед именем переменной для доступа к ее значению, — это лишь сокращенный вариант команды set. Пример, представленный в листинге 1.15, показывает, какой интересный результат можно получить, присваивая переменной имя другой переменной. Листинг 1.15. Использование команды set для доступа к значению переменной set var {the value of var} => the value of var set name var => var set name => var set $name => the value of var
Глава 1. Общие сведения о языке Tcl 71 Данный пример несколько сложнее предыдущих. В последней команде вместо $name подставляется var. Затем команда set возвращает значение переменной var, т.е. строку "the value of var". Вложенные команды set реализуют косвенное обращение к переменным. Последняя команда set в приведенном выше примере может быть переписана следующим образом: set [set name] => the value of var Вначале использование переменной для хранения имени другой переменной может показаться сложным. Однако в некоторых случаях такой подход оказывается очень полезным. Имеется даже специальная команда upvar, которая упрощает действия с использованием косвенной адресации. Эта команда будет рассмотрена в главе 7. Особенности применения различных символов в именах переменных Интерпретатор Tcl использует некоторые соглашения об именовании переменных, которые упрощают включение ссылок на переменные в состав строк. По умолчанию интерпретатор предполагает, что имена переменных содержат только буквы, цифры и знаки подчеркивания. Выражение $foo.o представляет собой конкатенацию значения переменной foo и литерала " .о". Если ссылка на переменную не отделена от остальной части строки знаками пунктуации или пробелами, вы можете выделить имя переменной с помощью фигурных скобок (например, ${х}). Использование фигурных скобок также позволяет включать в состав имен произвольные символы, однако работать с такими переменными не всегда удобно и для того, чтобы поступать так, надо иметь веские основания. Если вы хотите включить в имя переменной символы, отличные от букв, цифр и знаков подчеркивания, либо если вам необходимо формировать имя в процессе вычислений, вы можете воспользоваться командой upvar. Листинг 1.16. Различные способы формирования ссылок на переменные set foo filename set object $foo.o => filename.о set a AAA set b abc${a}def => abcAAAdef set .o yuk!
72 Часть I. Основы Tcl set x ${.o}y => yuk'.y Команда unset Переменную можно удалить с помощью команды unset. unset ?-nocomplain? ?--? имя__переменной_1 имя_переменной_2 . . . Команде unset может быть передано любое количество имен переменных. Если переменная не определена, unset сообщает об ошибке. Для того чтобы подавить генерацию сообщений об ошибке, надо указать опцию -nocomplain. Чтобы удалить переменную с именем -nocomplain, надо включить символы --. Проверка наличия переменных Проверить, имеется ли переменная с определенным именем, позволяет команда info exists. Некоторым командам, например команде incr, можно передавать только имена существующих переменных. Поэтому перед выполнением подобной команды желательно проверить наличие переменной с данным именем. Листинг 1.17. Использование команды info exists для проверки наличия переменной if {![info exists foobar]} { set foobar 0 } else { incr foobar } В листинге 7.6 показана версия incr, в процессе выполнения которой осуществляется проверка наличия переменной. Дополнительные сведения о математических выражениях В данном разделе описываются некоторые особенности работы с математическими выражениями в Tcl-сценариях. В Tcl 7.6 и более ранних версиях математические вычисления выполнялись неэффективно из-за преобразования строковых значений в числовые. Преобразование последовательности символов в число должна осуществлять команда expr. Эта же команда выполняет вычисления, используя числа двойной точности с плавающей точкой.
Глава 1. Общие сведения о языке Tcl 73 Результат преобразуется в строковое значение, содержащее по умолчанию 12 цифр. Число значащих цифр можно изменить, устанавливая значение переменной tcl.precision. Для того чтобы при преобразовании строки в число с двойной точностью и числа в строку не терялась информация, достаточно семнадцати значащих цифр. Листинг 1.18. Управление точностью с помощью переменной tcl_precision expr 1/3 => О expr 1/3.0 => 0.333333333333 set tcl_precision 17 => 17 expr 1/3.0 # Завершающая цифра 1 появляется вследствие специфики округления # по правилам IEEE => 0.33333333333333331 В Tcl 8.0 и более поздних версиях необходимость преобразования в большинстве случаев устраняется за счет использования встроенного компилятора. Но несмотря на это, Tcl нежелательно применять для создания приложений, в процессе выполнения которых должны выполняться интенсивные математические вычисления. По необходимости вы можете реализовать функцию, выполняющую математические вычисления, на компилируемом языке и зарегистрировать ее как команду Tcl. Подробно этот вопрос будет рассмотрен в главе 47. Команда expr поддерживает сравнение строк, поэтому вы можете проверять строковые значения в выражениях if. Для того чтобы команда expr знала о том, что надо сравнивать строки, в выражении следует использовать кавычки. if {$answer == "yes"} { ... } Следует заметить, что более надежными являются команды string compare и string equal, так как команда expr может выполнять нежелательное преобразование строк, которые напоминают числа. Вопросы обработки строк и использования функции expr рассматриваются в главе 4. В Tcl 8.4 были введены операции eq и пе expr, непосредственно предназначенные для сравнения строк. В выражениях, предполагающих подстановку переменных и команд, может выполняться группировка с помощью фигурных скобок. Это возможно потому, что в параметре команды expr подстановка осуществляется в два
74 Часть I. Основы Tcl прохода: первый проход выполняет интерпретатор Tcl, а второй — сама команда expr. Как правило, при этом проблемы не возникают, так как математические выражения не содержат символов, имеющих специальное значение. Второй проход подстановки необходим для поддержки while, if и других команд, в которых вычисляется значение логических выражений. Группировка позволяет обеспечить более эффективное выполнение команд. Чтобы команда expr самостоятельно выполняла подстановку команд и переменных, группировку необходимо выполнять с использованием фигурных скобок. В противном случае могут осуществляться нежелательные преобразования числовых значений в строковые и строковых — в числовые. Это замедляет выполнение программ, кроме того, в некоторых случаях преобразования могут влиять на точность вычислений. Предположим, что переменной х присваивается значение, являющееся результатом вычисления выражения: set х [expr {sqrt(2.0)}] Как и следовало ожидать, значение х представляет собой число с плавающей точкой двойной точности. Предположим теперь, что полученное значение х вы используете в следующем выражении: set two [expr $х * $х] Вполне возможно, что результат не будет равен 2.0! Причина состоит в том, что Tcl подставляет $х, expr объединяет все параметры в одну строку и снова выполняет разбор выражения. Если вы запишете то же выражение следующим образом: set two [expr {$х * $х}] то команда expr самостоятельно выполнит подстановку и значение х, являющееся числом с плавающей точкой, сохранится. Поскольку преобразование в строку символов не производится, значение выражения будет вычислено точнее и эффективнее. Более детально обработка Tcl-переменных обсуждается в главе 47. Комментарии Для обозначения комментариев в Tcl используется символ #. В отличие от многих других языков, он должен быть указан в начале команды. Тот же символ в другой позиции не будет обработан специальным образом. Часто комментарии располагают после окончания команды, предваряя символ # точкой с запятой, завершающей предыдущую команду.
Глава 1. Общие сведения о языке Tcl 75 # Использование комментариев set rate 7.0 ;# Общая оценка set months 60 ;# Срок возвращения кредита Заметьте, что символ обратной косой черты позволяет продолжить комментарии в следующей строке. Точка с запятой в составе комментариев рассматривается как обычный символ. Строку комментариев завершает только символ перевода строки. # Начало комментариев в программе на языке Tcl \ продолжение комментариев (обратите внимание на отсутствие символа #). Пример использования обратной косой черты при составлении комментариев приведен в листинге 2.3. Комментарии Tcl имеют следующую особенность: открывающей фигурной скобке в составе комментариев обязательно должна соответствовать закрывающая скобка. Это странное правило было принято для того, чтобы упростить реализацию программ разбора Tcl. В результате приведенный ниже фрагмент кода будет работать некорректно. # if {boolean expression1.} { if {boolean expression2} { команды У В предыдущем примере в комментариях присутствует лишняя открывающая скобка. При разборе будет сгенерировано сообщение о том, что в конце сценария отсутствует закрывающая фигурная скобка. Для того чтобы запретить выполнение больших фрагментов кода, можно поместить требуемые команды в блок, который никогда не будет выполнен. if {0} { неиспользуемый код У Правила подстановки и группировки Ниже приведен перечень правил группировки и подстановки, осуществляемых интерпретатором Tcl перед выполнением команды. • Параметры команды разделяются пробелами. Если параметр формируется посредством группировки с использованием фигурных скобок или двойных кавычек, пробелы в составе группы теряют специальное значение и рассматриваются как обычные символы.
76 Часть I. Основы Tcl • Группировка с помощью фигурных скобок запрещает подстановку. Интерпретатор объединяет в группу все символы, находящиеся между левой и соответствующей ей правой фигурной скобкой, в том числе символы перевода строки, точки с запятой и вложенные скобки. Группировка оканчивается при появлении закрывающей скобки. Внешние скобки не включаются в состав группы. • Группировка с помощью двойных кавычек разрешает подстановку. Интерпретатор группирует все символы, включая переводы строк и точки с запятой, до появления второй двойной кавычки. Кавычки не включаются в состав группы. Если в группу должен войти символ двойной кавычки, перед ним надо указать обратную косую черту. • Решение о группировке принимается перед подстановкой, поэтому значения переменных и результаты выполнения программ не влияют на группировку. • Символ $ вызывает подстановку переменной. Имя переменной может быть любой длины; регистр символов принимается во внимание. Если в строке содержится ссылка на переменную, не отделенная от остальной части строки символами, имеющими специальное значение, или если в составе имени переменной содержатся символы, отличные от букв, цифр и знака подчеркивания, то ссылка записывается в виде ${имя_переменой}. • Квадратные скобки вызывают подстановку команды. Выражение в квадратных скобках (включая сами скобки) заменяется результатом выполнения команды. Вложенные команды, в свою очередь, могут содержать другие команды. • Обратная косая черта отменяет специальное значение символов. Использование ее можно рассматривать как подстановку, при которой обратная косая черта и следующие за ней один или несколько знаков заменяются другим символом. • Подстановка может выполняться в любой позиции, за исключением тех случаев, когда для группировки используются фигурные скобки. Часть строки, сформированной при группировке, могут составлять строковые литералы, а остальную часть — результаты подстановок. С помощью подстановки можно сформировать даже имя команды. • Перед вызовом команды осуществляется подстановка; она выполняется в течение одного прохода. Результат подстановки повторно не интерпретируется. Благодаря этому появляется возможность задавать значения переменных, содержащие специальные символы, такие как символ $, пробелы, квадратные или фигурные скобки. Поскольку подстановка
Глава 1. Общие сведения о языке Tcl 77 выполняется в один проход, эти символы не выполняют специальных функций в сформированной строке. Особенности группировки и подстановки • При написании Tcl-программ начинающие программисты, используя фигурные скобки или двойные кавычки для группировки, иногда не указывают пробел между параметрами. Это приводит к возникновению ошибки. Дело в том, что пробелы играют роль разделителей, а фигурные скобки или кавычки лишь выполняют группировку. Если пробел пропущен, интерпретатор сообщит о том, что после закрывающей скобки или кавычки ожидается другой символ. В следующем примере ошибка возникает вследствие того, что между закрывающей и открывающей фигурной скобкой пропущен пробел: if {$x > l}{puts "х = $х"} • Двойная кавычка начинает группу лишь в том случае, если перед ней указан пробел или знак табуляции. Это означает, что вы можете разместить кавычку внутри последовательности символов, не предваряя ее обратной косой чертой. В этом случае группа должна быть отделена от других частей строки пробелами или фигурными скобками. Несмотря на то что приведенная ниже команда корректна, использовать подобные выражения не рекомендуется, так как текст программы становится трудным для восприятия. set silly a"b • Если для группировки используются двойные кавычки, то фигурные скобки теряют свои специальные свойства. В группе, ограниченной кавычками, подстановка осуществляется независимо от наличия скобок. В следующем примере видно, что фигурные скобки, находящиеся в группе, сформированной с помощью кавычек, не отменяют подстановку: set x xvalue set у "foo {$x} bar" => foo {xvalue} bar • Если для группировки используются кавычки, а в составе группы находится вложенная команда, то в команде также может содержаться группа, созданная с помощью кавычек. Сказанное иллюстрируется следующим примером: puts "results [format "°/0i °/0f" $x $y]"
78 Часть I. Основы Tcl • Если для подстановки команд используются квадратные скобки, то перед открывающей скобкой и после закрывающей скобки пробелы не обязательны. Интерпретатор рассматривает все символы, находящиеся между квадратными скобками, как часть текущей группы. Приведенная ниже команда присваивает переменной х значение, представляющее собой конкатенацию результатов выполнения двух команд. Так происходит потому, что между закрывающей и открывающей квадратной скобкой отсутствует пробел. set x [ команда_1'] [комаада_2] • При группировке с помощью фигурных скобок или двойных кавычек символы новой строки и точка с запятой теряют свое специальное значение. Они включаются в группу так же, как и обычные символы. В следующем примере переменной х присваивается значение, содержащее символы перевода строки: set х "This is line one. This is line two. This is line three." • При подстановке символ перевода строки и точка с запятой завершают команду. Если вам надо поместить в квадратных скобках длинную команду и вы хотите записать ее в нескольких строках, поставьте в конце каждой строки (кроме последней) обратную косую черту. Пример записи команды в нескольких строках содержится в листинге 1.9. • Если после знака $ нет ни буквы, ни цифры, ни знака подчеркивания, ни открывающей скобки, он интерпретируется как обычный литеральный символ. В приведенном ниже примере переменной х присваивается символ $. set x $
Глава 1. Общие сведения о языке Tcl 79 Справочная информация Последовательности символов, начинающиеся с обратной косой черты Таблица 1.1. Использование обратной косой черты для представления символов \а Звуковой сигнал (0x7) \b Возврат (0x8) \f Перевод страницы (Охс) \n Перевод строки (Оха) \г Возврат каретки (Oxd) \t Табуляция (0x9) \v Вертикальная табуляция (Oxb) \<перевод строки> Замена символов перевода строки и всех пробелов в начале следующей строки одним пробелом \\ Обратная косая черта (' \') \ооо Символ, заданный восьмеричным числом. После обратной косой черты могут следовать одна, две или три восьмеричные цифры (0-7). \xhh Символ, заданный шестнадцатеричным числом. После \х могут следовать одна или две шестнадцатеричные цифры. Используя данное обозначение, надо соблюдать осторожность, так как преобразуются все шестнадцатеричные цифры, расположенные после \х, но учитываются только две последние \\uhhhh Символ в формате Unicode (16 битов), заданный с помощью четырех шестнадцатеричных цифр \с Если символ с не указан в данной таблице, то данное выражение заменяется литеральным значением с. Например, последовательности \$,\",\{,\},\] и \ [ используются для отмены специальных значений соответствующих символов
80 Часть I. Основы Tcl Арифметические операции Таблица 1.2. Арифметические операции (приводятся по убыванию приоритета) - ~ ! Унарный минус, побитовое отрицание, логическое отрицание * / У, Умножение, деление, остаток от деления + - Сложение, вычитание << >> Сдвиг влево, сдвиг вправо <><=>= Сравнение: меньше, больше, меньше или равно, больше или равно == != eq ne Равенство, неравенство, равенство строк (Tcl 8.4), неравенство строк (Tcl 8.4) & Побитовое И Побитовое исключающее ИЛИ (XOR) I Побитовое ИЛИ && Логическое И I I Логическое ИЛИ х?у: z Если х, то у; иначе — z Встроенные математические функции Таблица 1.3. Встроенные математические функции в языке Tcl acos(x) Арккосинус х asin(x) Арксинус х atan(x) Арктангенс х atan2(y,x) При преобразовании прямоугольных координат (х,у) в полярные (r,th) atan2 дает th ceil(x) Наименьшее целое значение, большее или равное х cos(x) Косинус х cosh(x) Косинус гиперболический х ехр(х) Экспонента, ех floor(х) Наибольшее целое значение, меньшее или равное х fmod(x,y) Остаток от деления х/у с плавающей точкой hypot(x,y) Возвращает sqrt(x*x + у*у), т.е. компонент г полярных координат log(x) Натуральный логарифм х
Глава 1. Общие сведения о языке Tcl 81 Окончание табл. 1.3 loglO(x) Логарифм по основанию 10 от х pow(x,y) х в степени у, или ху sin(x) Синус х sinh(x) Синус гиперболический х sqrt(x) Квадратный корень из х tan(x) Тангенс х tanh(x) Тангенс гиперболический х abs (x) Абсолютное значение х double (х) Преобразование х в формат с плавающей точкой int(x) Усечение х до целочисленного значения round(х) Округление х до целочисленного значения rand () Возвращает случайное число с плавающей точкой в интервале от 0.0 до 1.0 srand(x) Определяет х как значение, используемое для инициализации генератора случайных чисел wide(x) Преобразует х в 64-битовое значение wide integer(Tcl 8.4) Основные Tcl-команды В табл. 1.4 содержатся сведения о Tcl-командах. Во втором столбце указан номер главы, в которой впервые встречается информация о соответствующей команде. Таблица 1.4. Встроенные команды Tcl Команда Глава Описание after 16 Планирует выполнение TCL-команды на более позднее время append 4 Добавляет параметры к значению переменной. Пробелы не добавляются array 8 Определяет состояние массива и выполняет поиск binary 4 Выполняет преобразование между строковым и двоичным представлением данных break б Прерывает цикл до выполнения условия завершения catch б Перехватывает ошибки cd 9 Изменяет рабочий каталог
82 Часть I. Основы Tcl Продолжение табл. 1.4 Команда Глава Описание clock 13 Предоставляет строки, содержащие данные о времени, и форматированное представление даты close 9 Закрывает и открывает поток ввода-вывода с one at 5 Выполняет конкатенацию параметров, разделяя их пробелами. Объединяет списки console 2 Осуществляет управление консолью, используемой для интерактивного ввода команд continue б Вызывает немедленный переход к следующей итерации цикла error б Эмулирует условия возникновения ошибки eof 9 Проверяет наличие признака конца файла eval 10 Выполняет конкатенацию параметров и интерпретирует результат как команду exec 9 Порождает процесс и выполняет команду Unix exit 9 Завершает процесс expr 1 Выполняет математическое выражение iblocked 16 Проверяет наличие данных в команде ввода iconfigure 16 Предоставляет параметры канала ввода-вывода и устанавливает новые значения этих параметров iсору 17 Выполняет копирование из одного канала ввода-вывода в другой file 9 Предоставляет информацию о файловой системе f ileevent 16 Регистрирует команду обратного вызова для поддержки обмена данными, управляемого событиями flush 9 Осуществляет принудительный вывод информации из внутреннего буфера for б Формирует цикл, действующий подобно циклу for в языке С for each б Организует перебор значений списка в цикле format 4 Форматирует строку. Действует подобно sprintf в языке С gets 9 Читает строку из входного потока glob 9 Расширяет шаблон, используемый при проверке соответствия имен файлов global 7 Объявляет глобальные переменные
Глава 1. Общие сведения о языке Tcl 83 Продолжение табл. 1.4 Команда Глава Описание history 13 Обеспечивает работу со списком предыстории if 6 Проверяет выполнение условия. Допускает наличие ветвей else и elseif incr 1 Увеличивает значение переменной на величину, выражаемую целым числом info 13 Предоставляет информацию о состоянии Тс1-интерпрета- тора interp 19 Создает дополнительные Тс1-интерпретаторы join 5 Осуществляет конкатенацию элементов списка, разделяя их указанной строкой lappend 5 Добавляет элементы к концу списка 1 index 5 Возвращает элемент списка 1 insert 5 Вставляет элемент в список list 5 Оформляет параметры в виде списка llength 5 Возвращает число элементов в списке load 47 Загружает разделяемые библиотеки, определяющие Tcl- команды lrange 5 Возвращает элементы списка в заданном диапазоне индексов lreplace 5 Заменяет элемент списка lsearch 5 Выполняет поиск элемента списка, соответствующего шаблону lset 5 Включает элемент в состав списка (Tcl 8.4) lsort 5 Выполняет сортировку списка namespace 14 Позволяет создавать пространства имен и выполнять с ними различные действия open 9 Открывает файл или канал package 12 Реализует пакет или указывает, какие пакеты нужны приложению pid 9 Возвращает идентификатор процесса ргос 7 Определяет Тс1-нроцедуру puts 9 Выводит строку в выходной поток pwd 9 Возвращает текущий каталог read 9 Читает блок символов из входного потока
84 Часть I. Основы Tcl Окончание табл. 1.4 Команда Глава Описание regexp 11 Предоставляет возможность использования регулярных выражений regsub 11 Выполняет подстановку в зависимости от результатов сравнения с регулярным выражением rename 7 Изменяет имя Тс1-команды return 6 Возвращает значение процедуры scan 4 Выполняет разбор строки в соответствии со спецификацией формата seek 9 Устанавливает смещение для потока ввода-вывода set 1 Присваивает значение переменной socket 17 Устанавливает сетевое соединение TCP/IP source 2 Выполняет Tcl-команды, содержащиеся в файле split 5 Разделяет строку на элементы списка string 4 Позволяет выполнять различные действия со строками subst 10 Осуществляет подстановку вложенных команд и переменных switch б Проверяет выполнение нескольких условий Tcll 9 Возвращает текущее смещение для потока ввода-вывода time 13 Определяет время выполнения команды trace 13 Отслеживает присвоение значений переменным unknown 12 Определяет действия при появлении неизвестной команды unset 1 Удаляет переменные uplevel 10 Выполняет команду в другой области видимости upvar 7 Ссылается на переменную в другой области видимости variable 14 Определяет переменные в пространстве имен vwait 16 Ожидает, пока переменная не будет модифицирована while б Формирует цикл, выполняющийся до тех пор, пока логическое выражение не примет значение false
Глава 2 Первые шаги В данной главе описывается процедура запуска средств Tcl и Тк в операционных средах Unix, Windows и Macintosh, а также рассматриваются Tcl-команды source, console и info. 2_1анная глава посвящена вопросам выполнения сценариев Tcl в различных операционных системах. Если вы собираетесь запускать Тс1-программы в средах Unix, Windows и Macintosh, вам надо знать особенности работы Tcl в каждой из этих систем. Последняя версия Tcl/Tk находится на прилагаемом к книге компакт-диске, либо ее можно скопировать из Internet. Список соответствующих URL приведен во введении к настоящей книге. Основная программа Tcl/Tk называется wish. Ее название расшифровывается как windowing shell. Эта программа позволяет создавать графические приложения и запускать их на любой из перечисленных выше платформ. Имя программы может несколько различаться в зависимости от конкретной системы. В системе Unix используется имя wish. В Windows исполняемый файл этой программы называется wish.exe, а в Macintosh — Wish. В состав имени файла может также входить номер версии, например wish8.0, wish83.exe или Wish 8.4. Различия между версиями рассмотрены во введении, а более детально они описываются в части VII. При изложении материала особенности разных вариантов основной программы Tcl не будут учитываться, и для ее обозначения будет использоваться имя wish. Tk добавляет к основному набору команд Tcl команды, предназначенные для создания графических пользовательских интерфейсов. Средства Тк рассматриваются в части III. Если в Tcl-программе графический интерфейс не нужен, средства Tcl могут использоваться без Тк. Примером такой программы является CGI-сценарий, который будет обсуждаться в главе 3. Для запус-
86 Часть I. Основы Tcl ка сценария без графического интерфейса может использоваться программа Tclsh, tclsh.exe или Tclsh. При запуске программа wish отображает окно, в котором содержится приглашение для ввода Tcl-команды. В качестве такого приглашения используется символ °/о. В ответ на приглашение вы можете ввести любую команду, например выполнить какой-либо из примеров, приведенных в данной книге. В системах Windows и Macintosh приглашение для ввода Tcl-команды отображается в консольном окне. В Unix для этой цели используется окно терминала. Вы также можете создать сценарий Tcl/Tk в виде независимого приложения. Способы создания подобных сценариев будут описаны далее. Команда source По мере чтения книги желательно выполнять программы и отдельные команды, предложенные в качестве примеров. Коды примеров содержатся на прилагаемом компакт-диске в каталоге exsource. При желании вы можете отредактировать сценарий, воспользовавшись любым текстовым редактором. Код сценария надо сохранить в файле, а затем запустить его с помощью команды source. source имя_файла После выполнения команды source происходит чтение Tcl-команд из файла. Прочитанные команды выполняются так же, как и команды, введенные в интерактивном режиме. Пример Tcl-приложения будет рассмотрен в главе 3. Откройте с помощью текстового редактора файл с именем cgil.tcl. После каждой модификации содержимого файла загрузите его с помощью команды source и проверьте результаты внесения изменений. Модификация программы и проверка ее работоспособности осуществляются быстро, так как компиляция не выполняется. Tcl-сценарии в системе Unix В системе Unix можно создавать независимые сценарии Tcl или Tcl/Tk. Интерпретатор, с помощью которого должен обрабатываться сценарий, задается в первой строке файла. В начале этой строки содержатся символы #!, за которыми указывается путь к программе интерпретатора. Ниже приведен код \гже знакомой вам программы Hello, World, которая была рассмотрена в главе 1. В листинге 2.1 к коду программы добавлена первая строка, указывающая расположение интерпретатора Tcl.
Глава 2. Первые шаги 87 Листинг 2.1. Независимый Tcl-сценарий в системе Unix #!/usr/local/bin/tclsh puts stdout {Hello, World!} В листинге 2.2 представлен код Tk-программы Hello, World, которая будет рассматриваться в главе 23. Листинг 2.2. Независимый Tk-сценарий в системе Unix #!/usr/local/bin/wish button .hello -text Hello -command {puts "Hello, World!"} pack .hello -padx 10 -pady 10 Команда puts воспринимает слова "Hello, World!" как второй параметр. В вашей системе пути к программам tclsh и wish могут отличаться от приведенных выше. Если вы неправильно укажете путь, то получите сообщение об ошибке "command not found". Путь к Tcl-интерпретатору можно узнать с помощью команды info. Ниже приведен пример вызова этой команды. info nameofexecutable => /home/welch/install/linux-ix86/bin/tclsh8.4 Обращайте внимание на длину пути к интерпретатору. Во многих версиях Unix первая строка файла, содержащая путь к интерпретатору, не может содержать более 32 символов (включая знаки #!). Если длина пути слишком велика, для интерпретации вашего сценария будет использоваться оболочка /bin/sh, что приведет к возникновению синтаксической ошибки. Для того чтобы сократить путь, можно использовать символьную ссылку. Однако в некоторых системах, например в старых версиях Solaris, символьные ссылки в первой строке файла указывать нельзя. К счастью, в Solaris отсутствует ограничение на длину первой строки, поэтому вы можете задавать путь, содержащий любое число каталогов. Ниже приведен код, позволяющий указывать путь любой длины. Этот способ был предложен Кевином Кении (Kevin Kenny) в группе новостей сотр. lang.tcl. В нем используются различия между правилами создания комментариев в Tcl и в оболочке Bourne shell. Правила написания Тс1-комментариев см. в главе 1. В приведенном примере команда Bourne shell exec, запускающая интерпретатор Tcl, воспринимается этим интерпретатором как комментарии, но доступна оболочке /bin/sh. Команда exec (в оболочке /bin/sh) заменяет текущую программу, поэтому действия Bourne shell ограничиваются выполнением этой команды; остальная часть сценария обрабатывается интерпретатором Tcl.
88 Часть I. Основы Tcl Листинг 2.3. Использование /bin/sh для запуска Тс1-сценария #!/bin/sh # Обратная косая черта превращает строку в Tcl-комментарии \ exec /some/very/long/path/to/wish "$0" ${l+"$@"} # ... Код Tcl-сценария ... Запускать программу Tclsh или wish можно, даже не зная пути к ней. Вы можете включить в начало файла следующие строки: #!/bin/sh # Запуск wish с использованием переменной окружения PATH \ exec wish -f "$0" ${l+"$@"} Подобный подход имеет существенный недостаток: пути к программам, реализующим средства Tcl и Тк, могут не указываться в переменной окружения PATH. По мере необходимости вы можете включить в сценарий несколько команд Bonnie shell. Например, с помощью кода, приведенного ниже, не только вызывается сценарий Tcl, но и задается значение переменной окружения. #!/bin/sh # \ export LD_LIBRARY_PATH=/usr/local/lib # \ exec /usr/local/bin/tclsh "$0" ${l+"$@"} Главное меню Windows Программы Tcl/Tk можно включать в главное меню Windows. Соответствующая команда должна содержать полное имя программы wish, ехе и имя сценария. Если путь к файлу wish.exe содержит пробелы, то необходимо использовать кавычки. В результате команда приобретает вид, подобный приведенному ниже. "c:\Program Files\Tcl84\wish84.exe" "c:\My Files\script.tcl" Данная команда запускает сценарий c:\My Files\script .tcl как независимую программу Tcl/Tk. Macintosh OS 8/9 и ResEdit Если вы хотите создать независимое приложение Tcl/Tk в среде Macintosh OS 8 или 9. вам надо скопировать программу Wish и добавить ресурс
Глава 2. Первые шаги 89 Macintosh с именем tclshrc, содержащий стартовый Тс1-код. Tcl-код должен содержать единственную команду source, которая читает файл сценария. Ниже приведена подробная инструкция по созданию ресурса с помощью ResEdit. • Создайте копию программы Wish и откройте ее в ResEdit. • Вызовите меню Resource и выберите операцию Create New Resource для создания нового ресурса TEXT. • ResEdit выведет окно, в котором вы сможете ввести текст. Задайте команду source и передайте ей имя сценария. • source "Hard Disk:Tcl/Tk 8.3:Applications:MyScript.tcl" • Присвойте ресурсу имя tclshrc. Сделать это можно с помощью диалогового окна, которое вызывается из меню Resources ResEdit. Данная последовательность команд может быть реализована с помощью приложения Drag и Drop Tclets. которое поставляется в составе дистрибутивного пакета Tcl для Macintosh. Если вы перетащите Tcl-сцепарий на пиктограмму приложения, будут созданы копия Wish и текстовый ресурс tclshrc, содержащий команду source, предназначенную для загрузки сценария. Используя среду разработки Macintosh, вы можете создать версию Wish со встроенным дополнительным ресурсом. Ресурс добавляется в файл applicationlnit.г. Ресурс, содержащий Tcl-код, можно использовать следующим образом: source -rcrc ресурс Если вы не хотите редактировать ресурсы, можете выбрать сценарий для запуска с помощью меню Wish Source. Macintosh OS X В Mac OS X можно запускать те же программы Tcl/Tk, что и в Macintosh OS 8 или 9. Однако лучше всего для Mac OS X подходит продукт Tcl/Tk Aqua, использующий оконную систему под названием Aqua. В этом случае структура приложения должна быть несколько иной. Wish ищет в каталоге Resources/Scripts файл AppMain.tcl. Если этот файл существует, он используется как стартовый сценарий и к auto_path добавляется папка Scripts. Это напоминает использование ресурса tclshrc, описанное ранее. На прилагаемом к книге компакт-диске находится дистрибутивный пакет Tcl/Tk Aqua. Дополнительные сведения о поддержке Tcl/Tk на Macintosh можно найти по следующим адресам: http://wiki.tcl.tk/macos/ http://www.maths.mq.edu.au/~steffen/tcltk/
90 Часть I. Основы Tcl Команда console На платформах Windows и Macintosh поддерживается встроенная консоль, которая может использоваться для ввода Tcl-команд в интерактивном режиме. Управление консолью осуществляется посредством команды console. По умолчанию консольное окно отображается на экране. Для того чтобы запретить его вывод, надо использовать приведенную ниже команду. console hide Разрешить отображение консоли можно следующим образом: console show Консоль реализуется вторым интерпретатором Tcl. Ниже показан способ, позволяющий выполнить команду с помощью этого интерпретатора. console eval команда Существует альтернативная версия консоли под названием ТкСоп. Она содержится на прилагаемом к книге компакт-диске, кроме того, вы можете найти последнюю версию консоли в Internet. Автор ТкСоп Джефф Хоббс (Jeff Hobbs) снабдил этот продукт рядом возможностей, упрощающих работу пользователя. Существует версия ТкСоп для системы Unix. Некоторые из возможностей ТкСоп были реализованы в программе console, содержащейся в Tcl 8.4. Параметры командной строки Если вы запускаете сценарий из командной строки, например с помощью оболочки Unix, вы можете передать ему параметры. Соответствующая команда выглядит следующим образом: % myscript.tcl параметр_1 параметре параметр_3 В Windows можно также сформировать команду, которая запускает wish, передает ей сценарий, а сценарий, в свою очередь, получает параметры. "c:\Program Files\Tcl84\wish.exeи c:\your\script.tcl параметры Параметры командной строки передаются сценарию как значение переменной argv. Число параметров содержится в переменной argc. Имя сценария не входит в состав argv и не учитывается в argc. Вместо этого имя сценария становится значением переменной argvO. В табл. 2.2 перечислены переменные, определенные в оболочках Tcl. Значение argv представляет собой список, поэтому при работе с данной переменной можно использовать команду lindex, которая будет рассмотрена в главе 5. set argl [lindex $argv 0]
Глава 2. Первые шаги 91 Сценарий, приведенный в листинге 2.4, выводит список параметров, переданных ему при вызове (с командой f oreach вы познакомитесь в главе 6). Листинг 2.4. Сценарий EchoArgs # Tcl-сценарий, отображающий параметры, # заданные в командной строке puts "Program: $argvO" puts "Number of arguments: $argc" set i 0 foreach arg $argv { puts "Arg $i: $arg" incr i 2 Опции командной строки, отображаемые программой wish Некоторые опции командной строки интерпретируются программой wish, поэтому они отсутствуют в переменной argv. Для вызова программы wish применяется следующее выражение: wish ?опции? ?сценарий? ?параметр_1 параметре? Если сценарий не указан, wish запускается в режиме интерактивного ввода команд. Опции, поддерживаемые wish, приведены в табл. 2.1. Таблица 2.1. Опции программы wish -colormap new Использовать новую приватную карту отображения цвета (см. главу 41) -display терминал Использовать указанный X-терминал (только для системы Unix) -geometry размеряй „расположение Задает размер и расположение окна (см. главу 44) -name имя Задает имя Tk-приложения (см. главу 43) -sync Синхронный запуск X Window (только для системы Unix) -use идентификатор Использовать в качестве главного окна, заданного посредством указанного идентификатора (см. главу 44) -visual визуальный_класс Задает визуальный класс для главного окна (см. главу 41) Завершает опции программы wish
92 Часть I. Основы Tcl Предопределенные переменные Таблица 2.2. Переменные, определенные в программах tclsh и wish argc Число параметров командной строки argv Список параметров, указанных в командной строке argvO Имя выполняемого сценария. При работе в интерактивном режиме argvO содержит имя программы, реализующей оболочку embed_args . Число атрибутов в составе дескриптора <EMBED>. Используется только для аплетов Tcl (см. главу 20) env Массив переменных окружения (см. главу 9) tcl_interactive Содержит значение true (1), если tclsh запрашивает команды в интерактивном режиме tcl_library Каталог библиотеки сценариев tcl_patchLevel Содержит модифицированный номер версии, например 8.0Ы tcl_platform Массив, содержащий сведения об операционной системе (см. главу 13) tcl_promptl Если данная переменная определена, она содержит команду, отображающую приглашение для ввода tcl_prompt2 Если данная переменная определена, она содержит команду, которая отображает приглашение в случае, когда текущая команда не завершена tcl_version Номер версии auto_path Пути к каталогам, в которых производится поиск библиотек сценариев (см. главу 12) auto_index Отображение имени команды в определяющую ее команду Tcl auto_noload Если данная переменная присутствует, работа с библиотеками запрещена auto.noexec Если данная переменная присутствует, автоматическое выполнение запрещено geometry Значение опции -geometry (только для wish)
Глава 3 Простой CGI-сценарий В данной главе рассматривается простая Tcl-программа, формирующая в процессе работы Web-страницу. Здесь также обсуждаются язык HTML и CGI-интерфейс, поддерживаемый Web-серверами. При написании программы использован пакет ncgi, принадлежащий стандартной библиотеке Tcl. 1—) данной главе описана простая программа, реализующая гостевую книгу. В процессе работы программа читает данные из базы и на их основе формирует HTML-доку мент. Работая с Web-браузером, пользователь может просматривать как статические Web-страницы, так и документы, динамически генерируемые программами. Особенности доставки сгенерированного документа клиентской программе зависят от конкретной системы. В главе 18 описан Tcl Web Server, в состав которого входит гостевая книга, рассматриваемая в этой главе. Данный CGI-сценарий можно использовать с сервером любого типа, но детали установки сценария надо согласовать с администратором Web-узла. В этой главе приведены краткие сведения о языке HTML и об основных правилах составления CGI-программ. HTML — это язык, позволяющий форматировать текст и включать в документ гипертекстовые ссылки на другие Web-страницы. CGI — это стандарт, определяющий взаимодействие Web- сервера, который осуществляет доставку документов, и программ, которые динамически формируют эти документы. Вопросам создания CGI-сценариев посвящены многочисленные книги. Гостевая книга — это структура, позволяющая посетителям сообщить администратору узла какие-либо сведения. В данной главе мы создадим гостевую книгу, использующую возможности World Wide Web. Адрес посетителя записывается как унифицированный локатор ресурсов (URL). Пользователю гостевая книга представлена как Web-страница, содержащая гипертекстовые
94 Часть I. Основы Tcl ссылки. Создаваемая в данной главе программа поддерживает базу данных адресов посетителей и использует ее для генерации Web-страницы. При создании рассмотренных здесь Tcl-сценариев применены команды, которые будут подробно описаны в последующих главах. Основная цель приведенных примеров — продемонстрировать возможности Tcl, не уделяя при этом излишнего внимания отдельным деталям. В процессе рассмотрения кодов у вас неизбежно возникнут вопросы. Ответить на них помогут приведенные в тексте ссылки на другие главы, в которых соответствующие темы рассматриваются более подробно. Общие сведения о языке HTML Для создания Web-страниц используется язык гипертекстовой разметки (HTML — Hyper Text Markup Language). Автор HTML-страницы помечает текст с помощью специальных дескрипторов, которые определяют структуру и формат документа. Например, заголовок Web-страницы формируется с помощью следующего элемента: <TITLE>My Home Page</TITLE> Дескрипторы задают лишь общие правила форматирования; окончательное решение о том, как следует отобразить тот или иной элемент, принимает браузер. Элементы разметки языка HTML очень просты. Большинство дескрипторов записывается в следующем формате: < д е скрипт'ор а трибу ты > о бычный тек ст< /де скрипт ор > Для форматирования текста, как правило, используются пары дескрипторов. В составе открывающего дескриптора могут присутствовать атрибуты. Перед именем закрывающего дескриптора указывается косая черта. В именах дескрипторов регистр символов не учитывается, например, дескриптор, предназначенный для формирования заголовка, может быть записан как <title>, <Title> или <TITLE>. Соответственно, в качестве закрывающего дескриптора можно указать </title>, </Title>, </TITLE> и даже </TiTlE>. Дескриптор <А> позволяет создавать гипертекстовые ссылки, указывающие на другие Web-страницы. С помощью гипертекстовых ссылок устанавливается отношение между ресурсами, в результате чего пользователь получает возможность переходить от документа к документу и просматривать связанные между собой данные. Именно ссылки делают Web столь привлекательной для пользователей. В составе открывающего дескриптора <А> указывается параметр HREF, значением которого является URL целевого документа. Если вы хотите создать ссылку на страницу одного из авторов этой книги, вы можете сделать это следующим образом: <А HREF=uhttp://www.beedub.com/">Brent Welch</A>
Глава 3. Простой CGI-сценарий 95 Отображая Web-страницу, которая содержит данный элемент, браузер выведет текст "Brent Welch" синим цветом с подчеркиванием. Если вы щелкнете мышью на этом тексте, браузер отобразит документ, находящийся по адресу http://www.beedub.com/. HTML предоставляет разработчику многие другие возможности, но мы не будем рассматривать их в данной книге. Приведенных здесь сведений достаточно для того, чтобы у читателя составилось общее представление о языке, позволяющее понять приведенные примеры. HTML- дескрипторы, которые используются в примерах, приведенных в данной книге, перечислены в табл. 3.1. Таблица 3.1. HTML-дескрипторы, используемые в данной книге HTML Основной дескриптор, содержащий код документа HEAD Определяет раздел заголовка HTML- документа TITLE Определяет заголовок Web-страницы BODY Формирует тело документа. С помощью атрибутов данного дескриптора можно задавать цвета для отображения Web-страницы HI — Н6 В языке HTML определены б уровней заголовков: HI, Н2, НЗ, Н4, Н5 и Н6 Р Задает новый абзац BR Включает перевод строки В Определяет текст, отображаемый полужирным шрифтом I Определяет текст, отображаемый курсивом А Используется для создания гипертекстовых ссылок IMG Позволяет включать в документ изображение DL Формирует список определений DT Термин в списке определений DD Определение в списке определений UL Маркированный список LI Пункт маркированного списка TABLE Используется для создания таблицы TR Строка таблицы TD Ячейка в строке таблицы FORM Определяет форму ввода INPUT Создает интерактивный элемент: флажок опции, переключатель опций, кнопка submit и т.д. TEXTAREA Текстовое поле, содержащее несколько строк
96 Часть I. Основы Tcl Стандарт CGI и динамическое создание Web-страниц В Web используются два типа документов: статические и динамические. Статическая Web-страница создается единожды и хранится на сервере. Она предоставляется клиенту в ответ на запрос, и содержимое ее не изменяется. Если у вас есть данные, которые вы хотите предоставить всем желающим, вы создаете документ, форматируете его с помощью HTML-дескрипторов и размещаете на Web-сервере. Большинство персональных Web-страниц является статическими. Динамическая Web-страница, в отличие от статической, создается при каждом обращении клиента. Так, например, формируются документы, предоставляющие пользователям информацию о котировке биржевых акций. Некоторые ошибочно считают динамическими те страницы, которые содержат анимационные изображения. Это не так. Динамическая страница отличается от статической только тем, что она формируется при каждом обращении клиента. Поскольку информация, на основе которой создается динамическая страница, может изменяться, не исключено, что пользователь, дважды обратившись к одному документу, увидит различные данные. В большинстве случаев часто изменяющиеся данные удобно хранить в базе и генерировать HTML-документы с помощью программы. CGI-программы используются для формирования Web-страниц. Стандарт CGI (Common Gateway Interface) определяет правила передачи программе входной информации и способы идентификации различных результатов, например изображений, текста и HTML-данных. CGI-программа записывает содержимое документа в стандартный выходной поток, а Web-сервер доставляет документ Web-браузеру. Пример CGI-сценария, который выполняет простейшие действия, приведен в листинге 3.1. Листинг 3.1. Простейший CGI-сценарий puts "Content-Type: text/html" puts "" puts "<TITLE>The Current Time</TITLE>" puts "The time is <B>[clock format [clock seconds]]</B>" Программа создает HTML-доку мент, предоставляющий текущее время. Каждый раз, когда пользователь обращается к Web-странице, он видит на экране показания системного таймера сервера. Сервер, на котором выполняется CGI-программа, и пользователь, обращающийся к нему, могут находиться на любом расстоянии друг от друга. Данные, генерируемые программой, можно разделить на две части: заголовок и содержимое Web-страницы.
Глава 3. Простой CGI-сценарий 97 В приведенном выше простом примере заголовок состоит из одной строки, начинающейся с идентификатора Content-Type. Эта строка сообщает Web- браузеру тип передаваемых данных. Пустая строка отделяет заголовок ответа от содержимого страницы. Содержимое Web-страницы в данном примере начинается с дескриптора <TITLE>. Команда clock используется в программе дважды. С ее помощью извлекается информация о текущем времени, и производится форматирование полученных данных. Эта команда будет подробно описана в главе 13. В данном случае средства разметки HTML не конфликтуют с правилами формирования вложенных команд Tcl, поэтому мы можем объединить два вызова clock в составе параметра, передаваемого команде puts. Для группировки параметра используются двойные кавычки. В листинге 3.2 показаны выходные данные, сгенерированные в результате выполнения программы. Листинг 3.2. Выходные данные, сгенерированные в результате выполнения программы, представленной в листинге 3.1 Content-Type: text/html <TITLE>The Current Time</TITLE> The time is <B>Wed Jul 10 14:29:36 2002</B> Данный пример чрезвычайно прост, однако данные, сгенерированные программой, корректно отображаются в большинстве Web-браузеров. Более сложные примеры будут рассмотрены далее в этой главе. Сценарий guestbook.cgi Сценарий guestbook.cgi формирует Web-страницу, содержащую список посетителей. В листинге 3.3 приведен код данного сценария. Фрагменты сценария мы рассмотрим далее в этой главе. Первые строки предназначены для обработки кода интерпретатором tclsh. Данный подход был описан в главе 2. Листинг 3.3. Первый вариант CGI-сценария guestbook.cgi #!/bin/sh # guestbook.cgi # Данный сценарий реализует простую гостевую книгу. # Информация о посетителях хранится в базе. # Для обновления базы используется сценарий newguest.cgi. # \ exec tclsh "$0" ${1+"$@"}
98 Часть I. Основы Tcl # База данных содержится в файле guestbook.data. # Этот файл находится в том же каталоге, что и сценарий. set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] puts "text/html" puts "" set title "Brent's Guestbook" puts "<HTML><HEAD><TITLE>$title</TITLEX/HEAD>" puts "<B0DY BGC0L0R=white TEXT=black>" puts "<Hl>$title</Hl>" if {![file exists $datafile]} { puts "No registered guests, yet. <P> Be the first <A href=,newguest.html'>registered guest!</A>" } else { puts "The following folks have registered in my GuestBook. <P> <A href=Jnewguest.htmlJ>Register</A> <H2>Guests</H2>" catch {source $datafile} foreach name [lsort [array names Guestbook]] { set item $Guestbook($name) set homepage [lindex $item 0] set markup [lindex $item 1] puts "<H3><A href=$homepage>$name</A></H3>" puts $markup } } puts "</B0DYX/HTML>" Использование библиотечного файла В своей работе вы вряд ли ограничитесь написанием одного сценария. За первой Tcl-программой последуют другие. Вы неизбежно начнете модифицировать свои разработки, и управлять многочисленными копиями будет все труднее и труднее. Освоив новые средства при написании третьего сценария, захотите ли вы изменять два первых? Наверное, нет. Решить эту проблему
Глава 3. Простой CGI-сценарий 99 можно, создав в отдельном файле набор Tcl-процедур и используя этот файл при разработке новых CGI-сценариев. Несколько пакетов процедур, которые можно применять для создания сценариев, предоставляет стандартная Tcl-библиотека tcllib. Далее в этой главе мы обсудим пакет ncgi, предназначенный для поддержки данных. Однако вначале мы рассмотрим, как можно применить при создании различных программ набор собственных процедур. Предположим, что в вашем распоряжении есть файл cgihacks. tcl, содержащий Tcl-процедуры. Команда source позволяет загружать этот файл в ваш сценарий. Проще всего вызвать эту команду следующим образом: source cgihacks.tcl Файл загружается из того же каталога, что и сценарий. В процессе работы текущим может быть каталог, отличный от того, в котором содержатся сценарий CGI и файл cgihacks.tcl. Для определения расположения CGI-сценария и соответственно файла cgihacks.tcl можно использовать команду info script. Команды file dirname и file join позволяют работать с именами файлов и каталогов, независимо от используемой платформы. Эти команды описаны в главе 9. Для включения в состав сценария полного имени файла (не исключено, что в дальнейшем при перемещении программы оно изменится) можно использовать следующие команды: set dir [file dirname [info script]] source [file join $dir cgihacks.tcl] Вы также можете воспользоваться библиотеками сценариев. В главе 12 будут описаны средства, позволяющие создавать список процедур, с помощью которого приложение сможет быстро загрузить нужную процедуру. Там же будут рассмотрены вопросы создания пакета процедур, позволяющего организовать имеющиеся коды программ. Формирование начала HTML-документа Возможно, что одной из первых вы захотите создать процедуру, формирующую начало HTML-документа. Предположим, что вы предпочитаете начинать HTML-документ с элемента <Н1>, причем заголовок, формируемый с помощью этого элемента, совпадает с содержимым элемента <TITLE>. Кроме того, вы привыкли задавать с помощью дескриптора <B0DY> определенные цвета и начертания шрифтов. Вы можете создать Tcl-процедуру, формирующую начало HTML-документа, и использовать ее в сценариях. Если впоследствии ваши вкусы изменятся, в ваших силах изменить процедуру и эти изменения отразятся во всех использующих ее сценариях. В листинге 3.4 при-
100 Часть I. Основы Tcl веден код процедуры Cgi_Header, генерирующей стандартное начало Web- страницы. Листинг 3.4. Процедура Cgi_Header proc Cgi.Header {title {body {bgcolor=white text=black}}} { puts stdout "Content-Type: text/html <HTML> <HEAD> <TITLE>$title</TITLE> </HEAD> <B0DY $body> <HT>$title</Hl>" 2 В качестве параметров процедуре Cgi.Header передаются строка заголовка и атрибуты дескриптора <B0DY>. В определении процедуры предусмотрено значение по умолчанию, поэтому параметр, с помощью которого задаются атрибуты дескриптора <B0DY>, указывать не обязательно. По умолчанию для создаваемого HTML-документа задается черный текст на белом фоне. Белый цвет заменяет стандартный серый фон, отображаемый большинством браузеров. Определение значений процедур по умолчанию рассматривается в главе 7. Листинг 3.5. Второй вариант CGI-сценария guestbook. cgi #!/bin/sh # guestbook.cgi # Данный сценарий реализует простую гостевую книгу. # Информация о посетителях хранится в базе. # Для обновления базы используется сценарий newguest.cgi. # \ exec Tclsh "$0" ${1+"$@"} # База данных содержится в файле guestbook.data. # Этот файл находится в том же каталоге, что и сценарий. set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] # Загрузка Tcl-процедур поддержки source [file join $dir cgihacks.Tcl]
Глава 3. Простой CGI-сценарий 101 Cgi.Header "Brent's Guestbook" if {![file exists $datafile]} { puts "No registered guests, yet. <P> Be the first <A href=,newguest.html'>registered guest!</A>" } else { puts "The following folks have registered in my GuestBook. <P> <A href='newguest.htmlJ >Register</A> <h2>Guests</h2>" catch {source $datafile} foreach name [lsort [array names Guestbook]] { set item $Guestbook($name) set homepage [lindex $item 0] set markup [lindex $item 1] puts "<H3><A href=$homepage>$name</A></H3>" puts $markup } } puts "</B0DY></HTML>" В листинге 3.5 приведен новый вариант рассмотренной ранее CGI-npo- граммы. Данный сценарий загружает файл cgihacks.tcl и вызывает Cgi_Header. Процедура Cgi_Header содержит только команду puts, с помощью которой генерируется стандартный текстовый шаблон, который помещается в начале данных, предназначенных для вывода. Заметьте, что в тексте процедуры несколько строк сгруппировано с помощью двойных кавычек. Такой способ группировки выбран потому, что в строке содержатся ссылки на переменные, для которых должна быть произведена подстановка. В результате выполнения процедуры Cgi_Header генерируется HTML-код, представленный в листинге 3.3. Данные, генерируемые CGI-сценарием В процессе работы CGI-программа проверяет, зарегистрированы ли посетители. Для проверки наличия данных используется команда file, которая будет подробно рассмотрена в главе 9. Символ ! означает логическую операцию отрицания. if {![file exists $datafile]} {
102 Часть I. Основы Tcl Если файл с данными отсутствует, отображается страница, которая содержит гипертекстовую ссылку на документ newguest.html, позволяющий выполнить регистрацию. Этот документ будет рассмотрен ниже. HTML-код, генерируемый CGI-программой в случае, если файла данных нет на диске, представлен в листинге 3.6. Листинг 3.6. Информация, генерируемая программой guestbook.cgi при отсутствии файла данных Content-Type: text/html <HTML> <HEAD> <TITLE>Brent's Guestbook</TITLE> </HEAD> <B0DY BGC0L0R=white TEXT=black> <Н1>Вгеп^з Guestbook</Hl> <P> No registered guests. <P> Be the first <A HREF="newguest.html,,>registered guest!</A> </B0DYx/HTML> . Заметьте, что несколько строк HTML-кода отображается с отступом, который при создании документа вручную был бы совершенно не оправдан. Дело в том, что этот отступ задан при написании команды puts, с помощью которой генерируется часть документа. Поскольку браузер игнорирует лишние пробелы и знаки табуляции в HTML-файле, на экране пользовательского компьютера этот документ отобразится корректно. При использовании команды puts для вывода строк вам надо решить, где выравнивание важнее: в исходом коде CGI-сценария или в генерируемом HTML-файле. В данном случае HTML-код обрабатывается лишь браузером, в то время как код CGI- программы должен легко восприниматься разработчиком, поэтому решение о выравнивании принимается в пользу исходного кода сценария. В листинге 3.7 показан код, генерируемый сценарием guestbook.cgi, если в файле содержатся данные. Листинг 3.7. Информация, генерируемая программой guestbook.cgi при наличии данных о посетителях Content-Type: text/html
Глава 3. Простой CGI-сценарий 103 <HTML> <HEAD> <TITLE>Brent's Guestbook</TITLE> </HEAD> <B0DY BGC0L0R=white TEXT=black> <Hl>Brent?s Guestbook</Hl> <P> The following folks have registered in my guestbook. <P> <A HREF='newguest.html'>Register</A> <H2>Guests</H2> <H3><A HREF=Mhttp://www.beedub.com/">Brent Welch</Ax/H3> <IMG SRC=Hhttp://www.beedub.com/welch.gif"> </B0DYX/HTML> Использование массива Tcl в качестве базы данных В файле данных находится Tcl-команда, которая определяет массив, содержащий данные для гостевой книги. Если файл данных располагается в том же каталоге, что и сценарий guestbook. cgi, вы можете определить его имя следующим образом: set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] Используя для представления информации Tcl-команду, вы можете загружать данные с помощью команды source. Если файл данных составлен некорректно, при выполнении команды source возникает ошибка. Для отслеживания этой ситуации используется команда catch. Подробно вопросы перехвата ошибок будут рассматриваться в главе 6. catch {source $datafile} Переменная Guestbook представляет массив, определенный в файле guestbook.data. Переменные массивов рассматриваются в главе 8. Каждый элемент массива определяется с помощью Tcl-команды, которая выглядит следующим образом: set Guestbook (индекс _мас сив a) {URL элемент_разметки} В качестве индекса массива, или ключа, здесь используется имя пользователя. Значением каждого элемента массива является Tcl-сиисок, состоящий из двух элементов: URL и элемента разметки, включаемого в гостевую книгу. Рассмотрению списков посвящена глава 5. Ниже представлен пример команды, определяющей элемент массива и включающей в него данные.
104 Часть I. Основы Tcl set {Guestbook(Brent Welch)} { http://www.beedub.com/ {<img src=http://www.beedub.com/welch.gif>} } Поскольку в имени пользователя имеется пробел, для группировки слов применяются дополнительные скобки, кроме того, скобки необходимы для группировки второго параметра. Подробно вопросы формирования массивов рассматриваются в главе 8. В данном случае главным является то, что в качестве ключа используется имя пользователя, а значение представляет собой список из двух элементов. Команда array names возвращает все индексы, или ключи массива, а по команде lsort автоматически выполняется сортировка, в результате которой ключи и соответствующие им элементы лексикографически упорядочиваются. Команда foreach создает цикл для обработки сортированного списка. Переменной цикла по очереди присваивается значение каждого ключа. foreach name [lsort [array names Guestbook]] { Сортировка по команде lsort осуществляется по первому имени каждого пользователя. Сортировка может выполняться и другими способами. Так, например, вы можете рассматривать каждый ключ как список и выполнять сортировку по последнему элементу списка (т.е. по второму имени). foreach name [lsort -index end [array names Guestbook]] { Подробно команда lsort будет описана в главе 5. Команда foreach связывает переменную name с каждым ключом массива Guestbook. Значение элемента можно получить следующим образом: set item $Guestbook($name) Для извлечения элементов списка используется команда 1 index, о которой речь пойдет в главе 5. set homepage [lindex $item 0] set markup [lindex $item 1] В результате обработки каждой записи гостевой книги генерируется заголовок третьего уровня, содержащий ссылку на Web-страницу посетителя. В качестве ссылки оформляется текст, предоставленный посетителем. puts "<H3><a href=$homepage>$name</a></H3>" puts $markup В данном примере можно обойтись без переменных homepage и markup и записать код более компактно. Однако при этом код станет более трудным для восприятия. Ниже показан фрагмент программы, который выполняет те же действия, но без использования переменных.
Глава 3. Простой CGI-сценарий 105 puts "<H3><a href=[lindex $item 0]>$name</a></H3>" puts [lindex $item 1] Создание HTML-форм и обработка данных, введенных пользователем Рассмотренный выше сценарий guestbook. cgi лишь генерирует выходные данные. Однако в спецификации CGI определены также средства обработки данных, задаваемых пользователями. Эта задача несколько сложнее, чем генерация HTML-документов. Во-первых, при этом необходимо создать дополнительную Web-страницу и включить в нее форму, с помощью которых пользователь мог бы вводить информацию. Во-вторых, при передаче на сервер осуществляется кодирование данных формы и CGI-сценарий должен декодировать их. В листинге 3.8 показан код, реализующий простую форму. Процедура декодирования данных формы рассматривается в главе 11. Код процедуры приведен в листинге 11.6. На странице, отображающей гостевую книгу, содержится ссылка на файл newguest .html. На этой же странице находится форма, позволяющая пользователю задавать свое имя, URL своей страницы и указывать дополнительные элементы разметки HTML. В составе формы содержится кнопка submit. Когда пользователь щелкает на этой кнопке, информация из формы передается сценарию newguest. cgi. Этот сценарий обновляет базу данных и формирует другую страницу, которая используется для подтверждения данных, введенных пользователем. Форма newguest.html В языке HTML предусмотрены дескрипторы, с помощью которых создаются поля редактирования, кнопки, флажки опций и другие интерфейсные элементы. Поле редактирования позволяет ввести одну строку текста. Это может быть, например, URL Web-страницы пользователя. Соответствующий HTML-элемент будет выглядеть следующим образом: <INPUT TYPE=text NAME=url> С помощью дескриптора <INPUT> формируются интерфейсные элементы. Тип элемента определяет атрибут TYPE. В нашем случае значение text этого атрибута указывает на то, что элемент является полем редактирования. Кнопка submit также создается с помощью дескриптора <INPUT>, но атрибуту TYPE присваивается значение submit. Посредством атрибута VALUE задается текст, отображаемый на кнопке. <INPUT TYPE=submit NAME=submit VALUE=Register>
106 Часть I. Основы Tcl Окно для редактирования текста, состоящего из нескольких строк, создается с помощью дескриптора <TEXTAREA>. Отображаемая посредством данного элемента область позволяет но необходимости осуществлять прокрутку. Текстовую область можно использовать, например, для ввода произвольных комментариев. В нашем примере в текстовой области задается HTML- код, который будет использоваться для отображения соответствующей записи в гостевой книге. Текст между открывающим и закрывающим дескриптором <TEXTAREA> отображается в окне при обращении пользователя к HTML- документу. <TEXTAREA NAME=markup R0WS=10 C0LS=50>Hello.</TEXTAREA> Практически в каждом дескрипторе, входящем в состав формы, содержится атрибут NAME. С его помощью задается имя, идентифицирующее данные формы. Дескрипторы, которые формируют интерфейсные элементы, содержат также атрибуты, управляющие отображением. С их помощью можно задавать, например, надпись на кнопке submit или размер отображаемой текстовой области. Здесь мы не будем обсуждать детали, а ограничимся лишь общими сведениями о создании форм. HTML-код страницы, содержащей форму, приведен в листинге 3.8. Листинг 3.8. Форма newguest.html <HTML> <HEAD> <TITLE>Register in my Guestbook</TITLE> </HEAD> <B0DY BGC0L0R=white TEXT=black> <F0RM ACTION="newguest.cgi" METHOD-"POST"> <Hl>Register in my Guestbook</Hl> <UL> <LI>Name <INPUT TYPE="text" NAME="name" SIZE="40"> <LI>URL <INPUT TYPE="text" NAME="url" SIZE="40"> <P> If you don't have a home page, you can use an email URL like "mailto:welch@acm.org" <LI>Additional HTML to include after your link: <BR> <TEXTAREA NAME="html" C0LS="60" R0WS="15"> </TEXTAREA>
Глава 3. Простой CGI-сценарий 107 «LIXINPUT TYPE="submit" NAME="new" VALUE="Add me to your guestbook"> <LI><INPUT TYPE="submit" NAME="update" VALUE="Update my guestbook entry"> </UL> </F0RM> </B0DY> </HTML> Пакеты ncgi и cgi.tcl В сценарии newguest. cgi для обработки данных формы используется пакет ncgi. Это один из многих пакетов, доступных посредством стандартной Tcl-библиотеки tcllib. Если на вашем компьютере не установлена tcllib, вы можете инсталлировать ее с прилагаемого к книге компакт-диска. Данная библиотека находится также в Internet на сервере SourceForge по адресу www.sf.net/projects/tcllib и на Web-узле www.tcl.tk. Если библиотека tcllib инсталлирована, вы можете загрузить требуемый пакет с помощью команды package. package require ncgi Процедуры в пакете ncgi принадлежат пространству имен ncgi. Пространства имен будут подробно обсуждаться в главе 14. При обращении к процедуре указывается имя пространства имен, которое отделяется от имени процедуры двумя двоеточиями (::). Например, в CGI-сценариях используется стандартная процедура ncgi: :parse. Первый символ в имени ncgi означает new (новый). Первоначально для создания CGI-сценариев использовался пакет cgi.tcl. Существует также пакет cgilib.tcl, который содержит Cgi.Header и ряд других процедур. Пакеты ncgi и html в составе tcllib предоставляют большинство из тех возможностей, которые были реализованы в cgi.tcl и cgilib.tcl и соответствуют соглашениям об использовании пространства имен. Если вы хотите пользоваться средствами cgi.tcl, обратитесь по следующему адресу: http://expect.nist.gov/cgi.tcl/ Сценарий newguest.cgi Когда пользователь щелкает на кнопке submit в окне своего браузера, данные из формы передаются программе, на которую указывает значение атрибута ACTION дескриптора <F0RM>. Программа получает данные, обрабатывает их и возвращает браузеру новую Web-страницу. В нашем случае
108 Часть I. Основы Tcl в дескрипторе <F0RM> в качестве программы, предназначенной для обработки данных, указан сценарий newguest .cgi. <F0RM ACTION=newguest.cgi METH0D=P0ST> * Особенности передачи данных формы программе определяет спецификация CGI. Данные кодируются таким образом, чтобы обрабатывающая программа могла выделить информацию, заданную пользователем посредством каждого элемента формы. Для декодирования параметров хорошо подходят средства разбора регулярных выражений, реализованные в ncgi: :parse. Процедура ncgi: :parse сохраняет данные формы, a ncgi: :value передает значения элементов формы сценарию. Эти процедуры будут описаны в главе 11. Сценарий, код которого представлен в листинге 3.9, начинается с вызова процедуры ncgi: :parse. Листинг 3.9. Сценарий newguest. cgi #!/bin/sh # \ exec Tclsh "$0" ${l+"$@"} # Для обработки данных формы используется пакет ncgi из Tcllib package require ncgi ncgi::parse # Загрузка файла данных и процедур поддержки set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] source [file join $dir cgihacks.tcl] # Открытие базы данных в режиме, допускающем включение информации if {[catch {open $datafile a} out]} { Cgi.Header "Guestbook Registration Error" \ {BGC0L0R=black TEXT=red} puts "<P>Cannot open the data file<P>" puts $out;# the error message exit 0 } # Добавление Tcl-команды set, определяющей запись для посетителя
Глава 3. Простой CGI-сценарий 109 puts $out "" puts $out [list set Guestbook([ncgi::value name]) \ [list [ncgi:rvalue url] [ncgi:rvalue html]]] close $out # Формирование Web-страницы для передачи браузеру Cgi_Header "Guestbook Registration Confirmed" \ {BGC0L0R=white TEXT=black} puts " <TABLE B0RDER=1> <TR><TD>Name</TD> <TD> [ncgi::value name]</TD></TR> <TR><TD>URL</TD> <TD><A HREF=' [ncgi: :value url] ?> [ncgi ::value url] </Ax/TDx/TR> <TR><TD>Extra HTML</TD> <TD> [ncgi::value html]</TD></TR> </TABLE> к puts </B0DY></HTML> Сохранение данных с помощью Tcl-сценариев Сценарий newguest. cgi сохраняет данные в файле как Tcl-команду, определяющую элемент массива Guestbook. Это позволяет загружать данные в сценарий guestbook. cgi с помощью Tcl-команды source. Такой подход избавляет разработчика от необходимости определять формат файла и создавать код для разбора данных, представленных в этом формате. Вместо этого всю работу, связанную с разбором, эффективно выполняют стандартные средства Tcl. Сценарий открывает файл данных в режиме, позволяющем добавлять новые записи в конец файла. Работа с файлами будет подробно рассмотрена в главе 9. Для отслеживания возможных ошибок в сценарии используется команда catch. При возникновении ошибки пользователю возвращается специальная страница с описанием проблемы. Следует заметить, что операции с файлами наиболее часто становятся источником ошибок. Ошибка может возникать вследствие неавторизованного доступа к ресурсам, в случае переполнения диска, отсутствия файла и т.д. По этой причине открывать файл следует в выражении catch, как это показано ниже.
110 Часть I. Основы Tcl if {[catch {open $datafile a} out]} { # Возникновение ошибки } else { # Корректное выполнение команды open } В данном примере переменная out получает результаты выполнения команды open: дескриптор файла либо сообщение об ошибке. Подробно использование команды catch рассматривается в главе 6. Для генерации Tcl-команд рекомендуется использовать списки. В сценарии для записи информации используется Tcl-команда set. Команда list позволяет корректно форматировать данные, puts $out [list set Guestbook([ncgi:rvalue name]) \ [list [ncgi:rvalue url] [ncgirrvalue html]]] Для представления данных используются два списка. Сначала значения url и html объединяются в один список, который становится значением элемента массива. Затем в виде списка форматируется вся Tcl-команда. В упрощенном виде генерацию команды можно представить себе следующим образом: list set переменная значение Использование команды list гарантирует, что результат всегда будет представлять собой допустимую Tcl-команду, присваивающую значение переменной. Данный подход очень удобен. Если вы хотите генерировать Tcl- команды, лучше всего делать это, посредством списков. Подробно команда list рассматривается в главе 5. Обработка ошибок в CGI-сценариях Одна из проблем, возникающая при разработке CGI-программ, состоит в том, что ошибка, возникающая при выполнении сценария, приводит к отображению в окне браузера пустой страницы. В результате выяснить причину происходящего бывает достаточно сложно. Кроме того, Web-сервер может и не найти сценарий вследствие неправильной настройки. Для устранения этих проблем можно использовать два простых приема. Первый прием — включение в начало программы команды создания пустого файла. Это позволяет легко определить, вызывается ли сценарий. Если сценарий предназначен для выполнения в системе Unix, вы можете включить в начало его кода следующую строку: close [open /tmp/my_cgi_script_ran w]
Глава 3. Простой CGI-сценарий 111 Когда браузер обратится к CGI-сценарию. тот должен как минимум создать пустой файл. Если файл отсутствует, это значит, что либо Web-сервер не может найти сценарий, либо отсутствует интерпретатор tclsh, необходимый для выполнения Tcl-программы. В этом случае надо еще раз проверить настройки сервера и путь, который указан в первой строке сценария, начинающейся с символов #!. В системе Windows рекомендуется использовать Web-сервер TclHttpd, содержащий встроенные средства запуска CGI-сцена- риев, написанных на языке Tcl. TclHttpd предоставляет также дополнительные возможности генерации Web-страниц. Если ваш сценарий после модификации кода перестал работать, это значит, что вы допустили ошибку в тексте программы. Чтобы выяснить причину ошибки, полезно поместить код сценария в выражение catch и выводить информацию о возникающих ошибках. В этом случае сведения об ошибках будут отображаться браузером. В листинге 3.10 показан код сценария newguest .cgi, переписанный так, что все команды находятся в составе команды catch. В конце листинга предусмотрены вывод сообщения и отображение переменной error Info при возникновении ошибки. Листинг 3.10. Сценарий newguest. cgi, в который добавлены средства обработки ошибок #!/bin/sh # \ exec tclsh "$0" ${1+"$<Э"} # Обработка всех ошибок if {[catch { # Для обработки данных используется пакет ncgi из tcllib package require ncgi ncgi::parse # Загрузка файла данных и процедур поддержки set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] source [file join $dir cgihacks.tcl] # Открытие базы данных в режиме, допускающем включение информации
112 Часть I. Основы Tcl set out [open $datafile a] # Добавление Tcl-команды set, определяющей запись для посетителя puts $out "" puts $out [list set Guestbook([ncgi::value name]) \ [list [ncgi:rvalue url] [ncgi:rvalue html]]] close $out # Формирование Web-страницы для передачи браузеру Cgi_Header "Guestbook Registration Confirmed" \ {BGC0L0R=white TEXT=black} puts " <TABLE B0RDER=1> <TRXTD>Name</TD> <TD>[ncgi::value name]</TD></TR> <TRXTD>URL</TD> <TD><A HREF=>[ncgi::value url]>>[ncgi::value url]</A></TD></TR> <TRXTD>Extra HTML</TD> <TD>[ncgi::value html]</TD></TR> </TABLE> </B0DY></HTML> к # Окончание основного сценария } err]} { # При возникновении ошибки отображается Web-страница puts "Content-Type: text/plain" puts "" puts "CGI error occurred in [info script]" puts $errorInfo }
Глава 3. Простой CGI-сценарий 113 Усовершенствование сценария Сценарий, рассмотренный в данной главе, желательно дополнить рядом новых возможностей. Некоторые пользователи захотят изменить свои записи. Сценарий, в его настоящем виде, позволяет сделать это, но пользователю придется повторно ввести всю информацию. Стоит также сообщить пользователю о результате регистрации и предоставить ему возможность скорректировать данные перед их записью в файл. Для этого потребуется новая Web- страница, которая отображала бы запись текущего пользователя так, как она будет выглядеть на основной странице, и содержала бы интерфейсные элементы, позволяющие изменить данные. Каждый Web-сервер по-своему настраивается для взаимодействия с CGI- сценарием. Информацию об особенностях используемого сервера вы можете получить у администратора Web-узла. В Tcl Web Server сценарий, реализующий гостевую книгу, уже установлен, кроме того, в его состав входят различные средства генерации Web-страниц. Многие разработчики, в том числе и авторы данной книги, предпочитают использовать шаблоны, поддерживаемые Tcl Web Server. Подробно этот вопрос будет рассмотрен в главе 18. В следующих главах описываются основные команды Tcl и структуры данных. К CGI-сценарию мы вернемся в главе 11 при рассмотрении регулярных выражений.
Глава 4 Обработка строк В данной главе описываются обработка строк и простые средства сравнения с шаблонами. В частности, рассматриваются команды string, append, format, scan и binary. Команда string реализует набор часто используемых операций со строками. V^ТРОКИ являются основным типом данных в Tcl, поэтому не удивительно, что в этом языке для их обработки предусмотрено большое количество команд. С обработкой строк непосредственно связаны операции сравнения с шаблонами, которые можно рассматривать как усложненные процедуры сравнения строк. Подобные средства имеются и в других языках сценариев. Сравнение с шаблонами с использованием регулярных выражений рассматривается в главе 11. Команда string Команда string реализует набор операций со строками. Например, приведенный ниже фрагмент кода определяет длину строки, содержащейся в переменной. set name "Brent Welch'1 string length $name => 11 Первый параметр команды string указывает, какая операция должна выполняться над строковым значением. Для того чтобы отобразить сведения о всех операциях, надо при вызове string задать недопустимый первый параметр. string junk
Глава 4. Обработка строк 115 => bad option "junk": should be byTclength, compare, equal, first, index, is, last, length, map, match, range, repeat, replace, tolower, totitle, toupper, trim, trim-' left, trimright, wordend, or wordstart Данный прием можно применять не только при работе с командой string. Задавая неверный параметр, можно получить сведения об использовании многих других команд. Операции, реализуемые посредством команды string, описаны в табл. 4.1. Таблица 4.1. Операции, реализуемые с помощью команды string Возвращает число байтов, необходимое для хранения строки. Это значение может отличаться от длины строки из-за особенностей формата UTF-8 (Unicode и UTF-8 рассматриваются в главе 15) Сравнивает строки по лексикографическому критерию. Опция -nocase задает сравнение без учета регистра символов. Опция -length ограничивает сравнение указанным числом символов. В случае равенства строк возвращается значение 0. Если первая строка лексикографически предшествует второй, возвращается значение -1, в противном случае — значение 1 Выполняет сравнение строк и возвращает значение 1, если они совпадают. Опция -nocase задает сравнение без учета регистра символов Возвращает индекс первого вхождения подстроки в строку. Если подстрока не найдена, возвращается значение - 1. Чтобы поиск осуществлялся не с начала строки, надо задать начальный индекс Возвращает символ, находящийся в указанной позиции. Индекс отсчитывается начиная с нуля. Для указания последнего символа в строке используется индекс end Если строка принадлежит классу, возвращается значение 1. Если задана опция -strict, пустая строка считается не принадлежащей классу, в противном случае пустая строка считается принадлежащей классу. Если задана опция -fail index, то указанной переменной присваивается индекс символа, который исключает принадлежность строки классу. Классы символов описаны в табл. 4.3 string byTclength строка string compare ?-nocase? ?-length число_символов? строка_1 строка_2 string equal ?-nocase? строка_1 строка_2 string first подстрока строка? начальный_индекс? string index строка индекс string is класс ?-strict? ?-failindex имя_ переменной ? строка
116 Часть I. Основы Tcl Продолжение табл. 4.1 string last подстрока строка ? начальный_индекс? string length строка string map ?-nocase? карга_ символов строка string match ?-nocase? шаблон строка string range строка первый_индекс в торой_индекс string repeat сгроха счетчик string replace строка первый_индеке втор ой_индеке ?новая_строка? string tolower строка ?'первый_индеке ? 1 второй_индеке? string totitle строка 1 первый_индеке? ?второй_индекс? string toupper строка ? первый_индекс? ?второй_индекс? string trim строка ?символы? Возвращает индекс последнего вхождения подстроки в строку. Если подстрока не найдена, возвращается значение -1. Чтобы поиск осуществлялся с середины строки, надо задать начальный индекс Возвращает число символов в строке Возвращает новую строку, сформированную путем преобразования по карте символов (карта символов будет обсуждаться далее в этой главе) Возвращает значение 1, если строка соответствует шаблону; в противном случае возвращается 0. Сравнение производится способом, типичным для команды glob Возвращает символы, расположенные в строке от первого до второго индекса Возвращает строку, повторенную указанное число раз Возвращает новую строку, в которой символы от первого до второго индекса либо удалены, либо заменены новой строкой Преобразует символы, содержащиеся в строке, в нижний регистр. Первый и второй индексы определяют диапазон, в котором должно выполняться преобразование Преобразует строку следующим образом. Первый символ преобразуется в верхний регистр или в регистр заголовка Unicode. Остальные символы преобразуются в нижний регистр. Первый и второй индексы задают диапазон, в котором осуществляется преобразование Преобразует символы, содержащиеся в строке, в верхний регистр. Первый и второй индексы определяют диапазон, в котором должно выполняться преобразование Если указанные символы присутствуют в начале или в конце строки, они удаляются. По умолчанию удаляются пробелы
Глава 4. Обработка строк 117 Окончание табл. 4.1 string trimlef t Если указанные символы присутствуют в начало строки, строка ?символы? они удаляются. По умолчанию удаляются пробелы string trimright Если указанные символы присутствуют в конце строки. строка ?символы? они удаляются. По умолчанию удаляются пробелы string wordend Возвращает индекс символа, следующего после слова, ко- строка ix то рое содержит символ с индексом ix string wordstart Возвращает индекс первого символа в слове, которое со- строка ix держит символ с индексом ix Ниже перечислены наиболее часто используемые операции. • Операция проверки совпадения строк — equal (она будет использована в листинге 4.2). • Операция проверки соответствия — match (она будет описана далее в этой главе). • Операции преобразования регистров символов tolower, totitle и toupper. • Операции удаления лишних пробелов (а но необходимости и других символов) — trim, trimright и trimleft. Операции, приведенные ниже, поддерживаются начиная с версии Tcl 8.1 (точнее, они впервые появились в реализации 8.1.1). • equal. Пользоваться данной операцией проще, чем string compare. • is. Эта операция проверяет тип строки. Классы строк перечислены в табл. 4.3. • тар. Преобразует символы, т.е. действует подобно команде tr системы Unix. • repeat и replace. • totitle. С помощью данной операции удобно преобразовывать регистр символов. Индексы Для выполнения некоторых операций со строками надо знать индексы символов, т.е. позиции в строке. В Tcl символы в строке нумеруются начиная с нуля. Для указания последнего символа используется специальный индекс с именем end. string range abed 2 end => cd
118 Часть I. Основы Tcl В Tcl 8.1 были предусмотрены средства указания индекса относительно конца строки. Для того чтобы получить положение символа, отстоящего на N позиций от конца строки, можно использовать выражение end-ДО. Например, приведенная ниже команда возвращает новую строку, которая получается из исходной путем отбрасывания первого и последнего символов. string range $string 1 end-1 При извлечении подстроки часто применяются операции first, last, wordstart, wordend, index и range. Если вы собираетесь использовать их, помните, что те же действия можно выполнить более эффективно с помощью регулярных выражений. Регулярные выражения будут детально рассматриваться в главе 11. Строки в составе выражений Выражения, содержащие строки, могут использоваться в командах expr, if и while. Для сравнения строк часто применяются операторы eq, ne, ==, !=, < и >. Однако, чтобы это стало возможным, надо соблюдать ряд правил, в противном случае при выполнении команд будут возникать проблемы. В первую очередь необходимо помещать строковое выражение в кавычки, чтобы при разборе последовательность символов была идентифицирована как строка. Для группировки выражений следует использовать фигурные скобки, чтобы кавычки корректно обрабатывались интерпретатором. if {$x == "foo"} команда В команде expr сравнение строк корректно выполняется только при использовании оператора eq или пе. Несмотря па кавычки, операторы, которые могут быть применены как к строковым, так и к числовым значениям, стараются по возможности преобразовывать строки в число. Если впоследствии выясняется, что строка, преобразованная в число, должна участвовать в операции сравнения строк, выполняется обратное преобразование. При преобразовании в строку числовое значение всегда представляется в десятичном формате. Если строка выглядит как шестна- дцатеричное или восьмеричное число, сравнение с другими строками может быть некорректным. Например, в результате выполнения следующего выражения возвращается значение true: if {"Oxa-= "10"} { puts stdout ack! } => ack! Для сравнения строк предпочтительнее использовать команды string compare и string equal. В Tcl 8.4 были введены операции eq и пе expr, предназначенные для сравнения строк. Они обеспечивают высокую эффек-
Глава 4. Обработка строк 119 тивность работы, так как при их использовании исключается ненужное преобразование. Подобно функции языка С strcmp, команда string compare возвращает 0, если строки совпадают, —1 — если первая строка лексикографически предшествует второй, и 1 — если первая строка лексикографически следует за второй. Листинг 4.1. Сравнение строк с помощью команды string compare if {[string compare $sl $s2] == 0} { # Строки совпадают 2 Еще проще использовать команду string equal, которая была реализована в Tcl 8.1. Листинг 4.2. Сравнение строк с помощью команды string equal if {[string equal $sl $s2]} { # Строки совпадают 2 Оператор eq, введенный в Tcl 8.4, выполняет те же действия, но записывается более компактно. Помимо того, он предотвращает нежелательные преобразования формата. Кроме оператора eq, существует также противоположный ему оператор пе. Листинг 4.3. Сравнение строк с помощью оператора eq if {$sl eq $s2} { # Строки совпадают > Сравнение строк с шаблонами Команда string match поддерживает сравнение с шаблоном, подобное тому, которое реализуется командой glob. Такое сравнение позволяет при работе в Unix и других операционных системах определять, соответствует ли имя файла шаблону, указанному в групповой операции. Команда glob описывается в главе 9. В табл. 4.2 показаны три синтаксические конструкции, используемые при сравнении с шаблонами. Любые другие символы в составе шаблона интерпретируются как литералы. В следующем примере указан шаблон, которому соответствуют все строки, начинающиеся с символа а:
120 Часть I. Основы Tcl Таблица 4.2. Специальные символы, используемые при выполнении операции string match * Помечает произвольное число символов ? Помечает один символ [символы] Помечает один символ из указанного набора string match a* alpha => 1 Шаблон, которому соответствуют лишь строки, состоящие из двух символов, приведен ниже. string match ?? XY => 1 Следующее выражение определяет строки, начинающиеся либо с символа а, либо с символа Ъ: string match {[ab]*} cello => О Будьте осторожны! Квадратные скобки имеют в языке Tcl специальное назначение, поэтому шаблон, в котором используются квадратные скобки, надо помещать в фигурные скобки, иначе выражение в квадратных скобках будет интерпретировано как вложенная команда. В качестве шаблона также можно использовать значение переменной. set pat { [ab]*x} string match $pat box => 1 Все символы в определенном диапазоне можно представить с помощью выражения [х-у]. Например, шаблону [a-z] соответствуют все буквы нижнего регистра, а выражение [0-9] определяет все цифры. В одном наборе может быть указано несколько диапазонов. В следующем выражении содержится шаблон, определяющий любую латинскую букву, цифру или знак подчеркивания: string match {[a-zA-Z0-9_]} $char Выражение в квадратных скобках определяет лишь один символ. Чтобы описать более сложный шаблон, например строку, содержащую один или несколько символов из набора, надо воспользоваться регулярными выражениями (они будут рассматриваться в главе 11). Если вам надо указать литеральное значение символа *, ? или одной из скобок, перед ними надо указать обратную косую черту.
Глава 4. Обработка строк 121 string match {*\?} what? => 1 Шаблон, в котором используется обратная косая черта, должен быть помещен в фигурные скобки, в противном случае интерпретатор Tcl выполнит подстановку. Если вы не хотите использовать скобки, вместо одной надо указать две обратные косые черты. Перед сравнением с шаблоном Tcl заменит две обратные косые черты одной. string match *\\? what? Классы символов Команда string is позволяет выяснить, принадлежит ли строка определенному классу. Таким образом могут проверяться данные, введенные пользователем. Например, чтобы удостовериться в том, что введенное значение является числом, можно использовать следующую команду: if {! [string is integer -strict $input]} { error "Invalid input. Please enter a number." } Классы определяются как наборы символов Unicode, поэтому они более содержательны, чем диапазоны значений ASCII-кодов. Например, класс alpha включает символы национальных кодировок, не попадающие в диапазон [A-Za-z]. Классы символов описаны в табл. 4.3. Таблица 4.3. Имена классов символов alnum Любая буква или цифра alpha Любая буква ascii Любой символ, код которого может быть представлен семью битами (т.е. код символа должен быть меньше 128) boolean Логическое значение, поддерживаемое в Tcl: 0, 1, true, false (регистр символов может быть любым) control Символ, код которого меньше 32, но отличный от символа NULL digit Любая цифра double Допустимое число с плавающей точкой false Допустимое логическое значение, обозначающее в языке Tcl "ложь"': О или false (регистр символов может быть любым) graph Любые печатные символы за исключением пробела integer Допустимое целое число lower Строка, представленная символами нижнего регистра
122 Часть I. Основы Tcl Окончание табл. 4.3 print Синоним alnum punct Любой знак пунктуации space Пробел, знак табуляции, перевод строки, возврат каретки, вертикальная табуляция, символ возврата true Допустимое логическое значение, обозначающее в языке Tcl "истина": 1 или true (регистр символов может быть любым) upper Строка, представленная символами верхнего регистра wordchar Буквы, цифры и знак подчеркивания xdigit Шестнадцатеричные цифры Преобразование с помощью карты символов С помощью команды string map выполняется преобразование строки с использованием карты символов. Карта символов представляет собой разновидность списка, содержащего входные и выходные данные. Если в строке содержатся символы, объявленные как входные, они заменяются соответствующими выходными символами. Пример подобного преобразования приведен ниже. string map {f p d 1} food => pool Входные и выходные данные не обязательно должны состоять из одного символа, кроме того, входная и выходная последовательности, принадлежащие одной паре, не обязательно должны быть одинаковой длины. string map {f p d 11 оо u} food => pull В листинге 4.4 приведен фрагмент кода, который может быть применен па практике для обработки текста. В данном примере производится замена кавычек, формируемых приложением Microsoft Word, на обычные прямые кавычки ASCII. Здесь используются команды open, read и close, предназначенные для работы с файлами (эти команды будут описаны в главе 9), и команда fconfigure, которая будет рассмотрена в главе 16.
Глава 4. Обработка строк 123 Листинг 4.4. Преобразование специальных символов, генерируемых Microsoft Word, в ASCII-символы proc Dos2Unix {filename} { set input [бреп $filename] set output [open $filename.new] fconfigure $output -translation If puts $output [string map { \223 " \224 " \222 > \226 - } [read $input]] close $input close $output } Команда append Команда append в качестве первого параметра получает переменную и добавляет к ней значения остальных параметров. Если на момент вызова команды переменная отсутствует, она создается. set foo z append foo a b с set foo => zabc Команда append эффективно работает с длинными строками. Данная команда непосредственно модифицирует переменные, поэтому она может использовать особенности распределения памяти в Tcl. Команда append записывается следующим образом: append х "дополнительные данные" Данная команда выполняется быстрее, чем следующая: set x "$x дополнительные данные" Команда lappend, которая будет рассматриваться в главе 5, обеспечивает такой же выигрыш в производительности при работе со списками Tcl. Команда format Команда format выполняет те же функции, что и команда printf в языке С. Она форматирует строку в соответствии со спецификацией формата.
124 Часть I. Основы Тс! format спецификация значение_1 значение_2 ... Параметр, с помощью которого задается спецификация, содержит литералы и ключевые слова. Литералы копируются в форматируемую строку, а ключевые слова управляют форматированием соответствующих параметров. Ключевое слово начинается с символа % за которым следует любое (в том числе нулевое) число модификаторов, и завершается спецификатором преобразования. В спецификаторе может содержаться до шести следующих компонентов. • Спецификатор позиции. • Флаги. • Ширина поля. • Точность. • Длина слова. • Символ преобразования. В качестве примеров ключевых слов можно привести следующие: °/0f — для чисел с плавающей точкой, °/0d — для целых чисел и °/0s — для строк. Если в состав литерала надо включить символ °/0, его надо повторить дважды. В примерах, приведенных в данном разделе, спецификация формата помещается в двойные кавычки. Это необходимо потому, что в составе строк, определяющих формат, содержатся пробелы. С другой стороны, в тех же строках находятся символы, формируемые с помощью обратной косой черты, что предполагает подстановку. Таким образом, альтернатива двойным кавычкам в данном случае отсутствует. В табл. 4.4 приведены символы преобразования. Таблица 4.4. Символы, используемые для преобразования формата d Целое число со знаком и Беззнаковое целое i Целое число со знаком. Параметром может быть шестнадцатеричный (Ох) или восьмеричный (0) формат о Беззнаковое восьмеричное число х или X Беззнаковое шестнадцатеричное число. Символ 'х' указывает на то, что результат должен быть представлен в нижнем регистре с Отображение целого числа в соответствующий символ ASCII s Строка. f Число с плавающей точкой в формате а.Ъ
Глава 4. Обработка строк 125 Окончание табл. 4.4 е или Е Число с плавающей точкой в формате, принятом для научных исследований, т.е. а.ЬЕ+-с g или G Число с плавающей точкой. В качестве формата выбирается либо °/0f, либо У0е, в зависимости от того, какой из них дает более короткое представление Спецификатор позиции записывается в формате 1$. Это означает, что вместо соответствующего параметра должно использоваться значение параметра i. Отсчет позиций начинается с единицы. Указав позицию для одного ключевого слова, надо указать позицию для всех слов. Если для группировки спецификации формата используются двойные кавычки, символ $ должен предваряться обратной косой чертой. set lang 2 format "°/0${lang}\$s" one un uno => ил Спецификатор позиции удобно применять для выбора строки из набора, как это делается в приведенном выше примере. Данную задачу также можно решить с помощью каталога сообщений. Механизм каталога сообщений будет описан в главе 15. Спецификатор позиции также применяется в том случае, если одно и то же значение должно несколько раз включаться в форматируемую строку. Флаги используются для управления заполнением и выравниванием. В приведенном ниже примере флаг # указывает на то, что перед шестна- дцатеричным значением должны выводиться символы Ох. Число 0 в последовательности 08 означает, что поле должно быть заполнено нулями. format '7o#x" 20 => 0x14 format '7o#08xM 10 => 0x0000000а Назначение символов, используемых в качестве флагов, описано в табл. 4.5. После флагов указывается минимальная длина поля. Если длина поля больше числа символов, лишние позиции поля заполняются пробелами. При наличии флага 0 в качестве заполнителя используется символ 0. format '7o-20s °/03dn Label 2 => Label 2
126 Часть I. Основы Tcl Таблица 4.5. Флаги форматирования Выравнивание по левому краю поля + Всегда отображать знак (4- или —) пробел Всегда предварять число пробелом, даже если оно содержит знак. Это удобно при последовательном выводе набора значений О Заполнение нулями # Начинать восьмеричные цифры с нуля, а шестнадцатеричные — с последовательности Ох. В состав числа с плавающей запятой всегда включать десятичную точку. Не удалять ведущие нули (°/0g) Если вы хотите, чтобы ширина поля вычислялась в программе и передавалась команде format как параметр, то в качестве спецификатора ширины поля надо использовать символ *. В приведенном ниже примере первый параметр определяет длину поля, а команда format форматирует последующие параметры. set maxl 8 format "°/o-*s = °/0s" $maxl Key Value => Key =Value Далее в ключевом слове определяется точность, которая задается посредством числа с десятичной точкой. Для °/0f и °/0е спецификатор точности задает, сколько цифр должно следовать за десятичной точкой. Для °/0g спецификатор указывает общее количество значащих цифр. Для °/0d и °/0х определяется, сколько цифр должно быть выведено (по мере необходимости лишние позиции заполняются нулями). format '7.6.2f °/06.2d" 1 1 => 1.00 01 Последним в ключевом слове указывается длина. Эта информация имеет смысл только в версии Tcl 8.4, в которой была реализована поддержка расширенных целых чисел. Значения с плавающей точкой Tcl интерпретирует как числа с двойной точностью, а целые — как длинные целые числа. Расширенные целые содержат как минимум 64 бита. Добавляя символ 1 (long) к спецификатору длины, можно продемонстрировать различие между обычными и расширенными целыми. format °/0u -I => 4294967295 format °/olu -1 => 18446744073709551615
Глава 4. Обработка строк 127 Команда scan Команда scan выполняет разбор строки в соответствии с описанием формата и присваивает значения переменным. Она возвращает число успешно выполненных преобразований. Если ни одна переменная не указана, команда возвращает список, содержащий результаты сканирования. Для обращения к команде scan применяется следующее выражение: scan строка формат ?'переменная? ?переменная? ?переменная? . . . Описание формата для команды scan составляется практически так же, как и для команды format. Например, посредством формата °/0с код единичного символа преобразуется в десятичное представление. С помощью описания формата команды scan можно задавать наборы символов. Для формирования набора используются квадратные скобки. В составе набора указывается один или несколько символов, которые должны быть скопированы в переменную. Диапазон символов задается через дефис. Ниже приведен пример команды scan, которая присваивает переменной символы нижнего регистра, находящиеся в составе исходной строки. scan abcABC {c/0[a-z]} result => 1 set result => abc Если первым символом набора является закрывающая квадратная скобка, она рассматривается как часть набора. Если набор начинается с символа ~, это означает, что формату строки соответствуют только символы, не принадлежащие указанному набору. Если в наборе должна присутствовать закрывающая квадратная скобка, ее надо поместить непосредственно после символа ~. Для того чтобы открывающая квадратная скобка оказалась частью набора, никаких специальных мер принимать не надо. Поскольку квадратные скобки являются специальными символами, вы, возможно, захотите использовать при указании формата фигурные скобки или указать перед квадратной скобкой обратную косую черту. Это вполне допустимо. Команда binary В Tcl 8.0 была реализована поддержка двоичных строк. В предыдущих версиях Tcl строки завершались нулевыми символами, что приводило к ошибкам в работе с некоторыми типами данных. Начиная с Tcl 8.0 число символов в строке задается с помощью счетчика, и наличие нулевого байта не приводит к усечению строки.
128 Часть I. Основы Tcl В данном разделе описывается команда binary, которая обеспечивает преобразование строк в двоичные данные. Команда binary format упаковывает переданные ей значения в соответствии с шаблонами. Так. например, с помощью данной команды можно сформировать вектор значений с плавающей точкой так. что его можно будет передать программе, написанной на языке Fortran. В результате выполнения команда binary format возвращает сформированное двоичное значение. binary format шаблон значение ?значение ...? Команда binary scan извлекает значения из двоичной строки; при этом используется аналогичный шаблон. Например, эта команда может быть применена для обработки данных, хранящихся в двоичном файле. Полученные значения присваиваются Тс1-переменным. binary scan значение шаблон значение ?значение ...? Шаблоны форматирования Шаблон форматирования состоит из ключей, определяющих тип, и счетчиков. В зависимости от указанного типа, значение счетчика интерпретируется по-разному. Для целочисленного типа (i) или типа с двойной точностью счетчик задает количество повторений (например, i3 означает три целых числа). Для строк счетчик определяет длину строки (например, аЗ соответствует строке из трех символов). Если счетчик не указан, то по умолчанию принимается значение, равное 1. Если в качестве счетчика задан символ *, команда binary scan использует все оставшиеся байты в составе значения. В шаблоне может быть задано несколько ключей типов. Каждое сочетание ключ-счетчик перемещает условный курсор но набору двоичных данных. Существуют специальные ключи типов, предназначенные для перемещения курсора. Ключ х генерирует нулевое число байтов в binary format, а при выполнении команды binary scan соответствующие байты пропускаются. Ключ @ указывает на то, что используемый в сочетании с ним счетчик определяет абсолютное смещение курсора. Сочетание символов @* вызывает перемещение в конец набора данных. Ключ X вызывает перемещение курсора назад на число байтов, определяемое счетчиком. Допустимые типы описаны в табл. 4.6. В таблице имя count означает значение счетчика, следующего за символом тина. При хранении числовых значений в памяти байты могут располагаться по-разному. Если первыми располагаются младшие байты, тип задается символом нижнего регистра, если же старшие байты предшествуют младшим, для указания тина используется символ верхнего регистра (такое расположение байтов используется, например, на платформах SPARC или Motorola).
Глава 4. Обработка строк 129 Целочисленные значения могут быть 16-битовыми (s или S) либо 32-битовыми (i или I). Кроме того, в Tcl 8.4 реализована поддержка 64-битовых целых чисел (w или W). Заметьте, что при обмене данными по сети принято вначале передавать старшие байты. Значения с плавающей точкой на разных платформах поддерживаются по-разному, поэтому желательно выводить и читать данные на одинаковых машинах. Таблица 4.6. Типы, используемые при двоичном преобразовании а Символьная строка длиной count. В команде binary format осуществляется заполнение нулями А Символьная строка длиной count. В команде binary format осуществляется заполнение пробелами. Ведущие нули и пробелы игнорируются командой binary scan b Двоичная строка длиной count. Порядок следования — от младшего к старшему В Двоичная строка длиной count. Порядок следования — от старшего к младшему h Строка шестнадцатеричных цифр длиной count. Порядок следования — от младшей к старшей Н Строка шестнадцатеричных цифр длиной count. Порядок следования — от старшей к младшей. (Данный тип используется значительно чаще, чем п.) с 8-битовый символьный код. Значение count определяет число повторений s 16-битовое целое число. Байты следуют от младшего к старшему (little- endian). Значение count определяет число повторений S 16-битовое целое число. Байты следуют от старшего к младшему (big-endian). Значение count определяет число повторений i 32-битовое целое число. Байты следуют от младшего к старшему (little- endian). Значение count определяет число повторений I 32-битовое целое число. Байты следуют от старшего к младшему (big-endian). Значение count определяет число повторений f Число с плавающей точкой единичной точности в формате, используемом на конкретной платформе. Значение count определяет число повторений d Число с плавающей точкой двойной точности в формате, используемом на конкретной платформе. Значение count определяет число повторений w 64-битовое целое число. Байты следуют от младшего к старшему (little- endian). Значение count определяет число повторений (Tcl 8.4) W 64-битовое целое число. Байты следуют от старшего к младшему (big-endian). Значение count определяет число повторений (Tcl 8.4)
130 Часть I. Основы Tcl Окончание табл. 4.6 х Для команды binary format — включение count нулевых байтов. Для команды binary scan — пропуск числа байтов, определяемого значением count X Сохранение числа байтов, определяемого значением count Ф Переход к абсолютной позиции, определяемой значением count. Если в качестве счетчика используется значение *, осуществляется переход в конечную позицию Существуют три типа строк: символьные (а или А), двоичные (b или В) и щестнадцатеричные (h или Н). При использовании указанных ключей счетчик задает длину строки. Для типа а значение дополняется до указанной длины нулевыми байтами, а для типа А — пробелами. Если длина строки больше, чем значение счетчика, строка усекается. При обработке типа А с помощью команды binary scan завершающие пробелы удаляются. Двоичная строка состоит из нулей и единиц. Тип b задает порядок размещения, при котором младшие биты предшествуют старшим. Для типа В порядок следования битов обратный. В шестнадцатеричной строке каждый символ представляется четырьмя битами. Если указан тип h, младшие символы предшествуют старшим; для типа Н порядок следования символов обратный. Ключи В и Н задают формат, в котором обычно записываются числа. Примеры Используя команды binary format и binary scan, не забывайте, что Tcl по умолчанию интерпретирует данные как строки. Например, символ 6 представляется кодом 54 или 0x36. Этот код символа вернет команда binary scan в случае, если указан тип с. set input б binary scan $input "c" 6val set 6val => 54 С помощью команды binary scan можно присвоить переменной коды нескольких символов. binary scan abc "сЗ" list => 1 set list => 97 98 99 В приведенном выше примере используется один ключ, определяющий тип, поэтому команда binary scan присваивает значение одной переменной
Глава 4. Обработка строк 131 Tcl. Если вам необходимо, чтобы код каждого символа был помещен в отдельную переменную, следует использовать несколько ключей. binary scan abc "ccc" x у z => 3 set z => 99 Для получения шестнадцатеричных значений применяется формат Н. binary scan 6 "Н2" 6val set 6val => 36 Для работы с нолями фиксированной длины используются форматы а и А. Если в качестве счетчика указан символ *, это означает, что переменной должна быть присвоена оставшаяся часть строки. Заметьте, что формат А предполагает удаление завершающих пробелов. binary scan "hello world " a3x2A* first second puts "\"$first\" \"$second\un => "hel" " world" Ключ @ позволяет поместить в переменную значение смещения. В приведенном ниже примере из вектора извлекается второе число двойной точности. Вектор может быть прочитай из двоичного файла данных. binary scan $vector "@8d" double При работе с командой binary format форматы а и А применяются для создания полей фиксированной длины. В случае необходимости поле дополняется пробелами. Если длина строки слишком велика, строка усекается. binary format "A9A3" hello world => hello wor Массив чисел с плавающей точкой может быть создан с помощью следующей команды: binary format "f*" 1.2 3.45 7.43 -45.67 1.03е4 Заметьте, что числа с плавающей точкой на различных машинах хранятся в разных форматах, поэтому читать значения с плавающей точкой надо на той же платформе, на которой они создавались. При хранении целочисленных значений может использоваться различный порядок следования байтов. Порядок следования, принятый на той платформе, на которой выполняется команда, можно выяснить, обратившись к переменной tcl_platform (она будет описана в главе 13).
132 Часть I. Основы Tcl Двоичные данные и обмен с файлами При работе с файлами, содержащими двоичные данные, необходимо отказаться от преобразования символа перевода строки и использования кодировок. Подробно эти вопросы описываются в главах 9 и 15. Например, если вы генерируете двоичные данные, следующая команда позволит переключить стандартный вывод в двоичный режим: fconfigure stdout -translation binary -encoding binary puts [binary format "B8" 11001010] Источники дополнительных сведений • Чтобы лучше понять вопросы обработки данных в Tcl, рекомендуется ознакомиться со списками (глава 5) и массивами (глава 8). • Вопросы сравнения с шаблонами будут обсуждаться в главе 11 при рассмотрении регулярных выражений. • Дополнительная информация об обмене данными с файлами содержится в главе 9. • Формат Unicode и вопросы интернационализации рассматриваются в главе 15.
Глава 5 Списки В данной главе рассматриваются списки Tcl, а также описываются команды list, lindex, llength, lrange, lappend, linsert, lreplace, lsearch, lset, lsort, concat, join и split. \J ПИСКИ в Tcl имеют ту же структуру, что и команды. Все правила группировки параметров, которые рассматривались в главе 1, применяются и при создании Tcl-списков. Работая с Tcl-списками, удобно мыслить в терминах операций. В языке Tcl предусмотрены команды, позволяющие помещать значения в список, извлекать элементы списков, определять количество элементов, выполнять замену и т.д. Вместо того чтобы создавать списки вручную, вы можете применять команды типа list и lappend. При вызове некоторых команд, например f oreach, списки передаются в качестве параметров. Списки часто применяются для создания команд, выполнение которых должно быть отложено на более позднее время. Для выполнения таких команд используется механизм eval, описанный в главе 10. Списки также находят применение при поддержке Tk-команд обратного вызова, которые рассматриваются в главе 30. Следует помнить, что Tcl-списки не всегда являются наилучшим средством создания сложных структур данных. В некоторых случаях гораздо удобнее пользоваться массивами, речь о которых пойдет в главе 8. Операции со списками, как правило, плохо подходят для поддержки неструктурированной информации, например данных, вводимых пользователем. Для обработки таких данных предпочтительнее использовать регулярные выражения, рассмотрению которых посвящена глава 11.
134 Часть I. Основы Tcl Списки в языке Tcl Tcl-список представляет собой последовательный набор некоторых значений. При создании списков используются те же синтаксические правила, что и при работе с Tcl-командами. Элементы списка разделяются пробелами. Для группировки нескольких слов в один элемент списка могут применяться фигурные скобки или двойные кавычки. Для создания списков и выполнения операций с ними используются специальные команды, которые будут описаны в этой главе. Начиная с версии Tcl 8.0 списки представляют собой одномерные массивы объектов. В ранних версиях Tcl все значения представлялись как строки. Для группировки элементов применялись специальные синтаксические конструкции. При каждом обращении к списку производился разбор представляющей его строки. В случае больших списков такой подход существенно снижал производительность программы. Эффективность работы со списками была значительно увеличена при появлении в версии Tcl 8.0 Tcl-компилятора. В настоящее время для хранения списков применяются массивы указателей. (Структура Tcl_0bj подробно рассматривается в главе 47.) Для доступа к любому элементу списка выполняется одинаковый объем вычислений. Для того чтобы операция добавления нового элемента была более эффективной, при формировании массива, представляющего список, резервируется дополнительное пространство. Внутренний формат, используемый для хранения списка, предусматривает запись информации о количестве элементов списка, поэтому при определении длины списка практически не расходуются ресурсы центрального процессора. Следует, однако, помнить, что при преобразовании длинного Tcl-списка в строку (это может понадобиться, например, для вывода данных) не исключены проблемы с производительностью программы. Преобразование списка в строку может понадобиться при выводе данных в файл или при передаче содержимого списка командам, предназначенным для работы со строками. В языке Tcl предусмотрен ряд команд для работы со списками (табл. 5.1).
Глава 5. Списки 135 Таблица 5.1. Команды для работы со списками list параметр_1 Формирует список из заданных параметров параметр.2 ... lindex список ?i ...? Возвращает i-й элемент списка. Задавая несколько индексов, можно получить доступ к вложенным спискам llength список Возвращает число элементов в списке lrange список i j Возвращает элементы, занимающие от i-й по j-io позицию в списке lappend Добавляет элементы к значению переменной списка переменная, списка параметр ... 1 insert список индекс Включает элементы в список. Они располагаются пе- парамегр параметр . . . ред элементом, заданным посредством индекса. Возвращает новый список lreplace список i j Заменяет элементы, занимающие от i-й по j-ю пози- параметр параметр . . . цию в списке, указанными параметрами. Возвращает новый список Isearch ?опции? список Возвращает индекс элемента, который выдерживает значение сравнение с указанным значением. Сравнение проводится по тем же правилам, что и в команде glob. Если элемент не найден, возвращается значение -1 lset переменная.списка Для i-ro элемента в составе переменной списка за- ?i . . .? дается новое значение (Tcl 8.4) lsort ?опции? список Выполняет сортировку списка, учитывая следующие опции: -ascii, -dictionary, -integer, -real, -increasing, -decreasing, -index индекс, -unique, -command команда. Возвращает новый список concat список список Объединяет несколько списков в один join список строка- Объединяет элементы списка, разделяя их указан- разделитель поп последовательностью символов split строка символы- Разделяет строку и представляет ее фрагменты в ви- разделители де элементов списка. Разделение строки осуществляется в тех позициях, в которых находятся указанные символы
136 Часть I. Основы Tcl Формирование списков Если вы захотите создать список вручную, соблюдая соответствующие синтаксические правила, это может оказаться сложной задачей. Вручную формируются только самые простые списки. В более сложных случаях следует использовать Tcl-команды, специально предназначенные для работы со списками. Использование подобных команд несколько упрощает задачу группировки, а список автоматически представляется во внутреннем формате Tcl. Если список создан вручную, то при нервом его использовании возникают дополнительные накладные расходы при первом использовании строки, связанные с разбором строки. Команда list Команда list формирует список на базе полученных параметров. Каждый параметр соответствует одному элементу списка. При использовании данной команды в элементы списка могут быть включены любые специальные символы. Пробелы в элементе списка не приводят к разделению его на несколько элементов. Команда list выполняется очень эффективно. Независимо от того, содержит ли элемент списка один символ или 10 Кбайт данных, накладные расходы, связанные с обработкой этого элемента, остаются стабильно низкими. Как показано в листинге 5.1, при составлении списка могут использоваться переменные, содержащие произвольные значения. Листинг 5.1. Создание списка с помощью команды list set x {1 2} => 1 2 set у \$foo => $foo set 11 [list $x "a b" $y] => {1 2} {a b} {$foo} set 12 [list $11 $x] => «I 2} {a b} {$foo}}} {1 2} Команда list автоматически выполняет цитирование. Как видно в приведенном выше примере, первый список, 11, содержит три элемента. Значения этих элементов не влияют на структуру списка. Во втором списке, 12, находятся два элемента: значения 11 и х. При формировании списков в языке Tcl не создаются копии объектов, а обеспечивается совместный доступ к ним, поэтому эффективность операции формировании списка высока.
Глава 5. Списки 137 Для тех. кто впервые встречается со списками Tcl, может быть не совсем понятен принцип использования фигурных скобок. Так, например, при присвоении значения переменной х фигурные скобки исчезают. Затем, при включении $х в другой список, скобки снова появляются. Кроме того, двойные кавычки вокруг значения a b также преобразуются в фигурные скобки. В чем причина подобных трансформаций? Дело в том, что формирование списка происходит в три этапа. На первом этапе средства разбора Tcl группируют параметры, передаваемые команде list. При этом фигурные скобки и двойные кавычки используются лишь для группировки и не являются частью группируемых значений. После формирования параметров скобки и кавычки удаляются. Скобки не входят в состав значения. На втором этапе команда list создает внутреннюю структуру списка — массив ссылок на конкретные значения. На третьем этапе происходит вывод сформированного значения. При этом возникает необходимость преобразования списка в строку. В строковом списке для группировки отдельных последовательностей символов в элементы списка используются фигурные скобки. Команда lappend Команда lappend добавляет элементы к концу списка. Первым параметром lappend является имя Tcl-переменной, а остальные параметры добавляются к значению этой переменной как новые элементы списка. Подобно команде list, lappend обеспечивает высокую эффективность работы с внутренним представлением списка. Использовать команду lappend гораздо эффективнее, чем добавлять элементы вручную. Листинг 5.2. Использование lappend для добавления элементов к списку lappend new I 2 => 1 2 lappend new 3 "4 5м => 1 2 3 {4 5} set new => 1 2 3 {4 5} Команда lappend существенно отличается от других команд, предназначенных для работы со списками. Первым параметром данной команды является переменная, содержащая список, в то время как другим командам в качестве соответствующего параметра передается значение списка. Если вы передадите lappend имя несуществующей переменной, эта переменная будет создана.
138 Часть I. Основы Tcl Команда Iset Команда lset была реализована в Tcl 8.4. Она упрощает изменение одного элемента списка или вложенного списка. Подобно команде lappend, первым параметром lset является имя переменной списка. Последний параметр -- это значение элемента. Если между первым и последним присутствуют другие параметры, они определяют элемент, значение которого должно быть изменено. Если индекс элемента не задан, новое значение присваивается всей переменной. Если индекс представляет собой целое число или выражение end-целое, значение присваивается указанному элементу. Если в списке содержится вложенный список, можно указывать несколько индексов и адресовать с их помощью элемент вложенного списка. Пример такого обращения приведен в листинге 5.3. При указании нескольких индексов они должны представлять собой отдельные параметры. При выполнении команды lset производится строгая проверка границ списка. Если вы укажете индекс за пределами основного или вложенного списка, будет сгенерирована ошибка. В результате выполнения команды lset возвращается новое значение списка, но оно используется крайне редко, так как данная команда непосредственно изменяет значение заданной переменной. Листинг 5.3. Использование lset для изменения элементов списка lset new "a b с" => a b с lset new l "d e" => a {d e} с lset new l 0 "g h" => a {{g h} e} с Команда concat Команда concat используется для слияния списков. Она объединяет параметры, разделяя их пробелами. Из нескольких списков формируется один, причем элементы, являющиеся главными в каждом из списков, становятся главными и в результирующем списке. Листинг 5.4. Использование команды concat для слияния списков set х {4 5 6} set у {2 3} set z 1 concat $z $y $x => 1 2 3 4 5 6
Глава 5. Списки 139 Двойные кавычки используются подобно команде concat. В простейших случаях двойные кавычки и команда concat дают одинаковые результаты. Отличие состоит в том, что команда concat перед слиянием удаляет лишние пробелы в конце каждого из параметров. Пример использования команд list, concat и двойных кавычек приведен в листинге 5.5. Листинг 5.5. Использование двойных кавычек, а также команд concat и list set x {1 2} => 1 2 set у "$х 3м => 1 2 3 set у [concat $x 3] => 1 2 3 set s { 2 } => 2 set у "1 $s 3м => 1 2 3 set у [concat l $s 3] => 1 2 3 set z [list $x $s 3] => {1 2} { 2 } 3 Очень важно различать команды list и concat при динамическом формировании Tcl-команд. Команды list и lappend сохраняют структуру списка, а команда concat (или двойные кавычки) удаляет один уровень списка. Различия между list и concat трудно заметить, так как в ряде примеров эти команды дают одинаковые результаты. В результате в программах возникают ошибки, которые проявляются лишь на некоторых данных. Во многих примерах, приведенных в данной книге, можно встретить команды list, используемые для формирования списков. Во многих случаях данная команда позволяет избежать возникновения проблем. Этот вопрос будет подробно обсуждаться в главе 10. Извлечение элементов списков: команды llength, lindex и Irange Команда llength возвращает число элементов в списке. llength {a b{cd} "e f g" h} => 5 llength {} => О
140 Часть I. Основы Tcl Команда 1 index возвращает указанный элемент списка. Данной команде передается индекс элемента. Индексы нумеруются начиная с нуля. set х {1 2 3} lindex $x 1 => 2 Для обозначения последнего элемента списка предусмотрено ключевое слово end. Кроме того, с помощью выражения end-номер можно отсчитывать элементы начиная с конца списка. Приведенные ниже команды демонстрируют два способа получения предпоследнего элемента списка. lindex $list [expr {[llength $list] - 2}] lindex $list end-1 Команда lrange возвращает элементы списка в заданном диапазоне индексов. При вызове данной команды в качестве параметров задаются список и два индекса. По необходимости индексы можно указывать посредством выражений end и end-номер. lrange {1 2 3 {4 5}} 2 end => 3 {4 5} Изменение списков: команды linsert и Ireplace Команда linsert включает элемент в указанную позицию в списке. Если в качестве индекса задано нулевое или отрицательное значение, элементы добавляются к началу списка. Если индекс равен длине списка или больше ее, элементы добавляются в конец списка. Если же в качестве индекса указан индекс существующего элемента, новый элемент вставляется перед указанным. Следующая кохманда добавляет элемент в начало списка: linsert {1 2} 0 new stuff => new stuff 1 2 Команда Ireplace заменяет элементы списка в указанном диапазоне индексов новыми. Если новые элементы не заданы, команда удаляет из списка элементы, подлежащие замене. В отличие от lappend и lset, команды linsert и Ireplace не модифицируют существующие списки. Они лишь возвращают вновь сформированные значения. Так, в примере, показанном в листинге 5.6, команда Ireplace не изменяет значение переменной х.
Глава 5. Списки 141 Листинг 5.6. Изменение списков с помощью команды lreplace set x [list a {b с} е d] => a {b с} е d lreplace $x 1 2 В С => а В С d lreplace $x 0 О => {Ъ с} е d Поиск в списке: команда Isearch Команда Isearch возвращает имя элемента списка с определенным значением; если элемент не найден, возвращается —1. При поиске Isearch поддерживает сравнение с шаблоном. Запретить сравнение с шаблоном позволяет опция -exact. Ограниченная поддержка шаблонов, подобная той, которую реализует команда glob, описывалась в главе 4. Опция -regexp позволяет задавать значение элемента списка с помощью регулярных выражений. Регулярные выражения будут подробно описаны в главе 11. В приведенном ниже примере шаблону 1* соответствует значение list, и команда Isearch возвращает индекс соответствующего элемента в списке. Isearch {here is a list} 1* => 3 В листинге 5.7 операция ldelete реализована с помощью команд lreplace и Isearch. Листинг 5.7. Удаление элемента списка с заданным значением proc ldelete { list value } { set ix [Isearch -exact $list $value] if {$ix >= 0} { return [lreplace $list $ix $ix] } else { return $list } 2 В Tcl 8.4 были реализованы дополнительные средства поиска, в частности, поиск по типам, оптимизированный поиск в сортированных списках и возможность находить все элементы списка, удовлетворяющие заданному значению. При поиске по тинам используется внутреннее представление объектов, повышающее эффективность выполнения данной операции. Например, если у вас есть список, содержащий целые числа, вы можете с помощью опции
142 Часть I. Основы Tcl -integer сообщить команде Isearch о том, что значения списка должны храниться в целочисленном формате. Если вы не сделаете этого, значения будут преобразованы в строки и использованы для поиска. Если содержимое списка было отсортировано, то опция -sorted сообщает команде Isearch о том, что необходимо выполнять поиск методом дихотомии. Вопросы сортировки списков рассматриваются далее в этой главе. Если указана опция -inline, команда поиска вместо индекса возвращает значение элемента. Эта возможность оказывается полезной в тех случаях, когда вам неизвестно точное значение элемента и вы выполняете поиск по шаблону. При этом целесообразно задавать опцию -all, с помощью которой команда Isearch возвращает индексы или значения всех найденных элементов. set foo {the quick brown fox jumped over a lazy dog} Isearch -inline -all $foo *o* => brown fox over dog Опции команды Isearch описаны в табл. 5.2. Таблица 5.2. Опции команды Isearch -all -ascii -decreasing -dictionary -exact -glob -increasing -inline -integer -not Поиск всех элементов, которые удовлетворяют заданному критерию, и возврат списка индексов Элементы списка должны сравниваться как строки ASCII. Используется с опцией -exact или -sorted При поиске считается, что элементы списка отсортированы по убыванию значений. Используется с опцией -sorted При поиске должно использоваться сравнение по принципу словаря. Используется с опцией -exact или -sorted Сравнение считается успешным тогда, когда строки совпадают. Данную опцию нельзя использовать совместно с -glob и -regexp Выполнять сравнение по принципу, используемому в команде glob. (Данная опция принимается по умолчанию.) Несовместима с опциями -exact и -regexp При поиске считается, что элементы списка отсортированы но возрастанию значений. Используется с опцией -sorted Вместо индекса возвращается сам элемент. Если ни один элемент не удовлетворяет критерию сравнения, возвращается пустая строка Элементы списка сравниваются как целочисленные значения. Данная опция используется с -exact или -sorted Инвертировать результаты сравнения
Глава 5. Списки 143 Окончание табл. 5.2 -real Элементы списка рассматриваются как значения с плавающей точкой. Данная опция используется с -exact или -sorted -regexp Выполняется сравнение с регулярным выражением. Данную г опцию нельзя использовать с -exact и -glob. (Регулярные выражения будут рассматриваться в главе 11.) -sorted Считается, что список предварительно отсортирован, поэтому поиск можно осуществлять методом дихотомии -start индекс Задает индекс элемента списка, с которого следует начинать поиск Сортировка списков: команда Isort С помощью команды lsort осуществляется сортировка списка. Сортировка может выполняться различными способами. Исходный вариант списка, подвергшегося сортировке, остается без изменений. Вместо этого команда lsort возвращает вновь сформированный список. Основные типы сортировки задаются с помощью опций -ascii, -dictionary, -integer или -real. Порядок сортировки задается опцией -increasing или -decreasing. По умолчанию предполагается, что заданы опции -ascii и -increasing. Если задана опция -ascii, сортировка выполняется по кодам символов, а опция -dictionary задает сортировку без учета регистра символов, причем последовательности цифр интерпретируются как числа. Примеры различных вариантов сортировки одного и того же списка приведены ниже. lsort -ascii {a Z n2 nlOO} => Z а пЮО п2 lsort -dictionary {a Z n2 nlOO} => а п2 nlOO Z Если вам надо выполнить сортировку по специальному критерию, вы можете реализовать собственную функцию сортировки. Предположим, например, что у вас есть список имен, в котором каждый элемент является списком, содержащим имя, отчество (или второе имя) и фамилию сотрудника. По умолчанию сортировка будет выполняться по именам. Если вы хотите реализовать сортировку но фамилиям, вам надо реализовать команду сортировки. Листинг 5.8. Сортировка списка с использованием функции сравнения proc NameCompare {a b} { set alast [lindex $a end] set blast [lindex $b end]
144 Часть I. Основы Tcl set res [string compare $alast $blast] if {$res != 0} { return $res } else { return [string compare $a $b] } > set list {{Brent B. Welch} {John Ousterhout} {Miles Davis}} => {Brent B. Welch} {John Ousterhout} {Miles Davis} lsort -command NameCompare $list => {Miles Davis} {John Ousterhout} {Brent B. Welch} Процедура извлекает последние элементы списков, заданных посредством параметров, и сравнивает их. Если элементы совпадают, процедура сравнивает элементы полностью. В Tcl 8.0 была добавлена опция -index команды lsort, которая предназначена для сортировки списков по указанным индексам. Вместо процедуры NameCompare вы можете использовать следующее выражение: lsort -index end $list В Tcl 8.3 была реализована опция -unique, при указании которой в процессе сортировки удаляются повторяющиеся элементы списка. lsort -unique {a b a z с b} => a b с z Команда split Команда split получает строку символов и преобразует ее в список. Точки разрыва устанавливаются в тех позициях, в которых располагаются указанные символы. Результатом выполнения команды является список, созданный с учетом всех правил Tcl. set line {welch:*:28405:100:Brent Welch:/usr/welch:/bin/csh} split $line : => welch * 28405 100 {Brent Welch} /usr/welch /bin/csh lindex [split $line :] 4 => Brent Welch He следует применять операцию преобразования в список к произвольным символьным данным. Преобразовывать в список информацию, вводимую пользователем, без предварительной проверки нежелательно. Даже если исходные
Глава 5. Списки 145 данные представляют собой набор слов, разделенных пробелами, в них могут присутствовать двойные кавычки или фигурные скобки, наличие которых приведет к искажению структуры списка. В этом случае ваш сценарий будет работать на простых тестовых примерах, но при появлении некоторых сочетаний символов возникнут ошибки,. В листинге 5.9 приведен пример некорректного списка. В строке присутствует только один символ двойных кавычек. Несмотря на то что ошибка возникает в середине списка, обратиться ни к одному из его элементов нельзя. Так происходит потому, что команда 1 index, перед тем как извлечь элемент списка, пытается преобразовать в список всю строку. Листинг 5.9. Использование команды split для преобразования входных данных в Тс1-список set line {this is "not a tcl list} lindex $line 1 => unmatched open quote in list lindex [split $line] 2 => "not По умолчанию в качестве символов-разделителей принимаются пробелы, знаки табуляции и символы перевода строки. Если в строке подряд следует несколько разделителей, то в сформированном списке будут присутствовать пустые элементы; символы-разделители не объединяются. Приведенная ниже команда преобразовывает строки в список, используя в качестве разделителей пробелы, запятые, точки и символы табуляции. Для включения в набор разделителей пробела он предваряется символом обратной косой черты. Параметры команды split можно также группировать с помощью двойных кавычек. set line "YtHello, world." split $line \ , At => {} Hello {} world {} Для того чтобы поместить в каждый элемент списка по одному символу, надо задать в качестве разделителя пустую строку. split abc {} => a b с Следует заметить, что сценарий, обрабатывающий данные посимвольно, будет работать медленно. В главе 11 вы ознакомитесь с приемами, позволяющими ускорить обработку строк с помощью команды regexp.
146 Часть I. Основы Tcl Команда join Команда join выполняет действия, противоположные действиям, выполняемым командой split. Она получает значение списка и представляет его в виде строки, разделяя элементы списка указанными символами. При этом фигурные скобки, присутствующие в строковом представлении списка, удаляются. Ниже приведен пример использования команды join. join {l {2 3} {4 5 6}} : => 1:2 3:4 5 6 Если назначение фигурных скобок не совсем понятно, вспомните, что первое значение интерпретируется как список. В листинге 5.10 показана Tcl- процедура, реализующая команду join. Код этой процедуры поможет вам понять принцип объединения элементов. Листинг 5.10. Реализация команды join в виде Тс1-процедуры proc join {list sep} { set s {} ;# s содержит текущий разделитель set result {} foreach x $list { append result $s $x set s $sep } return $result } Источники дополнительных сведений • Помимо списков, в Tcl часто применяются массивы — структуры данных Tcl, обеспечивающие гибкость создаваемых программ. Массивы описываются в главе 8. • Операции со списками используются при динамической генерации Tcl- кода. Особенности формирования кода в процессе выполнения программы и вопросы использования команды eval рассматриваются в главе 10. • Команда foreach позволяет организовать перебор элементов списка. Она будет описана в главе 6.
Глава б Управляющие структуры В данной главе описываются Tcl-команды if, switch, for each, while, for, break, continue, catch, error и return, с помощью которых формируются управляющие структуры. *У ПРАВЛЯЮЩИЕ структуры в языке Tcl формируются с помощью специальных команд. Это команды циклов while, for each и for, условные операции if и switch, а также команда обработки ошибок catch. К этой же категории относятся команды, позволяющие изменять порядок выполнения операций, задаваемых управляющими структурами: break, continue, return и error. В состав многих команд, реализующих управляющие структуры, входит тело команды, которое получает управление либо в цикле, либо при выполнении определенного условия. Фрагмент кода, составляющий тело команды, надо группировать с помощью фигурных скобок. Это позволит избежать подстановки и сохранит тело команды в неизменном виде до получения им управления. Управляющая структура возвращает значение последней выполненной команды. Фигурные скобки необходимы также потому, что они группируют тело команды вместе с символами перевода строки. Это, во-первых, делает код программы более удобным для восприятия, а во-вторых, позволяет записывать команды в нескольких строках. В состав команд if, for и while входят логические выражения. Они предполагают наличие команды expr, которая применяется по умолчанию, поэтому нет необходимости указывать ее явно.
148 Часть I. Основы Tcl Выражение if then else Команда if представляет собой основное средство формирования условных выражений. Если логическое выражение, используемое в качестве условия, принимает значение true, выполняется первое, или основное, тело команды, в противном случае выполняется второе, или альтернативное, тело команды. Второе тело команды (выражение else) может отсутствовать. Команда if записывается следующим образом. if выражение ?then? тело__команды_1 ?else? ?тело_команды_2? Ключевые слова then и else не обязательны. В приведенном ниже примере отсутствует ключевое слово then, но указано else. Основное и альтернативное тело команды рекомендуется помещать в фигурные скобки, даже если они представляют собой простейшие выражения. Листинг 6.1. Условное выражение, формируемое с помощью команды if then else if {$x == 0} { puts stderr "Divide by zero!" } else { set slope [expr $y/$x] } Расположение фигурных скобок имеет значение. Структура данного примера соответствует правилам, по которым интерпретатор Tcl осуществляет разбор команд. Как вы помните, символ перевода строки завершает команду, за исключением тех случаев, когда он находится в группе, ограниченной фигурными скобками или двойными кавычками. В данном примере открывающая фигурная скобка находится в конце первой и третьей строк. Это позволяет записать команду if в нескольких строках. Первым параметром команды является логическое выражение. В соответствии с требованиями стиля это выражение сгруппировано посредством фигурных скобок. При вычислении выражения осуществляется подстановка переменных и команд. Использование фигурных скобок гарантирует, что подстановка будет выполнена не ранее, чем необходимо. В некоторых случаях скобки могут отсутствовать. Пример конструкции, сформированной без фигурных скобок, приведен ниже. if $x break continue Включать такое выражение в программу вряд ли имеет смысл, однако формально оно совершено корректно. В данном случае, в зависимости от
Глава 6. Управляющие структуры 149 значения переменной х, цикл либо прерывается, либо продолжается. Если вы примените подобный стиль для написания более сложных условных выражений, то, вероятнее всего, допустите ошибку. Поместив основное и альтернативное тело команды в фигурные скобки, вы избежите проблем в дальнейшем, когда возникнет необходимость модификации программы. Ниже представлено то же условное выражение, созданное с использованием фигурных скобок. Такой вариант гораздо предпочтительнее. Если вы привыкли использовать ключевое слово then, можете включить его в состав команды. if {$x} { break } else { continue } Используя в качестве условного выражения результат выполнения команды, можно, как показано в следующем примере, обойтись без фигурных скобок: if [команда] тело_команды_1 Однако при написании выражения if не рекомендуется отступать от общепринятого стиля. if {[команда]} тело_команды_1 С помощью ключевого слова else if можно сформировать цепочку условных выражений. Использование elseif демонстрирует фрагмент кода, приведенный в листинге 6.2. Листинг 6.2. Цепочка условных выражений, формируемая с помощью ключевого слова elseif if {$key < 0} { incr range 1 } elseif {$key == 0} { return $range } else { incr range -1 2 В цепочке может содержаться любое количество условий. Однако, если число условий велико, следует рассмотреть целесообразность использования команды switch для решения той же задачи.
150 Часть I. Основы Tcl Команда switch Команда switch выполняет одно из нескольких тел команды в зависимости от значения выражения. Выбор тела для выполнения осуществляется на основе либо простого сравнения, либо сравнения с шаблоном. Вопросы сравнения с шаблонами обсуждаются в главах 4 и 11. Команда switch записывается следующим образом: switch опции значение ша6лон_1 тело_команды_1 шаблон_2 тело_команды_2 ... В составе switch может находиться любое количество пар шаблон-тело команды. Если значение выражения выдерживает сравнение с несколькими шаблонами, выполняется тело команды, соответствующее первому из этих шаблонов. Все пары шаблон-тело команды можно объединить в один параметр. switch опции значение { шаблон_1 тело_команды_1 шаблон_2 тело_команды_2 . . . } Первая форма записи команды switch допускает подстановку шаблонов, но для записи команды в нескольких строках приходится применять символы обратной косой черты. Пример такой записи приведен в листинге 6.4. Вторая форма отличается тем, что все шаблоны и тела команды группируются в один параметр. При этом команду можно записывать в удобном виде, не обращая внимания на символы перевода строки, но подстановка в составе шаблонов подавляется. Пример записи с группировкой шаблонов и тел команды в один параметр приведен в листинге 6.3. В любом случае тела команды должны быть сгруппированы с помощью фигурных скобок, чтобы подстановка осуществлялась при выполнении кода, составляющего тело команды, т.е. в том случае, если результат сравнения с соответствующим шаблоном оказался положительным. Способ обработки значения задается с помощью следующих опций. • -exact. Выполняется непосредственное сравнение с шаблоном. Сравнение считается успешным при точном соответствии значения и шаблона. Данная опция предполагается по умолчанию. • -glob. Выполняется сравнение с шаблоном, подобное тому, которое осуществляет команда glob. Особенности такого сравнения см. в главе 4. • -regexp. Значение шаблона интерпретируется как регулярное выражение. Особенности использования регулярных выражений будут рассмотрены в главе 11. • --. Отсутствие опции или конец набора опций. Такая опция необходима в том случае, если значение начинается с символа -.
Глава 6. Управляющие структуры 151 Если в составе команды switch указана опция, отличная от перечисленных выше, либо если значение начинается с символа -, возникает ошибка. Поэтому, независимо от используемого значения, полезно всегда указывать перед ним символы --. Шаблон, связанный с телом списка и имеющий значение default, обрабатывается специальным образом. Если результаты сравнения с остальными шаблонами отрицательны, выполняется тело команды, соответствующее шаблону default. Пара, содержащая шаблон default, должна располагаться последней. В противном случае последовательность символов default будет интерпретироваться как литеральное выражение. Листинг 6.3. Команда switch, в которой выполняется непосредственное сравнение switch -exact -- $value { foo { doFoo; incr count(foo) } bar { doBar; return $count(foo)} default { incr count(other) } 2 Если в шаблонах присутствуют ссылки на переменные или указана обратная косая черта, использовать фигурные скобки для группировки набора пар шаблон-тело команды нельзя. В этом случае команда должна содержаться в одной строке. Чтобы записать ее в нескольких строках, надо все строки, кроме последней, завершать обратной косой чертой. Листинг 6.4. Форма записи команды switch, позволяющая выполнять подстановку в шаблонах switch -regexp -- $value \ ~ $key { bodyl }\ \t### { body2 }\ {[0-9]*} { body3 } В данном примере во втором и в третьем шаблонах осуществляется подстановка: выражение $кеу заменяется значением переменной, a \t — символом табуляции. Третий шаблон помещен в фигурные скобки, в результате чего подстановка запрещается и квадратные скобки остаются частью регулярного выражения. Если тело команды, соответствующее шаблону, представляет собой символ -, то команда switch передает управление телу, связанному со следующим шаблоном. Таким образом можно объединить любое количество шаблонов.
152 Часть I. Основы Tcl Листинг 6.5. Команда switch, в которой тело команды соответствует двум шаблонам switch -glob -- $value { X* - Y* { takeXorYaction $value } } Комментарии в составе команды switch В команде switch комментарии можно включать только в тех позициях, в которых они будут правильно обработаны средствами разбора Tcl. Комментарии помещают в тело команды, связанное с определенным шаблоном, как показано в листинге 6.6. Комментарий, находящийся на том же уровне вложенности, что и шаблоны, интерпретатор будет пытаться обработать так же, как и обычную пару шаблон-тело команды. Листинг 6.6. Комментарии в команде switch switch -- $value { # Данные комментарии искажают команду switch pattern { # Эти комментарии расположены корректно } } Команда while Команде while передаются два параметра: один из них определяет условие завершения, а другой представляет собой тело цикла. while логическое_выражение тело_цикла При выполнении команды while периодически вычисляется значение логического выражения, используемого в качестве условия. Если это значение равно true, тело команды выполняется. Поскольку логическое выражение проверяется перед каждым выполнением тела цикла, важно записать его так, чтобы подстановка не осуществлялась перед вызовом команды while. Так, например, следующее выражение реализует бесконечный цикл: set i 0 ; while $i<10 {incr i} Если же условие продолжения цикла переписать так, как показано ниже, то по достижении переменной i значения 10 тело цикла перестанет выполняться. set i 0 ; while {$i<10} {incr i}
Глава 6. Управляющие структуры 153 В логическом выражении могут содержаться вложенные команды. В примере, приведенном в листинге 6.7, для получения информации из стандартного входного потока используется команда gets. Данная команда возвращает число прочитанных символов. При достижении конца файла возвращается значение, равное — 1. При каждом выполнении цикла переменной line присваивается очередная строка. Листинг 6.7. Использование цикла while для чтения данных из стандартного входного потока set numLines 0 ; set numChars 0 while {[gets stdin line] >= 0} { incr numLines incr numChars [string length $line] } Команда foreach Команда foreach выполняет тело цикла, присваивая одной или нескольким переменным каждое из значений, содержащихся в одном или нескольких списках. Поддержка нескольких переменных цикла, реализованная в Tcl 7.5, позволяет в ряде случаев упростить код программы. При использовании одной переменной цикла и одного списка синтаксис команды foreach имеет следующий вид: foreach переменная_цикла список_значений тело_команды Первый параметр представляет собой имя переменной цикла. Тело цикла выполняется столько раз, сколько элементов содержится в списке, причем перед началом каждой итерации переменной цикла присваивается значение очередного элемента. Список может быть задан явно, как показано в листинге 6.8. Листинг 6.8. Организация цикла с помощью команды foreach set i 1 foreach value {1 3 5 7 11 13 17 19 23} { set i [expr $i*$value] } set i => 111546435 Вместо статического списка в составе foreach часто используют переменную, содержащую список, либо результат выполнения команды. В примере,
154 Часть I. Основы Tcl код которого приведен в листинге 6.9, в цикле осуществляется перебор параметров командной строки. Переменная argv, устанавливаемая интерпретатором Tcl, содержит список параметров. Листинг 6.9. Разбор параметров командной строки # Значение argv устанавливает оболочка Tcl # Допустимы следующие опции: # -max целое число # -force # -verbose set state flag set force 0 set verbose 0 set max 10 foreach arg $argv { switch -- $state { flag { switch -glob — $arg { -f* {set force 1} -v* {set verbose 1} -max {set state max} default {error "unknown flag $arg"} } } max { set max $arg set state flag } } 2 В данном примере переменная state указывает на то, какие данные следует ожидать: опцию или целочисленное значение опции -max. Опция -- обязательно должна присутствовать, так как шаблоны начинаются с символа -. Опция -glob позволяет пользователю сокращенно задавать опции -force и -verbose одной или несколькими начальными буквами. Если список представлен переменной или результатом выполнения команды, то для его формирования должна использоваться команда list. Избегайте применения двойных кавычек; при наличии пробелов или фигурных скобок структура списка изменится, а это может привести к ошибке или к появлению непредсказуемых результатов.
Глава 6. Управляющие структуры 155 Листинг 6.10. Использование команды list совместно с f oreach foreach x [list $a $b [foo]] { puts stdout "x = $x" 2 Переменная цикла х получит значение переменной а, затем значение переменной Ь, а после этого результат выполнения команды foo. Порядок присвоения данных не зависит от того, содержатся в них специальные символы или нет. Использование нескольких переменных цикла В команде foreach можно использовать более одной переменной цикла. Предположим, что таких переменных две: х и у. При выполнении первой итерации х получает первое, а у — второе значение списка. При выполнении второй итерации переменным присваиваются соответственно третье и четвертое значения. Процесс закончится тогда, когда все элементы списка будут исчерпаны. Если оставшихся элементов списка не хватает для всех переменных цикла, "лишним" переменным в качестве значения будет присвоена пустая строка. Листинг 6.11. Использование нескольких переменных цикла в команде foreach foreach {key value} {orange 55 blue 72 red 24 green} { puts "$key: $value" } orange: 55 blue: 72 red: 24 green: Если некоторая команда возвращает список, состоящий из небольшого числа элементов, то foreach можно использовать "не по назначению", т.е. не для организации цикла, а лишь для присвоения элементов списка сразу нескольким переменным. Предположим, например, что некоторая команда MinMax возвращает список, состоящий из двух элементов — минимального и максимального значения. Присвоить эти значения переменным можно следующим образом: set result [MinMax $list] set min [lindex $result 0] set max [lindex $result 1] Команда foreach позволяет добиться того же результата с помощью более компактной записи.
156 Часть I. Основы Tcl foreach {min max} [MinMax $list] {break} Команда break в теле цикла необходима для того, чтобы исключить нежелательные последствия в случае, если список, возвращаемый командой, будет насчитывать больше элементов, чем предполагалось. Этот прием будет использоваться в главе 10 при создании процедуры lassign. Код этой процедуры приведен в листинге 10.4. Работа с несколькими списками Команда foreach может одновременно обрабатывать несколько списков. В этом случае каждому списку должна быть поставлена в соответствие одна или несколько переменных. Итерации будут продолжаться до тех пор, пока хотя бы в одном списке останется минимум одно значение. Если один из списков будет исчерпан раньше остальных, соответствующим переменным будет присвоена пустая строка. Листинг 6.12. Использование нескольких списков в команде foreach foreach {kl k2} {orange blue red green black} value {55 72 24} { puts "$kl $k2: $valueu } orange blue: 55 red green: 72 black : 24 Команда for Команда for выполняет те же функции, что и одноименное выражения языка С. При вызове команды for ей передаются четыре параметра: for инициализация условие действие тело_цикла Первый параметр — это команда инициализации цикла. Вторым параметром является логическое выражение, которое определяет, должно ли быть выполнено тело цикла. Третий параметр — это команда, выполняемая по окончании цикла. Листинг 6.13. Цикл for for {set i 0} {$i < 10} {incr i 3} { lappend aList $i } set aList => 0 3 6 9
Глава 6. Управляющие структуры 157 Команду for можно использовать для обработки списков, но следует помнить, что для этой цели больше подходит команда f oreach. Следующий фрагмент кода труден для восприятия, кроме того, он выполняется медленно: for {set i 0} {$i < [llength $list]} {incr i} { set value [lindex $list $i] } Тот же результат можно получить с помощью команды foreach value $list { } Команды break и continue Для управления телом цикла можно использовать команды break и continue. По команде break происходит немедленный выход из цикла, а по команде continue начинается новая итерация. В языке Tcl отсутствует команда goto. Команда catch До сих пор мы не рассматривали возможность возникновения ошибок. Однако на практике бывают ситуации, когда команда вызывается с некорректным количеством параметров либо когда в процессе ее выполнения выявляются недопустимые условия. В этих случаях возникают ошибки. Если ошибка не перехватывается, выполнение сценария прекращается1. Для перехвата ошибок используется команда catch. Ей передаются два параметра: catch команда ?переменная_результата? Первый параметр — это тело команды. Второй параметр — имя переменной, содержащей результаты выполнения команды или сообщение об ошибке в случае ее возникновения. Команда catch возвращает нуль в случае отсутствия ошибок или ненулевое значение при наличии ошибки. Поскольку команда вызывает интерпретатор Tcl, для группировки команд вместо двойных кавычек надо использовать фигурные скобки. В случае двойных кавычек еще до вызова catch осуществляется дополнительный 1 Строго говоря, в этом случае сценарий Tcl завершается, а текущая процедура Tcl.Eval библиотеки С возвращает значение TCL_ERR0R. Далее возможны три варианта развития событий. При интерактивной работе оболочка Tcl отображает сообщение об ошибке. При работе с Тк ошибка приводит к вызову Tcl-процедуры bgerror, которую вы можете реализовать в вашем приложении. И, наконец, если вы напишете программу на С, то можете проанализировать результат выполнения Tcl_Eval и выполнить в случае ошибки необходимые действия. - - Прим. авт.
158 Часть I. Основы Tcl этап подстановки. Простейший вызов данной команды выглядит следующим образом: catch { команда У В листинге 6.14 показан фрагмент кода, при выполнении которого сохраняются результаты вызова команды catch и выводится сообщение об ошибке. Листинг 6.14. Типичный пример использования команды catch if {[catch { команда параметр_1 параметр_2 ... } result]} { puts stderr $result } else { # Команда выполнена корректно, # переменная result содержит возвращаемое значение 2 Более универсальный вариант применения команды catch представлен в листинге 6.15. В тело команды catch включено несколько команд. При возникновении ошибки интерпретатор Tcl устанавливает значение переменной err or Info. Это необходимо для того, чтобы отследить состояние стека в момент ошибки. Листинг 6.15. Универсальный способ применения команды catch if {[catch { команда. 1 команда_2 команда_3 } result]} { global errorlnfo puts stderr $result puts stderr "*** Tcl TRACE ***" puts stderr $errorInfo } else { # Тело команды выполнено корректно, # переменная result содержит значение, # возвращаемое последней командой 2 Вызов catch не обязательно помещать в фигурные скобки. Команда catch всегда возвращает целое число, поэтому разбор команды if будет осуществляться корректно. Однако, если вместо if вы будете использовать команду
Глава 6. Управляющие структуры 159 while, фигурные скобки станут необходимы, чтобы обеспечить многократную обработку команды catch. Обработка ситуаций, не являющихся ошибочными Команда catch может обрабатывать не только ошибки. Если в теле команды содержится выражение return, break или continue, выполнение тела команды прерывается и catch возвращает ненулевое значение. Это нужно учитывать, планируя включение других команд в тело catch. He вызывающая на первый взгляд никаких подозрений, команда return способна стать причиной такого поведения команды catch, которое может быть интерпретировано как сигнал о наличии ошибки. В следующем примере с помощью команды switch выполняется анализ значений, возвращаемых командой catch. При отсутствии ошибки управление передается в нужную позицию путем вызова соответственно команды return, break или continue. Листинг 6.16. Команда catch может возвращать значения из определенного набора switch [catch { команда.1 команда_2 } result] { 0 { # Нормальное завершение } 1 { # Возникновение ошибки } 2 { return $result ;# Возврат из процедуры } 3 { break ;# Выход из цикла } 4 { continue ;# Продолжение цикла } default { # Коды ошибок, определенные пользователем } } Команда error Команда error создает условия, соответствующие возникновению ошибки. Если в сценарии не предусмотрен перехват ошибок с помощью команды catch, его работа завершается. Команде error передается до трех параметров: error сообщение ^информация? ?код? Сообщение, указанное в команде error, становится сообщением об ошибке и помещается командой catch в соответствующую переменную. Если при
160 Часть I. Основы Tcl вызове команды указан второй параметр, интерпретатор Tcl использует его для инициализации глобальной переменной errorlnf о. Эта переменная позволяет определить состояние стека при возникновении ошибки. Если этот параметр отсутствует, для инициализации переменной errorlnf о используется сама команда error. Листинг 6.17. Генерация ошибки proc foo {} { error bogus } foo => bogus set errorlnfo => bogus while executing "error bogus" (procedure "foo" line 2) invoked from within "foo" В приведенном выше примере информацию для формирования сведений о стеке дает сама команда error. Второй параметр этой команды используется для того, чтобы сохранить значение errorlnfo, предоставляемое командой catch. В листинге 6.18 сохраняется информация об ошибке, допущенной при выполнении сценария. Листинг 6.18. Формирование errorlnfo при генерации ошибки if {[catch {foo} result]} { global errorlnfo set savedlnfo $errorInfo # Предпринята неудачная попытка обработать ошибку ... error $result $savedInfo 2 Третий параметр команды error предоставляет краткое описание ошибки, предназначенное для машинной обработки. Это описание хранится в глобальной переменной errorCode. По умолчанию данной переменной присваивается значение NONE. Большинство команд, предназначенных для работы с файловой системой, возвращает errorCode, значение которой состоит из трех элементов: P0SIX, имя ошибки (например, EN0ENT) и сообщение об ошибке.
Глава 6. Управляющие структуры 161 POSIX ENOENT {No such file or directory} В случае необходимости вы можете определить в вашем приложении собственные коды ошибки. При перехвате ошибки анализируется содержимое глобальной переменной err or Code и принимается решение о том, какие ответные действия должны быть предприняты. Команда return Команда return осуществляет выход из процедуры. Она необходима в том случае, если процедура должна быть завершена раньше, чем будут выполнены все команды, находящиеся в теле процедуры, либо тогда, когда процедура должна вернуть некоторое значение. Несмотря на то что процедура возвращает результат последней выполненной команды, хорошим стилем считается использование команды return, даже если ее приходится включать в конце тела процедуры. Дополнительные параметры команды return позволяют определить специальные условия завершения процедуры. Синтаксис команды return представлен ниже. return ?-code с? ?-errorinfo i? ?-errorcode ее? строка Значением опции -code может быть ok, error, return, break, continue либо целое число. Если опция -code не указана, по умолчанию предполагается значение ок. Если указана опция -code error, команда return ведет себя подобно команде error. Опция -errorcode позволяет задать значение глобальной переменной errorCode, а опция -errorinf о инициализирует глобальную переменную errorlnfo. При выполнении команды return -code error в информации о стеке команда error не присутствует. Различие между командами error и return легко понять, сравнивая коды в листингах 6.17 и 6.19. Листинг 6.19. Генерация ошибки с помощью команды return proc bar {} { return -code error bogus } catch {bar} result => 1 set result => bogus set errorlnfo => bogus
162 Часть I. Основы Tcl while executing "bar" Значения опций -code return, break и continue проявляются в той части кода, в которой осуществлялся вызов процедуры. Опция -code return означает возврат из вызывающей процедуры. Если указано значение -code break, в вызывающей процедуре осуществляется выход из цикла, a -code continue влечет за собой начало новой итерации цикла в вызывающей процедуре. Наличие опции -code команды return позволяет формировать новые управляющие структуры в Tcl. Ниже в качестве примера приведена реализация команды break в виде Тс1-процедуры. proc break {} { return -code break } Существуют пакеты, реализующие в языке Tcl структуры, подобные блокам try-catch языка Java, однако механизм обработки исключений, используемый в Tcl, достаточно прост и в то же время предоставляет разработчикам мощные средства для решения их задач.
Глава 7 Процедуры и область видимости Процедуры содержат наборы команд. С ними же связано понятие области видимости переменных. В данной главе рассматриваются команды proc, global и upvar. 1 1РОЦЕДУРЫ позволяют организовать выполнение набора команд с заданными значениями параметров. Каждая процедура создает новую область видимости для переменных. Область видимости переменной — это множество команд, которым доступна данная переменная. Первоначально в Tcl была определена одна глобальная область видимости для общедоступных переменных, локальные области видимости в пределах процедур и одна глобальная область видимости для процедур. В Tcl 8.0 был введен механизм пространства имен, посредством которого реализуются новые области видимости для процедур и глобальных переменных. При создании простых программ разработчики обычно не используют пространства имен и ограничиваются глобальной областью видимости. Новые средства находят применение лишь при написании сложных приложений. Пространства имен будут подробно обсуждаться в главе 14. Команда ргос Для определения процедуры в Tcl используется команда ргос. Ей передаются три параметра: ргос имя список_параметров тело_процедуры
164 Часть I. Основы Tcl Первый параметр задает имя процедуры. Оно добавляется к набору команд, которые могут быть выполнены интерпретатором Tcl. В имени процедуры могут содержаться практически любые символы; регистр символов имеет значение. Имена процедур не конфликтуют с именами переменных. В качестве второго параметра команды pro с задается список параметров процедуры. Последним параметром команды является тело процедуры. Единожды определенная, Tcl-процедура может использоваться точно так же. как и встроенная команда Tcl. При вызове процедуры параметрам ставятся в соответствие переданные значения и выполняется тело процедуры. Возвращаемый результат определяет последняя команда в теле процедуры. Если необходимо, чтобы процедура возвращала другое значение, надо использовать команду return. Процедуры позволяют определять параметры по умолчанию. При вызове подобной процедуры некоторые параметры можно не указывать. Определение параметра по умолчанию предполагает указание имени и значения. Пример определения таких параметров приведен в листинге 7.1. Листинг 7.1. Значения параметров по умолчанию proc P2 {а {Ь 7} {с -2} } { expr $а / $Ь + $с } Р2 6 3 => 0 В данном примере процедура Р2 может быть вызвана с указанием одного, двух либо трех параметров. Если при вызове процедуры задан лишь один параметр, значения b и с принимаются такими, какие были заданы в команде ргос. Если при вызове процедуры указаны два параметра, значение по умолчанию получит только с. Меньше одного и больше трех параметров передать процедуре Р2 невозможно. Для того чтобы процедуру можно было вызывать с указанием произвольного числа параметров, надо в качестве последнего параметра указать ключевое слово args. При вызове процедуры args представляет список, содержащий все остальные параметры. Листинг 7.2. Вызов процедуры с произвольным числом параметров proc ArgTest {a {b foo} args} { foreach param {a b args} { puts stdout "\t$param = [set $param]"
Глава 7. Процедуры и область видимости 165 set х one set у {two things} set z \[special\$ ArgTest $x => a = one b = foo args = ArgTest $y $z => a = two things b = [special$ args = ArgTest $x $y $z => a = one b = two things args = {[special$} ArgTest $z $y $z $x => a = [special$ b = two things args = {[special$} one Переменная z в листинге 7.2 демонстрирует работу со списком при использовании ключевого слова args. Как видно в листинге, значение переменной z содержит специальный символ. Когда $z указывается в качестве параметра Ь, значение этой переменной передается процедуре в неизменном виде. Если $z входит в состав необязательных параметров, осуществляется автоматическая группировка, в результате чего значением args становится корректный список. Способ, позволяющий отменить формирование списка, будет показан в листинге 10.3. Изменение имен команд с помощью команды rename Команда rename позволяет изменять имена других команд. Как правило, rename используется для выполнения двух задач. Первая из них — это модификация существующих процедур. Перед тем как переопределить процедуру, вы можете переименовать ее. rename foo foo.orig В новую реализацию foo вы можете включить вызов foo.orig. В результате пользователи foo начнут работать с новой версией команды, причем они могут даже не заметить этого.
166 Часть I. Основы Tcl Команду rename можно также использовать для удаления других команд. Команда, подлежащая удалению, переименовывается, а в качестве нового имени указывается пустая строка. Предположим, например, что вы не хотите, чтобы пользователи вызывали другие Unix-программы. Для этого можно удалить команду exec с помощью следующего выражения: rename exec {} Переименование и удаление команд можно отследить с помощью команды trace, которая описывается в главе 13. Область видимости По умолчанию в Tcl-программах для имен процедур используется одна глобальная область видимости. Это означает, что любая процедура может быть вызвана в любой позиции сценария. Переменные, определенные за пределами процедур, являются глобальными. Однако, несмотря на их название, глобальные переменные не обязательно доступны внутри процедуры. Для переменных и процедур могут быть определены различные пространства имен, что позволяет создавать глобальные переменные и процедуры с одинаковыми именами, не вызывая конфликта. Используя механизм пространств имен, рассмотренный в данной главе, вы можете эффективно управлять процедурами и переменными. В каждой процедуре создается локальная область видимости для переменных. Время жизни переменных, определенных в процедуре, ограничено временем выполнения этой процедуры. После завершения процедуры локальные переменные становятся неопределенными. Переменные, определенные за пределами процедуры, видимы только в случае применения команды upvar или global. Для обращения к переменным в некоторой области видимости вы можете использовать полностью определенные имена этих переменных. Команды global и upvar будут рассмотрены ниже. Полностью определенные имена обсуждаются в главе 14. Наличие в разных областях видимости переменных с одинаковыми именами не становится причиной конфликта. В листинге 7.3 переменная а в глобальной области видимости не конфликтует с одноименным параметром процедуры Р1. Аналогично, глобальная переменная b отличается от локальной переменной b в составе Р1. Листинг 7.3. Область видимости и Тс1-процедура set a 5 set Ъ -8 proc P1 {а} { set Ъ 42
Глава 7. Процедуры и область видимости 167 if {$a < 0} { return $b } else { return $a } } PI $b => 42 PI [expr {$a*2}] => 10 Команда global Глобальная область видимости — это область видимости верхнего уровня. Данная область лежит за пределами всех процедур. Переменные, определенные в глобальной области, становятся доступными в процедуре посредством команды global. Эта команда вызывается следующим образом: global имя_переменной_1 имя_переменной_2 . . . Команда global вызывается внутри процедуры. Она добавляет глобальную переменную к текущей области видимости. Некоторые неопытные программисты считают, что единожды вызванная команда global применима ко всем процедурам. Это неверно. Команда global, вызванная в глобальной области видимости, не даст никакого эффекта. Данную команду надо указывать в каждой процедуре, в которой необходимо обеспечить доступ к глобальной переменной. В момент вызова команды global переменная не обязательно должна быть определена. Если же переменная определена, она становится видимой в глобальной области видимости. В листинге 7.4 приведен код генератора случайных чисел. Однако, перед тем как перейти к рассмотрению примера, следует заметить, что наилучший способ получения в Tcl-сценарии последовательности случайных чисел — это вызов функции rand(). expr rand() => .137287362934 При работе с генератором случайных чисел надо поддерживать переменную состояния, содержащую начальное значение. Эта переменная не должна изменяться между вызовами функции генератора. В рассматриваемом примере роль переменной состояния играет randomSeed. Правильно выбрать имя
168 Часть I. Основы Tcl очень важно. Это позволит избежать конфликтов с другими частями программы. В примере, представленном в листинге 14.1, для ограничения доступа к переменной состояния используется пространство имен. Листинг 7.4. Генератор случайных чисел proc Randomlnit { seed } { global randomSeed set randomSeed $seed } proc Random {} { global randomSeed set randomSeed [expr ($randomSeed*9301 + 49297) °/0 233280] return [expr $randomSeed/double(233280)] } proc RandomRange { range } { expr int([Random]*$range) } Randomlnit [pid] => 5049 Random => 0.517686899863 Random => 0.217176783265 RandomRange 100 => 17 Передача имени с помощью команды upvar Если вам надо передать процедуре не значение, а имя переменной, вы можете использовать для этой цели команду upvar. Данная команда связывает локальную переменную с переменной в области видимости, находящейся выше по стеку вызовов. Команда upvar записывается следующим образом: upvar ?'уровень? имя_переменной локальная_переменная Первый параметр команды не является обязательным. По умолчанию принимается значение 1, что соответствует перемещению вверх по стеку вызовов на один уровень. Вы можете указать другое число кадров для перемещения или задать абсолютный номер кадра с помощью выражения #число. Уровню #0 соответствует глобальная область видимости, поэтому команда global f оо эквивалентна следующему выражению:
Глава 7. Процедуры и область видимости 169 upvar #0 foo foo Переменная в кадре может быть скалярной переменной, элементом массива или именем массива. В первых двух случаях локальная переменная интерпретируется как скалярная. В случае имени массива локальная переменная рассматривается как массив. Использование команды upvar и массивов будет подробно рассмотрено в главе 8. В процедуре, приведенной в листинге 7.5, upvar используется для вывода значения переменной по ее имени. Листинг 7.5. Вывод переменной по имени proc PrintByName { varName } { upvar 1 $varName var puts stdout "$varName = $varM _} Команду upvar можно использовать для модификации команды incr. Встроенная команда incr обладает существенным недостатком: она порождает ошибку, если переменная отсутствует. В качестве примера рассмотрим новую версию incr, которая инициализирует отсутствующую переменную. Листинг 7.6. Модифицированная команда incr proc incr { varName {amount 1}} { upvar 1 $varName var if {[info exists var]} { set var [expr $var + $amount] } else { set var $amount } return $var 2 Создание псевдонимов с помощью команды upvar Команда upvar полезна тогда, когда имя одной переменной хранится в другой переменной. В коде, приведенном в листинге 7.2, переменной цикла param присваиваются имена других неременных. Их значения извлекаются с помощью следующего выражения: puts stdout M\t$param = [set $param]"
170 Часть I. Основы Tcl Сделать то же самое можно с помощью команды upvar. При этом исчезает необходимость в использовании неудобных конструкций типа [set $param]. Если переменная находится в той же области видимости, то команде upvar надо передать номер уровня 0. Приведенные ниже команды выполняют такие же действия, как и предыдущее выражение. upvar 0 $param x puts stdout "\t$param = $хи Использование команды upvar для поддержки состояния объектов Предположим, что ваша программа поддерживает информацию о состоянии набора объектов: файлов, Web-документов и др. Имя объекта можно использовать как имя переменной, в которой хранится информация о состоянии. Сделать это удобно с помощью команды upvar. upvar #0 $name state Однако непосредственно использовать имя объекта несколько рискованно. Если, например, у нас есть объект с именем х, то может возникнуть конфликт с переменной х, расположенной в другом месте программы. Для того чтобы описанный подход обеспечивал более высокую надежность, имя надо модифицировать. upvar #0 state$name state В этом случае имя выполняет роль дескриптора объекта, а команда upvar предоставляет доступ к связанным с ним данным. Код, выполняющий обработку, использует псевдоним переменной состояния текущего объекта. Более подробно данный подход будет рассматриваться в главе 17. В частности, конкретный пример представлен в листинге 17.7. Пространства имен и команда upvar Команда upvar может использоваться также для создания псевдонимов к переменным пространств имен. Пространства имен будут подробно обсуждаться в главе 14. Например, вместо того чтобы резервировать глобальные переменные, начинающиеся со слова state, для специальных целей, вы можете запретить к ним доступ помощью пространства имен. upvar #0 state::$name state В данном случае state представляет собой псевдоним переменной. Благодаря команде upvar появляется возможность работать с любым пространством имен.
Глава 7. Процедуры и область видимости 171 Команды, работающие с именами переменных Некоторые Tcl-команды предназначены для обработки имен переменных. Например, компоненты Тк могут быть связаны с глобальными переменными Tcl. Имена переменных передаются в качестве параметров командам vwait и tkwait. Команда upvar не позволяет создавать псевдонимы для текстовых компонентов Тк. Псевдонимы, созданные с помощью upvar, неприменимы к указанным выше командам. Они также не будут работать, если используется команда trace, рассматриваемая в главе 13. В этих случаях вы должны указывать реальные имена глобальных переменных. Продолжая разговор о примере, в котором state используется как псевдоним, заметим, что следующие две команды некорректны: vwait state(foo) button .b -textvariable state(foo) Вместо них должны использоваться такие выражения: vwait state$name\(foo) button .b -textvariable state$name\(foo) Символ обратной косой черты отменяет ссылку на массив, поэтому в Tcl- сценариях не следует пытаться использовать name в качестве массива. Не следует беспокоиться о наличии в $name специальных символов, за исключением круглых скобок. Как только имя будет передано компоненту Тк, оно будет непосредственно использоваться как имя переменной. Текстовые переменные для меток рассматриваются в главе 32, а переменные для полей редактирования —- в главе 34.
Глава 8 Массивы Tcl В данной главе описываются массивы Tcl, на базе которых можно создавать многие другие структуры данных. Из команд здесь рассматривается только array. £3 языке Tcl массив — это переменная, предполагающая наличие индекса. Индексом в данном случае является строковое значение. Индекс можно рассматривать как ключ, а массив — как набор данных, элементы которых идентифицируются с помощью ключей. В качестве индекса допустима любая строка. В составе Tcl массив представлен в виде хэш-таблицы, в результате чего накладные расходы, возникающие при обращении к элементу массива, практически не зависят от характеристик этого элемента. В версиях, предшествующих Tcl 8.O. массивы обеспечивали более высокую производительность, чем списки, так как время доступа к элементу списка было пропорционально длине списка. Массивы — гибкий инструмент, поэтому они очень часто используются при создании Tcl-нрограмм. Как привило, массивы применяются для организации набора переменных, подобно структурам в языке С и записям в Pascal. В данной главе мы рассмотрим некоторые простые структуры данных, создаваемые с использованием массивов. Языковые средства для работы с массивами Для выделения индекса массива используются круглые скобки. Индекс представляет собой произвольное строковое значение и может формировать-
Глава 8. Массивы Tcl 173 ся путем подстановки переменной или команды. Для формирования элементов массива используется команда set. set arr(индекс) значение Значение элемента массива извлекается с помощью подстановки, выполняемой при указании символа $. set foo $arr(индекс) В листинге 8.1 в качестве индекса используется значение переменной цикла $i. В результате выполнения приведенного кода каждому элементу массива агг(х) присваивается значение 1 * 2 * ... * х. Листинг 8.1. Пример использования массива set arr(O) l for {set i 1} {$i <= 10} {incr i} { set arr($i) [expr {$i * $arr([expr {$i-l}])}] } Индексы Индексом массива может быть любая строка, например orange, 5, 3.1415, foo, bar и т.д. В примерах, приведенных в данной главе и далее в книге, для создания гибких структур данных используются индексы массивов, представляющие собой достаточно большие строки символов. Несмотря на то что индексом может быть любая строка, следует избегать использования в качестве индексов строк, содержащих пробелы. Круглые скобки не осуществляют группировку. Основной синтаксический анализатор Tcl "не осведомлен" о правилах создания массивов. Принципы группировки и подстановки, описанные в главе 1, учитываются при работе с массивами так же, как и при использовании обычных команд. В отличие от фигурных скобок и кавычек, круглые скобки не осуществляют группировку, по этой причине могут возникать проблемы, связанные с наличием пробелов. При использовании составного индекса разделяйте различные его части с помощью запятых. Если же в индексе используется пробел, то перед ним надо указывать обратную косую черту либо осуществлять группировку. set {arr(I'm asking for trouble)} {I told you so.} set arr(Pm\ asking\ for\ trouble) {I told you so.} Если индекс массива хранится в переменной, наличие пробелов в ее значении не играет роли. Оба приведенных ниже выражения корректны.
174 Часть I. Основы Tcl set index {I'm asking for trouble} set arr($index) {I told you so.} Переменные массивов Элементы массива можно использовать как обычные переменные. Например, вы можете проверять их наличие с помощью команды info exists, ин- крементировать значение посредством команды incr и добавлять элементы, используя команду 1 append. if {[info exists stats($event)]} {incr stats($event)} По мере необходимости можно удалить с помощью команды unset весь массив или один его элемент. Применение команды unset к массиву — удобный способ очищать большие структуры данных. Одну и ту же переменную нельзя одновременно использовать как обычную переменную и как массив. Приведенное ниже сочетание команд недопустимо. set arr(O) l set arr 3 => can't set "arr": variable is array Имя массива может формироваться как результат подстановки. Такой подход использовался в примере, приведенном в листинге 8.2. Листинг 8.2. Косвенное обращение к массиву set name TheArray => The Arr ay- set ${name}(xyz) {some value} => some value set x $TheArray(xyz) => some value set x ${name}(xyz) => TheArray(xyz) set x [set ${name}(xyz)] => some value Аналогичные результаты можно получить, используя команду upvar, которая была описана в главе 7. Если переписать предыдущий пример, применяя команду upvar, код станет более понятным.
Глава 8. Массивы Tcl 175 Листинг 8.3. Косвенное обращение к массиву с использованием команды upvar set name TheArray => ТЪеЛггау upvar 0 $name a set a(xyz) {some value} => some value set x $TheArray(xyz) => some value Команда array Команда array возвращает информацию о переменных массива. В результате выполнения команды array names возвращаются имена индексов, определенных в массиве. Если переменная массива не определена, то array names вернет пустой список. Наличие списка имен индексов позволяет организовать перебор элементов массива в цикле ioreach. foreach index [array names arr pattern] { # Действия с использованием выражения arr($index) } Порядок следования имен, предоставляемых командой array names, зависит от особенностей внутреннего представления массива, в частности от структуры хэш-таблицы, с помощью которой реализуется массив. Диапазон возвращаемых имен можно ограничить, указав шаблон, которому должны соответствовать индексы. Шаблон поддерживается командой string match, которая была описана в главе 4. Для поочередной обработки элементов массива можно также использовать команды поиска, описанные в табл. 8.1. Порядок следования элементов и в этом случае остается произвольным, поэтому необходимо признать, что применять для перебора команду foreach более удобно. Если в массиве находится большое число элементов или если вам надо выполнять обработку в течение длительного периода времени, предпочтительнее использовать операции поиска в массиве. Описание различных операций, реализуемых посредством команды array, приведено в табл. 8.1.
176 Часть I. Основы Tcl Таблица 8.1. Команда array array exists переменная array get массив ?шаблон? array names массив ?режим? ?шаблон? array set массив список array size массив array unset массив ?шаблон? array startsearch массив array nexTclement массив идентификатор array anymore массив идентификатор array donesearch мас сив идентификатор array statistics массив Возвращает значение 1, если переменная является массивом Возвращает список, содержащий индексы и соответствующие им значения. С помощью шаблона задаются требуемые индексы. Если шаблон не указан, возвращаются все индексы и значения Возвращает список, содержащий все индексы массива, либо те из них, которые удовлетворяют шаблону. Режим определяет правила сравнения с шаблоном и задается с помощью опций -exact, -glob (значение по умолчанию) или -regexp Инициализирует массив значениями из списка. Формат списка совпадает с данными, возвращаемыми в результате выполнения операции array get Возвращает число индексов, определенных в указанном массиве Удаляет элементы массива, соответствующие шаблону. Сравнение с шаблоном выполняется по тому же принципу, что и в команде glob. Если шаблон не задан, удаляется весь массив Возвращает идентификатор поиска для массива Возвращает значение следующего элемента массива в поиске, идентифицируемом с помощью указанного идентификатора. Если элементы в поиске отсутствуют, возвращается пустая строка Возвращает значение 1, если в поиске присутствуют элементы Завершает поиск, определяемый указанным идентификатором Возвращает сведения о хэш-таблице массива (Tcl 8.4) Преобразование массивов в списки Для преобразования массивов в списки и списков в массивы используются соответственно команды array get и array set. Список, возвращаемый командой array get, содержит четное число элементов. В нем попеременно расположены индексы и значения. Первым элементом списка является индекс, а за ним следует значение соответствующего элемента массива и т.д.
Глава 8. Массивы Tcl 177 Такую же структуру должен иметь массив, передаваемый в качестве параметра команде array set. array set fruit { best kiwi worst peach ok banana } array get fruit => ok banana best kiwi worst peach Таким образом, становится очевидным еще один способ перебора содержимого массива - преобразование в список посредством команды array get, а затем использование команды for each с двумя переменными. foreach {key value} [array get fruit] { # Ключевыми значениями являются ok, best и worst # Значением является название соответствующего продукта } Передача имен массивов Команда upvar позволяет работать с массивами. Вы можете передать процедуре имя массива и использовать команду upvar для получения косвенной ссылки на переменную массива в той области видимости, из которой осуществляется вызов процедуры. Данный подход демонстрирует код программы, приведенный в листинге 8.4. Эта программа осуществляет инвертирование массива. Как и в случае команды array names, вы можете задать для array get шаблон и ограничить набор возвращаемых значений. Команда upvar используется потому, что процедуре Arraylnvert передается имя массива. В момент вызова Arraylnvert массив, содержащий инвертированные значения, может отсутствовать. Листинг 8.4. Процедура Arraylnvert осуществляет инвертирование массива proc Arraylnvert {arrName inverseName {pattern *}} { upvar $arrName array $inverseName inverse foreach {index value} [array get array $pattern] { set inverse($value) $index } }
178 Часть I. Основы Tcl Создание структур данных на базе массивов В данном разделе описываются некоторые структуры данных, которые можно сформировать на базе Tcl-массивов. Здесь же реализованы процедуры, обеспечивающие доступ к этим структурам. Создание интерфейса к структуре данных в виде процедуры считается хорошим стилем программирования. Это позволяет скрыть от пользователя детали реализации структуры и защитить саму структуру от некорректных действий. Для хранения взаимосвязанных переменных рекомендуется использовать массивы. Массивы удобно использовать для объединения взаимосвязанных переменных в один модуль. В этом случае массив выполняет ту же функцию, что и запись в других языках программирования. При объединении переменных в массив имя массива выполняет роль имени модуля. При этом исключаются конфликты между различными модулями. Для того чтобы обеспечить доступ процедуры ко всем переменным модуля, достаточно одного выражения global. Для управления набором массивов можно использовать переменную upvar. Пример такого применения показан в листинге 8.9. Простые записи Предположим, что в вашем распоряжении есть база данных с информацией о сотрудниках. Ниже приведен ряд примеров, демонстрирующих различные способы хранения имен сотрудников, их идентификационных кодов и номеров телефонов. В каждом из примеров реализованы запись Emp_AddRecord, предназначенная для хранения данных, и функция для доступа к данным. Например, функция Emp_Manager возвращает информацию о сотруднике. Если доступ к полям записи осуществляется с помощью простой процедуры, реализация этой записи остается скрытой от пользователя и автор разработки получает возможность изменять ее структуру; при этом программы, работающие с записью, остаются неизменными. В листинге 8.5 для хранения полей записи используются отдельные массивы. В качестве индекса каждого массива используется имя пользователя. Листинг 8.5. Использование массивов для создания записей (вариант 1) proc Emp_AddRecord {id name manager phone} { global employeelD employeeManager \ employeePhone employeeName
Глава 8. Массивы Tcl 179 set employeeID($name) $id set employeeManager($name) $manager set employeePhone($name) $phone set employeeName($id) $name } proc Emp_Manager {name} { global employeeManager return $employeeManager($name) J Массив employeeName поддерживает дополнительные ключи для обращения к базе. Он реализует отображение идентификационных номеров в имена. В результате пользователь, в распоряжении которого вместо имени оказывается идентификационный код, получает возможность извлечь требуемую информацию. В листинге 8.6 реализована та же база данных, но вся информация хранится в одном массиве, а структура индексов оказывается более сложной. Листинг 8.6. Использование массивов для создания записей (вариант 2) proc Emp_AddRecord {id name manager phone} { global employee set employee(id,$name) $id set employee(manager,$name) $manager set employee(phone,$name) $phone set employee(name,$id) $name } proc Emp_Manager {name} { global employee return $employee(manager,$name) 2 Еще один подход к решению данной задачи реализован в листинге 8.7. Каждый элемент массива представляет собой список полей, а для доступа к требуемому полю используется команда 1 index. Здесь обращение по идентификационному коду осуществляется по-другому. Если имена отличаются от идентификационных кодов, можно организовать перекрестные ссылки внутри одного массива. Листинг 8.7. Использование массивов для создания записей (вариант 3) proc Emp_AddRecord {id name manager phone} { global employee
180 Часть I. Основы Tcl set employee($name) [list $name $id $manager $phone] set employee($id) $name } proc Emp_Manager {name} { global employee return [lindex $employee($name) 2] 2 Различия между данными подходами несущественны. Конкретный выбор зависит от вкуса разработчика. Работать с одним массивом удобнее, так как при этом осуществляется управление меньшим числом переменных. Использование списков для хранения полей эффективно с точки зрения затрат памяти, поскольку число элементов массива сокращается. Однако при этом необходимо поддерживать смещения lindex. В любом случае реализация структуры должна быть скрыта от пользователей, а интерфейс должен быть реализован с помощью процедур. Стек Стек можно реализовать как на основе списка, так и на основе массива. При использовании списка накладные расходы, связанные с записью и извлечением данных, пропорциональны размеру стека. Если в стеке содержится небольшое количество элементов, с подобным эффектом можно мириться. При увеличении объема стека следует подумать о том, чтобы перейти к работе с массивом. Листинг 8.8. Реализация стека на базе списка proc Push { stack value } { upvar $stack list lappend list $value } proc Pop { stack } { upvar $stack list set value [lindex $list end] set list [lrange $list 0 [expr [llength $list]-2]] return $value 2 В примерах, приведенных в листингах 8.8 и 8.9, имя стека передается как параметр, а команда upvar используется для преобразования его в структуру,
Глава 8. Массивы Tcl 181 используемую в качестве стека. В листинге 8.8 для создания стека применяется список, а в листинге 8.9 — массив. Разработчик, использующий стек в своей программе, не обязан знать особенности его реализации. Если стек реализован на основе массива, один элемент массива используется для хранения информации об объеме стека, а в остальных элементах содержатся конкретные значения. Процедуры Push и Pop проверяют, существует ли указанный массив, используя для этого команду info exists. Когда процедура Push первый раз присваивает значение S(top), она создает переменную массива в области видимости вызывающего кода. Индексы массива используются двумя способами. В элементе, определяемом индексом top. хранятся сведения о глубине стека. Остальные индексы — это числа. Таким образом, выражение $S($S(top)) ссылается на вершину стека. Листинг 8.9. Реализация стека на базе массива proc Push { stack value } { upvar $stack S if {![info exists S(top)]} { set S(top) 0 } set S($S(top)) $value incr S(top) } proc Pop { stack } { upvar $stack S if {![info exists S(top)]} { return {} } if {$S(top) == 0} { return {} } else { incr S(top) -1 set x $S($S(top)) unset S($S(top)) return $x } } Список массивов Предположим, что у вас есть ряд массивов, в каждом из которых хранятся некоторые данные. Предположим также, что вам надо упорядочить
182 Часть I. Основы Tcl наборы данных. Один из способов решения этой задачи состоит в создании Tcl-сниска, содержащего имя каждого массива. В листинге 8.10 определены процедура RecordAppend, предназначенная для добавления массива к списку, и функция-итератор Recordlterate, которая позволяет применить некоторый сценарий к каждому массиву. В итераторе присутствует команда upvar. С ее помощью создается псевдоним для текущего массива. Для запуска сценария используется команда eval, которая будет подробно описана в главе 10. Tcl-команды, содержащиеся в сценарии script, ссылаются па массив с именем data. Листинг 8.10. Поддержка списка массивов proc RecordAppend {listName arrayName} { upvar $listName list lappend list $arrayName } proc Recordlterate {listName script} { upvar $listName list foreach arrayName $list { upvar #0 $arrayName data eval $script } _} Существуют и другие способы создания списка записей. Например, такую структуру можно реализовать, включая в состав каждой из записей ссылку на предшествующую и последующую запись. В листинге 8.11 показаны функция вставки и функция-итератор, которые созданы этим способом. Команда upvar создает псевдоним data для текущего массива. Цикл оканчивается тогда, когда выясняется, что следующий массив отсутствует. Создание псевдонима для несуществующей переменной -- вполне допустимая операция. Допустимо также изменять целевой объект для псевдонима, созданного с помощью команды upvar. В данном примере не учтена лишь необходимость инициализации первой записи, и для нее элемент next является пустой строкой. Листинг 8.11. Поддержка списка массивов proc Recordlnsert {recName afterThis} { upvar $recName record $afterThis after set record(next) $after(next) set after(next) $recName } proc Recordlterate {firstRecord body} {
Глава 8. Массивы Tcl 183 upvar #0 $firstRecord data while {[info exists data]} { eval $body upvar #0 $data(next) data } } Простая база данных Предположим, вам надо управлять набором записей, в каждой из которых содержится большой объем данных, а для поиска требуемой информации используются ключевые значения. Процедура добавления записи выглядит следующим образом: Db_Insert keylist datablob где datablob — это имя, значение списка, пригодное для передачи набору массивов, текст либо двоичные данные. Реализация Db_Insert может выглядеть следующим образом: foreach key $keylist { lappend Db($key) $datablob } При использовании данного подхода возникает проблема: информация, соответствующая каждому ключу, дублируется. Поэтому для решения этой задачи предпочтительнее использовать два массива. В одном из них содержатся все данные, помеченные простыми идентификаторами, которые генерируются автоматически. Во втором массиве хранится информация о связи между ключами и данными. Описанный подход иллюстрируется примером, показанным в листинге 8.12. Здесь используется механизм пространств имен, который будет рассмотрен в главе 14. Данный пример также показывает, насколько легко можно выводить структуры данных, записывая команды array set в файл, а затем загружать их с помощью команды source. Листинг 8.12. Простая база данных, предназначенная для хранения в памяти namespace eval db { variable data ;# Массив данных variable uid 0 ;# Индекс variable index ;# Перекрестные ссылки } proc db::insert {keylist datablob} { variable data
184 Часть I. Основы Tcl variable uid variable index set data([incr uid]) $datablob foreach key $keylist { lappend index($key) $uid } } proc db::get {key} { variable data variable index set result {} if {![info exist index($key)]} { return {} } foreach uid $index($key) { lappend result $data($uid) } return $result } proc db::save {filename} { variable uid set out [open $filename w] puts $out [list namespace eval db \ [list variable uid $uid]] puts $out [list array set db::data [array get db::data]] puts $out [list array set db::index [array get db::index]] close $out } proc db::load {filename} { source $filename 2 Альтернативы использованию массивов Несмотря на то что Tcl-массивы являются структурами общего назначения и обеспечивают достаточно высокий уровень гибкости, их нельзя рассматривать как универсальное решение всех задач, связанных с обработкой данных. Если вам надо создать сложные структуры данных, имеет смысл использовать средства, реализованные в библиотеках С и создать для них интерфейс с помощью команд Tcl. Так, например, в главе 47 будет рассмотрена структура данных blob, реализованная на С. Вы также можете исполь-
Глава 8. Массивы Tcl 185 зовать генератор кода SWIG, который позволяет формировать интерфейс к С API посредством Tcl-команд. Информация о SWIG приведена по адресу http://www.swig.org. При работе с Tcl можно использовать встроенную базу данных Metakit. Она предоставляет гораздо больше возможностей, чем "плоская"' база, реализованная в данной главе, однако Metakit все же не является базой данных SQL. База Metakit входит в состав Tclkit. Вы также можете использовать ее совместно с расширением mk4tcl. Tclkit и Metakit описываются в главе 22.
Глава 9 Работа с программами и файлами В данной главе рассказывается о запуске программ, работе с файловой системой и доступе к переменным сценария посредством массива env. В частности, речь пойдет о командах exec, file, open, close, read, write, puts, gets, flush, seek, Tcll, glob, pwd, cd, exit, pid и registry. 1 1РОЧИТАВ данную главу, вы узнаете, как осуществляется запуск программ и обеспечивается доступ к файловой системе средствами Tcl. Первоначально команды, предназначенные для выполнения программ и работы с файловой системой, были ориентированы на использование в системе Unix. В Tcl 7.5 они были адаптированы для Windows и Macintosh. Существуют платформенно- независимые средства именования файлов и выполнения различных действий с именами. Благодаря их наличию можно создавать сценарии, допускающие перенос с одной платформы на другую. В частности, с помощью этих средств можно создавать Tcl-сценарии, объединяющие различные программы в единый набор инструментов. В Tcl 8.4 была реализована поддержка 64-битовых файловых систем. Запуск программ с помощью команды exec Команда exec позволяет запускать программы из Tcl-сценариев1. Пример использования данной команды приведен ниже. !В отличие от команд exec, реализованных в оболочках Unix, Tcl-комаида exec не заменяет текущий процесс новым. Вместо этого она порождает новый процесс, в рамках которого и выполняется программа. — Прим. авт.
Глава 9. Работа с программами и файлами 187 set d [exec date] Значением, возвращаемым командой exec, являются данные, записанные программой в стандартный выходной поток. В то же время, если программа выводит информацию в стандартный поток ошибок либо завершается с ненулевым кодом состояния, то при выполнении команды exec возникает ошибка. Если вы собираетесь игнорировать состояние завершения программы или хотите использовать программу, в которой предусмотрен вывод в стандартный поток ошибок, вам надо маскировать ошибки с помощью команды catch. catch {exec программа параметр параметр} результат Команда exec поддерживает все средства перенаправления ввода-вы вода и конвейерной обработки. По умолчанию с каждым процессом связываю гея три потока: стандартный ввод, стандартный вывод и стандартный поток ошибок. Выполняя перенаправление, вы можете связать эти потоки с файлами или с другими каналами ввода-вывода, открытыми с помощью Тс1-комаиды open. Конвейерная обработка предполагает организацию цепочки процессов, в которой стандартный вывод предыдущей программы становится стандартным вводом следующей команды. По необходимости можно связать в цепочку любое количество программ. Листинг 9.1. Использование команды exec для организации конвейерной обработки set n [exec sort < /etc/passwd I uniq I wc -1 2> /dev/null] В примере, приведенном в листинге 9.1, команда exec используется для организации цепочки из трех программ. Первая программа, sort, получает входные данные из файла /etc/passwd. Выходные данные команды sort передаются команде uniq, которая исключает дублирование записей. Вывод uniq передается программе wc, которая выполняет подсчет строк. Для подавления сообщений об ошибках стандартный поток ошибок перенаправляется на устройство null. Правила перенаправления ввода-вывода, поддерживаемые командой exec, описаны в табл. 9.1. Таблица 9.1. Элементы команды exec, используемые для перенаправления ввода- вывода -keepnewline (Указывается перед остальными параметрами.) Не удалять завершающий символ перевода строки в составе результатов I Направляет стандартный вывод одного процесса другому процессу
188 Часть I. Основы Tcl Окончание табл. 9.1 |& < имя_файла <@ идентификатор_файла << значение > имя_файла 2> имя_файла >& имя_файла » имя_файла 2» имя_файла >>& имя_файла >@ идентификатор_файла 2>® идентификатор_файла >&@ идентификатор_файла & Направляет другому процессу как стандартный вывод, так и стандартный поток ошибок Организует прием входных данных из указанного файла Организует прием входных данных из канала ввода- вывода, определяемого указанным идентификатором Оформляет указанное значение в качестве входных данных Заменяет содержимое указанного файла данными, выводимыми в стандартный выходной поток Заменяет содержимое указанного файла данными, выводимыми в стандартный поток ошибок Заменяет содержимое указанного файла данными, выводимыми как в стандартный выходной поток, так и в стандартный поток ошибок Добавляет данные, выводимые в стандартный выходной поток, к содержимому указанного файла Добавляет данные, выводимые в стандартный поток ошибок, к содержимому указанного файла Добавляет к содержимому указанного файла данные, выводимые как в стандартный выходной поток, так и в стандартный поток ошибок Перенаправляет стандартный вывод в канал, определяемый указанным идентификатором Перенаправляет стандартный поток ошибок в канал, определяемый указанным идентификатором Перенаправляет в канал, определяемый указанным идентификатором, как стандартный выходной поток, так и стандартный поток ошибок Если данный символ указан в качестве последнего параметра, он означает выполнение в фоновом режиме Если команда завершается символом &, программа выполняется в фоновом режиме. При этом exec возвращает идентификатор процесса. В противном случае выполнение команды exec блокируется до завершения программы, и данные, записанные в стандартный выходной поток, становятся значением, возвращаемым exec. Завершающий символ новой строки удаляется. Чтобы это не происходило, в качестве первого параметра exec надо указать опцию -keepnewline.
Глава 9. Работа с программами и файлами 189 Внимательно изучив правила перенаправления ввода-вывода, нетрудно заметить, что для их формирования используется несколько основных базовых блоков. Символ I задает конвейерную обработку, > — вывод, а < — ввод данных. Для объединения стандартного потока ошибок со стандартным выводом используется символ &. Для перенаправления стандартного потока ошибок применяется последовательность 2>. Для указания собственных каналов ввода-вывода надо указать символ @. Переменная auto_noexec Оболочка Tcl настраивается так, что при интерактивной работе неизвестные Tcl-команды интерпретируются как имена программ. Так, например, вы можете получить информацию о содержимом каталога, задав команду Is вместо exec Is. В одних случаях это удобно, в других может приводить к непредсказуемым результатам. Для того чтобы отключить автоматический запуск программы, надо определить переменную auto.noexec. set auto.noexec любое„значение Особенности выполнения команды exec в системе Windows Windows 3.1 имеет ряд характерных особенностей, связанных с использованием консольного режима, 16- и 32-битовых программ. Конвейерная обработка в этой системе эмулируется путем записи выходных данных процесса во временный файл и передачи их следующему процессу. Если команда exec выполняется в Windows некорректно, причиной, скорее всего, являются характерные особенности данной системы. В Windows 98 большинство проблем, связанных с выполнением exec, устранено. В Windows NT, 2000 и ХР exec работает вполне удовлетворительно. Последней реализацией, в которой была предусмотрена поддержка Windows 3.1, является Tcl 8.0р2. В комплект ее поставки входит файл Tcll680.dll, предназначенный для работы с подсистемой Win32s. Если вы скопируете этот файл в каталог, содержащий другие динамические библиотеки Tcl, вы сможете обеспечить работу с Windows 3.1 некоторых более поздних версий Tcl. В Tcl 8.3 поддержка Win32s полностью прекратилась; в этой же реализации были добавлены средства для работы с системой Windows XP-64. AppleScript в системе Macintosh Команда exec не предназначена для работы в Macintosh. Вместо нее используется расширение AppleScript, позволяющее управлять приложения-
190 Часть I. Основы Tcl ми Macintosh. Документация на AppleScript находится в файле AppleScript. html, поставляемом в составе дистрибутивного пакета Tcl. Для загрузки AppleScript используется команда package require. package require Tclapplescript AppleScript junk => bad option "junk": must be compile, decompile, delete, execute, info, load, run, or store. Команда file Команда file позволяет получить информацию о состоянии файлов в файловой системе. Например, вы можете выяснить, существует ли файл, каков его тип, и определить значение других его атрибутов. Существуют также платформенно-независимые средства для работы с файлами. В табл. 9.2 описаны различные варианты команды file. Они будут подробно обсуждаться далее в этой главе. С момента появления команды file в нее было добавлено несколько операций. Для каждой из них в таблице указана версия Tcl, с которой началась поддержка операции. Таблица 9.2. Операции, реализуемые с помощью команды file Возвращает время обращения к файлу в виде строки, содержащей десятичное значение. Если в составе команды указано время, оно устанавливается в качестве времени обращения к файлу Запрашивает или устанавливает атрибуты файла (Tcl 8.0) file atime имя ?время? file attributes имя ?опция? ?значение? file channels ?шаблон? file copy ?-force? исходный_файл целевой_файл file delete ?-force? имя file dirname имя file executable имя Возвращает открытые каналы для текущего интерпретатора. В большинстве случаев осуществляется фильтрация с помощью шаблона. Сравнение с шаблоном выполняется по принципу, используемому в команде glob (Tcl 8.3) Копирует исходный файл в целевой. В качестве исходного и целевого файла могут быть указаны каталоги (Tcl 7.6) Удаляет файл с указанным именем (Tcl 7.6) Возвращает родительский каталог для указанного файла Возвращает значение 1, если указанный файл является исполняемым, в противном случае возвращается 0
Глава 9. Работа с программами и файлами 191 Продолжение табл. 9.2 file exists имя Возвращает значение 1, если файл или каталог с указанным именем существует, в противном случае возвращается О file extension имя Возвращается часть имени файла, следующая за последней точкой. Точка включается в состав возвращаемого значения file isdirectory Возвращает значение 1, если имя принадлежит катало- имя гу, в противном случае возвращается О file isfile имя Возвращает значение 1, если имя не принадлежит ни каталогу, ни символьной ссылке, ни устройству, в противном случае возвращается О file join путь путь Объединяет компоненты пути в новый путь (Tcl 7.5) file link ?тип? имя Возвращает указанную ссылку. Если в команде задан ?целевой_объект? целевой обект, создается ссылка на него. Тип может быть задан посредством опции -hard или -symbolic (Tcl 8.4) file lstat имя Помещает атрибуты заданной ссылки в указанную непеременная ременную file mkdir имя Создает каталог с указанным именем (Tcl 7.6) file mtime имя Возвращает время модификации указанного файла. Ес- ?время? ли в составе команды задано время, оно устанавливается в качестве нового времени модификации file nativename имя Возвращает версию имени для текущей платформы (Тк 8.0) file normalize имя Возвращает абсолютный путь для имени, удаляя компоненты /, /., и /.., не являющиеся необходимыми (Tcl 8.4) file owned имя Возвращает значение 1, если текущий пользователь является владельцем файла. В противном случае возвращается 0 file pathtype имя Возвращает значение relative, absolute или volumerelative (Tcl 7.5) file readable имя Возвращает значение 1, если для данного файла установлены права на чтение. В противном случае возвращается 0 file readlink имя Возвращает содержимое указанной символьной ссылки
192 Часть I. Основы Tcl Окончание табл. 9.2 file rename ?-force? старое_имя новое_имя file rootname имя file separator ?имя? file size имя file split имя file stat имя массив file system имя file tail имя file type имя file volumes имя file writable имя Изменяет старое имя на новое (Tcl 7.6) Возвращает все компоненты имени за исключением расширения (т.е. до последней точки в имени). Точка не включается в возвращаемое значение Возвращает символ, который в данной операционной системе используется по умолчанию в качестве разделителя для имени файла. Если имя задано, для него возвращается символ-разделитель (Tcl 8.4) Возвращает число байтов в имени Разделяет имя на компоненты пути (Tcl 7.5) Помещает атрибуты указанного объекта в массив. Перечень атрибутов приведен в табл. 9.3 Возвращает характеристики файловой системы (например, native или vfs) и платформенно-ориентирован- ный тип (например, NTFS или FAT32) (Tcl 8.4) Возвращает последний компонент пути Возвращает идентификатор типа file, directory, characterSpecial, blockSpecial, fifo, link или socket Возвращает доступные тома файловой системы для данного компьютера. В среде Unix это всегда /. В Windows может быть возвращено значение типа {а: / с: /}. (Tcl 8.3) Возвращает значение 1, если для указанного файла установлены права на запись. В противном случае возвращается О Имена файлов на различных платформах В системах Unix, Windows и Macintosh действуют различные правила именования файлов. В Unix компоненты пути к файлу разделяются косой чертой (/). в Macintosh ■■— двоеточием (:), а в Windows для этой цели используется обратная косая черта (\). Кроме того, относительные и абсолютные имена интерпретируются по-разному. В качестве примера ниже представлены записи абсолютного имени в библиотеке Tcl-сценариев ($tcl_library) в системах Macintosh. Windows и Unix.
Глава 9. Работа с программами и файлами 193 Disk:System Folder:Extensions:Tool Command Language:tcl7.6 c:\Program Files\Tcl\lib\Tcl7.6 /usr/local/tcl/lib/tcl7.6 В Tcl предусмотрены платформенно-независимые операции с именами файлов и путей. Операции с файлами, описанные в данной главе, позволяют использовать либо формат, специфический для конкретной операционной системы, либо соглашения об именовании, принятые в среде Unix. Большие неудобства доставляет обратная косая черта, используемая для разделения компонентов пути в системе Windows. Как вы уже знаете, этот символ имеет в языке Tcl специальное значение. Поэтому в Tcl предусмотрена замена этого символа обычной косой чертой. с:/Program Files/Tcl/lib/Tcl7.6 В ряде ситуаций формат записи Unix применить невозможно и приходится использовать соглашения об именовании для конкретной системы. Так, например, в Macintosh средства Tcl и Тк могут находиться в каталоге, в имени которого есть косая черта. В этом случае при обращении к соответствующим файлам можно использовать лишь путь, сформированный по правилам Macintosh. Disk:Applications:Tcl/Tk~4.2 При работе с файлами необходимо также внимательно следить за именами, начинающимися с символов //. В Windows двойная косая черта в начале пути означает ссылку на файл, находящийся на другом компьютере. Чтобы исключить возможность случайного формирования сетевого имени, надо использовать команду file join, которая будет описана ниже. Если возникнет необходимость организовать взаимодействие с внешними программами, вам придется формировать имя по соглашениям той платформы, на которой находится соответствующий файл. Для этого также используется команда file join. Кроме того, вы можете преобразовывать имена Unix в имена, специфические для конкретной платформы, с помощью команды file nativename. Некоторые операции, реализуемые посредством команды file, действуют не с самими файлами, а лишь с их именами. Так, например, команды dirname, extension, join, normalize, pathtype, rootname, split и tail могут быть выполнены над любыми строками подходящего формата. При этом не обязательно, чтобы соответствующий файл существовал. Формирование пути: команда file join Если вы формируете путь к файл^у, объединяя его компоненты в строку и разделяя их косой чертой, это может стать причиной возникновения проблем. Если фрагменты пути представлены в формате, специфическом для
194 Часть I. Основы Tcl конкретной системы (например, Macintosh или Windows), то, используя косую черту для их разделения, мы получим формат, недопустимый в этих операционных средах. Аналогичная проблема возникает при вводе имен файлов пользователями. Как правило, пользователь задает имена файлов, сформированные по правилам той операционной системы, в которой он работает. Например, если содержимое $tcl_library составлено по соглашениям Windows, то путь, полученный в результате выполнения приведенной ниже команды, в системе Macintosh будет некорректен. set file $tcl_library/init.tcl Для формирования имен файлов следует использовать команду file join. Команда file join представляет собой платформенно-независимый инструмент формирования имен файлов. Приведенная ниже строка кода возвращает имя файла init.tcl в формате текущей операционной системы, set file [file join $tcl_library init.tcl] С помощью команды file join можно объединять любое число компонентов пути к файлу. При работе с данной командой следует учитывать ее важную особенность: абсолютный путь заменяет собой все предшествующие компоненты. Например, в системе Unix выражение /b/с представляет собой абсолютный путь. Он записывается вместо всех параметров команды file join, указанных до него. file join a b/c d => a/b/c/d file join a /b/c d => /b/c/d В системе Macintosh относительный путь, в отличие от абсолютного, начинается с двоеточия. Для того чтобы определить относительный путь, надо добавить двоеточие к первому компоненту, при этом он будет интерпретироваться как спецификатор тома. В приведенном ниже примере относительные компоненты объединяются в относительный путь. file join a :b:c d => :a:b:c:d В следующем примере b:c — это абсолютный путь а Ь: — спецификатор тома. Абсолютное имя переопределяет указанное ранее относительное имя. file join a b:c d => b:c:d При выполнении операции file join происходит также преобразование записи Unix в формат конкретной операционной системы. Ниже приведен результат выполнения команды file join в системе Macintosh.
Глава 9. Работа с программами и файлами 195 file join /usr/local/lib => usr:local:lib Выделение компонентов пути: команды split, dirname, tail Команда file split разделяет путь к файлу на отдельные компоненты. Она выполняет действия, противоположные команде file join. Операция split автоматически определяет, представлен ли путь в формате используемой операционной системы или в формате Unix. Данные, полученные в результате выполнения команды file split, содержат специальные признаки, позволяющие исключить неоднозначную интерпретацию при последующем применении к ним команды file join. Например, при обработке в системе Macintosh имен путей, созданных по соглашениям Unix, разделителем является косая черта. Первым из компонентов, полученных в результате выполнения file split, является спецификатор тома (Disk:). file split "/Disk/System Folder/Extensions" => Disk: {System Folder} Extensions Чаще всего разделение пути к файлу на компоненты осуществляется для того, чтобы выделить имена каталога и файла. Эту задачу можно решить непосредственно, выполняя операции dirname и tail. Операция tail возвращает последний компонент пути, а операция dirname — каталог, в котором находится этот компонент. file dirname /a/b/c => /a/b file tail /a/b/c => с Если путь состоит из одного компонента, операция dirname возвращает указатель на текущий каталог. В Unix и Windows dirname возващает точку (.), а в системе Macintosh — двоеточие (:). Операции extension и root также выполняют противоположные действия. Команда extension возвращает последнюю точку и все следующие за ней символы до конца строки. Операция root возвращает часть имени пути до последней точки. file root /a/b.с => /a/b file extension /a/b.с => .с
196 Часть I. Основы Tcl Действия с файлами и каталогами В Tcl 7.6 были реализованы операции копирования, удаления, переименования файлов и создания каталогов. В предыдущих версиях Tcl для выполнения этих действий приходилось вызывать с помощью команды exec другие программы. Исключением было выполнение сценариев в системе Macintosh, где ср, rm, mv, mkdir и rmdir являются встроенными операциями. В настоящее время в Macintosh эти команды не поддерживаются. Ниже описаны операции, реализуемые с помощью команды file, предназначенные для обработки файлов, независимо от конкретной платформы. В команде file отсутствует непосредственная поддержка шаблонов. Для осуществления групповых операций с файлами надо использовать команду glob, описанную далее в этой главе. Команда glob возвращает список имен файлов, соответствующих шаблону. Копирование файлов Команда file copy позволяет копировать файлы и каталоги. В приведенном ниже примере f ilel копируется в f ile2. Если f ile2 существует, возникает ошибка. Чтобы исключить возникновение ошибки, надо указать опцию -force. file copy ?-force? filel file2 С помощью рассматриваемой команды можно копировать несколько файлов в целевой каталог. Опция -force указывает на то, что одноименные файлы в целевом каталоге должны быть заменены новыми. file copy ?-force? файл_1 файл_2 ... каталог Команда file copy позволяет осуществлять рекурсивное копирование каталогов. Опция -force указывает на то, что копируемые файлы заменяют одноименные файлы в каталоге dir2 или в его подкаталогах. file copy ?-force? dirl dir2 Создание каталогов Команда file mkdir создает один или несколько каталогов. file mkdir каталог каталог ... Если какой-либо из указанных каталогов уже существует, ошибка не возникает. При отсутствии заданного каталога создается не только он, но, если это необходимо, и родительские каталоги. Таким образом, после выполнения
Глава 9. Работа с программами и файлами 197 операции mkdir наличие каталога с требуемым именем гарантируется. Пред- оположим, что в каталоге /tmp нет ни одного подкаталога. При выполнении следующей команды создаются каталоги /tmp/subl и /tmp/subl/sub2: file mkdir /tmp/subl/sub2 В команде file mkdir опция -force не предусмотрена, поэтому при выполнении приведенной ниже команды создается не только каталог oops, но и каталог с именем -force. file mkdir -force oops Фиксированные и символьные ссылки Команда file link позволяет работать со ссылками. Фиксированными ссылками называются объекты, находящиеся в каталоге и непосредственно указывающие на существующий файл или каталог. Символьная ссылка представляет собой файл, содержащий имя другого файла или каталога. При обращении к ссылке происходит обращение к файлу, на который она указывает. В различных операционных системах ссылки поддерживаются по- разному. В Unix поддерживаются оба типа ссылок. В Macintosh поддерживаются только символьные ссылки (они называются псевдонимами). В Windows 95/98/МЕ ссылки не поддерживаются вовсе, а в Windows NT/2000/XP допустимы символьные ссылки на каталоги и фиксированные ссылки на файлы. Если при вызове команды file link указан один параметр, эта команда возвращает значение символьной ссылки. В случае, когда указанный файл не является ссылкой, возникает ошибка. Если этой команде передаются два параметра, то первый из них интерпретируется как имя ссылки, а второй — как имя файла, на который должна указывать ссылка. Если вы не укажете опцию -hard или -symbolic, будет создана ссылка, используемая по умолчанию на текущей платформе. file link ссылка существующий_файл Удаление файлов Команда file delete удаляет файлы и каталоги. Если файл отсутствует, ошибка не возникает. Если каталог не пуст, он не удаляется. Чтобы задать удаление непустых каталогов, надо указать опцию -force. При этом удаляются как каталог, заданный в качестве параметра, так и его подкаталоги. file delete ?-force? имя имя ... Для того чтобы удалить файл или каталог с именем -force, надо указать перед -force несуществующий файл. В этом случае последовательность
198 Часть I. Основы Tcl символов -force будет интерпретирована не как опция, а как имя файла или каталога. (Заметьте, что использование параметров -force -force не позволит получить желаемый эффект.) file delete xyzzy -force Переименование файлов и каталогов При выполнении операции file rename имя файла изменяется. Опция -force указывает на то. что переименование должно осуществляться даже в том случае, когда файл с целевым именем уже существует. file rename ?-force? старое_имя новое_имя Команду file rename удобно использовать для обновления существующих файлов. Это происходит следующим образом. Новая версия файла создается как временный файл, затем с помощью команды file rename старая версия заменяется новой. В результате ни одна из программ, обращающихся к файлу, не увидит его нового содержимого до тех пор, пока оно не будет полностью сформировано. Атрибуты файлов Некоторые варианты команды file, в частности atime, executable, exists, isdirectory, isfile, mtime, owned, readable, readlink, size и type, возвращают атрибуты файлов. Конкретные действия, выполняемые каждой из этих команд, описаны в табл. 9.2. В примере, приведенном в листинге 9.2, операция file mtime используется для сравнения времени модификации двух файлов. Если вы когда-либо передавали программе awk результаты выполнения команды Is -1, чтобы использовать их в сценарии оболочки, вы по достоинству оцените данный пример. Листинг 9.2. Сравнение времени модификации двух файлов proc newer { filel file2 } { if {![file exists $file2]} { return 1 } else { # Предполагается, что filel существует expr {[file mtime $filel] > [file mtime $file2]} } }
Глава 9. Работа с программами и файлами 199 При вызове операций mtime и atime можно указать необязательный параметр для установки соответствующих атрибутов файла. В этом случае данные команды работают подобно команде Unix touch. Операции stat и lstat возвращают набор атрибутов файла. Третий параметр интерпретируется как имя массива, элементы которого инициализируются значениями соответствующих атрибутов. Если файл представляет собой символьную ссылку, операция lstat возвращает информацию о самой ссылке, а операция stat предоставляет сведения о том объекте, на который указывает ссылка. Назначение элементов массива описано в табл. 9.3. Все значения, за исключением элемента type, являются строковыми представлениями десятичных чисел. Элемент type содержит значение, возвращаемое операцией file type. Имена элементов формируются на базе системного вызова Unix stat. Для того чтобы получить атрибуты для других платформ, следует использовать команду file attributes. Таблица 9.3. Элементы массива, определенные с помощью операции file stat atime Время последнего обращения в секундах ctime Время последнего изменения (не путать с временем создания) в секундах dev Целочисленный идентификатор устройства gid Целочисленный идентификатор группы ino Номер файла (индексный дескриптор файла) mode Биты прав доступа mtime Время последней модификации в секундах nlink Число ссылок на файл size Число байтов в файле type Значение file, directory, characterSpecial, blockSpecial, fifо, link или socket uid Целочисленный идентификатор владельца В листинге 9.3 используются атрибуты файла dev и ino, описывающие соответственно устройство и индексный дескриптор файла. Полученная информация позволяет определить, указывают ли два пути на один и тот же файл. Атрибуты являются специфическими для системы Unix. В Windows и Macintosh они не определены. Листинг 9.3. Код, предназначенный для распознавания двух путей к одному и тому же файлу proc fileeq { pathl path2 } { file stat $pathl statl
200 Часть I. Основы Tcl file stat $path2 stat2 expr {$statl(ino) == $stat2(ino) && \ $statl(dev) == $stat2(dev)} J В Tcl 8.0 была реализована операция file attributes, предоставляющая доступ к атрибутам, специфическим для конкретной платформы. Эта операция предназначена как для получения атрибутов, так и для их установки. При вызове команды ей передаются пары опция-значение. При отсутствии опций возвращаются текущие значения. file attributes book.doc => -creator FRAM -hidden 0 -readonly 0 -type MAKR Атрибуты для системы Macintosh описаны в табл. 9.4. Четырехсимволь- ные коды типов, используемые в системе Macintosh, рассматриваются в главе 39. Если указана одна опция, возвращается только ее значение. file attributes book.doc -readonly => 0 Для того чтобы модифицировать атрибуты, надо указать одну или несколько пар опция-значение. Если пользователь не имеет достаточных полномочий, то при попытке изменить значение атрибута возникает ошибка. file attributes book.doc -readonly 1 -hidden 0 Таблица 9.4. Атрибуты файлов, специфические для конкретной платформы -permissions режим Биты прав доступа к файлу. Режим — это восьмеричное число или символьное представление битов (например, а+х), определяемых системным вызовом chmod, либо строка в формате rwxrwxrwx, отображаемом командой Is, которая содержит 9 символов (Unix) -group идентификатор Группа-владелец файла (Unix) -owner идентификатор Пользователь-владелец файла (Unix) -archive Бит архивного файла, устанавливаемый программой логическое_значение backup (Windows) -system Если данное значение установлено, удалить файл нель- логическое_значение зя (Windows) -longname Длинная (расширенная) версия пути. Только для чтения (Windows) -shortname Короткая (8.3) версия пути. Только для чтения (Windows)
Глава 9. Работа с программами и файлами 201 Окончание табл. 9.4 -hidden логическое„значение -readonly логическое„значение -creator тип -type тип Если данный признак установлен, информация о файле не отображается при выводе содержимого каталога (Windows, Macintosh) Если данный признак установлен, запись в файл запрещена (Windows, Macintosh) Четырехсимвольный код типа приложения, при выполнении которого был создан файл (Macintosh) Четырехсимвольный код типа (Macintosh) Использование команд ввода-вывода В последующих разделах рассказывается, как открывать файлы, читать содержащуюся в них информацию и записывать новые данные. Базовая модель работы с файлом выглядит следующим образом: программа открывает файл, читает или записывает информацию, а затем закрывает файл. Команды, описанные в данной главе, также позволяют работать с гнездами (socket). Подробно реализация сетевого обмена с использованием гнезд описана в главе 17, а рассмотрению ввода-вывода данных, управляемого событиями, посвящена глава 16. В табл. 9.5 приведены основные команды, используемый при обмене данными с файлами. Таблица 9.5. Tcl-команды, используемые для работы с файлами open объект ?доступ? ?права? puts ?-nonewline? ?канал? строка gets канал ? имя_ переменной ? read канал ? число_байтов? read -nonewline канал Tcll канал seek канал смещение ?точка„отсчета? eof канал flush канал close канал Возвращает идентификатор канала для обмена с файлом или для конвейерной обработки Записывает строку Читает строку Читает указанное число байтов или все данные из канала Читает все данные из канала, удаляя завершающий символ \п Возвращает текущее смещение Устанавливает величину смещения. В качестве точки отсчета могут быть заданы значения start, current или end Осуществляет проверку на достижение конца файла Записывает содержимое буферов в канал Закрывает канал ввода-вывода
202 Часть I. Основы Tcl Открытие файлов Команда open создает канал ввода-вывода и связывает его либо с файлом, либо с некоторым процессом. В результате выполнения данная команда возвращает идентификатор канала. Результат выполнения команды open следует записать в переменную и использовать значение этой переменной так же, как идентификаторы stdout, stdin и stderr. Формат вызова команды open приведен ниже. open объект ?доступ? ?'права? Первым параметром является имя файла или спецификатор процесса. Второй параметр может представлять собой либо короткую последовательность символов (в этом случае обеспечивается совместимость с библиотечной процедурой f open), либо список флагов POSIX. Символы, используемые для описания особенностей доступа, приведены в табл. 9.6, а флаги POSIX — в табл. 9.7. Если параметр, определяющий характеристики доступа, не указан, принимаются значения по умолчанию. Листинг 9.4. Открытие файла для записи set fileld [open /tmp/foo w 0600] puts $fileld "Hello, foo!" close $fileld Третий параметр команды open — это значение, определяющее права доступа к вновь создаваемому файлу. В системе Unix используются три группы признаков: для владельца файла, для группы и для остальных пользователей. Каждая группа состоит из трех битов. Каждый из битов в группе задает разрешение на чтение, запись и выполнение файла. Обычно значения признаков указываются в виде восьмеричного числа, начинающегося с нуля. Группа, соответствующая каждому типу пользователей, представляется одной восьмеричной цифрой. По умолчанию устанавливаются права доступа 0666, что соответствует разрешению на чтение и запись для всех групп пользователей. В примере, приведенном в листинге 9.4, устанавливаются права доступа 0600, т.е. чтение и запись разрешены только владельцу файла. Значение 0775 соответствует разрешению на чтение, запись и выполнение файла для владельца и группы; остальным пользователям разрешается только читать содержимое файла и запускать файл на выполнение. Старшие биты соответствуют специальным свойствам файла. Подробно эти признаки рассмотрены в разделе справочной системе Unix, посвященном команде chmod.
Глава 9. Работа с программами и файлами 203 Таблица 9.6. Параметр команды open, управляющий доступом г Открыть для чтения. Файл должен существовать г+ Открыть для чтения и записи. Файл должен существовать w Открыть для записи. Если файл существует, его содержимое усекается. Если файл отсутствует, создать его w+ Открыть для чтения и записи. Файл создается либо его содержимое усекается а Открыть для записи. Данные записываются в конец файла а+ Открыть для чтения и записи. Данные записываются в конец файла Таблица 9.7. Флаги P0SIX, управляющие доступом RD0NLY Открыть для чтения WR0NLY Открыть для записи RDWR Открыть для чтения и записи APPEND Открыть в режиме, позволяющем записывать данные в существующий файл CREAT Если файл отсутствует, создать его EXCL Если флаг CREAT также указан, файл не должен существовать N0CTTY Запрещает использовать терминальное устройство в роли управляющего терминала N0NBL0CK В процессе открытия блокирование не производится TRUNC Если файл существует, его содержимое усекается Приведенная ниже строка кода иллюстрирует использование списка флагов P0SIX для открытия файла. В данном примере файл открывается для записи, по мере необходимости файл создается и не усекается. Более простая форма записи не позволяет определять такие детали доступа. set fileld [open /tmp/bar {RDWR CREAT}] Листинг 9.5. Обработка ошибок при открытии файла if [catch {open /tmp/data r} fileld] { puts stderr "Cannot open /tmp/data: $fileld" } else { # Чтение и обработка содержимого файла close $fileld }
204 Часть I. Основы Tcl Ошибки, возникающие при выполнении команды open, должны быть перехвачены. Открывая файлы, следует следить за возникающими ошибками. В примере, приведенном в листинге 9.5, демонстрируется использование выражения catch при открытии файла. Как вы помните, при наличии ошибки команда catch возвращает 1; в противном случае возвращается нулевое значение. Второй параметр, передаваемый команде catch, интерпретируется как имя перехменной. При возникновении ошибки в эту переменную записывается соответствующее сообщение. В случае нормального развития событий в переменную помещается результат выполнения команды. Создание канала связи с процессом Для того чтобы открыть с помощью команды open канал связи с процессом, надо сформировать первый параметр так, чтобы его значение начиналось с символа I. Остальная часть спецификации процесса интерпретируется так же, как и при выполнении программы exec; в частности, вы можете перенаправлять ввод и вывод. Второй параметр указывает на то, как расположены в цепочке текущий процесс и процесс, указанный в команде open. В примере, показанном в листинге 9.6, программе sort передается файл паролей, а команда split используется для разделения полученных строк на элементы. Листинг 9.6. Открытие канала связи с процессом set input [open "I sort /etc/passwd" r] set contents [split [read $input] \n] close $input Канал можно открыть для чтения и записи, указав в качестве режима доступа символы г+. В этом случае необходимо уделить внимание буферизации. После выполнения команды puts данные могут оставаться в буфере. Перед тем как принимать данные из канала, следует использовать команду flush, чтобы принудительно передать информацию процессу. Для управления буферизацией можно также использовать команду iconfigure, речь о которой пойдет в главе 16. Заметьте, что каналы для чтения и записи не работают в Windows 3.1, так как в этой системе передача данных по цепочке эмулируется с помощью файлов. При конвейерной обработке удобно использовать средства ввода-вывода, управляемые событиями. В то время как некоторый процесс подготавливает данные, связанная с ним программа может выполнять произвольные действия и обрабатывать информацию только при ее по-
Глава 9. Работа с программами и файлами 205 ступлении. Подробно ввод-вывод, управляемый событиями, рассматривается в главе 16. Расширение Expect Если вы собираетесь выполнять сложные действия с внешними приложениями, вам следует рассмотреть целесообразность использования расширения Expect, которое предоставляет гораздо более мощный интерфейс, чем обычная конвейерная обработка. Expect реализует Tcl-команды, используемые для управления интерактивными приложениями. В процессе работы часто возникает необходимость автоматизации различных приложений, например ssh, Tclnet или тестируемых программ. Средствами Tcl можно поддерживать простые сеансы FTP и Tclnet, а также многие приложения, управляемые из командной строки. Expect предоставляет дополнительную возможность контроля на уровне терминала, что может быть очень удобным при работе с прикладными программами. В состав некоторых систем Expect входит как Tcl-оболочка с именем expect. Этот инструмент также может поставляться как приложение, динамически загружаемое в оболочку с помощью команды package require Expect Продукт Expect был разработан в Национальном институте стандартов и технологий (NIST — National Institute of Standards and Technology); он описан в книге Exploring Expect (Libes, O'Reilly & Associates, Inc., 1995). Код Expect записан на компакт-диск, прилагаемый к данной книге, кроме того, его можно найти но адресу http://expect.nist.gov/. Чтение и запись данных Запуская программу, можно быть уверенным в том, что для нее уже открыт ряд каналов ввода-вывода, в частности стандартные ввод, вывод и поток ошибок. Для обозначения этих каналов используются идентификаторы stdin, stdout и stderr. Дескрипторы каналов ввода-вывода возвращаются в результате выполнения команд open и socket (о команде socket рассказывается в главе 17). В некоторых случаях стандартные каналы ввода-вывода недоступны. В частности, эти каналы не предоставляет оболочка wish в системах Windows и Macintosh. Некоторые оконные диспетчеры при запуске программы через меню закрывают стандартные каналы. В случае необходимости вы также можете закрыть стандартные каналы самостоятельно с помощью команды close.
206 Часть I. Основы Tcl Команды puts и gets Команда puts записывает указанную строку с завершающим символом перевода строки в выходной поток. Команда puts имеет ряд особенностей. При указании параметра -nonewline данная команда не добавляет символ перевода строки. Идентификатор канала указывать не обязательно. Если он отсутствует, то по умолчанию принимается значение stdout. Заметьте также, что для принудительного вывода содержимого буфера может использоваться команда flush. В листинге 9.7 показан пример кода, в котором применяется опция -nonewline, строка символов выводится без указания потока, а для вывода данных из буфера используется команда flush. Листинг 9.7. Приглашение к вводу данных puts -nonewline "Enter value: " flush stdout ;# Все данные должны быть выведены set answer [gets stdin] Команда gets принимает строку, вводимую пользователем. Она может быть представлена в одном из двух форматов. В листинге 9.7 использовалась команда gets с одним параметром. Команда, записанная в таком виде, возвращает строку, которая была прочитана из указанного входного потока. При выполнении команды gets полученные данные возвращаются без завершающего символа новой строки. По достижении конца файла возвращается пустая строка. Команда eof позволяет отличить пустую строку от конца файла. Если данная команда возвращает значение 1, это означает, что достигнут конец файла. Если при вызове команды gets указан второй параметр, полученная строка записывается в переменную, а команда возвращает число прочитанных байтов. При этом завершающий символ перевода строки не учитывается. По достижении конца файла возвращается значение —1. Листинг 9.8. Цикл чтения данных с использованием gets while {[gets $channel line] >= 0} { # Обработка строки } close $channel Команда read Команда read выполняет чтение блока данных; в большинстве случаев эта команда обеспечивает высокую эффективность получения информации. Команду read можно записать двумя способами: вы можете указать опцию
Глава 9. Работа с программами и файлами 207 -nonewline либо задать число байтов с помощью параметров, однако одновременно оба этих подхода использовать нельзя. Если число байтов для чтения не указано, читается или возвращается весь файл или та его часть, которая еще осталась во входном потоке. Опция -nonewline указывает на то, что завершающий символ новой строки должен быть удален. При наличии параметра, указывающего число байтов для ввода, читается заданный объем данных, либо, если в потоке находится меньшее количество байтов, читается вся оставшаяся информация. В этом случае завершающий символ перевода строки сохраняется. Листинг 9.9. Цикл чтения данных с использованием команд read и split foreach line [split [read $channel] \n] { # Обработка строки } close $channel При работе с файлами среднего размера цикл, в котором применяется команда read, выполняется приблизительно па 10% быстрее, чем при использовании команды gets. В этом случае команда read читает и возвращает весь файл, а команда split разделяет его на отдельные элементы. На базе каждого полученного элемента формируется отдельная строка. Для файлов малого размера (объемом меньше 1 Кбайт) способ обработки не имеет существенного значения. При обработке большого файла (объемом порядка нескольких мегабайтов) можно использовать команду read для чтения содержимого файла по частям. Окончание строки символов Tcl автоматически определяет, какому из соглашений соответствует окончание строки. В системе Unix строка текста завершается символом перевода строки (\п). В Macintosh для этой цели используется символ возврата каретки (\г). В Windows признаком завершения являются два символа: возврат каретки и перевод строки (\r\n). Tcl поддерживает все соглашения; более того, в одном файле могут использоваться различные признаки завершения строки. Все варианты признаков окончания строк при чтении преобразуются в стандарт Unix. т.е. строки текста всегда завершаются символом перевода строк (\п). По этим правилам работает как команда read, так и команда gets. Вывод текста осуществляется в формате, специфическом для текущей платформы. Благодаря автоматической поддержке форматов задача преобразования файла в формат для текущей платформы становится предельно простой. Вам достаточно прочитать данные и записать их в файл.
208 Часть I. Основы Tcl puts -nonewline $out [read $in] Запретить преобразование форматов позволяет команда fconfigure, которая подробно описывается в главе 16. В листинге 9.10 приведен код процедуры File.Copy, которая преобразует файлы в формат текущей платформы. Объем процедуры достаточно велик вследствие поддержки каталогов. Листинг 9.10. Копирование файлов с преобразованием в формат текущей платформы proc File_Copy {sre dest} { if {[file isdirectory $src]} { file mkdir $dest foreach f [glob -nocomplain [file join $src *]] { File.Copy $f [file join $dest [file tail $f]] } return } if {[file isdirectory $dest]} { set dest [file join $dest [file tail $src]] } set in [open $src] set out [open $dest w] puts -nonewline $out [read $in] close $out ; close $in } Произвольный доступ к данным Команды seek и Tcll обеспечивают произвольный доступ к данным в потоках. В каждом потоке определена текущая позиция или текущее смещение от начала файла. При каждой операции ввода-вывода текущая позиция сдвигается вперед на число прочитанных или записанных байтов. Определить текущую позицию позволяет команда Tcll. Команда seek устанавливает текущую позицию. Команде seek передаются положительное или отрицательное смещение и точка отсчета, в качестве которой может использоваться начало файла, конец файла либо сама текущая позиция. Работать с файлами размером более 2 Гбайт позволяет лишь версия Tcl 8.4, в которой реализована поддержка 64-битовой файловой системы.
Глава 9. Работа с программами и файлами 209 Закрытие каналов ввода-вывода Команда close не менее важна, чем остальные команды ввода-вывода, поскольку она освобождает ресурсы операционной системы, используемые для поддержки потоков. Если вы забудете закрыть поток, он будет автоматически закрыт при завершении процесса. Однако, если программа выполняется в течение длительного времени, как, например, сценарии Тк, лишние открытые потоки приводят к напрасному расходованию ресурсов. При выполнении команды close могут возникать ошибки. Если поток связан с другим процессом в цепочке конвейерной обработки и текущий процесс пытается записать данные в стандартный поток ошибок, Tcl воспринимает такую ситуацию как ошибочную. Кроме того, если один из процессов в цепочке конвейерной обработки завершается с ненулевым кодом состояния, при выполнении команды close также генерируется ошибка. Текущий каталог: команды cd и pwd Для каждого процесса определен текущий каталог. Он используется в качестве исходной точки при вычислении относительного пути. Информацию о том, какой каталог является текущим, предоставляет команда pwd, а команда cd позволяет изменить текущий каталог. В листинге 9.11 приведен код процедуры, в которой использовались эти команды. Действия с файлами с использованием команды glob Команда glob позволяет определить группу файлов, имена которых соответствуют некоторому шаблону. Команда glob записывается следующим образом: glob ?опции? шаблон ?шаблон? . . . Шаблон формируется так же, как и рассмотренный ранее шаблон, используемый для проверки соответствия строк. • *. Задает любое (в том числе нулевое) число символов. • ?. Определяет один символ. • [abc]. Определяет набор символов. • {а,Ь,с}. Определяет либо символ а, либо Ь, либо с. • Прочие символы интерпретируются как литералы.
210 Часть I. Основы Tcl Опции команды glob перечислены в табл. 9.8. Таблица 9.8. Опции команды glob -directory каталог Поиск фар1лов производится в указанном каталоге (Tcl 8.3) -join Шаблоны, заданные в качестве параметров, объединяются через разделитель каталогов и рассматриваются как один шаблон (Тс.1 8.3) -nocomplain Если ни один файл не найден, команда возвращает пустой список. Если данная опция не указана, то в подобной ситуации возникает ошибка -path путь Поиск файлов осуществляется начиная с указанного пути. Такой подход позволяет искать файлы в каталогах, содержащих специальные символы команды glob (Tcl 8.3) -tails Возвращается часть имени файла, которая следует за именем последнего каталога, указанного с помощью опции -directory или -path (Tcl 8.4) -types типы Возвращается лишь информация о файлах указанных типов Конец набора опций. Необходимо указывать в том случае, если шаблон начинается с символа - В отличие от команды glob оболочки csh, Tcl-команда glob распознает только имена существующих файлов. В csh выражение {а,Ь} может соответствовать имени отсутствующего файла. Результаты выполнения команды glob не подвергаются сортировке. По необходимости сортировку можно выполнить с помощью команды lsort. В листинге 9.11 приведен код процедуры FindFile, которая просматривает файловую систему или ее часть. Для обработки подкаталогов использовался рекурсивный вызов процедуры. На каждой итерации текущий каталог сохраняется и предпринимается попытка перехода к следующему подкаталогу. Команда catch осуществляет перехват в случае указания некорректного имени каталога. Команда glob проверяет соответствие имени файла шаблону.. Листинг 9.11. Поиск файла по имени proc FindFile { startDir namePat } { set pwd [pwd] if {[catch {cd $startDir} err]} { puts stderr $err return }
Глава 9. Работа с программами и файлами 211 foreach match [glob -nocomplain -- $namePat] { puts stdout [file join $startDir $match] } foreach file {[glob -nocomplain *]} { if [file isdirectory $file] { FindFile [file join $startDir $file] $namePat } } cd $pwd 2 Опция -types позволяет осуществлять фильтрацию, подобно команде find в системе Unix. Существуют две формы записи данной опции. Первая форма аналогична опции -type команды find; ее значениями могут быть b (файл на блочном устройстве), с (файл на символьном устройстве), d (каталог), f (обычный файл), 1 (символьная ссылка), р (именованный канал) и s (гнездо). В одном списке может быть указано несколько типов. Команда glob возвращает все файлы, соответствующие хотя бы одному из указанных типов. Вторая форма отличается от первой тем, что файл должен соответствовать всем указанным в опции типам. Значениями опции могут быть г (разрешение чтения), w (разрешение записи), х (разрешение выполнения), а также readonly и hidden. В системе Macintosh поддерживаются специальные типы; типом MacOS считается значение, состоящее из четырех символов (например, TEXT). Наличие нераспознанных типов приводит к возникновению ошибки. Обе формы опции -types могут использоваться одновременно. Например, опция -types {d f r w} определяет обычные файлы или каталоги, допускающие чтение и запись. Расширение символа ~ Команда glob расширяет символ ~, находящийся в начале имени файла. • Выражение ~I заменяется именем рабочего каталога текущего пользователя. • Вместо ~имя_пользователя подставляется рабочий каталог указанного пользователя. Если имя файла начинается с литерального символа ~, вы можете отменить его обработку, добавив перед именем файла символы . / (например, ./~foobar).
212 Часть I. Основы Tcl Команды exit и pid Команда exit завершает выполнение сценария. Обратите внимание, что команда exit вызывает завершение всего процесса. Если при вызове exit вы зададите целочисленный параметр, он станет кодом завершения процесса. Команда pid возвращает идентификатор текущего процесса. Она также часто используется для включения идентификатора процесса в имя временного файла. С помощью pid можно также определить идентификатор процесса, связанного с каналом. set pipe [open "Iкоманда"] set pids [pid $pipe] В Tcl отсутствует встроенный механизм управления процессами. В системе Unix для завершения процесса вы можете вызвать с помощью команды exec программу kill. exec kill $pid Переменные окружения Переменные окружения позволяют связывать с каждым процессом набор строковых значений. Переменные окружения доступны в программе посредством глобального массива env. Имя переменной представляет собой индекс элемента массива, содержащего ее текущее значение. При изменении массива env будут изменены значения переменных окружения. Переменные окружения наследуются дочерними процессами, поэтому программы, запущенные с помощью команды exec, выполняются в среде Tcl-сценария. Ниже приведен фрагмент кода, предназначенный для вывода значений переменных окружения. Листинг 9.12. Вывод значений переменных окружения proc printenv { args } { global env set maxl 0 if {[llength $args] == 0} { set args [lsort [array names env]] } foreach x $args { if {[string length $x] > $maxl} { set maxl [string length $x] }
Глава 9. Работа с программами и файлами 213 } incr maxl 2 foreach x $args { puts stdout [format u°/0*s = °/0su $maxl $x $env($x)] } } printenv USER SHELL TERM => USER = welch SHELL = /bin/csh TERM = tx Для приложений, выполняющихся в системе Macintosh, переменные окружения можно инициализировать путем редактирования ресурса типа STR# с именем Tcl Environment Variables. Этот ресурс является частью приложений tclsh и wish. Инструкции по использованию ResEdit см. в главе 2. Значения ресурсов записываются в формате ИМЯ=ЗНАЧЕНИЕ. Команда registry В системе Windows для хранения информации о конфигурации системы используется реестр. Просмотр и редактирование содержимого реестра осуществляется с помощью инструмента regedit. В Tcl предусмотрена команда registry. Она реализуется посредством пакета, который загружается с помощью выражения package require registry В реестре содержатся ключи, имена значений и типизированные данные. Имена связаны с ключами, а каждому имени соответствуют определенные данные. Ключи организованы в иерархическую систему, поэтому имена определяют уровень в иерархии. Для того чтобы извлечь информацию из реестра, надо указать ключ и имя. Ключи записываются в следующем формате: \ \ имя_ узла \ корнев ое_имя \ путь корневое _имя\путь корневое_имя Корневым именем может быть HKEY_LOCAL_MACHINE, HKEY_PERFORMANCE_ DATA, HKEY.USERS, HKEY_CLASSES_ROOT, HKEY.CURRENT.USER, HKEY_CURRENT_ CONFIG или HKEY_DYN_DATA. Варианты команды registry и типы данных описаны соответственно в табл. 9.9 и 9.10.
214 Часть I. Основы Tcl Таблица 9.9. Команда registry registry delete ключ ? имя_значения? registry get ключ ? имя_значения? registry keys ключ ?шаблон? registry set ключ registry set ключ имя_значения данные ?тип? registry type ключ имя_значения registry values ключ ?шаблон? Удаляет ключ и имя значения или, если имя значения не задано, удаляет все значения для указанного ключа Удаляет значение, связанное с указанным именем для ключа Возвращает список ключей или именованных значений для ключа, удовлетворяющих шаблону. Сравнение с шаблоном производится по правилам string match Создает ключ Создает именованное значение для ключа. Значение создается на основе данных указанного типа. Перечень типов приведен в табл. 9.10 Возвращает тип именованного значения для ключа Возвращает имена значений для указанного ключа, соответствующие шаблону. Сравнение с шаблоном осуществляется по правилам string match Таблица 9.10. Типы данных registry binary Произвольные двоичные данные попе Произвольные двоичные данные expand_sz Строка, содержащая ссылки на окружение. Ссылки создаются в формате 70ИМЯ_ПЕРЕМЕНН0Й°/0 dword 32-разрядное целое число dword_big_endian 32-разрядное целое число, в котором байты расположены от старшего к младшему (big endian). В Tcl оно представляется как строка десятичных чисел link Символьная ссылка multi_sz Строки, представленные в виде списка Tcl resource_list Список драйверов устройств
ЧАСТЬ Расширенные средства Tcl В части II описываются расширенные средства программирования на Tcl, позволяющие в короткий срок создавать сложные приложения. В главе 10 рассматривается команда eval, позволяющая динамически создавать Tcl-программы. Для того чтобы корректно использовать eval, надо соблюдать ряд правил. В главе 11 описываются регулярные выражения. Они представляют собой наиболее мощный из всех инструментов обработки строк, предоставляемых Tcl. В этой главе будут рассмотрены часто используемые регулярные выражения. В главе 12 описываются средства организации библиотек и пакетов, с помощью которых можно представить написанный вами код в виде модулей, пригодных для повторного использования. В главе 13 рассматриваются интроспекция и средства отладки. Интроспекция представляет информацию о состоянии интерпретатора Tcl. В главе 14 описываются пространства имен, которые выделяют в глобальной области видимости разделы для переменных и процедур. Пространства имен упрощают структурирование больших Тс1-приложений. В главе 15 речь пойдет о средствах поддержки интернационализации, в том числе Unicode и других кодировок символов, а также каталогов сообщений. В главе 16 описывается программирование операций ввода-вывода, управляемых событиями. Наличие такого режима обмена данными позволяет запускать процессы в фоновом режиме и реализовывать взаимодействие с ними с помощью каналов. Средства, описываемые в данной главе, также используются при программировании гнезд для сетевого взаимодействия; этот вопрос рассматривается в главе 17. В главе 18 обсуждается продукт TclHttpd — Web-сервер, полностью написанный на Tcl. На базе TclHttpd легко создавать прикладные программы. По необходимости вы можете интегрировать сервер с существующими при-
216 Часть II ложениями, создавая тем самым для них Web-интерфейс. TclHttpd также поддерживает обычные Web-узлы. В главе 19 рассказывается о Safe-Tcl и об использовании нескольких Tcl- итерпретаторов. Если интерпретатор является защищенным, вы можете ограничить набор его функциональных возможностей. Такая возможность очень полезна при работе с аплетами, которые копируются с узлов, не пользующихся доверием. Этот вопрос рассматривается в главе 20. В главе 21 описывается расширение Thread, используемое для создания многопотоковых Tcl-сценариев. Это расширение обеспечивает синхронизацию потоков, поддерживает переменные условий (condition variable) и пулы потоков. В главе 22 рассматриваются вопросы доставки Tcl-приложений. Виртуальная файловая система (Virtual File System) используется для создания в составе Starkit специальной файловой системы ограниченного применения, используемой для хранения сценариев, графических данных и документации для приложения.
Глава 10 Цитирование и использование команды eval В данной главе описывается непосредственный вызов интерпретатора с помощью команды eval. При использовании данной команды реализуется дополнительный этап подстановки. Этот эффект может с успехом использоваться при решении ряда задач. Кроме того, в главе также рассматриваются проблемы цитирования, возникающие при работе с командой eval, и способы их решения. Команда uplevel изменяет область видимости при выполнении выражений. Команда subst осуществляет подстановку, но не выполняет команды. ^_1ИНАМИЧЕСКОЕ выполнение выражений позволяет создавать гибкие и мощные Tcl-программы, но при этом надо внимательно следить за тем, чтобы соответствующие средства использовались корректно. При использовании данного подхода создается строка символов, после чего команда eval интерпретирует эту строку как одну или несколько команд. Создание кода в процессе работы программ не вызывает затруднений в интерпретируемых языках, таких как Tcl, и является чрезвычайно сложной задачей при использовании компилируемых языков, например C++. Ниже перечислены некоторые аргументы в пользу применения динамически создаваемого кода в языке Tcl. • В некоторых случаях применение простых процедур не оправдано. Вместо этого желательно сформировать команду из нескольких фрагментов и выполнить ее с помощью eval. Такой подход часто используется оболочками, реализующими некоторый функциональный уровень на базе существующих команд.
218 Часть II. Расширенные средства Tcl • Процедуры обратного вызова представляют собой фрагменты сценариев, которые выполняются при наступлении определенного события. В качестве примера можно привести команды Тк, связанные с кнопками, событиями ввода-вывода и таймера. Процедуры обратного вызова представляют собой гибкий инструмент, позволяющий связывать между собой части приложения. • Команда uplevel позволяет добавить к Tcl новые управляющие структуры. Так, например, вы можете написать функцию, которая будет применять команду к каждой строке текста в файле или к каждому узлу древовидной структуры. • В некоторых случаях целесообразно объединять код и данные и обрабатывать код с помощью команды subst. Такой подход применяется при работе с HTML-шаблонами, которые будут описаны в главе 18. Дополнительные возможности дает сочетание команд subst и regsub (команда regsub будет описана в главе 11). Формирование кода с помощью команды list При формировании команды, которая корректно обрабатывалась бы с помощью eval, разработчик сталкивается с определенными проблемами. Аналогичные трудности возникают при использовании команд after, uplevel и Tk-команды send, которые имеют назначение, подобное eval, но реальное выполнение команды происходит позже и в другом контексте. Основная трудность состоит в том, что многие динамически формируемые команды могут работать лишь в определенных условиях. При динамическом формировании команд надо использовать команду list. Проблемы цитирования в основном связаны с конкатенацией параметров в одну командную строку. При конкатенации могут теряться важные элементы структуры списка, в результате чего параметры передаются не так, как это планирует разработчик. Чтобы устранить эти проблемы, надо формировать процедуры обратного вызова в виде списка с четко определенной структурой, используя для этого команды list и lappend. Команда eval Если вы создаете команду динамически, для ее выполнения надо использовать eval. При выполнении команды eval осуществляется дополнительный
Глава 10. Цитирование и использование команды eval 219 вызов интерпретатора Tcl. Предположим, например, что мы собираемся создать приведенную ниже команду, но хотим, чтобы она выполнялась несколько позже. puts stdout "Hello, World!" Сделать это можно следующим образом: set and {puts stdout "Hello, World!"} => puts stdout "Hello, World!" # Впоследствии ... eval $cmd => Hello, World! В этом случае значение переменной cmd передается интерпретатору Tcl. К значению переменной, т.е. к содержащейся в ней команде puts, применяются все стандартные правила группировки и подстановки. Теперь представьте себе, что часть команды хранится в переменной, которая в момент выполнения eval недоступна. Эту ситуацию можно смоделировать следующим образом: set string "Hello, World!" set cmd {puts stdout $string} => puts stdout $string unset string eval $cmd => can't read "string": no such variable Как видите, в состав команды входит переменная $string. При обработке команды с помощью eval интерпретатор ищет текущее значение не определенной переменной string. Очевидно, что ситуация в данном примере создана искусственно, но аналогичная проблема возникнет в том случае, если string является локальной переменной, а обработка переменной cmd производится в глобальной области видимости. Типичной ошибкой в данном случае является попытка использования двойных кавычек для группировки команды. Это позволяет немедленно выполнить подстановку $string. Однако такой подход дает положительный результат только в том случае, когда переменная string содержит простое значение. Если в составе переменной содержатся пробелы или другие символы, имеющие специальное значение в языке Tcl, возникает ошибка. set cmd "puts stdout $string" => puts stdout Hello, World! eval $cmd => bad argument "World!": should be "nonewline"
220 Часть II. Расширенные средства Tcl Проблема состоит в том, что в случае немедленной подстановки теряются важные элементы структуры. На втором этапе синтаксического анализа, осуществляемого вследствие использования команды eval, значение $string не воспринимается как один параметр. Решением проблемы является формирование команды с использованием list (листинг 10.1). Листинг 10.1. Использование команды list для формирования команды, предназначенной для выполнения set string "Hello, World!" set cmd [list puts stdout $string] => puts stdout {Hello, World!} unset string eval $cmd => Hello, World! В данном примере команда list формирует список, содержащий три параметра: puts, stdout и значение переменной string. Подстановка $string осуществляется перед вызовом команды list, и эта команда берет на себя заботы о группировке значения. Для сравнения, использование двойных кавычек эквивалентно следующему выражению: set cmd [concat puts stdout $string] При использовании двойных кавычек теряется структура списка. Проблема состоит в том, что команда concat не сохраняет структуру списка. Таким образом, если в состав команды входит значение переменной или результат выполнения другой команды и подстановка должна быть выполнена немедленно, то для формирования такой команды следует использовать list. Используя двойные кавычки, вы добьетесь немедленной подстановки, но структура команды может стать некорректной. При использовании фигурных скобок подстановка откладывается на более позднее время, и не исключено, что это произойдет в неподходящем контексте. Команды, выполняющие конкатенацию параметров Команды uplevel, after и send выполняют конкатенацию параметров, формируя из них команду, и по прошествии определенного времени выполняют полученное выражение в другом контексте. Команда uplevel описывается далее в этой главе; команда after рассматривается в главе 16, а команда send ~ в главе 43. Где бы ни встретилась одна из этих команд, на нее надо обратить пристальное внимание и убедиться, что она формирует единственный параметр посредством команды list и не осуществляет конкатенацию. 4>
Глава 10. Цитирование и использование команды eval 221 after 100 [list doCmd $paraml $param2] send $interp [list doCmd $paraml $param2];# Корректные команды В данном случае опасность состоит в том, что команды concat и list дают одинаковые результаты, а ошибка проявляется позднее, когда значения переменных изменяются. Два примера, приведенных выше, работают всегда. Представленные ниже выражения корректны лишь в том случае, если в переменных paraml и param2 содержится по одному элементу списка. after 100 doCmd $paraml $param2 send $interp doCmd $paraml $param2;# Команды могут быть некорректны Если вы используете расширения Tcl, предоставляющие средства, подобные eval, внимательно ознакомьтесь с документацией и убедитесь в отсутствии команд, формирующих выражение посредством конкатенации. Например, Tcl-DP поддерживает сетевую версию команды send с именем dp_send, которая также использует конкатенацию. Команды, обеспечивающие обратный вызов Команды, позволяющие по прошествии некоторого времени выполнить отдельное выражение или фрагмент кода, представляют собой удобное средство объединения различных частей приложения, поэтому такие команды часто используются в Tcl-программах. В качестве примера можно привести команды Тк, которые выполняются после щелчка на кнопке; команды, обращение к которым происходит тогда, когда данные поступили через канал ввода-вывода, и команды клиентской программы, выполняющиеся при установлении сетевого соединения с сервером. Создать процедуру, или С-расши- рение, которое сценарий выполняет в ответ на некоторое событие, достаточно просто. Если команде обратного вызова передается единственный параметр, "проблема конкатенации" не возникает. Однако при использовании двойных кавычек для группировки элементов создаются условия для возникновения ошибки, связанной с конкатенацией. Поэтому основное правило, согласно которому для создания выражения должна использоваться команда list, остается в силе. Командный префикс Существует разновидность обратного вызова команд, которая называется командный префикс. При вызове команда получает дополнительные параметры. Другими словами, вы формируете лишь часть команды, или префикс, а модуль, осуществляющий обратный вызов, задает дополнительные параметры непосредственно перед обращением к команде eval.
222 Часть II. Расширенные средства Tcl Например, если вы создаете сервер для работы в сети, вы реализуете процедуру, которая вызывается при установлении соединения. При вызове этой процедуры задаются три дополнительных параметра, которые определяют гнездо на стороне клиента, IP-адрес и номер порта. (Более подробно этот вопрос рассматривается в главе 17.) Необходимо определить процедуру обратного вызова, обрабатывающую четыре (или более) параметра. Часть параметров задается при определении процедуры обратного вызова, а затем система поддержки гнезд указывает недостающие параметры. Ниже приведена команда, формирующая гнездо на стороне сервера. set virtualhost www.beedub.com socket -server [list Accept $virtualhost] 8080 Процедура Accept формируется следующим образом: proc Accept {myname sock ipaddr port} { ... } Параметр myname устанавливается при формировании командного префикса. Остальные параметры создаются при вызове процедуры. В данном случае использовать команду list не обязательно, поскольку virtualhost представляет единственный элемент списка. Тем не менее полезно выработать привычку всегда применять список при формировании команды обратного вызова. Этим вы предотвратите возникновение многих проблем. Можно привести большое количество примеров обратного вызова с использованием командных префиксов. В частности, так осуществляется взаимосвязь между полосами прокрутки Тк и соответствующими компонентами, создаются псевдонимы команд в Safe Tcl, реализуется сортировка в lsort и т.д. Пример использования eval для осуществления обратного вызова Tcl- процедур приведен в листинге 13.6. Динамическое формирование процедур В приведенных ранее примерах рассматривалось создание отдельных команд с использованием операции формирования списка. Однако в ряде случаев приходится динамически создавать целую процедуру. Сделать это достаточно трудно, поскольку тело процедуры нельзя представить в виде простого списка. Тело процедуры — это последовательность команд, разделенных символами перевода строки или точками с запятой; каждая из этих команд является списком. Ряд команд в составе процедуры ~ это команды условного выполнения или циклы, которые сами содержат тело команды. Задача динамического формирования процедуры усложняется тем, что при ее решении применяются два типа переменных: один из них используется для создания тела процедуры, а переменные другого типа используются при выполнении процедуры. В результате вероятность ошибки становится очень высокой.
Глава 10. Цитирование и использование команды eval 223 Для обработки шаблона динамически генерируемой процедуры используется команда format либо regsub. Если применяется команда format, то в тех точках шаблона, в которые должны быть включены значения переменных, надо указать символы °/0s. Если значение переменной должно присутствовать в нескольких позициях тела процедуры, хможно применить спецификатор позиции (например, °/0l$s или °/02$s). В примере, приведенном в листинге 10.2, показан пример процедуры, генерирующей другую процедуру. Генерируемая процедура содержит код, с помощью которого подсчитывается число вызовов этой процедуры и оценивается время, требуемое для ее выполнения. Листинг 10.2. Динамическая генерация процедуры с использованием шаблона proc TraceGen {procName} { rename s$procName $procName-orig set arglist {} foreach arg [info args $procName-orig] { append arglist "\$$arg " } proc $procName [info args $procName-orig] [format { global _trace_count _trace_msec incr _trace_count (°/0l$s) incr _trace_msec(°/0l$s) [lindex [time { set result [°/0l$s-orig °/02$s] } 1] 0] return $result } $procName $arglist] 2 Предположим, что в нашем распоряжении есть простая процедура с именем f оо. proc foo {х у} { return [expr $x * $у] } Если вы примените к ней процедуру TraceGen, то получите следующий результат: TraceGen foo info body foo => global _trace_count _trace_msec incr _trace_count(foo) incr _trace_msec(foo) [lindex [time {
224 Часть II. Расширенные средства Tcl set result [foo-orig $x $y] } 1] 0] return $result Средства трассировки, предоставляемые TraceGen, обеспечивают возможности, аналогичные возможностям команды trace, которая была реализована в Tcl 8.4. Команды трассировки, описанные в главе 13, позволяют отслеживать вызовы процедур и результаты их выполнения. Выполнение конкатенации в команде eval В предыдущем разделе много говорилось о том, какую опасность может представлять конкатенация при формировании команд. Однако бывают ситуации, в которых конкатенация не только допустима, но и необходима. В данном разделе мы рассмотрим случаи, когда при формировании команды несколько списков объединяется в один путем конкатенации. Команда eval выполняет конкатенацию, если ей передается несколько параметров. eval список_1 список_2 список^З ... В результате конкатенации из нескольких списков формируется один; при этом новый уровень структуры списка не добавляется. Осуществлять такое объединение необходимо в том случае, если списки содержат фрагменты команды. Данная форма eval часто используется, если в процедуре должен присутствовать параметр args. Параметр args применяется тогда, когда необходимо передать команде необязательные параметры. Если вы вызываете команду с помощью eval, все значения, указанные в $args, корректно присоединяются к строке команды. Пример использования специального параметра args см. в листинге 7.2. Использование команды eval в процедуре оболочки В данном разделе мы обсудим использование eval и $args в простой Тк- программе. В Тк команда button включает в состав пользовательского интерфейса кнопку. Команде button может передаваться несколько параметров, но обычно разработчики ограничиваются указанием текста, предназначенного для отображения на кнопке, и Tcl-команды, которая должна выполняться после щелчка на данном интерфейсном элементе. button .foo -text Foo -command foo После создания кнопки процедура диспетчера компоновки (pack) делает ее видимой на экране. Команде pack также может передаваться несколько параметров, которые управляют размещением управляющего элемента. Здесь мы лишь указываем, с какой стороны окна должна находиться кнопка,
Глава 10. Цитирование и использование команды eval 225 и предоставляем возможность системе самостоятельно принять решение об остальных деталях размещения элемента. pack .foo -side left Для создания кнопки используются только две Tcl-команды, однако их можно объединить в процедуру; в результате вместо двух команд будет вызываться одна. Первый вариант такой процедуры выглядит следующим образом: proc PackedButton {name txt cmd} { button $name -text $txt -command $cmd pack $name -side left } Данную процедуру нельзя назвать очень гибкой. Основная проблема состоит в том, что она не позволяет полностью использовать возможности команды button, которая поддерживает более 30 конфигурационных опций, например -background, -cursor, -relief и т.п. (Подробно команда button описывается в главе 30.) Например, создать кнопку красного цвета позволяет следующее выражение: button .foo -text Foo -command foo -background red В модифицированной версии процедуры PackedButton для передачи дополнительных конфигурационных опций команде button используется параметр args. Он представляет собой список дополнительных параметров, передаваемых Tcl-процедуре. На первый взгляд может показаться, что значение $args надо использовать так, как показано ниже, но подобное использование некорректно. proc PackedButton {name txt cmd args} { button $name -text $txt -command $cmd $args pack $name -side left } PackedButton .foo "Hello, World!" {exit} -background red => unknown option "-background red" Проблема состоит в том, что $args — это список, и команда button получает весь список как один параметр. Элементы $args следует передавать кохманде как отдельные параметры. Значение $args необходимо использовать совместно с командой eval. Если значение $args обрабатывается командой eval, происходит объединение параметров в один список. Один список — это то же самое, что одна Tcl-команда, поэтому разбор сформированной команды button осуществляется корректно. В приведенном ниже вы-
226 Часть II. Расширенные средства Tcl ражении команде eval передаются два списка, которые объединяются в одну команду, eval {button $name -text $txt -command $cmd} $args Использование в данном выражении фигурных скобок будет обсуждаться несколько позже. Внесем дополнительные изменения в создаваемую процедуру, чтобы команде pack могли передаваться опции. Окончательная версия PackedButton показана в листинге 10.3. Листинг 10.3. Использование значения $args совместно с командой eval # Процедура PackedButton создает кнопку и размещает ее на экране. proc PackedButton {path txt cmd {pack {-side right}} args} { eval {button $path -text $txt -command $cmd} $args eval {pack $path} $pack 2 В процедуре PackedButton значениями pack и args являются списки. Для этого конкретного случая данные, передаваемые команде eval, сформированы корректно. Наиболее простая форма вызова PackedButton приведена ниже. PackedButton .new "New" { New } В данном выражении кавычки и фигурные скобки излишни, но сохранены для наглядности. Кавычки подразумевают строковое значение, а фигурные скобки — команду. Параметр pack содержит значение по умолчанию, а значением переменной args является пустой список. Таким образом, реально при вызове процедуры PackedButton выполняются две приведенные ниже команды. button .new -text New -command New pack .new -side right По умолчанию процедура PackedButton создает горизонтальную строку кнопок. Управлять расположением можно, указывая дополнительные параметры команды pack. PackedButton .save "Save" { Save $file } {-side left} Теперь команды, выполняемые при вызове процедуры PackedButton, выглядят так: button .new -text Save -command { Save $file } pack .new -side left Оставшиеся параметры передаются команде button. Это позволяет вызывающей программе указать дополнительные атрибуты данной команды.
Глава 10. Цитирование и использование команды eval 227 PackedButton .quit Quit { Exit } {-side left -padx 5} \ -background red Команды, выполненные при обращении к процедуре PackedButton, имеют следующий вид: button .quit -text Quit -command { Exit } -background red pack .quit -side left -padx 5 При вызове процедуры PackedButton параметры pack и args используются по-разному. Опции команды pack должны явным образом группироваться в один параметр. Параметр args автоматически создает список параметров. Если вы попытаетесь явным образом сгруппировать дополнительные параметры команды button, это будет ошибкой. PackedButton .quit Quit { Exit } {-side left -padx 5} \ {-background red} => unknown option "-background red" Проблемы с цитированием в команде eval В процедуре PackedButton фигурные скобки были расположены несколько неожиданным образом: eval {button $path -text $txt -command $cmd} $args Используя фигурные скобки, можно контролировать, сколько раз различные части команды будут обработаны интерпретатором Tcl. При отсутствии скобок осуществляются два этапа подстановки. Скобки отменяют один из этих этапов. В приведенном выше выражении дважды обрабатывается только $args. Перед вызовом команды eval значение $args заменяется списком. Команда eval выполняет конкатенацию двух параметров, объединяя их в один список. Этот список представляет корректно сформированную команду. На втором этапе подстановки заменяются значения txt и cmd. Яря работе с командой eval не следует использовать двойные кавычки. Работая с командой eval, вы, возможно, захотите применить вместо фигурных скобок кавычки. Не делайте этого! Использование двойных кавычек в подавляющем большинстве случаев приведет к получению некорректных результатов. Предположим, что команда eval записана следующим образом: eval "button $path -text $txt -command $cmd $argsu Данное выражение оказывается эквивалентным следующему: eval button $path -text $txt -command $cmd $args
228 Часть II. Расширенные средства Tcl Данный вариант команды будет нормально работать, если процедура вызывается так, как показано ниже. Это происходит потому, что и значение txt, и значение cmd состоит из одного слова и не содержит специальных символов. PackedButton .quit Quit { Exit } Команда button выглядит следующим образом, button .quit -text Quit -command { Exit } Если процедура вызывается так, как показано ниже, возникает ошибка. PackedButton .save "Save As" [list Save $file] => unknown option "As" Причина в том, что команда button приобретает следующий вид. button .save -text Save As -command Save /a/b/c Такая команда некорректна. Правильная запись выглядит так. button .save -text {Save As} -command {Save /a/b/c} Теперь структура команды button недопустима. Подстановка txt и cmd выполняется в первую очередь (перед вызовом eval), после чего производится синтаксический разбор всей команды. Как видно в приведенных выше примерах, в некоторых случаях двойные кавычки оказываются допустимыми, в других ситуациях их применение приводит к возникновению ошибок. Это зависит от конкретных значений параметров. Если значения параметров содержат пробелы или специальные символы, при разборе команды возникает ошибка. Использование фигурных скобок — единственный правильный способ группировки параметров команды eval. Таким образом, следующее выражение не приведет к возникновению ошибок: eval {button $path -text $txt -command $cmd} $args Приведенные ниже выражения также корректны. В первом цитирование осуществляется автоматически благодаря наличию команды list. В остальных для отмены дополнительного этапа подстановки применяются символы обратной косой черты или фигурные скобки. eval [list button $path -text $txt -command $cmd] $args eval button \$path -text \$txt -command \$cmd $args eval button {$path} -text {$txt} -command {$cmd} $args Ниже приведен еще один пример некорректного цитирования. eval "button {$path} -text {$txt} -command {$cmd} $argsu
Глава 10. Цитирование и использование команды eval 229 Проблема состоит в том, что при использовании двойных кавычек и фигурных скобок мы получим различные результаты. Рассмотрим следующий простой пример, в котором использованы двойные кавычки. Фигурные скобки вокруг $blob обрабатываются специальным образом, и интерпретатор сообщает о несоответствии открывающих и закрывающих скобок. set blob "foo\{bar space" => foo{bar space eval "puts {$blob}" => missing close brace Если группировка осуществляется с помощью фигурных скобок, то подстановка переменной происходит один раз после группировки параметров puts и ошибка не возникает. eval puts {$blob} => foo{bar space Допустимо также использовать команду list. eval puts [list $blob] Понятно, что данная ситуация создана искусственно, но она иллюстрирует проблемы, которые могут возникнуть в реальных сложных программах. Вывод очевиден: используя eval, необходимо уделять пристальное внимание формированию списка. Команда uplevel Команда uplevel выполняет действия, аналогичные eval, за исключением того, что сформированная команда выполняется в области видимости, отличной от области видимости текущей процедуры. Данная возможность позволяет определять новые управляющие структуры Tcl. Команда uplevel вызывается следующим образом: uplevel ?уровень? команда ?список_1 список_2 ...? Подобно команде upvar, первый параметр, с помощью которого задается уровень, необязателен, но его использование является признаком хорошего стиля программирования. По умолчанию принимается значение этого параметра, равное 1, т.е. команда вызывается в той же области видимости, что и вызывающая процедура. Уровень #0 означает, что команда должна выполняться в глобальной области видимости. Вы можете использовать уровни 2, 3 и т.д. либо вести отсчет от глобального уровня (например, #1 или #2), но ситуации, в которых целесообразно использовать такие значения первого параметра, встречаются крайне редко.
230 Часть II. Расширенные средства Tcl Задавая второй параметр, т.е. указывая команду, вы должны ясно представлять себе, какие подстановки выполнит интерпретатор Tcl перед вызовом команды uplevel. Если вы непосредственно формируете команду, защитите ее фигурными скобками, чтобы подстановки были выполнены в другой области видимости. В приведенном ниже выражении обрабатывается переменная х в области видимости вызывающей процедуры. uplevel {set x [expr $x + 1]} В отличие от предыдущего примера, в выражении, приведенном ниже, действия производятся с переменной х в текущей области видимости, а установка значения переменной осуществляется в области видимости вызывающей процедуры. uplevel "set x [expr $x + 1]" Если вы создаете команду динамически, вам придется прибегнуть к помощи list. Следующее выражение будет использовано в листинге 10.4: uplevel [list foreach $args $valueList {break}] Часто разработчики помещают команду в переменную. При этом команда передается процедуре как параметр. В таком случае команда должна выполняться уровнем выше. Уровень желательно указывать явно, чтобы исключить ситуацию, при которой $cmd выглядит как число. uplevel 1 $cmd Нередко команды вводятся пользователем. В этом случае команда должна выполняться в глобальной области видимости. Использование команды uplevel иллюстрирует код, приведенный в листинге 16.2. uplevel #0 $cmd Если вы создаете команду на основе двух различных списков, то для формирования команды должна выполняться операция конкатенации. uplevel [concat $cmd $args] Списки в $cmd и $args объединены в один список, который представляет допустимую команду Tcl. Подобно eval, при наличии дополнительных параметров команда uplevel выполняет операцию конкатенации по умолчанию, поэтому явно указывать команду concat не обязательно. Приведенные ниже команды эквивалентны. uplevel [concat $cmd $args] uplevel u$cmd $argsu uplevel $cmd $args
Глава 10. Цитирование и использование команды eval 231 Код процедуры, представленный в листинге 10.4, демонстрирует использование команды f oreach, описанной в главе 6. Процедура lassign помещает элементы списка в несколько переменных. Для того чтобы присвоение значений переменных осуществлялось в требуемой области видимости, в данной процедуре использовалась команда uplevel. Команда list формирует команду foreach, выполняющуюся в области видимости вызывающей процедуры. Важно, чтобы подстановка переменных, определяющих переменные и присваиваемые им значения, осуществлялась перед выполнением команды в другой области видимости. Листинг 10.4. Процедура lassign: использование команды list совместно с foreach # Набору переменных присваиваются значения из списка. # Если значений больше, чем переменных, они возвращаются. # Если переменных больше, чем значений, "лишним" переменным # присваиваются пустые строки. proc lassign {valueList args} { if {[llength $args] == 0} { error "wrong # args: lassign list varname ?varname..?" } if {[llength $valueList] == 0} { # Гарантируется хотя бы одно выполнение цикла foreach set valueList [list {}] } uplevel 1 [list foreach $args $valueList {break}] return [lrange $valueList [llength $args] end] 2 В листинге 10.5 демонстрируется создание новой управляющей структуры посредством процедуры File_Process, которая применяет команду обратного вызова к каждой строке файла. Обращение к команде uplevel обеспечивает формирование команды путем конкатенации callback и line. Команда list используется для отмены специальных значений символов в line так, чтобы значение этой переменной интерпретировалось как один параметр команды. Листинг 10.5. Процедура File_Process осуществляет итерацию по строкам файла proc File_Process {file callback} { set in [open $file]
232 Часть II. Расширенные средства Tcl while {[gets $in line] >= 0} { uplevel 1 $callback [list $line] } close $in _} Рассмотрим различия между следующими двумя командами: uplevel 1 [list $callback $line] uplevel 1 $callback [list $line] В первом выражении callback может содержать только имя команды, в то время как во втором callback может выступать в роли командного префикса. Следующее выражение составлено неверно: uplevel 1 $callback $line Если произвольное значение $line объединяется с командой callback, может случиться так, что эта команда будет составлена некорректно. Команда subst Команда subst применяется при наличии разнообразных Tcl-команд, ссылок на переменные и обычных данных. Эта команда ищет квадратные скобки, знаки $ и символы обратной косой черты и выполняет подстановку. Остальные данные остаются неизменными. set a "foo bar" subst {a=$a date=[exec date]} => a=foo bar date=Thu Dec 15 10:13:48 PST 1994 При использовании команды subst подстановка выполняется независимо от наличия фигурных скобок. subst {a=$a date={[exec date]}} => a=foo bar date={Thu Dec 15 10:15:31 PST 1994} Чтобы запретить подстановку команд и переменных, можно использовать обратную косую черту. subst {a=\$a date=\[exec date]} => а=$а date=[exec date] Подстановки типа \uXXXX используются для получения символов Unicode, последовательность \п формирует символ новой строки. При вызове команды subst могут указываться опции -nobackslashes, -nocommands и -novariables, которые ограничивают ее возможности по выполнению подстановки. Эти опции указываются перед строкой, в которой должна осуществляться подстановка.
Глава 10. Цитирование и использование команды eval 233 subst -novariables {a=$a date=[exec date]} => a=$a date=Thu Dec 15 10:15:31 PST 1994 Обработка строк с помощью команды subst Команда subst может использоваться совместно с командой regsub для эффективной обработки строк, осуществляющейся в два этапа. На первом этапе regsub преобразует входную строку в набор данных, содержащий Tcl- команды. На втором этапе subst или eval заменяет Tcl-команды результатами их выполнения. Умело отображая данные в Tcl-команды, вы можете динамически формировать Tcl-сценарии, обрабатывающие данные. Обработка осуществляется эффективно, так как синтаксический анализатор Tcl и обработчик регулярных выражений непосредственно настроены для решения подобных задач. Примеры использования данного подхода приведены в главе 11.
Глава И Регулярные выражения В данной главе описываются регулярные выражения, используемые для создания шаблонов, на соответствие которым проверяются строки. Регулярные выражения представляют собой наиболее мощный из всех инструментов обработки строк, предоставляемых Tcl. В этой главе речь пойдет о командах regexp и regsub. 1 ЕГУЛЯРНЫЕ выражения представляют собой средство формального описания шаблонов, используемых для проверки строк. Механизм регулярных выражений в Tcl реализован достаточно эффективно. Если в создаваемом вами сценарии должна осуществляться обработка строк, вам имеет смысл затратить время и усилия на изучение команды regexp. Регулярные выражения позволяют получить компактный и эффективно работающий код сценария. В данной главе приведено большое количество примеров, иллюстрирующих возможности регулярных выражений. Команда regsub — еще один важный инструмент обработки строк. Реализуемая с ее помощью операция подстановки позволяет изменить строку в соответствии с заданным регулярным выражением. В данной главе вы встретите примеры, показывающие, как можно выполнять сложные действия, пользуясь лишь ограниченным числом Tcl-команд. Стефен Алер (Stephen Uhler) продемонстрировал ряд способов преобразования с помощью regsub данных, вводимых пользователем, и использованрш команды subst или eval для их обработки. В состав Tcl 8.1 вошла новая реализация регулярных выражений, обеспечивающая поддержку Unicode, а также механизм расширенных регулярных выражений (ARE — advanced regular expressions). В новой реализации появились дополнительные языковые средства, упрощающие процесс написания шаблонов. Если вы имеете опыт программирования на языке Perl, некото-
Глава 11. Регулярные выражения 235 рые из этих новых средств уже наверняка знакомы вам. Расширенные регулярные выражения Tcl практически идентичны регулярным выражениям, поддерживаемым в Perl 5. Следует учитывать, что расширенные регулярные выражения в отдельных деталях несовместимы с регулярными выражениями, поддерживаемыми в Tcl 8.0 и некоторых более ранних версиях, однако эти различия очень редко проявляются на практике. Поскольку новые регулярные выражения позволяют работать с символами Unicode, вы можете создавать шаблоны для проверки строк на самых различных языках, например на японском или хинди. В каких случаях целесообразно использовать регулярные выражения На первый взгляд регулярные выражения могут показаться очень сложными. Они создаются по определенным правилам с использованием специальных синтаксических конструкций. У начинающего разработчика, не знакомого с регулярными выражениями, может возникнуть соблазн отказаться от их использования и решить поставленную перед ним задачу более простыми в изучении средствами, например применить команды string first, string range или string match. Однако следует заметить, что часто одно регулярное выражение позволяет заменить достаточно длинную последовательность команд string. При использовании регулярных выражений сценарий получается не только более компактным, но и более эффективным. Это происходит потому, что средства обработки регулярных выражений реализованы на языке С и оптимизированы, в результате строки обрабатываются очень быстро. Средства обработки регулярных выражений не только информируют о результатах проверки всей строки. Они также сообщают о том, какая часть строки соответствует шаблону. Это полезно в тех случаях, когда приходится извлекать данные из строки большой длины. По необходимости вы можете получить несколько последовательностей, выполнив лишь одну операцию сравнения. Тс 1-команда regexp существенно упрощает присвоение переменным данных, выдержавших сравнение с шаблоном. Если вы привыкли использовать для получения строковой информации команды string first и string range, заметьте, что команда regexp выполняет соответствующие действия в рамках одной операции. Средства поддержки регулярных выражений преобразуют шаблон в формат, обеспечивающий максимальную эффективность операций сравнения. Если вы часто используете один и тот же шаблон, то преобразование, или компиляция, выполняется только единожды и все сравнения осуществляются
236 Часть II. Расширенные средства Tcl максимально эффективно. Детали такого сравнения скрыты от разработчика за интерфейсом Tcl. Если один и тот же шаблон применяется дважды, Tcl практически всегда извлекает его скомпилированную форму и использует ее для сравнения. Средства поддержки регулярных выражений оптимизированы для выполнения разнообразных сложных операций со строками. Решение часто возникающих проблем Для группировки шаблонов необходимо использовать фигурные скобки. Одна из проблем, возникающих при работе с регулярными выражениями, связана с тем, что в них используются некоторые символы, имеющие в языке Tcl специальное значение. Если шаблон содержит фигурные скобки, знаки $ или пробелы и входит в состав Тс1-ко- манды, специальное значение этих символов необходимо отменить. В большинстве случаев разработчики применяют для группировки регулярных выражений фигурные скобки, поэтому в документации по Tcl этому вопросу уделяется мало внимания. Однако необходимо учитывать, что в Tcl 8.0 или более ранних версиях данного языка приходилось предварять часть шаблона символом обратной косой черты. В этом случае вопрос отмены специальных значений символов в регулярных выражениях становится актуальным. Расширенные регулярные выражения решают эту проблему, поскольку подстановку выражений, содержащих обратную косую черту, выполняют средства поддержки регулярных выражений. В Tcl 8.0 и более ранних версиях приходилось принимать меры для того, чтобы подстановка таких символов, как \п и \t, выполнялась средствами Tcl. В Tcl 8.1 символы \п и \t в составе регулярного выражения автоматически преобразуются в перевод строки и знак табуляции. Реально при формировании шаблона можно использовать около 20 сочетаний символов, начинающихся с обратной косой черты. С появлением новых средств поддержки регулярных выражений необходимо еще более внимательно следить за тем, чтобы для группировки шаблонов применялись фигурные скобки. Это позволит избежать конфликтов между интерпретатором Tcl и средствами поддержки регулярных выражений. В шаблонах, приведенных в первых разделах данной главы, описанные особенности не учитываются. В табл. 11.7 использовано цитирование выражений, чтобы они могли непосредственно использоваться в Тс1-сценариях. Большинство выражений полностью помещено в фигурные скобки, но в некоторых случаях применены другие средства цитирования.
Глава 11. Регулярные выражения 237 Правила записи регулярных выражений В данном разделе описываются основы создания шаблонов с использованием регулярных выражений. Средства, предназначенные для формирования шаблонов, имеются во всех версиях Tcl. Работа с расширенными регулярными выражениями имеет некоторые особенности. Они будут рассмотрены несколько позже. Основные правила создания регулярных выражений приведены в пяти таблицах данной главы. В состав регулярного выражения могут входить следующие компоненты. • Литеральные символы. • Наборы и классы символов. • Итераторы. • Оператор выбора. • Вложенные шаблоны, заключенные в круглые скобки. Сравнение символов Большинство символов используется в регулярных выражениях как литералы. Приведенному ниже шаблону соответствует последовательность из двух символов: а и Ь. ab Точка является универсальным заменителем. Если в шаблоне присутствует точка, ей может соответствовать любой одиночный символ. Ниже представлен шаблон, которому соответствуют последовательности из двух символов: а и следующий за ним произвольный символ. а. Заметьте, что шаблон не обязательно должен описывать всю строку. Соответствовать шаблону может любая ее часть. Изменить это правило можно с помощью якорей, которые будут рассмотрены ниже. Наборы символов При сравнении может возникнуть необходимость в том, чтобы символ строки соответствовал одному из нескольких символов. Для этого символы набора помещаются в квадратные скобки, например [xyz]. Если символ совпадает с любым из символов, находящихся между открывающей и закрывающей квадратными скобками, принимается решение о соответствии данному компоненту шаблона. Например, приведенному ниже шаблону соответствует строка Hello или hello.
238 Часть II. Расширенные средства Tcl [Hh]ello Набор символов может быть представлен как диапазон значений. При этом первый и последний символы диапазона разделяются знаком -, например [х-у]. Следующее выражение определяет любую цифру: [0-9] Существует также возможность определить соответствие символа дополнению множества. Указав перед первым символом в квадратных скобках знак ~, вы сообщите о том, что проверяемый символ должен соответствовать любому знаку, кроме указанных в квадратных скобках. Например, выражение [~xyz] говорит о том, что символ может быть любым, кроме х, у и z. Дополнение до множества можно задавать одновременно с указанием диапазона. Например, приведенное ниже выражение определяет любой символ, кроме букв верхнего и нижнего регистра. Га-zA-Z] В наборе можно указывать специальные символы. Если вам необходимо включить в набор закрывающую квадратную скобку, ее надо разместить непосредственно после открывающей скобки. Чтобы задать в наборе символ [, не надо принимать никаких специальных мер. Приведенное ниже выражение определяет одну из квадратных либо фигурных скобок. [] [{}] Многие символы, предназначенные для формирования регулярных выражений в наборе, теряют свое специальное значение. Это означает, что, поместив такой символ в квадратные скобки, не обязательно указывать перед ним обратную косую черту. В следующем выражении содержится несколько символов, которые в обычных условиях интерпретируются как специальные. [][+*?() 1\\] В расширенных регулярных выражениях предусмотрены имена и последовательности, начинающиеся с косой черты, обозначающие часто используемые наборы символов, например пробел или знак табуляции, буква, буква или цифра и т.д. Соответствующие выражения описаны в табл. 11.3. Итераторы При формировании регулярного выражения можно использовать три итератора: * — любое (в том числе нулевое) количество повторений предыдущего компонента; + — одно или более повторений и ? — нулевое или единичное число вхождений компонента. Предыдущий компонент может представ-
Глава 11. Регулярные выражения 239 лять собой литеральный символ, набор символов или подшаблон, ограниченный круглыми скобками. Ниже приведен шаблон для строки, начинающейся с символа Ь, за которым следует произвольное (в том числе нулевое) количество символов а. Ьа* Поместив часть шаблона в круглые скобки, можно применить к ней итератор точно так же, как и к единичному символу. Ниже показан шаблон, определяющий строку, которая состоит из одной или нескольких пар символов ab. (ab) + Шаблон, определяющий любую, даже пустую строку, имеет следующий вид: .* Если компоненту шаблона, за которым следует итератор, удовлетворяют различные подстроки проверяемой строки, считается, что соответствие установлено для подстроки максимальной длины. При использовании расширенных регулярных выражений действуют несколько иные правила; они будут описаны далее в этой главе. В качестве примера можно привести следующий шаблон, которому соответствует одна строка, содержащая любое число произвольных символов: .*\п Такому шаблону удовлетворяет как одна строка, так и все содержимое текстового файла, последним символом которого является символ перевода строки. Видоизменив шаблон, мы определим с его помощью последовательность символов, заканчивающуюся первым символом перевода строки. [Лп]*\п Используя итераторы, не подчиняющиеся правилу проверки подстроки максимального размера, можно сократить приведенный шаблон. Кроме того, для определения строки можно включить специальный режим обработки символов перевода строки, который будет обсуждаться в одном из последующих разделов. Оператор выбора Оператор выбора позволяет использовать для проверки два или несколько шаблонов. Средства обработки регулярных выражений могут осуществлять проверку на соответствие сразу нескольким шаблонам, поэтому операторы
240 Часть II. Расширенные средства Tcl выбора работают очень эффективно. Оператор выбора обозначается символом I. Приведенное ниже выражение сравнивает строку с двумя шаблонами: Hello и hello. hello I Hello Тот же результат можно получить, используя следующие шаблоны: (hlH)ello или [hH]ello Использование якорей По умолчанию шаблон определяет как всю строку, так и любую ее часть. Перед подстрокой, соответствующей шаблону, и после нее могут присутствовать "лишние" символы. По необходимости вы можете "привязать" шаблон к началу строки, включив в его начало символ Л, либо к концу строки, указав в конце шаблона знак $. Использовав оба указанных символа, вы укажете, что шаблону должна соответствовать вся строка. Например, следующий шаблон определяет строку, начинающуюся с пробелов либо знаков табуляции: Ч \t] + Если вводимый пользователем текст состоит из нескольких строк, разработчику может показаться удобным использовать символы ~ и $ для определения начала и конца каждой строки. Однако это неверно. Причиной такого заблуждения является неоднозначность самого понятия "строка". Дело в том, что в составе объекта "строка", хранящегося в памяти и представляющего последовательность символов, могут присутствовать символы перевода строки, т.е. получается, что "строка состоит из нескольких строк". Как правило, символы ~ и $ соответствуют началу и концу всей последовательности символов, а не строк, содержащихся в ней. В расширенных регулярных выражениях предусмотрен режим, при котором символы ~ и $ определяют начало и конец строки, ограниченной символами \п. Кроме того, работая с расширенными регулярными выражениями, можно использовать якоря \А и \Z, соответствующие началу и концу строк, которые входят в последовательность символов, содержащихся в памяти. Использование обратной косой черты Обратная косая черта позволяет отменить специальное значение следующих символов: .*?+[]() ~ $ I \
Глава 11. Регулярные выражения 241 Например, для того, чтобы включить в состав шаблона знак +, надо указать символы \+. Заметьте, что в квадратных скобках указывать обратную косую черту не обязательно. Для того чтобы определить либо символ +, либо знак вопроса, можно воспользоваться любым из приведенных ниже шаблонов. (\+1\?) [+?] Чтобы определить одну обратную косую черту, надо включить в шаблон последовательность из двух таких символов. Это правило должно выполняться всегда, даже если обратная косая черта помещена в квадратные скобки. При работе с расширенными регулярными выражениями символ обратной косой черты можно задать с помощью выражения \В. Оба приведенных ниже выражения определяют единичный символ обратной косой черты. \\ \В Последовательность символов, начинающаяся с обратной косой черты, неизвестная интерпретатору, приводит к возникновению ошибки. В версиях, предшествующих Tcl 8.1, при появлении в регулярном выражении неизвестной последовательности, начинающейся с обратной косой черты, обратная косая черта игнорировалась. Например, последовательность \= интерпретировалась как =, a \w — как w. Даже \п превращалась в п, немало удивляя начинающих разработчиков, пытавшихся включить в шаблон символ перевода строки. В расширенные регулярные выражения включены выражения, определяющие табуляцию, перевод строки, классы символов и т.д. Такие последовательности очень удобны, однако в некоторых (правда, крайне редких) случаях они могут изменить семантику шаблона. Обычно это происходит тогда, когда случайно попавшая в шаблон обратная косая черта становится значимой. Порядок установления соответствия Если шаблону удовлетворяет несколько частей строки, средства обработки регулярных выражений устанавливают соответствие для той подстроки, которая встречается первой. В дальнейшем, если оказывается, что тому же шаблону соответствует и другая часть строки, выбирается подстрока наибольшей длины. Это правило можно отменить, установив режим, при котором более предпочтительной считается строка наименьшей длины.
242 Часть II. Расширенные средства Tcl При использовании итератора * могут получиться неожиданные результаты. Как известно, этот итератор означает "нуль или более вхождений предыдущего компонента". Предположим, что ваш шаблон имеет вид [a-z]* Этому шаблону соответствует строка 123abc, но соответствие это оказывается довольно странным. Вместо того чтобы пометить буквы в строке, данный шаблон ставится в соответствие подстроке нулевой длины, расположенной в начале проверяемой строки. Проверить это можно, указав при вызове команды regexp опцию -indices. Если указана эта опция, то, вместо того чтобы вернуть подстроку, соответствующую шаблону, команда сообщает о том, где расположена эта подстрока. Выделение подшаблонов Для выделения подшаблонов используются круглые скобки. Строка, соответствующая подшаблону, запоминается в специальной Tcl-переменной, называемой переменной соответствия. Подшаблоны очень удобно применять при решении многих практических задач. Предположим, что мы хотим получить все символы, содержащиеся в некотором HTML-файле между дескрипторами <td> и </td>. Для этого можно использовать следующий шаблон: <td>([~<]*)</td> В переменной соответствия запоминается часть строки, выдержавшая сравнение с подшаблоном в скобках. В одном шаблоне можно указать несколько подшаблонов, что позволяет эффективно разделять данные. Переменные соответствия будут подробно обсуждаться при рассмотрении команды regexp. В некоторых случаях разработчика не интересует строка, соответствующая подшаблону. Если при обработке регулярного выражения некоторые результаты не запоминаются, разбор осуществляется более эффективно. Под- шаблон без запоминания определяется с помощью следующего выражения: (?: шаблон) Расширенные регулярные выражения Большей частью расширенные регулярные выражения являются сокращенной записью языковых конструкций, сформированных на основе средств, рассмотренных в предыдущих разделах. Кроме того, расширенные регулярные выражения предоставляют ряд новых возможностей, например, "экономное" сопоставление, обратные ссылки, упреждающее сравнение и именован-
Глава 11. Регулярные выражения 243 ные классы символов. Если вы лишь начинаете работать с регулярными выражениями, можете отложить изучение данного раздела на более позднее время. Однако при этом все же стоит обратить внимание на последовательности, начинающиеся с обратной косой черты. Если вы уже имеете некоторый опыт использования данных средств либо если вы применяли регулярные выражения, работая с редактором vi или с утилитой grep, приведенный здесь материал наверняка представит для вас интерес. Совместимость с шаблонами, поддерживаемыми в Tcl 8.0 Расширенные регулярные выражения сформированы так, чтобы обеспечить совместимость снизу. Средства, реализованные в новых версиях Tcl, продолжают поддерживать старые шаблоны, но по необходимости вы можете использовать выражения, в которых учтены новые возможности; применение подобных выражений в предыдущих версиях Tcl стало бы причиной возникновения ошибки. Например, во многих новых конструкциях используется знак вопроса, но в ряде случаев его новое расположение было бы недопустимо в более ранних вариантах регулярных выражений. Правила записи расширенных регулярных выражений приведены в табл. 11.2. Если в созданных вами ранее программах применялись регулярные выражения, сформированные без использования фигурных скобок, то они почти наверняка будут корректны в Tcl 8.1 и более поздних версиях данного языка. Например, приведенному ниже шаблону соответствует произвольная последовательность символов, заканчивающаяся очередным переводом строки. Поскольку фигурные скобки отсутствуют, Tcl заменяет выражение \п символом новой строки. Квадратные скобки вместе с другими символами находятся в кавычках, поэтому они не определяют вложенную команду. regexp " (\Г\п\]+)\п" $input Данная последовательность одинаково интерпретируется средствами поддержки как обычных, так и расширенных регулярных выражений. Ее также можно записать в следующем виде: regexp {([~\n]+)\n} $input Квадратные скобки маскируются фигурными, поэтому их не надо предварять символами новой строки. В результате шаблон становится короче и удобнее для восприятия.
244 Часть II. Расширенные средства Tcl Последовательности, начинающиеся с обратной косой черты Наиболее существенным отличием расширенных регулярных выражений от подобных средств, применявшихся прежде, является подстановка последовательностей, начинающихся с обратной косой черты. В Tcl 8.0 и более ранних версиях языка обратная косая черта использовалась лишь для отмены специальных значений таких символов, как .+*?[]. В остальных случаях символ обратной косой черты игнорировался. В результате при обработке регулярных выражений средствами Tcl 8.0 последовательность \п превращалась в обычный символ п. Такая особенность интерпретации выражений часто становилась источником недоразумений, в частности, нельзя было помещать шаблоны в фигурные скобки, чтобы "спрятать" от синтаксического анализатора Tcl специальные символы. В расширенных регулярных выражениях последовательность \п означает символ перевода строки, поэтому нельзя позволять Tcl обрабатывать обратную косую черту. Во избежание ошибок шаблон, сформированный с использованием расширенных регулярных выражений, следует всегда помещать в фигурные скобки. В расширенных регулярных выражениях предусмотрено большое количество последовательностей, начинающихся с обратной косой черты (табл. 11.4). Среди них наиболее часто используются \s, с помощью которой помечаются пробелы и другие символы аналогичного назначения; \w, обозначающая буквы, цифры и символы подчеркивания; \у, соответствующая началу слова, и \В, которой помечается символ перевода строки. Классы символов Классом называют именованный набор символов. Класс символов имеет смысл только в наборе, помещенном в квадратные скобки. Класс символов записывается следующим образом: [:идентификатор :] Например, alpha — это имя набора букв верхнего или нижнего регистра. Приведенные ниже два шаблона почти идентичны. [A-Za-z] [[:alpha:]] Различие между ними состоит в том, что к классу символов alpha относятся также буквы, начертание которых содержит дополнительные элементы, например ё. При описании данных, которые содержат символы, не соот-
Глава 11. Регулярные выражения 245 ветствующие стандарту ASCII, использовать именованные классы намного удобнее, чем явно описывать каждый символ. Некоторые последовательности, начинающиеся с обратной косой черты, представляют собой сокращенную запись имен классов символов. Так, например, цифра соответствует одному из следующих шаблонов: [0-9] [[:digit:]] \d Приведенные ниже шаблоны определяют символы-разделители, к которым относятся обратная косая черта, перевод формата, возврат каретки, перевод строки, горизонтальная и вертикальная табуляция. [ \b\f\n\r\t\v] [[:space:]] \s Именованные классы символов и соответствующие им последовательности, начинающиеся с обратной косой черты, перечислены в табл. 11.3. Именованные классы можно использовать в наборе вместе с другими символами или классами. Приведенным ниже шаблонам соответствуют буквы, цифры и символ подчеркивания. [[:digit:][:alpha:]J [\d[:alpha:]_] [[:alnum:]_] \w Заметьте, что последовательности \d, \s и \w могут использоваться как в составе набора символов, так и за его пределами. Вне квадратных скобок они формируют отдельные наборы. Выражения \D, \S и \W представляют собой дополнения \d, \s и \w. Например, \D определяет любой символ, кроме цифры. Эти последовательности нельзя включать в квадратные скобки. Существуют два специальных класса символов, [[:<:] и [[:>:]], которые обозначают соответственно начало и конец строки. Слово определяется как один или несколько символов, которые соответствуют \w. 'Экономное" сопоставление Символы *, + и ? представляют собой итераторы, с помощью которых задается повторение предыдущего компонента шаблона. По умолчанию выбирается подстрока максимальной длины, которая соответствует выражению
246 Часть II. Расширенные средства Tcl перед итератором. Такой подход называется "расточительным" сопоставлением. При "экономном" сопоставлении выбирается наиболее короткая подстрока. Для того чтобы включить режим "экономного" сопоставления, надо поместить после итератора знак вопроса. Рассмотрим шаблон, определяющий один или несколько символов, отличных от перевода строки, за которыми следует символ новой строки. Символы, не являющиеся переводом строки, помечаются с помощью итератора, при этом осуществляется "расточительное" сопоставление. Г\п]+\п Ниже приведен шаблон, подобный предыдущему. .+\п Однако в этом шаблоне есть существенная особенность: точка определяет любой символ, в том числе символ новой строки. Поэтому такому шаблону соответствует вся входная последовательность символов до последнего перевода строки. При "экономном" сопоставлении будут помечены символы, оканчивающиеся первым переводом строки. .+?\п Благодаря использованию режима "экономного" сопоставления длина шаблона уменьшается с восьми символов до пяти. В качестве еще одного примера можно привести выражение, которому соответствует фрагмент HTML- файла. Следующий шаблон помечает все символы между HTML-дескрипторами <td> и </td>: <td>(.*?)</td> В режиме "экономного" сопоставления можно использовать даже символ ?. Выражение ?? означает, что отсутствие указанного компонента строки предпочтительнее его наличия. Итераторы с ограниченным числом повторений Выражение {m,n} означает, что фрагмент данных, соответствующий предыдущему элементу шаблона, должен повторяться минимум m раз и максимум п раз. Существуют две разновидности этого выражения. Последовательность символов {ш} означает ровно m повторений предыдущего компонента. Выражение {ш,} указывает на то, что соответствие должно быть установлено m или более раз. При наличии символа ? данный тип итератора также переводится в режим "экономного" сопоставления.
Глава 11. Регулярные выражения 247 Обратные ссылки Обратные ссылки — это одно из тех средств, которые чрезвычайно трудно реализовать с помощью традиционных регулярных выражений. Обратная ссылка помечает значение подшаблона, помещенного в круглые скобки. Если подшаблонов, содержащихся в скобках, несколько, вы можете ссылаться на них с помощью выражений \1, \2 и т.д. Отсчет ссылок ведется по левой открывающей скобке. Предположим, например, что вы хотите создать шаблон, определяющий последовательность символов, помещенную в одинарные либо в двойные кавычки. Для этого вам надо использовать два шаблона, объединенные оператором выбора. В одном из шаблонов указываются одинарные, а в другом — двойные кавычки. ('■ [~"]*и | > [л,]*>) Обратная ссылка \1 позволяет существенно упростить выражение. (Ч").*?\1 Одна из кавычек, приведенных в скобках, начинает строку, а ссылка \1 указывает на то, что завершающим должен быть тот же тип кавычки, что и открывающий. В режиме "экономного" сопоставления роль завершающей будет выполнять первая из кавычек, которая встречается после открывающей. Упреждающее сравнение Шаблоны упреждающего сравнения представляют собой регулярные выражения, которые используются для сравнения строк, но не удаляют символы из входного потока. Они выполняют роль ограничений для остальной части шаблона и обычно располагаются в конце выражения. При положительном упреждающем сравнении сопоставление считается успешным, если последовательность символов соответствует выражению, а при отрицательном сравнении успешным считается то сопоставление, при котором последовательность символов не соответствует регулярному выражению. Ограничение, накладываемое шаблонами упреждающего сравнения, как правило, имеет смысл в том случае, когда используются переменные соответствия и выполняется подстановка с помощью команды regsub. В качестве примера рассмотрим шаблон, который определяет имя файла, начинающееся с буквы А и заканчивающееся последовательностью символов .txt. ~A.*\.txt$ Ниже приведено то же выражение, в котором окончание имени файла помещено в круглые скобки.
248 Часть II. Расширенные средства Tcl ~A.*(\.txt$) В данном случае скобки излишни, но ниже мы создадим с их помощью выражение для упреждающего просмотра. Для того чтобы при сравнении строки с шаблоном осуществлялся упреждающий просмотр, выражение надо модифицировать следующим образом: ~A.*(?=\.txt$) Шаблон, в котором использовались ограничения упреждающего просмотра, помечает часть имени файла, предшествующую .txt, но происходит это только в том случае, если есть окончание имени .txt. Другими словами, при сравнении символы . txt не извлекаются из входного потока. Результат такого сравнения будет виден при просмотре значений переменных соответствия, используемых при выполнении команды regexp. Кроме того, упреждающее сопоставление влияет на результаты подстановки, выполняемой с помощью команды regsub. По мере необходимости вы можете задать отрицательное упреждающее сопоставление. Приведенный ниже шаблон определяет имя файла, которое начинается с А и заканчивается последовательностью символов, отличной от .txt. ~A.*(?!\.txt$) Если вы попытаетесь получить тот же результат с помощью традиционных регулярных выражений, вы убедитесь, что эта задача достаточно сложна. Коды символов Выражения \пл и \тшш (где пит — цифры), помимо обратных ссылок, могут определять также 8-битовые коды символов в восьмеричном формате. Интерпретация таких выражений, как кодов символов, более приоритетна, чем обработка их в качестве обратных ссылок. Однако задавать так коды символов не рекомендуется. Вместо этого лучше использовать последовательность \\innnn, определяющую 16-битовое значение. Выражение \хпп также задает 8-битовый код символа. Однако действие символов \х распространяется не на две, а на все шестнадцатеричные цифры, следующие за ними. Полученное выражение затем усекается до 8-разрядного значения. Такая интерпретация выражения \х не считается ошибкой, и в последующих версиях Tcl ситуация вряд ли изменится. Последовательность Wyyyyyyyy зарезервирована для 32-разрядных значений Unicode и в ближайшем будущем она использоваться не будет.
Глава 11. Регулярные выражения 249 Элементы замены Элементы замены (collating element) — это символы или их мнемонические имена, которые можно использовать в наборах символов. В настоящее время в Tcl определены мнемонические имена для некоторых знаков пунктуации ASCII. Теоретически можно сформировать имена для всех символов Unicode, но эта возможность не реализована и вряд ли будет реализована в дальнейшем, так как размер таблиц перекодировки был бы чрезвычайно велик. В данном разделе мы вкратце рассмотрим представление элементов замены. Это надо для того, чтобы, встретив подобные конструкции в коде программ, вы знали, с чем имеете дело. В настоящее время элементы замены на практике почти не применяются. Помещенный в квадратные скобки, элемент замены записывается следующим образом: [. идентификатор. ] В качестве идентификатора может использоваться символ или его мнемоническое имя. Информацию о поддерживаемых в настоящее время мнемонических именах можно найти в дистрибутивном пакете Tcl в файле generic/ regc.locale.c. Некоторые примеры элементов замены приведены ниже. Сс] [.number-sign.] Классы эквивалентности Класс эквивалентности представляет собой набор символов, которые при сортировке занимают одну и ту же позицию. В современных версиях Tcl классы эквивалентности практически бесполезны. Дело в том, что в настоящее время символы представляются посредством значений Unicode, поэтому классов эквивалентности, насчитывающих больше одного символа, не существует. Однако такой механизм находил применение при работе с более ранними кодировками. Класс эквивалентности указывается с помощью следующего выражения: [=символ=] В данном случае подразумевается любой из символов, принадлежащих классу эквивалентности. Сравнение, чувствительное к переводу строки По умолчанию символ новой строки рассматривается средствами обработки регулярных выражений как обычный символ. Однако вы можете пре-
250 Часть II. Расширенные средства Tcl вратить его в специальный символ. Для этой цели предусмотрены две опции: lineanchor и linestop. Эти опции указываются при вызове Tcl-команд regexp и regsub, кроме того, вы можете использовать встроенные опции, информация о которых приведена в табл. 11.5. Наличие опции lineanchor приводит к тому, что якоря и $ рассматривают в качестве признака начала и окончания строки не только начало и окончание всей последовательности символов, но и позицию, следующую за переводом строки и предшествующую ему. Другими словами, символом помечается позиция после перевода строки, а символом $ — позиция, находящаяся непосредственно перед переводом строки. Кроме того, якоря продолжают реагировать на начало и окончание всей последовательности символов. Независимо от того, указана ли опция lineanchor, вы можете для определения начала и конца строки использовать выражения \А и \Z. Опция linestop исключает перевод строки из множества символов, соответствующих точке и набору символов, начинающегося с ". Другими словами, для того чтобы шаблон помечал перевод строки, в него нужно явно включить последовательность \п. Встроенные опции В начале шаблона можно указывать встроенные опции, позволяющие включать и отключать режим чувствительности к регистру и к символу новой строки, а также разрешать открытый синтаксис. Вы также можете переходить от расширенных регулярных выражений к традиционным выражениям и даже к литеральным строкам. Встроенные опции записываются следующим образом: (?символы) В данном случае после знака вопроса могут указываться любые символы, интерпретируемые как опции. Список встроенных опций приведен в табл. 11.5. Открытый синтаксис Открытый синтаксис (expanded syntax) позволяет включать в состав шаблонов комментарии, а также дополнительные пробелы и другие символы, интерпретируемые подобным образом (например, знаки табуляции и переводы строк). Это упрощает работу со сложными шаблонами. Открытый синтаксис можно разрешить с помощью опций команды regexp или встроенных опций. Комментариями считаются последовательности символов от # до конца строки. Дополнительные пробелы и комментарии могут располагаться в любых позициях, за исключением выражений в квадратных скобках (т.е. наборов сим-
Глава 11. Регулярные выражения 251 волов) и последовательностей, интерпретируемых как единое целое, например (?=. В режиме открытого синтаксиса символы # и пробелы надо предварять обратной косой чертой. В листинге 11.1 показан шаблон, определяющий URL. Выражение (?х) в начале шаблона разрешает использование открытого синтаксиса. Весь шаблон помещен в фигурные скобки. Это сделано для того, чтобы предотвратить его обработку средствами Tcl. Более подробно данный пример будет рассмотрен далее в этой главе. Листинг 11.1. Открытый синтаксис позволяет включать комментарии в регулярные выражения regexp {(?х) # Шаблон для URL. ([":]+): # Имя протокола, завершающееся двоеточием //([":/]+) # Имя сервера. (:([0-9]+))? # Необязательный номер порта. (/.*) # Путь к ресурсу. } $input Синтаксис регулярных выражений В табл. 11.1 приведены правила записи регулярных выражений, которые поддерживаются всеми версиями Tcl. Таблица 11.1. Основные элементы регулярных выражений Произвольный символ * Любое (в том числе нулевое) число вхождений предыдущего элемента + Одно или несколько вхождений предыдущего элемента ? Предыдущий элемент может отсутствовать ( ) Группировка подшаблона. Итераторы и оператор выбора применяются ко всему подшаблону I Оператор выбора [ ] Определение набора символов. При описании набора могут указываться диапазоны символов в виде [х-у]. Если в качестве первого символа набора указан ~, решение о соответствии принимается только в том случае, если сравниваемый символ не содержится в наборе Якорь. Указывает на то, что сравниваемая последовательность символов должна начинаться с начала строки. Данный символ имеет такое значение только в том случае, если регулярное выражение начинается с него
252 Часть II. Расширенные средства Tcl Окончание табл. 11.1 Якорь. Указывает на то, что сравниваемая последовательность символов должна находиться в конце строки. Данный символ имеет такое значение только в том случае, если он расположен в конце регулярного выражения Расширенные регулярные выражения, поддержка которых была впервые реализована в Tcl 8.1, создаются с помощью языковых конструкций, описанных в табл. 11.2. Таблица 11.2. Дополнительные элементы расширенных регулярных выражений imj m вхождении предыдущего элемента {л.}? m вхождений предыдущего элемента. Выполняется "экономное" сопоставление {т,} т или более вхождений предыдущего элемента {га,}? т или более вхождений предыдущего элемента. Выполняется "экономное" сопоставление {т, п } От т до п вхождений предыдущего элемента {л2,п}? От т до п вхождений предыдущего элемента. Выполняется "экономное" сопоставление *? Любое (в том числе нулевое) число вхождений предыдущего элемента. Выполняется "экономное" сопоставление +? Одно или несколько вхождений предыдущего элемента. Выполняется "экономное" сопоставлегше ?? Одно вхождение предыдущего элемента, либо отсутствующий элемент. Выполняется "экономное" сопоставление (?-.регулярное„выражение) Регулярное выражение оформляется в виде под- шаблона. Результат не запоминается Нерегулярное „выражение) Положительное упреждающее сравнение. Помечает позицию, в которой начинается последовательность символов, соответствующая регулярному выражению (?'! регулярное „выражение) Отрицательное упреждающее сравнение. Помечает позицию при условии, что соответствие регулярному выражению не обнаружено (?abc) Встроенные опции. В данном случае аЪс — любое число опций, перечисленных в табл. 11.5 \символы Одна из последовательностей, перечисленных в табл. 11.4
Глава 11. Регулярные выражения 253 Окончание табл. 11.2 [: :] Определяет класс символов, перечисленных в табл. 11.3 [. . ] Определяет элемент замены [= =] Определяет класс эквивалентности В табл. 11.3 перечислены именованные классы символов и соответствующие им последовательности, начинающиеся с косой черты. Именованные классы символов могут использоваться в расширенных регулярных выражениях. Классы символов допустимы только в наборах, помещенных в квадратные скобки, и записываются с помощью выражений [:класс :]. Таблица 11.3. Классы символов alnum Буквы верхнего и нижнего регистра, а также цифры alpha Буквы верхнего и нижнего регистра blank Пробелы и знаки табуляции cntrl Управляющие символы с кодами от \u0001 до \u001F digit Цифры от 0 до 9. Тот же класс обозначается сочетанием \d graph Печатные символы, не принадлежащие классам cntrl и space lower Буквы нижнего регистра print To же, что и класс alnum punct Знаки пунктуации space Пробел, перевод строки, возврат каретки, табуляция, вертикальная табуляция, перевод формата. Тот же класс обозначается сочетанием \s upper Буквы верхнего регистра xdigit Шестнадцатеричные цифры: 0-9, a-f, A-F В табл. 11.4 описаны последовательности, начинающиеся с обратной косой черты, поддерживаемые в Tcl 8.1. Таблица 11.4. Последовательности символов, начинающиеся с обратной косой черты \а Звуковой сигнал \А Помечает начало строки \Ь Символ Backspace с кодом \u0008 \В Синоним обратной косой черты
254 Часть II. Расширенные средства Tcl Окончание табл. 11.4 \сХ <Ctrl+X> \d Цифры. То же, что и [[: digit:] ] \D Символы, отличные от цифр. То же, что и [Л [:digit:]] \е Символ Escape с кодом \u001B \f Символ перевода формата с кодом \u000C \ш Помечает начало слова \М Помечает конец слова \п Символ перевода строки с кодом \u000A \г Символ возврата каретки с кодом \uOOOD \s Пробел и другие символы подобного назначения. То же, что и [[:space:]] \S Символ, не относящийся к классу space. To же, что и [Л [:space:]] \t Символ горизонтальной табуляции с кодом \u0009 \uXXXX 16-битовый код символа Unicode \v Символ вертикальной табуляции с кодом \u000B \w Буквы, цифры и знак подчеркивания. То же, что и [[:alnum:]_] \W Символы, отличные от букв, цифр и знака подчеркивания. То же, что и [~ [:alnum:]_] \xhh Шестнадцатиричное представление 8-битового кода символа. Учитываются все восьмеричные цифры, следующие за \х \у Помечает начало или конец слова \Y Помечает точку, не являющуюся началом или концом слова \Z Помечает конец строки \0 Символ NULL с кодом \u0000 \х Если х — цифра, то данное сочетание представляет собой обратную ссылку \ху Если х и у — цифры, то данное сочетание представляет собой либо десятичное представление обратной ссылки, либо восьмеричное представление 8-битового кода символа \xyz Если х, у и z — цифры, то данное сочетание представляет собой либо десятичное представление обратной ссылки, либо восьмеричное представление 8-битового кода символа В табл. 11.5 приведены символы встроенных опций. Встроенные опции записываются с помощью выражения (?символы).
Глава 11. Регулярные выражения 255 Таблица 11.5. Символы встроенных опций b Оставшаяся часть шаблона представляет собой базовое регулярное выражение (типа используемых в vi или grep) с Сравнение с учетом регистра символов. Данная установка принимается по умолчанию е Оставшаяся часть шаблона представляет собой расширенное регулярное выражение (поддерживаемое в Tcl 8.0) i Сравнение без учета регистра символов m Синоним опции п п Сравнение с учетом символа перевода строки. Режимы lineanchor и line stop установлены р Сравнение с частичным учетом символа перевода строки. Установлен режим linestop q Оставшаяся часть шаблона рассматривается как литеральная строка s Сравнение без учета символа перевода строки. Данная установка принимается по умолчанию t Встроенные комментарии не допускаются. Данная установка принимается по умолчанию w Обратный учет символа перевода строки. Установлен режим lineanchor х Расширенные средства обработки пробелов и комментариев Команда regexp Команда regexp обеспечивает непосредственный доступ к средствам обработки регулярных выражений. Эта команда не только сообщает, соответствует ли строка шаблону, но также позволяет извлекать один или несколько фрагментов строк, выдержавших сравнение. Команда возвращает значение 1, если какая-либо часть строки соответствует шаблону, иО-в противном случае. Данная команда записывается следующим образом: regexp ?опции? шаблон строка ?переменная_соответствия_шаблону переменная^ соответствия__подша6лону_ 1 переменная__соответствия_подшаблону_2 . . .? Допустимые опции описаны в табл. 11.6. Таблица 11.6. Опции команды regexp -nocase Символы нижнего регистра в шаблоне могут соответствовать символам как верхнего, так и нижнего регистра в строке
256 Часть II. Расширенные средства Tcl Окончание табл. 11.6 -indices Каждая из переменных соответствия содержит два числа, которые являются индексами, отражающими фрагменты строк, выдержавших сравнение. Если данная опция не указана, последовательности символов копируются в переменные соответствия -expanded Шаблон составлялся с применением расширенных регулярных выражений -line Данная опция дает тот же эффект, что и совместно используемые опции -lineanchor и -linestop -lineanchor Изменяет правила интерпретации символов и $ так, что они становятся строчно-ориентированными -linestop Изменяет правила установления соответствия так, что точка и классы символов не распространяются на символ перевода строки -about Используется для отладки. Вместо того чтобы использовать шаблон для обработки данных, возвращается информация о самом шаблоне Конец набора опций. Данную последовательность необходимо использовать, если шаблон начинается с символа - Первый из параметров, следующих за опциями, представляет регулярное выражение. Если строка соответствует шаблону, regexp записывает результаты сравнения в переменных, указанных при вызове команды. Переменные соответствия задавать не обязательно. Если они присутствуют, первая из них содержит часть строки, удовлетворяющую шаблону. В остальные переменные записываются фрагменты строки, которые соответствуют подшаблонам в составе шаблона. Значения в переменные соответствия записываются в том порядке, в котором следуют открывающие круглые скобки в шаблоне. При этом исключается неоднозначность результатов, связанная с использованием вложенных подшаблонов. В листинге 11.2 команда regexp извлекает имя узла из переменной окружения DISPLAY, записываемой в следующем виде. имя_узла -.дисплей, экран Листинг 11.2. Использование регулярных выражений для разбора строки set env(DISPLAY) sage:0.1 regexp {([~:]*):} $env(DISPLAY) match host => 1 set match => sage;
Глава 11. Регулярные выражения 257 set host => sage В шаблоне содержится дополняющий набор [Л :], которому соответствует любой символ, кроме двоеточия. Помимо того, здесь используется итератор *, задающий любое (в том числе нулевое) число повторений символов из набора. Для выделения подшаблона используются круглые скобки. Часть строки, выдержавшая сравнение со всем шаблоном, записывается в переменную match. Фрагмент, соответствующий подшаблону, помещается в переменную host. Для того чтобы отменить специальную обработку квадратных скобок, весь шаблон помещен в фигурные скобки. Без фигурных скобок перед некоторыми символами пришлось бы указать обратную косую черту, и выражение приобрело бы следующий вид: regexp (\[~:\]*): $env(DISPLAY) match host Если указать в качестве параметра команды regexp расширенное регулярное выражение, в нем можно было бы задать режим "экономного" сопоставления. Это позволило бы заменить дополняющий набор точкой. regexp (.*?): $env(DISPLAY) match host Данное выражение обеспечивает высокую эффективность обработки. Если бы в пашем распоряжении была только команда string, мы вынуждены были бы написать следующий фрагмент кода, который интерпретировался бы приблизительно в два раза дольше. set i [string first : $env(DISPLAY)] if {$i >= 0} { set host [string range $env(DISPLAY) 0 [expr $i-l]] } Шаблон для разбора URL В листинге 11.3 представлен шаблон, содержащий несколько подшабло- нов, определяющих различные компоненты URL. Для того чтобы корректно обработать строку, необходимо выяснить, какой из компонентов URL будет записан в ту или иную переменную соответствия. Сделать это можно путем подсчета открывающих скобок. Листинг 11.3. Шаблон для разбора URL set url http://www.beedub.com:80/index.html regexp {(Г:]+)://([~:/]+)(:([0-9]+))?(/.*)} $url \ match protocol server x port path
258 Часть II. Расширенные средства Tcl => 1 set match => http://www. beedub. сот:80/index.html set protocol => http set server => www.beedub.com set x => :80 set port => 80 set path => /index.html Разберем представленный в листинге шаблон по частям. Первая часть шаблона приведена ниже; она определяет имя протокола, отделенное от остальных компонентов URL двоеточием. Данное выражение означает один или более символов, не являющихся двоеточием, за которыми следует двоеточие. Такому описанию удовлетворяет, например, протокол http:, встречающийся в составе многих URL. Г:] + : Используя итератор, работающий в режиме "экономного" сопоставления, можно переписать данное выражение в следующем виде: .+?: Следующий компонент шаблона помечает имя сервера; между ним и именем протокола в URL находятся две косые черты. Признаком окончания имени сервера является двоеточие, за которым следует номер порта, либо косая черта. Поэтому выражение, определяющее имя сервера, представляет собой один или более символов, отличных от двоеточия и косой черты. Такому описанию удовлетворяет, например, имя сервера //www.beedub.com. //[":/] + Номер порта в URL может отсутствовать, поэтому соответствующий компонент шаблона помещен в скобки; после закрывающей скобки расположен вопросительный знак. Дополнительная пара скобок позволяет выделить номер порта без двоеточия, находящегося перед ним. Например, приведенному ниже выражению удовлетворяет порт :80. (:([0-9]+))?
Глава 11. Регулярные выражения 259 Последняя часть шаблона — последовательность любых символов, отделенная от остальных компонентов косой чертой. Это может быть, например, имя файла /index.html. /.* При разборе строк рекомендуется использовать подшаблоны. Для того чтобы данным шаблоном можно было пользоваться для получения реальных данных, следует выделить с помощью круглых скобок ряд подшаблонов. (Г:]+)://(Г:/]+)(:([0-9]+))?(/.*) Наличие скобок не изменяет принцип разбора строки и принятия решения относительно соответствия ее шаблону. Необходимыми в данном примере являются лишь скобки, в которые помещено выражение, определяющее номер порта. Команда regexp предоставляет доступ к фрагментам строк, которые соответствуют подшаблонам. Посредством выполнения одной лишь команды regexp осуществляется проверка, соответствует ли строка формату URL, и выделяются ее компоненты, определяющие имя протокола, имя сервера, номер порта и путь к файлу. В выражении в скобках, определяющем номер порта, перед цифрами указано двоеточие. Двоеточие вместе с номером порта помещается в одну из переменных (эта переменная в дальнейшем не используется), а в другую переменную записывается только номер порта. Применяя незахватывающие скобки (эта возможность предоставляется расширенными регулярными выражениями), мы можем обойтись без использования лишней переменной. Кроме того, мы имеем возможность заменить все дополняющие наборы символов выражением .+?. Новый вариант регулярного выражения представлен в листинге 11.4. Листинг 11.4. Шаблон, описывающий URL set url http://www.beedub.com:80/book/ regexp {(.+?)://(.+?)(?::([0-9]+))?(/.*)$> $url \ match protocol server port path => 1 set match => http://www.beedub.com:80/book/ set protocol => http set server => www.beedub.com set port => 80
260 Часть II. Расширенные средства Tcl set path => /book/ Проблемы, возникающие при использовании итераторов При создании шаблонов, в которых одни итераторы работают в режиме "расточительного", а другие в режиме "экономного" сопоставления, могут возникать проблемы. Дело в том, что в сложных выражениях итераторы могут интерпретироваться неоднозначно. Интерпретатор Tcl стремится либо обрабатывать все итераторы как "экономные", либо как "расточительные". В листинге 11.4 символ $ использовался для "привязки" последнего выражения, работающего в режиме "расточительного" сопоставления, к концу строки. Такой режим работы последнего подшаблона должен означать соответствие ему всех символов до конца последовательности. На практике Tcl переводит все итераторы в режим "экономного" сопоставления, поэтому якорь $ помечает позицию перед символом перевода строки. Примеры регулярных выражений В табл. 11.7 описаны регулярные выражения, которые можно непосредственно использовать в Tcl-командах. Большинство из них помещено в фигурные скобки для того, чтобы отменить специальное значение квадратных скобок и символов $. В некоторых из шаблонов, содержащих последовательности, начинающиеся с обратной косой черты (например, \п или \t), для группировки использовались двойные кавычки. В Tcl 8.0 и более ранних версиях подстановка этих последовательностей должна быть выполнена перед вызовом команды regexp. Таблица 11.7. Простые регулярные выражения {"[yY]} Последовательность символов, начинающаяся с у или Y, например ответ Yes {"(yesl YES I Yes) $} Последовательность символов "yes", "Yes" или "YES" {~ [" \t: \] +:} Последовательность, заканчивающаяся двоеточием, не содержащая пробелов и знаков табуляции {~\S+?:} Выполняет те же действия, что и предыдущее выражение, но для указания того, что пробелы и другие подобные символы недопустимы, используется сочетание \S
Глава 11. Регулярные выражения 261 Окончание табл. 11.7 "Л\[ \t]*$" Строка, состоящая только из пробелов или символов табуляции {(?n) ~\s*$} Пустая строка. Для проверки используется режим с учетом перевода строки " (\п | ~) \ [~\п\] * (\n I $)" Пустая строка. Используются базовые средства регулярных выражений {Л [A-Za-z]+$} Строка, содержащая только буквы {" [[:alpha:]]+$> Строка, содержащая только буквы. Они могут быть представлены в формате Unicode {[A-Za-z0-9_]+} Буквы, цифры и символы подчеркивания {\w+} Буквы, цифры и символы подчеркивания, определенные с помощью сочетания \w {[] [${}\\] } Набор специальных символов Tcl: ] [ $ { } \ "\[Л\п\]*\п" Произвольная последовательность до символа перевода строки {.*?\п} Произвольная последовательность до символа перевода строки. Используется "экономное" сравнение {\.} Точка {[] [$"?+*() 1\\]} Набор специальных символов, используемых для создания регулярных выражений: ] [$"? + * ( ) I \ <Н1> (. *?) </Н1> HTML-дескриптор <Н1>. Подшаблон помечает строку между открывающим и закрывающим дескрипторами <!--.*? — > Комментарии HTML {[0-9a-hA-H] [0-9a-hA-H]} Две шестиадцатеричные цифры {[ [: xdigit: ] ] {2}} Две шестиадцатеричные цифры. Для определения шаблона используются средства расширенных регулярных выражений {\d{l ,3}} От одной до трех цифр. Для определения шаблона используются средства расширенных регулярных выражений
262 Часть II. Расширенные средства Tcl Команда regsub Команда regsub осуществляет подстановку строк на основании сравнения с шаблоном. Такая возможность бывает очень полезна при обработке данных. Подобным способом можно решать как простые задачи (например, заменять последовательность, состоящую из пробелов и знаков табуляции, одним пробелом), так и более сложные, которые будут рассмотрены в следующем разделе. Команда regsub записывается следующим образом: regsub ? опции? шаблон строка выражение „замены имя_переменной Данная команда возвращает число удачных сравнений и замен. Если соответствие не было обнаружено, возвращается значение 0. Команда regsub копирует строку в переменную, заменяя вхождения последовательностей символов, определяемых шаблоном, на указанное выражение. Если соответствие шаблону обнаружить не удалось, строка копируется в неизменном виде. Ниже перечислены необязательные опции команды regsub. • -all. Замена всех фрагментов, определяемых шаблоном. Если данная опция не указана, заменяется только первый фрагмент. • Опции -nocase, -expanded, -line, -linestop и -lineanchor выполняют те же функции, что и одноименные опции команды regexp. Эти опции описывались ранее в данной главе. • Опция -- отделяет шаблон от опций. Она необходима, если шаблон начинается с символа -. Выражение, определяющее замену, может содержать литеральные символы, а также перечисленные ниже символы и последовательности, имеющие специальное значение. • Символ & заменяется строкой, соответствующей шаблону. • Последовательность \число заменяется фрагментом строки, который выдержал сравнение с соответствующим подшаблоном. Отсчет подшаб- лонов осуществляется по открывающим круглым скобкам. Приведенное ниже выражение заменяет рабочий каталог пользователя символом ~. regsub ~$env(H0ME)/ $pathname ~/ newpath Следующие команды формируют строку вызова компилятора для обработки заданного файла: set file tclIO.c regsub {([~\.]*)\.c$} $file {cc -c & -o \l.o} ccCmd
Глава 11. Регулярные выражения 263 Шаблон помечает все символы имени файла, предшествующие суффиксу .с. Символ & заменяется строкой, соответствующей всему шаблону (в данном случае это tclIO. с), а ссылка \1 заменяется фрагментом tclIO, который соответствует подшаблону, содержащемуся в круглых скобках. В результате ccCrad присваивается следующее значение: ее -с tclIO.c -о tclIO.o Его можно выполнить с помощью приведенного ниже выражения. eval exec $ccCmd Следующая команда заменяет последовательность из нескольких пробелов, знаков табуляции и других подобных символов одним пробелом: regsub -all {\s+} $string " " string Переменную, в которой хранится входная строка, можно использовать для записи результатов выполнения команды; это не приведет к возникновению ошибки. С помощью команды regsub можно организовать счетчик. Приведенное ниже выражение подсчитывает количество символов перевода строки в тексте. В этом случае наличие или отсутствие подстановки не имеет значения. set numLines [regsub -all \n $text {} ignore] Преобразование данных в программу с помощью regsub Tcl-команды regsub и subst открывают перед разработчиком широкие возможности. В этом разделе описано несколько примеров, в которых команда regsub используется для преобразования данных в Tcl-команды, после чего replace заменяет эти команды новыми версиями данных. Такой подход очень эффективен, поскольку при этом используются средства обработки регулярных выражений и синтаксический анализатор Tcl. Обе подсистемы написаны на языке С, и коды программ оптимизированы. Декодирование URL Перед тем как переслать серверу данные по сети, HTTP-клиент кодирует их. Специальные символы заменяются трехеимвольной последовательностью У.хх, где хх — шестнадцатеричный код. Вдобавок каждый пробел заменяется знаком +. Выполнять посимвольную обработку данных средствами Tcl — не столь трудная, сколь скучная задача. Кроме того, подобная программа работала бы крайне неэффективно. Более высокую производительность обес-
264 Часть II. Расширенные средства Tcl печила бы программа на языке С, но ее написание отнимет неоправданно много времени у разработчика. Наилучшее решение — использовать сочетание regsub и subst. В этом случае для декодирования данных, переданных клиентом, потребуется лишь несколько Тс1-команд. Поскольку знак + обрабатывается в регулярных выражениях специальным образом, то при замене этого символа на пробел надо указать перед ним обратную косую черту. regsub -all {\+} $url { } url Последовательность °/0хх заменяется нужным символом с помощью команды format. regsub -all {•/.( [0-9a-hA-H] [0-9a-hA-H])} $url \ {[format °/oC Ox\l]} url Директива °/0с в составе команды format сообщает о том, что код надо представить как символ. Для того чтобы число воспринималось как шестна- дцатеричное, перед обратной ссылкой добавлены символы Ох. Расширенные регулярные выражения позволяют указать, что шестиадцатеричных цифр должно быть ровно две. Это дает возможность более точно сформировать шаблон. regsub -all Ш[[:xdigit:]]{2})} $url \ {[format °/oC Ox\l]} url Полученная в результате преобразования строка передается команде subst для выполнения подстановки. set url [subst $url] Например, если строка содержит символы °/07ewelch, то результатом выполнения команды regsub будет следующая последовательность: [format °/oC 0x7e] welch В результате команда subst сгенерирует строку "welch В листинге 11.5 описанный выше подход использовался при создании процедуры Url_Decode. Листинг 11.5. Процедура Url_Decode proc Url_Decode {url} { regsub -all {\+} $url { } url regsub -all {•/.( [:xdigit :]]{2})} $url \ {[format °/oC Ox\l]} url
Глава 11. Регулярные выражения 265 return [subst $url] } Разбор CGI-параметров В листинге 11.6 рассмотренный выше принцип декодирования символов используется для обработки входных данных, полученных CGI-программой. Эти данные обычно вводятся пользователем в HTML-форме. Каждый элемент формы имеет имя и значение. При передаче серверу некоторые символы кодируются так, как это было рассмотрено выше. Имена и значения передаются CGI-программе в следующем формате: имя_1=значение_1&имя_2=значение_2&имя_3=значение_3 В листинге 11.6 содержится код процедур Cgi_List и Cgi_Query. В зависимости от метода запроса (POST или GET), Cgi.Query получает данные формы из стандартного ввода либо из переменной окружения QUERY_STRING. Обработка HTTP-запросов будет подробно обсуждаться в главе 17. В процедуре Cgi_List осуществляется преобразование строки запроса в список имен и значений. Для преобразования используется команда split. Для декодирования полученных данных вызывается процедура Url_Decode. Она возвращает список имен и значений, удобный для обработки средствами Tcl. Вы можете организовать их перебор посредством команды f oreach либо поместить в массив, используя для этого операцию array set. Листинг 11.6. Процедуры Cgi.List и Cgi_Query proc Cgi.List {} { set query [Cgi.Query] regsub -all {\+} $query { } query set result {} foreach {x} [split $query &=] { lappend result [Url_Decode $x] } return $result } proc Cgi_Query {} { global env if {![info exists env(QUERY_STRING)] II [string length $env(QUERY_STRING)] == 0} { if {[info exists env(CONTENTJLENGTH)] && [string length $env(CONTENT_LENGTH)] != 0} { set query [read stdin $env(CONTENT_LENGTH)]
266 Часть II. Расширенные средства Tcl } else { gets stdin query } set env(QUERY_STRING) $query set env(CONTENT__LENGTH) 0 } return $env(QUERY_STRING) _} В HTML-форме может содержаться несколько элементов с одинаковыми именами. Это приведет к тому, что в строке параметров, передаваемой на сервер, с одним именем будет связано несколько значений. Если вы используете array set для отображения результатов, полученных при выполнении Cgi_List, в массив, это может привести к потере некоторых значений. В листинге 11.7 показаны процедуры Cgi_Parse и Cgi.Value, при выполнении которых данные запроса сохраняются в глобальном массиве cgi. Когда процедура Cgi_Parse обнаруживает повторяющиеся значения формы, она добавляет структуру списка. В глобальном массиве cgilist содержится информация о числе повторов значения формы. Процедура Cgi_Value возвращает элементы глобального массива cgi либо, если запрашиваемое значение отсутствует, пустую строку. Листинг 11.7. Процедуры Cgi_Parse и Cgi_Value, использующие для хранения данных запроса глобальный массив cgi proc Cgi_Parse {} { global cgi cgilist catch {unset cgi cgilist} set query [Cgi.Query] regsub -all {\+} $query { } query foreach {name value} [split $query &=] { set name [CgiDecode $name] if {[info exists cgilist($name)] && ($cgilist($name) == 1)} { # Добавление второго значения и создание структуры списка set cgi($name) [list $cgi($name) \ [Url.Decode $value]] } elseif {[info exists cgi($name)]} { # Добавление дополнительных элементов списка lappend cgi($name) [CgiDecode $value] } else { # Добавление первого значения # без создания структуры списка
Глава 11. Регулярные выражения 267 set cgi($name) [CgiDecode $value] set cgilist($name) 0 ;# Возможно, потребуется # преобразование в список } incr cgilist($name) } return [array names cgi] } proc Cgi_Value {key} { global cgi if {[info exists cgi($key)]} { return $cgi($key) } else { return {} } } proc Cgi_Length {key} { global cgilist if {[info exist cgilist($key)]} { return $cgilist($key) } else { return 0 } } Декодирование HTML-примитивов Рассмотрим пример применения регулярных выражений для декодирования HTML-примитивов. HTML-примитивами называются последовательности, начинающиеся со знака &, которыми представляются некоторые специальные символы. Если в документе должны содержаться литеральные символы < и >, то, чтобы избежать конфликта с угловыми скобками, использующимися в составе дескрипторов, их надо кодировать соответственно с помощью примитивов felt; и &gt;. Синтаксис языка HTML кратко описывался в главе 3. Кодировать посредством примитивов следует также символы, код которых превышает 127, например буквы национальных кодировок. Для часто используемых символов предусмотрены именованные примитивы. Остальные символы представляются числовым кодом. Например, чтобы включить в документ знак С, надо указать последовательность &#169;. В некоторых случаях, например в примитивах &lt; и &gt;, завершающую точку с запятой можно не указывать. Так, запись &lt эквивалента &lt;.
268 Часть II. Расширенные средства Tcl Процедура декодирования примитивов работает по тому же принципу, что и Url_Decode. Однако в данном случае надо соблюдать осторожность при использовании команды subst. Текст, переданный процедуре декодирования, может содержать специальные символы, например квадратные скобки или знак $. Работая с процедурой Url_Decode, мы предполагали, что все специальные символы закодированы последовательностями, начинающимися с °/0. Принцип формирования примитивов иной, поэтому во входной строке могут содержаться символы $, [ и ]. В процедуре необходимо отменить специальное значение данных символов. Это осуществляется с помощью команды regsub, которая помещает перед открывающей и закрывающей квадратной скобкой, а также перед символом $ обратную косую черту. Сама обратная косая черта предваряется таким же символом. regsub -all {[] [$\\]} $text {\Ш new Декодировать символ, представленный с помощью десятичного числа (например, &#169;); несколько сложнее, чем трехсимвольную последовательность, начинающуюся с °/0. Кроме того, число не обязательно должно быть десятичным. Если оно начинается с нуля (например, &#010;), то Tcl считает его восьмеричным. Команда scan интерпретирует числа как десятичные. Результат выполнения команды помещается во временную переменную. Для его получения используется команда set. regsub -all {&#( [0-9] [0-9]?[0-9]?);?} $new \ {[format °/oC [scan \1 °/0d tmp; set tmp]]} new При использовании расширенных регулярных выражений приведенная ниже команда может быть переписана так, как показано ниже. В ней используется итератор с ограниченным количеством повторений, указывающий на то, что число может состоять из одной, двух или трех цифр. regsub -all {&#(\d{l,3»;?} $new \ {[format °/0c [scan \1 °/0d tmp;set tmp]]} new Для преобразования именованных примитивов используется массив, отображающий имена в специальные символы. Неизвестные примитивы не преобразуются. Преобразование осуществляется в рамках процедуры HtmlMapEntity, которая отслеживает появление недопустимых примитивов. regsub -all {&([a-zA-Z]+)(;?)} $new \ {[HtmlMapEntity \1 \\\2 ]} new Если в исходной строке текста содержится выражение [х ftlt; у] то команда regsub преобразует его в вид
Глава 11. Регулярные выражения 269 \[х [HtmlMapEntity It \; ] у\] Результатом выполнения команды subst будет приведенная ниже последовательность символов. [х < у] Листинг 11.8. Процедура Html_DecodeEntity proc Html.DecodeEntity {text} { if {![regexp & $text]} {return $text} regsub -all {[] [$\\]} $text {\\&} new regsub -all {&#([0-9][0-9]? [0-9]?);?} $new {\ [format °/0c [scan \1 °/0d tmp;set tmp]]} new regsub -all {&([a-zA-Z]+)(;?)} $new \ {[HtmlMapEntity \1 \\\2 ]} new return [subst $new] } proc HtmlMapEntity {text {semi {}}} { global htmlEntityMap if {[info exist htmlEntityMap($text)]} { return $htmlEntityMap($text) } else { return $text$semi } } # Часть массива htmlEntityMap array set htmlEntityMap { It < gt > amp& aring \xe5 atilde\xe3 copy \xa9 ecirc \xea egrave \xe8 } Простая программа разбора HTML-кода В примере, приведенном в листинге 11.9, команда regsub используется для преобразования HTML-кода в Tcl-сценарий. При выполнении сценарий вызывает процедуру, обрабатывающую каждый дескриптор, содержащийся в HTML-документе. Для получения необходимых результатов к дескрипторам применяются различные процедуры обратного вызова. Например, пакет html_library-0.3 (его можно найти на прилагаемом к книге компакт-диске)
270 Часть II. Расширенные средства Tcl использует Html_Parse для отображения HTML-текста с помощью компонента Тк. Листинг 11.9. Процедура Html_Parse proc Html_Parse {html cmd {start {}}} { # Отображение скобок и символов обратной косой черты # в примитивы HTML regsub -all \{ $html {\&ob;} html regsub -all \} $html {\&cb;} html regsub -all {\\} $html {\&bsl;} html # Данный шаблон определяет компоненты HTML-дескриптора set s" \t\r\n" ;# пробел set exp <(/?)(\r$s>]+)\[$s]*(\r>]*)> # Генерация вызова cmd с указанием # компонентов HTML-дескрипторов # \1 - косая черта перед именем дескриптора (если имеются) # \2 - имя HTML-дескриптора # \3 - атрибуты дескриптора (если имеются) # Фигурные скобки используются для группировки; текст # после HTML-дескриптора становится последним параметром $cmd. set sub "\}\n$cmd {\\2} {\\1} {\\3} \{" regsub -all $exp $html $sub html # Поддержка соответствия фигурных скобок # и вызов $cmd. При этом $start используется как # псевдодескриптор в начале и в конце сценария, eval "$cmd {$start} {} {} {$html}" eval "$cmd {$start} / {} {}" Основной шаблон regsub может быть переписан с использованием расширенных регулярных выражений. set exp {<(/?)(\S+?)\s*(.*?)>} Процесс преобразования HTML-кода иллюстрирует следующий пример. Пусть HTML-текст выглядит так, как показано ниже. <Title>My Home Page</Title>
Глава 11. Регулярные выражения 271 <Body bgcolor=white text=black> <Н1>Му Home</Hl> This is my <b>home</b> page. Процедура Html.Parse вызывается следующим образом: Html_Parse $html {Render .text} hmstart Полученная программа будет иметь такой вид: Render .text {hmstart} {} {} {} Render .text {Title} {} {} {My Home Page} Render .text {Title} {/} {} { } Render .text {Body} {} {bgcolor=white text=black} { } Render .text {HI} {} {} {My Home} Render .text {HI} {/} {} { This is my } Render .text {b} {} {} {home} Render .text {b} {/} {} { page. } Render .text {hmstart} / {} {} Обратите внимание на особенности использования eval и subst. Программы декодирования, приведенные в листингах 11.5 и 11.8, используют subst для замены закодированных символов. Остальной текст остается неизменным. Процедура Html_Parse должна обработать весь HTML-код. Текст, удовлетворяющий шаблону (например, HTML-дескриптор), заменяется Tcl- кодом, который заканчивается открывающей фигурной скобкой и начинается закрывающей скобкой. Это позволяет эффективно группировать текст, не соответствующий шаблону. Если команда eval используется так, как в рассматриваемом примере, необходимо обеспечить правильную обработку фигурных скобок и символов обратной косой черты в тексте, не соответствующем шаблону. В противном случае результирующий сценарий может интерпретироваться некорректно. В данном случае эти специальные символы кодируются как HTML-примитивы. Вызываемая команда cmd должна получать закодированные примитивы. Отменять специально значение символов с помощью обратной косой черты мы не можем, поскольку весь текст находится в фигурных скобках и подстановка таких последовательностей не будет выполняться. Если вы попытаетесь поступить подобным образом, символы обратной косой черты станут доступны команде cmd. Может показаться, что приведенное ниже выражение не должно работать.
272 Часть II. Расширенные средства Tcl eval "$cmd {$start} {} {} {$html}" Однако это не верно. Подстановка $start и $html выполняется, несмотря на наличие фигурных скобок. Если для группировки используются двойные кавычки, то фигурные скобки теряют свои специальные свойства. Удаление HTML-комментариев Процедура Html_Parse не обрабатывает HTML-комментарии. Проблема заключается в том, что в языке HTML в составе комментариев могут содержаться дескрипторы, в состав которых, в свою очередь, входят символы >. Кроме того, HTML-комментарии могут содержать Javascript-сценарии, в которых также используются символы >. Поэтому создаваемую процедуру надо модифицировать так, чтобы комментарии исключались из рассмотрения. HTML-комментарии записываются следующим образом: <!-- HTML-комментарии, которые могут содержать элементы разметки --> Используя итераторы, работающие в режиме ''экономного" сравнения, мы можем удалить комментарии с помощью одного вызова команды regsub. regsub -all <!--.*?--> $html {} html Если в нашем распоряжении имеются только "расточительные"' итераторы, выделить последовательность --> несколько труднее. При этом можно некорректно обработать вложенный символ > или интерпретировать как комментарии всю последовательность, содержащуюся между первой группой символов (<!--) и последней (-->). Поэтому в данном случае целесообразно использовать такой прием: regsub -all --> $html \x81 html Представленное выше выражение заменяет признаки конца комментариев единичным символом, недопустимым в языке HTML. Теперь для удаления комментариев можно использовать следующую команду: regsub -all и<! —\ [Лх81\] *\х81" $html {} html Команды, использующие регулярные выражения Регулярные выражения применяются при выполнении следующих Тс1-ко- манд.
Глава 11. Регулярные выражения 273 • Isearch. При вызове данной команды может задаваться опция -regexp. В этом случае можно выполнять поиск пунктов списка, соответствующих регулярному выражению. (О назначении команды Isearch см. в главе 5.) • switch. В этой команде также предусмотрена опция -regexp, в результате вы можете организовывать условное выполнение фрагментов кода, исходя из соответствия строки регулярному выражению. (О команде switch см. в главе 6.) • Текстовые компоненты Тк могут выполнять поиск содержимого, сравнивая данные с регулярным выражением. Поиск в текстовых компонентах описывается в главе 36. • Tcl-расширение Expect позволяет сравнивать с реальными выражениями выходные данные программы. Рассмотрению расширения Expect посвящены отдельные издания, например книга Дона Лайбса (Don Libes) Exploring Expect (O'Reilly, 1995).
Глава 12 Библиотеки сценариев и пакеты Библиотеки содержат наборы Tcl-команд, организованные в пакеты. Если в приложении вызывается та или иная команда, Tcl автоматически загружает соответствующие библиотеки. В данной главе будут обсуждаться следующие команды: package, pkg_mkIndex, auto_mkindex, unknown и tcl_findLibrary. JD библиотеки помещаются наборы часто применяемых Tcl-процедур, и в результате они становятся доступны различным приложениям. В частности, код любого из примеров, приведенных в данной книге, можно использовать в библиотеке сценариев и указывать приложениям на то, что не определенные в программе процедуры следует искать в этой библиотеке. Некоторые разработчики создают сложные приложения как один главный сценарий небольшого объема и библиотеку, к которой обращается этот сценарий. Преимущество такого подхода состоит в том, что приложение может начать выполняться, еще не загрузив весь код, необходимый для его функционирования. В результате программа начинает работать сразу же после ее вызова, а конкретные процедуры автоматически загружаются по мере его выполнения. Средства работы с пакетами Tcl поддерживают номера версий и используют в работе модель предоставления по требованию (provide/require model). Обычно каждый библиотечный файл предоставляет один пакет с определенным номером версии. Пакеты также могут работать с библиотеками разделяемых объектов, которые содержат скомпилированный код (подробно этот вопрос будет обсуждаться в главе 47). Пакет может представлять собой сочетание файлов сценариев и объектных файлов. Приложение указывает, какие пакеты требуются ему для работы, и соответствующие библиотеки загружаются автоматически. Средства поддержки пакетов были разработаны как альтернатива схеме автозагрузки, используемой в ранних версиях Tcl. В дан-
Глава 12. Библиотеки сценариев и пакеты 275 ной главе описаны оба механизма; в своих программах вы можете применять любой из них. Создавая пакет, вы, вероятно, захотите использовать механизм пространства имен. Это позволит избежать конфликтов между именами процедур и глобальных переменных в различных пакетах. Пространства имен будут подробно обсуждаться в главе 14. В версиях, предшествующих Tcl 8.0, чтобы уменьшить вероятность возникновения конфликта, вам пришлось бы разрабатывать собственные соглашения об именовании. В данной главе будет обсуждаться несложное соглашение, которому можно следовать при разработке больших Tcl-программ. Подобное соглашение использовалось одним из авторов этой книги при написании интерфейса почтовой программы exmh. В результате доработок размеры этой программы возросли с 2000 до 35000 строк Tcl-кода. Основная часть дополнительного кода была создана пользователями exmh. Без соглашения об именовании доработать код подобным образом было бы невозможно. Доступ к пакетам: переменная auto_path Средства поддержки пакетов предполагают, что Tcl-библиотеки располагаются в так называемых известных (well-known) каталогах. Список известных каталогов содержится в Tcl-переменной auto_path. Эта переменная инициализируется tclsh и wish и включает каталог библиотеки сценариев Tcl, его родительский каталог и каталог библиотеки сценариев Тк (для wish). Например, в переменной auto_path на компьютере Macintosh могут быть указаны следующие каталоги: Disk: System Folder .-Extensions: Tool Command Language: tcl8.4 Disk:System Folder extensions:Tool Command Language Disk:System Folder extensions:Tool Command Language:tk8.4 В системе Windows 95 в переменной auto_path указаны каталоги, перечисленные ниже. c:\Program Files\Tcl\lib\Tcl8.4 c:\Program Files\Tcl\lib c:\Program Files\Tcl\lib\Tk8.4 И, наконец, на рабочей станции Unix auto_path содержит следующую информацию: /usr/local/tcl/lib/tcl8.4 /usr/local/tcl/lib /usr/local/tcl/lib/tk8.4
276 Часть II. Расширенные средства Tcl Поиск требуемых пакетов осуществляется в этих каталогах и содержащихся в них подкаталогах. Организовать управление своими пакетами проще всего, создав каталог на том же уровне, что и библиотека Tcl. /usr/local/tcl/lib/welchbook Пакеты будут найдены автоматически, поскольку auto_path содержит информацию о каталоге /usr/local/tcl/lib. По необходимости вы можете дополнить ее auto_path новыми сведениями о каталогах. lappend auto_path каталог Программисты часто указывают в auto_path каталог, содержащий основной сценарий. Сделать это можно с помощью приведенной ниже команды. lappend autojpath [file dirname [info script]] Если код программы содержится в каталогах bin и lib, сценарий, находящийся в каталоге bin, может включить в auto_path каталог lib с помощью следующей команды: lappend auto_path \ [file join [file dirname [info script]] ../lib] Использование пакетов В каждом файле сценария, принадлежащем библиотеке, указывается, какой пакет он реализует. Для этой цели используется команда package. package provide имя версия Имя является идентификатором пакета; версия задается в формате старший_номер. младший_номер. По соглашениям младший номер версии может изменяться, но при этом реализация пакета должна обеспечивать совместимость с теми же программами, что и другие реализации. Если пакет перестает быть совместимым с предыдущими вариантами, необходимо изменить номер версии. В качестве примера в главе 17 рассматривается несколько процедур, использующих протокол HTTP. Это процедуры http: :geturl, http: :wait и http: :cleanup. Файл, содержащий эти процедуры, начинается с команды package provide http 2.4 В имени пакета учитывается регистр символов. Например, пакет, о котором шла речь выше, называется http; в его имени присутствуют лишь символы нижнего регистра.
Глава 12. Библиотеки сценариев и пакеты 277 Одному пакету может соответствовать несколько файлов; надо лишь указывать в них одно и то же имя и версию. Аналогично, различные версии одного и того же пакета могут храниться в одном каталоге, но в разных файлах. Необходимые пакеты приложение указывает с помощью команды package require. package require имя ?версия? ?-exact? В результате выполнения данной команды загружается последняя версия с указанным старшим номером. Если версия не задана, загружается последняя из имеющихся версий. Например, если приложению необходима версия пакета с номером 1.1, то может быть загружена версия 1.2, но не версии 1.0 и 2.0. Для того чтобы осуществить привязку к конкретной реализации пакета, надо использовать опцию -exact. Если подходящая версия не обнаружена, при выполнении команды package require возникает ошибка. Автоматическая загрузка пакетов Команда package require использует базу данных, в которой указано, какие файлы реализуют те или иные пакеты. Базу данных поддерживает либо сам разработчик, либо сотрудник, отвечающий за состояние библиотек проекта, либо системный администратор. При внесении изменений в пакет необходимо соответствующим образом модифицировать базу данных. Для создании базы используется команда pkg_mklndex, которая помещает данные в файл pkglndex.tcl, находящийся в каждом каталоге библиотеки. При вызове команды pkg_mklndex ей передаются имя каталога, а также один или несколько glob-шаблонов, определяющих имена файлов в этом каталоге. О шаблонах имен файлов см. в главе 9. Команда pkg_mklndex вызывается следующим образом: pkg.mklndex ?опции? каталог шаблон ?шаблон ...? Например: pkg_mklndex /usr/local/lib/welchbook *.tcl pkgjnklndex -lazy /usr/local/lib/Sybtcl *.so Команда pkg_mklndex проверяет все файлы, имена которых соответствуют шаблону, определяет, каким пакетам они соответствуют, и формирует базу данных. Каждый разработчик должен представлять себе общие принципы выполнения этой команды и знать, что она работает только с библиотеками. Если после вызова команды pkg_mklndex прекращает работу та или иная программа, причина случившегося, вероятнее всего, в том, что этой команде вместо файла библиотеки был передан файл прикладной программы.
278 Часть II. Расширенные средства Tcl Обращение к базе данных пакетов, находящейся в файле pkglndex.tcl, происходит в ответ на package require. По умолчанию команды source или load выполняются сразу же после вызова package require. Такой подход называется непосредственной загрузкой. Однако в системе индексации пакетов реализован также механизм отложенной загрузки. Его работа основана на командах auto_load и unknown, которые будут описаны ниже. Если вы хотите включить режим отложенной загрузки, при вызове команды pkg_mklndex укажите опцию -lazy. Режим выполнения команды pkg_mklndex по умолчанию был изменен в Tcl 8.3. Если в более ранних версиях использовалась опция -lazy, то начиная с Tcl 8.3 по умолчанию предполагается использование опции -direct. Опции команды pkg_mklndex описаны в табл. 12.1. Таблица 12.1. Опции команды pkg_mkIndex -direct Генерирует базу данных пакетов, содержащую команды source и load. При вызове команды package require пакеты загружаются непосредственно. Такой подход по умолчанию применяется начиная с версии Tcl 8.3 -lazy Генерирует базу данных пакетов, которая предоставляет массив auto_index для отложенной загрузки команд. Данный подход использовался по умолчанию в версиях, предшествующих Tcl 8.3 -load шаблон Включает режим динамической загрузки пакетов, соответствующих шаблону, с использованием дополнительного интерпретатора. Данная опция обычно применяется для загрузки файлов, скомпилированных с помощью TclPro Compiler -verbose Включает отображение информации о всех обрабатываемых файлах и возникающих ошибках Пакеты, реализованные в виде С-программ Файлы библиотеки могут представлять собой либо файлы сценариев, содержащие Tcl-процедуры, либо двоичные файлы в формате разделяемых библиотек (DLL), в котором находится реализация Tcl-команд на компилируемом языке. Вопросы написания Tcl-команд на языке С рассматриваются в главе 47. В С API предусмотрены средства управления пакетами, которые, в частности, позволяют объявить имя пакета для команд, реализованных в файле. Пример использования API приведен в листинге 47.1. В главе 37 описывается Tcl-команда load, которая может использоваться вместо команды source для компоновки разделяемых библиотек. Команда pkg_raklndex также поддерживает разделяемые библиотеки. pkg_mklndex каталог *.tcl *.so *.shlib *.dll
Глава 12. Библиотеки сценариев и пакеты 279 В данном примере . so, . shlib и .dll -- это суффиксы имен файлов разделяемых библиотек для систем Unix, Macintosh и Windows. Вы можете создать пакет, одни команды которого будут реализованы па языке С, а другие как Tcl-процедуры. Необходимо лишь указать в файлах сценариев и разделяемых библиотеках, что они принадлежат одному и тому же пакету. В результате команда pkg.mklndex настроит необходимым образом auto_index. Если файловые серверы поддерживают различные архитектуры, например Solaris и Linux, вы, вероятно, захотите хранить файлы разделяемых библиотек в каталогах, ориентированных на конкретные машины. В этом случае в auto_path должны быть также перечислены соответствующие каталоги, в противном случае автоматическая загрузка разделяемых библиотек станет невозможна. Если системный администратор правильно настроил средства Tcl, разработчикам не приходится заботиться о конкретных установках. Если же при инсталляции были допущены ошибки или если вы собираетесь хранить разделяемые библиотеки в нестандартных позициях файловой системы, вам надо добавить сведения о расположении файлов в переменную auto_path. Порядок загрузки пакетов Процесс загрузки пакетов можно описать следующим образом. • Приложение вызывает команду package require. Если пакет уже загружен, команда лишь возвращает номер версии этого пакета. В противном случае выполняются следующие действия. • Проверяется наличие сведений о данном пакете. Если сведения имеются, вызываются Tcl-сценарии, зарегистрированные с помощью команды package ifneeded. Эта команда либо загружает пакет, либо настраивает его для автоматической загрузки. Во втором случае загрузка осуществляется тогда, когда впервые возникает необходимость в одной из команд пакета. • Если сведений о пакете нет, вызывается процедура tclPkgUnknown. По необходимости вы можете задать свою процедуру поиска, которая будет вызываться посредством команды package unknown, однако стандартной считается tclPkgUnknown. Процедура tclPkgUnknown проверяет каталоги, указанные в переменной auto_path, а также их подкаталоги и находит файлы pkglndex. tcl, которые используются для формирования внутренней базы данных, содержащей информацию о пакетах и их версиях. Файлы pkglndex.tcl включают обращения к package ifneeded. Эта операция реализует действия, необходимые для опреде-
280 Часть II. Расширенные средства Tcl ления пакета. Для создания файлов pkglndex.tcl можно использовать команду pkg_mklndex. Вы можете также создать эти файлы вручную. • В случае отложенной загрузки пакета процедура tclPkgSetup определяет массив auto_index, содержащий необходимую команду source или load для каждой команды пакета. Автоматическая загрузка и массив auto_index более подробно описываются далее в этой главе. Нетрудно заметить, что при поиске пакетов используется несколько уровней обработки. Система обладает достаточной гибкостью и позволяет изменять способы поиска и загрузки пакетов. Действия, выполняемые в режиме отложенной загрузки, который включается с помощью опции -lazy, достаточно сложны (подробно об этом — в следующем разделе). Если при вызове pkg_mklndex указана опция -direct, для обработки задействуются более простые средства. В любом случае выполняются следующие действия. • Для поддержки файлов базы данных используется команда pkg_mklndex. Перед тем как вызывать эту команду, необходимо решить, должна ли осуществляться непосредственная или отложенная загрузка. • В код программы включаются команды package require и package provide. • Библиотечные каталоги или их родительские каталоги должны быть указаны в переменной auto_path. Команда package Посредством команды package реализуется несколько операций, результаты которых используются в основном процедурой pkg_mkIndex и средствами автоматической загрузки. Эти операции описаны в табл. 12.2. Таблица 12.2. Операции, реализуемые с помощью команды package package forget пакет Удаляет регистрационные данные для пакета package ifneeded пакет Предоставляет сведения о кОхМанде, используемой ?команда? для поддержки автоматической загрузки пакета, либо устанавливает эту команду package names Возвращает сведения о зарегистрированных пакетах package provide пакет Сообщает о том, что в файле сценария определены версия команды для пакета указанной версии package present пакет Данная операция эквивалентна package require. ?версия? ?-exact? Отличие состоит лишь в том. что если пакет не загружен, попытка загрузить его не предпринимается
Глава 12. Библиотеки сценариев и пакеты 281 Окончание табл. 12.2 package require пакет ?версия? ?-exact? package unknown ?команда? package vcompare версия_1 в ерсия_2 package versions пакет package vsatisfies в ерсия_1 в ер сия_2 Сообщает о том, что сценарию требуется пакет. Опция -exact указывает на то, что необходимо загрузить ту же версию, которая задана в команде. Если данная опция отсутствует, загружается последняя из версий, соответствующих заданной Устанавливает команду, используемую для обнаружения пакета, либо запрашивает сведения о ней Сравнивает указанные версии. Возвращает 0, если версии эквивалентны. Возвращаемое значение —1 указывает на то, что первая версия предшествует второй, а значение 1 — на то, что версия 1 младше версии 2 Возвращает сведения о зарегистрированных версиях пакета Возвращает значение 1 в том случае, если версия 1 младше или равна версии 2 и при этом главные номера версий совпадают. В противном случае возвращается О Создание библиотек с помощью файла tcllndex Вы можете создавать библиотеки, не пользуясь командой package. Если в каталоге находится библиотека сценариев, то сведения о Tcl-командах, определенных в библиотеке, содержатся в файле tcllndex. Библиотеки, создаваемые вручную, имеют недостатки. В частности, в них не поддерживаются номера версий, а для формирования данных о библиотечных каталогах приходится часто модифицировать файл auto_path. Однако такой подход имеет свои преимущества; основное из них состоит в том, что данный механизм поддерживался начиная с самых ранних версий Tcl. Библиотека, сформированная с помощью файла tcllndex, будет работать во всех разновидностях среды Tcl. Для создания библиотеки надо сгенерировать базу, в которой содержались бы сведения о всех процедурах, определенных в библиотеке. Эта база создается с помощью процедуры auto.mkindex, код которой хранится в файле с именем tcllndex. Файл tcllndex, в свою очередь, располагается в каталоге библиотеки сценариев. (Обратите внимание на использование регистра символов в именах auto_mkindex и pkg_mklndex!) Предположим, что все примеры для данной книги располагаются в каталоге /usr/local/tcl/welchbook.
282 Часть II. Расширенные средства Tcl Коды примеров можно преобразовать в библиотеку сценариев, создав файл tcllndex. auto_mkindex /usr/local/tcl/welchbook *.tcl После добавления процедур или изменения имени любой из них надо обновить файл tcllndex. Пример традиционного подхода к решению этой задачи приведен в листинге 12.1. Данный способ можно скорее назвать консервативным, поскольку при любых изменениях библиотеки, внесенных после последней генерации файла tcllndex, база данных должна быть создана повторно. При этом не имеет значения, связаны ли эти изменения с добавлением или удалением ТсЛ-процедуры. Листинг 12.1. Поддержка файла tcllndex proc Library_UpdateIndex { libdir } { set index [file join $libdir tcllndex] if {![file exists $index]} { set doit 1 } else { set age [file mtime $index] set doit 0 # Изменения содержимого каталога могут означать # удаление файлов if {[file mtime $libdir] > $age} { set doit 1 } else { # Проверка каждого файла, был ли он модифицирован foreach file [glob [file join $libdir *.tcl]] { if {[file mtime $file] > $age} { set doit 1 break } } } } if { $doit } { auto_mkindex $libdir *.tcl } 2 Переменная auto_path содержит список каталогов, в которых должен осуществляться поиск неизвестной команды. Продолжая работу над кодом, обес-
Глава 12. Библиотеки сценариев и пакеты 283 печим доступ к примерам из данной книги. Для этого в начало сценария включим следующую команду: lappend auto_path /usr/local/tcl/welchbook Если файл tcllndex отсутствует, данная команда не выполнит никаких действий. Если вы хотите принять дополнительные меры предосторожности, можете вызвать Library„UpdaTclndex. В этом случае при появлении в библиотеке новых процедур произойдет обновление базы. lappend auto_path /usr/local/tcl/welchbook Library_UpdateIndex /usr/local/tcl/welchbook При отсутствии файла tcllndex данный подход не будет работать, поскольку Tcl не сможет найти реализацию Library_UpdateIndex. Создав tcllndex, вы можете быть уверены, что любая новая процедура, добавляемая к библиотеке, будет учтена в нем. Если вы выбрали данный способ автоматического обновления библиотеки, целесообразно включить процедуру LibraryJJpdaTclndex непосредственно в ваше приложение, исключив необходимость загрузки ее из библиотеки. Команда unknown Команда unknown осуществляет автоматическую загрузку Tcl-команд. Если интерпретатор Tcl обнаруживает команду, которую он не может обработать, он вызывает команду unknown, передавая ей в качестве параметра имя неизвестной команды. Код команды unknown написан на языке Tcl, поэтому вы можете реализовать ее самостоятельно. В данной главе будет рассмотрено поведение команды unknown, используемой по умолчанию. Код данной команды можно найти в файле init .tcl библиотеки Tcl. Информацию о расположении библиотеки предоставит вам команда info library. Автозагрузка Команда unknown использует в процессе работы массив auto_index. Каждый элемент массива соответствует процедуре, предназначенной для загрузки. Инициализацию auto_index осуществляют средства управления пакетами или файл tcllndex. Элемент auto__index представляет собой команду, определяющую процедуру. Примеры таких команд приведены ниже. source [file join $dir bind_ui.tcl] load [file join $dir mime.so] Mime В результате подстановки $dir в выражение включается имя каталога, содержащего файл библиотеки, а все выражение представляет собой команду
284 Часть II. Расширенные средства Tcl source или load для отсутствующей Tcl-команды. Подстановка выполняется с помощью eval, поэтому вы можете включить в auto_index любую команду по своему выбору. В листинге 12.2 представлена упрощенная версия кода, предназначенного для чтения файла tcllndex. Листинг 12.2. Загрузка файла tcllndex # Фрагмент упрощенной процедуры auto_load_index. # Проход по auto_path от конца к началу, set i [expr [llength $auto_path]-1] for {} {$i >= 0} {incr i -1} { set dir [lindex $auto_path $i] if [catch {open [file join $dir tcllndex]} f] { # База отсутствует continue } # Файл обрабатывается командой eval как сценарий. # Поскольку eval используется вместо source, # выполняется дополнительный этап подстановки, на котором # осуществляется расширение $dir. # В реальной процедуре здесь выполняется # проверка на наличие ошибок, eval [read $f] close $f } Запрет использования библиотеки: auto noload Если вы не хотите, чтобы при встрече неизвестных процедур предпринималась попытка обращения к библиотеке, вы можете запретить использование последней с помощью переменной auto_noload. set auto_noload любые_данные Автозагрузка осуществляется достаточно быстро. Опытные программисты используют эту возможность в программах различного размера и сложности. При наличии автозагрузки большие приложения запускаются быстрее, поскольку загружается лишь необходимый минимум кода. Остальная его часть автоматически загружается по мере необходимости. Автозагрузку целесообразно применять и для небольших программ. В этом случае разработчик привыкает помещать фрагменты кода общего назначения в библиотеки, что упорядочивает работу над проектом.
Глава 12. Библиотеки сценариев и пакеты 285 Соглашения, действующие при интерактивной работе При выполнении команды unknown соблюдается ряд соглашений. Они действуют только в том случае, когда пользователь непосредственно вводит команды. Если команда формируется в процессе работы сценария или если Tcl- оболочка не используется в интерактивном режиме, соглашения отменяются. К данным соглашениям относятся автоматический вызов программ, предыстория выполнения команд и сокращения имен команд. Tcl также предпринимают попытку применить данные средства в том случае, если команда не может быть загружена из библиотеки сценариев. Автоматический вызов программ В процедуре unknown реализован автоматический вызов внешних программ. Это делает оболочку Tcl еще более похожей на оболочки Unix. Подобно прочим оболочкам, при поиске внешних программ используется стандартная переменная окружения PATH. Если вы хотите отменить данную возможность, сделать это можно с помощью переменной auto_noexec. set auto_noexec лю6ые_данные Предыстория вызова команд Процедура unknown поддерживает предысторию вызова команд. Подробно этот вопрос будет обсуждаться в главе 13. Сокращения имен команд Если вы введете последовательность символов, совпадающую с началом имени команды, и эта последовательность позволит однозначно идентифицировать команду-, unknown распознает и выполнит ее. Попытка идентификации команды по сокращенному имени предпринимается лишь после того, как попытки вызова внешней команды и подстановки по предыстории оказались безуспешными. Среда оболочки Tcl В начале работы ТсЛ ищет каталог библиотеки сценариев. В ранних версиях Tcl местонахождение библиотек приходилось указывать в системном реестре Windows или в переменной окружения TCL_LIBRARY. В современных версиях расположение библиотек сценариев определяется с помощью стандартной схемы поиска. Средства поиска распознают структуру стандартной
286 Часть II. Расширенные средства Tcl среды Tcl, и потребность в содержимом переменной окружения TCL_LIBRARY отпадает. В системе Windows данные о библиотеках записаны в реестре, но их расположение также определяется с помощью стандартной процедуры. Как правило, при работе над проектом разработчикам не приходится заботиться о том, как производится поиск библиотек. Однако в случае возникновения проблем вам, возможно, придется разобраться в этом вопросе. Поэтому в данном разделе описывается механизм поиска библиотек сценариев. Расположение библиотеки сценариев Tcl Расположение библиотек, принятое по умолчанию, определяется при настройке дисгрибутивного пакета (этот вопрос подробно рассматривается в главе 41). В этот момент определяется начальное значение auto.path. (Значение по умолчанию хранится в tcl_pkgPath, но после запуска Tcl эта переменная уже не имеет значения.) В зависимости от результатов анализа файловой системы Tcl может проверять другие каталоги. В начале работы Tcl ищет каталог, содержащий стартовый сценарий init. tcl. По мере необходимости вы можете ускорить поиск, создав системную переменную TCL.LIBRARY. Если эта переменная определена, Tcl использует ее для работы с каталогом библиотеки сценариев. Однако в Tcl 8.0.5 и бо- iee поздних версиях нет необходимости использовать эту переменную. Если в системе присутствует несколько версий Tcl (они могут потребоваться для работы с различными приложениями и для тестирования), то TCL_LIBRARY даст корректный результат лишь в отдельных случаях. Если для работы с Tcl приходится задавать переменную окружения, это значит, что при инсталляции что-то было сделано неверно. Стандартная процедура поиска начинается со значения, определенного в конкретной реализации Tcl (например, /usr/local/lib/tcl8.4). Затем (в случае использования Tcl 8.4 и дополнительного модуля уровня 8.4.1) приведенные ниже каталоги проверяются на наличие файла init.tcl. ../Iib/tcl8.4 . ./. Vlib/tcl8.4 ../library ../../tcl8.4.1/library ../../../tcl8.4.1/library Первые два каталога соответствуют стандартному расположению инсталлируемой системы, а последние три строки задают стандартное окружение для Tcl или Тк. Первый из каталогов, в котором найден файл init.tcl, выполняет роль стандартной библиотеки Tcl. Информация о расположении этого каталога сохраняется в глобальной переменной tcl „library. Значение данной переменной возвращает команда info library.
Глава 12. Библиотеки сценариев и пакеты 287 Сценарий init.tcl решает различные задачи. В частности, в нем реализована процедура unknown. Этот сценарий также инициализирует auto_path, записывая в нее каталог, определяемый значением $tcl_library, и его родительский каталог. В зависимости от значения tcl_pkgPath, к auto_path могут быть добавлены и другие каталоги. Процедура tcl findLibrary Алгоритм поиска библиотек реализован в процедуре tcl_findLibrary. Данная процедура была создана для работы с такими расширениями, как Тк и [incr Tcl]. Tcl не может самостоятельно использовать tcl_f indLibrary, поскольку она определена в init.tcl. Процедура tcl_f indLibrary выполняет поиск относительно расположения основной программы (tclsh или wish) и считает, что средства Tcl были инсталлированы стандартным способом, а также предполагает наличие стандартной среды создания программ. На поведение данной процедуры влияют также значения переменной окружения. Кроме того, tcl_f indLibrary следит за запуском инициализационного сценария. tcl_findLibrary базовый_каталог версия уровень сценарий переменная_окружения имя_переменной В качестве первого параметра задается префикс имени каталога, используемого для библиотеки сценариев. Версией считается главный номер версии. В качестве третьего параметра указывается полный уровень дополнительного модуля (например, 8.0.3). Под сценарием подразумевается инициа- лизационный сценарий, вызываемый из каталога. Последние два параметра задают соответственно переменную окружения, предоставляющую информацию о пути поиска, и переменную, которой присваивается имя каталога, найденного tcl_f indLibrary. Побочным эффектом процедуры tcl_f indLibrary является запуск сценария из каталога. Ниже приведен пример обращения к tcl_findLibrary. tcl_findLibrary tk~8.0 8.0.3 tk.tcl TK.LIBRARY tk.library В процессе выполнения процедура в первую очередь проверяет, определена ли переменная окружения TK.LIBRARY. При наличии данной переменной процедура использует ее значение. В противном случае она ищет файл tk.tcl в каталогах, перечисленных ниже. ../Iib/tk8.0 ../../Iib/tk8.0 ../library ../../tk8.0.3/library ../../../tk8.0.3/library
288 Часть II. Расширенные средства Tcl Затем она вызывает сценарий и записывает в tk_library каталог, содержащийся в файле. Поиск осуществляется относительно позиции, которую возвращает команда info nameofexecutable. Tk также добавляет в конец содержимого auto_path значение $tk_library, чтобы остальные файлы сценариев в каталоге были доступны приложению. lappend auto_path $tk_library Стиль программирования Создавая пакет, вы, наверное, захотите, чтобы его могли без труда использовать другие программисты. В первую очередь следует исключить конфликт между именами. Для этого можно использовать пространства имен, поддержка которых была реализована в Tcl 8.O. Кроме того, избежать конфликтов между основным приложением и библиотечными пакетами позволяют специально разработанные для этой цели соглашения. В этом разделе описываются соглашения, которые были разработаны до появления в Tcl механизма пространств имен. Префиксы имен процедур Для идентификации процедур, входящих в состав вашего пакета, надо выбрать префикс. Например, для пакета, рассматриваемого в главе 45, используется префикс Pref. С него начинаются имена всех процедур, предоставляемых библиотекой. Подобное соглашение дает возможность различать внутренние и экспортируемые процедуры. В экспортируемой процедуре после префикса указывается знак подчеркивания. Эта процедура допускает вызов из главного приложения или других библиотечных пакетов. В качестве примеров имен внешних процедур можно привести Pref _Add, Pref _Init и Pref .Dialog. Внутренняя процедура может использоваться только процедурами, принадлежащими тому же пакету. В именах внутренних процедур между префиксом и остальной частью имени знак подчеркивания не указывается. Примеры таких имен — PrefDialogltem и Pref Xres. Не рекомендуется использовать часто встречающиеся имена, например doit, setup, layout и т.д. Если поддержка пространств имен отсутствует, спрятать имя процедуры нет возможности, поэтому следует применять соглашения об именовании для всех процедур в пакете.
Глава 12. Библиотеки сценариев и пакеты 289 Глобальные массивы и представление переменных состояния Для всех глобальных переменных, используемых пакетом, необходимо задавать один и тот же префикс. Чтобы отличать имена переменных от имен процедур, можно изменять регистр символов. Например, имена процедур могут начинаться с прописной, а имена переменных со строчной буквы. Тогда один и тот же префикс позволят не только избежать конфликтов между переменными, принадлежащими различным пакетам, но и отличить имена переменных от имен процедур. Информацию о состоянии желательно хранить в глобальном массиве либо в массиве, принадлежащем одному из пространств имен. Для представления информации о пакете желательно использовать один массив. Этот массив может быть глобальным или принадлежать конкретному пространству имен (пространства имен обсуждаются в главе 14). Такой массив удобен для хранения набора взаимосвязанных переменных; его можно условно сравнить со структурой языка С. Например, в пакете, рассматриваемом в главе 45, для хранения информации о состоянии используется массив pref. Этот массив можно объявить как внутренний. Предоставление другим модулям возможности непосредственно обращаться к структурам данных считается плохим стилем программирования. Гораздо лучше использовать для этой цели экспортируемые процедуры. Обращение к данным посредством процедур позволяет изменять пакет, не затрагивая использующие его программы. Выбирая пространство имен, старайтесь, чтобы его имя было содержательным. Если вы хотите экспортировать несколько основных переменных вашего модуля, отделяйте префикс от остальной части имени с помощью знака подчеркивания. Если вам нужно несколько глобальных переменных, тщательно следите за использованием префикса, либо определите для доступа к ним специальные функции. Официальные руководства Требования к стилю программирования изложены в двух работах Джона Остераута (John Ousterhout). Руководство The Engineering Manual посвящено программированию на С, a The Style Guide — составлению программ на языке Tcl. В них описаны детали файловой структуры, а также соглашения об именовании для модулей, процедур и переменных. В Tcl Style Guide для разделения пакетов предлагается применять механизм пространства имен. Пространства имен автоматически устраняют конфликты между именами
290 Часть II. Расширенные средства Tcl процедур, а также обеспечивают поддержку наборов переменных и избавляют разработчика от необходимости группировать их в массивы. Указанные руководства находятся на прилагаемом компакт-диске. Кроме того, вы можете найти их по адресу ftp://ftp.tcl.tk/pub/tcl/doc. The Engineering Manual поставляется в виде сжатого tar-архива engManual. tar. Z. В нем, помимо основного документа, содержится ряд примеров. The Style Guide поставляется в форматах PostScript и PDF (styleGuide.ps и styleGuide.pdf).
Глава 13 Сведения об интерпретаторе и средства отладки В данной главе описываются команды, предоставляющие информацию об интерпретаторе. Команда history и простой отладчик могут оказаться полезными в процессе разработки программ. Команда info предоставляет информацию о состоянии интерпретатора Tcl. Команда time определяет время, необходимое для выполнения команды. В данной главе также описаны Tcl-команды clock, и history. СРЕДСТВА отражения позволяют сценарию получить информацию о внутреннем состоянии интерпретатора. Эта информация может пригодиться в ряде случаев, например при проверке наличия переменной. Сведения об интерпретаторе предоставляет также команда info. Команда clock возвращает информацию о времени, форматирует временные значения и осуществляет разбор строк, содержащих данные о времени. Сам по себе этот инструмент может оказать разработчику существенную помощь. Кроме того, для работы с информацией о времени используется таймер высокого разрешения, предназначенный для точных измерений. К основным вопросам, рассматриваемым в данной главе, относится также предыстория вызова команд. Средства поддержки предыстории избавляют разработчика от необходимости повторно вводить команды вручную, экономя тем самым время для выполнения более важных действий. Заканчивается данная глава рассмотрением средств отладки. Даже обычное добавление в состав кода дополнительных команд puts, отображающих информацию о состоянии программы, может оказаться очень полезным. Однако такой подход неприемлем для профессиональной работы. Для отладки
292 Часть II. Расширенные средства Tcl сложных программ необходимы специальные отладчики. Набор инструментальных средств Tcl Dev Kit включает отладчик и средства проверки статического кода. Программа tkinspect позволяет получить подробные данные о состоянии Тк-приложения. Эта программа автоматически связывается с Тк-программами. Команда clock Команда clock позволяет получить текущее время, форматировать значения времени и выполнять разбор строк, извлекая из них данные о времени. Различные варианты команды clock описаны в табл. 13.1. Таблица 13.1. Операции, реализуемые с помощью команды clock clock clicks Обращение к таймеру высокого разрешения. Если ?-milliseconds? указана опция -milliseconds, обеспечивается милли- секундная точность (Tcl 8.4), в противном случае точность зависит от конкретной системы clock format значение Форматирует показания таймера в соответствии с за- ?-format строка? данным форматом. Информация о спецификаторах формата приведена в табл. 13.2 clock scan строка Выполняет разбор строки и возвращает результат, ?-base дата? ?-gmt представленный в секундах. Значение опции -base логическое .значение? определяет дату clock seconds Возвращает текущее время в секундах Приведенная ниже команда выводит значение текущего времени. clock format [clock seconds] => Fri Nov 22 4:09:14 PM PST 2002 Команда clock seconds возвращает время в секундах с начала эпохи (началом эпохи считается 00:00:00 GMT 1 января 1970 г). Команда clock format форматирует целочисленные данные о времени и преобразует их в строку. В этой команде предусмотрен необязательный параметр, предназначенный для управления форматом. Он представляет собой строку, содержащую последовательности, начинающиеся с символа °/0. Эти последовательности заменяются значением года, месяца, дня, часов, минут и секунд. По умолчанию принимается следующая строка формата: У.а °/ob °/0d 0/oH:0/oM:°/oS °/0Z °/0Y Назначение элементов форматирования данных о времени приведено в табл. 13.2.
Глава 13. Сведения об интерпретаторе и средства отладки 293 Таблица 13.2. Спецификаторы формата, используемые в команде clock format °/о7о Символ У, °/ва День недели в сокращенном виде (Mon, Tue и т.д.) УоА Полное название дня недели (Monday, Tuesday и т.д.) У.Ь Сокращенное представление месяца (Jan, Feb и т.д.) У,В Полное название месяца УоС Дата и время для конкретного часового пояса (например, Nov 24 16:00:59 1996) У,С Первые две цифры четырехзначного представления года (19 или 20) yod Число месяца (01-31) e/,D Дата, представленная как y,m/e/,d/y,y (например, 02/19/97) У,е Число месяца (1-31) без ведущего нуля e/0h Сокращенное название месяца У,Н Количество часов в 24-часовом формате (00-23) Ув1 Количество часов в 12-часовом формате (01-12) e/,j День в году (001-366) У,к Количество часов в 24-часовом формате без ведущего нуля (0-23) У,1 Количество часов в 24-часовом формате без ведущего нуля (1-12) У»т Номер месяца (01-12) У.М Число минут (00-59) У»п Новая строка У,р Индикатор АМ/РМ •/.г Время в формате У.1: %М: %S У.р (например, 02:39:29 РМ) e/,R Время в формате УвН:У,М (например, 14:39) y,s Число секунд с начала эпохи •/.S Секунды (00-59) •/•t Символ табуляции У.Т Время в формате 7eH: */eM: e/.S (например, 14:34:29) У,и Номер дня недели (понедельник — 1, воскресенье — 7) У,и Номер недели в году (00-52); первым днем недели считается воскресенье •/.V Номер недели в году по правилам ISO-8601 (4 января должно принадлежать первой неделе) e/,w Номер дня недели (воскресенье — 0) yeW Номер недели в году (00-52); первым днем недели считается понедельник
294 Часть II. Расширенные средства Tcl Окончание табл. 13.2 У0х Дата в формате конкретного часового пояса (например, Feb 19 1997) °/0Х Время в формате конкретного часового пояса (например, 20:10:13) °/0у Год в пределах столетия (00-99) УоУ Год, представленный четырьмя цифрами (например, 1997) °/,Z Название временного пояса Команда clock clicks возвращает значение системных часов с наивысшим разрешением. Если указана опция -milliseconds, значения тиков измеряются в миллисекундах, в противном случае единица измерения не определена. Как правило, данная команда используется для отсчета относительного времени при сравнении производительности двух фрагментов кода. Опция -milliseconds была добавлена в Tcl 8.4. В листинге 13.1 содержится фрагмент кода, предназначенный для калибровки тиков. Он измеряет число тиков в секунду; этот параметр может изменяться в зависимости от системы. В данном случае программа определяет число тиков в течение 10 секунд и делит полученное значение на 10. Листинг 13.1. Подсчет числа тиков в секунду set tl [clock clicks] after 10000 ;# См. главу 16 set t2 [clock clicks] puts "[expr ($t2 - $tl)/10] Clicks/second" => 1001313 Clicks/second Команда clock scan осуществляет разбор строки с данными о времени и возвращает значение в секундах. Эта команда поддерживает ряд форматов представления даты. Если год не указан, принимается текущий год. При разработке программ необходимо учитывать особенности представления года. В Tcl двухсимвольное представление года интерпретируется стандартным способом, т.е. числа в диапазоне 70-99 представляют годы 1970-1999, а числа 00-69 используются для обозначения лет в интервале 2000-2069. В версиях, предшествующих Tcl 8.0, двухсимвольное представление года может обрабатываться некорректно. Заметьте, что поведение Tcl зависит от времени начала эпохи, принятой в вашей системе, и количества битов в целом числе. В Windows, Macintosh и большинстве версий Unix началом эпохи считается 1 января 1970 г. 32-битового целого числа достаточно для подсчета секунд
Глава 13. Сведения об интерпретаторе и средства отладки 295 вперед до 2037 г. и назад до 1903 г. Если вы хотите работать с датами, выходящими за пределы этого диапазона, возникнет ошибка, вызванная переполнением счетчика. Таким образом, Tcl лишь отражает ограничения операционной системы. Некоторые 64-битовые системы, например Solaris 8, используют для хранения показаний системных часов 64-разрядные целые числа, поддерживаемые Tcl 8.4. При этом диапазон дат, которые могут быть представлены, расширяется до миллиардов лет. Если дата не указана, команда clock scan предполагает текущую дату. Для указания даты можно также применить опцию -base. В приведенном ниже примере в качестве базовой используется текущая дата. Очевидно, что в данном случае это излишне, так как текущая дата принимается по умолчанию. clock scan "10:30:44 РМ" -base [clock seconds] => 2931690644 В команде, предназначенной для разбора даты допустимы следующие модификаторы: year, month, fortnight (две недели), week, day, hour, minute, second. Перед модификатором можно указать положительный или отрицательный множитель. Примеры использования модификаторов приведены ниже. clock format [clock scan "10:30:44 РМ 1 week"] => Fri Nov 29 10:30:44 PM PST 2002 clock format [clock scan "10:30:44 PM -1 week"] => Fri Nov 15 10:30:44 PM PST 2002 В качестве модификаторов можно также задавать ключевые слова tomorrow, yesterday, today, now, last, this, next и ago. clock format [clock scan "3 years ago"] => Mon Nov 22 4:18:34 PM PST 1999 В командах clock format и clock scan предусмотрена опция -gmt. При наличии этой опции предполагается гринвичское время. В противном случае принимается время, соответствующее локальному часовому поясу. clock format [clock seconds] -gmt true => Sat Nov 23 12:19:13 AM GMT 2002 clock format [clock seconds] -gmt false => Fri Nov 22 4:19:35 PM PST 2002
296 Часть II. Расширенные средства Tcl Команда info Информация о команде info приведена в табл. 13.3. Особенности выполнения конкретных операций мы рассмотрим несколько позже. Таблица 13.3. Операции, реализуемые с помощью команды info info args процедура Список параметров процедуры info body процедура Команды в теле процедуры info cmdcount Число команд, которые были выполнены на данный момент времени info commands ?шаблон? Список команд, удовлетворяющих шаблону. Сюда же входят встроенные Тс1-процедуры info complete строка Возвращает значение true, если в строке содержится полная Тс1-команда info default процедура Возвращает значение true, если для заданного па- параметр переменная раметра предусмотрено значение по умолчанию. Значение по умолчанию записывается в указанную переменную info exists переменная Возвращает значение true, если переменная определена info functions ?шаблон? Список всех математических функций либо тех из них, которые удовлетворяют шаблону (Tcl 8.4) info globals ?шаблон? Список всех глобальных переменных либо тех из них, которые удовлетворяют шаблону info hostname Имя узла. Если сетевые средства не были инициализированы, данная операция возвращает пустую строку info level Уровень стека для текущей процедуры. Глобальной области видимости соответствует значение О info level число Список команд и их параметров на указанном уровне стека info library Путь к каталогу библиотеки Tcl info loaded Список библиотек, загруженных в интерпретатор ?интерпретатор? с указанным именем. По умолчанию предполагается текущий интерпретатор info locals ?шаблон? Список всех локальных переменных либо тех из них, которые удовлетворяют шаблону info nameofexecutable Имя файла, содержащего программу info patchlevel Номер реализации Tcl
Глава 13. Сведения об интерпретаторе и средства отладки 297 Окончание табл. 13.3 info procs ?шаблон? Список всех Tcl-процедур либо тех из них, которые удовлетворяют шаблону info script ?имя_файла? Имя обрабатываемого файла либо пустая строка info sharedlibextension Суффикс имени файла для разделяемых библиотек info t elvers ion Номер версии Tcl info vars ?шаблон? Список всех видимых переменных, либо тех из них, которые удовлетворяют шаблону Переменные Переменные можно разделить на три категории: локальные, глобальные и видимые. Информацию об этих категориях можно получить путем выполнения операций locals, globals и vars. К локальным переменным относятся как переменные, определенные локально, так и параметры процедур. Глобальными считаются все переменные, определенные в глобальной области видимости. Категория видимых переменных включает локальные переменные, а также переменные, доступ к которым обеспечен с помощью команды global или upvar. Определив шаблон, вы ограничите список возвращаемых переменных лишь теми, которые удовлетворяют этому шаблону. Шаблон обрабатывается по тем же правилам, по которым работает команда string match, описанная в главе 4. info globals auto* => auto.index auto_noexec auto^path Пространства имен, которые будут рассмотрены в следующей главе, позволяют помещать глобальные переменные в различные области видимости. Запрос переменных, видимых в конкретном пространстве имен, выглядит следующим образом: info vars область„видимости::* Заметьте, что в тот момент, когда посредством команды global или upvar переменная объявляется видимой в текущей области, она может быть еще не определена. Выяснить, определена ли переменная или элемент массива, можно с помощью команды info exists. Пример использования команды info exists см. в главе 8. Процедуры Получить сведения о Tcl-процедурах можно путем выполнения операций args, body и default. В листинге 13.2 приведен код процедуры Proc_Show, который демонстрирует использование этих операций. При вызове команды
298 Часть II. Расширенные средства Tcl puts указана опция -nonewline. Она задается потому, что в теле процедуры сохраняются символы перевода строки. Листинг 13.2. Вывод определения процедуры proc Proc_Show {{namepat *} {file stdout}} { foreach proc [info procs $namepat] { set space "" puts -nonewline $file "proc $proc {" foreach arg [info args $proc] { if [info default $proc $arg value] { puts -nonewline $file "$space{$arg $value}" } else { puts -nonewline $file $space$arg } set space " " } # Двойные кавычки разрешают подстановку # [info body $proc] puts $file "} {[info body $proc]}" } 2 Более сложный пример получения информации о процедуре приведен в листинге 13.3. Данный подход используется в файле direct.tcl, который входит в состав Tcl Web Server. Этот продукт рассматривается в главе 18. Приведенный ниже код предназначен для преобразования URL в составе запросов и отображает данные запроса непосредственно в вызовы Тс1-процедур. Более подробно этот вопрос рассматривается в главе 18. Web-сервер собирает данные, введенные посредством формы, в массив с именем form. В листинге 13.3 элементам массива form ставятся в соответствие параметры процедуры, а дополнительные элементы оформляются как параметр args. Если какое-либо значение формы пропущено, используется параметр по умолчанию, представляющий собой пустую строку. Листинг 13.3. Отображение данных формы в параметры процедуры # cmd - это имя вызываемой процедуры # form - массив, содержащий данные формы set cmdOrig $cmd
Глава 13. Сведения об интерпретаторе и средства отладки 299 set params [info args $cmdOrig] # Элементы массива form соответствуют параметрам foreach arg $params { if {![info exists form($arg)]} { if {[info default $cmdOrig $arg value]} { lappend cmd $value } elseif {[string equal $arg "args"]} { set needargs yes } else { lappend cmd {} } } else { lappend cmd $form($arg) } } # Если параметром является args, то данные формы, не # соответствующие остальным параметрам, оформляются как # дополнительные параметры if {[info exists needargs]} { foreach {name value} [array get form] { if {[lsearch $params $name] < 0} { lappend cmd $name $value } } } # Выполнение команды set code [catch $cmd result] Операция info commands возвращает список команд, содержащий как встроенные команды, реализованные на языке С, так и Tcl-процедуры. Операция, которая предоставляла бы только информацию о встроенных командах, не предусмотрена. Код, представленный в листинге 13.4, возвращает встроенные команды, удаляя из списка все процедуры. Листинг 13.4. Получение информации о встроенных командах proc Command_Info {{pattern *}} { # Создание таблицы процедур
300 Часть II. Расширенные средства Tcl foreach p [info procs $pattern] { set isproc($p) 1 } # Поиск команд, которые не содержатся в таблице процедур set result {} foreach с [info commands $pattern] { if {![info exists isproc($c)]} { lappend result $c } } return [lsort $result] > Стек вызова Операция info level возвращает информацию о стеке выполнения Tcl, который также называется стеком вызова. Глобальному уровню соответствует номер нуль. Процедура, вызванная на глобальном уровне, находится в стеке вызова на уровне 1. Процедура, вызванная на уровне 1, располагается на уровне 2 и т.д. Если номер уровня не указан, команда info level возвращает текущий номер уровня стека. Если задан положительный номер уровня (например, info level 3), команда возвращает имя процедуры и значения параметров на этом уровне стека вызова. При указании отрицательного номера уровень отсчитывается относительно текущего уровня стека вызова. Относительный уровень, равный — 1, представляет уровень, на котором была вызвана текущая процедура, а относительный уровень 0 соответствует самой текущей процедуре. Ниже приведен пример кода, с помощью которого выводится стек вызова. Процедура Call_trace запрещает вывод информации о самой себе, задавая уровень, на единицу меньший текущего. Листинг 13.5. Получение информации о стеке вызова Tcl proc Call_Trace {{file stdout}} { puts $file "Tcl Call Trace" for {set x [expr [info level]-1]} {$x > 0} {incr x -1} { puts $file "$x: [info level $x]" } }
Глава 13. Сведения об интерпретаторе и средства отладки 301 Выполнение команд Если вы хотите выяснить, сколько Tcl-команд было выполнено, необходимо использовать для этого команду info cmdcount. Она выполняет подсчет всех команд, а не только команд верхнего уровня. Счетчик никогда не сбрасывается, поэтому, если вам нужна информация о том, сколько команд выполнилось за время работы тестирующего примера, вам надо прочитать значение счетчика до начала и после окончания его выполнения. Информацию о выполнении команд предоставляет также команда trace, которая описывается далее в данной главе. Операция info complete определяет, является ли строка законченной Tcl- командой. Она полезна при интерпретации команд, вводимых пользователем. В этом случае перед передачей команды на выполнение программа должна ожидать окончания ее ввода. В листинге 13.6 определена процедура Command. Process, которая получает входные данные и формирует на их основе команду. Полностью сформированная команда выполняется в глобальной области видимости. В качестве параметров процедуре Command_Process передаются две команды обратного вызова. Команда inCmd вызывается для получения входной строки, а команда outCmd — для отображения результатов. Команды обратного вызова и использование фигурных скобок при обращении к ним см. в главе 10. Листинг 13.6. Процедура, осуществляющая чтение и выполнение команд proc Command_Process {inCmd outCmd} { global command append command(line) [eval $inCmd] if {[info complete $command(line)]} { set code [catch {uplevel #0 $command(line)} result] eval $outCmd {$result $code} set command(line) {} } } proc Command.Read {{in stdin}} { if {[eof $in]} { if {$in != "stdin"} { close $in } return {} } return [gets $in]
302 Часть II. Расширенные средства Tcl proc Command_Display {file result code} { puts stdout $result } while {![eof stdin]} { Command_Process {Command_Read stdin} \ {Command_Display stdout} } Сценарии и библиотеки Имя файла, содержащего код текущего сценария, возвращает операция info script. Например, если вы используете source для чтения команд из файла, то операция info script, вызванная в процессе выполнения команд из сценария, возвращает имя этого файла. Данное соглашение не нарушается, даже если операция info script вызывается в процедуре, которая определена за пределами сценария. Используйте операцию info script для обнаружения файлов, связанных с данным сценарием. Команду info script можно использовать для обработки файлов, которые хранятся в том же каталоге, что и выполняющийся сценарий. Пример использования этой команды приведен в листинге 13.7. Листинг 13.7. Использование команды info script для обнаружения файлов, связанных со сценарием # Определение каталога, содержащего текущий сценарий, set dir [file dirname [info script]] # Команда source применяется к файлу в том же каталоге source [file join $dir helper.tcl] # Информация о библиотечном каталоге добавляется в auto_path # Использование выражения ../lib и команды file join # обеспечивает выполнение на различных платформах lappend auto_path [file join $dir ../lib] Путь к Tcl-библиотеке хранится в переменной tcl_library и возвращается при выполнении команды info library. Несмотря на то что в каталог, содержащий сценарий, можно помещать и другие сценарии, библиотеку желательно располагать в отдельном каталоге и использовать для взаимодействия со средствами, описанными в главе 12. При этом упрощается работа с новыми версиями Tcl, а при переходе на другой компьютер вы сможете быстро подготовить код приложения для переноса.
Глава 13. Сведения об интерпретаторе и средства отладки 303 Номера версий Каждой реализации Tcl присваивается номер версии, например 7.4 или 8.0. Этот номер возвращает команда info tclversion. Если вы хотите, чтобы ваш сценарий работал с различными реализациями Tcl, вам необходимо предусмотреть в нем проверку номера версии и в зависимости от результата проверки выполнять действия, специфические для конкретной реализации. Перед тем как начать поставку окончательной реализации, пользователям предоставляются одна-две альфа- и бета-версии. Кроме того, время от времени производятся модификации окончательного варианта продукта, в ходе которых устраняются замеченные недостатки. Команда info patchlevel возвращает уточненный номер версии, например для первой бета-реализации версии 8.0 это номер 8.0Ы. Вместо того чтобы использовать в номере модификации готовой версии букву "р" (например, 8.0р2), номер формируют из трех элементов (например, 8.0.3). Для окончательной реализации номер модификации равен нулю (например, 8.2.0). Работая с бета-версией, надо быть готовыми к тому, что в дальнейшем продукт будет изменен, но эти изменения будут связаны лишь с устранением замеченных ошибок. Версии Tcl разрабатываются так, чтобы обеспечивалась совместимость снизу. Появление новых возможностей не влияет на работоспособность готовых программ. Среда выполнения Имя файла, содержащего выполняемую программу, возвращает команда info nameofexecutable. Использование этой команды предпочтительнее анализа переменной argvO, поскольку это может быть относительное имя или имя, найденное в каталоге с командами. В результате операции info nameofexecutable также может быть возвращен относительный путь. Это происходит в случае, если пользователь вызывает команду с указанием текущего каталога, например ./foo. Выражение, приведенное ниже, всегда возвращает абсолютный путь для текущей программы. Если info nameofexecutable возвращает абсолютный путь, текущий каталог игнорируется. Описанные команды pwd см. в главе 9. file join [pwd] [info nameofexecutable] Некоторые операции позволяют динамически загружать разделяемые библиотеки (они будут рассмотрены в главе 47). Команда info sharedlibextension возвращает суффикс имени файла динамической библиотеки. Команда info loaded предоставляет список библиотек, загруженных интерпретатором. Особенности работы с несколькими интерпретаторами рассматриваются в главе 19.
304 Часть II. Расширенные средства Tcl Выполнение программ на различных платформах Средства Tcl разработаны так, что один и тот же сценарий может работать на платформах Unix, Macintosh и Windows. При переходе из одной системы в другую изменять сценарий не приходится. Однако по мере необходимости вы можете создавать небольшие фрагменты кода, ориентированные на определенную платформу. Информацию о текущей платформе можно получить с помощью переменной tcl_platf orm. Эта переменная представляет собой массив, в котором определены следующие элементы. • tcl_platf orm(platf orm). Содержит одно из следующих значений: unix, macintosh или windows. • tcl_platform(os). Идентифицирует операционную систему. В качестве примера идентификатора можно привести MacOS, Solaris, Linux, Win32s (Windows 3.1 с подсистемой Win32), Windows 95, Windows NT и SunOS. • tcl_platform(osVersion). Содержит номер версии операционной системы. • tcl_platf orm (machine). Идентифицирует аппаратные средства. В качестве примера идентификатора можно привести ррс (Power PC), 68k (семейство 68000), spare, inTcl, mips и alpha. • tcl_platform(byteOrder). Определяет порядок следования байтов для данного компьютера. Может содержать значение littleEndian или bigEndian. • tcl_platform(wordSize). Указывает размер машинного слова в байтах. Данный элемент был добавлен в Tcl 8.4. • tcl_platf orm(isWrapped). Указывает на то, что приложение было оформлено в виде одного исполняемого модуля с помощью TclPro Wrapper. В обычных условиях этот элемент не определен. • tcl_platform(user). Предоставляет регистрационное имя текущего пользователя. • tcl_platform(debug). Указывает на то, что средства Tcl были скомпилированы для символьного отладчика. • tcl_platf orm (threaded). Сообщает, что при компиляции Tcl была включена поддержка потоков. На некоторых платформах определено имя узла. Если данная информация доступна, ее возвращает команда info hostname. Эта команда может также возвращать пустую строку.
Глава 13. Сведения об интерпретаторе и средства отладки 305 Одной из наиболее существенных особенностей платформы, влияющей на переносимость программ, является структура файловой системы и способ именования файлов (см. главу 9). Контроль переменных и команд Команда trace регистрирует команду, которая будет вызвана при попытке доступа к переменной, изменения ее содержимого или удаления. В Tcl 8.4 была реализована модифицированная команда trace, обеспечивающая контроль выполнения команд. Первоначально trace использовалась только для работы с переменными и задавалась в следующем виде (эти форматы вызова поддерживаются до сих пор): trace variable имя опции команда trace vdelete имя опции команда trace vinfo имя В качестве первого параметра задается имя переменной. Это может быть обычная переменная, массив или элемент массива. Если отслеживается весь массив, зарегистрированная команда будет вызвана при обращении к любому его элементу. Второй параметр — это одна или несколько букв, определяющих характеристики команды trace: г — отслеживание попытки чтения; w --• отслеживание попытки записи; и — вызов команды при удалении переменной; а — контроль массива. В качестве последнего параметра задается команда, которая выполняется при возникновении одного из описанных выше событий. Эта команда вызывается следующим образом: команда имя__1 имя_2 опции Первым параметром является имя переменной или массива. Второй параметр — это индекс массива. Если команда trace применяется к обычной переменной, вместо этого параметра задается значение null. Если необходимо контролировать попытку удаления всего массива, второй параметр также имеет значение null. Значение переменной не передается процедуре. Отслеживаемая переменная находится на один уровень стека вызовов Tcl выше, чем обрабатывающая процедура. Для того чтобы переменная была доступна в области видимости, необходимо использовать команду upvar, uplevel или global (см. главу 7). Команда отслеживания чтения вызывается перед тем, как возвращается значение переменной, поэтому если данная команда изменяет значение переменной, то возвращено будет ее новое значение. Команда отслеживания записи вызывается после модификации переменной. Команда отслеживания удаления вызывается после удаления переменной. При работе с массивом
306 Часть II. Расширенные средства Tcl (эта возможность была реализована в Tcl 8.4) отслеживающая команда вызывается перед тем, как команда массива (например, array names) применяется к переменной. При удалении переменной команда отслеживания автоматически отменяется. Контроль выполнения команд Новый вариант команды trace позволяет работать как с переменными, так и с командами. trace add тип имя опции команда trace remove тип имя опции команда trace info тип имя В качестве первого параметра задается одно из следующих значений: command, execution или variable. Если задано значение command, третий параметр представляет собой список и может содержать значение rename (отслеживание попытки переименования Tcl-команды) или delete (отслеживание попытки удаления команды). С помощью команды отслеживания нельзя предотвратить попытку удаления команды, она позволяет лишь организовать оповещение о событии. При удалении интерпретатора команда отслеживания не вызывается. Вызов команды осуществляется следующим образом: команда старое_имя новое_имя опции Если в качестве первого параметра задано значение execution, третий параметр может принимать значения enter, leave, enterstep и leavestep. Значение enter приводит к тому, что команда отслеживания вызывается перед выполнением команды, имя которой задано в качестве второго параметра, а значение leave указывает на то, что команда отслеживания должна быть вызвана сразу после завершения проверяемой команды. Значения enterstep и leavestep имеют тот же смысл, что и предыдущая пара значений, но они предназначены для работы с Tcl-процедурами. Если вторым параметром задана процедура, команда отслеживания вызывается для каждой Tcl-команды внутри процедуры. Для того чтобы это стало возможным, текст процедуры не преобразовывается в байтовый код. Если первый параметр имеет значение enter или enterstep, команда отслеживания вызывается так: команда командная_строка опции При указании значения leave или leavestep команда отслеживания вызывается так, как показано ниже. команда командная_строка код результат опции
Глава 13. Сведения об интерпретаторе и средства отладки 307 Здесь командная строка — это текущая команда, предназначенная для выполнения, код — результирующий код, а результат — строка результата. Различие результирующих кодов см. в листинге 6.16. Для отслеживания переменной (значение variable) третий параметр может принимать любое сочетание значений read, write, unset или array. Команда trace, заданная в таком виде, выполняет те же действия, что и рассмотренная ранее форма записи trace, специально предназначенная для отслеживания переменных. Переменные, предназначенные только для чтения В листинге 13.8 приведен пример использования команды trace для создания переменных, допускающих только чтение. Модификация переменной производится перед вызовом процедуры отслеживания, поэтому переменная Readonly необходима для восстановления исходного значения. При удалении переменной отслеживание автоматически отменяется, поэтому команда, вызываемая по данному событию, восстанавливает отслеживание. Заметьте, что псевдоним upvar (в данном случае var) не может использоваться при установке отслеживания. Вместо этого для контроля переменной в исходном контексте применяется команда uplevel. Практически всегда отслеживанию подвергаются глобальные переменные либо переменные, помещенные в то или иное пространство имен. Листинг 13.8. Отслеживание действий с переменными proc ReadOnlyVar {varName} { upvar 1 $varName var global Readonly set Readonly($varName) $var uplevel 1 [list trace variable $varName wu ReadOnlyTrace] } proc ReadOnlyTrace { varName index op } { global Readonly upvar 1 $varName var switch $op { w { set var $Read0nly($varName) } u { set var $ReadOnly($varName) # Повторно задать отслеживание, используя реальное имя uplevel 1 [list ReadOnlyVar $varName] }
308 Часть II. Расширенные средства Tcl } 2 В данном примере новое значение переменной заменяется сохраненным значением. При реализации переменных, допускающих только чтение, можно поступить и по-другому: написать код так, чтобы при попытке изменить значение этой переменной возникала бы ошибка. Сделать это позволяет команда error. Команда trace часто используется для обновления компонентов пользовательского интерфейса при изменении значений переменных. В некоторых случаях эта команда реализуется внутри самого компонента. Если с одной переменной связано несколько команд отслеживания, они вызываются в порядке, обратном тому, в котором были зарегистрированы. Другими словами, в первую очередь вызывается команда, зарегистрированная последней. Если необходимо контролировать действия с массивом и его элементом, команда отслеживания, заданная для всего массива, вызывается первой. Создание элементов массива с помощью команды trace В листинге 13.9 команда trace используется для динамического создания элементов массива. Листинг 13.9. Создание элементов массива в процессе отслеживания обращений # Убедиться в том, что переменная является массивом set dynamic() {} trace variable dynamic r FixupDynamic proc FixupDynamic {name index op} { upvar 1 $name dynArray if {![info exists dynArray($index)]} { set dynArray($index) 0 } 2 Информацию о том, осуществляется ли отслеживание обращений к переменной, можно получить с помощью операции trace vinf о. trace vinfo dynamic => {r FixupDynamic} Для того чтобы отказаться от контроля, надо указать параметр vdelete. Он задается по тем же правилам, что и variable. Команда, отменяющая отслеживание в приведенном выше примере, выглядит следующим образом: trace vdelete dynamic r FixupDynamic
Глава 13. Сведения об интерпретаторе и средства отладки 309 Предыстория вызова команд Оболочка Tcl поддерживает файл протокола, в который помещает сведения о командах, вызванных в интерактивном режиме. Для доступа к данному файлу протокола и управления им предназначена команда history. Для обозначения записи в файле протокола используется термин событие. Событию, т.е. записи, содержащей информацию о конкретном вызове команды, присваивается идентификатор, который представляет собой индекс записи в файле протокола. При работе со средствами предыстории можно задавать отрицательный идентификатор; в этом случае отсчет ведется от конца файла протокола. Событие с номером — 1 — это вызов предыдущей команды. Варианты команды history описаны в табл. 13.4. По умолчанию принимается идентификатор события, равный —1. Таблица 13.4. Операции, реализуемые с помощью команды history history Сокращенная запись history info без указания счетчика history add команда Добавляет команду к списку предыстории. Если ?ехес? указан параметр exec, команда выполняется history change Заменяет в списке предыстории команду, заданную яовая_комаяда посредством события, на новую команду ?событие? history event ?событие? Возвращает команду, заданную посредством события history info ?счетчик? Возвращает сформатированный список предыстории, содержащий все или последние несколько команд. Число команд задается с помощью счетчика history keep счетчик Ограничивает список предыстории несколькими последними командами. Число команд задается с помощью счетчика history next id Возвращает идентификатор следующего события history redo ?событие? Повторяет указанную команду Работу в интерактивном режиме существенно упрощает возможность задавать сокращенные имена команд, содержащихся в файле предыстории. Сокращать можно даже имя команды history. Сокращением является последовательность символов, позволяющая однозначно распознать команду. Длина этой последовательности зависит от вызываемой команды и от того, какой набор команд был определен на момент вызова. Что касается опций, то имя каждой из них может быть сокращено до одного символа. Например, вы можете вызвать последнее слово предыдущей команды с помощью выражения
310 Часть II. Расширенные средства Tcl [history w $]. В данном случае за символом $ не следует ни буква, ни цифра, поэтому используется его литеральное значение. Некоторые из вариантов команды history позволяют модифицировать список предыстории. Они удаляют команды, находящиеся в файле протокола, и заменяют их другими командами. Примерами таких операций являются event и redo. Использование этих операций вряд ли оправдано, поскольку значительно удобнее вызывать команды, реально находящиеся в списке, чем использовать команду history для ввода новых команд. Вызов команды history Команда history предоставляет дополнительные возможности, доступные в интерактивном режиме. Для их использования предназначены специальные выражения, описанные в табл. 13.5. Таблица 13.5. Специальные выражения, применяемые в команде history ! ! Повторение предыдущей команды ! номер Повторение команды с указанным номером. Если задано отрицательное число, команда отсчитывается назад от текущей команды. Предыдущая команда определяется как событие с номером -1 ! префикс Повторение последней из команд, начинающихся с указанного префикса ! шаблон Повторение последней из команд, соответствующих указанному шаблону Л старое_ значение ~новое_значение Глобальная замена старого значения на новое в последней команде Применение некоторых операций со списком предыстории иллюстрирует пример, приведенный в листинге 13.10. Листинг 13.10. Использование команды history в интерактивном режиме % set a 5 5 7. set а [expr $а+7] 12 7« history 1 set a 5 2 set a [expr $а+7] 3 history
Глава 13. Сведения об интерпретаторе и средства отладки 311 7. !2 19 7. М 26 °/0 ~7~13 39 У, !h 1 set a 5 2 set a [expr $a+7] 3 history 4 set a [expr $a+7] 5 set a [expr $a+7] 6 set a [expr $a+13] 7 history Обращение к предыстории в Tcl и С shell Синтаксические правила, используемые при обращении к списку предыстории, более просты, чем в оболочке С shell. При выполнении некоторых операций дополнительные возможности, описанные в табл. 13.5, недоступны. Подстановка с использованием выражения ~ старое__значение~новое_ значение применяется глобально к предыдущей команде. В оболочке С обработка осуществляется по-другому. В частности, вместо приведенного выше выражения применяется команда предыстории ! :gs/old/new/. Если, например, выражение ~а~Ь применяется для того, чтобы присвоить b значение 39, возникнет ошибка, поскольку в команде содержится неопределенная переменная Ь. set b [expr $b+7] Если вы хотите изменить синтаксис обращения к списку предыстории, необходимо модифицировать команду unknown (см. главу 12). В листинге 13.11 приведен код, содержащийся в определении команды unknown и реализующий поддержку специальных выражений для команды history. Основное ограничение по сравнению с оболочкой С shell состоит в том, что подстановка ! выполняется только тогда, когда этот символ находится в начале команды. Листинг 13.11. Реализация специальных выражений, используемых при работе с командой history # Фрагменты стандартной реализации команды unknown. # Команда uplevel используется для выполнения команды # в требуемом контексте if {$name == "!!"} { set newcmd [history event]
312 Часть II. Расширенные средства Tcl } elseif {[regexp {л!(.+)$} $name dummy event]} { set newcmd [history event $event] } elseif {[regexp W ([~л] *)V ([~~] *)V?$} $name x old new]} { set newcmd [history event -1] catch {regsub -all -- $old $newcmd $new newcmd} } if {[info exists newcmd]} { history change $newcmd 0 return [uplevel $newcmd] } Отладка При работе с Tcl можно быстро вводить новые команды и модифицировать существующие. Это наводит на мысль о том, что для того, чтобы получить сведения о состоянии программы, достаточно включить в нее несколько команд puts. Однако такое решение далеко не оптимально. Несколько лучшие результаты дает процедура Debug, которая предоставляет определенную степень контроля над отображением информации. По мере необходимости вы можете записать информацию в файл или запретить вывод данных. В Tk-приложениях можно относительно быстро создать текстовый компонент, отображающий содержимое файла протокола. В листинге 13.2 приведен код простой процедуры Debug. Для того чтобы разрешить ее выполнение, необходимо определить переменную debug (enabled). Чтобы выходные данные выводились на терминал, надо задать значение debug(f ile), равное stderr. Листинг 13.12. Процедура Debug proc Debug { args } { global debug if {![info exists debug(enabled)]} { # По умолчанию никакие действия не выполняются return } puts $debug(file) [join $args " "] } proc DebugOn {{file {}}} { global debug set debug(enabled) 1 if {[string length $file] == 0} { set debug(file) stderr
Глава 13. Сведения об интерпретаторе и средства отладки 313 } else { if [catch {open $file w} filelD] { puts stderr "Cannot open $file: $fileID" set debug(file) stderr } else { puts stderr "Debug info to $file" set debug(file) $fileID } } } proc DebugOff {} { global debug if {[info exists debug(enabled)]} { unset debug(enabled) flush $debug(file) if {$debug(file) != "stderr" && $debug(file) != "stdout"} { close $debug(file) unset debug(file) } } } Tcl Dev Kit Tcl Dev Kit — это среда разработки Tcl-программ. Данный продукт создан на базе TclPro и распространяется на коммерческой основе. Доступ к исходным кодам TclPro был открыт в ноябре 2001 года ActiveState дополнила Tcl Dev Kit новыми инструментами. Среда разработки включает ActiveTcl1, который представляет собой расширенную Tcl-платформу и в свою очередь включает [incr Tcl], Expect и TclX. Данные расширения распространяются как в исходных файлах, так и в двоичных кодах, ориентированных на выполнение в Windows и на различных платформах Unix. Дополнительную информацию по этому вопросу можно найти по следующему адресу: http: //www. act ivestate. com/Tcl. Ниже описаны инструменты, входящие в состав текущей версии Tcl Dev Kit ActiveTcl является торговой маркой ActiveState Corporation. — Прим. авт.
314 Часть II. Расширенные средства Tcl Отладчик с расширенными возможностями Инструмент Debugger предоставляет графический пользовательский интерфейс и полный набор средств, которые разработчик вправе ожидать от обычного отладчика. Вы можете задавать точки останова, переходить в режим пошагового выполнения, просматривать значения переменных и стек вызова. Он поддерживает вложенные стеки вызова — возможность, имеющую непосредственное отношение к использованию команды update. Данный инструмент позволяет загрузить новый Tcl-сценарий (загрузка сценария является побочным эффектом команды update) и поместить информацию о текущем состоянии в стек выполнения. Это событие легко отследить, контролируя содержимое стека с помощью отладчика. Отладчик поддерживает информацию о состоянии проекта, поэтому при повторном вызове программы расположение точек останова и другие настройки отладчика сохраняются. Инструмент Debugger также позволяет отлаживать удаленные приложения. Данный инструмент очень удобен для отладки Tcl-программ, выполняющихся в среде Tcl Web Server. Инструмент Checker Инструмент Checker осуществляет проверку кода в статическом режиме. Это позволяет несколько сократить время работы над большими программами. Данный инструмент проверяет каждую строку кода, ищет синтаксические ошибки и выделяет фрагменты кода, которые могут оказаться некорректными. Checker содержит подробные сведения о командах Tcl, Tk, Expect, [incr Tcl] и TclX и проверяет правильность их использования. Он также контролирует количество параметров при вызове Tcl-процедур и может проверять большие группы Tcl-файлов с перекрестными ссылками. Кроме того, данный инструмент имеет информацию об изменениях, внесенных в ту или иную версию Tcl, и предупреждает разработчика о том, что ранее созданный код нуждается в обновлении. Инструмент Compiler Compiler на самом деле представляет собой средство чтения и записи байтового кода, созданного внутренним генератором, содержащимся в среде Tcl. Он позволяет заранее компилировать сценарии и сохраняет результаты. В дальнейшем при вызове сценария интерпретируется не его исходный текст, а байтовый код. Это позволяет скрыть исходный код программы от посторонних глаз. При использовании Compiler на практике оказывается, что он экономит значительно меньше времени, чем можно было бы ожидать. Несмотря на то что данный инструмент читает файлы с диска, декодирует их, создает
Глава 13. Сведения об интерпретаторе и средства отладки 315 необходимые структуры Tcl, это происходит ненамного быстрее, чем чтение исходных файлов и компиляция их в процессе работы. Инструмент TclApp TclApp собирает Tcl-сценарии. файлы данных и интерпретатор Tcl/Tk в пакеты Starkit и Starpack (соответствующие средства будут описаны в главе 22). TclApp предоставляет более удобный интерфейс по сравнению с программой sdx. В состав Tcl Dev Kit входят подготовленные к работе средства Starkit, включающие Metakit, Expect, [incr Tcl] и TclX. Инструмент Service Manager Инструмент Tcl Service Manager упрощает оформление Tcl-приложения в виде службы Windows NT/2000/XP. Сервисные службы должны реализовать специальные интерфейсы, которые не поддерживают Tclsh и wish. Создаваемые вами службы могут использовать динамические библиотеки и сценарии, входящие в состав имеющейся среды Tcl/Tk, либо их можно оформить в виде автономных программ, работа которых не зависит от наличия файлов и прочих ресурсов. Инструмент Inspector Inspector является модернизированной версией приложения tkinspect, которое позволяет получать информацию о состоянии Tk-приложений. Данный инструмент отображает сведения о процедурах, переменных и об иерархии компонентов Тк. По необходимости вы можете передавать приложениям команды, изменяющие значения переменных и вызывающие другие команды. Данная возможность существенно упрощает процесс отладки Tk-приложений. Прочие инструменты Сообществом Tcl были разработаны многие интересные инструменты, которые могут оказать большую помощью при разработке Tcl-программ. Некоторые из них описаны ниже, другие вы можете найти на сервере Tcl Resource Center по адресу http://www.Tcl.tk/resource/
316 Часть II. Расширенные средства Tcl Консоль tkcon Приложение tkcon представляет собой Тк-консоль, дополненную расширенными средствами. Это приложение написано полностью на Tcl. Инструмент предоставляет богатый набор возможностей и может быть встроен в другие Tcl-приложения. Данный продукт можно скопировать с сервера, адрес которого приведен ниже. http://tkcon.sourceforge.net/ Critcl Critcl представляет собой инструмент, позволяющий встраивать С-код непосредственно в сценарии Tcl. Когда команда сргос впервые обрабатывает фрагмент исходного текста, написанный на С, она автоматически компилирует код и загружает его в ваше приложение. Реализуя небольшие фрагменты Tcl-программ на С, можно повысить их производительность. Web-страница данного продукта расположена по следующему адресу: http://www.equi4.com/critcl Команда bgerror Когда при работе Tcl-сценария в фоновом режиме возникает ошибка, оповещение о ней осуществляется путем вызова процедуры bgerror. Такая ситуация может возникать, например, при обработке событий ввода-вывода или в процессе выполнения команды, связанной с кнопкой. По умолчанию процедура bgerror выводит диалоговое окно, в котором отображается Tcl-стек вызова. По мере необходимости вы можете использовать собственный вариант bgerror. Например, приложение может в случае ошибки передавать почтовое сообщение, содержащее описание ситуации и копию стека вызова. Это позволяет разработчику следить за поведением своего продукта, находясь в любой точке планеты. Команде bgerror передается один параметр, который представляет собой сообщение об ошибке. Информацию о стеке вызова содержит глобальная переменная err or Info. Команда tkerror Команда bgerror может вызываться при выполнении команды tkerror. Если при использовании Tcl 7.5 и Тк 4.1 обработка событий передается из Тк в Tcl, имя tkerror изменяется на bgerror. При этом обеспечивается обратная совместимость, т.е. если tkerror определена, вызывается именно она, а не bgerror. Несмотря на обратную совместимость, некоторые разработчики
Глава 13. Сведения об интерпретаторе и средства отладки 317 считают более надежным явно использовать bgerror вместо tkerror. Если ваше приложение работает с Тк 4.0 или с Тк 4.1, вы можете использовать следующее определение: proc bgerror [info args tkerror] [info body tkerror] Контроль производительности программ Команда time определяет время, необходимое для выполнения Tcl-команды. При вызове этой команде передается необязательный параметр, представляющий собой счетчик повторений. time {set a "Hello, World!"} 1000 => 28 microseconds per iteration Если вам необходимо получить результат, предоставляемый командой, время выполнение которой измеряется, то для этого надо использовать команду set. puts $log "command: [time {set result [command]}]" Пакет с расширенными возможностями, предназначенный для сравнения различных версий Tcl, доступен по адресу http: //wiki. tcl«,J^k/Tcl'/.20Benchmarks Информация о времени в файле протокола Получить сведения о производительности различных частей программы можно также, записывая в файл протокола значения времени. Данные, получаемые в результате выполнения операции clock seconds, слишком грубые, но вы можете дополнить их значениями clock clicks, повысив тем самым точность измерений. Определить число тиков в секунду для вашей системы поможет сценарий, приведенный в листинге 13.1. Процедура, код которой представлен в листинге 13.13, создает файл протокола, содержащий текущее время и число тиков с момента последней записи. При работе с тиками могут быть сбои, связанные, например, с переустановкой системных часов, но в большинстве случаев такой подход позволяет получить точные результаты. Выполнение самой процедуры Log занимает определенное время, поэтому целесообразно определить его, выполнив процедуру в длинном цикле. Листинг 13.13. Создание файла протокола, содержащего данные о времени proc Log {args} { global log if [info exists log(file)] {
318 Часть II. Расширенные средства Tcl set now [clock clicks] puts $log(file) [format ,,0/0s (0/od)\t0/0su \ [clock format [clock seconds]] \ [expr $now - $log(last)] \ [join $args " "]] set log(last) $now } } proc Log.Open {file} { global log catch {close $log(file)} set log(file) [open $file w] set log(last) [clock clicks] } proc Log_Flush {} { global log catch {flush $log(file)} } proc Log_Close {} { global log catch {close $log(file)} catch {unset log(file)} } В состав пакета Extended Tcl (TclX) входит команда profile, которая позволяет отслеживать число вызовов, загрузку центрального процессора и время выполнения различных процедур. Tcl-компилятор Встроенный Tcl-компилятор предпринимает для повышения производительности следующие меры. • Преобразует сценарии во внутренний формат байтового кода, что ускоряет их выполнение. Байтовый код сохраняется, поэтому накладные расходы, связанные с компиляцией, имеют место лишь при первом выполнении процедуры или цикла. Компиляция осуществляется только но мере необходимости, поэтому не используемый код не компилируется. Если вы переопределите процедуру, то при следующем выполнении она будет повторно скомпилирована. • Хранит переменные и параметры команд в специальном внутреннем формате и преобразует их в строки только при реальной необходимо-
Глава 13. Сведения об интерпретаторе и средства отладки 319 сти. В Tcl предусмотрены форматы для целых чисел, чисел с плавающей точкой, списков, байтовых кодов и массивов. Для реализации новых типов реализованы специальные функции API. В Tcl используются динамические типы, поэтому в различные моменты времени переменная может содержать значения различных типов. • Компилирует выражения и управляющие структуры в специальный байтовый код для того, чтобы они выполнялись более эффективно. Так как команда expr реализует дополнительный этап подстановки, группировка выражений с помощью фигурных скобок повысит качество кода, генерируемого компилятором. В этом случае к выражениям будет применяться лишь один этап подстановки. Компилятор будет генерировать более эффективный код, поскольку ему не придется заботиться о специфических фрагментах кода наподобие следующего: set subexpr {$x+$y} expr 5 * $subexpr Приведенное ниже выражение определяется полностью только при работе программы, поэтому синтаксический разбор выражения приходится осуществлять при каждом новом выполнении. Если выражение помещено в фигурные скобки, компилятор заранее знает о том, какая операция будет использоваться, и генерирует код, реализующий ее наиболее эффективно. Действия компилятора полностью "прозрачны" для сценариев, но при работе со списками и с выражениями могут возникнуть некоторые сложности. Этот вопрос будет подробно обсуждаться в главе 54. Списки большого размера обрабатываются более эффективно; эта особенность безусловно должна быть расценена как положительная. Проблема состоит в том, что списки обрабатываются "агрессивно", поэтому синтаксическая ошибка в конце списка проявится даже в том случае, если вы обращаетесь только к его первым элементам. В генераторе кода, входящем в состав широко используемой реализации Tcl 8.0р2, выявлен ряд ошибок. Многие из них связаны с выражениями, которые принадлежат командам if и while и не помещены в фигурные скобки. Большинство ошибок было устранено в реализации 8.0.3, а остальные исправлены в версии 8.1 путем добавления в нее нового синтаксического анализатора. Внутренний компилятор постоянно модернизируется, в частности, в версии Tcl 8.4 была расширена базовая таблица команд, что существенно увеличило производительность программ по сравнению с предыдущими реализациями Tcl.
Глава 14 Пространства имен Пространства имен позволяют группировать процедуры и переменные, ограничивая их область видимости. Впервые пространства имен были реализованы в Tcl 8.O. В данной главе рассматриваются команды namespace и variable. 1 1РОСТРАНСТВА имен реализуют новые области видимости для процедур и глобальных переменных. Первоначально в Tcl были определены одна глобальная область видимости для общедоступных переменных, локальные области видимости в пределах процедур и одна глобальная область видимости для процедур. Глобальные переменные и процедуры могли находиться лишь в одной области видимости; это усложняло управление большими приложениями. В главе 12 описывались простые соглашения об именовании, которые можно использовать при написании больших программ. Более элегантным решением является использование пространств имен, которые выделяют в глобальной области видимости разделы для переменных и процедур. Пространства имен упрощают структурирование больших Тс1-приложе- ний, но требуют дополнительных затрат времени и усилий для создания и поддержки самой структуры. В частности, приходится принимать специальные меры, чтобы команды обратного вызова выполнялись в нужном пространстве имен. Каждый разработчик должен сам решить, стоит ли использовать пространства имен. Если размеры приложения невелики, применение этого механизма вряд ли оправдано. Если же вы разрабатываете библиотечные пакеты, которыми будут пользоваться другие разработчики, применение пространств имен позволит избежать конфликтов с компонентами того приложения, в котором эти пакеты будут использоваться.
Глава 14. Пространства имен 321 Использование пространств имен При работе с пространствами имен приходится соблюдать дополнительные правила формирования имен процедур и переменных. Для отделения имени пространства от имен процедур и переменных используются два двоеточия (: :). Эти символы надо использовать для обращения к процедурам и переменным, находящимся в других пространствах имен. Команда namespace позволяет использовать объекты из других пространств имен, не прибегая к дополнительным синтаксическим конструкциям. Пространства имен допускают вложенность, т.е. вы можете создавать иерархию областей видимости. Эти вопросы подробно обсуждаются в данной главе. Следует заметить, что пространства имен не обеспечивают защиту и не позволяют ограничить обращения к процедурам и переменным в других пространствах. Дело в том, что в динамических языках, каким является Tcl, реализовать эти средства чрезвычайно трудно. Так, например, вы в любой момент можете обратиться к любому пространству имен с помощью команды namespace eval. Но, несмотря на отсустствие непосредственного контроля, пространства имен предоставляют структуру, которая упрощает создание больших приложений. Средства поддержки пакетов, рассмотренные в главе 12, были созданы до появления пространств имен. В данной главе рассматриваются способы объединения этих механизмов, однако при работе с ними следует учитывать, что разработка пространств имен и пакетов велась без учета их совместного использования. Вы можете создать пакет с именем А, реализующий пространство имен В, а также использовать пакет без пространств имен или пространство имен без пакета. В листинге 14.1 показана модификация генератора случайных чисел, представленного в листинге 7.4. На этот раз он реализован с использованием пространств имен. Согласно соглашениям о стиле программирования имена пространств имен задаются символами нижнего регистра. Листинг 14.1. Генератор случайных чисел, созданный с использованием пространств имен package provide random 1.О namespace eval random { # Создание переменной в пространстве имен variable seed [clock seconds] # Обеспечение видимости процедур для namespace import namespace export init random range
322 Часть II. Расширенные средства Tcl # Создание процедур в пространстве имен proc init { value } { variable seed set seed $value } proc random {} { variable seed set seed [expr {($seed*9301 + 49297) % 233280}] return [expr {$seed/double(233280)}] } proc range { range } { expr {int([random]*$range)} } 2 В приведенном выше листинге в пространстве имен random определены три процедуры и переменная. В пределах пространства имен вы можете непосредственно обращаться к ним. При обращении из других пространств необходимо использовать составные имена, в которых имя пространства имен отделяется от имени процедуры или переменной с помощью двух двоеточий (: :). Например, если имя переменной, содержащей данные о состоянии, задается в текущем пространстве имен как seed, то при обращении из-за пределов этого пространства оно имеет вид random: : seed. Обращение к процедуре осуществляется следующим образом: random::random => 0.3993355624142661 random::range 10 => 4 Если в вашей программе интенсивно используется некоторый пакет, вы можете импортировать содержащиеся в нем процедуры. В пространстве имен процедуры, которые могут быть экспортированы, описываются с помощью команды export. Импортировав процедуру, вы можете обращаться к ней, не прибегая к составному имени. namespace import random::random random => 0.54342849794238679 Операции импортирования и экспортирования мы рассмотрим несколько позже.
Глава 14. Пространства имен 323 Переменные в пространствах имен В пространстве имен переменная определяется с помощью команды variable. Эту команду можно сравнить с командой set, так как она позволяет задавать значение переменной. С помощью одной команды variable в пространстве имен можно задать несколько переменных. Команда variable записывается следующим образом: variable имя ?значение? ?имя значение? . . . Работая с массивом, не следует задавать его значения с помощью команды variable. Для этой цели лучше использовать другие уже известные вам команды. Их можно разместить внутри блока namespace. namespace eval foo { variable arr array set arr {имя значение имя2 значение2} У Переменные пространств имен можно сравнить с глобальными переменными, так как они находятся вне областей видимости всех процедур. Для обращения к переменным пространств имен в процедурах используются команды variable или составные имена. Например, в процедуре random задана команда variable, которая переносит переменную пространства имен в текущую область видимости. variable seed Если в команде variable внутри процедуры указана новая переменная и если первой операцией, выполняемой над ней, является set, то переменная создается в указанной области видимости. При работе с пространствами имен необходимо принимать меры для исключения конфликтов с глобальными переменными. Используя переменные внутри блока namespace, следует соблюдать осторожность. Если вы определите переменную с помощью команды variable, она становится переменной пространства имен. Однако если вы забудете объявить ее, то невозможно заранее предсказать, станет ли она переменной области видимости или обращение к ней превратится в обращение к существующей глобальной переменной с тем же именем. Рассмотрим следующий фрагмент кода: namespace eval foo { variable table for {set i 1} {$i <= 256} {incr i} { set table ($i) [format °/0c $i]
324 Часть II. Расширенные средства Tcl } } Если в программе уже существует глобальная переменная с именем i. она будет использоваться в цикле for. В противном случае будет создана переменная foo: :i. Такое поведение Tcl может показаться неоправданным, однако при этом упрощается доступ к глобальным переменным, в частности. вы можете обращаться к env, не объявляя ее в блоке namespace как global. Составные имена Составными считаются полностью либо частично определенные имена. Полностью определенное имя начинается с символов : :, которые идентифицируют глобальное пространство имен. Полностью определенное имя однозначно идентифицирует процедуру или переменную. Такое имя можно использовать в любой части программы. Указывая полностью определенное имя, нет необходимости применять команду global. Предположим, например, что в пространстве имен foo объявлена переменная х, кроме того, в программе определена глобальная переменная х. Обращение к глобальной переменной х может осуществляться следующим образом: : :х Наличие символов : : не оказывает влияние на подстановку переменных. Чтобы получить значение глобальной переменной, надо использовать выражение $: :х. Переменная х пространства имен foo указывается так: ::foo::x В частично-определенном имени начальные символы : : отсутствуют. В этом случае имя определяется относительно текущего пространства имен. Например, следующее выражение подобно выражению, приведенному выше, и определяет переменную пространства имен х: foo::x Составные имена можно использовать в качестве параметра команды global. После выполнения этой команды к переменной можно обращаться, не указывая имени пространства. global ::foo::x set x 5 При объявлении переменных программа работает более эффективно, чем в случае использования составных имен. При объявлении глобальных переменных и переменных пространств имен Tcl-компилятор генерирует более эффективный байтовый код.
Глава 14. Пространства имен 325 В контексте каждой процедуры содержится своя таблица переменных. Обращение к таблице производится либо путем непосредственного указания индекса, либо посредством поиска имени переменной в хэш-таблице. Поиск в хэш-таблице осуществляется медленнее, чем прямое обращение. Если вы используете команды variable или global, компилятор может непосредственно обращаться к переменным. При работе с составными именами компилятор реализует поиск в хэш-таблице. Поиск команд Поиск команд осуществляется начиная с текущего пространства имен. Если команда не найдена, система ищет ее в глобальном пространстве имен. Это означает, что в любом пространстве можно использовать встроенные Tcl- команды, не принимая для этого никаких специальных мер. По мере необходимости вы можете влиять на поведение программы, переопределяя команды в некотором пространстве имен. Например, если вы определите новую команду set, то обращаться к встроенной команде set придется с помощью выражения : :set. Очевидно, что, поступая таким образом, надо соблюдать осторожность. Составные имена можно использовать при определении процедур. Это исключает необходимость помещать команду ргос в блок namespace. Однако, перед тем как определять процедуру в пространстве имен, вы должны создать само пространство имен с помощью команды namespace eval. В листинге 14.2 показана еще одна реализация генератора случайных чисел, на этот раз с использованием составных имен. В процедуре random: :init не нужна команда variable, поскольку для переменной seed задано составное имя. Листинг 14.2. Генератор случайных чисел, при создании которого использовались составные имена namespace eval random { # Создание переменной в пространстве имен variable seed [clock seconds] } # Создание процедур в пространстве имен ргос random::init { seed } { set ::random::seed $seed } proc random::random {} {
326 Часть II. Расширенные средства Tcl variable seed set seed [expr {($seed*9301 + 49297) °/0 233280}] return [expr {$seed/double(233280)}] } proc random::range { range } { expr {int([random]*$range)} } Вложенные пространства имен Пространство имен может находиться в другом пространстве имен. В листинге 14.3 показаны три пространства имен, в каждом из которых объявлена своя переменная х. Полостью определенные имена этих переменных имеют соответственно вид : :foo: :х, : :bar: :х и : :bar: :foo: :x. Листинг 14.3. Вложенные пространства имен namespace eval foo { variable x 1 ;# ::foo::x } namespace eval bar { variable x 2 ;# ::bar::x namespace eval foo { variable x 3 ;# ::bar::foo::x } puts $foo::x ;# Выводится значение З } puts $foo::x ;# Выводится значение 1 Частично определенное имя может соответствовать двум различным объектам. В листинге 14.3 частично определенное имя foo: :x может соответствовать одной из двух переменных, в зависимости от того, какое пространство имен является текущим. В глобальной области видимости имя foo: :x ссылается на переменную х в пространстве имен : :fоо. В пространстве имен : :bar foo: :x — это переменная х, определенная в ::bar::foo. Если вы хотите исключить неоднозначность при обращении к переменным из некоторого пространства имен, то можете поступить различными способами. Проще всего перенести переменную в нужную область видимости с помощью команды variable.
Глава 14. Пространства имен 327 variable x set х некоторые „данные Часто разработчики используют для формирования полностью определенного имени команду namespace current. trace variable [namespace current]::x r \ [namespace current]:rtraceproc Однако гораздо проще явным образом задать нужное имя. trace variable : imyname : :х г : imyname : :traceproc Недостаток такого подхода состоит в том, что в программе появляется большое число ссылок на пространство имен (в данном случае : rjnyname : :), которое впоследствии может быть переименовано. Импортирование и экспортирование процедур Для удобства работы команды могут быть импортированы из других пространств имен. При обращении к импортированной команде можно обойтись без идентификатора пространства имен. В каждом пространстве имен описываются экспортируемые процедуры, которые могут быть импортированы. Переменные не подлежат импортированию. Заметьте, что импортирование производится исключительно для того, чтобы упростить работу над приложением. Вы можете в любой момент обратиться к процедуре, не прибегая к импортированию, а лишь указав составное имя. Некоторые программисты вовсе не применяют импортирование, чтобы при чтении текста программы всегда точно знать, какому пакету принадлежит та или иная процедура. Команда namespace export указывается в блоке namespace и определяет процедуры, экспортируемые из данного пространства имен. Процедуры указываются как список шаблонов string match, на соответствие которым проверяются команды, определенные в пространстве имен. Список экспортируемых команд должен быть определен перед экспортированием. Если вам надо включить в список экспортирования большое количество процедур или указать много шаблонов, вы можете вызвать команду namespace export несколько раз. Опция -clear предназначена для очистки списка экспортирования. namespace export ?-clear? ?'шаблон? ?шаблон? . . . В базе данных пакета должны содержаться только имена экспортированных процедур. Создавая с помощью pkg_mklndex файл pkglndex.tcl, содержащий базу данных пакета,надо следить за тем, чтобы в нем присутство-
328 Часть II. Расширенные средства Tcl вали только имена экспортированных процедур. По этой причине многие программисты экспортируют все процедуры. От импортирования имен процедур можно отказаться и полагаться на средства загрузки кода, использующие базу данных пакета. Экспортировать все процедуры можно следующим образом: namespace export * Команда namespace import делает команды из других пространств имен видимыми в текущем пространстве имен. Если такая же команда есть в текущем пространстве, возникает конфликтная ситуация. В этом случае при выполнении команды namespace import возникает ошибка. Разрешить эту ситуацию позволяет опция -force. Команда namespace import записывается следующим образом: namespace import ?-force? про странств о_имен : :дга блон ?пространство_имен: -.шаблон?. . . В качестве параметров указываются шаблоны string match, на соответствие которым проверяются команды, определенные в пространстве имен. Пространства имен нельзя указывать с помощью шаблонов; они задаются с помощью полностью или частично определенных имен. Если вам не хочется заниматься импортированием отдельных процедур, вы можете указать в команде namespace import все процедуры из текущего пространства имен. namespace import random::* Недостатком такого подхода является высокая вероятность конфликта с именами, импортированными из других модулей. В частности, в приведенной выше команде указаны все процедуры из пространства имен random. Помимо прочих, там определена процедура init, имя которой используется довольно часто. Гораздо меньше проблем возникнет, если вы будете импортировать только те процедуры, которые собираетесь использовать. namespace import random::random random::range Команда namespace import работает с содержимым пространства имен, определенным в момент импортирования. Если набор процедур пространства имен изменится или изменится список экспортирования, это не окажет влияния на действие процедур, которые были ранее импортированы из пространства имен.
Глава 14. Пространства имен 329 Пространства имен и обратный вызов Некоторым командам в качестве параметров передаются Тс1-процедуры, которые должны быть выполнены позже. По умолчанию эти процедуры обратного вызова выполняются в глобальной области видимости. Если вы хотите, чтобы подобная процедура была выполнена в конкретном пространстве имен, вы должны создать ее с помощью операции namespace code. Данная команда не приводит к выполнению процедуры обратного вызова. Вместо этого она генерирует Tcl-команду, которая впоследствии выполняется в текущем пространстве имен. Предположим, например, что текущим пространством имен является : : current. Команда namespace code определяет текущую область видимости и указывает ее в качестве параметра генерируемой команды namespace inscope. set callback [namespace code {set x 1}] => namespace inscope ::current {set x 1} # Далее при выполнении программы... eval $callback При последующем вызове $callback процедура будет выполняться в пространстве имен : : current, поскольку ранее была вызвана команда namespace inscope. В частности, если существует переменная пространства имен ::current::х, она будет модифицирована. Альтернативой namespace code является использование составного имени переменной. set callback {set ::current::x 1} Недостатком такого подхода является необходимость выполнения рутинной работы по перемещению кода в другое пространство имен. Если вы хотите, чтобы подстановка команды осуществлялась при ее определении, вам следует использовать для ее создания команду list. Применение команды list детально обсуждается в главах 10 и 30. В листинге 14.4 показана процедура code, в которой присутствуют команды list и namespace inscope. Эту процедуру удобно применять в тех случаях, когда для создания команды обратного вызова используется список. Команда uplevel в составе процедуры code гарантирует, что пространство имен будет определено корректно. В результате данную процедуру можно вызывать в любой части программы. Листинг 14.4. Процедура code, предназначенная для организации обратного вызова proc code {args} { set namespace [uplevel {namespace current}] return [list namespace inscope $namespace $args]
330 Часть II. Расширенные средства Tcl } namespace eval foo { variable у иу value" x {} set callback [code set x $y] => namespace inscope ::foo {set x {y value}} 2 В данном примере команда обратного вызова присваивает переменной : :foo: :x значение переменной у. Если вы хотите, чтобы переменная х получила то значение, которое переменная у имеет в момент вызова, вам следует запретить все подстановки. В этом случае вызов namespace code должен выглядеть следующим образом: set callback [namespace code {set x $y}] => namespace inscope ::foo {set x $y} Если вызывающая процедура передает процедуре обратного вызова дополнительные параметры, команда namespace inscope корректно добавит их. Например, при организации полос прокрутки (этот вопрос будет обсуждаться в главе 33) параметры добавляются к команде обратного вызова, осуществляющего управление соответствующими интерфейсными элементами. Интроспекция Операция info commands возвращает все видимые в данный момент команды (см. главу 13). По необходимости вы можете ограничить объем возвращаемой информации, используя шаблоны string match. Можно также включить в шаблон идентификатор пространства имен, чтобы определить, какие компоненты этого пространства являются видимыми. Помните, что видимыми являются также глобальные и импортированные команды, поэтому информация, возвращаемая по команде info, не ограничивается содержимым пространства имен. В листинге 14.5 используется команда namespace origin, которая возвращает исходные имена импортированных команд. Такой подход позволяет выделить команды, которые действительно были определены в пространстве имен. Листинг 14.5. Процедура вывода команд, определенных в пространстве имен proc Namespace_List {{namespace {}}} { if {[string length $namespace] == 0} { # Определение пространства имен для вызывающей процедуры set namespace [uplevel {namespace current}] }
Глава 14. Пространства имен 331 set result {} foreach cmd [info commands ${namespace}::*] { if {[namespace origin $cmd] == $cmd} { lappend result $cmd } } return [lsort $result] Команда namespace В табл. 14.1 приведена информация об операциях, которые выполняются с помощью команды namespace. Таблица 14.1. Операции, реализуемые посредством команды namespace namespace current namespace children ?имя? ?шаблон? namespace code сценарий namespace delete имя ?имя? ... namespace eval имя команда ?args? ... namespace exists имя namespace export ?-clear? ?шаблон? ?шаблон? ... namespace forget шаблон ?шаблон? ... namespace import ?-force? шаблон Возвращает текущее пространство имен Возвращает имена вложенных пространств имен. Если имя не указано, по умолчанию принимается текущее пространство имен. Шаблон позволяет ограничить объем возвращаемой информации. Сравнение с шаблоном производится по правилам string match Генерирует команду namespace insсope, которая выполняет сценарий в текущем пространстве имен Удаляет переменные и команды из указанных пространств имен Если указаны параметры args, осуществляется их конкатенация с командой, которая выполняется в указанном пространстве имен Возвращает значение 1, если указанное пространство имен существует. В противном случае возвращается значеие 0 (Tcl 8.4) Добавляет процедуры, соответствующие шаблонам, в список экспортирования для данного пространства имен. Если шаблоны не заданы, возвращает список экспортирования Отменяет импортирование процедур, соответствующих шаблонам Добавляет имена, соответствующие шаблонам, к текущему пространству имен ?шаблон? . . .
332 Часть II. Расширенные средства Tcl Окончание табл. 14.1 namespace ins с ope имя Если указаны параметры args, они добавляются команда ?args? ... к команде как элементы списка. Команда выполняется в текущем пространстве имен namespace origin команда Возвращает исходное имя команды namespace parent ?имя? Возвращает родительское пространство имен для указанного имени либо текущее пространство имен namespace qualifiers имя Возвращает часть имени до последней пары символов : : namespace which ?опция? Возвращает полностью определенный вариант ука- имя занного имени. В данной операции могут задаваться опции -command, -variable или -namespace name space tail имя Возвращает последний компонент имени Преобразование существующих пакетов для работы с пространствами имен Представьте себе, что в вашем распоряжении есть набор Тс1-процедур и вам необходимо поместить их в пространство имен. Очевидно, что в первую очередь необходимо расположить существующий код в блоке namespace eval. Однако при этом необходимо учитывать глобальные переменные, экспортируемые процедуры и команды обратного вызова. • Глобальные переменные остаются таковыми до тех пор, пока вы не используете в программе variable вместо global. Некоторые переменные имеет смысл оставить в глобальной области видимости. Заметьте, что глобальными являются переменные, определенные в Tcl, например env, tcl_platf orm и др. (см. табл. 2.2). Если вы использовали upvar #0 (этот прием был описан в главе 7), можете применить данную команду для работы с пространствами имен. upvar #0 [namespace current]::$instance state • Экспортирование процедур упрощает работу разработчиков, использующих ваш пакет. Экспортировать процедуры не обязательно, поскольку обратиться к процедуре можно, указав составное имя. Однако список экспортированы позволяет определить, какие процедуры из других пакетов можно использовать. • Команды обратного вызова выполняются в глобальной области видимости. Если вы осуществляете отслеживание состояния переменных или
Глава 14. Пространства имен 333 связываете переменные с компонентами Тк, эти переменные рассматриваются как глобальные. Если вы хотите организовать обратный вызов процедуры пространства имен или использовать переменную пространства имен, следует сформировать полностью определенное имя переменной или процедуры. Вы можете непосредственно задавать текущее пространство имен button .foo -command ::myname::callback \ -textvariable ::myname:itextvar или использовать команду namespace current: button .foo -command [namespace current]::callback \ -textvariable [namespace current]:rtextvar Объектная система [incr Tcl] Пространства имен Tcl не предоставляют возможности организовывать классы и не обеспечивают механизм наследования. Они только позволяют сформировать новые области видимости и "прятать" в них процедуры и переменные. С помощью Tcl С API можно управлять обращениями к переменным и командам, поддерживать работу с классами и обеспечивать наследование. В результате появляется возможность добавления к Tcl различных объектных систем в качестве разделяемых библиотек. Пространства имен Tcl были предложены Майклом МакЛеннаном (Michael McLennan) как обобщение опыта работы с [incr Tcl] — наиболее популярным объектным расширением Tcl. Данное расширение позволяет организовывать классы, реализовать наследование, а также обеспечивает защиту переменных и команд. Если вы имеете опыт работы с C++, то без труда освоите [incr Tcl]. Подробное рассмотрение [incr Tcl] не является целью данной книги. Информацию по этому вопросу вы найдете в книге Чеда Смита (Chad Smith) liner Tcl] From The Ground Up (Osborn-McGraw Hill, 1999). Дистрибутивный пакет [incr Tcl] находится на компакт-диске, прилагаемом к данной книге. Web-страница данного продукта расположена по адресу http://www.tcltk.com/iTcl/. Исходные тексты [incr Tcl] можно найти на сервере SourceForge http://inertcl.sourceforge.net/. Объектная система xoTcl Еще одно объектное расширение Tcl называется xotcl. Данный продукт объединяет объектно-ориентированные средства и сценарии, позволяя поль-
334 Часть II. Расширенные средства Тс! зоваться преимуществами обоих. Продукт xotcl поддерживает динамическое объединение объектов, реализует фильтры, обеспечивает динамическую загрузку компонентов и предоставляет многие другие возможности. Web-страница xotcl расположена по адресу http://www.xotcl.org/. Замечания В последнем разделе данной главы речь пойдет о различных возможностях, предоставляемых пространствами имен. Имена компонентов, изображений и интерпретаторов Как вы уже знаете, механизм пространств имен действует только на команды и переменные и не затрагивает ряд расширений Tcl. Например, если вы создаете компонент Тк, создается также соответствующая ему Тс1-коман- да. Данная команда всегда располагается в глобальном пространстве имен, даже в том случае, когда вы создаете компонент Тк в блоке namespace eval. Другими примерами могут служить интерпретаторы Tcl, которые будут описаны в главе 19, и Тк-изображения (речь о них пойдет в главе 41). Команда variable в глобальной области видимости Команду variable можно использовать вместо global в процедуре, которая не принадлежит пространству имен. Это не является ошибкой, так как команда variable означает: "данная переменная принадлежит текущему пространству имен", а текущее пространство имен может быть глобальным. Автозагрузка и процедура auto_import Для импортирования команд из пакета foo могут использоваться следующие выражения: package require foo namespace import foo::* Однако свойства пакетов таковы, что после выполнения команды package require в пакете может не остаться компонентов, удовлетворяющих шаблону foo: :*. Вместо этого в массив auto_index включаются записи, которые используются для загрузки процедур тогда, когда в них возникает необходимость. (О механизме автозагрузки см. в главе 12.) В подобных ситуациях
Глава 14. Пространства имен 335 Tcl обращается к процедуре auto_import. Реализация этой процедуры, используемая по умолчанию, производит поиск в auto_index и принудительно загружает процедуры, соответствующие шаблону импортирования. Такие пакеты, как [incr Tcl], применяют эту процедуру для реализации более сложных схем. Процедура auto_import впервые появилась в Tcl 8.O.3. Пространства имен и команда uplevel Подобно процедурам, пространства имен влияют на стек вызовов Tcl. Если вы отобразите состояние стека с помощью команды info level, то увидите структуры, соответствующие пространству имен. Это означает, что доступ ко всем переменным можно получить с помощью команд uplevel и upvar. Уровень #0 по-прежнему соответствует глобальной области видимости, находящейся вне пространств имен и процедур. Для того чтобы проследить за использованием стека вызовов, можно использовать процедуру Са11_Тгасе, код которой приведен в листинге 13.5. Особенности именования пространств имен Задавая имя пространства имен, можно добавлять в конце его дополнительные пары двоеточий. Кроме того, число пар двоеточий, выполняющих роль разделителя между именами, не ограничено. Это упрощает объединение имен со значениями, возвращаемыми командой namespace current. Приведенные ниже имена идентифицируют одно и то же пространство имен. ::foo::bar : :foo::bar:: ::foo:::::::bar Глобальное пространство имен идентифицируется символами : : или пустой строкой. При работе с именами переменных или команд завершающие символы : : необходимы. Приведенная ниже команда модифицирует переменную, расположенную в пространстве имен : :foo: :bar. Имя переменной — пустая строка. set ::foo::bar:: 3 namespace eval ::foo::bar { set {} } => 3 Если вы хотите включить ссылку на переменную непосредственно перед парой двоеточий, необходимо указать перед первым из двоеточий обратную косую черту. set х xval set у $х\::foo => xval::foo
336 Часть II. Расширенные средства Tcl Дополнительные операции Импортированные имена можно удалять, namespace forget random::init Команда rename позволяет изменять имена импортированных процедур. rename range Range По необходимости можно переместить процедуру в другую область видимости с помощью команды rename. rename random::init myspace::init
Глава 15 Интернационализация В данной главе описываются средства, которые позволяют обрабатывать тексты, сформированные с использованием различных наборов символов, например ASCII или Japanese. Tcl может читать и записывать данные, соответствующие различным кодировкам, но обработка текста осуществляется в рамках стандартного набора символов Unicode. Tcl содержит каталог сообщений, позволяющий генерировать различные версии приложений для разных языков. В данной главе будут рассмотрены команды encoding и msgeat. LJ различных языках используются разные алфавиты, или наборы символов. Стандартным средством представления набора символов является кодировка. Tcl скрывает от пользователя многие детали, связанные с применением кодировок и наборов символов, однако, создавая приложения, предназначенные для использования в различных странах, разработчик должен знать эти особенности. При написании программы можно воспользоваться каталогом сообщений и обеспечить вывод информации, предназначенной для пользователя, на выбранном им языке. Использование каталога сообщений предполагает дополнительную работу для программиста, однако средства Tcl максимально упрощают ее. Большая часть действий, имеющих отношение к поддержке кодировок, выполняется средствами библиотеки Tcl С. С момента своего создания библиотека Tcl С претерпела существенные изменения, которые вносились с целью обеспечить поддержку различных наборов символов. В то время как во многих системах для хранения символов применяются 8-битовые коды, Tcl использует 16-битовый набор Unicode, который дает возможность предста-
338 Часть II. Расширенные средства Tcl вить символы любого языка. Помимо этого, в Unicode остается достаточно кодов для представления специальных символов, например V или ®. Несмотря на то что в Tcl было внесено большое число изменений, связанных с поддержкой Unicode, лишь немногие из них оказывают влияние на работу программиста. Сценарии, написанные для Tcl 8.0 и более ранних версий, продолжают нормально работать с Tcl 8.1 и последующими реализациями. Модифицировать сценарии разработчику приходится лишь в том случае, если он хочет воспользоваться средствами интернационализации, появившимися в новых версиях. В начале данной главы речь пойдет о наборах символов и кодировках, после чего мы рассмотрим каталоги сообщений. Наборы символов и кодировки Если вы живете в США, то, вероятно не задумываетесь о том, какой набор символов следует использовать. В большинстве компьютеров применяется кодировка ASCII, предусматривающая 127 символов. Этого достаточно для представления 26 английских букв как верхнего, так и нижнего регистра, цифр, знаков пунктуации и управляющих символов, например табуляции или перевода строки. ASCII-символы могут быть представлены 8 битами; этого достаточно для кодирования 256 знаков. Алфавиты некоторых европейских стран включают символы с дополнительными элементами, например ё, п или а. Кодировка ISO Latin-1 является расширением ASCII; в ней предусмотрены 256 символов. Коды 0-127 соответствуют ASCII-символам, а "верхняя половина" 256-символьного набора выделена для букв с дополнительными элементами, а также для специальных символов, например с. Существует несколько кодировок ISO Latin, соответствующих различным алфавитам. Все они построены по одному принципу: "нижняя половина" выделена для обычных ASCII-символов, а "верхняя" — для дополнительных букв и специальных знаков. Эти кодировки называются iso8859-l, iso8859-2 и т.д. Наборы символов азиатских языков слишком велики, и для их представления 8 битов не достаточно. Для этих языков созданы специальные 16-битовые кодировки. Тому, кто работал с языками некоторых азиатских стран, наверное известны кодировки "Big 5" или ShiftJIS. Unicode является международным стандартом представления символов. Существуют 16- и 32-битовая версия Unicode, однако в Tcl и в большинстве других систем используется 16-битовый вариант. Unicode позволяет представить все основные наборы символов так, что их коды не перекрываются и конфликты не возникают. В Tcl все символы преобразуются в формат Uni-
Глава 15. Интернационализация 339 code, что дает возможность одновременно работать с различными наборами символов. Tcl 8.4 поддерживает Unicode v3.1. Дополнительную информацию о стандарте Unicode можно найти по адресу http://www.unicode.org/. Системная кодировка В каждой операционной системе используется кодировка, являющаяся стандартной для нее. Если вы работаете только с системной кодировкой, вам не надо заботиться об использовании наборов символов. При чтении файла Tcl автоматически преобразует его содержимое в Unicode. Когда файл записывается средствами Tcl, осуществляется преобразование из Unicode в системную кодировку. Информацию о системной кодировке можно получить с помощью следующей команды: encoding system => ср1252 В названии сокращение ср расшифровывается как "code page" ("кодовая страница"). Этим термином в Windows обозначаются различные кодировки. Во многих версиях Unix в качестве системной кодировки используется iso8859-l. Не следует изменять системную кодировку Изменить системную кодировку позволяет команда encoding system кодировка Однако делать этого не следует. При изменении системной кодировки изменится порядок обмена строками между Tcl и операционной системой и не исключено, что дальнейшее взаимодействие с Tcl окажется невозможным. Tcl автоматически распознает системную кодировку и настраивается для работы с ней. Разработчику нет необходимости заниматься этим вопросом. Команда encoding возвращает информацию о всех кодировках, известных Tcl. Сведения о кодировках хранятся в файлах, которые помещаются в каталоге encoding. Этот каталог является подкаталогом каталога, содержащего библиотеку сценариев Tcl. Кодировки автоматически загружаются при первой попытке их использования. lsort [encoding names] => ascii big5 cpl250 cpl251 cpl252 cpl253 cpl254 cpl255 cpl256 cpl257 cpl258 cp437 cp737 cp775 cp850 cp852 cp855 cp857 cp860 cp861 cp862 cp863 cp864 cp865 cp866 cp869 cp874 cp932 cp936 cp949 cp950 dingbats euc-cn euc-jp euc- kr gbl2345 gbl988 gb2312 identity iso2022 iso2022-jp iso2022-kr iso8859-l iso8859-2 iso8859-3 iso8859-4
340 Часть II. Расширенные средства Tcl iso8859-5 iso8859-6 iso8859-7 iso8859-8 iso8859-9 jis0201 jis0208 jis0212 ksc5601 macCentEuro macCroatian macCyrillic macDingbats macGreek maclceland macJapan macRoman macRomania macThai macTurkish macUkraine shiftjis symbol Unicode utf-8 Имена кодировок отражают их назначение. Как было сказано выше, символы ср в начале имени кодировки означают "code page" ("кодовая страница"); эти кодировки используются в системе Windows. Кодировки, имена которых начинаются с символов mac, разрабатывались для Macintosh. Названия, начинающиеся с iso, euc, gb и jis, отражают имена различных стандартов. Кодирование файлов и команда fconfigure В Tcl преобразование в Unicode осуществляется автоматически. Необходимые для этого средства находятся в библиотеке Tcl С. При записи и чтении файла осуществляется преобразование из Unicode в системную кодировку и обратно. Если файл представлен в кодировке, отличающейся от системной, вам надо установить нужную кодировку, используя для этого команду fconfigure. Например, для чтения файла, представленного в стандартной кодировке для русского языка (iso8859-7), эта команда имеет вид set in [open README.russi ал] fconfigure $in -encoding iso8859-7 В листинге 15.1 представлен код простой функции, предназначенной для работы в программе просмотра почты exmh1. Просмотр почтовых сообщений осуществляется с учетом MIME. В стандарте MIME предусмотрены отдельные соглашения по использованию наборов символов, которые слегка отличаются от соответствующих соглашений Tcl. Представленная в листинге процедура получает имя набора и устанавливает кодировку. Программа просмотра почты поддерживает наборы MIME и может выбирать шрифты для отображения сообщений. Наличие данной процедуры и два обращения к ней — все, что нужно для того, чтобы адаптировать почтовую программу для работы с Unicode. Листинг 15.1. Наборы символов MIME и кодирование сообщений proc Mirae_SetEncoding {file charset} { regsub -all {(isoIjisI us)-} $charset {\1} charset 1 Web-страница exmh расположена по адресу http: //www. beedub. com/exmh/. Данную программу удобно использовать при работе с почтой. Она, конечно же, написана на Tcl/Tk и базируется на использовании почтовой системы МН. По этой причине сфера ее действия ограничена системой Unix. — Прим. авт.
Глава 15. Интернационализация 341 set charset [string tolower charset] regsub usascii $charset ascii charset fconfigure $file -encoding $charset } Сценарии, представленные в различных кодировках Если в вашем распоряжении имеется сценарий, кодировка которого отличается от системной, для его загрузки нельзя использовать команду source. Однако не составляет большого труда прочитать содержимое файла в исходной кодировке, а затем использовать команду eval для выполнения команд. В листинге 15.2 представлен код, посредством которого к команде source добавляется опция -encoding. Вполне вероятно, что подобная возможность будет встроена в последующие версии Tcl и такие команды, как info script, будут выполняться корректно. Листинг 15.2. Использование сценариев, представленных в нестандартной кодировке proc Source {args} { set file [lindex $args end] if {[llength $args] == 3 && [string equal -encoding [lindex $args 0]]} { set encoding [lindex $args 1] set in [open $file] fconfigure $in -encoding $encoding set script [read $in] close $in return [uplevel 1 $script] } elseif {[llength $args] == 1} { return [uplevel 1 [list source $file]] } else { return -code error \ "Usage: Source ?-encoding encoding? file?" } } Unicode и UTF-8 UTF-8 — это разновидность Unicode. Как вам уже известно, в Unicode символы представляются 16 битами. В UTF-8 для представления символов Unicode используются 8, 16 или 24 разряда. Применение кодов переменной
342 Часть II. Расширенные средства Tcl длины очень удобно, так как для ASCII-символов используются 8 битов. Другими словами, ASCII-строка, содержащая лишь символы с кодами меньше 128, является также строкой UTF-8. Tcl использует UTF-8, что упрощает переход к Unicode. Этим также обеспечивается взаимодействие с расширениями Tcl, не поддерживающими Unicode. Такие расширения могут передавать Tcl ASCII-строки, интерпретируемые корректно. Программисты, создающие Tcl-сценарии, могут не учитывать наличие кодировки UTF-8 и считать, что в Tcl используется лишь Unicode (т.е. 16-битовый набор символов). Если же перед вами стоит задача написания расширений Tcl на языке С или C++, вам придется разобраться в особенностях применения UTF-8 и Unicode. Более подробно этот вопрос рассматривается в главе 47. Tcl позволяет осуществлять чтение и запись файлов в UTF-8 или непосредственно в Unicode. Данная возможность полезна в тех случаях, когда вам надо использовать одни и те же файлы в системах с различными системными кодировками. Эти файлы могут содержать сценарии, каталоги сообщений или документацию. Вместо того чтобы применять формат, специфический для операционной системы, вы можете записать содержимое файла в Unicode или UTF-8, а затем читать файлы в разных системах одним и тем же способом. Очевидно, что при этом необходимо корректно задать кодировку, используя описанную выше команду fconfigure. Двоичная кодировка Если вы хотите читать данные из файла, запрещая все преобразования символов, вам надо использовать двоичную кодировку (кодировку binary). fconfigure $in -encoding binary В случае двоичной кодировки Tcl читает 8-битовые значения и записывает их в младшие разряды 16-битового символа Unicode, а старшие разряди символа обнуляет. При выводе Tcl записывает лишь младшие биты символа. Если при установленной двоичной кодировке прочитать информацию из файла, а затем записать ее обратно в файл, ни один бит не будет изменен. Используя двоичную кодировку, надо соблюдать осторожность. Если вы прочитаете данные в некоторой кодировке, а затем запишете их в кодировке binary, старший разряд символа Unicode будет утерян. Реальная обработка данных при установленной двоичной кодировке несколько отличается от описанной выше. С целью повышения эффективности в Tcl применяются некоторые специальные приемы, однако общий принцип обработки остается неизменным. Прочитав главу 47, вы узнаете, что Tcl позволяет работать не только со строками, но и с данными в других форматах.
Глава 15. Интернационализация 343 При чтении содержимого файла в двоичном режиме Tcl сохраняет информацию в формате ByteArray, т.е. помещает в каждый байт 8 битов данных. Если вы попытаетесь обработать эти данные как строковые (например, с помощью команды puts), Tcl автоматически преобразует 8-битовые значения в 16-битовые символы Unicode. При этом в старший байт будут записаны нулевые разряды. Для обработки данных в формате ByteArray используется команда binary. Если вы прочитаете содержимое файла при установленной двоичной кодировке, а затем используете для преобразования информации команду binary, Tcl сохранит данные в формате, обеспечивающем наиболее эффективную обработку. Команда string также поддерживает формат ByteArray, т.е. вы можете выполнять над двоичными данными такие операции, как string length, string range и string index, не преобразовывая ByteArray в строки UTF-8. Преобразование кодировок Команда encoding позволяет преобразовывать строки символов из одной кодировки в другую. Операция encoding convertf rom переводит данные из некоторой кодировки в строку Unicode. Операция encoding convertto преобразует строки Unicode в другую кодировку. Например, две приведенные ниже последовательности команд эквивалентны. Обе они читают данные из файла в кодировке Big5 и преобразуют их в Unicode. fconfigure $input -encoding gbl2345 set Unicode [read $input] Те же результаты можно получить с помощью следующих команд: fconfigure $input -encoding binary set Unicode [encoding convertfrom gbl2345 [read $input]] Преобразуя информацию из Unicode в другую кодировку, вы можете потерять часть информации. Поэтому, выполняя подобные действия, надо учитывать возможные ограничения. В частности, кодировка binary может не сохранить данные, начинающиеся с произвольной последовательности Unicode. Аналогично, в кодировке iso8859-2 могут отсутствовать средства для представления некоторых символов Unicode. Команда encoding В табл. 15.1 описаны различные варианты команды encoding.
344 Часть II. Расширенные средства Tcl Таблица 15.1. Операции, реализуемые с помощью команды encoding encoding convert from Преобразует двоичные данные, представленные в ука- ?кодировка? данные занной кодировке, в Unicode. По умолчанию в качестве исходной принимается системная кодировка encoding convertto Преобразует строку из Unicode в указанную кодировку. ?кодировка? строка По умолчанию принимается системная кодировка encoding names Возвращает имена известных кодировок encoding system Предоставляет сведения о системной кодировке или из- ?кодировка? меняет системную кодировку Каталоги сообщений Каталог сообщений — это список сообщений, отображаемых вашим приложением. Вы можете создать несколько каталогов — по одному на каждый поддерживаемый язык. К сожалению, средства обращения к каталогам сообщений необходимо явно включать в создаваемую программу. Каждый раз, когда надо генерировать выходные данные или отображать строку в компоненте Тк, должен выполняться фрагмент кода, работающий с каталогом сообщений. В Tcl предусмотрены специальные способы поддержки каталогов сообщений, которые существенно упрощают работу с ними. При этом код программы остается удобочитаемым. Вместо того чтобы применять для получения сообщений из каталога ключевые значений типа "message42". в Tcl лишь указываются строки, используемые по умолчанию. Предположим, например, что в вашей программе имеется следующая строка кода: puts "Hello, World!" Вариант кода, использующий каталоги сообщений, выглядит так: puts [msgcat::mc "Hello, World!"] Если соответствующий каталог сообщений не загружен или в нем отсутствует отображение для "Hello, World!", то команда msgcat::mc лишь вернет переданный ей параметр. По необходимости вы можете предусмотреть специальную реакцию на отсутствие выходной информации, определив свою версию процедуры msgcat: imcunknown, однако в большинстве случаев в этом нет надобности. Каталог сообщений реализован в пакете msgcat. Для того чтобы этот пакет был доступен вашему сценарию, вам надо использовать команду package require. package require msgcat
Глава 15. Интернационализация 345 Имена всех процедур в этом пакете начинаются с символов тс, поэтому вы можете вызывать команду namespace import и затем обращаться к этим процедурам, используя их короткие имена. Даже если вы не являетесь поклонником импортирования процедур, для каталогов сообщений можно сделать исключение. Процедура msgcat::mc используется так часто, что есть смысл импортировать ее. namespace import msgcat::mc puts [mc "Hello, World!"] Определение локального языка Локальный язык — это идентификатор языка, на котором представлены выходные данные. Идентификатор языка состоит из трех элементов: язык_ страна_диалект Коды языков определены стандартом ISO-3166. Например, en означает английский язык, a es — испанский. Код страны определяет стандарт ISO-639. Например, символы US идентифицируют США, a UK — Великобританию. Диалект разработчики задают по своему выбору. Страна и диалект — необязательные компоненты идентификатора. Идентификатор языка не зависит от регистра символов. Все приведенные ниже идентификаторы составлены корректно. es en enJJS en_us en_UK en_UK_Scottish en_uk_scottish Для указания языка пользователи могут использовать переменные окружения LANG и LOCALE. Локальный язык также можно задать с помощью процедуры msgcat::mclocale. msgcat::mclocale => с msgcat:imclocale en_US Процедура msgcat: :mcpreferences возвращает список идентификаторов локального языка, начиная с самого конкретного (включающего диалект) и заканчивая самым общим (т.е. состоящим только из идентификатора языка). Пример использования msgcat: :mcpreferences приведен ниже.
346 Часть II. Расширенные средства Tcl msgcat::mclocale en_UK_Scottish msgcat::mcpreferences => en__UK_Scottish en_UK en Управление файлами каталогов сообщений Каталог сообщений представляет собой файл, который содержит набор команд msgcat: :mcset, определяющий пункты каталога. Процедура msgcat: : mcset вызывается следующим образом: msgcat: rmcset исходный_язык исходная_строка ?целевая__строка? Первым параметром при вызове функции указывается идентификатор локального языка, например es, en_US_Scottish и т.д. Второй параметр — это строка, используемая в качестве ключа при обращении к msgcat::тс. Последний необязательный параметр представляет результат выполнения msgcat::mc. Процедура msgcat: :mcload используется для загрузки файла каталога сообщений. Она ищет файл, имя которого составлено в соответствии с идентификатором локального языка (например, en_US_Scottish.msg), и связывает каталог сообщений с текущим пространством имен. Процедура msgcat: :mcload загружает файлы, определяемые с помощью процедуры msgcat: :mcpref erences и имеющие суффикс .msg. Например, для идентификатора локального языка en_UK_Scottish процедура msgcat:: mcload ищет следующие файлы: en_UK_Scottish.msg en.UK.msg en.msg По принятым соглашениям для размещения файлов каталогов сообщений используется каталог msgs, находящийся в каталоге, содержащем пакет. Пример вызова msgcat: : mcload приведен ниже. Об использовании команды info script см. в главе 13. msgcat::mcload [file join [file dirname [info script]] msgs] Каталог сообщений вызывается посредством команды source, поэтому он может содержать любые Tcl-команды. Возможно, вы посчитаете удобным импортировать процедуру msgcat::mcset. При вызове команды namespace import обязательно указывайте опцию -force, поскольку эта команда может быть уже импортирована при загрузке других файлов каталогов сообщений. Пример каталогов сообщений приведен в листинге 15.3. Листинг 15.3. Три простых файла каталогов сообщений ## en.msg namespace import -force msgcat::mcset
Глава 15. Интернационализация 347 mcset en Hello Hello.en mcset en Goodbye Goodbye.en mcset en String String_en # Окончание en.msg ## enJJS.msg namespace import -force msgcat::mcset mcset en_US Hello Hello_en_US mcset en_US Goodbye Goodbye_en_US # Окончание en.US.msg ## en_US_Texan.msg namespace import -force msgcat::mcset mcset en_US_Texan Hello Howdy! # Окончание en_US_Texan.msg Предположим, что файлы, приведенные в листинге 15.3, расположены в каталоге msgs, который является подкаталогом каталога, содержащего ваш сценарий. Тогда вы можете загрузить их с помощью следующих команд: msgcat::mclocale en_US_Texan msgcat: :mcload [file join [file dimame [info script]] msgs] Диалект имеет наивысший приоритет. msgcat::mc Hello => Howdy! Если диалект не задает требуемое отображение, проверяется идентификатор страны. msgcat::mc Goodbye => Goodbye_en_US И, наконец, самый низкий приоритет имеет идентификатор языка. msgcat::mc String => Strings en Каталоги сообщений и пространства имен Что произойдет, если два различных пакета содержат каталоги сообщений, конфликтующие между собой? Предположим, что в пакете f оо присутствует следующий вызов: msgcat::set fr Hello Bonjour
348 Часть II. Расширенные средства Tcl В то же время в пакете bar имеется определение, конфликтующее с приведенным выше. msgcat:rmcset fr Hello Ello Как msgcat: :mcset, так и msgcat: :mc зависят от текущего пространства имен Tcl. Подробно о пространстве имен см. в главе 14. Если каталог сообщений пакета foo был загружен в пространстве имен foo, то при любом вызове msgcat: :mc из пространства foo будут доступны определения, содержащиеся в этом пакете. Если вы вызовете msgcat: :mc из любого другого пространства имен, будут найдены только сообщения из каталога, определенного в этом пространстве. Если вы хотите, чтобы каталоги сообщений совместно использовались в разных пространствах имен, вам надо реализовать собственную версию msgcat: :mcunknown, в которой был бы предусмотрен специальный поиск. В листинге 15.4 показана версия данной процедуры, которая, перед тем как вернуть строку по умолчанию, выполняет поиск в глобальном пространстве имен. Листинг 15.4. Использование msgcat: :mcunknown для разделения каталогов сообщений в разных пространствах имен proc msgcat::mcunknown {local sre} { variable insideUnknown if {![info exist insideUnknown]} { # Проверка глобального пространства имен. # Необходимо указать, что мы уже в составе процедуры. set insideUnknown true set result [namespace eval :: [list \ msgcat::mc $src \ ]] unset insideUnknown return $result } else { # Вызов происходит в случае, если сообщение не было # найдено в глобальном пространстве имен return $src } }
Глава 15. Интернационализация 349 Пакет msgcat В табл. 15.2 приведены сведения о пакете msgcat. Таблица 15.2. Процедуры, определенные в пакете msgcat msgcat::mc исходная_строка msgcat:tmclocale ? локальный_язык? msgcat::mcmax ?исходная_строка исходная_строка ...? msgcat::mcpreferences msgcat::mcload каталог msgcat::mcset локальный_язык строка перевод msgcat::mcmset список_исходных_строк_ и_переводов msgcat::mcunknown локальный_язык исходная_строка Возвращает перевод исходных данных с учетом локального языка и пространства имен Запрашивает или устанавливает локальный язык Возвращает длину самой длинной исходной строки после перевода (Tcl 8.3) Возвращает список локальных языков, начиная с самого конкретного определения и заканчивая самым общим Загружает из указанного каталога список сообщений для текущего локального языка Определяет отражение строки на локальном языке в строку перевода (Tcl 8.3) Определяет в рамках одного вызова несколько пар исходная строка-перевод Данная процедура вызывается в том случае, когда перевод неизвестен. Приложение должно обеспечить собственную интерпретацию исходной строки
Глава 16 Программы, управляемые событиями В данной главе рассматриваются вопросы создания программ, управляемых событиями. В роли источников событий могут выступать, например, таймер или устройства ввода-вывода. Команда after планирует выполнение Tcl-команд в указанное время, a f ileevent регистрирует команду для выполнения в ответ на событие, связанное с файловым обменом. В данной главе будут обсуждаться команды after, fblocked, fconfigure, fileevent и vwait. i\ak правило, принцип управления событиями реализуется в программах, ориентированных на длительную работу, например в сетевых серверах или приложениях с графическим пользовательским интерфейсом. В данной главе рассматриваются вопросы создания программ, управляемых событиями, на языке Tcl. В Tcl реализована простая модель, согласно которой команды, которые должны быть выполнены при наступлении того или иного события, регистрируются и впоследствии вызываются системой. Команда after используется для выполнения Tcl-команд в указанное время, а команда f ileevent позволяет вызвать Tcl-команды тогда, когда система готова к выполнению операций ввода-вывода. Команда vwait ожидает наступления события. В процессе ожидания по команде vwait Tcl автоматически вызывает команды, связанные с другими событиями. Модель обработки событий также используется при создании пользовательских интерфейсов для Tk-программ. Следует заметить, что первоначально обработка событий была реализована только в Тк. Начиная с Tcl 7.5/Тк 4.1 цикл ожидания событий был перенесен из Тк в Tcl.
Глава 16. Программы, управляемые событиями 351 Цикл обработки событий Tcl В Tcl реализован цикл обработки событий. В этом цикле программа ожидает наступления того или иного события и вызывает соответствующий зарегистрированный обработчик. Обработчики некоторых событий предусмотрены в составе Tcl. Tcl-команды можно зарегистрировать так, чтобы они были вызваны при наступлении события. С циклом обработки события связаны также функции С API, которые будут рассмотрены в главе 50. Средства обработки событий активны в течение всего времени выполнения Тк-приложе- ния. Если вы не используете Тк, можете запустить цикл обработки событий с помощью команды vwait. Пример использования этой команды показан в листинге 16.2. Обработке подлежат четыре класса событий, которые обрабатываются в следующем порядке. • События, связанные с окнами. Генерируются после нажатия клавиш и щелчков мышью. Обработчики подобных событий автоматически создаются при формировании компонентов Тк. Для регистрации обработчиков событий такого типа используется команда bind, описанная в главе 29. • События, связанные с файлами и гнездами. Регистрацию обработчиков этих событий осуществляет команда f ileevent. • События, связанные с таймером. Команда after регистрирует Tcl-команды для выполнения в указанное время. • События простоя. Эти события генерируются тогда, когда программа не выполняет никаких действий. События простоя используют компоненты Тк для отображения своего состояния. Команда after idle регистрирует Tcl-команды для выполнения в очередной период простоя. Команда after Команда after реализует отложенное выполнение команд. Заданная в самом простом виде, команда after приостанавливает выполнение приложения на заданное время. Время указывается в миллисекундах. Приведенная ниже команда вызывает паузу в работе программы длиной полсекунды. after 500 В течение этого времени приложение не обрабатывает события. Для того чтобы обеспечить активность цикла обработки событий в период ожидания, надо использовать команду vwait. Команда after позволяет также регистрировать Tcl-команды, которые будут выполнены по истечении указанного времени.
352 Часть II. Расширенные средства Tcl after число „миллисекунд команда параметр параметр . . . Команда after интерпретирует параметры так же, как и команда eval; если вы зададите дополнительные параметры, они будут присоединены к имеющимся и сформируют одну команду. Если структура параметров важна, то для создания команды надо использовать команду list. Приведенная ниже команда всегда корректна, независимо от значения myvariable. after 500 [list puts $myvariable] Значение, возвращаемое командой after, представляет собой идентификатор зарегистрированной команды. Вы можете отменить выполнение этой команды с помощью операции after cancel. В качестве параметра указывается либо идентификатор, полученный в результате выполнения after, либо командная строка. В последнем случае отменяется команда, в точности совпадающая с указанной. Информация о команде after приведена в табл. 16.1. Таблица 16.1. Использование команды after after миллисекунды Приостанавливает работу программы на указанное число миллисекунд after миллисекунды Объединяет параметры в команду, которая выполняет- параметр ^параметр ся по истечении указанного числа миллисекунд. Сразу . . . ? же после вызова данная операция возвращает идентификатор after cancel Отменяет команду, зарегистрированную под указан- идентификатор ным идентификатором after cancel команда Отменяет зарегистрированную команду after idle команда Команда будет вызвана в очередной период бездействия after info Возвращает список идентификаторов для событий, за- ? идентификатор? регистрированных с помощью команды after и ожидающих обработки. Если указан идентификатор, возвращается информация о команде, связанной с ним Команда fileevent Команда fileevent регистрирует процедуру для вызова в тот момент, когда устройство ввода-вывода будет готово для чтения или записи. Например, вы можете открыть канал или сетевое гнездо (socket) для чтения, а затем обрабатывать поступающие из него данные с помощью процедуры, которая
Глава 16. Программы, управляемые событиями 353 была зарегистрирована посредством fileevent. При таком подходе приложение, ожидая поступления данных из канала или гнезда, может одновременно выполнять другие действия, например обновлять компоненты пользовательского интерфейса. Сетевые серверы применяют команду fileevent для поддержки соединений с несколькими клиентами. Команду fileevent можно также использовать для обмена данными с потоками stdin и stdout. Сетевые гнезда и работа с ними подробно рассматриваются в главе 17. В процедурах, зарегистрированных с помощью fileevent, для обмена данными с устройствами ввода-вывода применяются обычные Тс1-команды. Например, если вы установили соединение с устройством символьного типа, то можете использовать команду gets для получения входной строки. Если вы попытаетесь прочитать большее количество данных, чем имеется в потоке, приложение будет ожидать поступления новой информации. По этой причине, работая со строковыми данными, целесообразно создавать обработчик события так, чтобы он читал входную информацию построчно. При работе с блочными устройствами надо читать в обработчике блоки фиксированного размера, используя для этого команду read. Команда fconfigure, которая будет описана ниже, предоставляет возможность перевести канал в неблокирующий режим. При работе с fileevent этой возхможностью пользоваться не обязательно. Преимущества и недостатки неблокирующего ввода-вывода мы обсудим далее в этой главе. При поступлении символа конца файла данные в потоке становятся доступными для чтения. В обработчике, осуществляющем чтение данных, необходимо предусмотреть проверку на наличие символа конца файла. Важно вовремя закрыть поток, так как при этом регистрация обработчика автоматически отменяется. Если вы забудете закрыть поток, обработчик события будет вызываться повторно. Код обработчика события, предназначенного для получения информации, приведен в листинге 16.1. В данном примере канал открывается для чтения, а процедура Reader вызывается при наличии в нем данных. При обнаружении конца файла устанавливается переменная done, указанная при вызове команды vwait. В противном случае обработчик читает и обрабатывает одну строку выходных данных. Команда vwait описана далее в этой главе. Еще один пример использования fileevent приведен в главе 24; его код содержится в листинге 24.1. Листинг 16.1. Код обработчика события, предназначенного для чтения данных proc Reader { pipe } { global done
354 Часть II. Расширенные средства Tcl if {[eof $pipe]} { catch {close $pipe} set done 1 return } gets $pipe line # Обработка строки... } set pipe [open "I some command"] fileevent $pipe readable [list Reader $pipe] vwait done С одним каналом ввода-вывода можно связать не более одного обработчика для чтения и одного — для записи данных. Если на момент регистрации для события уже была зарегистрирована процедура обработки, то старый обработчик удаляется. Если вы вызовете команду fileevent без параметра, то в результате ее выполнения будет возвращена зарегистрированная команда. В случае, если ни одна команда не зарегистрирована, fileevent вернет пустую строку. Регистрация пустой строки отменяет текущий обработчик. Информация о команде fileevent приведена в табл. 16.2. Таблица 16.2. Использование команды fileevent fileevent Запрашивает или регистрирует команду, которая вызы- идентификатор_файла вается тогда, когда информация в указанном файле ста- readable ?команда? новится доступной для чтения fileevent Запрашивает или регистрирует команду, которая вы- идентификатор_файла зывается тогда, когда появляется возможность записи writable ?команда? в указанный файл Команда vwait Команда vwait переводит программу в режим ожидания модификации переменной. В приведенном ниже примере команда after планирует выполнение через 500 миллисекунд после команды, присваивающей переменной новое значение. Затем вызывается команда vwait, которая приостанавливает работу программы до тех пор, пока не будет изменено значение переменной х. set х 0 after 500 {set x 1} vwait x
Глава 16. Программы, управляемые событиями 355 При ожидании, вызванном командой vwait, Tcl не прекращает цикл обработки событий. Поэтому во время ожидания обработчики событий продолжают вызываться. Команда vwait завершает свою работу тогда, когда в ответ на некоторое событие вызывается Tcl-команда, которая присваивает переменной новое значение. В приведенном выше примере Tcl-команда вызывается по событию таймера и присваивает переменной х значение 1. set х 1 В некоторых случаях команда vwait вызывается лишь для того, чтобы запустить цикл обработки событий. В листинге 16.2 задается обработчик событий для потока stdin, осуществляющий чтение и вызов команд. Команда vwait используется лишь для запуска цикла; обработка команд продолжается до тех пор, пока входной поток не будет закрыт. В этот момент программа завершает работу, потому переменная, указанная в vwait, не используется. Листинг 16.2. Применение команды vwait для активизации цикла обработки событий proc Stdin_Start {prompt} { global Stdin set Stdin(line) "" puts -nonewline $prompt flush stdout fileevent stdin readable [list StdinRead $prompt] vwait Stdin(wait) } proc StdinRead {prompt} { global Stdin if {[eof stdin]} { exit } append Stdin(line) [gets stdin] if {[info complete $Stdin(line)]} { catch {uplevel #0 $Stdin(line)} result puts $result puts -nonewline $prompt flush stdout set Stdin(line) {} } else { append Stdin(line) \n } }
356 Часть II. Расширенные средства Tcl Команда fconfigure Команда fconfigure позволяет устанавливать свойства каналов ввода-вывода и получать информацию о текущих свойствах. В большинстве случаев свойства каналов, установленные по умолчанию, устраивают разработчиков. Если вы организуете ввод-вывод, управляемый событиями, вам, возможно, потребуется перевести канал в неблокирующий режим. При работе с двоичными данными необходимо отключить преобразование наборов символов. Получить информацию о параметрах канала можно следующим образом: fconfigure stdin => -blocking 1 -buffering none -buffersize 4096 -encoding iso8859-l -eofchar {} -translation If В табл. 16.3 приведены свойства, поддерживаемые fconfigure, за исключением свойств, имеющих отношение к последовательным линиям. Таблица 16.3. Свойства, которыми можно управлять с помощью fconfigure -blocking Блокирование до готовности канала ввода-вываода: значение О или 1 -buffering Режим буферизации: значение none, line или full -buffersize Число символов в буфере -encoding Кодировка набора символов -eofchar Специальный символ конца файла. Для системы DOS это символ, соответствующий комбинации клавиш <Ctrl+Z> (\xla). В других случаях — значение null -lasterror Последнее сообщение об ошибке POSIX, связанное с каналом -translation Преобразование конца строки: значения auto, If, cr, crlf, binary -peername IP-адрес удаленного узла (только для сетевых гнезд) -peerport Номер порта удаленного узла (только для сетевых гнезд) Последовательные линии обладают рядом характеристик. В версиях, предшествующих Tcl 8.4, разработчик мог контролировать только скорость передачи, число битов и наличие бита четности. Для этой цели использовалась опция -mode. В Tcl 8.4 были добавлены средства, позволяющие управлять другими свойствами последовательных линий (табл. 16.4).
Глава 16. Программы, управляемые событиями 357 Таблица 16.4. Свойства последовательных линий, управляемых fconfigure -mode Формат: скорость_обмена,контроль_ четности, число _ битов _данных,стоповые_ биты -queue Возвращает список из двух целых чисел, которые представляют число байтов во входной и выходной очереди (Tcl 8.4) -timeout Задает время тайм-аута в миллисекундах для чтения данных в блокирующем режиме (Tcl 8.4) -ttycontrol Устанавливает выходные строки для установления связи (Tcl 8.4) -ttystatus Возвращает состояние последовательной линии (Tcl 8.4) -xchar Определяет программные символы для установления связи (Tcl 8.4) -handshake Определяет значения rtscts, xonxof f или (для системы Windows) dtrdsr (Tcl 8.4) -pollinterval Устанавливает максимальное время опроса для событий, связанных с файловым обменом (только для системы Windows) (Tcl 8.4) -sysbuf f er Определяет размер системных буферов для последовательных каналов (только для системы Windows) (Tcl 8.4) Неблокирующий режим ввода-вывода По умолчанию каналы ввода-вывода работают в блокирующем режиме. Команды gets и read ожидают поступления данных, а лишь затем возвращают управление вызывающей процедуре. Если канал не готов к записи данных, команда puts также ожидает того момента, когда информацию можно будет передавать в выходной поток. Когда обмен осуществляется с файлами на дисках, последние практически всегда готовы для записи и чтения информации, поэтому такое поведение системы не создает проблем. Если же вы работаете с каналами или с сетевыми гнездами, блокирующий режим может привести к тому, что приложение длительное время не будет реагировать на действия пользователя, ожидая поступления данных. Команда fconfigure позволяет переводить потоки в неблокирующий режим. В случае отсутствия данных во входном потоке команды gets и read завершаются немедленно. Команда puts в неблокирующем режиме принимает все данные и сохраняет их во внутреннем буфере. При готовности устройства (точнее канала или гнезда) Tcl автоматически записывает в него данные из буфера. При работе с неблокирующими потоками приложение, ожидая готовности каналов ввода-вывода, может выполнять другие действия. Если установлен неблокирующий режим, появляется возможность организовать
358 Часть II. Расширенные средства Tcl одновременную работу через несколько каналов ввода-вывода. Для обмена в неблокирующем режиме следует использовать описанную выше команду fileevent. Чтобы перевести канал в неблокирующий режим, надо выполнить следующую команду: fconfigure идентификатор „файла, -blocking О Следует заметить, что перевод потока в неблокирующий режим нельзя рассматривать как необходимую меру. Однако в случае блокирующего режима команды gets и read все же могут на некоторое время приостановить работу приложения. Предположим, например, что в потоке появились некоторые данные, но они не составляют законченную строку. В этом случае команда gets будет ожидать поступления остальных символов. Наверное, основной аргумент в пользу применения неблокирующего режима — это буферизация данных, осуществляемая при выполнении команды puts. Если вам надо закрыть выходной поток, вы можете сделать это, даже если в буфере есть информация. Tcl автоматически отследит момент готовности канала и выведет в него оставшиеся данные. В листинге 16.3 показан обработчик событий, связываемый с неблокирующим каналом с помощью команды fileevent. В момент наступления события во входном потоке может находиться лишь часть строки. В этом случае команда gets не извлекает из потока ни одного символа и возвращает значение, равное —1. Листинг 16.3. Обработчик событий для неблокирующего канала set pipe [open "I some command"] fileevent $pipe readable [list Reader $pipe] fconfigure $pipe -blocking 0 proc Reader { pipe } { global done if {[eof $pipe]} { catch {close $pipe} set done 1 return } if {[gets $pipe line] < 0} { # Блокирование происходит в любом случае, поскольку # во входном потоке присутствует лишь часть строки } else { # Обработка одной строки } } vwait done
Глава 16. Программы, управляемые событиями 359 Команда fblocked Команда fblocked возвращает значение 1, если в канале нет данных, готовых для чтения. Заметьте, что команда f ileevent принимает все необходимые меры для отслеживания момента поступления данных, поэтому fblocked обычно используется лишь для тестирования. Буферизация По умолчанию Tcl осуществляет буферизацию данных, что повышает эффективность операций ввода-вывода. Обращения к физическому устройству производятся реже и накладные расходы, связанные с обменом информацией, уменьшаются. В некоторых случаях бывает необходимо, чтобы данные поступали на устройство сразу после вывода их в поток. При этом буфер становится не только излишним, но даже нежелательным компонентом. Отключить буферизацию позволяет следующая команда: fconfigure идентификатор_файла -buffering none Когда выходные данные накапливаются в выходном буфере до его заполнения, говорят о полной буферизации. Если программа некоторое время не читает входные данные, Tcl накапливает их во входном буфере. Такое накопление данных уменьшает вероятность блокирования программы операциями ввода. Для управления размерами буфера предусмотрена опция -buf f ersize. fconfigure идентификатор„файла -buffering full -buffersize 8192 В потоках stdin и stdout по умолчанию осуществляется буферизация строк. Каждый символ новой строки, передаваемый в выходной поток, вызывает операцию записи. Буферизацию строк можно включить, вызвав приведенную ниже команду. fconfigure идентификатор^файла -buffering line Преобразование символа конца строки В системе Unix строка текста завершается символом новой строки (\п). В Macintosh роль завершающего символа строки играет возврат каретки (\г). В Windows признаком завершения являются два символа: возврат каретки и перевод строки (\г\п). Возврат каретки и перевод строки также используются в качестве признака конца строки в сетевых гнездах. Tcl поддерживает все соглашения; более того, данные, передаваемые через один канал, могут содержать различные признаки завершения строки. Все варианты признаков окончания строк при чтении преобразуются в стандарт Unix, т.е. строки текста всегда завершаются символом новой строки (\п). Эти соглашения поддер-
360 Часть II. Расширенные средства Tcl живают команды read и gets. По умолчанию вывод текста осуществляется в формате, специфическом для текущей платформы. Поведение системы по умолчанию подходит для решения практически всех задач, однако по необходимости вы можете управлять преобразованием символов конца строки с помощью команды f configure. В табл. 16.5 описаны значения опции -translation. Таблица 16.5. Режимы преобразования символов конца строки binary Преобразование не выполняется If Завершение строки по соглашениям Unix; преобразование не выполняется сг Завершение строки по соглашениям Macintosh. При вводе возврат каретки преобразуется в перевод строки. При выводе перевод строки заменяется возвратом каретки crlf Завершение строки по соглашениям, используемым в системе Windows, и при сетевом обмене. В процессе ввода вместо пары, состоящей из возврата каретки и перевода строки, используется один символ перевода строки. При выводе перевод строки превращается в пару символов: возврат каретки и перевод строки auto Преобразование по умолчанию. При вводе все варианты завершения заменяются символом перевода строки. Вывод осуществляется во внутреннем формате Обработка символа конца файла В файловой системе DOS файл оканчивается символом с кодом \xla. (Этот код генерируется после нажатия комбинации клавиш <Ctrl+Z>.) На платформе Windows данный символ, если встречается в файле, по умолчанию игнорируется. Этот же символ, переданный в поток, закрывает файл. Отменить символ конца файла можно, указав вместо него пустую строку. fconfigure идентификатор „файла, -eofchar {} В Tcl 8.4 символ конца файла используется Tcl_EvalFile и source для того, чтобы дать возможность Tclkit и другим инструментам добавлять произвольные данные к файлам сценариев. Символ конца файла в обычных условиях не влияет на выполнение сценариев. Последовательные устройства Опция -mode позволяет указать скорость передачи, число битов, наличие бита четности и число стоповых битов.
Глава 16. Программы, управляемые событиями 361 set tty [open /dev/ttya] fconfigure $tty -mode => 9600,0,8,2 В Tcl 8.4 были реализованы средства расширенного контроля последовательных каналов для систем Windows и Unix. Соответствующие опции приведены в табл. 16.4. В Windows предусмотрены специальные имена устройств. При выполнении команды open эти устройства всегда считаются последовательными. Это устройства coml-com9, системная консоль (con) и устройство mil. В Unix описания устройств содержатся в каталоге /dev. Последовательными являются /dev/ttya, /dev/ttyb и многие другие устройства. Системная консоль имеет имя /dev/console, текущий терминал — /dev/tty, а устройство null — /dev/null. В Macintosh для открытия последовательных устройств необходимо использовать специальные команды. Соответствующие программные расширения поставляются независимыми разработчиками. Информацию о них можно найти по следующему адресу: http://www.Tcl.tk/resource/software/extensions/macintosh/ Кодировки Tcl автоматически преобразует различные кодировки в Unicode. Однако, читая данные из файла или принимая их по сети, Tcl не может распознать кодировку. Поэтому, если кодировка символов отличается от системной, ее необходимо непосредственно указывать с помощью команды fconfigure -encoding. Подробно об использовании наборов символов см. в главе 15. Настройка каналов ввода-вывода Если один и тот же канал используется как для ввода, так и для вывода данных, вы можете независимо устанавливать свойства для его входного и выходного компонента. В этом случае значением соответствующей опции должен быть список из двух элементов. Первый элемент соответствует компоненту канала, реализующему ввод, а второй — компоненту, ответственному за вывод данных. Если вы зададите только один элемент, он будет применен как ко входному, так и к выходному компоненту. Например, приведенная ниже команда задает режим преобразования символа конца строки crlf для вывода. При вводе этот символ преобразуется автоматически. Кроме того, данная команда устанавливает размер входного и выходного буфера, равный 4096. fconfigure pipe -translation {auto crlf} -buffersize 4096
Глава 17 Использование сетевых гнезд В данной главе рассматриваются вопросы использования гнезд при разработке сетевых клиентов и серверов. Кроме того, обсуждаются расширенные средства, предоставляемые гнездами, например неблокирующий ввод-вывод и управление буферизацией. Приведена также информация о следующих Тс1-командах: socket, fconfigure и http::geturl. 1 НЕЗДА — это каналы, с помощью которых осуществляется сетевой обмен. В данной главе речь пойдет о TCP-гнездах, однако существуют расширения Tcl, которые позволяют работать с гнездами, созданными посредством других протоколов. Протокол TCP создает абсолютно надежный канал взаимодействия двух устройств, подключенных к сети. Протоколы семейства TCP/IP решают все задачи, связанные с маршрутизацией информации, передаваемой по сети, автоматически исправляют ошибки, возникающие при передаче, и восстанавливают утерянные данные. Протокол TCP используется при работе других протоколов, например Tclnet, FTP и HTTP. В Tcl-сценариях сетевые гнезда применяются так же, как открытые файлы и каналы. Вместо команды open для открытия гнезда используется Tcl- команда socket. После того как гнездо создано, для передачи данных можно использовать команды gets, puts и read. Команда close закрывает гнездо. Поведение сетевой программы зависит от того, выполняет ли она функции клиента или сервера. Сервер — это программа, которая обычно выполняется в течение длительного времени и управляет доступом к некоторым ресурсам. Например, FTP-сервер поддерживает обмен файлами, а HTTP-сервер предоставляет доступ к гипертекстовым данным в World Wide Web. Клиент обычно обращается к серверу для того, чтобы получить доступ к ресурсам; время взаимодействия клиента с сервером ограничено. В качестве примера
Глава 17. Использование сетевых гнезд 363 клиентской программы можно привести Web-браузер, запрашивающий Web- страницу. В данной главе приводится пример создания клиентской программы, поддерживающей протокол HTTP. Сетевые расширения Tcl В данной главе приводятся основные сведения об использовании сетевых гнезд в прикладных программах. Создание гнезда в языке Tcl не составляет труда, кроме того, в распоряжение разработчика предоставляются различные расширения, которые обеспечивают поддержку часто используемых протоколов. В данном разделе приводятся общие сведения о пакетах, которые можно использовать для создания приложений, предназначенных для работы в сети. Остальная часть главы посвящена вопросам создания и использования гнезд в программах. Scotty Расширение Scotty поддерживает различные сетевые протоколы. Расширение Tcl под названием Scotty позволяет разработчику использовать такие протоколы, как UDP, DNS и RPC. Кроме того, в этом продукте предусмотрена поддержка протокола управления сетевыми ресурсами SNMP и базы данных MIB, используемой совместно с этим протоколом. Пакет Scotty широко используется при создании управляющих приложений, работающих в сети. Данный продукт поставляется в исходных текстах на языке С; для получения двоичных файлов вам надо выполнить компиляцию программ самостоятельно. Web-страница Scotty расположена по адресу http: //wwwsnmp.cs.utwente.nl/~schoenw/scotty/. Стандартная библиотека Tcl Стандартная библиотека Tcl (Tcllib) содержит несколько пакетов, предназначенных для поддержки популярных протоколов семейства TCP/IP. Пакеты реализованы на языке Tcl и позволяют реализовать следующие типы сетевых программ. • DNS-клиент. Поддерживает соответствие между именами узлов и IP- адресами. • FTP-клиент. Позволяет устанавливать FTP-соединения и копировать файлы с FTP-серверов. • FTP-сервер. Реализует простой расширяемый FTP-сервер.
364 Часть II. Расширенные средства Tcl • IRC-клиент. Реализует клиент, поддерживающий обмен сообщениями в реальном времени (chat). • NNTP-клиент. Позволяет получать данные с сервера новостей. • РОРЗ-клиент. Post Office Protocol дает возможность получать входящие сообщения с почтового сервера. • РОРЗ-сервер. Реализует сервер доставки почты. • SMTP-клиент. Передает сообщения электронной почты, используя протокол SMTP. • SMTP-сервер. Принимает сообщения электронной почты посредством протокола SMTP. • Средства обработки URI. Пакет, предназначенный для синтаксического разбора URL. Документация на эти пакеты, представленная в электронном виде, находится по следующему адресу: http://tcllib.sourceforge.net/tcllib/doc/. HTTP В состав дистрибутивного пакета Tcl входит HTTP-клиент, который будет описан далее в этой главе. Благодаря этому воспользоваться возможностями клиента данного типа можно, не прибегая к услугам tcllib. Кроме того, в Tcl реализован Web-сервер, рассмотрению которого посвящена глава 18. Гнезда на стороне клиента Клиентская программа создает гнездо, указывая адрес сервера и номер порта на сервере. Адрес сервера задает расположение в сети компьютера, выполняющего роль сервера, а номер порта идентифицирует конкретную серверную программу на этом компьютере. Например, HTTP-серверы чаще всего используют порт 80, а FTP-серверы — порт 20. Ниже приведен пример создания клиентского гнезда, предназначенного для взаимодействия с Web- сервером. set s [socket www.tcl.tk 80] Имя узла может задаваться двумя способами. В приведенном выше примере используется доменное имя www.tcl.tk. Вместо доменного имени можно указать IP-адрес, состоящий из четырех десятичных целых чисел, разделенных точками (например, 192.220.75.86). Системные программы преобразуют доменные имена в IP-адреса. При установлении соединения рекомендуется всегда задавать доменное имя, поскольку IP-адрес узла может измениться.
Глава 17. Использование сетевых гнезд 365 В некоторых системах для часто используемых номеров портов определены символьные имена. Например, вместо порта 20 можно использовать имя ftp. В системе Unix номера портов и соответствующие им имена приведены в файле /etc/services. Опции, используемые при создании клиентских гнезд В команде socket предусмотрен ряд необязательных параметров, которые могут использоваться при создании клиентских гнезд. Команда socket записывается следующим образом: socket ?-async? ?-myaddr адрес? ?-myport порт? имя_узла порт В большинстве случаев адрес и порт на стороне клиента выбираются автоматически. Если на компьютере содержится несколько сетевых интерфейсов, вы можете выбрать один из них с помощью опции -myaddr. В качестве адреса может быть задано либо доменное имя, либо IP-адрес. Если для вашего приложения нужен конкретный клиентский порт, его можно указать посредством опции -myport. Если данный порт уже используется, при выполнении команды socket возникает ошибка. Опция -async указывает на то, что установление соединения должно осуществляться в фоновом режиме. В этом случае команда socket немедленно возвращает управление вызывающей процедуре. Гнездо становится доступным после того, как соединение успешно установлено, либо если при установлении соединения возникла ошибка. С помощью команды f ileevent вы можете определить процедуру обратного вызова, которая получит управление в этот момент. Пример обработки события установления соединения приведен в листинге 17.1. Если вы попытаетесь обратиться к гнезду до установления соединения и для гнезда задан режим блокировки, выполнение программы автоматически приостановится до тех пор, пока соединение не будет установлено. Если гнездо находится в неблокирующем режиме, команда, посредством которой осуществляется обращение в нему, немедленно вернет управление. В этом случае команды gets и read возвращают значение — 1, а команда f blocked — значение, равное 1. Иногда для подключения к серверу может потребоваться достаточно длительное время. Обычно это происходит тогда, когда сервер отключен. В этом случае процесс соединения продолжается до истечения времени тайм-аута. В примере, приведенном в листинге 17.1, устанавливается таймер, который определяет новое значение тайм-аута.
366 Часть II. Расширенные средства Tcl Листинг 17.1. Создание клиентского гнезда с указанием времени тайм-аута proc Socket.Client {host port timeout} { global connected after $timeout {set connected timeout} set sock [socket -async $host $port] fileevent $sock w {set connected ok} vwait connected fileevent $sock w {} if {$connected == "timeout"} { return -code error timeout } else { return $sock } } Серверные гнезда TCP-сервер ожидает обращение от клиента. Поэтому для сервера команда socket формирует гнездо, которое находится в режиме ожидания, и при обращении клиентов создает новые гнезда. Все детали этого процесса реализует Tcl, поэтому для создания гнезд разработчику не приходится прикладывать больших усилий. Необходимо лишь указать номер порта и определить Тс 1-команду обратного вызова, которая будет выполнена при обращении клиента к серверному гнезду. Пример создания гнезда на стороне сервера приведен в листинге 17.2. Листинг 17.2. Создание гнезда на стороне сервера set listenSocket [socket -server Accept 2540] proc Accept {newSock addr port} { puts "Accepted $newSock from $addr port $port" } vwait forever Команда Accept является командой обратного вызова и выполняется при попытке клиента установить соединение с сервером. При вызове этой команды Tcl задает дополнительные параметры: новое гнездо, а также имя узла и номер порта на стороне клиента. В приведенном простом примере процедура Accept лишь выводит полученные значения параметров. Команда vwait запускает цикл обработки событий, что необходимо для поддержки соединений. Команда vwait переводит программу в режим ожи-
Глава 17. Использование сетевых гнезд 367 дания до тех пор, пока переменная forever не будет модифицирована. В данном простом примере это не происходит никогда. Однако при ожидании модификации переменной Tcl продолжает обрабатывать другие события (например, попытки установить сетевое соединение, готовность к обмену данными с файлами и т.д.). В Тк-приложениях (например, wish) цикл обработки событий уже запущен, поэтому вызывать команду vwait не обязательно. О цикле обработки событий Tcl см. в главе 16. Опции, используемые при создании серверных гнезд По умолчанию Tcl предоставляет операционной системе выбирать сетевой интерфейс, используемый для серверного гнезда; вам остается лишь указать номер порта. Если в вашем компьютере есть несколько сетевых интерфейсов и вы хотите явно задать один из них, вам надо использовать для этой цели опцию -myaddr. Команда, предназначенная для создания серверного гнезда, имеет следующий вид: socket -server команда__обратного_вызова ?-myaddr адрес? порт Последним параметром команды socket является номер порта. Поскольку ваш сервер не имеет официального статуса, вам следует выбрать для него номер выше 1024, в противном случае может возникнуть конфликт с имеющимися службами. В системе Unix пользовательским программам запрещено создавать серверные гнезда, ожидающие обращения через порт с номером, превышающим 1024. Если вы зададите номер порта 0, то операционная система сама выберет номер порта для вашей программы. Определить номер выбранного порта можно с помощью команды fconfigure. fconfigure $sock -sockname => IP-адрес имя_узла порт Служба echo Листинг 17.3. Служба echo proc Echo.Server {port} { global echo set echo(main) [socket -server EchoAccept $port] } proc EchoAccept {sock addr port} { global echo puts "Accept $sock from $addr port $port"
368 Часть II. Расширенные средства Tcl set echo(addr,$sock) [list $addr $port] fconfigure $sock -buffering line fileevent $sock readable [list Echo $sock] } proc Echo {sock} { global echo if {[eof $sock] I I [catch {gets $sock line}]} { # end of file or abnormal connection drop close $sock puts "Close $echo(addr,$sock)" unset echo(addr,$sock) } else { if {[string compare $line "quit"] == 0} { # Запретить установление новых соединений. # Существующие соединения остаются открытыми, close $echo(main) } puts $sock $line } 2 Сервер, обеспечивающий работу службы echo, ожидает обращения клиентов. После установления соединения он читает данные, переданные клиентом, и возвращает обратно клиентской программе. В данном примере команда fileevent используется для организации чтения информации, а посредством команды fconfigure осуществляется управление буферизацией. Код, приведенный в листинге 17.3, можно использовать как заготовку для создания более сложных серверов. Процедура Echo_Server создает гнездо и сохраняет результаты выполнения команды socket в элементе массива echo (main). Когда впоследствии это гнездо закрывается, сервер прекращает установление новых соединений, но на существующие соединения закрытие гнезда не влияет. Если вы хотите поэкспериментировать с сервером, запустите его и переведите в режим ожидания обращений клиентов. Для этого надо выполнить следующие команды: Echo_Server 2540 vwait forever Процедура EchoAccept использует команду fconfigure для установления строковой буферизации. Это означает, что каждая команда puts, выполненная сервером, приводит к передаче данных клиенту по сети. Насколько такой режим важен для нормального обмена сервера с клиентом, станет ясно
Глава 17. Использование сетевых гнезд 369 из дельнейшего обсуждения. Подробное описание команды fconfigure см. в главе 16. С помощью команды f ileevent EchoAccept регистрирует процедуру, поддерживающую обмен данными с гнездом. В приведенном примере процедура Echo вызывается каждый раз, когда данные в гнезде доступны для чтения. Заметьте, что в случае, когда обмен осуществляется с помощью команд обратного вызова, переводить гнездо в неблокирующий режим не обязательно. Об особенностях неблокирующего режима см. в главе 16. Процедура EchoAccept сохраняет информацию о каждом клиенте в массиве echo. Она используется только для вывода сообщения о том, что клиент закрыл соединение. В более сложных случаях вам, возможно, придется хранить подробную информацию о каждом клиенте. Имя гнезда очень удобно использовать для идентификации клиента — на его основе может быть сформирован индекс массива. Процедура Echo проверяет, было ли гнездо закрыто клиентом или при чтении данных возникла ошибка. В условном выражении if команда gets выполняется только в том случае, если значение, возвращаемое eof, не равно true. if {[eof $sock] I I [catch {gets $sock line}]} { При закрытии гнезда автоматически отменяется регистрация f ileevent. Если вы не закроете гнездо, обнаружив символ конца файла, цикл обработки событий будет постоянно обращаться к вашей процедуре обратного вызова. Сервер, обеспечивающий работу службы echo, читает строку с помощью команды gets, а затем передает ее клиенту посредством команды puts. При получении строки "quit" сервер закрывает главное гнездо. Этим запрещается установление новых соединений с клиентами, однако клиенты, установившие соединение ранее, продолжают работу. В листинге 17.4 приведен пример кода клиентской программы, взаимодействующей со службой echo. Листинг 17.4. Клиент службы echo proc Echo_Client {host port} { set s [socket $host $port] fconfigure $s -buffering line return $s } set s [Echo.Client localhost 2540] puts $s "Hello!" gets $s => Hello!
370 Часть II. Расширенные средства Tcl Главная особенность данной программы состоит в том, что для гнезда установлен режим строковой буферизации, поэтому каждый вызов клиентом команды puts приводит к передаче информации по сети. (Другими словами, передача данных осуществляется при вводе символа новой строки.) Если вы не установите режим строковой буферизации (для этой цели используется команда iconfigure), команда gets клиента, вероятнее всего, никогда не будет вызвана, так как сервер не получит данных. Вся информация будет "заморожена" в выходном буфере клиента. Получение данных по протоколу HTTP Протокол HTTP (HyperText Transport Protocol) используется для организации работы системы World Wide Web. В данном разделе мы рассмотрим процедуру, позволяющую получать документы или изображения с Web-сервера. Данные, расположенные в Web, идентифицируются с помощью унифицированного локатора ресурсов (URL — Universal Resource Locator), в составе которого указываются узел сети, порт и путь к ресурсу. В ходе взаимодействия клиент передает серверу URL, а сервер возвращает заголовок ответа и запрашиваемые данные. Заголовок описывает информацию, передаваемую в составе ответа, которая может представлять собой гипертекстовый документ, изображение, postscript-описание и т.д. Листинг 17.5. Установление соединения с HTTP-сервером proc Http_0pen {url} { global http if {![regexp -nocase С(http://)?(Г:/]+)(:([0-9]+))?(/.*)} \ $url x protocol server у port path]} { error "bogus URL: $url" } if {[string length $port] == 0} { set port 80 } set sock [socket $server $port] puts $sock "GET $path HTTP/1.0" puts $sock "Host: $server" puts $sock "User-Agent: Tcl/Tk Http_0pen" puts $sock "" flush $sock return $sock }
Глава 17. Использование сетевых гнезд 371 Для извлечения адреса сервера и номера порта из URL в процедуре Http_0pen используется команда regexp. Подробно о регулярных выражениях см. в главе 11. Идентификатор протокола http:// в начале URL и номер порта после адреса узла могут отсутствовать. Если порт не указан, по умолчанию принимается стандартный порт 80. Если разбор регулярного выражения выполнен успешно, команда socket создает гнездо, т.е. устанавливает сетевое соединение. Обмен по протоколу HTTP начинается с того, что клиент передает серверу строку, в которой указана команда, или метод запроса (GET), путь к ресурсу и версия протокола. Путь является частью URL и следует после адреса сервера и номера порта. Остальная часть запроса представляет собой набор полей, т.е. строк, каждая из которых имеет следующий формат: ключ: значение Поле заголовка Host идентифицирует сервер; это необходимо в том случае, когда на одном компьютере поддерживается несколько серверных имен. Поле User-Agent определяет клиентскую программу, или браузер. В большинстве случаев это Netscape Navigator, Mozilla или Internet Explorer. После полей заголовка следует пустая строка. Данные, подготовленные в таком формате, передаются серверу. Немедленную передачу информации из буфера вызывает команда flush. Получив запрос, сервер передает клиенту ответ. Действия сервера мы обсудим позже, а сначала рассмотрим proxy-серверы. Proxy-сер веры Proxy-серверы предназначены для передачи данных через брандмауэры, защищающие локальные сети от несанкционированного обращения из Internet. Proxy-сервер получает от клиента, находящегося за брандмауэром, HTTP-запрос и перенаправляет его вовне, т.е. по другую сторону брандмауэра. Кроме того, proxy-сервер обрабатывает ответ сервера и возвращает его клиенту. При использовании proxy-сервера протокол остается практически неизменным. Отличие состоит лишь в том, что в первой строке запроса после команды GET указывается абсолютный URL. Это необходимо для того, чтобы proxy-сервер мог найти целевой сервер. Программа, код которой приведен в листинге 17.6, использует proxy-сервер (если таковой имеется). Листинг 17.6. Установление соединения через proxy-сервер HTTP # Http_Proxy задает proxy-сервер или запрашивает текущие установки proc Http_Proxy {{new {}}} { global http if ! [info exists http(proxy)] {
372 Часть II. Расширенные средства Tcl return {} } if {[string length $new] == 0} { return $http(proxy):$http(proxyPort) } else { regexp V(r:]+):([0-9]+)$} $new x \ http(proxy) http(proxyPort) } } proc Http.Open {url {cmd GET} {query {}}} { global http if {.'[regexp -nocase {~(http://)?(Г :/]+)(: ([0-9]+))?(/.*)} \ $url x protocol server у port path]} { error "bogus URL: $url" } if {[string length $port] == 0} { set port 80 } if {[info exists http(proxy)] && [string length $http(proxy)]} { set sock [socket $http(proxy) $http(proxyPort)] puts $sock "$cmd http://$server:$port$path HTTP/1.0" } else { set sock [socket $server $port] puts $sock "$cmd $path HTTP/1.0" } puts $sock "User-Agent: Tcl/Tk Http_0pen" puts $sock "Host: $server" if {[string length $query] > 0} { puts $sock "Content-Length: [string length $query]" puts $sock "" puts $sock $query } puts $sock "" flush $sock fconfigure $sock -blocking 0 return $sock
Глава 17. Использование сетевых гнезд 373 Запрос HEAD В листинге 17.6 процедуре Http_0pen передается параметр cmd, т.е. пользователь, работающий с программой, может выполнять различные операции. Запрос GET используется для получения ресурса, на который указывает URL. В ответ на запрос HEAD сервер возвращает лишь описание указанного ресурса. Такой тип запроса позволяет проверить корректность URL. Запрос POST используется для передачи информации на сервер (обычно это данные, введенные посредством формы), а также для получения ресурса, идентифицируемого с помощью URL. Все указанные типы запросов предусмотрены протоколом HTTP. В ответ на получение запроса сервер передает строку состояния, за которой следуют поля заголовка, т.е. строки, содержащие пары имя _ по ля-значение. Поля заголовка ответа имеют тот же формат, что и поля заголовка, передаваемые в составе запроса. Если сервер принял запрос типа GET или POST, то за заголовком ответа следуют запрашиваемые данные. Программа, приведенная в листинге 17.7, реализует запрос HEAD, который не предполагает наличие данных в ответе. Листинг 17.7. Процедура Http_Head, предназначенная для проверки URL proc Http_Head {url} { upvar #0 $url state catch {unset state} set state(sock) [Http_0pen $url HEAD] fileevent $state(sock) readable [list HttpHeader $url] # При вызове команды vwait указывается реальное имя. # Псевдоним upvar использовать нельзя, vwait $url\(status) catch {close $state(sock)} return $state(status) } proc HttpHeader {url} { upvar #0 $url state if {[eof $state(sock)]} { set state(status) eof close $state(sock) return } if {[catch {gets $state(sock) line} nbytes]} { set state(status) error lappend state(headers) [list error $nbytes] close $state(sock)
374 Часть II. Расширенные средства Tcl return } if {$nbytes < 0} { # Чтение может быть заблокировано return } elseif {$nbytes == 0} { # Заголовок завершен set state(status) head } elseif {![info exists state(headers)]} { # Сервер возвращает строку состояния set state(headers) [list http $line] } else { # Обработка пар ключ-значение regexp {Л([~:]+): *(.*)$} $line x key value lappend state(headers) [string tolower $key] $value } 2 Процедура Http.Head использует для установления соединения с сервером процедуру Http.Open. Команда fileevent регистрирует процедуру HttpHeader как обработчик, осуществляющий чтение ответов сервера. Информация о каждой операции сохраняется в глобальном массиве. В качестве имени массива используется URL, а команда upvar (см. главу 7) создает псевдоним. upvar #0 $url state Псевдоним, созданный upvar, нельзя использовать в качестве переменной, указываемой при вызове команды vwait. В подобных случаях необходимо задавать реальное имя. Символ обратной косой черты отменяет ссылку на массив. vwait $url\(status) Процедура HttpHeader выполняет проверку на возникновение специфических ситуаций: появление символа конца файла, появление ошибки при выполнении команды gets или неполное чтение данных из гнезда, которое находится в неблокирующем режиме. Первая строка ответа сервера содержит код состояния, формат которого имеет вид код сообщение Код представляет собой трехзначное число. Код 200 означает успешное завершение обработки запроса. Коды, начинающиеся с цифры 4 или 5, свидетельствуют о возникновении ошибки. Полное описание кодов состояния,
Глава 17. Использование сетевых гнезд 375 определяемых протоколом HTTP 1.0, содержится в документе RFC 1945. Первая строка ответа идентифицируется ключевым словом http. set state(headers) [list http $line] Остальные строки заголовка оформляются в виде пар имя-значение и добавляются к элементу state (headers). Этот формат используется для инициализации массива. array set header $state(headers) Когда процедура HttpHeader обнаруживает пустую строку, которая является признаком окончания заголовка, она устанавливает значение state (status). Изменение значения этого элемента является сигналом для процедуры Http_Head. Http_Head возвращает вызывающей процедуре информацию о состоянии. Полная информация о запросе находится в глобальном массиве, именем которого является URL. Пример использования Http_Head приведен в листинге 17.8. Листинг 17.8. Использование процедуры Http_Head set url http://www.sun.com/ set status [Http.Head $url] => eof upvar #0 $url state array set info $state(headers) parray info info(http) HTTP/1.0 200 OK info(server) Apache/1.1.1 info(last-modified) Nov ... info(content-type) text/html Запросы GET и POST В листинге 17.9 показан код процедуры Http_Get, реализующей запросы GET и POST. Различие между этими запросами состоит в том, что запрос POST после заголовка передает на сервер данные. В ответ на оба типа запросов сервер возвращает клиенту ответ, состоящий из заголовка и тела ответа. В теле ответа содержатся данные, URL которых был указан в запросе. Процедура Http_0pen передает запрос, по необходимости включает в него данные, предназначенные для передачи на сервер, и читает заголовок ответа сервера. Тело ответа читает процедура Http_Get. Поле Content-Type, содержащееся в заголовке ответа сервера, указывает тип передаваемых данных. Значения этого поля соответствуют стандарту
376 Часть II. Расширенные средства Tcl MIME, описанному в документе RFC 1521. Примеры МШЕ-типов приведены ниже. • text/html — HTML-документ. О языке HTML (HyperText Markup Language) см. в главе 3. • text/plain. Текст без элементов разметки. • image/gif. Изображение в формате GIF. • image/jpeg. Изображение в формате JPEG. • application/postscript. Postscript-документ. • application/x-tcl. Tcl-программа. Этот тип подробно рассматривается в главе 20. Листинг 17.9. Процедура Http_Get, предназначенная для получения ресурса proc Http_Get {url {query {}}> { upvar #0 $url state ;# Псевдоним для глобального массива, catch {unset state} ;# Корректность псевдонима, if {[string length $query] > 0} { set state(sock) [Http_0pen $url POST $query] } else { set state (sock) [Http__0pen $url GET] } set sock $state(sock) fileevent $sock readable [list HttpHeader $url] # Для vwait указывается реальное мнение. Псевдоним # upvar задавать нельзя, vwait $url\(status) set header(content-type) {} set header(http) "500 unknown error" array set header $state(headers) # Проверка состояния ответа. # Код 200 означает нормальную обработку запроса, # остальные коды свидетельствуют о возникновении ошибки, regsub "HTTP/1.. " $header(http) {} header(http) if {![string match 2* $header(http)]} { catch {close $sock} if {[info exists header(location)] && [string match 3* $header(http)]} { # Код Зхх означает перенаправление на другой URL
Глава 17. Использование сетевых гнезд 377 set state(link) $header(location) return [Http_Get $header(location) $query] } return -code error $header(http) } # Установки для чтения содержимого switch -glob -- $header(content-type) { text/* { # Читать HTML-код в память. fileevent $sock readable [list HttpGetText $url] } default { # Копирование данных в файл, fconfigure $sock -translation binary set state(filename) [FileJTempName http] if [catch {open $state(filename) w} out] { set state(status) error set state(error) $out close $sock return $header(content-type) } set state(fd) $out fcopy $sock $out -command [list HttpCopyDone $url] } } vwait $url\(status) return $header(content-type) 2 Http_Get использует процедуру Http_0pen для инициализации запроса и проверяет наличие ошибок. Данная процедура поддерживает сообщения перенаправления, которые сервер передает в случае, если URL ресурса был изменен. Перенаправлению соответствуют коды ответов, начинающиеся с цифры 3. Часто такие сообщения возникают в том случае, когда пользователь забывает указать завершающую косую черту в составе URL (например, http: //www.tcl.tk). В этом случае многие серверы возвращают следующий ответ: 302 Document has moved Location: http://www.tcl.tk/ Если в поле заголовка content-type указан тип текстовых данных, Http_Get задает с помощью команды fileevent обработчик событий, который осуществляет чтение в память. Гнездо находится в неблокирующем
378 Часть II. Расширенные средства Tcl режиме, поэтому обработчик при каждом вызове может считать максимально доступный объем информации. Такой режим гораздо эффективнее построчного чтения с помощью процедуры gets. Текст хранится в переменной state (body) и доступен вызывающей процедуре. Код обработчика HttpGetText, устанавливаемого с помощью команды fileevent, приведен в листинге 17.10. Листинг 17.10. Обработчик HttpGetText proc HttpGetText {url} { upvar #0 $url state if {[eof $state(sock)]} { # Данные полностью прочитаны set state(status) done close $state(sock) } elseif {[catch {read $state(sock)} block]} { set state(status) error lappend state(headers) [list error $block] close $state(sock) } else { append state(body) $block } 2 Данные в теле запроса могут быть представлены в двоичном формате. В Tcl 7.6 и более ранних версиях обработка такой информации была сопряжена с трудностями. Поскольку нулевой символ является признаком окончания последовательности, данные, содержащие нулевые значения, обрабатывались Tcl-сценариями некорректно. В Tcl 8.0 была реализована поддержка строк и значений переменных, представленных в виде двоичных данных. В примере, приведенном в листинге 17.9, информация копировалась из гнезда в файл с помощью команды f сору. При этом данные не сохранялись в Тс1-перемен- ных. Команда f сору была введена в Tcl 7.5 как unsupportedO и получила свое теперешнее имя в Tcl 8.O. В качестве параметра f copy передается команда обратного вызова, которая выполняется по завершении копирования. Команде обратного вызова предоставляются дополнительные параметры: число переданных байтов и необязательная строка сообщения об ошибке. В данном примере эти параметры добавляются к параметру url команды f copy. Процедура обратного вызова HttpCopyDone приведена в листинге 17.11.
Глава 17. Использование сетевых гнезд 379 Листинг 17.11. Процедура HttpCopyDone, используемая совместно с f copy proc HttpCopyDone {url bytes {error {}}} { upvar #0 $url state if {[string length $error]} { set state(status) error lappend state(headers) [list error $error] } else { set state(status) ok } close $state(sock) close $state(fd) 2 Пользователь, работающий с Http_Get, использует информацию, которая хранится в массиве state, для того, чтобы определять результаты копирования и получать доступ к данным. В процессе обработки запроса могут возникнуть следующие ситуации. • Ошибка, о которой сообщает элемент массива state (error). • Перенаправление. В этом случае в элементе массива state (link) будет находиться новый элемент. Клиентская программа, использующая Http_Get, должна изменить URL и проверить информацию в соответствующем массиве состояния. Для переназначения псевдонима массива используется команда upvar. upvar #0 $state(link) state • Полученные данные имеют текстовый формат. В этом случае они находятся в элементе state (body). • Тип данных отличается от текстового. В этом случае надо использовать элемент массива state (filename). Команда fcopy Команда fcopy позволяет копировать информацию из входного потока в выходной поток в фоновом режиме. Она автоматически устанавливает требуемые обработчики событий, поэтому вам нет необходимости использовать команду fileevent. Команда fcopy также обеспечивает эффективное управление буферами. Данная команда вызывается следующим образом: fcopy вход выход ?-size размер? ?-command обратный_вызов?
380 Часть II. Расширенные средства Tcl Опция -command переводит fcopy в фоновый режим. По окончании копирования или при возникновении ошибки выполняется команда обратного вызова, указанная в качестве значения этой опции. Команде обратного вызова передаются два дополнительных параметра: число скопированных байтов и, в случае возникновения ошибки, строка сообщения. fcopy $in $out -command [list CopyDone $in $out] proc CopyDone {in out bytes {error {}} { close $in ; close $out } При работе в фоновом режиме fcopy читает данные из входного потока до появления символа конца файла либо до тех пор, пока не будет скопировано число байтов, указанное в качестве значения опции -size. Если опция -size не указана, копируются все данные до конца файла. На результаты выполнения fcopy могут оказывать влияние другие операции ввода-вывода, осуществляемые одновременно с этой командой. Если в процессе копирования будет закрыт входной или выходной поток, копирование прекратится. Если закрывается входной поток, данные, ожидающие вывода, будут записаны в выходной поток. Если опция -command не указана, команда fcopy читает максимально возможный объем данных. Результаты чтения зависят от наличия опции -size и от того, находится ли поток в блокирующем режиме. Прочитанные данные помещаются в очередь на вывод. Если выходной поток находится в блокирующем режиме, fcopy завершает работу лишь по окончании записи информации. Если в блокирующем режиме находится входной поток, fcopy приостанавливает выполнение остальных команд до тех пор, пока не будет прочитано число байтов, указанное в качестве параметра опции -size, или пока не будет достигнут конец файла. При реализации команды fcopy была допущена ошибка, проявляющаяся в том, что команда игнорировала кодировки данных. Эта ошибка была устранена в Tcl 8.3.4. Пакет http В стандартную библиотеку Tcl входит пакет http. Содержащиеся в нем процедуры работают по тому же принципу, что и программы, рассмотренные ранее в данной главе. В этом разделе описан интерфейс пакета http. При создании библиотеки использовался механизм пространств имен. Процедуры Http.Get, Http_Head и Http_Post объединены в одну процедуру http: : geturl. Примеры, приведенные в данной главе, предназначены исключительно для
Глава 17. Использование сетевых гнезд 381 изучения общих принципов написания сетевых программ. Разрабатывая реальные приложения, следует пользоваться стандартным пакетом http. Команда http::config Команда http: :conf ig позволяет задать информацию о proxy-сервере, а также значения полей заголовка User-Agent и Accept. Если приложение должно использовать proxy-сервер, вы можете указать его адрес и номер порта либо задать Tcl-команду, предназначенную для поиска сервера. Если команда http: :config вызывается без параметров, она возвращает текущие установки. http::config => -accept */* -proxyfilter http::ProxyRequired -proxyhost {} -proxyport {} -useragent {Tcl http client package 2.4} Если вы зададите лишь один параметр, http: :conf ig вернет соответствующую установку. http::config -proxyfilter => http::ProxyRequired При вызове http: :config вы можете указать любое число опций. http::config -proxyhost webcache.eng -proxyport 8080 По умолчанию proxy-фильтр возвращает лишь установленные значения -proxyhost и -proxyport. По мере необходимости вы можете использовать фильтр с расширенными возможностями, который выбирает proxy-сервер по адресу узла в составе URL. Если proxy-фильтр был вызван с указанием имени узла, он возвращает список из двух элементов: в одном из них содержится адрес узла, а в другом — номер порта. Если proxy-сервер не востребован, возвращается пустой список. Команда http::geturl Процедура http::geturl формирует, в зависимости от параметров, запрос GET, POST или HEAD. По умолчанию http: :geturl блокирует до завершения транзакции выполнение остальных команд и возвращает маркер, представляющий транзакцию. Если вы зададите опцию -command, команда http:: geturl сразу же завершит свое выполнение и по окончании транзакции выполнит команду обратного вызова. Команде обратного вызова передается маркер транзакции. При создании простого приложения целесообразно блокировать его выполнение до завершения транзакции.
382 Часть II. Расширенные средства Tcl set token [http::geturl www.beedub.com/index.html] => http::1 Указывать протокол (http: //) в URL не обязательно. Существует ряд команд, принадлежащих пространству имен http, которые возвращают маркер транзакции. Маркер является также именем массива, содержащего информацию о состоянии транзакции. По окончании работы следует удалить этот массив, чтобы освободить занимаемую им память. http::cleanup $token Если вы хотите непосредственно обращаться к элементам массива, вам следует создать псевдоним, используя для этого команду upvar. upvar #0 $token data Опции http: :geturl описаны в табл. 17.1. Таблица 17.1. Опции команды http: :geturl -binary логическое„значение -blocksize число -channel идентификатор_файла -command команда, обратного „вызова -handler команда -headers список -progress команда -query кодовая_строка -queryblocksize число -querychannel идентификатор_файла Указывает, следует ли выполнять передачу двоичных данных (Tcl 8.3) Размер блока при копировании в канал Значением данной опции является идентификатор открытого файла или гнезда. Вместо сохранения в памяти данные копируются в указанный канал По завершении транзакции вызывается указанная команда. Команде передается маркер, полученный от http::geturl Вызывается обработчиком события для чтения данных Список содержит поля заголовка, включаемые в HTTP-запрос. В списке присутствуют как ключи, так и значения Указанная команда вызывается после копирования в канал очередного блока. При вызове ей передаются три параметра: маркер, общий размер и текущий размер Формирует запрос POST, включающий кодовую строку, в которой содержатся данные формы Размер блока, который используется при копировании в канал, соответствующий запросу Значением опции является идентификатор открытого файла или гнезда. Данные копируются из этого канала
Глава 17. Использование сетевых гнезд 383 Окончание табл. 17.1 -queryprogress команда -timeout миллисекунды -type MIME-тип -validate логическое„значение Указанная команда выполняется после копирования очередного блока из канала, соответствующего запросу. При вызове команды ей передаются три параметра: маркер, общий размер и текущий размер Отменяет запрос по истечении времени, указанного в миллисекундах Указанный MIME-тип включается в поле заголовка Content-Type запроса POST Если логическое значение равно true, формируется запрос HEAD В табл. 17.2 приведены функции, обеспечивающие доступ к массиву состояния. Таблица 17.2. Процедуры поддержки http http: : cleanup $token Удаляет массив состояния с именем, определяемым значением $token http::code $token Возвращает state(http) http::data $token Возвращает state(body) http::error $token Возвращает state(error) http: : ncode $token Возвращает числовой код, содержащийся в state (http) http: :size $token Возвращает число прочитанных байтов данных http::status $token Возвращает state(status) http: :wait $token Выполняет блокирование до завершения транзакции Назначение элементов массива описано в табл. 17.3. Таблица 17.3. Элементы массива состояния http: :geturl body Данные ресурса char set Сведения о кодировке, входящие в состав значения Content-Type. Если кодировка не указана, то по умолчанию принимается iso8859-l coding Копия значения Content-Encoding current size Число переданных байтов error Описание причины, по которой транзакция была прервана http Состояние HTTP-ответа
384 Часть II. Расширенные средства Tcl Окончание табл. 17.3 met а Список ключей и значений в заголовке ответа post error Объяснение причины, по которой транзакция была прервана в процессе записи данных status Текущее состояние: pending, ok, eof или reset totalsize Ожидаемый размер возвращенных данных type Тип возвращенных данных url URL запроса Указав команду обратного вызова, выполняемую по завершении транзакции, вы можете реализовать асинхронный режим работы http: :geturl. Команде обратного вызова передается маркер, возвращаемый http: :geturl, в результате чего эта команда получает доступ к информации о состоянии транзакции. http::geturl $url -command [list UrlJDisplay $text $url] proc Url_Display {text url token} { upvar #0 $token state # Отображение URL в тексте } Процедура http: :geturl позволяет скопировать ресурс, на который указывает URL, в файл или передать его по сети. Для этой цели предназначена опция -channel. Такой подход может применяться при получении больших файлов или изображений. В этом случае команда обратного вызова получает управление в ходе операции, что дает возможность информировать пользователя о выполнении транзакции. В листинге 17.12 показан код простого сценария, предназначенного для копирования данных. Листинг 17.12. Копирование файлов с помощью процедуры http: :geturl #!/usr/local/bin/tclsh8.4 if {$argc < 2} { puts stderr "Usage: $argv0 url file" exit 1 } package require http set url [1index $argv 0] set file [lindex $argv 1] set out [open $file w] proc progress {token total current} { puts -nonewline "."
Глава 17. Использование сетевых гнезд 385 } http::config -proxyhost webcache.eng -proxyport 8080 set token [http::geturl $url -progress progress \ -headers {Pragma no-cache} -channel $out] close $out # Вывод информации из заголовка puts "" upvar #0 $token state puts $state(http) foreach {key value} $state(meta) { puts "$key: $value" } exit 0 Команда http::formatQuery Если вы включите в запрос данные формы, указав опцию -query, процедура http: :geturl сформирует запрос POST. Для того чтобы данные могли быть переданы, их следует представить в нужном формате. Процедура http: :formatQuery получает в качестве параметров ключи и значения и преобразует их в формат x-www-url-encoded. Результат преобразования передается в теле запроса. http::formatQuery name "Brent Welch" title "Tcl Programmer" => name=Brent+Welch&title=Tcl+Programmer Команды http::register и http::unregister Процедура http: :register регистрирует обработчик для протоколов, отличных от HTTP. Процедура http: :unregister отменяет регистрацию обработчика. Данные средства нужны в основном для поддержки защищенного Web-взаимодействия посредством протокола HTTPS с использованием расширения TLS. package require tls http::register https 443 ::tls::socket set token [http::geturl https://my.secure.site/] Команда http::reset Команда http: :reset позволяет отменить текущую транзакцию. http::reset $token Для того чтобы транзакция отменялась автоматически, надо указать при вызове http: : config опцию -timeout.
386 Часть II. Расширенные средства Tcl Команда http::cleanup По окончании обработки информации, полученной в результате выполнения процедуры http: igeturl, вы можете вызвать http: : cleanup, удалив тем самым переменную, используемую для хранения данных. Basic Authentication Некоторые Web-страницы защищены паролями. Как правило, для поддержки паролей используется протокол Basic Authentication, который не обеспечивает серьезной защиты, но прост в реализации. Согласно схеме Basic Authentication при попытке клиента обратиться к защищенному ресурсу сервер передает в ответе код ошибки 401 и поле заголовка Www-Authenticate, в котором указывается используемый протокол аутентификации. Например, ответ сервера может выглядеть следующим образом: HTTP/1.0 401 Authorization Required Www-Authenticate: Basic realm="My Pages" Параметр realm задает домен аутентификации. Иногда он включается в строку, которая отображается как часть приглашения для ввода пароля. Например, Web-браузер может отобразить следующее сообщение: Enter the password for My Pages at www.beedub.com После того как пользователь введет регистрационное имя и пароль, Web- браузер повторяет передачу HTTP-запроса. На этот раз в запрос включается поле заголовка Authorization, содержащее пользовательское имя и пароль, представленные в коде base64. Данный код не предполагает шифрования; любой желающий без труда может декодировать строку. Таким образом, подобные средства не обеспечивают серьезной защиты. Стандартная библиотека Tcl (Tcllib) содержит пакет base64, в состав которого входят процедуры base64: : encode и base64: :decode. Процедура BasicAuthentication. реализующая протокол Basic Authentication, представлена в листинге 17.13. В ней вызывается процедура http: :geturl с указанием опции -headers. Опция -headers позволяет включать в запрос дополнительные заголовки. Листинг 17.13. Процедура BasicAuthentication, использующая http: :geturl package require base64 package require http proc BasicAuthentication {url promptProc} { set token [http::geturl $url] http::wait $token if {[string match *401* [http::code $token]]} {
Глава 17. Использование сетевых гнезд 387 upvar #0 $token data # Извлечение информации о домене из строки Www-Authenticate array set reply $data(meta) if {[regexp {realm=(.*)} $reply(Www-Authenticate) \ x realm]} { # Запрос имени пользователя и пароля set answer [$promptProc $realra] http::cleanup $token # Кодирование пользовательского имени и пароля и # передача их в поле заголовка Authorization set auth [base64::encode \ [lindex $answer 0]: [lindex $answer 1]] set token [http::geturl $url -headers \ [list Authorization "Basic $auth"]] http::wait $token } } return $token 2 В процедуре BasicAuthentication предусмотрен параметр promptProc, с помощью которого задается имя процедуры, предназначенной для получения пользовательского имени и пароля. Она может отображать диалоговое окно Тк или выводить на терминал приглашение для ввода имени и пароля. Очевидно, что, собираясь работать с документом, вы уже знаете пароль. Поэтому вы можете включить поле Authorization в заголовок первого запроса. http::geturl $url -headers \ [list Authorization \ "Basic [base64::encode $username:$password]"]
Глава 18 Web-сервер TclHttpd В данной главе описывается продукт TclHttpd — Web-сервер, полностью написанный на языке Tcl. Он может использоваться как независимый сервер либо в составе приложения, работающего в Web. TclHttpd предоставляет шаблон Tcl-f HTML, который упрощает создание Web-узлов, и средство Application Direct URL. предназначенное для вызова Tcl-процедур, содержащихся в приложении. 1 Ipoekt TclHttpd начался с создания программы, насчитывающей 175 строк Tcl-кода и обрабатывающей запросы на получение HTML-страниц и изображений. Благодаря наличию Tcl-команды socket и средств ввода-вывода для решения поставленной задачи не пришлось прилагать большие усилия, а реализация Tcl-библиотеки на языке С позволила добиться на удивление высокого быстродействия. Поскольку первая реализация TclHttpd была предельно простой, большинство специальных возможностей, реализованных в обычных Web-серверах, таких как Apache или Netscape, в ней отсутствовало. Затем, вследствие появления усовершенствованных средств обработки HTTP, размеры программы увеличились до 250 строк. После этого программа была переработана с учетом требования модульной архтектуры, в нее были добавлены возможности, реализованные в других Web-серверах, а также некоторые специальные средства взаимодействия с Тс1-приложениями. В настоящее время TclHttpd может использоваться либо как Web-cep- вер общего назначения, либо в качестве базового инструмента для создания серверных приложений. На его базе создан ряд Web-узлов, например http://www.tcl.tk. Он также послужил основой для разработки некоторых коммерческих приложений: серверов и почтовых фильтров, реализующих защиту от спама. Как и многие Tcl-программы, данный сервер распространяется бесплатно; вы можете использовать его в своих разработках. В конце главы
Глава 18. Web-сервер TclHttpd 389 приведены инструкции по инсталляции TclHttpd на различных платформах. Он может выполняться в операционных средах Unix, Windows и Macintosh. В данной главе описываются возможности сервера и приводится несколько примеров их использования. Заметьте, что изложенный здесь материал нельзя рассматривать как документацию на продукт. Вы не найдете подробного описания всех характеристик сервера. Основное внимание уделяется его главным, наиболее часто используемым средствам. Интеграция TclHttpd с прикладными программами Основное внимание в данной главе уделяется описанию различных способов расширения возможностей сервера и интеграции его с разрабатываемыми вами прикладными программами. Для программистов, использующих Tcl, продукт TclHttpd особенно интересен, так как он представляет собой Tcl- сценарий, а следовательно, его можно относительно просто включить в приложение. В настоящее время программисты часто реализуют в приложениях Web-интерфейс, т.е. предусматривают средства, позволяющие Web-браузерам, работающим в сети intranet или Internet, обращаться к программам. Web-сервер предоставляет несколько способов организации взаимодействия с приложением. • Поддержка статических Web-страниц. Подобно обычным Web-серверам, TclHttpd может поддерживать статические документы, описывающие ваше приложение. • Использование обработчиков домена. Вы можете отображать URL в разделе Web-узла, поддерживаемом вашим приложением. В этом случае используется наиболее общий интерфейс. Согласно этому интерфейсу программа определяет значение URL и тип документа, который необходимо вернуть при получении запроса. Подобным образом реализован узел http://www.tcl.tk/resource. URL. ссылающиеся на подкаталоги каталога /resource, рассматриваются как индексы базы данных, и сервер возвращает документы, соответствующие этим индексам. • Application Direct URL. Это средство представляет собой обработчик домена, отображающий URL в Tcl-процедуры. Данные формы, включенные в HTTP-запрос GET или POST, автоматически преобразуются в параметры процедуры Application Direct. Процедура формирует документ и использует его в качестве возвращаемого значения. Такой подход представляет собой альтернативу традиционному интерфейсу CGI.
390 Часть II. Расширенные средства Tcl Например, в TclHttpd URL, указывающие на подкаталоги /status, соответствуют сведениям об операциях, выполненных сервером. • Использование обработчиков документов. Вы можете определить Tcl- процедуру, предназначенную для обработки всех файлов конкретного типа. Например, в состав сервера включены обработчики CGI-сценари- ев, HTML-файлов, карт изображений и шаблонов HTML-f Tcl. • Использование шаблонов HTMLi-TcL Шаблоны HTML+Tcl представляют собой Web-страницы, на которых объединены языковые конструкции Tcl и элементы разметки HTML. Сервер заменяет Тс1-выражения с помощью команды subst и возвращает результат преобразования. Сервер может кэшировать результаты в обычных HTML-файлах, что позволяет избежать накладных расходов при последующих обращениях клиентов. Шаблоны часто используются Web-дизайнерами для согласования внешнего вида страниц, принадлежащих одному семейству документов, а также для динамической обработки содержимого HTML- страниц, например для автоматического контроля данных форм. Архитектура TclHttpd Исходный код предоставляет программисту исчерпывающие сведения о возможностях программы. В данном разделе будут описаны файлы, содержащие исходные тексты сервера. В дистрибутивной версии, находящейся на прилагаемом к книге компакт-диске, эти файлы расположены в каталоге lib. На рис. 18.1 показаны главные компоненты сервера. Основой сервера является модуль Httpd (httpd.tcl), который поддерживает протокол HTTP на стороне сервера. Буква "сГ в имени Httpd означает daemon (демон). Демонами в системе Unix называются программы, не взаимодействующие со стандартным входным и выходным потоком. Как правило, такие программы реализуют системные службы. Модуль Httpd обрабатывает запросы, полученные по сети, и передает их модулю Url. Кроме того, данный модуль содержит программы, возвращающие результаты обработки запросов. Модуль Url (url.tcl) обеспечивает разделение Web-узла на домены, которые представляют собой поддеревья иерархии URL, поддерживаемой сервером. Различные домены могут быть реализованы по-разному. Например, домен Document (doc.tcl) отображает URL в файлы и каталоги на жестком диске, в то время как домен Application Direct (direct .tcl) отображает URL в вызовы Tcl-процедур вашего приложения. Домен CGI (cgi.tcl) отображает URL в программы, формирующие Web-страницы.
Глава 18. Web-сервер TclHttpd 391 Рис. 18.1. Штриховой линией обозначено приложение, включающее TclHttpd. Компоненты, реализующие поддержку шаблонов и Application Direct URL, обеспечивают непосредственную связь между HTTP-запросом и приложением. По мере необходимости вы можете самостоятельно реализовать обработчики URL Добавление кода к TclHttpd Дистрибутивный пакет TclHttpd, который будет подробно описан в конце данной главы, создан так, что вы можете без труда добавить к серверу код вашего приложения. Простые программы достаточно поместить в специальный каталог, после чего сервер загрузит их автоматически. В файлах, помещаемых в эти каталоги, можно разместить Tcl-процедуры и зарегистрировать их как обработчики доменов, Direct URL и Document. В листинге 18.1 приведен код программы "Hello, World!". Листинг 18.1. Код, находящийся в файле hello.tcl, обрабатывает обращение к /hello/world Direct.Url /hello Hello proc Hello/world {} { return "<b>Hello, World!</b>" }
392 Часть II. Расширенные средства Tcl Предположим, что вы поместили созданный файл в каталог /tmp/ tclhttpd_test. В этом случае вам надо вызвать сервер так, как показано ниже. tclsh8.3 bin/httpd.tcl -library /tmp/tclhttpd_test -debug 1 Теперь обратиться к вашей программе можно с помощью следующего URL: http://localhost:8015/hello/world Модификация главной программы Основная программа TclHttpd, находящаяся в файле bin/httpd.tcl, может конфликтовать с основной программой существующего приложения. Для тех программ, которые явным образом вызывают Tcl-интерпретатор, вам, возможно, придется модифицировать файл bin/httpd.tcl. Объем кода данного сценария невелик, кроме того, код обильно снабжен комментариями. Основными элементами программы являются вызов процедуры Httpd_Server. создающей гнездо сервера, и команда vwait, активизирующая цикл обработки событий. Остальные фрагменты кода связаны с разбором параметров и инициализацией различных модулей, предназначенных для поддержки сервера. Обработчики доменов При создании приложения вы можете реализовать новые домены и обеспечить необходимую вам интерпретацию URL. Этим обеспечивается большая гибкость при расширении Web-сервера. Реализуя домен, надо создать процедуру обратного вызова, которая вызывается при запросе, адресованном выбранному каталогу или любому каталогу поддерева в иерархии URL. Процедура обратного вызова интерпретирует URL, формирует Web-страницу и возвращает подготовленные данные, используя программы модуля Httpd. В листинге 18.2 приведен пример реализации домена. В ответ на каждый запрос программа возвращает одну и ту же Web-страницу. Регистрация домена осуществляется с помощью команды Url_Pref ixlnstall. При вызове процедуры ей в качестве параметров передаются префикс URL и команда обратного вызова, которая выполняется в случае, если URL запроса имеет указанный префикс. В данном примере для обработки всех URL, начинающихся с префикса /simple, используется процедура SimpleDomain. Процедура SimpleDomain иллюстрирует ряд основных свойств обработчиков доменов. Url_Dispatch добавляет при вызове обработчика параметры sock и suffix. Параметр sock представляет гнездо, используемое для
Глава 18. Web-сервер TclHttpd 393 соединения с клиентом. Параметр suffix —- это часть URL, следующая за префиксом. Например, если сервер получает запрос, в котором указан URL /simple/page, то префиксом является /simple, а суффиксом — /page. Параметр prefix определяется при регистрации процедуры обратного вызова с помощью Url_Pref ixinstall. По необходимости вы можете передать обработчику домена любую информацию. В данном простом примере префикс передавать не обязательно, но если вы создадите процедуру, которая будет обрабатывать несколько доменов, значение префикса будет необходимо ей. Листинг 18.2. Простой обработчик домена Url_Prefixinstall /simple [list SimpleDomain /simple] proc SimpleDomain {prefix sock suffix} { upvar #0 Httpd$sock data # Генерация заголовка страницы set html "<title>A simple page</title>\n" append html "<hl>$prefix$suffix</hl>\n" append html "<hl>Date and Time</hl>\n" append html [clock format [clock seconds]] # Отображение состояния соединения append html "<hl>Connection State</hl>" append html [html::tableFromArray data border=l] # Отображение данных запроса if {[info exist data(query)]} { append html "<hl>Query Data</hl>\n" append html [html::tableFromList [ncgi::nvlist] border=l] } Httpd_ReturnData $sock text/html $html } Состояние соединения и данные запроса Параметр sock представляет собой дескриптор гнезда, используемого для взаимодействия с удаленным клиентом. Данная переменная также использу-
394 Часть II. Расширенные средства Tcl ется для именования переменной состояния, поддерживаемой модулем Httpd. Имя массива состояния — Httpd$sock. Если вам потребуется доступ к этой информации, целесообразно использовать команду upvar, чтобы создать удобный псевдоним для массива (например, data). upvar #0 Httpd$sock data Пакеты html и ncgi Пакет html содержит процедуры, используемые для генерации фрагментов HTML-документа. Процедура html: :tableFromArray применяется для вывода информации о состоянии соединения. Еще одна процедура из того же пакета, html: :tableFromList, выводит данные запроса. Данные запроса извлекаются с помощью процедуры ncgi: :nvlist. TclHttpd инициализирует модуль ncgi, поэтому для доступа к данным запроса в обработчике домена вы можете использовать ncgi: :nvlist, ncgi: :value и другие процедуры. Заметьте, что, в отличие от CGI-сценариев, вам нет необходимости вызывать процедуру ncgi: :parse. Пакет html содержит другие средства, которые будут описаны позже. Эти средства полезны при генерации HTML-форм. Пакеты html и ncgi входят в состав стандартной библиотеки tcllib, которая поставляется с Tcl и TclHttpd. Передача клиенту результатов обработки запроса После подготовки Web-страницы, представляющей собой результат обработки запроса, процедура Httpd_ReturnData возвращает ее клиенту. Эта же процедура отвечает за поддержку протокола HTTP. Помимо Httpd_ ReturnData, вы можете воспользоваться еще тремя подобными процедурами: Httpd_ReturnFile, Httpd_Error и Httpd.Redirect (табл. 18.1). Application Direct Самым простым способом расширения Web-сервера является использование Application Direct. При этом детали обработки данных запроса, декодирования URL и возврата результатов остаются скрытыми от разработчика. Все, что при этом надо, — это реализовать Tcl-процедуры, соответствующие определенным URL. Параметры процедуры автоматически ставятся в соответствие данным запроса. Tcl-процедура подготавливает строку, представляющую результаты обработки запросов. Обычно в ней содержится HTML-код. Таким образом, задача разработчика оказывается не слишком сложной. Имя Tcl-процедуры, реализующей Application Direct, соответствует имени URL. Это является признаком, по которому TclHttpd автоматически находит
Глава 18. Web-сервер TclHttpd 395 Tcl-процедуру, которая должна обрабатывать обращения по определенному URL. Tcl-процедура и URL могут иметь различные префиксы, но суффиксы должны совпадать. Например, если префикс Tcl-процедуры Demo и префиксом URL также является /demo, то процедура Demo/time обрабатывает обращение по URL /demo/time. Процедура Direct_Url устанавливает соответствие между процедурами и URL. Пример реализации процедур показан в листинге 18.3. Листинг 18.3. Реализация процедур для Application Direct Direct_Url /demo Demo proc Demo {} { return "<htmlxhead><title>Demo page</title></head>\n\ <body><hl>Demo page</hl>\n\ <a href=/demo/time>What time is it?</a>\n\ <form action=/demo/echo>\n\ Data: <input type=text name=data>\n\ <br>\n\ <input type=submit name=echo value='Echo Data'>\n\ </form>\n\ </body></html>M } proc Demo/time {{format ,,0/oH:0/oM:°/oSM}} { return [clock format [clock seconds] -format $format] } proc Demo/echo {args} { # Формирование страницы, содержащей данные запроса set html "<headxtitle>Echo</titleX/head>\n" append html n<body>" append html [html::tableFromList $args "border=l"] return $html 2 В листинге 18.3 роль домена Application Direct URL выполняет домен /demo. Его обработку осуществляет процедура Demo. В данном фрагменте кода определены три URL: /demo /demo/time /demo/echo Web-страница, формируемая процедурой Demo, содержит гипертекстовую ссылку на страницу, соответствующую /demo/time, и простую форму, кото-
396 Часть II. Расширенные средства Tcl рая обрабатывается процедурой, соответствующей /demo/echo. Данная страница статическая; в теле процедуры содержится лишь команда return. Каждая строка заканчивается символами \п\ Это лишь способ форматирования, позволяющий создать отступ в исходном тексте процедуры, не прибегая к отступам в результирующей строке. Обратная косая черта и следующий за ней перевод строки заменяются одним пробелом. Поэтому строка, передаваемая браузеру, и строка в исходном тексте программы выглядят по-разному. Создавая текст процедуры, можно было бы не использовать отступ, однако в этом случае внешний вид исходного текста несколько отличался бы от общепринятого. Процедура Demo/time лишь возвращает результаты выполнения операции clock format. Как видите, процедура даже не добавляет к возвращаемым данным дескрипторы <html>, <head> и <body>, поскольку современные браузеры позволяют обойтись без них. Возвращаемые значения, в которых отсутствуют дескрипторы, могут использоваться в программах, которые посредством протокола HTTP запрашивают информацию для внутреннего применения. Обработка данных, содержащихся в запросе Параметры обработчиков Application Direct URL автоматически принимают значения, указанные в запросе. Подобно любой Td-процедуре, обработчик Application Direct URL может содержать именованные параметры (с ними могут быть связаны значения по умолчанию) и параметр args. Исходной информацией для присвоения значений является соответствие имен параметров и имен, содержащихся в запросе. При этом возможны три случая. • Имя параметра процедуры соответствует имени, указанному в запросе. В этом случае параметру присваивается значение, содержащееся в запросе. • Имя параметра процедуры не встречается в запросе. Значением параметра становится пустая строка или значение по умолчанию (если оно предусмотрено в определении процедуры). Например, в определении Demo/time указан необязательный параметр format. Если имя format имеется в запросе, соответствующее значение присваивается параметру вместо значения по умолчанию. • В запросе имеется значение, которое не соответствует ни одному из параметров. Если в процедуре последним параметром указан args, то имя и соответствующее ему значение из запроса добавляются к значению
Глава 18. Web-сервер TclHttpd 397 args. В противном случае значение из запроса игнорируется. Например, в процедуре Demo/echo параметру args присваивается набор пар имя-значение, переданный в запросе. Как видите, пропущенные или лишние параметры не приводят к возникновению ошибки. Если вы хотите ввести строгий контроль передаваемых данных, вам надо указать в процедуре параметр args и выполнять разбор данных запроса самостоятельно. Рассмотрим еще один пример, иллюстрирующий процесс связывания данных формы с параметрами процедуры. Предположим, что процедура Application Direct определена следующим образом: proс Demo/param { a b {с cdef} args} { тело_процедуры } Вы можете создать HTML-форму, содержащую элементы с именами a, b и с и указать /demo/param в качестве значения атрибута ACTION дескриптора <F0RM>. Вы также можете задать URL в поле адреса браузера и указать после него данные, предназначенные для передачи на сервер. /demo/par am?a=5&b=7&c=red&d=°/07ewelch&e=two+words Символ ? отделяет данные формы от URL, а пары имя-значение разделяются символами &. В данном случае при вызове процедуры параметру а присваивается значение 5, параметру b — 7, а параметру с — значение red. Кроме того, параметр args получает в качестве значения следующий список: d ~welch e {two words} В приведенном выше примере °/07е и -I это специальные коды, используемые для представления символов, отличных от латинских букв и цифр. Знак -I- преобразуется в пробел, а последовательность °/0хх (где хх — двузначное шестнадцатеричное число) заменяется символом с кодом хх. Например, Уо7е заменяется символом ~. Как правило, такое кодирование автоматически осуществляет Web-браузер при обработке данных формы и передаче их на сервер. Однако, если вы непосредственно вводите данные в поле адреса, вам надо позаботиться о правильной записи специальных символов. Для кодирования URL, включаемых на Web-страницу, удобно использовать процедуру Url_Encode. При связывании данных формы с параметрами процедуры Application Direct Web-сервер автоматически выполняет декодирование. Если данные в запросе не соответствуют параметру, используется значение параметра по умолчанию, указанное при определении процедуры, либо параметру присваивается пустая строка. Рассмотрим следующий пример: /demo/param?b=5 В данном случае значением а становится пустая строка, b получает значение 5, с —■ cdef, а значение args представляет собой пустой список.
398 Часть II. Расширенные средства Tcl Работа с MIME-типами Для процедур Application Direct по умолчанию принимается МШЕ-тип text /html. По мере необходимости вы можете указать другой тип данных, используя для этого глобальную переменную, имя которой совпадает с именем процедуры. (Конечно, такой прием нельзя назвать элегантным, но в сервере TclHttpd используется именно он.) В листинге 18.4 показан фрагмент файла faces.tcl, реализующий интерфейс с базой данных пиктограмм. В базе данных содержатся изображения, применяемые для обозначения почтовых сообщений. Поиск подходящего изображения осуществляет процедура Faces_ByEmail, код которой в листинге отсутствует. Процедура Application Direct имеет имя Faces/byemail, а переменная Faces/byemail используется для установки поля Content-Type, создаваемого на базе расширения имени файла. Для отображения расширений в типы данных применяется процедура Mtype (mtype. tcl). MIME — это стандарт обозначения данных, содержащихся в сообщениях электронной почты и используемый также в протоколе HTTP. Листинг 18.4. Указание MIME-типов в процедуре Application Direct Direct_Url /faces Faces proc Faces/byemail {email} { global Faces/byemail set filename [Faces_ByEmail $email] set Faces/byemail [Mtype $filename] set in [open $filename] fconfigure $in -translation binary set X [read $in] close $in return $X } Типы документов Домен Document (doc.tcl) отображает URL в файлы и каталоги. Это обеспечивает дополнительные способы расширения возможностей сервера путем регистрации обработчиков для различных типов документов. В листинге 18.5 показан фрагмент кода, демонстрирующий обработчик для гипотетического тина application/my junk. Этот обработчик предназначен для поддержки файлов с суффиксом .junk. Процедура Mtype_Add используется для регистрации отображения суффиксов файлов в типы документов.
Глава 18. Web-сервер TclHttpd 399 Листинг 18.5. Пример обработчика типа документа # Регистрация отображения суффикса в М1МЕ-тип Mtype_Add application/myjunk .junk # Определение процедуры, выполняющей роль обработчика документа # path - имя файла на диске # suffix - часть URL после префикса домена # sock - дескриптор соединения с клиентом proc Doc_application/myjunk {path suffix sock} { upvar #0 Httpd$sock data # data(url) использовать более удобно. # Для формирования страницы используется содержимое # файла $path. set contents [somefunc $path] # Определение типа данных set type text/html # Возврат страницы Httpd_ReturnData $sock $type $data 2 Сервер осуществляет поиск обработчика документа в два этапа. В первую очередь по суффиксу определяется тип файла. В файле mime.types содержится информация о соответствии суффиксов MIME-типам. Для управления отображением используется модуль Mtype, содержащийся в файле mtype.tcl. Далее сервер ищет Tcl-процедуру с требуемым именем. \)ос_М1МЕ-тип Если процедуру удается найти, она вызывается для обработки запроса. При передаче ответа на запрос процедура использует средства, предоставляемые модулем Httpd. Если процедура Т)ос_М1МЕ_тип отсутствует, обработчик документа по умолчанию использует Httpd.ReturnFile и устанавливает поле Content-Type, основываясь на расширении файла. Основу обработчика по умолчанию составляет выражение, приведенное ниже. Httpd.ReturnFile $sock [Mtype $path] $path В качестве еще одного примера можно привести шаблоны HTAaL+Tcl. В этом случае суффикс .tml отображается в тип application/x-tcl-template
400 Часть II. Расширенные средства Tcl Обработчик Doc_application/x-tcl-template находится в файле doc.tcl. Дистрибутивный пакет TclHttpd также обеспечивает поддержку файлов с расширением .snmp, которые реализуют на базе шаблонов Web-интерфейс к Tcl-расширению Scotty SNMP. Шаблоны HTML + Tcl Система поддержки шаблонов работает с HTML-документами, содержащими Tcl-команды и ссылки на Tcl-переменные. Сервер замещает их, используя команду subst, и возвращает результаты обработки. В состав сервера входит система поддержки шаблонов, но применять команду subst настолько просто, что вы без труда можете создать собственную систему. В базовый набор средств поддержки шаблонов TclHttpd входят следующие компоненты. • Каждой Web-странице page.html может соответствовать файл шаблона page.tml. Поддержка этого файла задается в конфигурационном файле сервера с помощью команды Doc_CheckTemplates. Обычно сервер возвращает файл page.html; исключением являются те случаи, когда файл page.tml оказывается модифицированным после создания page.html. В подобной ситуации сервер обрабатывает шаблон с помощью команды subst, возвращает результаты клиенту и, кроме того, помещает их в файл page.html. • Динамические шаблоны (предназначенные, например, для поддержки форм) должны обрабатываться при каждом запросе. Если вы поместите на Web-страницу команду Doc_Dynamic, кэширование результатов в файле page.html будет запрещено. При получении запроса, в котором указан файл page.html, сервер обрабатывает шаблон page.tml. Обработка шаблона произойдет и в том случае, если клиент непосредственно обратится к файлу page.tml. • Глобальная Tcl-переменная page, содержащая контекст обрабатываемой страницы, создается в процессе работы сервера. Назначение элементов массива page описано в табл. 18.6. • Сервер инициализирует глобальную Tcl-переменную env, которая содержит информацию о запросе, сформатированную с учетом требования стандарта CGI. Элементы массива env заполняются с помощью процедуры Cgi_SetEnv, находящейся в файле cgi.tcl. Назначение элементов массива приведено в табл. 18.7. • Сервер инициализирует модуль ncgi, что позволяет разработчику использовать содержащиеся в нем процедуры для доступа к данным запроса.
Глава 18. Web-сервер TclHttpd 401 • Сервер обрабатывает файлы . tml, содержащие исходный код Tcl. В этих файлах находятся определения процедур и переменных, совместно используемых при формировании различных Web-страниц. Имя файла — .tml. т.е. оно начинается с точки. Имена, начинающиеся с точки, принадлежат скрытым файлам системы Unix. Эти файлы вносят свои коррективы в алгоритм обработки шаблонов page.tml, соответствующих Web-страницам page.html. Перед обработкой файла page.tml сервер загружает файлы .tml, находящиеся во всех каталогах, составляющих путь к шаблону. Сервер оценивает дату и время модификации этих файлов, и, если хотя бы один из файлов .tml модифицировался позднее, чем page.html, осуществляется повторная обработка шаблона. Таким образом, модифицируя файл .tml в корневом каталоге иерархии URL, можно сделать недействительными все файлы page.html. Размещение Tcl-кода Существуют три способа размещения кода приложения: непосредственно в файлах шаблонов, в файлах .tml и в библиотечном каталоге. Каждый из них имеет свои преимущества и недостатки. • В библиотечных каталогах располагается основная часть кода. Этот каталог задается с помощью опции -library, и сервер после запуска автоматически загружает файлы библиотек. В библиотеках удобно располагать процедуры. Будучи определенной один раз, процедура многократно используется различными сценариями. Библиотеки можно эффективно использовать в том случае, если исходный текст на языке Tcl необходимо скомпилировать в байтовый код. Однако такой подход; не свободен от недостатков. Если вы модифицируете процедуру, находящуюся в библиотечном файле, вам надо специально загрузить библиотеку, иначе внесенные вами изменения не окажут влияния на работу программ. Для этого приходится перезапускать сервер или использовать URL /debug/source для загрузки файлов в процессе работы сервера. • Файлы .tml наилучшим образом подходят для определения переменных, предназначенных для совместного использования различными документами в каталоге, либо для хранения процедур в процессе разработки сценариев. Преимущество такого подхода состоит в том, что изменения в составе файлов . tml становятся доступными серверу без затраты усилий со стороны разработчика. Сервер автоматически проверяет эти файлы и при обнаружении изменений повторно обрабатывает шаблоны. Однако использование файлов .tml ведет к "распылению" кода в каталогах, принадлежащих дереву URL. В результате сопровождение процедур затрудняется.
402 Часть II. Расширенные средства Tcl • Многие разработчики склоняются к тому, что в файлах шаблонов page . tml необходимо размещать как можно меньший объем кода. В противном случае становится невозможным разделение процедур и переменных. В файлы шаблонов следует включать только вызовы процедур, а сами процедуры надо хранить в библиотеках или файлах .tmp. Если вам необходимо включить в документ управляющие структуры, например if или f oreach, то для этой цели предпочтительнее использовать команды, реализованные в пакете html (они будут описаны далее в этой главе). Шаблоны и структура Web-узла В приведенных ниже листингах демонстрируется использование простой еисгемы поддержки шаблонов для обеспечения еДиного сталя документов в составе Web-узла. Основными элементами системы являются структура данных, определяющая общие характеристики документов, принадлежащих узлу, и ряд процедур, с помощью которых генерируются навигационные элементы HTML-страниц. Создав систему поддержки шаблонов, вы можете без труда включать в состав узла новые документы, при этом будет автоматически достигаться их согласованность с имеющимися Web-страницами. При включении новых Web-страниц система поддержки шаблонов автоматически переформатирует Web-узел. В листинге 18.6 показано описание простой одноуровневой структуры узла, которое хранится в файле .tml корневого каталога иерархии URL. В данном описании приведены заголовок и URL каждого документа, принадлежащего узлу. Листинг 18.6. Одноуровневая структура Web-узла set site(pages) { Home /index.html "Ordering Computers" /ordering.html "New Machine Setup" /setup.html "Adding a New User" /newuser.html "Network Addresses" /network.html _} Очевидно, что структура реальных Web-узлов гораздо сложнее и содержат они гораздо больше документов. Например, па узле может быть создано несколько разделов, каждый из которых содержит определенный набор Web- страниц. Трехуровневая структура узла также не редкость. В листинге 18.7 показана еще одна простая, но на этот раз двухуровневая структура данных. Переменная site (sections) содержит имена и URL основных разделов. Каждому разделу соответствует элемент массива site, в котором описаны
Глава 18. Web-сервер TclHttpd 403 документы, принадлежащие этому разделу. Из всех разделов в листинге 18.7 показан только раздел About. Листинг 18.7. Двухуровневая структура Web-узла set site(sections) { About /about Products /products Support /support } set site(About) { Company company.html Contacts contacts.html Directions directions.html 2 Для реальных узлов информация, содержащаяся в структуре, не ограничивается заголовками и URL. Например, если в основном разделе используется графическое изображение, вы можете записать его размеры. В структуру данных следует включить всю необходимую вам информацию, которая с помощью специальных процедур будет затем преобразована в HTML-код. Изменив процедуру генерации, вы автоматически измените внешний вид всех документов. То же самое можно сделать и вручную, но для этого потребуется в десятки раз больше времени и усилий. В листинге 18.8 показан простой шаблон для одноуровневой структуры узла, представленной в листинге 18.6. В каждый документ включаются две команды: SitePage и SiteFooter, которые генерируют код для создания навигационных элементов на Web-странице. Между этими командами находится HTML-код, с помощью которого формируется содержимое документа. Листинг 18.8. Файл шаблона HTML + Tcl [SitePage "New Machine Setup"] This page describes the steps to take when setting up a new computer in our environment. See [SiTclink "Ordering Computers"] for instructions on ordering machines. <ol> <li>Unpack and setup the machine. <li>Use the Network control panel to set the IP address and hostname. <!-- Several steps omitted --> <li>Reboot for the last time.
404 Часть II. Расширенные средства Tcl </ol> [SiteFooter] Процедуре SitePage передается в качестве параметра заголовок страницы. В результате выполнения данная процедура реализует стандартную навигационную структуру. Реализация SitePage показана в листинге 18.9. Листинг 18.9. Процедура шаблона SitePage (версия 1) proc SitePage {title} { global site set html "<html><headxtitle>$title</title></head>\nu append html "<body bgcolor=white text=black>\n" append html "<hl>$title</hl>\n" set sep "" foreach {label url} $site(pages) { append html $sep if {[string compare $label $title] == 0} { append html "$label" } else { append html "<a href= >$irrlJ>$label</a>" } set sep " I " } return $html 2 Цикл foreach, формирующий меню гипертекстовых ссылок, может применяться для различных целей. В листинге 18.10 представлены несколько видоизмененный цикл foreach, выделенный в отдельную процедуру, а также процедуры SitePage и SiteFooter. Данная версия шаблона создает в левой части страницы столбец навигационных элементов, а в правой части — столбец, определяющий содержимое документа. В примере, представленном в листинге 18.10, для удобства сопровождения в массив site помещаются дополнительные атрибуты, определяющие внешний вид документа (например, цвет фона). Листинг 18.10. Процедуры SiteMenu и SiteFooter array set site { bg white fg black mainlogo /images/mainLogo.gif
Глава 18. Web-сервер TclHttpd 405 } proc SitePage {title} { global site set html "<html><head><title>$title</title></head>\n\ <body bgcolor=$site(bg) text=$site(fg)>\n\ <!-- Two Column Layout -->\n\ <table cellpadding=0>\n\ <tr><td>\n\ <!-- Left Column -->\n\ <img src=J$site(mainlogo)?>\n\ <font size=+l>\n\ [SiteMenu <br> $site(pages)]\n\ </font>\n\ </td><td>\n\ <!-- Right Column -->\n\ <hl>$title</hl>\n\ <p>\n" return $html } proc SiteFooter {} { global site set html "<p><hr>\n\ <font size=-l>[SiteMenu I $site(pages)]</font>\n\ <!-- Close Right Column -->\n\ </tdx/trX/table>\n" return $html } proc SiteMenu {sep list} { global page set s "" set html "" foreach {label url} $list { if {[string compare $page(url) $url] == 0} { append html $s$label } else { append html "$s<a href='$url'>$label</a>" } set s $sep } return $html }
406 Часть II. Расширенные средства Tcl Существуют и другие применения для "макросов", упрощающих создание часто повторяющихся фрагментов HTML-кода. В качестве примера можно привести процедуру SiTclink (см. листинг 18.8). Вместо того чтобы вручную создавать дескриптор <А>, с помощью которого формируется гипертекстовая ссылка на /ordering.html, на странице вызывается процедура SiTclink, обеспечивающая форматирование соответствующего элемента и согласование его с поясняющей надписью. Использование процедуры также означает, что при изменении URL целевого документа все Web-страницы узла будут автоматически модифицированы. Код процедуры SiTclink показан в листинге 18.11. Листинг 18.11. Процедура SiTclink proc SiTclink {label} { global site array set map $site(pages) if {[info exist map($label)]} { return "<a href='$map($label)'>$label</a>" } else { return $label } 2 Использование переменных для хранения информации о Web-узле Одним из важных преимуществ шаблонов является возможность включать в них переменные, ссылающиеся на документы. Вместо того чтобы "жестко55 программировать телефонные номера потребителей, версии продуктов и даже имена программ, вы можете включить в состав документа переменные, представляющие необходимую информацию. Например, рассмотренные ранее процедуры SiTclink и SitePage получали в качестве параметра заголовок Web-страницы. По мере необходимости вы можете поместить все заголовки в массив и включать в шаблоны ссылки па его элементы. Массив выполняет роль единого хранилища для текстовых данных, что существенно упрощает внесение изменений в структуру узла. Определение массива может выг.1я, [слъ следующим образом: array set title { Коте Ноте Order "Ordering Computers" Setup "New Machine Setup"
Глава 18. Web-сервер TclHttpd 407 AddUser "Adding a New User" Network "Network Addresses" } В этом случае процедуры SitePage и SiTclink должны вызываться так, как показано ниже. [SitePage $title(Order)] Для определения переменных удобно применять файлы .tml, которые совместно используются всеми Web-страницами, размещенными в каталоге и его подкаталогах. Более того, установки, сделанные в файле .tml, который принадлежит определенному каталогу, заменяют соответствующие установки в файле .tml вышестоящих каталогов. Изменив переменную в файле .tml, вы тем самым измените все использующие ее Web-страницы. Работая с шаблонами, необходимо помнить, что символ $ используется для обозначения денежной единицы. Если, например, вы укажете в файле page.tml значение $10, то получите сообщение об ошибке, так как переменная с именем 10 наверняка не определена. Таким образом, применяя шаблоны, желательно отказаться от явного указания денежных сумм. Значительно лучше вместо $10 использовать выражение [price T-shirt] или $price(T-shirt). Если же вам необходимо явно указать цену товара, вам следует предварять символ $ обратной косой чертой. В этом случае вместо $10 надо записать \$10. Обработчики данных форм HTML-формы и программы обработки данных форм используются совместно. Форма предоставляется в распоряжение пользователя, работающего на клиентской машине. Программа обработки данных формы работает на сервере. Она вступает в действие после того, как пользователь введет с помощью формы необходимую информацию и щелкнет на кнопке submit. В форму включаются различные интерфейсные элементы, например переключатели и флажки опций, списки, поля редактирования и т.д. Этим элементам присваиваются имена. Кроме того, каждый из интерфейсных элементов генерирует значение, зависящее от действий пользователя. Программа обработки данных формы анализирует имена и значения и формирует следующий документ. Для обработки данных формы часто используются внешние программы. Связать внешние программы с Web-сервером позволяет спецификация CGI. CGI определяет стандартный способ кодирования передаваемых данных. Программа обработки либо читает данные формы из стандартного ввода, либо получает их в составе переменной окружения. Получив данные, про-
408 Часть II. Расширенные средства Tcl грамма декодирует, обрабатывает их и записывает в стандартный выходной поток HTML-код сгенерированной Web-страницы. Пример CGI-сценария. написанного на языке Tcl. см. в главе 3. Сервер TclHttpd предоставляет альтернативные средства обработки дан- пых формы. Эти средства обеспечивают более эффективное функционирование обработчика, так как обработчик встраивается непосредственно в сервер. Это исключает накладные расходы, связанные с вызовом внешней программы. Кроме того, при использовании такого подхода Web-сервер может хранить в Tcl-переменных информацию о состоянии запроса клиента. В результате задача поддержки сеанса взаимодействия решается относительно просто. Для сравнения, при использовании CGI для поддержки сеанса приходится включать в запрос дополнительные элементы и хранить информацию о сеансе г» базе данных либо в файле. Обработчики Application Direct Сервер TclHttpd поставляется со встроенными обработчиками; использовать их можно, не прилагая значительных усилий. Средства, для обращения к которым указывается URL /mail/forminfо, осуществляют предварительную обработку данных запроса и отправляют результаты работы по почте. Для формирования заголовков почтового сообщения в форму включается ряд интерфейсных элементов. Остальные данные помещаются в сообщение в формате, допускающем чтение средствами Tcl. Форма, для которой используется указанный обработчик, представлена в листинге 18.12. Другие встроенные обработчики будут обсуждаться далее в этой главе. Листинг 18.12. Форма, используемая /mail/f orminf о для создания сообщения <form action=/mail/forminfо method=post> <input type=hidden name=sendto value=mailreader@my.com> <input type=hidden name=subject value="Name and Address"> <table> <tr><td>Name</td><td><input name=name></td></tr> <tr><td>Address</tdxtd><input name=addrl></tdx/tr> <tr><td> </tdxtd><input name=addr2></tdx/tr> <tr><td>City</td><tdxinput name=city></tdx/tr> <tr><td>State</tdxtdxinput name=state></td></tr> <tr><td>Zip/Postal</td><tdxinput name=zip></tdx/tr> <tr><td>Country</tdxtd><input name=countryX/tdx/tr> </table> </form>
Глава 18. Web-сервер TclHttpd 409 Почтовое сообщение, передаваемое /mail/forminfо, представлено в листинге 18.13. Листинг 18.13. Почтовое сообщение, передаваемое /mail/f orminf о То: mailreader@my.com Subject: Name and Address data { name {Joe Visitor} addrl {Acme Company} addr2 {100 Main Street} city {Mountain View} state California zip 12345 country USA 2 Почтовое сообщение формируется с учетом последующей обработки Тс.1- программой. Для фильтрации сообщений может использоваться любой почтовый процессор, например procmail. Фильтрацию можно осуществлять на основе содержимого полей, например поля Subject или То. По необходимости несложно написать сценарий, который удалял бы заголовки и обрабатывал тело сообщения с помощью eval. Несмотря на то что данные содержатся в почтовом сообщении, форматируя их в виде списка ТсЛ, вы можете без труда обработать их. Базовая структура, предназначенная для обработки почтового сообщения, показана в листинге 18.14. Листинг 18.14. Обработка почтового сообщения, передаваемого /mail/ forminfо # Предполагается, что почтовое сообщение находится # в стандартном входном потоке set X [read stdin] # Удаление заголовка. Признаком конца заголовка # является пустая строка. if {[regsub {.*?\n\ndata} $X {data} X] != 1} { error "Malformed mail message" } proc data {fields} { foreach {name value} $fields {
410 Часть II. Расширенные средства Tcl # Выполнение необходимых действий. } } # Обработка сообщения, eval $X Непосредственно использовать команду eval в обработчике почтового сообщения небезопасно. Дело в том, что обработчик /mail/f orminf о — не единственный источник данных для вашей программы. Если компьютер подвергнется атаке, злоумышленник может передать набор Tcl-команд, который настроит ваш Web-узел в соответствии с его потребностями. Для обработки электронной почты можно использовать защищенный интерпретатор, который будет рассматриваться в главе 19. По сравнению с листингом 18.14, в листинг 18.15 добавлено несколько команд, предназначенных для вызова защищенного интерпретатора. Команда data выполняется с использованием механизма псевдонимов. Остальные команды сообщения обрабатываются защищенным интерпретатором; любое недопустимое действие приведет к возникновению ошибки и не принесет вреда системе. Листинг 18.15. Обработка почтового сообщения, передаваемого /mail/ f orminf о, с помощью защищенного интерпретатора (Safe-Tcl) # Предполагается, что почтовое сообщение находится # в стандартном входном потоке set X [read stdin] # Удаление заголовка. Признаком конца заголовка # является пустая строка. if {[regsub {.*?\n\ndata} $X {data} X] != 1} { error "Malformed mail message" } proc data {fields} { foreach {name value} $fields { # Do something } } # Создание защищенного интерпретатора, set i [interp create -safe] # Связывание команды data в запрещенном интерпретаторе # с процедурой data в обычном интерпретаторе.
Глава 18. Web-сервер TclHttpd 411 interp alias $i data {} data # Обработка сообщения в защищенном интерпретаторе, interp eval $i $X Шаблоны для обработчиков данных форм Использование Application Direct для обработки форм имеет существенный недостаток: чтобы изменить результирующий документ, вам надо модифицировать Tcl-код. Альтернативный подход состоит в применении шаблонов для результирующего документа. В документ включаются команды, обрабатывающие данные формы. Например, процедура Mail_FormInf о предназначена для передачи данных формы по почте. При ее вызове параметры не указываются. Вместо этого процедура ищет в запросе значения sendto и subject и, если они обнаружены, посылает остальную информацию в виде электронного письма. Она возвращает HTML-данные, информирующие о том. что письмо было передано. Если вы используете шаблоны для обработки данных формы, вам надо отключить кэширование результатов, так как сервер должен обрабатывать шаблон каждый раз, когда клиент передает информацию. Для того чтобы запретить кэширование, надо включить в состав страницы, выполняющей функции обработчика, команду Doc_Dynamic либо присвоить переменной page (dynamic) значение 1. Существует и альтернативное решение: вы можете отправить по почте вместо страницы file.html файл file.tml. Формы самопроверки В данном разделе мы рассмотрим формы самопроверки, которые передают данные тому же документу, в котором они размещены. Web-страница содержит Tcl-команды, предназначенные для проверки формы, находящейся на ней же. Если данные введены корректно, запрос перенаправляется следующей странице. Такой прием используется в тех случаях, когда необходимо реализовать сложную обработку информации на стороне сервера. Очевидно, что на каждом шаге обработки надо сохранять данные формы. Вы можете помещать данные в Tcl-переменные. записывать в базу либо использовать их для управления вашим приложением. В состав сервера TclHttpcl входит модуль Session, который реализует описанный способ управления информацией. Подробную информацию о работе модуля можно получить, анализируя код в файле session.Tcl, который содержится в дистрибутивном пакете. В листинге 18.16 показана процедура Form_Simple, генерирующая простую форму самопроверки. Параметрами процедуры являются уникальный
412 Часть II. Расширенные средства Tcl идентификатор формы, описание полей формы и URL следующей страницы. Описание полей представляет собой список, содержащий три элемента для каждого поля: флаг, оиределяюгций, является ли поле обязательным, имя поля и текстовую метку, отображаемую рядом с элементом формы. Листинг 18.16. Процедура, генерирующая форму самопроверки proc Form_Simple {id fields nextpage} { global page if {![html::varEmpty formid]} { # Входящие данные формы должны быть проверены set check l } else { # Страница вызвала впервые set check О } set html "<!-- Self-posting. Next page is $nextpage -->\n" append html "<form action=\"$page(url)\" method=post>\n" append html "<input type=hidden name=formid value=$id>\n" append html "<table border=l>\n" foreach {required key label} $fields { append html "<tr><td>" if {$check && $required && [html::varEmpty $key]} { lappend missing $label append html "<font color=red>*</font>" } append html "</tdxtd>$label</td>\n" append html "<td><input [html::formValue $key]></td>\nM append html "</tr>\n" } append html "</table>\n" if {$check} { if {![info exist missing]} { # Ни одно поле не пропущено, можно переходить к # следующей странице. Решая реальные задачи, # на данном этапе надо сохранить значения полей # и лишь потом перейти к следующей странице. Doc_Redirect $nextpage } else {
Глава 18. Web-сервер TclHttpd 413 set msg "<font color=red>Please fill in " append msg [join $missing ", "] append msg "</font>" set html <p>$msg\n$html } } append html "<input type=submit>\n</form>\n" return $html _} Процедура Form_Simple одновременно выполн51ет две задачи: создает HTML-форму и проверяет состояние полей. Для генерации элементов формы, сохраняющих значения предыдущей страницы, используются некоторые процедуры, принадлежащие модулю html. Если все требуемые поля присутствуют, процедура Form_Simple перенаправляет запрос, вызывая Doc_Redirect. В листинге 18.17 показан шаблон страницы, в котором предусмотрен вызов процедуры Form_Simple с указанием требуемых полей. Листинг 18.17. Страница, содержащая форму самопроверки <html><head> <title>Name and Address Form</title> </head> <body bgcolor=white text=black> <hl>Name and Address</hl> Please enter your name and address. [Form_Simple nameaddr { 1 name "Name" 1 addrl "Address" 0 addr2" "Address" 1 city "City" 0 state "State" 1 zip "Zip Code" 0 country "Country" } nameok.html] </body></html> Пакет html В стандартной библиотеке tcllib содержится пакет html, который предназначен для генерации Web-страниц и поддержки форм самопроверки. Пакет html работает совместно с пакетом ncgi (см. главу 3). Процедура Form_Simple
414 Часть II. Расширенные средства Tcl вызывает html: rvarEmpty для того, чтобы проверить, имеются ли требуемые значения формы в запросе. Например, она проверяет, имеются ли данные, соответствующие полю f ormid, и в зависимости от результатов этой проверки принимается решение, следует ли обрабатывать другие поля. Процедура html: :formValue может быть полезна для создания элементов формы с самопроверкой. Она возвращает следующие данные: name="имя" value="значение" В данном случае речь идет о значении элемента формы с определенным именем. Требуемые сведения извлекаются из строки запроса. Если имя не определено, возвращается пустая строка. Полученная в результате форма может передавать данные содержащему ее документу и сохранять значения, введенные на предыдущей странице. Процедура используется следующим образом: <input type=text [html::formValue name]> Процедуры html::checkValue и html::radioValue ориентированы для работы с флажками и переключателями опций. Процедура html: : select формирует список и подсвечивает выбранные значения. Пакет html включает реализации операторов foreach и if, специально ориентированные на работу с шаблонами. Вместо того чтобы выполнять тело команды, '.анные версии foreach и if применяют к нему операцию subst. Это позволяет помещать в тело команды HTML-код, содержащий переменные и команды. Результаты формируются с помощью подстановки. В листинге 18.18 приведен пример использования процедуры html: :foreach для генерации таблицы, содержащей несколько строк. Заметьте, что вам не надо заботиться о правильной обработке символа $ в обозначении цены товара, так как этот символ находится в фигурных скобках, в которые помещен список значений html::foreach. Листинг 18.18. Генерация таблицы с использованием процедуры html: : foreach <TABLE BORDERS1> [html::foreach {product price} { T-Shirt $10.00 YoYo $7.50 Footbag $15.00 } { <TR> <TD>$product</TD> <TD ALIGN=RIGHT><F0NT FACE=courier>$price</FONT></TD> </TR>
Глава 18. Web-сервер TclHttpd 415 } </TABLE> Назначение процедур В данном разделе приводятся сведения о наиболее часто используемых функциях сервера. Следует, однако, заметить, что в таблицах содержится далеко не полная информация. Ознакомившись с кодами программ, вы сможете получить дополнительные данные о возможностях, предоставляемых сервером. Для того чтобы отделить процедуры, использующиеся лишь в пределах конкретного файла (например, HttpdEvent), от процедур, к которым могут обращаться другие модули или основное приложение (например, Httpd_Server), используется простое соглашение об именовании. Знак подчеркивания после префикса в имени модуля указывает на то, что процедура является общедоступной. В данном разделе не приводится детальное описание пакетов ncgi и html, часто используемых при работе с TclHttpd. Документация, поставляемая с Tcllib, дает достаточно полное представление об этих пакетах; кроме того, вы можете найти справочную информацию на сервере http://www.tcl.tk. В табл. 18.1 показаны функции пакета Httpd, используемые для передачи документов клиенту. Таблица 18.1. Процедуры, содержащиеся в пакете Httpd Httpd_Error гнездо код Возвращает клиенту код ошибки. При вызове данной процедуры указывается числовой код. например 404 или 500 Httpd_ReturnData гнездо Возвращает документ, содержащий указан- тал данные ные данные. Тип данных содержится в поле Content-Type Httpd_ReturnFile гнездо Возвращает файл, тип которого указан в поле тип файл Content-Type Httpd_Redirect новый_ШИ Генерирует код 302. Новый URL находится в поле гнездо Location Httpd_SelfUrl URL Расширяет URL так, чтобы он содержал корректные данные http: //сервер : порт, указывающие па текущий сервер В табл. 18.2 содержатся сведения о некоторых процедурах, предоставляемых модулем Url (url.tcl). Процедура Url_DecodeQuery позволяет декодировать данные в запросе и преобразовывать их в список, удобный для
416 Часть II. Расширенные средства Tcl обработки Tcl-программами. Процедура Url.Encode представляет значения в формате, пригодном для включения в URL. Этот формат подробно рассматривался в начале данной главы. Таблица 18.2. Процедуры, входящие в состав модуля Url Url_DecodeQuery запрос Url_Encode значение Url_PrefxInstall префикс обработчик ?-thread логическое„значение? ?-callback команда? ?-readpost логическое_значение? Декодирует строку запроса www-url-encoded и возвращает список, состоящий из имен и значений. Данная процедура не рекомендуется для применения. Те же действия выполняет ncgi : rnvlist, которая вызывается без параметров Возвращает значение, закодированное в соответствии со стандартом www-url-encoded Регистрирует заданный обработчик для обработки всех URL, начинающихся с указанного префикса. При вызове обработчика указываются два дополнительных параметра: гнездо, выполняющее роль дескриптора соединения с клиентом, и суффикс — часть URL, следующая после префикса. Опция -thread 1 указывает на то, что обработчик должен выполняться в рабочем потоке. Посредством опции -callback регистрируется команда, которая вызывается в конце обработки запроса. Опция -readpost О запрещает преждевременную передачу данных Процедуры модуля Doc, предназначенные для настройки сервера, приведены в табл. 18.3. Таблица 18.3. Процедуры модуля Doc, предназначенные для настройки Doc_Root ?кагалог? Doc_AddRoot корень_поддерева каталог Doc_ErrorPage файл Doc_CheckTemplates флаг Устанавливает заданный каталог в качестве корневого каталога иерархии URL. Если каталог не указан, возвращается текущее значение соответствующей установки Отображает каталог в поддерево URL, начинающееся с указанной позиции Определяет указанный файл как шаблон для сообщений об ошибках. Путь к файлу отсчитывается от корневого каталога документов. Обработка осуществляется с помощью DocSubstSystem в файле doc.tcl Если флаг равен 1, файлы .html сравниваются с соответствующими файлами . tml и по необходимости повторно генерируются
Глава 18. Web-сервер TclHttpd 417 Окончание табл. 18.3 Doc_IndexFile шаблон Doc_NotFoundPage файл Doc_PublicHtml имя^каталога Doc_TemplaTclibrary каталог Doc_TemplateInterp интерпр е та тор Doc_Webmaster ?email? Регистрирует шаблон, используемый для поиска индексного файла по умолчанию Определяет указанный файл как шаблон для сообщений о том, что ресурс не найден. Путь к файлу отсчитывается от корневого каталога документов. Обработка осуществляется с помощью DocSubstSystem Определяет имя каталога в составе рабочего каталога каждого пользователя. При указании URL, сформированного по принципу ~имя_пользователя, осуществляется доступ в каталог с данным именем, содержащийся в рабочем каталоге соответствующего пользователя Добавляет каталог в auto.path, чтобы файлы, содержащиеся в нем, были доступны серверу Задает альтернативный интерпретатор, используемый при обработке шаблонов документов (файлов .tml) Задает либо запрашивает адрес электронной почты администратора Web-узла В модуле Doc содержатся также средства генерации результатов обработки запроса (табл. 18.4). Таблица 18.4. Процедуры модуля Doc, предназначенные для генерации ответов Doc_Error гнездо информация_ об_ошибке Doc_NotFound гнездо Doc_Subst гнездо файл ?интерпретатор? На основе шаблона, зарегистрированного с помощью Doc_ErrorPage, генерирует сообщение 500 и отправляет через указанное гнездо. Информация об ошибке представляет собой копию данных, отображаемых ТсЛ при возникновении ошибки На основе шаблона, зарегистрированного с помощью Doc_NotFoundPage, генерирует ответ 404 и отправляет его через указанное гнездо Выполняет subst для файла и возвращает результирующую страницу через гнездо. По мере необходимости можно задать альтернативный Тс1-интерпретатор Помимо прочих средств, модуль Doc содержит процедуры для обработки записей cookie и перенаправления запросов. Эти процедуры (табл. 18.5) нередко используются в шаблонах документов.
418 Часть II. Расширенные средства Tcl Таблица 18.5. Процедуры модуля Doc, поддерживающие обработку шаблонов Doc_Coookie имя Возвращает имя записи cookie, переданной серверу в составе запроса. Если запись cookie отсутствует, возвращается пустая строка Doc__Dynamic Отключает кэширование HTML-данных. Вызывается из шаблона Doc.IsLinkToSelf URL Возвращает значение 1, если URL является ссылкой на текущую страницу Doc_Redirect новый_иЯЬ Генерирует ошибку, в результате чего отключается обработка шаблона и происходит перенаправление на новый URL Doc_SetCookie -name имя Устанавливает запись cookie с указанным именем -value значение -path и значением, которая передается клиенту в составе путь domain домен ответа. Путь и домен ограничивают область види- - expires дата мости сооке. Дата определяет срок действия сооке В табл. 18.6 приведены элементы массива page, которые определяются в процессе обработки шаблона. Таблица 18.6. Элементы массива page query Декодированные данные запроса, которые представлены в виде списка, содержащего имя и значения. Эти данные также доступны с помощью средств ncgi dynamic Если значение данного элемента равно 1, результаты обработки шаблона не кэшируются в соответствующем файле . html filename Путь в файловой системе к запрашиваемому файлу (например, /usr/ local/htdocs/tclhttpd/index.html) template Путь в файловой системе к файлу шаблона (например, /usr/local/ htdocs/tclhttpd/index.tml) url Часть URL, следующая после имени сервера (например, /tclhttpd/ index.tml) root Относительный путь от файла шаблона к корню дерева UR.L И наконец, назначение элементов массива env описано в табл. 18.7. Эти элементы генерируются в процессе обработки запроса по соглашениям CGI, при использовании обработчиков Application Direct и при обработке шаблонов.
Глава 18. Web-сервер TclHttpd 419 Таблица 18.7. Элементы массива env AUTH.TYPE CONTENT.LENGTH CONTENT.TYPE DOCUMENT_ROOT GATEWAY,INTERFACE HTTP.ACCEPT HTTP.AUTHORIZATION HTTP.COOKIE HTTP.FROM HTTP.REFERER HTTP_USER_AGENT PATH.INFO PATH.TRANSLATED QUERY.STRING REMOTE.ADDR REMOTE_USER REQUEST.METHOD REQUEST.URI SCRIPT.NAME SERVER_NAME SERVER.PORT SERVER.PROTOCOL SERVER.SOFTWARE Протокол аутентификации (например, Basic) Размер данных запроса Тип данных запроса Путь к корневому каталогу документов в файловой системе Версия протокола (CGI/1.1) Значение поля Accept в составе запроса Значение поля Authorization в составе запроса Запись cookie в составе запроса Значение поля From в составе запроса Значение поля Ref erer, указывающее на предыдущий документ Строка, содержащая иднетификатор Web-броузера Дополнительная информация о пути Дополнительная информация о пути, добавленная к корневому каталогу документов Данные формы в составе запроса IP-адрес клиента Имя удаленного пользователя, определенное в процессе аутентификации Basic GET, POST или HEAD Полный URL запроса Имя текущего файла относительно корневого каталога документов Имя сервера, например www.beedub.com Порт на стороне сервера, например 80 Протокол (например, http или https) Строка, описывающая серверную программу Стандартные модули Application Direct В состав сервера входит несколько модулей, обеспечивающих работу Application Direct. Они позволяют управлять работой сервера или определять его состояние посредством любого Web-браузера. Эти модули могут служить основой для создания собственных программных решений.
420 Часть II. Расширенные средства Tcl Информация о состоянии сервера Для поддержки URL /status используется файл status.tcl. Модуль состояния позволяет отображать число обращений к серверу, к отдельным документам, а также количество запросов, окончившихся неудачей (например, если документ не был найден). Команда Status_Url включает режим поддержки Application Direct и связывает URL с модулем состояния. В конфигурационном файле содержится следующая команда: Status.Url /status URL, поддерживаемые модулем состояния, приведены в табл. 18.8. Таблица 18.8. Модуль состояния Application Direct /status Главная страница с обобщенной информацией о состоянии, содержащая гистограммы, которые отображают число обращений /status/doc Отображает число обращений для каждого документа. Позволяет выполнять сортировку по именам или по числу обращений и выделять файлы путем указания шаблона /status/domain Отображает счетчик обращений для каждого документа на сервере /status/hello Возвращает сообщение "hello" /status/not found Отображает информацию о неудачных попытках обращений к документам /status/size Отображает размер Tcl-кода и данных, используемых программой TclHttpd /status/text Вариант главной страницы состояния, в которой отсутствуют гистограммы Отладка Поддержка URL /debug осуществляется посредством файла debug.tcl. Модуль отладки позволяет определять значения переменных и предоставляет другую важную информацию о состоянии программы. В конфигурационном файле содержится следующая команда: Debug_Url /debug URL, принадлежащие поддереву /debug, приведены в табл. 18.9. В ряде случаев при указании URL требуются параметры, которые задаются непосредственно в его составе. Например, при обращении к /debug/echo параметры возвращаются в режиме аэхо".
Глава 18. Web-сервер TclHttpd 421 http: I/адрес :nopT/debug/echo?name=value&name2=val2 Заметьте, что конфигурация по умолчанию активизирует URL отладки. Если данные установки вам не подходят, вы можете удалить из файла httpdthread.tcl вызов Debug_Url. Средства поддержки дерева URL, включаемые по умолчанию в дистрибутивный пакет, содержат файл htdocs/hacks.html. В этом файле находится несколько простых форм, использующих URL /debug для получения сведений о переменных и исходных файлах. Может показаться, что наличие такой возможности недопустимо с точки зрения безопасности. Однако каталоги с исходными файлами контролирует администратор узла, и он может принять меры к тому, чтобы перезагрузить исходные файлы извне было невозможно. Библиотечные сценарии содержат только определения процедур. "Глобальный" код, посредством которого можно изменить поведение сервера, в них отсутствует. Следует признать, что возможность настройки сервера в процессе его работы может оказаться полезной во многих случаях. В частности, эти средства позволяют модифицировать приложение, не перезапуская его. Таблица 18.9. Модуль отладки Application Direct /debug/after /debug/dbg /debug/echo /debug/errorInfo /debug/parray /debug/pvalue /debug/raise Предоставляет информацию о событиях, спланированных посредством команды after Обеспечивает соединение с TclPro Debugger. В качестве параметров передаются имя узла и номер порта. Для его использования в каталог библиотеки сценариев надо установить prodebug.tcl из TclPro Возвращает информацию о параметрах запроса. Поддерживает параметр title Отображает значение переменной errorlnfo, номер версии и почтовый адрес администратора Web-узла. Поддерживает параметры title и errorlnfo Отображает глобальный массив. Имя массива задается с помощью параметра aname Универсальная функция, предназначенная для отображения различных значений. Имя переменной задается посредством параметра aname. Это может быть обычная переменная, массив или шаблон, на соответствие с которым проверяются имена других переменных Генерирует ошибку (для тестирования соответствующих обработчиков). Любые параметры рассматриваются как строка сообщения
422 Часть II. Расширенные средства Tcl Окончание табл. 18.9 /debug/source Загружает посредством команды source файл из основного библиотечного каталога сервера или из каталога Doc_TemplaTclibrary. Файл задается с помощью параметра source В листинге 18.19 показана реализация URL /debug/source. Просмотрев код процедуры, несложно заметить, что файлы, доступные для обработки, ограничены главной библиотекой сценариев и библиотеками, связанными с шаблонами документов. Листинг 18.19. Поддержка URL /debug/source proc Debug/source {source} { global Httpd Config errorInfo set source [file tail $source] set dirlist $Httpd(library) ;# Реализация TclHttpd lappend dirlist $Config(lib) ;# Код приложения foreach dir $dirlist { set file [file join $dir $source] if {[file exists $file]} break } set error [catch {uplevel #0 [list source $file]} result] set html "<title>Source $source</title>\n" if {$error} { append html "<Hl>Error in $source</Hl>\n" append html "<pre>$result<p>$errorInfo</pre>" } else { append html "<Hl>Reloaded $source</Hl>\n" append html "<pre>$result</pre>" } return $html } Передача почтовых сообщений Для поддержки URL /mail используется файл mail.tcl. Почтовый модуль реализует различные обработчики, которые позволяют оформлять данные формы в виде почтовых сообщений. В настоящее время этот модуль ориентирован только на работу в системе Unix, поскольку для передачи почты он использует /usr/lib/sendmail. Для включения средств поддержки данного модуля в конфигурационный файл помещается следующая команда:
Глава 18. Web-сервер TclHttpd 423 MailJJrl /mail Для обработки данных форм используются средства Application Direct (табл. 18.10). Они указываются в качестве значения атрибута ACTION дескриптора <F0RM>. Почтовый модуль предоставляет две Tcl-процедуры общего назначения. Одна из процедур, предназначенных для передачи почты, называется Mail Inner и вызывается следующим образом: MailInner адресат тема отправитель тип тело_сообщения Первый и третий параметры представляют собой адреса электронной почты. В качестве четвертого параметра задается MIME-тип (например, text /plain пли text /html). Он помещается в поле заголовка Content-Type. Последний параметр — это тело сообщения без заголовка. Таблица 18.10. Средства Application Direct, позволяющие оформлять данные формы в виде почтовых сообщений /mail/bugreport Передает почтовое сообщение, включающее информацию об ошибке сервера, содержащуюся в error Info. Получает в качестве параметров адрес назначения и переменную error Info. Все дополнительные параметры помещаются в сообщение /mail/f orminf о Передает почтовое сообщение, содержащее данные формы. Требует указывать в качестве параметров целевой адрес, тему сообщения и данные для формирования ссылки. Дополнительные параметры форматируются с помощью Tcl-команды list. Это упрощает их обработку программами чтения почты /mail/f ormdata Другая форма /mail/f orminf о отличается тем, что данные не объединяются в Tcl-список. Требует в качестве параметров только почтовый адрес и тему. Остальные параметры форматируются в теле сообщения Процедура Mail_FormInf о предназначена для использования в шаблонах HTML f Tcl. В ней не предусмотрены параметры, вместо этого она извлекает необходимые данные из запроса. Ома находит ту же информацию, что и Application Direct /mail/f orminf о. Используя Mail_FormInf о в шаблоне, вы получаете дополнительную возможность управлять созданием страницы результатов по сравнению со средствами поддержки /mail/f orminf о, описанными ранее в данной главе.
424 Часть II. Расширенные средства Tcl Дистрибутивный пакет TclHttpd Дистрибутивный пакет TclHttpd находится на прилагаемом к данной книге компакт-диске. Вы можете также скопировать его из Internet, обратившись по одному из следующих адресов: ftp://ftp.tcl.tk/pub/tcl/httpd/ http://www.tcl.tk/software/tclhttpd/ http://www.sourceforge.net/proj ects/tclhttpd Запуск сервера Распаковав tar-архив или zip-файл, вы можете запустить сервер, вызвав сценарий httpd.tcl, находящийся в каталоге bin. В системе Unix для этого надо выполнить следующую команду: tclsh bin/httpd.tcl -port 80 По этой команде Web-сервер запускается и ожидает обращения через стандартный порт (80). В системе Unix запустить сервер может только пользователь root. По умолчанию TclHttpd использует порт 8015. Если при вызове указанной выше команды вы укажете опцию -help, программа сообщит вам опции командной строки, которые могут быть указаны при запуске сервера. Если вместо tclsh вы используете wish, отобразится простой пользовательский интерфейс Тк, посредством которого выводится число обращений к серверу. В системе Windows для запуска сервера вам надо дважды щелкнуть на сценарии httpd.tcl. В результате будет загружена команда wish и отображены элементы пользовательского интерфейса. В системе Windows сервер также использует по умолчанию порт 8015. Если вы хотите, чтобы клиенты могли обращаться через другой порт, вам надо связать с пиктограммой командную строку, указав в ней параметр -port, либо отредактировать конфигурационный файл. Средства настройки сервера будут описаны ниже. После того как вы убедитесь в том, что сервер выполняется, вы можете обращаться к нему с помощью Web-браузера. Если сервер использует порт по умолчанию, то для обращения к нему надо указать следующий URL: http://hostname:8015/ Если сервер и клиент расположены на одном компьютере, то в качестве имени узла надо задать адрес 127.0.0.1. Это адрес интерфейса обратной петли, с помощью которого программа может обратиться к той же машине, на которой она выполняется, минуя сетевые средства. http://127.0.0.1:8015/
Глава 18. Web-сервер TclHttpd 425 Состав дистрибутивного пакета Элементы дистрибутивного пакета TclHttpd организованы в виде иерархии каталогов. Назначение каждого каталога описано ниже. • bin. Находятся сценарии запуска и файлы конфигурации. Сервер запускается с помощью сценария httpd.tcl. Стандартным конфигурационным файлом является tclhttpd.rc. • bin/mini. Содержится несколько упрощенных версий сервера, в том числе базовую версию, содержащую около 300 строк кода. Их можно использовать как отправную точку для реализации специальных серверов. Расширение функциональных возможностей осуществляется путем модификации процедуры HttpdRespond. • bin/test. Находятся тестовые сценарии, в том числе файл torture. tcl. Эти средства позволяют организовать одновременные обращения по нескольким URL. • certs. Содержатся примеры сертификатов, которые можно использовать для тестирования защищенного сервера, работающего по протоколу https. Если у вас есть собственный сертификат, вам надо поместить в данный каталог файл server.pem. • conf ig. Содержатся средства поддержки автоконфигурации, используемые расширениями С. • custom. В данный каталог вы можете поместить созданный вами код. Находящиеся здесь файлы автоматически загружаются при запуске сервера. Исходный дистрибутивный пакет содержит несколько примеров пользовательских сценариев. • doc. Содержится справочная информация о сервере, созданная по соглашениям Unix. • htaccess. Содержатся примеры файлов управления доступом. • htdocs. Приведен пример дерева URL, демонстрирующий возможности Web-сервера. Здесь же находится часть документации. Вам следует обратить внимание на каталог htdocs/libtml. Это стандартный каталог для размещения Tcl-сценариев, используемых с шаблонами Tcl-f HTML. • lib. Хранятся исходные Tcl-файлы. Практически в каждом из находящихся здесь файлов реализован пакет. Команды package require можно встретить в bin/httpd.tcl и в bin/httpdthread.tcl. • src. Находится несколько исходных файлов на языке С для некоторых необязательных пакетов. В дистрибутивных пакетах для ряда платформ эти файлы скомпилированы. Скомпилированные библиотеки можно найти в каталогах src/Solaris и src/Linux.
426 Часть II. Расширенные средства Tcl Настройка сервера Настройка сервера TclHttpd осуществляется путем установки конфигурационных параметров и загрузки пакетов. Конфигурационные параметры устанавливаются на основе содержимого конфигурационного файла и параметров, задаваемых в командной строке. Конфигурационный файл, используемый по умолчанию, называется tclhttpd.rc. Он расположен в том же каталоге, что и сценарий запуска. Указать серверу па то, что для настройки надо использовать другой конфигурационный файл, можно, задав при вызове сервера опцию -conf ig. Характеристики, заданные в конфигурационном файле, можно изменить, указав соответствующие опции командной строки, которые описаны в табл. 18.11. Значения, определенные посредством конфигурационного файла и командной строки, копируются в Tcl-массив Conf ig. Загрузку пакетов можно условно разделить на две части. Главный сценарий bin/httpd.tcl загружает несколько основных пакетов. Загрузку остальных осуществляет сценарий bin/httpdthread. tcl. Такое разделение обязанностей предусмотрено для того, чтобы отделить основные средства, обеспечивающие работу сервера, от функций, специфических для конкретного приложения. Кроме того, в тех версиях сервера, в которых реализована поддержка потока, за загрузку и запуск каждого потока отвечает сценарий bin/httpdthread.tcl. При желании вы можете указать другой сценарий загрузки пакетов. Для этого в командной строке надо задать опцию -main. Например, чтобы сервер обрабатывал дерево документов, начинающееся с корневого узла /usr/local/htdocs, а в качестве адреса администратора Web-узла использовал адрес электронной почты пользователя welch, необходимо запускать сервер так, как показано ниже. tclsh httpd.tcl -docRoot /usr/local/htdocs -webmaster welch Если вы используете версию Tclkit, которая будет описана в главе 22. то команда должна выглядеть следующим образом: tclkit tclhttpd.kit -docRoot /usr/local/htdocs -webmaster welch Вы также можете выполнить необходимые установки в конфигурационном файле и задать его при запуске сервера. tclsh httpd.tcl -config mytclhttpd.rc Опции командной строки В командной строке, используемой для запуска Web-сервера, предусмотрен ряд опций (табл. 18.11). Сценарий httpd.tcl копирует значения опций в массив Config.
Глава 18. Web-сервер TclHttpd 427 Таблица 18.11. Основные параметры TclHttpd Параметр Опция командной Значение Conf ig строки Номер порта. 8015 -port номер Config(port) Имя сервера. Значение по умол- -name имя Conf ig(name) чанию — [info hostname] IP-адрес. Значение по умолча- -ipaddr адрес Config(ipaddr) нию — 0 Каталог, являющийся корнем -docRoot каталог Conf ig(docRoot) дерева URL. По умолчанию принимается каталог htdocs Идентификатор пользователя -uid идентификатор_ Config(uid) для процесса TclHttpd. Значе- пользователя ние по умолчанию — 50 (имеет смысл только в системе Unix) Идентификатор группы для -gid Config(gid) процесса TclHttpd. Значение по идентификатор_группы умолчанию — 50 (имеет смысл только в системе Unix) Почтовый адрес администрато- -webmaster Conf ig(webmaster) pa Web-узла. Значение по умол- почтовый_адрес чанию — webmaster Конфигурационный файл. По -config имя_файла Config(file) умолчанию принимается файл tclhttpd.rc Каталог, содержащий код при- -library каталог Conf ig(library) ложения. Сервер загружает все файлы из этого каталога Имя сервера и номер порта Пользователь, работающий с Web-браузером, указывает имя и номер порта при обращении к серверу. URL, используемый для доступа к серверу, начинается с таких компонентов: http://имя:порт/ Если сервер использует порт 80, номер порта можно не указывать. Команда запуска сервера находится в файле httpd.tcl и выглядит следующим образом: Httpd.Server $Config(name) $Config(port) $Config(ipaddr)
428 Часть II. Расширенные средства Tcl Задавать IP-адрес надо только в том случае, если на компьютере находится несколько сетевых интерфейсов (либо если с одним интерфейсом связано несколько IP-адресов) и вы хотите, чтобы сервер воспринимал обращения только по одному адресу. По умолчанию сервер воспринимает попытки установить соединение по любому из адресов. Идентификатор пользователя и группы Идентификаторы пользователя и группы используются в системе Unix. Для работы с ними предназначены системные вызовы setuid и setgid. Вы можете запустить сервер от имени пользователя root (это необходимо в случае, если сервер должен воспринимать обращения через порт 80), а затем перейти в режим с меньшими привилегиями. Если вы используете шаблоны Tcl -f HTML, которые кэшируют результаты в HTML-файлах, вам надо выбрать учетную запись, обладающую привилегиями, достаточными для записи этих файлов. Если кэширование не используется, уровень привилегий может быть самым низким. Доступ к функции setuid предоставляет команда id, реализованная в TclX (Extended Tcl). Вы также можете использовать для этой цели расширение setuid, поставляемое с TclHttpd в каталоге src. Если вы не имеете возможности воспользоваться ни одним из этих средств, то, для того чтобы изменить идентификатор пользователя, придется применить значительно большие усилия. Вам придется прочитать файл README и найти в нем инструкции по компиляции и установке упомянутых выше расширений. Адрес администратора Web-узла Адрес администратора Web-узла используется для автоматической передачи сообщений об ошибках, которые возникают в процессе работы сервера. Этот адрес задается в конфигурационном файле с помощью команды Doc_Webmaster $Config(webmaster) Если вы вызовете Doc_Webmaster без параметров, эта команда вернет адрес, заданный ранее. Данная возможность может пригодиться при генерации Web-страниц, содержащих гипертекстовые ссылки mailto: на адрес администратора. Корневой каталог документов Корневой каталог документов — это главный каталог поддерева, содержащего статические файлы, CGI-сценарии и другие данные Web-узла. По умолчанию в качестве корневого каталога документов используется htdocs,
Глава 18. Web-сервер TclHttpd 429 который находится в том же каталоге, что и каталог, содержащий сценарий httpd.tcl. Обеспечить работу с этим каталогом несложно. file join [file dirname [info script]] ../htdocs Команда info script возвращает полное имя сценария http.tcl. Команда file dirname выделяет из нее имя каталога, а команда file join находит необходимый каталог. Путь . . /htdocs можно указывать в кохманде file join на любой платформе. Аналогичным образом можно найти расположение конфигурационного файла. file join [file dirname [info script]] tclhttpd.rc Для указания корневого каталога документов в конфигурационный файл включена строка Doc_Root $Config(docRoot) Если вы хотите определить, какой каталог является корневым каталогом документов, вам надо вызвать Doc.Root без параметров. Чтобы добавить к Web-узлу дополнительное дерево документов, включите в конфигурационный файл следующую запись: Doc_AddRoot каталог префикс_URL Различные установки для работы с документами Команда Doc_IndexFile задает образец, позволяющий найти главную Web-страницу в каталоге. Эта команда включается в конфигурационный файл и имеет следующий вид: Doc_IndexFile index.{htm,html,tml,subst} Если вам необходимо работать с другими суффиксами файлов, следует изменить шаблон. В данном случае шаблон ориентирован на обработку командой glob. Команда Doc_PublicHtml используется для организации на Web-узле рабочих каталогов пользователей. Если URL начинается с последовательности ~имя пользователя, Web-сервер будет искать указанный ресурс в рабочем каталоге этого пользователя. В конфигурационном файле, используемом по умолчанию, содержится следующая команда: Doc.PublicHtml public_html Например, если рабочий каталог пользователя /home/welch, то URL ~welch отображается в файл /home/welch/public_html. Если команда Doc_PublicHtml отсутствует, то данное отображение не производится.
430 Часть II. Расширенные средства Tcl Если понадобится, вы можете зарегистрировать две специальные Web- страницы. Одна из них будет возвращаться клиенту, если при работе сервера возникнет ошибка, а другая — в случае, если пользователь укажет URL несуществующего ресурса. Для определения этих страниц, в конфигурационном файле, используемом по умолчанию, содержатся приведенные ниже команды. Doc_ErrorPage error.html Doc_NotFoundPage notfound.html Эти файлы рассматриваются как шаблоны, т.е. они обрабатываются с помощью команды subst. При подстановке в них включается информация об ошибке или некорректно указанный URL. Данные шаблоны предельно просты по сравнению с шаблонами, которые рассматривались ранее. Об особенностях их обработки можно узнать, проанализировав реализацию Doc_SubstSystemFile в файле doc.tcl. Шаблоны документов Для поддержки механизма шаблонов предусмотрены две опции, которые указываются в конфигурационном файле. Первая из них задает дополнительный библиотечный каталог, содержащий сценарии, ориентированные на конкретное приложение. Это позволяет отделить файлы приложения от программ, реализующих сервер TclHttpd. В конфигурационный файл, применяемый по умолчанию, включается команда, с помощью которой указывается каталог libtml в поддереве файловой системы, предназначенной для размещения документов. Doc_TemplaTclibrary [file join $Config(docRoot) libtml] Вы также можете указать для обработки шаблонов альтернативный Тс.1- интерпретатор. По умолчанию применяется главный интерпретатор. По соглашениям об именовании его имя записывается как {}. (Соглашения об именовании интерпретаторов будут рассмотрены в следующей главе.) Doc.TemplaTclnterp {} Файлы протоколов Сервер поддерживает стандартный формат файлов протоколов. Команда Log.SetFile определяет базовое имя файла протокола. В конфигурационном файле содержится следующая команда: Log_SetFile /tmp/log$Config(port)_
Глава 18. Web-сервер TclHttpd 431 По умолчанию сервер осуществляет ротацию файлов протоколов один раз в сутки, в полночь. Файлу протоколов присваивается суффикс, сформированный на основании текущей даты (например, /tmp/logport_990218). Ротация файла, содержащего сообщения об ошибках, не производится. Информация об ошибках накапливается в файле /tmp/logport_error. Чтобы избежать лишних операций ввода-вывода, запись в файл протокола осуществляется один раз в несколько минут, независимо от того, насколько часто клиентские программы обращаются к серверу. Период между операциями записи данных на диск позволяет указать команда Log_FlushMinutes. Если вы зададите значение 0, запись будет производиться при выполнении каждой HTTP-транзакции. Пример команды, с помощью которой устанавливается интервал обновления файла протокола, приведен ниже. Эта команда включается в конфигурационный файл. Log_FlushMinutes 1 Каталоги CGI Для того чтобы обработка запросов, предполагающих вызов СGI-программ, осуществлялась корректно, каталог, содержащий эти программы, необходимо зарегистрировать. Регистрация производится с помощью команды Cgi_Directory. После выполнения этой команды все файлы, содержащиеся в каталоге, рассматриваются как CGI-сценарии, поэтому в данный каталог нельзя помещать обычные HTML-файлы. Для регистрации каталога с CGI- сценариями в конфигурационный файл включается следующая команда: Cgi.Directory /cgi-bin Приведенная выше запись означает, что каталог cgi-bin, являющийся подкаталогом корневого каталога документов, специально выделен для размещения CGI-сценариев. При вызове команды Cgi_Directory задается один параметр. Если второй параметр присутствует, то он интерпретируется как каталог файловой системы, который отображается в URL, заданный с помощью первого параметра. По необходимости сервер позволяет помещать CGI- сценарии в различные каталоги и использовать для их распознавания специальный суффикс. В случае, если принят такой подход, сервер будет рассматривать файл, оканчивающийся расширением .cgi, как файл, содержащий CGI-сценарий. В файле cgi.tcl предусмотрены дополнительные параметры, которые можно задавать только путем установки некоторых элементов Тс1-массива Cgi. Подробную информацию об этих параметрах можно получить, прочитав комментарии в начале файла.
Глава 19 Работа с несколькими интерпретаторами и использование Safe-Tcl В данной главе рассматриваются вопросы использования нескольких Tcl-интерпретаторов для обеспечения работы приложения. Дочерний интерпретатор может быть порожден таким образом, что будет работать лишь в защищенном режиме. Поэтому его можно использовать для выполнения сценариев, не пользующихся доверием, без риска нанести вред приложению или операционной системе. Взаимодействие интерпретаторов осуществляется с помощью псевдонимов, скрытых команд и разделяемых каналов ввода-вывода. В данной главе рассматривается Тс1-коман- да array. 1 10СЛЕ появления Safe-Tcl стало возможно передавать Tcl-сценарии по электронной почте и выполнять их без риска занести в компьютер вирус или стать жертвой атаки злоумышленника. Работа Safe-Tcl основана на удалении команд, выполнение которых может представлять опасность для системы. В качестве примеров таких команд можно привести exec и open. Такой интерпретатор можно рассматривать как "полностью контролируемую область'1, в пределах которой допускается работа непроверенных сценариев. Продолжая подобную аналогию, можно сказать, что, прежде чем предпринять потенциально опасные действия, сценарий, не пользующийся доверием, должен запросить разрешение на их выполнение. Такой режим выполнения достигается за счет введения дополнительных команд, или псевдонимов. Так, например, команда saf еореп может быть выполнена в ограниченной области
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 433 файловой системы — временном каталоге, который удаляется по завершении работы кода, не пользующегося доверием. В основу работы Safe-Tcl положено использование для выполнения приложения двух Tcl-интерпретаторов. Один из них обрабатывает фрагменты кода, пользующиеся доверием, а другой — непроверенный код. По аналогии с интерпретируемыми сценариями, первый называется интерпретатором, пользующимся доверием, а второй — интерпретатором, не пользующимся доверием. Интерпретатор, пользующийся доверием, может выполнять любые действия и применяется для иодьцержки главной программы (например, Web-браузера или почтового клиента). Если основное приложение получает сообщение, содержащее непроверенный сценарий, этот сценарий выполняется в среде, которая создается интерпретатором, не пользующимся доверием. Ограниченные возможности интерпретатора, не пользующегося доверием, гарантируют защиту приложения от атак извне. Данную модель работы интерпретаторов можно сравнить с пользовательским режимом и режимом ядра многопользовательской операционной системы, например Unix или Windows/NT. В этих системах приложения выполняются в пользовательском режиме и переходят в режим ядра только для получения ресурсов, т.е. для работы с файлами или обмена по сети. Ядро осуществляет контроль доступа, поэтому пользователь не может прочитать или записать данные в файл, принадлежащий другому пользователю, или несанкционированно воспользоваться сетевыми ресурсами. В Safe-Tcl приложение контролирует доступ со стороны сценариев, не пользующихся доверием. Модель Safe-Tcl была реализована в Tcl 7.5. Tcl-сценарий может создавать новые интерпретаторы, удалять их, объявлять псевдонимы команд, организовывать совместное использование каналов ввода-вывода несколькими интерпретаторами и выполнять с помощью созданных интерпретаторов другие сценарии. Команда interp Команда interp предназначена для создания интерпретаторов и управления ими. Создаваемый интерпретатор называется ведомым (slave), а интерпретатор, в среде которого был создан ведомый интерпретатор, называется ведущим (master). Ведущий интерпретатор может управлять ведомым. Варианты команды interp описаны в табл. 19.1.
434 Часть II. Расширенные средства Tcl Таблица 19.1. Операции, реализуемые с помощью команды interp interp aliases Предоставляет список псевдонимов, определенных ведомый_интерпретатор в ведомом интерпретаторе interp alias Возвращает команду и параметры для указанного ведомый_интерпретатор псевдонима в ведомом интерпретаторе псевдоним interp alias Определяет команду в ведомом интерпретаторе как ведомый_интерпретатор псевдоним команды с дополнительными параметрами команда. 1 в ведущем интерпретаторе в едущий_интерпрегагор команда_2 параметры interp create ?-saf e? Создает ведомый интерпретатор с указанным именем ведомый_интерпретатор interp delete Удаляет ведомый интерпретатор с указанным именем в едомый_интерпретатор interp eval Выполняет команду с параметрами в ведомом интер- ведомый_интерпретатор претаторе команда параметры . . . interp exists Возвращает значение 1, если в качестве параметра заведомый, ингерлрегагор дан ведомый интерпретатор. В противном случае возвращается О interp expose Предоставляет доступ ведомому интерпретатору ведомый_интерпретатор к указанной скрытой команде команда interp hide Скрывает указанную команду от ведомого иптерпре- ведомый_интерпретатор татора команда interp hidden Возвращает команды, скрытые от ведомого интерпре- ведомый_интерпретатор татора interp invokehidden Выполняет скрытую команду с параметрами в ведо- ведомый_интерпретатор мом интерпретаторе команда параметры ... interp is safe Возвращает значение 1, если при создании ведомого ведомый_интерпретатор интерпретатора была указана опция -safe interp marktrusted Очищает свойство issaf e ведомого интерпретатора ведомый_интерпретатор interp recursionlimit Предоставляет сведения о лимите рекурсивных вызо- ведомый_интерпретатор вов указанного ведомого интерпретатора или устанав- ?лимит? ливает новый лимит (Tcl 8.4)
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 435 Окончание табл. 19.1 interp share Устанавливает режим совместного использования ка- ведущий_интерпретатор нала ввода-вывода ведущим и ведомым интерпрета- дескриптор тором. Канал ввода-вывода задается посредством де- ведомый_интерпретагор скри птора interp slaves Возвращает список ведомых интерпретаторов для ведущшг_ингерпрегагор указанного ведущего интерпретатора interp target Возвращает имя интерпретатора, являющегося целе- ведомый_интерпретатор вым для указанного псевдонима ведущего интерпре- псевдоним татора interp transfer Передает канал ввода-вывода из ведущего интерпре- ведущий_интерпретатор татора ведомому. Канал ввода-вывода задается поде скриптор средством дескриптора ведомыи_интерпретатор Создание интерпретаторов В листинге 19.1 приведен простой пример создания интерпретатора, выполнения нескольких команд и удаления интерпретатора. Листинг 19.1. Создание и удаление интерпретатора interp create foo => foo interp eval foo {set a 5} => 5 set sum [interp eval foo {expr {$a + $a}}] => 10 interp delete foo В данном случае интерпретатор получает имя foo. С его помощью выполняются две команды. set a 5 expr {$а + $а} Заметьте, что фигурные скобки предотвращают обработку команд основным интерпретатором. Переменные определяются посредством интерпретатора foo и не конфликтуют с переменными, обрабатываемыми основным интерпретатором. Наборы переменных и процедур для каждого интерпретатора не зависят друг от друга.
436 Часть II. Расширенные средства Tcl Иерархия интерпретаторов Ведомый интерпретатор может, в свою очередь, создавать новые интерпретаторы. Таким образом, формируется иерархическая структура, состоящая из интерпретаторов различного уровня. Формирование иерархии интерпретаторов демонстрирует пример, приведенный в листинге 19.2. В нем, в частности, показано, как осуществляется доступ "через один уровень'', т.е. как интерпретатор обращается к интерпретатору, который был создан его дочерним интерпретатором. Для проверки существования дочерних интерпретаторов используется команда interp slaves. Листинг 19.2. Создание иерархии интерпретаторов interp create foo => foo interp eval foo {interp create bar} => bar interp create {foo bar2} => foo bar2 interp slaves => foo interp slaves foo => bar bar2 interp delete bar => interpreter named "bar" not found interp delete {foo bar} В данном примере создается интерпретатор foo, который затем порождает два дочерних интерпретатора. Для создания первого из них используется следующая команда: interp eval foo {interp create bar} Второй интерпретатор, дочерний по отношению к foo, создается основным интерпретатором. В данном случае имя создаваемого интерпретатора должно представлять собой двухэлементный список, так как он выступает в роли дочернего по отношению к интерпретатору, который, в свою очередь, является дочерним. Аналогичные правила выполняются и при удалении интерпретатора. interp create {foo bar2} interp delete {foo bar2} Операция interp slaves возвращает имена дочерних, или ведомых, интерпретаторов. Имена определяются относительно родительского интерпре-
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 437 татора, поэтому интерпретаторы, выступающие в роли ведомых по отношению к iоо, обозначаются как bar и Ьаг2. Именем текущего интерпретатора является пустой список ({}). Это удобно при работе с псевдонимами команд и организации совместного использования файлов. Имя ведущего интерпретатора недоступно из ведомого. Это правило было введено исходя из соображений безопасности. Имя интерпретатора в роли команды После создания ведомого интерпретатора в ведущем интерпретаторе появляется новая команда. Именем этой команды является имя ведомого интерпретатора; с ее помощью можно выполнять различные операции. Для большинства операций две приведенные ниже формы записи эквивалентны. ведомый_интерпретатор операция параметры . . . interp операция ведомый_интерпретатор параметры . . . В качестве примера можно привести две пары команд: foo eval {set a 5} interp eval foo {set a 5} и foo issafe interp issafe foo Следует заметить, что для операций delete, exists, share, slaves, target и transfer имя интерпретатора, на который они воздействуют, не может выступать в роли команды. В частности, если вы хотите выполнить операцию delete над интерпретатором foo, то вместо foo delete должны записать interp delete foo. В случае многоуровневой иерархии интерпретаторов команда, представляющая собой имя ведомого интерпретатора, доступна только в том интерпретаторе, с помощью которого ведомый интерпретатор был непосредственно создан. Например, если основной интерпретатор создал интерпретатор foo. a foo, в свою очередь, создал bar, то основной интерпретатор может выполнять операции с bar только посредством команды interp. Команда foo bar в основном интерпретаторе отсутствует. Использование команды list в составе interp eval Команда interp eval обрабатывает параметры подобно eval. При наличии дополнительных параметров выполняется их конкатенация. В этом случае, как было показано в главе 10, может быть потеряна структура команды.
438 Часть II. Расширенные средства Tcl Для того чтобы команда interp eval выполнялась корректно, при ее записи надо использовать команду list. Например, чтобы правильно определить переменную в ведомом интерпретаторе, надо использовать следующее выражение: interp eval slave [list set var $value] Защищенные интерпретаторы Дочерний интерпретатор может быть либо защищенным (т.е. не заслуживающим доверия), либо полнофункциональным. В примерах, рассмотренных ранее, дочерние интерпретаторы создавались как полнофункциональные; им был доступен базовый набор Tcl-команд. Для того чтобы сделать интерпретатор защищенным, надо удалить из него некоторые команды. В табл. 19.2 перечислены команды, удаленные из защищенного интерпретатора. По необходимости эти команды могут быть выполнены с помощью ведущего интерпретатора; подробно этот вопрос будет рассмотрен ниже. Для создания защищенного интерпретатора предназначена опция -safe. interp create -safe untrusted Таблица 19.2. Команды, скрытые от защищенного интерпретатора cd exec exit fconfigure file glob load open pwd socket scarce Переход к другому каталогу Выполнение программы Завершение процесса Установка режимов канала ввода-вывода Определение атрибутов файла Проверка имен файлов на соответствие шаблону Динамическая загрузка объектного кода Открытие файла Определение текущего каталога Открытие сетевого гнезда Загрузка сценариев В защищенном интерпретаторе отсутствуют команды, предназначенные для выполнения различных действий с файловой системой и другими программами (например, cd. open и exec). Благодаря этому сценарии, не пользующиеся доверием, не могут нанести вред операционной системе и другим программам. Команда socket удалена для того, чтобы непроверенные сцена-
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 439 рии пе могли обращаться к сети. Команды exit, source и load отсутствуют потому, что в противном случае сценарии могли бы повредить приложения. Заметьте, что команды puts и gets остаются доступными для сценариев, выполняющихся в среде защищенного интерпретатора. Защищенный интерпретатор может осуществлять операции ввода-вывода, но не может создавать необходимые для этого каналы. О том, как передать капал дочернему интерпретатору, рассказывается далее в этой главе. В исходном состоянии защищенный интерпретатор гарантирует достаточно высокий уровень безопасности, но его возможности чрезвычайно ограничены. Единственное, что может сделать такой интерпретатор, — сформировать строку и вернуть ее родительскому интерпретатору. Создавая псевдонимы команд, ведущий интерпретатор может предоставить ведомому контролируемый доступ к ресурсам. В соответствии с политикой защиты в защищенном интерпретаторе реализуется набор псевдонимов и добавляются некоторые контролируемые возможности. Например, в этой главе будет показано, как предоставить ведомым интерпретаторам, не пользующимся доверием, ограниченный доступ к сети и к файловой системе. В Tcl реализован набор базовых средств для создания политик защиты. Эти средства будут описаны в главе 20. Псевдонимы команд Псевдоним это команда одного интерпретатора, реализуемая с помощью команды в другом интерпретаторе. Ведущий интерпретатор задает псевдонимы команд для ведомых интерпретаторов. Команда, предназначенная для создания псевдонима, записывается в следующем формате: interp alias ведомый__интерпретатор команда_1 целевой_интерпретатор команда_2 ?параметр параметр ...? В результате выполнения приведенной команды в ведомом интерпретаторе создается команда 1, которая представляет собой псевдоним команды 2 в целевом интерпретаторе. При вызове команды 1 в ведомом интерпретаторе в ведущем вызывается команда 2. Механизм поддержки псевдонимов ^прозрачен" для ведомого интерпретатора. Независимо от того, с какими результатами завершилась команда 2, ведомый интерпретатор воспримет их как возвращаемое значение команды 1. Если при выполнении команды 2 возникнет ошибка, она будет отражена в ведомом интерпретаторе. Параметры, заданные при вызове команды 1, передаются команде 2, причем помещаются после всех дополнительных параметров, указанных при создании псевдонима. Эти скрытые параметры обеспечивают безопасный способ передачи дополнительных данных. Например, псевдониму может быть
440 Часть II. Расширенные средства Tcl передано имя ведомого интерпретатора. В листинге 19.3 команда exit в интерпретаторе iоо представляет собой псевдоним, реализованный в текущем интерпретаторе. Когда в ведомом интерпретаторе вызывается команда exit, команда, выполняемая ведущим интерпретатором, выглядит следующим образом: interp delete foo К текущему интерпретатору слезет обращаться по имени {}. Если целевым является текущий интерпретатор, обращение к нему осуществляется но имени {}, т.е. вместо имени указывается пустой список. Ситуация, при которой роль целевого играет текущий интерпретатор, встречается чаще всего, однако бывают случаи, когда целевым является другой ведомый интерпретатор. Кроме того, в качестве ведомого и целевого может быть указан один и тот же интерпретатор. Листинг 19.3. Псевдоним, соответствующий команде exit interp create foo interp alias foo exit {} interp delete foo interp eval foo exit # Дочерний интерпретатор foo удален Интроспекция псевдонимов По необходимости вы можете выяснить, какие псевдонимы определены для дочернего интерпретатора. Команда interp aliases возвращает список псевдонимов, а операция interp alias — значение псевдонима. Команда interp target предоставляет информацию о том, какой из интерпретаторов реализует псевдоним. Примеры применения этих команд приведены в листингах 19.4 и 19.5. Листинг 19.4. Получение информации о псевдонимах proc Interp_ListAliases {name out} { puts $out "Aliases for $name" foreach alias [interp aliases $name] { puts $out [format ,,0/0-20s => (°/0s) 0/0s" $alias \ [interp target $name $alias] \ [interp alias $name $alias]] } }
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 441 Процедура, код которой приведен в листинге 19.4, генерирует выходные данные в формате, удобном для восприятия. В листинге 19.5 приведена процедура, которая представляет псевдонимы как Tcl-команды. Это представление может быть использовано в дальнейшем для повторного создания псевдонимов. Листинг 19.5. Представление псевдонимов в виде Tcl-команд proc Interp_DumpAliases {name out} { puts $out n# Aliases for $nameu foreach alias [interp aliases $name] { puts $out [format "interp alias °/0s °/0s °/0s °/0s" \ $name $alias [list [interp target $name $alias]] \ [interp alias $name $alias]] } } Скрытые команды Команды, приведенные в табл. 19.2, не удалены безвозвратно, они лишь скрыты от интерпретатора, которому принадлежат. Скрытая команда ведомого интерпретатора может быть вызвана ведущим интерпретатором. Например, ведугций интерпретатор может загружать Tcl-сценарии в ведомый интерпретатор, используя скрытую команду source. interp create -safe ведомый_интерпретатор interp invokehidden ведомый_интерпретатор source имя_файла При отсутствии скрытых команд ведущему интерпретатору пришлось бы для выполнения ряда операций предпринимать специальные меры. В частности, ему потребовалось бы открывать файл и выполнять его содержимое в ведомом интерпретаторе посредством команды eval. Об операциях с файлами см. в главе 9. interp create -safe ведомый_интерпретатор set in [open имя_файла] interp eval ведомый_интерпретатор [read $in] close $in Скрытые команды были реализованы в Tcl 7.7 для того, чтобы оптимизировать поддержку дополнительных модулей в браузере Tcl/Tk. Подробно этот вопрос рассматривается в главе 20. В некоторых случаях действия, выполняемые скрытыми командами, невозможно эмулировать другими способами. В подобных ситуациях без скрытых команд обойтись не удается. В качестве примера можно привести действия, выполняемые в контексте Safe-Tk,
442 Часть II. Расширенные средства Tcl когда ведущий интерпретатор создает компоненты или выполняет потенциально опасные действия с использованием ведомого интерпретатора. Эти вопросы будут обсуждаться далее в данной главе. Ведущий интерпретатор может скрывать команды или предоставлять к ним доступ. Для этого предназначены команды interp hide и interp expose. По необходимости вы можете даже скрыть Tcl-процедуру. При этом следует иметь в виду, что команды, входящие в состав процедуры, действуют с привилегиями ведомого интерпретатора. Разработчики, которых чрезмерно беспокоят вопросы защиты, считают, что интерпретатор, не пользующийся доверием, не должен иметь доступ к информации о текущем времени. Для этого они скрывают команды clock и time, используя следующие выражения: interp create -safe slave ведомый_интерпретатор interp hide ведомый_интерпретатор clock interp hide ведомый_интерпретатор time При желании вы можете полностью удалить команды из ведомого интерпретатора. Ниже приведены два выражения, с помощью которых удаляются команды clock и time. interp eval ведомый_интерпретатор [list rename clock {}] interp eval ведомый_интерпретатор [list rename time {}] Подстановка Выполняя команды с помощью различных интерпретаторов, необходимо учитывать особенности синтаксического разбора и подстановки Тс1-выраже- иий. Следует различать три случая: выполнение команд interp eval, interp invokehidden и применение псевдонимов. В случае использования interp eval синтаксический разбор и подстановка полностью выполняются в целевом интерпретаторе. Это происходит после разбора и подстановки, применяемых к самой команде interp eval. Нельзя забывать, что если вы передадите interp eval несколько параметров, то перед выполнением команды будет произведена их конкатенация. Как было сказано ранее в данной главе, команда interp eval действует подобно команде eval. Для того чтобы исключить неожиданное™ при выполнении interp eval. следует при формировании команды использовать список. Это гарантирует, что структура команды будет корректна. interp eval ведомый_интерпретатор [list команда параметр_1 параметр.2] В случае скрытых команд сама команда и ее параметры задаются посредством параметров interp invokehidden, поэтому в целевом интерпретаторе
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 443 подстановка не выполняется. Это означает, что ведущий интерпретатор полностью контролирует структуру команды и в другом интерпретаторе никаких неожиданных эффектов быть не может. По этой причине список использовать не следует. Если вы сделаете это, то весь список будет интерпретироваться как имя команды. При вызове команды interp invokehidden надо задавать отдельные параметры, которые будут непосредственно переданы целевому интерпретатору. interp invokehidden ведомый_интерпретатор команда параметр_1 параметр _2 ФНс следует применять команду eval к параметрам псевдонима. При использовании псевдонимов синтаксический разбор и подстановка осуществляются в ведомом интерпретаторе перед тем, как команда, соответствующая псевдониму, будет выполнена ведущим интерпретатором. Во избежание выполнения произвольного кода в реализации псевдонима никогда не следует применять к значениям, полученным от ведомого интерпретатора, команду eval или subst. Предположим, например, что псевдоним используется для открытия файлов. При обработке псевдонима выполняются некоторые проверки, после чего вызывается скрытая команда open. Если непроверенный сценарий создан злоумышленником, в нем могут содержаться конструкции, специально включенные для того, чтобы вызвать некорректные действия компьютера. Например, в качестве имени файла может быть указано выражение [exit]. Делается это с расчетом на то, что к имени файла будет применена команда eval и приложение завершит свою работу. В данном случае мы предполагаем, что целью атаки является не выполнение каких-либо действий с открытым файлом, а лишь непредусмотренное завершение программы. В листинге 19.6 показан псевдоним, нечувствительный к атакам подобного рода. Листинг 19.6. Подстановка и скрытые команды interp alias slave open {} safeopen slave proc safeopen {slave filename {mode r}} { # Выполнение необходимых проверок interp invokehidden $slave open $filename $mode } interp eval slave {open \[exit\]} Первоначально команда, обрабатываемая ведомым интерпретатором, имеет следующий вид: open \[exit\]
444 Часть II. Расширенные средства Tcl Ведущий интерпретатор должен отменить специальное значение квадратных скобок в команде interp eval, в противном случае осуществится подстановка и ведомый интерпретатор вызовет команду exit. Сама команда exit может быть не определена или вызовет завершение ведомого интерпретатора. Если специальное значение квадратных скобок отменено, то именем файла является последовательность символов [exit]. не подлежащая подстановке. Таким образом, использование $filename в качестве параметра interp invokehidden не приводит к возникновению проблем, поскольку подстановка выполнена лишь один раз в ведущем интерпретаторе. Скрытая команда open также получает в качестве имени файла значение [exit], и это значение не интерпретируется как Tcl-команда. Поддержка ввода-вывода защищенными интерпретаторами Защищенный дочерний интерпретатор не может непосредственно открывать файл пли устанавливать сетевые соединения. Созданием канала ввода- вывода занимается псевдоним (т.е. при обработке псевдонима открывается файл или создается гнездо), после чего дочернему интерпретатору предоставляется доступ к каналу. Родительский интерпретатор может передать канал ввода-вывода дочернему интерпретатору либо использовать этот капал совместно с ним. Если канал передается, родительский интерпретатор теряет доступ к нему. Процедура передачи канала ввода-вывода проще, чем организация его совместного использования, но в последнем случае родительский интерпретатор получает дополнительные возможности контроля незащищенного дочернего интерпретатора. Различия между этими подходами демонстрирует код, приведенный в листингах 19.7 и 19.9. При передаче канала или организации его совместного использования необходимо принимать во внимание три важных параметра канала ввода- вывода: имя. текущую позицию и счетчик ссылок. • Имя канала (например, file4) остается одним и тем же, независимо от интерпретатора, работающего с каналом. Если родительский интерпретатор передает канал дочернему, тот может закрыть канал, выполнив команду close. Несмотря на то что имя канала не изменяется, интерпретатор не может обмениваться данными с каналом, доступа к которому он не имеет. • Текущая позиция канала ввода-вывода совместно используется всеми интерпретаторами, работающими с разделяемым каналом. При выполнении операций ввода-вывода текущая позиция изменяется для всех интерпретаторов. Это означает, что, работая с одним и тем же каналом.
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 445 интерпретаторы должны тщательно согласовывать свои действия. Если они оба выполнят операцию чтения, то получат различные данные. О понятии текущей позиции см. в главе 9. • Разделяемые каналы ввода-вывода поддерживают счетчик ссылок на них из разных интерпретаторов. До тех пор пока существует хотя бы одна ссылка, канал остается открытым. При передаче канала от родительского интерпретатора дочернему счетчик ссылок остается неизменным. Если родительский интерпретатор устанавливает режим совместного использования канала, счетчик ссылок увеличивается на единицу. При выполнении интерпретатором команды close (т.е. при закрытии канала) счетчик ссылок уменьшается на единицу. При удалении интерпретатора удаляются все его ссылки на канал ввода-вывода. Совместное использование и передача канала ввода-вывода осуществляется с помощью приведенных ниже команд. interp share интерпретатор_1 имя_канала интерпретатор_2 interp transfer интерпретатор_1 имя_канала интерпретатор_2 При выполнении приведенных выше команд предполагается, что указанный канал существует в интерпретаторе 1. В результате их выполнения организуется совместное использование этого канала с интерпретатором 2, или канал полностью передается этому интерпретатору. Как и при объявлении псевдонимов, если интерпретатор 1 является текущим интерпретатором, для его идентификации используется имя {}. В примере, представленном в листинге 19.7, для незащищенного интерпретатора создается временный файл. Файл открывается для чтения и записи, и ведомый интерпретатор может использовать его для хранения данных. Листинг 19.7. Открытие файла для незащищенного интерпретатора proc TempiileAlias {slave} { set i 0 while {[file exists Temp$slave$i]} { incr i } set out [open Temp$slave$i w+] interp transfer {} $out $slave return $out } proc TempfileExitAlias {slave} { foreach file [glob -nocomplain Temp$slave*] { file delete -force $file }
446 Часть II. Расширенные средства Тс interp delete $slave > interp create -safe foo interp alias foo Tempfile О TempfileAlias foo interp alias foo exit {} TempfileExitAlias foo Процедура Tempf ileAlias выполняется родительским интерпретатором тогда, когда дочерний интерпретатор вызывает команду Tempfile. Tempf ileAlias возвращает имя открытого канала; оно же становится возвращаемым значением Tempfile. Для передачи канала ввода-вывода дочернему интерпретатору Tempf ileAlias использует команду interp transfer: в результате дочерний интерпретатор получает доступ к каналу. В данном случае тот же результат можно было получить с помощью скрытой команды open, при этом канал ввода-вывода был бы создан непосредственно в ведомом интерпретаторе. Код, приведенный в листинге 19.7, не обеспечивает абсолютной защиты, так как у злоумышленника остается возможность организовать переполнение диска или создать с помощью этой процедуры миллионы файлов. Поскольку родительский интерпретатор передает канал ввода-вывода дочернему, он не может контролировать действия последнего по обмену данными. В листинге 19.9 показан способ решения этой проблемы. Защищенная база Защищенному интерпретатору, создаваемому с помощью команды interp create -safe, недоступна библиотечная среда, и он не может загружать сценарии посредством команды source. Tcl предоставляет так называемую защищенную базу, которая расширяет возможности обычного защищенного интерпретатора, предоставляя ему средства загрузки сценариев и пакетов (см. главу 12). В защищенной базе также определяется псевдоним exit, который завершает ведомый сценарий, подобно тому, как это показано в листинге 19.7. Защищенная база реализована в виде Tcl-сценариев, входящих в состав стандартной библиотеки Tcl. Для создания интерпретатора, использующего защищенную базу, применяется процедура safe: : interpCreate. safe::interpCreate foo В защищенной базе определены псевдонимы source и load, которые имеют доступ только к каталогам, принадлежащим защищенной области. Защищенную область определяет ведущий интерпретатор. Ведущий интерпретатор имеет полный контроль над файлами, которые могут быть загружены ведомым интерпретатором. В принципе допустимо было бы загрузить и интерпретатор, не пользующийся доверием, всю Tcl-программу. Однако в еще-
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 447 нариях, не пользующихся доверием, могут быть реализованы функции обучения, а в качестве исходных данных использоваться сообщения об ошибках, полученных при загрузке произвольных файлов. В защищенной базе содержатся версии команд package и unknown, предназначенных для поддержки библиотек. Tcl-процедуры, принадлежащие защищенной базе, приведены в табл. 19.3. Таблица 19.3. Основной интерфейс защищенной базы safe::interpCreate ?ведомый_интерпретатор? ?опции? safe::interplnit в едомый_интерпрегагор ?опции? safe::interpConfigure в едомый_интерпрегатор ?опции? safe::interpDelete в едомый_интерпрегатор safe::interpAddToAccessPath в едомый_интерпрегатор каталог safe::interpFindlnAccessPath safe::setLogCmd ?команда параметры ... ? Создает защищенный интерпретатор и инициализирует средства поддержки политики защиты Инициализирует защищенный интерпретатор так, чтобы он мог использовать политику защиты Допустимыми опциями являются -accessPath список_путей, -nostatics, -deleteHook сценарий, -nestedLoadOk Удаляет защищенный интерпретатор Добавляет каталог к защищенной области интерпретатора Устанавливает соответствие между каталогом и маркером, видимым для ведомого интерпретатора Устанавливает или запрашивает команду протоколирования, используемую защищенной базой В табл. 19.4 содержится список псевдонимов, которые определяются в защищенном интерпретаторе с помощью защищенной базы. Таблица 19.4. Псевдонимы, определяемые в защищенной базе source Загружает сценарии из каталогов, принадлежащих защищенной области load Загружает двоичные расширения из защищенной области ведомого сценария file Допустимы лишь операции dirname, join, extension, root, tail, pathname и split exit Удаляет ведомый интерпретатор
448 Часть II. Расширенные средства Tcl Политики безопасности Политика безопасности определяет, какие действия защищенного интерпретатора являются допустимыми. Разработка политики, обеспечивающей реальную защиту, представляет собой сложную задачу. Если вы реализуете собственное решение, предложите коллегам ознакомиться с кодом. Назначьте вознаграждение тем, кто сможет взломать вашу защиту. Хорошей считается та реализация политики защиты, которая выдержала критику многих специалистов и смогла успешно противодействовать пробным атакам. Средства Safe-Tcl позволяют реализовать политику защиты с помощью относительно небольшого числа Tcl-команд. Это упрощает анализ программных решений и внесение изменений. При создании политики защиты необходимо придерживаться следующих основных правил. • Политики небольшого размера лучше объемных сложных политик. Если для того, чтобы разрешить или запретить доступ к ресурсам, надо выполнять сложные действия, возрастает вероятность появления ошибок, которыми могут воспользоваться злоумышленники. Старайтесь, чтобы политика была настолько простой, насколько это возможно. • Никогда не применяйте к параметрам псевдонимов команду eval. Если при вызове псевдонима указываются параметры и они передаются ведомому интерпретатору, старайтесь избегать решений, которые могут привести к выполнению произвольного Tcl-кода. Первым шагом в этом направлении должен стать отказ от обработки параметров, передаваемых псевдониму с помощью команды eval. Проверяйте также и выражения. Команда expr реализует дополнительный этап подстановки, поэтому помещайте все выражения в фигурные скобки. В этом случае злоумышленник не сможет передать, например, [exit] тогда, когда вы ожидаете числовое значение. • Политика безопасности не должна представлять собой сочетание отдельных программных решений. Каждый раз, когда вы определяете новый псевдоним, политика безопасности изменяется. Даже если два псевдонима не связаны друг с другом, нет гарантии, что их нельзя использовать совместно для организации атаки. Каждое изменение политики безопасности должно быть тщательно проанализировано. Ограничение доступа к гнездам Политика, безопасности Saf esock ограничивает доступ к сетевым гнездам. Основой политики является таблица, содержащая адреса узлов и номера портов, доступ к которым разрешен. Интерпретатор, не пользующийся доверием, может устанавливать соединение только по тем адресам и с использованием
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 449 только тех портов, которые указаны в таблице. Например, не следует позволять непроверенным сценариям обращаться к портам, по которым ожидают обращение программы sendmail, ftp или Tclnet. Эти порты часто используются при организации атак. С другой стороны, непроверенным сценариям можно позволить получать данные с некоторых серверов или обращаться к серверам баз данных в intranet. Цель данной политики — явным образом указать, к каким узлам и по каким портам разрешен доступ. В листинге 19.8 показан упрощенный вариант политики безопасности Safe sock, поставляемой с Tcl 8.O. Листинг 19.8. Политика безопасности Saf esock # Индексом является имя узла. Значением является # список описаний портов. Описание может представлять собой # явно заданный номер порта, нижнюю # границу допустимых номеров (N-) # или диапазон номеров портов (N-M) array set safesock { sage.eng 3000-4000 www.sun.com 80 webcache.eng {80 8080} bisque.eng {80 1025-} } proc Safesock_PolicyInit {slave} { interp alias $slave socket {} SafesockAlias $slave } proc SafesockAlias {slave host port} { global safesock if ! [info exists safesock($host)] { error "unknown host: $host" } foreach portspec $safesock($host) { set low [set high ""] if {[regexp {~([0-9]+)-([0-9] *)$} $portspec x low high]} { if {($low <= $port && $high == "») || ($low <= $port && $high >= $port)} { set good $port break } } elseif {$port == $portspec} { set good $port
450 Часть II. Расширенные средства Tcl } } if [info exists good] { set sock [interp invokehidden $slave socket $host $good] interp invokehidden $slave fconfigure $sock \ -blocking 0 return $sock } error "bad port: $port" 2 Для инициализации данной политики используется процедура Saf esock_ Policylnit. Ее имя соответствует соглашению об именовании, принятому в защищенной базе. В данном случае создается один псевдоним. Этот псевдоним замещает команду socket, которая реализуется с помощью процедуры Saf esockAlias в ведущем интерпретаторе. Данный псевдоним проверяет номер порта на соответствие одному из заданных номеров. При положительном результате проверки вызывается операция invokehidden, с помощью которой в среде ведомого интерпретатора выполняются две команды. Команда socket устанавливает сетевое соединение, а команда fconfigure переводит гнездо в неблокирующий режим так, чтобы операции read и gets, выполняемые ведомым интерпретатором, не приостанавливали работу приложения. set sock [interp invokehidden $slave socket $host $good] interp invokehidden $slave fconfigure $sock -blocking 0 Псевдоним socket в ведомом сервере не конфликтует со скрытой командой socket. Скрытые и доступные команды принадлежат различным наборам. Очень часто при выполнении псевдонима вызывается скрытая команда. Очевидно, что делать это следует лишь после выполнения всех необходимых проверок. В состав Tcl Web-браузера входит модифицированная версия политики Saf esock. В ней добавлен псевдоним для fconfigure, в результате пакет http может устанавливать режимы буферизации и преобразования символа конца строки. Псевдоним fconfigure не позволяет изменять режим блокирования, заданный для гнезда. В составе политики также реализована классификация узлов: узлы подразделяются на пользующиеся и не пользующиеся доверием. Основанием для того, чтобы отнести узел к одной из этих категорий, является адрес узла. Для двух классов узлов используются различные таблицы портов. При принятии решения о том, заслуживает ли узел доверия, применяются две таблицы. В одной из них приведены шаблоны, которым должны
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 451 соответствовать адреса проверенных узлов, а в другой обозначены адреса тех узлов, доверять которым не следует. Если адрес соответствует второй таблице, то узел относится к классу узлов, не заслуживающих доверия даже в том случае, если этот же адрес соответствует и первой таблице. Данная версия политики также позволяет сценарию устанавливать соединение с тем Web-сервером, с которого он бы скопирован. Подробно Web-браузер описан в главе 20. Ограничения на использование временных файлов В листинге 19.9 содержится модификация кода, представленного в листинге 19.7. Здесь ограничиваются число временных файлов и их размер. Данный код написан для защищенной базы, поэтому в нем содержится процедура Tempf ile_PolicyInit, которой в качестве параметра передается идентификатор ведомого интерпретатора. Процедура Tempf ileOpenAlias позволяет дочернему интерпретатору задавать имя файла, но ограничивает возможности по созданию файлов одним каталогом. Данный пример иллюстрирует разделение канала ввода-вывода, позволяющее ведущему интерпретатору контролировать процесс вывода данных. Tempf ilePutsAlias ограничивает объем данных, записываемых в файл. В режиме совместного использования канала ввода-вывода ведомый интерпретатор может вызывать такие команды, как gets, eof и close, однако команду puts может вызывать только ведущий интерпретатор. С появлением скрытых команд необходимость в разделяемых каналах ввода-вывода стала менее острой. Но поскольку скрытые команды были реализованы после разделяемых каналов, этот механизм продолжает поддерживаться. Рассмотрим в качестве примера псевдоним puts. Он может либо записывать данные в разделяемый канал (первоначально проверив размер файла), либо вызывать скрытую команду puts в ведомом интерпретаторе. Формирование ограничений на использование команды puts демонстрирует код, приведенный в листинге 19.10. Листинг 19.9. Политика безопасности Tempf ile # Параметры политики: # directory - расположение файлов # maxfile - число файлов, которые могут содержаться в~каталоге # maxsize - максимальный размер одного файла array set tempfile { maxfile 4 maxsize 65536 }
452 Часть II. Расширенные средства Tcl # tempfile(directory) определяется динамически на основе # исходного кода сценария proc Tempfile_PolicyInit {slave} { global tempfile interp alias $slave open {} \ TempfileOpenAlias $slave $tempfile(directory) \ $tempfile(maxfile) interp alias $slave puts {} TempfilePutsAlias $slave \ $tempfile(maxsize) interp alias $slave exit {} TempfileExitAlias $slave } proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} { global tempfile # Удаление символов, которые потенциально могут быть # использованы для некорректных действий, regsub -all {I/:} [file tail $name] {} real set real [file join $dir $real] # Ограничение числа файлов. set files [glob -nocomplain [file join $dir *]] set N [llength $files] if {($N >= $maxfile) && (\ [lsearch -exact $files $real] < 0)} { error "permission denied" } if [catch {open $real $m $p} out] { return -code error "$name: permission denied" } lappend tempfile(channels,$slave) $out interp share {} $out $slave return $out } proc TempfileExitAlias {slave} { global tempfile interp delete $slave if [info exists tempfile(channels,$slave)] { foreach out $tempfile(channels,$slave) { catch {close $out} } unset tempfile(channels,$slave) }
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 453 } # См. также псевдоним puts в листинге 24.4 proc TempfilePutsAlias {slave max chan args} { # max - лимит размера файла в байтах # chan - канал ввода-вывода # args - либо один параметр, либо опция # -nonewline и строка if {[llength $args] > 2} { error "invalid arguments" } if {[llength $args] == 2} { if {{[string match -n* [lindex $argv 0]]} { error "invalid arguments" } set string [lindex $args 1] } else { set string [lindex $args 0]\n } set size [expr [Tcll $chan] + [string length $string]] if {$size > $max} { error "File size exceeded" } else { puts -nonewline $chan $string } 2 Процедуре Tempf ileOpenAlias в качестве параметров передаются каталог, имя и максимальное количество файлов. Каталог и максимальное количество файлов являются частью определения псевдонима. Их наличие "прозрачно" для ведомого интерпретатора. Он указывает только имя и режим доступа (чтение или запись). Политика Tempf ile может быть использована различными ведомыми интерпретаторами с разными параметрами. За выбор каталога для размещения файлов отвечает ведущий интерпретатор. Он отбрасывает информацию о пути, которую может указать ведомый интерпретатор, используя команду file tail. Определение tempf ile (directory) в данном листинге отсутствует. Приложение может выбрать каталог при создании защищенного интерпретатора. Политика безопасности Browser, которая будет рассмотрена в главе 20, выбирает каталог исходя из URL, соответствующего непроверенному сценарию. Процедура Tempf ilePutsAlias реализует ограниченный вариант команды puts. В процессе ее выполнения определяются размер файла (для этого ис-
454 Часть II. Расширенные средства Tcl пользуется команда Tcll) и длина выводимой строки. На основании этих данных производится оценка, не превысит ли размер файла максимально допустимое значение. Максимальный размер файла задается в качестве параметра при создании псевдонима. Файл не может превышать указанный размер; по крайней мере это не должно происходить в результате действий, выполненных дочерним интерпретатором. Параметр args используется для того, чтобы команде puts можно было передать необязательную опцию -nonewline. Значение args проверяется явным образом; приемы, связанные с использованием команды eval (см. листинг 10.3), здесь недопустимы. Если вы примените eval, дочерний интерпретатор сможет организовать атаку, передавая в качестве параметров Tcl-команды. Родительский и дочерний интерпретаторы совместно используют канал ввода-вывода. Имя канала содержится в tempf ile и Tempf ileExitAlias использует данную информацию для того, чтобы закрыть канал при удалении дочернего интерпретатора. Это необходимо, поскольку и родительский, и дочерний интерпретатор ссылается на разделяемый канал. При удалении дочернего интерпретатора ссылка удаляется автоматически, а родительский интерпретатор должен закрывать канал явным образом. При работе с разделяемым каналом ввода-вывода ведущий интерпретатор может использовать команды puts и Tcll. Ту же политику можно реализовать с помощью скрытых команд puts и Tcll. Команда Tcll скрывается для того, чтобы предотвратить создание ведомым интерпретатором собственной версии той же команды, которая сообщала бы неверное значение текущей позиции. По необходимости можно реализовать псевдоним puts "поверх" существующей реализации этой команды. Например, в сценарии можно определить puts как процедуру, которая включала бы данные в текстовый компонент. Использование скрытых команд демонстрируется в листинге 19.10. Листинг 19.10. Ограничение возможностей puts путем использования скрытых команд proc Tempfile.Policylnit {slave} { global tempfile interp alias $slave open {} \ TempfileOpenAlias $slave $tempfile(directory) \ $tempfile(maxfile) interp hide $slave Tcll interp alias $slave Tcll {} TempfileTcllAlias $slave interp hide $slave puts interp alias $slave puts {} TempfilePutsAlias $slave \ $tempfile(maxsize) # Специальный псевдоним exit не требуется }
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 455 proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} { # Удаление символов, которые потенциально могут быть # использованы для организации некорректных действий, regsub -all {I/:} [file tail $name] {} real set real [file join $dir $real] # Ограничение числа файлов set files [glob -nocomplain [file join $dir *]] set N [llength $files] if {($N >= $maxfile) && (\ [lsearch -exact $files $real] < 0)} { error "permission denied" } if [catch {interp invokehidden $slave \ open $real $m $p} out] { return -code error "$name: permission denied" } return $out } proc TempfileTcllAlias {slave chan} { interp invokehidden $slave Tcll $chan } proc TempfilePutsAlias {slave max chan args} { if {[llength $args] > 2} { error "invalid arguments" } if {[llength $args] == 2} { if {![string match -n* [lindex $args 0]]} { error "invalid arguments" } set string [lindex $args 1] } else { set string [lindex $args 0]\n } set size [interp invokehidden $slave Tcll $chan] incr size [string length $string] if {$size > $max} { error "File size exceeded" } else { interp invokehidden $slave \ puts -nonewline $chan $string
456 Часть II. Расширенные средства Tcl } } Защищенная команда after Команда after может представлять опасность для приложения, так как с ее помощью можно приостановить работу программы на произвольный интервал времени. Это происходит в том случае, если при вызове after задается только время, но не указывается команда. Приостановленная таким образом Tcl-программа не обрабатывает события. Так, можно остановить работу всех интерпретаторов, а не только того, в среде которого была вызвана программа after. Атака, при которой выполняются подобные действия, относится к категории атак с целью вывода служб из строя. Организаторов такой атаки не интересует информация, они стремятся так или иначе воспрепятствовать выполнению приложения. В листинге 19.11 определен псевдоним команды after для защищенного интерпретатора. В реализации данного псевдонима предусмотрена тщательная проверка параметров. В случае положительного результата проверки команда after выполняется в родительском интерпретаторе. Очень важной особенностью данной реализации является ограничение набора событий after. Для каждого события, запланированного с помощью команды after, ведущий интерпретатор формирует запись. В этой записи указываются два идентификатора: идентификатор, выбранный ведущим интерпретатором (myid), и идентификатор, установленный командой after (id). Ведущий интерпретатор отображает myid в id. Счетчик числа записей выполняет также роль счетчика запланированных событий. Данное отображение скрывает идентификатор, сформированный командой after, от ведомого интерпретатора. Благодаря этому ведомый идентификатор не может отменить запланированное событие. Процедура SafeAfterCallback соответствует запланированной процедуре. Она выполняет необходимую проверку, а затем активизирует программу обратного вызова в ведомом интерпретаторе. Листинг 19.11. Защищенная команда after # SafeAfter.Policylnit создает дочерний интерпретатор, # используя защищенную команду after proc SafeAfter.Policylnit {slave max} { # max ограничивает число событий, планируемых # с помощью команды after global after interp alias $slave after {} SafeAfterAlias $slave $max
Глава 19. Работа с несколькими интерпретаторами и использование Safe-Tcl 457 interp alias $slave exit {} SafeAfterExitAlias $slave # Генерация идентификаторов after для # дочернего интерпретатора, set after(id,$slave) 0 } # SafeAfterAlias представляет собой псевдоним для команды after. # Он запрещает вызывать команду after с указанием времени # при отсутствующей команде. proc SafeAfterAlias {slave max args} { global after set argc [llength $args] if {$argc == 0} { error "Usage: after option args" } switch -- [lindex $args 0] { cancel { # Стандартная реализация осуществляет интерпретацию, # игнорируя $args, но в args могут содержаться # данные, представляющие опасность, set myid [lindex $args 1] if {[info exists after(id,$slave,$myid)]} { set id $after(id,$slave,$myid) unset after(id,$slave,$myid) after cancel $id } return "" } default { if {$argc == 1} { error "Usage: after time command args..." > if {[llength [array names after id,$slave,*]]\ >= $max} { error "Too many after events" } # Поддержка конкатенации. set command [concat [lrange $args 1 end]] # Вычисление нового идентификатора для передачи # команде обратного вызова.
458 Часть II. Расширенные средства Tcl set myid after#[incr after(id,$slave)] set id [after [lindex $args 0] \ [list SafeAfterCallback $slave $myid $command]] set after(id,$slave,$myid) $id return $myid } > } # SafeAfterCallback - это процедура обратного вызова, # выполняющаяся в ведущем интерпретаторе. # Она вызывает требуемую команду в ведомом интерпретаторе. proc SafeAfterCallback {slave myid cmd} { global after unset after(id,$slave,$myid) if [catch { interp eval $slave $cmd > err] { catch {interp eval $slave bgerror $error} } } # SafeAfterExitAlias - псевдоним команды exit. proc SafeAfterExitAlias {slave} { global after foreach id [array names after id,$slave,*] { after cancel $after($id) unset after($id) } interp delete $slave }
Глава 20 Safe-Tk и дополнительный модуль браузера В данной главе описывается инструмент Safe-Tk, позволяющий непроверенным сценариям отображать элементы графического пользовательского интерфейса и выполнять с ними различные действия. Наиболее известным приложением Safe-Tk является дополнительный модуль Tcl/Tk для таких Web-браузеров, как Netscape Navigator и Internet Explorer. Oafe-Tk поддерживает аплеты, отображающие элементы пользовательского интерфейса. Из программ, использующих Safe-Tk, наиболее известен дополнительный модуль для браузеров Netscape Navigator, Mozilla и Internet Explorer. Данный модуль поддерживает Tcl-аплеты, или Tcl-леты (Tclet), которые копируются с Web-сервера и выполняются в окне Web-браузера. Большей частью приложения Tcl/Tk могут выполняться с помощью дополнительного модуля без изменений. Однако политика безопасности налагает на Тс-леты некоторые ограничения. Дополнительный модуль поддерживает различные политики безопасности, поэтому Tcl-леты могут выполнять ряд действий, не создавая опасности для системы и работающих в ней программ. Дополнительный модуль можно сконфигурировать так, что он будет использовать для выполнения Tcl-аплетов существующее приложение wish. Модуль также может загружать разделяемые библиотеки Tcl/Tk; в этом случае все компоненты выполняются в пределах процесса браузера. Допустимо также использовать вариант wish со встроенными или динамически загружае- мыми дополнительными функциями. Это позволяет приложениям, работающим в сети intranet, обращаться к базам данным и другим службам, которые не поддерживаются базовыми средствами Tcl/Tk. Политики безопасно-
460 Часть II. Расширенные средства Tcl сти обеспечивают опосредованный доступ к необходимым ресурсам. В данной главе описана процедура установки дополнительного модуля. Джефф Хоббс (Jeff Hobbs) недавно модифицировал дополнительный модуль для работы с Tcl/Tk 8.4. Скомпилированная версия модуля является частью Tcl Dev Kit. Исходные коды модуля распространяются бесплатно. Вы можете скомпилировать модуль для работы с новыми версиями Tcl/Tk либо создать на его основе новый модуль, реализовав в нем требуемые возможности. Исходные коды доступны по следующему адресу: http://tclplugin. sourceforge.net/. Tk в ведомых интерпретаторах Созданный ведомый интерпретатор поддерживает только базовый набор Tcl-команд. Он не позволяет работать с Tk и другими расширениями, которые, возможно, доступны посредством родительского интерпретатора. Такое положение дел не зависит от того, создан ли дочерний интерпретатор как защищенный. Вы можете расширить возможности дочернего интерпретатора с помощью следующего варианта команды load: load {} Tk ведомый_интерпретатор В обычных условиях при вызове команды load задается имя библиотечного файла, в котором реализовано расширение. В данном случае пакет Tk является статическим пакетом. Он уже связан с программой (wish или дополнительным модулем), поэтому вместо имени файла указывается пустая строка. Команда load вызывает процедуру инициализации Tk для регистрации всех Tcl-команд, реализуемых данным расширением. Встроенные окна Tk По умолчанию после загрузки Tk ведомый интерпретатор получает новое окно верхнего уровня. Программа wish поддерживает опцию командной строки -use, которая указывает Tk на то, что существующее окно получает имя, состоящее из точки. Это позволяет встраивать одно приложение в другое. Например, следующие команды запускают копию wish, которая использует в качестве главного окна . embed: toplevel .embed exec wish -use [winfo id .embed] somescript.Tcl & Еще чаще процедура встраивания используется ведомым интерпретатором. Если интерпретатор не является защищенным, вы можете перед загрузкой Tk объявить переменные argv и argc и присвоить им значения.
Глава 20. Safe-Tk и дополнительный модуль браузера 461 interp create trustedTk interp eval trustedTk \ [list set argv [list -use [winfo id .embed]]] interp eval trustedTk [list set argc 2] load {} Tk trustedTk Если дочерний интерпретатор является защищенным, непосредственно задать переменные argv и argc невозможно. Проще всего передать защищенному интерпретатору опцию, используя команду safe: :loadTk. safe::interpCreate safeTk safe::loadTk safeTk -use [winfo id .embed] Когда расширение Tk загружается в защищенный интерпретатор, оно обращается к ведущему интерпретатору и вызывает процедуру safe: :TkInit. Эта процедура должна вернуть ведомому интерпретатору требуемое значение argv. Процедура safe::loadTk сохраняет дополнительные параметры в переменной safe: :tklnit; эти значения извлекает процедура safe: :TkInit и возвращает их ведомому интерпретатору. Необходимость в таком сложном взаимодействии возникает потому, что защищенный интерпретатор не может создать собственную переменную argv; это в определенных условиях могло бы нанести вред ведущему интерпретатору. Ограничения Safe-Tk Когда Tk загружается посредством защищенного интерпретатора, некоторые команды Tk оказываются скрытыми. Такая мера принимается в первую очередь для того, чтобы предотвратить атаку на основной процесс, предпринимаемую с целью вывода его из строя. Например, если дочерний интерпретатор перехватит ввод, данные будет получать только он. В табл. 20.1 описаны команды Tk, которые скрываются при использовании защищенного интерпретатора. О командах Tcl, скрытых в защищенном интерпретаторе, см. в главе 19. Таблица 20.1. Команды Tk, скрытые от защищенного интерпретатора bell Звуковой сигнал, воспроизводимый терминалом clipboard Доступ к содержимому буфера обмена grab Непосредственный ввод данных в указанный компонент menu Создание меню и выполнение действий с ними. Команда скрыта потому, что при работе с меню предполагается выполнение grab selection Действия с выделенным текстом
462 Часть II. Расширенные средства Tcl Окончание табл. 20.1 send Выполнение команды в другом Тк-приложении tk appname Установка имени приложения tk_chooseColor Диалоговое окно для выбора цвета tk_chooseDirectory Диалоговое окно для выбора каталога tk.getOpenFile Диалоговое окно для выбора файла tk_getSaveFile Диалоговое окно для сохранения файла tk_messageBox Простое диалоговое окно toplevel Создание окна, не связанного с другими wm Управление диспетчером окон Если данные ограничения покажутся вам слишком строгими, вы сможете сделать некоторые команды доступными, выполнив операцию interp expose. Например, для работы с меню в окне верхнего уровня вам надо выполнить следующий фрагмент кода: interp create -safe safeTk foreach cmd {grab menu menubutton toplevel wm} { interp expose safeTk $cmd } Вместо непосредственного предоставления доступа к командам можно создать псевдонимы, которые реализовывали бы лишь некоторые из их функций. Например, вы можете запретить обработку опции -global в команде grab. О псевдонимах см. в главе 19. В дополнительном модуле для браузера реализована сложная система, позволяющая определять, какие команды должны быть доступны дочернему интерпретатору. Для обеспечения контроля над Tcl-летами вам нужна политика безопасности, которая указывает, какие действия могут выполнять Tcl-леты в среде, создаваемой дополнительным модулем. Вопросы настройки политики безопасности для дополнительного модуля будут рассмотрены ниже. Дополнительный модуль браузера HTML-дескриптор <EMBED> используется для включения различных объектов в состав Web-страницы. В качестве такого объекта может выступать Tcl-программа. Фрагмент кода, представленный в листинге 20.1, иллюстрирует использование дескриптора <EMBED> для включения Тс1-лета.
Глава 20. Safe-Tk и дополнительный модуль браузера 463 Листинг 20.1. Включение Тс 1-лета посредством дескриптора <EMBED> <EMBED TYPE="application/x-tcl" PLUGINSPAGE="http://www.tcl.tk/plugin/n WIDTH="400" HEIGHT="300" SRC="eval.tcl" </EMBED> Атрибуты width и height интерпретируются дополнительным модулем как размеры окна, предназначенного для включаемого объекта. Атрибут src задает URL программы. Имена параметров (например, width) чувствительны к регистру символов и должны задаваться строчными буквами. В приведенном выше примере eval.tcl представляет собой относительный URL, поэтому Tcl-файл должен находиться в том же каталоге, что и HTML-документ, содержащий дескриптор <EMBED>. При изменении размеров основного окна браузера размеры Tcl-лета остаются неизменными. Существуют также "полнооконые" Tcl-леты, для включения которых не используется дескриптор <EMBED>. Вместо этого файл .tcl указывается непосредственно в URL. В таком случае дополнительный модуль занимает все окно браузера, и при изменении размеров окна браузера соответствующим образом изменяется и окно дополнительного модуля. Переменные embed_args и plugin Атрибуты дескриптора <EMBED> доступны Tcl-программе посредством переменной embed_args. Эта переменная представляет собой массив, индексами которого являются имена атрибутов. Например, строка для Tcl-лета Ticker может быть передана дескриптору <EMBED> как параметр string. В этом случае Tcl-лет будет использовать $erabed_args(string) в качестве значения, предназначенного для отображения. <EMBED src=ticker.tcl width=400 height=50 string="Hello World"> Заметьте, что Tcl-лет может для удобства обработки преобразовать значения атрибутов HTML в нижний регистр. foreach {name value} [array get embed_args] { set embed_args([string tolower $name]) $value } В состав массива plugin входят элементы version, patchLevel и release, идентифицирующие номер версии и реализацию дополнительного модуля.
464 Часть II. Расширенные средства Tcl Пример дополнительного модуля На Web-странице дополнительного модуля можно найти большое количество примеров Tcl-летов. Некоторые из них разработаны группой Tcl/Tk в Sunlabs, другие созданы независимыми производителями. Чтобы получить информацию о Tcl-летах, обратитесь по адресу http://www.tcl.tk/plugin/. Первый дополнительный модуль, созданный одним из авторов данной книги, был предназначен для расчета параметров привода велосипеда. Эта информация привлекла внимание многих любителей велосипедного спорта. Tcl-лет, отображающий состояние передач на холсте Тк, позволял изменять в интерактивном режиме число звездочек и их размеры. С данной разработкой можно ознакомиться, обратившись по следующему адресу: http://www. beedub.com/plugin/bike.html. Установка дополнительного модуля В настоящее время доступны версии дополнительного модуля для систем Unix, Windows и Macintosh. В зависимости от платформы и версии модуля процедура инсталляции может иметь некоторые особенности. Для установки дополнительного модуля необходимы следующие компоненты. • Разделяемые библиотеки модуля (DLL). Когда возникает необходимость выполнить Tcl-лет, включенный в состав Web-страницы, Web-браузер динамически загружает программы, реализующие дополнительный модуль. Соответствующие библиотеки должны находиться в специально предназначенном для них каталоге. • Библиотеки сценариев Tcl/Tk. Для выполнения дополнительного модуля необходимы стандартные библиотеки сценариев Tcl и Тк, кроме того, в комплекте с модулем поставляются специальные сценарии. На каждой платформе определен каталог, содержащий сценарии дополнительного модуля. В этом каталоге должны содержаться подкаталоги tcl, tk, plugin, conf ig, saf etcl и utils. Программы, реализующие дополнительный модуль, находятся в каталоге plugin. • Политики безопасности. Содержатся в каталоге saf etcl. • Конфигурация доверия. Данная конфигурация определяет действия, которые могут выполнять Tcl-леты. Содержится в каталоге conf ig. • Средства привязки к локальному узлу. Настройка для работы с локальным узлом осуществляется с помощью двух процедур: siTclnit и siteSafelnit. Процедура siTclnit вызывается при первой загрузке дополнительного модуля, a siteSafelnit — при каждой загрузке
Плава 20. Safe-Tk и дополнительный модуль браузера 465 аплета. При вызове задаются ведомый интерпретатор и список атрибутов дескриптора <EMBED>. Данные процедуры можно оформить в виде сценариев, которые загружаются с помощью auto_path ведущего интерпретатора. Об управлении библиотеками сценариев, указанными в auto_path, см. в главе 12. Дополнительный модуль также загружает сценарий запуска, в котором вы можете определить процедуры siTclnit и siteSafelnit. В системе Unix этот сценарий вызывается как V.pluginrc а в Windows и Macintosh — как plugin/tclplugin.rc. Политики безопасности и дополнительный модуль Tcl-леты выполняются в среде защищенного интерпретатора, который оснащен средствами защищенной базы (см. главу 19). Благодаря этому возможности Tcl-лета ограничены лишь отображением данных. Для того чтобы расширить возможности Tcl-лета, надо предоставить ему специальные привилегии. Добавочные функции включаются в политику безопасности, которая реализуется как набор псевдонимов. В отличие от Java-аплстов. Tcl-леты могут выбирать различные политики. В составе дополнительных модулей поставляются некоторые политики безопасности; они будут описаны ниже. По необходимости вы можете создать собственную политику, предназначенную для поддержки intranet-приложений. Вы даже можете предоставить Тс1-ле- ту все возможности Tcl/Tk, хотя делать это вряд ли стоит. Для обращения к политике безопасности используется команда policy. policy имя Ниже приведено описание политик, входящих в состав стандартной поставки дополнительного модуля. Политики home, inside и outside предоставляют ограниченный доступ к сети. Они отличаются наборами доступных узлов. Конфигурация доверия, установленная по умолчанию, позволяет Tcl- лету обращаться к политике home, inside или outside. • home. Данная политика предоставляет специальные команды socket и f configure. Возможности данных команд ограничены взаимодействием с тем узлом, с которого Tcl-лет был загружен. Если в команде socket вы укажете вместо идентификатора узла пустую строку, соединение будет установлено с исходным узлом. Данная политика также поддерживает команды open и file delete, аналогично тому, как это делает политика Tempf ile (см. листинг 19.9). Для обеспечения работы этих команд используется локальное хранилище с ограниченными возможностями.
466 Часть II. Расширенные средства Tcl По умолчанию оно размещается в каталоге, доступном только Tcl-лету. После завершения Tcl-лета файлы в этом каталоге не удаляются, поэтому информация может сохраняться длительное время. Tcl-леты, загруженные с одного и того же сервера, могут совместно использовать каталог. Для этого в дескрипторе <EMBED> должны содержаться одинаковые значения атрибута pref \х=частичный_иг1. Частичный url может представлять собой префикс URL Tcl-лета. Помимо прочего, политика home автоматически предоставляет пакет browser, который будет описан далее. • inside. Данная политика напоминает политику home. Отличие состоит лишь в том, что администратор узла имеет возможность контролировать содержимое таблицы узлов и портов, по которым ведомые интерпретаторы, не пользующиеся доверием, могут устанавливать соединения при вызове команды socket. Аналогичный набор таблиц определяет, по каким URL может осуществлять доступ пакет browser. Данная политика напоминает политику Safesock (см. листинг 19.8). Предполагается, что набор узлов находится под защитой брандмауэра. Локальное хранилище, предоставляемое данной политикой, отличается от хранилищ, которые поддерживаются политиками home и outside. Сказанное остается справедливым, даже если Tcl-леты пытаются организовать совместное использование файла, задавая атрибут pref ±х=частичный_1ЛИ. • outside. Эта политика действует подобно политикам home и inside. Отличие состоит в том, что указанный набор узлов находится за пределами области, защищенной брандмауэром. Использование локального хранилища, предоставляемого данной политикой, имеет свои отличия по сравнению с подходами, реализованными в политиках home и inside. • trusted. Эта политика предоставляет все возможности Tcl и Тк. Она позволяет загружать с помощью Web-браузера приложения Tcl и Тк. По умолчанию отображение доверия не позволяет пользоваться этими возможностями всем Tcl-летам. Настройка отображения доверия будет рассмотрена ниже. • javascript. Данная политика реализует расширенные возможности по сравнению с пакетом browser. Она позволяет вызывать произвольные сценарии Javascript и записывать HTML-код непосредственно во фреймы. В отличие от политик home, inside и outside, данная политика не позволяет использовать ограниченный вариант команды socket и временные файлы. Однако политика javascript не ограничивает UR.L, с которых могут загружаться данные, и позволяет выполнять Javascript-сценарии, что связано с определенным риском. По умолчанию
Глава 20. Safe-Tk и дополнительный модуль браузера 467 отображение доверия не позволяет Tcl-летам пользоваться этими возможностями. Пакет browser Пакет browser оснащен несколькими политиками безопасности. Благодаря этому многие средства Web-браузеров становятся доступными Tcl-летам. Они получают возможность копировать данные с серверов и отображать HTML-документы во фреймах. Однако использование пакета browser сопряжено с некоторым риском. В HTTP-запросах можно передавать информацию, поэтому применение Tcl-лета, использующего эту политику, может стать причиной утечки важных данных за пределы сети, защищенной брандмауэром. Для того чтобы препятствовать утечке информации, надо ограничить набор URL, по которым можно обращаться с помощью browser: :getURL. В табл. 20.2 перечислены псевдонимы, определенные в пакете browser. Таблица 20.2. Псевдонимы, определенные в пакете browser browser: : status строка Отображает строку в окне состояния браузера browser: :getURL URL Загружает указанный ресурс (если это позволяет ?тайм-аут? политика безопасности). Команды обратного вызо- ?'newcallback? ва выполняются перед приемом данных, в течение ? write callback? и после приема данных ?endcallback? browser: :displayURL URL Указывает браузеру на то, что заданный ресурс фрейм должен быть отображен во фрейме browser: :getForm URL Передает данные по указанному URL. Если в каче- данные стве режима обработки указано значение 0, счита- ?режим_обра6отки? ?тайм- ется, что данные представляют собой список имен аут? ?newcallback? и значений и должны кодироваться автомата че- ?writecallbacic? ски. В противном случае предполагается, что ко- ?endcallback? дирование уже выполнено browser: :displayForm URL Передает данные по указанному URL и отобража- фрейм данные ет результат во фрейме. Режим обработки интер- ? режим „обработки? претируется так же, как и в browser: :getForm Процедура browser: :getURL использует встроенные функции браузера, поэтому она поддерживает URL типа ftp:, http: и file:, а также позволяет работать с proxy-серверами. К сожалению, интерфейс browser: :getURL отличается от интерфейса http: :geturl. При работе browser: :getURL используется более сложная схема обратного вызова; это связано с особенностями встроенных функций браузера. Если вы не указываете команды обратного
468 Часть II. Расширенные средства Tcl вызова, процедура заблокирует работу программы до тех пор, пока все данные не будут получены. Функции обратного вызова описаны в табл. 20.3. Таблица 20.3. Команды обратного вызова browser: rgetURL newcallback имя поток Данная команда вызывается тогда, когда данные URL MIME-тип модификация начинают поступать с указанного URL. Имя опре- размер деляет Tcl-лет, а поток идентифицирует соединение. Остальные параметры представляют собой атрибуты возвращаемых данных writecallback имя поток Вызывается тогда, когда указанное число байтов число_байтов данные поступает Tcl-лету с заданным именем через указанный поток endcallback имя поток Вызывается тогда, когда обработка запроса завер- признак данные шена, однако в составе данных могут присутствовать дополнительные байты. Признак может принимать следующие значения: EOF, NETW0R.ERR0R, USER.BREAK или TIMEOUT Настройка политик безопасности Безопасность при работе дополнительного модуля обеспечивают три компонента: политики, наборы возможностей и отображения доверия. Политика представляет собой своеобразный ''зонтик'' для средств, использовать которые разрешено определенным Tcl-летам. Допустимые средства определяются отображениями доверия. Набор возможностей — это команды и псевдонимы, определенные для защищенного интерпретатора, обращающегося к политике. Отображение доверия представляет собой фильтр, исходными данными для которого являются URL Tcl-летов. В будущем возможен переход к использованию цифровых подписей вместо URL. Отображение доверия определяет, имеет ли Tcl-лет право обращаться к определенной политике. Политики защиты настраиваются для каждого клиента. Заметьте, что конфигурационные файлы оказывают воздействие на работу всей клиентской машины, на которой выполняется Web-браузер. Если вы создадите Tcl-лет, которому требуется политика безопасности, определяемая пользователем, вам придется позаботиться о передаче конфигурационных файлов на те клиентские машины, где должен выполняться ваш Tcl-лет. Вам также надо будет предоставить убедительные доказательства того, что ваша политика безопасности не нарушит защиту системы.
Глава 20. Safe-Tk и дополнительный модуль браузера 469 Файл config/plugin.cfg Основным конфигурационным файлом является файл conf ig/plugin. cfg. Он входит в комплект поставки дополнительного модуля. В этом файле описаны возможности дополнительного модуля и определены URL-фильтры для отображений доверия. С помощью команды section конфигурационный файл разбивается на. разделы. В разделе политик указывается, какие политики безопасности могут использоваться Tcl-летами. Например, в конфигурационном файле, используемом по умолчанию, в разделе политик содержатся следующие строки: section policies allow home disallow intercom disallow inside disallow outside disallow trusted allow javascript ifallowed trustedJavaScriptURLS \ $originURL В конфигурационном файле всем Tcl-летам предоставляется доступ к политике home и запрещается использование политик intercom, inside, outside и trusted. Кроме того, Tcl-лет получает ограниченный доступ к политике javascript. На первый взгляд может показаться, что в конфигурационном файле содержатся Tcl-выражения, однако синтаксис записей несколько отличается от Tcl. При создании конфигурационного файла используются специальные правила. В частности, в конфигурационном файле переменная originURL не определена, но в нем приведено выражение $originURL. Указанное значение проверяется после загрузки Tcl-лета. Здесь приведен лишь пример содержимого конфигурационного файла. Чтобы понять принцип, по которому осуществляется синтаксический разбор выражений, надо ознакомиться с документацией. Результат обработки выражения if allowed зависит от содержимого других разделов, в частности от отображения доверия. Для политики javascript в файле config/plugin.cfg содержится следующая запись: section trustedJavascriptURLs allow http://sunscript.sun.com:80/plugin/javascript/* К сожалению, указанный здесь сервер уже не работает, поэтому вам придется задать в вашем файле Web-сервер Scriptics. allow http://www.Tcl.tk:80/plugin/javascript/* В разделе отображений доверия вы можете использовать сочетание правил allow и disallow. В выражениях allow и disallow в качестве параметров
470 Часть II. Расширенные средства Tcl используются шаблоны string match. Эти выражения обрабатываются в порядке их следования. Например, вы можете сначала предоставить доступ, указав правило allow, а затем ограничить его с помощью правила disallow. Из всех принципов заполнения конфигурационного файла наиболее надежным является непосредственное указание каждого сервера, заслуживающего доверия. Конфигурационные файлы для политик С каждой политикой безопасности связывается конфигурационный файл. Например, политика outside использует файл outside, cfg, находящийся в каталоге conf ig. В этом файле указано, какие узлы и порты доступны Tcl- летам, использующим политику outside. Для политик inside и outside используются файлы одинаковой структуры; они содержат массивы saf esock, которые использовались для настройки политики безопасности Saf esock (см. главу 19). В массиве указываются набор разрешенных узлов и портов, а также узлы, доступ к которым запрещен. Последние содержатся в списке исключений. Если узел присутствует как в списке разрешенных, так и в списке запрещенных узлов, доступ к нему запрещается. Набор разрешенных и запрещенных узлов воздействует на процедуру browser: igeturl. Установки для политики Tempf ile (см. главу 19) входят в состав конфигурационных файлов для политик home, inside и outside. Конфигурационные файлы снабжены комментариями, просматривая которые вы можете получить достаточно полное представление о политиках безопасности. Наборы возможностей Псевдонимы, составляющие политику безопасности, объединяются в так называемые наборы возможностей. Наборы возможностей описаны в главном конфигурационном файле conf ig/plugin. cfg. variable featuresList {url stream network persist unsafe} В свою очередь, в конфигурационном файле каждой политики безопасности указан набор возможностей, входящий в состав политики. Например, в файле conf ig/home. cfg содержатся следующие данные: section features allow url allow network allow persist unless {[string match {UNKNOWN *} \ [getattr originURL]]} Каждый набор возможностей реализован в файле, который находится в каталоге saf etcl дистрибутивного пакета. Например, набор возможностей
Глава 20. Safe-Tk и дополнительный модуль браузера 471 url реализован в файле safetcl/url.tcl. Для поддержки конфигурационного механизма код, содержащийся в этих файлах, создан с учетом определенных соглашений. Каждый набор возможностей реализован в пространстве имен, который является дочерним по отношению к пространству имен safefeature (например, saf ef eature: :url). Он создан как процедура инсталляции, которая вызывается тогда, когда нужно инициализировать набор возможностей для нового Tcl-лета. Именно эта процедура проверяет правила allow/disallow. Команда cfg: : allowed использует язык правил, используемый в файлах . cfg. Создание новой политики безопасности В данной книге не рассматриваются язык конфигурационных файлов и действия, необходимые для создания новой политики безопасности. В составе дополнительного модуля поставляется справочная система, которая содержит достаточно подробную информацию по этому вопросу. Кроме того, нужные сведения можно найти по следующему адресу: http://www.tcl.tk/plugin/man/ Если в процессе работы у вас возникнет необходимость модифицировать существующую политику безопасности или создать новую, вам надо тщательно изучить примеры, поставляемые в дистрибутивном пакете. Как правило, оказывается, что проще всего решить задачу, модифицируя рабочий пример. Политики безопасности дополнительного модуля реализованы на языке Tcl. Этим он выгодно отличается от многих других продуктов Tcl/Tk. Написав сравнительно небольшой фрагмент кода, вы можете создать свою политику безопасности, предоставляющую Tcl-летам необходимые полномочия.
Глава 21 Многопотоковые Tcl-сценарии В данной главе описывается расширение Thread, предназначенное для создания многопотоковых Тс1-сценариев. Ljcvim во многих других языках поддержка потоков является одним из базовых средств, то в Tcl она была реализована совсем недавно. Причина заключается в том, что управление графическим пользовательским интерфейсом, обеспечение работы сервера с несколькими клиентами, асинхронное взаимодействие и другие задачи, для которых традиционно выделяются специальные потоки, в Tcl решаются путем использования цикла обработки событий. Однако, несмотря на то, что цикл обработки событий позволил выполнять многие действия в рамках одного потока, встречаются случаи, когда поддержка нескольких потоков была бы очень желательной. Так. например, с помощью потоков можно реализовать длительные вычисления, при выполнении которых работу с циклом обработки событий использовать очень неудобно. С помощью циклов можно было бы работать с внешними библиотеками и процессами, которые не поддерживают асинхронное взаимодействие. Ниже перечислены основные задачи, для решения которых часто применяются потоки. • Длительные вычисления и прочие типы обработки данных, препятствующие использованию цикла обработки событий. • Взаимодействие с внешними библиотеками, не поддерживающими асинхронное взаимодействие. • Параллельная обработка, не вписывающаяся в рамки модели событий. • Включение Tcl-сценарисв в существующие многопотоковые приложения.
Глава 21. Многопотоковые Tcl-сценарии 473 Что такое поток Традиционный процесс имеет существенное ограничение: в его рамках в каждый момент времени может выполняться только одно действие. Если в вашем приложении необходимо параллельно решать несколько задач, вам надо создать несколько процессов. Однако такой подход имеет ряд недостатков. Процесс потребляет много ресурсов, а для его инициализации требуется большое количество времени. Если приложение должно постоянно создавать новые процессы, это существенно замедлит его работу. Например, если сервер обрабатывает запрос каждого клиента в отдельном процессе, то при увеличении количества обращений к нему время отклика возрастает. Если приложение должно создавать много процессов, оно может даже исчерпать имеющиеся ресурсы, что замедлит работу самой операционной системы. Еще один недостаток использования процессов связан с обменом данными. Если один процесс должен передать другому информацию, ему приходится делать это посредством файловой системы, каналов или гнезд, т.е. обращаться к операционной системе, что существенно замедляет взаимодействие. Кроме того, при переходе от одного процесса к другому приходится переключать контекст выполнения, что также снижает быстродействие приложения. Потоки потребляют гораздо меньше ресурсов, чем процессы. В рамках одного процесса можно породить несколько потоков. Все они могут совместно использовать память и другие ресурсы процесса. Поэтому обмен информацией между потоками происходит значительно быстрее. Созданием потоков и координацией их работы занимается операционная система. На однопроцессорной машине операционная система распределяет время центрального процессора между потоками приложения так, что один поток не блокирует другие. На многопроцессорной машине операционная система может даже запускать потоки на отдельных процессорах. В этом случае на аппаратном уровне происходит не эмуляция параллельной работы потоков, а реальное одновременное их выполнение. Недостаток традиционного многопотокового программирования состоит в том, что при использовании нескольких потоков процесс проектирования приложения становится намного сложнее. Необходимо написать программу так, чтобы один поток не мог повредить ресурсы, используемые другими потоками. Поскольку ресурсы совместно используются всеми потоками, приходится принимать меры для того, чтобы два или несколько потоков не смогли одновременно модифицировать один и тот же ресурс.
474 Часть II. Расширенные средства Tcl Поддержка потоков в Tcl Поддержка потоков была впервые реализована в Tcl 8.1. В ядро Tcl были включены средства, обеспечивающие безопасную работу с потоками (thread- safe). В новых функциях С работа с потоками обеспечивалась независимо от используемой платформы. Однако о поддержке многопотоковых сценариев официально объявлено не было. По этой причине первым общепринятым средством создания многопотоковых Tcl-сценариев стало расширение Thread, первоначально написанное одним из авторов данной книги и впоследствии поддерживаемое Зораном Василевичем (Zoran Vasiljevic). Последней версией Thread является версия 2.5. В основном для обеспечения ее работы достаточно Tcl 8.3. но для некоторых из команд нужна версия Tcl не ниже 8.4. На уровне С требуется, чтобы интерпретатор Tcl управлялся только одним потоком. Однако в каждом потоке можно создать столько Тс1-интерпре- таторов, сколько необходимо. Даже в однопотоковом приложении каждому Tcl-интерпретатору соответствует свой набор переменных и процедур. Если вам надо, чтобы один поток выполнил команду с помощью Тс1-интерпретато- ра другого потока, сделать это можно, лишь передав специальное сообщение в очередь обработки событий этого интерпретатора. Подобно другим типам событий, сообщения обрабатываются в том же порядке, в котором они были приняты. Подготовка Tcl-интерпретатора с поддержкой потоков В большинстве случаев дистрибутивные пакеты Tcl не обеспечивают поддержку потоков, так как при компиляции соответствующая опция обычно не указывается. Причина в том, что использование средств, обеспечивающих безопасную работу с потоками, связано с накладными расходами и снижает производительность однопотоковых Tcl-приложений, количество которых значительно больше, чем количество многопотоковых программ. Кроме того, при создании многих расширений Tcl не была предусмотрена поддержка потоков, и попытка использования таких расширений в многопотоковой среде приведет к возникновению ошибок. Следовательно, вам необходимо самостоятельно скомпилировать исходные модули, поставляемые в составе дистрибутивного пакета Tcl. Для этого надо вызвать команду configure, указав опцию --enable-threads. (Дополнительная информация о компиляции Tcl и расширений приведена в главе 48.) Проверить, поддерживает ли потоки конкретный интерпретатор, позволит элемент tcl_platform(threaded). Если он существует и содержит логи-
Глава 21. Многопотоковые Tcl-сценарии 475 ческое значение true, интерпретатор позволяет работать с потоками. Если же этот элемент отсутствует, поддержка потоков отключена. Использование расширений с многопотоковыми сценариями Поскольку каждому интерпретатору соответствует отдельный набор переменных и процедур, приходится явным образом загружать расширения в тот поток, где они необходимы. Исключением является лишь расширение Thread, которое автоматически загружается в каждый интерпретатор. Используя расширения для работы многопотоковых сценариев, надо соблюдать осторожность. Многие Tcl-расширения не поддерживают потоки. Попытка использовать их с многопотоковыми сценариями часто приводит к сбоям и разрушению данных. Расширения, действия которых не выходят за рамки Tcl, как правило, не представляют опасности при работе с потоками. Очевидно, что при этом они сами не должны обращаться к командам и расширениям, не обеспечивающим безопасной работы с потоками. Но при выполнении этого требования многопотоковые операции не приводят к возникновению ситуаций, принципиально отличных от тех, которые имели место для однопотоковых сценариев. Если в документации на расширение, поставляемое в виде двоичных кодов, явно не указано, что оно поддерживает потоки, следует полагать, что такая поддержка отсутствует. Но даже если расширение обеспечивает безопасную работу с потоками, его часто приходится компилировать, указав соответствующие опции. (В большинстве случаев опции, включающие поддержку потоков, предполагаются по умолчанию.) ТА' не обеспечивает безопасной работы с потоками. Большинство библиотек, предназначенных для отображения данных (например, X Window), не поддерживают потоки или по крайней мере скомпилированы при отключенных средствах их поддержки. Для того чтобы обеспечить безопасную работу Тк с потоками, необходимо приложить существенные усилия. Использовать Тк можно в тех могопотоковых Tcl-приложениях, в которых Тк-коман- ды, управляющие интерфейсом, вызываются только из одного потока. Если возникнет необходимость обновить состояние интерфейса из другого потока, это можно сделать, передав сообщение тому потоку, который отвечает за работу с интерфейсом.
476 Часть II. Расширенные средства Tcl Использование расширения Thread Работа с программами tclsh и wish, поддерживающими потоки, практически не отличается от использования тех же программ, в которых поддержка потоков не реализована. Сразу после запуска выполняется только один поток, содержащий один интерпретатор Tcl; он называется главным потоком. Если в процессе работы вы не создадите новые потоки, ваше приложение будет работать точно ггак же, как и однопотоковое. Главный поток должен завершаться последним. Главный поток занимает особое место среди других потоков. Если он прекратит работу, завершится выполнение всего приложения. Если это произойдет тогда, когда существуют другие потоки, в работе Tcl может возникнуть ошибка. Поэтому при разработке многопотокового приложения необходимо следить за тем, чтобы главный поток завершался лишь тогда, когда будет окончено выполнение всех остальных потоков. Для того чтобы ваше приложение могло использовать средства поддержки потоков, следует загрузить расширение Thread. package require Thread Расширение Thread автоматически загружается в каждый новый поток, создаваемый с помощью команды thread: : create. Все остальные расширения должны явным образом загружаться в каждый из тех потоков, где они требуются. Расширение Thread создает команды в трех отдельных пространствах имен. • Пространство имен thread содержит все команды, предназначенные для создания потоков и управления ими, а также средства обмена сообщениями между потоками, мютексы и переменные условий. • В пространстве имен tsv содержатся все команды, которые необходимы для поддержки переменных, совместно используемых различными потоками. • Пространство имен tpool содержит все команды для создания пулов потоков и управления ими. Создание потоков Команда thread: : create создает поток, содержащий новый интерпретатор Tcl. Новый поток может быть создан не только из главного, но также из любого существующего потока. Немедленно после вызова команда thread: : create передает управление вызывающей процедуре; ее возвращаемое значение представляет собой идентификатор созданного потока. Каждый
Глава 21. Многопотоковые Tcl-сценарии 477 идентификатор уникален; его можно использовать для организации взаимодействия потоков и управления ими. Использование идентификатора потока можно сравнить с применением идентификатора канала ввода-вывода, возвращаемого в результате выполнения команды open. В расширении Thread предусмотрены средства интроспекции каналов. Команда thread: : id возвращает рщентификатор текущего потока; thread: :names предоставляет список потоков, выполняющихся в данный момент времени, a thread: :exists проверяет, существует ли указанный поток. Команде thread: : create в качестве параметра передается Тс1-сценарий. Если сценарий указан, он выполняется с помощью интерпретатора вновь созданного потока. По окончании работы сценария завершается поток. В листинге 21.1 работа с потоками демонстрируется на примере сценария, предназначенного для рекурсивного поиска файлов в каталоге. Если объем дерева каталогов велик, поиск может занять длительное время. Осуществляя поиск в отдельном потоке, вы можете освободить главный поток для выполнения других операций параллельно с поиском. Обратите внимание на то, что загрузка расширений и открытие файлов в "рабочем" потоке происходят независимо от других потоков. Листинг 21.1. Создание потока для выполнения длительной операции package require Thread # Создание отдельного потока для рекурсивного поиска # всех файлов с суффиксами .tcl в текущем каталоге # и его подкаталогах. Результаты записываются в файл # files.txt. thread::create { # Загрузка пакета fileutil из библиотеки Tcllib. # В этом пакете нас интересует процедура findByPattern. package require fileutil set files [fileutil::findByPattern [pwd] *.tcl] set fid [open files.txt w] puts $fid [join $files \n] close $fid } # Основной поток может параллельно решать другие задачи...
478 Часть II. Расширенные средства Tcl Если при вызове команды thread: : create сценарий не указан, интерпретатор потока начинает выполнение цикла обработки событий. В этом случае вы можете использовать команду thread: :send, чтобы передать потоку сценарий для выполнения. Команда thread: :send будет рассмотрена далее в этой главе. В ряде случаев перед переводом потока в цикл обработки событий желательно выполнить некоторые действия по его инициализации. После инициализации надо вызвать цикл обработки событий явно, указав команду thread: :wait. Пример использования thread: :wait показан в листинге 21.2. Команда thread: rwait выполняет те же функции, что и команда vwait или tkwait, применяемая в обычных сценариях. Причина, по которой следует использовать thread: :wait, рассматривается далее в этой главе. Листинг 21.2. Инициализация потока перед переводом в цикл обработки событий set httpThread [thread::create { package require http thread::wait }] Если поток создан, это еще не означает, что началось его выполнение. Слс\ уст различать создание потока и начало его выполнения. При создании потока операционная система выделяет ресурсы и подготавливает поток к выполнению. Однако реальное выполнение может начаться с некоторой задержкой. Наличие задержки и ее длительность зависят от особенностей работы операционной системы. Команда thread: :create возвращает управление тогда, когда поток создан, но при этом не гарантирует, что он начал работу. Если временные соотношения между действиями, выполняемыми в различных потоках, существенны для вашего приложения, вам надо использовать средства синхронизации потоков, которые будут рассмотрены в этой главе. Создание соединяемых потоков Как вы уже знаете, главный поток должен завершаться последним. Поэтому необходимы средства, позволяющие выяснить, имеет ли право главный поток завершать свою работу. В листинге 21.3 показано одно из возможных решений этой задачи. Как видно в данном листинге, главный поток периодически проверяет, существуют ли другие потоки.
Глава 21. Многопотоковые Tcl-сценарии 479 Листинг 21.3. Создание в приложении нескольких потоков package require Thread puts "*** I'm thread [thread::id]" # Создание трех потоков for {set thread 1} {$thread <= 3} {incr thread} { set id [thread::create { # Сообщение выводится три раза. Перед выводом сообщения # выдерживается пауза, длительность которой выбирается # по случайному закону. for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Thread [thread::id] says hello" } }] ;# thread:-.create puts "*** Started thread $id" } ;# for puts "*** Existing threads: [thread::names]" # Ожидание завершения других потоков while {[llength [thread::names]] > 1} { after 500 } pUts "*** That's all, folks!" Гораздо лучшим решением является использование соединяемых (joinable) потоков, поддержка которых была реализована в Tcl 8.4. Соединяемый поток позволяет другим потокам ожидать его завершения посредством команды thread: : join. Команду thread: : join можно применять только для соединяемых потоков, которые создаются при указании в команде thread: : create опции -joinable. Попытка применить команду join к потоку, созданному без опции -joinable, приведет к возникновению ошибки. Если при работе с соединяемым потоком команда join не указана, это приведет
480 Часть II. Расширенные средства Tcl к непроизводительному расходованию памяти и других ресурсов. В листинге 21.4 показан вариант предыдущей программы, в которой использовались соединяемые потоки. Листинг 21.4. Использование соединяемых потоков для определения момента завершения основного потока package require Thread puts "*** I'm thread [thread::id]" # Создание трех потоков for {set thread 1} {$thread <= 3} {incr thread} { set id [thread::create -joinable { # Сообщение выводится три раза. Перед выводом сообщения # выдерживается пауза, длительность которой выбирается # по случайному закону. for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Thread [thread::id] says hello" } }] ;# thread::create puts "*** Started thread $id" lappend threadlds $id } ;# for puts "*** Existing threads: [thread::names]" # Ожидание завершения других потоков foreach id $threadlds { thread::join $id } puts "*** That's all, folks!"
Глава 21. Многопотоковые Tcl-сценарии 481 Команда thread: :join блокирует выполнение приложения. Применяя команду thread: : join, следует помнить, что она блокирует работу приложения. Если поток ожидает завершения команды thread: : join, он не может выполнять другие действия, в том числе обрабатывать события. Поэтому в тех случаях, когда поток отвечает за обработку событий, команду thread: : join использовать не следует. Передача сообщений потокам Команда thread: : send позволяет передать сценарий другому потоку для выполнения. Основной интерпретатор целевого потока принимает сценарий как специальный тип сообщения и добавляет его к своей очереди сообщений. Сообщения обрабатываются в таком же порядке, в котором они принимаются; в той же очереди находятся и события других типов. Очевидно, что для того, чтобы поток мог распознать поступление сообщения и должным образом отреагировать, он должен находиться в очереди обработки событий. Как вы уже знаете, цикл обработки событий запускается при вызове команды thread: :wait в инициализационном сценарии потока либо в том случае, если при вызове команды thread: : create сценарий не указан. Передача синхронных сообщений По умолчанию команда thread: :send блокирует работу потока до ее завершения. Возвращаемым значением thread:: send является значение, которое возвращает последняя команда, выполненная в сценарии. Если при работе сценария возникает ошибка, она отображается в поток, из которого было передано сообщение. Команда thread::send генерирует соответствующий код ошибки, и состояние стека вызовов целевого потока помещается в переменную err or Info потока, отправившего сообщение. Листинг 21.5. Пример передачи синхронного сообщения set t [thread::create] ;# Создание потока => 1572 set myX 42 ;# Создание переменной в главном потоке => 42 # Копирование значения в переменную в рабочем потоке thread::send $t [list set yourX $myX] => 42 # Выполнение вычислений в рабочем потоке thread::send $t {expr { $yourX / 2 } }
482 Часть II. Расширенные средства Tcl => 21 thread::send $t {expr { $yourX / 0 } } => divide by zero catch {thread::send $t {expr { $yourX / 0 } } } ret => 1 puts $ret => divide by zero puts $errorInfo => divide by zero while executing "expr { $yourX / 0 } " invoked from within "thread::send $t {expr { $yourX / 0 } } " Если при вызове команды thread: :send вы укажете имя переменной, эта команда будет выполняться аналогично команде catch; thread: :send будет возвращать код завершения сценария, а значение последней выполненной команды сценария (или сообщение об ошибке) будет помещено в переменную. Tcl записывает информацию о стеке вызова целевого потока в переменную errorlnf о потока-отправителя. Листинг 21.6. Использование возвращаемого значения при работе с синхронными сообщениями thread::send $t {incr yourX 2} myY => 0 puts $myY => 44 thread::send $t {expr { acos($yourX) } } ret => 1 puts $ret => domain error: argument not in valid range puts $errorInfo => domain error: argument not in valid range while executing "expr { acos($yourX) } " Когда поток-отправитель ожидает завершения команды thread: : send, он не может выполнять другие действия, в том числе обслуживать цикл обработки событий. Поэтому синхронные соотношения в основном используются в следующих случаях. • Если необходимо получить значение из другого потока наиболее простым способом.
Глава 21. Многопотоковые Tcl-сценарии 483 • Если блокирование выполнения потока не оказывает существенного влияния на работу приложения. • Если вы можете продолжать выполнение потока только после получения ответа от другого потока. Используя синхронные сообщения, необходимо принимать меры против взаимоблокировки. Предположим, что поток А передает синхронное сообщение потоку В, используя для этого команду thread: :send, а поток В передает такое же асинхронное событие потоку А. Поскольку поток А блокирован командой thread: :send, в нем не выполняется цикл событий, в результате сообщения, переданные другими потоками, не обрабатываются. Возникает так называемая взаимная блокировка. Подобная ситуация чаще всего имеет место тогда, когда сценарий, переданный в качестве сообщения, вызывает процедуру, а в этой процедуре содержится команда thread: :send. При этом опасность взаимной блокировки неочевидна. По этой причине следует избегать использования синхронной команды thread: :send в сложных программах. Асинхронные сообщения, описанные в следующем разделе, позволят избежать подобных ситуаций. Передача асинхронных сообщений Если при вызове команды thread: :send указана опция -async, сценарий передается целевому потоку в асинхронном режиме. В этом случае команда thread: :send немедленно возвращает управление вызывающей процедуре. По умолчанию асинхронное сообщение игнорирует значение, возвращаемое сценарием. Однако, если вы укажете в качестве дополнительного параметра имя переменной, в нее будет записано значение, возвращаемое последней выполненной командой сценария. Поступив таким образом, вы можете указать переменную в качестве параметра команды vwait либо отслеживать с помощью команды trace попытку записи в эту переменную. Например: thread::send -async $t [list ProcessValues $vals] result vwait result В данном примере команда thread: :send возвращает управление сразу после вызова. Поток, отправляющий сообщение, может продолжать свое выполнение. В потоке-отправителе вызывается команда vwait, которая переводит поток в режим ожидания. Выполнение потока будет продолжено тогда, когда сценарий в целевом потоке завершится. В режиме ожидания поток продолжает обрабатывать события. Если аналогичное сообщение будет отправлено в синхронном режиме, события обрабатываться не будут. thread::send $t [list ProcessValues $vals] result
484 Часть II. Расширенные средства Tcl Сохранение и освобождение потоков Поток, в котором выполняется сценарий, не содержащий команды thread: :wait, завершится, как только сценарий закончит свое выполнение. Если же поток находится в цикле обработки событий, его выполнение продолжается до завершения цикла. Поэтому разработчик должен знать, как завершить цикл обработки событий. В каждом потоке поддерживается счетчик обращений. Первоначально счетчику присваивается значение 0 либо, если для создания потока использовалась команда thread: :create -preserved, значение 1. После этого любой поток может увеличить значение счетчика, выполнив команду сохранения потока (thread: :preserve), или уменьшить это значение с помощью команды освобождения потока (thread: :release). Эти команды воздействуют на счетчик потока с заданным идентификатором или, если идентификатор не указан, на текущий поток. Если в результате выполнения команды thread: :release значение счетчика обращений становится равным нулю или меньше нуля, поток помечается для завершения. Использование счетчиков обращений к потокам позволяет сохранять рабочий поток. Он будет выполняться до тех пор, пока его не освободят все потоки, выполнившие ранее операцию сохранения. В большинстве приложений такой сложный механизм управления потоками не требуется. Чаще всего бывает достаточно создать поток, а затем использовать команду thread: -.release для его завершения. set worker [thread::create] thread::send -async $worker $script # Впоследствии рабочий поток завершается thread::release $worker Поток, помеченный для завершения, не принимает сообщений и перестает обрабатывать другие события. Он заканчивает работу с текущим сообщением, а затем завершает цикл обработки событий. Если цикл был запущен в потоке в результате выполнения команды thread: :wait, то перед завершением потока будут выполнены все команды, следующие за thread: :wait. Пример сценария, демонстрирующего поведение потока при его освобождении, приведен в листинге 21.7. С помощью команд, расположенных после thread: :wait, можно освободить используемые ресурсы. Листинг 21.7. Выполнение команд, следующих за thread: :wait set t [thread::create { puts "Starting worker thread" thread::wait
Глава 21. Многопотоковые Tcl-сценарии 485 # Следующая команда выполнится после того, # как поток будет завершен puts "Exiting worker thread" J] Заметьте, что если в момент вызова thread: :release поток, к которому применяется операция освобождения, обрабатывает сообщение, то работа потока продлится до тех пор, пока сценарий, содержащийся в сообщении, будет выполнен полностью. Поэтому если в потоке присутствует бесконечный цикл, то команда thread: .-release никак не воздействует на него. Способов завершить такой поток не существует. Для запуска цикла обработки событий следует использовать команду thread: :wait. Механизм сохранения и освобождения потоков работает только в том случае, если цикл обработки событий запущен с помощью команды thread: :wait (или если поток создан без указания сценария). При запуске цикла с помощью команды vwait или tkwait завершить поток посредством команды thread: :release не удастся. Обработка ошибок Если при выполнении сценария, заданного в команде создания потока (thread: : create), возникнет ошибка, работа потока прекратится. Если та же ошибка имеет место при работе сценария, заданного в сообщении (команда thread: :send), то поток прекращает выполнение сценария, но продолжает цикл обработки событий. Для того чтобы при возникновении непе- рехватываемой ошибки работа потока прекращалась, надо вызвать команду thread: :configure и указать при ее вызове опцию -unwindonerror так, как показано ниже. thread::configure $t -unwindonerror 1 Способ обработки ошибок определяется тем потоком, который создает новый поток или передает ему сообщение. Если ошибка возникает в сценарии, переданном в асинхронном сообщении, она отобразится в поток-отправитель. Если ошибка возникает при создании потока или при выполнении сценария, переданного в асинхронном сообщении, то информация о состоянии стека вызова записывается в стандартный поток ошибок. В качестве альтернативного решения вы можете указать собственную процедуру обработки ошибок с помощью команды thread: :errorproc. При возникновении "асинхронной" ошибки Tcl автоматически вызывает определенную таким образом процедуру и передает ей идентификатор потока, в котором была сгенерирована ошибка,
486 Часть II. Расширенные средства Tcl и информацию о состоянии стека вызова. Другими словами, при этом выполняются те же действия, что и при использовании процедуры bgerror (см. главу 13). Пример обработки неперехватываемой ошибки приведен в листинге 21.8. Здесь информация об ошибке записывается в файл errors.txt. Листинг 21.8. Создание процедуры для обработки ошибок set errorFile [open errors.txt a] proc logError {id error} { global errorFile puts $errorFile "Error in thread $id" puts $errorFile $error puts $errorFile "" } thread::errorproc logError Разделяемые ресурсы Текущий рабочий каталог является разделяемым ресурсом, который используют все интерпретаторы во всех потоках. Если в одном из потоков текущий рабочий каталог изменится, эти изменения отразятся в каждом интерпретаторе каждого потока. Если библиотечная процедура временно изменит текущий каталог, а затем восстановит его предыдущее значение, это может привести к возникновению проблем. Если подобное произойдет в многопотоковом приложении, то в момент, когда текущий рабочий каталог будет изменен, другой поток может предпринять попытку доступа к нему и получить некорректные данные. Таким образом, если в многопотоковом приложении возникает необходимость выполнять действия с текущим рабочим каталогом, то желательно в начале работы сохранить его значение в глобальной переменной, доступной всем потокам. В приведенном ниже фрагменте кода команда tsv: :set сохраняет текущий каталог в элементе pwd, разделяемом переменной application. package require Thread # Сохранение текущего каталога в переменной, # доступной всем потокам tsv::set application pwd [pwd] set t [thread::create {#...}] Еще одним разделяемым ресурсом являются переменные окружения. Если один из потоков изменит переменную окружения, данные изменения ста-
Глава 21. Многопотоковые Tcl-сценарии 487 нут доступны всем потокам приложения. Это может навести на мысль о применении глобального массива env для хранения информации, совместно используемой всеми потоками. Однако делать этого не стоит. Во-первых, это гораздо менее эффективно по сравнению с использованием разделяемых переменных, а во-вторых, на каждой платформе могут быть свои особенности обработки переменных окружения. Если вам необходимо организовать совместный доступ к информации из различных потоков, для этого лучше всего использовать разделяемые переменные, которые будут обсуждаться далее в этой главе. Команда exit завершает работу всего приложения. Несмотря на то что команда exit формально не является разделяемым ресурсом, необходимо помнить, что она завершает работу всего приложения, независимо от того, в каком из потоков она вызывается. Таким образом, команда exit не подходит в тех случаях, когда надо завершить лишь один поток. Управление каналами ввода-вывода В большинстве языков программирования каналы ввода-вывода являются разделяемыми ресурсами. Однако в Tcl каждый интерпретатор поддерживает свой набор каналов. Совместно использоваться могут лишь стандартные каналы ввода-вывода (stdin, stdout и stderr). При использовании стандартных каналов ввода-вывода в системах Windows и Macintosh надо соблюдать осторожность.newline При запуске wish на платформах Windows и Macintosh стандартные каналы ввода-вывода отсутствуют. Вместо этого работа с stdout и stderr эмулируется путем вывода данных в специальное консольное окно. В Thread 2.5 эти эмулируемые каналы учтены в главном потоке, но в списке каналов ввода-вывода других потоков информация о них отсутствует. Поэтому попытка доступа к ним из любого потока, кроме главного, приведет к возникновению ошибки. Доступ к файлам из различных потоков В многопотоковом приложении не следует открывать один и тот же файл в различных потоках. Чтение данных из одного файла в разных потоках не приводит к возникновению ошибок, но гораздо более эффективно делать это в одном потоке и обеспечить разделение полученной информации с другими потоками. Открытие файла в разных потоках для записи часто приводит к появлению ошибок. Операционные системы обычно организуют буфериза-
488 Часть II. Расширенные средства Tcl цию записываемой информации для каждого канала. При наличии нескольких каналов для записи в один файл вполне может возникнуть ситуация, при которой данные из одного потока будут записаны поверх данных из другого потока. Если вам надо организовать запись в файл из разных потоков, необходимо, чтобы за решение этой задачи отвечал только один поток, а другие передавали ему соответствующие сообщения. В листинге 21.9 приведен пример реализации потока, предназначенного для записи данных в файл протокола. После открытия файла другие потоки могут вызывать процедуру AddLog потока logger для записи данных. Листинг 21.9. Реализация потока, осуществляющего запись данных в файл протокола set logger [thread::create { proc OpenLog {file} { global fid set fid [open $file a] } proc CloseLog {} { global fid close $fid } proc AddLog {msg} { global fid puts $fid $msg } thread::wait }] Передача каналов между потоками Если вы работаете с Tcl 8.4 или более поздними версиями, вы можете воспользоваться возможностью передачи канала из одного потока другому, предоставляемой расширением Thread. Передача осуществляется с помощью команды thread: :transfer. После выполнения команды thread: :transfer ноток, осуществляющий передачу, теряет доступ к каналу. Символьный идентификатор канала остается тем же самым, однако необходимо передать целевому каналу значение идентификатора. Для этой цели можно использовать разделяемую переменную. Команда thread: :transfer блокирует выполнение вызывающего ее сценария до тех пор, пока целевой канал не окончит действия по включению переданного канала. В приведенном ниже примере осуществляется передача канала. В данном случае разделяемые переменные
Глава 21. Многопотоковые Tcl-сценарии 489 не используются, вместо этого значение идентификатора дублируется целевым каналом. set fid [open myfile.txt г] # . . . set t [thread::create] thread::transfer $t $fid # Дублирование идентификатора канала в целевом потоке thread::send $t [list set fid $fid] В Thread 2.5 реализован другой способ передачи каналов. Сначала канал отключается от одного потока с помощью команды thread: :detach, затем по команде thread: .-attach отсоединенный ранее канал связывается с целевым потоком. Преимущество такого подхода состоит в том, что поток, отказывающийся от канала, не обязан знать, какой поток продолжит работу с ним. Этот способ удобно использовать при работе с пулами потоков. Вопросы применения пулов потоков будут рассмотрены ниже. Возможность передавать каналы между потоками широко используется при реализации многопотоковых серверов, в которых различные потоки обслуживают соединения с различными клиентами. В этом случае один из потоков отвечает за прием обращений клиентов, желающих установить соединение. После того как соединение установлено, этот поток создает новый поток, предназначенный для работы с данным клиентом, и передает ему гнездо, соответствующее соединению с этим клиентом. Для передачи гнезд необходимо реализовать специальный обработчик. При передаче гнезд возникает проблема, связанная с невозможностью передачи их непосредственно из обработчика соединения. socket -server ClientConnect 9001 proc ClientConnect {sock host port} { set t [thread: .-create { ... }] # Следующая команда некорректна thread::transfer $t $sock } Дело в том, что во время выполнения процедуры обратного вызова Tcl поддерживает внутреннюю ссылку на гнездо. Команда thread: :transfer (равно как и команда thread: :detach) не может передать канал, пока существует эта дополнительная ссылка. Поэтому необходимо отложить передачу до тех пор, пока не завершится процедура вызова. Для этого удобно использовать команду after. Пример отложенной процедуры передачи показан в листинге 21.10.
490 Часть II. Расширенные средства Tcl Листинг 21.10. Передача гнезда после завершения команды обратного вызова proc _ClientConnect {sock host port} { after 0 [list ClientConnect $sock $host $port] } proc ClientConnect {sock host port} { # Создание клиентского потока и передача канала 2 Необходимо заметить, что в первых реализациях Tcl 8.4 была ошибка, вследствие которой невозможно было инициализировать средства поддержки при передаче гнезда другому потоку. Приходилось явным образом создавать в потоке гнездо (оно может быть немедленно закрыто), а затем осуществлять передачу гнезда. Впоследствии эта ошибка была исправлена. В качестве примера в листинге 21.11 показан фрагмент кода, с помощью которого инициализация осуществлялась во вновь созданном потоке перед запуском цикла обработки событий. Листинг 21.11. Инициализация при передаче гнезда set t [thread::create { # Инициализация средств поддержки путем открытия # и закрытия серверного гнезда. close [socket -server {} 0] # Теперь может выполняться процедура передачи гнезда. thread::wait J} В листинге 21.12 показан пример создания простого потокового сервера, который реализует режим "эхо", т.е. возвращает полученную информацию. Заметьте, что в каждом потоке взаимодействие с клиентом управляется событиями. В таком простом сервере в этом нет надобности, поскольку поток создается для обслуживания одного клиента и не должен обрабатывать сообщения, переданные из других потоков. Если возникнет необходимость в обработке сообщений из другого потока, наличие цикла обработки событий будет обязательным.
Глава 21. Многопотоковые Tcl-сценарии 491 Листинг 21.12. Многопотоковый сервер, реализующий режим "эхо" package require Tcl~8.4 package require Thread 2.5 if {$argc > 0} { set port [lindex $argv 0] } else { set port 9001 > socket -server _ClientConnect $port proc _ClientConnect {sock host port} { # В процессе обратного вызова Tcl поддерживает ссылку на # клиентское гнездо, поэтому мы не можем немедленно # передать канал рабочему потоку. Вместо этого мы планируем # событие after для создания рабочего потока и передачи # канала, после того, как будет запущен # цикл обработки событий. after 0 [list ClientConnect $sock $host $port] } proc ClientConnect {sock host port} { # Создание отдельного потока для управления клиентом. # Сценарий инициализации потока определяет все процедуры # взаимодействия с клиентом и запускает в потоке цикл # обработки событий. set thread [thread::create { proc ReadLine {sock} { if {[catch {gets $sock line} len] II [eof $sock]} { catch {close $sock} thread::release } elseif {$len >= 0} { EchoLine $sock $line } } proc EchoLine {sock line} { if {[string equal -nocase $line quit]} {
492 Часть II. Расширенные средства Tcl SendMessage $sock \ "Closing connection to Echo server" catch {close $sock} thread::release } else { SendMessage $sock $line } } proc SendMessage {sock msg} { if {[catch {puts $sock $msg} error]} { puts stderr "Error writing to socket: $error" catch {close $sock} thread::release > # Запуск цикла обработки событий thread::wait }] # Исключение канала из главного потока. В этом случае # мы используем thread::detach/thread::attach для того, # чтобы предотвратить блокирование. thread::detach $sock # Копирование идентификатора гнезда в # клиентский поток. thread::send -async $thread [list set sock $sock] # Присоединение гнезда к потоку, обслуживание клиента # и завершение настройки гнезда. thread::send -async $thread { thread::attach $sock fconfigure $sock -buffering line -blocking 0
Глава 21. Многопотоковые Tcl-сценарии 493 fileevent $sock readable [list ReadLine $sock] SendMessage $sock "Connected to Echo server" } } vwait forever Разделяемые переменные Стандартные переменные Tcl представляют собой ресурс, ориентированный на конкретный интерпретатор. Другие интерпретаторы не имеют доступа к таким переменным. Для того чтобы организовать обмен данными между потоками, приходится передавать значения переменных сценариям, предназначенным для выполнения в другом потоке, и получать возвращаемые значения. Такой подход не соответствует принципу разделения данных между потоками. Кроме того, он не позволяет организовать обмен большими объемами данных. Расширение Thread поддерживает специальные переменные, которые называются разделяемыми переменными потоков, или просто разделяемыми переменными. Доступ к этим переменным имеют все потоки приложения. Разделяемые переменные хранятся за пределами всех интерпретаторов, поэтому если поток, в рамках которого была создана разделяемая переменная, завершится, переменная по-прежнему будет существовать и другие потоки смогут использовать ее. Разделяемые переменные хранятся в наборах, называемых массивами. Этот термин нельзя признать удачным. Несмотря на то что массивы разделяемых переменных имеют ту же структуру, что и стандартные массивы Tcl, для работы с ними должны использоваться специальные выражения. В приложении можно создать любое количество массивов разделяемых переменных. Поскольку разделяемые переменные имеют специальное назначение, для создания их и выполнения различных действий с ними нельзя использовать стандартные Tcl-команды. Аналогично, стандартные средства подстановки переменных неприменимы для получения значений разделяемых переменных. (Сказанное также означает, что разделяемые переменные нельзя использовать в качестве значений опций -textvariable и -listvariable компонентов, в командах vwait и tkwait и в команде trace.) С разделяемыми переменными можно выполнять действия только с помощью команд, предоставляемых расширением Thread; эти команды принадлежат пространству имен tsv. Большинство команд из пространства имен tsv имеет аналоги среди обычных Tcl-команд, предназначенных для создания стандартных Tcl-
494 Часть II. Расширенные средства Tcl переменных и выполнения действий с ними. Команды, принадлежащие пространству имен tsv, описаны в табл. 21.3. Для создания разделяемой переменной используется команда tsv: :set. В качестве параметров указываются имя массива, имя переменной (иногда ее называют элементом массива) и значение, присваиваемое переменной. Ниже приведен пример создания разделяемой переменной. tsv::set application timeout 10 Получить значение разделяемой переменной можно, вызвав команду tsv: :set без указания значения, либо посредством команды tsv: :get. Приведенные ниже две команды эквивалентны. tsv::set application timeout tsv::get application timeout Все команды, используемые для работы с разделяемыми переменными, являются атомарными. Это означает, что поток блокирует доступ к переменной до завершения операции. До тех пор пока выполнение команды не будет окончено, ни один из потоков не сможет получить доступ к переменной. Если поток предпримет такую попытку, его выполнение приостановится до тех пор, пока блокировка переменной не будет снята. В результате использование разделяемых переменных существенно упрощается. Для сравнения, во многих других языках необходимо явным образом блокировать и разблокировать переменные, чтобы их значения не стали некорректными вследствие одновременного обращения из различных потоков. Такая особенность блокировки становится особенно полезной при выполнении команд из пространства имен tsv, предназначенных для обработки списков. Стандартные Tcl-команды, такие как 1 insert и lreplace, принимают список в качестве входного значения, а затем возвращают новый список. Для модификации значения списка, содержащегося в стандартной Тс1-пере- менной, используется выражение наподобие следующего: set states [linsert $states 1 California Nevada] Сделать то же самое с разделяемыми переменными несколько сложнее. Предположим, что мы использовали для этой цели следующее выражение: tsv::set common cities \ [linsert [tsv::get common cities] 1 Yreka Winnemucca] После чтения значения разделяемой переменной с помощью команды tsv: :get, но перед выполнением команды tsv: :set, другой поток может изменить значение переменной. Это приведет к разрушению данных. По этой причине tsv-команды, обрабатывающие списки, выполняют модификацию значения разделяемых переменных. При этом никакие действия с другими
Глава 21. Многопотоковые Tcl-сценарии 495 потоками не нанесут вреда данным, хранящимся в переменной, поскольку она блокируется на все время выполнения команды. tsv::linsert common cities 1 Yreka Winnemucca Мютексы и переменные условий Мютексы и переменные условий предназначены для синхронизации потоков. В других языках программирования они используются очень часто, но в Tcl потребность в них значительно ниже. Это связано с особенностями модели потоков Tcl, а также с тем, что все команды, ориентированные на работу с разделяемыми неременными, являются атомарными. Команды для работы с мютексами и переменными условий предоставляются расширением Thread и находятся в пространстве имен thread. Мютексы Название этого механизма блокировки произошло от слов mutual exclusion (взаимное исключение). Мютексы применяются для защиты совместно используемых ресурсов (разделяемых переменных, последовательных портов, баз данных и т.д.) от одновременного обращения из различных потоков. Перед тем как начать работу с разделяемым ресурсом, поток предпринимает попытку блокировать мютекс. Если мютекс ранее не был захвачен другим потоком, блокировка осуществляется успешно и поток может начинать работу с ресурсом. Если мютекс уже захвачен другим потоком, то блокируется сама попытка блокировки мютекса. Работа потока приостанавливается до тех нор, пока мютекс не будет освобожден. Использование мютексов демонстрирует фрагмент кода, представленный в листинге 21.13. Первым шагом является создание мютекса. Для этого используется операция thread::mutex create, которая возвращает уникальный маркер, идентифицирующий мютекс. Этот маркер используется всеми потоками, поэтому необходимо обеспечить доступ к нему. Сделать это можно, например, с помощью разделяемых переменных. Листинг 21.13. Использование мютексов для защиты разделяемых ресурсов # Создание мютекса и сохранение маркера в разделяемой # переменной с тем, чтобы другие потоки имели доступ к нему. tsv::set db mutex [thread::mutex create] # . . .
496 Часть II. Расширенные средства Tcl # Блокировка мютекса перед обращением к разделяемому ресурсу, thread::mutex lock [tsv::get db mutex] # Выполнение необходимых действий с разделяемым ресурсом # и разблокирование мютекса. thread::mutex unlock [tsv::get db mutex] # По мере необходимости повторяется та же последовательность # действий thread::mutex destroy [tsv::get db mutex] Для того чтобы мютексы оказывали реальную помощь при работе с разделяемыми ресурсами, необходимо обеспечить их корректное использование. Мютексы оказывают помощь только в том случае, если они правильно используются всеми потоками приложения. Если поток игнорирует мютексы и непосредственно обращается к разделяемому ресурсу, мютексы становятся бесполезны. По этой причине при разработке приложения необходимо тщательно следить за правильным применением мютексов. Переменные условий Переменные условий представляют собой механизм синхронизации, благодаря использованию которого один или несколько потоков может переходить в ждущий режим до получения оповещения от другого потока. Переменная условия связывается с мютексом и бинарным условием, которое называется предикатом. Поток использует переменную условия для перехода в ждущий режим, в котором он будет находиться до тех пор, пока значение предиката остается истинным. Другой поток устанавливает значение предиката равным true, а затем оповещает переменную условия. Мютекс синхронизирует доступ к данным, используемым для вычисления значения предиката. Поток, который оповещает другие потоки, должен выполнять следующие действия. • Блокирование мютекса. • Установка значения предиката равным true. • Оповещение переменной условия.
Глава 21. Многопотоковые Tcl-сценарии 497 • Разблокирование мютекса. В свою очередь, ждущий поток должен предпринять приведенную ниже последовательность действий. • Блокирование мютекса. • Проверка предиката. • Если значение предиката равно false, переход в режим ожидания до оповещения с помощью переменной условия. • Выполнение необходимых действий. • Разблокирование мютекса. На практике ждущий поток должен проверять предикат в цикле while, поскольку одновременно несколько потоков может ожидать, пока состояние переменной условия изменится. Ожидая изменения состояния переменной условия, ждущий поток автоматически освобождает мютекс. Когда сигнализирующий поток оповещает переменную условия, все потоки, ожидающие изменения состояния этой переменной, начинают состязание за право блокировать мютекс. Когда сигнализирующий поток освобождает мютекс, один из ожидающих потоков блокирует его. Вполне возможно, что состояние потока изменится и при освобождении мютекса значение предиката уже не будет равно true. Например, несколько рабочих потоков, составляющих пул, могут ожидать появления задания. При оповещении один из потоков выполняет задание, оставляя остальные потоки свободными. На первый взгляд действия, связанные с использованием переменной условия, могут показаться сложными, но реализующий их код достаточно прост. В листинге 21.14 приведен фрагмент программы для сигнализирующего потока. В первую очередь поток создает переменную условия. Для этого выполняется операция thread: : cond create, которая возвращает маркер, представляющий переменную. Этот маркер используется всеми потоками, поэтому необходимо обеспечить доступ к нему тех потоков, которые должны работать с разделяемым ресурсом. (Для этого можно, например, применять разделяемые переменные.) Если поток готов изменить состояние предиката, то перед тем как сделать это, он блокирует мютекс, связанный с переменной условия. Затем он оповещает переменную условия, вызывая команду thread::cond notify, после чего разблокирует мютекс. Листинг 21.14. Использование переменной условия сигнализирующим потоком # Создание переменной условия и связанного с ней мютекса. # Для того чтобы соответствующие маркеры были доступны # требуемым потокам, используются разделяемые переменные.
498 Часть II. Расширенные средства Tcl set cond [tsv::set tasks cond [thread::cond create]] set mutex [tsv::set tasks mutex [thread::mutex create]] # Когда мы будем готовы изменить состояние предиката, # мы сначала должны получить защищающий его мютекс. thread::mutex lock $mutex # Обновление предиката. В данном примере мы всего лишь # присваиваем разделяемой переменной значение true. # На практике предикат может быть более сложным, # например, может потребоваться, чтобы длина списка, # хранящегося в разделяемой переменной, стала # равной нулю. tsv::set tasks predicate 1 # Оповещение переменной условия, активизация всех ожидающих # потоков. Выполнение каждого потока блокировано до тех пор, # пока поток не сможет блокировать мютекс. thread::cond notify $cond # Разблокирование мютекса. thread::mutex unlock $mutex В листинге 21.15 представлен код для ждущего потока. Если поток готов проверить предикат, он предварительно блокирует защищающий его мютекс. Если значение предиката равно true, поток может продолжить обработку и разблокировать мютекс в нужный момент. Если значение предиката равно false, поток выполняет команду thread: :cond wait, с помощью которой переходит в ждущий режим. Команда thread: :cond wait автоматически разблокирует мютекс. При оповещении поток автоматически блокирует мютекс (блокирование осуществляется еще до получения мютекса), после чего команда thread: : cond wait завершается. Затем поток проверяет предикат и повторяет процесс до тех пор, пока значение предиката не станет равно true. Листинг 21.15. Использование переменной условия ждущим потоком set mutex [tsv::get tasks mutex] set cond [tsv::get tasks cond]
Глава 21. Многопотоковые Tcl-сценарии 499 # Блокирование мютекса перед проверкой предиката, thread::mutex lock $mutex # Проверка предиката; по необходимости ожидание до тех пор, # пока значение предиката не станет равным true. while {![tsv::get tasks predicate]} { # Ожидание оповещения переменной условия. # thread::cond wait автоматически разблокирует мютекс, # блокирует выполнение до получения оповещения, затем # снова блокирует мютекс. thread::cond wait $cond $mutex } # В данный момент мы владеем мютексом и знаем, что # значение предиката равно true. Выполняем необходимую # обработку, а затем, когда мютекс станет не нужен, # разблокируем его. thread::mutex unlock $mutex Модель потоков Tcl составлена так, что потребность в переменных условий оказывается значительно меньшей по сравнению с другими языками программирования. Как правило, гораздо проще вызвать в потоке команду thread: :wait, запустив тем самым цикл обработки событий, а затем передавать потоку сообщения с помощью команды thread: :send. Если же ваше приложение должно использовать пул потоков для выполнения заданий, целесообразно воспользоваться для этой цели средствами, реализованными в расширении Thread. Пулы потоков Пулы потоков применяются при разработке многопотоковых приложений. Пул состоит из нескольких рабочих потоков, ожидающих поступления задания. Когда задание передается пулу потоков, один из рабочих потоков выполняет его. Если все потоки заняты, то, в зависимости от решения разработчика, либо для задания создается новый поток, либо задание помещается в очередь и ожидает освобождения одного из потоков.
500 Часть II. Расширенные средства Tcl В составе расширения Thread реализованы команды для создания пулов потоков и управления ими. Эти команды содержатся в пространстве имен tpool. Использовать команды, специально предназначенные для работы с пулами потоков, гораздо проще, чем создавать собственные пулы с нуля, используя мютексы и переменные условий. Поддержка пулов потоков реализована в Thread 2.5. Команда tpool: : create создает новый пул потоков и возвращает его идентификатор. В данной команде предусмотрен ряд опций, с помощью которых можно управлять поведением создаваемого пула. Опция -minthreads задает минимальное число потоков в пуле. Эти потоки создаются при создании самого пула. Если рабочий поток завершается и общее число потоков становится меньше минимально допустимого, создается новый поток. Опция -maxthreads позволяет задать максимально допустимое число рабочих потоков в пуле. Если пулу передается новое задание, а в пуле нет доступных потоков, то создание нового потока происходит только в том случае, если общее число потоков в пуле меньше максимально допустимого. По достижении максимального количества потоков новые задания помещаются в очередь, где дожидаются освобождения потока. С помощью опции -idletime задается интервал времени в секундах, в течение которого поток ожидает поступления нового задания. По истечении указанного времени поток завершается, освобождая тем самым системные ресурсы. Посредством опций -initcmd и -exitcmd задаются сценарии, предназначенные соответственно для инициализации вновь создаваемого рабочего потока и освобождения ресурсов при завершении потока. После создания пула потоков ему можно передавать задания для выполнения. Это осуществляется с помощью команды tpool: :post. Задание представляет собой произвольный Tcl-сценарий. Для выполнения задания выбирается любой доступный поток в пуле. Если свободных потоков в пуле нет и если общее число рабочих потоков не превышает максимально допустимого, для задания создается новый поток. Если новый поток не может быть создан, команда tpool: :post блокирует выполнение потока до тех пор, пока какой- либо из рабочих потоков не примет задание на выполнение. Блокированный поток продолжает обслуживать события. Команда tpool: :post возвращает идентификатор задания. Чтобы получить оповещение о выполнении задания, надо использовать в потоке, из которого задание было передано, команду tpool: :wait. Данная команда приостанавливает работу потока, но при этом обработка событий продолжается. По команде tpool: :wait можно ожидать окончания сразу нескольких заданий. В этом случае выполнение потока будет продолжено тогда, когда хотя бы одно из заданий будет завершено. Возвращаемым значением tpool: :wait является список идентификаторов завершенных заданий.
Глава 21. Многопотоковые Tcl-сценарии 501 После того как команда tpool: :wait оповестит вас о том, что задача завершена, вы можете получить результаты ее выполнения, вызвав команду tpool: :get, которая возвращает значение последней выполненной команды сценария, переданного в качестве задания. Если при выполнении сценария возникнет ошибка, она отобразится в тот поток, который передал задание на выполнение. Ошибка возникнет при выполнении команды tpool::get; при этом будут соответствующим образом установлены значения error Info и errorCode. К пулу потоков, как и к отдельным потокам, могут быть применены команды сохранения и освобождения. Каждый пул потоков поддерживает внутренний счетчик ссылок, для которого после создания пула устанавливается значение 0. Любой поток может увеличить значение счетчика, выполнив команду tpool: :preserve, или уменьшить это значение с помощью команды tpool: :release. Если в результате выполнения команды tpool: :release значение счетчика обращений становится равным нулю или меньше нуля, пул потоков помечается для удаления. При любом обращении к такому пулу возникнет ошибка. Команды пакета Thread Команды, реализованные в расширении Thread, в зависимости от их назначения, сгруппированы в трех отдельных пространствах имен. В данном разделе рассматриваются команды, принадлежащие каждому из пространств имен. Пространство имен thread Пространство имен thread содержит все команды, предназначенные для создания потоков и управления ими, а также средства обмена сообщениями между потоками, поддержки мютексов и переменных условий. Команды, принадлежащие пространству имен thread, описаны в табл. 21.1. Таблица 21.1. Команды пространства имен thread thread: :attach канал Присоединяет к текущему интерпретатору в текущем потоке канал, который ранее был отсоединен thread: :cond create Возвращает маркер для вновь созданной переменной условия thread: :cond destroy Удаляет указанную переменную условия переменная_условия thread: :cond notify Активизирует все ждущие потоки, соответствующие переменная_условия указанной переменной условия
502 Часть II. Расширенные средства Tcl Продолжение табл. 21.1 thread: :cond wait Блокирует выполнение потока до тех пор, пока дру- переменная_условия гой поток не оповестит переменную условия, вы- мютекс ?тайм-аут? звав thread: :cond notify, либо до истечения времени тайм-аута, заданного в миллисекундах. Перед обращением к thread: : cond wait вызывающий поток должен заблокировать мютекс. При переходе в состояние ожидания команда автоматически освободит мютекс. Перед возвратом в вызывающий поток команда снова захватывает мютекс thread: : configure Запрашивает или устанавливает опции конфигурации ядеягяфякагор ?опция потока (подробные сведения о данных опциях приве- зяаченяе? ?опция дены в табл. 21.2) зяачеяяе...? thread: : create Создает поток и возвращает его идентификатор. ?-joinable? Опция -joinable позволяет другим потокам ожи- ?-preserved? дать завершения данного потока с помощью коман- ?сценарий? ды thread::join. Опция -preserved устанавливает счетчик ссылок потока равным 1 вместо значения 0, принимаемого по умолчанию (см. описания thread::preserve и thread::release.) Если при вызове команды указан сценарий, он выполняется в потоке и завершается. В противном случае запускается цикл обработки событий thread: : detach каяал Отсоединяет указанный канал от текущего потока так, что поток теряет доступ к нему. После этого любой поток может выполнить команду thread: :attach и получить возможность работать с потоком thread: :errorproc Регистрирует процедуру в качестве обработчика оши- ?процедура? бок, которые возникают при выполнении асинхронных команд thread: :send. При вызове процедуре передаются два параметра: идентификатор потока, сгенерировавшего ошибку, и значение переменной error Info из этого потока thread: : eval ?-lock Выполняет конкатенацию параметров и выполняет по- мютекс? параметр лучившееся выражение под защитой мютекса. Если ?параметр ...? мютекс не задан, используется внутренний статический объект thread: : exists Возвращает логическое выражение, которое указыва- идентификатор ет, существует ли заданный поток thread: : id Возвращает идентификатор текущего потока thread::join Блокирует работу до завершения указанного потока идентификатор (доступна только в Tcl 8.4 и более поздних версиях)
Глава 21. Многопотоковые Tcl-сценарии 503 Окончание табл. 21.1 thread::mutex create thread::mutex destroy мютекс thread::mutex lock мютекс thread::mutex unlock мютекс thread::names thread::preserve ?идентификатор? thread::release ?-wait? ?идентификатор? thread::send ?-async? идентификатор сценарий ?имя_переменной? thread::transfer идентификатор канал thread::unwind thread::wait Возвращает маркер для вновь созданного мютекса Удаляет мютекс Блокирует мютекс. Выполнение приостанавливается до тех пор, пока мютекс не предоставит исключительные права доступа Разблокирует мютекс Возвращает список идентификаторов всех выполняющихся потоков Возвращает список идентификаторов всех выполняющихся потоков Уменьшает на единицу счетчик ссылок для заданного потока. Если поток не указан, данная команда воздействует на счетчик ссылок текущего потока. Если значение счетчика ссылок равно нулю или меньше нуля, поток помечается для удаления. Если указана опция -wait, выполнение приостанавливается до удаления целевого потока Передает сценарий потоку с указанным идентификатором. Если задана опция -async, команда не ожидает, пока сценарий окончит свою работу. Если указана переменная, в нее помещаются результаты выполнения сценария Передает открытый канал из текущего потока целевому потоку. Данная команда блокирует выполнение текущего потока до тех пор, пока целевой поток не окончит действия по включению канала (доступна только в Tcl 8.4 и более поздних версиях) Завершает команду thread: :wait, вызывая окончание работы потока. Данная команда не рекомендована для применения. Вместо нее следует использовать thread::release Запускает цикл обработки событий Команда thread: : configure позволяет приложению определять и устанавливать значения конфигурационных опций потока, подобно тому, как команда fconfigure используется для настройки каналов. Опции, предназначенные для настройки потоков, перечислены в табл. 21.2.
504 Часть II. Расширенные средства Tcl Таблица 21.2. Операции, посредством которых осуществляется настройка потоков -eventmark целое_число -imwindonerror логическое„выражение Определяет максимальное число сценариев, ожидающих обработки. Эти сценарии были переданы потоку с помощью команды thread: : send. Когда количество сценариев достигает максимума, последующие команды thread: : send блокируются до тех пор, пока число сценариев не уменьшится. Значение 0 (принимается по умолчанию) указывает на то, что число сценариев, ожидающих выполнения, не ограничено Если значение логического выражения равно true, то при возникновении неперехватываемой ошибки поток завершает цикл обработки событий. По умолчанию предполагается значение false Пространство имен tsv В пространстве имен tsv содержатся все команды, которые необходимы для поддержки разделяемых переменных потоков. Описание команд из пространства имен tsv приведено в табл. 21.3. Таблица 21.3. Команды, принадлежащие пространству имен tsv tsv::append массив элемент значение ?значение ...? tsv::exists array ?элемент? tsv::get массив элемент /переменная: tsv::incr массив элемент ?значение? tsv::lappend массив элемент значение ?значение ...? tsv::lindex массив элемент индекс Присоединяет указанные данные к разделяемому значению. Действует подобно команде append Возвращает логическое значение, которое сообщает, существует ли указанный элемент. Если элемент не задан, возвращаемое значение сообщает о том, существует ли указанный массив Возвращает значение разделяемой переменной. Если указано имя переменной, значение сохраняется в этой переменной. В этом случае команда действует следующим образом: если элемент существует, возвращается значение 1, в противном случае команда возвращает значение 0 Увеличивает разделяемую переменную подобно команде incr Добавляет элементы к разделяемой переменной. Действует подобно команде lappend Возвращает указанный элемент разделяемой переменной. Действует подобно команде 1 index
Глава 21. Многопотоковые Tcl-сценарии 505 Продолжение табл. 21.3 tsv::linsert массив элемент индекс зна чение ? значение . . . ? tsv::llength массив элемент tsv::lock массив параметр ^параметр tsv::lpop массив элемент tsv::lpush массив элемент значение ?'индекс? tsv::lrange массив элемент первый последний tsv::lreplace массив элемент значение ?'значение ...? tsv::lsearch массив элемент ?режим? шаблон tsv::move массив прежнее_имя новое_имя tsv::names ?шаблон? tsv::object массив элемент tsv::pop массив элемент Включает элементы в разделяемую переменную. Действия данной команды можно сравнить с действиями команды 1 insert, однако в данном случае происходит изменение переменной Возвращает число элементов в разделяемой переменной. Действует подобно команде llength Осуществляет конкатенацию параметров и выполняет полученный сценарий. В процессе выполнения сценария команда блокирует доступ к указанному разделяемому массиву с помощью внутреннего мютекса Удаляет значение разделяемой переменной, определяемое индексом, и возвращает удаленное значение. По умолчанию принимается индекс, равный нулю Включает значение в позицию разделяемой переменной, определяемую индексом. По умолчанию принимается индекс, равный нулю Возвращает элементы разделяемо*! переменной, находящиеся в указанном интервале. Действует подобно команде lrange Заменяет элементы разделяемой переменной. Действия данной команды можно сравнить с действиями команды lreplace, однако в данном случае происходит изменение переменной Возвращает индекс первого из элементов разделяемой переменной, соответствующих шаблону. Действует подобно команде lsearch. Поддерживаются режимы -exact, -glob (принимается по умолчанию) и -regexp Переименовывает разделяемую переменную Возвращает список всех массивов разделяемых переменных или тех из них, которые соответствуют шаблону (соответствие шаблону устанавливается по принципу, используемому в команде glob) Создает и возвращает имя команды доступа для разделяемой переменной. Остальные tsv-команды используются как вспомогательные для команды доступа Возвращает значение разделяемой переменной и удаляет указанный элемент
506 Часть II. Расширенные средства Tcl Окончание табл. 21.3 tsv::set массив элемент ?значение? tsv::unset массив ?элемент? Задает значение разделяемой переменной. По необходимости переменная создается. Если значение не указано, возвращается текущее значение Удаляет разделяемую переменную. Если элемент не указан, удаляется весь массив Пространство имен tpool Пространство имен tpool содержит команды, предназначенные для создания пулов потоков и управления ими. Команды, принадлежащие пространству имен tpool, описаны в табл. 21.4. Таблица 21.4. Команды из пространства имен tpool tpool::create ?опции? tpool::post идентификатор_пула сценарий tpool::wait идентификатор_пула список_з аданий ?переменная? tpool::get иденгяфякагор_ пула идентификатор_задания tpool::names tpool::preserve идентификатор^пула Создает пул потоков и возвращает идентификатор пула. Опции, определяющие конфигурацию потока, описаны в табл. 21.5 Передает Tcl-сценарий указанному пулу потоков для выполнения. Возвращает идентификатор переданного задания. Данная команда блокирует выполнение потока до тех пор, пока поток из пула не станет обслуживать задание. Во время паузы продолжается обработка событий Блокирует выполнение программы до тех пор, пока одно или несколько заданий, идентификаторы которых указаны в списке, не будут завершены. Возвращает список завершенных заданий. Если указана переменная, ей присваивается список заданий, ожидающих обработки Возвращает результаты выполнения указанного задания. Предварительно команда tpool: :wait должна сообщить о том, что задание завершено. При отсутствии ошибок результатом выполнения задания является значение, возвращаемое последней выполненной командой. Ошибка при выполнении задания генерируется, в свою очередь, командой tpool: :get. Соответствующим образом устанавливаются переменные errorCode и errorlnfо Возвращает список существующих пулов потоков Увеличивает на единицу счетчик ссылок для указанного пула потоков
Глава 21. Многопотоковые Tcl-сценарии 507 Окончание табл. 21.4 tpool::release идентификатор_пула Уменьшает на единицу счетчик ссылок для указанного пула потоков. Если значение счетчика ссылок становится меньше нуля или равно нулю, пул потоков помечается для завершения Команда tpool: : create поддерживает ряд опций, предназначенных для конфигурации пулов потоков. Эти опции описаны в табл. 21.5. Таблица 21.5. Опции, используемые для настройки пулов потоков -minthreads число -maxthreads число -idletime время_в_секундах -initcmd сценарий -exitcmd сценарий Минимальное количество потоков. Если число потоков в пуле становится меньше указанного количества, создаются новые потоки. По умолчанию принимается значение, равное нулю Минимальное количество потоков. При передаче задания пулу потоков происходит следующее. Если свободные потоки отсутствуют и общее число потоков достигло максимума, поток, по инициативе которого было передано задание, блокируется до тех пор, пока какой-либо поток из пула не освободится для выполнения задания. При этом обработка событий продолжается. По умолчанию принимается значение, равное 4 Максимальное время бездействия, заданное в секундах. По истечении этого времени поток удаляется (при условии, что общее число потоков больше, чем значение опции -minthreads). По умолчанию принимается значение, равное нулю и указывающее на то, что поток может находиться в режиме бездействия как угодно долго Сценарий, который должен выполнить вновь созданный поток Сценарий, который рабочий поток должен выполнить перед завершением
Глава 22 Tclkit и Starkit Tclkit — это версия интерпретатора Tcl/Tk, предназначенная для доставки упакованных Tcl-приложений. Tclkit включает Tcl/Tk, [incr Tcl], базу данных Metakit и TclVFS. Starkit — это специальный файл, содержащий все сценарии и средства их поддержки, необходимые для выполнения Tcl-приложения. В данной главе рассматриваются вопросы упаковки приложения в пакет Starkit и его доставки. У±МСТРУМЕНТ Tclkit был создан для того, чтобы упростить процесс доставки Tcl-приложений. Tclkit представляет собой расширенный интерпретатор Tcl, включающий базу данных Metakit, объектно-ориентированную систему [incr Tcl] и виртуальную файловую систему (VFS — Virtual File System). База данных помещается в тот же пакет, что и само Tclkit-приложение, а интерфейс VFS представляет данные, содержащиеся в базе, как файловую систему. Tclkit помещает в эту базу Tcl-сценарии и расширения, необходимые для их функционирования. В результате приложение оформляется в виде одного файла, включающего средства поддержки пользовательского интерфейса и объектно-ориентированного программирования, а также базу данных и другие необходимые компоненты. Metakit — это быстродействующая база данных с поддержкой транзакций, предоставляющая программный интерфейс. Подобно TcJ, Metakit представляет собой компактную библиотеку, специально предназначенную для включения в состав приложения. Благодаря наличию Tcl-интерфейса вы можете легко выполнять различные действия с данными, хранящимися в базе Metakit. Несмотря на то что при использовании Starkit нет необходимости непосредственно программировать Metakit, в данной главе приведены общие сведения о применении Metakit для хранения данных.
Глава 22. Tclkit и Starkit 509 Пакет Starkit представляет собой файл базы данных Metakit, содержащей приложение. Благодаря интерфейсу VFS особенности хранения программ становятся прозрачными как для пользователя, так и для самого приложения. Tclkit обрабатывает Starkit-файл точно так же, как tclsh или wish. Приложение не имеет никакой информации о том, что оно было запущено из Starkit. Первоначально в состав Tclkit входили ранние версии VFS. Начиная с реализации 8.4.1 в Tcl были включены средства TclVFS. В настоящее время вы можете создавать Tclkit, используя немодифицированные исходные коды Tcl. Дистрибутивный пакет ActiveTcl включает Metakit, TclVFS и инструменты для создания файлов Starkit. Использование Tclkit Инструмент Tclkit прост в использовании. Для того чтобы начать работу с ним, надо лишь разместить версию, ориентированную на вашу платформу, в подходящей для вас позиции файловой систехмы. В Unix (например, Linux или Solaris) используется имя файла tclkit, а в Windows — tclkit.exe. На компакт-диске, прилагаемом к данной книге, находятся версии Tclkit для различных платформ. Кроме того, вы можете найти другие реализации этого инструмента на Web-странице Tclkit по следующему адресу: http: //www.equi4.com/tclkit. Приложение tclkit используется почти так же, как и tclsh. Если при запуске программы параметры были не заданы, на экране отобразится приглашение для интерактивного ввода команд. Если при запуске указан файл, tclkit, подобно tclsh, загружает его. Для того чтобы tclkit использовался так же, как wish, надо включить в сценарий следующую строку: package require Tk Несмотря на то что с помощью tclkit можно обрабатывать файлы .tcl, этот инструмент обычно используется для интерпретации файлов Starkit, имена которых оканчиваются суффиксом .kit. В системе Unix файлы Starkit включают строку, начинающуюся с символов #!. Как известно, таким образом указывается программа, которая должна использоваться для обработки файла. Начиная работу с tclkit, удостоверьтесь, что каталог, в котором находится этот файл, указан в переменной окружения PATH. В системе Windows файл tclkit.ехе связывается с расширением .kit. В Mac OS X используется подход, подобный тому, который применяется в системе Unix. В системе Мае Classic загрузка файлов .kit осуществляется с помощью меню File Source. Создание файлов Starkit рассматривается далее в этой главе.
510 Часть II. Расширенные средства Tcl Структура файла Starkit Благодаря использованию в Tclkit расширения Virtual Filesystem записи базы данных Metakit представляются как файлы и каталоги. В процессе упаковки все Tcl-сценарии и средства их поддержки собираются вместе так, что приложение представляет собой единый файл базы данных. Расширение Virtual Filesystem обеспечивает доступ к этим файлам теми же средствами, с помощью которых осуществляется работа с обычной файловой системой (команды open, gets, source и даже cd). Starkit — это база данных Metakit, содержащая приложение. Поскольку Starkit представляет собой единый файл, поддержка его обычно не составляет труда. При использовании Starkit не возникает необходимости распаковывать файлы или запускать программу инсталляции для подготовки его к работе. Для доставки приложения достаточно скопировать два файла: интерпретатор Tclkit и сам файл Starkit. Оба этих файла содержат виртуальную файловую систему, в которой организованы все средства, необходимые для работы как интерпретатора Tcl/Tk, так и приложения. Файл Tclkit ориентирован на конкретную платформу, поскольку он содержит Tcl и расширения в скомпилированном виде. Существуют версии Tclkit для Windows, Macintosh и многих разновидностей Unix. Файл Starkit не зависит от платформы. Чтобы запустить его в той или иной системе, достаточно использовать соот- огствующу) версию интерпретатора Tclkit. Доставка приложений в виде файлов Starkit Основным преимуществом Tclkit и Starkit является простота доставки приложений. Пользователю достаточно скопировать tclkit и файл Starkit в систему. Никаких специальных действий, связанных с инсталляцией приложения, выполнять не надо. Вы можете даже использовать различные версии tclkit; они не будут конфликтовать друг с другом. Если необходимости в приложении нет, пользователю достаточно удалить соответствующие файлы. Создать пакет Starkit позволяет приложение sdx. Вам надо лишь организовать набор сценариев приложения, файлов данных, графических файлов и электронных документов в виде поддерева файловой системы и вызвать sdx. В результате данная программа объединит все данные в один файл Starkit. Рассмотрение вопросов создания файлов Starkit мы продолжим далее в этой главе. По мере необходимости вы можете включить в состав Starkit двоичные расширения и организовать их динамическую загрузку. Команда load автоматически копирует разделяемые библиотеки из VFS во временный каталог, а затем загружает библиотечные процедуры из этого каталога. Времен-
Глава 22. Tclkit и Starkit 511 ные файлы необходимы, так как операционная система не может найти библиотеку в составе Starkit. Наличие двоичных библиотек превращает Starkit в илатформенно-ориентированный файл, но вы можете включать в Starkit библиотеки для различных платформ. Например, Starkit kitten.kit включает расширения для Windows, Linux и Solaris. Tclkit и Starkit можно объединить в файл Starpack. В результате процедура доставки упрощается, так как копировать приходится только один файл. Однако такой подход имеет и недостатки. В частности, размеры файлов Starpack оказываются велики, а сами файлы становятся платформенно- ориентированными. Использование sdx для создания файлов Starpack будет обсуждаться далее в этой главе. Архив Starkit включает набор файлов Starkit, которые содержат приложения, игры, инструменты доставки, Wiki, справочную информацию и различные документы. Копия архива находится на прилагаемом к книге компакт- диске. Более полную информацию об архиве Starkit вы можете получить, обратившись по следующему адресу: http://mini.net/sdarchive/. Виртуальные файловые системы Одним из основных понятий, используемых при создании Tclkit и Starkit, является виртуальная файловая система (VFS — Virtual File System). Если вы знакомы с файловой системой Unix, вы согласитесь с тем, что она построена так, что обращение к различным ее объектам (файлам, накопителям на магнитных лентах, сетевым гнездам, каналам конвейерной обработки и т.д.) производится одинаково. Другими словами, для доступа к объектам различного типа программист может использовать одинаковые функции API. Создатели интерфейса VFS преследовали подобную цель — сделать возможным использование общепринятых Tcl-средств работы с файловой системой для доступа к встроенным базам данных, FTP-серверам и zip-файлам. В Tcl 8.4 уровень VFS расположен "ниже" функций Tcl С API, предназначенных для доступа к файловой системе (например, Tcl.CreateCharmel или Tcl_FSDeleteFile). В результате команды Tcl (например, open, file, glob) и любые С-расшире- ния, которые используют соответствующие функции API, могут применяться для работы с компонентами виртуальной файловой системы, входящей в состав Starkit. Подобно обычным файловым системам, к виртуальной файловой системе применяется операция монтирования. По умолчанию точкой монтирования является файл Starkit. Например, если Starkit-файл имеет имя foo.kit, а в его виртуальной файловой системе содержится файл main.tcl, то этот файл доступен Tcl-приложению как foo.kit/main.tcl. Виртуальная файло-
512 Часть II. Расширенные средства Tcl вая система может реализовывать дерево каталогов (например, в нем могут находиться файлы foo.kit/lib/httpd.tcl или foo.kit/htdocs/help/index. html). В следующем разделе мы рассмотрим простые файлы Starkit и структуру их файловых систем. Основной подход к созданию Starkit-файлов состоит в следующем: сам файл является корнем виртуальной файловой системы, а любые компоненты этой системы доступны программам с помощью обычных Tcl-команд. Если VFS поддерживает эти команды, вы можете создавать файлы, читать их содержимое и записывать в них данные. Tclkit содержит расширение TclVFS, которое позволяет реализовывать в Tcl новые файловые системы. В большинстве случаев при работе с Starkit нет необходимости непосредственно использовать API vfs. Однако в рамках проекта TclVFS были реализованы VFS, позволяющие обращаться к Web- узлам, FTP-серверам, zip-файлам, tar-архивам и многим другим объектам, используя для этого средства, предназначенные для работы с файловой системой. Tclkit поддерживает не все указанные возможности; для выполнения некоторых действий необходимо использовать расширение TclVFS. Информацию о нем можно найти по следующему адресу: http://sourceforge.net/ projects/tclvfs. Обращение к содержимому ZIP-файла с помощью VFS В состав Tclkit входит пакет zipvf s, позволяющий монтировать сжатый ZIP-архив и читать его содержимое. В настоящее время допустимые действия с архивом ограничены лишь операциями чтения. В листинге 22.1 для обеспечения доступа к VFS используется команда vfs: :zip: :Mount. Если вы применяете другие типы VFS, поддерживаемые расширением TclVFS, следует помнить, что для каждого из них реализована своя функция API — vfs::тип_vfs ::Mount. Листинг 22.1. Организация доступа к ZIP-файлу с помощью VFS package require vfs::zip => 1.0 # Монтирование ZIP-файла в точке "xyz" vfs::zip::Mount с:/downloads/tclhttpd343.zip xyz => filecbl5a8 # Проверка содержимого glob xyz/* => xyz/tclhttpd3.4.3 # Открытие файла, содержащегося в ZIP-архиве, и чтение данных
Глава 22. Tclkit и Starkit 513 set in [open xyz/tclhttpd3.4.3/README] => rechanl6 gets $in This HTTPD is written in Tcl and Tk. Использование sdx для сборки приложений Название sdx представляет собой сокращение от Starkit Developer extension. Это приложение предназначено для создания файлов Starkit и выполнения с ними различных действий. Оно запускается из командной строки и может работать в операционных средах Unix, Windows или MacOS. Само приложение sdx реализовано в виде файла Starkit. Данное приложение находится на прилагаемом к книге компакт-диске. Дополнительную информацию о нем можно найти на Web-странице Starkit по следующему адресу: http://www.equi4.com/starkit/. Создание простого файла Starkit Создание файла Starkit сводится к созданию поддерева файловой системы, содержащего необходимые файлы, и к объединению этих файлов с помощью sdx. Если вы разместили файлы в подкаталогах kit-имя.vf s, то для создания Starkit-файла kit-имя. kit надо использовать следующую команду: sdx wrap kit-имя.kit В самых простых случаях программа sdx может сама создавать структуру каталогов. Например, если в вашем распоряжении есть Tcl-сценарий с именем hello.Tcl, вы можете преобразовать его в Starkit-файл следующим образом: sdx qwrap hello.Tcl Операция qwrap создает новый Starkit-файл с именем hello.kit, содержащий исходный сценарий hello.Tcl, который вместе с дополнительными средствами поддержки включен в состав виртуальной файловой системы. Для запуска такого Starkit-файла надо использовать приведенную ниже команду. tclkit hello.kit В системе Unix для запуска Starkit достаточно ввести имя файла. Это становится возможным потому, что в файле содержится строка, начинающаяся с символов #!, в которой указано, что для обработки этого файла должна использоваться программа tclkit. В системе Windows вы можете достичь тех же результатов, связав программу tclkit.exe с файлами, имеющими расширение .kit.
514 Часть II. Расширенные средства Tcl Просмотр содержимого Starkit-файла Выяснить состав Starkit-файла можно двумя способами. Вы можете получить список файлов, выполнив операцию sdx lsk, либо распаковать файлы из Starkit в каталог kit-имя.vf s, использовав для этого команду sdx unwrap. В листинге 22.2 показан результат применения операции sdx lsk к файлу hello.kit. Даты представлены в формате YY/MM/DD. Листинг 22.2. Выходные данные, полученные в результате обработки файла hello.kit с помощью команды sdx lsk hello.kit: dir lib/ 67 02/11/08 12:07 main.tcl hello.kit/lib: dir app-hello/ hello.kit/lib/app-hello: 43 02/11/08 12:10 hello.tcl 72 02/11/08 12:07 pkglndex.tcl Стандартная организация пакета В результате выполнения операции qwrap сценарий hello.tcl преобразуется в пакет app-hello. По необходимости sdx добавляет к сценарию hello.tcl команду package provide app-hello 1.0. Программа sdx также создает простой сценарий main.tcl, который инициализирует систему Starkit и с помощью команды package require вызывает hello.tcl. Код сценария main.tcl показан в листинге 22.3. Листинг 22.3. Основная программа пакета Starkit package require starkit starkit::startup package require app-hello При запуске Starkit содержащаяся в нем база данных Metakit монтируется как виртуальная файловая система, доступная Tcl-приложению. Tclkit загружает сценарий main.tcl, найденный в VFS. Процедура starkit: :startup изменяет auto_path, включая информацию о каталоге lib Starkit; в результате все содержащиеся в нем пакеты становятся доступными. По умолчанию приложение помещается в пакет app-kit_HM#. В листинге 22.4 показано содержимое файла pkglndex.tcl, в котором вызывается команда загрузки hello.tcl.
Глава 22. Tclkit и Starkit 515 Листинг 22.4. Сценарий pkglndex.tcl в пакете Starkit package ifneeded app-hello 1.0 \ [list source [file join $dir hello.tcl]] Значение переменной dir устанавливается средствами поддержки пакетов и представляет собой каталог, содержащий файл pkglndex. tcl. Тот факт, что каталог lib содержится в составе виртуальной файловой системы, совершенно не влияет на работу механизма поддержки пакетов. О средствах работы с пакетами см. в главе 12. Создание файла Starpack Файл Starpack содержит копию Tclkit и пакет Starkit. Для создания файла Starpacks используется программа sdx. Опция -runtime позволяет указать, какое из приложений Tclkit надо связать с пакетом Starkit. Например, для того чтобы оформить приложение hello.tcl в виде Starpack-файла для системы Windows, надо выполнить следующую команду: sdx wrap hello.kit -runtime tclkit-win32.exe Для системы Linux команда формирования пакета Starpack выглядит несколько по-другому. sdx wrap hello.kit -runtime tclkit-linux-x86 Существуют четыре варианта программы Tclkit для Windows. Во-первых, вы можете использовать zlib для автоматического сжатия Tclkit и базы данных Metakit. В этом случае в имени Tclkit присутствует последовательность символов .ирх. Во-вторых, можно создать консольный вариант программы, не включающий Тк. Такой вариант Tclkit содержит в имени символы -sh. Минимальный вариант Tclkit, tclkit-win32-sh.upx.exe, имеет размер 450 К. Размер tclkit-win32 .upx. exe несколько больше, этот файл занимает на диске 907 К. Но в любом случае вы можете создать готовое приложение, которое поместится на гибком диске. Информацию о последних версиях Tclkit можно найти по следующему адресу: http://www.equi4.com/tclkit. Использование виртуальной файловой системы пакета Starkit В листинге 22.2 представлена стандартная структура VFS пакета Starkit. Все компоненты, в том числе главное приложение, содержатся в пакете. Именно такая структура рекомендована для доставки приложений. Однако, чтобы
516 Часть II. Расширенные средства Tcl получить представление о виртуальной файловой системе, мы рассмотрим в данном разделе Starkit, не содержащий пакетов. При этом в сценарии, код которого приведен в листинге 22.3, команду package require app-hello надо заменить на команду source, применив ее к файлу hello.tcl. source hello.kit/lib/app-hello/hello.tcl Следует заметить, что такая команда будет корректно выполняться только в случае, если текущим является тот каталог, в котором содержится файл hello.kit. Для указания компонентов виртуальной файловой системы Starkit следует применять переменную starkit: :topdir. Переменная starkit: :topdir устанавливается в результате выполнения процедуры starkit: : startup. Она содержит имя файла Starkit который является корнем виртуальной файловой системы пакета. Значение переменной starkit: : topdir представляет собой абсолютный путь, поэтому компонент, заданный с ее помощью, всегда определяется однозначно. В листинге 22.5 показан код, предназначенный для работы с виртуальной файловой системой. Листинг 22.5. Код, выполняющий действия с виртуальной файловой системой package require starkit starkit::startup puts "Contents of VFS before" foreach f [glob [file join $starkit::topdir *]] { puts "[file size $f] $f" } puts "Reading data file" set in [open [file join starkit::topdir data]] set X [read $in] puts $X close $in set out [open [file join $starkit::topdir data.new w]] puts $out $X close $out puts "Contents of VFS after" foreach f [glob [file join $starkit::topdir *]] { puts "[file size $f] $f" }
Глава 22. Tclkit и Starkit 517 Создадим пакет Starkit, поместив код, приведенный в листинге 22.5, в файл raain.tcl, находящийся в каталоге write.vfs. После этого вызовем sdx так, как показано в листинге 22.6. Листинг 22.6. Создание простого пакета Starkit # Команды выполняются в оболочке Unix mkdir write.vfs cp 22_5.tcl write.vfs/main.tcl sdx wrap write.kit tclkit write.kit Если вы вызовете файл write.kit несколько раз, вы заметите, что между вызовами файл write.kit/data.new не сохраняется. Причина в том, что база данных Metakit модифицируется в памяти и результаты не записываются в файл Starkit. Если вы хотите обеспечить длительное хранение данных, вам надо указать при вызове sdx опцию -writable. sdx wrap write.kit -writable Создание tclhttpd.kit Дерево каталогов, содержащее коды Web-сервера TclHttpd, организовано так, что запускать сервер можно, не выполняя никаких действий по его инсталляции. Это позволяет создать сервер в виде пакета Starkit. Для выполнения первой версии сервера необходимо было иметь в наличии лишь исходные коды TclHttpd и копию стандартной библиотеки Tcl. Для их хранения использовались каталоги tcllibl.3 и tclhttpd3.4.3. В листинге 22.7 показано содержимое каталога tclhttpd.vf s. Листинг 22.7. Содержимое каталога tclhttpd.vfs (первая версия сервера) main.tcl tclhttpd3.4.3/bin/httpd.tcl tclhttpd3.4.3/bin/httpdthread.tcl tclhttpd3.4.3/bin/tclhttpd.re tclhttpd3.4.3/lib/ (большое количество различных файлов) tclhttpd3.4.3/htdocs/ (большое количество различных файлов) tcllibl.3 (копия /usr/local/lib/tclibl.3) В листинге 22.8 показан сценарий main.tcl, используемый для запуска Starkit. Первые две строки встречаются во всех Starkit-файлах. Команда starkit: rautoextend включает каталог tcllibl.3, в результате чего обеспечивается доступ к пакетам стандартной библиотеки Tcl. В последней строке
518 Часть II. Расширенные средства Tcl переменная starkit: itopdir используется для указания сценария запуска TclHttpd bin/httpd.tcl. Листинг 22.8. Главный сценарий Starkit TclHttpd (первая версия сервера) package require starkit starkit::startup starkit::autoextend [file join $starkit:itopdir tcllibl.3] source [file join $starkitiitopdir tclhttpd3.4.3/bin/httpd.tcl] Для создания и запуска Starkit используются приведенные ниже команды; при этом предполагается, что tclhttpd.vfs находится в текущем каталоге. Обратите внимание на опции, задаваемые в командной строке. Опция -docRoot позволяет разместить каталог htdocs за пределами Starkit. Если вы не укажете эту опцию, будет использоваться каталог htdocs, принадлежащий иерархии каталогов Starkit. sdx wrap tclhttpd.kit tclkit tclhttpd.kit -port 8080 -docRoot /my/htdocs Стандартная структура, приведенная в листинге 22.2, предполагает наличие пакетов в каталоге lib. Согласно действующим соглашениям, номера версий не указываются в именах каталогов, соответствующих пакетам. Поскольку все средства, необходимые для выполнения приложения, находятся в составе Starkit, информация о версиях не обязательна. Структура файловой системы второй версии tclhttpd.kit показана в листинге 22.9. Листинг 22.9. Содержимое каталога tclhttpd.vfs (вторая версия сервера) main.tcl bin/httpd.tcl bin/httpdthread.tcl bin/tclhttpd.rc lib/tclhttpd/pkglndex.tcl lib/tclhttpd/*.tcl (большое количество различных файлов) lib/tcllib/pkglndex.tcl lib/tcllib/* (большое количество различных подкаталогов) Главный сценарий main.tcl показан в листинге 22.10. Теперь корректировать auto_path не надо, поскольку starkit: : startup гарантирует, что каталог lib находится в файле.
Глава 22. Tclkit и Starkit 519 Листинг 22.10. Главный сценарий Starkit TclHttpd (вторая версия сервера) package require starkit starkit::startup source [file join $starkit::topdir bin/httpd.tcl] Проанализировав состав tclhttpd.vfs, нетрудно заметить, что библиотека tcllib занимает гораздо больше места, чем остальные компоненты TclHttpd. В то же время TclHttpd реально использует лишь небольшую часть модулей из tcllib. Уменьшить размеры Starkit можно, включив только те модули, которые действительно необходимы для работы сервера. Решить проблему можно и по-другому: применить Starkit tcllib.kit, который использовался бы совместно разными приложениями. Созданию разделяемых файлов Starkit посвящен следующий раздел. Создание разделяемых файлов Starkit В файлах Starkit могут храниться модули, совместно используемые различными приложениями. В качестве примера можно привести Starkit kitten.kit, содержащий около 50 популярных расширений (некоторые из них являются двоичными). Размер этого файла превышает 4 Мбайт. Файл kitten.kit можно найти на прилагаемом компакт-диске и в архиве Starkit. Для упрощения совместного использования модуля надо поместить его в соответствующий Starkit-файл. При загрузке Starkit Tclkit монтирует его виртуальную файловую систему и ищет файл main.tcl. Такие действия осуществляются как для файла, содержащего приложение, так и для разделяемых Starkit. В сценарии main.tcl вызывается процедура starkit: :startup, в результате чего каталог lib виртуальной файловой системы автоматически добавляется к auto_path. Любая библиотека, содержащаяся в подкаталогах lib, становится автоматически доступна приложению, по инициативе которого был загружен файл Starkit. Для того чтобы основной пакет Starkit и пакет, вызванный другим приложением, действовали по-разному, необходимо внести в состав пакета небольшие изменения. Так, например, если модифицированный Starkit tcllib вызывается как главное приложение, он запускает независимый модуль Wiki, описывающий API стандартной библиотеки Tcl. Будучи запущенным по инициативе другого приложения, он позволяет использовать свои библиотечные функции. Код сценария main.tcl, входящего в состав tcllib.kit, показан в листинге 22.11. Добавлять каталог tcllib в auto_path нет необходимости, поскольку виртуальная файловая система содержит как lib, так и tcllib.
520 Часть II. Расширенные средства Tcl Листинг 22.11. Сценарий main.tcl Starkit-файла стандартной библиотеки Tcl spackage require starkit if {[starkit::startup] eq "starkit"} { # Запуск приложения package require app-tcllib } else { # Настройка для использования в качестве библиотеки set vfsroot [file dirname [file normalize [info script]]] lappend auto_path [file join $vfsroot tcllib] 2 Еще одним побочным эффектом выполнения процедуры starkit: : startup является установка значения переменной starkit: :topdir. Эта переменная устанавливается единожды. Если вы загрузите другой файл Stark- its, в котором вызывается starkit: :startup, значение переменной starkit: : topdir не изменится. Описанное выше правило было установлено в Tclkit 8.4.2. В предыдущих версиях переменная starkit: :topdir модифицировалась при вызове каждого файла Starkit, поэтому при загрузке нового пакета необходимо было принимать меры для сохранения значения starkit: :topdir. Если вы загрузили файл tcllib.kit и не можете получить доступ к содержащимся в нем пакетам с помощью команды package require, проверьте сценарий main.tcl. Если значение переменной starkit: : topdir не равно starkit, но запись в нее производится, это означает, что вы имеете дело со старой версией файла. Распакуйте Starkit, измените код main.tcl так, как показано в листинге 22.11, а затем снова сформируйте пакет. Этим вы решите возникшую проблему. Процедура starkit: : startup проверяет состояние среды для выполнения приложения. Значение, возвращаемое данной процедурой, помогает сценарию main.tcl выяснить, был ли Starkit запущен как основное приложение или загружен другой прикладной программой как библиотека. В табл. 22.1 приведены значения, возвращаемые starkit:: startup. Значения в таблице расположены в том порядке, в котором они должны проверяться. Таблица 22.1. Значения, возвращаемые процедурой starkit: :startup starpack Starkit был объединен с tclkit в пакет Starpack starkit Starkit был запущен в неизменном виде unwrapped Для запуска Starkit использовались программы, распакованные из каталога VFS tclhttpd Starkit был загружен в TclHttpd plugin Starkit был загружен в дополнительный модуль броузера
Глава 22. Tclkit и Starkit 521 Окончание табл. 22.1 service Starkit был запущен в составе службы NT sourced Starkit был загружен в другой пакет Starkit Проще всего организовать использование Starkit как разделяемого файла, поместив его в один каталог с вызывающим приложением. В листинге 22.12 показано, как должен быть модифицирован Starkit TclHttpd для того, чтобы загружать Starkit tcllib, находящийся в том же каталоге. Листинг 22.12. Главный сценарий Starkit TclHttpd (третья версия сервера) package require starkit starkit::startup set dir [file dirname $starkit:rtopdir] if {![file exists [file join $dir tcllib.kit]]} { puts stderr "Please install tcllib.kit in $dir" exit 1 } source [file join $dir tcllib.kit] source [file join $starkit::topdir tclhttpd/bin/httpd.tcl] Metakit В данном разделе приводятся общие сведения о базе данных Metakit, используемой для хранения данных в файлах Starkit. В большинстве случаев необходимость непосредственно программировать Metakit не возникает, так как интерфейс VFS обеспечивает "прозрачный" доступ к данным. Однако Metakit можно использовать и как обычную базу данных. Она предоставляет гораздо больший набор возможностей по сравнению с хранением информации в файлах, но является намного менее мощным инструментом, чем полнофункциональная база данных с поддержкой SQL-запросов. Разработчик, использующий Metakit, получает в свое распоряжение простой, гибкий и эффективно реализованный API. Помещая данные приложения в таблицы Metakit, вы обеспечите постоянное их хранение. Информация может содержаться в отдельных файлах либо в Starkit-файле приложения. В данной главе рассматриваются возможности базы Metakit и приводятся примеры работы с ней. Материал, изложенный здесь, нельзя рассматривать как исчерпывающее руководство по использованию Metakit. Ниже приводятся адреса, по которым можно найти дополнительную информацию о Metakit и Tcl-интерфейсе к ней.
522 Часть II. Расширенные средства Tcl http://www.equi4.com/metakit/tcl.html http://www.equi4.com/metakit/wiki.cgi/mk4tcl http://www.markroseman.com/tcl/mktcl.html Кроме того, ряд полезных сведений об этой базе содержится на прилагаемом к книге компакт-диске. Модель данных Metakit Модель данных Metakit ориентирована на работу с таблицами. При работе с этой базой используется понятие просмотра (view). Просмотр — это набор строк, содержащих значения. Каждой строке просмотра соответствует индекс, т.е. номер этой строки. Строки нумеруются целыми числами, начиная с нуля. Элементы строк (т.е. столбцы, или поля) называются свойствами. Свойство, в свою очередь, может представлять собой просмотр. В этом случае говорят о вложенных просмотрах. Все строки, принадлежащие просмотру, имеют одинаковый набор свойств, который может динамически изменяться. Понятия просмотр, строка и свойство Metakit соответствуют понятиям таблица, строка и поле для других баз. База данных Metakit содержит один или несколько просмотров. При открытии файла Metakit с ним связывается дескриптор. Для идентификации просмотров используется выражение типа дескриптор.просмотр. Строка под номером N в составе просмотра определяется с помощью выражения дескриптор.просмотри. Определенная таким образом позиция внутри просмотра называется курсором. Для создания переменных курсора и выполнения действия с ними предусмотрен ряд операций. Если свойство представляет собой вложенный просмотр, вы можете указать строку под номером М во вложенном просмотре следующим образом: дескриптор.просмотр.!N.вложенный_ просмотр\М. Обращение к базе данных Metakit Ознакомление с базой данных Metakit лучше всего начать с открытия Starkit-файла и просмотра содержащейся в нем базы. Варианты команды mk: :file реализуют ряд операций. Операция open открывает базу данных и связывает с ней дескриптор. Операция views предоставляет сведения о просмотрах, которые содержатся в базе данных, идентифицируемой с помощью дескриптора. Операция close завершает текущие модификации базы. Остальные операции mk: rfile используются для управления поведением базы, а также для сохранения базы данных во внешнем файле и извлечения ее. Пример открытия базы данных Metakit и получения информации о содержащихся в ней просмотрах приведен в листинге 22.13.
Глава 22. Tclkit и Starkit 523 Листинг 22.13. Получение сведений о просмотрах базы данных Metakit package require Mk4tcl => 2.4.8 mk::file open tclhttpd tclhttpd.kit => tclhttpd mk::file views tclhttpd => dirs Команда mk: :view реализует операции, позволяющие выполнять различные действия с просмотрами. Операция layout предоставляет информацию о свойствах просмотра или устанавливает эти свойства. Если при вызове команды mk: :view layout указан просмотр, возвращаются лишь свойства данного просмотра. Каждому свойству соответствует тип. Вложенные просмотры представляются как список, содержащий имя свойства и вложенный список свойств. Если при вызове mk: :view layout указаны свойства, то в результате выполнения этой операции для просмотра определяются новые свойства. Таким способом можно добавлять или удалять свойства существующих строк. В листинге 22.14 показан пример использования операции mk::view layout для получения сведений о просмотре dirs базы данных, принадлежащей Starkit-файлу. Свойство files представляет собой вложенный просмотр. Использование вложенного просмотра очень удобно для представления иерархической структуры каталогов. В этом же листинге приведен пример использования команды mk: :get для получения значения свойства tclhttpd.dirs!0.iiles!0. Как видно в листинге, это имя первого файла в первом каталоге. Листинг 22.14. Извлечение данных из просмотра базы данных Metakit mk::view layout tclhttpd.dirs => name parent:I {files {name size:I date:I contents:B}} mk::view size tclhttpd.dirs => 48 mk::get tclhttpd.dirs!0 => name <root> parent -1 mk::get tclhttpd.dirs!1 => паше tcllibl.3 parent 0 mk::get tclhttpd.dirs!1 name => tcllibl.3 mk::get tclhttpd.dirs!0.files!0 name => main.tcl Очевидно, что при выполнении реального приложения необходимо выполнять операцию поиска определенных значений в просмотрах. Команда
524 Часть II. Расширенные средства Tcl mk: :select возвращает номера тех строк, которые удовлетворяют заданным критериям. Если критерий не указан, возвращаются номера всех строк. Вы можете организовать проверку нескольких свойств и задать тип сравнения. Например, вы можете задать сравнение чисел и указать, что при сравнении должны использоваться регулярные выражения или учитываться минимальное и максимальное значение. В листинге 22.15 показаны два варианта вызова команды mk::select. Процедура KitWalk организует перебор файлов в каталоге, который задается с помощью выражения $tag.dirs!$dir.files. Затем эта процедура запрашивает индексы строк просмотра $tag.dirs при условии, что свойство parent равно $dir, и рекурсивно вызывает саму себя для обработки подкаталогов. Как видно в листинге, процедура KitWalk выполняет те же действия, что и операция sdx lsk. Листинг 22.15. Выбор данных с помощью команды mk: :select proc KitWalk {tag dir {indent 0}} { set prefix [string repeat " " $indent] puts "$prefix[mk::get $tag.dirs!$dir name]/" incr indent 2 # Перебор файлов в каталоге (если они присутствуют) foreach j [mk::select $tag.dirs!$dir.files] { puts "$prefix [mk::get $tag.dirs!$dir.files!$j name]" } # Рекурсивная обработка каталогов; $dir - родительский каталог foreach i [mk::select $tag.dirs parent $dir] { KitWalk $tag $i $indent } } proc Kitlnit {starkit} { mk::file open starkit $starkit if {[mk::file views starkit] != "dirs"} { mk::file close $starkit error "This database is not a starkit" } return starkit } proc KitTest {} {
Глава 22. Tclkit и Starkit 525 set tag [Kitlnit tclhttpd.kit] KitWalk $tag 0 } Создание просмотров Metakit Для того чтобы создать просмотр Metakit, не приходится затрачивать много усилий. В примере, приведенном в листинге 22.16, открывается файл базы данных mydb.tkd и создается просмотр test, содержащий три свойства: name, blob и i. Если файл отсутствует, он автоматически создается. Если просмотр test отсутствует, от также создается. Если просмотр имеется в наличии, в него включаются новые свойства. Тип свойства name не указан явно. По умолчанию принимается, что данному свойству соответствует строка, оканчивающаяся нулевым символом. Свойство blob содержит двоичные значения (В), т.е. в этом свойстве могут храниться любые данные, в том числе нулевые символы. Типом свойства i является 32-разрядное целое число (I). Кроме указанных выше типов, вы можете также задавать 64-битовое целое число (L), 32-битовое число с плавающей точкой (F), 64-битовое число с плавающей точкой двойной точности (D) и строку, заканчивающуюся нулевым символом (S). Последний тип является типом по умолчанию; указывать его не обязательно. Листинг 22.16. Создание нового просмотра mk::file open mydb mydb.tkd => mydb mk::view layout mydb.test {name blob:В i:I} => mydb.test mk::file close mydb Команда mk: : set устанавливает значения свойств, а команда mk: : row позволяет модифицировать строки. В листинге 22.17 показаны действия, выполняемые при добавлении значений к просмотру test. Вы можете включать значения в строки, номера которых превышают номер последней строки просмотра; при этом размеры просмотра автоматически увеличиваются. Если вы укажете лишь часть свойств строки, остальным свойствам присваиваются значения по умолчанию. Кроме операций, указанных в листинге 22.17, с помощью команды mk: :row реализуются также операции insert, replace и delete. Листинг 22.17. Добавление данных к просмотру mk::set mydb.test!О name hello => mydb.test 10
526 Часть II. Расширенные средства Tcl шк::get mydb.test!О => hello {} О mk::row append mydb.test "line two" 0x0 65 => mydb.test!1 mk::view size mydb.test => 2 mk::set mydb.test!100 i 1234 => mydb.test!100 mk::view size mydb.test => 101 Сохранение данных в Starkit-файле Ваше приложение может создавать в составе Starkit новые просмотры и использовать их для постоянного хранения данных. При этом нельзя забывать, что, оформляя приложение в виде пакета, надо указывать опцию -writable. В процессе работы приложение может определить имя Starkit- файла посредством $starkit: : topdir, а затем создать в нем новый просмотр. Необходимо помнить, что в файлах Starkit просмотр dirs используется для размещения файлов, поэтому хранить данные надо в просмотрах с любыми другими именами. Фрагмент кода, приведенный в листинге 22.18, записывает данные в просмотр audit при каждом запуске приложения. Код, приведенный в этом листинге, ищет существующий дескриптор базы данных Metakit, открытой по инициативе Tclkit. Команда vfs: rfilesystem info возвращает список имен VFS и дескриптор открытой базы Metakit. Дескриптор извлекается из списка и сохраняется в переменной $db. Описанные здесь действия очень важны, так как дважды открыв одну и ту же базу данных Metakit, можно повредить содержащуюся в ней информацию. Листинг 22.18. Сохранение данных в Starkit-файле package require starkit starkit::startup set db [lindex [vfs:rfilesystem info [$starkit::topdir]] 1] mk::view layout $db.audit {action timestamp:I} mk::row append $db.audit "Run as pid [pid]" [clock seconds] puts "$argv0 has been run [mk::view size $db.audit] times" Чтобы проверить работу данного кода, поместите его в сценарий main.tcl обычного Starkit-файла. Создавая Starkit-файл, надо обязательно указать при вызове sdx опцию -writable. mkdir bundle.vfs
Глава 22. Tclkit и Starkit 527 ср 22_18.tcl bundle.vfs/main.tcl sdx wrap bundle.kit -writable- Wikit и Wiki Альтернативой хранению данных в Starkit-файле является размещение их в отдельном файле данных Metakit. Такой подход используется при работе Wikit. Приложение Wikit реализовано в виде файла wikit.kit, а файл wikit. tkd представляет собой базу данных Metakit, в которой хранятся Web- страницы Wiki. (Создать новую копию Wiki несложно: достаточно задать другое имя файла .tkd.) Преимуществом хранения данных в отдельном файле Metakit является удобство поддержки приложения. Вы можете распаковывать приложение и снова оформлять его в виде Starkit-файла, не затрагивая данные. Если бы информация хранилась в Starkit-файле, ее пришлось бы извлечь, а затем снова запаковать. Wiki представляет собой Web-узел, который пользователи могут модифицировать, используя упрощенные средства разметки. Реализацией Wiki на языке Tcl является Wikit; этот продукт использует для хранения Web-страниц базу данных Metakit. Wikit можно запускать как независимое Тк-приложение, GGI-сценарий, отдельный Web-сервер или встраивать в другое приложение как набор документов. Копия wikit .tkd находится на компакт-диске, прилагаемом к данной книге. Независимую копию Wiki можно запустить с помощью команды tclkit wikit.kit wikit.tkd Информацию о Wiki можно получить, обратившись по следующему адресу: http://wiki.tcl.tk/wikit Особенности применения Starkit-файлов В данной главе были приведены общие сведения о Tclkit, Starkit и Metakit. Этих сведений достаточно для того, чтобы вы могли создавать простые Starkit-файлы и использовать Metakit для хранения данных. Более подробная информация содержится в документах, доступных через Web. Наборы документов В архиве Starkit содержится большое число наборов документов. Например, файл mk4dok.kit содержит документацию на MetaKit. Все документы созданы на базе Wikit. По необходимости вы можете без труда создать документы в стиле Wiki для вашего приложения, а затем оформить их как файл Metakit. Файл wikit.kit вместе с любым набором документов .tkd можно
528 Часть II. Расширенные средства Tcl загрузить в приложение и использовать "локальный" интерфейс Wikit для отображения документации. Например, Starkit critcl отображает подсказку с помощью следующей простой команды: Wikit::init [file join $::starkit::topdir doc critcl.tkd] Самообновляющиеся приложения Клиенты, входящие в состав приложений клиент/сервер, как нельзя лучше подходят на роль самообновляющихся программ. Базовые средства клиента могут быть реализованы в виде Starkit-файла, который устанавливает соединение с сервером по протоколу HTTP. Сервер (его роль может выполнять TclHttpd) передает клиенту новые варианты его компонентов. Клиент кэширует код в виртуальной файловой системе Starkit. Таким образом приложение поддерживается на сервере, а клиенты лишь получают готовые компоненты. Данный принцип взаимодействия напоминает работу приложения, интерфейс которого создан на базе Web-браузера. На клиентской машине устанавливаются основные компоненты, которые не модифицируются (если модификация имеет место, это происходит редко), а основные изменения затрагивают элементы, находящиеся на стороне сервера. По такому принципу были созданы многие реальные коммерческие приложения, которые пользовались успехом у потребителей. Система, подобная описанной выше, используется для работы с архивом Starkit. Предположим, вы вызвали следующую команду: sdx update tclhttpd.kit Приложение sdx установит соединение с Web-сервером, на котором поддерживается архив, и выполнит поиск обновлений для Starkit. При этом по сети передаются только компоненты, претерпевшие изменения, поэтому обновление происходит быстро. При желании вы можете организовать обновление Starkit-файлов посредством архива, хранящегося на компакт-диске. Простые инсталляторы В некоторых случаях бывает необходимо установить набор файлов как часть вашего приложения. Эти файлы можно включить в виртуальную файловую систему, а затем при первом запуске приложения извлечь их в локальную файловую систему. Подобным образом вы можете реализовать традиционный "инсталлятор", который полностью распаковывал бы приложение, содержащееся в Starkit или Starpack.
ЧАСТЬ \\\ Основы Тк В части III рассматривается набор инструментальных средств Тк, предназначенных для создания графических пользовательских интерфейсов. Тс 1-команды, ориентированные для работу с Тк, позволяют решать задачи формирования интерфейсов, не прилагая больших усилий. Тк-программы являются переносимыми; они могут работать на платформах Unix, Windows и Macintosh. В главе 23 описываются понятия, лежащие в основе работы Тк, и рассматриваются возможности, предоставляемые этим набором инструментов. В главе 24 представлены три программы, демонстрирующие работу с Тк, в том числе браузер, который можно использовать для проверки некоторых примеров, приведенных в данной книге. При создании программ, рассматриваемых в данной главе, применены средства, подробно описанные в последующих главах. Внешний вид пользовательского интерфейса определяется особенностями применяемого диспетчера компоновки. В главах 25-27 рассматриваются соответственно диспетчеры компоновки pack, grid и place. Диспетчеры компоновки pack и grid являются диспетчерами общего назначения. Они позволяют с помощью небольших фрагментов кода создавать достаточно мощные и гибкие интерфейсы. Диспетчер компоновки place является специальным инструментом, применяемым для создания специфических эффектов. В главе 28 описывается компонент panedwindow, который также является диспетчером компоновки. В главе 29 рассматривается связывание Tcl-команд с различными событиями. В качестве примеров событий можно привести нажатие клавиши на клавиатуре или перемещение мыши.
Глава 23 Общие сведения о Тк В данной главе рассматриваются базовые понятия, лежащие в основе работы набора инструментальных средств Тк. В Тк реализовано около 45 Tcl-команд, которые позволяют создавать интерфейсные компоненты и выполнять различные действия с ними. Tk-программы могут выполняться в системах X Window, Windows и Macintosh. При переносе сценария с одной платформы на другую вносить в него изменения не требуется. — это набор инструментальных средств, предназначенных для программирования графических пользовательских интерфейсов. Он был разработан для системы X Window, используемой в среде Unix, а впоследствии был перенесен на платформы Macintosh и Windows. В Тк действуют многие из понятий, лежащих в основе других оконных систем, однако, для того чтобы решать простые задачи с помощью Тк, не обязательно обладать специальными знаниями о графических пользовательских интерфейсах. Тк предоставляет разработчику набор Tcl-команд, предназначенных для создания компонентов и выполнения различных действий с ними. Компонент представляет собой окно в составе графического интерфейса, имеющее определенный внешний вид и выполняющее некоторые функции. В принципе термины компонент и окяо настолько близки, что они могут использоваться один вместо другого. Компоненты бывают различных типов. Чаще других используются кнопки, полосы прокрутки, меню и текстовые окна. В Тк также реализован компонент общего назначения под названием холст (canvas), который позволяет выводить простые рисунки и растровые изображения. Возможности холста достаточно велики, при этом работать с ним очень просто. Tcl-команды, реализованные в Тк, описаны в конце данной главы.
Глава 23. Общие сведения о Тк 531 Компоненты организованы в иерархическую структуру. С точки зрения приложения существует главное, или первичное, окно, в котором создаются дочерние окна. Дочерние окна, в свою очередь, могут выступать в качестве родительских по отношению к другим окнам и т.д. Подобно тому, как в иерархической файловой системе каталоги выступают в качестве контейнеров для файлов и других каталогов, так и в оконной системе некоторые окна играют роль контейнеров для других окон. Иерархия окон влияет на принцип именования компонентов Тк и позволяет упорядочивать окна на экране. (Схема именования компонентов будет рассмотрена далее в этой главе.) Компонентами управляют диспетчеры компоновки (geometry manager), которые определяют размеры компонентов и их размещение на экране. До тех пор пока диспетчер компоновки не получит сведения о компоненте, для последнего не будет выделено пространство на экране и пользователь не увидит его. В Тк содержатся мощные диспетчеры компоновки, которые существенно упрощают размещение компонентов на экране. В основу работы любого диспетчера компоновки положен тот факт, что одни компоненты, называемые фреймами, выступают в роли контейнеров для других компонентов. Помещая фреймы в другие фреймы, можно реализовать сложные экранные формы. В Тк реализованы три диспетчера компоновки: grid, pack и place. Кроме того, компонент panedwindow также может рассматриваться как диспетчер компоновки. Диспетчеры компоновки подробно рассматриваются в главах 25-27, а компонент panedwindow — в главе 28. Tk-приложения, как и большинство оконных приложений, представляют собой программы, управляемые событиями. Компоненты Тк автоматически обрабатывают большинство событий, что упрощает задачу разработчиков по созданию приложений. Чтобы реализовать более сложное поведение интерфейса, надо связать с помощью команды bind с событием одну или несколько Tcl-команд. При работе Tk-приложения могут возникать самые различные события, например, перемещение мыши, изменение размеров окна, нажатие клавиши на клавиатуре, удаление окна и т.д. Кроме того, вы можете определять виртуальные события, например Cut или Paste; на разных платформах им будут соответствовать различные события. Связывание команд с событиями подробно рассматривается в главе 29. В главе 16 обсуждались события ввода-вывода и цикл обработки событий Tcl, а в главе 50 будут описаны особенности этого цикла в программах на С. Связи между событиями и командами объединяются в классы, называемые дескрипторами связей (bindtag). Команда bindtags связывает компонент с упорядоченным наборов дескрипторов связей. Возможность использования косвенных связей между обработчиками события и компонентами, генерирующими их, позволяют создавать гибкие и мощные системы. По необ-
532 Часть III. Основы Тк ходимости вы можете создавать свои дескрипторы связей и динамически изменять их, влияя тем самым на поведение приложения. Понятие фокуса имеет непосредственное отношение к связыванию событий с обработчиками. В каждый момент времени тот или иной компонент имеет фокус ввода, и события клавиатуры направляются этому компоненту. Существуют два основных способа назначения фокуса: передача его компоненту, на котором расположен курсор мыши, и явная передача фокуса конкретному компоненту. Тк предоставляет команды для обработки фокуса ввода, поэтому вы можете реализовать любой из способов управления фокусом. Для реализации модальных диалоговых окон приходится перехватывать фокус. Действия с фокусом, перехват его и организация диалоговых окон подробно рассматриваются в главе 39. Как правило, выполнение Tk-сценария начинается с создания компонентов и размещения их с помощью диспетчера компоновки, после чего сценарий связывает обработчики событий с компонентами. После того как интерпретатор оканчивает разбор команд, ответственных за инициализацию пользовательского интерфейса, он переходит в цикл обработки событий. С этого момента приложение начинает реагировать на действия пользователя. Программу wish можно запустить для интерактивной работы. В этом режиме она создает и отображает пустое главное окно, а также выводит подсказку для ввода команды. Команды, вводимые с клавиатуры, обрабатываются в цикле событий, что позволяет постепенно, шаг за шагом, формировать интерфейс Tk-приложения. Как вы вскоре убедитесь, Тк позволяет изменить в интерактивном режиме почти все характеристики приложения. Tk-программа "Hello, World!" Первый рассматриваемый нами Тк-сценарий очень прост. При его выполнении создается кнопка, после щелчка на которой в стандартный выходной поток записывается последовательность символов "Hello, World!". Над кнопкой размещается строка заголовка, которую формирует оконный диспетчер. Поскольку данный пример запускался в среде X Window, в качестве оконного диспетчера использовался twm. Листинг 23.1. Tk-программа "Hello, World!" #!/usr/local/bin/wish button .hello -text Hello \ -command {puts stdout "Hello, World!"} pack .hello -padx 20 -pady 10
Глава 23. Общие сведения о Тк 533 Первая строка идентифицирует интерпретатор, который должен использоваться для обработки сценария. #!/usr/local/bin/wish Если вы хотите, чтобы данный сценарий запускался подобно другим командам Unix, эта строка необходима. Об особенностях выполнения сценариев на различных платформах см. в главе 2. Сценарий, код которого приведен в листинге 23.1, содержит две Тс1-ко- манды: одна из них формирует кнопку, в другая делает ее видимой на экране. Для создания экземпляра кнопки предназначена команда button. button .hello -text Hello \ -command {puts stdout "Hello, World!"} => .hello Созданная кнопка имеет имя .hello. Последовательность символов Hello представляет надпись на кнопке. Кроме того, с кнопкой связывается следующая команда: puts stdout "Hello, World!" Команда pack размещает кнопку на экране. При вызове этой команды указаны опции, управляющие заполнением, поэтому вокруг кнопки выделяется свободное пространство. pack .hello -padx 20 -pady 10 Если вы введете эти две команды в командной строке программы wish, вы будете наблюдать следующее. После выполнения команды button на экран не будут выводиться никакие данные. После того как вы введете команду pack, размеры главного окна изменятся так, чтобы вместить кнопку и пустое пространство вокруг нее. Действия диспетчера компоновки pack будет обсуждаться в главах 24 и 25. Для создания и именования компонентов в Тк используется объектная система. С каждым классом компонентов (например, классом Button) связывается команда, предназначенная для создания экземпляра этого класса. После того как компонент создан, определяется новая Tcl-команда, которая обрабатывает конкретный экземпляр компонента. В листинге 23.1 формируется кнопка .hello, и мы получаем возможность выполнять действия с этой
534 Часть III. Основы Тк кнопкой, используя ее имя как Tcl-команду. Например, мы можем указать способ подсвечивания, имитирующий мигание кнопки. .hello flash Мы также можем непосредственно вызвать команду, связанную с кнопкой. .hello invoke => Hello, World! Tk действует с классами и экземплярами компонентов, но не является в полном смысле слова объектно-ориентированным. Так, например, невозможно создать подкласс класса компонента и использовать наследование. Вместо этого Тк предоставляет очень гибкие компоненты, которые можно настраивать различными способами для получения требуемого эффекта. Конфигурационные данные могут содержаться в базе ресурсов и совместно использоваться разными компонентами. Одинаковое поведение различных компонентов достигается за счет применения дескрипторов связывания. Вместо создания иерархии классов в Тк используются наборы компонентов с одинаковыми атрибутами и сходным поведением. Именование компонентов Тк Точка, находящаяся в начале имени кнопки .hello, обязательно должна присутствовать. В Тк принята система именования компонентов, отражающая их расположение в иерархии. Корнем иерархии является главное окно приложения. Его имя состоит из одной точки. В начале имени компонента, дочернего по отношению к главному окну, указывается точка (например, .foo). Компонент bar, дочерний по отношению к .foo, обозначается как .foo.bar и т.д. Такой подход можно сравнить с принципом именования элементов файловой системы Unix, где корневой каталог обозначается символом /, а затем тот же символ используется для разделения частей имени файла. Аналогичную роль в именах компонентов Тк выполняет точка. Подобно тому, как в файловой системе каталоги являются контейнерами для файлов и других каталогов, так и в иерархии окон Тк компоненты-фреймы выступают в роли контейнеров для компонентов и других фреймов. Каждый элемент в составе пути к Tk-компоненту должен начинаться со строчной буквы или с цифры. Очевидно, что в именах конкретных компонентов не может присутствовать точка. Применение символов нижнего регистра позволяет избежать конфликтов с именами классов ресурсов, которые по соглашению начинаются с прописной буквы. Имена ресурсов могут включать
Глава 23. Общие сведения о Тк 535 элементы пути к компонентам Тк, и использование регистров символов позволяет различать их. Вопросы работы с классами ресурсов подробно описаны в главе 31. Имена компонентов желательно хранить в переменных. Система именования компонентов Тк имеет существенный недостаток. Изменения интерфейса могут повлиять на позицию компонента в иерархии. В этом случае приходится изменять его имя. Смягчить негативные последствия возникновения подобной ситуации можно, используя для хранения важных компонентов переменные. Команда, с помощью которой создается компонент, возвращает его имя. set b [button .hello -text "Hello" -command {puts "Hello!"}] Для выполнения операций с кнопкой вы можете использовать выражение $Ь. $b configure -background green Настройка компонентов Tk Код, приведенный в листинге 23.1, демонстрирует принцип использования именованных параметров в Tk-командах. Атрибуты компонентов задаются с помощью опций и их значений. Имя опции представляет собой имя атрибута, которому предшествует символ -; за опцией следует значение атрибута. Даже в самых простых Tk-компонентах предусмотрено около десяти атрибутов, которые задаются описанным выше способом, а сложные компоненты насчитывают более 30 атрибутов. Однако при создании интерфейса с помощью Тк разработчик должен задавать лишь те атрибуты, значения которых, принимаемые по умолчанию, не устраивают его. Этим обусловлена предельная простота примера "Hello, World!". Каждый экземпляр компонента поддерживает операцию configure (ее имя можно сокращать до config), которая позволяет определять текущие значения атрибутов и изменять их. При указании операции config задаются такие же пары опция-значение, как и при создании компонента. Например, для того чтобы изменить цвет кнопки на красный, надо выполнить приведенную ниже команду. Сделать это можно после того, как кнопка создана и отображена на экране. .hello config -background red С помощью операции config вы можете переопределить любые атрибуты, даже атрибуты text и command, которые задаются при создании компонента. Приведенная ниже команда превращает кнопку Hello в кнопку Goodbye.
536 Часть III. Основы Тк .hello config -text Goodbye! -command exit Для компонентов Tk предусмотрена операция cget, позволяющая определить текущее значение атрибута. .hello cget -background => red Подробную информацию об атрибуте можно получить, задавая при вызове операции configure соответствующую опцию, но не указывая ее значение. .hello config -background => -background background Background #ffe4c4 red Возвращаемое значение содержит опцию командной строки, имя ресурса, имя класса, значение по умолчанию и текущее значение. Имена классов и ресурсов, а также другие вопросы функционирования механизма ресурсов рассматриваются в главе 31. Если при вызове configure вы не укажете ни одной опции, то получите информацию обо всех атрибутах компонента. Этот способ получения сведений о компоненте используется в процедуре, код которой приведен в листинге 23.2. Листинг 23.2. Получение информации об атрибутах компонента proc Widget_Attributes {w {out stdout}} { puts $out [format '7,-20s °/0-10s °/0s" Attribute Default Value] foreach item [$w configure] { puts $out [format "e/.-20s °/0-10s °/0s" \ [lindex $item 0] [lindex }item 3] \ [lindex $item 4]] } } Атрибуты компонентов Tk и база ресурсов К атрибуту компонента можно обратиться тремя различными способами: с помощью опции командной строки, по имени ресурса и посредством класса ресурса. Формат опций командной строки был рассмотрен выше. Имя опции формируется путем добавления дефиса (-) к имени атрибута (например, -offvalue). Имя ресурса для атрибута не содержит дефиса, и внутри имени каждое слово начинается с прописной буквы (например, off Value). Имя класса ресурсов начинается с прописной буквы, и каждое слово внутри имени также начинается с прописной буквы (например, Off Value).
Глава 23. Общие сведения о Тк 537 В таблицах, приведенных в данной книге, для обозначения атрибутов компонентов используются имена ресурсов. Если вы собираетесь использовать для работы с атрибутами компонентов средства поддержки ресурсов, вам необходимо знать соглашения об именовании. Опция командной строки формируется из имени ресурса путем преобразования символов в нижний регистр. Основное преимущество использования ресурсов для работы с атрибутами состоит в том, что вам не придется "засорять" код сценария фрагментами, предназначенными для определения атрибутов. Ограничившись несколькими записями базы ресурсов, вы сможете задать атрибуты для всех компонентов. Кроме того, если атрибуты определяются с помощью ресурсов, пользователь может указать альтернативную спецификацию ресурса и переопределить значения, заданные в приложении. Чаще всего этой возможностью пользуются для переопределения цвета и начертаний шрифтов. Описания ресурсов приведены в главе 31. Справочная информация Тк В данной книге приводятся основные сведения обо всех командах Тк, атрибутах компонентов, а также о связях обработчиков событий с атрибутами. Однако чтобы быть абсолютно уверенными, что данные, имеющиеся в вашем распоряжении, верны, вам стоит ознакомиться со справочной информацией, поставляемой в составе Тк. В ней приведены исчерпывающие описания Тк- команд. Для просмотра их можно использовать команду man системы Unix. % man button По команде tkman пользователь получает в свое распоряжение удобный графический интерфейс, с помощью которого отображаются страницы справочной системы Unix. На платформе Macintosh страницы справочной системы форматируются как HTML-документы. Они расположены в папке HTML Docs, поставляемой в составе дистрибутивного пакета Tcl/Tk. В Windows справочная информация представляется в таком же формате, как и справочные данные самой системы и большинства приложений. Руководства но работе с Тк можно найти в Web по адресу http://www.tcl.tk/man/ Многие атрибуты являются общими для большинства компонентов Тк. Они описаны в отдельном документе справочной системы options. Каждая страница начинается с раздела STANDARD OPTIONS, в котором указано, какие из стандартных атрибутов применимы к компоненту. В таблицах, приведенных в данной книге, всегда указывается полный набор атрибутов компонента.
538 Часть III. Основы Тк Команды Тк Приведенные в данном разделе таблицы содержат список команд, реализованных в Тк. Для каждой из них указан номер главы, в которой можно получить более полную информацию. Команды для создания компонентов В табл. 23.1 описаны команды, посредством которых создаются компоненты. В Тк содержится 18 компонентов, однако четыре из них представляют собой разновидности кнопок и еще пять предназначены для отображения текста. Таблица 23.1. Команды, предназначенные для создания компонентов Команда Глава Описание button 30 Создает кнопку canvas 37 Создает компонент холст (canvas), который поддерживает линии, прямоугольники, битовые карты, изображения, дуги, текст, многоугольники и встроенные компоненты checkbutton 30 Создает кнопку-переключатель, связанную с Тс1-пере- менной entry 34 Создает компонент, предназначенный для ввода и редактирования одной строки текста frame 32 Создает компонент-контейнер, используемый совместно с диспетчером компоновки label 32 Создает многострочную текстовую метку. С ее помощью отображается текст, предназначенный только для чтения labelframe 32 Создает компонент, выполняющий функции контейнера и используемый совместно с диспетчером компоновки. Этот компонент имеет дополнительные атрибуты по сравнению с обычной меткой (Тк 8.4) listbox 35 Создает текстовый компонент, ориентированный на работу со строками, предоставляющий возможности прокрутки menu 30 Создает меню menubutton 30 Создает кнопку, управляющую отображением меню message 32 Создает многострочное текстовое сообщение, предназначенное только для чтения panedwindow 28 Создает контейнер, способный управлять другими компонентами (Тк 8.4)
Глава 23. Общие сведения о Тк 539 Окончание табл. 23.1 Команда Глава Описание radiobutton 30 Создает набор переключателей опций (кнопок с зависимой фиксацией), связанных с одной переменной scale 32 Создает линейный регулятор, с помощью которого можно изменять значение переменной scrollbar 33 Создает полосу прокрутки, которая может быть связана с другим компонентом spinbox 34 Создает инкрементный регулятор — составной компонент, который содержит поле редактирования и кнопки, предназначенные для изменения значений в поле (Тк 8.4) text 36 Создает текстовый компонент общего назначения с возможностью редактирования topi eve 1 32 Создает фрейм, который может выполнять функции нового окна верхнего уровня Команды для выполнения действий с компонентами Команды, приведенные в табл. 23.2, предназначены для выполнения различных действий с компонентами. Они также обеспечивают управление фокусом ввода и связывание обработчиков событий. Кроме того, некоторые команды реализуют диспетчеры компоновки. Таблица 23.2. Команды для выполнения действий с компонентами Команда Глава Описание bell 32 Воспроизводит звуковой сигнал на терминале bind 29 Связывает Tcl-команду с событием bindtags 29 Создает классы связывания и управляет наследованием связывания clipboard 38 Управляет буфером обмена destroy 39 Удаляет компонент event 29 Определяет и генерирует виртуальные события focus 39 Обеспечивает управление фокусом ввода font 42 Запрашивает или устанавливает атрибуты шрифта grab 39 Перехватывает фокус ввода, не позволяя другим компонентам получить его
540 Часть III. Основы Тк Окончание табл. 23.2 Команда Глава Описание grid 26 Размещает компоненты в виде таблицы и накладывает ряд дополнительных ограничений на расположение компонентов image 41 Создает изображения и выполняет с ними различные действия lower 25 Перемещает окно вниз по стеку option 31 Создает базу данных ресурсов и формирует запросы к ней pack 25 Компонует содержимое компонента place 27 Размещает компоненты на экране с указанием позиций raise 25 Перемещает окно вверх по стеку selection 38 Выполняет действия над выделенными данными send 43 Передает Tcl-команду другому Тк-приложению tk 44 Запрашивает или устанавливает имя приложения tkerror 13 Обработчик ошибок tkwait 39 Ожидание события update 39 Обновляет отображаемые данные путем обработки цикла событий winf о 44 Запрашивает состояние окна wm 44 Поддерживает взаимодействие с диспетчером окон Процедуры поддержки В табл. 23.3 перечислены процедуры, с помощью которых реализуются стандартные типы диалогов, поддерживаются пункты меню и выполняются некоторые другие действия. Таблица 23.3. Процедуры поддержки Тк Команда Глава Описание tk_bisque 41 Устанавливает семейство цветов tk.chooseColor 39 Реализует диалоговое окно для выбора цвета (Тк 4.2) tk_chooseDirectory 39 Реализует диалоговое окно для выбора каталога (Тк 8.2) tk_dialog 39 Создает простые диалоговые окна
Глава 23. Общие сведения о Тк 541 Окончание табл. 23.3 Команда Глава Описание tk_f ocusFollowsMou.se 39 Устанавливает модель, согласно которой фокус ввода определяется по положению курсора мыши tk_f ocusNext 39 Передает фокус ввода следующему компоненту tk.focusPrev 39 Передает фокус ввода предыдущему компоненту tk_getOpenFile 39 Создает диалоговое окно, позволяющее открыть существующий файл (Тк 4.2) tk_getSaveFile 39 Создает диалоговое окно, позволяющее открыть новый файл (Тк 4.2) tk_messageBox 39 Создает окно для отображения сообщения (Тк 4.2) tk_optionMenu 30 Создает меню для работы с опциями tk_popup 30 Создает контекстное меню tk_setPalette 41 Устанавливает цветовую палитру (Тк 4.2) Наборы компонентов В данной книге описывается набор компонентов, поставляемых в базовом дистрибутивном пакете Тк. Помимо него, разработчик может также использовать другие наборы компонентов. Некоторые из них реализованы как Tcl-процедуры, объединяющие стандартные компоненты (в качестве примера можно привести В Widgets). Другие представляют собой наборы инструментов, реализованные на языке С (например, Tix и BLT). Некоторые из наиболее популярных наборов компонентов описаны ниже. BLT Набор BLT содержит компоненты, с помощью которых можно реализовать эффективную поддержку наборов данных. Кроме того, BLT включает компоненты, предназначенные для реализации блокнота со вкладками и представления данных в виде древовидной структуры. В состав набора входит также "прозрачный" компонент, отображающий курсор просмотра. Его удобно использовать в тех случаях, когда приложение выполняет некоторые действия и не обрабатывает щелчки мышью. Данный набор реализован на языке С. Адрес его Web-страницы приведен ниже. http://www.sourceforge.net/proj ects/blt/
542 Часть III. Основы Тк Tix Tix включает ряд компонентов, а также реализует инфраструктуру для создания новых компонентов средствами Tcl. В нем реализованы специальная система подсказки, окна со вкладками, окна с панелями и браузер для просмотра иерархии элементов. Данный набор реализован на С, однако он также содержит составные компоненты, созданные на Tcl. http://tix.sourceforge.net/ [incr Tk] и [incr Widgets] Базовый набор инструментов [incr Tk] реализован на языке С и предназначен для создания составных компонентов с помощью объектной системы. Средства [incr Tk] использованы для создания набора компонентов [incr Widgets]. Этот набор описан в книге Чеда Смита (Chad Smith) [incr Tcl] from the Ground Up (Osborne-McGraw Hill, 1999). Информацию о [incr Tcl] и [incr Widgets] можно найти по следующему адресу: http://inertcl.sourceforge.net BWidgets BWidgets - это набор компонентов, созданных средствами Tcl. Он содержит })нзлпчиые составные компоненты, в том числе блокнот со вкладками. раскрывающийся список и браузер для просмотра иерархии элементов. Данный набор расположен на том же узле, что и стандартная библиотека Tcl (Tcllib). http://www.sourceforge.net/proj ects/tcllib TkTable TkTable — это сочетание диспетчера компоновки grid и ряда компонентов, ориентированных на работу с текстом. С его помощью удобно представлять табличные данные, например электронные таблицы. Он предоставляет различные средства для управления форматом ячеек и содержащейся в них информации. http://www.sourceforge.net/proj ects/tktable
Глава 24 Тк в примерах В данной главе приведено несколько простых примеров, демонстрирующих возможности Тк. Сценарий ExecLog выполняет указанную программу в фоновом режиме и отображает ее выходные данные. Специальный браузер выводит результаты выполнения примеров Tcl-кода, приведенные в этой книге. Tcl Shell позволяет вводить Tcl-команды и выполнять их в ведомом интерпретаторе. 1 К. предоставляет простые и удобные средства генерации пользовательских интерфейсов. В данной главе мы рассмотрим несколько несложных примеров, демонстрирующих возможности Тк. Некоторые детали создания сценариев лишь упомянуты в данной главе и подробно будут рассматриваться позже. Так. например, особенности использования диспетчера компоновки pack будут обсуждаться в главе 25, а связывание обработчиков событий с компонентами — в главе 29. ExecLog В первом из рассмотренных здесь примеров реализуется простой пользовательский интерфейс, позволяющий выполнять указанную программу с помощью команды exec. В состав интерфейса входят две кнопки, Run it и Quit, компонент для ввода команд и текстовый элемент, в котором отображаются результаты выполнения программы. Сценарий запускает программу путем организации конвейера и использует команду f ileevent для ожидания выходных данных. Подобный подход обеспечивает доступ к интерфейсу во время выполнения кода программы. Таким образом, можно, например, запускать программу make и выводить сгенерированные ею сообщения. В листинге 24.1 приведен полный текст сценария, за ним следует детальное обсуждение его структуры.
544 Часть III. Основы Тк Листинг 24.1. Получение выходных данных программы, запущенной с помощью команды exec #!/usr/local/bin/wish # execlog - run запуск программы с помощью команды exec # и вывод выходных данных. # Установка заголовка окна. wm title . ExecLog # Создание фрейма для кнопок и поля редактирования. frame .top -borderwidth 10 pack .top -side top -fill x # Создание командных кнопок. button .top.quit -text Quit -command exit set but [button .top.run -text "Run it" -command Run] pack .top.quit .top.run -side right # Создание поля редактирования и поясняющей метки. # Поле редактирования предназначено для ввода команды. label .top.l -text Command: -padx 0 entry .top.cmd -width 20 -relief sunken \ -textvariable command pack .top.l -side left pack .top.cmd -side left -fill x -expand true
Глава 24. Тк в примерах 545 # Связывание команд с клавишами, дублирующими кнопки bind .top.cmd <Return> Run bind .top.cmd <Control-c> Stop focus .top.cmd # Создание текстового компонента для отображения выходных данных frame .t set log [text .t.log -width 80 -height 10 \ -borderwidth 2 -relief raised -setgrid true \ -yscrollcommand {.t.scroll set}] scrollbar .t.scroll -command {.t.log yview} pack .t.scroll -side right -fill у pack .t.log -side left -fill both -expand true pack .t -side top -fill both -expand true # Запуск программы и организация ввода данных proc Run {} { global command input log but if [catch {open "|$command |& cat"} input] { $log insert end $input\n } else { fileevent $input readable Log $log insert end $command\n $but config -text Stop -command Stop } } # Вывод выходных данных proc Log {} { global input log if [eof $input] { Stop } else { gets $input line $log insert end $line\n $log see end }
546 Часть III. Основы Тк } # Остановка программы и восстановление состояния кнопок proc Stop {} { global input but catch {close $input} $but config -text "Run it" -command Run } Заголовок окна Первая команда создает строку заголовка. Заголовок формируется оконным диспетчером и отображается в верхней части окна. Как вы уже знаете, точка является именем главного окна. wm title . ExecLog Команда wm взаимодействует с оконным диспетчером. Оконный диспетчер — это программа, позволяющая открывать, закрывать окна и изменять их размеры. Помимо прочего, оконный диспетчер создает строку заголовка и размещает в ней несколько небольших кнопок, посредством которых можно закрыть окно или изменить его размеры. Окна, создаваемые различными оконными диспетчерами, имеют разный внешний вид. На рисунке, сопровождающем приведенный выше сценарий, показана строка заголовка, созданная диспетчером twm, используемым в системе X Window. Фрейм для кнопок Для размещения компонентов, которые должны отображаться в верхней части главного окна, создается фрейм. Данный фрейм снабжен рамкой, резервирующей некоторое пространство вокруг компонентов. frame .top -borderwidth 10 Фрейм располагается в главном окне. По умолчанию он будет размещен в верхней части окна, поэтому опцию -side top можно было бы и не указывать. Она приведена здесь только для того, чтобы действия сценария были более понятны начинающим разработчикам. Опция -fill x указывает на то, что фрейм должен быть растянут на всю ширину главного окна. pack .top -side top -fill x
Глава 24. Тк в примерах 547 Командные кнопки В состав интерфейса входят две кнопки. Одна из них предназначена для запуска программы, а вторая — для завершения самого сценария. Имена кнопок, .top.quit и .top.run, указывают на то, что они являются дочерними элементами фрейма .top. Это влияет на выполнение команды pack, которая по умолчанию размещает компоненты в пределах их родительского компонента. button .top.quit -text Quit -command exit set but [button .top.run -text "Run it" \ -command Run] pack .top.quit .top.run -side right Текстовая метка и элемент ввода Текстовая метка и поле редактирования, предназначенное для ввода текста, также являются дочерними по отношению к фрейму .top. При создании метки указано, что по бокам от нее не должно оставаться пустого пространства, поэтому метка и поле редактирования расположены рядом друг с другом. Размер поля редактирования задается в символах. Атрибут relief используется для того, чтобы визуально отделить элемент от окружающего пространства. Содержимое компонента ввода связано с Тс 1-переменной command. label .top.l -text Command: -padx 0 entry .top.cmd -width 20 -relief sunken \ -textvariable command Метка, элемент и поле редактирования расположены в левой части фрейма .top. Дополнительные параметры команды pack позволяют элементу поля редактирования изменять свои размеры, заполняя все доступное пространство упаковки. Различия между пространством упаковки и пространством отображения рассматриваются в главе 25. pack .top.l -side left pack .top.cmd -side left -fill x -expand true Обработка нажатий клавиш и фокус ввода Связывание нажатий клавиш с полем редактирования, используемым для ввода текста, является еще одним способом вызова функций приложения. Команда bind связывает Tcl-команду с событием некоторого компонента. Событие <Return> генерируется тогда, когда пользователь нажимает клавишу
548 Часть III. Основы Тк <Return>. Чтобы произошло событие <Control-c>, пользователь должен нажать клавишу <С>, удерживая нажатой клавишу <Ctrl>. Чтобы события направлялись компоненту ввода текста .top.cmd, этот компонент должен иметь фокус ввода. По умолчанию элемент ввода получает фокус после щелчка на нем левой кнопкой мыши. Кроме того, фокус можно передать явно; так поступают тогда, когда фокус должен получать тот компонент, на который попадает курсор мыши. В этом случае как только курсор мыши попадает в пределы главного окна, пользователь может вводить текст в соответствующем элементе. bind .top.cmd <Return> Run bind .top.cmd <Control-c> Stop focus .top.cmd Размеры текста и полоса прокрутки Компонент, предназначенный для отображения текста, помещается во фрейм, снабженный полосой прокрутки. Ширина и высота компонента задаются соответственно в символах и строках. Атрибут setgrid компонента установлен. Это ограничивает изменение размеров компонента так, что в нем могут отображаться только полные строки. Расчет производится исходя из среднего размера символов. Полоса прокрутки представляет собой отдельный компонент Тк. Он может связываться с различными компонентами так, как это делается в данном сценарии. Опция -yscrollcommand управляет обновлением содержимого текстового компонента, а команда command компонента scrollbar прокручивает содержимое компонента, связанного с полосой прокрутки, если пользователь изменяет состояние последней. frame .t set log [text .t.log -width 80 -height 10 \ -borderwidth 2 -relief raised -setgrid true\ -yscrollcommand {.t.scroll set}] scrollbar .t.scroll -command {.t.log yview} pack .t.scroll -side right -fill у pack .t.log -side left -fill both -expand true pack .t -side top -fill both -expand true При создании компонента определяется новая Tcl-команда, которая выполняет действия с этим компонентом. Имя Tcl-команды совпадает с полным именем компонента. В данном сценарии имя команды . t. log. При создании Tk-сценариев рекомендуется помещать имя компонента в переменную, поскольку при изменении интерфейса путь к компоненту может измениться.
Глава 24. Тк в примерах 549 Недостаток такого подхода состоит в том, что переменная должна объявляться в процедуре как глобальная. В данном примере таким образом приходится объявлять переменную log. Процедура Run Процедура Run запускает программу, имя которой указал пользователь. Эти данные доступны через глобальную переменную command, которая задается с помощью опции -textvariable, указанной при создании компонента ввода текста. Программа запускается посредством механизма конвейерной обработки и выполняется в фоновом режиме. Символ I в составе параметров команды open указывает на необходимость создания канала. Команда catch использовалась на случай, если пользователь введет некорректную информацию. В переменную input записывается сообщение об ошибке, а если команда open завершится корректно, то этой переменной присваивается дескриптор файла. Ниже приведена команда, используемая для запуска программы. if [catch {open "l$command |& cat"} input] { При выполнении приложения необходимо организовать перехват событий, возникших при конвейерной обработке. При конвейерной обработке ошибки, возникшие в результате выполнения команды, передаются с помощью программы cat. Если вы не используете эту программу, то информация об ошибке будет отображена при закрытии канала. В рассматриваемом примере было бы слишком сложно делать различия между ошибками, сгенерированными программой, и ошибками, связанными с выполнением процедуры Stop. Более того, некоторые программы не делают различия между выходным потоком и потоком ошибок, поэтому данные, выводимые в поток ошибок, приходится отображать наряду с обычной информацией. Если конвейерная обработка запущена успешно, с помощью команды f ileevent задается процедура обратного вызова. При генерации выходных данных программой, выполняющейся в режиме конвейерной обработки, сценарий может прочитать их. Процедура Log регистрируется так, чтобы она была вызвана по готовности данных в канале. fileevent $input readable Log В состав протоколируемых данных включается значение переменной command (либо сообщение об ошибке). Для этого имя текстового компонента, хранящееся в переменной log, используется как Tcl-команда. Значение command и символ перевода строки присоединяются к переменной log, таким образом, выходные данные отображаются в новой строке.
550 Часть III. Основы Тк $log insert end $command\n Функция insert текстового компонента получает два параметра: маркер и строку, которая должна быть отображена. Символьный маркер end представляет конец содержимого текстового компонента. После запуска программы кнопка Run it превращается в кнопку Stop. Это исключает необходимость включения в состав интерфейса лишних компонентов и демонстрирует возможности Тк по динамическому созданию интерфейсов. Поскольку данная кнопка используется в различных частях сценария, ее имя сохраняется в переменной but. $but config -text Stop -command Stop Процедура Log Процедура Log вызывается тогда, когда данные могут быть прочитаны из канала либо когда достигается конец файла. В первую очередь производится проверка на конец файла и, если она дает положительный результат, вызывается процедура Stop. В противном случае читается одна строка данных и включается в состав компонента. Операция see текстового компонента управляет отображением текста, поэтому новая строка становится видимой для пользователя. if [eof $input] { Stop } else { gets $input line $log insert end $line\n $log see end } Процедура Stop Процедура Stop завершает программу, закрывая канал. Данная процедура вызывается из команды catch. Это необходимо для перехвата ошибки. В результате выполнения процедуры Stop кнопка Run it приобретает свой первоначальный вид и пользователь получает возможность вызвать другую команду. catch {close $input} $but config -text "Run it" -command Run В большинстве случаев закрытие канала эквивалентно завершению задания. В системе Unix это приводит к передаче программе сигнала SIGPIPE.
Глава 24. Тк в примерах 551 Программа получает его при очередной операции записи в стандартный выходной поток. Встроенные средства завершения процесса здесь не предусмотрены, но вы можете вызвать с помощью команды exec стандартную программу Unix kill. Команда pid возвращает идентификатор выполняющегося процесса. foreach pid [pid $input] { catch {exec kill $pid} } Если вы хотите обеспечить более строгий контроль над другим процессом, вам надо воспользоваться Tcl-расширением expect, которое описано в книге Дона Лайбса (Don Libes) Exploring Expect (O'Reilly Sz Associates, Inc., 1995). Расширение expect предоставляет мощные средства управления работой интерактивных программ. Вы можете написать сценарий, который будет передавать данные интерактивной программе, и сравнивать с шаблоном ее выходную информацию. Расширение expect предназначено для автоматизации взаимодействия с программами, специально написанными для интерактивной работы. Работа на различных платформах Данный сценарий может работать в системах Unix и Windows, однако не может выполняться в Macintosh, поскольку в этой системе отсутствует команда exec. Еще одна проблема связана с использованием события <Control-c> для завершения задания. Комбинация клавиш <Ctrl+C> применяется в системе Unix. В Windows для этой цели обычно используется клавиша <Esc>, а в Macintosh задание завершается по событию <Command-period>. Ниже приведена процедура Platform_CancelEvent, которая определяет виртуальное событие <<Сапсе1>>. С ним надо связать процедуру Stop. Листинг 24.2. Обработка событий завершения задания на разных платформах proc Platform_CancelEvent {} { global tcl_platform switch $tcl_platform(platform) { unix { event add «Cancel» <Control-c> } windows { event add «Cancel>> <Escape> } macintosh {
552 Часть III. Основы Тк event add «Cancel» <Command-period> } } } bind .top.entry «Cancel>> Stop В Тк определен ряд других виртуальных событий. Команда event и виртуальные события будут рассматриваться в главе 29. Браузер В листинге 24.3 приведен код простого браузера для работы с примерами, содержащимися в данной книге. Для решения данной задачи достаточно создать меню, с помощью которого можно было бы выбрать тот или иной пример, и текстовое окно для их отображения. Перед тем как можно будет использовать эту программу, необходимо указать в ней правильное расположение каталога exsource, содержащего коды примеров из данной книги. В листинге 24.4 показан код, расширяющий возможности браузера. В частности, к нему будет добавлена оболочка, которую можно использовать для тестирования примеров. Листинг 24.3. Браузер для работы с примерами из данной книги #!/usr/local/bin/wish # Браузер для работы с примерами из данной книги. # browse(dir) - каталог, содержащий все tcl-файлы. # Расположение каталога надо проверить и по необходимости # отредактировать код. switch $tcl_platform(platform) { "unix" {set browse(dir) /cdrom/tclbook2/exsource} "windows" {set browse(dir) D:/exsource} "macintosh" {set browse(dir) /tclbook2/exsource} } wm minsize . 30 5 wm title . "Tcl Example Browser" # Создание ряда кнопок в верхней части экрана
Глава 24. Тк в примерах 553 set f [frame .menubar] pack $f -fill x button $f.quit -text Quit -command exit button $f.next -text Next -command Next button $f.prev -text Previous -command Previous # Кнопки Run и Reset используют процедуру EvalEcho, которая # будет определена в листинге 24.4. button $f.load -text Run -command Run button $f.reset -text Reset -command Reset pack $f.quit $f.reset $f.load $f.next $f.prev -side right # Метка для текущего примера label $f.label -textvariable browse(current) pack $f.label -side right -fill x -expand true # Создание компонентов menubutton и menu raenubutton $f.ex -text Examples -menu $f.ex.m pack $f.ex -side left set m [menu $f.ex.m] # Создание текстового компонента для отображения примеров. # Процедура Scrolled_Text будет определена в листинге 33.1. set browse(text) [Scrolled_Text .body \ -width 80 -height 10\ -setgrid true] pack .body -fill both -expand true # Просмотр файлов с примерами и поиск идентификаторов. foreach f [lsort -dictionary [glob [file join $browse(dir) *]]] { if [catch {open $f} in] { puts stderr "Cannot open $f: $in" continue } while {[gets $in line] >= 0} { if [regexp {~# Example ([0-9]+)-([0-9]+)} $line \ x chap ex] {
554 Часть III. Основы Тк lappend examples($chap) $ex lappend browse(list) $f # Чтение заголовка примера gets $in line set title($chap-$ex) [string trim $line "# "] set file($chap-$ex) $f close $in break } } } # Создание двух уровней каскадного меню. # На первом уровне главы книги объединяются в группы. # На втором уровне пункт меню соответствует # конкретному примеру. option add *Menu.tearOff 0 set limit 8 set с 0; set i 0 foreach chap [lsort -integer [array names examples]] { if {$i == 0} { $ra add cascade -label "Chapter $chap..." \ -menu $m.$c set subl [menu $m.$c] incr с } set i [expr ($i +1) °/0 $limit] $subl add cascade -label "Chapter $chap" -menu $subl.sub$i set sub2 [menu $subl.sub$i] foreach ex [lsort -integer $examples($chap)] { $sub2 add command -label "$chap-$ex $title($chap-$ex)" \ -command [list Browse $file($chap-$ex)] } } # Отображение конкретного файла. Метка обновляется так, # чтобы она соответствовала отображаемым данным. После вывода # примера текст остается в режиме, допускающем только чтение. proc Browse { file } {
Глава 24. Тк в примерах 555 global browse set browse(current) [file tail $file] set browse(curix) [lsearch $browse(list) $file] set t $browse(text) $t config -state normal $t delete 1.0 end if [catch {open $file} in] { $t insert end $in } else { $t insert end [read $in] close $in } $t config -state disabled } # Просмотр соседних файлов в списке set browse(curix) -1 proc Next О { global browse if {$browse(curix) < [llength $browse(list)] - 1} { incr browse(curix) } Browse [lindex $browse(list) $browse(curix)] } proc Previous {} { global browse if {$browse(curix) > 0} { incr browse(curix) -1 } Browse [lindex $browse(list) $browse(curix)] } # Выполнение примера в оболочке proc Run {} { global browse EvalEcho [list source \ [file join $browse(dir) $browse(current)]] }
556 Часть III. Основы Тк # Сброс ведомого интерпретатора proc Reset {} { EvalEcho reset } Изменение размеров окон В данном примере вызывается команда wm minsize, с помощью которой задается минимально возможный размер окна. Минимальная ширина и высота определяются с помощью параметров, передаваемых команде. Значения этих параметров могут интерпретироваться двумя способами. По умолчанию они определяют размеры в пикселях. Однако, если внутренний компонент допускает обработку диспетчером компоновки grid, то размеры вычисляются в единицах сетки, создаваемой для этого компонента. В данном случае текстовый компонент допускает разбиение с помощью атрибута setgrid, поэтому минимальный размер окна вычисляется так, чтобы текстовый компонент вмещал как минимум пять строк по 30 символов в каждой. wm minsize . 30 5 В некоторых версиях Тк, например в версии 3.6, разбиение на ячейки автоматически разрешает изменение размеров окна в интерактивном режиме. Эта возможность включена по умолчанию в Тк 4.0 и последующих версиях. Управление состоянием В рассматриваемой программе для хранения значений глобальных переменных используется массив browse. Это упрощает работу процедур с данными, определяющими состояние программы, так как глобальным оказывается только один массив. По мере развития переменных и добавления новых свойств объявлять новые глобальные переменные не приходится. Такой подход также позволяет обратить особое внимание на важные переменные. В массиве browse содержится имя каталога, в котором хранятся примеры (dir), путь к текстовому элементу (text) и имя текущего файла (current). Элементы list и curix используются для реализации процедур Next и Previous. Поиск файлов Чтобы найти файлы для отображения, браузер просматривает файловую систему. Переменная tcl_platform(platform) используется для выбора каталога в зависимости от текущей платформы. В данном сценарии для поиска
Глава 24. Тк в примерах 557 файлов в каталоге exsource применяется команда glob. Команда file join действует независимо от платформы и создает шаблон имен файла. Результаты выполнения команды glob сортируются, поэтому пункты меню располагаются в нужном порядке. Информация из каждого файла читается построчно с помощью команды gets, после чего осуществляется поиск ключевых слов посредством команды regexp. Ниже приведен соответствующий фрагмент сценария. foreach f [lsort -dictionary [glob -directory $browse(dir) *]] { if {[catch {open $f} in]} { puts stderr "Cannot open $f: $in" continue } while {[gets $in line] >= 0} { if {[regexp {~# Example ([0-9]+)-([0-9]+)} $line \ x chap ex]} { lappend examples($chap) $ex lappend browse(list) $f # Чтение заголовка примера gets $in line set title($chap-$ex) [string trim $line "# "] set file($chap-$ex) $f close $in break } } } В файлах примеров содержатся строки наподобие следующих: # Example 1-1 # The Hello, World! program Команда regexp извлекает с помощью фрагмента шаблона ([0-9]+)- ([0-9]+) номер примера и присваивает фрагменты номера переменным chap и ex. В переменной х хранится весь отмеченный текст, но в данном случае он нас не интересует. После того как номер примера найден, сценарий читает следующую строку, в которой содержится описание примера. В цикле foreach информация записывается в массив examples. Для каждой главы выделяется элемент массива; этот элемент содержит список примеров из данной главы.
558 Часть III. Основы Тк Каскадное меню Значения массива examples используются для создания каскадного меню. В первую очередь создается кнопка menubutton, которая управляет главным меню. Для связывания с меню используется ее атрибут menu. Для того чтобы информация отображалась корректно, меню должно быть дочерним элементом menubutton. menubutton $f.ex -text Examples -menu $f.ex.m set m [menu $f.ex.m] Глав в книге слишком много, чтобы расположить их в одном меню. В главном меню находится пункт cascade для каждой группы из восьми глав. В каждом подменю содержится пункт cascade для каждой главы из группы, а главе соответствует меню с примерами. Подменю определяются как дочерние элементы родительского меню. Обратите внимание на различие между пунктами меню и кнопками. Текст пунктов меню определяется с помощью опции -label, в то время как в кнопках для этой цели используется опция -text. В основном работа с пунктами меню организуется по тому же принципу, что и действия с кнопками. Более детально меню описываются в главе 30. Фрагмент кода, предназначенный для формирования каскадного меню, приведен ниже. set limit 8 ; set с 0 ; set i 0 foreach key [lsort -integer [array names examples]] { if {$i == 0} { $m add cascade -label "Chapter $key..." \ -menu $m.$c set subl [menu $m.$c] incr с } set i [expr {($i +1) % $limit}] $subl add cascade -label "Chapter $key" -menu $subl.sub$i set sub2 [menu $subl.sub$i] foreach ex [lsort -integer $examples($key)] { $sub2 add command -label "$key-$ex $title($key-$ex)" \ -command [list Browse $file($key-$ex)] } }
Глава 24. Тк в примерах 559 Текстовый компонент, предназначенный только для чтения Процедура Browse очень проста. В ней имя файла записывается в элемент browse (current). При этом изменяется текстовая метка, поскольку атрибут textvariable обеспечивает связь с соответствующей переменной. Атрибут state текстового компонента изменяется так, что после включения текста компонент становится доступен только для чтения. Перед включением текста следует задать значение normal опции -state, в противном случае команда insert не выполнит никаких действий. Ниже приведено несколько команд из тела процедуры Browse. global browse set browse(current) [file tail $file] $t config -state normal $t insert end [read $in] $t config -state disabled Tcl-оболочка В данном разделе рассматривается приложение, реализующее Тс1-оболоч- ку. В этом приложении для ввода команд и отображения результатов их выполнения используется текстовый компонент. Для выполнения введенных пользователем команд применяется второй интерпретатор. Система из двух интерпретаторов используется консолью, встроенной в версии wish, предназначенные для систем Windows и Macintosh. Приложение TkCon реализует консоль, предоставляющую дополнительные возможности по организации интерактивной работы со средствами Tcl. Сценарий, приведенный в листинге 24.4, может использоваться совместно с браузером, рассмотренным ранее. При активизации кнопки Run браузера текущий пример будет выполняться в оболочке. Альтернативный подход состоит в запуске оболочки в качестве отдельного процесса и обмене Тс1-коман- дами посредством команды send. Этот подход рассматривается в главе 43. Листинг 24.4. Tcl-оболочка, реализуемая с помощью текстового компонента #!/usr/local/bin/wish # Система, выполняющая Tcl-программы в ведомом интерпретаторе. set t [ScrolledJText .eval -width 80 -height 10] pack .eval -fill both -expand true
560 Часть III. Основы Тк # Благодаря использованию текстовых дескрипторов # выходные данные сценария, информация об ошибках, # результаты выполнения команд и приглашение для ввода # выглядят по-разному. $t tag configure prompt -underline true $t tag configure result -foreground purple $t tag configure error -foreground red $t tag configure output -foreground blue # Формирование текста приглашения и инициализация маркера limit set eval(prompt) "tcl> " $t insert insert $eval(prompt) prompt $t mark set limit insert $t mark gravity limit left focus $t set eval(text) $t # Основные связывания, влияющие на ввод и выполнение кода. # Команда break отменяет связывание по умолчанию класса Text # для данного события. bind $t <Return> {EvalTypein ; break} bind $t <BackSpace> { if {[°/0W tag nextrange sel 1.0 end] != ""} { °/0W delete sel.first sel.last } elseif {[7oW compare insert > limit]} { °/0W delete insert-lc °/0W see insert } break } bind $t <Key> { if [°/0W compare insert < limit] { °/oW mark set insert end } } # Интерпретация текста между маркерами limit и end как Tcl-команды
Глава 24. Тк в примерах 561 proc EvalTypein {} { global eval $eval(text) insert insert \n set command [$eval(text) get limit end] if [info complete $command] { $eval(text) mark set limit insert Eval $command } } # Вывод в режиме "эхо" и выполнение команды. proc EvalEcho {command} { global eval $eval(text) mark set insert end $eval(text) insert insert $command\n Eval $command } # Выполнение команды и отображение результатов. proc Eval {command} { global eval $eval(text) mark set insert end if [catch {$eval(slave) eval $command} result] { $eval(text) insert insert $result error } else { $eval(text) insert insert $result result } if {[$eval(text) compare insert != "insert linestart"]} { $eval(text) insert insert \n } $eval(text) insert insert $eval(prompt) prompt $eval(text) see insert $eval(text) mark set limit insert return } # Создание и инициализация ведомого интерпретатора. proc Slavelnit {slave} {
562 Часть III. Основы Тк interp create $slave load {} Tk $slave interp alias $slave reset {} ResetAlias $slave interp alias $slave puts {} PutsAlias $slave return $slave } # Псевдоним reset удаляет ведомый интерпретатор # и запускает новый. proc ResetAlias {slave} { interp delete $slave Slavelnit $slave } # Псевдоним puts отображает данные, выводимые в потоки # stdout и stderr, в составе текстового компонента. proc PutsAlias {slave args} { if {[llength $args] > 3} { error "invalid arguments" } set newline "\n" if {[string match "-nonewline" [lindex $args 0]]} { set newline "" set args [lreplace $args 0 0] } if {[llength $args] == 1} { set chan stdout set string [lindex $args 0]$newline } else { set chan [lindex $args 0] set string [lindex $args l]$newline } if [regexp (stdoutIstderr) $chan] { global eval $eval(text) mark gravity limit right $eval(text) insert limit $string output $eval(text) see limit $eval(text) mark gravity limit left } else {
Глава 24. Тк в примерах 563 puts -nonewline $chan $string } } set eval(slave) [Slavelnit shell] Текстовые маркеры, дескрипторы и связывание Для того чтобы пользователь мог вводить данные только в конец текста, содержащегося в компоненте, в рассматриваемом сценарии используются текстовые маркеры. Маркер представляет позицию в тексте. При вводе или удалении символов маркер обновляется. Маркер limit определяет границу между текстом, доступным только для чтения, и областью, допускающей редактирование. Маркер insert отмечает ту точку, в которой отображается курсор. Маркер end всегда расположен в конце текста. Процедура EvalTypein обрабатывает текст, находящийся между маркерами limit и end, и проверяет, представляет ли собой этот текст законченную Tcl-команду. Если проверка дает положительный результат, команда выполняется в ведомом интерпретаторе. Обработчик <Кеу> определяет расположение маркера insert и, если он находится перед маркером limit, перемещает его в конец текста. Псевдоним alias обеспечивает перемещение маркера limit в случае, если программа выводит информацию в позиции, отмеченной маркером. Если же данные вводит пользователь, маркер limit не перемещается. Текстовые дескрипторы используются для выявления различий между областями текста. Дескриптор применяется к тексту в некотором диапазоне. Дескрипторы настраиваются в начале сценария и применяются к тексту, введенному пользователем. Подробно текстовые компоненты рассматриваются в главе 36. Использование нескольких интерпретаторов Процедура Slavelnit создает новый интерпретатор для выполнения команды. Такой подход предотвращает конфликты с процедурами и переменными, используемыми при реализации оболочки. Первоначально ведомый интерпретатор имеет доступ только к Tcl-командам. Команда load устанавливает Тк-команды. В результате создается новое окно верхнего уровня, имя которого для ведомого интерпретатора состоит только из точки. При создании интерпретатора shell не указывается флаг -safe, поэтому интерпретатор может выполнять любые действия. Например, если пользователь введет команду exit, это приведет к завершению всего приложения. Процедура Slavelnit задает псевдоним reset, при обращении к которому
564 Часть III. Основы Тк лишь удаляется ведомый интерпретатор и создается новый. Вы можете использовать эту команду для удаления результатов своей работы в оболочке. О команде interp см. в главе 19. Внешний вид окон При выполнении Tk-сценария на различных платформах отображаются кнопки, меню и полосы прокрутки, типичные для текущей операционной системы. То же происходит с компонентами, предназначенными для отображения текста и ввода данных. На рис. 24.1 24.3 показан внешний вид браузера и оболочки в системах Macintosh, Windows и Unix. Рис. 24.1. Внешний вид окон в системе Macintosh Рис. 24.2. Внешний вид окон в системе Windows
Глава 24. Тк в примерах 565 Рис. 24.3. Внешний вид окон в системе Unix
Глава 25 Диспетчер компоновки pack В данной главе рассматривается диспетчер компоновки pack, который упорядочивает компоненты на экране. ^_1исПЕТЧЕРЫ компоновки предназначены для управления размещением компонентов. С помощью диспетчера pack определяется набор правил, которые задают порядок размещения элементов. В последующих главах будут описаны дескрипторы grid и place. Диспетчеры компоновки pack и grid представляют собой диспетчеры общего назначения, a place применяется для специальных целей. В примерах, приведенных в данной книге, в основном используется pack — первый из диспетчеров компоновки, реализованных для Тк. Диспетчер grid стал поддерживаться начиная с версии Тк 4.1. Диспетчер компоновки рассматривает некоторый компонент как родительский, в котором размещены дочерние, или ведомые, элементы. Родительское окно почти всегда представляет собой фрейм, но в некоторых случаях в этой роли выступают компоненты других типов. В каждый момент времени компонент может управляться только одним диспетчером компоновки, однако для различных компонентов можно применять разные диспетчеры. Если компонент не находится под управлением диспетчера, он не отображается на экране. Не следует применять диспетчеры pack и grid к одному и тому же компоненту. Для каждого конкретного компонента следует выбрать диспетчер, который будет размещать его дочерние элементы (чаще всего в качестве такого диспетчера выбирается pack или grid). Попытка одновременного использования обоих диспетчеров приведет к тому, что программа окажется в бесконечном цикле. Данное правило относится только к непосредственным потомкам управляемого компонента.
Глава 25. Диспетчер компоновки pack 567 Для следующего уровня дочерних компонентов можно использовать другой диспетчер. Например, вы можете принять решение обработать все дочерние элементы главного окна (.) с помощью диспетчера компоновки pack. Если один из дочерних элементов главного окна представляет собой фрейм, то для размещения элементов в нем вы с равным успехом можете выбрать как pack, так и grid. Диспетчер компоновки pack предоставляет достаточно обширные возможности. Вместо того чтобы непосредственно указывать детали размещения компонентов в каждом окне, программист определяет общие правила позиционирования окон, а выполнение всех действий, необходимых для соблюдения этих правил, берет на себя диспетчер. Создавая пользовательские интерфейсы, необходимо хорошо знать алгоритм работы диспетчера компоновки. В противном случае размещение элементов будет не таким, как вы ожидаете. В данной главе приведен ряд примеров применения диспетчера pack. В каждом из них в качестве фона главного окна выбран черный цвет. Прочие фреймы используют другие цвета. Это помогает идентифицировать отдельные фреймы и оценивать эффект, полученный от использования параметров диспетчера pack. Некоторые из приведенных ниже примеров лишь незначительно отличаются друг от друга. Команды, добавленные в очередном примере (но отсутствующие в его предыдущем варианте), выделяются полужирным шрифтом. Размещение относительно направления В приведенном ниже примере создаются два фрейма, которые размещаются относительно выбранного направления в главном окне. Верхний фрейм, .one, имеет меньшие размеры, чем размеры главного окна, поэтому цвет фона главного окна виден по бокам компонента. Дочерние элементы по очереди выравниваются относительно указанного направления, поэтому компонент .one располагается в верхней части окна. В команде pack можно указать одно из четырех направлений: top, right, bottom и left. Направление top принимается по умолчанию. Листинг 25.1. Размещение двух фреймов в главном окне
568 Часть III. Основы Тк # Для главного окна выбирается черный цвет фона . config -bg black # Создание двух фреймов и размещение их # с помощью диспетчера компоновки frame .one -width 40 -height 40 -bg white frame .two -width 100 -height 50 -bg grey50 pack .one .two -side top Размеры окон и команда pack propagate В предыдущем примере размеры главного окна уменьшались до величины, достаточной для размещения двух дочерних компонентов. В большинстве случаев такое поведение является наиболее приемлемым. Однако, если вам надо, чтобы окно не уменьшалось, следует вызвать команду pack. Применяя эту команду к родительскому окну, вы добьетесь того, что оно не будет согласовывать свои размеры с размерами дочерних элементов. Листинг 25.2. Отключение средств согласования размеров frame .one -width 40 -height 40 -bg white frame .two -width 100 -height 50 -bg grey50 pack propagate . false pack .one .two -side top Горизонтальное и вертикальное размещение Внутри фрейма компоненты могут размещаться по горизонтали или но вертикали. Если вы попытаетесь объединить горизонтальное и вертикальное размещение, одновременно задавая различные типы выравнивания (например, left и top), результаты могут оказаться неожиданными для вас. Гораздо лучше объявлять несколько фреймов и указывать в них различные типы
Глава 25. Диспетчер компоновки pack 569 выравнивания. Предположим, например, что в рассмотренном выше примере нам надо разместить в верхнем фрейме горизонтальный ряд кнопок. Сделать это можно с помощью фрагмента кода, приведенного в листинге 25.3. Листинг 25.3. Вертикальное размещение фреймов и горизонтальное размещение компонентов внутри фрейма frame .one -bg white frame .two -width 100 -height 50 -bg grey50 # Создание ряда кнопок foreach b {alpha beta gamma} { button .one.$b -text $b pack .one.$b -side left } pack .one .two -side top По необходимости вы можете реализовать более сложную компоновку элементов. Для этого надо создать вложенные фреймы и задать в них разные типы выравнивания. В том фрейме, в котором компоненты должны располагаться горизонтально, надо задать опцию -side left или -side right. Если же вам надо организовать вертикальное размещение компонентов, это достигается с помощью опций -side top и -side bottom. В листинге 25.4 вместо кнопки .one.gamma в состав фрейма включен еще один фрейм, содержащий две кнопки: .one.right .delta и .one.right .epsilon. Эти кнопки расположены по вертикали. Поскольку при компоновке компонентов в .one.right указана опция -side bottom, первая включаемая кнопка размещается в нижней части фрейма. Листинг 25.4. Объединение в окне фреймов с горизонтальным и вертикальным размещением компонентов frame .one -bg white frame .two -width 100 -height 50 -bg grey50
570 Часть III. Основы Тк foreach b {alpha beta} { button .one.$b -text $b pack .one.$b -side left } # Создание фрейма для двух дополнительных кнопок frame .one.right foreach b {delta epsilon} { button .one.right.$b -text $b pack .one.right.$b -side bottom } pack .one.right -side right pack .one .two -side top В данном примере фрейм .one.right располагается в правой части родительского фрейма, а в предыдущем случае при компоновке кнопки . one. gamma была указана опция -side left. Несмотря на различие опций, расположение этих компонентов относительно двух других кнопок, включенных во фрейм .one, остается неизменным. Причина этого будет объяснена в следующем разделе. Модель полостей Алгоритм компоновки распределяет доступное пространство внутри фрейма в соответствии с моделью полостей. При создании окна wish главный фрейм пуст, и пространство для размещения компонентов не определено. Это пространство принято называть полостью (cavity). Согласно основному правилу компоновки, компонент заполняет полость по одной из координат. Рассмотрим пример размещения трех компонентов в главном окне. Для первых двух компонентов зададим тип выравнивания bottom, а для третьего — тип выравнивания right. Листинг 25.5. Совместное использование типов выравнивания bottom и right # Размещение двух фреймов с выравниванием по нижней части, frame .one -width 100 -height 50 -bg grey50
Глава 25. Диспетчер компоновки pack 571 frame .two -width 40 -height 40 -bg white pack .one .two -side bottom # Размещение фрейма с выравниванием по правой части. frame .three -width 20 -height 20 -bg grey75 pack .three -side right Если при размещении третьего фрейма в главном окне мы зададим опцию -side left или -side right, то расположим его в полости, находящейся над двумя фреймами, которые к этому моменту помещены в окно. Может показаться, что фрейм должен быть расположен справа от двух существующих фреймов, однако это не так. Причина подобного поведения диспетчера компоновки состоит в том, что фрейм .two занимает всю нижнюю часть полости, несмотря на то, что его размеры по горизонтали меньше размеров полости. Как вы думаете, где будет расположена полость после выполнения данного примера? Конечно же, она располагается слева от фрейма .three, который последним был обработан диспетчером компоновки с выравниванием типа right и выше фрейма .two, который последним был обработан с выравниванием типа bottom. Данный пример объясняет, почему отсутствуют различия между двумя предыдущими примерами, в одном из которых для .one.gamma был указан тип выравнивания left, а для .one.right — тип выравнивания right. В данном случае выравнивание по левому и по правому краю полости дает одинаковый результат. Однако, если в том же фрейме появится дополнительный компонент, внешний вид окна для разных конфигураций будет различаться. Задайте после выполнения сценариев, представленных в листингах 25.3 и 25.4, следующие две команды, и различия отобразятся на экране1: button .one.omega -text omega pack .one.omega -side right Каждый из родительских фреймов содержит свою полость, поэтому для реализации сложного взаимного расположения компонентов приходится прибегать к помощи вложенных фреймов. Если внутри каждого фрейма используется выравнивание только по горизонтали или только по вертикали, вы сможете без труда предсказать, каким будет внешний вид окна. 1 После выполнения кода, приведенного в листинге 25.3, новая кнопка разместится справа от всех имеющихся кнопок. Если же перед представленными здесь командами был выполнен код, показанный в листинге 25.4, то новая кнопка будет располагаться между .one. bet а и .one. right. — Прим. авт.
572 Часть III. Основы Тк Пространство компоновки и пространство отображения Выполняя действия по упорядочению компонентов, диспетчер компоновки оперирует понятиями пространства компоновки и пространства отображения. Пространство отображения — это область, необходимая компоненту для того, чтобы он мог быть представлен на экране. Пространство компоновки — это область, которую диспетчер компоновки выделяет для этого компонента. Из-за особенностей размещения на экране для компонента может быть выделена область большая или меньшая, чем необходимо. Лишнее пространство отсчитывается вдоль координаты, по которой выравнивается компонент. Опция -fill Если при вызове диспетчера компоновки указана опция -fill, компонент будет занимать все доступное для него пространство. Заполнение может выполняться по оси X, Y или по обеим координатам. По умолчанию заполнение не производится. Именно поэтому в рассмотренных ранее примерах на экране был виден черный фон главного окна. Листинг 25.6. Заполнение компонентом свободного пространства frame .one -width 100 -height 50 -bg grey50 frame .two -width 40 -height 40 -bg white # Компоновка при разрешенном заполнении pack .one .two -side bottom -fill x frame .three -width 20 -height 20 -bg red pack .three -side right -fill x Представленный выше код очень похож Ha код примера, приведенный в листинге 25.5, за исключением того, что для всех фреймов задана опция -fill х. Фрейм .two заполняет свободную область, а фрейм .three — нет. Такое различие в поведении фреймов связано с тем, что операция заполнения не затрагивает полость. В данном случае полость — это область окна, окрашенная в черный цвет. С другой стороны, фрейму .two выделена вся область от левой до правой границы окна, поэтому данный фрейм может быть рас-
Глава 25. Диспетчер компоновки pack 573 ширен. Фрейму .three выделена только правая часть верхней полосы окна, в результате заполнение по координате X не дает никакого эффекта. Операцию заполнения удобно применять при формировании панелей инструментов. Предположим, что кнопки надо разместить на обоих краях фрейма, а между ними должна остаться свободная область. Фрейм, содержащий кнопки, располагается в верхней части окна. Кнопки выровнены по левому и по правому краю фрейма. Без заполнения произошло бы сжимание фрейма, и кнопки были бы расположены вплотную одна к другой. Если же объявлено заполнение по координате X. панель инструментов заполняет всю верхнюю часть окна. Листинг 25.7. Использование горизонтального заполнения при формировании панели инструментов frame .menubar -bg white frame .body -width 150 -height 50 -bg grey50 # Кнопки располагаются на разных концах панели инструментов foreach b {alpha beta} { button .menubar.$b -text $b } pack .menubar.alpha -side left pack .menubar.beta -side right # Опция -fill позволяет панели инструментов располагаться # в верхней части окна на всю его ширину pack .menubar -side top -fill x pack .body Внутреннее дополнение, задаваемое с помощью опций -ipadx и -ipady Расширить область, занимаемую компонентом, можно также, используя опции -ipadx и -ipady команды pack. Эти опции запрашивают дополнительное пространство по оси X и по оси Y. Наличие ограничений может привести к тому, что запрос не будет удовлетворен, но, как правило, данные опции позволяют расширить область отображения компонента. Пример, приведенный в листинге 25.8, похож на пример, рассмотренный нами ранее. Различие лишь в использовании внутреннего дополнения.
574 Часть III. Основы Тк Листинг 25.8. Результаты использования внутреннего дополнения (опции -ipadx и -ipady) # Создание и размещение двух фреймов frame .menubar -bg white frame .body -width 150 -height 50 -bg grey50 # Кнопки располагаются на разных концах панели инструментов foreach b {alpha beta} { button .menubar.$b -text $b } pack .menubar.alpha -side left -ipady 10 pack .menubar.beta -side right -ipadx 10 # Let the menu bar fill along the top pack .menubar -side top -fill x -ipady 5 pack .body Кнопка alpha превосходит по высоте кнопку beta, а кнопка beta шире, чем кнопка alpha. Внутреннее дополнение применяется и при компоновке фрейма, что, с одной стороны, уменьшает размеры полости, а с другой стороны, формирует дополнительную свободную область над кнопками и под ними. Для некоторых компонентов предусмотрены атрибуты, управляющие размерами области отображения. Поэтому трудно, например, отличить фрейм, для которого задана ширина 50 пикселей и не указано внутреннее дополнение, от фрейма, для которого указаны опции -width 40 и -ipadx 5. И в том и в другом случае ширина области отображения будет равна 50 пикселям. Опции -padx и -pady предусмотрены также и для кнопок. При использовании этих опций размеры области отображения, занимаемой кнопками, увеличиваются. При работе с кнопками дополнение обычно используется для того, чтобы обеспечить интервал между текстом, отображаемым на кнопке, и ее краями. Различие между кнопками с внутренним отображением и без него демонстрирует код, приведенный в листинге 25.9. Опция -anchor e указывает на то, что текст должен размещаться в правой части кнопки. Еще один пример применения данной опции будет приведен в листинге 40.5.
Глава 25. Диспетчер компоновки pack 575 Листинг 25.9. Отличие внутреннего дополнения компонента от внутреннего дополнения диспетчера компоновки # Компонент foo получает внутреннее дополнение от # диспетчера компоновки button .foo -text Foo -anchor e -padx 0 -pady 0 pack .foo -side right -ipadx 10 -ipady 10 # Для компонента bar задано собственное внутреннее дополнение button .bar -text Bar -anchor e -pady 10 -padx 10 pack .bar -side right -ipadx 0 -ipady 0 При указании дополнений используется единица измерения длины области отображения, допустимая в Тк. Обычное числовое значение интерпретируется как количество пикселей. Если за числом следует i, m, с или р, то предполагается соответственно расстояние в дюймах, миллиметрах, сантиметрах или пунктах. Внешнее дополнение, задаваемое с помощью опций -padx и -pady Диспетчер компоновки может реализовать внешнее дополнение, т.е. резервировать область компоновки, в которой не могут размещаться никакие элементы. Это пространство выделяется за пределами обрамления компонента, используемого для эмуляции трехмерных элементов. Примеры эффектов, имитирующих трехмерное представление, приведены в листинге 40.2. Для формирования кнопки по умолчанию используются дополнительный фрейм и внешнее дополнение. Листинг 25.10. Внешний вид кнопки по умолчанию . config -borderwidth 10 # Кнопка 0К является кнопкой по умолчанию frame .ok -borderwidth 2 -relief sunken button .ok.b -text OK pack .ok.b -padx 5 -pady 5 # Cancel представляет собой обычную кнопку
576 Часть III. Основы Тк button .cancel -text Cancel pack .ok .cancel -side left -padx 5 -pady 5 Внешний вид кнопки .ok.b не изменился бы даже в том случае, если бы при вызове команды pack была указана опция -fill both. Дочерний компонент не заполняет область дополнения. На рисунке, приведенном в листинге 25.10, показан внешний вид кнопки по умолчанию. В Тк 8.0 для таких кнопок был реализован атрибут default. Он реализует отображение кнопки по умолчанию, принятой для текущей платформы. В системе Unix кнопки по умолчанию выглядят приблизительно так, как показано на рисунке, а в системах Macintosh и Windows их внешний вид несколько отличается. В Тк 8.4 была добавлена возможность создавать асимметричное дополнение, указывая в качестве значения опции список из двух элементов. Например, в приведенном ниже выражении добавляется по 5 пикселей слева и справа от компонентов, 3 пикселя выше них и 6 пикселей под ними. pack .ok .cancel -side left -padx 5 -pady {3 6} Изменение размеров окон и опция -expand Опция -expand true диспетчера компоновки позволяет расширять компонент так, чтобы он занимал невостребованную полость. В листинге 25.6 был приведен пример создания фрейма в верхней части окна, который распространялся на всю ширину окна, независимо от того, что при вызове команды pack была указана опция -side right. Рассмотрим более общий случай — окно с изменяемыми размерами. Когда пользователь увеличивает окно, компоненты могли бы занять дополнительное пространство. Предположим, что какой-то из компонентов, например текстовый компонент, список или холст, находится во фрейме, снабженном полосами прокрутки. Этот фрейм должен расширяться, занимая свободную область в родительском фрейме (например, в главном окне), а компонент (например, холст) должен занимать свободную область, возникшую во фрейме с полосами прокрутки. Подобная структура была показана в листинге 24.1. Опция -fill both практически всегда используется с опцией -expand true, поэтому компоненты расширяются на свободную область компоновки. Обратное утверждение неверно. Во многих случаях компонент должен был бы заполнить полость, но не предпринимает попытки к расширению. Различия между этими двумя случаями демонстрируют примеры, приведенные ниже.
Глава 25. Диспетчер компоновки pack 577 Теперь рассмотрим, что произойдет при увеличении окна. Начало следующего примера напоминает пример, приведенный в листинге 25.7, но размеры главного окна увеличиваются. Листинг 25.11. Изменение размеров окна без опции expand # Для главного окна задается черный цвет . config -bg black # Создание и компоновка двух фреймов frame .menubar -bg white frame .body -width 150 -height 50 -bg grey50 # Кнопки размещаются в разных концах панели инструментов foreach b {alpha beta} { button .menubar.$b -text $b } pack .menubar.alpha -side left pack .menubar.beta -side right # Панель инструментов заполняет пространство по ширине окна pack .menubar -side top -fill x pack .body # Увеличение размеров главного окна wm geometry . 200x100 # Интерактивное изменение размеров разрешается wm minsize . 100 SO Появившееся пространство занимает только компонент .menubar, поскольку при вызове команды pack была указана опция -fill x. Для того чтобы фрейм .body вел себя подобным образом, надо изменить команду pack так, как показано в листинге 25.12. Листинг 25.12. Изменение размеров окна при включенном режиме расширения
578 Часть III. Основы Тк # Выполняются все действия, предусмотренные в листинге 25.11, # затем к .body применяется команда pack pack .body -expand true -fill both Если расширение разрешено для нескольких компонентов, являющихся дочерними по отношению к одному родительскому компоненту, диспетчер компоновки пропорционально распределяет между ними дополнительное пространство. В рассматриваемом примере такой подход вряд ли уместен. В частности, для .menubar расширение задавать не следует. Листинг 25.13. Наличие нескольких расширяемых компонентов # Выполняются все действия, предусмотренные в листинге 25.11, # затем к .menubar и .body применяется команда pack pack .menubar -expand true -fill x pack .body -expand true -fill both Фиксация Если для компонента выделено большее пространство компоновки, чем требуется для его отображения, вы можете размещать компонент в пределах этого пространства с помощью опции -anchor, задаваемой при вызове команды pack. Значения данной опции принято называть якорями. По умолчанию предполагается фиксация по центру области. Остальные значения опции -anchor (n, ne, e, se, s, sw, w и nw) ориентируют компонент "по компасу", т.е. задают направление "север", "северо-восток", "восток" и т.д. Листинг 25.14. Компоновка элементов, демонстрирующая использование опции -anchor # Для главного окна задается черный цвет . config -bg black
Глава 25. Диспетчер компоновки pack 579 # Два фрейма создаются так, что полость остается свободной frame .prop -bg white -height 80 -width 20 frame .base -width 120 -height 20 -bg grey50 pack .base -side bottom # Размещение в полости текстовой метки и компонента .prop label .foo -text Foo pack .prop .foo -side right -expand true Фрейм .base выравнивается по нижней части окна. Затем фрейм .prop и текстовая метка .foo выравниваются по правой части окна; при этом задается опция -expand true, но опция -fill не указывается. Вместо того чтобы разместить компоненты как можно ближе к правому краю, опция -expand выделяет каждому из компонентов половину области, свободной после включения компонента .base. Область делится линией, перпендикулярной оси X. Поскольку опция -anchor не указана, по умолчанию предполагается, что для нее задано значение center. Пример, приведенный в листинге 25.15, демонстрирует другие способы фиксации компонента. Листинг 25.15. Использование значений опции -anchor, отличных от center . config -bg black # Два фрейма создаются так, что полость остается свободной frame .prop -bg white -height 80 -width 20 frame .base -width 120 -height 20 -bg grey50 pack .base -side bottom # Размещение в полости текстовой метки и компонента .prop. # Позиции данных компонентов уточняются с помощью якорей label .foo -text Foo pack .prop -side right -expand true -anchor sw pack .foo -side right -expand true -anchor ne Свободное пространство присутствует как по бокам текстовой метки, так и сверху и снизу от нее. Фрейм .prop может перемещаться только вдоль оси X, поэтому для него доступны лишь три позиции: левая, центральная и правая. Любое из значений, w, nw и sw, опции -anchor приводит к тому, что фрейм будет расположен в левой части области. Значение center, n или s
580 Часть III. Основы Тк располагает фрейм по центру. И, наконец, если опция -anchor имеет значение е, se или пе, фрейм будет расположен в правой части области. Если вы хотите просмотреть все варианты фиксации компонентов, запустите на выполнение фрагмент кода, приведенный в листинге 25.16. Команда update idletasks принудительно выполняет отложенные операции отображения. Команда after 500 переводит сценарий в режим ожидания на 500 миллисекунд. Листинг 25.16. Поочередная демонстрация различных типов фиксации компонентов foreach anchor {center n ne e se s sw w nw center} { pack .foo .prop -anchor $anchor # Обновление отображения update idletasks # Ожидание в течение 500 миллисекунд after 500 } Очередь компоновки Диспетчер компоновки поддерживает порядок очередности, в котором дочерние компоненты помещаются во фрейм. Набор компонентов, включаемых в состав родительского окна, называют очередью компоновки. По умолчанию очередной дочерний компонент добавляется в конец очереди компоновки. В результате первый из дочерних компонентов оказывается ближе других к тому краю окна, который указан в качестве значения опции -side. Опции диспетчера компоновки -before и -after позволяют управлять очередью компоновки, а также дают возможность изменять порядок следования тех компонентов, которые уже включены в родительский фрейм. Листинг 25.17. Управление очередью компоновки # В цикле создаются пять текстовых меток foreach label {one two three four five} { label .$label -text $label pack .$label -side left -padx 5 } # Процедура ShuffleUp перемещает компонент в начало очереди proc ShuffleUp { parent child } {
Глава 25. Диспетчер компоновки pack 581 set first [1index [pack slaves $parent] 0] pack $child -in $parent -before $first } # Процедура ShuffleDown перемещает компонент в конец очереди proc ShuffleDown { parent child } { pack $child -in $parent } ShuffleUp . .five ShuffleDown . .three Интроспекция Команда pack slaves возвращает список дочерних компонентов. Компоненты в списке расположены в том же порядке, что и в очереди компоновки. Процедура ShuffleUp использует команду pack slaves для того, чтобы выделить тот дочерний компонент, перед которым необходимо включить дополнительный элемент. Процедура ShuffleDown реализована несколько проще, поскольку по умолчанию компоненты добавляются в конец очереди компоновки. При повторной обработке диспетчером компоновки компонент сохраняет параметры, определяющие расположение к окне, которые были заданы ранее. Чтобы просмотреть текущие параметры компоновки компонента, надо использовать команду pack info. pack info .five => -in . -anchor center -expand 0 -fill none -ipadx 0 \ -ipady 0 -padx 0 -pady 0 -side left Особенности компоновки полос прокрутки Если размеры окна слишком малы, в нем не помещаются все дочерние компоненты. Для разрешения таких ситуаций также используется очередь компоновки. Если в окне может поместиться лишь часть дочерних компонентов, то элементы, расположенные в хвосте очереди компоновки, не отображаются. Поэтому, если вам необходимо поместить в окно полосу прокрутки и текстовый компонент, то полосу прокрутки надо включать первой. В противном случае текстовый компонент может занять все доступное пространство и полоса прокрутки окажется за пределами окна.
582 Часть III. Основы Тк Выбор родительского компонента при компоновке Практически во всех примерах, приведенных в данной главе, компоненты при компоновке размещаются в родительском фрейме. Однако компонент можно включить в состав любого из потомков этого фрейма. Например, компонент .а.Ь может быть скомпонован в составе .а, .а.с, .a.d.e.f и т.д. Опция -in команды pack позволяет указать альтернативный родительский компонент для компоновки. Решение изменить родительский элемент может быть принято из-за того, что иерархия, обеспечивающая оптимальное размещение компонентов, привела бы к появлению важных компонентов со слишком сложными именами. В листинге 25.4 использовались имена кнопок .one.alpha и .one.right .delta, которые плохо согласуются между собой. Ниже приведена альтернативная реализация того же примера, в котором тот же результат получается при использовании более простых имен кнопок. Листинг 25.18. Использование альтернативных родительских компонентов при компоновке # Создание и компоновка двух фреймов frame .one -bg white frame .two -width 100 -height 50 -bg grey50 # Создание набора кнопок foreach b {alpha beta} { button .$b -text $b pack .$b -in .one -side left } # Создание фрейма для двух дополнительных кнопок frame .one.right foreach b {delta epsilon} { button .$b -text $b pack .$b -in .one.right -side bottom } pack .one.right -side right pack .one .two -side top Используя подобный подход, следует помнить, что порядок создания компонентов имеет большое значение. Фреймы надо создавать в первую очередь и лишь после этого определять компоненты, которые будут включены в состав фреймов. Окна, предназначенные для отображения на экране, формируют стек окон; при этом те из них, которые были созданы последними, закрывают окна, сформированные ранее. Приведенные ниже строки кода написаны некорректно, так как при их выполнении фрейм закроет кнопку.
Глава 25. Диспетчер компоновки pack 583 button .a -text hello frame .b pack .a -in .b Если порядок создания компонентов изменить нельзя, надо использовать команду raise. Стек окон будет более подробно обсуждаться далее в этой главе. raise .a Исключение компонента из очереди компоновки Команда pack forget исключает компонент из очереди компоновки. В результате отображение компонента на экране прекращается и он становится невидимым. Если вы примените эту команду к родительскому фрейму, то структура, сформированная в результате компоновки, сохранится, но все компоненты в составе фрейма не будут отображаться. К исключению компонентов из очереди компоновки можно прибегнуть в том случае, если вы хотите временно сделать недоступной часть интерфейсных элементов. Вы можете создать все интерфейсные элементы и отложить компоновку некоторых из них до тех пор, пока они не понадобятся пользователю. Компоновку компонентов и исключение их из очереди можно осуществлять динамически. Правила компоновки Работая с диспетчером компоновки pack, надо соблюдать следующие правила. • Все элементы в одном фрейме должны быть скомпонованы либо по вертикали (опции -side top и -side bottom), либо по горизонтали (опции -side left и-side right). Объединяя различные типы выравнивания, очень трудно добиться требуемых результатов. Если вам надо реализовать сложное взаиморасположение компонентов, используйте дополнительные фреймы. • По умолчанию диспетчер компоновки pack помещает компоненты в их родительский фрейм. Родительский фрейм должен быть создан ранее, чем компоненты, предназначенные для размещения в нем. • Если вы включаете компоненты в другие фреймы, необходимо помнить о том, что фреймы должны быть созданы раньше компонентов и расположены в стеке окон ниже их.
584 Часть III. Основы Тк • По умолчанию диспетчер компоновки игнорирует атрибуты width и height фреймов, в которых должны быть расположены компоненты. Размеры фрейма изменяются так, чтобы в нем могли поместиться все требуемые компоненты. Для того чтобы отключить автоматическое изменение размеров, надо использовать команду pack propagate. • При работе диспетчера компоновки используются понятия пространство компоновки и пространство отображения. Компонент не обязательно занимает все пространство компоновки, выделенное ему. • Опция -fill разрешает заполнение пространства компоновки по оси X, по оси Y или в обоих направлениях. • Опция -expand true задает расширение пространства компоновки на те части полости, которые в противном случае оставались бы невостребованными. Если на дополнительное пространство претендует несколько компонентов, это пространство распределяется пропорционально между ними. • Опции -ipadx и -ipady резервируют дополнительное пространство отображения в пределах объекта. Это пространство выделяется только в том случае, если есть реальная возможность сделать это. • Опции -padx и -pady резервируют дополнительное пространство компоновки за пределами компонента. Пространство выделяется только в том случае, если есть реальная возможность сделать это. Компонент ни при каких условиях не заполняет пространство, выделенное посредством опций -padx и -pady. Для выделения областей разного размера в качестве значения опции можно указать список из двух элементов (эта возможность реализована в Тк 8.4). Команда раек В табл. 25.1 приведены основные сведения о команде pack. В табл. 25.2 описаны опции, управляющие компоновкой компонента. Эти опции указываются при вызове команды pack configure, а для получения информации о текущих установках применяется команда pack info. Таблица 25.1. Команда pack pack win ?окно . .? Выполняет те же действия, что и pack configure ?опции? pack configure окно Выполняет компоновку одного или более компонен- ?окно . . .? ?опции? тов, используя опции, описанные в табл. 25.2 pack forget окно ?окно Удаляет указанные окна из очереди компоновки ?
Глава 25. Диспетчер компоновки pack 585 Окончание табл. 25.1 pack info окно pack propagate окно ?логическое„значение? pack slaves окно Возвращает параметры компоновки для указанного окна Запрашивает или задает режим согласования размеров для окна, в котором содержатся другие компоненты Возвращает список компонентов, управляемых указанным окном Таблица 25.2. Опции команды pack -after окно -anchor якорь -before окно -expand логическое„значение -fill стиль -in окно -ipadx размер -ipady размер -padx размер -pady размер -side выравнивание Размещение после указанного окна в стеке Значение опции -anchor, или якорь, может быть одним из следующих: center, n, ne, e, se, s, sw, w или nw Размещение перед указанным окном в стеке Управляет расширением на невостребованные части полости Управляет заполнением пространства компоновки. В качестве стиля могут быть указаны следующие значения: х, у, both или попе Размещение в пределах окна Внутреннее дополнение по горизонтали. Задается в экранных единицах измерения Внутреннее дополнение по вертикали. Задается в экранных единицах измерения Внешнее дополнение по горизонтали. Задается в экранных единицах измерения. Значение опции может представляет собой список из двух элементов; в этом случае осуществляется асимметричное дополнение (Тк 8.4) Внешнее дополнение по вертикали. Задается в экранных единицах измерения. Значение опции может представляет собой список из двух элементов; в этом случае осуществляется асимметричное дополнение (Тк 8.4) В качестве значения опции могут быть заданы следующие типы выравнивания: top, right, bottom или left
586 Часть III. Основы Тк Стек окон Для управления расположением элементов в стеке окон используются команды raise и lower. Позиция элементов в стеке окон определяет их отображение на экране. Окна, занимающие более высокое положение в стеке, закрывают окна, расположенные ниже. По умолчанию новые окна создаются в верхней части стека и располагаются поверх окон, созданных ранее. Рассмотрим приведенные ниже команды. button .one frame .two pack .one -in .two Если вы выполните эти команды, кнопка не будет отображаться на экране. Причина в том, что фрейм занимает более высокое положение в стеке и закрывает кнопки. Изменить порядок следования окон в стеке позволяет команда raise. raise .one .two Приведенная выше команда располагает окно .one непосредственно над окном .two. Если второй параметр не указан, окно .one будет помещено в вершине стека. Команда lower имеет тот же формат. Если при ее вызове указывается один параметр, окно помещается ниже всех остальных окон в стеке. В противном случае оно располагается непосредственно под окном, указанным в качестве второго параметра. Команды raise и lower можно применять к окнам верхнего уровня, управляя тем самым их взаимным расположением. Например, если пользователю понадобилось диалоговое окно, которое уже было отображено ранее, вы можете использовать команду raise, чтобы разместить это окно поверх других на рабочем столе. Для того чтобы определить взаимное расположение окон верхнего уровня в стеке, надо использовать команду wm stackorder. (Дополнительная информация по этому вопросу приведена в главе 44.)
Глава 26 Диспетчер компоновки grid В данной главе рассматривается диспетчер компоновки grid, который размещает компоненты в виде таблицы, автоматически согласовывая размеры ячеек с размерами компонентов. Диспетчер компоновки grid был реализован в Тк 4.1. ^Диспетчер компоновки grid размещает компоненты в виде таблицы с изменяющимися размерами строк и столбцов. Вы задаете строки и столбцы, занимаемые компонентами, а размеры ячеек автоматически подбираются такими, чтобы в них могли поместиться все необходимые компоненты. Данный тип диспетчера очень удобен для представления данных в виде таблиц. Кроме того, этот диспетчер предоставляет средства для управления размерами строк и столбцов, а также для динамического изменения размеров таблицы. Объявляя вложенные фреймы и связывая с ними свои диспетчеры компоновки, вы можете реализовать достаточно сложные конфигурации компонентов на экране. Не следует применять диспетчеры раек и grid к одному и тому же компоненту Как было сказано в главе 25, при формировании пользовательского интерфейса можно использовать как диспетчер pack, так и диспетчер grid. Однако для размещения дочерних элементов конкретного компонента можно использовать только один диспетчер. Размещение компонентов в виде таблицы В листинге 26.1 показан пример использования диспетчера компоновки grid для размещения набора текстовых меток и фреймов в виде таблицы. При этом используется возможность относительной компоновки, предоставляемая
588 Часть III. Основы Тк данным диспетчером. Вместо того чтобы явно объявлять строки и столбцы, мы будем формировать таблицу, исходя из порядка следования команд grid и размещения их компонентов. Каждая команда grid создает новую строку, а указанные в ней компоненты формируют столбцы. В данном примере таблица состоит из двух столбцов; при каждой итерации цикла к ней добавляется новая строка. Диспетчер компоновки grid создает столбцы размера, достаточного для включения наибольшего компонента. Компоненты меньших размеров располагаются по центру ячеек. Именно по этой причине текстовые метки в рассматриваемом примере выровнены по центру. Листинг 26.1. Простой пример использования диспетчера компоновки grid foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color 2 Опция -sticky Если размер ячейки оказывается больше, чем размер содержащегося в ней компонента, вы можете изменять размеры и размещение компонента, задавая опцию -sticky. Данная опция объединяет функциональные возможности опций -fill и -anchor диспетчера компоновки pack. С помощью этой опции вы можете указать, с какой из границ ячейки должен непосредственно соприкасаться компонент. В качестве значения опции -sticky допустимо любое сочетание букв n, e, w и s. Соответственно размеры и расположение компонента будут изменяться так, чтобы он непосредственно соприкасался с верхней, правой, левой и нижней границами ячейки. Вы можете использовать конкатенацию указанных выше букв (например, news) либо разделять их пробелами или запятыми (например, n,e,w,s). В листинге 26.2 опция -sticky w изменяет расположение текстовой метки и выравнивает ее по левому краю ячейки. Опция -sticky ns растягивает закрашенный фрейм так, что он занимает по высоте всю строку.
Глава 26. Диспетчер компоновки grid 589 Листинг 26.2. Использование опции -sticky при вызове команды grid foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color grid .l$color -sticky w grid .f$color -sticky ns 2 В примере, представленном в листинге 26.2, команда grid используется двумя способами. Первый раз она вызывается для определения позиций компонентов. Далее эта же команда модифицирует существующие параметры; поскольку распределения компонентов по столбцам и строкам таблицы уже известно, при вызове grid задается лишь опция -sticky. Позиции компонентов в строках и столбцах таблицы можно указать непосредственно с помощью атрибутов row и column. При этом приходится затрачивать дополнительные усилия по сравнению с относительной компоновкой, но в ряде случаев это приходится делать. Например, такой подход придется использовать в том случае, если вам необходимо динамически перемещать компонент из одной ячейки в другую. В листинге 26.3 показан код, в котором реализовано непосредственное управление строками и столбцами. При его запуске получается тот же результат, что и при выполнении предыдущего примера. Листинг 26.3. Явное указание строк pi столбцов в команде grid set row 0 foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 grid .l$color -row $row -column 0 -sticky w grid .f$color -row $row -column 1 -sticky ns incr row }
590 Часть III. Основы Тк Внешнее дополнение, реализуемое с помощью опций -padx и -pady Задавая опции -padx и -pady, можно указать размер свободной области между компонентом и границами ячейки. В листинге 26.4 с помощью внешнего дополнения реализуется отступ текстовых меток от левого края таблицы, а также задается свободное пространство между цветными полосами. Листинг 26.4. Использование опций команды grid, определяющих внешнее дополнение foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color grid .l$color -sticky w -padx 3 grid .f$color -sticky ns -pady 1 2 В Tk 8.4 разработчику была предоставлена возможность создавать асимметричное дополнение, указывая в качестве значения опции список из двух элементов. Например, опция -padx {0.125i 0.25i} определяет слева от компонента свободное пространство размером 1/8 дюйма, а справа — пространство размером 1/4 дюйма. Внутреннее дополнение, реализуемое с помощью опций -ipadx и -ipady Применяя внутреннее дополнение, вы можете предоставить компоненту большее пространство отображения, чем он обычно занимает. При использовании внутреннего дополнения размеры таблицы увеличиваются. В отличие от -ipadx и -ipady, опция -sticky позволяет растянуть некоторые компоненты, но при этом размеры таблицы останутся неизменными. В примере, приведенном в листинге 26.5, благодаря использованию опции -ipady высота текстовых меток увеличивается.
Глава 26. Диспетчер компоновки grid 591 Листинг 26.5. Использование внешнего дополнения при работе с диспетчером компоновки grid foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color grid .l$color -sticky w -padx 3 -ipady Б grid .f$color -sticky ns -pady 1 2 Размещение нескольких компонентов в одной ячейке Фрагмент кода, приведенный в листинге 26.6, демонстрирует различные значения опции -sticky. При этом используется возможность размещения нескольких компонентов в одной ячейке таблицы. В данном случае в каждую ячейку помещается квадратный фрейм, после чего в той же ячейке располагается текстовая метка. Для меток, находящихся в разных ячейках, заданы различные значения опции -sticky. Необходимо следить за тем, чтобы фрейм был создан первым, иначе он будет закрывать метку. (О стеке окон см. в главе 25.) Опции -padx и -pady создают внешнее заполнение и тем самым формируют зазор между меткой и границами ячейки. Листинг 26.6. Варианты значений опции -sticky set index 0 foreach x {news ns ew " " new sew wsn esn nw ne sw se n s w e} { frame .f$x -borderwidth 2 -relief ridge -width 40 -height 40 grid .f$x -sticky news \ -row [expr {$index/4}] -column [expr {$index°/04}] label .l$x -text $x -background white
592 Часть III. Основы Тк grid .l$x -sticky $x -padx 2 -pady 2 \ -row [expr {$index/4}] -column [expr {$index°/04}] incr index Объединение строк и столбцов Компонент может занимать несколько ячеек. Число строк и столбцов, необходимых для размещения компонента, задается с помощью опций -rowspan и -colmnnspan. В листинге 26.7 приведен пример использования опций row, column, rowspan и columnspan. Листинг 26.7. Размещение компонентов в нескольких ячейках . config -bg white foreach color {888 999 aaa bbb ccc fff} { frame .$color -bg #$color -width 40 -height 40 } grid .888 -row 0 -column 0 -columnspan 3 -sticky news grid .999 -row 1 -column 0 -rowspan 2 -sticky news grid .aaa -row 1 -column 1 -columnspan 2 -sticky news grid .bbb -row 2 -column 2 -rowspan 2 -sticky news grid .ccc -row 3 -column 0 -columnspan 2 -sticky news grid .fff -row 2 -column 1 -sticky news
Глава 26. Диспетчер компоновки grid 593 При выполнении команды grid, в которой размещение управляется порядком следования компонентов, бывает необходимо объединять строки или столбцы либо оставлять свободными ячейки. Для этой цели предусмотрены специальные символы. Символ - задает объединение столбцов. Символ ~ задает объединение строк. Символ х указывает на то, что ячейка должна быть пропущена. При использовании размещения, управляемого порядком следования команд и их параметров, очень просто вносить небольшие изменения в расположение элементов. В листинге 26.8 приведен код, с помощью которого реализуется то же размещение, что и в предыдущем примере. Листинг 26.8. Альтернативный способ объединения строк и столбцов . config -bg white foreach color {888 999 aaa bbb ccc ddd fff} { frame .$color -bg #$color -width 40 -height 40 } grid .888 - - -sticky news grid .999 .aaa - -sticky news grid ~ .fff .bbb -sticky news grid .ccc - ~ -sticky news Атрибуты строк и столбцов Диспетчер компоновки grid поддерживает атрибуты, задаваемые для целой строки или столбца. Эти атрибуты управляют размерами соответствующих ячеек. Для установки атрибутов или определения их текущих значений используются операции grid rowconf igure и grid columnconf igure. grid columnconf igure окно столбец ?атрибуты? grid rowconf igure окно строка ?атрибуты? Если атрибуты не заданы, возвращается информация о текущих установках. В качестве второго параметра может быть задан список, что позволяет одновременно изменять атрибуты нескольких строк или столбцов. Дополнение строк и столбцов Атрибут -pad увеличивает размер ячеек в строке или в столбце. Первоначально размер ячеек определяется наибольшим компонентом. Опция -pad увеличивает эти размеры. Если даже ячейка увеличена, существует возможность заполнения ее компонентом. Сделать это позволяет опция -sticky.
594 Часть III. Основы Тк Дополнение строк и столбцов можно сравнить с внутренним дополнением компонентов, так как выделенное пространство может быть заполнено. Для сравнения, атрибуты padx и pady компонента создают пустое пространство между ним и границами ячейки. Это пространство не подлежит заполнению. В листинге 26.9 показано различие между дополнением строки или столбца и дополнением компонента. Дополнение строки увеличивает ее высоту, в то время как дополнение компонента . f 1 отделяет границы компонента от границ ячейки. Листинг 26.9. Дополнение строк и компонентов . config -bg black label .fl -text left -bg #ccc label Л2 -text right -bg #aaa grid .fl .f2 -sticky news ;# дополнение отсутствует grid .fl -padx 10 -pady 10 ;# дополнение для ячейки grid rowconfigure . 0 -pad 20 ;# дополнение для строки Минимальный размер ячейки Опция -minsize ограничивает минимальные размеры ячеек, принадлежащих строке или столбцу. Размеры строк или столбцов могут увеличиваться с увеличением содержащихся в них компонентов, но не могут стать меньше указанной величины. Опция -minsize полезна при формировании пустых строки и столбцов. Такой подход более эффективен, чем создание дополнительного фрейма. Поддержка окон с изменяемыми размерами Если размеры родительского окна больше, чем необходимо для отображения таблицы, окно сжимается до требуемых размеров. Запретить изменение размеров окна можно с помощью команды grid propagate. В этом случае таблица будет размещаться по центру окна. Если размеры окна настолько малы, что в нем не помещается таблица, она фиксируется в левом верхнем
Глава 26. Диспетчер компоновки grid 595 углу окна, а фрагменты, выступающие вправо и вниз за границы окна, не отображаются. По умолчанию при увеличении родительского фрейма размеры строк и столбцов остаются постоянными. Задать изменение размеров строки или столбца можно с помощью опции -weight. Значением этой опции является целое число больше нуля. В листинге 26.10 показаны текстовый компонент и две полосы прокрутки, обработанные диспетчером компоновки grid. Протокол взаимодействия полосы прокрутки и текстового компонента описывается в главе 33. Текстовый компонент находится в нулевом столбце нулевой строки и может расширяться в обоих направлениях. Вертикальная полоса прокрутки расположена в первом столбце нулевой строки, и размеры ее могут изменяться только в направлении Y. И, наконец, горизонтальная полоса прокрутки находится в нулевом столбце первой строки, поэтому размеры ее могут изменяться только в направлении X. Листинг 26.10. Текстовый компонент и полосы прокрутки text .text -yscrollcommand ".yscroll set" \ -xscrollcommand ".xscroll set"-width 40 -height 10 scrollbar .yscroll -command ".text yview" -orient vertical scrollbar .xscroll -command u.text xview" -orient horizontal grid .text .yscroll -sticky news grid .xscroll -sticky ew grid rowconfigure . 0 -weight 1 grid columnconfigure . 0 -weight 1 Для того чтобы размеры строк или столбцов по-разному изменялись при увеличении родительского окна, надо задать для них различные веса. Однако этой возможностью надо пользоваться аккуратно, так как изменение размеров происходит только при наличии дополнительного пространства. Предположим, например, что четыре столбца таблицы имеют ширину 10, 20, 30 и 40 пикселей. Таким образом, общая их ширина составляет 100 пикселей. Если ширина родительского окна возрастет до 140 пикселей, окажется, что для отображения таблицы есть 40 дополнительных пикселей. Если для каждого
596 Часть III. Основы Тк столбца был задан вес 1, каждый из них получил бы по 10 пикселей дополнительного пространства. Теперь предположим, что для столбца 0 задан вес 0, для столбцов 1 и 2 — вес 1 и для столбца 3 — вес 2. Тогда с увеличением размеров родительского окна столбец 0 не будет увеличиваться, столбцы 1 и 2 получат по 10 дополнительных пикселей и столбец 3 увеличится на 20 пикселей. В большинстве случаев применяются веса, равные 0 или 1. При сжатии окна наличие весов приводит в возникновению обратного эффекта. Если строка или столбец должны сжиматься, вес определяет степень сжатия. Строки и столбцы сжимаются тем больше, чем большие значения весов для них заданы. Предположим, например, что с помощью диспетчера компоновки grid два одинаковых фрейма одинакового размера размещены в двух столбцах таблицы. Когда пользователь увеличивает размеры окна, фрейм в столбце, которому соответствует больший вес, увеличивается с большей скоростью. Если окно уменьшается, то этот фрейм также уменьшается быстрее другого. Опция -uniform Опция -uniform упрощает создание столбцов (или строк) одинаковой ширины (высоты). С помощью данной опции создается группа столбцов или строк. Значение опции может быть произвольным (например, xyz). Считается, что все столбцы (или строки) с одинаковым идентификатором, заданным с помощью опции -uniform, принадлежат одной и той же группе. Если для них задано одно и то же значение опции -weight, размеры их будут одинаковыми. Если же, например, один из столбцов или одна из строк в группе имеет вес, вдвое превышающий вес других столбцов (или строк), то этот столбец (или строка) будет вдвое больше остальных. Пример использования различных весов при работе с компонентами из одной группы показан в листинге 26.11. Листинг 26.11. Ширина столбцов, сгруппированных с помощью опции -uniform foreach x {alpha beta gamma x у z} { label .$x -text $x }
Глава 26. Диспетчер компоновки grid 597 .beta config -bg white .у config -bg white grid .alpha .beta .gamma -sticky news grid .x .y .z -sticky news grid columnconfigure . H0 1 2" -uniform groupl -weight 1 grid columnconfigure . 1 -weight 2 Команда grid В табл. 26.1 приведены основные сведения о команде grid. В табл. 26.2 перечислены опции, которые задаются при вызове команды grid configure. Таблица 26.1. Команда grid grid bbox окно ? столбец.. 1 строка^ 1 ? ?столбец„2 строка_2? grid columnconfigure окно столбец ?опции? grid configure окно ?окно ...? ?опции? grid forget окно ?окно . . .? grid info окно grid location окно х у grid propagate окно ?логическое_значение? grid rowconfigure окно строка ?опции? grid remove компонент grid size окно grid slaves окно ?-row строка? ?-column столбец? Возвращает ограничивающий прямоугольник для всей таблицы, для ячейки, находящейся на пересечении указанных столбца и строки, либо ячеек, находящихся в заданном диапазоне Устанавливает либо запрашивает конфигурацию столбца. При вызове команды могут быть заданы опции -minsize, -weight, -pad и -uniform Выполняет компоновку одного или нескольких компонентов. Детали компоновки задаются с помощью опций, описанных в табл. 26.2 Удаляет указанные окна из стека Возвращает опции, использованные при компоновке указанного окна Возвращает номер столбца и строки той ячейки, которой принадлежат точки х, у в указанном окне Разрешает или запрещает согласование размеров окна Устанавливает либо запрашивает конфигурацию строки. При вызове команды могут быть заданы опции -minsize, -weight, -pad и -uniform Отменяет компоновку компонента, но запоминает его конфигурацию Возвращает число столбцов и строк Возвращает список либо всех компонентов, содержащихся в окне, либо тех из них, которые находятся в указанной строке или столбце
598 Часть III. Основы Тк Таблица 26.2. Опции, используемые при компоновке с помощью диспетчера grid -in окно -column столбец -columnspan n -ipadx число„пикселей -ipady число„пикселей -padx число„пикселей -pady число„пикселей -row строка -rowspan n -sticky границы Размещение в указанном окне Номер столбца. Отсчет столбцов начинается с нуля Объединение п столбцов Внутреннее дополнение компонента по горизонтали Внутреннее дополнение компонента по вертикали Внешнее дополнение компонента по горизонтали. Значение данной опции может представлять собой список из двух элементов. В этом случае осуществляется несимметричное дополнение (Тк 8.4) Внешнее дополнение компонента по вертикали. Значение данной опции может представлять собой список из двух элементов. В этом случае осуществляется несимметричное дополнение (Тк 8.4) Номер строки. Отсчет строк начинается с нуля Объединение п строк Позиционирование компонента путем указания любого сочетания северной (п), южной (s), восточной (е) и западной (w) границ ячейки. Значение {} задает позиционирование по центру ячейки
Глава 27 Диспетчер компоновки place В данной главе рассматривается диспетчер компоновки place, осуществляющий позиционирование компонентов на экране. J Диспетчер компоновки place гораздо проще, чем диспетчеры pack и grid. При работе с данным диспетчером разработчик определяет абсолютные либо относительные позиции и размеры окон. Такой подход может быть приемлемым в некоторых частных случаях, но если вам необходимо размещать большое число элементов в разных окнах, диспетчер компоновки place подходит плохо. Относительное позиционирование, поддерживаемое диспетчером place, удобно для создания специальных диспетчеров компоновки. Чаще всего place применяется для формирования границы между двумя соприкасающимися окнами и управления ею. Общие сведения о диспетчере компоновки place Команда place позволяет задавать ширину и высоту окна, а также фиксировать его по горизонтали и по вертикали. Размеры и расположение могут задаваться как в абсолютных, так и в относительных единицах. Относительное позиционирование считается более удобным для создания интерфейсов. В листинге 27.1 команда place используется для размещения окна по центру его родительского окна. Подобное выражение может использоваться для отображения диалоговых окон. Листинг 27.1. Выравнивание окна по центру с помощью диспетчера компоновки place place $w -in $parent -relx 0.5 -rely 0.5 -anchor center
600 Часть III. Основы Тк С помощью опций -relx и -rely компонент $w фиксируется в окне $parent. Относительное значение X (или Y), равное нулю, соответствует выравниванию компонента по левому (или верхнему) краю окна $parent. Значение, равное единице, определяет выравнивание по правому (или нижнему) краю $parent. И, наконец, значение 0,5 задает выравнивание по центру. Опция -anchor определяет, относительно какой позиции окна $w должна быть выполнена фиксация. В листинге 27.1 указана точка фиксации center; в результате приведенное выше выражение приводит к выравниванию центра окна $w по центру окна $parent. Относительная высота и ширина используются для того, чтобы вычислять размеры одного компонента, исходя из размеров другого. В листинге 27.2 окно полностью закрывает родительское окно. В данном случае используется точка фиксации по умолчанию, находящаяся в верхнем левом углу окна (nw). Листинг 27.2. Использование команды place для размещения одного окна поверх другого place $w -in $parent -relwidth 1 -relheight 1 -x 0 -у О Абсолютные размеры окна и размеры, вычисленные относительно другого окна, складываются (например, в одной команде ширина может быть задана в результате суммарного действия опций -width и -relwidth). Используя обе опции, вы можете сделать ваше окно несколько меньше или несколько больше родительского. В листинге 27.3 отрицательные значения -width и -height заданы для того, чтобы уменьшить размеры окна $w относительно $parent. Листинг 27.3. Совместное использование опций, определяющих абсолютные и относительные размеры place $w -in $parent -relwidth 1 -relheight 1 -x 0 -у О \ -width -4 -height -4 Окно $parent не обязательно должно быть родительским по отношению к $w. Оно может быть потомком родительского окна или окном верхнего уровня. Необходимо лишь, чтобы $w было доступным тогда, когда доступно окно $parent. Аналогичные правила действуют при использовании диспетчера компоновки pack. Кроме того, нет необходимости размещать компонент внутри другого компонента. В листинге 27.4 окно $w располагается на пять пикселей выше окна $sibling. Если позиция $sibling изменится, окно $w переместится вместе с ним. Данный подход применим тогда, когда необходимо разместить в углах окна с изменяемыми размерами другие компоненты. При увеличении или уменьшении окна эти компоненты автоматически изменяют свои позиции на экране.
Глава 27. Диспетчер компоновки place 601 Листинг 27.4. Использование команды place для размещения одного окна выше другого place $w -in $sibling -relx 0.5 -у -5 -anchor s \ -bordermode outside Опция -bordermode outside указывает на то, что при позиционировании $w обрамление $sibling игнорируется. В этом случае позиция вычисляется относительно внешней границы $sibling. По умолчанию обрамление учитывается, что упрощает размещение окна в родительском окне. Родительский компонент не обязательно должен быть фреймом. В листинге 27.1 предпринимается попытка поместить диалоговое окно в центре текстового компонента. В листинге 27.4 и $sibling, и $w могут представлять собой текстовые метки. Управление панелями Поддержка относительного размера и расположения делает команду place пригодной для создания новых диспетчеров компоновки. В листинге 27.5 показан код диспетчера компоновки, ориентированного на управление панелями. В данном примере два фрейма, или панели, располагаются в третьем фрейме. Третий фрейм играет роль контейнера, который вмещает две панели и управляет границей между ними. В Тк 8.4 был реализован компонент panedwindow, с помощью которого можно управлять произвольным числом горизонтальных или вертикальных панелей. Подробно компонент panedwindow рассматривается в главе 28. Листинг 27.5. Процедура Pane_Create, формирующая вертикальные или горизонтальные панели proc Pane.Create {fl f2 args} { # Отображение необязательных параметров в элементы массива set t(-orient) vertical set t(-percent) 0.5 set t(-in) [winfo parent $fl] array set t $args
602 Часть III. Основы Тк # Связывание элемента массива с родительским фреймом set master $t(-in) upvar #0 Pane$master pane array set pane [array get t] # Формирование маркера и установка атрибутов, которые # не должны изменяться. Для формирования тонкой # разделительной линии размеры фреймов по соответствующей # координате устанавливаются на один пиксель меньше, # чем необходимо, а для главного окна задается черный цвет. set pane(l) $fl set pane(2) $f2 set pane(grip) [frame $master.grip -background gray50 \ -width 10 -height 10 -bd 1 -relief raised \ -cursor crosshair] if {[string match vert* $pane(-orient)]} { set pane(D) Y;# Adjust boundary in Y direction place $pane(l) -in $master -x 0 -rely 0.0 -anchor nw \ -relwidth 1.0 -height -1 place $pane(2) -in $master -x 0 -rely 1.0 -anchor sw \ -relwidth 1.0 -height -1 place $pane(grip) -in $master -anchor с -relx 0.8 } else { set pane(D) X~;# Adjust boundary in X direction place $pane(l) -in $master -relx 0.0 -y 0 -anchor nw \ -relheight 1.0 -width -1 place $pane(2) -in $master -relx 1.0 -y 0 -anchor ne \ -relheight 1.0 -width -1 place $pane(grip) -in $master -anchor с -rely 0.8 } $master configure -background black # Установка связываний для изменения размеров, # для перетаскивания маркера и связывания <Configure>. bind $master <Configure> [list PaneGeometry $master] bind $pane(grip) <ButtonPress-l> \ [list PaneDrag $master °/0$pane(D)] bind $pane(grip) <Bl-Motion> \ [list PaneDrag $master °/0$pane(D)] bind $pane(grip) <ButtonRelease-l> \
Глава 27. Диспетчер компоновки place 603 [list PaneStop $master] # Создание исходной компоновки PaneGeometry $master } Разбор параметров и поддержка состояния При вызове процедуре Pane_Create передаются два компонента и произвольный набор параметров. Обращение к данной процедуре осуществляется следующим образом: Pane_Create fl f2 ?-orient xy? ?-percent значение? ?-in окно? Все необязательные параметры доступны через $args. Структура атрибут-значение этих параметров используется для инициализации временного массива t. Перед обработкой $args устанавливаются значения атрибутов по умолчанию. Заполнение массива t значениями опций демонстрирует следующий фрагмент кода. Размеры его невелики, но в нем не предусмотрена обработка ошибок: set t(-orient) vertical set t(-percent) 0.5 set t(-in) [winfo parent $fl] array set t $args Информация о состоянии содержится в массиве, имя которого ассоциируется с основным фреймом. Имя основного фрейма неизвестно до тех пор, пока не будет выполнен разбор параметров, поэтому используется t. После выполнения команды upvar значения параметров копируются из временного массива в глобальный массив, предназначенный для хранения данных о состоянии. set master $t(-in) upvar #0 Pane$master pane array set pane [array get t] Выравнивание фреймов В листинге 27.5 при создании фреймов задается ряд параметров. Эти параметры запоминаются, и остальные характеристики фреймов согласуются с ними, чтобы сформировать границу между фреймами. Подобным образом параметры настройки поддерживаются во всех диспетчерах компоновки Тк. Первоначально размещение по вертикали задается следующим образом:
604 Часть III. Основы Тк place $pane(l) -in $parent -x 0 -rely 0.0 -anchor nw \ -relwidth 1.0 -height -1 place $pane(2) -in $parent -x 0 -rely 1.0 -anchor sw \ -relwidth 1.0 -height -1 place $pane(grip) -in $parent -anchor с -relx 0.8 Расположение верхнего и нижнего фреймов задается путем указания абсолютных значений X и относительных значений Y; точка фиксации выбирается так, чтобы каждый фрейм был видимым в главном фрейме. Например, нижний фрейм располагается в левом нижнем углу контейнера; на это указывают опции -х 0 и -rely 1.0. Опция -anchor sw фиксирует в этой позиции левый нижний угол фрейма. Размеры вложенных фреймов также задаются как сочетание абсолютных и относительных значений. Ширина фрейма указывается равной ширине контейнера с помощью опции -relwidth 1.0. Опция -height -1 уменьшает высоту фрейма на единицу. Значение -1 складывается с относительной высотой фрейма. В результате между фреймами, содержащимися в основном окне, остается небольшой зазор. Маркер, используемый для изменения размеров, представляет собой небольшой фрейм, расположенный на границе между вложенными фреймами. Первоначально маркер располагается ближе к одной из границ окна (опция -relx 0.8). Впоследствии он помещается на границу между фреймами с помощью опции -rely. Для того чтобы пользователь мог определить момент активизации маркера, с ним связывается курсор специальной формы. Связывание событий В данном примере осуществляется связывание событий. В частности, событие <Conf igure> возникает тогда, когда пользователь изменяет размеры главного окна. Когда пользователь помещает курсор мыши на маркер, нажимает кнопку мыши и перетаскивает границу между фреймами, то в результате его действий последовательно генерируются событие <ButtonPress-l>, одно или несколько событий <Bl-Motion> и, наконец, событие <ButtonRelease-l>. Подробно связывание событий рассматривается в главе 29. В данном примере для связывания событий вызываются следующие Тс1-команды: bind $parent <Configure> [list PaneGeometry $parent] bind $pane(grip) <ButtonPress-l> \ [list PaneDrag $parent °/0$pane(D)] bind $pane(grip) <Bl-Motion> \ [list PaneDrag $parent °/0$pane(D)] bind $pane(grip) <ButtonRelease-l> [list PaneStop Sparent]
Глава 27. Диспетчер компоновки place 605 Управление размещением фреймов Код данного примера составлен так, что с его помощью можно поддерживать как горизонтальное, так и вертикальное расположение. Переменная pane(D) может иметь либо значение X (горизонтальное расположение), либо значение Y (вертикальное расположение). Это значение используется при обработке событий для получения °/0Х или °/0Y. При возникновении событий УоХ и °/oY замещаются значениями позиции курсора мыши X и Y. Данное значение передается процедуре PaneDrag как параметр D. Процедура PaneDrag запоминает предыдущую позицию в pane(lastD) и использует ее для обновления границы между вложенными панелями. Листинг 27.6. Процедура PaneDrag поддерживает соотношение между вложенными панелями proc PaneDrag {master D} { upvar #0 Pane$master pane if [info exists pane(lastD)] { set delta [expr double($pane(lastD) - $D) \ / $pane(size)] set pane(-percent) [expr $pane(-percent) - $delta] if {$pane(-percent) < 0.0} { set pane(-percent) 0.0 } elseif {$pane(-percent) > 1.0} { set pane(-percent) 1.0 } PaneGeometry $master } set pane(lastD) $D } proc PaneStop {master} { upvar #0 Pane$master pane catch {unset pane(lastD)} 2 Процедура PaneGeometry устанавливает позиции фреймов. Она вызывается при изменении размеров главного окна, поэтому при ее выполнении обновляется pane (size). Она также вызывается при перетаскивании маркера. Размеры двух вложенных фреймов определяются путем установки относительного значения высоты. Как вы помните, относительные значения высоты используются совместно с абсолютным значением, равным —1, в результате чего между фреймами сохраняется небольшой зазор.
606 Часть III. Основы Тк Листинг 27.7. Процедура PaneGeometry изменяет расположение вложенных фреймов proc PaneGeometry {master} { upvar #0 Pane$master pane if {$pane(D) == "X"} { place $pane(l) -relwidth $pane(-percent) place $pane(2) -relwidth [expr 1.0 - $pane(-percent)] place $pane(grip) -relx $pane(-percent) set pane(size) [winfo width $master] > else { place $pane(l) -relheight $pane(-percent) place $pane(2) -relheight [expr 1.0 - $pane(-percent)] place $pane(grip) -rely $pane(-percent) set pane(size) [winfo height $master] } } proc PaneTest {{p .p} {orient vert}} { catch {destroy $p} frame $p -width 200 -height 200 label $p.l -bg blue -text foo label $p.2 -bg green -text bar pack $p -expand true -fill both pack propagate $p off Pane_Create $p.l $p.2 -in $p -orient $orient -percent 0.3 } Команда place В табл. 27.1 приведены основные сведения о команде place. Таблица 27.1. Команда place place окно ?окно . .? Выполняется аналогично place configure ?опции? place configure окно Размещает один или несколько компонентов, учиты- ?окно . . ? ?опции? вая заданные значения опций. Назначение опций описывается в табл. 27.2 place forget окно ?окно ...? Удаляет указанные окна из стека
Глава 27. Диспетчер компоновки place 607 Окончание табл. 27.1 place info окно place slaves окно Возвращает параметры, определяющие размещение указанного окна Возвращает список компонентов, содержащихся в указанном окне В табл. 27.2 перечислены опции, устанавливаемые командой place configure. Сведения о текущих установках можно получить, вызвав команду place info. Таблица 27.2. Опции, определяющие особенности размещения компонентов -in окно -anchor тип_фиксации -х координата -relx смещение -у координата -rely смещение -width размер -relwidth размер -height размер -relheight размер -bordermode режим Размещение внутри или относительно указанного окна Значения опции, или якоря: center, n, ne, e, se, s, sw, w или nw. По умолчанию принимается значение nw Позиция точки фиксации по горизонтали. Задается в экранных единицах измерения Относительная позиция по горизонтали: 0.0 — левая граница, 1.0 — правая граница Позиция точки фиксации по вертикали. Задается в экранных единицах измерения Относительная позиция по вертикали: 0.0 — верхняя граница, 1.0 — нижняя граница Ширина окна в экранных единицах измерения Ширина окна. Вычисляется относительно ширины родительского окна. Если указано значение 1.0, окна имеют одинаковую ширину Высота окна в экранных единицах измерения Высота окна. Вычисляется относительно высоты родительского окна. Если указано значение 1.0, окна имеют одинаковую высоту Если задан режим inside, то размер и позиция вычисляются относительно части родительского окна, лежащей в обрамлении. Режим outside указывает на то, что размер и позиция вычисляются относительно внешней границы родительского окна. По умолчанию принимается значение inside
Глава 28 Компонент panedwindow Компонент panedwindow был впервые реализован в Тк 8.4. С его помощью можно отображать компоненты в составе панелей, размеры которых изменяются по горизонтали либо по вертикали. Г\омпонент panedwindow может содержать произвольное число панелей, разделенных горизонтальными либо вертикальными границами. В каждой панели содержится один компонент, а граница, разделяющая каждую пару панелей, может перемещаться. Изменение положения границы приводит к изменению размеров панелей, находящихся по обе стороны от нее. Если размеры самого компонента panedwindow изменяются (например, в результате действий пользователя), то расширяется либо сокращается последняя панель (самая правая либо самая нижняя). Использование panedwindow Компонент panedwindow достаточно прост. Для того чтобы включить его в приложение, требуются лишь несложные действия, связанные с его конфигурированием. Наиболее часто используется атрибут orient, определяющий, должны ли панели разделяться по горизонтали или по вертикали. Нередко также применяется атрибут showHandle. Он управляет отображением небольшого прямоугольного маркера, указывающего пользователю на то, что граница между панелями может перемещаться. В системе Windows атрибут showHandle по умолчанию имеет значение false, т.е. граница между панелями выглядит так, как и в большинстве приложений, написанных для этой системы. Другие атрибуты предназначены для управления размером, позиционированием, а также определяют внешний вид маркеров, границ между компонентами и самих компонентов.
Глава 28. Компонент panedwindow 609 Управление содержимым панелей Создав panedwindow, можно добавлять к нему компоненты, используя для этой цели операцию add. Одна операция add позволяет добавить несколько компонентов. В окне panedwindow каждый компонент помещается в отдельную панель, отделенную от других панелей перемещающейся границей. По умолчанию компоненты располагаются в том порядке, в котором они были включены с помощью операции add. Однако вы можете изменить поведение, принятое по умолчанию, указав опции -after и -before. В этом случае новые компоненты включаются перед текущим компонентом или после него. Подобно другим диспетчерам компоновки, panedwindow позволяет создавать дополнения, указывая опции -padx и -pady. Атрибут -minsize позволяет задавать минимальный размер компонента (размер указывается в любых единицах, поддерживаемых Тк). Вы также можете управлять размещением компонента в составе панели, указав опцию -sticky; она действует аналогично одноименной опции диспетчера компоновки grid. По умолчанию в panedwindow принимается значение опции -sticky, равное nsew, т.е. компонент полностью занимает панель. К компоненту panedwindow нельзя применять диспетчеры компоновки pack, grid и place. Компонент panedwindow является не только контейнером для других компонентов, но также выполняет функции диспетчера компоновки. Он управляет размерами и расположением содержащихся в нем компонентов. Поэтому попытки управлять содержимым panedwindow с помощью команд pack, grid или place недопустимы. При создании сложных интерфейсов вы можете включить в panedwindow в качестве компонентов фреймы, к которым применимы указанные выше диспетчеры компоновки. В качестве примера рассмотрим два текстовых компонента, содержащихся в окне. Каждый из них надо снабдить горизонтальной и вертикальной полосой прокрутки; проще всего сделать это, используя диспетчер компоновки grid. Все эти компоненты удобно разместить в двух панелях, которые в свою очередь содержатся в panedwindow. В данном случае в качестве контейнера для текстового компонента и полос прокрутки удобно использовать фрейм labelf rame. Два компонента labelf rame следует поместить в panedwindow. Код, реализующий данное размещение, приведен в листинге 28.1.
610 Часть III. Основы Тк Листинг 28.1. Компонент panedwindow, содержащий компоненты сложной структуры # Создание компонента panedwindow, предназначенного # для управления другими компонентами panedwindow .p -orient vertical pack .p -expand yes -fill both -showhandle 1 # Создание двух компонентов labelframe. Каждый из них содержит # текст, кроме того, с ними связаны полосы прокрутки. foreach {w label} {code "Code:" notes "Notes:"} { set f [labelframe .p.$w -text $label] text $f.t -height 10 -width 40 \ -wrap none -font {courier 12} \ -xscrollcommand [list Sf.xbar set] \ -yscrollcommand [list $f.ybar set] scrollbar $f.xbar -orient horizontal \ -command [list $f.t xview] scrollbar $f.ybar -orient vertical \ -command [list $f.t yview] grid $f.t -row 0 -column 0 -sticky news -padx 2 -pady 2 grid $f.ybar -row 0 -column 1 -sticky ns -padx 2 -pady 2 grid $f.xbar -row 1 -column 0 -sticky ew -padx 2 -pady 2 grid columnconfigure $f 0 -weight 1
Глава 28. Компонент panedwindow 611 grid rowconfigure $f 0 -weight 1 # Добавление фреймов к panedwindow .p add $f -minsize li -padx 4 -pady 6 2 Операция forget удаляет компоненты из panedwindow. При этом компонент не разрушается, но контроль над ним теряется и соответствующая панель удаляется из окна. По необходимости вы можете получить список компонентов, находящихся под управлением panedwindow. Для этого надо выполнить операцию panes. Элементы списка расположены в том же порядке, в котором они были включены в состав окна. Включаемые компоненты следует создавать как дочерние по отношению к panedwindow. Наилучшие результаты можно получить, создавая компоненты, предназначенные для включения в окно panedwindow, как дочерние по отношению к нему. При этом Тк автоматически поддерживает стек окон, и дочерние компоненты отображаются поверх родительских. Если включаемый компонент не является дочерним для panedwindow, этот компонент должен быть создан после panedwindow. В противном случае для получения требуемого внешнего вида окна придется использовать команду raise. О стеке окон и применении команды raise см. в главе 25. Программирование компонента panedwindow В табл. 28.1 приведена информация об операциях, которые могут выполняться над компонентом panedwindow. В таблице компонент panedwindow обозначается как $w, а термин окно используется для обозначения компонентов, управляемых panedwindow. Таблица 28.1. Операции над компонентом panedwindow $w add окно ?окно ...? Добавляет один или несколько компонентов ?опция значение ...? к panedwindow. Каждый компонент располагается на отдельной панели. Опции, управляемые размещением компонентов, описаны в табл. 28.2 $w cget опция Возвращает значение указанной опции. Опции, соответствующие атрибутам panedwindow, описаны в табл. 28.3
612 Часть III. Основы Тк Окончание табл. 28.1 $w configure ?опция значение ...? $w forget окно ?окно . . .? $w identify x у $w panecget окно опция $w paneconfigure индекс ?опция? ?значение? ?. . .? $w panes $w proxy coord $w proxy forget $w proxy place x у $w sash coord индекс $w sash dragto индекс х 7 $w sash mark индекс x у $w sash place индекс х у Запрашивает конфигурацию panedwindow либо изменяет параметры, используя опции, описанные в табл. 28.3 Удаляет из panedwindow панели, содержащие компоненты Идентифицирует компонент panedwindow, которому принадлежит указанная точка Возвращает значение конфигурационной опции компонента. Назначение опций описано в табл. 28.2 Запрашивает или модифицирует значения конфигурационных опций компонента. Назначение опций описано в табл. 28.2 Возвращает список компонентов, содержащихся в panedwindow Возвращает текущие координаты маркера, используемого для изменения размеров панелей Удаляет маркер с экрана Располагает границу в точке с указанными координатами Возвращает координаты для границы, заданной посредством индекса Перемещает границу с предыдущей маркированной позиции Начинает перемещение границы. Индекс определяет границу, а х и у представляют собой координаты относительно компонента Помещает указанную границу в заданную точку В табл. 28.2 приведены опции, влияющие на поведение компонентов. Они задаются при включении компонента в panedwindow по команде add либо позже, с помощью операции paneconf igure. Для получения текущих установок используется операция panecget. Таблица 28.2. Опции panedwindow, предназначенные для управления компонентами -after окно Включение компонента после указанного окна -before окно Включение компонента перед указанным окном
Глава 28. Компонент panedwindow 613 Окончание табл. 28.2 -height размер -mmsize размер -padx размер -pady размер -sticky расположение -width размер Высота компонента (включая обрамление) в экранных единицах измерения. Реальная высота компонента может изменяться в зависимости от значения опции -sticky, при изменении размеров panedwindow и перемещении границы Минимальный размер компонента. Задается в экранных единицах измерения Внешнее дополнение компонента по горизонтали. Задается в экранных единицах измерения Внешнее дополнение компонента по вертикали. Задается в экранных единицах измерения Размещение компонента. Значением опции может быть любое сочетание букв n, s, e и w, обозначающих северную (north), южную (south), восточную (east) и западную (west) границу панели. Значение {} задает размещение по центру. Если указаны два противоположных направления (например, ns), компонент растягивается на всю панель по соответствующему направлению. По умолчанию принимается значение nsew Ширина компонента (включая обрамление) в экранных единицах измерения. Реальная ширина компонента может изменяться в зависимости от значения опции -sticky, при изменении размеров panedwindow и перемещении границы Атрибуты panedwindow В табл. 28.3 перечислены атрибуты компонента panedwindow. Атрибуты указаны с помощью имен ресурсов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В составе Tcl-команд атрибутам соответствуют опции, начинающиеся с символа - и содержащие только буквы нижнего регистра. Таблица 28.3. Атрибуты компонента panedwindow background Цвет фона (имя ресурса может быть сокращено до bg) borderWidth Дополнительное пространство вокруг компонента. Задается в экранных единицах измерения cursor Курсор мыши, отображаемый в пределах компонента handlePad При перемещении маркера — расстояние в экранных единицах измерения от верхнего или левого края (в зависимости от ориентации) границы, к которой относится маркер
614 Часть III. Основы Тк Окончание табл. 28.3 handleSize Размер маркера в экранных единицах измерения. Маркер всегда имеет прямоугольную форму height Высота компонента. Задается в экранных единицах измерения opaqueResize Логическое значение true указывает на то, что панель должна изменять свои размеры при перемещении маркера. Если задано значение false (принимается по умолчанию), то реальное изменение размеров откладывается до тех пор, пока граница не будет установлена в окончательную позицию orient Значение horizontal или vertical relief Значение flat, sunken, raised, groove, solid или ridge sashCursor Курсор, отображаемый при помещении на границу. По умолчанию он принимает вид двунаправленной стрелки sashPad Дополнение по обеим сторонам границы sashRelief Рельефное изображение границы. Допустимо значение flat, sunken, raised (по умолчанию) groove, solid или ridge sashWidth Ширина каждой границы. Задается в экранных единицах измерения showHandle Логическое значение, указывающее на то, должен ли отображаться маркер перемещения. По умолчанию для Windows принимается значение false, а для остальных систем — true width Ширина компонента. Задается в экранных единицах измерения
Глава 29 Связывание команд с событиями В данной главе описывается механизм связывания событий, используемый в Тк. Связывание — это установление соответствия между Tcl-командой и событием, например щелчком мыши или нажатием клавиши. Существуют также виртуальные события, например «Cut» или <<Paste». На разных платформах такие события вызываются нажатием различных комбинаций клавиш. В этой главе рассматриваются следующие Tcl-команды: bind, bindtags и event. 1 1РОЦЕДУРА связывания устанавливает соответствие между Tcl-командами и последовательностью событий, возникающих при работе оконной системы. Событиями считаются нажатие и отпускание клавиши на клавиатуре, нажатие и отпускание кнопки мыши, попадание курсора мыши в пределы окна, изменение размеров окна, открытие и закрытие окон, получение и потеря ими фокуса ввода, разрушение компонента и т.д. Связывание определяется с помощью дескрипторов связывания. Между компонентом и упорядоченным набором дескрипторов связывания устанавливается определенное соотношение. Возможность использования косвенных связей между обработчиками события и компонентами, генерирующими их, позволяет создавать гибкие и мощные системы. Виртуальные события обеспечивают работу приложений на различных платформах. Виртуальные события относятся к высокоуровневым; им присваиваются имена, которые также считаются высокоуровневыми. В качестве примера подобного события можно привести событие с именем «Сору». Низкоуровневым событиям соответствуют низкоуровневые имена, например <Control-c> или <Key-F6>. Виртуальные события скрывают за логическим
616 Часть III. Основы Тк оператором конкретные комбинации клавиш, типичные для конкретной платформы. Некоторые виртуальные события определены в Тк, кроме того, в процессе работы вы можете определять собственные события. Команда bind Команда bind связывает событие и возвращает информацию об имеющихся связях. Данная команда вызывается следующим образом: bind дескриптор_связывания ?последовательность„событий? ?команда? Если указаны все параметры, для дескриптора связывания определяется связь между последовательностью событий и командой. В качестве дескриптора связывания, как правило, задается имя класса (например, Button) или имя экземпляра компонента (например, .buttons.foo). Дескрипторы связывания будут рассмотрены несколько позже. Если команда bind вызвана с одним параметром, она возвращает информацию о событиях, связанных с командами. bind Menubutton => <Key-Return> <Key-space> <ButtonRelease-l> <Bl-Motion> <Motion> <Button-l> <Leave> <Enter> В данном примере связи установлены для событий мыши и клавиатуры. Событие <Button-l> генерируется, если пользователь нажал левую, или первую, кнопку мыши. Событие <Bl-Motion> соответствует перемещению курсора мыши при нажатой ее левой кнопке. Событие <Key-space> происходит тогда, когда пользователь нажимает клавишу пробела. Одиночные угловые скобки ограничивают описание одиночного события. По мере необходимости вы можете связать команду с последовательностью событий. Синтаксические конструкции, используемые для описания событий и их последовательностей, рассматриваются далее в этой главе. Если при вызове команды bind были заданы лишь дескриптор связывания и последовательность событий, она возвращает Tcl-команду, связанную с этой последовательностью. bind Menubutton <Bl-Motion> => tk::MbMotion °/0W down %X %Y При определении Tcl-команд, участвующих в связывании, предусмотрен специальный синтаксис, позволяющий работать с ключевыми словами событий. Ключевые слова начинаются с символа % за которым следует символ, идентифицирующий некоторый атрибут события. Перед выполнением Tcl- команды ключевые слова замещаются данными, специфическими для конкретного события. Например, °/0W может быть заменено на путь к компоненту.
Глава 29. Связывание команд с событиями 617 Ключевые слова °/0Х и °/0Y заменяются на экранные координаты. Вместо слов УоХ и %у подставляются координаты относительно компонента. Подробная информация о ключевых словах событий приведена в конце данной главы. Подстановка ключевых слов, начинающихся со знака % происходит в пределах всей команды, связанной с событием, при этом схемы цитирования не учитываются. Чтобы включить в команду знак процента, надо использовать последовательность символов VL Рекомендуется следить за тем, чтобы команды, связываемые с событиями, были краткими. По необходимости можно создавать новые процедуры (например, tk: :MbMotion). При создании нового связывания указываются дескриптор связывания, последовательность событий и команда. bind Menubutton <Bl-Motion> {tk: :MbMotion °/,W down °/0X °/.Y} Если перед связываемой командой указан символ +, то она (без символа +) добавляется к уже имеющимся командам. bind дескриптор_связывания событие {+ команда параметры} Для того чтобы разорвать связь между событием и командой, надо связать событие с пустой строкой. bind дескриптор_связывания событие {} Связанные команды выполняются в глобальной области видимости. При активизации связывания команда выполняется в глобальной области видимости. Начинающиеся программисты нередко путают область видимости, активизированную в момент вызова команды bind, с областью видимости, которая становится активной при обработке связи. Аналогичная проблема возникает при использовании команд, связанных с кнопками; она подробно обсуждается в главе 30. Команда bindtags Дескриптор связывания объединяет связи в группу. Каждому компоненту ставится в соответствие упорядоченный набор дескрипторов. Косвенные отношения между компонентами и командами, обрабатывающими события, позволяют разработчикам объединять функциональные возможности различных дескрипторов связывания. Например, дескриптор связывания all содержит связи <ТаЬ>, управляющие передачей фокуса ввода между компонентами. Дескриптор Text объединяет события, состоящие в нажатиях клавиш, предназначенные для включения и редактирования текста. С дескриптором связывания Text работают
618 Часть III. Основы Тк только текстовые компоненты, а дескриптор all совместно используется всеми компонентами. По мере необходимости вы можете объявлять новые дескрипторы связывания и динамически ставить их в соответствие различным компонентам. В результате вы получите мощные и гибкие средства обработки событий. Команда bindtags задает дескрипторы связывания для компонентов и предоставляет информацию о дескрипторах, уже поставленных в соответствие тому или иному компоненту. Данная команда вызывается следующим образом: bindtags компонент ?дескриптор_связывания? Например, приведенная ниже команда возвращает дескрипторы связывания для текстового компонента .t. bindtags .t => .t Text . all Разработчик имеет возможность изменять соотношения между компонентами и дескрипторами связывания, а также задавать новый порядок следования дескрипторов. Дескрипторы, передаваемые команде bindtags в качестве параметра, должны представлять собой корректный список Tcl. Следующая команда изменяет порядок дескрипторов связывания для компонента .t и разрывает соотношение между дескриптором с именем . и компонентом: bindtags .t [list all Text .t] По умолчанию всем компонентам Тк, за исключением окна верхнего уровня, поставлены в соответствие четыре дескриптора связывания. Порядок их следования совпадает с порядком, в котором они будут перечислены ниже. • Путь к компоненту Тк (например, .t). Этот дескриптор связывания реализует поведение, специфическое для конкретного компонента. По умолчанию для этого дескриптора не определяется ни одна связь. • Класс компонента (например, Text). Имя класса компонента формируется на базе имени команды, используемой для создания этого компонента. Так, например, кнопка принадлежит классу Button, а текстовый компонент — классу Text. Связи для класса определяют поведение компонента Тк, реализуемое по умолчанию. • Путь к окну верхнего уровня (например, .). Для компонента верхнего уровня путь к окну верхнего уровня — это путь к самому компоненту, поэтому ему вместо четырех ставятся в соответствие три дескриптора связывания. По умолчанию для этого дескриптора не определяется ни
Глава 29. Связывание команд с событиями 619 одна связь. Связи с окном верхнего уровня могут использоваться в диалоговых окнах для поддержки комбинаций клавиш. • Глобальный дескриптор связывания all. По умолчанию дескриптор all используется для передачи фокуса ввода от одного компонента другому. Подробно данный дескриптор связывания описывается в главе 39. Если компоненту поставлено в соответствие несколько дескрипторов связывания, одно и то же событие может активизировать связи из разных дескрипторов. Связи обрабатываются в том же порядке, в котором следуют дескрипторы связывания. По умолчанию сначала обрабатывается наиболее конкретный дескриптор, а общие дескрипторы обрабатываются в конце. В листинге 29.1 показаны средства обработки событий для двух фреймов. Компоненты ведут себя следующим образом. При наведении курсора мыши на компонент, последний окрашивается в красный цвет. Если курсор мыши выходит за пределы компонента, то компонент становится белым. Когда пользователь нажимает комбинацию клавиш <Ctrl+C>, компонент, на который указывает курсор мыши, разрушается. После щелчка мышью компонент .two сообщает координаты курсора. Листинг 29.1. Использование различных дескрипторов связывания для обработки событий frame .one -width 30 -height 30 frame .two -width 30 -height 30 bind Frame <Enter> {°/0W config -bg red} bind Frame <Leave> {°/0W config -bg white} bind .two <Button> {puts "Button e/0b at °/0x e/0y"} pack .one .two -side left bind all <Control-c> {destroy °/0W} bind all <Enter> {focus °/0W} С классом Frame связаны события <Enter> и <Leave>. Эти связи изменяют цвет фона фрейма при наведении на него курсора мыши и выходе курсора за пределы компонента. Данная связь совместно используется обоими фреймами. Кроме того, дескриптору связывания all поставлена в соответствие связь события <Enter> с командой передачи фокуса ввода. Обе связи активизируются тогда, когда курсор мыши попадает в пределы фрейма. Обработка нажатий клавиш и фокус ввода В приведенном выше примере связь <Control-c> используется всеми компонентами. Будучи активизированной, она приводит к разрушению компонента. Поскольку событие состоит в нажатии клавиш, важно следить за тем,
620 Часть III. Основы Тк чтобы фокус ввода принадлежал нужному объекту. По умолчанию фокусом ввода владеет главное окно, разрушение которого приведет к завершению приложения. Связь <Enter>, которая передает компоненту фокус при попадании на него курсора мыши, используется обоими фреймами. В рассматриваемом примере для того, чтобы удалить компонент, надо поместить на него курсор мыши, а затем нажать клавиши <Ctrl+C>. Чтобы фокус ввода передавался после щелчка мышью, необходимо вместо <Enter> связать команду focus с событием <Button>. Особенности передачи фокуса ввода будут обсуждаться в главе 39. Использование команд break и continue Команды break и continue позволяют управлять обработкой набора дескрипторов связывания. Команда break прекращает обработку текущего связывания и подавляет оставшиеся дескрипторы. Команда continue, будучи включена в состав команды, связанной с событием, прекращает обработку текущего связывания и переходит к следующему дескриптору. Предположим, например, что дескриптор связывания Entry имеет связь для редактирования текста в составе компонента. Вы можете подключить к дескриптору связывание <Return>, выполняющее Tcl-команду над текстом, находящимся в компоненте. В приведенном ниже примере команда, указанная в связывании, выполняется перед добавлением символа возврата каретки в компонент. Дескриптор связывания имеет имя компонента, т.е. он будет обрабатываться первым в наборе дескрипторов, и связывание Entry, включающее символ в текстовый компонент, не получит управления. bind .entry <Return> {команда ; break} Заметьте, что в процедуре, вызываемой из команды, которая входит в состав связывания, использовать команду break или continue нельзя. Дело в том, что средства поддержки процедуры не могут распространять сигнал прерывания или продолжения. Чтобы прервать или продолжить обработку из процедуры, вам надо указать в команде return опцию -code (см. главу 6). Пример такой команды приведен ниже. return -code break Определение новых дескрипторов связывания Чтобы объявить новый дескриптор связывания, достаточно указать его в составе команды bind или bindtags. Дескрипторы связывания удобны для объединения связей в наборы. Так, например, эмулировать редактор vi можно с помощью двух дескрипторов связывания: один будет использоваться для
Глава 29. Связывание команд с событиями 621 текстового режима, а другой — для командного режима. При вводе пользователем символа i редактор переходит в текстовый режим, а после нажатия клавиши <Esc> возвращается в командный режим. bindtags $t [list Vilnsert Text $t all] bind Vilnsert <Escape> {bindtags °/.W {ViCmd °/0W all}} bind ViCmd <Key-i> {bindtags °/0W {Vilnsert Text °/0W all}} В текстовом режиме действуют связи класса Text. Вместо того чтобы изменять связи Text, используемые по умолчанию, команда перевода компонента в командный режим помещается в новый дескриптор Vilnsert. Команда bindtag изменяет режим путем присвоения компоненту нового набора дескрипторов связывания. Ключевое слово °/eW заменяется именем компонента, которое в данном примере совпадает со значением $t. Очевидно, что, для того чтобы реализовать все команды vi, надо определить намного больше связей, чем приведено в данном примере. Описание событий Для описания событий в команде bind используются следующие выражения: <модификатор-модификатор-тип-детальное_описалие> «Событие» Первая форма применяется для определения низкоуровневых, или физических, событий, таких как нажатие клавиши или перемещение мыши. Другая разновидность событий — это виртуальные события, например Cut или Paste, которым на различных платформах соответствуют разные физические события. Рассмотрению физических события посвящен данный раздел. Виртуальные события будут подробно описаны далее в данной главе. Основу описания события составляет указание его типа (например, Button или Motion). Детальное описание применяется для идентификации клавиш или кнопок мыши (например, Кеу-а или Button-1). Модификатор определяет состояние некоторой клавиши или кнопки на момент возникновения события (например, Control-Key-a или B2-Motion). В описании события может присутствовать несколько модификаторов (например, Control -Shift-x). Описание события помещается в угловые скобки (т.е. располагается между символами < и >). Типы физических событий перечислены в табл. 29.1. Если в одной строке таблицы указаны два события (например, ButtonPress и Button), это значит, что они эквивалентны.
622 Часть III. Основы Тк Таблица 29.1. Типы событий Activate ButtonPress, Button ButtonRelease Circulate CirculateRequest Colormap Configure Conf igureRequest Create Deactivate Destroy Enter Expose Focusln FocusOut Gravity Keypress, Key KeyRelease Leave Map MapRequest Motion MouseWheel Property Reparent ResizeRequest Активизация приложения (Macintosh) Нажатие кнопки мыши Отпускание кнопки мыши Изменение положения окна в стеке Приложение требует изменить состояние стека окон. (Используется оконными диспетчерами.) Изменение палитры цветов Изменение размеров, позиции, обрамления окна или положения его в стеке Приложение требует изменить конфигурацию окна. (Используется оконными диспетчерами.) Приложение требует создать окно. (Используется оконными диспетчерами.) Деактивизация приложения (Macintosh) Удаление окна Попадание курсора мыши в область, занимаемую окном Отображение окна Получение окном фокуса ввода Потеря окном фокуса ввода Перемещение окна вследствие изменения размеров родительского окна Нажатие клавиши Отпускание клавиши Перемещение курсора мыши за пределы окна Открытие окна Приложение требует открыть окно. (Используется оконными диспетчерами.) Перемещение курсора мыши в пределах окна Изменение состояния колесика прокрутки мыши Изменение или удаление свойства окна Передача окна другому родительскому окну Приложение требует изменить размеры окна. (Используется оконными диспетчерами.)
Глава 29. Связывание команд с событиями 623 Окончание табл. 29.1 Unmap Окно представляется в виде пиктограммы Visibility Изменение способности окна к отображению События клавиатуры События KeyPress и KeyRelease принадлежат разным типам, поэтому их можно включить в различные связывания. Событие KeyPress можно сокращенно записать как Key, а если детальное описание указывает на то, что событие связано с клавиатурой, тип можно вовсе не задавать. Для события KeyPress можно также не указывать угловые скобки. Приведенные ниже описания эквивалентны. <KeyPress-a> <Кеу-а> <а> а Детальное описание события, связанного с нажатием клавиши, принято также называть символом клавиши, или просто символом. В данном случае символом считается изображение на клавише. Для знаков пунктуации и клавиш, не имеющих печатного представления, определены специальные символы клавиш. В обозначении символа клавиш учитывается регистр, но работу с символами клавиш затрудняет тот факт, что их обозначения не укладываются в единую схему. Например, Backspace содержит прописные буквы В и S. В процессе работы программистам часто приходится использовать следующие символы клавиш: Return, Escape, Backspace, Tab, Up, Down, Left, Right, comma, period, dollar, asciicircum, numbersign и exclam. Начиная с Tk 8.3.2 в состав интерактивной документации включена специальная страница, содержащая описания стандартных символов клавиш. Эта страница вызывается по имени keysym. В некоторых случаях для определения символа клавиатуры приходится принимать специальные меры. Часто разработчик не знает, какой символ клавиатуры генерируется той или иной клавишей. Символы клавиатуры по-разному определяются в различных оконных системах, а в среде Unix на их генерацию также влияет динамическое отображение клавиатуры. Приведенное ниже связывание вы можете использовать для определения символов клавиш в вашей системе, bind $w <KeyPress> {puts stdout ШК=в/вК в/0°/,А=р/,А}}
624 Часть III. Основы Тк Ключевое слово °/0К заменяется на символ клавиши для данного события. Ключевое слово °/0А заменяется печатным символом, который генерируется во время нажатия клавиши с учетом модификатора (например, Shift). Последовательность Уо°/о заменяется одиночным знаком процента. Заметьте, что данные подстановки осуществляются независимо от наличия фигурных скобок, используемых для группировки. Предположим, что пользователь ввел прописную букву Q. При этом генерируются два события: одно — для нажатия клавиши <Shift>, а другое — для клавиши <Q>. Результирующие данные будут иметь следующий вид: °/0K=Shift_R °/оА={} °/0K=Q 7.A=Q Символ клавиши Shif t_R указывает на то, что пользователь нажал правую клавишу <Shift>. При нажатии клавиш-модификаторов ключевое слово °/оА заменяется на {}. Определяя обработчик события <KeyPress>, надо следить за теАм, какая клавиша нажата, и, если необходимо, исключить выполнение действий в ответ на нажатие клавиши-модификатора. В системе Macintosh при нажатии клавиш-модификаторов события не генерируются. Приведенное ниже связывание можно применить для программирования текстового компонента. Двойные скобки необходимы для того, чтобы выполнять сравнение строк. bind $w <KeyPress> { if {'"/.A" != "О"} ОТ insert insert °/0A} } События мыши События мыши также подразделяются на типы ButtonPress (или Button) и ButtonRelease. Если детальное описание определяет кнопку с помощью номера, тип Button можно не указывать. Приведенные ниже описания эквивалентны. <ButtonPress-l> <Button-l> <1> Обратите внимание на то, что <1> интерпретируется как событие ButtonPress, a 1 воспринимается как событие KeyPress. Для того чтобы избежать путаницы, следует всегда указывать тип Key или Button. Действия с мышью отслеживаются также с помощью событий Enter, Leave и Motion. Событие Enter генерируется тогда, когда курсор мыши попадает на компонент, а событие Leave — когда курсор выходит за его границы. Событие Motion возникает при движении курсора в пределах компонента.
Глава 29. Связывание команд с событиями 625 Координаты курсора мыши, связанные с событием, представляются с помощью ключевых слов °/0х и °/0у. Координаты определяются относительно компонента; начальной точкой отсчета является верхний левый угол окна компонента. Ключевые слова °/0Х и °/0Y представляют экранные координаты. bind $w <Enter> {puts stdout "Entered °/0W at °/0x °/0yu} bind $w <Leave> {puts stdout "Left °/0W at °/0x °/0y"} bind $w <Motion> {puts stdout "°/0W °/0x e/.y"} Перетаскивание курсора мыши представляет событие Motion при нажатой кнопке. В данном случае кнопка мыши выступает в роли модификатора. Подробнее этот вопрос будет обсуждаться несколько позже. Связывание выглядит следующим образом: bind $w <Bl-Motion> {puts stdout "°/0W °/0x 0/0yM} Прочие события События <Мар> и <Unmap> генерируются соответственно при открытии и закрытии окна, а также тогда, когда компонент обрабатывается диспетчером компоновки. События <Activate> и <Deactivate> генерируются при активизации и де- активизации приложения операционной системой. Данные события поддерживаются в системе Macintosh и возникают после щелчков пользователя в окне приложения. Событие <Conf igure> генерируется при изменении размеров окна. Данному событию обычно ставится в соответствие процедура отображения данных. Событие <Configure> возникает независимо от того, изменяются ли размеры в результате действий пользователя или вследствие вызова команды configure компонента. При обработке события нельзя изменять размеры компонента. Если вы сделаете это, вы инициируете бесконечную последовательность событий данного типа. Событие <Destroy> генерируется при разрушении компонента. С помощью данного события можно перехватывать запросы на удаление окон. (Подробное описание команды wm приводится в главе 44.) Событие <MouseWheel> генерируется системой Windows при движении колесика прокрутки устройства Microsoft Mouse. Величина смещения задается с помощью ключевого слова °/0D. В настоящее время смещение — это целое число; положительные значения соответствуют прокрутке вверх, а отрицательные — прокрутке вниз. В большинстве версий Unix события, подобные <MouseWheel>, отсутствуют, а в некоторых разновидностях этих систем движение колесика прокрутки представляется событиями <ButtonPress-4> и <ButtonPress-5>.
626 Часть III. Основы Тк В главе 39 приведены некоторые примеры использования событий <FocusIn> и <FocusOut>. Остальные события, приведенные в табл. 29.1, являются специфическими для протокола X Window и используются редко. Информацию о них можно получить в руководстве Эдриана Ная (Adrian Nye) Xlib Reference Manual (O'Reilly & Associates, Inc., 1992). Связывания для окон верхнего уровня Связи, определенные для окна, верхнего уровня, могут использоваться содержащимися в нем компонентами. Определяя связывания для окна верхнего уровня, необходимо быть очень внимательным, так как имя этого окна используется в качестве дескриптора связывания для всех содержащихся в нем компонентов. Например, ниже определяется связывание для события, которое возникает при закрытии пользователем главного окна (т.е. при завершении приложения). bind . <Destroy> {puts "goodbye"} Побочным эффектом данного связывания является разрушение всех компонентов, содержащихся в главном окне. Такие действия в большинстве случаев являются наиболее естественными. Однако в некоторых ситуациях разработчику может понадобиться реализовать другое поведение приложения. Ниже приведено связывание, в котором перед выполнением конкретных действий производится идентификация компонента. bind . <Destroy> {if {"°/0W" == "."} {puts "goodbye"}} Модификаторы Модификатор указывает на то, что в момент возникновения события некоторая клавиша или кнопка мыши находилась в нажатом состоянии. Обычно в роли модификатора выступают клавиши <Shift> и <Ctrl>. В качестве модификатора также может рассматриваться кнопка мыши. Если в описании события модификатор не указан, то обработчик события не учитывает состояние соответствующей клавиши или кнопки. Если событие соответствует более чем одному описанию, используется то из них, которое определяет это событие наиболее подробно. В качестве примера рассмотрим три приведенных ниже связывания. bind $w <KeyPress> {puts "key=°/0A"} bind $w <Key-c> {puts "just a c"} bind $w <Control-Key-c> {exit}
Глава 29. Связывание команд с событиями 627 В последнем случае событие описывается точнее, чем в первых двух. Именно оно будет активизировано, когда пользователь нажмет клавишу <С>, удерживая нажатой клавишу <Ctrl>. Если пользователь нажмет клавишу <С>, удерживая нажатой клавишу <Meta>, будет использовано второе из приведенных выше связываний. В данном случае событие в точности не соответствует ни одному описанию, поэтому состояние клавиши <Meta> не принимается во внимание. Если пользователь нажмет клавишу, отличную от <С>, активизируется первое связывание. И, наконец, при нажатии комбинации клавиш <Shift+C> также выполнится команда, связанная с событием <KeyPress>, так как при этом генерируется символ клавиатуры С (а не с) и, следовательно, событие не соответствует второму и третьему связыванию. В настоящее время существует восемь клавиш-модификаторов. Клавиши <Ctrl>, <Shift> и <Lock> имеются практически на каждой клавиатуре. Модификаторы <Meta> и <Alt> изменяются в зависимости от конкретной платформы; в некоторых системах они вовсе не определены. Обычно они отображаются в модификатор Modi или Mod2, и Тк пытается определить, какое отображение установлено в конкретном случае. В системе Macintosh используется модификатор Command. Остальные модификаторы (Mod3-Mod5) иногда ставятся в соответствие другим специальным клавишам. Например, в среде OpenLook функциональная клавиша <Paste> отображается в модификатор Mod5. Кнопки мыши (В1-В5) используются в качестве модификаторов в основном тогда, когда надо различать обычное перемещение и перетаскивание мыши. При этом модификаторы указываются в описании события Motion. Например, событие <Bl-Motion> генерируется тогда, когда пользователь перетаскивает курсор мыши, нажав ее левую кнопку. Обработка двойного щелчка мышью имеет важные особенности. События Double, Triple и Quadruple соответствуют повторению некоторого события в течение короткого интервала времени. Обычно они используются при работе с мышью. Связывая различные события, в том числе указанные выше, с командами, следует соблюдать осторожность: команда, связанная с обычным щелчком мышью, будет выполнена после первого из двух щелчков, составляющих событие Double. После второго щелчка будет активизировано связывание для Double. Аналогично событие Double будет распознано при первых двух щелчках события Triple и т.д. Рассмотрим следующие связывания: bind . <1> {puts stdout 1} bind . <Double-l> {puts stdout 2} bind . <Triple-l> {puts stdout 3}
628 Часть III. Основы Тк Если вы несколько раз подряд щелкнете левой кнопкой мыши, то увидите, что на экран выводятся все три цифры (1, 2 и 3). Необходимо принимать во внимание тот факт, что событию Double, Triple или Quadruple может соответствовать несколько связываний. Этот эффект иногда используется при создании интерфейсов. В частности, вы можете выделить объект по первому щелчку, а затем уточнять действия с объектом при возникновении события Double. Так, например, в текстовом редакторе может по первому щелчку выбираться символ, по второму — слово, по третьему — строка и по четвертому — абзац1. Существующие модификаторы перечислены в табл. 29.2. Таблица 29.2. Модификаторы Control Клавиша <Ctrl> Shift Клавиша <Shift> Lock Клавиша <CapsLock> Command Клавиша <Command> (Macintosh) Meta, M Определяются при отображении модификаторов (М1-М5) в символы клавиш Meta_L и Meta_R Alt Определяется при отображении различных клавиш в символы Alt_L и Alt_R Modi, Ml Первый модификатор Mod2, М2, Второй модификатор Alt Mod3, МЗ Дополнительный модификатор Mod4, М4 Дополнительный модификатор Mod5, М5 Дополнительный модификатор Buttonl, B1 Левая, или первая, кнопка мыши Button2, B2 Средняя, или вторая, кнопка мыши Button3, B3 Правая, или третья, кнопка мыши Button4, B4 Четвертая кнопка мыши Button5, B5 Пятая кнопка мыши Double Определяет событие, дважды повторяющееся в течение определенного интервала времени Если вы хотите изменить реакцию на многократные щелчки мышью, вам надо реализовать отложенную обработку события с помощью команды after. Обычно событие <Double> возникает, если интервал между двумя одинаковыми событиями не превышает 500 миллисекунд. Поэтому действие по одиночному событию следует спланировать через 600 миллисекунд и убедиться, что за это время событие <Double> не произошло. — Прим. авт.
Глава 29. Связывание команд с событиями 629 Окончание табл. 29.2 Triple Определяет событие, трижды повторяющееся в течение определенного интервала времени Quadruple Определяет событие, которое в течение определенного интервала времени повторяется четыре раза Any Любое сочетание модификаторов. (Использовался в версиях, предшествующих Tk 4.O.) Работая в системе Unix, можно воспользоваться программой xmodmap, которая возвращает информацию о соответствии между клавишами и модификаторами. Пример данных, выводимых этой программой, приведен в листинге 29.2. В первом столбце выводится модификатор. Остальная часть строки содержит символы клавиш и коды низкого уровня, соответствующие модификатору. С помощью программы xmodmap можно также изменить отображение клавиш в модификаторы. Заметьте, что в вашей системе соответствие между клавишами и модификаторами может отличаться от приведенного в листинге 29.2. Листинг 29.2. Результаты выполнения программы xmodmap в системе Unix xmodmap: up to 3 keys per modifier, (keycodes in parentheses): shift Shift_L (0x6a), Shift_R (0x75) lock Caps_Lock (0x7e) control Control_L (0x53) modi Meta.L (0x7f), MetaJR (0x81) mod2 Mode.switch (0x14) mod3 Num_Lock (0x69) mod4 Alt_L (0x1a) mod5 F13 (0x20), F18 (0x50), F20 (0x68) Последовательности событий Команда bind позволяет определять последовательность событий. В большинстве случаев такая последовательность состоит из событий, связанных с клавиатурой. Ниже приведены две команды, определяющие связывания для событий клавиатуры. Здесь применена сокращенная запись, т.е. последовательность символов abc определяет три события Key. bind . a {puts stdout A} bind . abc {puts stdout C}
630 Часть III. Основы Тк Когда пользователь вводит символы abc, генерируются оба события. После нажатия клавиши <А> активизируется связывание для а, несмотря на. то. что этот символ входит в последовательность abc. Такое поведение программы можно сравнить с обработкой событий при наличии модификаторов Double и Triple. Поэтому, определяя связывания для последовательностей символов, надо соблюдать осторожность. Для того чтобы при вводе символа, не являющегося окончанием последовательности, не выполнялись никакие действия, надо связать соответствующее событие с командой break. bindtags $w [list $w Text [winfo toplevel $w] all] bind $w <Control-x> break bind $w <Control-x><Control-s> {Save ; break} bind $w <Control-x><Control-c> {Quit ; break} В данном случае команда break гарантирует, что связывание Text, установленное по умолчанию, не будет активизировано. Такой подход использован в процедуре BindSequence, код которой приведен в листинге 29.3. При обнаружении последовательности с префиксом связывается команда break. Данная процедура также поддерживает соглашение emacs, согласно которому сочетание <Meta-x> эквивалентно <Esc><X>. Данное соглашение необходимо потому, что клавиша <Meta> на некоторых клавиатурах отсутствует. В частности, такой клавиши нет на клавиатурах Windows и Macintosh. Для обработки события <Meta> используется команда regexp. Листинг 29.3. Связывание для Met а и Escape с учетом соглашения emacs proc BindSequence { w seq cmd } { bind $w $seq $cmd # Двойное связывание Meta и Escape if [regexp {<Meta-(.*)>} $seq match letter] { bind $w <Escape><$letter> $cmd } # Код ведущей клавиши должен быть сохранен if [regexp {(<.+>)<.+>} $seq match prefix] { bind $w $prefix break } 2 В Tk 3.6 и более ранних версиях команды break и continue в составе связываний не поддерживаются. Причина в том, что в этих версиях событие может соответствовать единственному дескриптору связывания. Для того чтобы, работая с Тк 3.6, исключить выполнение действий в ответ на получение префикса, надо связать с ним пробел. bind $w $prefix { }
Глава 29. Связывание команд с событиями 631 При этом для компонента создается связывание, которое подавляет связывание класса в Tk 3.6. Пробел отличается от пустой строки {}. Связывание с пустой строкой удаляет связь, в то время как пробел, заданный в составе связывания, подавляет выполнение нежелательных действий. Виртуальные события Виртуальное событие соответствует одной или нескольким последовательностям событий. Оно происходит тогда, когда генерируется любая из указанных последовательностей. В листинге 29.4 показаны виртуальные события, соответствующие операциям вырезания, копирования и вставки для различных платформ. Листинг 29.4. Виртуальные события для вырезания, копирования и вставки switch $tcl_platform(platform) { "unix" { event add «Cut» <Control-Key-x> <Key-F20> event add «Copy» <Control-Key-c> <Key-F16> event add «Paste» <Control-Key-v> <Key-F18> } "windows" { event add «Cut» <Control-Key-x> <Shift-Key-Delete> event add «Copy» <Control-Key-c> <Control-Key-Insert> event add «Paste» <Control-Key-v> <Shift-Key-Insert> } "macintosh" { event add «Cut» <Control-Key-x> <Key-F2> event add «Copy» <Control-Key-c> <Key-F3> event add «Paste» <Control-Key-v> <Key-F4> } _> По мере необходимости вы можете задать отображение нескольких физических событий в одно виртуальное. event add «Cancel» <Control-c> <Escape> <Command-period> В данном примере любое из указанных физических событий вызовет событие «Cancel». Это удобно в тех случаях, когда пользователь работает с вашим приложением на различных платформах. Однако при этом может случиться так, что связи для различных платформ будут конфликтовать между собой.
632 Часть III. Основы Тк По умолчанию новое определение виртуального события добавляется к уже имеющимся определениям того же события. Поэтому предыдущая команда может быть заменена тремя приведенными ниже. event add «Cancel» <Control-c> event add «Cancel» <Escape> event add «Cancel» <Command-period> Некоторые компоненты используют виртуальные события в качестве механизма оповещения. Они генерируют виртуальные события при возникновении определенных условий. Таким образом, вы можете определить связывания, реагирующие на возникновение этих условий. Например, компонент, реализующий список, генерирует виртуальное событие «ListboxSelect» тогда, когда выбранный пункт списка изменяется. Если ваша программа должна реагировать на изменение выделения в списке, проще всего сделать это, создав связывание для виртуального события. Пример такого связывания приведен ниже. bind .lbox «ListboxSelect» {ListboxChanged °/,W} Генерация событий Создавая приложение, вы можете использовать команду event generate для программной генерации событий. Это иногда бывает необходимо, например для эмуляции взаимодействия с пользователем. Генерировать можно как стандартные события оконной системы, так и виртуальные события. Генерация событий возможна только для текущего приложения; передавать события другим программам, выполняющимся в операционной системе, нельзя. Иными словами, команда event generate неприменима для управления другими приложениями. При вызове команды event generate ей в качестве первого параметра передается целевой компонент. Вы можете задавать либо путь к компоненту, либо идентификатор окна (возвращаемый командой winfo id). Вторым параметром является описание события. Оно задается в том же формате, как и при определении связывания. Однако следует помнить, что генерировать можно только одиночные события. Сгенерировать последовательность событий (например, <KeyPress-Escape><KeyPress-a>) невозможно. Ниже в качестве примера приведена команда, которая доставляет компоненту событие <ButtonPress-3>. event generate .b <ButtonPress-3>
Глава 29. Связывание команд с событиями 633 Для того чтобы получить событие клавиатуры, компонент должен иметь фокус ввода. Следует помнить, что получить событие KeyPress или KeyRelease может только тот компонент, который имеет фокус ввода. Передать фокус ввода компоненту можно с помощью команды focus. focus .el event generate .el <KeyPress-a> В команде event generate также предусмотрены опции, позволяющие задать дополнительные атрибуты события, например позицию курсора мыши. Опции команды event generate приведены в табл. 29.4. Следует помнить, что в Тк 8.3 была реализована опция -warp. Если вы зададите значение опции -warp, равное true, то курсор мыши переместится в позицию, заданную координатами х и у сгенерированного события. В противном случае положение курсора мыши останется неизменным. Ниже в качестве примера приведена команда, которая перемещает курсор мыши в точку с координатами (10,20) относительно верхнего левого угла главного окна. event generate . <Motion> -x 10 -у 20 -warp l Информация о событиях Синтаксис команды event Правила вызова команды event приведены в табл. 29.3. Таблица 29.3. Команда event event add Добавляет отображение одного или нескольких виртуальное „событие физических событий в виртуальное * физическое^, событие^ 1 физическое_событие_2 event delete Удаляет виртуальное событие виртуальное_событие event info Возвращает информацию о виртуальных событиях event info Возвращает информацию о физических собы- виртуальное_событие тиях, отображаемых в виртуальное событие event generate окно событие Генерирует событие для указанного окна. Допу- ?опция значение? . . . стимые опции описаны в табл. 29.4
634 Часть III. Основы Тк Ключевые слова событий В табл. 29.4 перечислены ключевые слова, начинающиеся с символа °/0, и соответствующие им опции команды event generate. Следует помнить, что подстановка ключевых слов осуществляется во всей команде; соглашения Tcl о цитировании при этом не учитываются. Команды, включаемые в состав связывания, должны быть краткими. По необходимости следует создавать новые процедуры. Подробные сведения о событиях можно получить в руководстве Xlib Reference Manual (O'Reilly & Associates, Inc.). В табл. 29.4 приведены строковые значения для подстановки ключевых слов и краткое их описание. Если строковое значение не задано, вместо ключевого слова подставляется координата или идентификатор окна. Таблица 29.4. Ключевые слова, используемые при определении связываний УоУ, Формирование одиночного символа У,. Все события У0# -serial номер Последовательный номер события. Все события °/0а -above окно Поле above. Событие Configure °/0b -button номер Номер кнопки. События ButtonPress и ButtonRelease У0с -count номер Поле count. События Expose и Map e/od -detail значение Поле detail. Значения: NotifyAncestor, NotifyNonlinearVirtual, NotifyDetailNone, NotifyPointer, Notifylnferior, NotifyPointerRoot, NotifyNonlinear и NotifyVirtual. События Enter, Leave, Focusln и FocusOut °/0f -focus Поле focus (0 или 1). События Enter и Leave логическое^значение e/0h -height номер Поле height. События Configure и Expose e/0i Поле window события, представленное в виде шестнадцатеричного целого числа. Все события У0к -keycode номер Поле keycode. События KeyPress и KeyRelease °/0m -mode значение Поле mode. Значения Notif yNormal, NotifyGrab, Notif yUngrab или Notif yWhileGrabbed. События Enter, Leave, Focusln и FocusOut °/0o -override Поле override_redirect. События Map, Reparent логическое_значение и Configure °/0p -place значение Поле place. Значения PlaceOnTop, PlaceOnBottom. Событие Circulate
Глава 29. Связывание команд с событиями 635 Окончание табл. 29.4 °/0s -state значение Поле state. Для событий ButtonPress, ButtonRelease, Enter, Leave, KeyPress, KeyRelease и Motion — строка, содержащая десятичное значение. Значения для события Visibility: VisibilityUnobscured. VisibilityPartiallyObscured или VisibilityFullyObscured °/0t -time номер Поле time. Все события °/Qv Поле value_mask. Событие Configure °/0w -width номер Поле width. События Configure и Expose 7-х -х пиксели Координата X относительно компонента. События мыши У0у -у пиксели Координата Y относительно компонента. События мыши УоА Печатный символ, соответствующий событию или {}. События KeyPress и KeyRelease УоВ -borderwidth число Толщина обрамления. Событие Configure °/0D -delta значение Величина смещения. Событие MouseWheel УоЕ -sendevent Поле send_event. Все события логическое_значение УоК -keysym символ Символ клавиши. События KeyPress и KeyRelease °/,N Символ клавиши, представленый в виде десятичного числа. События KeyPress и KeyRelease У0Р Атомарное имя удаляемого или изменяемого свойства. Событие Property °/,R -root окно Идентификатор корневого окна. Все события °/oS -subwindow окно Идентификатор подокна. Все события У,Т Поле type. Все события C/0W Путь к компоненту, выступающему в качестве приемника события. Все события УоХ -rootx пиксели Поле x_root. Значение определяется относительно корневого окна (возможно, виртуального). События ButtonPress. ButtonRelease, KeyPress. KeyRelease и Motion °/0Y -rooty пиксели Поле y_root. Значение определяется относительно корневого окна (возможно, виртуального). События ButtonPress, ButtonRelease, KeyPress, KeyRelease и Motion
ЧАСТЬ IV Компоненты Тк В части IV описываются компоненты Тк, которые используются разработчиками при создании графического пользовательского интерфейса. Компоненты Тк просты в использовании, поэтому время, затраченное на формирование интерфейса, минимально. В то же время они позволяют реализовывать многофункциональный интерфейс и обеспечивают выполнение приложением сложных действий при получении данных от пользователей. В главе 30 описываются кнопки и меню. В Тк 8.0 были реализованы кнопки и меню, специфические для конкретных платформ, в результате чего разработчики получили возможность создавать сценарии, которые внешне выглядят подобно большинству приложений данной операционной системы. С компонентами может быть связана база данных ресурсов, содержащая информацию о таких параметрах компонентов, как цвет и начертание шрифта. База данных ресурсов описывается в главе 31. Там же рассматриваются вопросы хранения в ней конфигурации кнопок и меню. Глава 32 посвящена рассмотрению нескольких простых компонентов. Фреймы, фреймы с метками и окна верхнего уровня могут выступать в роли контейнеров для других компонентов. Текстовая метка отображает строку символов. Компонент сообщения форматирует длинную последовательность символов, представляя ее в виде нескольких строк. Линейный регулятор позволяет управлять числовыми значениями. Команда bell инициирует звуковой сигнал. В главе 33 описываются полосы прокрутки, которые обычно связываются с другими компонентами. В главе 34 рассматриваются поля редактирования и инкрементные регуляторы. Поле редактирования позволяет вводить и изменять строку текста, а инкрементный регулятор — выбирать значения из заданного набора, "прокручивая" их в направлении возрастания или убывания. В главе 35 описываются окна списков. В окне списка выводится несколько строк текста, которые интерпретируются как пункты списка.
638 Часть IV В главе 36 описывается текстовый компонент общего назначения. Информация в нем может отображаться различными шрифтами, а для обозначения фрагментов текста используются специальные дескрипторы. В главе 37 описан компонент, реализующий холст. С помощью этого компонента можно управлять строками, изображениями, текстовыми метками и другими объектами. Вы можете определять дескрипторы связывания для отдельных объектов и их классов.
Глава 30 Кнопки и меню Кнопки и меню — это основные элементы интерфейса, посредством которых функциональные возможности приложений становятся доступными пользователям. В данной главе описываются создание кнопок и меню и управление ими. Г\омпоненту, реализующему кнопку, ставится в соответствие Tcl-команда, которая выполняет некоторые действия. Компоненты типа checkbutton и radiobutton (флажки и переключатели опций) непосредственно связываются с Tcl-переменными. Пункты меню могут быть организованы в наборы и вызываться в виде каскадных меню. Компонент menubutton представляет собой специальный тип кнопки, после щелчка на которой отображается меню. В Тк 8.0 была реализована строка меню, поддерживаемая на разных платформах. Строка меню представляет собой обычное меню, пункты которого расположены горизонтально в главном окне программы. В системе Macintosh строка меню располагается в верхней части экрана. Данный интерфейсный элемент определяется одинаково для всех платформ. В Тк 8.0 были реализованы кнопки и меню, специально ориентированные для использования на платформах Windows и Macintosh. Это позволяет создавать интерфейсы прикладных программ, имеющие внешний вид, типичный для используемой платформы. В более ранних версиях внешний вид интерфейсных элементов Тк не изменялся в зависимости от используемой системы. Связывание команды с кнопкой осуществляется очень просто. Ниже приведен пример кнопки, в соответствие которой поставлена команда, отображающая строку "Hello, World!". button .hello -command {puts stdout uHello, World!"}
640 Часть IV. Компоненты Тк В данной главе рассматриваются также более универсальные способы определения команд. Чтобы использовать в командах переменные, надо хорошо представлять себе правила работы с различными областями видимости. Этот вопрос подробно рассматривается в начале данной главы. Если вы сможете разобраться с областями видимости для кнопок и связанных с ними команд, то изучить остальные детали использования данных компонентов будет достаточно просто. Команды, вызываемые посредством кнопок, и области видимости Наверное, самый сложный вопрос, имеющий отношение к использованию команд, вызываемых посредством активизации кнопок, связан с работой в различных областях видимости. Команда, связанная с кнопкой, выполняется в глобальной области видимости, т.е. за пределами области видимости любой процедуры. Если вы создадите кнопку внутри процедуры, необходимо помнить, что соответствующая команда будет выполняться в другой области видимости. То же касается команд, поставленных в соответствие некоторым событиям. Рассматривая особенности использования областей видимости при действиях с кнопками, мы будем применять термины "сейчас" и "позже". "Сейчас" означает момент определения кнопки, а "позже" — момент активизации этой кнопки. Например, при определении кнопки могут использоваться значения некоторых переменных, но при активизации кнопки команда будет оперировать с совершенно иными значениями. Следует четко различать контексты определения и активизации кнопки. В противном случае при разработке приложения будут возникать серьезные проблемы. Одну из таких проблем иллюстрирует пример кода, приведенный в листинге 30.1. В команде, связанной с кнопкой, используются две переменные: х и val. Глобальная переменная х потребуется "позже", т.е. при выполнении команды, связанной с кнопкой. Локальная переменная требуется "сейчас", т.е. при определении команды. Листинг 30.1. Проблемы, которые могут возникать при использовании команды, связанной с кнопкой proc Trouble {args} { set b 0 # Отображение значения глобальной переменной х label .label -textvariable x set f [frame .buttons -borderwidth 10]
Глава 30. Кнопки и меню 641 # Создание кнопки, при активизации которой х # умножается на соответствующее значение foreach val $args { button $f.$b -text $val \ -command "set x \[expr \$x * $val\]" pack $f.$b -side left incr b } pack .label $f } set x 1 Trouble -1 4 7 36 Для отображения текущего значения переменной х в данном примере используется текстовая метка. Атрибут textvariable позволяет сформировать текстовую метку так, чтобы она отображала текущее значение переменной, которая, как вам уже известно, является глобальной. Команда global в процедуре Trouble не обязательна, так как значение х в процедуре не используется. Команда кнопки выполняется "позже" в глобальной области видимости. Определение команды, связанной с кнопкой, нельзя назвать изящным. Переменная цикла необходима для того, чтобы определить кнопку, однако остальные подстановки должны быть выполнены "позже". Подстановка значения $х и команды expr подавляется путем использования символов обратной косой черты. set х \[expr \$х * $val\] Для сравнения рассмотрим приведенную ниже команду. В ней после каждого щелчка на кнопке переменной х присваивается значение выражения, кроме того, выполнение команды зависит от текущего значения х, которое при первой итерации еще не определено. Следовательно, команда составлена некорректно. button $f.$b -text $val \ -command "set x [expr $x * $val]"
642 Часть IV. Компоненты Тк Помещать всю команду в фигурные скобки также нельзя. В этом случае подстановка откладывается "слишком далеко" и значение переменной val не может использоваться в нужный момент времени. В командах кнопок целесообразно применять вызовы процедур. Описанные выше проблемы можно разрешить, используя в качестве команд кнопок Tcl-процедуры. В листинге 30.2 представлена процедура, содержащая выражение, которое должно выполняться после щелчка на кнопке. Листинг 30.2. Разрешение проблем, связанных с областью видимости, с помощью процедуры proc LessTrouble { args } { set b 0 label .label -textvariable x set f [frame .buttons -borderwidth 10] foreach val $args { button $f.$b -text $val \ -command "UpdateX $val" pack $f.$b -side left incr b } pack .label $f } proc UpdateX { val } { global x set x [expr $x * $val] } set x 1 LessTrouble -1 4 7 36 В данном случае для создания вспомогательной процедуры UpdateX приходится затрачивать дополнительные усилия. Однако такой подход делает код программы более понятным. Во-первых, отпадает необходимость в символах обратной косой черты; несмотря на то, что они предоставляют возможность корректно определить команду, они в то же время делают ее менее удобочитаемой. Во-вторых, действия, выполняемые кнопкой, становятся более понятными. Как видно в приведенном примере, задача кнопки — обновлять значение глобальной переменной х. Процедуру UpdateX можно сделать более универсальной, в частности передать ей имя переменной, значение которой следует модифицировать. Новый вариант UpdateX работает подобно команде incr.
Глава 30. Кнопки и меню 643 button $f.$b -text $val -command "Update x $val" Для обработки переменной в глобальной области видимости в процедуре Update используется команда upvar (см. главу 7). proc Update {varname val} { upvar #0 $varname x set x [expr $x * $val] } Двойные кавычки используются в команде, связанной с кнопкой, для того, чтобы подстановка $val могла быть выполнена. Применяя цитирование, необходимо представлять себе возможные значения подстановок. В противном случае разбор команды может быть произведен некорректно. Самым надежным способом является использование команды list. button $f.$b -text $val -command [list UpdateX $val] При этом можно быть уверенным, что команда представляет собой список из двух элементов: UpdateX и значение переменной val. Это важно, поскольку в процедуре UpdateX предусмотрен лишь один параметр. Если бы значение val содержало пробел, то при разборе команды интерпретатор выделил бы дополнительные слова. В данном случае мы планируем вызов LessTrouble с указанием целочисленных значений, в составе которых пробелы отсутствуют. В листинге 30.3 приведен еще один пример использования команд кнопок. В данном случае процедура MaxLineLength создает область видимости для локальных переменных, используемых при действиях с кнопкой. В результате исключаются случайные конфликты между локальными и глобальными переменными. Эта процедура также может найти применение в других частях программы. Листинг 30.3. Связывание кнопки с Tcl-процедурой
644 Часть IV. Компоненты Тк proc MaxLineLength { file } { set max 0 if [catch {open $file} in] { return $in } foreach line [split [read $in] \n] { set len [string length $line] if {$len > $max} { set max $len } } return "Longest line is $max characters" } # Создание поля редактирования для ввода имени файла, # текстовой метки для отображения результата # и кнопки для вызова действия. . config -borderwidth 10 entry .e -width 30 -bg white -relief sunken button .doit -text "Max Line Length" \ -command {.label config -text [MaxLineLength [.e get]]} label .label -text "Enter file name" pack .e .doit .label -side top -pady 5 Основой данного примера является процедура MaxLineLength. Она открывает файл и проверяет в цикле все строки в поисках самой длинной. Команда open помещена в состав catch. Такая защита предусмотрена на случай, если пользователь введет некорректное имя файла. При наличии ошибки процедура возвращает соответствующее сообщение, иначе возвращается информация о самой длинной из строк файла. Локальные переменные in, max и len скрыты в области видимости процедуры. Пользовательский интерфейс включает три компонента: поле редактирования, предназначенное для ввода данных пользователем, кнопку и текстовую метку, с помощью которой отображается результат. Они размещены по вертикали в главном окне, которое снабжено рамкой. Очевидно, что этот интерфейс можно модернизировать различными способами. Так, например, не лишней была бы кнопка Quit. Основные действия выполняются посредством команды, связанной с кнопкой. .label config -text [MaxLineLength [.e get]] Эта команда помещена в фигурные скобки, в результате подстановка выполняется лишь при активизации кнопки. Значение, введенное пользовате-
Глава 30. Кнопки и меню 645 лем в поле редактирования, извлекается посредством выражения .е get. Это значение передается процедуре MaxLineLength, а результат отображается посредством текстовой метки. Следует заметить, что данная команда слишком громоздка по сравнению с командами, которые обычно связываются с кнопками. Предположим, например, что вам надо вызывать эту же команду, когда пользователь нажимает клавишу <Return>. Вам придется повторить аналогичное выражение в команде связывания. Гораздо удобнее создать процедуру, реализовав в ней требуемые действия. В этом случае процедуру можно без труда использовать в других частях программы. Данная процедура может иметь следующий вид: proc Doit {} { .label config -text [MaxLineLength [.e get]] } button .doit -text "Max Line Length" -command Doit bind .e <Return> Doit Детально о команде bind, посредством которой определяются связывания, см. в главе 29. Текстовые метки рассматриваются в главе 32, а поля редактирования — в главе 35. Кнопки, связанные с Tcl-переменными Компонентам checkbutton и radiobutton ставятся в соответствие глобальные Tcl-переменные. После щелчка на кнопке такого типа переменной присваивается определенное значение. Кроме того, если переменная изменит свое значение в процессе выполнения программы, внешний вид флажков и переключателей опций также изменится, отражая новое значение переменной. Набор компонентов radiobutton связывается с одной глобальной переменной. Каждая кнопка набора представляет одно из возможных значений данной переменной. В отличие от переключателей опций, каждому компоненту checkbutton ставится в соответствие одна переменная. В листинге 30.4 содержится код процедуры ShowChoices. В ней с помощью набора компонентов radiobutton отображается одно из взаимоисключающих значений. В том же листинге приведена процедура ShowBooleans, использующая компоненты checkbutton. Листинг 30.4. Использование компонентов radiobutton и checkbutton proc ShowChoices { parent varname args } { set f [frame $parent.choices -borderwidth 5] set b 0 foreach item $args {
646 Часть IV. Компоненты Тк radiobutton $f.$b -variable $varname \ -text $item -value $item pack $f.$b -side left incr b } pack $f -side top } proc ShowBooleans { parent args } { set f [frame $parent.booleans -borderwidth 5] set b 0 foreach item $args { checkbutton $f.$b -text $item -variable $item pack $f.$b -side left incr b } pack $f -side top } set choice kiwi ShowChoices {} choice apple orange peach kiwi strawberry set Bold 1 ; set Italic 1 ShowBooleans {} Bold Italic Underline В качестве параметров процедуре ShowChoices передается родительский фрейм, имя переменной и набор допустимых значений переменной. Если вместо родительского фрейма задано нулевое значение {}, то элементы интерфейса размещаются в главном окне. При выполнении процедуры ShowChoices для каждого из указанных значений формируется кнопка radiobutton. кроме того, значение используется в качестве текста для данной кнопки. При создании кнопки должно быть указано значение, присваиваемое переменной, так как по умолчанию с компонентом radiobutton связывается пустая строка. Процедура. ShowBooleans похожа на ShowChoices. Она получает в качестве параметра имена переменных и создает для каждой переменной флажок опции (компонент checkbutton). По умолчанию для переменных, связанных с кнопкой checkbutton. выделяются значения 0 и 1. что вполне подходит для
Глава 30. Кнопки и меню 647 данного примера. Если вам нужны другие значения, вы можете задать их с помощью опций -onvalue и -offvalue. Подобно обычным кнопкам, с компонентами radiobutton и checkbutton могут быть связаны команды. Эти команды выполняются после обновления значений Tcl-переменных. Не следует забывать, что Tcl-переменные, соответствующие кнопкам, определены в глобальной области видимости. В листинге 30.5 показан пример использования команд, связанных с кнопка*ми radiobutton и checkbutton, для вывода значений переменных, соответствующих этим кнопкам. Листинг 30.5. Использование команд, связанных с кнопками radiobutton и checkbutton proc PrintByName { varname } { upvar #0 $varname var puts stdout "$varname = $var" } checkbutton $f.$b -text $item -variable $item \ -command [list PrintByName $item] radiobutton $f.$b -variable $varname \ -text $item -value $item \ -command [list PrintByName $varname] Атрибуты кнопок В табл. ЗОЛ описаны атрибуты компонентов button, checkbutton, menubutton и radiobutton. Многие из атрибутов применимы ко всем видам кнопок. Если атрибут используется лишь с отдельными типами, это оговаривается в его описании. Многие из указанных здесь атрибутов более подробно описываются в главах 40-42. Некоторые из атрибутов не поддерживаются на платформах Windows и Macintosh. В таблице указаны имена ресурсов для атрибутов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В составе Tcl-команд атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра. Сравните следующие строки: option add *Menubutton.activeBackground: red .mb configure -activebackground red Первая команда определяет запись базы данных ресурсов, соответствующую всем кнопкам menubutton. Эта запись задает красный цвет фона для активизированного компонента. Она воздействует только на те компоненты
648 Часть IV. Компоненты Тк menubutton, которые были созданы после добавления ее в базу. Вторая команда изменяет атрибут существующей кнопки menubutton (.mb) и указывает, что при активизации кнопки фон должен отображаться красным цветом. Обратите внимание на написание слова background в этих двух командах. Более подробно работа с базой данных ресурсов будет рассмотрена в конце данной главы и в главе 31. Таблица 30.1. Имена ресурсов для атрибутов кнопок act iveBackground act iveForeground anchor background bitmap borderWidth command compound cursor default direction disabledForeground font foreground height highlightBackground Цвет фона, отображаемый в тот момент, когда курсор мыши находится на кнопке Цвет текста, отображаемый в тот момент, когда курсор мыши находится на кнопке Точка фиксации, используемая при позиционировании текста Цвет фона, отображаемый в нормальных условиях Битовая карта, отображаемая вместо текста Толщина обрамления вокруг кнопки Tcl-команда, вызываемая после щелчка на кнопке Определяет способ отображения изображения или битовой карты относительно текста: bottom, center, left, right, top или none (принимается по умолчанию) (Tk 8.4) Курсор, находящийся на кнопке Значение active задает отображение кнопки по умолчанию. Значение normal или disabled приводит к тому, что элемент отображается как обычная кнопка. Дополнительная информация о данном ресурсе приведена в главе 54 (Тк 8.0) Значения up, down, left, right, active. Направление смещения для меню. Кнопка menubutton (Tk 8.0) Цвет переднего плана (текста), отображаемый в случае, если доступ к кнопке запрещен Шрифт для отображения текста Цвет переднего плана (текста). Допустимо сокращение до f g Высота. Для текста задается в строках, а для изображений — в экранных единицах измерения Цвет, которым подсвечивается элемент, когда не имеет фокуса ввода
Глава 30. Кнопки и меню 649 Продолжение табл. 30.1 highlight Color Цвет, которым осуществляется подсветка тогда, когда элемент не имеет фокуса ввода highlight Thickness Толщина обрамления, используемого для подсветки image Изображение, которое выводится вместо текста или битовой карты indicatorOn Логическое значение, управляющее отображением индикатора. Кнопки checkbutton, menubutton и radiobutton justify Выравнивание текста. Значения: center, left или right menu Меню, отображаемое после щелчка на кнопке menubutton offRelief Тип рельефа, используемый тогда, когда кнопка не выбрана. Кнопки checkbutton и radiobutton (Tk 8.4) offValue Значение Tcl-переменной в случае, если флажок checkbutton сброшен onValue Значение Tcl-переменной в случае, если флажок checkbutton установлен overRelief Тип рельефа, используемый тогда, когда курсор мыши расположен на кнопке. Кнопки button, checkbutton и radiobutton (Tk 8.4) padX Дополнительное пространство слева и справа от текста на кнопке padY Дополнительное пространство сверху и снизу от текста на кнопке relief Значение flat, sunken, raised, groove, solid или ridge repeatDelay Время в миллисекундах, в течение которого кнопка или клавиша должна удерживаться в нажатом состоянии для того, чтобы началась операция автоповтора. Только для компонента button (Tk 8.4) repeat Interval Время в миллисекундах между операциями автоповтора. Только для компонента button (Tk 8.4) selectColor Цвет селектора. Элементы checkbutton и radiobutton select Image Альтернативное изображение для селектора. Элементы checkbutton и radiobutton state Значение normal (доступ разрешен), disabled (элемент не активизирован) или active (курсор мыши расположен на кнопке) takeFocus Управляет передачей фокуса ввода в результате действий с клавиатурой text Текст, отображаемый на кнопке
650 Часть IV. Компоненты Тк Окончание табл. 30.1 textVariable underline value variable width wrapLength Tcl-переменная, содержащая текст Индекс символа, который должен отображаться с подчеркиванием Значение Tcl-переменной в случае, если выбрана кнопка radiobutton Элементы Tcl-переменная, связанная checkbutton и radiobutton кнопкой. Ширина. Для текста определяется в символах, а для изображений — в экранных единицах измерения. В Тк 8.4 для Windows отрицательное значение интерпретируется как минимальная ширина компонента button Максимальная длина, по достижении которой должен осуществляться перенос текста. Задается в экранных единицах измерения Операции с кнопками В табл. 30.2 приведена информация об операциях, которые могут выполняться с кнопками. Здесь $w обозначает компонент button, checkbutton, radiobutton или menubutton. Если какая-либо операция применима не ко всем типам кнопок, это указано в описании. В большинстве случаев приведенные в таблице операции используются библиотеками сценариев, которые определяют связывания для кнопок. Операции cget и configure часто применяются в приложениях. Таблица 30.2. Операции с кнопками $w cget опция Возвращает значение указанного атрибута $w configure Запрашивает информацию о конфигурации компонента ?опция? ?значение? либо создает новые установки $w deselect $w flash $w invoke Отменяет выбор для radiobutton или checkbutton. Переменной, связанной с radiobutton, присваивается нулевая строка. Переменной, связанной с checkbutton, присваивается значение, соответствующее сброшенному флажку Несколько раз отображает кнопку различными цветами Вызывает команду, связанную с кнопкой
Глава 30. Кнопки и меню 651 Окончание табл. 30.2 $w select Выбирает radiobutton или checkbutton. Соответствующим образом устанавливаются переменные, связанные с этими элементами $w toggle Изменяет состояние checkbutton на противоположное. Соответствующим образом устанавливается переменная, связанная с этим элементом Меню и menubutton Меню предоставляет пользователю пункты, которые внешне выглядят как кнопки. Пункт меню не является полнофункциональным компонентом Тк. При формировании интерфейса создается компонент меню, а затем в него включаются пункты различного назначения. Типы пунктов меню перечислены ниже. • Пункты вызова команд; их можно сравнить с обычными кнопками. • Пункты с независимой фиксацией; действуют подобно компонентам checkbutton. • Пункты с зависимой фиксацией; их можно сравнить с компонентами radiobutton. • Разделители, предназначенные для визуального разделения пунктов меню на группы. • Каскадные пункты; используются для вызова подменю. • Пункты разъединения. При активизации такого пункта связь между меню и соответствующей кнопкой разрывается и меню становится новым окном верхнего уровня. Компонент menubutton представляет собой кнопку специального типа, после щелчка на которой передается, или отображается, меню. Если вы щелкнете мышью на компоненте menubutton, меню отобразится и будет присутствовать на экране до выбора пункта меню или до тех пор, пока вы не щелкнете за пределами меню. Если, поместив курсор мыши на компонент menubutton, вы нажмете кнопку и будете удерживать ее, то при отпускании кнопки меню не будет отображаться на экране. Если вы отпустите кнопку тогда, когда курсор мыши указывает на меню, будет выбран тот пункт, на который указывает курсор. С компонентом menubutton можно также связывать команды. Команда выполняется перед отображением меню, а это значит, что вы можете динамически формировать содержимое меню с ее помощью.
652 Часть IV. Компоненты Тк В листинге 30.6 приведен код, посредством которого создаются пункты меню различных типов. Листинг 30.6. Примеры пунктов меню menubutton .mb -text Sampler -menu .mb.menu pack .mb -padx 10 -pady 10 set m [menu .mb.menu -tearoff 1] $m add command -label Hello! -command {puts "Hello, World!1'} $m add check -label Boolean -variable foo \ -command {puts "foo = $foo"} $m add separator $m add cascade -label Fruit -menu $m.subl set m2 [menu $m.subl -tearoff 0] $m2 add radio -label apple -variable fruit -value apple $m2 add radio -label orange -variable fruit -value orange $m2 add radio -label kiwi -variable fruit -value kiwi В данном примере создаются компонент menubutton и два меню. Главное меню .mb.menu является дочерним по отношению к компоненту menubutton .mb. Это соотношение позволяет корректно отображать меню при выборе menubutton. Аналогично, каскадное подменю .mb.menu.subl является дочерним по отношению к главному меню. Первый пункт — пункт разъединения — меню отображается штриховой линией. При его выборе создается копия меню, которая отображается на экране как новое окно верхнего уровня. Эта возможность полезна в том случае, если пользователю приходится часто выполнять действия с меню. Параметр -tearof f 0 используется для создания подменю без пункта разъединения.
Глава 30. Кнопки и меню 653 Пункты вызова команд, а также пункты с независимой и зависимой фиксацией аналогичны соответствующим типам кнопок. Конфигурационные параметры для этих пунктов подобны параметрам для кнопок. Основное отличие состоит в том, что в пунктах меню текстовые строки задаются с помощью опции -label, а в кнопках — посредством опции -text. Опции для пунктов меню описаны в табл. 30.6. Каскадные пункты предназначены для организации связи с другими меню. В правой части каскадного пункта отображается небольшая стрелка. При выборе такого пункта отображается подменю. По необходимости вы можете организовать несколько уровней каскадных меню. При этом число уровней ограничивается только соображениями удобства работы пользователя. Строка меню Строку меню можно сформировать вручную, включив с помощью диспетчера компоновки в состав фрейма несколько компонентов menubutton. По умолчанию для menubutton связывание определено таким образом, что при перетаскивании курсора мыши по компонентам отображаются соответствующие меню. Тк 8.0 позволяет создавать горизонтальное меню, связанное с окном верхнего уровня. В системах Windows и Unix строка меню отображается в верхней части самого окна. В Macintosh при активизации окна строка меню заменяет главное меню в верхней части экрана. Все пункты в строке меню должны быть каскадными, т.е. при активизации такого пункта должно отображаться подменю. Пример создания строки меню показан в листинге 30.7. В нем определены переменные для хранения имен компонентов меню. set $m [menu .menubar.m$m] При выполнении кода создаются переменные File, Edit и Help, в которых находятся имена компонентов меню. Далее в этой главе рассматривается модификация данного примера со скрытыми именами компонентов меню. Листинг 30.7. Строка меню в Тк 8.0 menu .menubar # Связь с главным окном . config -menu лпепиЬаг # Создание каскадных меню foreach m {File Edit Help} { set $m [menu лпепиЬаг лп$т] .menubar add cascade -label $m -menu лпепиЬаг.т$т }
654 Часть IV. Компоненты Тк $File add command -label Quit -command exit # Добавление пунктов меню ... Системные меню Строка меню, реализованная в Тк 8.0, позволяет добавлять пункты в системное меню Windows, в меню Macintosh, а также в меню Help для всех платформ. Добавление меню осуществляется путем распознавания специальных имен. Например, если строка меню имеет имя лпепиЬаг, то специальными именами являются лпепиЬаг.system, .menubar.apple и .menubar.help. Меню Help на всех платформах выравнивается по правому краю. Меню Apple обычно связывается с пунктом About... меню приложения. Пункты, добавленные в меню Apple, размещаются в его верхней части. Меню System отображается в строке заголовка окна Windows и содержит, в частности, пункты Close и Minimize. Контекстные меню Контекстные меню не связываются с компонентом menubutton. Вместо этого они отображаются в ответ на нажатие клавиши или при возникновении другого события в приложении. Контекстное меню вызывается с помощью команды tk_popup. tk_popup меню х у ?пункт? Последний параметр определяет пункт, который выбирается при отображении меню. Этот параметр не является обязательным. По умолчанию его значение принимается равным 1, т.е. выбирается пункт, следующий за пунктом разъединения. Меню выводится внутри родительского окна, в позиции, определяемой координатами х и у. Меню опций Меню опций содержит пункты с зависимой фиксацией; надпись на компоненте menubutton отображает выбранный пункт. Для создания компонента menubutton и меню, заполненного пунктами с зависимой фиксацией, используется команда tk_optionMenu. tk_optionMenu путь имя_переменной первое „значение ?значение значение ...? При вызове команды в качестве первого параметра задается путь к создаваемому компоненту menubutton. Второй параметр содержит имя переменной. Третий параметр задает исходное значение переменной, а остальные —
Глава 30. Кнопки и меню 655 другие допустимые значения. Помимо текущего выбранного пункта, компонент menubutton отображает символ небольшого размера, указывающий на то. что данный компонент представляет меню опций. Расположение пунктов меню в несколько столбцов В Тк 8.0 была реализована поддержка опции -columnbreak, помещающей пункт меню в начало нового столбца. Такая возможность полезна в том случае, когда пунктами меню являются изображения и они должны быть размещены в виде палитры. Для того чтобы задать в качестве пункта меню изображение, надо использовать опцию -image. Опция -hidemargin позволяет использовать изображения для пунктов с зависимой и независимой фиксацией. В этом случае вокруг изображения, представляющего выбранный пункт, отображается рамка. События меню Выбор пунктов меню с помощью клавиатуры По умолчанию связывание для меню позволяет выбирать его пункты с помощью клавиатуры. Процедура выбора начинается с нажатия комбинации клавиш <Alt-f х>, где х — буква, идентифицирующая компонент menubutton или каскадный пункт в строке меню. Эта буква определяется с помощью атрибута underline. Значением данного атрибута является номер позиции в названии пункта (отсчет ведется начиная с нуля). Например, чтобы меню File выбиралось с помощью символа F, компонент menubutton должен быть создан следующим образом: menubutton .menubar.file -text File -underline 0 \ -menu .menubar.f ile ли Если пункт File содержится в строке меню и представляет собой каскадный пункт, то надо использовать следующее выражение: menu .mbar . configure -menu .mbar .mbar add cascade -label File -underline 0 \ -menu .mbar.file Если в главном окне пользователь нажмет комбинацию клавиш <Alt-fF>, данное меню отобразится на экране. Регистр символа, идентифицирующего меню, не имеет значения. После того как меню отобразится на экране, вы можете использовать для выбора пункта клавиши со стрелками. Стрелки, направленные вверх и вниз,
656 Часть IV. Компоненты Тк осуществляют перемещение внутри меню, а стрелки, направленные влево и вправо, — переход между соседними меню. Связывание, определенное пс умолчанию, предполагает, что вы создаете меню слева направо. Если для какого-либо из пунктов меню с помощью опции -underline был определен идентифицирующий символ, то после нажатия соответствующей клавиши этот пункт активизируется. Например, для того, чтобы после нажатия клавиши <Х> вызывался пункт Export, он должен быть создан следующим образом: .menubar.file.m add command -label Export -underline 1 \ -command File.Export После нажатия клавиши пробела или <Return> активизируется текущий пункт меню. Клавиша <Esc> отменяет выбор и удаляет меню с экрана. Виртуальные события меню Начиная с Тк 8.0 после выбора пунктов меню генерируется виртуальное событие «MenuSelect». Команда, связанная с событием, вызывается после выбора нового пункта и имеет доступ к этому пункту. Получать информацию об изменении состояния меню проще всего, определив связывание для данного виртуального события. Пример такого подхода показан в листинге 30.8. Подобное оповещение бывает необходимо для организации контекстной подсказки. Листинг 30.8. Использование виртуального события <<MenuSelect>> proc MenuChanged {w} { puts "Menu $w selection: [$w entrycget active -label]" } bind .mbar.file «MenuSelect» {MenuChanged °/0W} Выполнение действий с меню Над пунктами меню можно выполнить ряд операций. С операцией add вы уже знакомы. Операция entry configure выполняется подобно операции configure, определенной для компонентов. Она получает такие же пары атрибут-значение, которые используются при добавлении пункта меню. Операция delete удаляет пункты меню в определенном диапазоне. Остальные операции используются библиотечными сценариями, которые реализуют стандартные связывания для меню.
Глава 30. Кнопки и меню 657 При обращении к пункту меню используется его индекс. Индекс может быть числовым (индекс первого пункта равен нулю) или символьным. В табл. 30.3 приведены сведения о формате индекса. Чаще всего в качестве индекса используется шаблон, по которому определяется метка нужного пункта меню. Сравнение с шаблоном происходит по тем же правилам, что и при выполнении команды string match. Применение шаблона избавляет от необходимости вычислять числовое значение индекса. Таблица 30.3. Ключевые слова для определения индексов пунктов меню индекс Числовой индекс. Отсчет индексов ведется с нуля active Активизированный пункт. Активизация может быть осуществлена как путем помещения на пункт курсора мыши, так и посредством клавиатуры end Последний пункт меню last Синоним end none Отсутствие пунктов меню (&координата_у Пункт, определяемый посредством координаты у. В связываниях используется выражение @У0у pattern Строка-шаблон для проверки текста, соответствующего пункту меню В табл. 30.4 описаны операции с меню. Компонент меню обозначается как $w. Таблица 30.4. Операции с меню $w activate индекс Подсвечивание указанного пункта меню $w add тип ? опция Добавляет новый пункт меню указанного типа, за- значение? . . . давая при этом значения атрибутов $w cget опция Возвращает значение конфигурационной опции $w clone Создает копию меню. Данная операция используется для создания строк меню, а также меню, не связанного с вышестоящими компонентами $w configure ?опция? Возвращает конфигурационную информацию для ?значение? . . . меню $w delete индекс_1 Удаляет пункты меню от первого до второго ин- ?'индекс _2? декса ф
658 Часть IV. Компоненты Тк Окончание табл. 30.4 $w entrycget индекс опция $w entryconfigure индекс ?опция? ?значение? ... $w index индекс $w insert тип индекс ?опция значение? ... $w invoke индекс $w post х у $w postcascade индекс $w type индекс $w unpost $w yposition индекс Возвращает значение опции для указанного пункта меню Запрашивает конфигурационную информацию для указанного пункта меню или создает новые установки Возвращает числовое значение индекса Действует подобно операции add, но включает новый пункт после пункта с указанным индексом Вызывает команду, связанную с указанным пунктом меню Отображает меню в позиции с указанными координатами Отображает каскадное меню, соответствующее указанному пункту Возвращает тип указанного пункта меню Удаляет меню Возвращает значение координаты у, соответствующей верхней части пункта Атрибуты меню Компонент меню содержит несколько глобальных атрибутов, кроме того, для каждого пункта меню определяются атрибуты, которые описывают его внешний вид и поведение. В табл. 30.5 представлены атрибуты, которые относятся к меню в целом, однако они могут быть переопределены для каждого пункта. В таблице используются имена ресурсов X Window; составляющие их слова начинаются с прописной буквы. В Tcl-командах для определения атрибутов применяются опции. Имя опции совпадает с именем соответствующего атрибута, за исключением того, что опция начинается с символа - и в ней используются только буквы нижнего регистра. Таблица 30.5. Имена ресурсов для атрибутов меню activeBackground Цвет фона, отображаемый тогда, когда курсор мыши находится на пункте меню activeBorderWidth Толщина обрамления вокруг активизированного пункта activeForeground Цвет, которым текст отображается в тот момент, когда курсор мыши находится на пункте меню
Глава 30. Кнопки и меню 659 Окончание табл. 30.5 background borderWidth cursor disabledForeground font foreground postCommand Цвет фона, отображаемый тогда, когда пункт меню находится в нормальном состоянии Толщина обрамления, отображаемого вокруг меню (за исключением тех систем, в которых используются плат- форменно-ориентированные меню, например Windows) Курсор, отображаемый тогда, когда он находится на пункте меню Цвет переднего плана (текста), отображаемый в случае, если пункт меню недоступен Шрифт для отображения текста, используемый по умолчанию Цвет переднего плана. Допустимо сокращение до f g Tcl-команда, которая вызывается перед отображением relief selectColor takeFocus tearOff tearOffCommand title type Тип рельефного отображения для меню (за исключением тех систем, в которых используются платформенно- ориентированные меню, например Windows) Цвет селектора для пунктов меню с зависимой и независимой фиксацией Управляет передачей фокуса ввода в результате действий с клавиатурой Принимает значение true, если меню содержит пункт разъединения Команда, которая выполняется при разъединении меню. При вызове команды добавляются два параметра: исходное меню и новое разъединенное меню Заголовок окна, создаваемого при разъединении меню. Если значение представляет собой пустую строку (такое значение принимается по умолчанию), в качестве заголовка используется текст, отображаемый на menubutton, либо текст каскадного пункта, от которого меню было отсоединено (Тк 8.0) Значение normal, menubar или tearof f. Доступно только для чтения (Тк 8.0) В табл. 30.6 описаны атрибуты пунктов меню. Они представлены в формате опций, используемых в Tcl-командах (т.е. начинаются со знака - и содержат только символы нижнего регистра). Атрибуты для пунктов меню непосредственно не поддерживаются базой данных ресурсов. Однако в ли-
660 Часть IV. Компоненты Тк стинге 31.6 будет показан способ использования базы данных ресурсов для работы с пунктами меню. Таблица 30.6. Атрибуты пунктов меню -activebackground Цвет фона, отображаемый тогда, когда курсор мыши находится на пункте меню -activef oreground Цвет переднего плана, т.е. цвет, которым текст отображается в тот момент, когда курсор мыши находится на пункте меню -accelerator Текст, представляющий собой напоминание о связывании с клавишей -background Цвет фона, отображаемый тогда, когда пункт меню находится в нормальном состоянии -bitmap Битовая карта, отображаемая вместо текста -columnbreak Указывает на то, что пункт меню должен стать первым в новом столбце (Тк 8.0) -command Tcl-команда, которая вызывается при активизации пункта меню -compound Расположение изображения или битовой карты относительно текста. Значения: bottom, center, left, right, top или none (принимается по умолчанию) (Tk 8.4) -font Шрифт, используемый по умолчанию для отображения текста -foreground Цвет переднего плана. Допустимо сокращение до fg -hidemargin Отказ от границ, зарезервированных для индикатора (Тк 8.0) -image Изображение, отображаемое вместо текста или битовой карты -indicatoron Логическое значение, управляющее отображением индикатора. Используется для пунктов с зависимой или независимой фиксацией -label Текст пункта меню -menu Меню, отображаемое при вызове каскадного пункта -offvalue Значение переменной при условии, что соответствующий ей пункт с независимой фиксацией не установлен -onvalue Значение Tcl-переменной при условии, что соответствующий ей пункт с независимой фиксацией установлен -selectcolor Цвет селектора. Используется для пунктов с зависимой и независимой фиксацией
Глава 30. Кнопки и меню 661 Окончание табл. 30.6 -selectimage Изображение, соответствующее выбранному пункту. Используется для пунктов с зависимой и независимой фиксацией -state Состояние пункта: normal, active или disabled -underline Индекс символа, который должен отображаться с подчеркиванием -value Значение Tcl-переменной в случае, если выбран пункт с зависимой фиксацией -variable Tcl-переменная, связанная с пунктом с зависимой или независимой фиксацией Пакет для работы с меню по именам Если ваше приложение поддерживает расширяемое меню или меню, определяемое пользователем, то для уточнения всех его деталей потребуется много времени и усилий. Мы создадим в качестве примера простой пакет, который позволит пользователям обращаться к меню и его пунктам по именам. Кроме того, данный пакет будет поддерживать комбинации клавиш для быстрого доступа к меню. Процедура Menu_Setup инициализирует пакет. Она создает фрейм, содержащий набор кнопок, и инициализирует некоторые переменные состояния, в частности счетчик для генерации путей к компонентам. Информация о состоянии пакета содержится в массиве с именем menu. Процедура Menu создает компонент menubutton и меню. Она также записывает информацию о соответствии между текстом на компоненте menubutton и меню, созданном для него. Данное отображение используется остальными частями пакета так, что клиент, работающрш с пакетом, может вместо пути к меню (например, .top.menubar.file.menu) обращаться к нему по имени (например, File). Листинг 30.9. Простой пакет для обращения к меню по имени proc Menu_Setup { menubar } { global menu frame $menubar pack $menubar -side top -fill x set menu(menubar) $menubar set menu(uid) 0
662 Часть /V. Компоненты Тк proc Menu { label } { global menu if [info exists menu(menu,$label)] { error "Menu $label already defined" } # Создание menubutton и меню set name $menu(menubar).mb$menu(uid) set menuName $name.menu incr menu(uid) set mb [menubutton $name -text $label -menu $menuName] pack $mb -side left menu $menuName -tearoff 1 # Запоминание имени set menu(menu,$label) $menuName 2 В листинге 30.10 содержатся новые определения тех же процедур. Отличие от предыдущего примера состоит в том, что здесь используется механизм для работы со строкой меню, реализованный в Tk 8.O. Остальные процедуры пакета остаются такими же, как и при работе с предыдущей версией строки меню. Листинг 30.10. Использование средств работы с меню, определенных в Тк 8.0 proc Menu_Setup { menubar } { global menu menu $menubar # Связывание меню с главным окном set top [winfo parent $menubar] $top config -menu $menubar set menu(menubar) $menubar set menu(uid) 0 } proc Menu { label } { global menu if [info exists menu(menu,$label)] { error "Menu $label already defined" } # Создание каскадного меню set menuName $menu(menubar).mb$menu(uid) incr menu(uid) menu $menuName -tearoff 1
Глава 30. Кнопки и меню 663 $menu(menubar) add cascade -label $label -menu $menuName # Запоминание имени set menu(menu,$label) $menuName _} После настройки меню массив menu используется для отображения имени меню (например, File) в имя компонента Тк (например, .menubar.mb3). Несмотря на то что для этого достаточно нескольких строк Tcl-кода, действия по отображению имен оформлены в виде процедуры MenuGet. Если имя меню неизвестно, в процедуре MenuGet выполняется команда return -code error, которая слегка видоизменяет сообщение об ошибке. (Подробно о формировании сообщений об ошибках см. в главе 6.) Если пользователь некорректно укажет имя меню, переменная ошибки будет перехвачена и процедура сформирует более информативное сообщение. Процедура MenuGet предназначена для использования внутри пакета, поэтому в ее имени отсутствует подчеркивание. Листинг 30.11. Процедура MenuGet отображает имя в меню proc MenuGet {menuName} { global menu if [catch {set menu(menu,$menuName)} m] { return -code error "No such menu: $menuName" } return $m 2 Процедуры Menu_Command, Menu_Check, Menu_Radio и Menu.Separator играют роль простых оболочек, в которые помещаются базовые команды меню. Они используют MenuGet для отображения текстовой метки в имя компонента Тк. Листинг 30.12. Добавление новых пунктов меню proc Menu_Command { menuName label command } { set m [MenuGet $menuName] $m add command -label $label -command $command } proc Menu_Check { menuName label var { command {} } } { set m [MenuGet $menuName] $m add check -label $label -command $command \ -variable $var
664 Часть IV. Компоненты Тк proc Menu_Radio { menuName label var {val {}} {command {}} } { set m [MenuGet $menuName] if {[string length $val] == 0} { set val $label } $m add radio -label $label -command $command \ -value $val -variable $var } proc Menu_Separator { menuName } { [MenuGet $menuName] add separator 2 Для генерации каскадного меню также необходимо сохранять отображение между меткой каскадного пункта и имени пути Тк к подменю. Данный пакет накладывает ограничение, согласно которому для различных меню, в том числе подменю, не может использоваться одна и та же текстовая метка. Листинг 30.13. Оболочка для каскадных пунктов proc Menu_Cascade { menuName label } { global menu set m [MenuGet $menuName] if [info exists menu(menu,$label)] { error "Menu $label already defined" } set sub $m.sub$menu(uid) incr menu(uid) menu $sub -tearoff 0 $m add cascade -label $label -menu $sub set menu(menu,$label) $sub 2 В листинге 30.14 приведен пример создания меню с использованием пакета, рассмотренного в данном разделе. Листинг 30.14. Использование пакета для обращения к меню по имени Menu_Setup .menubar Menu Sampler
Глава 30. Кнопки и меню 665 Menu__Command Sampler Hello! {puts "Hello, World!"} Menu_Check Sampler Boolean foo {puts "foo = $foo"} Menu.Separator Sampler Menu.Cascade Sampler Fruit Menu_Radio Fruit apple fruit Menu.Radio Fruit orange fruit Menu.Radio Fruit kiwi fruit Комбинации клавиш для доступа к меню Как было сказано ранее, пакет, позволяющий обращаться к меню по именам, поддерживает также комбинации клавиш, предназначенные для обращения к пунктам меню. Во многих приложениях рядом с некоторыми пунктами меню приведены сведения о комбинациях клавиш. Они напоминают пользователям о том, что с комбинациями клавиш связаны те же команды, что и с соответствующими пунктами меню. Однако при этом нет никакой гарантии, что символы указаны верно или что при модификации меню связывание не будет изменено. В листинге 30.15 приведен код процедуры, отслеживающей подобные ситуации. Листинг 30.15. Поддержка корректных данных о комбинациях клавиш proc Menu_Bind { what sequence accText menuName label } { variable menu set m [MenuGet $menuName] if {[catch {$m index $label} index]} { error "$label not in menu $menuName" } bind $what $sequence [list Menulnvoke $m $index] $m entryconfigure $index -accelerator $accText } proc Menulnvoke {m index} { set state [$m entrycget $index -state] if {[string equal $state normal]} { $m invoke $index } 2 Процедура Menu.Bind использует операцию index для поиска пункта меню с данной текстовой меткой. Она устанавливает для комбинаций клавиш такое связывание, которое вызывает соответствующую операцию меню и отображает корректные сведения о комбинациях клавиш, используя для
666 Часть IV. Компоненты Тк этого операцию entryconf igure. Данный подход позволяет автоматически поддерживать соответствие между пунктами меню и комбинациями клавиш. Процедура Menulnvoke используется для связывания. Команда загружается с помощью операции entrycget, после чего с ней устанавливается непосредственная связь. Однако при этом не учитывается состояние пункта меню, доступ к которому может быть временно запрещен. Операция invoke предназначена для обработки специальных ситуаций, например, для обновления переменных, соответствующих пунктам меню с зависимой фиксацией. Для того чтобы проверить, как работает процедура MenuJ3ind, надо создать пустой фрейм и связать с ним комбинацию клавиш и одну из команд меню. Необходимые для этого команды приведены ниже. frame .body -width 100 -height 50 pack .body ; focus .body Menu_Bind .body <Control-q> Ctrl-Q Sampler Hello!
Глава 31 База данных ресурсов В данной главе описываются особенности использования базы данных ресурсов и рассматриваются вопросы определения кнопок и меню с помощью этой базы. В данной главе также описывается команда option. Tk поддерживает базу данных ресурсов, в которой находятся описания атрибутов компонентов, например начертания шрифтов и цвета. База данных ресурсов позволяет управлять всеми атрибутами компонентов Тк. Она также может использоваться для хранения параметров приложения, как и обычная база данных. Поскольку для настройки приложения Тк применимы средства Tcl, база данных ресурсов может показаться излишней. Однако она представляет собой чрезвычайно удобный инструмент, позволяющий организовывать работу с приложениями Тк. Посредством лишь нескольких записей базы разработчик может изменить параметры всего приложения. Кроме того, база данных ресурсов предоставляет возможность пользователям и системным администраторам изменять характеристики приложения, не модифицируя его код. Общие сведения о ресурсах При создании компонента Тк для получения значений его атрибутов используется один из трех перечисленных ниже источников. Необходимо заметить, что опции, задаваемые при вызове Tcl-команды, приоритетнее описаний, содержащихся в базе данных. • Наиболее очевидным источником значений атрибутов являются опции Tcl-команд, например опция -text quit для кнопки.
668 Часть IV. Компоненты Тк • Если в командной строке соответствующая опция не задана, запрашивается база данных ресурсов. Механизм запросов будет рассмотрен далее в этой главе. • Если в базе данных ресурсов отсутствует информация об атрибуте, используется значение, заданное при реализации компонента. База данных ресурсов содержит набор ключей и соответствующих им значений. В отличие от других баз данных, ключи представляют собой шаблоны, с которыми сравниваются имена компонентов и их атрибутов. В результате становится возможным определять значения атрибутов для самых разных компонентов посредством небольшого числа записей базы данных. Кроме того, база данных ресурсов может совместно использоваться многими прикладными программами, поэтому пользователи и системные администраторы имеют возможность задавать атрибуты для целого набора приложений. Инструментальные средства Тк размещают базу данных ресурсов в оперативной памяти. В системе Unix база данных инициализируется посредством свойства RESOURCE_MANAGER корневого окна или с помощью файла . Xdef aults, содержащегося в рабочем каталоге пользователя. В системах Windows и Macintosh новые ресурсы добавлены в библиотечный файл tk.tcl. Дополнительные файлы должны непосредственно загружаться с помощью команды option readf ile, а записи базы данных добавляются с помощью Тс1-коман- ды option add. Возможности базы данных ресурсов отличаются от набора инструментов Xt, который загружает описания из пяти разных файлов, обеспечивая настройки для пользователя, узла, приложения, компьютера и для каждого пользователя, работающего с данным приложением. Подобные действия можно выполнять и в Тк, но их надо запрограммировать самостоятельно. Частичное решение данной проблемы будет описано в главе 45. Шаблоны ресурсов Язык шаблонов для ключевых слов учитывает соглашения об именовании компонентов Тк. Как вы помните, имя компонента отражает его позицию в иерархии окон. Имена ресурсов можно представить себе как расширение иерархии имен компонентов. При этом добавляется дополнительный нижний уровень, представляющий атрибуты конкретного компонента и один верхний уровень, на котором задается имя приложения. Например, база данных ресурсов может содержать запись, подобную приведенной ниже. Эта запись определяет шрифт для кнопки quit, содержащейся во фрейме с именем .buttons. Exmh.buttons.quit.font: fixed
Глава 31. База данных ресурсов 669 В данном случае последовательность символов Exmh. соответствует имени класса для приложения Tcl/Tk. Имя класса приложения формируется на основе имени файла, содержащего сценарий; при этом первый символ переводится в верхний регистр. Например, для сценария /usr/local/bin/f oobar формируется имя класса Foobar. Для обозначения группы приложений можно также использовать символ *. *buttpns.quit.font: fixed С помощью ключей в базе данных ресурсов можно задавать не только отдельные экземпляры компонентов, но и их классы. Например, кнопка quit является экземпляром класса Button. Имена классов для компонентов совпадают с именами Tcl-команд, используемых для их создания, за исключением того, что первая буква переводится в верхний регистр. Спецификация, определяющая шрифт для всех кнопок во фрейме .buttons, имеет следующий вид: Exmh.buttons.Button.font: fixed He следует использовать имена, компонентов в качестве имен сценариев. Класс приложения становится именем класса для главного окна верхнего уровня. Например, если вы используете имя сценария button, tcl, классом для . станет Button. В результате будут унаследованы все стандартные связывания для кнопок и значения атрибутов, что может стать источником проблем при работе приложения. В шаблонах можно заменить один или несколько компонентов имени ресурса на символ *. Например, для того, чтобы установить шрифт для всех компонентов, расположенных во фрейме .buttons, можно использовать имя ресурса *buttons*f ont; вы также можете задать шрифт для всех кнопок с помощью шаблона *Button.font. Одна звездочка может заменить несколько уровней иерархического имени. Это позволяет задавать атрибуты для многих компонентов с помощью небольшого числа записей базы данных. В таблицах, приведенных в данной книге, для обозначения атрибутов компонентов используются имена ресурсов. Слова, составляющие имя ресурса, начинаются с прописной буквы. Например, если в командной строке задается опция -offvalue, то соответствующий ей ресурс имеет имя offValue. Имена классов для атрибута начинаются с прописной буквы (например, Off Value). Порядок следования записей в базе данных ресурсов имеет значение! При поиске соответствия между именами компонентов и шаблонами, содержащимися в базе данных компонентов, может оказаться, что одному компоненту соответствует несколько шаблонов. В этом
670 Часть IV. Компоненты Тк случае шаблон, который будет использоваться, определяется порядком следования записей в базе данных. Более высокий приоритет имеет тот шаблон, который расположен ближе к концу базы данных. (Этим база данных ресурсов отличается от набора инструментов Xt, в котором наиболее приоритетными являются самые длинные шаблоны, а описание компонента приоритетнее, чем описание класса.) Предположим, что в базе данных компонентов содержатся две следующие записи, причем они расположены в таком же порядке: *Text*foreground: blue *foreground: red Несмотря на то что более конкретной является запись *Text*foreground, для всех компонентов, даже для компонента text, будет отображаться красный цвет фона. По этой причине шаблоны, содержащие более общее описание, должны располагаться перед более конкретными шаблонами. Тк также поддерживает приоритеты ресурсов. Более подробно этот вопрос рассматривается в следующем разделе. Порядок следования важен лишь для ресурсов с одинаковым приоритетом. Загрузка файла базы данных Команда option предназначена для выполнения различных действий с базами данных ресурсов. Ниже приведен вариант данной команды, посредством которого производится загрузка файла, содержащего записи базы данных. option readfile имя_файла ?приоритет? В качестве последнего необязательного параметра задается приоритет, присваиваемый источнику информации о ресурсах. В качестве приоритета может использоваться любое число от 0 до 100. С помощью символьных имен задаются стандартные приоритеты — widgetDef ault (20), startupFile (40), userDefault (60) и interactive (80). Эти имена можно применять в сокращенном виде. По умолчанию принимается приоритет interactive. Листинг 31.1. Чтение файла базы данных с помощью команды option if [file exists $appdefaults] { if [catch {option readfile $appdefaults startup} err] { puts stderr "error in $appdefaults: $err" } J Записи в файле содержатся в следующем формате: ключ: значение
Глава 31. База данных ресурсов 671 Ключ представляет собой шаблон, описанный выше. Значение может быть любым. Если оно состоит из нескольких слов, группировать слова с помощью фигурных скобок или двойных кавычек не надо, так как символы группировки будут интерпретироваться как часть значения. Строки, содержащие комментарии, начинаются с восклицательного зна- ка (!). Листинг 31.2. Файл, содержащий описания ресурсов ! Установки уровней серого цвета ! Данные значения соответствуют значениям, используемым ! Tk-компонентами в~системе Unix *background: #d9d9d9 *foreground: black *activeBackground: #ececec *activeForeground: black *selectColor: #b03060 *selectBackground: #c3c3c3 *troughColor: #c3c3c3 *disabledforeground: #a3a3a3 В данном примере файл ресурсов задает цветовую схему для компонента Тк. Цветовая схема базируется на семействе оттенков серого. Подсветка хорошо выделяется на общем фоне. Указанные в базе цвета заданы для всех компонентов. Цвета указываются с помощью шестнадцатеричных значений, по две цифры (восемь битов) на каждый из основных цветов: красный, зеленый и синий. Вопросы работы с цветом детально описываются в главе 41. Включение записей в базу данных Добавить в базу данных ресурсов запись позволяет Tcl-команда option add. Необходимость в использовании этой команды возникает, если надо обрабатывать специальные ситуации. Возможно также, что разработчик не хочет создавать файлы базы данных для каждого приложения. В этом случае он вынужден настраивать базу данных ресурсов из программы. Команда option add записывается следующим образом: option add шаблон значение ?приоритет? Как и в команде option readf ile, последним параметром указывается приоритет. Шаблон и значение имеют тот же смысл, что и соответствующие
672 Часть IV. Компоненты Тк элементы записи в файле базы данных. Отличие лишь в том, что в команде option add ключ задается без двоеточия. Если значение содержит пробелы или специальные символы, вам необходимо выполнить группировку. Ниже приведены команды, с помощью которых добавляются некоторые описания из рассмотренного выше файла базы данных. option add *foreground black option add *selectBackground #bfdfff Команда option позволяет также очищать базу данных. option clear Работая в системе Unix, следует помнить, что при следующем обращении база данных будет инициализирована посредством файла V.Xdefaults или с помощью свойства RESOURCE_MANAGER корневого окна. Обращение к базе данных Во многих случаях бывает достаточно сформировать базу данных ресурсов и предоставить реализации компонента возможность использовать содержащиеся в ней данные. Однако не следует забывать, что в этой базе можно хранить информацию, специфическую для конкретного приложения. Для получения значения ресурса используется команда option get. option get окно имя класс В данном случае окном считается путь к компоненту Тк. В качестве имени задается имя ресурса. В данном случае имя не является полным именем или шаблоном. Вместо этого задается имя, подобное показанному в таблицах этой книги. Аналогично, класс — это имя класса. Допускается указывать пустое имя класса. Если соответствующая запись в базе данных не найдена, команда option get возвращает пустую строку. Команда option get не позволяет отличить значение, представляющее собой пустую строку, от отсутствия записи в базе. Справиться с этим можно путем введения специальных имен ресурсов, содержащих список других ресурсов. Кнопки, определяемые пользователем Предположим, что вам необходимо, чтобы пользователь вашей программы имел возможность определять новые кнопки и связывать с ними часто выполняемые команды. Не исключено также, что пользователю придется дополнить приложение своим Tcl-кодом. В этом разделе мы рассмотрим схему,
Глава 31. База данных ресурсов 673 предложенную Джоном ЛоВерсо (John LoVerso), которая позволяет определять новые кнопки и вызывать с их помощью некоторые команды. В приложении создается специальный фрейм, предназначенный для размещения кнопок, определяемых пользователем. Этот фрейм можно создать следующим образом: frame .user -class User В команде указан класс для фрейма. Это значит, что мы можем именовать ресурсы для компонентов в составе фрейма как *User. Пользователь описывает кнопки, которые должны быть помещены во фрейм, в отдельном файле, содержащем спецификации ресурсов. Первая проблема состоит в том, что у нас нет средств, позволяющих организовать перечисление в базе данных, поэтому нам надо создать ресурс, в котором содержался бы список кнопок, определяемых пользователем. Мы используем для этой цели имя buttonlist и создадим запись *User .buttonlist, которая описывала бы определяемые кнопки. Допустимо использовать искусственно сформированные имена ресурсов (например, buttonlist), но они должны быть связаны с существующими компонентами Тк. Листинг 31.3. Использование ресурсов для описания кнопок, определяемых пользователями *User.buttonlist: save search justify quit *User.save.text: Save *User.save.command: File_Save *User.search.text: Search *User.search.command: Edit_Search *User.justify.text: Justify *User.justify.command: Edit_Justify *user.quit.text: Quit *User.quit.command: File_Quit *User.quit.background: red В листинге 31.3 описаны четыре кнопки, для которых определено несколько атрибутов; в основном это атрибуты text и command. Конечно же, мы предполагаем, что набор команд, которые можно использовать, не подвергая опасности программу, описан в руководстве по использованию приложения. В данном простом примере все команды состоят из одного слова, однако при работе с командами, включающими несколько слов, проблемы обычно не возникают. Значения атрибутов не интерпретируются, поэтому вы можете включать в них ссылки на Tcl-переменные и организовывать вложенные вызовы команд. В листинге 31.4 приведена процедура, использующая описания ресурсов для определения кнопок.
674 Часть IV. Компоненты Тк Листинг 31.4. Процедура Resource_ButtonFrame определяет кнопки на основе имеющихся ресурсов proc Resource_ButtonFrame { f class } { frame $f -class $class -borderwidth 2 pack $f -side top -fill x foreach b [option get $f buttonlist {}] { if [catch {button $f.$b}] { button $f.$b -font fixed } pack $f.$b -side right } 2 Выражение catch призвано разрешить ряд часто встречающихся проблем, связанных с использованием шрифтов и созданием компонентов. Если в составе пользовательских ресурсов шрифт указан некорректно или заданный шрифт отсутствует, то при создании компонента возникает ошибка. Команда catch отслеживает эту ситуацию, задавая шрифт fixed, который обязательно имеется в наличии. В Тк 8.0 эта проблемы была решена за счет механизма поиска альтернативных шрифтов. При написании кода, приведенного в листинге 31.5, предполагалось, что спецификация ресурсов, показанная в листинге 31.2, содержится в файле button.resources. В результате выполнения данного кода кнопка, определяемая пользователем, создается во фрейме .users. Листинг 31.5. Использование процедуры Resource.ButtonFrame option readfile button.resources Resource_ButtonFrame .user User Меню, определяемые пользователями Схема, подобная описанной выше, может быть применена для работы с меню, определяемыми пользователем. Однако данная задача несколько сложнее предыдущей, поскольку ресурсы для конкретных пунктов меню отсутствуют. Поэтому нам необходимо использовать большее количество искусственных ресурсов. Для именования набора меню мы выберем имя menulist.
Глава 31. База данных ресурсов 675 Для каждого из меню определим ресурс entrylist. И, наконец, для каждого пункта создадим дополнительные ресурсы. В имени пункта должна присутствовать дополнительная информация, соответствующая следующим соглашениям. • 1_пункт — метка пункта. • 1;_пункт — тип пункта. • с_пункт — команда, связанная с пунктом. • у_пункт — переменная, связанная с пунктом. • т_пункт -- меню, связанное с пунктом. Листинг 31.6. Описание пунктов меню с помощью ресурсов *User.menulist: stuff *User.stuff.text: My stuff *User.stuff.m.entrylist: keep insert find *User.stuff.m.l_keep: Keep on send *User.stuff.m.t„keep: check *User.stuff.m.v_keep: checkvar *User.stuff.m.l_insert: Insert File... *User.stuff.m.c_insert: InsertFileDialog *User.stuff.m.l_find: Find *User.stuff.m.t_find: cascade *User.stuff.m.m_find: find *User.stuff.m.find.entrylist: next prev *User.stuff.m.find.tearoff: 0 *User.stuff.m.find.l_next: Next *User.stuff.m.find.c_next: Find_Next
676 Часть IV. Компоненты Тк *User.stuff.m.find.l_prev: Previous *User.stuff.m.find.c_prev: Find_Previous В листинге 31.6 .user.stuff — это Тк-компонент menubutton. Его дочерним элементом является меню .user, stuff .m. Список пунктов меню напоминает ресурс buttonlist. Для каждого пункта мы должны позаботиться о следующем уровне имен. Так, например, приведенное ниже выражение некорректно. *User.stuff.m.keep.label: Keep on send Проблема заключается в том, что Tk не обеспечивает непосредственную поддержку ресурсов для пунктов меню, поэтому программа, работающая с описанием, ошибочно предполагает, что .stuff .m.keep — это путь к компоненту. Несмотря на то что вы можете добавлять ресурсы, их нельзя получить с помощью команды option get. Вместо этого необходимо объединить информацию об атрибуте с именем пункта. *User.stuff.m.l_keep: Keep on send Нечто подобное вы должны сделать, если хотите определять ресурсы для холста, поскольку они непосредственно не поддерживаются Тк. Код, используемый для поддержки меню, определяемых с помощью ресурсов, приведен в листинге 31.7. Листинг 31.7. Определение меню посредством описания ресурсов proc Resource_Menubar { f class } { set f [frame $f -class $class] pack $f -side top foreach b [option get $f menulist {}] { set cmd [list menubutton $f.$b -menu $f.$b.m \ -relief raised] if [catch $cmd t] { eval $cmd {-font fixed} } if [catch {menu $f.$b.m}] { menu $f.$b.m -font fixed } pack $f.$b -side left ResourceMenu $f.$b.m } } proc ResourceMenu { menu } {
Глава 31. База данных ресурсов 677 foreach e [option get $menu entrylist {}] { set 1 [option get $menu l_$e {}] set с [option get $menu c_$e {}] set v [option get $menu v_$e {}] switch -- [option get $menu t_$e {}] { check { $menu add checkbutton -label $1 -command $c \ -variable $v } radio { $menu add radiobutton -label $1 -command $c \ -variable $v -value $1 } separator { $menu add separator } cascade { set sub [option get $menu m_$e {}] if {[string length $sub] != 0} { set submenu [menu $menu.$sub] $menu add cascade -label $1 -command $c \ -menu $submenu ResourceMenu $submenu } } default { $menu add command -label $1 -command $c } } } } Приложение и пользовательские ресурсы Примеры, приведенные выше, являются частями пакета, использованного в таких приложениях, как exmh и webtk. Практически все кнопки приложения определяются с помощью ресурсов, таким образом, пользователи и администраторы узлов могут переопределять их. Ресурсы buttonlist, menulist и entrylist объединены в списки, ориентированные на пользователя, узел и приложение. Прикладная программа использует список приложения для установления исходной конфигурации. Списки, ориентированные на пользо-
678 Часть IV. Компоненты Тк вателей и узлы, позволяют добавлять и удалять компоненты. Примеры таких списков приведены ниже. • buttonlist — список кнопок, ориентированный на приложение. • 1-buttonlist — список, ориентированный на узел; содержит кнопки, предназначенные для здаления. • lbuttonlist — список, ориентированный на узел; содержит кнопки, которые должны быть добавлены. • u-buttonlist — список, ориентированный на пользователя; содержит кнопки, предназначенные для удаления. • ubuttonlist — список, ориентированный на пользователя; содержит кнопки, которые должны быть добавлены. Описанная идея была высказана Акимом Боне (Achim Bonet), он же впервые реализован данный подход. Процедура Resource_GetFamily объединяет пять описанных выше наборов ресурсов. Ее можно использовать в листингах 31.4 и 31.7 вместо команды option get для buttonlist, menulist и entrylist. Листинг 31.8. Процедура Resource.GetFamily объединяет пользовательские ресурсы и ресурсы приложения proc Resource_GetFamily { w resname } { set res [option get $w $resname {>] set Ires [option get $w l$resname {}] set ures [option get $w u$resname {}] set 1-res [option get $w l-$resname {}] set u-res [option get $w u-$resname {}] # Удаление ресурсов приложения для узла set list [lsubtract $res ${l-res>] # Добавление ресурсов приложения для узла set list [concat $list $lres] # Удаление ресурсов для пользователя set list [lsubtract $list ${u-res}] # Добавление ресурсов для пользователя return [concat $list $ures] } proc lsubtract { orig nuke } { # Удаление из $orig элементов, содержащихся в $nuke foreach x $nuke { set ix [lsearch $orig $x] if {$ix >= 0} {
Глава 31. База данных ресурсов 679 set orig [lreplace $orig $ix $ix] } } return $orig } Особенности подстановки переменных Если в ресурсе определена команда, содержащая выражения подстановки, например $ или [], они обрабатываются при активизации кнопки или пункта меню. Это происходит потому, что в момент создания компонента команда не интерпретируется. Однако в некоторых случаях может потребоваться, чтобы подстановка осуществлялась при создании кнопки или меню. В этом случае надо использовать команду subst. set cmd [$button cget -command] $button config -command [subst $cmd] При выборе области видимости для subst могут возникать проблемы. Предыдущая команда выполняет subst в текущей области видимости. Если действия происходят в процедуре Resource_ButtonFrame, то в ней нет переменных, которые представляли бы интерес. В приведенной ниже команде использовалась команда uplevel, благодаря наличию которой subst выполняется в области видимости процедуры, вызвавшей Resource_ButtonFrame. В данном примере команда list необходима для того, чтобы при выполнении uplevel сохранялась структура команды subst. $button config -command [uplevel [list subst $cmd]] Если команда subst используется в процедуре ResourceMenu, необходимо отслеживать уровень рекурсии для того, чтобы вернуться в область видимости процедуры, из которой была вызвана Resource_Menubar. Следующий фрагмент кода демонстрирует изменения в составе ResourceMenu: proc ResourceMenu { menu {level 1} } { foreach e [option get $menu entrylist {}] { # Часть кода пропущена set с [option get $menu c_$e {}] set с [uplevel $level [list subst $c]] # Рекурсивный вызов ResourceMenu $submenu [expr $level+l] # Часть кода пропущена } }
680 Часть IV. Компоненты Тк Если вы хотите, чтобы команда subst выполнялась в глобальной области видимости, вам надо использовать выражение, подобное следующему: $button config -command [uplevel #0 [list subst $cmd]] Однако глобальная область видимости, как правило, не изменяется с момента определения кнопки до ее активизации. На практике команду subst можно использовать для обработки переменных, которые определены в процедуре, вызывающей Resource_Menubar.
Глава 32 Простые компоненты Тк В данной главе описываются простые компоненты Tk: frame, label, labelframe, message, scale и toplevel. Для настройки этих компонентов практически не приходится затрачивать время и усилия. Команда bell инициирует звуковой сигнал на пользовательском терминале. JD данной главе рассматриваются шесть простых компонентов и команда bell. • Компонент frame является основным "строительным блоком" при создании пользовательских интерфейсов. • Компонент labelframe представляет собой фрейм с дополнительными возможностями. В частности, он позволяет отображать текст в строке заголовка. • Компонент toplevel — это фрейм, не связанный с главным окном. • Компонент label отображает текст или изображение. • Компонент message реализует текстовый блок, предназначенный только для чтения. Последовательность символов внутри блока представляется в виде нескольких строк. • Компонент scale представляет собой линейный, или ползунковый, регулятор, посредством которого можно задавать числовые значения. • Команда bell воспроизводит звуковой сигнал на терминале. Дополнительную информацию о некоторых из атрибутов указанных компонентов можно найти в главах 40-42 данной книги. В примерах, приведенных в этой главе, чаще всего используются атрибуты компонентов, принимаемые по умолчанию.
682 Часть IV. Компоненты Тк Фреймы и окна верхнего уровня С фреймами вы уже знакомы. Речь о них шла в главах данной книги, посвященных диспетчерам компоновки. Фрейм содержит небольшое количество атрибутов. Как правило, настройка фрейма сводится к установке цвета фона и обрамления. Для фрейма можно также указать карту отображения цвета (colormap) и визуальный тип (visual type). Подробно карты цветов и визуальные типы будут рассмотрены в главе 41. Компонент labelframe, реализованный в Тк 8.4, представляет собой фрейм, который может отображать компоненты вдоль своего обрамления. Данный компонент может содержать внутреннюю текстовую метку либо автоматически осуществляет позиционирование независимой метки. Компонент toplevel напоминает фрейм, но он создается как окно верхнего уровня. Другими словами, он не обязательно должен размещаться в пределах главного окна приложения. Данный компонент удобно использовать, например, для создания диалоговых окон. Компонент toplevel содержит те же атрибуты, что и фрейм, а также дополнительные атрибуты screen и menu. Атрибут menu используется для создания строки меню, расположенной вдоль верхней границы окна. Поддержка меню была реализована в Tk 8.O. Подробно об особенностях создания меню см. в главе 30. В системе Unix опция screen позволяет отобразить компонент в любом окне X Window. Значение опции screen задается в следующем формате: уз ел: дисплей. номер_ экрана Предположим, например, что на рабочей станции sage работает один X- сервер, который управляет двумя экранами. Эти экраны идентифицируются как sage:0.0 и sage:0.1. Если номер экрана не указан, по умолчанию принимается значение 0. Атрибуты компонентов frame, labelframe и toplevel В табл. 32.1 описаны атрибуты компонентов frame, labelframe и toplevel. Для идентификации атрибутов используются имена ресурсов; слова, составляющие их, начинаются с прописной буквы. При создании компонента или изменении его конфигурации доступ к атрибутам осуществляется с помощью опций. Имя опции совпадает с именем атрибута, за исключением того, что оно начинается с символа - и в нем присутствуют только символы нижнего регистра. Описания ресурсов для атрибутов см. в главе 31. Многие из указанных здесь атрибутов более подробно описываются в главах 40-42. После создания компонента frame, labelframe или toplevel атрибуты class, colormap, visual и screen изменять нельзя. Эти параметры являют-
Глава 32. Простые компоненты Тк 683 ся основными, поэтому, чтобы изменить их, надо удалить фрейм и создать новый. Таблица 32.1. Атрибуты компонентов frame, labelframe и toplevel background Цвет фона (имя атрибута может быть сокращено до bg) borderWidth Дополнительное пространство вокруг границ фрейма class Имя класса ресурсов и связывания colormap В качестве значения указывается new или имя окна container Если данный атрибут имеет значение true, фрейм включает другое приложение cursor Курсор, отображаемый в пределах фрейма font Шрифт, используемый для отображения метки. Только для компонента labelf rame foreground Цвет, используемый для отображения текста метки. Только для компонента labelf rame height Высота, представленная в экранных единицах измерения highlightBackground Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода highlightColor Цвет, используемый для подсветки, когда компонент имеет фокус ввода highlight Thickness Толщина прямоугольника, указывающего на наличие фокуса ввода labelAnchor Расположение встроенной метки. Данный атрибут может принимать следующие значения: nw (принимается по умолчанию), n, ne, en, e, es, se, s, sw, ws, w, wn. Только для компонента labelf rame labelWidget Путь к компоненту, используемому в качестве метки. Переопределяет установки, выполненные с помощью опции -text. Компонент должен существовать. Только для компонента labelframe menu Строка меню. Только для компонента toplevel padX Дополнительное внутреннее пространство слева и справа от содержимого padY Дополнительное внутреннее пространство слева и справа от содержимого relief Допустимые значения: flat, sunken, raised, groove, solid и ridge screen Спецификация X-терминала. (Только для компонента toplevel. Указывать в базе данных ресурсов нельзя.)
684 Часть IV. Компоненты Тк Окончание табл. 32.1 takeFocus Управляет изменением фокуса ввода в результате действий с клавиатурой text Текст встроенной метки. Только для компонента labelframe use Идентификатор окна, возвращаемый командой winfo id. Фрейм или окно верхнего уровня включается в указанное окно visual Допустимые типы: staticgrey, greyscale, staticcolor, pseudocolor, directcolor и truecolor width Ширина, представленная в экранных единицах измерения Использование компонента labelframe Поддержка компонентов labelframe была реализована в Тк 8.4. Данные компоненты во многом похожи на обычные фреймы, но в отличие от них labelframe позволяет отображать текстовую метку вдоль границы. Для этой цели может использоваться специально сформированная внешняя или внутренняя метка, автоматически генерируемая при создании компонента. Следует также отметить, что для labelframe по умолчанию принимается значение 2 атрибута borderWidth и значение groove атрибута relief, в то время как для обычного фрейма атрибут borderWidth по умолчанию имеет значение 0, а атрибут relief — значение flat. Это отличие связано с тем, что компоненты labelframe представляют отдельные области интерфейса, а фреймы чаще всего применяются лишь для группировки других компонентов. В большинстве случаев текстовая метка, которая должна отображаться в левом верхнем углу фрейма, задается с помощью атрибута text. В листинге 32.1 приведен код, посредством которого создается компонент labelframe, содержащий группу переключателей опций. Листинг 32.1. Пример использования компонента labelframe labelframe . s -text Sizes radiobutton .s.small -text Small -variable size -value small radiobutton .s.med -text Medium -variable size -value medium
Глава 32. Простые компоненты Тк 685 radiobutton . s.large -text Large -variable size -value large .s.large select pack .s.small .s.med .s.large -anchor w -padx 2 -pady 1 pack .s Вы можете изменять внешний вид текстовой метки, задавая значения атрибутов font и foreground. Атрибут labelAnchor определяет расположение метки около обрамления компонента. По умолчанию (значение nw) метка размещается вдоль верхней границы в левой ее части. Если вы зададите значение wn, метка будет находиться на левой границе ближе к верхней части окна. Значения атрибута labelanchor приведены в листинге 32.2, а соответствующие варианты расположения метки показаны на соответствующих рисунках. Листинг 32.2. Использование опции -labelanchor для позиционирования текстовой метки По мере необходимости вы можете использовать внешнюю текстовую метку. Она связывается с компонентом labelframe с помощью атрибута labelwidget. Атрибут labelwidget отменяет текущее значение атрибута text. В листинге 32.3 показан фрейм, в котором в качестве метки используется битовая карта. Листинг 32.3. Связывание внешней метки с компонентом labelf rame label .1 -bitmap question .s configure -labelwidget .1 -labelanchor wn Включение других приложений Для включения во фрейм других приложений, а также для включения фрейма в окна других приложений предназначены опции -container и -use. -labelanchor wn -labelanchor s -labelanchor ne
686 Часть IV. Компоненты Тк С помощью атрибута use указывается идентификатор окна, которое должно содержать Tk-фрейм. Программа wish поддерживает опцию аналогичного назначения, -use, задаваемую в командной строке. Если вы хотите включить другое окно в состав фрейма, вам надо воспользоваться атрибутом container. Приведенные ниже команды демонстрируют процесс запуска нового приложения wish и включения его окна в один из фреймов. frame .embed -container 1 -bd 4 -bg red exec wish somescript.tcl -use [winfo id .embed] & Стили окон верхнего уровня В системах Windows и Macintosh поддерживаются различные стили окон верхнего уровня. Стили определяют поведение окон и их внешний вид. В системе Unix внешний вид окон верхнего уровня обычно определяет диспетчер окон, который представляет собой отдельное приложение. Взаимодействие с диспетчером окон описывается в главе 44. В системе Macintosh в состав Тк включается команда unsupported!., которая может использоваться для установки стилей окон. unsupportedl style window style Значениями последнего параметра, задающего стиль, могут быть documentProc, dBoxProc, plainDBox, altDBoxProc, movableDBoxProc, zoomDocProc, rDocProc, floatProc, floatZoomProc, floatSideProc и floatSideZoomProc. Стили dBoxProc, plainDBox и altDBoxProc не предполагают отображения строки заголовка, соответственно в таких окнах отсутствует кнопка, предназначенная для их закрытия. Другие стили задают различные строки заголовка, содержащие кнопку закрытия и элемент масштабирования. По умолчанию применяется стиль documentProc. Продемонстрировать особенности каждого стиля позволяет код, приведенный в листинге 32.4. Листинг 32.4. Стили окон в системе Macintosh set x {documentProc dBoxProc plainDBox altDBoxProc \ movableDBoxProc zoomDocProc rDocProc floatProc \ floatZoomProc floatSideProc floatSideZoomProc} foreach у $x { toplevel .$y label .$y.l -text $y pack .$y.l -padx 40 -pady 20 if [catch {unsupportedl style .$y $y} err] { puts "$y: $err"
Глава 32. Простые компоненты Тк 687 } 2 Возможно, средства для настройки стилей будут реализованы в последующих реализациях Тк в составе команды wm. В системе Windows можно задавать различные стили, используя окна transient и overrideredirect, а также опции команды wm attributes. Их мы рассмотрим в главе 44. Компонент label Компонент label реализует текстовую метку. Его атрибуты позволяют задавать расположение метки на экране. Текст в составе метки доступен только для чтения. Пример команды, используемой для создания метки, приведен ниже. label .version -text "MyApp vl.O" Текст метки может быть либо указан в виде литерала, либо содержаться в Tcl-персменной. В последнем случае метка обновляется при каждом изменении переменной. Переменная, содержащая текст метки, должна находиться в глобальной области видимости. Это правило действует даже в том случае, когда метка создается в составе процедуры, содержащей переменную с таким же именем. set version "MyApp vl.O" label .version -textvariable version Внешний вид метки можно изменять динамически, используя операцию configure. Если вы измените текст или шрифт, которым он должен отображаться, вы обязаны соответствующим образом изменить размеры компонента, вызвав тем самым перекомпоновку интерфейсных элементов. Избежать этого можно, создав метку достаточно больших размеров, так, чтобы в ней помещалась любая из строк, которые вы планируете отобразить. Ширина компонента label задается не в экранных координатах, а в символах. Листинг 32.5. Текстовая метка, предназначенная для отображения различных строк proc FixedWidthLabel { name values } { # name - имя создаваемого компонента # values - список строк set maxWidth О foreach value $values { if {[string length $value] > $maxWidth} {
688 Часть IV. Компоненты Тк set maxWidth [string length $value] } } # Опция -anchor w используется для выравнивания # коротких строк по левому краю label $name -width $maxWidth -anchor w \ -text [lindex $values 0] return $name 2 Процедура FixedWidthLabel создает метку, размеры которой позволяют отображать различные строки. Опция -anchor w указывает на то, что строки, размер которых меньше размера компонента, должны выравниваться по левому краю. Текст метки можно изменить впоследствии, используя операцию configure компонента (имя операции можно указывать сокращенно как conf ig). FixedWidthLabel .status {OK Busy Error} .status config -text Busy Вместо строки текста метка может содержать изображение. Способы включения изображений в состав метки рассматриваются в главе 41. В примере, приведенном в листинге 32.5, можно использовать метрику шрифта, поддержка которой была реализована в Tk 8.O. В этом случае удастся более точно подобрать размеры компонента. Дело в том, что при использовании пропорциональных шрифтов строки, содержащие одинаковое количество символов, могут иметь различную длину. Например, строка из трех букв ООО будет шире, чем строка, состоящая из символов 111. Команда font metrics, позволяющая решить проблему работы с пропорциональными шрифтами, описывается в главе 42. Атрибуты width и wrapLength Если в составе метки отображается текст, значение атрибута width интерпретируется как число символов. Если же метка содержит изображение, представленное в виде битовой карты или в другом формате, то ширина компонента определяется в пикселях или в других единицах, пригодных для измерения размеров экранных объектов. Атрибут wrapLength определяет разбиение текста метки на несколько строк. Значение атрибута wrapLength всегда задается в единицах измерения экранных объектов. Если вам надо вычислить wrapLength, исходя из метрики шрифта, следует использовать команду font metrics. В Tk 4.2 и более ранних версиях необходимо измерять текст, используя текстовый компонент,
Глава 32. Простые компоненты Тк 689 в котором символы представлены с помощью того же шрифта. В главе 36 описаны операции компонента text, которые возвращают информацию о размерах символов.Разрыв строки можно задать, включая в текст метки символ конца строки (\п). Таким образом, можно создавать метки, содержащие несколько строк текста. Атрибуты компонента label В табл. 32.2 описаны атрибуты компонента label. Для идентификации атрибутов использовались имена ресурсов; слова, составляющие их, начинаются с прописной буквы. Атрибуты можно задавать с помощью опций Tcl- команд, используемых для создания компонента или изменения его конфигурации. Имя атрибута совпадает с именем опции, за исключением того, что имя опции начинается с символа - и содержит лишь буквы нижнего регистра. Описания ресурсов для атрибутов см. в главе 31. Многие из указанных здесь атрибутов более подробно описываются в главах 40-42. Таблица 32.2. Атрибуты компонента label act iveBackground act iveForeground anchor background bitmap borderWidth compound cursor disabledForeground font foreground height Цвет фона, отображаемый тогда, когда метка находится в активизированном состоянии (Тк 8.3.2) Цвет, которым отображается текст, когда метка находится в активизированном состоянии (Тк 8.3.2) Относительная позиция метки в пространстве, управляемой диспетчером компоновки Цвет фона. (Имя атрибута может быть сокращено до bg.) Имя битовой карты, отображаемой вместо текстовой строки Дополнительное пространство вокруг метки Расположение изображения или битовой карты относительно текста. Допустимые значения: bottom, center, left, none (принимается по умолчанию), right и top (Tk 8.4) Курсор, находящийся на метке Цвет переднего плана (текста) в тот момент, когда доступ к метке запрещен (Тк 8.3.1) Шрифт для отображения текста метки Цвет переднего плана. (Имя атрибута может быть сокращено до f g.) Высота. Для битовых карт задается в экранных единицах измерения, а для текста -- в строках
690 Часть IV. Компоненты Тк Окончание табл. 32.2 highlightBackground highlightColor highlightThickness image justify padX padY relief state takeFocus text textVariable underline width wrapLength Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода Цвет, используемый для подсветки, когда компонент имеет фокус ввода Толщина прямоугольника, указывающего на наличие фокуса ввода Изображение, которое выводится вместо битовой карты или текста Выравнивание текста. Допустимые значения: left, right и center Дополнительное пространство слева и справа от метки Дополнительное пространство сверху и снизу от метки Допустимые значения: flat, sunken, raised, groove, solid и ridge Допустимые значения: normal (компонент доступен), disabled (компонент не активизирован) и active (Tk 8.3.1) Управляет изменением фокуса ввода при действиях с клавиатурой Текст для отображения Имя Tcl-переменной. Ее значение отображается Индекс символа, который должен отображаться с подчеркиванием Ширина. Для текстовых меток задается в символах Максимальная длина, по достижении которой должен осуществляться перенос текста. Задается в экранных единицах измерения Компонент message Компонент message позволяет отображать длинные последовательности символов, представляя их в виде нескольких строк. Этот компонент был специально создан для использования в диалоговых окнах. Он форматирует текст так, чтобы тот размещался в области, для которой задается ширина или коэффициент сжатия (aspect ratio). Коэффициент сжатия — это отношение ширины области к ее высоте, умноженное на 100. Например, если коэф-
Глава 32. Простые компоненты Тк 691 фициент сжатия равен 150, это означает, что ширина области, содержащей текст, в полтора раза больше, чем ее высота. В листинге 32.6 приведен пример создания компонента message, предназначенного для отображения одной длинной последовательности символов. Символы обратной косой черты используются для размещения текста в нескольких строках. (Требуемую последовательность символов можно также задать в интерактивном режиме при работе сценария.) Заметьте, что символ обратной косой черты, указанный в конце строки, заменяет любую последовательность пробелов и знаков табуляции на один пробел. Листинг 32.6. Компонент message, осуществляющий форматирование длинной последовательности символов message .msg -justify center -text "This is a very long text\ line that will be broken into many lines by the\ message widget" pack .msg Символ перевода строки в составе текста задает отображение последующих символов с новой строки. По необходимости вы можете непосредственно контролировать формат текста, используя символы новой строки и задавая очень большой коэффициент сжатия. В листинге 32.7 для представления текста, состоящего более чем из одной строки, применяется группировка с помощью двойных кавычек. Символ новой строки между кавычками включается в состав последовательности символов и вызывает разрыв строки. Листинг 32.7. Управление форматом текста в составе компонента message message .msg -aspect 1000 -justify left -text \ "This is the first long line of text, and this is the second line." pack .msg Компонент message имеет один существенный недостаток: он не позволяет выделять отображаемый текст. В главе 38 описывается работа с дескриптора-
692 Часть IV. Компоненты Тк ми выделенного текста. Их можно использовать для строк в составе сообщений. Компонент message можно заменить текстовым компонентом, который предоставляет гораздо более богатый набор возможностей. Если выделение текста, работа с различными шрифтами и другие вопросы форматирования имеют большое значение, вместо компонента message следует использовать текстовый компонент. Рассмотрению компонента text посвящена глава 36. Атрибуты компонента message В табл. 32.3 описаны атрибуты компонента message. Для указания атрибутов использовались имена ресурсов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В составе Тс1-команд атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра. Таблица 32.3. Атрибуты компонента message anchor Относительная позиция текста в пространстве, управляемом диспетчером компоновки aspect Частное от деления ширины на высоту, умноженное на 100. По умолчанию используется значение 150 background Цвет фона. (Имя атрибута может быть сокращено до bg.) borderWidth Дополнительное пространство вокруг текста cursor Курсор, находящийся на компоненте font Шрифт для отображения текста foreground Цвет переднего плана. (Имя атрибута может быть сокращено до f g.) highlightBackground Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода highlightColor Цвет, используемый для подсветки, когда компонент имеет фокус ввода highlightThickness Толщина прямоугольника, указывающего на наличие фокуса ввода justify Выравнивание текста. Допустимые значения: left, right и center padX Дополнительное пространство слева и справа от текста padY Дополнительное пространство сверху и снизу от текста relief Допустимые значения: flat, sunken, raised, groove, solid и ridge
Глава 32. Простые компоненты Тк 693 Окончание табл. 32.3 takeFocus Управляет изменением фокуса ввода при действиях с клавиатурой text Текст для отображения textVariable Имя Tcl-переменной. Ее значение отображается width Ширина в экранных единицах измерения Выравнивание текста, содержащегося в компонентах label и message Компоненты label и message содержат атрибуты, определяющие расположение содержащегося в них текста. Позиционирование текста в составе компонентов осуществляется приблизительно так же, как и размещение компонентов во фрейме, выполняемое с помощью диспетчера компоновки. Для выравнивания текста в компонентах label и message предусмотрены следующие атрибуты: padX, padY, anchor и borderWidth. Атрибут anchor действует, если размеры компонента больше, чем необходимо для отображения текста. Это происходит тогда, когда вы явно задаете ширину компонента с помощью опции -width, либо если диспетчером компоновки предусмотрено заполнение свободной области. В главе 40 вопросы размещения текста внутри компонента будут рассмотрены более подробно. Компонент scale Компонент scale реализует линейный, или ползунковый, регулятор. Рядом с направляющими, по которым перемещается ползунок, отображается набор числовых значений. Текущим значением является то, на которое указывает ползунок. С данным компонентом может быть связана текстовая метка; кроме того, он позволяет отображать текущее значение рядом с ползунком. Значение регулятора можно использовать тремя способами. • Непосредственно читать и устанавливать значение с помощью соответствующих команд компонента. • Связать компонент с Tcl-переменной. При этом значение переменной будет соответствовать текущему значению регулятора, а изменение значения переменной приведет к изменению позиции ползунка регулятора. • Зарегистрировать Tcl-команду, которая будет выполняться при изменении состояния компонента. При этом вы задаете начало вызова команды, а реализация компонента добавит текущее значение в качестве дополнительного параметра.
694 Часть IV. Компоненты Тк Листинг 32.8. Использование компонента scale scale .scale -from -10 -to 20 -length 200 -variable x \ -orient horizontal -label "The value of X" \ -tickinterval 5 -showvalue true pack .scale В листинге 32.8 показан пример использования компонента scale для изменения значения переменной в интервале от —10 до +20. Переменная х определена в глобальной области видимости. Опция -tickinterval задает интервал отображения значений в нижней части компонента, а опция -showvalue указывает на необходимость отображать текущее значение регулятора. Длина компонента задается в единицах измерения экранных объектов (например, в пикселях). Связывания для компонента scale В табл. 32.4 описаны связывания для компонента scale. Для того чтобы после нажатия клавиш со стрелками состояние регулятора изменялось, он должен иметь фокус ввода. Таблица 32.4. Связывания для компонента scale <Button-l> Щелчок на области справа или слева от ползунка перемещает ползунок на одно деление по направлению к курсору мыши <Control-Button-l> Щелчок на области справа или слева от ползунка при нажатой клавише <Ctrl> перемещает ползунок в крайнюю позицию по направлению к курсору мыши <Lef t> <Up> Перемещает ползунок на одно деление влево (вверх) <Control-Lef t> Перемещает ползунок влево (вверх) на величину, задавае- <Control-Up> мую атрибутом biglncrement <Right> <Down> Перемещает ползунок на одно деление вправо (вниз) <Control-Right> Перемещает ползунок вправо (вниз) на величину, задавае- <Control-Down> мую атрибутом biglncrement <Home> Перемещает ползунок в крайнюю левую (верхнюю) позицию <End> Перемещает ползунок в крайнюю правую (нижнюю) позицию
Глава 32. Простые компоненты Тк 695 Атрибуты компонента scale В табл. 32.5 приведены сведения об атрибутах компонента scale. В таблице указаны имена ресурсов для атрибутов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В составе Tcl- команд атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра. Таблица 32.5. Атрибуты компонента scale activeBackground Цвет фона, отображаемый тогда, когда курсор мыши находится в пределах области, занимаемой регулятором background Цвет фона. (В составе команд имя атрибута может быть сокращено до bg.) biglncrement Грубая настройка регулятора borderWidth Дополнительное пространство вокруг компонента command Команда, вызываемая при изменении значения компонента. При вызове команды текущее значение добавляется в качестве дополнительного параметра cursor Курсор, отображаемый в то время, когда он находится на компоненте digits Число значащих цифр в значении регулятора from Минимальное значение, соответствующее левому или верхнему положению ползунка регулятора font Шрифт для отображения метки foreground Цвет переднего плана. (Имя атрибута может быть сокращено до fg.) highlight Background Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода highlight Color Цвет, используемый для подсветки, когда компонент имеет фокус ввода highlightThickness Толщина прямоугольника, указывающего на наличие фокуса ввода label Строка, отображаемая в составе регулятора length Длина шкалы, представленная в экранных единицах измерения orient Допустимые значения: horizontal и vertical relief Допустимые значения: flat, sunken, raised, groove, solid и ridge
696 Часть IV. Компоненты Тк Окончание табл. 32.5 repeatDelay repeatInterval resolution showValue sliderLength sliderRelief state takeFocus ticklnterval to troughColor variable width Задержка перед началом автоповтора. Автоповтор осуществляется тогда, когда пользователь удерживает левую кнопку мыши на области справа или слева от ползунка Интервал времени между последовательными событиями автоповтора Значение компонента округляется так, чтобы оно было кратным значению данного атрибута Если значение данного атрибута равно true, значение компонента отображается рядом с ползунком Длина ползунка, представленная в экранных единицах измерения Рельеф ползунка Допустимые значения: normal, active и disabled Управляет изменением фокуса ввода при действиях с клавиатурой Расстояние между отметками. Если значение данного атрибута равно нулю, отметки не отображаются Максимальное значение, соответствующее правому или нижнему положению ползунка регулятора Цвет области, в пределах которой перемещается ползунок Имя Tcl-переменной. Изменения состояния компонента приводят к изменению значения переменной, а изменение значения переменной, в свою очередь, отражается на состоянии компонента Ширина области, по которой движется ползунок Программирование линейных регуляторов Операции, определенные для компонента scale, в основном используются в командах, связанных с событиями, поэтому разработчикам редко приходится заниматься программированием этого компонента. В табл. 32.6 описаны операции, поддерживаемые scale. Здесь компонент scale обозначается как $w. Таблица 32.6. Операции с линейным регулятором $w cget опция $w configure . Возвращает значение конфигурационной опции Запрашивает или изменяет конфигурацию компонента
Глава 32. Простые компоненты Тк 697 Окончание табл. 32.6 $w coords Возвращает координаты ползунка, которые соответствуют ?значение? указанному значению или текущему значению компонента $w get ?х у? Возвращает значение линейного регулятора, соответствующее указанной позиции ползунка $w identify x у В зависимости от того, какому элементу линейного регулятора соответствует указанная позиция, возвращает значение troughl, slider или trough2 $w set значение Устанавливает значение линейного регулятора Команда bell Команда bell инициирует звуковой сигнал. Сигнал связывается с конкретным дисплеем. Если даже программа выполняется на удаленной машине, звук будет воспроизведен на том устройстве, с которым работает пользователь. Если ваше приложение поддерживает окна или несколько дисплеев, вы можете направлять звуковой сигнал на конкретный дисплей или в конкретное окно. Для этой цели используется опция -displayof. Команда bell вызывается следующим образом: bell ?-displayof окно? ?-nice? В системе Unix имеется программа xset, которая управляет длительностью, тоном и громкостью звукового сигнала. Громкость задается в процентах от максимального значения, например 50. Многие устройства позволяют управлять лишь длительностью сигнала. Тон и громкость остаются фиксированными. Порядок указания параметров xset, которые определяют характеристики звукового сигнала, приведен ниже. exec xset b ?громкость? ?тон? ?время? Параметр b устанавливает параметры звукового сигнала по умолчанию. Чтобы отключить звуковой сигнал, надо указать параметр -Ь; для этой же цели можно использовать параметры on и off. exec xset -b exec xset b ?on? ?off? Команда bell вызывает побочные действия. В большинстве систем она отключает заставку, отображая содержимое экрана. В Тк 8.4 была предусмотрена опция -nice, которая отменяет воздействие звукового сигнала на экранную заставку.
Глава 33 Полосы прокрутки В данной главе описываются полосы прокрутки. Компоненты данного типа поддерживают протокол, посредством которого они взаимодействуют с другими компонентами. 1 1ОЛОСЫ прокрутки осуществляют управление другими компонентами посредством стандартного протокола, основанного на использовании Тс1-ко- манд. С помощью Tcl-команд полоса прокрутки запрашивает связанный с ней компонент о том, какая часть его содержимого отображается на экране. В свою очередь, компонент, допускающий прокрутку, использует ТЫ-коман- ды для того, чтобы сообщить полосе прокрутки, какая часть его содержимого является видимой. Работать с полосами прокрутки могут компоненты entry, listbox, text и canvas. Протокол достаточно универсален для того, чтобы обеспечить работу новых компонентов или их наборов. В данной главе описывается протокол взаимодействия полос прокрутки и управляемых ими компонентов. Однако следует заметить, что знать детали работы полос прокрутки не обязательно. Достаточно иметь общее представление о том, как осуществить их настройку. Если вы сделаете это, компоненты автоматически будут выполнять требуемые действия. Использование полос прокрутки Приведенная ниже команда создает текстовый компонент и две полосы прокрутки, которые позволяют прокручивать содержимое компонента как по горизонтали, так и по вертикали. scrollbar .yscroll -command {.text yview} -orient vertical scrollbar .xscroll -command {.text xview} -orient horizontal text .text -yscrollcommand {.yscroll set} \ -xscrollcommand {.xscroll set}
Глава 33. Полосы прокрутки 699 Операция полосы прокрутки set выполняется по инициативе другого компонента. Происходит это при изменении отображаемой в нем информации. Компонент, связанный с полосой прокрутки, содержит операции xview и yview. которые полоса прокрутки вызывает тогда, когда пользователь выполняет некоторые действия. При вызове этих операций указываются параметры, которые будут рассмотрены позже. В большинстве случаев детали работы протокола взаимодействия можно не учитывать. Достаточно правильно связать полосу прокрутки с компонентом. В листинге 33.1 определена процедура Scrolled_Text, которая создает текстовый компонент и связывает его с двумя полосами прокрутки. В процессе выполнения кода, приведенного в листинге, происходит чтение одного из файлов, который затем отображается в текстовом компоненте. Размеры компонента недостаточны для того, чтобы сразу вывести все содержимое файла, и полосы прокрутки показывают, какая часть текста отображается на экране. Подробно текстовые компоненты рассматриваются в главе 36. Листинг 33.1. Текстовый компонент и две полосы прокрутки proc ScrollecLText { f args } { frame $f eval {text $f.text -wrap none \ -xscrollcommand [list $f.xscroll set] \ -yscrollcommand [list $f.yscroll set]} $args scrollbar $f.xscroll -orient horizontal \ -command [list $f.text xview] scrollbar $f.yscroll -orient vertical \ -command [list $f.text yview] grid $f.text $f.yscroll -sticky news grid $f.xscroll -sticky news grid rowconfigure $f 0 -weight 1 grid columnconfigure $f 0 -weight 1 return $f.text }
700 Часть IV. Компоненты Тк set t [Scrolled.Text .f -width 40 -height 8 \ -font {courier 12}] pack .f -side top -fill both -expand true set in [open [file join $tk_library demos colors.tcl]] $t insert end [read $in] close $in Значение опций -command и -xscrollcommand формируются с помощью команды list. В данном примере можно было бы указать двойные кавычки, однако для создания значений, которые впоследствии могут использоваться как Tcl-команды, рекомендуется всегда применять команду list. В листинге 33.1 для передачи текстовому компоненту дополнительных данных применяется параметр args. Вопросы использования eval и args были рассмотрены в листинге 10.3. Для размещения в окне полос прокрутки и текстового компонента используется диспетчер компоновки grid. Подробно об этом диспетчере компоновки см. в главе 26. Протокол взаимодействия с полосами прокрутки Когда пользователь изменяет состояние полосы прокрутки, она вызывает зарегистрированные команды и передает с помощью дополнительных параметров сведения о том, какие действия были выполнены пользователем. Компонент, связанный с полосой прокрутки, обрабатывает команду (например, выполняет операцию xview), изменяя тем самым отображаемую в нем информацию. После того как внешний вид компонента изменился, он обращается к полосе прокрутки с помощью команд, зарегистрированных посредством опций -xscrollcommand и -yscrollcommand (например, вызывает операцию set). При вызове команды ей передаются параметры, указывающие на новые размеры и позицию отображаемых данных. Полоса прокрутки изменяет свое состояние в соответствии с полученной информацией. Описанный протокол допускает взаимодействие только с теми компонентами, которые могут самостоятельно изменять свой внешний вид. Такое изменение происходит, например, при добавлении новой информации. Компоненты, допускающие прокрутку, также поддерживают событие <B2-Motion> (перетаскивание курсора с нажатой средней кнопкой), которое вызывает прокрутку содержимого компонента. При возникновении событий, влияющих на внешний вид компонента, компонент вызывает команды обновления полосы прокрутки.
Глава 33. Полосы прокрутки 701 Операция set полосы прокрутки При вызове операции set полосы прокрутки задаются два значения с плавающей точкой, каждое из которых может изменяться в интервале от 0 до 1. Эти значения определяют относительную позицию верхней и нижней (левой и правой) границы содержимого компонента, отображаемого на экране. Компонент, связанный с полосой прокрутки, указывает эти значения посредством yscrollcommand или xscrollcommand. Предположим, например, что текстовый компонент отображает четверть своего содержимого. Тогда обращение к операции set будет иметь следующий вид: .yscroll set 0.0 0.25 Если при вызове операции set указаны значения 0.0 и 1.0, то все содержимое компонента отображается на экране и полоса прокрутки не нужна. Вы можете отслеживать эту ситуацию, используя вместо непосредственного обращения к операции set процедуру Scroll_Set. Данная процедура, перед тем как отобразить полосу прокрутки с помощью диспетчера компоновки, проверяет, нужна ли она. Следует помнить, что при удалении полосы прокрутки размеры компонента изменяются и при этом снова может понадобиться полоса прокрутки. В результате существует опасность возникновения бесконечного цикла. Листинг 33.2. Процедура Scroll_Set, используемая для управления полосой прокрутки proc Scroll_Set {scrollbar geoCmd offset size} { if {$offset != 0.0 II $size != 1.0} { eval $geoCmd ;# Make sure it is visible } $scrollbar set $offset $size 2 В качестве параметра процедуре Scroll_Set передается диспетчер компоновки, с помощью которого полоса прокрутки отображается на экране. В листинге 33.3 показан пример использования Scroll_Set с компонентом list box. Заметьте, что диспетчер компоновки непосредственно не вызывается. Вместо этого процедуре Scroll_Set предоставляется возможность отобразить полосу прокрутки тогда, когда это станет необходимо. Листинг 33.3. Компонент listbox, отображающий в случае необходимости полосы прокрутки proc ScrollecLListbox { f args } { frame $f
702 Часть IV. Компоненты Тк listbox $f.list \ -xscrollcommand [list Scroll_Set $f.xscroll \ [list grid $f.xscroll -row 1 -column 0 -sticky we]] \ -yscrollcommand [list Scroll_Set $f.yscroll \ [list grid $f.yscroll -row 0 -column 1 -sticky ns]] eval {$f.list configure} $args scrollbar $f.xscroll -orient horizontal \ -command [list $f.list xview] scrollbar $f.yscroll -orient vertical \ -command [list $f.list yview] grid $f.list -sticky news grid rowconfigure $f 0 -weight 1 grid columnconfigure $f 0 -weight 1 return $f.list } set 1 [Scrolled_Listbox .f -listvariable fonts] pack .f -expand yes -fill both set fonts [lsort -dictionary [font families]] Процедуре Scrolled_Listbox передаются необязательные параметры для управления компонентом listbox. Настройка listbox в соответствии с полученными параметрами осуществляется посредством команды eval. Об особенностях использования eval см. в главе 10. Пример связывания двух компонентов listbox с одной полосой прокрутки рассматривается в главе 46. Операции xview и yview Операции xview и yview вызываются полосами прокрутки. Во всех компонентах, допускающих прокрутку, они выполняют одни и те же действия. Данные операции предназначены не только для организации совместной работы с полосами прокрутки. Их можно применять в любом случае, когда
Глава 33. Полосы прокрутки 703 необходимо изменить отображение содержимого компонента. Далее в примерах используется компонент .text. В результате выполнения операций xview и yview возвращаются значения, определяющие относительное положение начала и конца той части содержимого компонента, которая отображается на экране. Эти значения могут быть переданы команде set полосы прокрутки. .text yview => 0.2 0.55 Когда пользователь щелкает мышью на стрелке, расположенной на одном из концов полосы прокрутки, полоса прокрутки добавляет к команде выражение scroll n units, где п — положительное число при прокрутке вниз и отрицательное — при прокрутке вверх. Ниже приведена команда, демонстрирующая прокрутку на одну строку вверх. .text yview scroll -1 units После щелчка выше или ниже ползунка полоса прокрутки добавляет к команде scroll n pages. Следующая команда демонстрирует прокрутку на одну страницу вниз: .text yview scroll 1 pages Содержимое компонента можно позиционировать так, что оно будет отображаться с некоторым смещением вниз или вправо. Смещение задается с помощью числа с плавающей точкой, значение которого лежит в пределах от нуля до единицы. Например, следующая команда отображает начало данных: .text yview moveto 0.0 Если смещение равно 1.0, отображаться будет последняя часть содержимого компонента. В компонентах Тк окончание их содержимого всегда выравнивается по нижней (или по правой) границе, за исключением тех случаев, когда размеры компонента больше, чем необходимо для отображения содержащихся в нем данных. Так, для того, чтобы отобразить окончание длинной строки в поле редактирования, надо использовать следующую команду: .entry xview moveto 1.0 Компонент scrollbar В Tk 8.0 для систем Macintosh и Windows реализованы платформенно- ориентированные полосы прокрутки. Несмотря на то что полосы прокрутки и другие компоненты используются одинаково во всех системах, интерпретация атрибутов и особенности связывания изменяются в зависимости от
704 Часть IV. Компоненты Тк платформы. В данном разделе описываются полосы прокрутки для системы Unix. Атрибуты и связывания, установленные по умолчанию, подходят для всех платформ, поэтому при их использовании различия между платформами не очень важны. Полоса прокрутки состоит из пяти компонентов: arrowl, troughl, slider, trough2 и arrow2. На каждом конце данного компонента расположены стрелки; они представляются с помощью элементов arrowl и arrow2. Для горизонтальной полосы прокрутки стрелка arrowl расположена слева, а для вертикальной полосы прокрутки — в верхней ее части. Ползунок представляет относительную позицию данных, которые отображаются в составе компонента, связанного с полосой прокрутки, а размер ползунка соответствует объему отображаемых данных относительно общего объема информации, содержащейся в компоненте. Элементы troughl и trough2 соответствуют областям между ползунком и стрелками. Если ползунок занимает всю полосу прокрутки, это означает, что вся информация, содержащаяся в компоненте, отображается на экране. Связывания для полос прокрутки В табл. 33.1 описаны связывания, которые устанавливаются по умолчанию для полос прокрутки в системе Unix. Для левой и средней кнопок мыши создаются одинаковые связи. Для того чтобы после нажатия клавиш со стрелками состояние полосы прокрутки изменялось, ей надо передать фокус ввода. Таблица 33.1. Связывания для компонента scrollbar <Button-l> <Button-2> После щелчка на стрелке происходит прокрутка на одну единицу представления содержимого компонента. Щелчок на области между ползунком и стрелкой приводит к отображению следующей страницы данных (страницей считается объем информации, помещающийся в окне компонента) <Bl-Motion> <B2-Motion> При перетаскивании ползунка происходит динамическая прокрутка <Control-Button-l> Щелчок на стрелке или на области между ползун- <Control-Button-2> ком и стрелкой вызывает переход к началу (в конец) содержимого компонента <Up> <Down> Прокрутка вверх (вниз) на одну единицу представления содержимого компонента <Control-Up> Прокрутка вверх (вниз) на одну страницу <Control-Down>
Глава 33. Полосы прокрутки 705 Окончание табл. 33.1 <Left> <Right> Прокрутка влево (вправо) на одну единицу представления содержимого компонента <Control-Lef t> Прокрутка влево (вправо) на одну страницу <Control-Right> <Prior> <Next> Прокрутка вперед (назад) на одну страницу <Ноте> Переход к левой (верхней) части содержимого <End> Переход к правой (нижней) части содержимого Атрибуты компонента scrollbar В табл. 33.2 описаны атрибуты полос прокрутки. Для атрибутов в таблице указаны имена ресурсов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В составе Tcl-команд атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра. Для полос прокрутки отсутствует атрибут length. При разработке данного компонента предполагалось, что при его компоновке будет использоваться опция заполнения, в результате чего на экране изображение полосы прокрутки будет расширяться требуемым образом. Поэтому задать можно только рельеф активизированного элемента. Для ползунка, стрелок и обрамления можно также указать цвет фона. При помещении курсора мыши на ползунок или стрелку элемент отображается с использованием цвета activeBackground. При отображении областей, расположенных между стрелками и ползунком, всегда используется цвет troughColor. Таблица 33.2. Атрибуты компонента scrollbar activeBackground Цвет фона, отображаемый в тот момент, когда курсор мыши находится на ползунке или на стрелке activeRelief Рельеф ползунка или стрелок в тот момент, когда курсор мыши находится на них background Цвет фона. (Имя атрибута может быть сокращено до bg.) borderWidth Дополнительное пространство вокруг границ компонента command Префикс команды, вызываемой при изменении состояния полосы прокрутки. Обычно роль команды выполняет операция xview или yview cursor Курсор, отображаемый в пределах компонента elementBorderWidth Толщина обрамления для стрелок и ползунка
706 Часть IV. Компоненты Тк Окончание табл. 33.2 highlight Background Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода highlightColor Цвет, используемый для подсветки, когда компонент имеет фокус ввода highlightThickness Толщина прямоугольника, указывающего на наличие фокуса ввода elementBorderWidth Толщина обрамления с имитацией трехмерных эффектов для стрелок и ползунка jump Если значение данного атрибута равно true, перетаскивание ползунка не вызывает динамической прокрутки. Вместо этого содержимое компонента остается неизменным до установки конечной позиции orient Ориентация: horizontal или vertical repeatDelay Задержка, представленная в миллисекундах до начала операции автоповтора. Автоповтор генерируется тогда, когда пользователь удерживает левую кнопку мыши (событие <Button-l>) на одной из стрелок repeat Interval Интервал в миллисекундах между последовательными событиями автоповтора troughColor Цвет области, в пределах которой перемещается ползунок width Толщина полосы прокрутки (размер меньшей стороны ограничивающего прямоугольника) Программирование полос прокрутки Операции с полосами прокрутки обычно используются командами, которые входят в состав связей, установленных по умолчанию. В табл. 33.3 описаны операции, поддерживаемые компонентом scrollbar. Здесь компонент scrollbar обозначается как $w. Таблица 33.3. Операции с компонентом scrollbar $w activate ?элемент? $w cget опция $w configure . . Предоставляет сведения об активном элементе или устанавливает активный элемент. В качестве активного элемента может быть указан arrow1, arrow2 или slider Возвращает значение конфигурационной опции Запрашивает или изменяет конфигурацию компонента
Глава 33. Полосы прокрутки 707 Окончание табл. 33.3 $w delta dx dy $w fraction x у $s get $w identify x у $w set первая„позиция последняя_позиция Возвращает изменение первого параметра команды set, необходимое для перемещения ползунка на величину dx или dy Возвращает значение в интервале от 0 до 1, которое определяет относительное расположение указанной точки в области, по которой перемещается ползунок Возвращает параметры операции set В зависимости от того, какому элементу принадлежит указанная точка, возвращает arrow 1, troughl, slider, trough2 или arrow2 Устанавливает параметры полосы прокрутки. Первый параметр определяет относительную позицию информации, отображаемой в верхней (левой) части окна, второй параметр определяет относительную позицию данных, которые выводятся в нижней (правой) части окна
Глава 34 Поля редактирования и инкрементные регуляторы Компонент entry предоставляет возможность вводить и редактировать одну строку текста. Эта строка может быть связана с Tcl-переменной. Инкрементный регулятор представляет собой модифицированный вариант поля редактирования и предоставляет возможность пользователю перебирать значения из фиксированного набора. 1 1оле редактирования представляет собой специализированный текстовый компонент, отображающий одну строку текста и позволяющий изменять ее. Данный компонент поддерживает подмножество функций более универсального текстового компонента, который будет рассмотрен в главе 36. Поля редактирования часто используются в составе диалоговых окон. С полем редактирования можно связать Tcl-переменную; это очень удобно при разработке прикладных программ. При этом в поле редактирования отображается значение переменной, а при редактировании содержимого компонента соответствующим образом изменяется Тс1-переменная. Компонент spinbox, реализованный в Тк 8.4, представляет собой модифицированное поле редактирования. В его составе отображаются стрелки, направленные вверх и вниз. С их помощью пользователь может выбирать данные из фиксированного набора, задавая, например, дату или время. Часто компонент spinbox также позволяет пользователю вводить произвольный текст, однако его можно настроить так, чтобы пользователь мог выбирать только допустимые значения.
Глава 34. Поля редактирования и инкрементные регуляторы 709 Использование полей редактирования Компонент entry позволяет редактировать текст и поддерживает функции прокрутки и выделения. Этим он существенно отличается от более простых компонентов label и message. Установки, принимаемые по умолчанию, обеспечивают нормальную работу с полем редактирования, и часто разработчику не приходится дополнительно настраивать данный компонент. Щелкнув в поле левой кнопкой мыши, пользователь задает точку ввода, после чего он может вводить текст. Выделение текста осуществляется путем перетаскивания курсора мыши при нажатой левой кнопке. Для прокрутки содержимого компонента используется перетаскивание курсора при нажатой средней кнопке. Очень часто с полем редактирования связываются текстовая метка и команда, которая выполняется при нажатии пользователем клавиши <Return> (при этом поле редактирования должно иметь фокус ввода). Для размещения нескольких полей редактирования и связанных с ними текстовых меток лучше всего подходит диспетчер компоновки grid. Пример связывания полей редактирования с переменными и командами, а также отображения их с помощью диспетчера компоновки grid приведен в листинге 34.1. Листинг 34.1. Связывание поля редактирования с переменными и командами foreach {field label} {name Name addressl Address address2 {} phone Phone} { label .l$field -text $label -anchor w entry .e$field -textvariable address($field) -relief sunken grid .l$field .e$field -sticky news bind .e$field <Return> UpdateAddress } В данном примере создаются четыре поля редактирования, которые связываются с переменными с помощью атрибута textvariable. В качестве переменных используются элементы массива address. Опция -relief sunken выделяет компоненты entry среди остальных элементов. Вопросы определения рельефа для полей редактирования подробно рассматриваются в главе 40. С клавишей <Return> связывается Tcl-команда UpdateAddress. Процедура UpdateAddress здесь на показана. Она получает текущие значения компонентов entry посредством глобального массива address.
710 Часть IV. Компоненты Тк Проверка содержимого полей редактирования Начиная с Тк 8.3 в распоряжение разработчиков предоставлены новые средства проверки, которые помогают защитить программу от ввода некорректных данных в полях редактирования. Атрибут validate определяет, когда должна производиться проверка. Значение попе (оно принимается по умолчанию) запрещает проверку. Кроме того, данный атрибут может иметь значения f ocusin (получение фокуса ввода), focusout (потеря фокуса ввода), focus (получение или потеря фокуса ввода), key (нажатие любой клавиши) и all. Для того чтобы проверка могла быть выполнена, надо также установить значение атрибута validateCommand (опция -vcmd). В качестве значения атрибута указывается Tcl-сценарий, выполняемый при проверке. Если сценарий возвращает значение true, предлагаемые изменения содержимого компонента принимаются, если же сценарий возвращает false, изменения отвергаются и текст остается в прежнем виде. По мере необходимости вы также можете указать сценарий в качестве значения атрибута invalidCommand. Этот сценарий будет выполняться в случае, если сценарий проверки вернет значение false. Подобно командам, связанным с событиями, сценарий проверки, заданный с помощью атрибута validateCommand, может содержать ключевые слова, начинающиеся с символа % Подстановка ключевых слов осуществляется в тот момент, когда принимается решение о начале проверки, т.е. перед выполнением сценария. Правила подстановки ключевых слов описаны в табл. 34.1. Таблица 34.1. Подстановка ключевых слов в сценариях проверки для полей редактирования и инкрементных регуляторов °/0d Тип действия, при котором инициируется проверка: 1 — вставка; 0 — удаление; — 1 — изменение фокуса ввода e/,i Индекс строки для включения или удаления; если индекс не используется, значение равно --1 УоР Значение компонента, полученное в результате изменений yos Текущее значение компонента перед внесением предлагаемых изменений °/0v Установленный в данный момент тип проверки (текущее значение атрибута validate) °/0V Тип проверки, вызвавшей процедуру обратного вызова: key, focus in, focusout, forced °/eW Имя компонента, для которого выполняется проверка
Глава 34. Поля редактирования и инкрементные регуляторы 711 proc Validlnt {val} { return [ expr {[string is integer $val] II [string match {[-+]} $val]} ] } entry .e -validate all -vcmd {Validlnt °/0P} pack .e Возникновение ошибки в процессе проверки приводит к отключению проверки. Если при выполнении проверки возникает ошибка, атрибуту validate присваивается значение попе, что запрещает дальнейшую проверку содержимого компонента. Проверка также запрещается, если процедура обратного вызова возвращает значение, не являющееся логическим. Таким образом, при создании приложения необходимо следить за тем, чтобы процедура проверки могла вернуть только значение true или false и при ее выполнении не возникали ошибки. Используя опцию -textvariable и задавая режим проверки, необходимо соблюдать осторожность. Применение опции -textvariable для чтения содержимого ноля редактирования никогда не приводит к возникновению проблем. Однако если вы попытаетесь изменить содержимое компонента entry, изменяя значение связанной с ней переменной, результаты могут быть неожиданными для вас. Если значение переменной, связанной посредством опции -textvariable, не соответствует требованиям сценария проверки (т.е. если этот сценарий должен вернуть значение false), Tk разрешает внесение изменений, но запрещает дальнейшую проверку, устанавливая значение попе атрибута validate. Таким образом, если проверка разрешена, переменные, связываемые с помощью опции -textvariable, лучше всего использовать лишь для чтения содержимого компонента. В листинге 34.2 приведен пример проверки, в результате применения которой пользователю разрешается вводить в поле редактирования только целочисленные значения. Листинг 34.2. Ограничение возможностей пользователя, работающего с полем редактирования, вводом целочисленных значений
712 Часть IV. Компоненты Тк Изменение содержимого компонента в процессе проверки запрещает дальнейшую проверку. Если при выполнении сценария проверки или сценария, заданного с помощью атрибута invalidCommand, содержимое поля редактирования будет изменено, устанавливается значение попе атрибута validate, что запрещает дальнейшую проверку. Дело в том, что внесение изменений вызывает новую проверку, что может привести к возникновению бесконечного цикла. Для большинства приложений это ограничение нельзя назвать серьезным. Во многих случаях бывает достаточно запретить нежелательные изменения, возвращая в результате выполнения сценария проверки значение false. Некоторые сложные схемы проверки предполагают редактирование текста, содержащегося в компоненте. Если вам надо изменить содержимое поля редактирования из сценария, задаваемого посредством validateCommand или invalidCommand, необходимо спланировать задачу, которая выполнялась бы в период бездействия; при выполнении этой задачи должно восстанавливаться предыдущее значение атрибута validate. В примере, приведенном в листинге 34.3, сценарий проверки следит за тем, чтобы все буквы, содержащиеся в поле редактирования, принадлежали к верхнему регистру. Для этого он преобразует в верхний регистр все вводимые символы. Поскольку в данном примере осуществляется непосредственное редактирование содержимого компонента, приходится восстанавливать значение атрибута validation в период бездействия. Листинг 34.3. Восстановление значения атрибута validation proc Upper {w validation action new} { if {$action == 1} { $w insert insert [string toupper $new] after idle [list $w configure -validate $validation] } return 1 } entry .e -validate all -vcmd {Upper °/0W °/0v °/0d °/0S} pack . e Советы по использованию полей редактирования Если в компоненте entry содержится длинная строка, вы можете отобразить ее окончание с помощью приведенной ниже команды. В данном примере передаваемые параметры указывают на то, что вся строка должна быть
Глава 34. Поля редактирования и инкрементные регуляторы 713 сдвинута влево, однако при реализации компонента возможности прокрутки ограничены, поэтому последние символы строки будут видны на экране. Sentry xview moveto 1.0 Атрибут show удобно применять для организации ввода паролей с помощью полей редактирования. Символ, заданный в качестве значения show, будет отображаться вместо реальных данных. Sentry config -show * Атрибут state определяет, может ли изменяться содержимое поля редактирования. Установив значение disabled данного атрибута, вы запретите модификацию данных в поле редактирования, а установив значение state, равным normal, вы разрешите вносить изменения. Sentry config -state disabled ;# только для чтения Sentry config -state normal ;# возможно редактирование В Tcl 8.4 была добавлена поддержка нового значения атрибута state. Если установлено значение readonly этого атрибута, то, как и в случае значения disabled, содержимое компонента entry не может быть отредактировано. Однако в такой ситуации пользователь получает возможность выделять текст, содержащийся в компоненте, и копировать его. При работе с компонентом spinbox пользователь также может выполнять действия с кнопками со стрелками, изменяя содержимое компонента (значение disabled атрибута state запрещает это). Средняя кнопка мыши (<Button-2>) может использоваться двумя способами. Если вы щелкнете этой кнопкой, выделенный текст будет вставлен в той точке, в которой расположен текстовый курсор. При этом расположение курсора мыши в момент щелчка не имеет значения. Если вы нажмете среднюю кнопку и будете удерживать ее, то, перемещая мышь влево или вправо, сможете прокручивать содержимое компонента. Использование компонента spinbox Инкрементный регулятор создан на базе поля редактирования, поэтому все опции и операции, определенные для компонента entry, доступны также и при работе с компонентом spinbox. Кроме того, разработчик может также использовать дополнительные опции и операции, специфические для spinbox. В дополнение к средствам, предоставляемым компонентом entry, инкрементный регулятор содержит кнопки со стрелками, направленными вверх и вниз. Эти кнопки позволяют пользователю перебирать значения из фиксированного набора. В обычных условиях компонент spinbox также позволяет
714 Часть IV. Компоненты Тк пользователю вводить произвольный текст, однако его можно настроить так, чтобы пользователь мог выбирать только допустимые значения. Задать диапазон значений для инкрементного регулятора можно двумя способами. Вы можете указать минимальное и максимальное значение, а также приращение, используя для этого атрибуты from, to и increment. Каждый раз, когда пользователь щелкает на кнопке со стрелкой, направленной вверх или вниз, инкрементный регулятор изменяет отображаемое значение на величину приращения. В примере, приведенном в листинге 34.4, задается диапазон значений от —10 до 10, а по умолчанию принимается приращение, равное 1. Листинг 34.4. Простой инкрементный регулятор spinbox .si -from -10 -to 10 pack .si Если задать минимальное, максимальное значение и приращение как числа с плавающей точкой, может понадобиться указать формат отображаемых данных. Сделать это можно, задав значение атрибута format в виде Уо<™сло_зяаков>.<число_знаков>f, подобно тому, как это задается для команды format. (Заметьте, что остальные соглашения команды format здесь неприменимы.) Пример использования атрибута format показан в листинге 34.5. Листинг 34.5. Форматирование числовых значений в компоненте spinbox spinbox .s2 -from -10 -to 10 -increment .25 -format °/04.2f pack .s2 Задавать значения инкрементного регулятора можно, перечислив их. Для этого надо присвоить атрибуту values список значений. Если задано значение атрибута values, атрибуты from, to и increment не принимаются во внимание. Для управления перебором значений используется также атрибут wrap. Если значение этого атрибута равно true, то за последним значением инкрементного регулятора следует первое и наоборот. По умолчанию для атрибута wrap устанавливается значение false. В этом случае при достижении последнего или первого значения перебор прекращается. Атрибут wrap можно
Глава 34. Поля редактирования и инкрементные регуляторы 715 использовать как в случае работы с набором предопределенных значений (атрибут values), так и при установке диапазона значений (атрибуты from и to). В листинге 34.6 демонстрируется явное указание набора значений и использование атрибута wrap. Листинг 34.6. Использование атрибутов values и wrap set months {Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec} spinbox .month -values $months -textvariable date(month) \ -state readonly -width 8 spinbox .date -from 1 -to 31 -textvariable date(date) \ set states [list Arizona California Nevada "New Mexico"] spinbox .s3 -values $states -wrap 1 pack .s3 Во всех рассмотренных до сих пор примерах применения инкрементных регуляторов пользователь может ввести в поле редактирования, содержащееся в составе данного компонента, произвольный текст. В некоторых приложениях это допустимо; гарантировать корректность введенных данных можно, используя механизм проверки, описанный ранее в этой главе. Однако в ряде случаев приходится запрещать пользователю вводить любые значения, кроме тех, которые присутствуют в стандартном наборе. Проще всего сделать это, задав значение readonly атрибута state. В отличие от режима disabled, в котором пользователь теряет доступ к инкрементному регулятору, а программное изменение состояние компонента запрещается, режим readonly позволяет перебирать значения с помощью кнопок со стрелками. В режиме readonly допускается также программное изменение содержимого компонента с помощью операции set. В листинге 34.7 показан пример использования нескольких инкрементных регуляторов, находящихся в режиме readonly, для выбора даты. Компоненты spinbox связаны с помощью опции -textvariable с элементами глобального массива, поэтому после выбора пользователем даты можно извлекать данные из массива. Листинг 34.7. Использование компонента spinbox в режиме readonly
716 Часть IV. Компоненты Тк -state readonly -width 8 spinbox .year -from 2003 -to 2010 -textvariable date(year) \ -state readonly -width 8 label .l_month -text "Month:" label .l_date -text "Date:" label .l_year -text "Year:" grid .l_month .month grid .l_date .date grid .l_year .year grid .l_month .l_date .l_year -padx 2 -sticky e grid .month .date .year -sticky ew Связывания для компонентов entry и spinbox В табл. 34.2 приведены сведения о связываниях для компонентов entry и spinbox. Если в одной строке таблицы указаны два события, это значит, что действия, выполняемые при их возникновении, эквивалентны. Таблица 34.2. Связывания для компонентов entry и spinbox <Button-l> Установка точки ввода и начало выделения <Bl-Motion> Перетаскивание выделенного фрагмента <Double-Button-l> Выделение слова <Triple-Button-l> Выделение всего текста в компоненте <Shif t-Bl-Motion> Установка конца выделения <Control-Button-l> Установка точки ввода. Выделенный фрагмент остается без изменений <Button-2> Включение выделенного фрагмента в точку, в которой установлен текстовый курсор <B2-Motion> Горизонтальная прокрутка <Up> Переход к следующему значению в порядке возрастания. Только для компонентов spinbox <Down> Переход к следующему значению в порядке убывания. Только для компонентов spinbox <Lef t> <Control-b> Перемещение текстового курсора на одну позицию влево и начало выделения
Глава 34. Поля редактирования и инкрементные регуляторы 717 Окончание табл. 34.2 <Shift-Left> <Control-Left> <Meta-b> <Control-Shift-Left> <Right> <Control-f> <Shift-Right> <Control-Right> <Meta-f> <Control-Shift-Right> <Home> <Control-a> <Shift-Home> <End> <Control-e> <Shift-End> <Select> <Control-Space> <Shift-Select> <Control-Shift-Space> <Control-slash> <Control-backslash> <Delete> <Backspace> <Control-h> <Control-d> <Meta-d> <Control-k> <Control-t> «Cut» <Control-x> «Copy» <Control-c> «Paste» <Control-v> Перемещение текстового курсора на одну позицию влево и расширение выделенного фрагмента Перемещение текстового курсора на одно слово влево и начало выделения Перемещение текстового курсора на одно слово влево и расширение выделенного фрагмента Перемещение текстового курсора на одну позицию вправо и начало выделения Перемещение текстового курсора на одну позицию вправо и расширение выделенного фрагмента Перемещение текстового курсора на одно слово вправо и начало выделения Перемещение текстового курсора на одно слово вправо и расширение выделенного фрагмента Перемещение текстового курсора в начало компонента Перемещение текстового курсора в начало компонента и расширение выделенного фрагмента Перемещение текстового курсора в конец компонента Перемещение текстового курсора в конец компонента и расширение выделенного фрагмента Фиксация выделения в позиции текстового курсора Установка конца выделения в позиции текстового курсора Выбор всего текста в составе компонента Очистка выделения Удаление выделенного фрагмента или удаление следующего символа Удаление выделенного фрагмента или удаление предыдущего символа Удаление следующего символа Удаление следующего слова Удаление данных до конца текстового компонента Перестановка символов Удаление выделенного фрагмента (если он определен) Копирование выделенного фрагмента в буфер обмена Включение содержимого буфера обмена в позицию текстового курсора
718 Часть IV. Компоненты Тк Атрибуты компонентов entry и spinbox В табл. 34.3 приведены атрибуты компонентов entry и spinbox. Для указания атрибутов используются имена ресурсов. Слова, входящие в состав имен, начинаются с прописной буквы. В Tcl-командах этим атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра. Таблица 34.3. Атрибуты компонентов entry и spinbox act iveBackground background borderWidth buttonBackground command cursor disabledBackground disabledForeground exportSelection font foreground format from Цвет фона активизированного элемента. Только для компонента spinbox (Tk 8.4) Цвет фона. (Имя атрибута может быть сокращено до bg.) Дополнительное пространство вокруг текста. (Имя атрибута может быть сокращено до bd.) Цвет фона для кнопок со стрелками, направленными вверх и вниз. Только для компонента spinbox (Tk 8.4) Tcl-сценарий, вызываемый при активизации кнопок со стрелками, направленными вверх и вниз. В состав сценария могут входить ключевые слова, начинающиеся с символа У, (см. табл. 34.1). Только для компонента spinbox (Tk 8.4) Курсор мыши, отображаемый в пределах компонента Цвет фона, отображаемый в тот момент, кода доступ к компоненту запрещен (Tk 8.4) Цвет переднего плана, отображаемый в тот момент, кода доступ к компоненту запрещен (Tk 8.4) Если значение данного атрибута равно true, выделенный текст экспортируется посредством механизма выделения X Window Шрифт, используемый для отображения текста Цвет переднего плана. (Имя атрибута может быть сокращено до f g.) Альтернативный формат для числовых значений ин- крементного регулятора. Используется только тогда, когда заданы опции -from и -to. Задается в виде °/с<число_знаков>.<число_знаков>f, подобно команде format. Только для компонента spinbox (Tk 8.4) Число с плавающей точкой, определяющее нижнюю границу значений инкрементного регулятора. Используется в сочетании с опциями -to и -increment. Только для компонента spinbox (Tk 8.4)
Глава 34. Поля редактирования и инкрементные регуляторы 719 Продолжение табл. 34.3 highlightBackground highlightColor highlightThickness increment insertBackground insertBorderWidth insertOffTime insertOnTime insertWidth invalidCommand justify readonlyBackground relief repeatDelay repeatlnterval selectBackground selectForeground Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода Цвет, используемый для подсветки, когда компонент имеет фокус ввода Толщина прямоугольника, указывающего на наличие фокуса ввода Число с плавающей точкой, определяющее значение приращения. Используется совместно с опциями -from и -to. При активизации кнопки со стрелкой значение компонента увеличивается или уменьшается на величину приращения. Только для компонента spinbox (Tk 8.4) Цвет фона для области, занимаемой текстовым курсором Толщина обрамления для курсора. Имеет ненулевое значение только в том случае, когда применяется имитация трехмерного представления Время, в течение которого мигающий курсор отсутствует. Задается в миллисекундах Время, в течение которого мигающий курсор присутствует на экране. Задается в миллисекундах Толщина текстового курсора. По умолчанию принимается значение, равное 2 Tcl-сценарий, который выполняется в том случае, когда сценарий, заданный посредством опции -validatecommand, возвращает значение О Выравнивание текста. Допустимые значения: left, right и center Цвет фона, используемый в том случае, когда курсор находится в режиме только для чтения (Tk 8.4) Допустимые значения: flat, sunken, raised, groove, solid и ridge Время в миллисекундах, в течение которого кнопка со стрелкой должна быть активизированной, прежде чем начнется операция автоповтора. Только для компонента spinbox (Tk 8.4) Интервал времени между последовательными событиями автоповтора. Только для компонента spinbox (Tk 8.4) Цвет фона для выделенного фрагмента Цвет переднего плана для выделенного фрагмента
720 Часть IV. Компоненты Тк Окончание табл. 34.3 selectBorderWidth show state takeFocus textVariable to validate validateCommand values width wrap xScrollCommand Толщина обрамления для выделенного фрагмента. Имеет ненулевое значение только в том случае, когда применяется имитация трехмерного представления Символ заполнения, отображаемый вместо содержимого компонента (обычно в качестве символа заполнения используется *) Состояние: normal, disabled (значение не изменяется, и компонент не реагирует на действия пользователя) или readonly (значение не изменяется, но пользователь имеет возможность выделять и копировать фрагменты содержимого компонента) Управляет изменением фокуса ввода при действиях с клавиатурой Имя Tcl-переменной, значение которой связывается со значением компонента Число с плавающей точкой, определяющее верхнюю границу значений инкрементного регулятора. Используется в сочетании с опциями -from и -increment. Только для компонента spinbox (Tk 8.4) Описание ситуаций, при которых должна производиться проверка: попе (принимается по умолчанию), focus, f ocusin, f ocusout, key или all (Tk 8.3) Tcl-сценарий, предназначенный для проверки содержимого компонента. Возвращает 1, если новое значение компонента допустимо, и 0, если новое значение недопустимо. В последнем случае содержимое компонента не изменяется (Tk 8.3) Список значений компонента spinbox. Отменяет действие опций -from и -to (Tk 8.4) Ширина, представленная в символах Логическое значение. Если оно равно true, то за последним значением инкрементного регулятора следует первое и наоборот. По умолчанию принимается значение false. Только для компонента spinbox (Tk 8.4) Связывает поле редактирования с полосой прокрутки
Глава 34. Поля редактирования и инкрементные регуляторы 721 Программирование полей редактирования и инкрементных регуляторов Связывания, установленные по умолчанию для компонентов entry и spinbox, обычно удовлетворяют требованиям разработчиков. Однако в некоторых случаях может понадобиться полный контроль над операциями, связанными с вводом и удалением символов, а также с выбором значений и с прокруткой. При выполнении этих операций используются идентификаторы расположения символов, называемые индексами. Индексы отсчиты- ваются от нуля. Для полей редактирования определены также символьные индексы, например end. Индекс, соответствующий координате х, задается как @коордияага, например @26. Описание форматов индексов приведено в табл. 34.4. Таблица 34.4. Индексы, используемые в поле редактирования и в инкрементном регуляторе О Индекс первого символа anchor Индекс точки фиксации выделения end Индекс, на единицу больший, чем индекс последнего символа число Индекс символа. Отсчет индексов начинается с нуля insert Символ, находящийся справа от текстового курсора sel.first Первый символ выделенного фрагмента sel. last Символ, следующий за последним символом выделенного фрагмента @координата_х Символ, расположенный в точке с указанной координатой х В табл. 34.5 описаны операции для полей редактирования и инкрементных регуляторов. В данном случае $w — это компонент entry или spinbox. Таблица 34.5. Операции с компонентами entry и spinbox $w bbox индекс Возвращает список из четырех чисел, которые описывают ограничивающий прямоугольник символа, представленного с помощью индекса $w cget опция Возвращает значение конфигурационной опции $w configure . . . Запрашивает или изменяет конфигурацию компонента
722 Часть IV. Компоненты Тк Продолжение табл. 34.5 $w delete начало Удаляет символы, начиная с указанного начально- ?конец? го и заканчивая символом, предшествующим конечному. Если конечный символ не указан, удаляется лишь символ, заданный посредством первого параметра $w get Возвращает строку, содержащуюся в ноле редактирования $w i curs or индекс Перемещает текстовый курсор $w identify x у Идентифицирует элемент инкрементного регулятора, которому принадлежит указанная точка: none, buttondown, buttonup, entry. Только для компонента spinbox (Tk 8.4) $w index индекс Возвращает числовой индекс, соответствующий указанному индексу $w insert индекс строка Включает строку в позиции, указанной посредством индекса $w invoke элемент Активизирует элемент buttondown или buttonup инкрементного регулятора, вызывая связанное с ним действие. Только для компонента spinbox (Tk 8.4) $w scan mark x Запускает операцию прокручивания. В данном случае х является экранной координатой $w scan dragto x Выполняет прокручивание из предыдущей помеченной позиции $w selection adjust Перемещает границы существующего выделения индекс $w selection clear Очищает выделение $w selection element Предоставляет информацию о выбранном элемен- 1элемент? те инкрементного регулятора пли устанавливает элемент. Только для компонента spinbox (Tk 8.4) $w selection from индекс Устанавливает позицию фиксации для выделения $w selection present Возвращает значение 1, если в поле редактирования есть выделенный фрагмент $w selection range Выделяет символы, начиная с указанного началь- начало конец ного и заканчивая символом, предшествующим конечному $w select to индекс Расширяет выделение
Глава 34. Поля редактирования и инкрементные регуляторы 723 Окончание табл. 34.5 $w set ?значение? $w validate $w xview $w xview индекс $w xview moveto часть $w xview scroll число единица_ измер ения Возвращает значение инкрементного регулятора либо устанавливает новое значение. При наличии соответствующих установок выполняется проверка. Только для компонента spinbox (Tk 8.4) Принудительно вызывает сценарий, указанный с помощью опции -validatecommand. Данный сценарий возвращает значение 0 или 1 (Tk 8.3) Возвращает смещение и диапазон видимого содержимого. Оба значения представляют собой числа с плавающей точкой от 0 до 1.0 Смещает отображаемые данные так, чтобы символ с указанным индексом размещался в левой части области Смещает данные так, чтобы указанная часть содержимого компонента оказалась левее левой границы области отображения Прокручивает данные на указанное число единиц представления содержимого или страниц Например, связывание <Button-l> включает следующие команды: e/oW icursor Qe/oX e/oW select from Qe/ex if {[9/,W cget -state] == "normal"} {focus Ш Как вы помните, в командах, связанных с событиями, могут использоваться ключевые слова, начинающиеся с символа %. Для ключевых слов выполняется подстановка, в частности, e/eW заменяется на путь к компоненту, а У.х — на координату X, связанную с событием мыши. О связывании и правилах подстановки см. в главе 29. Команды, входящие в состав связывания, устанавливают текстовый курсор, а следовательно, и позицию ввода в той точке, на которой пользователь щелкает мышью. При этом используется индекс ФУвх, который при активизации связывания превращается в Ф17 или другое подобное значение. Если поле ввода не находится в режиме disabled и имеет фокус ввода, то оно реагирует на события KeyPress.
Глава 35 Окна списков Компонент listbox отображает список строк, допускающий прокрутку. Данный компонент позволяет выделять одну или несколько строк. V^/кно списка отображает набор текстовых строк, допускающий прокрутку. Основным элементом является строка. Компонент listbox поддерживает операции включения, выделения и удаления строк, однако изменять символы в строке невозможно. Компонент listbox хорошо подходит для отображения набора пунктов. Например, с его помощью может быть создано диалоговое окно для выбора файлов. По умолчанию пользователю разрешается выделять один пункт из списка, но посредством атрибута, управляющего режимом выбора, можно разрешить выделение нескольких пунктов. Использование окон списков Выполнение действий с содержимым компонента listbox Строки, представляющие собой пункты списка, нумеруются начиная с нуля. Символьный индекс end указывает на последнюю строку. Другие варианты указания индекса описываются далее в этой главе. При написании программ, предназначенных для работы с окнами списка, чаще всего приходится выполнять операцию вставки текстовой строки. Если исходные данные представлены в виде списка, вы можете реализовать в программе цикл и включать каждый элемент в конец окна списка. foreach item $list { $listbox insert end $item
Глава 35. Окна списков 725 По мере необходимости можно вставить несколько пунктов с помощью одной операции. В приведенном ниже выражении команда eval используется для объединения списка в одну команду insert. eval {$listbox insert end} $list Операция delete удаляет пункты из компонента listbox. Удалять можно как один пункт, так и пункты, идентифицируемые с помощью индексов, которые принадлежат указанному диапазону. $listbox delete 0 ;# Удаление первого пункта $listbox delete 3 5 ;# Удаление пунктов с номерами # 3--5 включительно В Тк 8.3 для компонента listbox была реализована поддержка опции -listvariable, которая выполняет действия, аналогичные опции -textvariable, используемой в других компонентах. С помощью опции -listvariable задается имя переменной, значением которой является список. Если переменная связана с окном списка с помощью указанной опции, Тк синхронизирует содержимое окна списка и значение переменной; при этом каждому пункту компонента listbox соответствует пункт списка, содержащегося в переменной. В листинге 35.1 приведен простой пример модификации содержимого окна списка с помощью переменной, связанной с ним посредством опции listvariable. Листинг 35.1. Использование опции -listvariable для связывания компонента listbox с переменной listbox .choices -height 5 -width 20 -listvariable states pack .choices lappend states Arizona lappend states California "New Mexico" Программирование компонента listbox Несмотря на то что связывания, установленные по умолчанию, в большинстве случаев выполняют все действия, которые необходимы для выбора пунктов, разработчики часто определяют обработчики событий, связанных со щелчками мышью в окне списка. Операция nearest находит пункт окна
726 Часть IV. Компоненты Тк списка, наиболее близкий к координатам курсора мыши в момент возникновения события. Если пользователь щелкнет мышью за последним элементом, в результате выполнения операции будет возвращен индекс последнего элемента. set index [$list nearest $y] В примере, представленном в листинге 35.2, формируются два окна списка. Для связывания полос прокрутки с окнами списка используется процедура Scrolled_Listbox (см. главу 33). Когда пользователь щелкает на пункте в первом окне списка, этот пункт копируется во второе окно. Выбор пункта во втором окне списка приводит к его удалению. Код, содержащийся в данном листинге, демонстрирует выполнение действий с пунктами компонентов listbox. Листинг 35.2. Выбор пунктов в окне списка proc List_Select { parent values } { # Создание двух окон списков, расположенных рядом frame $parent set choices [Scrolled_Listbox $parent.choices \ -width 20 -height 5 ] set picked [Scrolled_Listbox $parent.picked \ -width 20 -height 5] pack $parent.choices $parent.picked -side left \ -expand true -fill both # Выделение пункта в окне choices переводит его в окно picked bind $choices <ButtonRelease-l> \ [list ListTransferSel °/0W $picked] # Выделение пункта в окне picked приводит к его удалению bind $picked <ButtonRelease-l> \ {ListDeleteSel °/0W e/ey}
Глава 35. Окна списков 727 # Заполнение окна choices foreach x $values { $choices insert end $x } } proc ListTransferSel {src dst} { foreach i [$src curselection] { $dst insert end [$src get $i] } } proc ListDeleteSel {w y} { foreach i [lsort -integer -decreasing [$w curselection]] { $w delete $i } } proc List.SelectValues {parent} { set picked $parent.picked.list set result {} foreach i [$w curselection] { lappend result [$w get $i] > } List_Select .f {apples oranges bananas \ grapes mangos peaches pears} pack .f -expand true -fill both Для перемещения строк из $choices в $picked и удаления пунктов из $picked создаются связывания. Основную работу по выделению пунктов списка выполняют встроенные связывания, соответствующие дескриптору связывания Listbox. Другие модели выделения будут описаны далее в этой главе. Процесс выделения завершается при наступлении события <ButtonRelease-l>. Для $choices связывание <ButtonRelease-l> выглядит следующим образом: bind $choices <ButtonRelease-l> \ [list ListTransferSel °/0W $picked] Для формирования Tcl-команды используется команда list. Это необходимо потому, что нам надо в момент связывания расширять значение $picked. Выполнение команды происходит позже в глобальной области видимости, а после завершения процедуры List.Select переменная picked уже не определена. Ситуация может быть еще хуже: при наличии глобальной переменной
728 Часть IV. Компоненты Тк с именем picked она будет использоваться, что вряд ли планировалось при написании программы. Для выполнении команд в составе связываний реализована небольшая процедура. Такой подход имеет два преимущества. Во-первых, подстановки ключевых слов ограничиваются одной командой. Во-вторых, временно используемые переменные, например переменная цикла i, оказываются скрытыми в области видимости процедуры. Процедура ListTransf erSel получает в качестве параметра список выбранных пунктов и в цикле включает их в другое окно списка. Процедура ListDeleteSel работает аналогично, однако она сортирует индексы выбранных пунктов в порядке убывания. Удаление пунктов начинается с конца списка, так, что индексы остальных пунктов остаются действительными до тех пор, пока удаление не будет произведено. Компонент listbox При выполнении операций с компонентом listbox пункты списка идентифицируются с помощью индексов. Нумерация пунктов начинается с нуля. Для некоторых строк также определены символьные индексы. Компонент listbox поддерживает активный элемент, который отображается с подчеркиванием. Символьные индексы описаны в табл. 35.1. Таблица 35.1. Индексы, используемые в компоненте listbox О Индекс первого символа active Индекс активизированной строки anchor Индекс точки фиксации выделения end Индекс последней строки число Индекс строки. Отсчет индексов начинается с нуля Ох,у Строка, наиболее близкая к точке с указанными координатами В табл. 35.2 представлены операции, используемые при программировании компонентов listbox. В этой таблице компонент listbox обозначается как $w. Большинство операций связано с выбором пунктов; они поддерживаются связями, установленными по умолчанию для класса компонентов Listbox. В Тк 8.3 была реализована поддержка операций itemcget и itemconf igure, которые позволяют управлять внешним видом отдельных строк в окне списка. В табл. 35.3 перечислены конфигурационные опции, поддерживаемые для отдельных пунктов. Заметьте, что эти опции нельзя указывать в ба-
Глава 35. Окна списков 729 зе данных ресурсов; они могут быть заданы только посредством операции itemconfigure. Таблица 35.2. Операции, осуществляемые с компонентом listbox $w activate индекс Активизирует указанную строку $w bbox индекс Возвращает прямоугольник, ограничивающий текст в указанной строке. Информация о прямоугольнике представляется в виде значений смещение_х смещение у ширина высота $w cget опция Возвращает значение конфигурационной опции $w configure ... Запрашивает или изменяет конфигурацию компонента $w cur select ion Возвращает список индексов выделенных строк $w delete начало Удаляет строки от указанной начальной по конечную ?конец? включительно. Если конечная строка не задана, удаляется только начальная $w get начало Возвращает строки от указанной начальной по конеч- ?конец? ную в виде списка $w index индекс Возвращает числовой индекс, соответствующий указанному индексу $w insert индекс Включает указанные пункты перед строкой, заданной ? строка строка строка посредством индекса. Если задан индекс end, пункты . . . ? добавляются в конец списка $w itemcget индекс Возвращает текущее значение указанной конфигура- опция ционной опции для пункта (Тк 8.3) $w itemconf igure Запрашивает или изменяет конфигурационные опции индекс ?опция? для пункта (Тк 8.3) ?значение? ?...? $w nearest у Возвращает индекс строки, наиболее близкой к координате у. Координата задается относительно компонента $w scan mark x у Начинает операцию прокрутки. Точка задается в ( л- стеме координат компонента $w scan dragto x у Прокрутка от предыдущей помеченной позиции $w see индекс Изменение отображения данных таким образом, чтобы строка с указанным индексом выводилась на экран $w selection anchor Фиксация выделения в указанной строке индекс $w selection clear Очистка выделения начало ?конец?
730 Часть IV. Компоненты Тк Окончание табл. 35.2 $w selection includes индекс $w selection set начало ?конец? $w size $w xview $w xview индекс $w xview moveto часть $w xview scroll число единица_ измерения |>w yview $w yview index $w yview moveto часть $w yview scroll число единица _ из мер ения Возвращает значение 1, если строка с указанным индексом выделена Выделение строк от указанной начальной по конечную включительно Возвращает число пунктов в составе компонента Возвращает смещение и диапазон видимого содержимого. Оба значения представляют собой числа от 0 до 1.0 Смещает отображаемые данные так, чтобы символ с указанным индексом размещался в левой части области отображения Смещает данные так, чтобы указанная часть содержимого компонента оказалась левее левой границы области отображения Прокручивает данные по горизонтали на указанное число единиц представления содержимого или страниц Возвращает смещение и диапазон видимого содержимого. Оба значения представляют собой числа от 0 до 1.0 Смещает отображаемые данные так, чтобы строка с указанным индексом размещалась в верхней части области отображения Смещает данные так, чтобы указанная часть содержимого компонента оказалась выше верхней границы области отображения Прокручивает данные по вертикали на указанное число единиц представления содержимого или страниц Таблица 35.3. Конфигурационные опции пунктов окна списка -background цвет -foreground цвет -selectbackground цвет -selectforeground цвет Цвет фона для пункта Цвет переднего плана для пункта Цвет фона для выделенного пункта Цвет переднего плана для выделенного пункта
Глава 35. Окна списков 731 Связывания для компонента listbox Для окна списка определены активизированный пункт и один или несколько выделенных пунктов. Активизированный пункт отображается в соответствии со значением опции -activestyle (по умолчанию используется подчеркивание), а выделенные пункты отмечаются цветом. Для компонента listbox определено большое число обработчиков событий клавиатуры. Чтобы связывания для событий клавиатуры активизировались, компонент должен иметь фокус ввода. Вопросы получения фокуса ввода рассматриваются в главе 39. Для окна списка определены четыре режима выделения пункта. В зависимости от режима изменяются используемые связывания. Допустимые значения атрибута selectMode приведены а табл. 35.4. Таблица 35.4. Значения атрибута selectMode компонента listbox single Выделен может быть только один элемент browse Выделен может быть только один элемент, а выделение можно перетаскивать с помощью мыши. Данный режим установлен по умолчанию multiple Допускается выделение нескольких элементов. Поддерживается переключение выделения. В каждый момент времени можно выделить или отменить выделение только одной строки extended Допускается выделение нескольких элементов. Поддерживается перетаскивание выделения с использованием клавиши <Shift> или <Ctrl> Режим выделения Browse В режиме browse событие <Button-l> выделяет пункт, на который указывает курсор мыши. При перетаскивании курсора выделение переходит на другой пункт. Связывания для режима browse описаны в табл. 35.5. Таблица 35.5. Связывания для режима выделения browse <Button-l> <Bl-Motion> <Shift-Button-l> <Key-Up> <Key-Down> Выделяет элемент, на который указывает курсор мыши. Этот элемент становится активизированным Выполняет те же действия, что и <Button-l>, кроме того, выделение перемещается вместе с курсором мыши Активизирует пункт, на который указывает курсор мыши. Выделение не изменяется Перемещает активизированный элемент на одну строку вверх (вниз) и выделяет его
732 Часть IV. Компоненты Тк Окончание табл. 35.5 <Control-Home> <Control-End> <space> <Select> <Control-slash> Активизирует и выделяет первый элемент в окне списка Активизирует и выделяет последний элемент в окне списка Выделяет активизированный элемент Режим выделения single В режиме single событие <Button-l> выделяет пункт, на который указывает курсор мыши, но перетаскивание курсора не влияет на выделение. При отпускании кнопки мыши активизируется пункт, на который указывает курсор. В табл. 35.6 показаны связывания для режима single. Таблица 35.6. Связывания для режима выделения single <ButtonPress-l> <ButtonRelease-1> <Shift-Button-l> <Key-Up> <Key-Down> <Control-Home> <Control-End> <space> <Select> <Control-backslash> Выделяет элемент, на который указывает курсор мыши Активизирует элемент, на который указывает курсор мыши Активизирует пункт, на который указывает курсор мыши. Выделение не изменяется Перемещает активизированный элемент на одну строку вверх (вниз). Выделение не изменяется Активизирует и выделяет первый элемент в окне списка Активизирует и выделяет последний элемент в окне списка Выделяет активизированный элемент Очищает выделение Режим выделения extended В режиме extended допускается выбор нескольких пунктов путем перетаскивания курсора мыши при нажатой левой кнопке. Удерживая клавишу <Shift>, можно корректировать границы выделения. Выделять пункты, не являющиеся соседними, позволяет клавиша <Ctrl>. Клавиша <Ctrl> действует как переключатель, т.е. предоставляет возможность выделять или отменять выделение пункта, на который указывает курсор мыши. Если при нажатой клавише <Ctrl> выделяется элемент, то при перетаскивании курсора мыши элементы продолжают выделяться. Если в режиме переключателя произошла отмена выделения, то перетаскивание курсора приведет к отмене
Глава 35. Окна списков 733 выделения других пунктов. Если вы хотя бы немного поработаете в режиме extended, он станет интуитивно понятным для вас. В табл. 35.7 описаны связывания для данного режима. Таблица 35.7. Связывания для режима выделения extended <Button-l> <Bl-Motion> <ButtonRelease-1> <Shift-Button-l> <Shift-Bl-Motion> <Control-Button-l> <Control-Bl-Motion> <Key-Up> <Key-Down> <Shift-Up> <Shift-Down> <Control-Home> <Control-Shift-Home> <Control-End> <Control-Shift-End> <space> <Select> <Escape> <Control-slash> <Control-backslash> Выделяет элемент, на который указывает курсор мыши. Этот пункт становится точкой фиксации для выделения Расширяет выделение по мере удаления от точки фиксации Активизирует пункт, на который указывает курсор мыши Формирует выделение от точки фиксации до пункта, на который указывает курсор мыши Продолжает формирование выделения от точки фиксации Включает или отменяет выделение пункта, на который указывает курсор мыши. Этот пункт становится точкой фиксации Устанавливает состояние выделения пунктов от точки фиксации до пункта, на который указывает курсор мыши, таким же, как и состояние точки фиксации Перемещает активизированный элемент на одну строку вверх (вниз). Начинает новое выделение, принимая пункт в качестве точки фиксации Перемещает активизированный элемент на одну строку вверх (вниз) и расширяет выделение так, чтобы этот элемент был выделен Активизирует и выделяет первый элемент в окне списка Расширяет выделение на первый элемент в окне списка Активизирует и выделяет последний элемент в окне списка Расширяет выделение на последний элемент в окне списка Выделяет активизированный элемент Отменяет предыдущее действие по формированию выделения Выделяет все пункты в окне списка Очищает выделение
734 Часть IV. Компоненты Тк Режим выделения multiple В режиме multiple можно выбирать несколько пунктов, но добавлять пункты к числу выделенных можно только по одному. То же касается отмены выделения. Перетаскивание курсора мыши не распространяет выделение на другие пункты. Если вы щелкнете на выделенном пункте, выделение будет снято. В табл. 35.8 приведены сведения о связываниях для режима multiple. Таблица 35.8. Связывания для режима выделения multiple <Button-l> Выделяет элемент, на который указывает курсор мыши <ButtonRelease-l> Активизирует пункт, на который указывает курсор мыши <Key-Up> <Key-Down> Перемещает активизированный элемент на одну строку вверх (вниз). Начинает новое выделение, принимая пункт в качестве точки фиксации <Shift-Up> <Shift-Down> Перемещает активизированный элемент на одну строку вверх (вниз) <Control-Home> Активизирует и выделяет первый элемент в окне списка <Control-Shif t-Home> Активизирует первый элемент в окне списка <Control-End> Активизирует и выделяет последний элемент в окне списка <Control-Shif t-End> Активизирует последний элемент в окне списка <space> <Select> Выделяет активизированный элемент <Control-slash> Выделяет все пункты в окне списка <Control-backslash> Очищает выделение Связывания для прокрутки Для компонента listbox определены связывания, позволяющие прокручивать его содержимое. В дополнение к стандартной прокрутке посредством средней кнопки мыши определены дополнительные связывания. Информация об этих связываниях приведена в табл. 35.9. Таблица 35.9. Связывания для прокрутки содержимого компонента listbox <Button-2> Помечает начало операции прокрутки <B2-Motion> Прокрутка по вертикали или по горизонтали <MouseWheel> Прокрутка по вертикали
Глава 35. Окна списков 735 Окончание табл. 35.9 <Button-4> Поддержка колесика прокрутки в системе Unix. Прокрутка вверх (Тк 8.3) <Button-5> Поддержка колесика прокрутки в системе Unix. Прокрутка вниз (Тк 8.3) <Lef t> <Right> Прокрутка по горизонтали на один символ <Control-Lef t> Прокрутка по горизонтали на один размер экрана <Control-Right> <Control-Prior> <Control-Next> <Prior> <Next> Прокрутка по вертикали на одну страницу. Страницей считается объем информации, помещающийся в окне <Home> <End> Прокрутка соответственно до левой или правой границы окна Виртуальные события для компонента listbox Начиная с Тк 8.1 при изменении выделения в окне списка компонент listbox генерирует виртуальное событие «ListboxSelect>>. Команда, связанная с событием, вызывается после выделения нового пункта и имеет доступ к этому пункту. Получать информацию об изменении состояния окна списка проще всего, определив связывание для данного виртуального события. Пример такого подхода показан в листинге 35.3. Листинг 35.3. Использование виртуального события <<ListboxSelect» proc ListboxChanged {w} { puts -nonewline "Listbox $w selection is now: " foreach index [$w curselection] { puts -nonewline "[$w get $index] u } puts "" } bind .lbox «ListboxSelect>> {ListboxChanged °/0W} Атрибуты компонента listbox В табл. 35.10 описаны атрибуты компонента listbox. Для идентификации атрибутов использовались имена ресурсов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В Тс1-командах атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра.
736 Часть IV. Компоненты Тк Таблица 35.10. Атрибуты компонента listbox activeStyle Стиль отображения активизированного элемента: dotbox, underline (принимается по умолчанию) или none (Tk 8.4) background Цвет фона. (Имя атрибута может быть сокращено ДО bg.) borderWidth Дополнительное пространство вокруг текста cursor Курсор мыши, отображаемый в пределах компонента disabledForeground Цвет фона, отображаемый в тот момент, кода доступ к компоненту запрещен (Тк 8.4) export Select ion Если значение данного атрибута равно true, выделенный текст экспортируется посредством механизма выделения X Window font Шрифт, используемый для отображения текста foreground Цвет переднего плана. (Имя атрибута может быть сокращено до f g.) height Число строк в окне списка highlight Background Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода highlight Color Цвет, используемый для подсветки, когда компонент имеет фокус ввода highlight Thickness Толщина прямоугольника, указывающего на наличие фокуса ввода listVariable Имя Tcl-переменной, содержащий список, синхронизированный с содержимым компонента. Каждый элемент списка соответствует строке в окне списка (Тк 8.3) relief Допустимые значения: flat, sunken, raised, groove, solid или ridge selectBackground Цвет фона для выделения selectForeground Цвет переднего плана для выделения selectBorderWidth Толщина обрамления для выделения. Имеет ненулевое значение в том случае, когда применяется имитация трехмерного представления selectMode Возможные режимы: browse, single, extended и multiple setGrid Логическое значение. Включает режим сетки state Состояние компонента: normal или disabled (Tk 8.4)
Глава 35. Окна списков 737 Окончание табл. 35.10 takeFocus width xScrollCommand yScrollCommand Управляет изменением фокуса ввода при действиях с клавиатурой Ширина, выраженная в символах. Для измерения используется усредненная ширина символа Связывает окно списка с горизонтальной полосой прокрутки Связывает окно списка с вертикальной полосой прокрутки Режим сетки Атрибут setGrid влияет на изменение размеров окна списка. По умолчанию могут устанавливаться произвольные размеры окна. Если для атрибута setGrid установлено значение true, включается режим сетки. При этом на возможные размеры окна накладываются ограничения: в окне должно отображаться целое число строк и целое число символов средней ширины. Режим сетки определяет способ предоставления информации о размерах. Если режим сетки отключен, размеры определяются в пикселях. Если режим сетки установлен, размеры определяются в единицах сетки.
Глава 36 Текстовый компонент Текстовый компонент позволяет редактировать текст, управлять междустрочными интервалами и выравниванием, а также использовать дескрипторы, маркеры и встроенные окна. 1 ЕКСТОВЫЙ компонент Тк представляет собой простое в использовании, но достаточно мощное средство отображения текста, позволяющее выполнять с ним различные действия. Он обеспечивает богатые возможности разработчикам приложений. В частности, для каждой строки можно задавать междустрочный интервал и выравнивание. Начертание и размер шрифтов, а также цвет применимы для любых последовательностей символов. При редактировании поддерживаются маркеры, которыми отмечаются позиции в тексте. Эти маркеры сохраняются при вводе и удалении символов. Наиболее важным из средств, предоставляемых текстовым компонентом, являются дескрипторы. С помощью дескрипторов можно задавать различные атрибуты, например начертание шрифта или тип выравнивания. Символы, к которым применен тот или иной дескриптор, получают атрибуты, заданные с его помощью. Последовательности символов могут получать атрибуты от различных дескрипторов, поэтому при разработке приложения можно использовать различные дескрипторы для выравнивания, установки шрифтов и выполнения других действий, определяющих характеристики текста в составе компонента. Дескрипторы позволяют создавать связывания, в результате чего последовательности символов начинают реагировать на действия мышью. Дескрипторы широко применяются практически в каждом приложении, выполняющем нетривиальные действия с помощью текстовых компонентов.
Глава 36. Текстовый компонент 739 Индексы Для адресации символов в составе текстового компонента используются номер строки и позиция символа в пределах этой строки. Нумерация строк начинается с единицы, а символы в строке нумеруются начиная с нуля. Подобный способ отсчета строк выбран для обеспечения совместимости с другими программами, которые нумеруют строки начиная с единицы. В качестве примера такой программы можно привести компилятор, генерирующий сообщения об ошибках. Ниже приведены примеры индексов символов. 1.0 Первый символ 1.1 Второй символ в первой строке 2.end Символ перевода строки в конце второй строки Кроме того, для текстового компонента определены также символьные индексы. Индекс insert задает позицию, в которой новый символ, вводимый пользователем, будет включен в содержимое компонента. В распоряжение разработчика предоставляются также специальные символы, называемые маркерами, которые будут описаны далее в этой главе. Различные варианты индексов приведены в табл. 36.1. Таблица 36.1. Текстовые индексы строка, символ Отсчет строк начинается с единицы. Символы в строке от- считываются начиная с нуля @х,у Номер символа, соответствующего указанной позиции на экране current Символ, на который указывает курсор мыши end Позиция, следующая за последним символом image Позиция встроенного изображения insert Позиция символа, следующего за текстовым курсором маркер Позиция, следующая за именованным маркером дескриптореirst Первый символ диапазона, помеченного дескриптором дескриптор.last Символ, следующий за последним символом диапазона, помеченного дескриптором окно Позиция встроенного окна
740 Часть IV. Компоненты Тк Вставка и удаление текста Для включения текста в состав компонента используется операция insert, которая вызывается следующим образом (где $t — текстовый компонент): $t insert индекс строка ?список_дескрипторов? ?строка список_дескрипторов? ... Индекс может быть задан в любом из форматов, указанных в табл. 36.1. Кроме того, в качестве индексов можно использовать индексные выражения, которые будет рассмотрены ниже. С текстом, включаемым в состав компонента, могут быть связаны дескрипторы. Если дескрипторы не указаны, строка получает атрибуты, заданные дескрипторахми, которые связаны с символами, расположенными слева и справа от позиции, заданной посредством индекса. Дескрипторы будут подробно описаны далее в этой главе. С помощью одной команды можно включить несколько строк, связанных с различными дескрипторами. Наиболее часто применяется индекс insert, определяющий текущую позицию ввода. Связывание, установленное по умолчанию, включает вводимый текст в позицию insert. По мере необходимости можно принудительно завершить строку, включив в состав компонента символ перевода строки. $t insert insert "Hello, World\n" Для удаления текста используется операция delete. Если при вызове операции указан только один индекс, удаляется лишь символ в указанной позиции. Если заданы два индекса, удаляются все символы, находящиеся между указанными позициями (символ, определяемый вторым индексом, остается в составе компонента). Например, для удаления первой строки можно использовать следующую команду: $t delete 1.0 2.0 В Tk 8.4 была реализована возможность удалять несколько последовательностей символов с помощью одной операции delete. Например, приведенная ниже команда удаляет первую, четвертую и восьмую строки. $t delete 1.0 2.0 4.0 5.0 8.0 9.0 Индексные выражения Текстовые компоненты поддерживают арифметические выражения над индексами. Так, например, вы можете задать "окончание строки, на которую указывает индекс", "три символа перед индексом" и другие выражения. Модифицирующие выражения группируются с индексом. Например, индекс insert можно изменить следующим образом: "insert lineend" "insert -3 chars"
Глава 36. Текстовый компонент 741 Интерпретация индексов и их модификаторов осуществляется так, чтобы обеспечить удобные средства для выполнения операций delete и tag add с компонентом text. Данные операции применяются к последовательности символов, определяемой двумя индексами. Второй индекс указывает на первый из символов, следующих за определяемой последовательностью. Например, приведенная ниже команда удаляет слово, в пределах которого располагается текстовый курсор. $t delete "insert wordstart" "insert wordend" Если вам необходимо удалить строку, включая символ перевода строки, следует использовать модификатор "lineend +l char". Если вы не увеличите число удаляемых символов на единицу, в составе компонента останется пустая строка. Если с индексом связано несколько модификаторов, они применяются по очереди в порядке следования, т.е. слева направо. $t delete "insert linestart" "insert lineend +1 char" В табл. 36.2 приведены сведения о модификаторах индексов. Таблица 36.2. Модификаторы индексов для текстового компонента + число chars Указанное число символов после индекса - число chars Указанное число символов перед индексом + число lines Указанное число строк после индекса. Позиция символа сохраняется - число lines Указанное число строк перед индексом. Позиция символа сохраняется linestart Начало строки lineend Конец строки (т.е. символ перевода строки) words tart Первый символ слова wordend Символ, следующий за последним символом слова Сравнение индексов Операция compare сравнивает два индекса или индексных выражения. Операцию compare надо применять всегда, когда необходимо выяснить взаимное расположение индексов. В частности, она позволяет определить, что индекс 1.3 меньше, чем индекс 1.13. Если вы попытаетесь сделать то же самое, сравнивая числа, результаты будут некорректными. Операция compare вызывается следующим образом: $t compare индекс_1 оператор индекс_2
742 Часть IV. Компоненты Тк Для сравнения могут использоваться операторы <, <=, ==, >=, > и !=. В качестве индексов можно задавать простые индексы в форматах, приведенных в табл. 36.1, либо индексные выражения. Операция compare будет использоваться в примере, приведенном в листинге 36.6. Текстовые маркеры Маркер — это символьное имя, с помощью которого помечается позиция между двумя символами. При вставке и удалении текста логическая позиция маркера (но не физическая позиция, определяемая индексом) остается неизменной. Маркер сохраняется даже в том случае, если окружающий его текст будет удален. Для создания маркеров используется операция mark set; удалить маркер можно с помощью операции mark unset. Будучи определен, маркер может использоваться в тех операциях, в которых требуется указание индексов. Ниже приведены команда установки маркера в начале слова, на котором расположен текстовый курсор, и команда удаления символов до конца, строки. $t mark set foobar "insert wordstart" $t delete foobar "foobar lineend" $t mark unset foobar При определении маркера он устанавливается непосредственно перед символом, указанным с помощью индексного выражения. В предыдущем примере маркер располагался перед первым символом слова, на который указывал текстовый курсор. Если маркер используется в тех операциях, в которых требуется указывать индекс, он ссылается на следующий за ним символ. Таким образом, маркер всегда указывает на следующий за ним символ, за исключением тех случаев, когда этот символ был удален. В качестве имени маркера можно применять практически любую строку символов. Нельзя лишь использовать числа, а также строки, содержащие пробелы и знаки + и -. Эти символы используются в индексных выражениях, поэтому при включении их в состав имен маркеров могут возникать проблемы. Операция mark names возвращает список всех определенных маркеров. Маркер insert отмечает ту точку, в которой отображается текстовый курсор. Его нельзя удалить с помощью операции mark unset. Однако при попытке сделать это ошибка не возникает, поэтому приведенное ниже выражение представляет собой удобный способ удаления всех маркеров. Команда eval необходима для объединения списка имен маркеров с командой mark unset. eval {$t mark unset} [$t mark names]
Глава 36. Текстовый компонент 743 Направление маркера Каждому маркеру ставится в соответствие направление (gravity), которое определяет, что будет происходить при вводе символа в позиции маркера. По умолчанию используется правое направление. Это означает, что маркер связывается с символом, находящимся справа от него. При вводе текста в позицию маркера с направлением вправо, маркер сдвигается и располагается справа от введенного символа. При ввода текста в позицию, определяемую маркером с левым направлением, символы размещаются справа от маркера. В результате положение маркера не изменяется. В версиях Тк, предшествующих 4.0, поддерживалось только правое направление маркеров, что в некоторых случаях затрудняло работу с ними. Определить текущее направление маркера и задать новое позволяет операция mark gravity. $t mark gravity foobar => right $t mark gravity foobar left Дескрипторы Дескриптор — это символьное имя, которое связывается с одной или несколькими последовательностями символов. Дескриптор содержит атрибуты, определяющие отображение связанного с ними текста. Эти атрибуты позволяют задавать шрифты, цвет, табуляторы, междустрочный интервал и типы выравнивания. Для дескрипторов можно определять связывания, что позволяет реализовать гипертекстовые ссылки. Дескрипторы также могут использоваться для представления информации, специфической для конкретных приложений. Операции tag names и tag ranges, которые будут подробно рассмотрены ниже, предоставляют информацию о том, какие дескрипторы определены в текстовом компоненте и к каким последовательностям символов они применяются. В качестве имени дескриптора можно использовать почти любую строку символов. Нельзя использовать лишь числа, а также строки, содержащие пробелы и знаки + и -. Эти символы используются в индексных выражениях, поэтому при включении их в состав имен дескрипторов могут возникать проблемы. Дескриптор связывается с последовательностью символов с помощью операции tag add. Приведенная ниже команда связывает дескриптор everywhere со всем текстом, содержащимся в составе компонента. $t tag add everywhere 1.0 end
744 Часть IV. Компоненты Тк Один или несколько дескрипторов можно задавать также при выполнении операции insert. $t insert insert "new text" {некоторый^дескриптор другой_дескриптор} Если при включении текста дескрипторы не указаны, текст приобретает атрибуты, определяемые теми дескрипторами, которые заданы для символов, расположенных по обеим сторонам от позиции ввода. (В версиях, предшествующих Тк 4.0, использовались только дескрипторы, определенные для символа, расположенного слева.) Если при выполнении операции insert вы укажете дескрипторы, то только они будут применены к включаемому тексту. Для разрыва связи между дескриптором и последовательностью символов используется операция tag remove. Однако даже при отключении дескрипторов атрибуты, заданные с их помощью, запоминаются. Для того чтобы удалить всю информацию о дескрипторе, надо использовать операцию tag delete. $t tag remove everywhere 3.0 6.end $t tag delete everywhere Атрибуты дескрипторов Атрибуты дескрипторов определяются с помощью операции tag configure. Например, дескриптор, который задает отображение текста синим цветом, можно создать с помощью следующей команды: $t tag configure blue -foreground blue В табл. 36.3 описаны атрибуты дескрипторов. Некоторые атрибуты могут быть заданы только с помощью дескрипторов. Так, например, атрибуты, соответствующие опциям -bgstipple,-elide,-fgstipple,-justify, -lmarginl, -lmargin2, -offset, -overstrike, -rmargin и -underline, в текстовом компоненте отсутствуют. Атрибуты компонента text приведены в табл. 36.10 в конце данной главы. Таблица 36.3. Атрибуты дескрипторов, используемых в текстовом компоненте -background цвет Цвет фона при отображении цвета -bgstipple Маска для отображения цвета фона битовая_карга -borderwidth пиксели Толщина обрамления при имитации трехмерного представления -elide Если значение данного атрибута равно true, текст не логическое „выражение отображается (Тк 8.3)
Глава 36. Текстовый компонент 745 Окончание табл. 36.3 -fgstipple битовая, карта -font шрифт -foreground цвет -justify тип -lmarginl пиксели -lmargin2 пиксели -offset пиксели -overstrike логическое„выражение -relief тип -гmargin пиксели -spacing1 пиксели -spacing2 пиксели -spacing3 пиксели -tabs табуляторы -underline логическое„выражение -wrap режим Маска для отображения цвета переднего плана Шрифт, используемый для отображения текста Цвет переднего плана при отображении цвета Выравнивание текста. Допустимые значения: left, right и center Обычный отступ для строки Отступ для части строки, к которой применялась операция переноса Смещение от базовой линии. Для верхних индексов имеет положительное значение Текст, перечеркнутый горизонтальной линией Допустимые значения: flat, sunken, raised, groove, solid и ridge Правая граница Дополнительное пространство над строкой Дополнительное пространство над той частью строки, к которой применялась операция переноса Дополнительное пространство под строкой Установка табуляторов Если логическое выражение принимает значение true, текст отображается с подчеркиванием Режимы переноса строки: none, char и word Атрибуты relief и borderwidth используются совместно. Если вы зададите только рельеф, это не приведет к изменению внешнего вида текстового компонента. По умолчанию принимается значение flat опции -relief, поэтому если вы зададите ширину обрамления, не указав рельеф, это также не повлияет на отображение компонента. Значениями атрибутов bgstipple и fgstipple является битовая карта. Работа с битовыми картами и цветом будет детально описана в главе 41. Например, для того, чтобы отобразить текст серым цветом, надо указать для опции -fgstipple значение gray50. $t tag configure disabled -fgstipple gray50 Атрибут elide, добавленный в Tk 8.3, разрешает или запрещает отображение текста. С помощью данного атрибута удобно отображать и скрывать
746 Часть IV. Компоненты Тк вспомогательную информацию, например HTML- или XML-дескрипторы или URL гипертекстовых ссылок. Целесообразно настроить дескрипторы перед их использованием. Задавать атрибуты дескрипторов и определять связывания для них можно еще перед тем, как они будут поставлены в соответствие символьным последовательностям. Значения атрибутов сохраняются до тех пор, пока дескриптор не будет явно удален. Если вы планируете многократно использовать одни и те же установки, то желательно заранее настроить дескрипторы, определив таким образом графический контекст. Такой подход имеет еще одно преимущество. Если вы измените конфигурацию дескриптора, все фрагменты текста, связанные с ним, изменят свой внешний вид в соответствии со вновь заданными атрибутами. Аналогично, если вы измените связывание для дескриптора, все символы, соответствующие этому дескриптору, начнут реагировать на указанные события. В листинге 36.1 заданы дескрипторы, определяющие стили символов, которые могут использоваться в текстовом редакторе. В данном примере использована система именования шрифтов, принятая в Tk 8.O. Подробно имена шрифтов будут рассмотрены в главе 42. Листинг 36.1. Конфигурация дескрипторов, определяющих стили символов proc TextStyles { t } { $t tag configure bold -font {times 12 bold} $t tag configure italic -font {times 12 italic} $t tag configure fixed -font {courier 12} $t tag configure underline -underline true $t tag configure super -offset 6 -font {helvetica 8} $t tag configure sub -offset -6 -font {helvetica 8} Использование атрибутов нескольких дескрипторов С одним символом может быть связано несколько дескрипторов. Например, один дескриптор может определять шрифт, другой — цвет фона и т.д. Если различные дескрипторы предоставляют символу один и тот же атрибут, то приоритет этих дескрипторов определяется порядком их следования. Тот дескриптор, который был связан с последовательностью символов позже других, имеет наивысший приоритет. Порядком следования дескрипторов можно управлять с помощью команд tag raise и tag lower.
Глава 36. Текстовый компонент 747 Объединяя атрибуты различных дескрипторов, можно добиться интересных результатов. Например, в программе просмотра почты можно отмечать одним цветом сообщения, предназначенные для удаления, а другим цветом — те сообщения, которые должны быть перемещены в другую папку. Для этой цели подходят, например, следующие дескрипторы: $t tag configure deleted -background grey75 $t tag configure moved -background yellow Нетрудно заметить, что эти дескрипторы конфликтуют между собой, но они никогда не применяются в одному и тому же сообщению. Выбранное сообщение может отображаться с подчеркиванием. Дескриптор, применяемый для этого, имеет следующий вид: $t tag configure select -underline true Дескриптор select можно добавлять к символам и удалять, обозначая тем самым выбранные сообщения. Подчеркивание, которым они отмечаются, не зависит от цвета фона, определяемого дескрипторами moved и deleted. В реализации почтовой программы exmh присутствует файл ftocColor.tcl, в котором определены дескрипторы, подобные описанным выше. Междустрочный интервал и выравнивание Для управления междустрочным интервалом и выравниванием предусмотрены специальные атрибуты. Установка их значений усложняется тем, что в текстовом компоненте осуществляется автоматический перенос символов на новую строку. Текстовый компонент способен отличать первую отображаемую строку от остальных строк. Например, если строка содержит 80 символов, а ширина окна позволяет отобразить только 30 символов, то на экране эта последовательность символов будет отображена в виде трех строк. Сведения об атрибуте wrap текстового компонента приведены в табл. 36.10. Для управления междустрочным интервалом определены три атрибута. Они могут воздействовать как на все содержимое компонента, так и на последовательности символов, связанные с конкретными дескрипторами. Атрибут spacingl увеличивает размер пустого пространства над первой отображаемой строкой, а атрибут spacing2 задает пространство над последующими строками, возникшими в результате автоматического переноса. Атрибут spacing3 определяет пространство под последней отображаемой строкой. Если перенос не используется, оно может совпадать с соответствующей характеристикой первой отображаемой строки. Значения отступов также по-разному задаются для первой и последующих строк. Атрибут lmarginl задает отступ от левой границы окна для первой строки, а атрибут lmargin2 — величину отступа для последующих отоб-
748 Часть IV. Компоненты Тк ражаемых строк (если они существуют). Для управления отступом от правой границы предусмотрен лишь один атрибут — margin. Атрибуты, управляющие отступом, могут задаваться только с помощью дескрипторов. Наиболее близким средством в составе текстового компонента является атрибут padx, который определяет отступ от обеих границ окна. Листинг 36.2. Междустрочный интервал и отступы в текстовом компоненте proc TextExample { f } { frame $f pack $f -side top -fill both -expand true set t [text $f.t -setgrid true -wrap word \ -width 42 -height 14 \ -yscrollcommand n$f.sy set"] scrollbar $f.sy -orient vert -command "$f.t yview" pack $f.sy -side right -fill у pack $f.t -side left -fill both -expand true $t tag configure para -spacingl 0.25i -spacing2 O.li \ -lmarginl 0.5i -lmargin2 O.li -rmargin 0.5i $t tag configure hang -lmarginl O.li -lmargin2 0.5i $t insert end "Here is a line with no special settings\n" $t insert end "Now is the time for all good women and men to come to the aid of their country. In this great time of
Глава 36. Текстовый компонент 749 need, no one can avoid their responsibility.\nM $t insert end "The quick brown fox jumps over the lazy dog." $t tag add para 2.0 2.end $t tag add hang 3.0 3.end 2 В примере, приведенном в листинге 36.2, определены два дескриптора, para и hang, в которых заданы различные междустрочные интервалы и значения отступов. Значение опции -spacingl, указанное при определении дескриптора para, управляет размерами пространства над второй из заданных строк текста. Опция -spacing2 определяет интервал между строками, отображаемыми в результате обработки второго абзаца. В дескрипторе hang атрибуты, управляющие междустрочным интервалом, не заданы, поэтому последний абзац отображается практически вплотную к предыдущему. В данном примере ясно видны различия между опциями -lmarginl и -lmargin2. В содержимое компонента явным образом включены символы перевода строки. Каждый из них вызывает переход на новую строку. Это влияет на формирование индексов. Как видно из примера, в третьей строке символ перевода строки отсутствует. Это означает, что если к концу содержимого будет добавлен новый текст, он будет отображаться в третьей строке. Значения междустрочного интервала и отступов задаются в единицах, используемых для определения размеров экранных объектов. Поскольку разные шрифты имеют различные размеры, междустрочный интервал надо вычислять исходя из высоты символов. Операция bbox возвращает ограничивающий прямоугольник для символа (координаты х и у, высота и ширина). $t insert 1.0 "ABCDE" $t bbox 1.0 => 4 4 8 12 В Tk 8.0 была реализована команда font metrics, которая также предоставляет информацию о размере символов. Подробно данная команда будет обсуждаться в главе 42. font metrics {times 12} -ascent 9 -descent 3 -linespace 12 -fixed 0 Возможности выравнивания текста ограничены тремя значениями: left, right и center. Выравнивание по обеим границам не предусмотрено. Реализовать его можно, изменяя расстояние между словами.
750 Часть IV. Компоненты Тк Табуляторы В текстовом компоненте реализованы средства управления табуляторами. Значением атрибута tabs является список табуляторов, которые задаются в экранных единицах измерения, а также необязательных ключевых слов, определяющих выравнивание. Для указания типа выравнивания применяются ключевые слова left, right, center и numeric; допускается использование их сокращений. По умолчанию принимается значение left. В следующем примере определяются табуляторы, следующие с интервалом в 2 сантиметра, для которых заданы различные типы выравнивания: *Text.tabs: 2c left 4c right бс center 8c numeric Атрибут tabs может применяться как ко всему тексту в составе компонента, так и к последовательности символов, связанной с дескриптором. По мере необходимости список табуляторов может быть экстраполирован. Приведенная ниже команда определяет дескриптор, который задает табуляторы с выравниванием типа left, следующие через каждые полдюйма. $t tag configure foo -tabs u.5i left" Выделение Выделение реализуется с помощью предопределенного дескриптора sel. Если символ помечается с помощью данного дескриптора, он добавляется к множеству выделенных символов. Эти действия осуществляются посредством связываний, заданных по умолчанию для текстового компонента. Атрибут exportSelection текстового компонента определяет, может ли выделенный текст быть экспортирован в другие приложения. По умолчанию экспортирование разрешено. В этом случае, если другой компонент или другое приложение утверждает, что выделенный текст принадлежит ему, связь между дескриптором sel и всеми символами разрывается. Механизм выделения детально рассматривается в главе 38. Дескриптор sel нельзя удалить с помощью операции tag delete. Однако при попытке сделать это ошибка не возникает. Для того чтобы удалить из текстового компонента все дескрипторы, можно воспользоваться приведенной ниже командой. В данном случае eval используется для объединения списка имен дескрипторов с командой tag delete. eval {$t tag delete} [$t tag names]
Глава 36. Текстовый компонент 751 Связывания для дескрипторов Для дескрипторов можно задавать связывания. В результате после щелчка на разных фрагментах текста, отображаемого в составе компонента, будут выполняться различные действия. Связывание для дескрипторов определяется с помощью обычной команды bind. Вы можете запрашивать информацию о связываниях, выполненных для дескрипторов, или устанавливать новые связывания. Описание команды bind и правила описания событий см. в главе 29. Команда tag bind обеспечивает поддержку лишь следующих событий: Enter, Leave, ButtonPress, ButtonRelease, Motion, KeyPress и KeyRelease. Как и при работе с обычной командой bind, для описания событий ButtonPress и KeyPress можно использовать сокращения Button и Key. Событие Enter генерируется тогда, когда курсор мыши попадает в область, которая занята символами, связанными с дескриптором, а событие Leave —- когда курсор покидает эту область. Если символу соответствует несколько дескрипторов, то при наступлении события активизируются связывания для всех их. Первыми вызываются обработчики дескрипторов с самым низким приоритетом, а последними вступают в действие связывания дескрипторы с наивысшим приоритетом. После выполнения всех обработчиков активизируется связывание для главного компонента (если оно определено). Команды continue и break в составе связываний для дескрипторов выполняются так же, как и в обычных командах, связанных с событиями. Подробно о связывании см. в главе 29. В листинге 36.3 определена "текстовая кнопка", и для нес задан рельеф; кроме того, с ней связано действие. Программа автоматически генерирует имя дескриптора, поэтому каждая кнопка уникальна. Рельеф и цвет фона выделяют элемент на фоне остального содержимого. Команда winf о visual позволяет перед указанием цвета фона выяснить особенности поддержки цвета дисплеем. На черно-белом дисплее изображение кнопки инвертируется. Команда связывается с событием <Button-l>; данное описание эквивалентно <ButtonPress-l>. При попадании курсора мыши в область, которой соответствует связывание, его внешний вид изменяется. Изменение курсора происходит в ответ на события <Enter> и <Leave>. Когда курсор покидает данную область, восстанавливается его прежний вид. Для того чтобы запомнить предыдущие установки курсора, обычно определяют еще один дескриптор. Для этой цели можно использовать глобальную переменную, но с помощью дескрипторов задача решается гораздо проще. Для того чтобы описанный элемент был еще больше похож на обычную кнопку, он должен реагировать на событие <Button-Release-l>. Кроме того,
752 Часть IV. Компоненты Тк при возникновении события <ButtonPress-l> его внешний вид должен изменяться. По мере необходимости вы можете включить в состав компонента обычную кнопку Тк. Процедура включения компонентов будет рассмотрена ниже. Листинг 36.3. Создание "текстовой кнопки" proc TextButton { t start end command } { global textbutton if ![info exists textbutton(uid)] { set textbutton(uid) 0 } else { incr textbutton(uid) > set tag button$textbutton(uid) $t tag configure $tag -relief raised -borderwidth 2 if {[regexp color [winfo visual $t]]} { $t tag configure $tag -background thistle } else { $t tag configure $tag -background [$t cget -fg] $t tag configure $tag -foreground [$t cget -bg] } # Связывание команды с дескриптором $t tag bind $tag <Button-l> $command $t tag add $tag $start $end # Использование дополнительного дескриптора # для запоминания курсора $t tag bind $tag <Enter> \ [list TextButtonChangeCursor °/0W $start $end tcross] $t tag bind $tag <Leave> {TextButtonRestoreCursor °/0W} } proc TextButtonChangeCursor {t start end cursor} { $t tag add cursor=[$t cget -cursor] $start $end $t config -cursor $cursor } proc TextButtonRestoreCursor {t} { regexp {cursor=([~ ]*)} [$t tag names] x cursor $t config -cursor $cursor }
Глава 36. Текстовый компонент 753 Поиск текста При выполнении операции search в текстовом компоненте осуществляется поиск строки. В результате возвращается индекс фрагмента текста, который соответствует шаблону. Поиск начинается с символа, соответствующего указанному индексу, и продолжается до индекса остановки. Если индекс остановки не указан, поиск осуществляется во всем документе. Если вы хотите, чтобы по достижении конца документа поиск не был продолжен с его начала, надо в качестве индекса остановки использовать end. Команда поиска вызывается следующим образом: $t search ?опции? шаблон индекс ?индекс„остановки? Опции команды search описаны в табл. 36.4. Таблица 36.4. Опции, используемые при выполнении операции search -forward Поиск в прямом направлении, начиная с указанного индекса. Этот режим поиска используется по умолчанию -backward Поиск в обратном направлении, начиная с указанного индекса -exact Точное соответствие шаблону. Данный режим используется по умолчанию -regexp Интерпретация шаблона как регулярных выражений -nocase Сравнение без учета регистра символов -count имя^переменной В указанную переменную записывается число символов, соответствующих шаблону Окончание набора опций. Необходимо указывать в том случае, если шаблон начинается с символа - Если вы используете при поиске регулярные выражения, то, вероятно, захотите узнать длину фрагмента текста, соответствующего шаблону. Эти сведения могут понадобиться, например, для того, чтобы выделить найденный текст. С помощью опции -count задается переменная, в которую записывается число символов, соответствующих шаблону. set start [$t search -count cnt -regexp -- $pattern 1.0 end] $t tag add sel $start "$start +$cnt chars"
754 Часть IV. Компоненты Тк Встроенные компоненты Помимо символов, в составе текстового компонента могут отображаться встроенные компоненты. Например, вы можете реализовать изображение, разместив его на холсте и включив последний в состав текстового компонента. Встроенный компонент присутствует в составе текстового компонента на правах обычного символа. Обращаться к встроенным компонентам можно по индексу либо с помощью пути Тк. Предположим, например, что значением $t является имя текстового компонента. Приведенные ниже команды создают кнопку и включают ее в текстовый компонент. Кнопка, включенная в текстовый компонент, ведет себя так же, как и обычная кнопка. В данном примере она после щелчка мышью вызывает команду Help. button $t.help -bitmap questhead -command Help $t window create end -window $t.help По умолчанию для встроенного компонента осуществляется выравнивание по центру строки. Изменить тип выравнивания можно, задавая при вызове команды window create опцию -align. Данная опция влияет на внешний вид компонента только в том случае, если размеры окна меньше, чем высота строки текста. Как правило, это условие не выполняется; поэтому опция -align обычно не принимается во внимание. Выравнивание чаще всего имеет смысл для изображений, так как размеры некоторых из них бывают очень малы (в качестве примера можно привести маркеры специальной формы). В табл. 36.5 описаны значения, используемые для выравнивания окон и изображений. Таблица 36.5. Опции, применяемые для выравнивания окон и изображений top Верхняя часть компонента выравнивается по верхней части текстовой строки center Центр компонента выравнивается по центру текстовой строки baseline Нижняя часть компонента выравнивается по базовой линии строки bottom Нижняя часть компонента выравнивается по нижней части текстовой строки По необходимости вы можете создать включаемый компонент с некоторой задержкой. Для этого надо вместо опции -window указать Tcl-команду формирования окна. Отложенное создание компонентов может применяться тогда, когда вам надо включать в состав текста большое количество различных элементов. При этом Tcl-команда, предназначенная для создания ком-
Глава 36. Текстовый компонент 755 понента, вызывается непосредственно перед тем, как этот компонент должен появиться на экране. Другими словами, когда пользователь прокрутит содержимое компонента до позиции, в которой расположен элемент, вызывается соответствующая Tcl-команда. Листинг 36.4. Отложенное создание компонентов, включаемых в состав текста $t window create end -create [list MakeGoBack $t] proc MakeGoBack { t } { button $t.goback -text "Go to Line 1" \ -command [list $t see 1.0] 2 Процедура MakeGoBack позволяет решить возможные проблемы, связанные с цитированием. Если для создания компонента вам надо выполнить больше одной Tcl-команды или если с вложенной кнопкой связана сложная команда, вероятность ошибки при формировании выражения существенно увеличивается. В табл. 36.6 описаны опции, используемые при создании вложенных компонентов. Характеристики, заданные с помощью этих опций, можно изменить впоследствии посредством операции window configure. Пример использования этой операции приведен ниже. $t window configure $t.goback -padx 2 Таблица 36.6. Опции операции window create -align тип Типы выравнивания: top, center, bottom и baseline -create команда Tcl-команда, предназначенная для создания компонента -padx пиксели Выделение дополнительного пространства слева и справа от компонента -pady пиксели Выделение дополнительного пространства сверху и снизу от компонента -stretch Если значение логического выражения равно true, ком- логическое.выражение понеит растягивается по вертикали так, чтобы он заполнял пространство, выделенное для текстовой строки -window путь Путь Тк к включаемому компоненту Окно, параметры которого должны быть изменены, идентифицируется с помощью пути либо посредством индекса, определяющего расположение в составе текстового компонента. Путь к компоненту применяется чаще, чем индекс. Обратите внимание, что end нельзя использовать для идентификации
756 Часть IV. Компоненты Тк вложенного окна, так как это имя в текстовом компоненте интерпретируется специальным образом. Вы можете включить окно, указав индекс end, но этот индекс всегда указывает на позицию, следующую за последним символом или за последним элементом в составе текстового компонента. Встроенные изображения В Тк 8.0 была реализована поддержка встроенных изображений, которые во многом напоминают встроенные окна. Средства, предоставляемые Тк 8.0, позволяют работать с изображениями более эффективно по сравнению с созданием холста или меток, содержащих графические фрагменты вместо текста. По мере необходимости вы также можете многократно включать в текстовый компонент одно и то же изображение. В листинге 36.5 приведен пример использования изображения в качестве маркера списка. Листинг 36.5. Использование встроенных изображений для формирования маркированного списка proc BList_Setup { t imagefile } { global blist set blist(image) [image create photo -file $imagefile] $t tag configure bulletlist -tabs ".5c center lc left" \ -lmarginl 0 -lmargin2 lc } proc BList_Item { t text {mark insert}} { global blist # Предположим, что мы находимся в начале строки $t insert $mark \t bulletlist $t image create $mark -image $blist(image) $t insert $mark \t$text bulletlist 2 В данном примере для выравнивания маркеров и левого края текста используются табуляторы. Первый из них размещает центр маркера на расстоянии 0,5 сантиметра от левой границы компонента. Второй табулятор выполняет те же функции, что и опция -lmargin2; таким образом, текст в первой строке выравнивается по той же позиции, что и строки, полученные в результате автоматического переноса. Если вы динамически измените изображения, внесенные изменения проявятся на всех экземплярах этого изображения, содержащихся в текстовом компоненте. Такое поведение определяется моделью обработки изображений, используемой в Тк, которая будет подробно рассмотрена в главе 41.
Глава 36. Текстовый компонент 757 Опции для включаемых изображений в основном совпадают с опциями для включаемых окон. Отличие состоит лишь в том, что при работе с изображениями поддерживается также опция -name, которая позволяет обращаться к ним, не зная расположения в текстовом компоненте. Имя изображения непосредственно использовать нельзя, поскольку одно и то же изображение может присутствовать в нескольких позициях текстового компонента. Если вы не укажете имя для изображения, оно будет сгенерировано автоматически. Операция image create возвращает это имя. $t image create 1.0 -image imagel => imagel $t image create end -image imagel => image1#1 В табл. 36.7 описаны опции для вложенных изображений. Характеристики, заданные с помощью этих опций, можно изменить впоследствии посредством операции image configure. Таблица 36.7. Опции операции image create -align тип Тип выравнивания: top, center, bottom или baseline. Данная установка действует только в том случае, если высота изображения меньше высоты строки (см. табл. 36.5) -image изображение Тк-изображение, предназначенное для включения в состав текстового компонента -name имя Имя экземпляра изображения. Для генерации уникального имени может использоваться суффикс, сформированный по принципу #число -padx пиксели Выделение дополнительного пространства слева и справа от изображения -pady пиксели Выделение дополнительного пространства сверху и снизу от изображения Чтение содержимого текстового компонента В текстовом компоненте предусмотрено несколько операций, которые предоставляют доступ к его содержимому. Наиболее простой из этих операций является get, которая возвращает текст, содержащийся в компоненте. Если при вызове операции указан только один индекс, возвращается лишь символ,
758 Часть IV. Компоненты Тк содержащийся в указанной позиции. Если заданы два индекса, возвращаются все символы, находящиеся между указанными позициями (за исключением символа, соответствующего второму индексу). Например, для того, чтобы получить весь текст, содержащийся в компоненте, надо использовать следующую команду: $t get 1.0 end Начиная с Tk 8.4 стало возможно получать с помощью одной операции get несколько последовательностей символов. В этом случае возвращаемым значением операции get является список фрагментов текста, полученных из компонента. Например, приведенная ниже команда возвращает список из двух элементов, содержащих первую и третью строку текста без учета символов перевода строки. $t get 1.0 Lend 3.0 3.end Получение информации о дескрипторах Команда tag names возвращает либо имена всех дескрипторов, либо имена дескрипторов, соответствующих указанному индексу. $t tag names ?индекс? Один дескриптор может соответствовать нескольким последовательностям символов. Операция tag ranges возвращает список индексов, отмечающих начало и конец области действия дескриптора. Для организации работы с диапазонами индексов, в пределах которых действует дескриптор, удобно использовать команду for each с двумя переменными цикла. foreach {start end} [$t tag ranges $tag] { # start - начало диапазона # end - конец диапазона } Операции tag nextrange и tag prevrange возвращают два индекса, которые определяют соответственно следующую и предыдущую область действия дескриптора. При вызове этих операций указывается начальный индекс и необязательный конечный индекс. Если начальный индекс для tag nextrange попадает в область действия дескриптора, операция переходит к следующему диапазону, за исключением случая, когда начальный индекс точно совпадает с началом диапазона. Операция tag prevrange действует противоположным образом. Она пропускает текущую область действия дескриптора только в том случае, если начальный индекс совпадает с началом
Глава 36. Текстовый компонент 759 диапазона. В листинге 36.6 приведен пример процедуры, возвращающей текущую область действия дескриптора. В ней использовались описанные выше операции. Листинг 36.6. Определение области действия дескриптора proс Text„CurrentRange { t tag mark } { set range [$t tag prevrange $tag $mark] set end [lindex $range 1] if {[llength $range] == 0 I I [$t compare $end < $mark]} { # Если маркер в начале узла set range [$t tag nextrange $tag $mark] if {[llength $range] == 0 I I [$t compare $mark < [lindex $range 0]]} { return {} } } return $range } Получение сведений о маркерах Операция mark names возвращает список всех маркеров. В отличие от операции tag names, использовать индексы при нахождении маркеров невозможно. Для этого приходится применять операцию dump, которая будет описана ниже. Операции mark next и mark previous осуществляют поиск маркера, начиная с указанного индекса. Если маркер находится в позиции, указанной посредством стартового индекса, операция mark next находит его. Дамп содержимого компонента Операция dump представляет собой самый универсальный способ проверки содержимого текстового компонента. Команда dump вызывается следующим образом: $t dump ?опции? индекс_1 ?'индекс_2? Операция dump возвращает информацию для элементов, находящихся в диапазоне от первого до второго индекса. Если второй индекс не задан, то данная операция предоставляет лишь сведения об элементе, определяемом первым индексом. Вы можете ограничить возвращаемые данные с помощью опций -text, -mark, -tag, -image, -window или -all. Для каждого элемента в составе текстового компонента возвращаются тип, значение и индекс. Допустимыми типами являются text, tagon, tagof f,
760 Часть IV. Компоненты Тк mark, image и window. Информация о типе отражает способ, которым текстовый компонент представляет свое содержимое. Дескрипторам соответствуют элементы tagon и tagoff. Текстовыми считаются сегменты, которые не содержат маркеров, элементов, помеченных дескрипторами, и изображений. Текстовый сегмент завершает символ новой строки. Процедура, код которой приведен в листинге 36.7, выводит содержимое текстового компонента. Листинг 36.7. Вывод информации о текстовом компоненте proc Text_Dump {t {start 1.0} {end end}} { foreach {key value index} [$t dump $start $end] { if {$key == "text"} { puts "$index \"$value\"" } else { puts "$index $key $value" } } _} Команду dump можно вызывать так, чтобы, вместо того чтобы возвращать информацию, она вызывала Tcl-команду, предназначенную для обработки каждого элемента. В листинге 36.8 показан еще один способ вывода содержимого текстового компонента. Листинг 36.8. Вывод информации о текстовом компоненте с помощью команды обратного вызова proc Text_Dump {t {start 1.0} {end end}} { $t dump -command TextDump $start $end } proc TextDump {key value index} { if {$key == "text"} { puts "$index \"$value\"" } else { puts "$index $key $value" } } Отмена выполненных действий Начиная с версии Тк 8.4 в текстовых компонентах были реализованы отмена и возврат неограниченного числа выполненных действий. Для того что-
Глава 36. Текстовый компонент 761 бы разрешить работу соответствующих средств, надо установить значение атрибута undo текстового компонента, равным true. По умолчанию с целью обеспечения обратной совместимости принимается значение false данного атрибута. Механизм отмены и возврата выполненных действий работает следующим образом. При выполнении каждой операции вставки и удаления текста информация о ней записывается в стек undo. Отменить действие можно из программы с помощью операции edit undo. Сведения об отмененных изменениях помещаются в стек redo, поэтому отмененные действия можно вернуть посредством операции edit redo. При помещении в стек undo информации о новом действии стек redo очищается. (Если стек undo или redo пуст, при выполнении операции edit undo или edit redo возникает ошибка.) Для отмены и возврата выполненных действий в текстовом компоненте предусмотрено связывание. В Windows операция отмены действия связана с комбинацией клавиш <Ctrl+Z>, а возврат — с <Ctrl+Y>. На остальных платформах используется только связывание <Ctrl-Z>. Каждая операция edit undo отменяет последнее действие. Действиями считаются команды вставки и удаления, находящиеся в стеке undo между двумя разделителями. Если атрибут autoSeparators имеет значение true (такая установка используется по умолчанию), разделители автоматически помещаются в стек в следующих случаях. • При переходе от вставки к удалению и наоборот. • При перемещении точки ввода с помощью клавиатуры или мыши. • При нажатии клавиши <Return>. Если значение атрибута autoSeparators установлено равным false, то за размещение разделителей в стеке отвечает программа. Отключив автоматическую расстановку разделителей, вы можете устанавливать их в требуемых позициях, определяя тем самым составные действия, например поиск и замену. В качестве примера можно привести связывание для вставки. В результате выполнения данной операции выделенный текст заменяется содержимым буфера обмена. Эта последовательность действий рассматривается как одна операция. Средства отмены выполненных действий учитывают только операции insert и delete. Например, такие действия, как применение дескриптора к тексту, средствами отмены не обрабатываются, даже если дескриптор задается в операции insert. В качестве примера рассмотрим следующее выражение: $t insert end "Let's insert some " {} \ "special" blue " text." {}
762 Часть IV. Компоненты Тк Если отменить, а затем восстановить эту операцию, текст будет повторно включен, но дескриптор blue к слову "special" не будет применен. Связывания и события Связывания для текстового компонента Для текстового компонента по умолчанию задается большое количество связываний. Команда, перемещающая курсор, автоматически отменяет выделение. Удерживая нажатой клавишу <Shift>, можно продолжать выделение символов, а клавиша <Ctrl> позволяет перемещать текстовый курсор, не воздействуя на выделенный текст. В табл. 36.8 перечислены связывания, определенные по умолчанию для текстового компонента. Таблица 36.8. Связывания для текстового компонента <Апу-Кеу> <Button-l> <Control-Button-l> <Bl-Motion> <Double-Button-l> Включение обычного печатного символа Установка текстового курсора, очистка выделения, передача фокуса Установка текстового курсора без воздействия на выделенный текст Распространение выделения от текстового курсора Выделение слова, на которое указывает курсор <Triple-Button-l> <Shift-Button-l> <Shift-Bl-Motion> <Button-2> <B2-Motion> <MouseWheel> <Button-4> <Button-5> <Key-Left> <Control-b> <Shift-Left> Выделение строки, на которую указывает курсор мыши Формирование конца выделения в точке, ближайшей к курсору мыши Продолжение выделения Включает выделенный элемент или устанавливает точку фиксации для прокрутки Осуществляет прокрутку содержимого окна Осуществляет прокрутку по вертикали Поддержка колесика прокрутки в системе Unix. Прокрутка вверх (Тк 8.3) Поддержка колесика прокрутки в системе Unix. Прокрутка вниз (Тк 8.3) Перемещает курсор на один символ влево и очищает выделение Перемещает курсор и расширяет выделение
Глава 36. Текстовый компонент 763 Продолжение табл. 36.8 <Control-Left> <Control-Shift-Left> Перемещает курсор по словам. Очищает выделение Перемещает курсор по словам. Расширяет выделе- <Key-Right> <Control-f> <Meta-b> <Meta-f> <Key-Up> <Control-p> <Shift-Up> <Control-Up> <Control-Shift-Up> <Key-Down> <Control-n> <Next> <Prior> Связывания Right аналогичны связываниям Left To же, что <Control-Lef t> и <Control-Right> Перемещает курсор на одну строку вверх. Очищает выделение Перемещает курсор на одну строку вверх. Расширяет выделение Перемещает курсор вверх на абзац. Абзацем считается группа строк, разделенных пустой строкой Перемещает курсор вверх на абзац. Расширяет выделение Связывания Down аналогичны связываниям Up Перемещает курсор на один экран. Очищает выде- <Shift-Next> <Shift-Prior> <Home> <Control-a> <Shift-Home> <End> <Control-e> <Shift-End> <Control-Home> <Meta-less> <Control-End> <Meta-greater> <Select> <Control-space> <Shift-Select> <Control-Shift-space> <Control-slash> <Control-backslash> Перемещает курсор на один экран. Расширяет выделение Перемещает курсор в начало строки. Очищает выделение Перемещает курсор в начало строки. Расширяет в1- - деление Перемещает курсор в конец строки. Очищает выделение Перемещает курсор в конец строки. Расширяет выделение Перемещает курсор в начало текста. Очищает выделение Перемещает курсор в конец текста. Очищает выделение Устанавливает точку фиксации выделения в позиции курсора Устанавливает выделение в соответствии с позицией курсора Выделяет все содержимое текстового компонента Очищает выделение
764 Часть IV. Компоненты Тк Окончание табл. 36.8 <Delete> <BackSpace> <Control-h> <Control-d> <Meta-d> <Control-k> <Control-o> <Meta-Delete> <Meta-BackSpace> <Control-t> «Cut» <Control-x> «Copy» <Control-c> «Paste» <Control-v> «Undo» <Control-z> «Redo» <Control-Z> (Unix и Macintosh) <Control-y> (Windows) Удаляет выделенный фрагмент. Если выделения нет, то удаляет символ справа от курсора Удаляет выделенный фрагмент. Если выделения нет, то удаляет символ слева от курсора Удаляет символ справа от курсора Удаляет слово справа от курсора Удаляет символы от курсора до конца строки. Если текущая позиция находится в конце строки, удаляется символ перевода строки Включает символ новой строки, но не перемещает курсор Удаляет слово слева от курсора Переставляет символы, соседствующие с курсором Копирует выделение в буфер обмена Вырезает выделение и сохраняет его в буфере обмена Вставляет информацию из буфера обмена Если значение атрибута undo равно true, отменяет последнее действие по редактированию (Тк 8.4) Если значение атрибута undo равно true, возвращает последнее отмененное действие (Тк 8.4) Виртуальные события В версии Тк 8.4 была реализована поддержка виртуального события <<Modif ied>>. Это событие генерируется при каждой вставке или удалении текста. Команда, связанная с событием, вызывается после внесения изменений в содержимое компонента, поэтому она имеет доступ к вновь включенному тексту. Если ваша программа должна реагировать на изменение текста, проще всего сделать это, создав связывание для виртуального события «Modified». Пример такого связывания приведен ниже. bind .t «Modified» {ContentsChanged °/0W} Помимо «Modified», в Tk 8.4 также определено виртуальное событие «Selection», которое генерируется при изменении выделенного текста. Команда, связанная с событием, вызывается после изменения выделения, поэтому она имеет доступ к вновь выделенному тексту.
Глава 36. Текстовый компонент 765 Операции с текстом В табл. 36.9 описаны операции с текстовым компонентом, включая те из них. которые рассматривались в данной главе. Здесь $t обозначает текстовый компонент. Таблица 36.9. Операции с текстовым компонентом $t bbox индекс $t cget опция $t compare индекс_1 оператор индекс_2 $t configure ... $t debug логическое„выражение $t delete индекс_1 7'индекс_2? ?...? $t dlineinfo индекс $t dump ?опции? индекс_1 ?индекс_2 ? $t edit modified ?логическое„выражение? $t edit redo $t edit reset $t edit separator $t edit undo Возвращает прямоугольник, который ограничивает символ, указанный посредством индекса. Прямоугольник представляется четырьмя значениями: х, у, ширина и высота Возвращает значение конфигурационной опции Выполняет сравнение индексов. В качестве оператора могут использоваться <, <=, ==, >=, > и ! = Запрашивает или изменяет конфигурационные опции Разрешает проверку соответствия для кода дерева Удаляет символы между первым и вторым индексом. Символ, соответствующий второму индексу, не удаляется. Если задан только первый индекс, удаляется только идентифицируемый им символ Возвращает ограничивающий прямоугольник для строки, в состав которой входит указанный индекс. Возвращаются пять значений: х, у, ширина, высота и базовая линия. Данные величины представляются в пикселях Возвращает маркеры, дескрипторы, окна, изображения и текст, содержащиеся в компоненте. Допустимые опции: -all, -command команда, -image, -mark, -tag, -text pi -window Запрашивает или устанавливает флаг модификации компонента (Тк 8.4) Если значение атрибута undo равно true, возвращает последнее отмененное действие (Тк 8.4) Очищает стеки undo и redo (Tk 8.4) Если значение атрибута undo равно true, включает разделитель в стек undo (Tk 8.4) Если значение атрибута undo равно true, отменяет последнее действие по редактированию (Тк 8.4)
766 Часть IV. Компоненты Тк Продолжение табл. 36.9 $t get индекс_1 ?индекс_2? ?...? $t image cget опция $t image configure ?опции? $t image create опция значение ... $t image names $t index индекс $t insert индекс символы ?дескрипторы? ? символы дескрипторы? Возвращает текст между первым и вторым индексом. Если второй индекс не указан, возвращается только символ, идентифицируемый посредством первого индекса. Если указано несколько диапазонов индексов, возвращается список, содержащий соответствующие фрагменты Возвращает значение указанной опции изображения Запрашивает или устанавливает конфигурацию включенного изображения Создает включаемое изображение. Соответствующие опции описаны в табл. 36.7 Возвращает имена включенных изображений Возвращает числовое значение указанного индекса Включает символы в позиции, указанной посредством индекса. Если указаны дескрипторы, они связываются с новыми символами $t mark gravity имя ?направление? $t mark names $t mark next индекс $t mark previous индекс $t mark set имя индекс $t mark unset имя_1 ?имя_2 ...? $t scan mark x у $t scan dragto x у $t search ?опции? шаблон индекс ?индекс_остановки? $t see индекс Запрашивает или устанавливает направление для маркера. Допустимые значения направления: left и right Возвращает список определенных маркеров Возвращает маркер после указанного индекса Возвращает маркер перед указанным индексом Устанавливает маркер с указанным именем в позиции, определяемой посредством индекса Удаляет один или несколько маркеров Определяет точку фиксации для операции прокрутки Выполняет прокрутку для новой позиции Выполняет поиск на соответствие шаблону, начиная с указанного индекса. Возвращает индекс, с которого начинается последовательность, удовлетворяющая условиям поиска. Опции описаны в табл. 36.4 Изменяет отображение так, чтобы символ с указанным индексом отображался на экране
Глава 36. Текстовый компонент 767 Продолжение табл. 36.9 $t tag add имя Ставит дескриптор в соответствие последовательно индекс_1 ? индекс _2? сти символов, находящейся между первым и вторым ?индекс_1 индекс_2? индексом. Символ, соответствующий второму индек- ?индекс_1 индекс_2? су, в последовательность не включается. Если второй индекс не задан, операция выполняется только над символом, идентифицируемым посредством первого индекса $t tag bind имя Запрашивает или устанавливает связывание для де- ?последовательность? скриптора с указанным именем ?сценарий? $t tag cget имя опция Возвращает значение опции для дескриптора с указанным именем $t tag configure имя Запрашивает или устанавливает конфигурацию дескриптора с указанным именем $t tag delete Удаляет информацию о дескрипторах с указанными дескриптор _1 именами ?дескриптор_2 . . . ? $t tag lower Понижает приоритет дескриптора до самого низкого дескриптор ?образец? значения или устанавливает его ниже, чем приоритет дескриптора, указанного в качестве образца $t tag names ?индекс? Возвращает имена дескрипторов для указанного индекса или для всего компонента. Дескрипторы сортируются по возрастанию приоритета $t tag nextrange Возвращает список из двух индексов, которые опре- дескриптор индекс_1 деляют следующий диапазон текста с дескриптором, ?индекс_2? начало которого совпадает с первым индексом или следует после него, но находится перед вторым индексом. Если второй индекс не указан, вместо него используется конец содержимого компонента $t tag prevrange Возвращает список из двух индексов, которые опре- дескриптор индекс_1 деляют предыдущий диапазон текста с дескрипто- ?индекс_2? ром, конец которого совпадает с первым индексом или предшествует ему, но находится после второго индекса. Если второй индекс не указан, вместо него используется индекс 1.0 $t tag raise Повышает приоритет дескриптора до самого высоко- дескриптор ? образец? го значения или устанавливает его выше, чем приоритет дескриптора, указанного в качестве образца $t tag ranges Возвращает список, содержащий информацию о всех дескриптор диапазонах действия дескриптора
768 Часть IV. Компоненты Тк Окончание табл. 36.9 $t tag remove Разрывает связь между указанным дескриптором дескриптор индекс^ и последовательностью символов, находящейся меж- ?индекс_2? ?иядекс_1 ду первым и вторым индексом. Символ, соответ- индекс_2? ?индекс_1 ствующий второму индексу, в последовательность не индекс_2? . . . включается. Если второй индекс не задан, операция выполняется только для символа, идентифицируемого посредством первого индекса $t window cget окно Возвращает значение опции для указанного окна опция $t window conf ig окно Запрашивает или изменяет конфигурацию указанного окна. Окно задается с помощью пути Тк либо посредством индекса $t window create Создает включенное окно в позиции, указанной по- индекс параметры средством индекса $t window names Возвращает список окон, включенных в состав компонента $t xview Возвращает два числа в интервале от нуля до единицы, которые представляют объем текста слева за пределами экрана и объем отображаемого текста $t xview mo vet о часть Смещает отображаемые данные так, чтобы указанная часть текста размещалась за левой границей области отображения $t xview scroll число Прокручивает данные по горизонтали на указанное единица_измерения число единиц представления содержимого или страниц $t yview Возвращает два числа в интервале от нуля до единицы, которые представляют объем текста до начала отображаемой части и объем отображаемого текста $t yview mo vet о часть Смещает отображаемые данные так, чтобы указанная часть текста размещалась выше верхней границы области отображения $t yview scroll число Прокручивает данные на указанное число единиц единица_измерения представления содержимого или страниц $t yview ?-pickplace? Операция устарела. Вместо нее рекомендуется ис- индекс пользовать операцию see, которая предоставляет аналогичные возможности $t yview номер Операция устарела. Она располагает строку с указанным номером в верхней части экрана
Глава 36. Текстовый компонент 769 Атрибуты текстового компонента В табл. 36.10 описаны атрибуты текстового компонента, представленные именами ресурсов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В составе Tcl-команд атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра. Таблица 36.10. Атрибуты текстового компонента, представленные именами ресурсов autoSeparators background borderWidth cursor exportSelection font foreground height highlightBackground highlightColor highlightThickness insertBackground insertBorderWidth insertOffTime insertOnTime insertWidth Значение true (принимается по умолчанию) указывает на то, что разделитель undo должен включаться после каждой операции insert или delete. Если установлено значение false, за включение разделителей отвечает программа. Включение разделителей осуществляется посредством операции undo separator (Tk 8.4) Цвет фона. (Имя атрибута может быть сокращено до bg.) Дополнительное пространство вокруг текста Курсор мыши, отображаемый в пределах компонента Если значение атрибута равно true, выделенный текст рассматривается как экспортируемый Шрифт для отображения текста, используемый по умолчанию Цвет переднего плана. (Имя атрибута может быть сокращено до fg.) Высота, представленная как число строк Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода Цвет, используемый для подсветки, когда компонент имеет фокус ввода Толщина прямоугольника, указывающего на наличие фокуса ввода Цвет текстового курсора Толщина обрамления для текстового курсора при имитации трехмерного представления Время, в течение которого мигающий курсор отсутствует. Задается в миллисекундах Время, в течение которого мигающий курсор присутствует на экране. Задается в миллисекундах Толщина текстового курсора
770 Часть IV. Компоненты Тк Окончание табл. 36.10 maxUndo padX padY relief selectBackground selectForeground selectBorderWidth setGrid spacingl spacing2 spacing3 state tabs takeFocus undo width wrap xScrollCommand yScrollCommand Максимальное число действий пунктов отмены предыдущих операций в стеке undo. Нулевое или отрицательное значение предполагает неограниченный стек undo (Tk 8.4) Дополнительное пространство слева и справа от текста Дополнительное пространство выше и ниже текста Допустимые значения: flat, sunken, raised, groove, ridge и solid Цвет фона выделенного текста Цвет переднего плана для выделенного текста Толщина обрамления для выделенного фрагмента при имитации трехмерного представления Включение или отключение режима сетки Дополнительное пространство выше каждой строки, перенос которой не осуществлялся Пространство между частями строки, для которой был выполнен перенос Дополнительное пространство под строкой, перенос которой не производился Определяет полный доступ к компоненту (normal) или режим только для чтения (disabled) Табуляторы Управляет изменением фокуса ввода при действиях с клавиатурой Значение true разрешает, а значение false (принимается по умолчанию) запрещает механизм отмены предыдущих действий (Тк 8.4) Ширина отображаемого текста, представленная как число символов Режимы переноса строки: none, char или word Префикс Tcl-команды, используемой для горизонтальной прокрутки Префикс Tcl-команды, используемой для вертикальной прокрутки
Глава 37 Компонент canvas Компонент canvas — это компонент общего назначения, позволяющий отображать различные объекты, в том числе прямые линии, дуги, овалы, многоугольники, а также текст, изображения и встроенные окна. YYOJICT, или компонент canvas, отображает различные объекты, в частности различные типы линий и изображения. Каждому объекту может соответствовать связывание, в результате чего появляется возможность организовать отклик на действия пользователя или реализовать анимационные изображения. Объектам, находящимся на холсте, могут быть поставлены в соответствие дескрипторы, для которых задаются атрибуты и связывания. В данной главе описываются различные типы объектов холста. В главе 50 рассматривается интерфейс, позволяющий создавать новые объекты canvas из программ, написанных на языке С. Координаты холста С холстом связывается система координат, начало которой расположено в левом верхнем углу компонента. Координата х возрастает при движении вправо, а координата у — при движении вниз. Выбранные координаты определяют позицию и в некоторых случаях размер холста. Различные объекты характеризуются разными наборами координат. Например, для текстового объекта определяются две координаты, xl и yl, с помощью которых задается точка фиксации. Линии может соответствовать несколько пар координат, которые указывают на конечные точки сегментов. Координаты задаются при создании объекта, а затем могут быть изменены с помощью операции coords. По умолчанию координаты определяются в пикселях. Для того чтобы пе-
772 Часть IV. Компоненты Тк рейти от пикселей к другим единицам измерения, надо дополнить значение координаты одной из следующих букв: • с — сантиметры; • i — дюймы; • m — миллиметры; • р — пункты (1/72 дюйма). Команда tk scale, которая рассматривается далее в данной главе, изменяет отображение пикселей в другие единицы измерения. Ее надо использовать перед созданием холста. Атрибуты width и height определяют размер отображаемой области. Атрибут scrollRegion компонента салуав задает границы холста. Значением этого атрибута являются четыре числа, которые определяют координаты левого верхнего и правого нижнего угла холста. Если вы не зададите атрибут scrollRegion, то по умолчанию размеры холста будут выбраны равными размерам отображаемой области. В листинге 37.1 приведен пример создания холста с размерами прокручиваемой области 1000x400 и с отображаемой областью 300x200. С холстом связываются две полосы прокрутки: вертикальная и горизонтальная. Листинг 37.1. Холст большого размера с возможностью прокрутки proc ScrollecLCanvas { с args } { frame $c eval {canvas $c.canvas \ -xscrollcommand [list $c.xscroll set] \ -yscrollcommand [list $c.yscroll set] \ -highlightthickness 0 \ -borderwidth 0} $args scrollbar $c.xscroll -orient horizontal \ -command [list $c.canvas xview] scrollbar $c.yscroll -orient vertical \ -command [list $c.canvas yview] grid $c.canvas $c.yscroll -sticky news grid $c.xscroll -sticky ew grid rowconfigure $c 0 -weight 1 grid columnconfigure $c 0 -weight 1 return $c.canvas } Scrolled_Canvas .c -width 300 -height 200 \ -scrollregion {0 0 1000 400}
Глава 37. Компонент canvas 773 => .с.canvas pack . с -fill both -expand true Обрамление отображается внутри холста. В листинге 37.1 заданы нулевые значения опций -highlightthickness и -borderwidth. Если размеры обрамления отличаются от нуля, обрамление занимает часть отображаемой области. Если вы хотите увеличить толщину рамки, для этого надо либо использовать другой фрейм, либо увеличить размеры холста. В противном случае обрамление может отсечь часть объекта, отображаемого на холсте. Программа Hello, World! В листинге 37.2 показан пример создания объекта, который можно перемещать с помощью мыши. Данный пример демонстрирует использование дескрипторов для настройки объектов. Для дескриптора movable создаются связывания, которые позволяют перетаскивать объект. Таким образом, любой объект, помеченный с помощью дескриптора movable, наследует эту возможность. При создании данного примера использовалась процедура Scrolled_Canvas из листинга 37.1. Используя холст с возможностью прокрутки, вы можете реализовать отображение координат видимой области, значения которых предоставляет связанная команда, в координаты холста, используемые локальными объектами. Листинг 37.2. Простой пример использования холста proc CanvasHello {} { set can [Scrolled.Canvas .c -width 400 -height 100 \ -scrollregion {0 0 800 400}] pack .c -fill both -expand true # Создание текстового объекта на холсте $can create text 50 50 -text "Hello, World!" -tag movable # Связывания для дескриптора movable $can bind movable <Button-l> {CanvasMark °/0x °/0y °/0W} $can bind movable <Bl-Motion> {CanvasDrag °/0x °/0y °/0W} } proc CanvasMark { x у can} { global canvas # Отображение координат видимой области в координаты холста set х [$can canvasx $x]
774 Часть IV. Компоненты Тк set у [$can canvasy $y] # Запоминание объекта и его расположения set canvas($can,obj) [$can find closest $x $y] set canvas($can,x) $x set canvas($can,y) $y } proc CanvasDrag { x у can} { global canvas # Отображение координат видимой области в координаты холста set х [$can canvasx $x] set у [$can canvasy $y] # Перемещение текущего объекта set dx [expr $x - $canvas($can,x)] set dy [expr $y - $canvas($can,y)] $can move $canvas($can,obj) $dx $dy set canvas($can,x) $x set canvas(Scan,y) $y 2 В листинге 37.2 приведен код, предназначенный для создания текстового объекта, с которым связывается дескриптор movable. .с create text 50 50 -text "Hello, World!" -tag movable Первый параметр, указанный после имени create, задает тип элемента, а последующие параметры зависят от типа создаваемого объекта. Для каждого объекта, предназначенного для отображения на холсте, задаются координаты, за которыми могут следовать пары атрибут-значение. Координаты задаются в виде отдельных параметров. В версии Тк 8.3 была реализована возможность указывать координаты в виде списка; в этом случае для описания координат достаточно одного параметра. Описание атрибутов объектов, отображаемых на холсте, приводятся далее в этой главе. Для указания расположения текстового объекта надо задать две координаты. Дескрипторы холста Операция create возвращает идентификатор созданного объекта. Однако в программах для выполнения действий с объектами вместо идентификатора чаще всего используются дескрипторы. С одним дескриптором может быть связано несколько объектов, а одному объекту может соответствовать несколько дескрипторов. Для дескрипторов можно определять связывания и задавать атрибуты. Они будут применены к соответствующим объектам.
Глава 37. Компонент canvas 775 Именем дескриптора может быть практически любая строка. Нельзя лишь, чтобы в ней содержались пробелы, так как это вызовет проблемы при грамматическом разборе. Кроме того, нельзя применять в качестве имен дескрипторов числа, поскольку они могут быть интерпретированы как идентификаторы объектов. Существуют два предопределенных дескриптора: current и all. Дескриптор current применяется к любому объекту, на который указывает мышь. Дескриптор all воздействует на все объекты на холсте. При вызове многих операций с холстом указываются параметры, идентифицирующие объекты. Значениями этих параметров могут быть либо имена дескрипторов, либо числовые идентификаторы, возвращаемые в результате выполнения операции create. Начиная с версии Тк 8.3 появилась возможность задавать в качестве единичного параметра логические выражения, в которых дескрипторы объединяются с помощью операторов && (И), I I (ИЛИ), ~ (исключающее ИЛИ) и ! (отрицание). Для объединения выражений можно использовать скобки. Например, чтобы для всех объектов, связанных с дескриптором highlight или warning, изменить цвет заполнения на красный, надо выполнить следующую команду: Scan itemconfigure {highlight I I warning} -fill red Для того чтобы объекты, соответствующие дескриптору plotl или plot2, за исключением объектов, помеченных дескриптором fixed, могли перемещаться, надо использовать приведенную ниже команду. $can move {(plotl || plot2) && !fixed} 50 0 Код, определяющий поведение объектов, помеченных дескриптором movable, приведен в листинге 37.2. При нажатии левой кнопки мыши включается режим перетаскивания. Для того чтобы перетащить объект в новую позицию, надо перемещать мышь, удерживая нажатой левую кнопку. Путь к холсту (°/oW) передается процедурам CanvasMark и CanvasDrag, которые могут использоваться с различными холстами. Вместо ключевых слов °/0х и °/0у в момент возникновения события подставляются координаты х и у. $can bind movable <Button-l> {CanvasMark °/0x °/0y °/0W} $can bind movable <Bl-Motion> {CanvasDrag °/0x °/ey °/eW} Процедуры CanvasMark и CanvasDrag позволяют перетаскивать объект в пределах холста. Поскольку CanvasMark используется для любого объекта, помеченного дескриптором movable, она должна сначала обнаружить объект, на котором находится курсор мыши в момент нажатия кнопки. В первую очередь координаты области видимости отображаются в координаты холста с помощью операций canvasx и canvasy. set x [$can canvasx x] set у [$can canvasy y]
776 Часть IV. Компоненты Тк После этого можно использовать операцию find. set canvas($can,obj) [$can find closest $x $y] Реальное перемещение осуществляется в процедуре CanvasDrag с помощью операции move. $can move $canvas($can,obj) $dx $dy По мере необходимости можно создавать другие типы объектов, допускающие перетаскивание. $can create rect 10 10 30 30 -fill red -tag movable $can create line 1 1 40 40 90 60 -width 2 -tag movable $can create poly 1 1 40 40 90 60 -fill blue -tag movable Процедуры CanvasMark и CanvasDrag могут использоваться с любым холстом. Они работают с глобальным массивом canvas, в котором хранится информация о состоянии компонента. Для того чтобы исключить конфликт между различными холстами в составе приложения, в качестве индексов массива выбраны имена путей. Если подобный стиль программирования войдет у вас в привычку, то вы сможете относительно просто создавать повторно используемый код. Дескрипторы, предназначенные для холста, не допускают постоянное хранение. Дескрипторы, предназначенные для холста, не поддерживают непосредственную настройку, как это допускалось при работе с дескрипторами текстового компонента. В компоненте text дескрипторы были полностью независимы от текста. При работе с текстовым компонентом вы могли сначала настроить дескриптор, а затем связать его с компонентом. В случае холста конфигурировать можно только тот дескриптор, который уже применялся к объекту. Если вы попытаетесь настроить дескриптор, не имеющий связи с объектом, установки будут утеряны. Если удалить все объекты, совместно использующие дескриптор, установки для данного дескриптора не сохранятся. Линейный регулятор для определения минимального и максимального значения В данном разделе рассматривается код, с помощью которого создается компонент, напоминающий линейный регулятор. От обычного линейного регулятора этот компонент отличается тем, что содержит два ползунка. Код, предназначенный для создания компонента, представлен в листинге 37.3.
Глава 37. Компонент canvas 777 С помощью ползунков устанавливается минимальное и максимальное значение. Очевидно, что минимальное значение не может быть больше максимального. В данном примере на холсте отображаются три прямоугольника. Первый из них используется в качестве направляющих для ползунков и представляет диапазон возможных значений. Остальные два прямоугольника — это маркеры, которые представляют минимальное и максимальное значение. Под ползунками отображаются текущие установленные значения. Данный пример демонстрирует использование четырех операций с холстом: bbox, coords, scale и move. Операция bbox возвращает ограничивающий прямоугольник для одного или нескольких объектов, соответствующих определенному дескриптору. Операция coords запрашивает или устанавливает координаты объекта. Операция scale изменяет размеры объекта, а операция move перемещает его в новую позицию. Вместо идентификаторов объектов рекомендуется использовать дескрипторы. Тщательный выбор имен дескрипторов позволяет создать "самодокументированный код". Кроме того, при использовании дескрипторов упрощаются последующие изменения реализации объектов. В коде, представленном в листинге 37.3, идентификаторы объектов не используются. Вместо этого каждому объекту с помощью дескриптора ставится в соответствие символьное имя. Некоторые дескрипторы специально созданы для представления классов объектов. В данном случае дескриптор all используется для перемещения всех объектов и, кроме того, позволяет найти ограничивающий прямоугольник для изображения. Левый прямоугольник, представляющий левый ползунок, и текст, отображающийся под ним, связаны с дескриптором left. Они перемещаются одновременно, поэтому для них определено одно и то же связывание. Аналогично, правый прямоугольник и соответствующий текст совместно используют дескриптор right. Кроме того, для каждого объекта задан уникальный дескриптор, поэтому любой объект можно обрабатывать независимо от других. В данном примере используются имена дескрипторов slider, lbox, lnum, rbox и mum. Листинг 37.3. Пример использования холста для установки минимального и максимального значения proc Scale2 {w min max {width {}} } { global scale2 if {$width == {}} { # Установка ширины в пикселях set width [expr $max - $min]
778 Часть IV. Компоненты Тк } # Сохранение параметров set scale2($w,scale) [expr ($max-$min)/$width.O] set scale2($w,min) $min ;# Текущее минимальное значение set scale2($w,max) $max set scale2($w,Min) $min ;# Нижняя граница масштабирования set scale2($w,Max) $max set scale2($w,L) 10 set scale2($w,R) [expr $width+10] # Расстояние между левыми границами прямоугольников # ргшно 100. Ширина прямоугольника равна 10, следовательно, # длина полосы, по которой они перемещаются, должна быть # равна 110. # Левый прямоугольник выступает вверх, а правый - вниз. canvas $w $w create rect 0 0 110 10 -fill grey -tag slider $w create rect 0 -4 10 10 -fill black -tag {left lbox} $w create rect 100 0 110 14 -fill red -tag {right rbox} $w create text 5 16 -anchor n -text $min -tag {left lnum} $w create text 105 16 -anchor n -text $max \ -tag {right mum} -fill red # Растягивание или сжимание полосы до нужного размера set scale [expr ($width+10) / 110.0] $w scale slider 0 0 $scale 1.0 # Перемещение правого прямоугольника и текста # в соответствии с новым значением длины set nx [lindex [$w coords slider] 2] $w move right [expr $nx-110] 0 # Помещение в область видимости $w move all 10 10 # Холст должен соответствовать изображению set bbox [$w bbox all] set height [expr [lindex $bbox 3]+4] $w config -height $height -width [expr $width+30] # Связывания для выполнения перетаскивания
Глава 37. Компонент canvas 779 $w bind left <Button-l> {Scale2Mark °/0W °/,x lbox} $w bind right <Button-l> {Scale2Mark °/0W °/0x rbox} $w bind left <Bl-Motion> {Scale2Drag °/,W °/0x lbox} $w bind right <Bl-Motion> {Scale2Drag °/0W %x rbox} При создании направляющих используются абсолютные координаты, после чего объект масштабируется до необходимых размеров. Возможен и другой подход, состоящий в вычислении координат на основе требуемой ширины. Для простоты восприятия при начальной компоновке, вместо того, чтобы вводить дополнительные переменные или применять команду expr, используются числовые значения. Операция scale изменяет размеры полосы, выполняющей функции направляющих, для получения требуемой длины. При вызове операции scale задаются контрольная точка (в данном случае это точка с координатами 0, 0) и коэффициенты масштабирования по осям х и у. Коэффициент масштабирования вычисляется на основе параметра width; при этом принимается во внимание дополнительное значение длины (10). set scale [expr ($width+10) / 110.0] $w scale slider 0 0 $scale 1.0 Операция move изменяет позицию правого ползунка и соответствующего ему текста. Если прямоугольники, изображающие правый и левый ползунок, подвергаются масштабированию, их форма остается неизменной. Операция coords возвращает список из четырех чисел: xl yl х2 у2. Расстояние, на которое должен быть перемещен объект, — это лишь разность между новой координатой и значением, используемым при построении направляющих. Прямоугольник и соответствующий ему текст связаны с одним и тем же дескриптором right, поэтому для их перемещения достаточно одной операции move. set nx [lindex [$w coords slider] 2] $w move right [expr $nx-110] 0 После создания направляющие сдвигаются относительно точки (0, 0), которой соответствует левый верхний угол холста. Операция bbox возвращает четыре координаты, xl yl х2 у2, которые определяют ограничивающий прямоугольник для объектов, связанных с данным дескриптором. В данном
780 Часть IV. Компоненты Тк примере значение yl равно 0, поэтому у2 — это высота изображения. Информация, возвращаемая bbox, может незначительно отличаться от реальных данных, поэтому во избежание отсечения текста к высоте прибавляется несколько пикселей. Ширина вычисляется с учетом размеров ползунков; все изображение сдвигается на 10 пикселей, и еще 10 пикселей добавляется к правой части. set bbox [$w bbox all] set height [expr [lindex $bbox 3]+4] $w config -height $height -width [expr $width+30] Связывания необходимы для прямоугольников, представляющих левый и правый ползунок, а также соответствующий им текст. Поэтому реально связывание создается для дескрипторов left и right. Это означает, что, для того чтобы переместить ползунок, можно перетаскивать как представляющий его прямоугольник, так и находящийся под ним текст. Процедурам, связанным с событиями, передается путь к компоненту, поэтому данные процедуры могут работать с несколькими регуляторами, входящими в состав интерфейса. $w bind left <Button-l> {Scale2Mark °/0W °/0x lbox} $w bind right <Button-l> {Scale2Mark °/eW °/0x rbox} $w bind left <Bl-Motion> {Scale2Drag °/0W °/0x lbox} $w bind right <Bl-Motion> {Scale2Drag °/0W %x rbox} Листинг 37.4. Перемещение ползунков регулятора proc Scale2Mark { w x what } { global scale2 # Запоминание точки фиксации для перетаскивания set scale2($w,$what) $x } proc Scale2Drag { w x what } { global scale2 # Вычисление приращения и обновление точки фиксации set xl $scale2($w,$what) set scale2($w,$what) $x set dx [expr $x - $xl] # Определение текущего местонахождения прямоугольников set rx [lindex [$w coords rbox] 0] set lx [lindex [$w coords lbox] 0]
Глава 37. Компонент canvas 781 if {$what == "lbox"} { # Перемещение ограничивается левой границей # и правым ползунком, if {$lx + $dx > $rx} { set dx [expr $rx - $lx] set scale2($w,$what) $rx } elseif {$lx + $dx < $scale2($w,L)} { set dx [expr $scale2($w,L) - $lx] set scale2($w,$what) $scale2($w,L) } $w move left $dx 0 # Обновление минимального значения # и соответствующего текста set lx [lindex [$w coords lbox] 0] set scale2($w,min) [expr int($scale2($w,Min) + \ ($lx-$scale2($w,L)) * $scale2($w,scale))] $w itemconfigure lnum -text $scale2($w,min) } else { # Перемещение ограничивается правой границей # и левым ползунком if {$rx + $dx < $1х} { set dx [expr $lx - $rx] set scale2($w,$what) $lx } elseif {$rx + $dx > $scale2($w,R)} { set dx [expr $scale2($w,R) - $rx] set scale2($w,$what) $scale2($w,R) } $w move right $dx 0 # Обновление максимального значения # и соответствующего текста set rx [lindex [$w coords right] 0] set scale2($w,max) [expr int($scale2($w,Min) + \ ($rx-$scale2($w,L)) * $scale2($w,scale))] $w itemconfigure mum -text $scale2($w,max) } } proc Scale2Value {w} { global scale2 # Возврат текущих значений двойного ползункового регулятора
782 Часть IV. Компоненты Тк return [list $scale2($w,min) $scale2($w,max)] 2 Процедура Scale2Mark инициализирует позицию фиксации scale2($w,$what), a Scale2Drag использует эти данные для того, чтобы определить, на какое расстояние переместился курсор мыши. Изменение позиции, dx, ограничивается так, что ползунки не удается переместить за пределы направляющих. При использовании ограничений точка фиксации обновляется. Это значит, что ползунок не будет двигаться до тех пор, пока курсор мыши не вернется на него. (Попробуйте закомментировать присваивание scale2($w,$what) в выражении if.) После перемещения ползунка и связанного с ним текста вычисляется значение соответствующего параметра. Процедура Scale2Value запрашивает текущее значение регулятора. Объекты холста В нескольких последующих разделах описываются встроенные типы объектов для холста: arc, bitmap, image, line, oval, polygon, rectangle, text и window. Каждый объект содержит свой набор атрибутов; некоторые из атрибутов встречаются во всех типах объектов или в большинстве из них. В табл. 37.1 описаны наиболее распространенные атрибуты. Все атрибуты, начинающиеся со слов active и disabled, а также некоторые другие были реализованы в Tk 8.3. Таблица 37.1. Атрибуты объектов холста -dash шаблон -activedash Шаблон отображения штриховой линии, или рам- шаблон -disableddash ка, в состоянии normal, в активном состоянии шаблон (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3) -dashof f set смещение Смещение от начала линии для шаблона, задаваемого с помощью опции -dash (Tk 8.3) -fill цвет -activefill Цвет внутренней части объекта в состоянии цвет -disabledf ill цвет normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -fill) -stipple битовая, карта Маска для заполнения объекта в состоянии normal, -activestipple в активном состоянии (когда курсор мыши нахо- битовая_карта дится на объекте) и в состоянии disabled (Tk 8.3, -disabledstipple за исключением опции -stipple) битовая, карга
Глава 37. Компонент canvas 783 Окончание табл. 37.1 -offset смещение -outline цвет -activeoutline цвет -disabledoutline цвет -outlinestipple битов ая_ карта -activeoutlinestipple битов ая_карта -disabledoutlinestipple битов ая_ карта -width число -activewidth число -disabledwidth число -state состояние -tags список^де скрипторов Смещение маски. Задается как координаты х,у. Может определять ориентацию и представлять собой значение n, ne, e, se, s, sw, w, nw или center. Координаты х,у задают смещение от начала координат холста. Указав символ #, можно указать координаты окна верхнего уровня (Тк 8.3) Цвет рамки в состоянии normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -outline) Маска рамки в состоянии normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -outlinestipple) Толщина рамки в экранных координатах в состоянии normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -width) Допустимые значения: normal, disabled и hidden. Значение данной опции переопределяет значение атрибута state холста (Tk 8.3) Список дескрипторов для объекта Каждый объект поддерживает атрибут tags, с помощью которого можно пометить объект списком символьных имен. Для большинства объектов, даже для объекта text, можно задать цвет с помощью атрибута fill, в то время как атрибуты foreground и background поддерживают только объект bitmap. Если объект содержит обрамление, цвет рамки задается с помощью опции -outline, а толщина — посредством опции -width. Начиная с Tk 8.3 для линий и объектов с обрамлением используется атрибут dash, обеспечивающий отображение линий и рамок пунктиром. Состояние холста и его объектов В Tk 8.3 для компонентов canvas и объектов холста был добавлен атрибут state. Значением атрибута state компонента canvas может быть normal (принимается по умолчанию) или disabled. Этот атрибут задает состояние объектов холста, устанавливаемое по умолчанию. Если значением атрибу-
784 Часть IV. Компоненты Тк та state объекта является пустая строка, он наследует состояние, заданное с помощью атрибута state холста. Если необходимо, вы можете переопределить "глобальное" состояние холста, установив значение атрибута state для конкретного объекта равным normal, disabled или hidden. В нормальном состоянии объект является видимым, и определенные для него связывания функционируют. Кроме того, если курсор мыши находится на объекте с состоянием normal, этот объект активизируется и для него действуют все атрибуты, установленные с помощью опций -active*. Как и следует ожидать, объект в состоянии hidden невидим и связывания для него не действуют. Объект в состоянии disabled отображается на экране, но связывания для него заблокированы; при помещении на него курсора мыши он не активизируется. Дополнительно для объекта disabled вступают в действие атрибуты, заданные посредством опций -disabled*. Штриховые линии В Тк 8.3 была реализована возможность рисования и представления обрамлений объектов штриховой линией. Основным атрибутом, управляющим отображением штриховой линией, является dash, однако для объектов, находящихся в некоторых состояниях, действуют также атрибуты activedash и disableddash. Значением каждого из приведенных выше атрибутов является шаблон штриховой линии. Формат штриховой линии можно определить различными способами. Первый способ состоит в задании формата с помощью списка целых чисел. Каждый элемент списка представляет число пикселей в сегменте линии. Сегменты, соответствующие нечетным элементам списка, выводятся на экран, а сегменты, определяемые четными элементами, не видны. Приведенная ниже команда задает рисование линии, которая состоит из штрихов длиной 6 пикселей, разделенных интервалами в 2 пикселя. $с create line -dash {6 2} Формат штриховой линии может быть задан в виде набора символов. Назначение символов приведено в табл. 37.2. Таблица 37.2. Символы, предназначенные для формирования штриховых линий Штрих составляет половину длины последующей пустой области , Штрих равен по длине последующей пустой области Штрих в полтора раза длиннее последующей пустой области Штрих в два раза длиннее последующей пустой области пробел Удваивает длину пустой области
Глава 37. Компонент canvas 785 В качестве примера можно привести шаблон {_. ,}, который приблизительно равен шаблону {842844}. Основное отличие символьного шаблона от шаблона, задаваемого в виде списка целых чисел, состоит в том, что символьный шаблон сохраняет форму штрихов. Это означает, что значения длины штрихов и интервалов между ними перед отображением умножаются на толщину линии. Благодаря этому обеспечивается отображение точки именно в виде точки, а штрих не потеряет свою форму, независимо от толщины линии. Атрибут dashof f set определяет начальное смещение шаблона, определенного с помощью опции -dash (смещение задается в пикселях). Некоторые шаблоны поддерживаются не на всех платформах. Если система поддерживает лишь ограниченный набор шаблонов для определения штриховых линий, то используется шаблон, наиболее.близкий к заданному. Например, в системе Windows шаблоны {.},{,},{. }и{, } отображаются одинаково. Дуги Дуга является частью овала. Овал задается с помощью ограничивающего прямоугольника, который, в свою очередь, определяется четырьмя координатами. Для определения дуги, которая реализуется с помощью объекта arc, помимо ограничивающего прямоугольника используются два угла: начальный угол (опция -start) и угол протяженности (опция -extent). Область, ограниченная овалом, может быть заполненной или незаполненной. Существуют три способа заполнения области. Согласно стилю pieslice конечные точки дуги соединяются прямыми линиями с центром овала. Стиль chord указывает на то, что конечные точки дуги должны быть соединены между собой прямой. И, наконец, стиль arc задает рисования самой дуги без заполнения. В листинге 37.5 показан фрагмент кода, с помощью которого создаются три дуги. Для них задан один и тот же ограничивающий прямоугольник, но разные углы и стили. Листинг 37.5. Отображение дуг # $с представляет холст $с create arc 10 10 100 100 -start 45 -extent -90 \ -style pieslice -fill orange -outline black $c create arc 10 10 100 100 -start 135 -extent 90 \ -style chord -fill blue -outline white -width 4 $c create arc 10 10 100 100 -start 255 -extent 45 \ -style arc -outline black -width 3
786 Часть IV. Компоненты Тк Объект arc поддерживает все атрибуты, перечисленные в табл. 37.1. Кроме того, в табл. 37.3 указаны дополнительные атрибуты, имеющие отношение только к данному объекту. Таблица 37.3. Атрибуты объекта arc -extent градусы -start градусы -style стиль Длина дуги в направлении против часовой стрелки Начальный угол дуги Допустимые значения: pieslice, chord, arc Битовые карты Битовая карта — это простое изображение, все точки которого отображаются либо цветом переднего плана, либо цветом фона. Каждая точка представляется одним битом. Если вы не укажете цвет фона, пиксели, соответствующие фону, отображаться не будут и вместо них будет виден фон холста. Для размещения битовой карты на холсте используются две координаты и точка фиксации. Размер объекта определяется его данными. Сама битовая карта задается с помощью символьного имени либо посредством имени файла, содержащего данные. Если имя начинается с символа @, это означает, что битовая карта задана с помощью имени файла. Код, приведенный в листинге 37.6, демонстрирует работу с битовыми картами, встроенными в Тк. В главе 50 описывается С-интерфейс для регистрации битовых карт под именами. Листинг 37.6. Работа с битовыми картами set о [$с create bitmap 10 10 -bitmap Ocandle.xbm -anchor nw\ -background white -foreground blue] set x [1index [$c bbox $o] 2] ;# Правая граница битовой карты foreach builtin {error grayl2 gray50 hourglass \
Глава 37. Компонент canvas 787 info questhead question warning} { incr x 20 set о [$с create bitmap $x 30 -bitmap $builtin -anchor c] set x [lindex [$c bbox $o] 2] Из атрибутов, перечисленных в табл. 37.1, объект bitmap поддерживает только атрибуты state и tags. В табл. 37.4 указаны дополнительные атрибуты, ориентированные только на битовые карты. Таблица 37.4. Атрибуты битовых карт -anchor позиция -background цвет -activebackground цвет -disabledbackground цвет -bitmap имя -activebitmap имя -disabledbitmap имя -foreground цвет -activeforeground цвет -disabledforeground цвет Точка фиксации: с (принимается по умолчанию), n, ne, e, se, s, sw, w или nw Цвет фона (для нулевых битов) в состоянии normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -background) Битовая карта для отображения в состоянии normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -bitmap) Цвет переднего плана (для битов, значения которых равны 1), отображаемый в состоянии normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -foreground) Изображения Действия с объектами image предполагают использование универсальных средств Tk, предназначенных для работы с изображениями. В первую очередь вам надо определить изображение с помощью команды image (данная команда рассматривается в главе 41). После этого вам остается лишь указать его расположение, точку фиксации и необходимые дескрипторы. Размер и цвет задаются при создании изображения. При переопределении изображе-
788 Часть IV. Компоненты Тк ния все его экземпляры автоматически обновляются. В листинге 37.7 показан код, посредством которого создается одно изображение, а затем на холсте размещаются шесть его экземпляров. Листинг 37.7. Вывод изображений с помощью компонента canvas image create bitmap hourglass2 \ -file hourglass.bitmap -maskfile hourglass.mask \ -background white -foreground blue for {set x 20} {$x < 300} {incr x 20} { $c create image $x 10 -image hourglass2 -anchor nw incr x [image width hourglass2] 2 Из атрибутов, перечисленных в табл. 37.1, объект image поддерживает только state и tags. Кроме того, в табл. 37.5 указаны дополнительные атрибуты, имеющие отношение только к объекту image. Таблица 37.5. Атрибуты изображений -anchor позиция Точка фиксации: с (принимается по умолчанию), n, ne, e, se, s, sw, w или nw -image имя -activeimage Имя изображения, используемое в состоянии имя -disabledimage имя normal, в активном состоянии (когда курсор мыши находится на объекте) и в состоянии disabled (Tk 8.3, за исключением опции -image) Линии Линия задается с помощью двух или нескольких наборов координат. Каждый набор определяет конечную точку сегмента линии. Существуют различные способы объединения сегментов; кроме того, вся линия может быть представлена не как набор отрезков прямых, а в виде сплайна. В следующем примере рисование линии осуществляется в два этапа. На первом этапе происходит рисование линий, состоящих из одного сегмента. После этого набор отрезков заменяется на одну кривую, построенную как сплайн.
Глава 37. Компонент canvas 789 Листинг 37.8. Пример рисования линий средствами компонента canvas proc Strokelnit {} { canvas .с ; pack .с bind .с <Button-l> {StrokeBegin °/.W У.х У.у} bind .c <Bl-Motion> {Stroke e/.W У,х e/ey} bind .с <ButtonRelease-l> {StrokeEnd e/.W У,х У.у} } proc StrokeBegin { w x у } { global stroke catch {unset stroke} set stroke(N) 0 set stroke(0) [list $x $y] } proc Stroke { w x у } { global stroke set coords $stroke($stroke(N)) lappend coords $x $y incr stroke(N) set stroke($stroke(N)) [list $x $y] eval {$w create line} $coords {-tag segments} } proc StrokeEnd { w x у } { global stroke set coords {} for {set i 0} {$i <= $stroke(N)} {incr i} { append coords $stroke($i) " " } $w delete segments
790 Часть IV. Компоненты Тк eval {$w create line} $coords \ {-tag line -joinstyle round -smooth true -arrow last} } В примере, приведенном в листинге 37.8, для хранения точек, задаваемых при построении линии, используется массив stroke. По окончании формирования отрезков точки объединяются в список. Команда eval включает этот список в состав команды create line. Как вы помните, при обработке нескольких параметров eval выполняет их конкатенацию. Остальные части команды create line защищены фигурными скобками, поэтому обрабатываются только единожды (см. главу 10). Заметьте, что при работе Тк 8.3 такие действия не являются необходимыми; команда create line может принимать список координат как один параметр. Атрибут arrow указывает на то, что к концу линии надо добавлять стрелку. Если вы проверите работу данного кода, то заметите, что стрелка не всегда ориентируется так, как вы того ожидаете. Причина заключается в том, что при отпускании кнопки мыши часто генерируется несколько точек, расположенных близко друг к другу. Процедура StrokeEnd воспринимает те же координаты х и у, что и при последнем вызове Stroke. Если дублирующие друг друга точки добавить к концу списка, стрелка не будет сформирована. По мере необходимости вы можете организовать фильтрацию, в результате которой исключались бы точки, расположенные слишком близко друг к другу. Объект line поддерживает все атрибуты, перечисленные в табл. 37.1, за исключением семейств атрибутов outline и outlinestipple, а также атрибута offset. Следует помнить, что цвет, которым отображается линия, задает атрибут fill, а не outline. В табл. 37.6 указаны дополнительные атрибуты, поддерживаемые только объектом line. Атрибут capstyle указывает способ отображения концов линии. Атрибут joinstyle управляет объединением сегментов линии. Атрибуты capstyle и joinstyle определены в системе X Window и могут быть не реализованы на платформах Macintosh и Windows. Таблица 37.6. Атрибуты линии -arrow расположение Расположение стрелок. Допустимые значения: попе, first, last и both -arrowshape {a b с} Три параметра, описывающие форму стрелки. В данном случае с — это ширина, b — общая длина, а — длина той части стрелки, которая соединяется с линией (например, 8 10 3)
Глава 37. Компонент canvas 791 Окончание табл. 37.6 -capstyle тип Концы линий. Допустимые значения: butt, projecting и round -joinstyle тип Соединения линий. Допустимые значения: bevel, miter и round -smooth Если данный атрибут имеет значение true, отобража- логическое_значение ется сплайн -splinesteps число Число сегментов линии, аппроксимируемых сплайном Овалы Овал определяется посредством двух пар координат. Эти координаты задают ограничивающий прямоугольник. Если ограничивающий прямоугольник представляет собой квадрат, то овал превращается в круг. Разработчик может задавать цвет для заполнения овала и рисования его контура. Примеры построения овалов приведены в листинге 37.9. Листинг 37.9. Отображение овалов на холсте $с create oval 10 10 80 80 -fill red -width 4 $c create oval 100 10 150 80 -fill blue -width 0 $c create oval 170 10 250 40 -fill black -stipple grayl2 Объект oval поддерживает все атрибуты, перечисленные в табл. 37.1. Дополнительные атрибуты, специально предназначенные для овала, не определены. Многоугольники Многоугольник — это замкнутая фигура, задаваемая набором точек. Каждая точка соответствует вершине прямоугольника. При необходимости линии, сходящиеся в вершинах, могут соединяться со скруглениями. В листин-
792 Часть IV. Компоненты Тк ге 37.10 формируется изображение стоп-сигнала. Центр изображения размещается в точке (0, 0), после чего вся фигура перемещается на холст. Листинг 37.10. Формирование изображения на основе многоугольника $с create poly 20 -40 40 -20 40 20 20 40 -20 40 \ -40 20 -40 -20 -20 -40 -fill red \ -outline white -width 5 $c create text 0 0 -text STOP -fill white \ -font {helvetica 18 bold} $c move all 50 50 Объект polygon поддерживает все атрибуты, перечисленные в табл. 37.1. В табл. 37.7 указаны дополнительные атрибуты, поддерживаемые только данным объектом. Таблица 37.7. Атрибуты многоугольника -joinstyle тип Соединения линий. Допустимые значения: bevel, miter и round -smooth Если данный атрибут имеет значение true, точки ап- логическое_значение проксимируются сплайном -splinesteps число Число сегментов, аппроксимируемых сплайном Прямоугольники Объект rectangle определяется двумя парами координат, которые задают расположение противоположных углов. Для данного объекта может быть задан цвет заполнения и цвет контура. Если цвет заполнения не указан, внутри прямоугольника отображается фон холста или другого объекта. Если заполнение осуществляется посредством маски, цвет фона холста также выводится в точках, соответствующих нулевым битам битовой карты. Если
Глава 37. Компонент canvas 793 вам необходимо, чтобы прямоугольник, заполняемый по маске, полностью заслонял собой нижележащие объекты, необходимо создать еще один такой же прямоугольник. В листинге 37.11 показан пример рисования прямоугольника, который пользователь может перетаскивать с помощью мыши. Чтобы прямоугольник можно было удалить при перетаскивании, необходимо запоминать последнее его расположение. Листинг 37.11. Перетаскивание прямоугольника proc Boxlnit {} { canvas .с -bg white ; pack .с bind .с <Button-l> {BoxBegin %W %x %y> bind .c <Bl-Motion> {BoxDrag °/0W °/0x °/0y> } proc BoxBegin { w x у } { global box set box($w,anchor) [list $x $y] catch {unset box($w,last)} } proc BoxDrag { w x у } { global box catch {$w delete $box($w,last)} set box($w,last) [eval {$w create rect} $box($w,anchor) \ {$x $y -tag box}] 2 В данном примере для запоминания начальной точки используется элемент box($w, anchor). В него записывается список, содержащий координаты х и у Команда eval представляет элементы списка как параметры команды create rect. Заметьте, что при работе Тк 8.3 такие действия не являются необходимыми; команда create rect может принимать список координат как один параметр. Объект rectangle поддерживает все атрибуты, перечисленные в табл. 37.1. Дополнительные атрибуты, ориентированные на данный объект, отсутствуют. Текстовые объекты Текстовые объекты обеспечивают дополнительную возможность для отображения и изменения текста. Данные объекты поддерживают выделение и редактирование, кроме того, позволяют отображать последовательность символов в виде нескольких строк. Размещение текстового объекта задают пара координат и позиция фиксации. Размеры текста определяются числом стро-
794 Часть IV. Компоненты Тк ки и длиной каждой строки. Если в составе последовательности находится символ перевода строки, остальные символы отображаются с новой строки. Если ширина объекта задана в экранных единицах измерения, то любая строка, длина которой превышает указанное значение, представляется в виде нескольких экранных строк. Перенос на новую строку осуществляется перед символом пробела. При выполнении редактирования и выделения позиция определяется с помощью индексов. Используемые при этом индексы очень похожи на те, которые применяются при работе с полями редактирования. В табл. 37.8 приводятся сведения об индексах для текстовых объектов, отображаемых на холсте. Таблица 37.8. Индексы текстового объекта, отображаемого на холсте О Индекс первого символа end Индекс, на единицу больший, чем индекс последнего символа число Индекс символа. Отсчет индексов начинается с нуля insert Символ, находящийся справа от текстового курсора sel.first Первый символ выделенного фрагмента sel. last Символ, следующий за последним символом выделенного фрагмента Ох,у Символ, расположенный в точке с указанными координатами Для выполнения действий с текстовыми объектами в компоненте canvas предусмотрен ряд операций. Они напоминают некоторые операции, определенные для полей редактирования. Операции dchars и select to интерпретируют второй индекс несколько иначе, чем соответствующие операции компонентов entry и text. Символ, определяемый вторым индексом, включается в набор символов, над которым выполняется операция, в то время как в компонентах entry и text второй индекс указывает на первый символ, не принадлежащий этому набору. При вызове операций над текстом на холсте используется идентификатор объекта либо дескриптор. Если дескриптор указывает на несколько объектов, операция применяется к первому из объектов в списке отображения, поддерживающему курсор ввода. Список отображения будет рассматриваться далее в этой главе. В табл. 37.9 приведены сведения об операциях над текстовым объектом. Здесь $t обозначает текстовый объект, а $с — холст.
Глава 37. Компонент canvas 795 Таблица 37.9. Операции над текстовыми объектами, расположенными на холсте $с dchars $t начало Удаляет символы в указанном интервале. Если ко- ?конец? нечный символ не задан, удаляется только начальный символ $с focus ?$t? Передает фокус ввода указанному объекту. Если объект не задан, возвращает идентификатор того объекта, который имеет фокус ввода $с icursor $t индекс Устанавливает текстовый курсор перед символом с указанным индексом $с index $t индекс Возвращает числовое значение, соответствующее заданному индексу $с insert $t индекс Включает заданную строку перед символом с ука- строка занным индексом $с select adjust $t Перемещает границы существующего выделения индекс $с select clear Очищает выделение $с select from $t индекс Начинает формирование выделения $с select item Возвращает идентификатор выделенного объекта, если таковой существует $с select to $t индекс Расширяет выделение до указанного индекса По умолчанию для текстового объекта связывания не определяются. В листинге 37.12 приведен пример установки некоторых основных связей для данного объекта. Связывания <Button-l> и <Button-2> относятся к холсту в целом. Остальные связывания устанавливаются для объектов с помощью дескриптора text. Данный дескриптор следует добавить к текстовым объектам, которые должны поддерживать функции редактирования. Для связываний определены небольшие процедуры. Они позволяют скрыть детали использования локальных переменных. Следует различать операции find overlapping и find closest. В процедуре CanvasFocus процедура холста find overlapping используется для того, чтобы определить, был ли курсор мыши в момент щелчка расположен на текстовом объекте. Для этой цели операцию find closest использовать нельзя, так как она находит объект, даже в том случае, если курсор мыши расположен достаточно далеко от него. В данной процедуре также используется операция type; она позволяет убедиться, что фокусом ввода владеет текстовый объект. По необходимости вы можете предусмотреть реакцию объектов других типов на события клавиатуры.
796 Часть IV. Компоненты Тк Листинг 37.12. Связывания для текстового объекта proc Canvas_EditBind { с } { bind $c <Button-l> \ {CanvasFocus °/0W [°/0W canvasx °/0x] [°/0W canvasy °/0y]} bind $c <Button-2> \ {CanvasPaste °/0W [°/0W canvasx °/ex] [°/0W canvasy °/0y]} bind $c «Cut» {CanvasTextCopy °/0W; CanvasDelete °/0W} bind $c «Copy» {CanvasTextCopy %W} bind $c «Paste» {CanvasPaste %W} $c bind text <Button-l> \ {CanvasTextHit °/0W [°/,W canvasx °/.x] [°/0W canvasy °/„y]} $c bind text <Bl-Motion> \ {CanvasTextDrag °/eW [e/0W canvasx e/0x] [°/0W canvasy °/0y]} $c bind text <Delete> {CanvasDelete °/0W} $c bind text <Control-d> {CanvasDelChar °/,W} $c bind text <Control-h> {CanvasBackSpace °/0W} $c bind text <BackSpace> {CanvasBackSpace °/0W} $c bind text <Control-Delete> {CanvasErase °/0W} $c bind text <Return> {CanvasNewline °/eW} $c bind text <Any-Key> {Canvaslnsert °/0W C/0A} $c bind text <Key-Right> {CanvasMoveRight e/0W} $c bind text <Control-f> {CanvasMoveRight °/0W} $c bind text <Key-Left> {CanvasMoveLeft °/0W} $c bind text <Control-b> {CanvasMoveLeft °/0W} > proc CanvasFocus {с х у} { focus $c set id [$c find overlapping [expr $x-2] [expr $y-2] \ [expr $x+2] [expr $y+2]] if {($id == {}) II ([$c type $id] != "text")} { set t [$c create text $x $y -text ,Mt \ -tags text -anchor nw] $c focus $t $c select clear $c icursor $t 0 } } proc CanvasTextHit {с х у {select 1}} { $c focus current $c icursor current @$x,$y $c select clear
Глава 37. Компонент canvas 797 $с select from current @$x,$y } proc CanvasTextDrag {с х у} { $c select to current @$x,$y } proc CanvasDelete {c} { if {[$c select item] !={}}{ $c dchars [$c select item] sel.first sel.last } elseif {[$c focus] !={}}{ $c dchars [$c focus] insert } } proc CanvasTextCopy {c} { if {[$c select item] != {}} { clipboard clear set t [$c select item] set text [$c itemcget $t -text] set start [$c index $t sel.first] set end [$c index $t sel.last] clipboard append [string range $text $start $end] } elseif {[$c focus] !={}}{ clipboard clear set t [$c focus] set text [$c itemcget $t -text] clipboard append $text } } proc CanvasDelChar {c} { if {[$c focus] !={}}{ $c dchars [$c focus] insert } } proc CanvasBackSpace {c} { if {[$c select item] !={}}{ $c dchars [$c select item] sel.first sel.last } elseif {[$c focus] != {}} { set _t [$c focus] $c icursor $_t [expr [$c index $_t insert]-1] $c dchars $_t insert }
798 Часть IV. Компоненты Тк proc CanvasErase {с} { $с delete [$c focus] } proc CanvasNewline {с} { $с insert [$c focus] insert \n } proc Canvaslnsert {c char} { $c insert [$c focus] insert $char } proc CanvasPaste {c {x {}} {y {}}} { if {[catch {selection get} _s] && [catch {selection get -selection CLIPBOARD} _s]} { return ;# Выделение отсутствует } set id [$c focus] if {[string length $id] == 0 } { set id [$c find withtag current] } if {[string length $id] == 0 } { # В точке, на которую указывает курсор мыши, объектов нет if {[string length $x] == 0} { set x [expr [winfo pointerx $c] - [winfo rootx $c]] set у [expr [winfo pointery $c] - [winfo rooty $c]] } CanvasFocus $c $x $y } else { $c focus $id } $c insert [$c focus] insert $_s } proc CanvasMoveRight {c} { $c icursor [$c focus] [expr [$c index current insert]+1] } proc CanvasMoveLeft {c} { $c icursor [$c focus] [expr [$c index current insert]-1] 2 Процедура CanvasPaste действует следующим образом. Если объект имеет фокус ввода, он вставляет выделенный фрагмент в компонент canvas. Если объект не владеет фокусом, создается новый текстовый объект, значением которого становятся выделенные данные.
Глава 37. Компонент canvas 799 Из атрибутов, перечисленных в табл. 37.1, текстовый объект поддерживает только семейства атрибутов fill и stipple, а также атрибуты state и tags. В табл. 37.10 описаны дополнительные атрибуты для текстовых объектов. Заметьте, что специальные атрибуты для указания цвета переднего плана и фона отсутствуют. Вместо этого цвет текста определяется цветом заполнения. При работе с текстом можно использовать прерывистое заполнение или заполнение по маске. Кроме того, следует заметить, что атрибут width интерпретируется не так, как для других объектов холста. Таблица 37.10. Атрибуты текстового объекта -anchor позиция Точка фиксации: с (принимается по умолчанию), n, ne, e, se, s, sw, w или nw -font шрифт Шрифт для отображения текста, используемый по умолчанию -justify тип Тип выравнивания: left, right или center -text строка Строка, предназначенная для отображения -width ширина Ширина области, представленная в экранных единицах измерения. Если длина строки превышает ширину этой области, осуществляется перенос Окна Объект window позволяет располагать на холсте другие компоненты Тк. Размещение окна определяется парой координат и позицией фиксации. Вы можете непосредственно указать ширину и высоту или предоставить компоненту самостоятельно определить свой размер. В примере, приведенном в листинге 37.13, холст используется для отображения прокручиваемого набора полей редактирования. После создания фрейма в нем размещается набор компонентов entry, снабженных метками. Данный фрейм помещается на холст как один объект окна. Для выравнивания полей редактирования, снабженных метками, используется команда grid. Размер холста устанавливается так, чтобы в нем поместилось целое число полей редактирования. Параметры прокрутки также установлены таким образом, чтобы после щелчка на кнопке со стрелкой, принадлежащей полосе прокрутки, содержимое экрана сдвигалось ровно на один элемент (элементом в данном случае является поле редактирования). Листинг 37.13. Использование холста для отображения прокручиваемого набора компонентов proc Example37--13 { top title labels } { # Создание окна верхнего уровня с изменяемыми размерами
800 Часть IV. Компоненты Тк toplevel Stop wm minsize $top 200 100 wm title $top $title # Создание фрейма для кнопок. # Полезные действия выполняет только кнопка Dismiss set f [frame $top.buttons -bd 4] button $f.quit -text Dismiss -command "destroy $top" button $f.save -text Save button $f.reset -text Reset pack $f.quit $f.save $f.reset -side right pack $f -side top -fill x # Создание холста с возможностью прокрутки frame $top.c canvas $top.с.canvas -width 10 -height 10 \ -yscrollcommand [list $top.c.yscroll set] scrollbar $top.c.yscroll -orient vertical \ -command [list $top.c.canvas yview] pack $top.c.yscroll -side right -fill у pack $top.с canvas -side left -fill both -expand true pack $top.c -side top -fill both -expand true Scrolled_EntrySet $top.с.canvas $labels } proc Scrolled_EntrySet { canvas labels } { # Создание фрейма для включения объектов и # размещение его на холсте set f [frame $canvas.f -bd 0] $canvas create window 0 0 -anchor nw -window $f # Создание полей редактирования с метками # и размещение их с помощью диспетчера компоновки grid set i 0 foreach label $labels { label $f.label$i -text $label entry $f.entry$i grid $f.label$i $f.entry$i grid $f.label$i -sticky w grid $f.entry$i -sticky we incr i
Глава 37. Компонент canvas 801 } set child $f.entryO # После отображения окна устанавливается область # прокрутки на основе требуемого размера фрейма. # На основе высоты компонента устанавливается # приращение прокрутки. tkwait visibility $child set bbox [grid bbox $f 0 0] set incr [lindex $bbox 3] set width [winfo reqwidth $f] set height [winfo reqheight $f] $canvas config -scrollregion "0 0 $width $height" $canvas config -yscrollincrement $incr set max [llength $labels] if {$max > 10} { set max 10 } set height [expr $incr * $max] $canvas config -width $width -height $height } Example37--13 .ex "An example" { alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega} В рассматриваемом примере используется команда tkwait visibility. Она приостанавливает выполнение сценария до тех пор, пока окно верхнего уровня $top не отобразится на экране. Эта команда необходима для того, чтобы команды grid bbox возвращали корректную информацию. Ожидая появления фрейма $child в главном окне, мы гарантируем, что диспетчер компоновки выполнит все необходимые действия по размещению компонентов. Область прокрутки холста установлена достаточной для того, чтобы в ней мог разместиться весь фрейм. Приращение прокрутки выбрано равным высоте ячейки сетки, создаваемой диспетчером компоновки grid. Из атрибутов, перечисленных в табл. 37.1, объект image поддерживает только state и tags. В табл. 37.11 указаны дополнительные атрибуты, поддерживаемые только объектом window. Кроме того, следует учитывать особенности интерпретации атрибута width.
802 Часть IV. Компоненты Тк Таблица 37.11. Атрибуты окна -anchor Точка фиксации: с (принимается по умолчанию), n, ne, e, se, позиция s, sw, w или nw -height Высота компонента в экранных единицах измерения. Если в ка- высота честве высоты задана пустая строка (данное значение принимается по умолчанию), высота выбирается исходя из потребностей по размещению содержимого -width ширина Ширина компонента в экранных единицах измерения. Если в качестве высоты задана пустая строка (данное значение принимается по умолчанию), ширина выбирается исходя из потребностей по размещению содержимого -window имя Имя компонента, предназначенного для отображения на холсте Операции над компонентом canvas В табл. 37.12 приведены сведения об операциях, выполняемых над холстом. Здесь $с обозначает компонент canvas. Значение $t представляет числовой идентификатор объекта или дескриптор. Кроме того, в Тк 8.3 и более поздних версиях $t может быть логическим выражением, состоящим из дескрипторов, объединенных с помощью операций && (И), I I (ИЛИ), ~ (исключающее ИЛИ), ! (отрицание), а также выражений, сгруппированных с помощью круглых скобок (например, {(plotl I I plot2) && !fixed}). Некоторые операции применимы только к одному объекту. Если при этом дескриптор или выражение идентифицирует несколько объектов, то выбирается первый объект в списке отображения. Список отображения задает порядок следования объектов холста. Новый объект помещается в конце списка. Объекты "заслоняют" другие объекты, расположенные ближе к началу списка. Говоря "первый объект расположен над вторым", мы имеем в виду, что первый объект находится ближе к концу списка, чем второй. В табл. 37.9 приведено несколько операций холста, применимых лишь к текстовым объектам. Это операции dchars, focus, index, icursor, insert и select. В табл. 37.12 сведения об этих операциях не дублируются. Таблица 37.12. Операции над компонентом canvas $с addtag дескриптор Добавляет дескриптор к объекту выше $t в списке above $t отображения $с addtag дескриптор all Добавляет дескриптор ко всем объектам холста
Глава 37. Компонент canvas 803 Продолжение табл. 37.12 $с addtag дескриптор below $t $с addtag дескриптор closest x у ?окрестность? ?начальная_позиция? $с addtag дескриптор enclosed xl yl х2 у2 $с addtag дескриптор overlapping xl yl x2 y2 $с addtag дескриптор withtag $t $с bbox $t ?дескриптор дескриптор ...? $с bind $t ?последовательность? ?команда? $с canvasx ко ордината_х ? сетка ? $с canvasx координата_у ?сетка? $с сget опция $с configure . . . $с coords $t ?xl yl ...? $с create тип х у ?х2 у2 ...? ?опция значение ? Добавляет дескриптор к объекту ниже $t в списке отображения Добавляет дескриптор к объекту, который ближе других расположен к указанной точке. Если несколько объектов расположено на одинаковом расстоянии от точки или если несколько объектов попадает в окрестность, возвращается тот объект, который расположен выше других в списке отображения. Если задана начальная позиция, выбирается ближайший объект из тех, которые расположены в списке отображения после указанной позиции Добавляет дескриптор к тем объектам, которые целиком содержатся в указанной области. Здесь xl <= х2, yl <= у2 Добавляет дескриптор к тем объектам, которые пересекаются с указанной областью. Здесь xl <= х2, yl <= у2 Добавляет дескриптор к тем объектам, которые идентифицирует $t Возвращает ограничивающий прямоугольник для объектов, идентифицируемых посредством указанных дескрипторов. Возвращаемая информация представляется в виде xl yl х2 у2 Запрашивает или устанавливает связывания для объектов холста Отображает экранную координату х в пространство холста. Если указана сетка, полученное значение округляется до величины, кратной шагу сетки Отображает экранную координату у в пространство холста Возвращает значение опции для холста Запрашивает или изменяет атрибуты холста Запрашивает или изменяет координаты объекта. С версии Тк 8.3 список координат может быть оформлен в виде одного параметра Создает объект холста указанного типа в указанной позиции. Начиная с версии Тк 8.3 список координат может быть оформлен в виде одного параметра
804 Часть IV. Компоненты Тк Продолжение табл. 37.12 $с delete $t ?дескриптор . . . ? $с dtag $t ?дескриптор? $с find описание ... $с gettags $t $с itemcget $t опция $с itemconfigure $t ... $c lower $t ?объект? $c move $t dx dy $c postscript ... $c raise $t ?объект? $c scale $t xO yO xS yS $c scan mark x у $c scan dragto x у $c type $t $t xview $t xview moveto часть Удаляет объекты, заданные с помощью дескрипторов или идентификаторов Разрывает связь между дескрипторами и объектами, идентифицируемыми посредством $t. Если дескриптор не указан, по умолчанию принимается $t Возвращает идентификаторы дескрипторов, которые удовлетворяют описанию: above, all, below, closest, enclosed, overlapping и withtag Возвращает дескрипторы, связанные с первым из объектов, идентифицируемых посредством $t Возвращает значение опции для объекта $t Запрашивает или изменяет конфигурацию объекта $t Помещает объекты, идентифицируемые $t, в начало списка отображения либо перед указанным объектом Перемещает $t на указанное расстояние Генерирует Postscript-описание. Соответствующие опции приведены в табл. 37.13 Помещает объекты, идентифицируемые $t, в конец списка отображения либо после указанного объекта Масштабирует координаты объектов, идентифицируемых посредством $t. Расстояние между хО и координатой х умножается на xS. To же происходит и с координатой у Устанавливает маркер для операции прокрутки Выполняет прокрутку холста от предыдущего маркера Возвращает тип первого из объектов, идентифицируемых посредством $t Возвращает два числа в интервале от нуля до единицы, которые представляют объем содержимого холста слева за пределами экрана, и объем отображаемых данных Смещает отображаемые данные так, чтобы указанная их часть размещалась за левой границей области отображения
Глава 37. Компонент canvas 805 Окончание табл. 37.12 $t xview scroll число единица^ измерения $t yview $t yview moveto часть $t yview scroll число единица., измерения Прокручивает данные по горизонтали на указанное число единиц представления содержимого или страниц Возвращает два числа в интервале от нуля до единицы, которые представляют объем содержимого холста за верхней границей области отображения, и объем выводимых данных Располагает отображаемые данные так, чтобы указанная их часть размещалась выше верхней границы области отображения Прокручивает данные по вертикали на указанное число единиц представления содержимого или страниц Генерация Postscript-описаний Операция postscript генерирует Postscript-описание содержимого холста. В ранних версиях Тк действовало существенное ограничение: изображения и встроенные окна не включались в результирующий Postscript-документ. В Тк 8.3 данный недостаток был устранен. Начиная с версии Тк 8.3 для Unix и Тк 8.4.1 для Windows встроенные окна учитываются при генерации Postscript-данных, если они отображаются на экране (т.е. попадают в область отображения и не заслоняются другими окнами). В табл. 37.13 приведены сведения об опциях, используемых для генерации Postscript-описаний. Таблица 37.13. Опции, применяемые для генерации Postscript-описаний -channel Идентификатор канала, открытого для записи. идентификатор Postscript-информация записывается в этот канал; после завершения операции канал остается открытым. Если не задана ни опция -channel, ни опция -file, Postscript-данные возвращаются как результат выполнения команды -colormap переменная Индексом переменной являются цвета, а содержимое каждого элемента представляет собой Postscript-код для генерации RGB-значения соответствующего цвета -colormode режим Допустимые режимы: color, grey и mono
806 Часть IV. Компоненты Тк Окончание табл. 37.13 -file имя -fontmap переменная -height размер -pageanchor гочка_фиксации -pageheight размер -pagewidth размер -pagex позиция -pagey позиция -rotate логическое„значение -width размер -х позиция -у позиция Файл, в который должны быть записаны Postscript- данные. Если не задана ни опция -channel, ни опция -file, Postscript-данные возвращаются как результат выполнения команды Индексом переменной является имя Х-шрифта. Каждый элемент содержит список, включающий имя Postscript-шрифта и размер Высота области, предназначенной для вывода на печать В качестве точки фиксации может быть задано значение с, n, ne, e, se, s, sw, w или nw Высота изображения на устройстве вывода. В качестве значения опции задается число с плавающей точкой, за которой следует буква с (сантиметры), i (дюймы), m (миллиметры) или р (пункты) Ширина изображения на устройстве вывода Координата х точки фиксации для выходных данных Координата у точки фиксации для выходных данных Если данная опция имеет значение true, поворот осуществляется так, что по оси х располагается длинная сторона изображения (альбомная ориентация) Ширина области, предназначенной для вывода на печать Координата х холста для левой границы изображения Координата у холста для верхней границы изображе- Опции -width, -height, -x и -у позволяют определить область холста для печати. Размерами и расположением этой области в составе выходных данных управляют опции -pageanchor, -pagex, -pagey, -pagewidth и -pageheight. Postscript-описание записывается в файл, имя которого задается с помощью опции -file, передается в уже открытый канал записи (идентификатор канала указывается с помощью опции -channel) либо предоставляется как возвращаемое значение операции холста postscript. Управление шрифтами осуществляется путем отображения экранных шрифтов X Window в Postscript-шрифты. Для этого применяется массив, в котором индексы элементов являются именами шрифтов X Window, а содержимое представляет имена и размеры шрифтов Postscript.
Глава 37. Компонент canvas 807 В листинге 37.14 приведен код, который располагает на холсте текстовые объекты, использующие различные шрифты. Для шрифтов X Window формируются записи, отображающие их в Postscript-шрифты. В приведенном примере применяется очень простое отображение шрифтов; на практике подобную задачу компонент canvas решает автоматически. Лишь для редко встречающихся экранных шрифтов вам может понадобиться задавать карту отображения в явной форме. В данном примере выходные данные размещаются с верхнего левого угла страницы; для этой цели используются опции -pagex, -pagey и -pageanchor. При выполнении позиционирования следует помнить, что по соглашениям Postscript начало координат размещается в левом нижнем углу страницы. Листинг 37.14. Генерация Postscript-описания содержимого холста proc Setup {} { global fontMap canvas .с pack .с -fill both -expand true set x 10 set у 10 set last [,c create text $x $y -text "Font sampler" \ -font fixed -anchor nw] # Создание нескольких строк с использованием различных # шрифтов и их размеров foreach family {times courier helvetica} { set weight bold switch -- $family { times { set fill blue; set psfont Times} courier { set fill green; set psfont Courier } helvetica { set fill red; set psfont Helvetica } } foreach size {10 14 24} { set у [expr 4+[lindex [.c bbox $last] 3]] # Действия при отсутствии шрифтов if {[catch {.с create text $x $y \ -text $family-$weight-$size \ -anchor nw -fill $fill \ -font -*-$family-$weight-*-*-*-$size-*} \
808 Часть IV. Компоненты Тк it] == 0} { set fontMap(-*-$family-$weight-*-*-*-$size-*)\ [list $psfont $size] set last $it } } } set fontMap(fixed) [list Courier 12] } proc Postscript { с file } { global fontMap # Действия с цветом для отображения выходных данных set colorMap(blue) {0.1 0.1 0.9 setrgbcolor} set colorMap(green) {0.0 0.9 0.1 setrgbcolor} # Позиционирование текста в левом верхнем углу # листа размером 8,5x11 дюймов $с postscript -fontmap fontMap -colormap colorMap \ -file $file \ -pagex O.i -pagey 11.i -pageanchor nw Атрибуты компонента canvas В табл. 37.14 перечислены атрибуты компонента canvas. Здесь указаны имена ресурсов для атрибутов. Слова, составляющие эти имена (за исключением первого), начинаются с прописной буквы. В составе Tcl-команд данным атрибутам соответствуют опции, начинающиеся с символа - и содержащие буквы лишь нижнего регистра. Таблица 37.14. Атрибуты компонента canvas, представленные с помощью имен ресурсов background Цвет фона borderWidth Толщина обрамления вокруг холста closeEnough Расстояние от курсора мыши до объекта, при котором принимается решение о наличии пересечения confine Логическое значение true ограничивает область видимости областью прокрутки cursor Курсор мыши, отображаемый в пределах компонента height Высота области отображения холста, представленная в экранных единицах измерения
Глава 37. Компонент canvas 809 Окончание табл. 37.14 highlightBackground highlightColor highlightThickness insertBackground insertBorderwidth insertOffTime insertOnTime insertWidth relief scrollRegion selectBackground selectForeground selectBorderWidth state takeFocus width xScrollCommand xScrollIncrement yScrollCommand yScrollIncrement Цвет, используемый для подсветки, когда компонент не имеет фокуса ввода Цвет, используемый для подсветки, когда компонент имеет фокус ввода Толщина рамки, указывающей на наличие фокуса ввода Цвет фона для отображения области, занимаемой текстовым курсором Толщина обрамления для текстового курсора. Значение данного атрибута отлично от нуля, если осуществляется имитация трехмерного представления Время, в течение которого мигающий курсор отсутствует. Задается в миллисекундах Время, в течение которого мигающий курсор присутствует на экране. Задается в миллисекундах Толщина текстового курсора. По умолчанию принимается значение 2 Допустимые значения: flat, sunken, raised, groove, solid и ridge Координаты левой, верхней, правой и нижней границы холста Цвет фона для выделения Цвет переднего плана для выделения Толщина обрамления для выделенного фрагмента. Значение данного атрибута отлично от нуля, если осуществляется имитация трехмерного представления Состояние по умолчанию для объектов холста: normal или disabled (Tk 8.3) Порядок изменения фокуса ввода при действиях с клавиатурой Ширина области отображения холста, представленная в экранных единицах измерения Префикс Tcl-команды для горизонтальной прокрутки Шаг горизонтальной прокрутки Префикс Tcl-команды для вертикальной прокрутки Шаг вертикальной прокрутки
810 Часть IV. Компоненты Тк Область прокрутки определяет границы холста. Для ее определения используются четыре координаты: xl yl х2 у2, где (xl, yl) — это левый верхний, а (х2, у2) — правый нижний угол. Если значение атрибута confine равно true, то холст не может прокручиваться за пределы этой области. Объекты могут частично или полностью располагаться за пределами области прокрутки, в этом случае они будут невидимы. Атрибуты xScrollIncrement и yScrollIncrement определяют величину сдвига, который осуществляется после щелчка пользователя на кнопке со стрелкой, принадлежащей полосе прокрутки. Атрибут closeEnough указывает расстояние до объекта, при котором считается, что перекрытие имеет место. Этот атрибут применяется при поиске по критерию overlapping. Советы Экранные координаты и координаты холста Операции canvasx и canvasy осуществляют отображение экранных координат в координаты холста. Если область прокрутки больше, чем область отображения, эти операции необходимо использовать при обработке события (т.е. применять их к данным, доступным посредством ключевых слов °/0х и °/0у). Пример использования операций canvasx и canvasy приведен ниже. set id [$c find closest [$c canvasx °/0x] [$c canvasy °/0y]] Представление координат Координаты холста хранятся как числа с плавающей точкой; именно в таком виде они возвращаются при выполнении операции coords. Если размеры холста велики, вам, возможно, потребуется указать точность представления координат. Для этой цели предназначена переменная tcl_precision. Это важно в тех случаях, когда вы запрашиваете координаты, выполняете с ними некоторые действия, а затем используете их для обновления содержимого холста. (Начиная с версии Tcl 8.0 значение tcl_precision по умолчанию увеличено с 6 до 12.) Масштабирование и вращение Операция scale изменяет координаты одного или нескольких объектов холста. Масштабировать все пространство координат невозможно. При многократном изменении масштаба объектов может быть потеряна точность, так
Глава 37. Компонент canvas 811 как масштабирование изменяет внутреннее представление координат. В простых случаях эта проблема легко разрешима, но в ряде ситуаций данное явление может привести к искажениям содержимого холста. Компонент canvas не поддерживает вращение. Работа с ресурсами В компоненте canvas и в объектах холста отсутствуют встроенные средства поддержки базы данных ресурсов. Однако вы можете определять ресурсы и непосредственно обращаться к ним. Так, например, можно создать следующую запись: *Canvas.foreground: blue Сама по себе она не даст никакого результата. Однако в вашей программе можно предусмотреть обращение к ресурсам с помощью операции option get и использования полученных данных в качестве значения опции -fill. set fg [option get $c foreground {}] $c create rect 0 0 10 10 -fill $fg Подобный подход в основном применяется тогда, когда необходимо предоставить пользователям возможность изменять внешний вид объектов холста, не модифицируя код программы. Объекты, определяемые посредством большого количества точек В реализации компонента canvas многие операции с объектами холста оптимизированы. Однако, если объект, например линия или многоугольник, определен с помощью большого количества точек, то при выполнении операций эти точки обрабатываются последовательно, одна за другой. Это влияет на время обработки событий мыши, возникающих в той области, в которой размещены подобные компоненты. Причина заключается в том, что после щелчка мышью определяется связывание, которое должно вступить в действие, а для этого проверяются все близлежащие объекты. Выбор объектов холста В листинге 38.5 будет приведен пример реализации операции вырезания и вставки объектов холста. В данном примере логическое описание объектов холста заменяется механизмом выделения.
ЧАСТЬ V Особенности работы Тк В части V описываются те инструменты Тк, которым не было уделено достаточного внимания в предыдущих частях. В главе 38 обсуждается механизм выделения, использующийся при обмене данными между прикладными программами методом вырезания и вставки. Здесь же приведен пример вставки графических объектов в компонент canvas. Глава 39 посвящена рассмотрению диалоговых окон. В Тк имеется несколько встроенных диалоговых окон, в которых используются платформен- но-ориентированные средства отображения. В этой же главе обсуждаются вопросы формирования собственных диалоговых окон. Глава 40 — первая из трех глав, в которых подробно рассматриваются атрибуты компонентов. В этой главе речь идет об атрибутах, определяющих размеры и размещение компонентов. В главе 41 описывается работа с цветом, изображениями и курсорами. В ней объясняется использование битовых карт и цветных растровых изображений (например, фотоснимков). В этой же главе приводятся сведения о шрифтах курсоров. В главе 42 рассматриваются шрифты и другие текстовые атрибуты. В этой главе представлен также пример приложения, позволяющего пользователям выбирать шрифты. В главе 43 описывается Tk-команда send, которая позволяет передавать команды между различными Tk-приложениями. Там же представлены средства обмена на базе сетевых гнезд, которые могут использоваться приложениями, работающими на различных узлах сети, и механизм Safe-Тс, ограничивающий возможности команд, вызванных по инициативе удаленных приложений. В главе 44 обсуждаются вопросы взаимодействия с диспетчером окон с использованием команды wm. В этой главе также описываются все данные, доступные с помощью команды winf о. Глава 45 — это своеобразное продолжение главы 31. В ней рассказывается о создании пакета для хранения параметров, установленных пользователем,
814 Часть V и об интерфейсах, ориентированных на конкретных пользователей. Пакет настроек связывает Tcl-переменные, присутствующие в приложениях, с описаниями ресурсов. В главе 46 представлен пользовательский интерфейс к механизму связывания. С его помощью можно просматривать и редактировать связывания для компонентов и классов.
Глава 38 Выделение данных и буфер обмена Средства вырезания и вставки обеспечивают обмен информацией между прикладными программами. Их работа основана на универсальном механизме выделения данных. Выделение CLIPBOARD используется для обмена данными на всех платформах. Приложения X Window могут также использовать выделение PRIMARY. В данной главе описываются команды selection и clipboard. Г\опирование и вставка очень часто применяются для переноса данных из одного приложения в другое. В Тк копирование и вставка базируются на общем механизме выделения, где выделенному фрагменту информации соответствуют имя, тип, формат и значение. В большинстве случаев эти дополнительные сведения можно не учитывать, так как они обрабатываются компонентами Тк. Однако по необходимости вы можете управлять выделенными данными явным образом. В данной главе описаны модель выделения, а также команды selection и clipboard. В последнем разделе главы рассматривается пример вырезания графического объекта и вставки его в компонент canvas. Модель выделения В Windows и Macintosh модель выделения проще, чем соответствующая модель в системе X Window. В Macintosh и Windows используется одно выделение, в котором могут храниться различные типы данных, например текст и изображения. Пользователи копируют данные из приложения в буфер обмена, а затем вставляют их из буфера в другую прикладную программу.
816 Часть V. Особенности работы Тк В системе X Window используется более универсальная модель, которая допускает существование нескольких выделений, которые идентифицируются с помощью имен, например PRIMARY и CLIPBOARD. Выделение CLIPBOARD используется для копирования и вставки, подобно тому, как это происходит в системах Macintosh и Windows. Выделение PRIMARY описывается далее в этой главе. Если понадобится, вы можете использовать и другие имена выделения, например SECONDARY или F00BAR, но, для того, чтобы они действовали, их имена должны быть известны другим приложениям. Выделенным данным ставятся в соответствие тип и формат. Они будут рассмотрены далее в этой главе. При выделении копирование данных не происходит. Вместо этого приложение объявляется владельцем выделенного фрагмента, а другое приложение запрашивает у владельца значение этого фрагмента. Данная модель используется на всех платформах. Отношение владения поддерживает оконная система, при этом приложение оповещается о том, что другая программа стала владельцем выделенных данных. В некоторых компонентах Тк реализованы процедура выделения, установка владельца выделенных данных и возврат значения. Способ использования выделения PRIMARY в системе X Window исключает операцию копирования при передаче данных. При выделении объекта приложение автоматически помещает его значение в выделение PRIMARY. В компонентах entry, listbox и text это происходит при выделении текста; изменить поведение данных компонентов позволяет атрибут exportSelection. Значение выделения PRIMARY обычно включается после щелчка средней кнопкой мыши. Все компоненты и приложения совместно используют один экземпляр выделения PRIMARY. Если пользователь создает новое выделение, оно автоматически заменяет текущее значение PRIMARY. Выделение CLIPBOARD действует на всех платформах. Если вам нужен механизм, действующий во всех операционных системах, то таким механизмом является выделение CLIPBOARD. Выделение PRIMARY реализуется на всех платформах, и его можно использовать без ограничений, однако в системах Windows и Macintosh программы, созданные с помощью средств, отличных от Тк, не знают о наличии такого выделения. Механизм копирования и вставки в основном предназначен для обеспечения взаимодействия всех прикладных программ; это позволяет сделать CLIPBOARD. В Тк 3.6 и более ранних версиях поддерживалось только выделение PRIMARY. В Тк 4.0 была добавлена поддержка CLIPBOARD. В листинге 38.1 содержится код процедуры Paste, которая включает в состав текстового комио-
Глава 38. Выделение данных и буфер обмена 817 нента либо выделение PRIMARY, либо CLIPBOARD. Значение выделения предоставляет команда selection get. Листинг 38.1. Включение выделения PRIMARY или CLIPBOARD proc Paste { text } { if [catch {selection get} sel] { if [catch {selection get -selection CLIPBOARD} sel] { # Выделение и данные в буфере обмена отсутствуют return } } $text insert insert $sel 2 Функция Paste может пригодиться при разработке приложения, однако пользователи должны представлять себе различия между этими двумя типами выделения. Если пользователь привык использовать только CLIPBOARD, то информация о наличии PRIMARY будет для него неожиданностью. Наилучшим решением, по-видимому, являлось бы использование различных действий для вставки разных типов выделений. Событие <ButtonRelease-2> устанавливает точку ввода и включает выделение PRIMARY. (При использовании двухкно- почной мыши в системах Macintosh и Windows сгенерировать такое событие нельзя.) Событие «Paste>> (оно возникает, например, при нажатии клавиши <Paste>) происходит лишь после вставки выделения CLIPBOARD в текущей точке ввода. Соответствующие связывания показаны в листинге 38.2. Для компонентов text и entry они устанавливаются автоматически. Листинг 38.2. Раздельные действия по вставке данных bind Text «Paste» { catch {°/0W insert insert \ [selection get -selection CLIPBOARD] } } bind Text <ButtonRelease-2> { °/0W mark set insert @0/0x,0/0y catch {°/oW insert insert \ [selection get -selection PRIMARY] } }
818 Часть V. Особенности работы Тк Команда selection Для работы с выделенными фрагментами данных используются две Tcl- команды. Команда selection позволяет устанавливать и получать различные выделения. По умолчанию данная команда работает с выделением PRIMARY. Команда clipboard введена для удобства работы и позволяет выполнять действия только над выделением CLIPBOARD. Команда selection поддерживает общую модель выделения информации и применима для различных типов и форматов данных. По мере необходимости вы можете определять обработчики, которые должны возвращать значения выделений, объявлять владельца выделения и получать информацию о передаче выделения в собственность другого приложения. В листинге 38.5 показан код обработчика выделения для холста. Для выделений определены типы. По умолчанию принимается тип STRING. Не следует путать название типа с именем выделения (например, PRIMARY или CLIPBOARD). Каждому типу может соответствовать формат; по умолчанию принимается формат STRING. Как правило, значения по умолчанию удовлетворяют требованиям разработчиков приложений. Если необходимо организовать взаимодействие с прикладной программой, созданной с помощью средств, отличных от Тк, необходимо предусмотреть запрашивание типов выделений. Разработчики программ могут использовать форматы выделений UTF8_STRING, STRING, ATOM и INTEGER. Имя АТОМ зарегистрировано на Х-сервере, и для идентификации используется число. Тк-команды, применяемые для выполнения действий с выделениями типа АТОМ, рассматриваются в главе 44. При создании приложений не рекомендуется использовать типы, отличные от STRING, так как в этом случае ограничиваются возможности по обмену данными с другими приложениями. Подробную информацию о типах и форматах выделений в системе X Window можно найти в руководстве Дэвида Розенталя (David Rosenthal) и Стюарта Маркса (Stuart Marks) Inter-Client Communication Conventions Manual (X Consortium Standard). Оно поставляется в составе дистрибутивного пакета XII, кроме того, его можно скопировать, обратившись по адресу http://tronche.eom/gui/x/icccm/. Все операции над выделениями предполагают наличие опции -selection, с помощью которой задается имя выделения. По умолчанию принимается имя PRIMARY. Для некоторых из операций задается опция -displayof, с помощью которой указывается, на каком из дисплеев находится выделение. Значением этой опции является имя пути к окну Тк. Заданная операция выполняется над выделением в этом окне. Данную опцию удобно использовать для приложений X Window, которым могут соответствовать окна на удаленных терминалах. По умолчанию действия осуществляются над выделением в главном окне. Сведения о команде selection представлены в табл. 38.1.
Глава 38. Выделение данных и буфер обмена 819 Таблица 38.1. Команда selection selection clear ?-displayof окно? ?-selection выделение? selection get ?-displayof окно? ?-selection выделение? ?-type тип? selection handle ?-selection выделение? ?-type тип? ?-format формат? окно команда selection own ?-displayof окно? ?-selection выделение? selection own ?-command команда? ?-selection выделение? окно Очищает указанное выделение Возвращает указанное выделение. По умолчанию принимается тип STRING Определяет команду как разработчик запроса к выделению, когда указанное окно владеет выделением Возвращает путь Тк к окну в составе приложения, владеющего выделением Окно объявляется владельцем выделения. Команда вызывается тогда, когда окно перестает обладать выделением Команда clipboard Команда clipboard обрабатывает значение выделения CLIPBOARD. Как правило, выделение CLIPBOARD используется для поддержки операций копирования и вставки. В версиях, предшествовавших Тк 8.4, для того, чтобы получить значение выделения CLIPBOARD, надо было использовать команду selection. selection get -selection CLIPBOARD В Tk 8.4 для получения значения CLIPBOARD была реализована операция clipboard get. Сведения о команде clipboard приведены в табл. 38.2. Таблица 38.2. Команда clipboard clipboard append ?-displayof окно? ?-format формат? ?-type тип? ?-? данные clipboard clear ?-displayof окно? Добавляет данные указанного типа и формата к CLIPBOARD. По умолчанию принимаются тип STRING и формат STRING Очищает выделение CLIPBOARD
820 Часть V. Особенности работы Тк Окончание табл. 38.2 clipboard get Возвращает выделение CLIPBOARD. По умолчанию ?-displayof окно? ?-type принимается тип STRING тип? Обработчики выделений Команда selection handle регистрирует Tcl-команду, предназначенную для обработки запросов к выделению. Зарегистрированная команда предоставляет приложению, от которого исходит запрос, значение выделения. Если объем значения велик, команда может быть вызвана несколько раз; при этом значение выделения возвращается по частям. Команде передаются два параметра; с их помощью определяются смещение, начиная с которого должны возвращаться данные, и максимальное количество байтов, которые могут быть возвращены. Если команда возвращает число байтов меньше максимального, запрос к выделению считается обработанным. В противном случае команда вызывается снова для получения остальных данных, при этом корректируется величина смещения. Вы также можете организовать обратный вызов при потере владения выделением. В этот момент желательно отменить подсветку выделенного объекта. Команда, вызываемая при потере владения выделением, регистрируется с помощью команды selection own. Обработчик выделения для холста В листингах 38.3-38.7 показан код, реализующий операцию вырезания и вставки для холста. Процедура CanvasSelect_Demo создает холст и устанавливает некоторые связывания. Листинг 38.3. Связывания для холста proc CanvasSelect_Demo { с } { canvas $c pack $c $с create rect 10 10 50 50 -fill red -tag object $c create poly 100 100 100 30 140 50 -fill orange \ -tag object # Установка связываний для вырезания и вставки $с bind object <Button-l> [list CanvasSelect $c °/0x °/ey] bind $c <Key-Delete> [list CanvasDelete $c] bind $c «Cut» [list CanvasCut $c]
Глава 38. Выделение данных и буфер обмена 821 bind $c <<Copy>> [list CanvasCopy $c] bind $c «Paste» [list CanvasPaste $c] bind $c <Button-2> [list CanvasPaste $c °/0x e/.y] # Регистрация обработчика запросов к выделению selection handle $c [list CanvasSelectHandle $c] 2 Процедура CanvasSelect выбирает объект. Для нахождения объекта, на который указывает курсор мыши, используется операция find closest. Ее работу обеспечивает связывание для дескриптора object. Если бы связывание было установлено для всего холста, вам пришлось бы использовать операцию find overlapping, чтобы исключить выбор объектов, расположенных возле курсора мыши. Процедура CanvasHighlight используется для подсвечивания выделенного объекта. Она отображает небольшие маркеры на углах прямоугольника, ограничивающего объект. Процедура CanvasSelectLose вызывается тогда, когда другое приложение получает выделение PRIMARY в свое владение. Листинг 38.4. Выделение объектов proc CanvasSelect { w х у } { # Выделение объекта холста, global canvas set id [$w find closest $x $y] set canvas(select,$w) $id CanvasHighlight $w $id # Объявление владения выделением PRIMARY selection own -command [list CanvasSelectLose $w] $w focus $w } proc CanvasHighlight {w id {clear clear}} { if {$clear == "clear"} { $w delete highlight } foreach {xl yl x2 y2} [$w bbox $id] { # lassign } foreach x [list $xl $x2] { foreach у [list $yl $y2] { $w create rectangle [expr $x-2] [expr $y-2] \ [expr $x+2] [expr $y+2] -fill black \ -tag highlight } }
822 Часть V. Особенности работы Тк } proc CanvasSelectLose { w } { # Другое приложение требует выделение. global canvas $w delete highlight unset canvas(select,$w) } Если вы заявили владение выделением, то при любом запросе к выделению (даже если он исходит от вашего приложения) Тк осуществляет обратный вызов процедуры CanvasSelectHandle. Для получения описания объекта используется процедура CanvasDescription. В ней с помощью операций над холстом запрашиваются сведения о конфигурации, которые объединяются в команду создания объекта. Листинг 38.5. Обработчик выделения для холста proc CanvasSelectHandle { w offset maxbytes } { # Обработка запроса к выделению global canvas if ![infо exists canvas(select,$w)] { error "No selected item" } set id $canvas(select,$w) # Возврат запрашиваемых данных. return [string range [CanvasDescription $w $id] \ $offset [expr $offset+$maxbytes]] } proc CanvasDescription { w id } { # Генерация описания объекта, которое может # быть использовано впоследствии для его создания, set type [$w type $id] set coords [$w coords $id] set config {} # Установки, отличные от принятых по умолчанию foreach conf [$w itemconfigure $id] { # itemconfigure возвращает список типа # -fill {} {} {} red set default [lindex $conf 3] set value [lindex $conf 4] if {[string compare $default $value] != 0} { lappend config [lindex $conf 0] $value
Глава 38. Выделение данных и буфер обмена 823 } } return [concat CanvasObject $type $coords $config] 2 Процедура CanvasCopy помещает описание выделенного элемента в буфер обмена. Для этой цели используется команда clipboard append. Процедура CanvasDelete удаляет объект, при этом отменяется также подсвечивание. Процедура CanvasCut состоит лишь из вызова процедур CanvasCopy и CanvasDelete. Листинг 38.6. Операции копирования и вырезания proc CanvasCopy { w } { global canvas if [info exists canvas(select,$w)] { set id $canvas(select,$w) clipboard clear clipboard append [CanvasDescription $w $id] } } proc CanvasDelete {w} { global canvas catch { $w delete highlight $w delete $canvas(select,$w) unset canvas(select,$w) } } proc CanvasCut { w } { CanvasCopy $w CanvasDelete $w } Процедура CanvasPaste получает значение выделения CLIPBOARD. Это значение содержит все параметры, которые необходимы, чтобы выполнить операцию create для холста. Ей передается позиция нового объекта. Она либо определяется в результате обработки события <Button-2>, либо, если генерируется событие «Paste», представляет собой текущее положение курсора мыши. Если курсор мыши находится за пределами окна, объект помещается в середину холста. Исходная и новая позиция используются для вычисления значений, необходимых для выполнения операции move компонента canvas.
824 Часть V. Особенности работы Тк Листинг 38.7. Вставка объекта в компонент canvas proc CanvasPaste { w {x {}} {у {}}} { # Вставка выделения из CLIPBOARD if [catch {selection get -selection CLIPBOARD} sel] { # В буфере обмена нет данных return } if {[string length $x] == 0} { # <<Paste>>, получение текущих координат курсора мыши set x [expr [winfo pointerx $w] - [winfo rootx $w]] set у [expr [winfo pointery $w] - [winfo rooty $w]] if {$x < 0 II $y < 0 II $x > [winfo width $w] I I $y > [winfo height $w]} { # Курсор мыши за пределами окна - # объект располагается по центру set х [expr [winfo width $w]/2] set у [expr [winfo height $w]/2] } } if [regexp {~CanvasObject} $sel] { if [catch {eval {$w create} [lrange $sel 1 end]} id] { return; } # Получить первые координаты и определить, куда # перемещать объект. Первый элемент - это тип # остальные два элемента - это первые координаты set xl [lindex $sel 2] set yl [lindex $sel 3] $w move $id [expr $x-$xl] [expr $y-$yl] } 2 Очевидно, что описанных выше операций недостаточно для создания программы рисования. Для этого надо предусмотреть возможность выделения нескольких объектов, создания новых элементов, а также реализовать многие другие действия. В качестве примера такой программы можно привести продукт ImPrcss, созданный Кристофером Коксом (Christopher Сох). Он представляет собой полнофункциональную прикладную программу, реализованную на базе Тк-компонента canvas. Вы можете найти это приложение в Web по адресу http://www.ntlug.org/~ccox/impress/.
Глава 39 Диалоговые окна и фокус ввода Диалоговые окна представляют собой стандартный элемент любого пользовательского интерфейса. Некоторые из диалоговых окон реализованы в составе Тк. В данной главе рассматриваются вопросы создания диалоговых окон с нуля, а также использования фокуса ввода и режима захвата. Фокус ввода управляет передачей событий клавиатуры требуемым компонентам. Механизм захвата позволяет компонентам перехватывать фокус ввода. В данной главе описываются команды focus, grab, tk_dialog и tkwait. Начиная с Tk 4.2 в распоряжение разработчиков были предоставлены стандартные диалоговые окна tk_getOpenFile, tk.getSaveFile, tk_chooseColor и tk_messageBox. В Tk 8.3 было добавлено окно tk_chooseDirectory. J-Jl НАЛОГОВЫЕ окна используются почти всеми приложениями с графическим пользовательским интерфейсом. В некоторых случаях приложение должно приостановить свое выполнение до получения данных от пользователя. Диалоговое окно отображает некоторую информацию и предоставляет элементы управления. Прежде чем приложение продолжит свою работу, пользователь должен выполнить определенные действия. Для того чтобы обеспечить подобное поведение, программа перехватывает фокус ввода и запрещает пользователю взаимодействовать со всеми компонентами, отображаемыми на экране, за исключением диалогового окна. В Тк реализован ряд встроенных диалоговых окон, в частности стандартные средства доступа к файлам и выбора цветов. На каждой платформе для всех стандартных диалоговых окон имеется один и тот же Tcl-интерфейс, реализованный с помощью платформенно-ориентированных библиотек. Благодаря этому обеспечивается внешний вид диалоговых окон, типичных для каждой платформы. В данной главе описываются встроенные диалоговые окна Тк, а также рас-
826 Часть V. Особенности работы Тк сматриваются вопросы получения фокуса ввода и использования механизма захвата. Стандартные диалоговые окна Команда tk_dialog отображает набор кнопок и возвращает номер кнопки, на которой пользователь щелкнул мышью. Эта команда вызывается следующим образом: tk_dialog окно заголовок текст 6итовая_карта значение_по_умолчанию ?метка? ?метка? ... Заголовок отображается в строке заголовка, текст — в самом диалоговом окне, а битовая карта выводится слева от текста. Если битовая карта не используется, вместо соответствующего параметра надо указать {}. Сведения о встроенных битовых картах приводятся в главе 41. С помощью меток задаются надписи на кнопках, расположенных в нижней части диалогового окна. Пятый параметр задает индекс кнопки по умолчанию. Индекс отсчитывает- ся начиная с нуля. Если кнопка по умолчанию не используется, вместо этого параметра задается значение {} или -1. Окна сообщений Диалоговое окно tk_messageBox представляет собой ограниченный вариант tk_dialog. Его реализация зависит от конкретной платформы. Подобно tk_dialog, tk.messageBox предоставляет сообщение, битовую карту и набор кнопок. Однако возможные совокупности кнопок заранее определены, а набор битовых карт ограничен. Набор кнопок yesno содержит кнопки Yes и No. В набор abortretryignore входят кнопки Abort, Retry и Ignore. Команда tk_messageBox возвращает символьное имя выбранной кнопки (например, yes или retry). Окно yesnocancel в основном используется тогда, когда необходимо предоставить пользователю возможность завершить свои действия, не сохраняя внесенные изменения. set choice [tk_messageBox -type yesnocancel -default yes \ -message "Save changes before quitting?" \ -icon question] Полный набор опций команды tk_messageBox приведен в табл. 39.1. Таблица 39.1. Опции tk_messageBox -default имя Имя кнопки по умолчанию (например, yes) -icon имя Допустимые имена: error, info, question и warning -message строка Сообщение, предназначенное для отображения
Глава 39. Диалоговые окна и фокус ввода 827 Окончание табл. 39.1 -parent окно Включает диалоговое окно в указанное окно -title заголовок Заголовок диалогового окна (Unix и Windows) -type тип Допустимые типы: abortretrycancel, ok, okcancel, retrycancel, yesno, и yesnocancel Диалоговые окна для работы с файлами и каталогами Для работы с файлами предназначены стандартные диалоговые окна tk_getOpenFile и tk_getSaveFile, а окно tk_chooseDirectory ориентировано на выполнение действий с каталогами. Окно tk_getOpenFile позволяет находить существующие файлы, a tk.getSaveFile предоставляет возможность определить новый файл. Диалоговое окно tk_chooseDirectory, реализованное в Тк 8.3, предоставляет пользователю средства для выбора каталога. Указанные команды возвращают имя выбранного файла или каталога. Если же пользователь отменил операцию, то возвращается пустая строка. При вызове данных команд могут быть указаны опции, перечисленные в табл. 39.2. Таблица 39.2. Опции стандартных диалоговых окон для работы с файлами и каталогами -def aultextension Если расширение не указано, добавляется расши- расширение рение, заданное посредством этой опции. Только для окон tk.getOpenFile и tk_getSaveFile -filetypes список_типов Заданный список определяет набор типов файлов, предлагаемых на выбор пользователю. Данная опция позволяет ограничить набор файлов, отображаемых в диалоговом окне. Только для окон tk_getOpenFile и tk_getSaveFile -initialdir каталог Отображает содержимое указанного каталога при выводе окна. Если данная опция не задана, отображается текущий каталог -initialfile файл Файл по умолчанию. Только для окна tk_getSaveFile -message строка Сообщение, которое должно быть включено в клиентскую область диалогового окна. (Данная опция имеет смысл лишь для системы Macintosh при установленном Navigation Services.) Только для окон tk_getOpenFile и tk_getSaveFile (Tk 8.3.1) -multiple Позволяет пользователю выбрать несколько файлов. Информация о них возвращается в виде списка. Только для окна tk.getOpenFile (Tk 8.4)
828 Часть V. Особенности работы Тк Окончание табл. 39.2 -mustexist Если значение данной опции — false (принимает- логическое_значение ся по умолчанию), пользователь имеет право задавать несуществующие каталоги. Только для окна tk_chooseDirectory -parent окно Создает диалоговое окно как дочернее для указанного окна. Дочернее окно отображается поверх родительского -title строка Отображает заданную строку в заголовке окна (Unix и Windows) В состав диалогового окна, предназначенного для выбора файла, может входит окно списка, в котором задаются типы файлов. Содержимое окна списка ограничивает набор файлов, отображаемых в диалоговом окне лишь указанными типами. Значение typelist задает набор расширений файлов и типов файлов Macintosh. Если данное значение не определено, пользователю будет предоставлена информация обо всех файлах, находящихся в каталоге. Каждый элемент typelist представляет собой список, содержащий три следующих значения: имя расширения ?типы__соответствия? Имя отображается в списке типов файлов. Расширения — это список расширений файлов, соответствующих указанному типу. Пустому элементу расширений () соответствуют все файлы без расширений, а символ * отмечает все файлы. Тип соответствия — необязательный элемент. Он представляет собой список четырехсимвольных обозначений типов файлов Macintosh, который игнорируется на других платформах. Если, работая в системе Macintosh, вы зададите и расширения, и типы соответствия, файл должен будет соответствовать им обоим. Если в качестве расширений задается пустой список, принимаются во внимание только типы соответствия. Вы можете также д;важды задать в typelist одно и то же имя, указать для одного из них расширения, а для другого типы соответствия. Если вы поступите таким образом, г, окне будут отображаться файлы, удовлетворяющие одному из двух критериев. Приведенное ниже значение typelist определяет файлы Framemaker Interchange Files, которые имеют расширение .mif и принадлежат типу MIF. set typelist { {"Maker Interchange Files" {".mif"} {"MIF "}} }
Глава 39. Диалоговые окна и фокус ввода 829 Следующее значение typelist отмечает файлы изображений, представленные в формате GIF. Они имеют расширение .gif и принадлежат типу GIFF. Заметьте, что тип соответствия может не указываться. set typelist { {"GIF Image" {".gif"}} {"GIF Image" {} {"GIFF"}}} } Ниже приведено значение typelist, объединяющие обе категории файлов, описанные выше. Пункт, содержащийся первым в списке, отображается раньше других. set typelist { {"All Files" {*}} {"GIF Image" {".gif"}} {"GIF Image" {} {"GIFF"}} {"Maker Interchange Files" {".mif"} {"MIF "}} } Диалоговые окна для выбора цвета Диалоговое окно tk_chooseColor отображает набор цветов, из которого пользователь может выбрать требуемое значение. В результате выполнения данной команды возвращается цвет либо, если пользователь отменил операцию, пустая строка. Опции команды tk_chooseColor описаны в табл. 39.3. Таблица 39.3. Опции tk_chooseColor -initialcolor цвет Цвет для начала отображения -parent окно Диалоговое окно создается как встроенное дочернее для указанного окна -title строка Отображает строку в заголовке окна (Unix и Windows) Диалоговые окна, определяемые разработчиком Для того чтобы создать новое диалоговое окно, необходимо представлять себе фокус ввода и механизм захвата, а также уметь реализовать программу так, чтобы она ожидала, когда пользователь закончит действия с диалоговым окном. Код, с помощью которого создается диалоговое окно, имеет следующую структуру:
830 Часть V. Особенности работы Тк # Следующие действия выполняются после создания компонентов focus $toplevel grab $toplevel tkwait window $toplevel Данная последовательность команд передает фокус ввода окну верхнего уровня, содержащему диалоговое окно. Команда grab ограничивает действия пользователя, позволяя ему работать только с диалоговым окном. Другие окна приложения доступны только после закрытия диалогового окна. Команда tkwait завершает свое действие при закрытии окна верхнего уровня, в этом случае результаты захвата, осуществленного с помощью команды grab, отменяются. При этом предполагается, что окно верхнего уровня разрушается с помощью команды, связанной с кнопкой в диалоговом окне. В последующих разделах основные этапы создания диалогового окна будут рассмотрены более подробно, а в листинге 39.1 будет показан код, элементы которого можно использовать на практике. Фокус ввода В оконной системе события клавиатуры направляются тому окну верхнего уровня, которое имеет в этот момент фокус ввода. Приложение, в свою очередь, направляет событие одному из компонентов в составе этого окна. Команда focus передает фокус ввода конкретному компоненту; она используется в некоторых связываниях, заданных по умолчанию для компонентов Тк. При переходе от одного окна верхнего уровня к другому Тк запоминает, какой из компонентов имел фокус ввода. При возврате к предыдущему окну этот компонент автоматически получает фокус. В системе Windows и Macintosh фокус передается приложению после щелчка мышью в его окне. В системе Unix передачей фокуса ввода управляет диспетчер окон. Модель "щелчок-ввод", согласно которой после щелчка мышью на компоненте становится доступным ввод с клавиатуры, используется одинаковым образом в Windows и Macintosh. В этих же системах может применяться и модель отслеживания фокуса, согласно которой фокус ввода автоматически передается окну, на которое указывает курсор мыши. Следует помнить, что щелчок мышью, посредством которого передается фокус ввода, недоступен приложению. Если приложение имеет фокус ввода, вы можете организовать передачу его между различными компонентами. Модель "щелчок-ввод" используется в Тк по умолчанию. Текстовый компонент и поле редактирования получают фокус ввода тогда, когда пользователь щелкает на них левой кнопкой мыши. Для того чтобы использовать модель отслеживания фокуса, надо вызвать tk_f ocusFollowsMouse. Следует помнить, что в большинстве случаев
Глава 39. Диалоговые окна и фокус ввода 831 традиционная модель "щелчок-ввод" лучше подходит для работы с приложением. Если размеры компонентов невелики, удерживать курсор на них бывает трудно. Команда focus В табл. 39.4 приведены сведения о команде focus. Средства, обеспечивающие передачу фокуса ввода, поддерживают работу с несколькими терминалами, на каждом из которых может находиться окно, обладающее фокусом. Это удобно при работе в среде Unix, где система X Window обеспечивает отображение данных на нескольких дисплеях. Опция -displayof может быть использована для передачи фокуса на конкретном дисплее. Опция -lastfor дает возможность выяснить, какой компонент в пределах окна верхнего уровня последним обладал фокусом ввода. Если компонент, обладающий фокусом ввода, удаляется, Тк возвращает фокус предыдущему владельцу. Если фокус ввода не требуется ни одному из компонентов, он передается окну верхнего уровня. Таблица 39.4. Команда focus focus Возвращает компонент, отображающийся на том же дисплее, что и главное окно приложения, который в данный момент имеет фокус ввода focus ?-force? окно Передает фокус ввода указанному окну. Опция -force приводит к тому, что оконный диспетчер игнорируется, поэтому использовать ее следует только в случае крайней необходимости focus -displayof окно Возвращает компонент, отображающийся на том же дисплее, что и указанное окно, который в данный момент имеет фокус ввода focus -lastfor окно Возвращает последний из компонентов, который имел фокус ввода Передача фокуса ввода Пользователь может осуществлять передачу фокуса ввода от одного компонента другому, нажимая клавиши <ТаЬ> или <Shift+Tab>. Порядок передачи фокуса, учитываемый при выполнении процедур tk_focusNext и tk_f ocusPrev, определяется порядком, в котором создавались компоненты. Процедуры tk_focusNext и tk_focusPrev связываются с событиями <ТаЬ> и <Shift-Tab>.
832 Часть V. Особенности работы Тк bind all <Tab> {tk_focusNext °/0W} bind all <Shift-Tab> {tk_focusPrev °/0W} Компоненты Тк, получившие фокус ввода, подсвечиваются. Размеры элементов подсветки задаются с помощью атрибута highlightThickness, а цвет подсветки определяется значением атрибута highlightColor. Для компонентов Тк, в частности, для кнопок и полос прокрутки создаются связывания, посредством которых поддерживается управление компонентами с клавиатуры. Если кнопка имеет фокус ввода, то событие <space> приводит к выполнению связанной с ней команды. Каждый компонент имеет атрибут takeFocus. Значение этого атрибута используется процедурами tk_f ocusNext и tk_f ocusPrev для того, чтобы выяснить, должен ли компонент получать фокус ввода с помощью клавиатуры. Допустимые значения атрибута takeFocus перечислены ниже. • Значение 0 указывает на то, что компонент никогда не должен получать фокус ввода. • Значение 1 указывает на то, что компонент всегда должен получать фокус ввода. • Пустая строка означает, что решение о передаче компоненту фокуса ввода принимают процедуры tk_f ocusNext и tk_f ocusPrev на основании состояния компонента и определенных для него связываний. • Значением является префикс Tcl-команды. В этом случае команда вызывается, а в качестве параметра ей передается имя компонента. В результате выполнения команда должна вернуть значение 0, 1 или пустую строку. Захват ввода Захват изменяет нормальную работу механизма поддержки фокуса ввода. Например, диалоговое окно может захватить ввод, в результате чего пользователю будет запрещено взаимодействовать с другими окнами приложения. Захват ввода в основном используется тогда, когда приложение должно выполнить некоторую задачу, для чего ему нужно получить данные от пользователя. Вследствие захвата возможности пользователя по вводу данных ограничиваются, и он не может перевести приложение в недопустимое состояние. Для организации захвата обычно бывает достаточно команд grab и grab release. Заметьте, что синонимом grab является команда grab set. Сведения о команде grab приводятся в табл. 39.5. Глобальный захват исключает работу пользователя с другими приложениями и даже с диспетчером окон. Так, например, глобальный захват используется в Тк для обеспечения работы меню; это позволяет реагировать
Глава 39. Диалоговые окна и фокус ввода 833 на щелчки мышью, независимо от того, в какой точке экрана расположен курсор. Глобальный захват очень полезен при запросе пароля. При этом исключается возможность случайного ввода конфиденциальных данных в окне другого приложения. Далее в данной главе будут приведены примеры, в которых используется команда grab. Таблица 39.5. Команда grab Команда tkwait Команда tkwait переводит программу в режим ожидания; этот режим длится до тех пор, пока пользователь не выполнит необходимые действия с диалоговым окном. В это время может осуществляться обработка событий. Подобно vwait, с помощью команды tkwait можно ожидать изменения значения переменной. В качестве событий, при наступлении которых прекращается ожидание, можно указать разрушение окна или момент, когда оно становится видимым. Сведения о команде tkwait приведены в табл. 39.6. Таблица 39.6. Команда tkwait tkwait variable переменная Ожидание изменения глобальной переменной. В данном случае tkwait действует как команда vwait tkwait visibility окно Ожидает, пока указанное окно отобразится на экране tkwait window окно Ожидает, пока указанное окно будет удалено При вызове tkwait следует задавать глобальные переменные. Переменная, указываемая при вызове команды tkwait variable, должна быть глобальной. Это надо помнить, применяя для модификации переменной процедуру. Если переменная не объявлена как глобальная, tkwait не отреагирует на изменение ее значения. grab ?-global? окно Установка захвата для указанного окна grab current ?окно? Запрашивает сведения о захвате на том дисплее, на котором отображается указанное окно. Если окно не задано, запрос осуществляется для всех дисплеев grab release окно Отмена захвата для указанного окна grab set ?-global? окно Установка захвата для указанного окна grab status окно Возвращает одно из следующих значений: попе, local или global
834 Часть V. Особенности работы Тк При использовании операции tkwait visibility программа ожидает изменения внешнего вида окна. Как правило, эта команда применяется тогда, когда необходимо дождаться появления на экране вновь созданного окна. Например, если в сложном диалоговом окне вы собираетесь отображать анимационные изображения, то запускать анимационную последовательность можно только тогда, когда окно появится на экране. Удаление компонентов Команда destroy удаляет один или несколько компонентов. Если в состав компонента входят дочерние окна, они также удаляются. В главе 44 описывается протокол, используемый при поддержке событий, генерируемых диспетчером окон и соответствующих удалению окна. Ожидание момента удаления окна можно реализовать с помощью команды tkwait window. Совместное использование команд focus, grab и tkwait При решении практических задач работа с диалоговым окном не ограничивается применением команд focus, grab и tkwait. Как вы помните, после окончания работы с диалоговым окном фокус ввода получает тот компонент, который имел его ранее. Для того чтобы приложение работало более надежно, желательно перед удалением диалогового окна явным образом восстановить фокус ввода. Это исключит конфликты между вашим приложением и диспетчером окон. Для решения этой задачи можно использовать последовательность команд, подобную приведенной ниже. set old [focus] focus $toplevel grab $toplevel tkwait variable переменная grab release $toplevel focus $old destroy $toplevel Вместо того чтобы удалять диалоговое окно, можно лишь отменить его отображение. Если вы поступите таким образом, то при следующем обращении время, требуемое для вывода диалогового окна, уменьшится. Данный подход несколько усложняет создание диалогового окна, поскольку вам необходимо проверять, существует ли окно верхнего уровня. В главе 44 будут описаны команды оконного диспетчера, используемые для установки и отмены отображения окон. В листинге 39.1 показаны процедуры Dialog_Create, Dialog_Wait и Dialog_Dismiss, в которых реализован описанный подход.
Глава 39. Диалоговые окна и фокус ввода 835 Листинг 39.1. Процедуры, упрощающие создание диалоговых окон proc Dialog_Create {top title args} { global dialog if [winfo exists $top] { switch -- [wm state $top] { normal { # Инициализация скрытого окна raise $top } withdrawn - iconic { # Восстановление размеров wm deiconify $top catch {wm geometry $top $dialog(geo,$top)} } } return 0 } else { eval {toplevel $top} $args wm title $top $title return 1 } } proc Dialog_Wait {top varName {focus {}}} { upvar $varName var # Запись переменной при разрушении окна пользователем bind $top <Destroy> [list set $varName cancel] # Захват фокуса диалоговым окном if {[string length $focus] == 0} { set focus $top } set old [focus -displayof $top] focus $focus catch {tkwait visibility $top} catch {grab $top} # Ожидание завершения действий окном tkwait variable $varName
836 Часть V. Особенности работы Тк catch {grab release $top} focus $old } proc Dialog__Dismiss {top} { global dialog # Сохранение текущих размеров и позиции catch { # Окно могло быть удалено set dialog(geo,$top) [wm geometry $top] wm withdraw $top } } Процедура Dialog_Wait обеспечивает передачу фокуса компоненту, отличному от окна верхнего уровня. Благодаря этому в начале работы с диалоговым окном фокус ввода может принадлежать конкретному компоненту, например полю редактирования. В противном случае пользователю перед началом ввода пришлось бы щелкать мышью на соответствующем элементе. При выполнении команды grab может возникнуть ошибка. Опыт использования процедур, подобных Dialog_Wait, на различных платформах показал, что в составе данной процедуры желательно использовать выражение catch. В некоторых случаях бывает необходимо использовать tkwait visibility, поскольку, если диалоговое окно не отображается на экране, при выполнении команды grab может возникнуть ошибка. В некоторых системах могут быть обстоятельства, при которых сама операция tkwait visibility завершится с ошибкой. Тк обрабатывает эти ошибки, поэтому единственным их следствием является отсутствие захвата. Пользователь может взаимодействовать с диалоговым окном и без захвата, поэтому в приведенном выше примере данные ошибки игнорируются. Диалоговое окно для ввода строки Листинг 39.2. Простое диалоговое окно proc Dialog_Prompt { string } { global prompt set f .prompt if [Dialog_Create $f "Prompt" -borderwidth 10] { message $f.msg -text $string -aspect 1000 entry $f.entry -textvariable prompt(result)
Глава 39. Диалоговые окна и фокус ввода 837 set b [frame $f.buttons] pack $f.msg $f.entry $f.buttons -side top -fill x pack $f.entry -pady 5 button $b.ok -text OK -command {set prompt(ok) 1} button $b.cancel -text Cancel \ -command {set prompt(ok) 0} pack $b.ok -side left pack $b.cancel -side right bind $f.entry <Return> {set prompt(ok) 1 ; break} bind $f.entry <Control-c> {set prompt(ok) 0 ; break} } set prompt(ok) 0 Dialog.Wait $f prompt(ok) $f.entry Dialog_Dismiss $f if {$prompt(ok)} { return $prompt(result) } else { return {} } } Dialog_Prompt "Please enter a name" В листинге 39.2 показан код процедуры Dialog_Prompt. Эта процедура реализует диалоговое окно, которое запрашивает данные у пользователя и возвращает введенное значение. Если пользователь отменил операцию, возвращается пустая строка. На окончание работы с диалоговым окном указывает Tcl-переменная prompt (ok). Значение переменной устанавливается после щелчка на кнопке ОК или Cancel либо после нажатия клавиш < Ret urn > или <Ctrl4-C>. Процедура Dialog_Wait ожидает изменения значения prompt (ok), захватывает и освобождает ввод. Процедура Dialog_Create возвращает значение 1, если диалоговое окно создается, значение 0 указывает на то. что окно уже существует.
838 Часть V. Особенности работы Тк Комбинации клавиш и фокус ввода Процедура Dialog_Wait передает фокус ввода полю редактирования в составе диалогового окна. Работа пользователя упростится, если он сможет завершать работу с окном нажатием клавиш. Для этого необходимо установить соответствующие связывания. При их отсутствии пользователь вынужден будет после окончания ввода закрывать окно с помощью мыши. В данном примере созданы связывания для событий <Return> и <Control-c>. При возникновении этих событий выполняются те же действия, что и при активизации кнопок ОК и Cancel. Данные связывания включают команду break, поэтому остальные связывания отменяются. Если бы эта команда отсутствовала, то активизировалось бы связывание для класса Entry и в поле редактирования был бы введен дополнительный символ. Анимация и команда update В некоторых случаях желательно обеспечить изменения внешнего вида интерфейса в то время, когда приложение занято обработкой данных. По умолчанию до окончания обработки интерфейс остается статичным. Даже если вы измените текстовую метку или поле редактирования, окно обновится лишь в момент бездействия программы. Пользователь не увидит отклика от приложения. Решить данную проблему можно, используя команду update. Эта команда заставляет Тк продолжить цикл обработки событий и обновить данные на экране. В листинге 39.3 приведен код процедуры Feedback, которая обеспечивает отображение сообщений. Информация выводится в поле редактирования, находящемся в режиме только для чтения. Благодаря использованию команды update пользователь видит все сообщения. В данном примере использовано поле редактирования, так как размеры его не изменяются при любой длине сообщения, а перетаскивая курсор мыши при нажатой средней кнопке, можно прокручивать содержимое компонента. Операция update idletasks, которая будет описана ниже, улучшает работу компонента. Листинг 39.3. Процедура Feedback proc Feedback { message } { global feedback set e $feedback(entry) $e config -state normal $e delete 0 end $e insert 0 $message # Оставить поле редактирования в состоянии только для чтения
Глава 39. Диалоговые окна и фокус ввода 839 $е config -state disabled # Принудительное обновление изображения update idletasks 2 Внешний вид компонентов Тк обновляется в моменты бездействия приложения, т.е. тогда, когда приложение выполнило свои текущие задачи. В результате все действия по обновлению происходят в течение одного сеанса взаимодействия с оконной системой. В системе Unix такой подход оптимизирует пакетную обработку данных. Операция update idletasks приводит к немедленному выполнению всех ожидающих своей очереди действий по обновлению внешнего вида интерфейса. О цикле обработки событий Тк см. в главе 16. По возможности следует использовать операцию update idletasks. Наиболее безопасный способ обновления отображаемых данных — это использование команды update idletasks. В случае обычной команды update обрабатываются все события, в том числе события, связанные с вводом данных пользователем. При этом может быть запущен еще один поток, что не исключает возникновения непредвиденных результатов. Текущий поток приостанавливается, и выполняются команды обратного вызова, связанные с пользовательскими событиями. Для обработки действий пользователя по вводу данных лучше использовать команду tkwait, так как она приостанавливает основное приложение в определенной точке. Операция update idletasks имеет небольшой недостаток: в некоторых случаях при ее использовании изменение внешнего вида компонента происходит в ответ на системное событие. Например, если вы измените текст в составе метки, это может привести к изменению размеров самой метки. В этом случае поведение компонента оказывается "слишком интеллектуальным". Вместо того чтобы спланировать повторный вывод на время, когда программа будет находиться в состоянии бездействия, он запрашивает новый размер, а затем ожидает события <Conf igure>, генерируемого оконной системой. По событию <Conf igure> устанавливается размер, выбранный диспетчером компоновки. Таким образом, изменение текста метки и выполнение команды update idletasks происходят не так, как это планировал разработчик.
Глава 40 Атрибуты компонентов Тк Для каждого компонента Тк определен ряд атрибутов, которые влияют на его внешний вид и поведение. В данной главе приводятся общие сведения об атрибутах, кроме того, здесь описываются те из них, которые влияют на размеры компонентов. В последующих двух главах будут рассмотрены атрибуты, посредством которых задаются цвет, изображения и текст. г/_1анная глава посвящена рассмотрению атрибутов, определенных для большинства компонентов Тк. В каждом компоненте обязательно предусмотрены значения атрибутов по умолчанию, поэтому разработчик избавлен от необходимости указывать их при каждом обращении к атрибуту. Если же вы хотите настроить компонент для конкретных условий работы, вы можете переопределить требуемые значения. Платформенно-ориентированные компоненты, реализованные в Тк 8.0, игнорируют некоторые атрибуты Тк. Причина в том, что соответствующие системные компоненты не поддерживают их. Например, в системе Macintosh атрибут borderWidth для кнопки не имеет смысла, кроме того, кнопки в этой системе не подсвечиваются при наличии фокуса ввода. Аналогичные ограничения накладываются на полосы прокрутки в системах Windows и Macintosh. В данной главе при обсуждении каждого атрибута ограничения, накладываемые на их использование, оговариваются особо. Установка значения атрибутов Атрибуты задаются при создании компонентов Тк. Кроме того, значения атрибутов можно изменять динамически в процессе работы. В обоих случаях в составе соответствующей команды задаются пары параметров. Первый
Глава 40. Атрибуты компонентов Тк 841 элемент пары идентифицирует атрибут, а второй определяет значение этого атрибута. Например, команда создания кнопки имеет следующий вид: button .doit -text Doit -command DoSomething В данном случае создаваемая кнопка имеет имя .doit. Для нее определены два атрибута: text и command. По необходимости характеристики кнопки можно переопределить позже с помощью операции configure данного компонента. .doit configure -text Stop -command Stoplt Информацию о текущей конфигурации компонента можно получить с помощью все той же операции configure, записанной в другой форме. Если вы зададите лишь опцию, соответствующую атрибуту, команда вернет значение этого атрибута. .doit configure -text => -text text Text { } Stop Данная команда возвращает следующие данные: опцию командной строки, имя ресурса, имя класса ресурса, значение по умолчанию и текущее значение. Если при вызове операции configure ни одна опция не задана, возвращается информация о всех атрибутах компонента. Следующий фрагмент кода обеспечивает форматированный вывод этих данных: foreach item [$w configure] { puts "[lindex $item 0] [lindex $item 4]" } Если вам надо лишь получить текущее значение атрибута, его может предоставить операция cget. .doit cget -text => Stop Значения атрибутов компонентов можно также установить косвенным образом, используя базу данных ресурсов. Преимущество такого подхода состоит в том, что пользователь может изменить конфигурацию своего приложения, не затрагивая кода. Если атрибуты задаются непосредственно в программе, изменить их можно, лишь модифицировав код. Возможность оперативного изменения атрибутов в особенности важно в том случае, если необходимо изменять шрифты или цвета. В таблицах данной главы для обозначения атрибутов используются имена ресурсов, т.е. слова, составляющие имя (кроме первого), начинаются с прописной буквы (например, activeBackground). В Tcl-команде атрибуты задаются с помощью опций. Имя опции совпадает с именем атрибута, за исклю-
842 Часть V. Особенности работы Тк чением того, что он начинается с дефиса и содержит только буквы нижнего регистра. Сравните следующие строки: option add *Button.activeBackground red $button configure -activebackground red Первая команда определяет ресурс, который воздействует на все кнопки, а вторая команда изменяет внешний вид существующей кнопки. Установки, заданные в базе данных ресурсов, можно переопределить, указав соответствующие параметры в командной строке. О работе с ресурсами см. в главе 31. Размеры компонента Для большинства компонентов определены атрибуты width и height, посредством которых задаются требуемые размеры. Если эти атрибуты не указаны либо значения их заданы равными 0 или отрицательными, компонент автоматически устанавливает свои размеры так. чтобы его содержимое могло отображаться в нем. Начиная с Тк 8.4, в системе Windows для кнопок (но не для компонентов checkbutton, radiobutton или menubutton) могут задаваться отрицательные значения атрибута width, определяющие минимальную ширину компонента. Такой шаг предпринят для обеспечения наилучшего соответствия соглашениям по отображению интерфейсных элементов в системе Windows. В любом случае диспетчер компоновки может изменить размеры компонента на несколько процентов. Операция winf о, которая будет описана в главе 44, предоставляет информацию о текущих размерах компонента. Для большинства компонентов, ориентированных на работу с текстом, ширина определяется как число символов, а высота ■— как количество строк. Для остальных компонентов, в том числе для компонента message, размеры представляются в единицах измерения экранных объектов (по умолчанию — в пикселях). Команда tk scale, которая будет рассмотрена в главе 44, управляет преобразованием размеров, заданных в пикселях, в другие единицы измерения и наоборот. Единицы измерения определяются с помощью следующих суффиксов: • с — сантиметры; • i — дюймы; • м — миллиметры; • р — пункты (1/72 дюйма). Линейные регуляторы и полосы прокрутки могут быть, в зависимости от атрибута orient, ориентированы двумя способами, поэтому высоту и ширину элемента задавать не следует. Указанные компоненты не поддерживают
Глава 40. Атрибуты компонентов Тк 843 атрибут height, а значение атрибута width интерпретируется как размер короткой стороны прямоугольника, ограничивающего элемент. Для линейного регулятора предусмотрен атрибут length, с помощью которого задается длина компонента. Для полос прокрутки не указывается даже ширина. Считается, что они объединяются с компонентами, которыми управляют, и при компоновке используется атрибут fill, который устанавливает длину полосы прокрутки, равной соответствующему размеру связанного с ней компонента. Пример сопряжения полосы прокрутки с другим компонентом см. в листинге 33.1. Компонент message отображает заданную последовательность символов в нескольких строках. Для ограничения его размеров используется либо атрибут aspect, либо width. Коэффициент сжатия (aspect ratio) определяется как частное от деления ширины компонента на его высоту, умноженное на 100. Текст форматируется так, чтобы выполнялось указанное соотношение. Если для компонента задана ширина, то он формирует необходимое количество строк символов (которым определяется его высота). Пример отображения текста посредством компонента message см. в листинге 32.6. Атрибуты, с помощью которых определяются размеры компонентов, перечислены в табл. 40.1. Таблица 40.1. Атрибуты, используемые для определения размеров компонентов aspect Коэффициент сжатия компонента message, т.е. частное от деления ширины компонента на его высоту, умноженное на 100 height Высота, заданная в экранных единицах измерения или указанная как число текстовых строк. Компоненты: button, canvas, checkbutton, frame, label, labelframe, listbox, menubutton, panedwindow, radiobutton, text и toplevel length Длина для компонента scale orient Ориентация длинных и узких компонентов или размещение панелей в составе компонента panedwindow. Значение horizontal или vertical. Компоненты panedwindow, scale и scrollbar width Ширина, заданная в экранных единицах измерения или указанная как число текстовых строк. Компоненты: button, canvas, checkbutton, entry, frame, label, labelframe, listbox, menubutton, message, panedwindow, radiobutton. scale, scrollbar, spinbox, text и toplevel Размеры компонентов, предназначенных для отображения текста, представляются как число символов и строк. В некоторых случаях это может создавать неудобства при написании программ. Размеры таких компонентов
844 Часть V. Особенности работы Тк изменяются при изменении шрифта, вследствие чего оказывается невозможным выбрать точную позицию для компонента. Диспетчеры компоновки pack и grid предоставляют компонентам самостоятельно определять свои размеры. Если вам необходимо задавать размеры элементов интерфейса явным способом, вы можете помещать каждый компонент (например, текстовую метку) во фрейм. Для фрейма можно задать требуемые размеры и запретить их изменение при компоновке. В листинге 40.1 приведен пример использования подобного подхода. Листинг 40.1. Создание текстовых меток с одинаковыми размерами proc EqualSizedLabels { parent width height strings args } { set 1 0 foreach s $strings { frame $parent.$l -width $width -height $height pack propagate $parent.$l false pack $parent.$l -side left eval {label $parent.$l.l -text $s} $args pack $parent.$l.l -fill both -expand true incr 1 } } frame .f ; pack .f EqualSizedLabels .f li lc {apple orange strawberry kiwi} \ -relief raised В данном случае все фреймы $parent. $1 имеют одинаковые размеры. Команда pack propagate предотвращает изменение размеров фреймов при включении в них текстовых меток. При компоновке текстовых меток включаются режимы fill и expand, чтобы метка заполняла фрейм фиксированных размеров, в который она помещается. Получить компоненты одинаковых размеров можно также, используя опцию -uniform, которая была добавлена к диспетчеру компоновки grid в Тк 8.4. Подробно о данном подходе см. в главе 26.
Глава 40. Атрибуты компонентов Тк 845 Обрамление и рельеф В листинге 40.2 показан пример использования опции -relief, с помощью которой задаются различные типы обрамления вокруг компонента. Листинг 40.2. Обрамление с имитацией трехмерного представления frame .f -borderwidth 10 pack .f foreach relief {raised sunken flat ridge groove solid} { label .f.$relief -text $relief -relief $relief \ -bd 2 -padx 3 pack .f.$relief -side left -padx 4 2 Характеристики рамки вокруг компонента задаются с помощью двух атрибутов: borderWidth и relief. Атрибут borderWidth задает размер области вокруг компонента, а атрибут relief определяет, как именно должна отображаться эта область. Рельеф solid был добавлен в Тк 8.0 для обеспечения представления компонентов, принятого в системе Macintosh. Использовать его лучше всего в тех случаях, когда компонент отображается на белом фоне. В системе Macintosh для кнопок не поддерживаются рельеф и толщина обрамления. Атрибут activeBorderWidth определяет толщину рамки для компонентов меню. Задавать рельеф меню нет необходимости, так как атрибуты по умолчанию в большинстве случаев полностью устраивают разработчиков. Плат- форменно-ориентированные меню для систем Windows и Macintosh не поддерживают атрибут activeBorderWidth. Атрибут activeRelief применяется для отображения элементов полосы прокрутки (ползунка и стрелок) при попадании на них курсора мыши. С помощью атрибута elementBorderWidth устанавливается размер обрамления для этих элементов. Изменять значение activeRelief не рекомендуется, так как оно специально выбрано с учетом особенностей отображения элементов. Как правило, изменение этого атрибута ухудшает внешний вид полосы прокрутки. Платформенно-ориентированные меню для систем Windows и Macintosh не поддерживают данный атрибут.
846 Часть V. Особенности работы Тк Атрибуты of f Relief и overRelief используются соответственно для компонента, находящегося в неактивном состоянии, и компонента, на котором расположен курсор мыши. Эти атрибуты были добавлены Тк 8.4 и предназначаются в основном для создания панелей инструментов. Атрибут overRelief применим к компонентам button, checkbutton и radiobutton. Атрибут of f Relief может использоваться только с флажками и переключателями опций. В табл. 40.2 перечислены атрибуты, предназначенные для управления обрамлением и рельефом. Таблица 40.2. Имена ресурсов для атрибутов, управляющих обрамлением и рельефом activeBorderWidth activeRelief borderWidth bd elementBorderWidth offRelief overRelief relief Толщина обрамления для пунктов меню. Только для системы Unix Рельеф для активизированных элементов полосы прокрутки. Только для системы Unix Толщина обрамления вокруг компонента, представленная в экранных единицах измерения. Данный атрибут применим ко всем компонентам Сокращенное представление borderwidth. Используется только в Тс1-командах Толщина обрамления для элементов scrollbar и scale Альтернативный рельеф, для не выбранного компонента. Компоненты: checkbutton и radiobutton (Tk 8.4) Альтернативный рельеф, отображаемый в тот момент, когда курсор мыши расположен на компоненте. Компоненты: checkbutton и radiobutton (Tk 8.4) Внешний вид обрамления: flat, raised, sunken, ridge, groove или solid. Данный атрибут применим ко всем компонентам Подсветка компонентов, обладающих фокусом ввода Для того чтобы пользователь мог определить, какой компонент обладает фокусом ввода, его внешний вид несколько изменяется. Это изменение внешнего вида называется подсвечиванием. По умолчанию вокруг компонента отображается прямоугольный контур; цвет контура совпадает с цветом фона. Когда компонент получает фокус ввода, этот прямоугольник изменяет свой цвет. Помимо элемента подсветки, к обрамлению, описанному в преды-
Глава 40. Атрибуты компонентов Тк 847 дущем разделе, добавляется небольшая область. Атрибуты, представленные в табл. 40.3, управляют размерами и цветом прямоугольника, используемого для подсветки. Если толщина равна нулю, элемент подсветки не отображается. Таблица 40.3. Имена ресурсов, управляющих элементом подсветки highlightColor Цвет, который используется для подсветки, когда компонент имеет фокус ввода highlightBackground Цвет, который используется для подсветки, когда компонент не имеет фокуса ввода highlightThickness Толщина прямоугольника, указывающего на наличие фокуса ввода По умолчанию рамка подсветки ненулевой толщины отображается только для тех компонентов, которые хмогут иметь фокус ввода. К ним относятся text, entry и listbox. К этой же группе можно отнести и меню, так как для этого компонента установлен ряд связываний, имеющих отношение к событиям клавиатуры. Ненулевую толщину элемента подсветки можно задать для любого компонента, за исключением кнопок в системе Macintosh. Дополнение и точки фиксации В табл. 40.4 перечислены атрибуты, управляющие дополнениями и точками фиксации. Они действуют подобно соответствующим атрибутам диспетчера компоновки (см. главу 25). Однако между этими атрибутами есть определенные различия, которые описываются в данном разделе. Таблица 40.4. Атрибуты, управляющие дополнениями и точками фиксации anchor Точка фиксации компонента. Значения: n, ne, e, se, s, sw, w, nw или center. Компоненты: button, checkbutton, label, menubutton, message или radiobutton padX, padY Размеры дополнения по горизонтали и по вертикали, представленные в экранных единицах измерения. Компоненты: button, checkbutton, frame, label, labelframe, menubutton, message, radiobutton, text и toplevel Атрибуты дополнения для компонента определяют область, которая никогда не заполняется содержимым этого компонента. Например, если вы создадите текстовую метку и укажете атрибуты так, как показано в листинге 40.3,
848 Часть V. Особенности работы Тк а затем включите эту метку в состав фрейма, то, независимо от значения атрибута anchor, текст будет выровнен по центру. Листинг 40.3. Дополнения для текстовых меток и кнопок label .foo -text Foo -padx 20 -anchor e pack .foo Атрибут anchor воздействует на отображение компонента только в том случае, когда имеется излишек пространства. Можно получить лишнее пространство, установив для атрибута width значение, большее, чем необходимо для отображения текста. В примере, приведенном в листинге 40.4, текст выровнен по правому краю. По умолчанию используется значение padX, соответствующее одному пикселю. Листинг 40.4. Использование точки фиксации для текстовой метки label .foo -text Foo -width 10 -anchor e pack .foo Сформировать дополнительную область можно также, используя опции диспетчера компоновки pack -ipadx и -ipady. Пример, приведенный в следующем разделе, иллюстрирует данный подход. Примеры использования опций диспетчера компоновки pack см. также в главе 25. Совместное использование различных атрибутов Листинг 40.5. Обрамление и дополнение frame .f -bg white label .f.one -text One -relief raised -bd 2 -padx 3m -pady 2m pack .f.one -side top label .f.two -text Two \ -highlightthickness 4 -highlightcolor red \ -borderwidth 5 -relief raised \ -padx 0 -pady 0 \
Глава 40. Атрибуты компонентов Тк 849 -width 10 -anchor nw pack .f.two -side top -pady 10 -ipady 10 -fill both focus .f.two pack .f В процессе разработки некоторые атрибуты, определяющие размер и внешний вид компонентов, могут использоваться некорректно. В листинге 40.5 на примере текстовой метки демонстрируются различия между атрибутами, управляющими размерами, обрамлением, дополнением и подсветкой. Дополнение может быть задано как посредством диспетчера компоновки, так и с помощью атрибутов самого компонента. Для первой текстовой метки задано значение raised атрибута relief, в результате на экране отображается рамка толщиной в два пикселя. По умолчанию подсветка текстовой метки отсутствует. В данном примере применяется внутреннее дополнение, поэтому текст отстоит от границ компонента на заданное расстояние. Для второй метки используется элемент подсвечивания; толщина этого элемента задана отличной от нуля. Для кнопок, полей редактирования, окон списков и текстовых компонентов наличие элемента подсвечивания принимается по умолчанию. Во второй текстовой метке величина дополнения уменьшена до нуля. Точка фиксации задана так, что текст располагается в верхнем левом углу компонента (nw) возле обрамления. Обратите внимание на дополнение, определенное с помощью диспетчера компоновки. В данном случае задано как внешнее, так и внутреннее дополнение по оси у. Внешнее дополнение (опция -pady команды pack) приводит к появлению незаполненного пространства. Внутреннее дополнение (опция -ipady команды pack) используется при отображении текстового компонента. Данное дополнение отличается от дополнения текстовой метки, задаваемого с помощью опции -pady. Дополнение в составе метки сдвигает текст относительно верхней границы компонента.
Глава 41 Цвет, изображения и курсоры В данной главе описываются атрибуты компонентов Тк, предназначенные для управления цветом. В некоторых типах компонентов изображения и битовые карты могут отображаться вместо текста. Данная глава посвящена рассмотрению команд, предназначенных для создания изображений и выполнения различных действий над ними. Атрибуты управления курсором позволяют изменять форму и цвет курсора мыши в тот момент, когда он попадает в область, занимаемую конкретным компонентом. В данной главе также приведены сведения о всех курсорах, доступных вТк. L_iBET — одна из наиболее важных характеристик пользовательского интерфейса. Однако формирование вкуса разработчика не является задачей этой книги. В данной главе будут лишь описаны атрибуты, посредством которых задается цвет. При написании приложений принято предоставлять пользователю возможность самостоятельно выбирать цвета для элементов интерфейсов. В особенности сильна данная традиция среди разработчиков, которые создают продукты для системы Unix. Сделать это относительно несложно, так как в X Window для поддержки цвета используется база ресурсов. При работе с Тк данная традиция распространяется на Windows и Macintosh. Однако, если необходимо, чтобы окно приложения выглядело в соответствии с соглашениями для конкретной платформы, изменять цвета, принятые по умолчанию, не следует. С другой стороны, опытный дизайнер, правильно выбрав цвет, способен существенно улучшить внешний вид приложения. Кроме того, зная принципы использования цвета, проще работать с компонентом canvas. В данной главе рассматривается также работа с изображениями. Тк позволяет создать изображение, которое затем будет выводиться в составе раз-
Глава 41. Цвет, изображения и курсоры 851 личных компонентов. Одно и то же изображение может многократно отображаться в одном или нескольких компонентах. Если вы переопределите изображение, то тем самым измените внешний вид всех компонентов, использующих его. Завершается данная глава рассмотрением курсоров. В каждом компоненте можно определить, как должен выглядеть расположенный на нем курсор мыши. Кроме того, для компонентов, поддерживающих работу с текстом, определяется также текстовый курсор. Для его формирования предусмотрены специальные атрибуты. Работа с цветом В табл. 41.1 представлены имена ресурсов для атрибутов, управляющих цветом. В данной таблице также приводятся сведения о компонентах, использующих те или иные атрибуты. Не забывайте, что в Tcl-командах значения атрибутов задаются с помощью опций. Имена опций совпадают с именами ресурсов, за исключением того, что опция содержит только буквы нижнего регистра и начинается с символа -. Таблица 41.1. Атрибуты для работы с цветом, представленные именами ресурсов background Цвет фона. Данный атрибут применим ко всем компонентам bg Сокращенная запись background. Используется только в командной строке foreground Цвет переднего плана. Компоненты: button, checkbutton, entry, label, listbox, menu, menubutton, message, radiobutton, scale, spinbox и text fg Сокращенная запись foreground. Используется только в командной строке activeBackground Цвет фона, отображаемый в тот момент, когда посредством кнопки мыши инициализируется действие. Компоненты: button, checkbutton, label, menu, menubutton, radiobutton, scale, scrollbar и spinbox activeForeground Цвет переднего плана, отображаемый тогда, когда курсор мыши расположен на активизированном компоненте. Компоненты: button, checkbutton, entry, label, menu, menubutton и radiobutton disabledBackground Цвет фона, отображаемый тогда, когда доступ к компоненту запрещен. Компоненты: entry и spinbox
852 Часть V. Особенности работы Тк Окончание табл. 41.1 disabledForeground Цвет переднего плана, отображаемый тогда, когда доступ к компоненту запрещен. Компоненты: button, checkbutton, menu, menubutton и radiobutton highlightBackground Цвет, используемый для подсветки тогда, когда компонент не имеет фокуса ввода. Данный атрибут применим ко всем компонентам highlightColor insertBackground readonlyBackground selectBackground selectColor selectForeground troughColor Цвет, используемый для подсветки тогда, когда компонент имеет фокус ввода. Данный атрибут применим ко всем компонентам Цвет текстового курсора. Компоненты: canvas, entry, spinbox и text Цвет фона, отображаемый тогда, когда компонент находится в состоянии, допускающем только чтение. Компоненты: entry и spinbox Цвет фона для выделенного текста. Компоненты: canvas, entry, listbox, spinbox и text Цвет селектора. Компоненты: checkbutton, menu и radiobutton Цвет переднего плана для выделенного текста. Компоненты: canvas, entry, listbox, spinbox и text Цвет для отображения области, по которой движется ползунок в линейном регуляторе и на полосе прокрутки Цвет переднего плана (атрибут foreground) используется для рисования элемента, а цветом фона (background) закрашивается область за элементом. Так, например, текст отображается цветом переднего плана. Различные сочетания цвета переднего плана и фона представляют разные состояния компонента и отображаемых в нем объектов. Для каждого атрибута определен класс ресурса. Его удобно использовать для изменения цвета переднего плана и фона. Например, в Тк не предусмотрен режим инверсного изображения. Однако посредством описания ресурсов вы можете преобразовать монохромное изображение в инверсное. Необходимые для этого выражения приведены в листинге 41.1. В данном примере используются имена классов ресурсов Foreground и Background. Они применимы для различных установок, влияющих на цвет переднего плана и фона. Данные ресурсы надо задать перед тем, как будут созданы использующие их компоненты.
Глава 41. Цвет, изображения и курсоры 853 Листинг 41.1. Ресурсы для реализации инверсного изображения proc ReverseVideo {} { option add *Foreground white option add *Background black } Цветовые палитры Команда tk.setPalette позволяет изменить цвета существующих компонентов и установить значения ресурсов, используемые при отображении новых компонентов. Если при вызове данной команды задается один параметр, он интерпретируется как цвет фона; в соответствии с указанным значением определяются остальные цвета. Например, если вам по каким-то причинам не подходит серый цвет, принятый в Тк, вы можете задать голубой фон. tk_setPalette #0088cc Если же вы привыкли к светло-коричневому фону, отображаемому в окнах Тк 3.6, можете восстановить данную цветовую палитру, вызвав команду tk.bisque. Команда tk_setPalette позволяет изменить значение любого из атрибутов, имеющего отношение к цвету. Для этого указываются пары "имя- значение", где под именами подразумеваются имена ресурсов, а в качестве значений задаются цвета. tk.setPalette activeBackground red activeForeground white Цветовые значения Цвет задается двумя способами: с помощью символьных выражений (например, red) или посредством шестнадцатеричных чисел (например, #f f 0000). Перед шестнадцатеричным значением указывается символ #. Шест- надцатеричное число можно условно разделить на три части, которые определяют красную, зеленую и синюю составляющую цвета. Для представления цвета может использоваться 4, 8, 12 или 16 битов. #RGB 4 бита на цвет #RRGGBB 8 битов на цвет. #RRRGGGBBB 126итов на цвет. #RRRRGGGGBBBB 1ббитов на цвет. Если указанное число битов превышает реальную разрешающую способность дисплея, младшие биты для каждого цвета игнорируются. Типы дисплеев, поддерживаемые Тк, будут описаны в следующем разделе. В иоле, соответствующем определенному цвету, могут содержаться числа в диапазоне
854 Часть V. Особенности работы Тк от нуля (отсутствие данного цвета) до максимального значения, которое соответствует всем единицам двоичного числа или всем цифрам i в шестнадца- теричном представлении (полностью насыщенный цвет). Например, красный цвет может быть задан следующими четырьмя способами: #f00 #if0000 #iif000000 #fiif00000000 Для представления цвета предусмотрено большое количество символьных имен, например red, blue, green, thistle, yellow4 и т.д. Эти имена были впервые введены в X Window, и Тк поддерживает соответствующие им цвета па всех платформах. Начиная с Тк 8.3.2 имена цветов содержатся на странице справочной системы colors. В предыдущих версиях эту информацию можно было найти в исходном файле Тк xlib/xcolor.с. Вы также можете получить необходимые сведения, запустив программу xcolors, которая входит в стандартный комплект поставки X Window. На платформах Windows и Macintosh обязательно поддерживается ограниченный набор цветов. В Тк для этих цветов определены специальные имена. Использовать цвета из данного набора удобно тем, что они поддерживаются всеми приложениями, поэтому система может эффективно управлять ими. В табл. 41.2 описаны системные цвета для Windows. Некоторые из этих цветов совпадают, т.е. им соответствуют одинаковые значения RGB. В табл. 41.3 приведены те же сведения для системы Macintosh. Таблица 41.2. Системные цвета Windows system3dDarkShadow Затененная часть кнопки, изображаемой с имитацией трехмерного представления system3dLight Светлая часть кнопки, изображаемой с имитацией трехмерного представления systemActiveBorder Обрамление активизированного окна systemActiveCaption Заголовок активизированного окна systemAppWorkspace Цвет фона рабочей области MDI systemBackgroimd Цвет фона компонента systemButtonFace Цвет фона для кнопки systemButtonHighlight Наиболее светлая часть кнопки, изображаемой с имитацией трехмерного представления systemButtonShadow Самая темная часть кнопки, изображаемой с имитацией трехмерного представления systemButtonText Цвет переднего плана, используемый при отображении кнопки systemCaptionText Заголовок окна
Глава 41. Цвет, изображения и курсоры 855 Окон чание табл .41.2 systemDisabledText systemGrayText systemHighlight systemHighlightText systemlnactiveBorder systemlnactiveCaption systemlnactiveCaptionText systemlnfoBackground systemlnfoText systemMenu systemMenuText systemScrollbar systemWindow systemWindowText Отображаемый текст Текст, отображаемый серым цветом Цвет фона для отображения выделенного текста Цвет переднего плана для отображения выделенного текста Обрамление неактивизированного окна Цвет фона для отображения заголовка неактивизированного окна Текст заголовка неактивизированного окна Цвет фона для отображения окна подсказки Цвет для отображения текста подсказки Цвет фона меню Цвет переднего плана меню Цвет фона для отображения полосы прокрутки Цвет фона окна, предназначенного для отображения текста Цвет для отображения текста в окне Таблица 41.3. Системные цвета Macintosh systemHighlight Цвет фона для отображения выделенного текста systemHighlightText Цвет переднего плана для отображения выделенного текста systemButtonFace Цвет фона для отображения кнопки systemButtonFrame Элементы обрамления кнопки systemButtonText Цвет фона для отображения кнопки systemWindowBody Цвет фона компонента systemMenuActive Цвет фона для отображения выбранного пункта меню systemMenuActiveText Цвет переднего плана отображения выбранного пункта меню systemMenu Цвет фона для отображения меню systemMenuDisabled Цвет фона для отображения пункта меню, доступ к которому запрещен systemMenuText Цвет переднего плана для отображения меню
856 Часть V. Особенности работы Тк Значения RGB могут быть представлены в виде трех чисел. Команда winf о rgb преобразует имя или значение цвета в три числа, соответствующие красной, зеленой и синей составляющей. Эти значения можно использовать для дальнейшего преобразования цвета. В качестве примера в листинге 41.2 представлен код процедуры ColorDarken, в которой используется команда winfo rgb. Данная процедура уменьшает яркость каждой составляющей цвета на 5% и восстанавливает значение цвета, используя команду format. Листинг 41.2. Уменьшение яркости цвета proc ColorDarken { win color } { set rgb [winfo rgb $win $color] return [format п#7о03х0/003х0/о03х" \ [expr round([1index $rgb 0] * 0.95)] \ [expr round([lindex $rgb 1] * 0.95)] \ [expr round([lindex $rgb 2] * 0.95)]] 2 Карты отображения цвета и визуальные классы Число цветов, отображаемых в каждый момент времени на экране компьютера, не может превышать некоторое фиксированное значение. Самые лучшие мониторы отображают 24 миллиона цветов, но нередко встречаются и дисплеи, для которых максимальное число цветов равно 256. Использовавшиеся ранее дисплеи VGA отображают лишь 16 цветов. Если несколько приложений одновременно выполняется в системе, то не исключена ситуация, при которой программам потребуется большее количество цветов, чем может обеспечить устройство отображения. На платформах Windows и Macintosh данная проблема разрешается автоматически. Система X Window предоставляет низкоуровневые средства, которые Тк использует для управления цветом. Таким образом, в большинстве случаев разработчику не приходится заботиться о том, как будут отображаться компоненты приложения. Однако в некоторых ситуациях, в особенности при работе в системе X Window, необходимо иметь большую степень контроля над цветом. В этом случае надо понимать, как работают карты отображения цвета и иметь представление о визуальных классах. Каждый пиксель на экране представляется с помощью одного или нескольких битов. Соответствие между представлением значения пикселя в памяти и цветом этого пикселя на экране может быть задано различными спо-
Глава 41. Цвет, изображения и курсоры 857 собами. Отображение значения в цвет зависит от числа бит, выделенных для каждого пикселя (число битов называется глубиной цвета) и типа интерпретации, или визуального класса. В системе X Window определены шесть визуальных классов. Они описаны в табл. 41.4. Таблица 41.4. Визуальные классы staticgrey Монохромное изображение с фиксированной картой отображения цвета, которая формируется системой greyscale Монохромное изображение с перезаписываемой картой отображения цвета staticcolor Цветное изображение с фиксированной картой отображения цвета, которая формируется системой pseudocolor Цветное изображение, определяемое единственной перезаписываемой картой отображения цвета truecolor Цветное изображение, определяемое тремя картами отображения: для красного, зеленого и синего цвета directcolor Цветное изображение, определяемое тремя перезаписываемыми картами отображения: для красного, зеленого и синего цвета best Используется наилучшее визуальное представление для данной глубины цвета Некоторые из визуальных классов используют карты отображения цвета, с помощью которых задается соответствие данных, представляющих пиксель, и значения используемого аппаратными средствами для генерации цвета. Карта отображения цвета обеспечивает компактное кодирование большого набора цветовых оттенков. Например, для индексации карты отображений, состоящей из 256 записей, достаточно 8 битов, однако в этой карте может содержаться информация о цветах из 24-битового набора. Программа xdpyinfo, предназначенная для работы в системе Unix, предоставляет информацию о различных визуальных классах, поддерживаемых дисплеем. Для фреймов и компонентов верхнего уровня определены атрибуты colormap и visual. Их можно использовать на всех платформах. В системах Windows и Macintosh в каждый момент времени присутствует только один визуальный тип, и если пользователь изменяет его, то он изменяется для всей системы. В системе Unix Х-сервер обычно поддерживает на одном и том же дисплее несколько визуальных классов, поэтому разработчик может использовать разные классы для фреймов и окон верхнего уровня. Значение атрибута visual состоит из двух частей: визуального типа и требуемой глубины цвета. В приведенном ниже выражении задается визуальный класс greyscale и глубина цвета 4 бита на пиксель.
858 Часть V. Особенности работы Тк toplevel .grey -visual "greyscale 4" При запуске программы wish можно указать в командной строке параметр -visual. wish -visual "truecolor 24" Визуальный класс связывается с картой отображения цвета. В системах Windows и Macintosh присутствует единственная карта отображения, которая совместно используется всеми прикладными программами. Unix предоставляет отдельные карты отображения; они полезны тогда, когда необходимо контролировать большое количество цветовых оттенков. Недостатком карт отображения в системе Unix является наличие нежелательных цветовых эффектов при попадании курсора мыши на окно. Происходит это потому, что монитор в каждый момент времени может работать только с одной картой отображения, в результате Х-сервер должен заменять карты. В системах Macintosh и Windows управление картами отображения осуществляется более элегантно, однако согласованное представление различных окон обеспечивается лишь до тех пор, пока число используемых цветов не превысит некоторое граничное значение. Тк может имитировать приватные карты отображения в системе Windows, однако лучшие результаты получаются тогда, когда разработчик предоставляет возможность управления картами отображения операционной системе. В системе Macintosh Tk всегда использует 24-битовый визуальный класс truecolor, поэтому число цветов практически не ограничено и по мере необходимости система имеет возможность смешивать цвета. По умолчанию компоненты наследуют карты отображения цвета и визуальные классы от родительских компонентов. В качестве значения атрибута colormap может быть задано ключевое слово new либо имя другого компонента. В первом случае фрейм или окно верхнего уровня получает новую приватную карту отображения, а во втором случае компоненты совместно используют одну карту отображения. Компоненты, разделяющие карту отображения цветов, должны выводить данные на один терминал и использовать один и тот же визуальный класс. Битовые карты и изображения Для текстовых меток и всех типов кнопок определен атрибут image, с помощью которого задается графическое изображение. Работа с изображением состоит из двух этапов. На первом этапе изображение создается с помощью команды image create. Эта команда возвращает идентификатор изображения, который затем указывается в качестве значения атрибута image.
Глава 41. Цвет, изображения и курсоры 859 Листинг 41.3. Включение изображения в состав компонента set im [image create bitmap \ -file glyph.bitmap -maskfile glyph.mask \ -background white -foreground blue] button .foo -image $im В составе текстовых меток и кнопок могут отображаться текст, битовые карты и изображения. Если установлены атрибуты для разных типов данных, то изображение считается приоритетнее битовой карты, а битовая карта, в свою очередь, имеет более высокий приоритет по сравнению с текстом. Для того чтобы удалить атрибут, соответствующий изображению или битовой карте, надо указать в качестве его значения пустую строку. .foo config -image {} В Tk 8.4 для текстовых меток, пунктов меню и различных типов кнопок был введен атрибут compound, который позволяет указать, могут ли в составе компонента одновременно отображаться изображение (или битовая карта) и текст. Если совместное отображение таких типов информации разрешено, то тот же атрибут задает расположение изображения относительно текста. Ниже приведена команда label, в которой указано, что битовая карта должна отображаться слева, а текст — справа. label .warn -text Warning -bitmap warning -compound left Команда image В табл. 41.5 приведены сведения о команде image. Таблица 41.5. Команда image image create тип ?имя? Создает изображение указанного типа. Если имя не ?опции? указано, оно генерируется автоматически. Остальные параметры формируются в зависимости от заданного типа image delete имя Удаляет изображение с указанным именем image height имя Возвращает высоту изображения в пикселях image inuse имя Возвращает логическое значение, которое указывает, используется ли изображение, заданное посредством имени, каким-либо компонентом image names Возвращает список изображений, которые были определены image type имя Возвращает тип изображения
860 Часть V. Особенности работы Тк Окончание табл. 41.5 image types Возвращает список допустимых типов изображения image width имя Возвращает ширину изображения в пикселях Набор опций image create зависит от типа изображения. В Тк существуют два встроенных типа изображений: bitmap и photo. В главе 50 описан С-интерфейс для определения новых типов изображений. Битовые карты В состав битовой карты входят главное изображение и необязательное изображение маски. Главное изображение выводится цветом переднего плана. Изображение маски отображается цветом фона, за исключением тех битов, которые присутствуют в главном изображении. Остальные биты "прозрачны"; через них "просвечивает" фон компонента. В табл. 41.6 приведена информация об опциях, определенных для битовых карт. Таблица 41.6. Опции битовых карт -background цвет Цвет фона (сокращение -bg недопустимо) -data строка Содержимое битовой карты, представленное в виде строки -file имя Имя файла, содержащего определение битовой карты -foreground цвет Цвет переднего плана (сокращение -fg недопустимо) -maskdata строка Содержимое маски, представленное в виде строки -maskf ile имя Имя файла, содержащего данные маски Файлы, определяющие битовые карты, имеют структуру, подобную С-файлам. Эти файлы обычно имеют расширение .xbm. Они генерируются редакторами битовых карт, поставляемых в составе дистрибутивного пакета X Window (в качестве примера такого редактора можно привести программу bitmap). Опции -file и -maskfile указывают на файлы, которые содержат соответствующие определения. С помощью опций -data и -maskdata задаются строки в формате указанных выше файлов. Атрибут bitmap Текстовые метки и все типы кнопок поддерживают также атрибут bitmap. Данный атрибут удобнее использовать, чем атрибут image, поскольку в этом случае не требуются дополнительные действия по созданию изображения. Однако при этом теряются некоторые средства, предоставляемые командой
Глава 41. Цвет, изображения и курсоры 861 image, например, возможность реконфигурации именованного изображения, которая применяется при организации анимационных последовательностей. Листинг 41.4. Указание битовой карты для компонента button .foo -bitmap @glyph.xbm -fg blue Символ @ указывает на то, что значение опции -bitmap представляет собой имя файла, содержащего битовую карту. С помощью данной опции можно также указывать встроенные битовые карты. Эти битовые карты и их символьные имена приведены на рисунке, который демонстрирует результат выполнения кода, представленного в листинге 41.5. В главе 50 будет описан С-интерфейс для определения встроенных битовых карт. Листинг 41.5. Встроенные битовые карты frame .f -bd 4; frame .g -bd 4 ; pack .f .g -side left set parent .f ; set next .g foreach name {error grayl2 gray50 hourglass \ info questhead question warning} { frame $parent.$name label $parent.$name.1 -text $name -width 9 -anchor w label $parent.$name.b -bitmap $name pack $parent.$name.1 -side right pack $parent.$name.b -side top pack $parent.$name -side top -expand true -fill x set tmp $parent ; set parent $next ; set next $tmp > Изображения photo Поддержка в Tk изображений photo была реализована Полом Макерра- сом (Paul Mackerras). Они представляют собой полноцветные изображения
862 Часть V. Особенности работы Тк и позволяют смешивать цвета, а также осуществлять гамма-коррекцию. Опции, посредством которых задаются атрибуты изображений photo, представлены в табл. 41.7. Эти опции указываются при вызове команды image create photo. Таблица 41.7. Атрибуты изображений photo -data строка Содержимое изображения photo, представленное как бинарная строка в кодировке base64 -file имя Имя файла, содержащего определение изображения photo -format формат Определяет формат строки или формат данных для хранения в файле -gamma значение Коэффициент гамма-коррекции, который должен быть больше нуля. Значение больше единицы увеличивает яркость изображения -height значение Ширина изображения, представленная в экранных единицах измерения -palette описание Число градаций цвета для изображения -width значение Ширина изображения, представленная в экранных единицах измерения Опция -format предоставляет сведения о формате данных. Изображения photo могут быть представлены в различных форматах. В Тк реализованы встроенные средства поддержки PPM, PGM и GIF. Для определения новых форматов изображений photo предусмотрен специальный С-интерфейс. Расширение Tk Img поддерживает карты пикселей и JPEG-файлы. В обычных условиях задавать формат нет необходимости, так как средства поддержки изображений photo пытаются применить все обработчики и найти тот из них, который поддерживает полученные данные. При явном указании формата уменьшается число обработчиков, которые система пытается использовать. Имя формата интерпретируется как начало имени обработчика. Регистр символа в имени формата не учитывается. Опция -palette указывает, сколько цветов или полутонов должно использоваться при выводе изображения. Если в качестве значения опции указано одно число, изображение воспроизводится с помощью оттенков серого цвета, а само число задает количество оттенков. Для полноцветного изображения должны быть указаны три числа, разделенные косой чертой. С их помощью задается количество оттенков красного, зеленого и синего цвета. Чем больше полутопов вы зададите, тем большим будет объем карты отображения цвета. По необходимости компонент photo можно переключить на работу
Глава 41. Цвет, изображения и курсоры 863 с приватной картой отображения. Для того чтобы определить число различных цветов, которые можно использовать в изображении, надо перемножить количество оттенков красного, зеленого и синего цвета. Если в вашем распоряжении имеется 8-битовый дисплей, вам доступны только 256 цветов. При работе с такими дисплеями рекомендуется задавать палитру 5/5/4 или 6/6/5. Для синего цвета можно указывать меньшее количество оттенков, так как человеческий глаз менее восприимчив к свету в этом диапазоне. После создания изображения вы можете выполнять с ним различные действия. В табл. 41.8 приведены сведения об операциях над экземпляром изображения. В данном случае $р — это дескриптор изображения photo, возвращаемый в результате выполнения команды image create photo. Таблица 41.8. Операции над изображениями photo $р blank $р сget опция $р configure ... $р сору источник ?опции? $р data ?опции? $р get х у $р put данные ?-to xl yl х2 у2? $р read файл опции $р redither $р transparency get х у $р transparency set х у логическое_значение $р write файл ?опции? Создает изображение, которое первоначально формируется как прозрачное Возвращает значение конфигурационной опции Изменяет атрибуты изображения photo Создает еще один экземпляр изображения. Опции операции сору описаны в табл. 41.9 Возвращает изображение как список строк, где каждая строка представляет собой список цветов в формате #rrggbb. Опции, указываемые при вызове операции data, приведены в табл. 41.11 Возвращает значение пикселя в позиции х у Включает данные в состав изображения. Данные представляют собой список строк, где каждая строка представляет собой список цветов в формате #rrggbb Загружает изображение из файла. Опции, указываемые при вызове операции read, приведены в табл. 41.10 Повторно применяет алгоритм смешивания к изображению Возвращает логическое значение, указывающее, является ли заданный пиксель прозрачным Если указанное логическое значение равно true, пиксель становится прозрачным и непрозрачным в противном случае Сохраняет изображение в файле с учетом указанных опций. Опции, указываемые при вызове операции write. приведены в табл. 41.11
864 Часть V. Особенности работы Тк В табл. 41.9 приведены опции, которые используются при копировании данных из одного изображения в другое. Область для копирования задается путем указания ее левого верхнего и правого нижнего угла. Если правый нижний угол области источника не указан, по умолчанию принимается правый нижний угол изображения. Если не указан правый нижний угол целевой области, размер определяется исходной областью. При несоответствии размеров исходной и целевой областей исходное изображение либо усекается, либо многократно воспроизводится, чтобы заполнить целевую область. Таблица 41.9. Опции, используемые при копировании изображений photo -compositingrule правило -from xl yl 1x2 y21 -to xl yl 1x2 y21 -shrink -zoom x lyl -subsample x lyl Определяет, как прозрачные пиксели исходного изображения сочетаются с целевым изображением. Если значением опции является overlay (данное значение принимается по умолчанию), старое содержимое целевого изображения остается видимым. Значение set указывает на то, что старое содержимое целевого изображения должно быть полностью заменено исходным изображением Задает расположение и размер исходного изображения. Если х2 и у2 не указаны, принимается правый нижний угол изображения Задает расположение и размер целевого изображения. Если х2 и у2 не указаны, размер определяется по исходному изображению. Для заполнения целевого изображения исходное изображение может быть усечено или многократно воспроизведено Сжимает целевое изображение так, чтобы его нижний правый угол соответствовал нижнему правому углу копируемого изображения. Если для изображения указаны опции -width и -height, значение -shrink не учитывается Увеличивает исходное изображение так, чтобы каждый его пиксель соответствовал блоку х х у пикселей. Если значение у не указано, оно принимается равным х Сжимает исходное изображение, выбирая каждый пиксель с номером х по горизонтали и каждый пиксель с номером у по вертикали. Если значение у не указано, оно принимается равным х
Глава 41. Цвет, изображения и курсоры 865 В табл. 41.10 представлены опции команды read. Если формат не указан, он определяется автоматически. Если одни и те же данные могут соответствовать различным форматам, то формат чтения следует указать явно. Таблица 41.10. Опции операции read для изображений photo -format формат Задает формат данных. По умолчанию формат определяется автоматически -from xl yl 1x2 у2? Указывает подобласть исходных данных. Если х2 и у2 не заданы, размер определяется по данным -to xl yl Задает верхний левый угол для новых данных -shrink Сжимает целевое изображение так, что его нижний правый угол соответствует нижнему правому углу данных, прочитанных из исходного изображения. Если для изображения указаны опции -width и -height, значение -shrink не учитывается В табл. 41.11 описаны опции для команд write и data. При записи в файл опция -format очень важна. Если ее не указать, будет использован первый из подходящих форматов. С другой стороны, опцию -format не следует использовать при вызове операции data. Эта операция возвращает изображение как список строк, где каждая строка — это список цветов в формате #rrggbb. Таблица 41.11. Опции операции write для изображений photo -background цвет Если данная опция указана, все прозрачные пиксели заменяются на пиксели с заданным цветом -format формат Задает формат данных -from xl yl 1x2 Задает подобласть для сохраняемых данных. Если х2 и у2 у2? не указаны, они принимаются равными координатам нижнего правого угла -grayscale Если данная опция задана, цвета пикселей преобразуются в оттенки серого Текстовый курсор Компоненты text, entry и canvas, помимо курсора мыши, отображают еще один курсор, который отмечает точку ввода. Текстовый курсор описывается набором атрибутов. Эти атрибуты позволяют задать внешний вид курсора: определить, должен ли он выглядеть как тонкая вертикальная линия, как прямоугольник или иметь другую форму. Атрибуты, воздействующие на
866 Часть V. Особенности работы Тк текстовый курсор, приведены в табл. 41.12. По умолчанию текстовый курсор выглядит как вертикальная линия толщиной в два пикселя. Курсор большего размера чаще всего нежелателен. Данный курсор существенно отличается от прямоугольного курсора, используемого в эмуляторах терминалов. Если прямоугольный курсор занимает все знакоместо, то вертикальная линия помещается между двумя символами. Таблица 41.12. Атрибуты курсора, представленные как имена ресурсов cursor insertBackground insertBorderWidth insertOfiTime insertOnTime insertWidth Курсор мыши. Данный атрибут применим ко всем компонентам Цвет текстового курсора. Компоненты: canvas, entry и text Толщина курсора для случаев, когда используется имитация трехмерного представления. Компоненты: canvas, entry и text Время, в течение которого мигающий курсор отсутствует. Задается в миллисекундах. Если значение данного атрибута равно нулю, мигание курсора запрещено. Компоненты: canvas, entry и text Время, в течение которого мигающий курсор отображается на экране. Задается в миллисекундах Толщина текстового курсора. Задается в экранных единицах измерения. Компоненты: canvas, entry и text Курсор мыши Атрибут cursor определяет внешний вид курсора мыши. В системе Unix для курсора можно задать цвет переднего плана и цвет фона. Ниже приведено несколько примеров выражений, используемых для определения курсора. $w config -cursor watch ;# Курсор в виде часов, # означающий ожидание. $w config -cursor {gumby blue} ;# Изображение gumby синего цвета. $w config -cursor {X_cursor red white} ;# Красные перекрещенные # линии на белом фоне. В качестве значения атрибута, определяющего курсор, может быть также задано имя файла, в котором содержится битовая карта изображения курсора. Если заданы два файла, то во втором файле содержится маска, определяющая точки, которые должны быть закрашены цветом фона. Для созда-
Глава 41. Цвет, изображения и курсоры 867 ния таких файлов могут использоваться программы редактирования битовых карт, например idraw или i coned it. Ниже приведены примеры выражений, в которых указываются файлы, определяющие внешний вид курсора. $w config -cursor "@timer.xbm black" $w config -cursor "Otimer.xbm timer.mask black red" В подобных выражениях необходимо задавать цвет переднего плана, а если присутствует файл маски, то следует задать также цвет фона. На рис. 41.1 показаны курсоры, встроенные в Тк. Эти курсоры первоначально были определены в системе X Window, но поддерживаются на всех платформах, на которых выполняется Тк. На рисунке отсутствуют некоторые платформенно-ориентированные курсоры, о которых будет сказано ниже. В системах Windows и Macintosh ряд курсоров отображаются в курсоры, специфические для данной платформы, и внешний вид их несколько изменяется. В системе Macintosh отображению в платформенно-ориентированные курсоры подвергаются ibeam, xterm, cross, crosshair, plus, watch и arrow. Кроме того, в данной системе определены также курсоры text и cross-hair. В системе Windows вид, специфический для этой платформы, приобретают курсоры ibeam, icon, crosshair, fleur, sb_v_double_arrow, sb_h_double_arrow, center_ptr, watch и xterm. Для этой системы определены дополнительные курсоры starting, size, size_ne_sw, size_ns, size_nw_se, size_we, uparrow и wait. Кроме того, в системе Windows используется идентификатор по. который указывает на то, что курсор должен отсутствовать. Начиная с Тк 8.3, при работе в Windows вы можете использовать курсоры этой системы, указав имя соответствующего файла — . ani или . cur. Например: $w config -cursor @C:/WINNT/Cursors/globe.ani
868 Часть V. Особенности работы Тк Рис. 41.1. Курсоры Тк
Глава 42 Шрифты и текстовые атрибуты В данной главе описывается соглашение об именовании шрифтов. В Тк предусмотрены объекты шрифтов, которые можно динамически настраивать и связывать с компонентами. В этой главе также рассматриваются атрибуты, с помощью которых можно задавать выравнивание, точки фиксации и особенности компоновки. I I I рифты определяют внешний вид символов, отображаемых на экране. Кнопки, текстовые метки и окна списков содержат атрибут font, который определяет, какой шрифт должен использоваться при выводе текста в составе этих компонентов. Для текстового компонента атрибут font содержится в дескрипторах, которыми помечаются различные фрагменты текста. Тк поддерживает платформенно-независимые средства именования шрифтов и по необходимости заменяет отсутствующие шрифты. При разработке приложения вы можете определять именованные объекты шрифтов, а затем ставить их в соответствие компонентам и текстовым дескрипторам. При изменении конфигурации объектов шрифтов использующие их компоненты автоматически обновляют отображаемые данные. Для определения шрифтов, применяемых в интерфейсах прикладных программ, можно использовать базу данных ресурсов. В версиях, предшествующих Тк 8.0, использовались имена шрифтов X Window (например, -*-times-bold-r-normal-*-12-*), а если шрифт не мог быть найден, возникала ошибка. В именах X Window могут применяться шаблоны, которые позволяют избежать ошибок, связанных с отсутствием шрифтов. В новых версиях Тк имена шрифтов X Window по-прежнему поддерживаются. Однако в этом случае Тк не обеспечивает подстановку шрифтов, поэтому если вы собираетесь работать с именами шрифтов X Window,
870 Часть V. Особенности работы Тк будьте готовы к тому, что вам придется обрабатывать ошибки. Рекомендуется использовать платформенно-независимые имена шрифтов. Помимо шрифтов, в данной главе описываются некоторые атрибуты компонентов, предназначенные для работы с текстом. Эти атрибуты позволяют управлять выравниванием, точками фиксации и компоновкой содержимого компонентов. Именование шрифтов Существуют два основных способа именования шрифтов. Вы можете использовать предопределенное имя (например, system) или задать набор атрибутов, указывая платформенно-независимое имя. label .foo -text "Hello" -font {times 12 bold} В последнем случае шрифт задается как список, состоящий из трех элементов. Первым элементом является семейство шрифтов, вторым — размер, указанный в пунктах, а третий элемент представляет собой список параметров стиля. Семейство определяет внешний вид шрифта; в качестве примера имени семейства можно привести courier или helvetica. Полный набор параметров стилей включает модификаторы normal, bold, roman, italic, underline и overstrike. Например, для того чтобы задать отображение полужирным шрифтом и курсивом, надо сформировать выражение, подобное приведенному ниже. label .foo -text "Hello" -font {times 12 {bold italic}} Размер шрифта задается в пунктах (пункт — это 1/72 дюйма). В Тк обеспечивается соответствие между размерами в пунктах и в пикселях. Масштаб, используемый по умолчанию, формируется исходя из разрешения экрана; изменить его можно с помощью команды tk scaling, которая будет рассматриваться в главе 44. Указывая размеры в пикселях, можно задавать отрицательные значения. Размеры шрифтов предпочтительнее определять в пунктах; при этом, независимо от разрешения экрана, размер текста остается неизменным. (В системах Windows и Macintosh удается достичь лучших результатов, чем в Unix.) Если же размер шрифта должен определяться относительно размеров компонентов, то предпочтительнее задавать его в пикселях. Значение атрибута font можно также задать как набор пар имя-значение. Соответствующие опции описаны в табл. 42.1. Данный формат менее компактен, чем описанный ранее, но если надо изменить отдельные параметры шрифта, то вам нет необходимости указывать все опции. Выражение, приведенное выше, можно переписать следующим образом:
Глава 42. Шрифты и текстовые атрибуты 871 label .foo -text "Hello" -font \ {-family times -size 12 -weight bold -slant italic} Таблица 42.1. Атрибуты шрифтов -family имя В качестве имени может быть задано times, courier, helvetica и другие идентификаторы, возвращаемые в результате выполнения команды font families -size пункты Размеры шрифтов задаются в пунктах (пункт равен 1/72 дюйма) -weight значение Значением данной опции может быть bold или normal -slant значение Значением данной опции может быть roman или italic -underline Если логическое значение равно true, текст отобража- логическое_значение ется с подчеркиванием -overstrike Если логическое значение равно true, отображается пе- логическое_значение речеркнутый текст Тк сравнивает описания шрифтов со шрифтами, имеющимися в вашей системе. При этом выбирается наиболее подходящий шрифт; однако некоторые его параметры могут быть изменены. Тк гарантирует наличие шрифтов семейств Times, Courier и Helvetica. Кроме того, Тк имеет сведения о том, что Courier New является синонимом Courier, a Arial или Geneva — синонимом Helvetica. Команда actual command возвращает параметры, наилучшим образом соответствующие характеристикам указанного шрифта. font actual {times 13 bold} -family Times -size 13 -weight bold -slant roman -underline 0 -overstrike 0 На платформах Macintosh и Windows определен размер шрифта по умолчанию. Получить его можно, указав в спецификации шрифта размер, равный 0. font actual system -family Chicago -size 0 -weight normal -slant roman -underline 0 -overstrike 0 Именованные шрифты По мере необходимости вы можете определять имена шрифтов с помощью команды font create. Это позволяет реализовать косвенную ссылку из компонента на шрифт с определенными параметрами. Если вы измените конфигурацию именованного шрифта, компонент автоматически обновит
872 Часть V. Особенности работы Тк отображаемые данные. Благодаря этому выбор шрифтов пользователем реализуется относительно просто. Например, следующее выражение позволяет определить на всех платформах шрифт с именем default: font create default -family times -size 12 Размер шрифта default можно в любой момент увеличить с помощью команды font configure. Это изменение автоматически отразится в составе компонентов, использующих данный шрифт. font configure default -size 14 Системные шрифты На платформах Windows и Macintosh определены системные шрифты, которые используются большинством приложений. Запросив конфигурацию компонентов Тк, вы увидите имена системных шрифтов. Параметры системного шрифта устанавливает пользователь с помощью панели управления. Получить информацию об атрибутах системного шрифта позволяет команда font actual. Ниже перечислены системные шрифты для каждой платформы. • Система Windows поддерживает шрифты system, systemf ixed, ansi, ansifixed, device и oemfixed. Суффикс fixed означает шрифт, в котором все символы имеют один и тот же размер. • На платформе Macintosh определены шрифты system и application. • В системе Unix используется fixed. Это единственный шрифт X Window, наличие которого гарантируется. Имена шрифтов X Window описаны в следующем разделе. Шрифты Unicode При отображении символов Unicode Tk осуществляет подстановку шрифтов для каждого символа. В результате становится возможным, например, одновременное отображение символов ASCII и Kanji. Подобный подход очень удобен: разработчику не приходится заботиться о выборе шрифтов. Однако такая подстановка выполняется медленно. Возможна ситуация, при которой Тк будет обращаться к каждому шрифту, установленному в системе, чтобы выяснить, можно ли с его помощью отобразить тот или иной символ. Если вы знаете, что отображаемые символы относятся к конкретному набору, вы можете оптимизировать работу интерфейса вашей программы, указав шрифт, наиболее подходящий для отображения данных.
Глава 42. Шрифты и текстовые атрибуты 873 Имена шрифтов X Window На любой платформе имена шрифтов можно задавать по соглашениям X Window. Более того, для версий, предшествующих Тк 8.0, это единственная возможность работы со шрифтами. В качестве примера сокращенного имени шрифта X Window можно привести имя fixed. 6x12, 9x15 или timesl2. По сути, все имена шрифтов X Window зависят от конкретного узла, так как в различных системах могут быть установлены разные шрифты. Единственный шрифт, который обязательно установлен на всех платформах Unix, — это fixed. Универсальный формат записи имен шрифтов X Window включает несколько компонентов, которые описывают параметры шрифта. Каждый компонент отделяется от других дефисами (-), а отсутствующие компоненты заменяются звездочками (*). Сокращенные имена представляют собой псевдонимы, которыми заменяются полные спецификации. Пример полного имени шрифта приведен ниже. -*-times4nedimn-r-normal-*-18-*-*-*-*-*-iso8859-l Компоненты полного имени шрифта X Window приведены в табл. 42.2. Они расположены в том же порядке, в котором включаются в спецификацию шрифта. В таблице также приведены допустимые значения компонентов. Многоточие (...) указывает на то, что набор возможных значений не исчерпывается приведенными в таблице. Наиболее важными атрибутами шрифта являются имя семейства (family), вес (weight), наклон (slant) и размер (size). В качестве значения компонента weight обычно указывается bold или medium. Компонент slant расшифровывается так: i — курсив (italic), г — обычный шрифт (roman), о — наклонный шрифт (oblique). В семействе шрифтов может присутствовать либо курсив, либо наклонный шрифт. В семействе шрифтов не обязательно присутствуют все веса. Размер может быть задан в пикселях или в пунктах. Указание размера в пунктах обеспечивает независимость от разрешения экрана. При разрешении 75 dpi на каждый пиксель приходится приблизительно 10 пунктов. Заметьте, что в данном случае пункты не совпадают с печатными пунктами, которые используются в Тк для определения размеров экрана. Если вы применяете имена шрифтов X Window, размер шрифта не оказывает влияния на коэффициент масштабирования, который будет рассмотрен в главе 44. Таблица 42.2. Компоненты спецификации шрифтов X Window Компонент Возможные значения foundry adobe xerox linotype misc ... family times helvetica lucida courier symbol ...
874 Часть V. Особенности работы Тк Окончание табл. 42.2 Компонент Возможные значения weight bold medium demibold demi normal book light slant i г о swidth normal sans narrow semicondensed adstyle sans pixels 8 10 12 14 18 24 36 48 72 144 ... points 0 80 100 120 140 180 240 360 480 720 ... resx 0 72 75 100 resy 0 72 75 100 space p m с avgWidth 73 94 124 ... registry iso8859 xerox dec adobe jisx0208.1983 ... encoding 1 fontspecific dectech symbol dingbats Рекомендуется явно задавать лишь часть компонентов спецификации шрифта, а вместо остальных компонентов указывать символ *. Х-сервер пытается найти среди шрифтов, установленных в системе, тот, который соответствовал бы спецификации, однако поиск не будет успешным при несовпадении одного из компонентов. Если вместо первого или последнего символа имени шрифта указана звездочка, она обозначает несколько компонентов. Приведенное ниже описание определяет шрифт Times размером в 12 пикселей. *times-medium-r-*-*-12* При работе со шрифтами X Window в системе Unix можно использовать программы xlsfonts и xfontsel, которые входят в стандартный комплект поставки XII. Программа xlsfonts выводит имеющиеся шрифты, которые соответствуют указанному имени. Она использует тот же шаблон, что и сервер. Поскольку символ * в большинстве оболочек Unix имеет специальное значение, необходимо помещать имя шрифта, задаваемое в качестве параметра, в кавычки. Программа xfontsel предоставляет графический пользовательский интерфейс и выводит шрифт, соответствующий указанному имени. Особенности работы со шрифтами в ранних версиях Тк Если шрифт не найден, то версии Тк, предшествующие 8.0, не предпринимают попытку его подстановки. В последних версиях Тк подстановка выполняется только в том случае, если указано платформенно-независимое имя
Глава 42. Шрифты и текстовые атрибуты 875 шрифта. Если шрифт не найден, то при создании или изменении конфигурации компонента возникает ошибка. В листинге 42.1 показан код процедуры FontWidget, которая представляет собой "оболочку" для команды создания компонента Тк. Эта процедура предназначена для замены шрифта. Листинг 42.1. Процедура FontWidget отслеживает отсутствие шрифта proc FontWidget { args } { # В качестве параметров указана Тс1-команда if {[catch $args w]} { # Если в составе параметров указан шрифт, он удаляется set ix [lsearch $args -font] if {$ix >= 0} { set args [lreplace $args $ix [expr $ix+l]] } # Сведения о шрифте переопределяют соответствующую # информацию, приведенную в базе данных ресурсов # Шрифт "fixed" является специфическим для системы Unix set w [eval $args {-font fixed}] } return $w 2 Вызов FontWidget осуществляется следующим образом: FontWidget button .foo -text Foo -font garbage Если при выполнении команды создания компонента возникает ошибка, процедура FontWidget использует шрифт, заданный по умолчанию. Следует удалить ссылки на шрифты из args. Шрифт, заданный явным образом, отменяет все соответствующие установки в базе данных ресурсов, а также шрифты, используемые по умолчанию в Тк. Очевидно, что причина ошибки при создании компонента не обязательно связана с использованием шрифтов. В этом случае она вторично возникнет при выполнении процедуры. Проблема отсутствующих шрифтов исчезает, если вы будете использовать платфор- менно-независимые имена шрифтов. Таким образом, процедура FontWidget может найти реальное применение лишь при работе с ранними версиями Тк. Метрика шрифта Команда font metrics возвращает сведения о размерах шрифтов. В частности, с ее помощью можно получить общую информацию о всех символах шрифта.
876 Часть V. Особенности работы Тк Рис. 42.1. Метрика шрифта font metrics {times 10} -ascent 9 -descent 2 -linespace 11 -fixed 0 Параметр fixed равен true для тех шрифтов, в которых размеры всех прямоугольников, ограничивающих символы, одинаковы. Параметр linespace представляет расстояние между базовыми линиями двух строк, следующих одна за другой. Назначение параметров ascent и descent демонстрируется на рис. 42.1. Команда font measure возвращает длину строки, отображаемой указанным шрифтом. При этом наклон, при котором часть символа выходит за пределы ограничивающего прямоугольника, не принимается во внимание. Не учитываются также специальные эффекты, связанные с наличием символов табуляции и перевода строки. Команда font В табл. 42.3 представлена информация о команде font. В данной таблице шрифт — это либо описание параметров, либо логическое имя, либо имя системного шрифта, либо имя шрифта X Window. Опция -displayof применяется при работе в системе X Window, в частности, в тех случаях, когда окна присутствуют на различных дисплеях, поддерживающих разные шрифты. Обратите также внимание на особенность выполнения операции font delete. Если шрифт используется каким-либо компонентом, то по этой команде он не будет удален. Таблица 42.3. Команда font font actual шрифт Возвращает реальные параметры шрифта ?-displayof окно? ?опция? font configure Устанавливает или запрашивает параметры имя.шрифта ?опция? шрифта ?значение опция значение?
Глава 42. Шрифты и текстовые атрибуты 877 Окончание табл. 42.3 font create ?имя_шрифта? Определяет шрифт с указанными параметрами ?опция значение ...? font delete имя_шрифта Удаляет определения именованных шрифтов ?имя2 ...? font families Возвращает список семейств шрифтов, поддержи- ?-displayof окно? ваемых дисплеем, соответствующим указанному окну font measure щрифт Возвращает ширину текста, отображаемого ука- ?-displayof окно? текст занным шрифтом font metrics шрифт При вызове даной команды могут быть указаны ?-displayof окно? опции -ascent, -descent, -linespace и -fixed ?опция? font names Возвращает имена доступных шрифтов Текстовые атрибуты Размещение В табл. 42.4 приведена информация о двух атрибутах, влияющих на размещение текста: justify и wrapLength. Для компонента text определены также дополнительные атрибуты подобного назначения (см. главу 36). Атрибуты, рассматриваемые в данном разделе, применимы к различным типам кнопок, текстовым меткам, полям редактирования и компонентам message. Описание перечисленных компонентов см. в главах 30, 32 и 34. Атрибут justify выравнивает текст по центру, по левой или по правой границе. Для всех компонентов, указанных в таблице, по умолчанию используется выравнивание по центру. Исключением является компонент entry, в котором текст по умолчанию выравнивается по левому краю. Атрибут wrapLength позволяет задать длину последовательности символов, при превышении которой осуществляется автоматический перенос. Он используется при создании кнопок и текстовых меток с несколькими строками текста. Значение данного атрибута задается в единицах измерения экрана. В некоторых случаях разместить текст в несколько строк бывает проще, непосредственно включая в него символы перевода строки. В этом случае значение wrapLength должно быть равно нулю (это значение принимается по умолчанию).
878 Часть V. Особенности работы Тк Таблица 42.4. Атрибуты, управляющие размещением текста (представлены именами ресурсов) justify Выравнивание строки текста. Значения: left, center или right. Компоненты: button, checkbutton, entry, label, menubutton, message и radiobutton wrapLength Максимальная длина строки текста, представленная в экранных единицах измерения. Компоненты: button, checkbutton, label, menubutton и radiobutton Атрибуты, управляющие выделенным текстом В табл. 42.5 перечислены атрибуты, применяемые для управления выделенными фрагментами текста. Атрибут export Select ion указывает, должен ли выделенный текст экспортироваться другим компонентам при выполнении вырезания и вставки. Цвет выделенного текста задается с помощью атрибутов selectForeground и selectBackground. При выводе выделенного текста используется рельеф типа raised; атрибут selectBorderWidth влияет на особенности трехмерного представления. Выбрав толщину рамки, равной нулю, вы получите рельеф типа flat. Таблица 42.5. Атрибуты, управляющие выделением exportSelection Организация совместного использования выделения. Компоненты: entry, canvas, listbox и text selectForeground Цвет переднего плана для отображения выделенного текста selectBackground Цвет фона для отображения выделенного текста selectBorderWidth Ширина обрамления с имитацией трехмерного представления, применяемого для подсветки выделенного фрагмента Использование сетки и изменение размеров Компоненты text, listbox и canvas поддерживают режим сетки. В режиме сетки компонент разбивается на ячейки; обычно ячейки определяются так, чтобы в каждой из них помещался один символ. Атрибут setGrid включает или выключает режим сетки; для данного атрибута указывается логическое значение. В компонентах listbox и text единицей определения размера сетки является размер символа. Пример сетки для холста приведен в листинге 44.1.
Глава 42. Шрифты и текстовые атрибуты 879 Если для компонента включен режим сетки, его размеры могут быть выбраны лишь так, чтобы в нем помещалось целое число ячеек. Другими словами, высота и ширина компонента должны быть такими, чтобы в составе компонента по высоте отображалось целое число строк, а по ширине — целое число символов (шириной символа считается средняя ширина для данного шрифта). Данные ограничения влияют на изменение размеров компонента, независимо от того, производятся ли эти изменения интерактивно или в результате выполнения команды оконного диспетчера. Когда режим сетки включен, размеры определяются как число ячеек (например, 24x80), в противном случае они задаются в пикселях. Команды оконного диспетчера, предназначенные для установки размеров, приведены в табл. 44.1. В примере, представленном в листинге 42.2, создается компонент listbox, для которого включается режим сетки. Проверьте, как изменяются размеры окна при установленном и сброшенном флаге -setgrid, а также попробуйте выполнить данный код, удалив команду wm minsize, которая задает минимальный размер окна. Определение процедуры Scrolled_Listbox см. в листинге 33.3. Листинг 42.2. Окно списка, для которого включен режим сетки wm minsize . 5 3 button .quit -text Quit -command exit pack .quit -side top -anchor e Scrolled.Listbox .f -width 10 -height 5 -setgrid true pack .f -side top -fill both -expand true Программа, реализующая выбор шрифтов В завершение данной главы вам предлагается пример программы, позволяющей выбирать шрифты. Данная программа реализует диалоговое окно, которое можно включить в состав разрабатываемого вами приложения. Пункты меню связаны с элементами массива font, используемого при выполнении команды font configure. В окне отображаются внешний вид символов данного шрифта, а также установки, сделанные пользователем. После щелчка на кнопке О К возвращается конфигурация шрифта. Листинг 42.3. Диалоговое окно выбора шрифта proc Font_Select {{top .fontsel}} { global font # Создание меню File, Font, Size и Format
880 Часть V. Особенности работы Тк toplevel $top -class Fontsel -bd 10 set menubar [menu $top.menubar] $top config -menu $menubar foreach x {File Font Size Format} { set menu [menu $menubar.[string tolower $x]] $menubar add cascade -menu $menu -label $x } $menubar.file add command -label Reset -command FontReset $menubar.file add command -label OK \ -command {set font(ok) ok} $menubar.file add command -label Cancel \ -command {set font(ok) cancel} # В меню Fonts перечислены доступные семейства шрифтов. set allfonts [font families] set numfonts [llength $allfonts] set limit 20 if {$numfonts < $limit} { # Отображение шрифтов в одном меню foreach family $allfonts { $menubar.font add radio -label $family \ -variable font(-family) \ -value $family \ -command FontUpdate } } else { # Слишком много шрифтов. Создание набора каскадных # меню для отображения всех вариантов set с 0 ; set 1 0 foreach family $allfonts { if {$1 == 0} { $menubar.font add cascade -label $family... \ -menu $menubar.font.$c set m [menu $menubar.font.$c] incr с }
Глава 42. Шрифты и текстовые атрибуты 881 $m add radio -label $family \ -variable font(-family) \ -value $family \ -command FontUpdate set 1 [expr ($1 +1) % $limit] } } # Формирование остальных меню foreach size {7 8 10 12 14 18 24 36 72} { $menubar.size add radio -label $size \ -variable font(-size) \ -value $size \ -command FontUpdate } $menubar.size add command -label Other... \ -command [list FontSetSize $top] $menubar.format add check -label Bold \ -variable font(-weight) \ -onvalue bold -offvalue normal \ -command FontUpdate $menubar.format add check -label Italic \ -variable font(-slant) \ -onvalue italic -offvalue roman \ -command FontUpdate $menubar.format add check -label underline \ -variable font(-underline) \ -command FontUpdate $menubar.format add check -label overstrike \ -variable font(-overstrike) \ -command FontUpdate # FontReset инициализирует массив шрифтов, в результате # чего подсвечиваются пункты меню с зависимой фиксацией. FontReset # Данная метка отображает текущий шрифт label $top.font -textvar font(name) -bd 5
882 Часть V. Особенности работы Тк # Данное сообщение отображает пример шрифта. message $top.msg -aspect 1000 \ -borderwidth 10 -font fontsel \ -text " ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdef ghi j klmiiopqr stuvwxyz 0123456789 ! (ШГ&* ()_+-=[] О ;:\""~,.<>/?\\ I # Компоновка диалогового окна pack $top.font $top.msg -side top set f [frame $top.buttons] button $f.ok -text 0k -command {set font(ok) 1} button $f.cancel -text Cancel -command {set font(ok) 0} pack $f.ok $f.cancel -padx 10 -side left pack $f -side top # Процедура Dialog_Wait определена в листинге 39.1 set font(ok) cancel Dialog.Wait $top font(ok) destroy $top if {$font(ok) == "ok"} { return [array get font -*] } else { return {} } } # Процедура FontReset повторно формирует шрифт по умолчанию proc FontReset {} { catch {font delete fontsel} font create fontsel FontSet } # FontSet инициализирует массив font данными, возвращаемыми
Глава 42. Шрифты и текстовые атрибуты 883 # командой font actual proc FontSet {} { global font # Элемент name содержит конфигурационную информацию # для шрифта. Для улучшения восприятия в состав # данных включены символы перевода строки set font(name) [font actual fontsel] regsub -- "-slant" $font(name) "\n-slant" font(name) # Сохранение реальных параметров после подстановки шрифта array set font [font actual fontsel] } # Процедура FontSetSize добавляет к диалоговому окну # поле редактирования, с помощью которого пользователь # может ввести размер шрифта. proc FontSetSize {top} { set f [frame $top.size -borderwidth 10] pack $f -side top -fill x label $f.msg -text "Size:" entry $f.entry -textvariable font(-size) bind $f.entry <Return> FontUpdate pack $f.msg -side left pack $f.entry -side top -fill x } # Процедура FontUpdate вызывается при изменении установок # шрифтов, независимо от того, осуществляется ли # такое изменение с помощью меню или посредством # процедуры FontSetSize. proc FontUpdate { } { global font # Элемент шрифта с ведущим символом - используется # непосредственно в команде конфигурации шрифта.
884 Часть V. Особенности работы Тк eval {font configure fontsel} [array get font -*] FontSet
Глава 43 Команда send В данной главе описывается команда send, с помощью которой можно вызывать Tcl-команды в других приложениях. Кроме того, здесь будут также обсуждаться средства, позволяющие добиться того же эффекта с помощью сетевых гнезд. Г\ОМАНДА send позволяет приложениям Тк, выполняющимся на одном дисплее, передавать друг другу Tcl-команды, что обеспечивает их взаимодействие при решении задач. Большое приложение может быть реализовано как набор небольших программ, взаимодействующих друг с другом. Такой подход повышает степень повторного использования кода. Средства поддержки команды send предоставляют пространство имен приложениям Тк. Команда winf о interps возвращает имена всех приложений Тк, доступных для команды send. Механизм взаимодействия, реализуемый с помощью команды send, ограничивается прикладными программами, отображающими данные на одном дисплее. Одна рабочая станция в многоэкранном режиме рассматривается как один дисплей X Window. В системе Unix при организации взаимодействия и записи имен приложений используются свойства X-терминала. Для Тк 8.0 и более новых версий команда send еще не реализована на платформах Macintosh и Windows. Для системы Windows имеется расширение, в котором send эмулируется средствами DDE. В данной главе также обсуждаются средства, использующие сетевые гнезда. Их можно рассматривать как альтернативу команде send. Возможности, предоставляемые этими средствами, не ограничены одним дисплеем. Они могут применяться в сочетании с защищенными интерпретаторами, ограничивающими сферу действия удаленных операций. Аналогичные возможности предоставляют многие расширения Tcl, например GroupKit и Tcl-DP. Особого внимания заслуживает пакет comm, являющийся частью стандартной библио-
886 Часть V. Особенности работы Тк теки Tcl. Этот пакет базируется на использовании сетевых гнезд и призван заменить команду send на всех платформах. Дополнительную информацию о comm можно найти на сервере SourceForge по следующему адресу: http://tcllib.sourceforge.net. Возможности команды send Команда send вызывает Tcl-команду в другом приложении. Обращение к этой команде происходит следующим образом: send опции приложение параметр ?параметр. . .? Команда send действует подобно eval; при наличии дополнительных параметров она выполняет их конкатенацию, формируя одну команду. Если структура параметров важна, надо для формирования команды использовать операцию list. Опции команды send описаны в табл. 43.1. Таблица 43.1. Опции команды send -async Отмена ожидания завершения удаленной команды -displayof окно Обращение к приложению на том же дисплее путем указания окна Отделяет опции от следующего параметра, определяющего имя приложения При вызове команды send за опциями следует имя другого приложения. Прикладная программа определяет свое имя при создании главного окна. Оболочка wish использует в качестве имени последний компонент имени файла, в котором содержится сценарий. Например, если wish интерпретирует сценарий /usr/local/bin/exmh, то именем приложения считается exmh. Однако это справедливо только в том случае, если в системе не выполняются программы с таким именем. При наличии экземпляра exmh wish выберет имя exmh #2 и т.д. Если wish не выполняет сценарий, именем программы будет wish. В заголовке окна можно встретить имя wish #2 или wish #3; это указывает на то, что в системе уже выполняется несколько приложений wish. Сценарию доступно его имя, поэтому вы можете передавать имена или помещать их в файл, создавая тем самым условия для взаимодействия программ. Команда tk appname запрашивает или изменяет имя приложения. set myname [tk appname] tk appname aNewName
Глава 43. Команда send 887 Команда send и авторизация X Window Работа команды send основана на использовании механизма авторизации X Window. Если средства авторизации не установлены, команда будет отвергнута целевым интерпретатором. Решить эту проблему можно двумя способами. Во-первых, вы можете отменить проверку корректности доступа, скомпилировав файл tkSend. с со сброшенным флагом -DTK_NO_SECURITY. Если вам приходится следить за тем. чтобы ваше приложение не выполнило команду, переданную программой, которая была составлена злоумышленником, то данный способ не подходит. Во-вторых, вы можете запустить Х-сервер с опцией -auth, в результате чего инициализируется механизм авторизации X Window. Детали инициализации изменяются в зависимости от используемого Х-сервера; последние версии инициализируются автоматически. Обычно генерируется псевдослучайная строка, которая сохраняется в файле ~/.Xauthority. Права для чтения данного файла имеет только один пользователь. С помощью опции -auth имя файла сообщается Х-серверу. Каждое Х-приложепие читает данный файл и при установлении соединения передает содержимое Х-серверу. Если переданная информация совпадает с данными, прочитанными при запуске сервера, разрешается установление соединения. На самом деле система действует несколько сложнее, чем описано здесь. В файле реально содержится набор записей, предназначенных для поддержки нескольких дисплеев и клиентских узлов. Особенности реализации данного механизма вы можете найти в документации на вашу систему. Список xhost не должен содержать ни одной записи. Для нормальной работы Тк необходимо, чтобы список xhost был пуст. Механизм аутентификации xhost был разработан достаточно давно и не обеспечивает должной защиты. Программы, выполняющиеся на узлах, указанных в списке xhost, имеют право устанавливать соединение с вашим дисплеем. Проблема заключается в том, что рабочая станция, находящаяся в многопользовательском режиме, допускает удаленную регистрацию. Таким образом, каждый, кто работает на узле, указанном в списке xhost, может зарегистрироваться на рабочей станции. Механизм Xauthority обеспечивает более высокую степень защиты, так как в этом случае доступ ограничивается только теми учетными записями, которые могут предоставить секретный ключ. Однако даже в том случае, когда средства Xauthority включены, пользователь или программа может воспользоваться xhost и получить доступ к дисплею.
888 Часть V. Особенности работы Тк Если при запуске xhost параметры не заданы, эта программа возвращает информацию о том, какие узлы указаны в списке. Ниже приведен пример вызова xhost. В данном случае осуществляется контроль доступа, но программы, выполняющиеся на узле sage, имеют право доступа к дисплею. exec xhost => Access control enabled: all hosts being restricted sage Такое состояние системы неприемлемо для выполнения команды send. Причина в том, что в списке указан узел sage. В некоторых средах сценарии, разработанные достаточно давно, могут постоянно добавлять сведения в список xhost. Для того чтобы исключить возникновение проблем, была разработана версия команды send, которая проверяет на наличие ошибок и очищает список xhost, выполняя приведенные ниже действия. xhost - ;# Разрешение контроля доступа foreach host [lrange [split [exec xhost] \n] 1 end] { exec xhost -$host ;# clear out exceptions } Сценарий, осуществляющий передачу данных В данном разделе рассматривается пример сценария, который читает входные данные и затем передает их другому приложению. Этот сценарий можно поместить в конце конвейерной цепочки, реализовав тем самым "обратную петлю". (Следует заметить, что тех же результатов можно добиться, используя fileevent.) Однако send имеет преимущество по сравнению с f ileevent: при использовании этой команды передающая и принимающая программы оказываются относительно независимы друг от друга. Листинг 43.1. Приложение, осуществляющее передачу данных #!/usr/local/bin/wish # Программе передается до четырех параметров: #1) Имя приложения для взаимодействия. # 2) Префикс команды. # 3) Имя приложения, которое необходимо оповестить # по достижении конца данных. # 4) Команда для использования при оповещении. # Скрыть ненужное окно
Глава 43. Команда send 889 wm withdraw . # Обработка параметров командной строки if {$argc == 0} { puts stderr "Usage: send name ?cmd? TuiName? ?uiCmd?" exit 1 } else { set app [1index $argv 0] } if {$argc > 1} { set cmd [lindex $argv 1] } else { set cmd Send_Insert } if {$argc > 2} { set ui [lindex $argv 2] set uiCmd Send_Done } if {$argc > 3} { set uiCmd [lindex $argv 3] } # Чтение входных данных и передача их регистрирующему приложению while {[gets stdin input] >= 0} { # Ошибки при работе с регистрирующим приложением игнорируются catch {send $app [concat $cmd [list $input\n]]} } # Оповещение управляющего приложения if [info exists ui] { if [catch {send $ui $uiCmd} msg] { puts stderr "send.tcl could not notify $ui\n$msg" } } # Данная команда необходима для принудительного завершения wish. exit Передающее приложение поддерживает взаимодействие с двумя процессами. Оно передает входные данные регистрирующему приложению. По окончании ввода оно может передать сообщение управляющему приложению. Функции регистрирующего и управляющего приложения могут быть реализованы в рамках одной программы. Для цитирования передаваемых параметров следует использовать команду list.
890 Часть V. Особенности работы Тк Рассмотрим команду send, которая использовалась в приведенном выше примере. send $app [concat $cmd [list $input\n]] Сочетание команд concat и list может показаться несколько странным. Команда list обеспечивает цитирование входной строки. Полученное значение присоединяется к команде, т.е. оно выглядит как один дополнительный параметр. При отсутствии цитирования результаты разбора команды удаленным интерпретатором будут зависеть от входной строки. Рассмотрим альтернативный вариант приведенного выше выражения. send $app [list $cmd $input] Данная команда корректна лишь в том случае, если значение $cmd представляет собой одно слово. Если cmd содержит значение, подобное приведенному ниже, удаленный интерпретатор будет рассматривать составляющие его несколько слов как имя команды. .log insert end .log see end ; .log insert end Рассмотрим еще одно выражение. send $app $cmd $input Команда send осуществляет конкатенацию $cmd и $input, а результат передается удаленному интерпретатору. Успех разбора удаленной команды зависит от значения входных данных. Если они содержат специальные символы, например $ или [ ], возникнет ошибка либо результаты будут непредсказуемыми. Взаимодействие процессов В главе 24 рассматривались браузер для просмотра примеров, приведенных в данной книге, и простая оболочка, позволяющая выполнять команды Tcl. В этой главе мы объединим их в одно приложение. Для их объединения будет использоваться команда send. В листинге 43.2 приведены модифицированные варианты процедур Run и Reset браузера, передающие команды EvalEcho оболочке. Листинг 43.2. Связывание браузера с сервером eval # Процедуры Run и Reset браузера, представленного в # листинге 24.3, заменяются на процедуры, приведенные ниже # Запуск сценария evalsrv.tcl. proc StartEvalServer {} {
Глава 43. Команда send 891 global browse # Запуск оболочки и передача ей имени. exec evalsrv.tcl [tk appname] & # Ожидать, пока evalsrv.tcl передаст свое имя tkwait variable browse(evallnterp) } proc Run {} { global browse set apps [winfo interps] set ix [lsearch -glob $apps evalsrv.tcl*] if {$ix < 0} { # Приложение evalsrv.tcl не выполняется StartEvalServer } if {![info exists browse(evallnterp)]} { # Связывание с выполняющимся сервером eval set browse(evallnterp) [lindex $apps $ix] } if [catch {send $browse(evallnterp) {info vars}} err] { # Возможно, программа завершилась; следует перезапустить ее. StartEvalServer } # Передача команды в асинхронном режиме. Две команды # list отменяют конкатенацию, предполагаемую send и # uplevel в EvalEcho send -async $browse(evallnterp) \ [list EvalEcho [list source $browse(current)]] } # Сброс интерпретатора на сервере eval proc Reset {} { global browse send $browse(evallnterp) {EvalEcho reset} 2 Число списков, создаваемых перед выполнением команды send, может показаться чрезмерно большим, однако все они необходимы. Команда send осуществляет конкатенацию параметров; чтобы это не происходило, мы передаем ей один список. Аналогично, EvalEcho требует указания одного параметра, представляющего собой допустимую команду. Для ее формирования также используется команда list. Процедура StartEvalServer запускает оболочку. Параметры командной строки используются для передачи оболочке имени программы. Для того что-
892 Часть V. Особенности работы Тк бы завершить процесс установления соединения, оболочка передает браузеру свое имя. Браузер сохраняет имя оболочки в элементе массива browser(evallnterp). Код, выполняющий эти действия, показан в листинге 43.3. Листинг 43.3. Превращение оболочки в сервер eval # Данный фрагмент кода добавляется к программе оболочки, # показанной в листинге 24.4 if {$argc > 0} { # Передача имени приложения браузеру send [lindex $argv 0] \ [list set browse(evallnterp) [tk appname]] 2 Удаленное выполнение eval с использованием сетевых гнезд Для выполнения Tcl-команд другими приложениями применим механизм, базирующийся на использовании сетевых гнезд. В этом случае именем приложения считаются узел и порт, указываемые при создании гнезда. Для управления именами можно использовать различные подходы. Несколько грубый, но зато эффективный способ состоит в записи имен узлов и портов в файл, доступный посредством сетевой файловой системы. Сервер выбирает порт, при этом предполагается, что клиент знает его номер. Проблема получения клиентом номера порта здесь не рассматривается. В листинге 43.4 приведена реализация процедуры Eval_Server, которая позволяет другим приложениям устанавливать соединение и выполнять Tcl- команды. Параметр interp определяет интерпретатор для обработки Тс 1-команды. Если в приложении, вызывающем Eval_Server, вместо этого параметра указывается {}, то разбор команды осуществляет текущий интерпретатор. При установке соединения вызывается openCmd. С помощью openCmd можно осуществлять необходимую настройку или аутентификацию. Если соединение не удовлетворяет каким-либо критериям, гнездо может быть закрыто. Листинг 43.4. Удаленное выполнение команды eval с использованием сетевых гнезд proc Eval__Server {port {interp {}} {openCmd EvalOpenProc}} { socket -server [list EvalAccept $interp $openCmd] Sport } proc EvalAccept {interp openCmd newsock addr port} { global eval
Глава 43. Команда send 893 set eval(cmdbuf ,$newsock) {} fileevent $newsock readable [list EvalRead $newsock $interp] if [catch { interp eval $interp $openCmd $newsock $addr $port }] { close $newsock } } proc EvalOpenProc {sock addr port} { # Выполнение аутентификации и # закрытие $sock для прекращения взаимодействия _} В листинге 43.5 представлен код процедуры EvalRead. Эта процедура читает команды и выполняет их с помощью интерпретатора. Если в качестве параметра interp задано значение {}, команда выполняется с помощью текущего интерпретатора. В этом случае выражение uplevel #0 необходимо для того, чтобы команда выполнялась в глобальной области видимости. Если вы используете interp eval, то выражение будет выполнено в текущей области видимости. Листинг 43.5. Чтение команд из сетевого гнезда proc EvalRead {sock interp} { global eval errorlnfo errorCode if [eof $sock] { close $sock } else { gets $sock line append evaKcmdbuf ,$sock) $line\n if {[string length $eval(cmdbuf,$sock)] && \ [info complete $eval(cmdbuf,$sock)]} { set code [catch { if {[string length $interp] == 0} { uplevel #0 $eval(cmdbuf,$sock) } else { interp eval $interp $eval(cmdbuf,$sock) } } result] set reply [list $code $result Serrorlnfo \ $errorCode]\n # Использование regsub для подсчета строк, set lines [regsub -all \n $reply {} junk]
894 Часть V. Особенности работы Тк # Ответом является счетчик строк, сопровождаемый # Tcl-списком, который занимает соответствующее # количество строк, puts $sock $lines puts -nonewline $sock $reply flush $sock set eval(cmdbuf,$sock) {} } } 2 В листинге 43.6 содержится код процедур Eval_0pen и Eval_Remote, которые поддерживают соединение на стороне клиента. Процедура Eval.Open устанавливает соединение с сервером и возвращает дескриптор гнезда. Основная задача процедуры Eval.Remote состоит в том, чтобы сохранить информацию в случае, если при выполнении удаленной команды возникает ошибка. Сетевой протокол ориентирован на обмен строками символов. Процедура Eval_Remote записывает команду в гнездо. Для определения окончания команды используется операция info complete. В ответ сервер передает значение счетчика и соответствующее количество строк. Команда regsub подсчитывает символы перевода строки. Ответ представляет собой список кодов ошибок, результатов и данных о состоянии. Об особенностях выполнения команды return см. в главе 6. Листинг 43.6. Клиентская программа, участвующая в удаленном выполнении команд proc Eval_0pen {server port} { global eval set sock [socket $server $port] # Сохранение информации для отчета об ошибках set eval(server,$sock) $server:$port return $sock } proc Eval_Remote {sock args} { global eval # Специальная обработка конкатенации, выполняемой eval if {[llength $args] > 1} { set cmd [concat $args] } else { set cmd [lindex $args 0] }
Глава 43. Команда send 895 puts $sock $cmd flush $sock # Чтение счетчика строк и результата, gets $sock lines set result {} while {$lines > 0} { gets $sock x append result $x\n incr lines -1 } set code [1index $result 0] set x [lindex $result 1] # Очистка конца стека regsub "\СЛп]+$" [lindex $result 2] \ "*Remote Server $eval(server,$sock)*u stack set ec [lindex $result 3] return -code $code -errorinfo $stack -errorcode $ec $x } proc Eval_Close {sock} { close $sock 2 Если при выполнении удаленной команды возникает ошибка, возвращается информация о состоянии стека. В нее входит команда, используемая в составе EvalRead для вызова команды, т.е. uplevel или interp eval. Она содержится в последней строке информации о стеке; regsub используется для замены ее на сведения о передаче управления удаленному серверу. catch [Eval_Remote sock6 set xx] => 1 set errorinfo => can't read "xx": no such variable while executing "set xx n ("uplevel" body line 1) invoked from within *Remote Server sage:4000* invoked from within "catch [Eval_Remote sock6 set xx]"
Глава 44 Диспетчеры окон Диспетчеры окон управляют размером и расположением окон прикладных программ. В Windows и Macintosh диспетчеры окон встроены в операционную систему, а в Unix выполняются как отдельные приложения. Интерфейс диспетчера окон реализует команда wm. Команда winf о возвращает информацию об окнах. Команда tk предоставляет различные сведения как о Тк в целом, так и об оконной системе. ^У правление окнами верхнего уровня осуществляется с помощью диспетчеров окон. На платформах Macintosh и Windows диспетчеры окон встроены в операционную систему, а в Unix выполняются как отдельные приложения. Диспетчер окон управляет расположением окон верхнего уровня, предоставляет средства для изменения их размеров, позволяет открывать и закрывать их, а также реализует обрамление и строку заголовка. Команда wm позволяет пользователю и приложениям взаимодействовать с диспетчером, в результате приложения получают возможность управлять размерами, расположением окон и представлять их в виде пиктограмм. Если вам необходимо контролировать детали отображения окон, то понадобится подробная информация о компонентах. Команда winf о возвращает детальные сведения не только об окнах верхнего уровня, но и о содержащихся в них компонентах. Команда wm Команда wm реализует около 20 операций, предназначенных для взаимодействия с диспетчерами окон. Данная команда вызывается следующим образом: wm операция окно ?параметры?
Глава 44. Диспетчеры окон 897 Для любой операции должно быть задано окно верхнего уровня. В противном случае возникнет ошибка. Операции, реализуемые с помощью команды wm, предназначены в основном для получения или установки определенных значений. Если новое значение не задано, возвращается информация о текущих установках. Например, приведенная ниже команда возвращает позицию и размеры текущего окна. wm geometry . => 300x200+327+20 Следующая команда задает новое расположение и размер: wm geometry . 400x200+0+0 Большое количество операций wm отражает сложность протокола взаимодействия с диспетчерами окон в системе Unix. Далее в этом разделе буду приведены сведения о наиболее часто применяемых операциях. Все операции можно разделить на следующие четыре категории. • Управление размерами, расположением и оформлением окон. Для позиционирования окон и формирования строк заголовка используются операции geometry и title. • Управление пиктограммами. Отрывать и закрывать окна позволяют операции iconify, deiconify и withdraw. В системе Unix минимизированные окна представляются с помощью пиктограмм. • Управление состоянием сеанса. Для организации обратного вызова при закрытии окон пользователем используется операция protocol. • Прочие действия. Операции transient и overrideredirect используются для формирования специальных окон. Эти операции являются платформенно-ориентированными и позволяют выбирать различные стили окон верхнего уровня. Размеры, расположение и оформление окон верхнего уровня Каждое окно содержит строку заголовка, которую оконный диспетчер располагает в верхней части окна. Если сценарий выполняется в среде wish, то по умолчанию в строке заголовка отображается последний компонент имени файла, содержащего сценарий. Изменить содержимое строки заголовка позволяет команда wm title. Строка заголовка может также присутствовать в пиктограмме, представляющей окно. Изменить ее можно с помощью операции wm iconname. wm title . "My Application"
898 Часть V. Особенности работы Тк Команда wm geometry позволяет управлять расположением и размерами окон верхнего уровня. Информация о позиции и размере задается в формате WxH+X+Y, где W — ширина, Я — высота, а X и Y указывают расположение верхнего левого угла окна. Позиция +0+0 определяет верхний левый угол дисплея. Вы можете также указывать отрицательные значения X или У. В этом случае расположение нижней (правой) границы окна отсчитывается от нижней (правой) границы дисплея. Например, значение +0—0 представляет нижний левый угол, а значение —100—100 — точку, отстоящую от нижнего правого угла на 100 пикселей по каждой координате. Если вы не зададите позицию и размеры, команда вернет текущие установки. Листинг 44.1. Установки сетки для холста canvas .с -width 300 -height 150 pack .с -fill both -expand true wm geometry . => 300x200+678+477 wm grid . 30 15 10 10 wm geometry . => 30x20+678+477 В листинге 44.1 приведены установки сетки для холста. Термин "установки сетки" означает, что размеры и позиция задаются в единицах измерения, отличных от пикселей. При работе с холстом для определения размеров ячейки сетки используется команда wm grid. Размеры сетки для компонентов text и listbox задаются исходя из размеров символов, отображаемых в поле редактирования или в окне списка. Данные компоненты содержат атрибут set grid, управляющий установками сетки (см. главу 42). Команда wm resizable позволяет определить, имеет ли пользователь право изменять размеры окна. Приведенная ниже команда разрешает изменение размеров по горизонтали, но не по вертикали. wm resizable . 1 0 По мере необходимости вы можете ограничить минимальный, максимальный размер и коэффициент сжатия (aspect ratio) окна верхнего уровня. Как было сказано ранее, коэффициент сжатия определяется как частное от деления ширины окна на его высоту. Ограничения вступают в действие тогда, когда пользователь изменяет размеры окна в интерактивном режиме. Ограничения задаются с помощью опций minsize, maxsize и aspect. Некоторые диспетчеры окон требуют, чтобы пользователи указывали размеры окон. Обойти это ограничение позволяют операции sizefrom и positionfrom. Они создают иллюзию того, что размеры и позиция окна были заданы пользователем.
Глава 44. Диспетчеры окон 899 В табл. 44.1 приведены сведения об операциях, реализуемых командой wm и предназначенных для управления размерами, оформлением и расположением окон. Таблица 44.1. Операции, определяющие размеры, расположение и оформление окон wm aspect окно ?а b с d? wm geometry окно ?значение? wm grid окно ?w h dx dy? wm maxsize окно ?ширина высота? wm minsize окно ?ширина высота? wm positionfrom окно ?объект? wm resizable окно ?xok у ok? wm sizefrom окно ?объект? wm stackorder окно wm stackorder окно ?isabove|isbelow окно? wm title окно ?строка? Ограничивает коэффициент сжатия для окна, позволяя ему принимать значения a/b-c/d Запрашивает или устанавливает размеры и размещение окна Запрашивает или устанавливает размер сетки. Значения w и h представляют собой базовые размеры, выраженные в экранных единицах измерения. Размеры dx и dy определяются в пикселях или в экранных единицах измерения Ограничивает максимальный размер окна Ограничивает минимальный размер окна Запрашивает или устанавливает объект как программу или пользователя Запрашивает или устанавливает информацию о том, могут ли размеры окна быть изменены в интерактивном режиме Запрашивает или устанавливает объект как программу или пользователя Возвращает список окон верхнего уровня. Порядок следования элементов списка соответствует положению их в стеке от низшего до высшего (Тк 8.4) Возвращает логическое значение, которое указывает, расположено ли первое окно выше или ниже в стеке по сравнению со вторым окном (Тк 8.4) Запрашивает или устанавливает заголовок окна Операция stackorder, реализованная в Тк 8.4, возвращает информацию о стеке окон верхнего уровня для приложения. При вызове данной операции ей передается имя окна, а в результате ее выполнения возвращается список дочерних окон в порядке следования их в стеке. Операция предоставляет информацию только о тех окнах, которые в данный момент отображаются на экране. Пример вызова wm stackorder приведен ниже. wm stackorder .
900 Часть V. Особенности работы Тк С помощью операции stackorder можно также выяснить, расположено ли одно из окон верхнего уровня выше или ниже другого. Если при вызове команды задать два параметра, разделенных ключевым словом isabove или isbelow, она вернет логическое значение, указывающее на то, действительно ли первое окно находится выше или ниже второго в стеке окон. Подобное использование операции wm stackorder демонстрируется следующим примером: wm stackorder . isabove .dialog Пиктограммы В системе Unix диспетчеры окон позволяют минимизировать окна и заменять их пиктограммами. Для приложения такое окно продолжает существовать, и пользователь может впоследствии открыть его. Заменять окно пиктограммой и восстанавливать его нормальный вид позволяют соответственно операции iconify и deiconify. Операция withdraw отменяет отображение окна, не заменяя его пиктограммой. Операция state возвращает текущее состояние окна, которое может быть normal, iconif ied или withdrawn. Если к окну применена операция withdraw, вы можете восстановить его нормальный вид с помощью операции deiconify. В системах Windows и Macintosh замена окон программ пиктограммами не предусмотрена. Вместо этого пиктограммы используются для представления файлов и приложений на рабочем столе. Если вы минимизируете окно в системе Windows, это окно будет представлено кнопкой на панели задач в нижней части окна. После щелчка на такой кнопке восстанавливается нормальное состояние окна. В системе Macintosh операция iconify приводит к отмене отображения окна. Начиная с версии Тк 8.3 для системы Windows было введено новое состояние приложения, zoomed, которое соответствует окну, развернутому на весь экран. В последующих версиях Тк такое состояние, возможно, будет введено и для остальных операционных систем. Для установки атрибутов пиктограмм в системе Unix предусмотрены операции iconname, iconposition, iconbitmap и iconmask. Маска используется для получения пиктограмм нерегулярной формы. Определение масок и битовых карт см. в главе 41. В случае пиктограмм определение битовой карты помещается в файл, поэтому вызов операции имеет следующий вид: wm iconbitmap . @ияя_файла Начиная с реализации Тк 8.3.3 вы можете, вызывая в системе Windows команду wm iconbitmap, задавать путь к файлам, содержащим пиктограммы для данной системы (обычно это файлы .ico или .icr). Если вы зададите
Глава 44. Диспетчеры окон 901 опцию -default, которая была введена в Тк 8.4, указанная битовая карта будет по умолчанию применяться ко всем окнам. Если вы указываете битовую карту для пиктограммы в системе Windows, помните, что в качестве параметра следует задавать имя файла без ведущего символа @. wm iconbitmap . -default [file join $lib myapp.ico] В табл. 44.2 описаны операции, предназначенные для работы с окнами. Таблица 44.2. Команды оконного диспетчера для работы с окнами wm deiconif у окно Открывает указанное окно wm iconbitmap окно Запрашивает или устанавливает битовую карту для ?битовая^карта? пиктограммы. Для системы Unix wm iconbitmap окно Устанавливает битовую карту для пиктограммы, ис- ?-default? файл пользуя указанный файл. Опция -default указывает на то, что данная битовая карта должна использоваться по умолчанию для всех окон приложения. Для системы Windows (Tk 8.3.3) wm iconif у окно Закрывает указанное окно wm iconmask окно Запрашивает или определяет маску для пиктограм- ? маска? мы. Используется в системе Unix wm iconname окно ?имя? Запрашивает или задает имя пиктограммы. Используется в системе Unix wm iconposition окно Запрашивает или задает расположение пиктограм- ?х у? мы. Используется в системе Unix wm iconwindow окно Запрашивает или определяет альтернативное окно ?окно? для отображения в состоянии, предполагающем замену окна пиктограммой. Используется в системе Unix wm state окно Возвращает информацию о состоянии окна. Возмож- ?состояние? ные значения: normal, iconic, withdrawn или zoomed (только для системы Windows). Окно переводится в новое состояние, если таковое указано wm withdraw окно Прекращает отображение окна. Пиктограмма не выводится Состояние сеанса Диспетчер окон позволяет закрывать окна, выполняя операцию close. При удалении главного окна Тк завершается программа wish. Если вам необходимо выполнить специальную обработку после того, как пользователь уда-
902 Часть V. Особенности работы Тк лит окно, вам надо переопределить операцию close. Для регистрации команды, обрабатывающей сообщение диспетчера окон WM_DELETE_WINDOW, следует использовать операцию wm protocol. Эта операция поддерживается на всех платформах, даже несмотря на то, что в Unix используется термин delete, а в Windows и Macintosh — close. wm protocol . WM_DELETE_WINDOW Quit Если вы переопределите операцию закрытия главного окна Тк, вам придется рано или поздно явным образом завершить приложение, вызвав команду exit. Желательно также сообщить пользователю о несохраненных изменениях и даже предоставить ему возможность отказаться от решения завершить приложение. Возможно, вам понадобится также перехватывать сообщения WM_SAVE_YOURSELF и WM_TAKE_F0CUS. Первое из этих сообщений периодически генерируется некоторыми диспетчерами окон в системе Unix. Второе сообщение используется при реализации активной модели фокуса. При работе Тк предполагается использование пассивной модели фокуса, согласно которой диспетчер окон передает фокус окну верхнего уровня (эта модель описана в данной книге). Некоторые диспетчеры окон, предназначенные для работы в системе Unix, поддерживают сеанс, который включает в себя несколько последовательных запусков оконной системы. Сеанс реализуется путем сохранения состояния выполняемых приложений и использования информации о состоянии при повторном вызове приложений. Простейший способ поддержки сеанса — сохранить команду, которая использовалась для запуска приложения. Это можно сделать с помощью операции wm command. Оболочка wish сохраняет необходимую информацию, поэтому для поддержки сеанса необходимо лишь зарегистрировать ее в оконном диспетчере. В приведенном ниже примере argvO — это команда, a argv — параметры командной строки. wm command . [1insert $argv 0 $argv0] Если ваше приложение обычно выполняется на одном компьютере, а результаты отображаются на другой машине, выполняющей функции Х-терми- нала, вы также должны записать сведения об узле, на котором было запущено приложение. Сделать это можно с помощью операции wm client. В приведенном ниже примере в некоторых случаях вместо uname приходится указывать hostname. wm client . [exec uname -n] В табл. 44.3 описаны операции оконного диспетчера, имеющие отношение к поддержке сеанса.
Глава 44. Диспетчеры окон 903 Таблица 44.3. Операции оконного диспетчера, предназначенные для поддержки сеанса wm client окно Записывает имя узла в свойстве WM_CLIENT_MACHINE. Ис- ?имя? пользуется в системе Unix wm command окно Записывает команду запуска в свойстве WM_C0MMAND. Ис- ?команда? пользуется в системе Unix wm protocol окно Регистрирует команду для поддержки запроса, в каче- ?имя? ?'команда? стве которого может быть указано WM_DELETE_WINDOW, WM_SAVE_YOURSELF или WM_TAKE_F0CUS Прочие операции с диспетчером окон В системе Unix диспетчеры окон изменяют родительское окно для окна приложения. В результате окно приложения становится дочерним по отношению к окну, для которого формируются обрамление и строка заголовка. Операция wm frame возвращает идентификатор нового родительского окна. Если принадлежность родительскому окну не была изменена, возвращается идентификатор самого окна. Операция wm overrideredirect устанавливает признак, отменяющий процесс переподчинения новому родительскому окну. Это означает, что для данного окна не будут отображаться обрамление и строка заголовка; кроме того, данное окно не будет управляться диспетчером окон. Операция wm group определяет группы окон. Диспетчер компоновки может одновременно открывать и закрывать все окна, принадлежащие группе. Обычно одно из окон объявляется главным, или лидирующим. Если лидирующее окно представляется в виде пиктограммы, та же операция выполняется над другими членами группы. В системах Windows и Macintosh подобный механизм не реализован; более того, его поддерживают не все оконные диспетчеры, выполняющиеся в системе Unix. Операция wm transient информирует диспетчер окон о том, что данное окно является временным и для него не надо создавать обрамление и строку заголовка. Такая операция применяется, например, в случае, когда надо создать контекстное меню. В системе Windows временные окна используются для тех панелей инструментов, которые не представлены на панели задач. В системе Macintosh предусмотрена команда tk: : unsupported: : MacWindowStyle (см. главу 32). Эта команда позволяет создавать различные стили окон верхнего уровня. В Tk 8.4 была реализована команда wm attributes, которая позволяет запрашивать или устанавливать атрибуты окна, специфические для конкретной платформы. Для системы Windows поддерживаются следующие атрибу-
904 Часть V. Особенности работы Тк ты: disabled — запрещает доступ к окну или предоставляет информацию о том, разрешено ли обращение к окну; toolwindow — устанавливает или запрашивает соответствующий стиль окна; topmost — устанавливает или проверяет режим отображени51 окна поверх остальных окон. В табл. 44.4 приведена информация о некоторых операциях диспетчера окон. Таблица 44.4. Операции диспетчера окон различного назначения wm attributes окно ?...? wm colormapwindows окно ?список окон? wm focusmodel окно ?'модель ? wm frame окно wm group окно ?лидирующее__ окно ? wm overrideredirect окно ?логическое_значение? wm transient окно ?лидирующее_окно? Устанавливает или запрашивает платформенно- ориентированные атрибуты окна (Тк 8.4) Устанавливает или запрашивает свойство WM_C0L0RMAP_WIND0WS, посредством которого упорядочиваются окна с различными картами отображения цвета Устанавливает или запрашивает модель фокуса ввода: active или passive. (В Тк применяется модель passive.) Если ранее производилась операция смены родительского окна, возвращается идентификатор окна, выступающего в качестве родительского по отношению к заданному. В противном случае возвращается идентификатор окна, указанного при вызове команды Запрашивает или устанавливает лидирующее окно в группе (окно верхнего уровня), которой принадлежит указанное окно. Диспетчер окон может одновременно отменить отображение всей группы Устанавливает или запрашивает бит, который подавляет операцию смены родительского окна, осуществляемую диспетчером окон Помечает окно как временное, работающее в интересах лидирующего, либо запрашивает соответствующую информацию об окне Команда winfo С помощью команды winfo реализуется приблизительно 50 операций, возвращающих информацию о компоненте или дисплее. Эти операции можно условно разделить на следующие категории. • Передача команд между приложениями.
Глава 44. Диспетчеры окон 905 • Поддержка связей внутри семейства. • Управление размером. • Управление расположением. • Поддержка координат виртуального корневого окна. • Работа с атомами и идентификаторами. • Поддержка карт отображения цвета и визуальных классов. Передача команд между приложениями Каждое Tk-приложение имеет имя, которое используется при передаче команд между программами с помощью команды send (о команде send см. в главе 43). Список приложений Тк предоставляет операция interps. Команда tk appname используется для получения имени приложения, с ее же помощью можно задавать имя. В листинге 44.2 показан пример взаимодействия прикладной программы с несколькими существующими приложениями. Приведенный фрагмент кода обращается к каждому зарегистрированному интерпретатору Тк и передает команду, указывая в качестве параметра имя приложения. Это имя может использоваться другими программами для поддержки взаимодействия. Листинг 44.2. Фрагмент кода, сообщающий другим программам имя приложения foreach app [winfo interps] { catch {send $app [list lam [tk appname]]} 2 Информация об упомянутых выше командах содержится в табл. 44.5. Таблица 44.5. Средства получения информации для команды send tk appname ?новое_имя? Запрашивает или устанавливает имя, используемое командой send winfo name . Возвращает имя, используемое командой send. Данная команда поддерживается для обеспечения обратной совместимости с Тк З.б и более ранними версиями winfo name путь Возвращает последний компонент пути winfo ?-displayof окно? Возвращает список зарегистрированных Тк-при- interps ложений для того же дисплея, на котором отображается указанное окно
906 Часть V. Особенности работы Тк Взаимосвязь между компонентами одного семейства Каждый компонент Тк занимает определенное место в иерархической структуре, сведения о которой можно получить с помощью команды winfо. Операция winf о children возвращает сведения о дочерних окнах, а операция winf о parent — о родительских. Вместо сведений о родительском окне главного окна возвращается пустая строка. Компонент является также членом класса; имя класса используется в качестве ключа при работе с базой данных ресурсов. Информацию о классе возвращает операция winf о class. Проверить наличие окна позволяет операция winf о exists, а определить, отображается ли окно на экране, можно с помощью операции winf о viewable. Заметьте, что значение winf о ismapped равно true для компонентов, которые отображаются под управлением диспетчера компоновки. Если же соответствие между окном верхнего уровня и экранным образом не определено, компонент не выводится на экран. Операция winf о manager сообщает, какой из диспетчеров компоновки управляет размещением окна. В результате выполнения эта операция возвращает имя команды, используемой для работы с диспетчером компоновки. Такими командами могут быть pack, place, grid, canvas и text. Последние две команды указывают на то, что компонент включен в состав холста или текстового компонента. Операции winf о описаны в табл. 44.6. Таблица 44.6. Информация об иерархии окон winf о children окно Возвращает список дочерних компонентов окна winf о class окно Возвращает класс ресурсов для окна winf о exists окно Возвращает значение 1, если окно существует winf о ismapped окно Возвращает значение 1, если окно отображается на экране winf о manager окно Возвращает одно из следующих значений: pack, place, grid, canvas или text winf о parent окно Возвращает родительский компонент для указанного окна winf о viewable окно Возвращает значение 1, если окно и все его родительские окна отображаются на экране Размер компонента Операции winf о width и winf о height возвращают соответственно ширину и высоту окна. Кроме того, вы можете запросить требуемую ширину и вы-
Глава 44. Диспетчеры окон 907 соту. Для получения этих данных используются операции winfo reqwidth и winfo reqheight. Требуемые размеры могут быть неточными, так как в некоторых случаях диспетчер компоновки расширяет или сокращает пространство, предназначенное для компонента, а пользователь может изменить размеры окна. До установления соответствия между окном и его экранным образом информация о размерах окна может быть некорректной. Размер окна не устанавливается до тех пор, пока диспетчер компоновки не создаст соответствие между окном и экранным образом. Первоначально высота и ширина окна принимаются равными 1. Дождаться отображения окна позволяет команда tkwait visibility. После этого в ответ на запрос высоты и ширины окна вы получите реальную информацию. Кроме того, вы можете использовать команду update, предоставив тем самым Тк возможность обновить содержимое экрана. При использовании update могут возникать проблемы (см. главу 39). В примере, приведенном в листинге 39.1, применялась операция tkwait visibility. Операция winfo geometry возвращает размер и расположение окна в стандартном формате WxH+X+Y. В данном случае элементы X и Y представляют собой смещение относительно родительского компонента; в случае главного окна смещение определяется относительно корневого окна. Вы также можете определить размеры экрана. Операции winfo screenwidth и winfo screenheight возвращают размеры в пикселях. Операции winfo screenmmwidth и winfo screenmmheight предоставляют те же данные, выраженные в миллиметрах. Преобразовать в пиксели размеры, выраженные в экранных единицах, позволяют операции winfo pixels и winfo fpixels. При вызове операции ей передается размер в экранных единицах измерения, например Ют, Зс или 72р, а в результате возвращается соответствующее число пикселей. Операция winfo pixels округляет данные до целого числа пикселей, a winfo fpixels возвращает число с плавающей точкой. Соотношение между размерами в пикселях и других единицах может быть неточным, так как пользователь имеет возможность изменять размер пикселя на мониторе, а Тк не получает сведений об этом. Об экранных единицах измерения см. в главе 40. Пример использования операции winfo pixels приведен ниже. set pixelsToInch [winfo pixels . 2.54c] Сведения об упомянутых выше операциях приведены в табл. 44.7.
908 Часть V. Особенности работы Тк Таблица 44.7. Информация о размерах окон winfo fpixels окно размер winfo geometry окно winfo height окно winfo pixels окно размер winfo reqheight окно winfo reqwidth окно winfo screenheight окно winfo screenmmheight окно winfo screenmmwidth окно winfo screenwidth окно winfo width окно Преобразует размер, указанный в экранных единицах измерения, в пиксели. Возвращает число с плавающей точкой Возвращает информацию о размере и размещении окна. Данные измеряются относительно родительского окна и представляются в пикселях в формате WxH+X+Y Возвращает высоту окна в пикселях Преобразует размер, указанный в экранных единицах измерения, в целое число пикселей Возвращает требуемую высоту окна в пикселях Возвращает требуемую ширину окна в пикселях Возвращает высоту экрана в пикселях Возвращает высоту экрана в миллиметрах Возвращает ширину экрана в миллиметрах Возвращает ширину экрана в пикселях Возвращает ширину окна в пикселях Расположение компонентов Таблица 44.8. Информация о расположении окон winfo containing ?-displayof окно? окно х у winfo pointerx окно winfo pointery окно winfo pointerxy окно winfo rootx окно winfo rooty окно winfo screen окно winfo server окно Возвращает путь к окну, которому принадлежит точка с координатами х и у Возвращает экранную координату х курсора мыши Возвращает экранную координату у курсора мыши Возвращает координаты х и у курсора мыши Возвращает координату х, определяющую расположение окна на экране Возвращает координату у, определяющую расположение окна на экране Возвращает идентификатор дисплея, на котором отображается окно Возвращает строку, в которой указана версия сервера отображения
Глава 44. Диспетчеры окон 909 Окончание табл. 44.8 winfo toplevel окно Возвращает путь к окну верхнего уровня, которому принадлежит указанное окно winfo х окно Возвращает координату х, определяющую позицию окна относительно его родительского окна winfo у окно Возвращает координату у, определяющую позицию окна относительно его родительского окна Операции winfo x и winfo у возвращают координаты верхнего левого угла окна. Позиция отсчитывается относительно родительского окна. В случае главного окна отсчет производится относительно экранных координат. Операции winfo rootx и winfo rooty возвращают позицию верхнего левого угла компонента на экране; эта информация предоставляется даже в том случае, если окно не является окном верхнего уровня. Операция winfo containing возвращает путь к окну, содержащему указанную точку на экране. Эту команду можно применить при формировании меню и при реализации перетаскивания объектов. Операция winfo toplevel возвращает путь к окну верхнего уровня, содержащему компонент. Если окно само является окном верхнего уровня, то данная операция возвращает путь к этому окну. Операция winfo screen возвращает идентификатор дисплея, на котором отображается окно. Виртуальное корневое окно Некоторые диспетчеры окон предоставляют пользователю виртуальный экран большого размера, используя для этой цели виртуальное корневое окно. В каждый момент времени лишь часть виртуального окна отображается на экране. Перемещая экран по виртуальному окну, пользователь может отображать окна различных приложений. Операции winfo x и winfo у возвращают координаты главного окна относительно виртуального корневого окна. Операции winfo vrootheight и winfo vrootwidth возвращают размеры виртуального корневого окна. Если виртуальное окно не используется, то эти операции предоставляют лишь сведения о размерах экрана. Для преобразования координат виртуального окна в экранные координаты можно использовать winfo vrootx и winfo vrooty. Если виртуальное корневое окно отсутствует, данные операции возвращают нулевое значение. При наличии окна они возвращают отрицательное значение. Если вы сложите эти значения со значениями, возвращаемыми winfo x или winfo у, то получите координаты окна относительно экрана. set screenx [expr [winfo x $win] + [winfo vrootx $win]]
910 Часть V. Особенности работы Тк Сведения об упомянутых выше операциях приведены в табл. 44.9. Таблица 44.9. Информация о виртуальном корневом окне winfo vrootheight окно Возвращает высоту виртуального корневого окна для заданного окна winfo vrootwidth окно Возвращает ширину виртуального корневого окна для заданного окна winfo vrootx окно Возвращает координату х заданного окна в виртуальном корневом окне winfo vrooty окно Возвращает координату у заданного окна в виртуальном корневом окне Работа с атомами и идентификаторами Термин атом используется в системе X Window и обозначает идентификатор, зарегистрированный на Х-сервере. Приложения отображают имена в атомы, а Х-сервер ставит в соответствие каждому атому 32-разрядный идентификатор, который может передаваться от одной прикладной программы другой. В Тк данный механизм применяется редко. В качестве примера можно привести использование выделения для взаимодействия с различными инструментальными средствами. В некоторых случаях информация о выделении возвращается как атом и представляется 32-разрядным целым числом. Операция winfo atomname преобразует числовой идентификатор в атом, т.е. в строковое значения, a winfo atom регистрирует строку на Х-сервере и возвращает 32-битовый идентификатор как строку, состоящую из шестна- дцатеричных цифр. Каждому компоненту соответствует идентификатор, назначаемый оконной системой. Этот идентификатор возвращает команда winfo id. Операция winfo pathname возвращает путь к компоненту с заданным идентификатором, однако это происходит лишь в том случае, если окно принадлежит тому же приложению. Операция id может использоваться в том случае, когда необходимо включить другое приложение в иерархию окон. В программе wish предусмотрена опция -use, в качестве значения которой задается идентификатор окна. Данная опция указывает на то, что существующее окно должно использоваться в качестве главного окна программы. Аналогичные возможности предоставляют и другие инструментальные средства. Например, приведенные ниже выражения позволяют включить приложение Тк во фрейм. frame .embed -container true exec wish -use [winfo id .embed] otherscript.tcl
Глава 44. Диспетчеры окон 911 Указанные выше операции описаны в табл. 44.10. Таблица 44.10. Атомы и идентификаторы окон winfo atom ?-displayof Возвращает 32-битовый идентификатор для указан- окно? атом ного атома winfo atomname Возвращает атом, соответствующий 32-битовому ?-displayof окно? идентификатору идентификатор winfo id окно Возвращает идентификатор указанного окна winfo pathname Возвращает путь Тк для окна с указанным иденти- ?-displayof окно? фикатором или значение null идентификатор Карты отображения цвета и визуальные классы Операция winfo depth возвращает число битов, используемых для представления цвета каждого пикселя. Команда winfo cells предоставляет данные о числе входов карты отображения цвета, которая используется визуальным классом, связанным с окном. Эти два значения связаны друг с другом. Для окон, в которых пиксели представляются посредством 8 битов, обычно применяются карты отображения цветов, состоящие из 256 входов. Операции winfo screendepth и winfo screencells возвращают сведения о визуальных классах, используемых по умолчанию. Команда winfo visualsavailable представляет список, содержащий сведения о визуальных классах и глубине представления цвета. Например, дисплей, в котором для представления каждого пикселя используется 8 битов, возвращает следующие сведения о доступных визуальных классах: winfo visualsavailable . => -fstaticgray 8} {grayscale 8} {staticcolor 8} \ {pseudocolor 8} Операция winfo visual возвращает визуальный класс окна, a winfo screenvisual предоставляет сведения о визуальном классе экрана, используемом по умолчанию. Операция winfo rgb преобразует имя или значение цвета в компоненты, соответствующие красной, зеленой и синей составляющей этого цвета. В результате выполнения операция возвращает три десятичных числа. В листинге 41.2 данная команда использовалась для формирования цвета, более темного по сравнению с исходным. В табл. 44.11 приведены сведения об операциях, предназначенных для работы с картами отображения цвета и визуальными классами, и о данных, воз-
912 Часть V. Особенности работы Тк вращаемых этими операциями. Подробно о картах отображения цвета и визуальных классах см. в главе 41. Таблица 44.11. Информация о картах отображения цвета и визуальных классах winfo cells окно winfo colormapfull окно winfo depth окно winfo rgb окно цвет winfo screencells окно winfo screendepth окно winfo screenvisual окно winfo visual окно winfo visualsavailable окно Возвращает число ячеек карты отображения цвета для визуального класса окна Возвращает значение 1, если последнее распределение цвета было неудачным Возвращает число битов, используемых для представления пикселей в окне Возвращает значения, соответствующие красной, зеленой и синей составляющей указанного цвета Возвращает число ячеек карты отображения цвета для визуального класса по умолчанию Возвращает число битов на пиксель в визуальном классе экрана по умолчанию Возвращает визуальный класс для экрана по умолчанию Возвращает визуальный класс для окна Возвращает список пар значений, определяющих визуальный тип, и число битов на пиксель доступных визуальных классов Команда tk Команда tk обеспечивает доступ к различным средствам библиотеки Тк. Операция appname применяется для получения сведений об имени приложения, используемого при выполнении команды send. Данная операция также позволяет задавать имя приложения. Если имя, определенное вами, уже используется другой прикладной программой (или другим экземпляром той же программы), то к имени добавляется номер (#2, #3 и т.д.). Команда tk appname записывается следующим образом. tk appname ?имя? Размеры шрифтов, объектов холста и компонентов задаются в пикселях, пунктах, сантиметрах, миллиметрах или в дюймах. Команда tk scaling, реализованная в Тк 8.0, используется для установки соответствия между пикселями и пунктами. С помощью данной команды можно также получать сведения о текущем отображении. Масштаб 1,0 соответствует 72 пикселям в дюй-
Глава 44. Диспетчеры окон 913 ме. Задавая масштаб 1,25, вы указываете, что в дюйме должно содержаться 90 пикселей. При переходе от 90 точек на дюйм к 72 точкам на дюйм размеры увеличатся на 25%. Изменение масштаба влияет только на компоненты, созданные после вызова команды tk scaling. Обращение к данной команде производится следующим образом: tk scaling ?'число? Для обеспечения нормальной работы приложения необходимо получить сведения о текущей оконной системе. Команда tk windowingsystem, реализованная в Tk 8.4, возвращает одно из следующих значений: xll (X Window), Win32 (MS Windows), classic (Mac OS Classic) или aqua (Mac OS X Aqua). Обычно Tk-приложения, содержащие платформенно-ориентированные фрагменты кода, проверяют содержимое элемента глобального массива tcl_platform(platform), в котором может содержаться значение macintosh, unix или windows. Однако с появлением интерфейсов Apple OS X и Aqua ситуация несколько усложнилась. Mac OS X помещает в элемент tcl_platform(platform) значение unix. Однако при этом оконная система не соответствует X Window. Для получения более точных сведений приходится использовать операцию tk windowingsystem. Операция caret, появившаяся в Tk 8.4, позволяет запрашивать и устанавливать позицию текстового курсора. Операция useinputmethods изменяет поведение Tk в системе X Window с учетом X Input Methods (XIM). В версиях, предшествующих Tk 8.3, распознавание и использование XIM осуществлялось автоматически. Начиная с Tk 8.3 эти средства распознаются и инициализируются, но для их использования надо установить специальное разрешение с помощью операции useinputmethods. tk useinputmethods 1 Операции, реализуемые с помощью команды tk, описаны в табл. 44.12. Таблица 44.12. Операции, реализуемые с помощью команды tk tk appname ?имя? Запрашивает или устанавливает имя приложения, используемое Tk-командой send tk caret окно ?-х х? Запрашивает или устанавливает позицию тексто- ?-у у? ?-height высота? вого курсора для указанного окна Tk. Значения х и у представляют координаты относительно окна. Данные возвращаются в виде пар опция-значение (Tk 8.4)
914 Часть V. Особенности работы Тк Окончание табл. 44.12 tk scaling ?-displayof окно? ?число? tk useinputmethods ?-displayof окно? ?логическое_значение ? tk windowingsystem Запрашивает или устанавливает коэффициент масштабирования, используемый в Тк для преобразования экранных единиц измерения в пиксели. При вызове данной команды может быть указано число с плавающей точкой, определяющее количество пикселей на пункт, для дисплея, на котором отображается окно. Если окно не указано, по умолчанию принимается главное окно. Если число не указано, возвращается текущее значение коэффициента масштабирования Запрашивает или устанавливает состояние, определяющее, будет ли Тк использовать XIM (X Input Methods) для фильтрации событий. В результате выполнения операции возвращается вновь установленное состояние. Если ХШ не поддерживается, операция возвращает значение 0. Если окно не указано, по умолчанию принимается главное окно. Если логическое значение не задано, возвращается текущее состояние (Тк 8.3) Возвращает информацию о текущей оконой системе: xll (система на базе XII), Win32 (MS Windows), classic (Mac OS Classic) или aqua (Mac OS X Aqua) (Tk 8.4)
Глава 45 Поддержка пользовательских установок В данной главе описывается пакет поддержки пользовательских установок. Необходимые параметры хранятся в базе данных ресурсов. Записи базы данных используются для инициализации Tcl-переменных. Пользовательский интерфейс позволяет просматривать и изменять установки. СРЕДСТВА настройки являются неотъемлемой частью любого сложного приложения. Обычно в программе по умолчанию применяется набор предопределенных значений, но по необходимости пользователь может изменить их. В данной главе рассматривается пакет поддержки пользовательских установок, который связывается с Tcl-переменными в составе приложения и с описаниями ресурсов, задаваемыми пользователем. Данный пакет реализует пользовательский интерфейс, который избавляет от необходимости непосредственно редактировать базу данных. Файлы, используемые приложением по умолчанию Предположим, что для работы приложения достаточно двух источников параметров, используемых по умолчанию. Этими источниками являются база данных, ориентированная на приложение, и база данных, ориентированная на пользователя. Кроме того, будем считать, что для цветного и монохромного дисплея применяются отдельные ресурсы. Ниже приведен пример выражения, с помощью которого осуществляется инициализация пакета поль-
916 Часть V. Особенности работы Тк зовательских установок. Для инициализации производится чтение ресурсов, ориентированных на приложение и на пользователя. В процессе инициализации устанавливаются также значения глобального массива pref. Они содержат информацию о состоянии пакета пользовательских установок. Вызов процедуры Pref _Init осуществляется следующим образом: Pref_Init $library/foo-defaults ~/.foo-defaults В данном случае $library — это каталог, содержащий файлы для приложения f оо. Значения, применяемые по умолчанию при работе конкретного пользователя, хранятся в файле ~/.foo-defaults. Приведенные здесь имена файлов сформированы в соответствии с соглашениями Unix. При написании Tk-приложений, предназначенных для выполнения на различных платформах, невозможно избежать использования имен, специфических для конкретной операционной системы. Несмотря на наличие платформенно-независи- мых операций (см. главу 9), данные, определяющие установки пользователя, все же должны находиться в файле с:/webtk/userpref .txt (Windows), либо Hard Disk:System:Preferences:WebTk Prefs (Macintosh), либо V.webtk (Unix). Поэтому в приложение целесообразно включить фрагменты платфор- менно-ориентированного кода, в которых определялись бы требуемые имена файлов. Пакет поддержки пользовательских установок использует файлы ресурсов, которые действуют на всех платформах. Листинг 45.1. Инициализация пользовательских установок proc Pref_Init { userDefaults appDefaults } { global pref set pref(uid) 0;# Уникальный идентификатор для компонентов set pref(userDefaults) $userDefaults set pref(appDefaults) $appDefaults PrefReadFile $appDefaults startup if [file exists $userDefaults] { PrefReadFile $userDefaults user } } proc PrefReadFile { basename level } { if [catch {option readfile $basename $level} err] { Status "Error in $basename: $err" } if {[string match *color* [winfo visual .]]} { if [file exists $basename-color] { if [catch {option readfile \
Глава 45. Поддержка пользовательских установок 917 $basename-color $level} err] { Status "Error in $basename-color: $erru } } } else { if [file exists $basename-mono] { if [catch {option readfile $basename-mono \ $level} err] { Status "Error in $basename-mono: $err" } } } _} Процедура PrefReadFile читает файл ресурсов, а затем ищет другой файл, имя которого, в зависимости от типа терминала, содержит суффикс -color или -mono. В соответствии с данной схемой при работе в системе Unix пользователь размещает общие установки в файле ~/.foo-defaults. Соответственно спецификация, определяющая использование цветов, помещается в файл ~/.foo-def aults-color, а спецификация для черно-белого дисплея — в файл ~/.foo-defaults-mono. По мере необходимости вы можете дополнить процедуру PrefReadFile, чтобы она поддерживала файлы, ориентированные на конкретную рабочую станцию. В данной главе мы будем предполагать, что процедура Status отображает сообщения для пользователя. Эта процедура может быть очень проста; пример ее реализации приведен ниже. proc Status { s } { puts stderr $s } Определение пользовательских установок В данном разделе описывается процедура Pref _Add, которая применяется для определения пользовательских установок. Каждая установка представляет собой соответствие между Tcl-переменной и именем ресурса. Если во время выполнения Pref_Add Tcl-переменная отсутствует, она определяется и ей присваивается значение соответствующего ресурса. Если ресурс не задан, то вновь определяемой переменной присваивается значение по умолчанию. При разработке приложений рекомендуется скрывать структуры данных за Тс1-процедурами. С каждой записью пользовательских установок связаны значение по умолчанию, метка и строка подсказки. Они представлены Тс1-спис- ком, состоящим из пяти элементов. Структуру списков скрывают
918 Часть V. Особенности работы Тк короткие процедуры; благодаря им остальной код становится более удобочитаемым. Листинг 45.2. Добавление записей пользовательских установок proc PrefVar { item } { lindex $item 0 } proc PrefRes { item } { lindex $item 1 } proc PrefDefault { item } { lindex $item 2 } proc PrefComment { item } { lindex $item 3 } proc PrefHelp { item } { lindex $item 4 } proc Pref_Add { prefs } { global pref append pref(items) $prefs " " foreach item $prefs { set varName [PrefVar $item] set resName [PrefRes $item] set value [PrefValue $varName $resName] if {$value == {}} { # Установка переменных, значения которым еще не присвоены set default [PrefDefault $item] switch -regexp -- $default { ^CHOICE { PrefValueSet $varName [lindex $default 1] } ~QFF { PrefValueSet $varName 0 } ~0N { PrefValueSet $varName 1 } default { # Строка или числовое значение PrefValueSet $varName $default } } } } 2 Процедуры PrefValue и PrefValueSet предоставляют содержимое именованных переменных и позволяют устанавливать их значения. В качестве
Глава 45. Поддержка пользовательских установок 919 переменных могут также использоваться элементы массивов. Команда upvar #0 устанавливает переменную в глобальной области видимости. Листинг 45.3. Установка значений переменных # PrefValue возвращает значение существующей переменной, # если она отсутствует, извлекается значение из базы данных # ресурсов. proc PrefValue { varName res } { upvar #0 $varName var if [info exists var] { return $var } set var [option get . $res {}] } # PrefValueSet определяет переменную в глобальной # области видимости. proc PrefValueSet { varName value } { upvar #0 $varName var set var $value J Используя Pref _Add, важно помнить, что переменные, соответствующие пользовательским установкам, определяются в глобальной области видимости. Необходимо также заметить, что PrefValue учитывает имеющиеся значения переменной. Если переменная уже существует в глобальной области видимости, то ни значение ресурса, ни значение по умолчанию не используются. Если подобное поведение процедуры PrefValue вас не устраивает, ее код можно изменить с тем, чтобы значение переменной записывалось в любом случае. В листинге 45.4 показан пример обращения к Pref _Add. Листинг 45.4. Использование пакета пользовательских установок Pref_Add { {win(scrollside) scrollbarSide {CHOICE left right} "Scrollbar placement" "Scrollbars can be positioned on either the left or right side of the text and canvas widgets."} {win(typeinkills) typeinKills OFF "Type-in kills selection" "This setting determines whether or not the selection is deleted when new text is typed in."} {win(scrollspeed) scrollSpeed 15 "Scrolling speed"
920 Часть V. Особенности работы Тк "This parameter affects the scrolling rate when a selection is dragged off the edge of the window. Smaller numbers scroll faster, but can consume more CPU."} J При вызове Pref .Add можно указать любое количество записей пользовательских установок. "Список списков" создается путем соответствующей расстановки фигурных скобок; структура сохраняется при добавлении параметра к основному списку установок pref (items). В данном примере процедуре Pref_Add передается единственный параметр, который представляет собой Tcl-список, состоящий из трех элементов. Переменными Tcl являются элементы массива, имеющие отношение к модулю Win приложения. Имена ресурсов связываются с главным приложением. Они определены в базе данных так, как показано ниже. *scrollbarSide: left *typeinKills: 0 *scrollSpeed: 15 Интерфейс пользовательских установок На приведенном ниже рисунке показан интерфейс, позволяющий добавлять записи пользовательских установок, применяя для этого рассмотренную ранее процедуру Pref _Add. Всплывающее окно с дополнительным текстом подсказки отображается после щелчка на элементе Scrollbar placement. В результате вызова процедуры Pref .Add формируется список. Процедура, реализующая пользовательский интерфейс, перебирает в цикле элементы списка и для каждого из них создает запись пользовательских установок. Листинг 45.5. Интерфейс для создания пользовательских установок proc PrefJDialog {} { global pref if [catch {toplevel .pref}] { raise .pref } else {
Глава 45. Поддержка пользовательских установок 921 wm title .pref "Preferences" set buttons [frame .pref.but -bd 5] pack .pref.but -side top -fill x button $buttons.quit -text Dismiss \ -command {PrefDismiss} button $buttons.save -text Save \ -command {PrefSave} button $buttons.reset -text Reset \ -command {PrefReset ; PrefDismiss} label $buttons.label \ -text "Click labels for info on each item" pack $buttons.label -side left -fill x pack $buttons.quit $buttons.save $buttons.reset \ -side right -padx 4 frame .pref.b -borderwidth 2 -relief raised pack .pref.b -fill both set body [frame .pref.b.b -bd 10] pack .pref.b.b -fill both set maxWidth 0 foreach item $pref(items) { set len [string length [PrefComment $item]] if {$len > $maxWidth} { set maxWidth $len } } set pref(uid) 0 foreach item $pref(items) { PrefDialogltem $body $item $maxWidth } } } Данный интерфейс поддерживает три типа пользовательских установок: логическое значение, выбор из нескольких вариантов и общее значение. Логическое значение задается с помощью флажка опций, связанного с переменной Tcl. В зависимости от состояния флажка переменной присваивается значение 0 или 1. По умолчанию логическое значение может быть принято равным либо ON, либо OFF. Возможность выбора реализуется набором кнопок с зависимой фиксацией: каждая кнопка соответствует одному варианту
922 Часть V. Особенности работы Тк выбора. Значение по умолчанию представляет собой список с первым элементом CHOICE. Для проверки вместо операций над списками используется команда regexp. Причина такого решения состоит в том, что Tcl 8.0 требует, чтобы значение представляло собой корректный список, а это условие в некоторых случаях может не выполняться. Для указания общего значения записи пользовательских установок применяется поле редактирования. Листинг 45.6. Объекты для поддержки различных типов пользовательских установок proc PrefDialogltem { frame item width } { global pref incr pref(uid) set f [frame $frame.p$pref(uid) -borderwidth 2] pack $f -fill x label $f.label -text [PrefComment $item] -width $width bind $f.label <1> \ [list PrefltemHelp °/0X °/0Y [PrefHelp $item]] pack $f.label -side left set default [PrefDefault $item] if {[regexp '^CHOICE " $default]} { foreach choice [lreplace $default 0 0] { incr pref(uid) radiobutton $f.c$pref(uid) -text $choice \ -variable [PrefVar $item] -value $choice pack $f.c$pref(uid) -side left } } else { if «default == "OFF" I I $default == "ON"} { # Логическое значение set varName [PrefVar $item] checkbutton $f.check -variable $varName \ -command [list PrefFixupBoolean $f.check $varName] PrefFixupBoolean $f.check $varName pack $f.check -side left } else { # Строка или числовое значение entry $f.entry -width 10 -relief sunken pack $f.entry -side left -fill x -expand true set pref(entry,[PrefVar $item]) $f.entry set varName [PrefVar $item] $f.entry insert 0 [uplevel #0 [list set $varName]]
Глава 45. Поддержка пользовательских установок 923 bind $f.entry <Return> "PrefEntrySet %W $varName" } } } proc PrefFixupBoolean {check varname} { upvar #0 $varname var # При каждом измерении флажка опции обновляется текст if {$var} { $check config -text On } else { $check config -text Off } } proc PrefEntrySet { entry varName } { PrefValueSet $varName [$entry get] 2 Если пользователь щелкает на флажке или переключателе опции, соответствующая Tcl-переменная устанавливается немедленно. Для того чтобы добиться того же эффекта для общего значения, надо связать клавишу <Return> с процедурой, которая присваивала бы Tcl-переменной значение, введенное в поле редактирования. Для этой цели используется процедура PrefEntrySet. Использование двойных кавычек обеспечивает подстановку $ varName, но при этом необходимо отменить специальное действие квадратных скобок, чтобы запретить немедленную подстановку команды. bind $f. entry <Return> "PrefValueSet $varName \[e/.W get\]" Вместо опции -textvariable используется связывание для клавиши <Return>; при таком подходе упрощается взаимодействие со средствами отслеживания состояния переменной. При использовании команды trace необходимо предусмотреть выполнение Tcl-команды при изменении значения переменной (листинг 45.10). При работе с записями пользовательских установок, предполагающих указание общего значения, желательно дождаться, пока новое значение будет полностью введено, а лишь затем реагировать на данное событие. В рассматриваемом интерфейсе предусмотрено также отображение дополнительной справочной информации. Если число записей пользовательских установок велико, непосредственно отобразить справочные данные невозможно. Вместо этого после щелчка на кратком описании пункта отобразится новое окно верхнего уровня, содержащее подсказку для конкретной записи. Окно верхнего уровня помечено как transient, поэтому диспетчер окон не создает для него рамку и другие элементы внешнего оформления.
924 Часть V. Особенности работы Тк Листинг 45.7. Отображение текста подсказки proc PrefltemHelp { х у text } { catch {destroy .prefitemhelp} if {$text == {}} { return } set self [toplevel .prefitemhelp -class Itemhelp] wm title $self "Item help" wm geometry $self + [expr $x+10]+[expr $y+10] wm transient $self .pref message $self.msg -text $text -aspect 1500 pack $self.msg bind $self.msg <1> {PrefNukeltemHelp .prefitemhelp} .pref.but.label configure -text \ "Click on pop-up or another label" } proc PrefNukeltemHelp { t > { .pref.but.label configure -text \ "Click labels for info on each item" destroy $t 2 Управление файлом пользовательских установок Пользовательские установки сохраняются в файлах. Каждый файл ориентирован на конкретного пользователя. Содержимое файла можно условно разделить на две части; последняя часть автоматически переписывается пакетом поддержки установок. Если пользователь вручную добавит данные в начале файла, эти данные будут сохранены. Листинг 45.8. Сохранение пользовательских установок в файле # PrefSave записывает спецификации ресурсов в конец # файла ресурсов, ориентированного на пользователя, proc PrefSave {} { global pref if [catch { set old [open $pref(userDefaults) r] set oldValues [split [read $old] \n]
Глава 45. Поддержка пользовательских установок 925 close $old }] { set oldValues {} } if [catch {open $pref(userDefaults).new w} out] { .pref.but.label configure -text \ "Cannot save in $pref(userDefaults).new: $out" return } foreach line SoldValues { if {$line == \ "!!! Lines below here automatically added"} { break } else { puts $out $line } } puts $out "!!! Lines below here automatically added" puts $out "!!! [exec date]" puts $out "!!! Do not edit below here" foreach item $preferences { set varName [PrefVar $item] set resName [PrefRes $item] if [info exists pref(entry,$varName)] { PrefEntrySet $pref(entry,$varName) $varName } set value [PrefValue $varName $resName] puts $out [format "e/es\te/es" *${resName}: $value] } close $out set new [glob $pref(userDefaults).new] set old [file root $new] if [catch {file rename -force $new $old} err] { Status "Cannot install $new: $err" return } PrefDismiss } Помимо прочего, в состав интерфейса входят также процедуры Pref Reset и PrefDismiss. Сброс осуществляется путем очистки опций и повторной за-
926 Часть V. Особенности работы Тк грузки базы данных, после чего отменяются пользовательские установки, удаляются значения соответствующих им переменные, а затем вызывается процедура Pref .Add. Листинг 45.9. Чтение данных из файла пользовательских установок proc PrefReset {} { global pref # Повторное чтение пользовательских установок по умолчанию option clear PrefReadFile $pref(appDefaults) startup PrefReadFile $pref(userDefaults) user # Очистка переменных set items $pref(items) set pref(items) {} foreach item $items { uplevel #0 [list unset [PrefVar $item]] } # Восстановление значений Pref_Add $items } proc PrefDismiss {} { destroy .pref catch {destroy .prefitemhelp} J Отслеживание изменений в переменных пользовательских установок Предположим, что пользователь имеет возможность изменять значение scrollside, задавая тем самым отображение полос прокрутки слева или справа. При этом необходимо изменить размещение полосы прокрутки на экране. Сделать это можно, отслеживая изменение значения переменной win (scrollside). Когда пользователь внесет с помощью интерфейса соответствующие изменения, вызывается заданная процедура. В составе кода, представленного в листинге 45.10, содержится команда trace, с которой связывается процедура. Перед использованием команды trace переменная, предназначенная для отслеживания, должна быть объявлена глобальной.
Глава 45. Поддержка пользовательских установок 927 Листинг 45.10. Отслеживание изменения значения Tcl-переменной при изменении пользовательской установки Pref_Add { {win(scrollside) scrollbarSide {CHOICE left right} "Scrollbar placement" "Scrollbars can be positioned on either the left or right side of the text and canvas widgets."} } global win set win(lastscrollside) $win(scrollside) trace variable win(scrollside) w ScrollFixup # Предполагается, что win(scrollbar) идентифицирует # полосу прокрутки proc ScrollFixup { namel name2 op } { global win if {$win(scrollside) != $win(lastscrollside)} { set parent [lindex [pack info $win(scrollbar)] 1] pack forget $win(scrollbar) set firstchild [lindex [pack slaves $parent] 0] pack $win(scrollbar) -in $parent -before $firstchild \ -side $win(scrollside) -fill у set win(lastscrollside) $win(scrollside) } } Доработка пакета Процедуру Pref _Add можно модернизировать. Если пользователь задает логическое значение ресурса вручную, то вместо нуля или единицы он может указывать true или false. Для того чтобы исключить ошибки при интерпретации значений переменных в составе выражений, процедура Pref _Add должна устанавливать нулевое или единичное значение переменной. В некоторых случаях желательно отказаться от выполненных установок и не сохранять их. Такое решение пользователь обычно принимает в случае, если он собирался лишь проверить, как действует тот или иной параметр, либо если в процессе настройки приложения он допустил ошибку. Для того чтобы получить возможность вернуться к прежним установкам, надо предусмотреть дополнительный набор переменных, используемый параллельно с основным. Значения в этих переменных должны храниться до тех пор, пока пользователь не сохранит новые установки. По необходимости
928 Часть V. Особенности работы Тк вы можете использовать средства захвата (см. главу 39) и запретить пользователю выполнять действия, не относящиеся к настройке. Описанный в данной главе пакет поддержки пользовательских установок представляет собой упрощенный набор средств, использованных при разработке приложения exmh. Поскольку в этом приложении число установок слишком велико, при его создании пришлось применить двухуровневую схему. Первый уровень меню позволяет выбрать раздел; для каждого раздела предусматривается отдельный вызов Pref _Add. При обращении к Pref „Add используются дополнительные параметры, с помощью которых задается имя раздела и дополнительная информация о нем. Процедуры, обеспечивающие отображение данных, лишь незначительно отличаются от описанных в данной главе. Код приложения exmh содержится на компакт-диске, прилагаемом к данной книге.
Глава 46 Интерфейс для определения связываний В данной главе рассматривается пользовательский интерфейс, позволяющий просматривать имеющиеся связывания и редактировать их. /-\пя того чтобы составить представление о поведении компонента, надо проанализировать связывания, определенные для него. В данной главе рассматривается пользовательский интерфейс, который позволят просматривать связывания, определенные для компонента или для класса компонентов, и вносить в них требуемые изменения. В состав данного интерфейса входят два окна списка, в которых отображается информация о событиях и командах, связанных с ними. Поле редактирования позволяет вводить имя компонента или класса компонентов. Набор кнопок дает возможность пользователю добавлять новое связывание, редактировать существующее, сохранять связывания в файле и отменять выполненные ранее действия. Интерфейс реализован в виде диалогового окна, показанного ниже. Листинг 46.1. Пользовательский интерфейс для работы со связываниями proc BincLInterface { w } { # Состояние, global bind set bind(class) $w # Установка класса, используемого для описания ресурсов, set frame [toplevel .bindui -class Bindui]
930 Часть V. Особенности работы Тк # Рельеф по умолчанию. option add *Bindui*Entry.relief sunken startup option add *Bindui*Listbox.relief raised startup # Размеры окон списков, принятые по умолчанию option add *Bindui*key.width 18 startup option add *Bindui*cmd.width 25 startup option add *Bindui*Listbox.height 5 startup # Поле редактирования в верхней части окна, снабженное # меткой, предназначено для указания имени компонента # или класса. set t [frame $frame.top -bd 2] label $t.l -text "Bindings for" -width 11 entry $t.e -textvariable bind(class) pack $t.l -side left pack $t.e -side left -fill x -expand true pack $t -side top -fill x bind $t.e <Return> [list Bind__Display $frame] # Командные кнопки button $t.quit -text Dismiss \ -command [list destroy $frame] button $t.save -text Save \ -command [list Bind_Save $frame] button $t.edit -text Edit \ -command [list Bind_Edit $frame] button $t.new -text New \ -command [list Bind_New $frame] pack $t.quit $t.save $t.edit $t.new -side right # Два окна списка и полоса прокрутки scrollbar $frame.s -orient vertical \ -command [list BindYview \ [list $frame.key $frame.cmd]] listbox $frame.key \ -yscrollcommand [list $frame.s set] \ -exportselection false listbox $frame.cmd \ -yscrollcommand [list $frame.s set] pack $frame.s -side left -fill у pack $frame.key $frame.cmd -side left \
Глава 46. Интерфейс для определения связываний 931 -fill both -expand true foreach 1 [list $frame.key $frame.cmd] { bind $1 <B2-Motion>\ [list BindDragto °/0x °/ey $frame.key $frame.cmd] bind $1 <Button-2> \ [list BindMark °/0x °/0y $frame.key $frame.cmd] bind $1 <Button-l> \ [list BindSelect °/0y $frame.key $frame.cmd] bind $1 <Bl-Motion> \ [list BindSelect °/0y $frame.key $frame.cmd] bind $1 <Shift-Bl-Motion> {> bind $1 <Shift-Button-l> {} } # Инициализация дисплея. Bind.Display $frame } При вызове процедуре Bind_Interf асе в качестве параметра передается имя компонента или команда. Данная процедура создает окно верхнего уровня и задает для него класс Bindui так, чтобы атрибутами компонента можно было управлять с помощью ресурсов. Команда option add применяется для установки размеров окон списка по умолчанию. Данным ресурсам присваивается самый низкий приоритет, startup, поэтому при использовании пакета можно переопределять размеры, указывая ресурсы. В верхней части окна располагается поле редактирования, снабженное текстовой меткой. В этом поле содержится имя класса или компонента, к которому относятся связывания, отображаемые в окне. При создании поля редактирования используется опция -textvariable, в результате чего содержимое поля становится доступным посредством переменной bind(class). Если при работе с полем редактирования пользователь нажмет клавишу <Return>, будет вызвана процедура BindJDisplay, которая заполнит окно данными.
932 Часть V. Особенности работы Тк Листинг 46.2. Процедура Bind_Display отображает информацию о связываниях для компонента или класса proc BindJDisplay { frame } { global bind $frame.key delete 0 end $frame.cmd delete 0 end foreach seq [bind $bind(class)] { $frame.key insert end $seq $frame.cmd insert end [bind $bind(class) $seq] } 2 Процедура Bind_Display отображает данные о связываниях. Команда bind возвращает информацию о событиях, для которых определены связывания, и о командах, соответствующих каждому событию. Эта информация обрабатывается в цикле и отображается в окнах списков. Совместная работа окон списков В составе интерфейса присутствуют два окна списка, $frame.key и $frame.cmd, которые совместно решают одну задачу представления связываний. Выделение пункта в одном из окон списков приводит к выделению соответствующего пункта в другом окне. Одно из окон списков экспортирует выделение как PRIMARY. Если это не происходит, окно, в котором пункт был выделен последним, получает права на работу с выделением. В примере, приведенном в листинге 46.3, показаны фрагмент кода Bind_Interf асе, содержащий команды bind, а также процедура BindSelect, которая выбирает пункт в обоих окнах списков. Листинг 46.3. Выделение осуществляется в двух окнах списков foreach 1 [list $frame.key $frame.cmd] { bind $1 <Button-l> \ [list BindSelect */«y $frame.key $frame.cmd] bind $1 <Bl-Motion> \ [list BindSelect e/ey $frame.key $frame.cmd] } proc BindSelect { у args } { foreach w $args { $w select clear 0 end $w select anchor [$w nearest $y] $w select set anchor [$w nearest $y]
Глава 46. Интерфейс для определения связываний 933 } } С обоими окнами списков связана одна полоса прокрутки. Двум компонентам listbox соответствует одна полоса прокрутки. Код, приведенный в листинге 46.4, демонстрирует команду scrollbar из процедуры Bind_Interf асе, а также процедуру BindYview, которая осуществляет прокрутку содержимого окон списков. Листинг 46.4. Управление двумя окнами списков с помощью одной полосы прокрутки scrollbar $frame.s -orient vertical \ -command [list BindYview [list $frame.key $frame.cmd]] proc BindYview { lists args } { foreach 1 $lists { eval {$1 yview} $args } 2 Процедура BindYview изменяет представление окон списков, связанных с полосой прокрутки. В качестве первого параметра процедуре BindYview передается список компонентов, для которых необходимо выполнить прокрутку. Остальные параметры задает полоса прокрутки, указывая, как должно отображаться содержимое компонентов. Детали взаимодействия полосы прокрутки и окон списков см. в главе 33. Ключевое слово args представляет дополнительные параметры, а команда eval используется для их обработки в составе процедуры. Об особенностях подобного использования eval см. в главе 10. Для класса Listbox определены связывания <Button-2> и <B2-Motion>, благодаря наличию которых содержимое окна списка прокручивается в случае, когда курсор перетаскивается при нажатой средней кнопке мыши. Эти связывания реализованы так, что содержимое обоих окон списка перемещается синхронно. Код, приведенный в листинге 46.5, содержит команды bind, входящие в состав Bind_Interf асе, а также процедуры BindMark и BindDrag, которые осуществляют прокрутку содержимого окон списков. В процедуре BindMark выполняется команда scan mark, которая определяет исходную точку, а в процедуре BindDragto содержится команда scan dragto, которая прокручивает содержимое компонентов в зависимости от расстояния от исходной точки. Каждый компонент Тк, допускающий прокрутку, поддерживает yview, scan mark и scan dragto. Поэтому BindYview, BindMark и BindDragto можно рассматривать как универсальные процедуры,
934 Часть V. Особенности работы Тк пригодные для использования с любым набором компонентов, содержимое которых должно прокручиваться одновременно. Листинг 46.5. Прокрутка содержимого окон списка с помощью перетаскивания курсора мыши bind $1 <B2-Motion>\ [list BindDragto °/0x °/0y $frame.key $frame.cmd] bind $1 <Button-2> \ [list BindMark °/0x °/0y $frame.key $frame.cmd] proс BindDragto { x у args } { foreach w $args { $w scan dragto $x $y } } proc BindMark { x у args } { foreach w $args { $w scan mark $x $y } } Средства редактирования связываний Для создания новых связываний и редактирования существующих используются два поля редактирования. Эти компоненты динамически создаются и размещаются в окне после щелчка на кнопке New или Edit. Листинг 46.6. Средства определения новых связываний proc Bind_New { frame } { if [catch {frame $frame.edit} f] { # Фрейм уже создан set f $frame.edit } else { foreach x {key cmd} { set ±2 [frame $f.$x] pack $f2 -fill x -padx 2 label $f2.1 -width 11 -anchor e pack $f2.1 -side left entry $f2.e pack $f2.e -side left -fill x -expand true bind $f2.e <Return> [list BindDefine $f]
Глава 46. Интерфейс для определения связываний 935 } $f.key.l config -text Event: $f.cmd.l config -text Command: } pack $frame.edit -after $frame.top -fill x } proc Bind_Edit { frame } { Bind_New $frame set line [$frame.key curselection] if {$line == {}} { return } $frame.edit.key.e delete 0 end $frame.edit.key.e insert 0 [$frame.key get $line] $frame.edit.cmd.e delete 0 end $frame.edit.cmd.e insert 0 [$frame.cmd get $line] } Сохранение и загрузка связываний Чтобы завершить создание средств для поддержки связываний, надо обеспечить реальное определение связи между событием и командой, а также сохранение созданного связывания до следующего запуска приложения. Процедура BindDef ine предпринимает попытку выполнить команду bind, передавай ей данные, отображаемые в окнах списка. Если команда bind выполняется успешно, отображение окна редактирования прекращается. Сохранение связываний осуществляет процедура Bind_Save. Связывания хранятся как набор определяющих их Tcl-команд. Для корректного формирования команд используется команда list.
936 Часть V. Особенности работы Тк Для чтения сохраненных команд в процедуре BincLRead используется команда source. Для реализации связываний для компонентов и их классов процедура Bind_Read должна быть вызвана при инициализации приложения. В приложении также должна быть предусмотрена возможность вызова процедуры Bind_Interf асе. Эта процедура может быть связана с кнопкой, пунктом меню или клавишей. Листинг 46.7. Определение и сохранение связываний proc BindDefine { f } { if [catch { bind [$f.top.e get] [$f.edit.key.e get] \ [$f.edit.cmd.e get] } err] { Status $err } else { # Удаление окна редактирования pack forget $f.edit } } proc Bind_Save { dotfile args } { set out [open $dotfile.new w] foreach w $args { foreach seq [bind $w] { # Вывод Tcl-команды puts $out [list bind $w $seq [bind $w $seq]] } } close $out file rename -force $dotfile.new $dotfile } proc Bind_Read { dotfile } { if [catch { if [file exists $dotfile] { # Чтение сохраненных Tcl-команд source $dotfile } } err] { Status "Bindjlead $dotfile failed: $err" } }
ЧАСТЬ VI Программирование на языке С В части VI описываются вопросы создания программ на языке С, предназначенных для работы совместно с Tcl-приложениями. При написании глав, входящих в часть VI, авторы старались лишь указать читателю основные направления, по которым следует двигаться для решения стоящих перед ним задач. Для того чтобы разработать серьезную программу, необходима подробная информация о С API. В главе 47 представлены общие сведения об использовании Tcl на уровне С-программ. В ней рассматриваются вопросы интеграции средств Tcl и Тк и создания Tcl-расширений, динамически загружаемых в tclsh или wish. В главе 48 описана Tcl-среда, позволяющая компилировать исходные тексты Tcl/Tk и подготавливать новые расширения. В главе 49 представлен пример Тк-компонента, реализованного на языке С и предназначенного для отображения времени. Заканчивается данная часть главой 50, в которой приведен обзор основных возможностей, предоставляемых библиотеками С для Tcl иТк.
Глава 47 С-программы и язык Tcl В данной главе рассказывается о том, как расширить Тс1-прило- жение, создавая новые встроенные команды. В Tcl 8.0 строковый командный интерфейс был заменен более эффективным двойным объектным интерфейсом. Оба они рассматриваются в данной главе. Ч^УРЕДСТВА Tcl реализованы в виде библиотеки С, что упрощает процесс интеграции с существующими программами. Добавляя к приложению Tcl-интерпретатор, вы можете управлять им с помощью Tcl-сценариев, а средства Тк позволяют создавать графический пользовательский интерфейс. Так выглядела первоначальная модель Tcl. Прикладные программы в основном состояли из кода, написанного на языке С, и включали небольшой объем Tcl-кода, предназначенного для настройки приложения и создания графического интерфейса. Несмотря на это, первые оболочки Tcl стали настолько удобны, что лишь немногие программисты, использующие Tcl, вынуждены были заботиться о создании программного кода на С или C++. Средства Tcl разработаны так, что их возможности можно легко расширять путем написания новых команд на языке С. Команда, реализованная на С, работает гораздо эффективнее, чем Tcl-процедура, выполняющая те же действия. Еще одним аргументом в пользу создания С-кода является тот факт, что некоторые функции попросту невозможно реализовать средствами Tcl. Предположим, что в ваше распоряжение предоставлено новое устройство, например цветной сканер. Интерфейс данного устройства представляет собой набор С-процедур, предназначенных для инициализации и управления. Не приложив определенных усилий по созданию интерфейса, вы не сможете обеспечить доступ Tcl-сценариев к устройству. Подобные проблемы возникают при наличии библиотеки, написанной на языке С или C++, которая
Глава 47. С-программы и язык Tcl 939 реализует специальные функции, например обеспечивает управление базой данных. К счастью, написать интерфейс Tcl, соответствующий интерфейсу С или C++, относительно несложно. В данной главе термином С обозначается не только язык С, но также и C++. Кроме того, существует пакет под названием TclBlend, который позволяет расширять Tcl путем написания Java-кода, а также вызывать Тс1-сце- нарии из Java-программ. Дополнительную информацию о TclBlend можно найти по следующему адресу: http://www.tcl.tk/j ava/ Основные понятия Для того чтобы воспринимать материал данной главы, необходимо иметь хотя бы общее представление о языке С или C++. Чтобы использовать Tcl API, не обязательно быть программистом высокого класса. Одно из преимуществ Tcl состоит в том, что данный язык можно без труда расширять путем написания С-кода. В этой главе рассматривается несколько примеров, которые демонстрируют инициализацию приложения и создание Тс1-команд. Здесь также обсуждаются вопросы оформления кода в пакеты. В завершение приводятся некоторые сведения о компиляции Tcl в средах Unix, Windows и Macintosh. Основные подходы к написанию С-кода для Tcl-приложения Приступая к написанию С-кода для Tcl-приложения, можно выбрать один из следующих двух подходов. Проще всего написать расширение, реализовав с его помощью новые команды для стандартной Tcl-оболочки (tclsh или wish). В этом случае базовым набором средств является сама оболочка Tcl, а С-код лишь расширяет ее возможности. Tcl поддерживает динамическую загрузку, поэтому вы можете оформить созданное вами расширение как разделяемую библиотеку и загружать ее в работающую оболочку. Такой подход реализовать проще всего, так как все необходимые действия, связанные с запуском и завершением работы, выполняет сама оболочка; она же является интерактивной консолью для ввода Tcl-команд. При использовании wish вы также получаете базовые средства для создания графического пользовательского интерфейса. Кроме того, с загружаемым расширением могут одновременно работать несколько пользователей. Вторым способом применения Tcl-библиотеки является связывание ее с существующим приложением. Если ваше приложение достаточно просто, его
940 Часть VI. Программирование на языке С можно оформить как расширение стандартной Tcl-оболочки, т.е. вернуться к более простому способу, рассмотренному выше. Если же для вашего приложения уже создана достаточно сложная среда (например, если оно использует постоянно работающий сервер), вы можете лишь экспортировать функциональные возможности программы в виде одной или нескольких Тс1-команд. Поступив таким образом, вы увидите, что в ваших силах расширить приложение любыми средствами, предоставляемыми Tcl. Командные процедуры С и объекты данных Код, написанный на языке С или C++ и реализующий Tcl-команду, называется командной процедурой. Интерфейс командной процедуры напоминает интерфейс основной программы. В качестве входных данных задается массив, содержимое которого соответствует параметрам Tcl-сценария. Результат выполнения командной процедуры является результатом выполнения Тс1-ко- манды. Существуют два типа командных процедур: один из них ориентирован на работу со строками, а другой — на работу с объектами. В данном случае слово объект уместно поместить в кавычки, так как мы обсуждаем представления параметров и возвращаемых значений. Мы не обсуждаем методы, наследование и другие понятия, относящиеся к объектно-ориентированному программированию. Однако в Tcl С API применяется структура Tcl_0bj, называемая двойным объектом (dual ported object). Здесь для обозначения подобного объекта будет использоваться термин "значение Tcl_0bj". Строковый интерфейс достаточно прост. Командная процедура получает в качестве параметров массив строк и вычисляет результирующую строку. В Tcl 8.0 средства для работы со строками обобщены в тип Tcl_0bj, для которого предусмотрены два представления: в виде строки и в виде целого числа, чисел с плавающей точкой, списка или байтовых кодов. Команда, базирующаяся на объектах, получает в качестве параметров массив указателей на Tcl_0bj; в виде Tcl_0bj представляется и вычисленный результат. Тип Tcl__0bj предназначен для того, чтобы уменьшить число преобразований из строкового в другие типы представлений. Команды, базирующиеся на объектах, более эффективны, чем эквивалентные им строковые команды, однако прикладной программный интерфейс становится несколько сложнее. При обучении, а также при решении простых задач вполне оправдано использование более простого строкового интерфейса. SWIG Инструмент SWIG (Simple Wrapper Interface Generator), созданный Деви- дом Бисли (David Beasley), предназначен для генерации С-кода, реализующе-
Глава 47. С-программы и язык Tcl 941 го командные процедуры. Эти процедуры представляют API С или С+-г как Tcl-команды. Данный подход существенно экономит время разработки, если необходимо реализовать большое количество Tcl-вызовов. Единственный недостаток интерфейса С состоит в том, что разработчик сценариев может испытывать неудобства при работе с ним. Tcl-интерфейсы, разработанные вручную, намного удобнее, а интерфейсы, сгенерированные автоматически, позволяют быстро создавать среду для тестирования программ. Дополнительную информацию о SWIG можно получить на Web-узле по следующему адресу: http://www.swig.org/ Инициализация Tcl Для того чтобы командные процедуры можно было использовать в Tcl- сценариях, их надо зарегистрировать в среде Tcl. В некоторых случаях необходимо также создать интерпретатор Tcl, однако эту задачу выполняют стандартные Тс1-оболочки. Если вы создаете расширение, вам необходимо предусмотреть инициали- зационную процедуру. Данная процедура регистрирует Tcl-команды, вызывая Tcl_CreateCommaiid или Tcl_CreateObj Command. Пример процедуры регистрации приведен в листинге 47.1. Если вы собираетесь оформлять расширение как разделяемую библиотеку, то имя данной процедуры должно заканчиваться последовательностью символов _Init, например Expect_Init, Blt_Init или Foo_Init. Инициализационная процедура автоматически вызывается тогда, когда Tcl-сценарий загружает библиотеку с помощью команды load. Если вы встраиваете Tcl-код в существующее приложение, вам надо инициализировать Tcl с помощью Tcl_FindExecutable и Tcl.CreaTclnterp. Первая процедура помогает инициализировать среду выполнения Tcl и определяет значение, возвращаемое info nameofexecutable. Tcl_CreateInterp создает интерпретатор, который реализует стандартные команды, перечисленные в табл. 1.4. Вам также необходимо инициализировать созданные вами команды (например, с помощью вызова Foo_Init) и подготовить сценарий к выполнению посредством Tcl.Eval или Tcl_EvalFile. Для того чтобы программа выполнялась корректно, необходимо учесть много деталей, поэтому Tcl предоставляет интерфейс более высокого уровня, реализуемый с помощью процедур Tcl_Main и Tcl_AppInit. Процедура Tcl_Main создает интерпретатор, обрабатывает параметры командной строки, запускает инициализацион- ный сценарий и реализует цикл обработки интерактивных команд. Процедура Tcl_AppInit, предоставляемая разработчиком, завершает инициализацию интерпретатора. Пример использования Tcl_Main показан в листинге 47.13. Помимо описанных выше действий, необходимо подготовить оконную систе-
942 Часть VI. Программирование на языке С му и цикл обработки событий. Процедура Tk_Main скрывает эти действия от разработчика. В ней предусмотрен вызов процедуры Tk.AppInit, которая завершает инициализацию. Вызов Tcl-сценариев Приложение хможет обращаться к уровню сценариев на любом этапе своего выполнения, даже из командных процедур. Базовый API, используемый для этой цели, реализует процедуры Tcl_Eval. Обратившись к справочной информации, вы найдете исчерпывающее описание семейства процедур Tcl.Eval. Вы также можете читать содержимое Tcl-переменных из С-программ и присваивать им новые значения. Для этой цели используются процедуры Tcl_SetVar и Tcl_GetVar. Существует несколько разновидностей этих процедур. Выбор конкретной процедуры зависит от типа данных, в качестве которых могут использоваться строки, значения Tcl_0bj, скалярные переменные или массивы. Процедура Tcl_LinkVar формирует Тс1-переменную, связанную с некоторой переменной С. Изменение Tcl-переменной приводит к изменению состояния С-переменной, а при чтении Tcl-переменной возвращается значение переменной С. Процедура Tcl_LinkVar построена на основе универсальных средств отслеживания значений переменных. Эти средства представлены в Tcl в виде команды trace, кроме того, они доступны через С API как Tcl_TraceVar. Расширение, созданное на высоком профессиональном уровне, должно предоставлять как С, так и Tcl API, но большинство основных команд Tcl и Тк не экспортирует С API. По этой причине, чтобы получить в свое распоряжение возможности данных команд, приходится обрабатывать сценарии специальным образом, например с помощью команды eval. В листинге 47.15 будет показана процедура Tcl__Invoke, которая помогает обойти это ограничение. Tcl_Invoke используется для вызова Tcl-команд, при этом накладные расходы, связанные с разбором и подстановкой, типичные для Tcl_Eval, не возникают. Использование библиотеки Tcl С С момента своего возникновения библиотека Tcl С развилась от простого интерпретатора языка в полнофункциональную библиотеку. Важным свойством Tcl API является поддержка нескольких платформ; она одинаково хорошо работает в системах Unix, Windows и Macintosh. Многие считают, что кросс-платформенное приложение проще написать на Tcl, чем на Java! Ниже перечислен ряд возможностей, которые могут стать своеобразным "сюр-
Глава 47. С-программы и язык Tcl 943 призом" для разработчика, так как он не вправе ожидать их от обычного интерпретатора языка. • Универсальный пакет поддержки хэш-таблицы. При включении дополнительных данных таблица автоматически расширяется. При работе с такой таблицей можно использовать различные типы ключей, включая строки и целые числа. • Пакет, поддерживающий динамические строки (DString). При работе с этим пакетом эффективность формирования строк повышается. • Пакет поддержки каналов ввода-вывода, который заменяет собой использовавшуюся ранее стандартную библиотеку ввода-вывода. Новый пакет работает на нескольких платформах, обеспечивает буферизацию и неблокирующий ввод-вывод, а также осуществляет преобразование наборов символов. С помощью данного пакета можно создавать новые типы каналов ввода-вывода. • Сетевые гнезда для ТСР/1Р-взаимодействия. • Средства преобразования наборов символов с поддержкой Unicode, UTF-8 и других кодировок. • Диспетчер цикла обработки событий, который обеспечивает интерфейс с сетевыми соединениями и событиями оконной системы. По мере необходимости вы можете создавать новые "источники событий", работающие с диспетчером цикла. • Средства поддержки многопотоковых программ, в частности мютексы, переменные условий и локальные хранилища для потоков. • Система регистрации, обеспечивающая завершение вызванных ранее обработчиков по окончании работы Tcl. В данной главе внимание уделяется в основном функциям Tcl С API, имеющим отношение к работе интерпретатора Tcl. В главе 50 приведен обзор всех процедур библиотек Tcl С и Тк С, но несмотря на это, данную книгу нельзя рассматривать как исчерпывающее руководство по данному вопросу. Всю необходимую информацию вы можете найти в интерактивной справочной системе. Справочная информация входит в состав любого дистрибутивного пакета Tcl. Она находится на компакт-диске, прилагаемом к данной книге, кроме того, вы можете получить необходимые сведения, обратившись по следующему адресу: http://www.tcl.tk/man/ Исходный код Tcl заслуживает того, чтобы потратить время на ознакомление с ним. Необходимо заметить, что исходный код библиотеки Tcl С может стать важным источником информациидля разработчика. Код снаб-
944 Часть VI. Программирование на языке С жен подробными комментариями. Если вы хотите выяснить, как в действительности работает та или иная функция, просмотрите исходный код, чтобы получить необходимые сведения. Создание загружаемых пакетов С-код можно организовать в виде загружаемого пакета, динамически присоединяемого к tclsh, wish и другим Tcl-приложениям. Вопросы компиляции кода и оформления его в виде разделяемой библиотеки рассматриваются в главе 48. В данном разделе рассматривается пакет, реализующий Tcl-команду random, которая возвращает псевдослучайные значения. Команда load Tcl-команда load используется для установления динамической связи со скомпилированным пакетом. load библиотека пакет ?интерпретатор? Первым параметром команды является имя файла разделяемой библиотеки (DLL-файла), после которого указывается имя пакета, реализованного посредством данной библиотеки. Имя пакета связано с именем процедуры najcer_Init, используемой для его инициализации (например, Random_Init). Необязательный третий параметр позволяет загружать библиотеку посредством ведомого интерпретатора. Например, при работе с библиотекой /usr/ local/lib/random.so Tcl-сценарий загружает пакет следующим образом: load /usr/local/lib/random.so Random В большинстве версий Unix в переменную окружения LD_LIBRARY_PATH можно поместить список каталогов, содержащих разделяемые библиотеки. Каталоги в списке разделяются двоеточиями. Поступив подобным образом, вы сможете использовать для идентификации библиотек относительные имена. load librandom.so Random В системе Macintosh команда load ищет библиотеки в той же папке, в которой расположено приложение Tcl/Tk (т.е. Wish), а также в папке System: Extensions:Tool Command Language. load random.shlib Random В системе Windows команда load ищет библиотеки в том каталоге, в котором располагается приложение Tcl/Tk, в текущем каталоге, в каталоге C:\Windows\System (в Windows NT вместо этого каталога используется
Глава 47. С-программы и язык Tcl 945 C:\Windows\System32), в C:\Windows, а затем в каталогах, перечисленных в переменной окружения PATH. load random.dll Random К счастью, в большинстве случаев разработчику не приходится принимать во внимание указанные особенности, так как средства поддержки пакетов Tcl берут на себя заботу об управлении библиотеками. Вместо непосредственного вызова команды load в приложении следует использовать операцию package require. Средства поддержки пакетов автоматически определяют местонахождение библиотек и вызывают команду load с учетом особенностей вашей платформы. Подробно о работе с пакетами см. в главе 12. Процедура инициализации пакета После загрузки пакета Tcl вызывает С процедуру naicer^Init (т.е. первой частью названия процедуры является имя пакета). Пример процедуры Random_Init приведен в листинге 47.1. При выполнении Random_Init регистрируется командная процедура RandomCmd, которая реализует новую Tcl- команду random. Когда Tcl-сценарий обращается к команде random, Tcl-ин- терпретатор выполняет процедуру RandomCmd. В листинге 47.1 для сравнения приведены два стиля регистрации: команда Tcl_CreateCommand, используемая в ранних версиях Tcl, и команда Tcl_CreateObjCommand, добавленная в Tcl 8.O. Командные процедуры рассматриваются в последующих разделах. Листинг 47.1. Инициализационная процедура загружаемого пакета /* * random.с */ #include <tcl.h> /* * Описания командных процедур, специфических для приложения. */ int RandomCmd(ClientData clientData, Tcl_Interp *interp, int argc, CONST char *argv[]); int RandomObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_0bj *C0NST objv[]); /*
946 Часть VI. Программирование на языке С * Random_Init вызывается при загрузке пакета. */ int Random_Init(Tcl_Interp *interp) { /* * Инициализация интерфейса, который будет описан * в главе 48. */ if (Tcl_InitStubs(interp, "8.1", 0) == NULL) { return TCL.ERROR; } /* * Регистрация двух вариантов random. * Команда orandom использует объектный интерфейс. */ Tcl_CreateCommand(interp, "random", RandomCmd, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL); Tcl_CreateObjCommand(interp, "orandom", RandomObjCmd, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL); /* * Объявление о том, что пакет random реализован. * В результате сценарии, вызывающие package require random, * получают возможность автоматически загружать библиотеку. */ Tcl_PkgProvide(interp, "random", "1.1"); return TCL_0K; } Использование Tcl_PkgProvide Для того чтобы объявить возможности пакета, Random_Init использует процедуру Tcl_PkgProvide. В результате этого вызова процедура pkg_mklndex получает информацию о пакетах, предоставляемых библиотеками. Процедура pkg_mklndex сохраняет эту информацию в базе данных пакетов, которая находится в файле pkglndex.tcl. При выполнении команды package require осуществляется поиск в файлах базы данных пакетов, указанных в auto_path, и требуемый пакет автоматически загружается. Для работы с пакетами выполняются следующие действия.
Глава 47. С-программы и язык Tcl 947 • Создание разделяемой библиотеки и размещение ее в каталоге, указанном в переменной auto_path, либо в одном из подкаталогов этого каталога. • Запуск процедуры pkg_mklndex в этом каталоге. При вызове процедуры ей передаются имена всех файлов сценариев и разделяемых библиотек, предназначенных для индексации. С этого момента разделяемая библиотека готова к использованию другими сценариями. • Запрос пакета сценарием с использованием команды package require. При первом обращении к команде из пакета будет корректно сформирована и вызвана команда load. Команда package используется одинаково на всех платформах. package require random => 1.1 Подробную информацию по этому вопросу см. в главе 12. Командная процедура С В Tcl 8.0 был реализован новый интерфейс для работы с Тс1-командами. Основной целью его разработки было повышение эффективности за счет использования встроенного компилятора байтового кода. Существующий интерфейс был ориентирован на работу со строками. Вследствие этого осуществлялись многократные преобразования строк во внутренний формат (целые числа, числа с плавающей точкой двойной точности и списки). Новый интерфейс был создан на базе типа Tcl_0bj, что позволяет хранить значения различного типа. Появилась возможность откладывать реальные преобразования до того момента, когда они становились действительно необходимы. В результате экономии ресурсов на преобразовании эффективность работы сценариев возросла. В данном разделе мы рассмотрим создание команды, возвращающей псевдослучайное число, с использованием обоих интерфейсов. Интерфейс, ориентированный на работу со строками, проще для понимания, поэтому он лучше подходит для иллюстрации основных понятий. Этот интерфейс можно использовать для первых экспериментов с командными процедурами. Приобретя некоторый опыт, вы можете переходить к интерфейсам, использующим вместо обычных строк значения Tcl_0bj. Если у вас уже есть командные процедуры для версий, предшествующих Tcl 8.0, модифицировать их целесообразно только в том случае, если эффективность работы не устраивает вас. Строковый интерфейс и интерфейс на базе Tcl_0bj очень похожи, поэтому изменить командные процедуры будет не очень сложно.
948 Часть VI. Программирование на языке С Строковый командный интерфейс Интерфейс командной процедуры С, основанный на использовании строк, напоминает интерфейс программы main. Командная процедура регистрируется следующим образом: Tcl_CreateCommand(interp, "cmd", CmdProc, data, DeleteProc); Когда сценарий вызывает cmd, Tcl обращается к CmdProc следующим образом: CmdProc(data, interp, argc, argv); В данном случае параметр interp имеет тип Tcl_Interp * и представляет собой дескриптор состояния интерпретатора. Этот параметр передается большинству функций Tcl С API. Параметр data имеет тип Client-Data и является указателем, тип которого нуждается в уточнении. Данный параметр используется для связывания состояния и команды, что часто используется при работе с компонентами Тк. Создаваемая нами простая процедура RandomCmd не использует эту возможность, поэтому мы передаем Tcl.CreateCommand значение NULL. Процедура DeleteProc вызывается при удалении команды; обычно это происходит при удалении всего интерпретатора Tcl. Данное средство также не используется RandomCmd. Параметры Tcl-команды доступны как массив строк, определенных посредством параметра argv. Параметр argc представляет собой счетчик. Аналогичный интерфейс использует основная программа для работы с параметрами командной строки. В листинге 47.2 показана командная процедура RandomCmd. Листинг 47.2. Командная процедура С RandomCmd /* * RandomCmd -- * Реализует Tcl-команду random. При вызове без параметров * данная команда возвращает псевдослучайное целое число. * Если в качестве параметра указано целое значение, * возвращает псевдослучайное число в интервале от О * до этого значения. */ int RandomCmd(ClientData clientData, Tcl_Interp *interp, int argc, CONST char *argv[]) { int rand, error; int range = 0;
Глава 47. С-программы и язык Tcl 949 char buffer [20]; if (argc > 2) { interp->result = "Usage: random ?range?"; return TCL_ERR0R; } if (argc == 2) { if (Tcl_GetInt(interp, argv[l], fcrange) != TCLJDK) { return TCL_ERR0R; } } rand = random(); if (range != 0) { rand = rand °/0 range; } sprintf(buf, »7.d", rand); Tcl_SetResult(interp, buf, TCL_V0LATILE); return TCL_0K; _} Значение, возвращаемое в результате выполнения Tcl-команды, представляет собой результирующую строку и код состояния. Результирующая строка - это либо значение, полученное в результате выполнения команды, либо сообщение об ошибке. Например, если командной процедуре будут переданы лишние параметры, то процедура сгенерирует ошибку следующим образом: Tcl_SetResult(interp, "Usage: random ?range?", TCL.STATIC); return TCL.ERROR; Реализация команды random предусматривает необязательный параметр, который указывает диапазон, в котором будет сгенерировано псевдослучайное значение. Узнать, был ли данный параметр передан при вызове Тс1-ко- манды, можно, анализируя параметр argc. Наряду с параметрами в argc учитывается имя команды, поэтому в нашем случае argc == 2 означает, что вызов команды имел приблизительно такой вид: random 25 Процедура Tcl_GetInt преобразует строку в целое число. Она проверяет на наличие ошибок и в случае ошибки выполняет все необходимые установки, поэтому нам остается только вернуть соответствующее значение. if (Tcl_GetInt(interp, argv[l], fcrange) != TCL_0K) { return TCL.ERROR; }
950 Часть VI. Программирование на языке С На этом действия по вызову random окончены. Во временном буфере результаты представляются в виде строки и устанавливаются с помощью Tcl_SetResult. Возврат из процедуры при отсутствии ошибок выглядит следующим образом: sprintf(buffer, '7od", rand); Tcl_SetResult(interp, buffer, TCL_V0LATILE); return TCL.OK; Коды завершения командных процедур Командная процедура возвращает код состояния. Обычно это TCL_0K или TCL_ERR0R, указывающие на нормальное завершение или на наличие ошибки. Если командная процедура возвращает TCL_ERR0R, генерируется ошибка и результирующее значение используется как сообщение об ошибке. Процедура также может возвращать коды TCL.BREAK, TCL.CONTINUE и TCL_RETURN, которые воздействуют на поведение таких управляющих структур, как f oreach и proc. По мере необходимости можно даже возвращать код, специфический для конкретного приложения (например, 5 или выше), который будет полезен в том случае, если вы реализуете новые типы управляющих структур. Код состояния командной процедуры представляет собой значение, возвращаемое семейством Tcl_Eval С API (этот вопрос будет обсуждаться в конце данной главы) либо командой catch (см. главу 6). Управление результирующими строками Для управления хранением строк, формируемых в результате выполнения командных процедур, разработан простой протокол. Для хранения значения используется поле interp->result, a interp->freeProc определяет особенности очистки хранимого значения. При вызове команды интерпретатор инициализирует interp->result как статический буфер размером TCL_RESULT_SIZE, т.е. 200 байтов. По умолчанию для очистки не предпринимаются никакие действия. В ранних версиях Tcl при непосредственном обращении к interp->result не возникало проблем. С появлением интерфейсов Tcl_0bj, которые будут описаны ниже, подобное обращение не всегда безопасно. Для управления полями result и f reeProc можно использовать приведенные ниже процедуры. Они осуществляют автоматическое управление хранением результатов. Tcl_SetResult(интерпретатор , строка , процедура_очистки) Tcl_AppendResult(интерпретатор , строка_1 , строка_2, строка_3, (char *)NULL) Tcl_AppendElement(интерпретатор , строка)
Глава 47. С-программы и язык Tcl 951 Tcl_SetResult устанавливает заданную строку в качестве возвращаемого значения. Параметр, представляющий процедуру очистки, описывает размещение результатов. TCL_STATIC используется в том случае, когда результат представляет собой строку-константу, за расположение которой отвечает компилятор. TCL_DYNAMIC применяется, если результат размещается посредством Тс1_А11ос — версии malloc, независимой от платформы и компилятора. TCL_VOLATILE означает, что результат содержится в переменной, которая находится в стеке. В случае TCL_VOLATILE Tcl-интерпретатор перед вызовом другой командной процедуры создает копию результата. Если вы используете собственные средства выделения памяти, необходимо передать адрес процедуры, которая выполнит очистку результатов. Tcl_AppendResult копирует параметры в буфер, предназначенный для хранения результатов. По необходимости память для буфера выделяется повторно. При наличии результирующего значения параметры присоединяются к нему посредством операции конкатенации. Для формирования результата Tcl_AppendResult можно вызвать несколько раз. Поскольку память для буфера может выделяться повторно, многократные добавления значений осуществляются достаточно эффективно. Tcl_AppendElement добавляет строку к результирующему значению в виде элемента Tcl-списка. Для получения требуемой структуры могут быть добавлены фигурные скобки или символы обратной косой черты. Tcl_ResetResult вызывается перед каждой командной процедурой. Однако если вы сформировали результат и собираетесь отказаться от него (это может, например, потребоваться при возникновении ошибки), то можете использовать TclJResetResult для восстановления исходного состояния. Командный интерфейс Tcl_Obj Командный интерфейс Tcl.Obj заменяет строки так называемыми двойными значениями (dual-ported value). Параметры команды представляют собой массив указателей на структуры Tcl_0bj; в виде такой же структуры представляются и результаты выполнения команды. Замена строк значениями Tcl_0bj широко применяется в Tcl. В виде объектов Tcl_0bj хранятся как значения Tcl-переменных, так и Tcl-сценарии. При желании вы можете продолжать пользоваться API на базе строк, однако связанное с этим преобразование в значения Tcl_0bj потребует большого объема ресурсов. Структура Tcl_0bj хранит как строковое, так и платформенно-ориенти- рованное представление. Платформенно-ориентированное представление зависит от типа значения. Списки Tcl хранятся как массивы указателей на строки. Для хранения целых чисел выделяется 32 бита. Значения с плавающей точкой представляются как числа двойной точности. Tcl-сценарии хранятся
952 Часть VI. Программирование на языке С в виде байтового кода. Преобразование строкового значения в платформенно- ориентированное и наоборот осуществляется по запросу. Существуют специальные API, предназначенные для организации доступа к значениям Tcl_0bj, поэтому вас не должны беспокоить проблемы преобразования типов. Заняться решением подобных вопросов вам придется лишь в том случае, если вы реализуете новый тип. В листинге 47.3 показана командная процедура random, использующая интерфейс Tcl_0bj. Листинг 47.3. Командная процедура С RandomObjCmd /* * RandomObjCmd -- * Реализует Tcl-команду random, используя * объектный интерфейс. */ int RandomObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_0bj *CONST objv[]) { Tcl_0bj *resultPtr; int rand, error; int range = 0; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?range?"); return TCL_ERR0R; } if (objc == 2) { if (Tcl_GetIntFromObj(interp, objv[l], ferange) != TCL_0K) { return TCL_ERR0R; > } rand = random(); if (range != 0) { rand = rand e/0 range; } resultPtr = Tcl_GetObjResult(interp); Tcl_SetIntObj(resultPtr, rand); return TCL_0K; } .
Глава 47. С-программы и язык Tcl 953 Сравните код, приведенный в листингах 47.2 и 47.3. Вы видите, что две версии командной процедуры С практически совпадают. Вызов Tcl_GetInt заменен вызовом Tcl_GetIntFromObj. Посредством параметра функции передается целочисленное значение. Такой вызов исключает преобразование строки, когда значение Tcl_0bj и так является целым числом. Формирование результата сводится к получению дескриптора результирующего объекта и установке его значения. Такой подход используется вместо непосредственного обращения к полю interp->result. resultPtr = Tcl_GetObjResult(interp); Tcl_SetIntObj(resultPtr, rand); Процедура Tcl_WrongNumArgs форматирует сообщение об ошибке. Ей передаются objv, число параметров и дополнительная строка. Ниже приведен пример сообщения, сформированного этой процедурой. wrong # args: should be "random ?range?" В листинге 47.3 отсутствуют очевидные действия по управлению хранением данных. Tcl инициализирует результирующий объект перед обращением к командной процедуре и принимает меры для его очистки. В данном случае достаточно установить значение и вернуть TCL_0K или TCL_ERR0R. В более сложных ситуациях приходится заботиться о подсчете ссылок на значения Tcl_0bj. Этот вопрос будет обсуждаться несколько позже. Если командная процедура возвращает строку, вам надо использовать Tcl.SetStringObj. Данная команда создает копию передаваемой ей строки. Новые интерфейсы Tcl, помимо строки, получают также сведения о ее длине, поэтому вы можете передавать в составе строк двоичные данные. Если длина равна -1, это означает, что строка оканчивается нулевым байтом. Ниже приведена команда, которая всегда возвращает значение "boring". resultPtr = Tcl_GetObjResult(interp); Tcl.SetStringObj(resultPtr, uboringu, -1); На практике возникает необходимость формировать результат по частям. При работе со строковым API следует использовать Tcl_AppendResult. Применяя API на базе Tcl_0bj, надо получить указатель на результат и вызвать Tcl_AppendToObj или Tcl.AppendStringsToObj. resultPtr = Tcl_GetObjResult(interp); Tcl_AppendStringsToObj(resultPtr, "hello ", username, NULL); Управление счетчиком ссылок Tcl_Obj При передаче параметров и результатов строковые интерфейсы осуществляют копирование строк, в то время как интерфейсы на базе Tcl_0bj действуют со счетчиками ссылок. Ссылки создаются для Tcl-переменных, для
954 Часть VI. Программирование на языке С результатов, возвращаемых интерпретатором, и для разделяемых значений, которые передаются Tcl-процедурам. Константы также относятся к разделяемым значениям. Объекту Tcl.Obj, на который указывает objv, соответствует как минимум одна ссылка. Нередки также случаи наличия двух и нескольких ссылок. В листинге 47.4 показано определение Tcl_0bj средствами С. Для доступа к объекту предусмотрены функции API, поэтому вам не следует непосредственно обращаться к Tcl_0bj. Исключение составляют те случаи, когда вы реализуете новый тип. Листинг 47.4. Структура Tcl.Obj typedef struct Tcl_0bj { int refCount; /* Счетчик разделяемых ссылок */ char *bytes; /* Представление строки */ int length; /* Число байтов в строке */ Tcl_ObjType *typePtr; /* Реализация типа */ union { long longValue; double doubleValue; VOID *otherValuePtr; struct { VOID *ptrl; VOID *ptr2; } twoPtrValue; } internalRep; } Tcl.Obj; В реализации каждого типа предусмотрены процедуры, подобные представленным ниже. Tcl_GetTypeFromObj(интерпретатор, objPtr, valuePtr); Tcl_SetTypeObj(resultPtr, значение); objPtr = Tcl_NewTypeObj(значение); Начальное значение счетчика ссылок равно нулю. Tcl_NewTypeObj выделяет память для хранения Tcl_0bj и устанавливает счетчик ссылок равным нулю. Tcl_IncrRefCount и Tcl_DecrRefCount вызывают соответственно увеличение и уменьшение на единицу счетчика ссылок на объект. Когда счетчик ссылок становится равным нулю, Tcl_DecrRefCount освобождает память, выделенную для хранения Tcl__0bj. Начальное значение счетчика ссылок выбрано равным нулю потому, что такие функции, как
Глава 47. С-программы и язык Tcl 955 Tcl_SetObjResult, автоматически инкрементируют счетчик ссылок на объект. Процедуры Tcl_GetTypeFromObj и Tcl_SetTypeObj лишь получают и устанавливают значение; счетчик ссылок остается неизменным. Преобразование типов выполняется автоматически. Вы можете задать в качестве значения Tcl_0bj целое число, а затем получить строку или число с двойной точностью. В реализации типа предусмотрены средства, которые автоматически принимают меры для хранения изменяемых значений Tcl_0bj. Если тип Tcl_0bj остается неизменным, преобразование в строку не требуется и доступ к объекту осуществляется более эффективно. Модификация значений Tcl Obj Модификация разделяемых объектов Tcl.Obj может привести к нежелательным последствиям. Совместный доступ используется только для повышения эффективности работы программ; логически каждая ссылка считается копией. Эту модель следует учитывать, создавая и модифицируя значения Tcl_0bj. Tcl_IsShared возвращает значение 1, если существует больше одной ссылки на объект. Если командная процедура модифицирует разделяемый объект, она должна создавать специальную копию, используя Tcl_DuplicateObj. Первоначально для новой копии устанавливается значение счетчика ссылок, равное нулю. После этого вы можете либо использовать Tcl_SetResultObj, в результате чего будет добавлена ссылка, либо явным образом добавить ссылку к копии, применив Tcl_IncrRefCount. В листинге 47.5 представлена реализация команды plusl, которая до( являет единицу к параметру. Если параметр не является разделяемым, можно организовать эффективно работу plusl, модифицируя представление целого числа, специфическое для конкретной платформы. В противном случае перед модификацией объекта необходимо создать его копию. Листинг 47.5. Процедура Plus 10bjCmd /* * Plus10bjCmd -- * Добавление единицы к входному параметру. */ int PluslObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_0bj *C0NST objv[]) { Tcl_0bj *objPtr;
956 Часть VI. Программирование на языке С int i; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "value11); return TCL_ERR0R; } objPtr = objv[l]; if (Tcl_GetIntFromObj(interp, objPtr, &i) != TCL.OK) { return TCL__ERR0R; } if (Tcl_IsShared(objPtr)) { objPtr = Tcl_DuplicateObj(objPtr); /* Счетчик ссылок 0 */ Tcl_IncrRefCount(objPtr); /* Счетчик ссылок 1*/ } /* * Считаем, что значение счетчика ссылок для objPtr * равно 1. Если значение не разделяемое, его можно * изменить. Tcl_SetIntObj задает новое значение. */ Tcl_SetIntObj(objPtr, i+1); /* * Установка результатов добавляет новую ссылку. * Уменьшение на единицу производится потому, что мы * больше не должны заботиться о модифицированном * целочисленном значении. */ Tcl_SetObjResult(interp, objPtr); /* Счетчик ссылок 2 */ Tcl_DecrRefCount(objPtr); /* Счетчик ссылок 1 */ /* * теперь результату соответствует ссылка на objPtr. */ return TCL_0K; } Проблемы, связанные с использованием разделяемых значений Tcl_Obj Используя значения из структуры Tcl_0bj, следует соблюдать осторожность. Библиотека Tcl С содержит много процедур, например Tcl.GetStringFromObj, Tcl_GetIntFromObj, Tcl.GetListFromObj и т.д. Они действуют эффективно, возвращая указатель на представление объекта, специфическое для конкретной платформы. По мере необходимости происходит
Глава 47. С-программы и язык Tcl 957 преобразование объекта в требуемый тип. Проблема состоит в том, что преобразованию типов подвергаются разделяемые значения, в результате чего ссылка на значение конкретного типа становится некорректной. Ссылка на значение остается гарантированно правильной лишь до следующего вызова Tcl_Get*FromObj. Рассмотрим командную процедуру, которая получает два параметра: целое число и список. В процедуре содержится приведенный ниже фрагмент кода. Tcl_ListObjGeTclements(iiiterp, objv[l] , feobjc, felistPtr); /* Выполнение действий со списком. */ Tcl_GetIntFromObj(interp, objv[2], feint); /* В данный момент список может быть некорректным. */ Если обстоятельства сложатся так, что оба параметра будут иметь одно и то же значение (например, 1 и 1), что возможно для целочисленной переменной и Tcl-списка, то Tcl автоматически примет меры для обеспечения совместного доступа к значениям обоих параметров. Указатели obj v [1] и obj v [2] будут совпадать, а счетчик ссылок для Tcl_0bj будет равен как минимум 2. После первого вызова Tcl_ListObjGeTclements значением будет список, поэтому функция вернет непосредственный указатель на список, представленный средствами текущей платформы. Однако впоследствии Tcl_GetIntFromObj преобразует значение Tcl_0bj в целое число. При этом память, выделенная для списочного представления, будет освобождена, и listPtr станет "повисшим" указателем. В данном конкретном случае разрешить проблему можно, изменив порядок вызовов. Это возможно, поскольку Tcl.GetlntFromObj копирует целочисленное значение. Tcl_GetIntFromObj(interp, objv[2], feint); Tcl_ListObjGeTclements(interp, objv[l], feobjc, felistPtr); /* Целое число представляет собой копию значения. */ Таким образом, если формат значения несовместим с требуемым типом, необходимо всегда проверять вызовы Tcl_Get*. Если объект не является списком, при выполнении следующей команды возникнет ошибка: if (Tcl_ListObjGeTclements(interp, obj [1], feobjc, felistPtr) != TCL.OK) { return TCL_ERR0R; }
958 Часть VI. Программирование на языке С Команда blob В данном разделе некоторые стандартные подходы к созданию кода будут продемонстрированы на достаточно большом примере. Несмотря на объем, пример во многом остается искусственным, так как действия, выполняемые им, имеют ограниченное применение. Однако он все же демонстрирует некоторые подходы, которыми вы должны владеть, создавая Тс1-команды. Команда blob создает специальные объекты и выполняет с ними различные действия. Каждый объект blob имеет имя и несколько свойств. Для идентификации объектов по именам в реализации команды blob предусмотрена хэш-таблица. Хэш-таблицу можно рассматривать как пример информации о состоянии. По завершении работы интерпретатора она должна быть очищена. В Tcl реализация хэш-таблицы является достаточно универсальной, поэтому ее можно применять в различных ситуациях. С объектом blob связывается Tcl-сценарий. При записи объекта сценарий активизируется. В листинге 47.6 показаны структуры данных, используемые при реализации объектов blob. Листинг 47.6. Структуры данных Blob и BlobState /* * Структура Blob создается для каждого объекта blob. */ typedef struct Blob { int N; /* Целочисленное свойство */ Tcl.Obj *objPtr; /* Свойство общего назначения */ Tcl_0bj *cmdPtr; /* Сценарий обратного вызова */ } Blob; /* * Структура BlobState создается единожды для каждого * интерпретатора. */ typedef struct BlobState { Tcl_HashTable hash; /* Список объектов blob по именам */ int uid; /* Используется для генерации имен */ } BlobState; Создание и удаление хэш-таблиц В листинге 47.7 показаны процедуры Blob_Init и BlobCleanup. Процедура Blob_Init формирует команду и инициализирует хэш-таблицу. Она реги-
Глава 47. С-программы и язык Tcl 959 стрирует процедуру удаления BlobCleanup, с помощью которой производится очистка хэш-таблицы. Процедура Blob_Init инициализирует хэш-таблицу как часть структуры BlobState. Эта структура передается Tcl_CreateObjCommand как ClientData; впоследствии BlobCmd снова получает ее. На первый взгляд может показаться удобным использовать одну статическую хэш-таблицу, вместо того, чтобы формировать ее динамически. Однако в рамках процесса может выполняться несколько интерпретаторов Tcl и каждому из них может потребоваться своя хэш-таблица для хранения объектов blob. При инициализации хэш-таблицы задаются ключи. В данном случае ключом является имя объекта blob, поэтому используется TCL_STRING_KEYS. При работе с целочисленным ключом или адресом структуры данных надо использовать TCL_ONE_WORD_KEYS. Ключом может также быть массив целых чисел. В этом случае необходимо передавать целое число, большее 1, представляющее размер целочисленного массива, используемого в качестве ключа. Команда BlobCleanup очищает хэш-таблицу. Она перебирает в цикле все элементы таблицы и получает значения, связанные с каждым ключом. Это значение преобразуется в указатель на структуру данных Blob. Каждая запись таблицы удаляется посредством процедуры BlobDelete. Если хэш- таблица не была модифицирована, то вместо повторения вызовов Tcl.FirstHashEntry можно продолжать поиск с помощью Tcl_NextHashEntry. Листинг 47.7. Процедуры Blob_Init и BlobCleanup /* * Прямые ссылки. */ int BlobCmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_0bj *C0NST objv[]); int BlobCreate(Tcl_Interp *interp, BlobState *statePtr); void BlobCleanup(ClientData data); /* * Blob_Init -- * * Инициализация модуля. * * Побочные эффекты: * Выделяется память для хэш-таблицы, используемой для * отслеживания действий с объектами blob. Создается
960 Часть VI. Программирование на языке С * команда blob. */ int Blob_Init(Tcl_Interp *interp) { BlobState *statePtr; /* * Выделение памяти для хэш-таблицы и инициализация таблицы. * BlobState связывается с командой; при этом используется * ClientData. */ statePtr = (BlobState *)ckalloc(sizeof(BlobState)); Tcl_InitHashTable(&statePtr->hash, TCL_STRING_KEYS); statePtr->uid = 0; Tcl.CreateObjCommand(interp, "blob", BlobCmd, (ClientData)statePtr, BlobCleanup); return TCL_0K; } /* * BlobCleanup -- * Вызывается при удалении команды blob. * * Побочные эффекты: * Просмотр хэш-таблицы и удаление содержащихся в ней * объектов. Удаление таблицы. */ void BlobCleanup(ClientData data) { BlobState *statePtr = (BlobState *)data; Blob *blobPtr; Tcl_HashEntry *entryPtr; Tcl_HashSearch search; entryPtr = Tcl_FirstHashEntry(&statePtr->hash, fesearch); while (entryPtr != NULL) { blobPtr = Tcl_GetHashValue(entryPtr); BlobDelete(blobPtr, entryPtr); /*
Глава 47. С-программы и язык Tcl 961 * Поскольку хэш-таблица была модифицирована, надо * получить ссылку на первый пункт. */ entryPtr = Tcl_FirstHashEntry(&statePtr->hash, fesearch); } ckfree((char *)statePtr); } Tcl_Alloc, ckalloc и malloc Для работы с памятью Tcl предоставляет разработчику процедуры Тс1_А11ос и Tcl_Free, предназначенные для замены реализаций malloc, которые в некоторых системах далеки от совершенства. В Tcl 8.4 добавлены новые средства выделения памяти, которые поддерживают многопотоковые приложения. Если при компиляции указана опция -DTCL_MEM_DEBUG, включается поддержка режима отладки. Tcl-команда memory предоставляет сведения об использовании памяти и может помочь при решении многих проблем. Для того чтобы обеспечить отладку операций с памятью, в tcl.h определены макросы ckalloc и ckf гее, которые, в зависимости от опций, заданных при компиляции, вызывают различные процедуры выделения памяти. По этой причине, составляя программы, следует избегать непосредственного вызова malloc и free; не следует также вызывать Тс1_А11ос и Tcl_Free. Вместо этого надо использовать макровызовы ckalloc и ckfree. Заметьте, что выделение памяти с помощью Тс1_А11ос или ckalloc и освобождение ее посредством free или наоборот, использование malloc для выделения памяти и Tcl_Free или ckfree для ее освобождения может стать причиной возникновения проблем. Аналогично, если при компиляции части кода вы укажете опцию -DTCL__MEM_DEBUG, а другую часть кода скомпилируете без этой опции, при выполнении программы может возникнуть ошибка. Обработка параметров и использование Tcl_GetlndexFromObj В листинге 47.8 показан код процедуры BlobCmd. Он иллюстрирует разбор параметров, переданных команде. Процедура Tcl_GetIndexFromObj используется для отображения первого параметра (например, "names") в индекс (например, Nameslx). При этом осуществляется проверка корректности и, если первый параметр не удовлетворяет правилам, формируется сообщение об ошибке. Во всех подчиненных командах, за исключением create и names, второй параметр интерпретируется как имя объекта blob. Tcl_FindHashEntry используется для поиска этого имени в хэш-таблице, и соответствующая
962 Часть VI. Программирование на языке С структура Blob выбирается с помощью Tcl_GetHashValue. После завершения проверки параметра BlobCmd вызывает вспомогательную процедуру, которая осуществляет реальные действия по выполнению команды. Листинг 47.8. Командная процедура BlobCmd /* * BlobCmd -- * Реализует команду blob. * * Результаты: * Результаты стандартной Тс1-команды. */ int BlobCmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_0bj *C0NST objv[]) { BlobState *statePtr = (BlobState *)data; Blob *blobPtr; Tcl_HashEntry *entryPtr; Tcl_0bj *valueObjPtr; /* * Массив subCmds определяет допустимые значения первого * параметра. Они отображаются в значения, соответствующие * нумерации Bloblx, посредством Tcl_GetIndexFromObj. */ char *subCmds[] = { "create", "command", "data", "delete", "N", "names", "poke", NULL }; enum Bloblx { CreaTclx, Commandlx, Datalx, DeleTclx, Nix, Nameslx, Pokelx >; int result, index; if (objc ==1|| objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERR0R;
Глава 47. С-программы и язык Tcl 963 } if (Tcl_GetIndexFromObj(interp, objv[l], subCmds, "option", 0, feindex) != TCL_0K) { return TCL_ERR0R; } if (((index == Nameslx I I index == CreaTclx) && (objc > 2)) II ((index == Pokelx II index == DeleTclx) && (objc == 4))) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERR0R; } if (index == CreaTclx) { return BlobCreate(interp, statePtr); } if (index == Nameslx) { return BlobNames(interp, statePtr); > if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "option blob ?arg ...?"); return TCL.ERROR; } else if (objc == 3) { valueObjPtr = NULL; } else { valueObjPtr = objv[3]; } /* * Остальные команды получают имя объекта blob в качестве * третьего параметра. Отображение имени в структуру Blob * производится посредством хэш-таблицы. */ entryPtr = Tcl_FindHashEntry(&statePtr->hash, Tcl_GetString(objv[2])); if (entryPtr == NULL) { Tcl_AppendResult(interp, "Unknown blob: ", Tcl_GetString(objv[2]), NULL); return TCL_ERR0R; } blobPtr = (Blob *)Tcl_GetHashValue(entryPtr); switch (index) {
964 Часть VI. Программирование на языке С case Commandlx: { return BlobCommand(interp, blobPtr, valueObjPtr); } case Datalx: { return BlobData(interp, blobPtr, valueObjPtr); } case Nix: { return BlobN(interp, blobPtr, valueObjPtr); } case Pokelx: { return BlobPoke(interp, blobPtr); } case DeleTclx: { return BlobDelete(blobPtr, entryPtr); } } } Создание и удаление элементов хэш-таблицы Реальные действия по обработке данных BlobCmd выполняет посредством нескольких вспомогательных процедур. Они формируют основу С API для выполнения операций над объектами blob. В листинге 47.9 показан код процедур BlobCreate и BlobDelete. Эти процедуры обрабатывают каждую запись хэш-таблицы, выделяя память для хранения объектов blob или освобождая ее. Листинг 47.9. Процедуры BlobCreate и BlobDelete int BlobCreate(Tcl_Interp *interp, BlobState *statePtr) { TclJJashEntry *entryPtr; Blob *blobPtr; int new; char name[20]; /* * Создание имени blob и помещение его в хэш-таблицу. */ statePtr->uid++; sprintf (name, Ublob°/Odn, statePtr->uid); entryPtr = Tcl_CreateHashEntry(&statePtr->hash, name, &new);
Глава 47. С-программы и язык Tcl 965 blobPtr = (Blob *)ckalloc(sizeof(Blob)); blobPtr->N = 0; blobPtr->objPtr = NULL; blobPtr->cmdPtr = NULL; Tcl_SetHashValue(entryPtr, (ClientData)blobPtr); /* * Копирование имени в результаты интерпретатора. */ Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1); return TCL_0K; } int BlobDelete(Blob *blobPtr, Tcl_HashEntry *entryPtr) { TclJ)eleteHashEntry(entryPtr); if (blobPtr->cmdPtr != NULL) { TclJDecrRefCount(blobPtr->cmdPtr); } if (blobPtr->objPtr != NULL) { Tcl_DecrRefCount(blobPtr->objPtr); } /* * Tcl_EventuallyFree используется, поскольку Tcl_Preserve * была выполнена в BlobPoke. */ Tcl_EventuallyFree((char *)blobPtr, TclJFree); return TCL_0K; 2 Формирование списка Процедура BlobNames обрабатывает в цикле элементы хэш-таблицы, используя процедуры Tcl_FirstHashEntry и Tcl_NextHashEntry. В процессе работы она формирует список имен объектов. При этом также поддерживаются счетчики ссылок. Tcl_NewStringObj возвращает объект Tcl_0bj, для которого счетчик ссылок установлен равным нулю. Когда объект добавляется к списку, процедура Tcl_ListObjAppendElement инкрементирует счетчик ссылок. Аналогично, Tcl_NewListObj возвращает Tcl_0bj со счетчиком ссылок, равным нулю, который затем инкрементируется посредством Tcl_SetObjResult.
966 Часть VI. Программирование на языке С Листинг 47.10. Процедура BlobNames int BlobNames(Tcl_Interp *interp, BlobState *statePtr) { Tcl_HashEntry *entryPtr; Tcl_HashSearch search; Tcl_0bj *listPtr; Tcl_0bj *objPtr; char *name; /* * Просмотр хэш-таблицы и формирование списка имен. */ listPtr = Tcl_NewListObj(0, NULL); entryPtr = Tcl_FirstHashEntry(&statePtr->hash, fesearch); while (entryPtr != NULL) { name = Tcl__GetHashKey(&statePtr->hash, entryPtr); if (Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(name, -1)) != TCL.OK) { return TCL_ERR0R; } entryPtr = Tcl_NextHashEntry(&search); } Tcl_SetObjResult(interp, listPtr); return TCL_0K; Поддержка ссылок на значения Tcl_Obj Объект blob имеет два простых свойства: целое число N и значение Tcl_0bj. Запрашивать и устанавливать значения этих свойств можно с помощью процедур BlobN и BlobData. Процедура BlobData получает указатель на параметр Tcl_0bj, поэтому она должна инкрементировать счетчик ссылок. Листинг 47.11. Процедуры BlobN и BlobData int BlobN(Tcl_Interp *interp, Blob *blobPtr, Tcl_0bj *objPtr) { int N; if (objPtr != NULL) { if (Tcl_GetIntFromObj(interp, objPtr, &N) != TCL.OK) {
Глава 47. С-программы и язык Tcl 967 return TCL.ERROR; } blobPtr->N = N; } else { N = blobPtr->N; } Tcl.SetObjResult(interp, Tcl_NewIntObj(N)); return TCL_0K; } int BlobData(Tcl_Interp *interp, Blob *blobPtr, Tcl_0bj *objPtr) { if (objPtr != NULL) { if (blobPtr->objPtr != NULL) { TclJDecrRefCount(blobPtr->objPtr); } Tcl.IncrRefCount(objPtr); blobPtr->objPtr = objPtr; } if (blobPtr->objPtr != NULL) { Tcl_SetObjResult(interp, blobPtr->objPtr); } return TCL_0K; 2 Использование Tcl_Preserve и Tcl_Release для защиты данных Операции BlobCommand и BlobPoke позволяют регистрировать Tcl-коман- ды и выполнять их впоследствии. Организовывая подобный запуск Tcl-команд, надо отслеживать все, в том числе самые худшие варианты развития событий. Команда может использоваться для удаления связанного с ней объекта. Для поддержки необходимых действий используются процедуры Tcl_Preserve, Tcl_Release и Tcl_EventuallyFree. Перед обращением к Tcl_Eval BlobPoke вызывается Tcl_Preserve. BlobDelete вместо Tcl_Free вызывает Tcl_EventuallyFree. Если процедура Tcl_Release не была вызвана, то Tcl_EventuallyFree лишь помечает память, но не освобождает ее. Реальное освобождение памяти происходит позже, при выполнении Tcl_Release. Если Tcl_EventuallyFree действительно освобождает память, то Tcl_Release не выполняет никаких действий. В листинге 47.12 показаны коды процедур BlobCommand и BlobPoke.
968 Часть VI. Программирование на языке С Листинг 47.12. Процедуры BlobCommand и BlobPoke int BlobCommand(Tcl__Interp *interp, Blob *blobPtr, Tcl_0bj *objPtr) { if (objPtr != NULL) { if (blobPtr->cmdPtr != NULL) { Tcl.DecrRefCount(blobPtr->cmdPtr); } Tcl.IncrRefCount(objPtr); blobPtr->cmdPtr = objPtr; } if (blobPtr->cmdPtr != NULL) { Tcl_SetObjResult(interp, blobPtr->cmdPtr); } return TCL.OK; } int BlobPoke(Tcl_Interp *interp, Blob *blobPtr) { int result = TCL.OK; if (blobPtr->cmdPtr != NULL) { Tcl_Preserve(blobPtr); result = Tcl_EvalObj(interp, blobPtr->cmdPtr); /* * Здесь можно использовать blobPtr. */ Tcl.Release(blobPtr); /* * Здесь blobPtr может быть некорректным. */ } return result; 2 Оказывается, что BlobCmd после вызова Tcl_EvalObj реально не использует blobPtr, поэтому она может завершиться без обращения к Tcl_Preserve и Tcl_Release. С этими процедурами связаны некоторые накладные расходы. Указатель помещается в список и извлекается оттуда. Если вы способны тщательно отследить все особенности своей программы, вы можете отказаться от
Глава 47. С-программы и язык Tcl 969 этих вызовов. Однако следует заметить, что в этом случае при выполнении произвольных Tcl могут возникать проблемы. Макрос CONST в Tcl 8.4 API Ключевое слово const языка С используется для создания переменных, предназначенных только для чтения. Единожды установленное значение этой переменной остается неизменным. Как правило, ключевое слово const используется при объявлении параметров и указывает на то, что процедура не может модифицировать их. В определениях Tcl API вместо ключевого слова const используется макрос CONST. Это обеспечивает совместимость со старыми компиляторами, в которых ключевое слово const не поддерживается. В Tcl 8.4 некоторые из Tcl API были изменены, в частности, макрос CONST был включен в те из них, в которых он отсутствовал. Наиболее существенные изменения претерпели заголовки командных процедур на базе строк. Примером такой процедуры является RandomCmd, рассмотренная в данной главе. Изменения, внесенные в tcl.h, стали предметом бурных дебатов. Многие из разработчиков приветствовали включение CONST, поскольку это позволило более тщательно организовать контроль за возникновением ошибок. Однако изменение tcl.h в Tcl 8.4 привело к тому, что при компиляции кода, созданного ранее, начали выводиться предупреждающие сообщения. В некоторых организациях не допускаются даже предупреждающие сообщения при компиляции, поэтому разработчикам пришлось корректировать уже готовый код. В некоторых случаях изменение старого кода невозможно. Иногда приходится компилировать один и тот же код в новых и старых версиях Tcl. В этом случае на модификацию кода попросту не хватает времени. К тому же определения CONST часто остаются незамеченными. Учитывая проблемы, связанные с изменениями в tcl.h, в Tcl 8.4 были добавлены средства, позволяющие на этапе компиляции изменить действия CONST. Некоторые из имен, позволяющих делать это, описаны в табл. 47.1. Таблица 47.1. Управление обработкой CONST в Tcl API N0_C0NST Отменяет определение CONST так, что ключевое слово const не используется USE_N0N__C0NST Запрещает использование нового ключевого слова CONST, добавленного в Tcl 8.4 USE_COMPAT_CONST Ключевое слово CONST, добавленное в Tcl 8.4, используется только для возвращаемых значений
970 Часть VI. Программирование на языке С Действия со строками и интернационализация С поддержкой строк непосредственно связаны такие важные вопросы, как динамическое их создание и преобразование наборов символов. В простых примерах, рассмотренных до сих пор, эти вопросы не затрагивались, однако при написании серьезных приложений их неминуемо придется решать. Интерфейс DString При разработке программ часто возникает необходимость "собирать" строку из нескольких частей. Тип Tcl_DString и соответствующий API позволяют решать эту задачу максимально эффективно. Интерфейс DString скрывает от разработчика детали управления памятью, а тип Tcl_Dstring позволяет начинать формирование строки, используя небольшой статический буфер. Поместив данные типа Tcl_String в стек (т.е. организовав их в виде локальной переменной), можно избежать динамического распределения памяти. Стандартный код выполняет действия наподобие приведенных ниже. TclJDString ds; TclJDStringInit(&ds); Tcl_DStringAppend(&ds, "some value", -1); TclJ)StringAppend(&ds, "something else", -1); Tcl_DStringResult(interp, &ds); TclJDStringlnit инициализирует указатель на строку в составе структуры так, чтобы он указывал на статический буфер, который также является частью структуры. Tcl_DStringAppend увеличивает размеры строки. Если длина строки превышает размер статического буфера, динамически выделяется память для нового буфера и строка копируется в него. В качестве последнего параметра Tcl_DStringAppend задается длина. Если вы хотите, чтобы копирование осуществлялось до появления в вашей строке нулевого байта, вам надо указать значение, равное — 1. Строковое значение можно использовать как результат выполнения Tcl-команды; для этой цели служит процедура Tcl_DStringResult. Она передает интерпретатору информацию о принадлежности строки pi автоматически очищает структуру Tcl_DString. Если вы не собираетесь использовать строку в качестве результата, вы должны вызвать процедуру Tcl_DStringFree для того, чтобы динамически выделенная память была освобождена. Tcl_DStringFree(&ds); Используя Tcl_DStringValue, можно получить указатель на созданную строку.
Глава 47. С-программы и язык Tcl 971 name = TclJDStringValue(&ds); Помимо рассмотренных выше, DString API предоставляет целый набор дополнительных процедур. Информацию о них можно получить в справочном руководстве. Некоторые из них позволяют создавать списки, однако эта задача лучше решается с помощью интерфейса Tcl_0bj. Следует заметить, что Tcl_0bj в некоторой степени может заменить TclJDString. Например, Tcl_NewStringObj и Tcl_AppendToObj формируют Tcl_0bj и добавляют к нему строку. Однако существует ряд процедур Tcl API, в которых предусмотрены параметры типа Tcl_DString; в этом случае тип Tcl_0bj становится непригоден. Для небольших строк интерфейс DString очень эффективен, так как он уменьшает число операций с динамически распределяемой памятью. Преобразование наборов символов Как было сказано в главе 15, для организации внутренних действий со строками Tcl использует UTF-8. UTF-8 — это представление Unicode, в котором отсутствуют нулевые байты. Семибитовые символы ASCII представляются одним байтом, поэтому если в вашем распоряжении есть старый С-код, в котором предусмотрены действия только с ASCII-строками, то его можно использовать совместно с Tcl-программами, не модифицируя. В общем случае может понадобиться преобразование строк UTF-8, полученных как значения Tcl_0bj, в строки с конкретной кодировкой. Например, если вы передаете строку операционной системе, она должна быть представлена по правилам, принятым на конкретной платформе; это может быть 16-битовая кодировка Unicode, ISO-Latin-1 (т.е. iso-8859-l) и т.д. Tcl предоставляет API, выполняющие необходимые преобразования. Часто для хранения результатов используется Tcl_DString, так как предсказать размер, полученный в результате перекодировки, бывает невозможно. Например, для преобразования строки UTF-8 в TclJDString для конкретной операционной системы можно использовать следующий вызов: Tcl JJtfToExternalDString(NULL, string, -1, &ds); Результат выполнения TclJDStringValue(&ds) можно передать системной функции, которая ожидает строку, представленную по правилам данной платформы. После этого вам потребуется вызвать Tcl_DStringFree(&ds) для того, чтобы освободить память, выделенную посредством Tcl_UtfToExternalDString. Преобразование строки можно выполнить также, используя Tcl.ExternalToUtfDString. Tcl_ExternalToUtfDString(NULL, string, -1, &ds);
972 Часть VI. Программирование на языке С Третьим параметром указанной процедуры является длина строки (она задается не в символах, а в байтах!). Значение —1 указывает на то, что длина должна быть вычислена автоматически, путем поиска нулевого байта. При хранении строк UTF-8 символы дополняются нулевым байтом, поэтому значение — 1, заданное при вызове функции, является корректным. В качестве первого параметра процедуры задается кодировка, из которой или в которую необходимо преобразовать строку. Значение NULL указывает на системную кодировку. Если ваши данные представлены в нестандартной кодировке или вам надо осуществить преобразование в кодировку, отличающую от системной, необходимо получить дескриптор кодировки с помощью Tcl_GetEncoding и затем освободить его, используя Tcl_FreeEncoding. encoding = Tcl_GetEncoding(interp, name); Tcl_FreeEncoding(encoding); Имена кодировок возвращает Tcl-команда encoding names, кроме того, вы можете запросить их с помощью С API. В Windows определен специфический тип строки TCHAR, который в Windows 95/98 представляет 8-битовые данные, а в Windows NT и Windows СЕ — 16-битовые символы Unicode. Если вы используете функцию С API, которой необходимо передать массив TCHAR, вам, чтобы корректно сформировать вызов, необходимо знать, в какой системе выполняется ваша программа. Tcl предоставляет две процедуры, которые автоматически решают эту задачу. Tcl_WinTCharToUf действует подобно Tcl_ExternalToUtfDString, a Tcl_WinUtfToTChar можно сравнить с TclJJtfToExternalDString. Tcl JtfinUtfToTChar(string, -1, &ds); Tcl_WinTCharToUtf(string, -1, &ds); И наконец, в Tcl предусмотрено несколько процедур для работы с символами Unicode типа TclJJniChar и символами UTF-8. В качестве примеров можно привести процедуры Tcl^UniCharToUtf, Tcl_NumUtfChars и Tcl_UtfToUniCharDString. Информация о их работе представлена в справочных руководствах. Tcl_Main и Tcl_Applnit В данном разделе описывается создание основной программы, включающей средства Tcl. Считается, что потребности разработчика в основном сводятся к использованию загружаемых модулей. Если вы создадите ваши команды как загружаемые пакеты, то сможете загружать их в Tclsh или в wish. Даже если создание главной программы не входит в круг ваших задач, материал этого раздела поможет вам понять, как взаимодействуют различные компоненты приложения.
Глава 47. С-программы и язык Tcl 973 Библиотека Tcl поддерживает базовую структуру приложения посредством процедуры Tcl_Main, которая предназначена для вызова из главной программы. Ниже перечислены три основных действия, выполняемых Tcl_Main. • Tcl_Main вызывает Tcl_CreateInterp для создания интерпретатора, включающего стандартные Tcl-команды, например set или ргос. Она также определяет некоторые переменные Tcl, например argc и argv. Посредством этих переменных становятся доступными параметры командной строки, передаваемые приложению. • Tcl_Main вызывает процедуру Tcl_AppInit, которая не входит в состав библиотеки Tcl. Эту процедуру предоставляет приложение. В Tcl_AppInit вы можете зарегистрировать дополнительные команды Tcl, специфические для вашей программы. • Tcl.Main читает сценарий или переходит в цикл интерактивного обмена. Tcl_Main вызывается из главной программы; для того чтобы использовать ее, вам надо предоставить ей реализацию процедуры Tcl_AppInit. Листинг 47.13. Функция main и процедура Tcl_AppInit /* main.с */ #include <tcl.h> int Tcl_AppInit(Tcl_Interp *interp); /* * Объявление командной процедуры, специфической для приложения */ int PluslObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_0bj *C0NST objv[]); main(int argc, char *argv[]) { /* * Инициализация приложения, а также * инициализация и запуск Tcl. */ Tcl_Main(argc, argv, Tcl.AppInit); exit(O); } /* * Tcl_AppInit вызывается из Tcl_Main после создания * интерпретатора Tcl и перед выполнением сценария или
974 Часть VI. Программирование на языке С * переходом в цикл интерактивной обработки. */ int Tcl_AppInit(Tcl_Interp *interp) { /* * Tcl_Init читает init.tcl из библиотеки Tcl-сценариев. */ if (Tcl_Init(interp) == TCL_ERR0R) { return TCL_ERR0R; } /* * Регистрация команд, специфических для приложения. */ Tcl.CreateObjCommand(interp, "plusl", PluslObjCmd, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL); Random_Init(interp); Blob_Init(interp); /* * This file is read if no script is supplied. */ Tcl_SetVar(interp, "tcl_rcFileName,f, "V.mytcl", TCL_GL0BAL_0NLY); /* * Проверка Tcl_Invoke. */ Tcl_Invoke(interp, "set", "foo1», "$xyz [foo] {", NULL); return TCL_0K; 2 При вызове Tcl_Main ей передаются значения argc и argv. Эти параметры описывают данные, указанные в командной строке; Tcl_Main сохраняет их в Tcl-переменных с соответствующими именами. Tcl.Main также получает в качестве параметра инициал изационную процедуру; в нашем случае это Tcl_AppInit. При вызове Tcl_AppInit ей передается один параметр — дескриптор вновь созданного интерпретатора. Процедуру Tcl_AppInit можно условно разделить на три части. • В первой части осуществляется инициализация различных пакетов, используемых приложением. В приведенном выше примере производится обращение к Tcl_Init. Основные Tcl-команды определяются при выполнении процедуры Tcl_CreateInterp, которая вызывается из Tcl_Main перед обращением к Tcl_AppInit.
Глава 47. С-программы и язык Tcl 975 • Во второй части Tcl_AppInit выполняются действия по инициализации, специфические для приложения. В данном примере осуществляется регистрация командных процедур, определенных ранее в этой главе. • В третьей части определяется Tcl-переменная tcl_RcFileName, которая задает имя сценария запуска приложения. Этот сценарий выполняется при интерактивной работе с программой. Подобно tclsh, можно использовать любую программу, но вам придется определить в процедуре Tcl_AppInit дополнительные команды. На компакт- диске находится пример программы mytcl. Вы можете скомпилировать ее и проверить работу random и других команд. Tk_Main Структуру, подобную рассмотренной выше, имеют Тк-приложения. Процедура Tk.Main создает Tcl-интерпретатор и главное окно Тк. Для завершения действия по инициализации она обращается к процедуре, созданной вами для этой цели. После возврата из процедуры Tk_AppInit Tk_Main переходит в цикл обработки событий и находится в нем до тех пор, пока все окна приложения не будут закрыты. В листинге 47.14 показан код процедуры Tk_AppInit, предназначенной для работы с Tk_Main. Основная программа обрабатывает переданные ей параметры с помощью Tk_ParseArgv; при этом для формирования сообщений об ошибках необходим интерпретатор Tcl. Процедура Tk_AppInit инициализирует компонент, который рассматривается в главе 49. Листинг 47.14. Программа main и процедура Tk_AppInit /* main.с */ #include <tk.h> int Tk_AppInit(Tcl_Interp *interp); /* * Таблица для параметров командной строки. */ char *myoptionl = NULL; int myint2 = 0; static Tk_ArgvInfo argTable[] = { {"-myoptionl", TK_ARGV_STRING, (char *) NULL, (char *) femyoptionl, "Explain myoptionl"}, {"-myint2", TK_ARGV_CONSTANT, (char *) 1, (char *) &myint2,
976 Часть VI. Программирование на языке С "Explain myint2u}, {"", TK_ARGV_END, }, >; main(int argc, char *argv[]) { Tcl_Interp *interp; /* * Данный вызов должен предшествовать созданию интерпретаторов. */ Tcl.FindExecutableO ; /* * Создание интерпретатора для сообщения об ошибке, * полученной от Tk_ParseArgv. Еще один интерпретатор * создает Tk.Main. * Разбор параметров. */ interp = Tcl_CreateInterp(); if (Tk_ParseArgv(interp, (Tk.Window) NULL, &argc, argv, argTable, 0) != TCL.OK) { fprintf (stderr, "5is\n", interp->result); exit(l); } Tcl_DeleteInterp(interp); Tk_Main(argc, argv, Tk.AppInit); exit(0); } int ClockCmd(ClientData clientData, Tcl_Interp *interp, int argc, CONST char *argv[]); int ClockObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_0bj *C0NST objv[]); void ClockObjDestroy(ClientData clientData); int Tk_AppInit(Tcl_Interp *interp) { /* * Инициализация пакетов. */ if (Tcl.Init(interp) == TCL_ERR0R) {
Глава 47. С-программы и язык Tcl 977 return TCL.ERROR; } if (Tk_Init(interp) == TCL_ERR0R) { return TCL_ERR0R; } /* * Определение команд, специфических для приложения. */ Tcl_CreateCommand(interp, "wclock", ClockCmd, (CIientData)Tk_MainWindow(interp), (Tcl.CmdDeleteProc *)NULL); Tcl.CreateObjCommand(interp, "oclock", ClockObjCmd, (ClientData)NULL, ClockObjDestroy); /* * Определение имени стартового файла. Этот файл читается * в том случае, если программа выполняется в * интерактивном режиме. */ Tcl.SetVar(interp, "tcl.rcFileName" , "V.mytcl", TCL_GL0BAL_0NLY); return TCL_0K; } Цикл обработки событий Цикл обработки событий предназначен для поддержки как событий оконной системы, так и других событий, связанных, например, с таймерами или с сетевыми гнездами. В каждом приложении Тк должен выполняться цикл обработки событий, в противном случае приложение не будет корректно работать в оконной среде. В Тк стандартный цикл обработки событий реализован с помощью процедуры Tk_MainLoop, которая вызывается в конце выполнения Tk_Main. Оболочка wish автоматически обеспечивает запуск цикла обработки событий. В tclsh цикл обработки событий не предусмотрен, но вы можете реализовать его средствами Tcl, как было показано в листинге 16.2. В некоторых приложениях предусмотрен собственный цикл обработки событий. Добавить средства Тк к таким приложениям можно двумя способами. Во-первых, вы можете модифицировать существующий цикл обработки событий, предусмотрев в нем вызов TclJDoOneEvent. В каталоге unix дистрибутивного пакета находится файл XtTest.c, который добавляет средства Tcl к приложениями Xt (т.е. Motif). Во-вторых, вы можете настроить
978 Часть VI. Программирование на языке С цикл обработки так, чтобы события выглядели как события, сгенерированные Tcl-программой, и зарегистрировать новый источник событий. После этого можно использовать Tk_Main. Существуют четыре класса событий. Они обрабатываются Tcl_DoOneEvent в следующем порядке. • Оконные события. Для регистрации обработчика таких событий используется процедура Tk_CreateEventHandler. При обработке подобных событий в Tcl_DoOneEvent надо использовать флаг TCL_WINDOW_EVENTS. • События файловой системы. Эти события используются для ожидания готовности к обмену устройств с низким быстродействием или сетевых соединений. В системе Unix вы можете зарегистрировать обработчик для всех файлов, гнезд и устройств с помощью Tcl_CreateFileHandler. В Windows и Macintosh для регистрации используются различные API, поскольку программа имеет дело с различными системными обработчиками для файлов, гнезд и устройств. На всех платформах для обработки в Tcl_DoOneEvent используется флаг TCL_FILE_EVENTS. • События таймера. По мере необходимости вы можете спланировать наступление событий по истечении определенного периода времени. Для регистрации обработчика таких событий используется процедура Tcl_CreateTimerHandler. При обработке в Tcl__DoOneEvent надо использовать флаг TCL_TIMER_EVENTS. • События бездействия. Данные события обрабатываются тогда, когда программа не выполняет никаких других действий. Практически каждый компонент Тк использует события бездействия для обновления своего внешнего вида. Для регистрации процедуры, которая вызывается при наступлении очередного периода бездействия, используется процедура Tcl_DoWhenIdle. При обработке таких событий в TclJDoOneEvent надо использовать флаг TCL_IDLE_EVENTS. Вызов сценариев из С-программ Tcl-сценарий может быть вызван не только из функции main. Практически в любой точке программы вы можете выполнить Tcl-команду, вызвав процедуру Tcl_Eval. Tcl_Eval(Tcl_Interp *interp, char *script); В результате выполнения Tcl_Eval возвращает код TCL_0K, TCL_ERR0R, TCL.BREAK, TCL_CONTINUE или TCL_RETURN. Для получения результирующего значения используется Tcl_GetStringResult или Tcl_GetObjResult. Данные функции возвращают те значения, которые были установлены Tcl_SetResult,
Глава 47. С-программы и язык Tcl 979 Tcl_SetObjResult или другими функциями, предназначенными для формирования результатов командных процедур. Указанный сценарий выполняется в области видимости текущей Тс1-про- цедуры, которая может быть глобальной областью. Аналогично, вызовы Tcl__GetVar и Tcl_SetVar обеспечивают доступ к переменным в текущей области видимости. Если по каким-либо причинам вам необходимо создать новую область видимости, сделать это проще всего, вызывая С-код из Тс1-процеду- ры, используемой для этой цели. Работая с экспортируемым С API, создать новую область видимости достаточно сложно. Tcl_Eval модифицирует параметры. Необходимо учитывать, что при выполнении Tcl_Eval возможны побочные эффекты. В частности, данная процедура может изменить переданную ей строку. Если вы передаете Tcl_Eval строку- константу, удостоверьтесь, что компилятор не помещает ее в область памяти, предназначенную только для чтения. Если вы используете компилятор gcc, вам, возможно придется задать опцию -f writable-strings. Вопросы настройки компилятора для конкретной системы рассматриваются в главе 48. Разновидности Tcl_Eval Существует несколько разновидностей Tcl_Eval. Конкретный вариант Tcl_Eval зависит от того, надо ли обрабатывать строки или значения Tcl_0b j, осуществляется ли выполнение в текущей или глобальной области видимости, подлежит ли обработке одна строка (или значение Tcl_0bj) или переменное число параметров и должна ли выполняться компиляция. Чаще всего используется строковый вариант процедуры Tcl_EvalEx, которой передаются строка, счетчик и несколько флагов. Tcl_EvalEx(interp, string, count, flags); В качестве флагов могут быть указаны TCL_GLOBAL_EVAL и TCL_EVAL_DIRECT; флаг TCL_EVAL_DIRECT отменяет компиляцию. Если код должен выполняться лишь один раз, использование TCL__EVAL_DIRECT повысит эффективность его работы. Tcl_GlobalEval эквивалентна рассмотренной выше процедуре с флагом TCL_GLOBAL_EVAL. Процедура Tcl_VarEval получает произвольное число строковых параметров и перед выполнением осуществляет их конкатенацию. Tcl_VarEval(Tcl_Interp *interp, char *str, ..., NULL); Tcl_EvalObj получает в качестве параметра не строку, а объект. При первом использовании строковые данные преобразуются в байтовый код. Если вы собираетесь многократно выполнять сценарий, значение Tcl_0bj поз-
980 Часть VI. Программирование на языке С волит организовать кэширование байтового кода. При работе со значениями Tcl_0bj в качестве интерфейса к Tcl_Eval используется процедура Tcl_EvalObjEx, для которой предусмотрены те же флаги, что и для Tcl_EvalEx. TclJEvalObjEx(interp, objPtr, flags); При наличии переменного числа параметров используется процедура Tcl.EvalObjv, которой передается массив указателей на Tcl_0bj. Эта процедура выполняет конкатенацию строк, соответствующих значениям Tcl_0bj, а затем осуществляет разбор результирующей Тс1-команды. Tcl_EvalObjv(interp, objc, objv); Отказ от Tcl _ Eva I Если производительность программы критически важна, вы, возможно захотите избежать накладных расходов, связанных с TclJSval. Можно непосредственно вызывать командную процедуру С. Для этого используется процедура Tcl.GetCommandlnfо, которая предоставляет Tcl-команде адрес командной процедуры С и указатель на данные. Код процедуры Tcl_Invoke показан в листинге 47.15. Она используется подобно Tcl.VarEval, за исключением того, что один из ее параметров становится параметром Тс1-команды; при этом подстановка не выполняется. Предположим, например, что вам необходимо вставить в текстовый компонент большой фрагмент текста и вы хотите, чтобы разбор не выполнялся. Для этого вы должны использовать Tcl_Invoke следующим образом. Tcl_Invoke(interp, ,f.tHf "insert", "insert", buf, NULL); или Tcl_Invoke(interp, "set", "foo", "$xyz [blah] {", NULL); Подстановка параметров не выполняется, поскольку Tcl_Eval не участвует в работе. Переменная foo получает следующее литеральное значение: $xyz [blah] { Код Tcl_Invoke показан в листинге 47.15. Процедура несколько усложнена по следующим причинам. Во-первых, она должна поддерживать Tcl- команды как с объектным, так и со строковым интерфейсом. Во-вторых, необходимо формировать вектор параметров, и в процессе его построения может возникнуть потребность в увеличении объема памяти для его хранения. Выполнить оба указанных требования достаточно сложно, но при этом мы получаем возможность сравнить объектный и строковый интерфейс. Строковый интерфейс проще, а объектный обеспечивает более эффективную работу, так как при этом нет необходимости копировать и преобразовывать типы.
Глава 47. С-программы и язык Tcl 981 Листинг 47.15. Непосредственный вызов командной процедуры С с помощью Tcl_Invoke #include <tcl.h> /* * Tcl_Invoke -- * Непосредственный вызов Tcl-команды или процедуры * * Вызов Tcl_Invoke наломинает вызов Tcl_VarEval. * Каждый параметр становится параметром команды; * подстановка или разбор не выполняется. */ int Tcl_Invoke TCL_VARARGS_DEF(Tcl_Interp *, argl) { va_list argList; Tcl_Interp *interp; char *cmd; /* Имя команды */ char *arg; /* Параметр команды */ char **argv; /* Вектор параметров */ int argc, i, max; /* Число параметров */ Tcl_CmdInfo info; /* Информация о командных процедурах */ int result; /* TCL_0K или TCL.ERROR */ interp = TCL_VARARGS_START(Tcl_Interp *, argl, argList); Tcl_ResetResult(interp); /* * Отображение имени команды в процедуру С */ cmd = va_arg(argList, char *); if (! Tcl_GetCommandInfo(interp, cmd, &info)) { Tcl.AppendResult(interp, "unknown command Vf", cmd, If\"\ NULL); va_end(argList); return TCL.ERROR; } max =20; /* Начальный размер вектора параметров */
982 Часть VI. Программирование на языке С #if TCL_MAJ0R_VERSION > 7 /* * Проверить, подходит ли объектный интерфейс для * данной команды. */ if (info.isNativeObjectProc) { Tcl_0bj **objv; /* Object vector for arguments */ Tcl_0bj *resultPtr; /* The result object */ int objc; objv = (Tcl_0bj **) ckallocCmax * sizeof(Tcl_0bj *)); objv[0] = Tcl_NewStringObj(cmd, strlen(cmd)); Tcl_IncrRefCount(objv [0]); /* ref count == 1*/ objc = 1; /* * Формирование вектора из остальных параметров */ while (l) { arg = va_arg(argList, char *); if (arg == (char *)NULL) { objv[objc] = (TclJDbj *)NULL; break; } objv[objc] = Tcl_NewStringObj(arg, strlen(arg)); Tcl_IncrRefCount(objv[objc]); /* ref count == 1*/ obj C++; if (objc >= max) { /* Выделение памяти для вектора большего размера и копирование старых данных. */ Tcl_0bj **oldv = objv; max *= 2; objv = (Tcl.Obj **) ckalloc(max * sizeof(Tcl_0bj *)); for (i = 0 ; i < objc ; i++) { objv[i] = oldv[i] ; } Tcl_Free((char *)oldv); }
Глава 47. С-программы и язык Tcl 983 } va_end(argList); /* * Вызов С-процедуры */ result = (*info.objProc)(info.objClientData, interp, objc, objv); /* * Убедиться, что строковое значение результата корректно, * и освободить ссылки на параметры. */ (void) Tcl_GetStringResult(interp); for (i = 0 ; i < objc ; i++) { Tcl.DecrRefCount(objv[i]); } Tcl_Free((char *)objv); return result; } #endif argv = (char **) ckalloc(max * sizeof(char *)); argv[0] = cmd; argc = 1; /* * Формирование вектора из оставшихся параметров. */ while (l) { arg = va_arg(argList, char *); argv[argc] = arg; if (arg == (char *)NULL) { break; } argc++; if (argc >= max) { /* Выделение памяти для вектора большего размера и копирование старых данных. */ char **oldv = argv; max *= 2;
984 Часть VI. Программирование на языке С argv = (char **) ckalloc(max * sizeof(char *)); for (i = 0 ; i < argc ; i++) { argv[i] = oldv[i] ; } Tcl_Free((char *) oldv); } } va_end(argList); /* * Вызов С-процедуры. */ result = (*info.proc)(info.clientData, interp, argc, argv); /* * Освобождение параметров. */ Tcl_Free((char *) argv); return result; 2 Данная версия Tcl.Invoke была разработана Джином Брауэрсом (Jean Brouwers). Он использовал макросы TCL_VARARGS_DEF и TCL_VARARGS_START для определения процедур, которые получают переменное количество параметров. Эти стандартные макросы Tcl скрывают различия, связанные с работой в разных операционных системах и с использованием разных компиляторов. Оказывается, что разные компиляторы незначительно отличаются друг от друга. Эти различия усложняют задачу переносимости программ. Однако существует схема, позволяющая решить эту проблему и создавать переносимые программы. Этот вопрос будет обсуждаться в следующей главе.
Глава 48 Компиляция Tcl и программных расширений В данной главе рассматриваются построение Tcl из исходного дистрибутивного пакета и подготовка к работе С-расширений, написанных с учетом требований Tcl Extension Architecture (TEA). /Для компиляции исходных дистрибутивных кодов и построения средств Tcl не приходится прикладывать больших усилий. Одним из преимуществ языка Tcl является переносимость, т.е. вы можете подготовить средства Tcl для работы в самых различных средах, например, в Unix, Windows, Macintosh, AS/400, на мэйнфреймах IBM, а также во встроенных системах. Однако создание переносимых Tcl-расширений представляет собой гораздо более трудную задачу. В спецификации Tcl Extension Architecture (TEA) изложены основные принципы написания расширений и приведены примеры, помогающие разрабатывать переносимые программы такого типа. Создание TEA стало результатом взаимодействия многих пользователей Tcl; при изложении материала этой главы будет использоваться вторая версия данной спецификации, т.е. ТЕА2. В начале главы кратко рассматриваются основные правила построения самой системы Tcl. Приведенные здесь сведения можно рассматривать как модель построения расширений. Если вы собираетесь изучить правила написания расширений Tcl, вам лучше всего начать с компиляции Tcl. Исходные коды Tcl и Тк расположены на прилагаемом к данной книге компакт-диске. Кроме того, их можно найти в Web по следующему адресу: http://www.tcl.tk/software/tcltk/
986 Часть VI. Программирование на языке С Исходные коды, оформленные в виде дистрибутивного пакета, также распространяются через FTP. ftp://ftp.tcl.tk/pub/tcl/ Интерактивное хранилище CVS для программного обеспечения Tcl доступно посредством следующих ссылок: http://www.tcl.tk/software/tcltk/netcvs.html http://www.sourceforge.net/proj ects/tcl Если при обращении по данным URL у вас возникнут проблемы, вы можете получить новую информацию об исходных кодах Tcl на Web-узле данной книги (http://www.beedub.com/book/). Стандартная структура каталогов Дистрибутивный пакет В табл. 48.1 описана структура каталогов дистрибутивного пакета, посредством которого распространяются исходные коды Tcl. Дистрибутивный пакет Тк имеет аналогичную структуру. Подобным образом вы можете формировать дистрибутивные пакеты своих программ. Существуют также соглашения о размещении Tcl, Tk и других исходных пакетов в файловой системе. Выполнять эти соглашения необходимо в случае, если пакеты зависят друг от друга. Таблица 48.1. Структура каталогов, содержащих исходные коды Tcl tcl8.4 Корневой каталог для исходных Tcl-кодов. В нем содержатся файлы README и license.terms, а также ряд подкаталогов tcl8.4/compat Содержатся файлы .с, реализующие процедуры, которые в стандартной библиотеке С для некоторых платформ реализованы некорректно tcl8.4/doc Содержится справочная документация. В настоящее время она представлена в формате nrof f, пригодном для использования в системе Unix совместно с программой man. Предполагается перевод данных в формат XML tcl8.4/generic Содержатся исходные файлы .си .h общего назначения, используемые в системах Unix, Windows и Macintosh
Глава 48. Компиляция Tcl и программных расширений 987 Окончание табл. 48.1 tcl8.4/mac tcl8.4/library tcl8.4/library/encoding tcl8.4/library/naKer tcl8.4/test tcl8.4/tools tcl8.4/unix tcl8.4/unix/dltest tc!8.4/vnix/платформа tc!8.4/win Хранятся исходные файлы .си . h, специфические для системы Macintosh. В нем также находятся файлы проекта Code Warrior Содержатся init. Tcl и другие Tcl-файлы стандартной библиотеки сценариев Tcl Содержатся таблицы преобразования Unicode Находится ряд подкаталогов (например, http2.3), которые содержат Тс1-пакеты Содержится тестирующий пакет Tcl. В него входят сценарии, воздействующие на реализацию Tcl Набор сценариев, упрощающих создание дистрибутивных пакетов Tcl Хранятся исходные файлы .си .h, специфические для системы Unix. Здесь же находятся сценарий configure и шаблон Makefile, in Содержатся тестовые файлы для динамической загрузки Содержимое этого каталога может использоваться для построения средств Tcl для нескольких различных платформ. Подкаталоги для конкретных платформ необходимо создать самостоятельно Содержатся файлы .си .h, специфические для системы Windows. Здесь же находятся сценарий configure и шаблон Makefile.in. В этом каталоге может также находиться файл makefile.vc, совместимый с nmake Структура инсталляционного каталога При инсталляции Tcl окончательное размещение файлов не соответствует исходному дистрибутивному пакету. Стандартный инсталляционный каталог организован так, что его содержимое может быть совместно использовано компьютерами с различной архитектурой, работающими под управлением разных операционных систем (например, Windows, Linux или Solaris). Tcl-сценарии — файлы и документация — находятся в разделяемых каталогах. Приложения и библиотеки (например, DLL-файлы) располагаются в каталогах, предназначенных для работы с конкретной операционной системой. Для того чтобы указать, где должны устанавливаться эти две группы файлов, надо задать опции --prefix и --exec-prefix программы configure.
988 Часть VI. Программирование на языке С Подробно этот вопрос рассматривается в следующем разделе. В табл. 48.2 описана структура стандартного инсталляционного каталога. Таблица 48.2. Структура инсталляционного каталога архив /bin Содержатся приложения, специфические для конкретной платформы. В системе Windows здесь также могут находиться двоичные библиотеки (DLL). Обычно в качестве имени архива указывается solaris-sparc, linux-ix86 или win-ix86 архив /lib Содержатся двоичные библиотеки, ориентированные на работу в системе Unix (например, libtcl8.4.so) bin Содержатся приложения, не зависящие от конкретной платформы (например, Тс1-сценарии) doc Содержится документация include Предназначен для хранения файлов .h lib Содержатся подкаталоги для платформенно-независимых пакетов. Находящиеся здесь пакеты автоматически обнаруживаются средствами автозагрузки Tcl lib/tcl8.2 Находится содержимое каталога tcl8.4/library и его подкаталогов lib/пакег Содержит Tcl-сценарии для пакета. Примерами каталогов, соответствующих пакетам, являются tk8.4 и itcl3.2 man Находится справочная информация в формате Unix man Если вы хорошо знаете программу configure, вам наверняка известны опции, позволяющие управлять размещением различных компонентов системы. Однако следует учитывать, что Tcl автоматически ищет сценарии и библиотеки, поэтому не следует без важных причин изменять рекомендуемую структуру каталогов. Построение Tcl из исходных кодов Процесс компиляции Tcl из исходного дистрибутивного пакета можно разделить на два этапа: настройку посредством сценария configure и собственно компиляцию, осуществляемую с помощью программы make. Сценарий configure проверяет текущее состояние системы и выполняет установки, используемые в процессе компиляции. При запуске configure надо указать особенности компиляции, например, сообщить, следует ли подключать средства отладки, необходима ли поддержка многопотокового выполнения и т.д. При запуске configure также указывается инсталляционный каталог
Глава 48. Компиляция Tcl и программных расширений 989 Tcl. Программа make компилирует исходный код, устанавливает скомпилированные программы, запускает тестирующий пакет и освобождает ресурсы, использованные в процессе работы. Программа make хорошо знакома всем программистам, работающим в системе Unix. Используя свободно распространяемый инструмент Cygwin, вы можете также запускать configure и make в системе Windows. При работе в Windows вы можете выбрать либо свободно распространяемый компилятор mingw, либо Microsoft VC++. К сожалению, gec в Windows не поддерживается. Некоторые из программистов, использующих Windows и Macintosh, не имеют достаточного опыта работы с программой make. По этой причине в состав дистрибутивного пакета иногда включаются файлы проекта для Microsoft Visual C++ и среды разработки Macintosh. Использование этих файлов несколько упрощает работу (особенно это заметно в системе Macintosh). Обычно файлы проекта располагаются в подкаталогах win и mac. Несмотря на наличие таких файлов, в данной главе основное внимание уделяется использованию configure и make для построения Tcl-приложений и расширений. Инструменты configure и autoconf Для формирования среды построения программ на различных платформах может использоваться специальный инструмент autoconf. Применяя autoconf, разработчик, работающий в системе Windows или Linux, может сгенерировать сценарий configure и шаблон Makefile. Полученные результаты можно использовать в Solaris, HP-UX, PreeBSD, AIX и многих других версиях Unix. Сценарий configure проверяет характеристики системы и настраивает шаблон Makefile для конкретной платформы. В данной главе рассматриваются действия по настройке шаблона, конфигурированию и построению программ. • Настройка шаблона. Разработчики исходного кода создают шаблон configure, in, в котором отражается зависимость исходного кода от системы. Для преобразования шаблона в сценарий configure используется программа autoconf. Кроме шаблона configure. in, создается также шаблон Makefile, in. Особенности этих шаблонов будут описаны далее в этой главе. В исходных дистрибутивных пакетах Tcl и Тк уже содержится сценарий configure. Его можно найти в подкаталогах unix и win. • Конфигурирование. Пользователь запускает сценарий configure в той системе, в которой будут компилироваться исходные коды. На этом этапе происходит преобразование Makefile.in в файл Makefile для
990 Часть VI. Программирование на языке С конкретной платформы. Если вы собираетесь работать только на одной платформе, вам достаточно запустить configure в каталоге unix (или win). e/e cd /usr/local/src/tcl8.4/unix 7. ./configure опции Опции сценария configure описаны в табл. 48.3. Если текущий каталог не указан в переменной окружения PATH, то для запуска сценария надо использовать выражение ./configure. Оно позволяет убедиться, что сценарий вызван именно из текущего каталога. Если вы выполняете построение для нескольких платформ, создайте подкаталоги unix и вызывайте configure из них. В этом случае выражение, используемое для запуска сценария, принимает вид . ./configure. 7о cd /usr/local/src/tcl8.2/unix 7ф mkdir linux 7e cd linux 7e ../configure flags • Построение программ. Сценарий configure использует для генерации Makefile шаблон Makefile.in. После завершения configure вы можете приступать к построению программы с помощью инструмента make. 7о make Посредством make можно также выполнять и другие действия. Для того чтобы запустить тестирующий пакет, надо вызвать приведенную ниже команду. 7e make test Инсталляция скомпилированной программы или расширения осуществляется следующим образом: 7о make install Tcl Extension Architecture определяет стандартный набор действий, или целевых инструкций make. Стандартные целевые инструкции описаны в табл. 48.4. При построении программ необходимо убедиться в работоспособности компилятора. В процессе выполнения сценарий configure выводит сообщения о свойствах платформы. Появление одного из следующих сообщений означает наличие проблемы: checking for cross compiler ... yes checking if compiler works ... no
Глава 48. Компиляция Tcl и программных расширений 991 Любое из приведенных выше сообщений означает, что configure не может найти работоспособный компилятор. В первом случае он предполагает, что вы выполнили конфигурацию в целевой системе, но осуществляете перекрестную компиляцию. Во втором случае попытка компиляции небольшой программы, используемой в качестве примера, окончилась неудачей. В любой из этих ситуаций полученный файл Makef ile непригоден. Если перекрестная компиляция часто применяется для специализированных процессоров, то в системах Unix и Windows необходимость в ней практически никогда не возникает. Приведенные выше сообщения появляются лишь тогда, когда системные настройки не позволяют найти компилятор. Многие производители Unix не снабжают свои системы компиляторами. При работе в подобной системе можно воспользоваться компилятором gcc, который адаптирован практически ко всем версиям Unix. Пакет gcc, готовый для выполнения на вашей платформе, вы можете найти в Internet. В системе Windows построение Tcl осуществляется либо с помощью свободно распространяемого компилятора mingw, либо посредством Microsoft Visual C++. Файл vcvars32.bat позволяет установить среду выполнения так, что появляется возможность запустить компилятор Microsoft из командной строки. Вам следует прочитать содержимое файла и настроить соответствующим образом окружение. Стандартные опции программы configure В табл. 48.3 показаны стандартные опции Tcl-сценария configure. Они реализованы посредством конфигурационных файлов (aclocal .m4 и Tcl .ш4), которые вы можете использовать в собственных сценариях. Средства, предоставляемые tcl.m4, будут подробно описаны далее в этой главе. Таблица 48.3. Стандартные опции сценария configure --pref 1х=каталог Определяет корневой каталог в иерархии инсталляционных каталогов. По умолчанию принимается каталог /usr/local —exec -pref ix=Karajior Определяет корневой каталог области, предназначенной для хранения платформенно-ориентиро- ванных файлов. По умолчанию используется значение опции --prefix. В качестве примера значения данной опции можно привести /usr/local/ Solaris-spare --enable-gcc Использование компилятора gcc вместо системного компилятора, применяемого по умолчанию
992 Часть VI. Программирование на языке С Окончание табл. 48.3 --disable-shared Запрещает генерацию разделяемых библиотек и Tcl-оболочек, динамически связанных с ними. Вместо этого создаются статически связанные оболочки и статические архивы --enable-symbols Задает компиляцию с учетом отладочных символов --enable-threads Задает поддержку многопотокового выполнения --ыз.1;11--Ьс1=каталог Указывает каталог для построения Tcl --ч±ЪЬ.-^к=каталог Указывает каталог для построения Тк --with-tclinclude=xaTajror Задет каталог, содержащий tcl.h --with-tcllib=Kara#or Задает каталог, содержащий двоичную библиотеку Tcl(например, libtclstubs.a) --и1"Ы1-х11:111с]лк1е=кагалог Указывает каталог, содержащий XII .h - -with-xl 1ПЬ=кагалог Задает каталог, содержащий двоичную библиотеку XII (например, libXll.6.0.so) Для каждой опции, в имени которой присутствует disable или enable, существует опция, противоположная ей. В табл. 48.3 приведены только средства, позволяющие изменить установку по умолчанию, поэтому если вы хотите отменить какую-либо из установок, описанных в таблице, вам достаточно не указывать соответствующую опцию. Например, если при построении Tcl в системе Solaris с использованием компилятора gcc вы хотите включить разделяемые библиотеки, символы отладки и поддержку многопотокового выполнения, используйте следующую команду: configure --prefix=/home/welch/install \ --exec-prefix=/home/welch/install/solaris \ --enable-gcc --enable-threads --enable-symbols Разработанные вами исходные коды надо согласовывать с исходны- ^ ми кодами Tcl. Созданные вами программы будут работать наилучшим образом в том случае, если вы разместите их в общем каталоге. При компиляции вам следует задавать для расширений те же опции, что и для Tcl. В частности, следует использовать одинаковые значения опций --prefix и --exec-prefix, чтобы все программы были инсталлированы одинаково. Если дерево каталогов с вашими исходными кодами не соответствует дереву каталогов Tcl, надо указывать опцию --with-tclinclude или --with-tcllit
Глава 48. Компиляция Tcl и программных расширений 993 так, чтобы файлы заголовков и библиотеки могли быть найдены при компиляции. Обычно подобная ситуация возникает тогда, когда вы строите расширения в своем рабочем каталоге и используете при этом копии средств Tcl, инсталлированных системным администратором. Опции --with-xllinclude и --with-xlllib необходимы при построении Тк в случае, если средства XII инсталлированы в нестандартных позициях файловой системы. Инсталляция При инсталляции главный каталог задается с помощью опции --prefix (например, таким способом можно указать каталог /home/welch/install). Каталоги, перечисленные в табл. 48.2, являются подкаталогами этого каталога. Если вы не укажете опцию --exec-prefix, все ваши илатформенно- ориентированные двоичные файлы будут находится в каталогах bin и lib. Например, программа tclsh8.4 и разделяемая библиотека libtcl8.4.so будут установлены следующим образом: /home/welch/install/bin/tclsh8.4 /home/welch/install/lib/libtclsh8.4.so Библиотеки сценариев и страницы справочной системой разместятся так, как показано ниже. /home/welch/install/lib/tcl8.4 /home/welch/install/man Если вам надо инсталлировать средства для нескольких различных платформ, значение опции --exec-prefix должно быть уникальным для каждой платформы. Например, если вы укажете опцию --exec-prefix=/home/ welch/install/freebsd, программа tclsh8.4 и разделяемая библиотека libtcl8.4.so разместятся так: /home/welch/install/freebsd/bin/tclsh8.4 /home/welch/install/freebsd/lib/libtclsh8.4.so Библиотеки сценариев и страницы справочной системы останутся в позициях, указанных выше, и их можно будет использовать на всех платформах. Следует заметить, что в Windows двоичные библиотеки размещаются по-другому. Они вместе с исполняемыми программами находятся в каталоге архив/bin. Использование библиотек-заглушек При использовании расширений возникает проблема, связанная с тем, что расширения компилируются для конкретной версии Tcl. При появлении но-
994 Часть VI. Программирование на языке С вых реализаций Tcl приходится перекомпилировать расширения. Это необходимо по двум причинам. Во-первых, API библиотеки Tcl С имеет тенденцию изменяться. Изменения в таблице символов привязывает расширение к конкретной версии библиотеки Tcl. Во-вторых, если вы выполняете статическую компиляцию tclsh, а затем пытаетесь динамически загрузить библиотеку, то при работе tclsh могут возникать ошибки. Причина состоит в том, что некоторые системы не поддерживают обратные ссылки. Поль Даффин (Paul Duffin) предложил механизм библиотек-заглушек (stub library), позволяющий решить эти проблемы. Предложенный подход состоит в создании двух двоичных библиотек: главной библиотеки (например, libtcl8.4.so) и библиотеки-заглушки (например, libtclstub.a). Весь основной код находится в главной библиотеке. Библиотека-заглушка представляет собой большую таблицу переходов, которая содержит адреса функций в главной библиотеке. Обращение к расширениям осуществляется посредством таблицы переходов. Благодаря косвенному обращению расширение становится нечувствительным к изменениям библиотеки Tcl. Такой подход также позволяет решить проблему обратных ссылок. Если вам кажется, что подобное решение приводит к напрасному расходованию ресурсов, вспомните, что аналогичные действия выполняет операционная система при работе с разделяемыми библиотеками. Для того чтобы ваше расширение использовало библиотеки-заглушки, вам надо указать соответствующие опции при компиляции и добавить новый вызов к процедуре Init расширения (например, Sample_Init). Опция TCL_USE_STUBS оформляет обращения к Tcl С API в виде макросов, использующих таблицу библиотеки-заглушки. Вызов Tcl_InitStubs обеспечивает инициализацию таблицы переходов, поэтому целесообразно обращаться к Tcl_InitStubs в начале процедуры Init. Типичный вызов Tcl_InitStubs выглядит следующим образом: if (Tcl_InitStubs(interp, "8.1", 0) == NULL) { return TCL.ERROR; } Подобно Tcl.PkgRequire, при вызове Tcl_InitStubs запрашивается минимальный номер версии Tcl. Библиотеки-заглушки поддерживаются начиная с Tcl 8.1, и API обеспечивает обратную совместимость. Если вы не используете новые С API, объявленные в последних версиях, вам следует указывать самую старую версию Tcl. Этим вы обеспечите совместимость с большим количеством приложений.
Глава 48. Компиляция Tcl и программных расширений 995 Использование autoconf Инструмент autoconf использует макропроцессор ш4 для преобразования шаблона configure, in в сценарий configure. Полученный сценарий запускается под управлением оболочки /bin/sh. Создание шаблона configure.in упрощает стандартная макробиблиотека т4, распространяемая вместе с autoconf. Кроме того, в дистрибутивном пакете Tcl находится файл Tcl .m4, в котором содержатся дополнительные макросы autoconf. Данные макросы поддерживают стандартные опции configure, описанные в табл. 48.3. Создание макросов может потребовать затраты больших усилий. Создание шаблонов configure — сложная задача, при решении которой легко допустить ошибку. Данный шаблон предполагает несколько уровней макрообработки: макросы т4 в configure.in, переменные оболочки в configure, подстановка autoconf в Makefile.in и переменные Makefile. Попытавшись изменить конфигурационные файлы, вы наверняка оцените простоту языка Tcl! В настоящее время в распоряжение разработчика предоставляются стандартный набор Tcl-ориентированных макросов autoconf и пример расширения, использующего их. Редактируя шаблоны configure, in и Makefile, in, вы можете не обращать внимания на детали, не требующие вашего вмешательства. Файл tcl.m4 В дистрибутивный пакет Tcl входят файлы tcl.m4 и aclocal.m4. Программа autoconf читает содержимое файла aclocal.m4, находящегося в том же каталоге, что и шаблон configure, in. В простейшем случае файл aclocal.m4 лишь включает файл tcl.m4. В примере расширения, поставляемом со спецификацией TEA, файл Tcl .m4 находится в подкаталоге Tclconf ig. Файл tcl.m4 определяет макросы, имена которых начинаются с TEA. Имена стандартных макросов autoconf начинаются с АС. В данной книге не приводится исчерпывающее описание макросов autoconf. Назначение наиболее важных из них объясняется при рассмотрении примера расширения. Файл tcl.m4 заменил tclConf ig. sh, присутствовавший в предыдущих версиях Tcl. (Файл tclConf ig.sh до сих пор генерируется в Tcl 8.4 сценарием configure, но он не рекомендован к применению.) Файл tclConf ig.sh создавался для того, чтобы перехватывать некоторые важные установки, выполняемые при настройке Tcl, и включения их в сценарии configure для расширений. Однако при настройке приложений эти установки лучше повторить, поскольку для построения Tcl и расширений могут использоваться различные компьютеры. Таким образом, вместо включения tclConf ig.sh
996 Часть VI. Программирование на языке С в сценарий configure расширения лучше использовать в configure.in TEA- макросы, определенные в файле tcl.m4. Создание шаблонов Программа autoconf реализует также механизм макросов для шаблонов Makefile, in. При получении информации о системе сценарий configure устанавливает переменные оболочки. Эти переменные используются для преобразования Makefile.in в рабочий файл Makefile. Для подстановки в Makefile, in используются следующие выражения: <&имя_ пер еменной @ Например, значение опции --prefix помещается в переменную оболочки prefix, а затем используется для подстановки в выражение @prefix@ в шаблоне Makefile. in. Приведенное ниже выражение, содержащееся в файле Makefile, in, передает в Makefile значение TCL.LIBRARY, определенное посредством configure. TCL.LIBRARY = @TCL_LIBRARY@ Макрос AC.SUBST указывает, какие переменные оболочки должны подставляться в шаблоне Makefile, in. Пример такого макроса приведен ниже. AC.SUBST(TCL.LIBRARY) Пример расширения В данном разделе описывается пример расширения, распространяемый в составе спецификации Tcl Extension Architecture (TEA). Целью работы над TEA было создание стандарта, упрощающего написание, инсталляцию и использование Tcl-расширений. Пример Tcl-расширения записан на компакт- диске, прилагаемом к данной книге. Его также можно найти в Web по следующему адресу: ftp://ftp.tcl.tk/pub/tcl/examples/tea/ В Web также находится соответствующая документация: http://www.tcl.tk/software/tcltk/tea/ Рассматриваемое здесь расширение хранится в CVS в составе модуля samplextension. Если вам нужен доступ к последним версиям исходного кода Tcl, вам надо иметь информацию о хранилище С VS. Нужные сведения можно получить, ознакомившись со следующей Web-страницей: http://www.tcl.tk/software/tcltk/netcvs.html
Глава 48. Компиляция Тс! и программных расширений 997 Расширение, рассматриваемое в качестве примера, реализует защищенный алгоритм хэширования (Secure Hash Algorithm — SHA1.). Исходный С-код SHA1 был написан Стивом Рейдом (Steve Reid), а Дейв Дейкста (Dave Dyk- stra) создал Tcl-интерфейс для него. Майкл Томас (Michael Thomas) разработал шаблоны configure и Makefile, а Джефф Хоббс (Jeff Hobbs) оформил расширение в качестве примера для ТЕА2. Вместо исходного имени shal для данного примера используется более универсальное имя sample. Это имя присутствует в именах файлов, библиотек и пакета. Адаптируя шаблоны для расширения, вам надо заменить все вхождения ''sample" выбранным вами именем. Файлы примера хорошо прокомментированы, и фрагменты, требующие внесения изменений, сразу заметны. Файл configure.in В файле configure, in содержится шаблон для сценария configure. Этот файл снабжен подробными комментариями. Фрагменты, которые должны быть модифицированы, помечены как CHANGE . Первый макрос, требующий изменения, выглядит так: AC_INIT(generic/sample.h) Макрос AC_INIT определяет файлы, являющиеся частью дистрибутивного пакета. Имена задаются относительно расположения файла configure, in. В зависимости от местонахождения configure, in, макросу может быть, например, передан параметр . ./generic/tcl.h или src/mylib.h. Данный макрос необходим для поддержки построения пакетов в различных каталогах (например, в tcl8.4/unix и tcl8.4/unix/solaris). Помимо макроса ACLINIT, в файле configure, in присутствует набор переменных, с помощью которых определяются имя пакета и номер версии. PACKAGE = sample MAJOR.VERSION = О MIN0R_VERSION = 4 PATCH.LEVEL = Имя пакета определяет имена файлов, генерируемых Makefile. Эти имена также присутствуют в некоторых переменных. Вам необходимо заменить все ссылки на "sample" именем вашего пакета. Версия определяется по трехуровневой схеме, однако третий уровень может не использоваться, поэтому переменной PATCH_LEVEL не обязательно присваивать значение. Если вы собираетесь задать значение этой переменной, оно должно начинаться с точки или с символа р. Значения переменных объединяются так, как показано ниже, формируя номер версии.
998 Часть VI. Программирование на языке С VERSION = ${MAJOR_VERSION}.${MINOR_VERSION}${PATCH.LEVEL} В файле configure, in содержатся определения разделяемых библиотек (например, sample04.dll, libsample.0.4.so, sample.0.2.shlib и т.д.). Следует изменить макросы так, чтобы имена библиотек соответствовали имени вашего пакета. Если вы хотите, чтобы была сгенерирована библиотека-заглушка, вам надо определить samplestub_LIB_FILE. AC_SUBST(sample_LIB_FILE) AC_SUBST(samplestub_LIB_FILE) В файле configure, in содержится ряд стандартных макросов TEA, которые расширяются в набор правил, определяющих использование компилятора и другие установки. Большинство из них можно оставить в неизменном виде. Изменения приходится вносить лишь при создания расширений Тк или в том случае, когда используются внутренние файлы заголовков Tcl или Тк. Так, например, вам может понадобиться добавить TEA_PATH_TKCONFIG и TEA_L0AD_TKC0NFIG и сделать выбор между TEA_PUBLIC_TCL_HEADERS и TEA_PRIVATE_TCL_HEADERS, а также выбрать из TEA_PUBLIC_TK_HEADERS и TEA_PRIVATE_TK_HEADERS. Использовать приватные заголовки (например, tcllnt.h) не рекомендуется. В файле configure, in существует также платформенно-ориентирован- ный раздел, в котором можно задать макросы CLEANFILES и EXTRA_S0URCES с учетом ваших потребностей. В этом же разделе определен макрос BUILD_sample для Windows. В системе Windows компиляторы создают специальный тип разделяемых библиотек (т.е. DLL). Если вы компилируете саму библиотеку, вам надо объявлять ее функции одним способом. При компиляции кода, использующего библиотеку, те же функции объявляются по- другому. Это усложняет структуру файла sample.h. Однако сложность оказывается скрытой от разработчика за макросом BUILD.sample. Последний макрос в configure, in определяет, какие шаблоны должны обрабатываться сценарием configure. Следующая директива указывает на то, что фал Makefile генерируется на основе шаблона Makefile.in: AC_0UTPUT([Makefile]) Файл Makefile.in Шаблон Makefile, in преобразуется сценарием configure в файл Makefile. В рассматриваемом примере Makefile, in снабжен подробными комментариями, поэтому найти фрагменты, требующие внесения изменений, нетрудно. В именах некоторых переменных имеется последовательность символов "sample". В частности, sample_LIB_FILE соответствует имени перемен-
Глава 48. Компиляция Tcl и программных расширений 999 ной в сценарии configure. Модифицируя имя, надо соответствующим образом изменить оба файла: sampleJLIBJFILE = @sample_LIB_FILE@ Выражение <&имя_переменной<& используется для подстановки платфор- менно-ориентированного имени (например, libsample.dll или libsample.so). Вам необходимо определить набор исходных файлов и соответствующих объектных файлов, которые являются частью библиотеки. В рассматриваемом примере sample.с реализует базовые средства алгоритма защищенного хэширования, а в файле tclsample.c находится реализация командного интерфейса Tcl. sample_SOURCES = sample.с tclsample.c @EXTRA_SOURCES@ Для определения объектных файлов используется переменная OBJEXT: . о для Unix и .obj для Windows. sample_OBJECTS = $(sample_SOURCES:.c=.@0BJEXT@) Файлы заголовков задаются с помощью переменной GENERIC.HDRS. Переменная sredir определяется при выполнении configure и указывает на каталог, в котором содержатся файлы, заданные с помощью макроса AC.INIT. GENERIC.HDRS = $(sredir)/generic/sample.h Пример файла Makefile включает несколько стандартных целевых инструкций. Если даже вы решите не использовать пример шаблона Makefile, in, вы все же должны определить целевые инструкции для того, чтобы обеспечить ТЕА-совместимость вашего приложения. Возможность автоматизации построения программ зависит от расширений, реализующих стандартные целевые инструкции. Целевые инструкции могут быть пустыми, но они должны быть определены так, чтобы при их использовании программа make работала корректно. Таблица 48.4. Стандартные целевые инструкции Makefile all Создает целевые инструкции в следующем порядке: binaries, libraries, doc binaries Выполняет построение исполняемых программ и двоичных библиотек (например, DLL) libraries Создает платформенно-независимые библиотеки doc Генерирует файлы с документацией install Создает целевые инструкции в следующем порядке: install-binaries, install-libraries, install-doc install-binaries Инсталлирует программы и двоичные библиотеки
1000 Часть VI. Программирование на языке С Окончание табл. 48.4 install-libraries Инсталлирует библиотеки сценариев ins tall-do с Инсталлирует файлы с документацией test Запускает тестовый пакет depend Генерирует сведения о зависимостях clean Удаляет файлы, созданные в процессе построения программ distclean Удаляет файлы, созданные во время настройки Стандартные файлы заголовков В данном разделе рассматривается применение символов, определенных в двоичной библиотеке. Этот вопрос приходится решать при использовании компиляторов в системе Windows; символы должны быть явным образом импортированы и экспортированы. При создании библиотеки производится экспортирование символов. При связывании программы с библиотекой символы импортируются. В системе Windows переменная BUILD_sample определяется при построении библиотеки. Данная переменная не должна быть определена в Unix. В файле заголовка переменная BUILD_sample используется следующим образом: #ifdef BUILD.sample #undef TCL_STORAGE_CLASS #define TCL_STORAGE_CLASS DLLEXPORT #endif /* BUILD.sample */ Переменная TCL_STORAGE_CLASS используется в определении макроса EXTERN. Макрос EXTERN указывается перед прототипом любой экспортируемой функции. EXTERN int Sample_Init _ANSI_ARGS_((Tcl_Interp *Interp)); Макрос _ANSI_ARGS_ применяется при работе со старыми С-компилято- рами, которые не поддерживают прототипы. Использование расширения Расширение, рассматриваемое в качестве примера, можно настраивать, компилировать и инсталлировать, не модифицируя. В системе Solaris двоичная библиотека называется sajnple0.4. so, а на компьютере под управлением Windows NT аналогичная библиотека носит название sample04.dll. Пакет имеет имя Tclshal и реализует Tcl-команду shal. Если для копирования двоичной библиотеки в стандартную позицию на вашем узле применяется
Глава 48. Компиляция Tcl и программных расширений 1001 команда make install, то вы можете использовать пакет из Тс1-программы следующим образом: package require Tclshal shal -string "some string" Команда shal возвращает хэш-функцию входной строки, закодированную 128 битами. В shal предусмотрен ряд опций. Информацию о них можно получить в справочном руководстве, являющемся частью расширения.
Глава 49 Создание компонентов Тк на языке С В данной главе описывается простой компонент, отображающий на экране часы. Мы рассмотрим два варианта реализации компонента: с использованием исходного строкового командного интерфейса и интерфейса Tcl_0bj. Г\омпоНЕНТЫ, написанные на языке С, отличаются эффективностью и гибкостью. Однако для их создания приходится прилагать большие усилия. В данной главе объясняется структура компонента clock, отображающего время в виде форматированной строки. В простейшем случае решить подобную задачу можно с помощью компонента label и команды clock, предусмотрев периодическое обновление содержимого компонента посредством команды after. Однако основная цель данного примера — показать не возможности Tcl, а проиллюстрировать особенности написания компонента Тк на языке С. Реализация данного компонента включает следующие элементы. • Структура данных, описывающая экземпляр компонента. • Процедура класса для создания нового экземпляра компонента. • Процедура экземпляра для выполнения действий с экземпляром компонента. • Набор конфигурационных параметров для компонента. • Конфигурационная процедура, используемая при создании компонента и изменении его конфигурации. • Процедура поддержки событий. • Процедура отображения. • Прочие процедуры, специфические для конкретного компонента.
Глава 49. Создание компонентов Тк на языке С 1003 В данной главе сравниваются две реализации: одна на базе строк, другая — основанная на применении объекта Tcl_0bj. Версия, использующая значения Tcl_0bj, позволяет более эффективно интерпретировать опции командной строки. В первую очередь мы рассмотрим строковую версию каждой процедуры, а затем -- версию, использующую Tcl_0bj. Фрагменты кода, отвечающие за отображение, одинаковы для обеих версий. Инициализация расширения Компонент оформлен как расширение, пригодное для динамической загрузки в wish. В листинге 49.1 показан код процедуры Clock_Init. Эта процедура регистрирует команды clock и oclock, которые используют соответственно строковой интерфейс и Tcl_0bj. Данная процедура также инициализирует таблицу переходов заглушки (см. главу 48) и объявляет пакет так, что сценарии могут загружать его с помощью команды package require. Листинг 49.1. Процедура Clock__Init int ClockCmd(ClientData clientData, Tcl_Interp *interp, int argc, CONST char *argv[]); int ClockObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_0bj *C0NST objv[]); void ClockObjDelete(ClientData clientData); /* * Clock_Init вызывается при загрузке пакета. */ int Clock_Init(Tcl_Interp *interp) { if (Tcl_InitStubs(interp, "8.Iм, 0) == NULL) { return TCL_ERR0R; } Tcl_CreateCommand(interp, "wclock", ClockCmd, (ClientData)NULL, (Tcl.CmdDeleteProc *)NULL); Tcl_CreateObjCommand(interp, "oclock", ClockObjCmd, (ClientData)NULL, ClockObjDelete); Tcl_PkgProvide(interp, "Tkclock", "1.0"); return TCL_0K; }
1004 Часть VI. Программирование на языке С Структура данных компонента Каждому компоненту соответствует описывающая его структура данных. В любой подобной структуре должны содержаться указатель на интерпретатор Tcl, окно Тк и дисплей. Интерпретатор используется при обращении к библиотекам Tcl и Тк, а также обеспечивает вызов сценариев, определение текущего содержимого Tcl-переменных и установку новых значений. Окно Тк необходимо для выполнения различных команд Тк, а дисплей используется для поддержки графических операций низкого уровня. Остальные данные в структуре определяются особенностями конкретного компонента. Структура данных для компонента, отображающего часы, показана в листинге 49.2. Листинг 49.2. Структура данных компонента clock #include "tk.h" #include <sys/time.h> typedef struct { Tk_Window tkwin; /* Окно для компонента */ Display *display; /* Дескриптор дисплея */ Tcl_Interp *interp; /* Интерпретатор для компонента */ Tcl_Command widgetCmd; /* Команда экземпляра */ Tk_OptionTable optionTable; /* Используется для разбора опций */ /* * Атрибуты, специфические для компонента clock. */ int borderWidth; /* Размер обрамления с имитацией трехмерных эффектов */ Tcl_0bj *borderWidthPtr; /* Исходное значение строки */ int relief; /* Стиль обрамления с имитацией трехмерных эффектов */ Tk_3DBorder background; /* Цвет для обрамления и фона */ XColor *foreground; /* Цвет для текста */ XColor *highlight; /* Цвет для подсветки */ XColor *highlightBg; /* Цвет для нейтральной подсветки */ int highlightWidth; /* Толщина контура подсветки */ Tcl_0bj *highlightWidthPtr; /* Исходное значение строки */ Tk_Font tkfont; /* Информация о шрифте */ char *format; /* Формат строки для отображения времени */ /* * Графический контекст и поддержка других возможностей.
Глава 49. Создание компонентов Тк на языке С 1005 */ GC textGC; /* Текст графического контекста */ Tk_TimerToken token; /* Дескриптор периодического обратного вызова */ char *clock; /* Указатель на строку отображения времени */ int mimChars; /* Длина текста */ int textWidth; /* задается в пикселях */ Tcl__0bj *widthPtr; /* Исходная ширина строки */ int textHeight; /* задается в пикселях */ Tcl_0bj *heightPtr; /* Исходная высота строки */ int padX; /* Горизонтальное дополнение */ Tcl_0bj *padXPtr; /* Исходное строковое значение padX */ int padY; /* Вертикальное дополнение */ Tcl__0bj *padYPtr; /* Исходное строковое значение padY */ int flags; /* Флаги, описанные ниже */ } Clock; /* * Flag bit definitions. */ #define REDRAW_PENDING Oxl #define G0T_F0CUS 0x2 #define TICKING 0x4 Команда класса компонента Tcl-команда, которая создает экземпляр компонента, называется командой класса. В нашем примере для создания компонента, отображающего часы, используется команда clock. В листинге 49.3 представлена командная процедура для команды clock. Процедура выделяет память для структуры данных Clock. Далее в этой процедуре регистрируется обработчик события, который вызывается при отображении или изменении размеров компонента, а также тогда, когда компонент получает фокус ввода. После регистрации обработчика событий создается Tcl-команда, предназначенная для выполнения действий с компонентом. И наконец, в командной процедуре вызывается ClockConf igure, в результате чего осуществляется настройка компонента в соответствии с атрибутами, указанными в командной строке, и установками, принятыми по умолчанию.
1006 Часть VI. Программирование на языке С Листинг 49.3. Командная процедура ClockCmd int ClockCmd(clientData, interp, argc, argv) ClientData clientData; /* Главное окно приложения */ Tcl_Interp *interp; /* Текущий интерпретатор. */ int argc; /* Число параметров */ CONST char **argv; /* Строки параметров */ { Tk_Window main = (Tk.Window) clientData; Clock *clockPtr; Tk_Window tkwin; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be V", argv[0], " pathName ?options?\"", (char *) NULL); return TCL.ERROR; } tkwin = Tk_CreateWindowFromPath(interp, main, argv[l], (char *) NULL); if (tkwin == NULL) { return TCL.ERROR; } /* * Установка класса ресурсов. */ Tk__SetClass(tkwin, "Clock"); /* * Вьщеление памяти и инициализация записи для компонента. */ clockPtr = (Clock *) Tcl_Alloc(sizeof(Clock)); clockPtr->tkwin = tkwin; clockPtr->display = Tk.Display(tkwin); clockPtr->interp = interp; clockPtr->borderWidth = 0; clockPtr->highlightWidth = 0; clockPtr->relief = TK_RELIEF_FLAT; clockPtr->background = NULL; clockPtr->foreground = NULL; clockPtr->highlight = NULL; clockPtr->highlightBg = NULL;
Глава 49. Создание компонентов Тк на языке С 1007 clockPtr->tkfont = NULL; clockPtr->textGC = None; clockPtr->token = NULL; clockPtr->clock = NULL; clockPtr->format = NULL; clockPtr->numChars = 0; clockPtr->textWidth = 0; clockPtr->textHeight = 0; clockPtr->padX = 0; clockPtr->padY = 0; clockPtr->flags = 0; /* * Регистрация обработчика событий, связанных с * отображением и изменением размеров. */ Tk_CreateEventHandler(clockPtr->tkwin, ExposureMaskIStructureNotifyMaskIFocusChangeMask, ClockEventProc, (ClientData) clockPtr); /* * Создание Tcl-команды для выполнения действий с компонентом. */ clockPtr->widgetCmd = Tcl_CreateCommand(interp, Tk_PathName(clockPtr->tkwin), ClocklnstanceCmd, (ClientData) clockPtr, (void (*)()) NULL); /* * Разбор параметров командной строки. */ if (ClockConfigure(interp, clockPtr, argc-2, argv+2, 0) != TCL_0K) { Tk_DestroyWindow(clockPtr->tkwin); return TCL_ERR0R; } Tcl_SetResult(interp, Tk_PathName(clockPtr->tkwin), TCL.VOLATILE); return TCL_0K; } Версия Tcl_0bj ClockObjCmd выполняет дополнительные действия по настройке таблицы опций, используемой для эффективного разбора командной строки. Таблица опций создается при первом обращении к команде clock.
1008 Часть VI. Программирование на языке С Указатель clientData для ClockObjCmd первоначально имеет значение NULL. Он применяется для организации хранения инициализированной таблицы опций. Если ClockCmd использует clientData для хранения ссылки на главное окно Тк, то ClockObjCmd получает эту ссылку с помощью процедуры Tk_MainWindow. Листинг 49.4. Командная процедура ClockObjCmd int ClockObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Главное окно приложения */ Tcl_Interp *interp; /* Текущий интерпретатор. */ int objc; /* Число параметров */ Tcl_0bj **objv; /* Значения параметров */ { Tk_0ptionTable optionTable; Clock *clockPtr; Tk_Window tkwin; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); return TCL_ERR0R; } optionTable = (Tk.OptionTable) clientData; if (optionTable == NULL) { Tcl_CmdInfo info; char *name; /* * При создании компонента clock происходит * инициализация таблицы опций. */ optionTable = Tk_CreateOptionTable(interp, optionSpecs); name = Tcl_GetString(objv[0]); Tcl_GetCommandInfo(interp, name, feinfo); info.objClientData = (ClientData) optionTable; Tcl_SetCommandInfо(interp, name, &info); } tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), Tcl_GetString(objv[l]), (char *) NULL); if (tkwin == NULL) {
Глава 49. Создание компонентов Тк на языке С 1009 return TCL_ERR0R; } /* * Установка класса ресурсов. */ Tk_SetClass(tkwin, "Clock"); /* * Выделение памяти и инициализация записи для компонента. */ clockPtr = (Clock *) ckalloc(sizeof(Clock)); clockPtr->tkwin = tkwin; clockPtr->display = Tk_Display(tkwin); clockPtr->interp = interp; clockPtr->optionTable = optionTable; clockPtr->borderWidth = 0; clockPtr->borderWidthPtr = NULL; clockPtr->highlightWidth = 0; clockPtr->highlightWidthPtr = NULL; clockPtr->relief = TK_RELIEF_FLAT; clockPtr->background = NULL; clockPtr->foreground = NULL; clockPtr->highlight = NULL; clockPtr->highlightBg = NULL; clockPtr->tkfont = NULL; clockPtr->textGC = None; clockPtr->token = NULL; clockPtr->clock = NULL; clockPtr->format = NULL; clockPtr->numChars = 0; clockPtr->textWidth = 0; clockPtr->widthPtr = NULL; clockPtr->textHeight = 0; clockPtr->heightPtr = NULL; clockPtr->padX = 0; clockPtr->padXPtr = NULL; clockPtr->padY = 0; clockPtr->padYPtr = NULL; clockPtr->flags = 0; /* * Регистрация обработчика событий, связанных с * отображением и изменением размеров.
1010 Часть VI. Программирование на языке С */ Tk_CreateEventHandler(clockPtr->tkwin, ExposureMaskIStructureNotifyMaskIFocusChangeMask, ClockEventProc, (ClientData) clockPtr); /* * Создание Tcl-команды для выполнения действий с компонентом. */ clockPtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(clockPtr->tkwin), ClocklnstanceObj Cmd, (ClientData) clockPtr, (void (*)()) NULL); /* * Разбор параметров командной строки. */ if ((Tk_InitOptions(interp, (char *)clockPtr, optionTable, tkwin) != TCL_0K) I I (ClockObj Configure(interp, clockPtr, objc-2, objv+2, 0) != TCL.OK)) { Tk_DestroyWindow(clockPtr->tkwin); return TCL.ERROR; } Tcl_SetStringObj(Tcl_GetObjResult(interp), Tk_PathName(clockPtr->tkwin), -1); return TCL_0K; } Команда экземпляра компонента Для каждого экземпляра компонента создается новая команда, с помощью которой выполняются действия с этим компонентом. Такая команда называется командой экземпляра компонента. В качестве имени команды используется путь к компоненту. В нашем примере все действия с компонентом сводятся к запросу и изменению значений атрибутов. Основную работу выполняют процедуры Tk_Conf igureWidget и ClockConf igure, которые будут рассматриваться в следующем разделе. Код командной процедуры ClocklnstanceCmd показан в листинге 49.5. Листинг 49.5. Командная процедура ClocklnstanceCmd static int ClocklnstanceCmd(clientData, interp, argc, argv)
Глава 49. Создание компонентов Тк на языке С 1011 ClientData clientData; /* Указатель на структуру Clock */ Tcl.Interp *interp; /* Интерпретатор */ int argc; /* Число параметров */ CONST char *argv[]; /* Параметры командной строки */ { Clock *clockPtr = (Clock *)clientData; int result = TCL_0K; char c; int len; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \,MI, argv[0], " option ?arg arg ...?\П\ (char *) NULL); return TCL_ERR0R; } с = argv[l] [0] ; len = strlen(argv[l]); if ((с == 'с') && (strncmp(argv[l], "cget", len) == 0) && (len >= 2)) { if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " cget option\"", (char *) NULL); return TCL.ERROR; } result = Tk_ConfigureValue(interp, clockPtr->tkwin, configSpecs, (char *) clockPtr, argv[2], 0); } else if ((c == >с') && (strncmp(argv[l], "configure", len) == 0) && (len >= 2)) { if (argc == 2) { /* * Возврат всех сведений о конфигурации. */ result = Tk_ConfigureInfo(interp, clockPtr->tkwin, configSpecs, (char *) clockPtr, (char *) NULL,0); } else if (argc == 3) { /* * Возврат сведений об одном атрибуте. */ result = Tk_ConfigureInfo(interp, clockPtr->tkwin,
1012 Часть VI. Программирование на языке С configSpecs, (char *) clockPtr, argv[2], 0); } else { /* * Изменение одного или нескольких атрибутов. */ result = ClockConfigure(interp, clockPtr, argc-2, argv+2,TK_C0NFIG_ARGV_0NLY); } } else { Tcl_AppendResult(interp, "bad option V", argv[l], "\": must be cget, configure, position, or size", (char *) NULL); return TCL.ERROR; } return result; } В листинге 49.6 показана процедура ClocklnstanceObjCmd. Она применяет Tk_GetIndexFromObj для отображения первого параметра в индекс, который затем используется в выражении switch. Процедуры Tk_GetOptionValue и Tk.GetOptionlnf о выполняют разбор конфигурационных опций компонента. Листинг 49.6. Командная процедура ClocklnstanceObjCmd static int ClockInstanceObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Указатель на структуру Clock */ Tcl_Interp *interp; /* Интерпретатор */ int objc; /* Число параметров */ Tcl_0bj *objv[]; /* Параметры командной строки */ { Clock *clockPtr = (Clock *)clientData; CONST char *commands[] = {"cget", "configure", NULL}; enum command {CL0CK_CGET, CL0CK_C0NFIGURE}; int result; Tcl_0bj *objPtr; int index; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); return TCL_ERR0R;
Глава 49. Создание компонентов Тк на языке С 1013 } result = Tcl_GetIndexFromObj(interp, objv[l], commands, "option", 0, feindex); if (result != TCL.OK) { return result; } switch (index) { case CL0CK_CGET: { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "cget option"); return TCL_ERR0R; } objPtr = Tk_GetOptionValue(interp, (char *)clockPtr, clockPtr->optionTable, (objc == 3) ? objv[2] : NULL, clockPtr->tkwin); if (objPtr == NULL) { return TCL_ERR0R; } else { Tcl_SetObjResult(interp, objPtr); } break; } case CLOCK.CONFIGURE: { if (objc <= 3) { objPtr = Tk_GetOptionInfо(interp, (char *) clockPtr, clockPtr->optionTable, (objc == 3) ? objv[2] : NULL, clockPtr->tkwin); if (objPtr == NULL) { return TCL_ERR0R; } else { Tcl_SetObjResult(interp, objPtr); } } else { /* * Изменение одного или нескольких атрибутов. */
1014 Часть VI. Программирование на языке С result = ClockObjConfigure(interp, clockPtr, objc-2, objv+2); } } } return TCL_0K; } Установка и изменение значений атрибутов При создании компонента или изменении его конфигурации вступают в действие средства, необходимые для управления ресурсами, соответствующими атрибутам. Для компонента clock могут задаваться цвета и шрифты. Они описываются в графическом контексте. Вместо того чтобы формировать вызов для каждого атрибута, осуществляется инициализация графического контекста и набор параметров передается графическим командам. В контексте могут быть описаны цвет переднего плана и фона, маски отсечения, стили линий и т.д. Компонент, отображающий часы, формирует графический контекст единожды. Используется он при каждом отображении компонента. Компонент использует два типа ресурсов, связанных с управлением цветов. Подсветка фокуса и передний план текста отображаются обычным цветом. Фон компонента предполагает набор цветов для имитации трехмерных рамок. Цвет фона задается с помощью атрибута, а остальные цвета определяются исходя из заданного фона. Для определения цвета, используемого для отображения фона компонента, применяется функция Tk_3DBorderColor. После установки ресурсов повторное отображение компонента планируется на следующий период бездействия. Такой подход считается стандартным при создании компонентов Тк. Это означает, что вы можете создавать компонент в процессе выполнения сценария и изменять его конфигурацию. Все изменения проявятся в течение одной операции отображения. Флаг REDRAVLPENDING используется для того, чтобы в каждый момент времени в очереди не было более одной операции повторного отображения. Код процедуры ClockConf igure приведен в листинге 49.7. Листинг 49.7. Управление ресурсами компонента с помощью функции ClockConf igure static int ClockConfigure(interp, clockPtr, argc, argv, flags) Tcl_Interp *interp; /* Для возвращаемых значений и ошибок */ Clock *clockPtr; /* Структура данных для экземпляра */ int argc; /* Число пунктов argv */
Глава 49. Создание компонентов Тк на языке С 1015 char *argv[]; /* Параметры командной строки */ int flags; /* Флаги Tk_ConfigureWidget */ { XGCValues gcValues; GC newGC; /* * Tk.ConfigureWidget выполняет разбор параметров командной * строки и ищет значения по умолчанию в базе данных ресурсов. */ if (Tk_ConfigureWidget(interp, clockPtr->tkwin, configSpecs, argc, argv, (char *) clockPtr, flags) != TCL.OK) { return TCL_ERR0R; } /* * Компоненту передается цвет фона по умолчанию. В * противном случае между первоначальным выводом * с помощью Х-сервера и отображением из программы * мог бы выводиться произвольный цвет */ Tk_SetWindowBackground(clockPtr->tkwin, Tk_3DBorderColor(clockPtr->background)->pixel); /* * Установка графического контекста для отображения компонента. * Контекст используется для рисования карт пикселей, * поэтому оповещение об отображении отключается. */ gcValues.background = Tk_3DBorderColor(clockPtr->background)->pixel; gcValues.foreground = clockPtr->foreground->pixel; gcValues.font = Tk^FontId(clockPtr->tkfont); gcValues. graphics__exposures = False; newGC = Tk_GetGC(clockPtr->tkwin, GCBackgroundIGCForegroundIGCFontIGCGraphicsExposures, fegcValues); if (clockPtr->textGC != None) { Tk_FreeGC(clockPtr->display, clockPtr->textGC); } clockPtr->textGC = newGC; /*
1016 Часть VI. Программирование на языке С * Определение размеров, запрашиваемых компонентом. */ ComputeGeometry(clockPtr); /* * Обращение к дисплею. */ if ((clockPtr->tkwin != NULL) && Tk_IsMapped(clockPtr->tkwin) && !(clockPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr); clockPtr->flags |= REDRAW_PENDING; } return TCL.OK; 2 В листинге 49.8 показан код процедуры ClockObjConfigure. Интерфейс Tk.SetOptions, используемый для установки полей структуры данных Clock, может стать причиной возникновения проблемы. Не исключено, что некоторые конфигурационные опции будут заданы корректно, а другие — с ошибкой. В этом случае ClockObjConf igure отменит изменения, поэтому действия по настройке не дадут никаких результатов. Необходима обработка в два этапа, причем второй этап должен использоваться для восстановления исходных значений. Tk^Set Opt ions позволяет классифицировать изменения компонента. Биты GE0METRY_MASK и GRAPHICSJ4ASK определяются компонентом для разделения атрибутов на два класса. Графический контекст или расположение компонента на экране изменяется только при изменении атрибута соответствующего класса. Листинг 49.8. Управление ресурсами компонента с помощью функции ClockObjConf igure static int ClockObjConfigure(interp, clockPtr, objc, objv) Tcl_Interp *interp; /* Для возвращаемых значений и ошибок */ Clock *clockPtr; /* Структура данных для экземпляра */ int objc; /* Число пунктов argv */ Tcl_0bj *objv[]; /* Параметры командной строки */ { XGCValues gcValues; GC newGC; Tk_SavedOptions savedOptions; int mask, error;
Глава 49. Создание компонентов Тк на языке С 1017 Tcl_0bj *errorResult; /* * Первый раз мы устанавливаем конфигурацию на основе * параметров, заданных в командной строке. Второй проход * предназначен для восстановления конфигурации в случае * ошибки. */ for (error = 0 ; error <= 1 ; error++) { if (!error) { /* * Tk_SetOptions выполняет разбор параметров командной * строки и ищет значения по умолчанию в базе данных * ресурсов. */ if (Tk_SetOptions(interp, (char *) clockPtr, clockPtr->optionTable, objc, objv, clockPtr->tkwin, fesavedOptions, &mask) != TCL_0K) { continue; > } else { /* * Восстановление опций из сохраненных значений */ errorResult = Tcl_GetObjResult(interp); Tcl_IncrRefCount(errorResult); Tk_RestoreSavedOptions(fesavedOptions); } if (mask & GRAPHICS_MASK) { /* * Компоненту передается цвет фона по умолчанию. В * противном случае между первоначальным выводом * с помощью Х-сервера и отображением из программы * мог бы выводиться произвольный цвет */ Tk_SetBackgroundFromBorder(clockPtr->tkwin, clockPtr->background); /* * Установка графического контекста для отображения * компонента. Контекст используется для рисования
1018 Часть VI. Программирование на языке С * карты пикселей, поэтому оповещение об * отображении отключается. */ gcValues.background = Tk_3DBorderColor(clockPtr->background)->pixel; gcValues.foreground = clockPtr->foreground->pixel; gcValues.font = Tk_FontId(clockPtr->tkfont); gcValues.graphics_exposures = False; newGC = Tk_GetGC(clockPtr->tkwin, GCBackgroundIGCForegroundIGCFontIGCGraphicsExposures, fegcValues); if (clockPtr->textGC != None) { Tk_FreeGC(clockPtr->display, clockPtr->textGC); } clockPtr->textGC = newGC; } /* * Определение требуемых размеров компонента. */ if (mask & GE0METRY_MASK) { ComputeGeometry(clockPtr); } /* * Обращение к дисплею. */ if ((clockPtr->tkwin != NULL) && Tk_IsMapped(clockPtr->tkwin) && !(clockPtr->flags к REDRAW_PENDING)) { Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr); clockPtr->flags |= REDRAW_PENDING; } /* * Результаты корректны; выход и отказ от * возврата по ошибке. */ break; } if (!error) { Tk_FreeSavedOptions(fesavedOptions); return TCL_0K;
Глава 49. Создание компонентов Тк на языке С 1019 } else { Tcl_SetObjResult(interp, errorResult); TclJDecrRefCount(errorResult); return TCL.ERROR; } } Определение атрибутов компонента Некоторые поля структуры Clock представляют собой атрибуты, которые могут быть установлены при создании компонента или при изменении его конфигурации с помощью операции configure. Процедура Tk_Conf igureWidget помогает управлять значениями по умолчанию, именами ресурсов и именами классов. Эта процедура ставит в соответствие опции смещение в структуре данных компонента. Если вы используете параметр командной строки для изменения атрибута, Tk_Conf igureWidget обращается к структуре компонента и изменяет соответствующее значение. При этом поддерживаются различные типы данных, в частности информация о цвете и шрифтах. Процедура Tk_Conf igureWidget обеспечивает также выделение памяти для хранения значений. В листинге 49.9 показана структура Tk_Conf igSpec, используемая для представления информации о каждом атрибуте. Листинг 49.9. Структура Tk_Conf igSpec typedef struct Tk_ConfigSpec { int type; char *name; char *dbName; char *dbClass; char *defValue; int offset; int specflags; Tk_CustomOption *customPtr; } Tk_ConfigSpec; Первое поле определяет тип, например TK_C0NFIG_B0RDER. Следующее поле содержит опцию командной строки для атрибута (например, -background). Далее следуют имена ресурса и класса. За ними расположено значение по умолчанию (например, light blue). После него находится смещение элемента структуры; оно вычисляется с помощью макроса Tk_0ffset. Значением
1020 Часть VI. Программирование на языке С поля specf lags является набор флагов. В данном примере используются два флага: TK_C0NFIG_C0L0R_0NLY и TK_C0NFIG_M0N0_0NLY. Они задают соответственно использование цветов и монохромное отображение. Если у вас есть семейство компонентов, которые совместно используют большинство, но не все атрибуты, вы можете определить дополнительные флаги и передавать их Tk_Conf igureWidget. Пример соответствующего решения приведен в файле tkButton. с, который содержится в каталоге с исходными кодами Тк. Поле customPtr используется при работе с типом TK_C0NFIG_CUST0M, который детально описан на странице справочной системы, посвященной Tk_ConfigureWidget. В листинге 49.10 описаны атрибуты компонента Tk_ConfigSpec. Листинг 49.10. Спецификация конфигурации компонента static Tk_ConfigSpec configSpecs[] = { {TK_C0NFIG_B0RDER, "-background", "background", "Background", "light blue", Tk_0ffset(Clock, background), TK_C0NFIG_C0L0R_0NLY}, {TK_C0NFIG_B0RDER, "-background", "background", "Background", "white", Tk_0ffset(Clock, background), TK_C0NFIG_M0N0_0NLY}, {TK_C0NFIG_SYN0NYM, "-bg", "background", (char *) NULL, (char *) NULL, 0, 0}, {TK_C0NFIG_SYN0NYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, 0, 0}, {TK_C0NFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth","2", Tk^Offset(Clock, borderWidth), 0}, {TK.CONFIGJRELIEF, "-relief", "relief", "Relief", "ridge", TkJDffset(Clock, relief), 0}, {TK_C0NFIG_C0L0R, "-foreground", "foreground", "Foreground", "black", Tk_0ffset(Clock, foreground),0}, {TK_C0NFIG_SYN0NYM, "-fg", "foreground", (char *) NULL, (char *) NULL, 0, 0}, {TK_C0NFIG_C0L0R, "-highlightcolor", "highlightColor", "HighlightColor", "red", TkJDffset(Clock, highlight), TK_C0NFIG_C0L0R_0NLY}, {TK_C0NFIG_C0L0R, "-highlightcolor", "highlightColor", "HighlightColor", "black", Tk_0ffset(Clock, highlight),TK_C0NFIG_M0N0_0NLY}, {TK_C0NFIG_C0L0R, "-highlightbackground", "highlightBackground", "HighlightBackground", "light blue", Tk__0ffset(Clock, highlightBg),
Глава 49. Создание компонентов Тк на языке С 1021 TK_C0NFIG_C0L0R_0NLY}, {TK.CONFIG.COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground", "black", TkJDffset(Clock, highlightBg), TK_C0NFIG_M0N0_0NLY}, {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness","HighlightThickness", "2", Tk_0ffset(Clock, highlightWidth), 0}, {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", "2", TkJDffset(Clock, padX), 0}, {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", "2", TkJDffset(Clock, padY), 0}, {TK_CONFIG_STRING, "-format", "format", "Format", "°/oH:e/oM:e/0S", TkJDff set (Clock, format), 0}, {TK_C0NFIG_F0NT, "-font", "font", "Font", "Courier 18", Tk_0ffset(Clock, tkfont), 0}, {TK_C0NFIGJEND, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} _h Существует альтернатива интерфейсу Tk_Conf igureWidget, обеспечивающая поддержку значений Tcl_0bj в структуре данных компонента. Tk_0ptionSpec и Tk.Conf igureWidget заменяются Tk_Set0ptions, Tk_Get(DptionValue и Tk_Get0ptionInf о. Структура Tk_0ptionSpec, используемая альтернативным интерфейсом, показана в листинге 49.11. Листинг 49.11. Структура Tk.OptionSpec typedef struct Tk_0ptionSpec { Tk_0ptionType type; char *optionName; char *dbName; char *dbClass; char *defValue; int objOffset; int internalOffset; int flags; ClientData clientData; int typeMask; } Tk_0ptionSpec;
1022 Часть VI. Программирование на языке С В составе Tk_0ptionSpec содержатся два смещения: одно для обычных значений, а другое — для значений Tcl_0bj. Второе смещение используется для непосредственной установки Tcl_0bj при указании конфигурации в командной строке. Тип TK_CONFIG_PIXELS предполагает использование обоих смещений. Значение пикселей хранится в целочисленном поле, a Tcl_0bj используется для запоминания строки, определяющей экранное расстояние. Функции поля specf lags структуры Tk_Conf igSpec теперь выполняют другие элементы. Поле flags поддерживает только TK_C0NFIG_NULL_0K, а остальные настройки осуществляются посредством поля clientData. Поле typeMask обеспечивает группировку опций. Например, в компоненте clock атрибуты, влияющие на размещение компонента на экране и установку цвета, объединены в два набора. Это позволяет оптимизировать работу процедуры настройки. В листинге 49.12 описаны атрибуты компонента clock. Листинг 49.12. Структура Tk_0ptionSpec для компонента clock #define GE0METRY_MASK 0X1 #define GRAPHICS_MASK 0X2 static Tk_0ptionSpec optionSpecs[] = { {TK_0PTI0N_B0RDER, "-background", "background", "Background", "light blue", -1, Tk_0ffset(Clock, background), 0, (ClientData) "white", GRAPHICS_MASK}, {TK_0PTI0N_SYN0NYM, "-bg", "background", (char *) NULL, (char *) NULL, -1, 0, 0, 0, 0}, {TK_0PTI0N_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", "2", Tk_0ffset(Clock, borderWidthPtr), Tk_0ffset(Clock, borderWidth), 0, 0, GEOMETRY.MASK}, {TK_0PTI0N_SYN0NYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, -1, 0, 0, 0, 0}, {TK_0PTI0N_RELIEF, "-relief", "relief", "Relief", "ridge", -1, Tk^Offset(Clock, relief), 0, 0, 0}, {TK_0PTI0N_C0L0R, "-foreground", "foreground", "Foreground", "black",-1, Tk^Offset(Clock, foreground), 0, (ClientData) "black", GRAPHICS_MASK}, {TK_0PTI0N_SYN0NYM, "-fg", "foreground", (char *) NULL, (char *) NULL, -1, 0, 0, 0, 0}, {TK_0PTI0N_C0L0R, "-highlightcolor", "highlightColor", "HighlightColor", "red",-l, TkJDffset(Clock, highlight),
Глава 49. Создание компонентов Тк на языке С 1023 0, (ClientData) "black", GRAPHICS.MASK}, {TK.0PTI0N.C0L0R, "-highlightbackground", "highlightBackground", "HighlightBackground", "light blue",-l, Tk.Offset(Clock, highlightBg), 0, (ClientData) "white", GRAPHICS.MASK}, {TK.OPTION.PIXELS, "-highlightthickness", "highlightThickness","HighlightThickness", "2", Tk.Offset(Clock, highlightWidthPtr), Tk.Offset(Clock, highlightWidth), 0, 0, GEOMETRY.MASK}, {TK.OPTION.PIXELS, "-padx", "padX", "Pad", "2", Tk.Offset(Clock, padXPtr), Tk.Offset(Clock, padX), 0, 0, GEOMETRY.MASK}, {TK.OPTION.PIXELS, "-pady", "padY", "Pad", "2", Tk.Offset(Clock, padYPtr), Tk.Offset(Clock, padY), 0, 0, GEOMETRY.MASK}, {TK.0PTI0N.STRING, "-format", "format", "Format", "°/oH:e/,M:°/oS",-l, Tk.Offset(Clock, format), 0, 0, GEOMETRY.MASK}, {TK.0PTI0N.F0NT, "-font", "font", "Font", "Courier 18", -1, Tk.Offset(Clock, tkfont), 0, 0, (GRAPHICS.MASKIGEOMETRY.MASK)}, {TK.0PTI0N.END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, -1, 0, 0, 0, 0} _h В табл. 49.1 приведена информация о соответствии типов опций и типов соответствующих полей в структуре данных компонента. Те же типы, за некоторыми исключениями, поддерживаются Tk.ConfigSpec и Tk.OptionSpec. Тип TK.C0NFIG.ACTIVE.CURS0R соответствует TK.OPTION.CURSOR; оба они устанавливают курсор компонента. Типы TK.C0NFIG.MM и TK.C0NFIG.CURS0R не поддерживаются Tk.OptionSpec, поскольку они практически не применяются. Более общий тип, TK.0PTI0N.STRING.TABLE, поддерживаемый Tcl.GetlndexFromObj, заменил TK.C0NFIG.CAP.STYLE и TK.C0NFIG.J0IN.STYLE. В данном случае ClientData представляет собой массив строк, передаваемых Tcl.GetlndexFromObj. Значение индекса соответствует целочисленному значению, возвращаемому такими процедурами, как Tk.GetCapStyle.
1024 Часть VI. Программирование на языке С Таблица 49.1. Конфигурационные флаги и соответствующие типы С TK_CONFIG_ACTIVE_CURSOR Cursor TK.OPTION.CURSOR TK_CONFIG_ANCHOR Tk.Anchor TK_0PTI0N_ANCH0R TK_CONFIG_BITMAP Pixmap TK_OPTION_BITMAP TK_C0NFIG_B00LEAN int (0 или 1) TK_0PTI0N_B00LEAN TK_C0NFIG_B0RDER Tk_3DBorder * TK_0PTI0N_B0RDER TK_CONFIG_CAP__STYLE int (см. Tk.GetCapStyle) TK_C0NFIG_C0L0R XColor * TK_0PTI0N_C0L0R clientData no умолчанию задает монохромный вариант отображения TK_C0NFIG_CURS0R Cursor TK_C0NFIG_CUST0M TK.CONFIG.DOUBLE double TK_0PTI0N_D0UBLE TK_C0NFIG_END (определяет окончание опций) TK_0PTI0N_END TK_C0NFIG_F0NT Tk_Font TK_0PTI0N_F0NT TK_CONFIG_INT int TK_0PTI0N_INT TK_C0NFIG_J0IN_STYLE int (см. Tk_GetJoinStyle) TK_C0NFIG_JUSTIFY Tk_Justify TK_0PTI0N_JUSTIFY TK_C0NFIG_MM double TK_CONFIG_PIXELS int; TK_0PTI0N_PIXELS для исходного значения используется objOffset TK_CONFIG_RELIEF int (см. Tk.GetRelief) TK_0PTI0N_RELIEF TK_CONFIG_STRING char * TK_0PTI0N_STRING TK_0PTI0N_STRING_TABLE clientData представляет собой массив строк, используемый с Tcl_GetIndexFromObi
Глава 49. Создание компонентов Тк на языке С 1025 Окончание табл. 49.1 TK_C0NFIG_SYN0NYM TK_0PTI0N_SYN0NYM TK.CONFIGJJID TK.CONFIG.WINDOW TK_0PTI0N_WIND0W (псевдоним другой опции] client Data задает имя другой опции Tk.Uid Tk_Window Отображение часов Процесс отображения компонента начинается с определения размеров. При настройке необходимое пространство запрашивается у диспетчера компоновки. При последующем отображении компонента используются вызовы Tk_Width и Tk_Height. Они позволяют выяснить размеры пространства, которое должен выделить диспетчер компоновки. Код функции ComputeGeometry показан в листинге 49.13. Она может без изменений использоваться в обеих версиях компонента. Листинг 49.13. Функция ComputeGeometry вычисляет размеры компонента static void ComputeGeometry(Clock *clockPtr) { int width, height; Tk_FontMetrics fm; /* Информация о размере шрифта */ struct tm *tmPtr; /* Информация о времени представляется в виде полей */ struct timeval tv; /* Значение времени в стиле BSD */ int bd; /* Дополнение */ char clock[1000]; /* Отображаемое время */ /* * Получение времени и форматирование для определения размеров. */ gettimeofday(&tv, NULL); tmPtr = localtime(&tv.tv_sec); strftime(clock, 1000, clockPtr->format, tmPtr); if (clockPtr->clock != NULL) { ckfree(clockPtr->clock); } clockPtr->clock = ckalloc(l+strlen(clock));
1026 Часть VI. Программирование на языке С clockPtr->numChars = strlen(clock); bd = clockPtr->highlightWidth + clockPtr->borderWidth; TkJJetFontMetrics(clockPtr->tkfoiit, fcfm); height = fm.linespace + 2*(bd + clockPtr->padY); Tk_MeasureChars(clockPtr->tkfont, clock, clockPtr->numChars, 0, 0, &clockPtr->textWidth); width = clockPtr->textWidth + 2*(bd + clockPtr->padX); Tk_GeometryRequest(clockPtr->tkwin, width, height); Tk_SetInternalBorder(clockPtr->tkwin, bd); 2 Наконец, мы подошли к реальному отображению компонента! Программа должна проверить, существует ли компонент и отображается ли он. Это важно, потому что повторный вывод осуществляется асинхронно. Текущее время преобразуется в строку. Для этого используются процедуры библиотеки POSIX gettimeofday, localtime и strftime. В вашей системе подобные функциональные возможности могут поддерживаться другими средствами. Строка преобразуется в карту пикселей, которая находится в области памяти, отличной от экранной. После того как формирование карты пикселей окончено, карта копируется в экранную память. Такой подход позволяет избежать мерцания. Текст выводится раньше, чем обрамление. Это гарантирует, что если диспетчер компоновки выделит компоненту недостаточное пространство, рамка будет выведена поверх текста. В данном примере при каждом выводе компонента выделяется, а затем освобождается память для карты пикселей. Такой подход считается стандартным при создании компонентов Тк. Для компонента clock, содержимое которого должно обновляться каждую секунду, целесообразно было бы выделить область памяти для постоянного использования и хранить указатель на нее в структуре данных Clock. При изменении размеров компонента необходимо изменить объем памяти, выделенной для карты пикселей. После окончания процедуры отображения повторное отображение планируется через одну секунду. Модернизируя компонент, можно предусмотреть в нем указание времени обновления в качестве параметра. Для указания на то, что обратный вызов программы запланирован, используется флаг TICKING. Он проверяется при удалении компонента, и запланированный обратный вызов отменяется. В листинге 49.14 показана функция ClockDisplay. Она может без изменений использоваться в обеих версиях компонента.
Глава 49. Создание компонентов Тк на языке С 1027 Листинг 49.14. Функция ClockDisplay static void ClockDisplay(ClientData clientData) { Clock *clockPtr = (Clock *)clientData; Tk_Window tkwin = clockPtr->tkwin; GC gc; /* Графический контекст для подсветки */ Tk_TextLayout layout; /* Информация о тексте */ Pixmap pixmap; /* Временная область для отображения */ int offset, х, у; /* Координаты */ int width, height; /* Размер */ struct tm *tmPtr; /* Информация о времени разделяется на поля */ struct timeval tv; /* Значение времени в стиле BSD */ /* * Перед выводом надо убедиться, что компонент * существует и отображается на дисплее. */ clockPtr->flags &= ~(REDRAW.PENDINGI TICKING); if ((clockPtr->tkwin == NULL) II !Tk_IsMapped(tkwin)) { return; } /* * Информация о времени представляется в виде строки. * localtime разделяет данные на поля. * strftime оформляет поля в виде строки. */ gettimeofday(&tv, NULL); tmPtr = localtime(&tv.tv_sec); strftime(clockPtr->clock, clockPtr->numChars+l, clockPtr->format, tmPtr); /* * Для того чтобы исключить мерцание при обновлении, * изображение выводится в карту пикселей, а затем копируется * на дисплей за одну операцию. Для карты пикселей выделяется * память и определяется цвет фона. */ pixmap = Tk_GetPixmap(clockPtr->display, Tk_WindowId(tkwin), Tk.Width(tkwin),
1028 Часть VI. Программирование на языке С Tk_Height(tkwin), TkJDepth(tkwin)); Tk_Fill3DRectangle(tkwin, pixmap, clockPtr-background, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); /* * В первую очередь отображается текст. */ layout = Tk_ComputeTextLayout(clockPtr->tkfont, clockPtr->clock, clockPtr->numChars, 0, TK_JUSTIFY_CENTER, 0, fewidth, feheight); x = (Tk.Width(tkwin) - width)/2; у = (Tk.Height(tkwin) - height)/2; Tk_DrawTextLayout(clockPtr->display, pixmap, clockPtr->textGC, layout, x, y, 0, -1); /* * Отображение обрамления. При выводе рамка отображается * поверх текста, выходящего за пределы отведенной * для него области. */ if (clockPtr->relief != TK_RELIEF_FLAT) { Tk_Draw3DRectangle(tkwin, pixmap, clockPtr->background, clockPtr->highlightWidth, clockPtr->highlightWidth, Tk_Width(tkwin) - 2*clockPtr->highlightWidth, Tk_Height(tkwin) - 2*clockPtr->highlightWidth, clockPtr->borderWidth, clockPtr->relief); } if (clockPtr->highlightWidth != 0) { GC gc; /* * Данный графический контекст связывается с цветом, и Тк * кэширует контекст до освобождения цвета. Поэтому freeGC * отсутствует. */ if (clockPtr->flags & G0TJF0CUS) { gc = Tk_GCForColor(clockPtr->highlight, pixmap);
Глава 49. Создание компонентов Тк на языке С 1029 } else { gc = Tk_GCForColor(clockPtr->highlightBg, pixmap); } Tk__DrawFocusHighlight (tkwin, gc, clockPtr->highlightWidth, pixmap); } /* * Копирование информации из карты пикселей на экран. * После копирования карта пикселей удаляется. */ XCopyArea(clockPtr->display, pixmap, Tk_WindowId(tkwin), clockPtr->textGC, 0, 0, Tk.Width(tkwin), Tk.Height(tkwin), 0, 0); Tk_FreePixmap(clockPtr->display, pixmap); /* * Планирование следующего обращения. */ clockPtr->token = Tk_CreateTimerHandler(1000, ClockDisplay, (ClientData)clockPtr); clockPtr->flags |= TICKING; } Поддержка оконных событий Каждый компонент регистрирует обработчик событий, связанных с отображением и изменением размеров. Если в компоненте осуществляется подсветка при получении фокуса ввода, он также должен оповещаться о событиях, связанных с фокусом. Возможно, вам потребуется зарегистрировать процедуры обратного вызова для событий мыши и клавиатуры. Это не следует делать явно. Лучше использовать связывание Тк и определить обработчики средствами Tcl. В листинге 49.15 показана процедура, предназначенная для поддержки оконных событий. Она может использоваться в обеих версиях компонента. Листинг 49.15. Процедура ClockEventProc осуществляет поддержку оконных событий static void ClockEventProc(ClientData clientData, XEvent *eventPtr) {
1030 Часть VI. Программирование на языке С Clock *clockPtr = (Clock *) clientData; if ((eventPtr->type == Expose) && (eventPtr->xexpose.count ==0)) { goto redraw; } else if (eventPtr->type == DestroyNotify) { Tcl_DeleteCommandFromToken(clockPtr->interp, clockPtr->widgetCmd); /* * Обнуление tkwin предоставляет другим процедурам * сведения об удалении. */ clockPtr->tkwin = NULL; if (clockPtr->flags & REDRAW_PENDING) { Tk_CancelIdleCall(ClockDisplay, (ClientData) clockPtr); clockPtr->flags &= ~REDRAW_PENDING; } if (clockPtr->flags & TICKING) { TkJ)eleteTimerHandler(clockPtr->token); clockPtr->flags &= ^TICKING; } /* * Результатом является вызов ClockDestroy. */ Tk_EventuallyFree((ClientData) clockPtr, ClockDestroy); } else if (eventPtr->type == Focusln) { if (eventPtr->xfocus.detail != NotifyPointer) { clockPtr->flags |= G0T_F0CUS; if (clockPtr->highlightWidth > 0) { goto redraw; } } } else if (eventPtr->type == FocusOut) { if (eventPtr->xfocus.detail != NotifyPointer) { clockPtr->flags &= ~G0T_F0CUS; if (clockPtr->highlightWidth > 0) { goto redraw; } }
Глава 49. Создание компонентов Тк на языке С 1031 } return; redraw: if ((clockPtr->tkwin != NULL) && !(clockPtr->flags & REDRAW.PENDING)) { Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr); clockPtr->flags |= REDRAW_PENDING; } } Освобождение ресурсов При удалении компонента необходимо освобождать все ресурсы, которые были выделены для него. Ресурсы, связанные с атрибутами, освобождает Tk_FreeOptions. Об остальных ресурсах вам надо позаботиться самостоятельно. Процедура ClockDestroy вызывается в результате обращения к Tk.EventuallyFree в ClockEventProc. Процедура Tk_EventuallyFree является частью протокола, который определяет действия по удалению компонентов в процессе их обработки. Обычно процедуры Tk_Preserve и Tk_Release вызываются в начале и в конце выполнения команды экземпляра компонента. Они помечают компонент как используемый. Процедура Tk_EventuallyFree, перед тем как вызвать процедуру очистки, ожидает выполнения Tk_Release. В листинге 49.16 показан пример процедуры ClockDestroy. Листинг 49.16. Процедура ClockDestroy static void ClockDestroy(clientData) ClientData clientData; /* Информация о поле редактирования. */ { register Clock *clockPtr = (Clock *) clientData; /* * Удаление всех ресурсов, требующих специальной поддержки. * Tk_FreeOptions получает возможность обрабатывать ресурсы, * связанные с атрибутами компонента. */ if (clockPtr->textGC != None) { Tk_FreeGC(clockPtr->display, clockPtr->textGC); } if (clockPtr->clock != NULL) { Tcl_Free(clockPtr->clock);
1032 Часть VI. Программирование на языке С } if (clockPtr->flags & TICKING) { Tk_DeleteTimerHandler(clockPtr->token); } if (clockPtr->flags & REDRAW_PENDING) { Tk_CancelIdleCall(ClockDisplay, (ClientData) clockPtr); } /* * Освобождение цветов и шрифтов, а также памяти, * связанной с атрибутами компонента. */ Tk_FreeOptions(configSpecs, (char *) clockPtr, clockPtr->display, 0); Tcl_Free((char *) clockPtr); } Версия ClockDestroy, использующая интерфейс Tcl_0bj, вместо Tk_FreeOptions вызывает Tk.FreeConfigOptions. Команда ClockObjDelete вызывается тогда, когда команда oclock удаляется из интерпретатора. При этом, если таблица опций, используемая для разбора, была инициализирована, она очищается. Для строковой версии компонента соответствующая процедура удаления отсутствует. Функция ClockObjDelete показана в листинге 49.17. Листинг 49.17. Команда ClockObjDelete void ClockObjDelete(ClientData clientData) { Tk_0ptionTable optionTable = (Tk_0ptionTable) clientData; if (optionTable != NULL) { Tk_DeleteOptionTable(optionTable); } }
Глава 50 Библиотеки С В данной главе приводится краткий обзор возможностей, предоставляемых С-библиотеками Tcl и Тк. Детально API описывается в интерактивной справочной системе. V^-БИБЛИОТЕКИ обеспечивают полный доступ к реализациям Tcl и Тк. Они позволяют контролировать среду выполнения Tcl-сценариев. Кроме того, расширять Tcl и Тк можно путем реализации новых средств на языке С. В частности, вы можете создавать новые команды, каналы ввода-вывода, источники событий, компоненты, объекты холста, типы изображений и диспетчеры компоновки. Платформенно-независимая подсистема ввода-вывода и цикл обработки событий доступны из С-функций. В данной главе приводятся общие сведения о С-библиотеках Tcl и Тк. Для написания реальных программ на С недостаточно сведений, приведенных в данной главе, поэтому вам придется обратиться к интерактивной справочной системе. Каждая страница справочной информации посвящена группе С-процедур. Работая в системе Unix, вы можете указать имя процедуры в качестве параметра команды man и получить документацию на группу связанных между собой процедур. man Tcl.CreateCommand В версиях справочной системы, представленных в форматах Windows Help и HTML, осуществляется индексация для каждой процедуры. Информация в формате HTML содержится на прилагаемом к данной книге компакт- диске, кроме того, вы можете найти аналогичные сведения в Web по следующему адресу: http://www.tcl.tk/man/ В качестве справочного материала могут также использоваться исходные коды Tcl и Тк. Они оформлены в виде, удобном для восприятия, и сиабже-
1034 Часть VI. Программирование на языке С ны комментариями. Практически все экспортируемые API используются Tcl и Тк, поэтому, читая исходные тексты, вы получите сведения об использовании API. Исходные коды Tcl и Тк расположены на прилагаемом компакт- диске в каталогах tcl8.4 и tk8.4. О построении системы Tcl из исходных кодов см. в главе 48. Общие сведения о С-библиотеке Tcl Инициализация приложения Процедуры Tcl_Main и Tcl_AppInit были показаны в листинге 47.13. Они предоставляют стандартный набор возможностей для написания программ, использующих интерпретатор Tcl. Tcl_Init используется для загрузки сценария init.tcl. Tcl_SourceRCFile загружает стартовый файл, ориентированный на конкретного пользователя. Tcl_SetMainLoop используется для запуска цикла обработки событий (это необходимо, например, при выполнении Тк-программ). Процедура Tcl_InitStubs должна вызываться в процессе инициализации расширением, связанным с библиотекой-заглушкой (см. главу 48). Пример вызова Tcl_InitStubs см. в листинге 47.1. Процедура Tcl_FindExecutable определяет абсолютное имя файла для выполняемой программы. Эта процедура может быть вызвана перед Tcl_Main или Tcl_CreateInterp. Если процедура Tcl_FindExecutable была вызвана, для получения имени программы используется Tcl_GetNameOfExecutable. Создание и удаление интерпретаторов Для создания и удаления Tcl-интерпретатора используются соответственно процедуры Tcl.CreaTclnterp и Tclj)eleTcliiterp. Чтобы выяснить, идет ли процесс удаления интерпретатора, следует обратиться к Tcl__InterpDeleted. Tcl_CallWhenDeleted дает возможность зарегистрировать обратный вызов при удалении интерпретатора. Отменить регистрацию обратного вызова позволяет Tcl_DontCallWhenDeleted. Для создания ведомого интерпретатора и выполнения действия с ним служат процедуры Tcl_CreateSlave, Tcl_GetSlave, Tcl_GetSlaves, Tcl_GetMaster, Tcl_CreateAlias, Tcl_CreateAliasObj, Tcl_GetAlias, Tcl.GetAliasObj, Tcl_GetAliases, Tcl_GetInterpPath, Tcl_IsSafe, Tcl_MakeSaf e, Tcl_ExposeCommand и Tcl_HideCommand.
Глава 50. Библиотеки С 1035 Создание и удаление команд Регистрация новой Tcl-команды осуществляется с помощью Tcl_CreateCommand, а удаление — посредством Tcl_DeleteCommand. Процедуры Tcl^GetCommandlnfo и Tcl_SetCommandInfo позволяют определить или изменить процедуру, реализующую Tcl-команду, и объект ClientData, связанный с этой командой. Процедура создания команды возвращает маркер. Выполнять действия с командой, используя этот маркер, позволяют процедуры Tcl.GetCommandlnfoFromToken, Tcl_SetCommandInfoFromToken и Tcl_DeleteCommandFromToken. Команда, использующая интерфейс Tcl_0bj, создается посредством процедуры Tcl_CreateObj Command. Подробно о командных процедурах см. в главе 47. Пакеты и динамическая загрузка Процедура Tcl.PkgRequire проверяет наличие зависимости от других пакетов. Tcl_PkgProvide объявляет, что пакет предоставляет библиотека. Эти процедуры действуют так же, как и Tcl-команды package require и package provide. Процедура Tcl_PkgPresent возвращает номер версии загруженного пакета. Процедуры Tcl_PkgProvideEx, Tcl_PkgRequireEx и Tcl_PkgPresentEx позволяют запросить объект clientData, связанный с пакетом, или установить новый объект. Обращение к Tcl_StaticPackage используется для загрузки статически связанных пакетов в дочерние интерпретаторы. Управление результирующими строками Для управления результирующими строками используются процедуры Tcl_SetResult, Tcl_AppendResult, Tcl.AppendElement, Tcl_GetStringResult и Tcl_ResetResult. Объектный интерфейс обеспечивают Tcl_SetObjResult и Tcl_GetObjResult. Информацию об ошибках поддерживают процедуры Tcl_AddErrorInfo, Tcl.AddObjErrorlnfo, Tcl.SetErrorCode, Tcl_SetObjErrorCode, Tcl__SetErrorCodeVA, Tcl_LogCommandInfo и Tcl_PosixError. Tcl_WrongNumArgs генерирует стандартное сообщение об ошибке. Tcl_SetErrno, Tcl.GetErrno, Tcl_ErrnoId и Tcl_ErrnoMsg обеспечивают платформенно-независимый доступ к глобальной переменной errno, в которой хранятся коды ошибок POSIX. Распределение памяти Tcl_Alloc, Tcl_Realloc и Tcl_Free реализуют функции, независимые от платформы и компилятора, применяющиеся для выделения и осво-
1036 Часть VI. Программирование на языке С бождения памяти в области под названием "куча". Эти процедуры надо использовать вместо alloc, realloc и free. Заметьте, что в случае, если доступная память отсутствует, Тс1_А11ос и Tcl_Realloc вызывают аварийное завершение задачи. В тех же ситуациях процедуры Tcl_AttemptAlloc и Tcl_AttemptRealloc вместо завершения задачи возвращают указатель NULL. Процедуры Tcl_Preserve и Tcl.Release, взаимодействующие с Tcl.EventuallyFree, предотвращают преждевременное освобождение структур данных (см. главу 47). Макросы ckalloc, ckfree, ckrealloc, attemptckalloc и attemptckrealloc базируются на функциях API, предназначенных для управления памятью, и обеспечивают дополнительные средства отладки. Для того чтобы эти макросы были доступны, надо задать для компилятора опцию TCL_MEM_DEBUG. Просмотр отладочной информации обеспечивают процедуры Tcl_DumpActiveMemory и Tcl_ValidateAllMemory. Для создания Tcl-команды, предоставляющей доступ сценариев к функциям API управления памятью, можно использовать Tcl_InitMemory. Работа со списками Процедура Tcl_SplitList, возвращающая массив строк, позволяет разделить список на элементы. Создать список из массива строк дает возможность Tcl_Merge. Эти процедуры действуют подобно операциям, реализуемым командой list; в частности, они похожи тем, что в соответствие элементу списка ставится строка. Tcl_Merge использует процедуры Tcl_ScanElement, Tcl_ScanCountedElement, Tcl_ConvertCountedElement и Tcl_Convert Element. Объектный интерфейс к спискам обеспечивают процедуры Tcl_NewListObj, Tcl_SetListObj, Tcl_ListObjIndex, Tcl_ListObjLength, Tcl_ListObjAppendList, Tcl_ListObjAppendElement, Tcl_ListObjGeTclements и Tcl_ListObjReplace. Разбор команд При чтении команд можно организовать проверку с помощью процедуры Tcl_CommandComplete. Подстановку символов обратной косой черты позволяет реализовать Tcl_Backslash. Более формальные средства разбора предоставляют процедуры Tcl_ParseCommand, Tcl_ParseExpr, Tcl_ParseBraces, Tcl_ParseQuotedString, Tcl_ParseVarName. Tcl.ParseVar и Tcl_FreeParse. Результатом разбора является последовательность маркеров, которые могут быть обработаны Tcl_EvalTokens и Tcl^EvalTokensStandard.
Глава 50. Библиотеки С 1037 Конвейерная обработка Процедура Tcl_OpenComandChannel выполняет все необходимые действия по установлению каналов между процессами. Этим обеспечивается перенаправление файлов и реализуются синтаксические конструкции, поддерживаемые командами exec и open. Дождаться завершения процесса позволяет процедура Tcl_WaitPid. Если команда, вызванная посредством конвейерной обработки, выполняется в фоновом режиме, возвращается список идентификаторов процессов. Вы можете отключать процессы посредством Tcl_DetachPids и выполнять очистку с помощью TclJleapDetachedProcs. Отслеживание действий интерпретатора Существует ряд процедур, позволяющих отслеживать выполнение Tcl-интерпретатора и контролировать его поведение. Tcl_CreateTrace и Tcl_CreateObjTrace дают возможность зарегистрировать процедуру, которая вызывается перед выполнением каждой Tcl-команды. Процедура Tcl_DeleteTrace отменяет регистрацию. Отслеживание отдельных команд осуществляется с помощью Tcl_TraceCommand, Tcl_UntraceCommand и Tcl_CommandTraceInfо. Tcl_TraceVar и Tcl_TraceVar2 позволяют отслеживать модификацию и доступ к Tcl-переменным. Вторая из указанных процедур используется для работы с элементами массивов. Отменяется отслеживание посредством Tcl.UntraceVar и Tcl_UntraceVar2. Tcl_VarTraceInfо и Tcl.VarTracelnfо2 предоставляют информацию об отслеживании переменных. Выполнение Tcl-команд Поддержкой выполнения Tcl-команд занимается большое семейство процедур. Tcl.Eval выполняет строку как Tcl-команду. Tcl.VarEval и Tcl_VarEvalVA получают произвольное число строковых параметров и перед выполнением осуществляют их конкатенацию. Tcl_EvalFile читает команды из файла. Tcl_GlobalEval выполняет строку в глобальной области видимости. Tcl_EvalEx обрабатывает наборы флагов. Флаг TCL_GLOBAL_EVAL указывает на то, что выполнение должно осуществляться в глобальной области видимости. Флаг TCL_EVAL_DIRECT сообщает, что сценарий должен быть выполнен без предварительного преобразования в байтовые коды. Tcl_EvalObj и Tcl_GlobalEvalObj реализуют объектный интерфейс. Их параметрами являются объекты сценариев, для которых выполнено преобразование в байтовый код и кэширование. Эти процедуры надо использовать в том случае, если планируется многократное выполнение одного и того же
1038 Часть VI. Программирование на языке С сценария. Процедура Tcl_EvalObjEx обрабатывает описанные выше флаги. Процедура Tcl_EvalObjv получает в качестве параметра массив Tcl_0bj, который представляет команду и ее параметры. В отличие от других процедур, Tcl_EvalObjv не осуществляет подстановку параметров. Если вы реализуете интерактивную интерпретацию команд, вам, возможно, потребуются средства поддержки предыстории. В этом случае вам помогут процедуры Tcl_RecordAndEval и Tcl_RecordAndEvalObj. Они записывают команду в список, а затем действуют подобно Tcl_GlobalEval. Ограничить уровень рекурсивных вызовов интерпретатора позволяет Tcl_SetRecursionLimit. Если вы реализуете новую управляющую структуру, вам потребуется процедура Tcl_AllowExceptions. Это позволит Tcl_Eval и другим подобным процедурам возвращать значения, отличные от TCL_0K и TCL_ERR0R. Если вы хотите выполнить Tcl-команду, не модифицируя текущие результаты интерпретации и информацию об ошибках, используйте Tcl_SaveResult, Tcl_RestoreResult и Tcl_DiscardResult. Процедура Tcl_SubstObj реализует механизм подстановки Тс1-команд. Информация об ошибках Если ваш компонент осуществляет обратный вызов на уровне сценария, необходимо учесть случаи, когда при выполнении процедуры обратного вызова возникнет ошибка. Для того чтобы предоставить пользователю информацию об ошибках, надо использовать процедуру Tcl_BackgroundError, которая вызывает стандартную процедуру bgerror. Действия с Tcl-переменными для установки значений Tcl-переменных используются процедуры Tcl_SetVar и Tcl_SetVar2. Эти две процедуры присваивают переменным строковые значения; вторая процедура ориентирована на элементы массивов. Процедура Tcl_SetVar2Ex присваивает переменной значение Tcl_0bj и может использоваться при работе с элементами массивов. Получить значение Tcl-переменной позволяют Tcl_GetVar и Tcl_GetVar2. Процедура Tcl_GetVar2Ex вместо строкового значения возвращает значение Tcl_0bj. В тех редких случаях, когда в Tcl_0bj вместо обычной строки содержится имя переменной, необходимо использовать процедуры Tcl_0bjSetVar2 и Tcl_0bjGetVar2. Для удаления переменных служат процедуры Tcl_UnsetVar и Tcl__UnsetVar2. Связать Tcl-переменную с переменной С можно посредством процедуры Tcl_LinkVar, а разорвать связь позволяет Tcl__UnlinkVar. Если связь
Глава 50. Библиотеки С 1039 установлена, то присвоение нового значения Tcl-переменной модифицирует С-переменную, а при чтении Tcl-переменной возвращается значение переменной С. Если вам надо непосредственно изменить значение Tcl-переменной, используйте процедуру Tcl_UpdaTclinkedVar. Процедуры Tcl_UpVar и Tcl_UpVar2 дают возможность связать Tcl-переменные из различных областей видимости. Это может понадобиться в том случае, если команда получает в качестве параметра не значение, а имя переменной. Указанные выше процедуры использованы в реализации Тс 1-команды upvar. Обработка выражений Средства обработки Tcl-выражений доступны посредством процедур Tcl_ExprLong, Tcl_ExprDouble, Tcl_ExprBoolean и Tcl_ExprString. Эти процедуры отличаются друг от друга только способом возврата результатов. Объектный интерфейс реализуют процедуры Tcl_ExprLongObj, Tcl.ExprDoubleObj, Tcl.ExprBooleanObj и Tcl_ExprObj. Зарегистрировать реализацию новой математической функции позволяет процедура Tcl_CreateMathFunc. Информацию о таких функциях можно получить с помощью Tcl_GetMathFuncInf о и Tcl_ListMathFuncs. Преобразование чисел Преобразовать строковые значения в числа можно с помощью процедур Tcl.Getlnt, Tcl.GetDouble и Tcl_GetBoolean. Процедура TclJPrintDouble преобразует число с плавающей точкой в строку. Tcl использует ее при каждом подобном преобразовании. Объекты Tcl В Tcl 8.0 вместо строк используются так называемые двойные объекты (dual-ported object). Они позволяют повысить эффективность выполнения программ. Интерфейс к этим объектам реализуют процедуры TclJJewObj, TIJDuplicateObj, Tcl_IncrRefCount, TclJDecrRefCount, Tcl_InvalidateStringRep и Tcl_IsShared. Примеры использования некоторых из этих процедур см. в листингах 47.5 и 47.15. По необходимости вы можете определить новые типы объектов. В состав интерфейса входят также Tcl_RegisterObjType, Tcl_GetObjType, Tcl.AppendAllObjTypes и Tcl.ConvertToType. Основные типы объектов Базовыми типами Tcl-объектов считаются логические значения, целые числа, числа с плавающей точкой двойной точности и строки. Этим
1040 Часть VI. Программирование на языке С тинам соответствуют процедуры, позволяющие создавать объекты, устанавливать и читать их значения: Tcl_NewBooleanObj, Tcl.SetBooleanObj, Tcl.GetBooleanFromObj, Tcl.NewDoubleObj, Tcl.SetDoubleObj, Tcl_GetDoubleFromObj, Tcl_NewIntObj, Tcl.GetlntFromObj, Tcl.SetlntObj, Tcl_NewLongObj, Tcl_GetLongFromObj и Tcl_SetLongObj. Для поддержки 64-разрядных целых чисел предусмотрены процедуры Tcl.NewWidelnt, Tcl_SetWideInt и Tcl.GetWidelntFromObj. Строковые объекты Значения Tcl_0bj используются для хранения строк, представленных в различных кодировках. Основной для объекта Tcl_0bj считается кодировка UTF-8. Кроме того, в нем могут содержаться строки Unicode (16-битовые символы) или ByteArray (8-битовые символы). Преобразование из одного типа в другой осуществляется автоматически. Однако некоторые операции выполняются наиболее эффективно при использовании определенной кодировки. В этом случае значение Tcl_0bj будет полезным для кэширования соответствующего представления данных. Для работы со строковыми объектами, представленными в кодировке UTF-8, используются следующие процедуры: Tcl_NewStringObj, Tcl_SetStringObj, Tcl_GetString, Tcl.GetStringFromObj, Tcl_AppendToObj, Tcl_AppendStringsToObj и Tcl_Append0bjTo0bj. Процедуры Tcl_NewUnicodeObj, Tcl_SetUnicodeObj, Tcl_AppendUnicodeToObj, Tcl.GetUnicode, Tcl.GetUnicodeFromObj, Tcl_GetRange и Tcl_GetUniChar предназначены для обработки строк Unicode. Tcl_Append0bjTo0bj сохраняет исходное представление присоединяемой строки (например, Unicode или UTF-8). Процедура Tcl__GetCharLength возвращает длину строки, выраженную в символах. Процедура Tcl_Set Ob j Length устанавливает размер памяти для хранения строки, выраженный в байтах. В общем случае этот размер отличается от длины строки. Данные сведения используются для повторного выделения памяти при добавлении к строке новых символов. Избежать аварийного завершения в случае, если объект невозможно увеличить до требуемого размера, поможет процедура Tcl.Att empt Set Ob j Length. Процедуры Tcl_Concat и Tcl_ConcatObj действуют подобно Tcl-команде concat. В качестве параметра им передается массив строк (для Tcl_Concat) или значения Tcl_0bj (для Tcl_Concat0bj). Процедуры удаляют ведущие и завершающие пробелы и объединяют строки, разделяя их одним пробелом.
Глава 50. Библиотеки С 1041 ByteArray для двоичных данных Tcl_0bj типа ByteArray используется для хранения произвольных двоичных данных. По сути этот тип представляет собой байтовый массив. Для работы с данным типом используются процедуры Tcl_NewByteArrayObj, Tcl_SetByteArrayObj, Tcl_GetByteArrayFromObj и Tcl_SetByteArrayLength. Динамические строки Tcl-пакет для работы с динамическими строками используется в тех случаях, когда строки должны формироваться и дополняться в процессе выполнения программы. Необходимость в подобных строках возникает, например, при использовании процедуры Tcl_TranslateFileName. В состав пакета для работы с динамическими строками входят процедуры TclJDStringAppend, Tcl_DStringAppendElement, TclJDStringStartSublist, Tcl_DStringEndSublist, TclJDStringLength, TclJDStringValue, TclJDStringSetLength, TclJDStringFree, TclJDStringResult и Tcl.DStringGetResult. Наборы символов Процедура, преобразующая строку из одного набора символов в другой, использует абстрактный дескриптор набора. Для получения и удаления этих дескрипторов применяются процедуры Tcl_GetEncoding и Tcl_FreeEncoding. Tcl_SetSystemEncoding вызывается Tcl и устанавливает кодировку для текущей системы. Процедура Tcl_CreateEncoding создает новую кодировку. Процедуры Tcl_GetEncodingName и Tcl_GetEncodingNames предоставляют сведения о доступных кодировках. Кодировки хранятся в файлах, которые находятся в каталогах по умолчанию. Получить информацию о каталоге и установить новый каталог позволяют процедуры Tcl_GetDef aultEncodingDir и Tcl_SetDef aultEncodingDir. Существуют три группы процедур, предназначенных для перевода строк из одной кодировки в другую. Проще всего использовать Tcl_ExternalToUtf DString и Tcl_Utf ToExternalDString, которые помещают результат в Tcl_DString. Указанные выше процедуры базируются на процедурах Tcl_ExternalToUtf и Tcl_UtfToExternal. Использовать последние несколько сложнее, так как в них применяются специальные преобразования. Процедуры Tcl_WinTCharToUtf и Tcl_WinUtf ToTChar предназначены для работы с типом TChar, используемым в системе Windows. В Windows 98 этому типу соответствуют 8-битовые символы, а в Windows NT — 16-битовые символы Unicode.
1042 Часть VI. Программирование на языке С Существует много вспомогательных процедур, ориентированных на работу со строками Unicode и UTF-8. Это Tcl_UniChar, TclJJniCharToUtf, TclJJtfToUniChar, TclJJniCharToUtfDString, Tcl_UtfToUniCharDString, TclJJniCharLen, Tcl_UniCharNcmp, TclJJtfCharComplete, Tcl JfumUtfChars, TclJJtfFindFirst, TclJJtfFindLast, Tcl.UtfNext, TclJJtfPrev, Tcl.UniCharAtlndex, TclJJtfAtlndex и Tcl.UtfBackslash. Процедуры Tcl_UniCharToUpper, Tcl_UniCharToLower и Tcl.UniCharToTitle преобразуют регистр символов Unicode. Процедуры Tcl_UtfToUpper, TclJJtfToLower и TclJJtfToTitle предназначены для преобразования строк. Процедуры Tcl_UniCharCaseMatch и Tcl_UniCharNcasecmp сравнивают Unicode-строки. Процедуры Tcl_UniCharIsAlnum, Tcl_UniCharIsAlpha, TclJJniCharlsControl, TclJJniCharlsDigit, Tcl.UniCharlsGraph, Tcl_UniCharIsLower, TclJJniCharlsPrint, Tcl.UniCharlsPunct, Tcl_UniCharIsSpace, TclJJniCharlsUpper и Tcl_UniCharIsWordChar проверяют классификацию символов Unicode. AssocData и структуры данных интерпретатора Если ваше приложение должно хранить информацию, не соответствующую конкретной команде, эти данные можно связать с интерпретатором, используя средства AssocData. Процедура Tcl_SetAssocData регистрирует строковый ключ для структуры данных. Tcl_GetAssocData получает данные по ключу, a Tcl_DeleteAssocData удаляет ключ и указатель. При регистрации также может быть определена процедура обратного вызова, которая получает управление при удалении интерпретатора. Описанные здесь средства базируются на пакете хэш-таблиц, который будет описан ниже. Хэш-таблицы В Tcl реализован пакет хэш-таблиц, который обеспечивает автоматическое изменение размеров таблиц при добавлении к ним данных. Поскольку в Tcl любая информация представляется в виде строки, вам, возможно, понадобится хэш-таблица, отображающая строковый ключ во внутреннюю структуру данных. В состав пакета входят процедуры Tcl_InitHashTable, Tcl_InitObjHashTable, Tcl.InitCustomHashTable, Tcl_DeleteHashTable, Tcl_CreateHashEntry, Tcl_DeleteHashEntry, Tcl_FindHashEntry, Tcl_GetHashValue, Tcl_SetHashValue, Tcl_GetHashKey, Tcl_FirstHashEntry, Tcl_NextHashEntry и Tcl_HashStats. Хэш-таблицы использовались при реализации команды blob (см. главу 47).
Глава 50. Библиотеки С 1043 Обработка опций Процедуры Tcl.GetlndexFromObj и Tcl_GetIndexFromObjStruct обеспечивают поиск ключевых слов в таблице. Они предназначены для обработки опций Tcl-команд. Пример использования Tcl_GetIndexFromObj см. в листинге 47.8. Регулярные выражения и проверка строк Средства библиотеки регулярных выражений, используемые Tcl- сценариями, экспортируются посредством процедур Tcl_RegExpMatch, Tcl_RegExpCompile, Tcl_RegExpExec и TclJlegExpRange. Версия Tcl_0bj данного интерфейса использует процедуры Tcl_RegExpMatchObj, Tcl_GetRegExpFromObj, Tcl_RegExpExecObj и Tcl_GetRegExpInfo. Средства сравнения строк доступны через Tcl_StringMatch и Tcl_StringCaseMatch. Реализация цикла обработки событий В цикле обработки событий участвуют средства оповещения, которые управляют набором источников событий и очередью событий, ожидающих обработки. В приложениях tclsh и wish цикл обработки событий уже реализован. Простейший интерфейс обработки предоставляет процедура Tcl_DoOneEvent. В некоторых случаях возникает необходимость реал изовывать новые источники событий. Для создания и удаления источника используются соответственно процедуры Tcl_CreateEventSource и TclJDeleteEventSource. Источник событий выполняет действия с очередью посредством Tcl_QueueEvent, Tcl_DeleteEvents и Tcl_SetMaxBlockTime. Средства оповещения выполняются в каждом потоке. Tcl_ThreadQueueEvent позволяет помещать события в очередь другого потока. Поступив подобным образом, вы должны передать этому потоку сообщение, использовав процедуру Tcl_ThreadAlert. Процедура Tcl_GetCurrentThread возвращает идентификатор текущего потока. Реализация средств оповещения включает общедоступный API. По мере необходимости вы можете заменить функции API своими функциями. В состав API входят Tcl_InitNotifier, Tcl_FinalizeNotifier, Tcl_WaitForEvent, Tcl_AlertNotifier, Tcl_Sleep, Tcl_CreateFileHandler и Tcl JDeleteFileHandler. Если вы хотите интегрировать цикл обработки событий Tcl с внешним циклом, например, с циклом Xt, применяемым Motif, вы можете использовать следующие процедуры: Tcl_WaitForEvent, Tcl_SetTimer, Tcl_ServiceAll, Tcl_ServiceEvent, Tcl_GetServiceMode и Tcl_SetServiceMode.
1044 Часть VI. Программирование на языке С Работа с файлами Для регистрации обработчиков событий, связанных с вводом-выводом, используется процедура Tcl_CreateFileHandler. Обработчики вызываются тогда, когда поток готов для чтения или записи данных. Обработчики, ориентированные на выполнение действий с файлами, вызываются после обработчиков оконных событий. Для удаления обработчика предназначена процедура Tcl.DeleteFileHandler. Процедура Tcl_CreateFileHandler ориентирована на работу в системе Unix, поскольку в этой операционной среде используются универсальные обработчики для файлов, гнезд, каналов и устройств. В Windows и Macintosh для обработки событий, соответствующих различным классам объектов, с которыми осуществляется обмен данными, применяются различные системные API. Для унификации операций обмена данными используются драйверы каналов ввода-вывода. Если необходимо обеспечить обмен с нестандартным устройством, лучше всего создать для него драйвер канала и источник событий. События таймера Для того чтобы зарегистрировать обратный вызов, который должен быть выполнен по прошествии определенного времени, используется процедура Tcl_CreateTimerHandler. Обработчик вызывается только один раз. Если вам необходимо удалить обработчик перед тем, как он получит управление, используйте процедуру Tcl_DeleteTimerHandler. Обратные вызовы времени бездействия Если события, ожидающие обработки, отсутствуют, то, перед тем, как перейти в режим ожидания новых событий, Тк осуществляет обратные вызовы времени бездействия. В большинстве случаев на время бездействия планируется вызов процедур обновления внешнего вида компонентов Тк. Для того чтобы поставить в очередь команду обратного вызова для выполнения ее в период бездействия, надо обратиться к процедуре Tcl_DoWhenIdle. Удаление из очереди осуществляется путем обращения к Tcl_CancelIdleCall. Процедура Tcl.Sleep вызывает задержку выполнения на указанное число миллисекунд. В вод-вы вод Подсистема ввода-вывода реализует буферизацию и осуществляет обмен данными, управляемый событиями, взаимодействуя при этом с циклом обработки событий. В состав интерфейса входят процедуры Tcl_OpenFileChannel, Tcl^OpenCommandChannel, Tcl.MakeFileChannel,
Глава 50. Библиотеки С 1045 Tcl_GetChannel, Tcl_GetChannelNames, Tcl_GetChannelNamesEx, Tcl_GetOpenFile, Tcl.RegisterChannel, Tcl.UnregisterChannel, TclJDetachChannel, Tcl.IsStandardChannel, Tcl_Close, Tcl_Read, TclJleadChars, Tcl__Gets, Tcl_Write, Tcl_WriteObj, Tcl_WriteChars, Tcl_Flush, Tcl_Seek, TclJTcll, Tcl.Eof, Tcl_GetsObj, Tcl.InputBlocked, Tcl_InputBuffered, Tcl_OutputBuffered, Tcl_Ungets, Tcl.ReadRaw, Tcl_WriteRaw, Tcl_GetChannelOption и Tcl_SetChannelOption. Драйверы каналов ввода-вывода В состав Tcl входит расширяемая система ввода-вывода. По необходимости вы можете реализовывать новые каналы (например, для поддержки гнезд UDP), сформировав Tcl-команды для объявления каналов и зарегистрировав набор процедур обратного вызова, используемых стандартными командами ввода-вывода, такими как puts, gets и close. Интерфейс поддержки каналов включает следующие процедуры: Tcl_CreateChannel, Tcl_GetChannel, Tcl_GetChannelType, Tcl_GetChannelInstanceData, Tcl.GetChannelName, Tcl.GetChannelHandle, Tcl.GetChannelMode, Tcl__BadChannelOption, Tcl.GetChannelBufferSize, Tcl_SetDefaultTranslation, Tcl_SetChannelBufferSize и Tcl_NotifyChannel. В интерфейсе главного цикла обработки событий используются процедуры Tcl_CreateChannelHandler и Tcl_DeleteChannelHandler. Tcl_CreateCloseHandler и Tcl_DeleteCloseHandler устанавливают и удаляют команду обратного вызова, которая выполняется при закрытии канала. Tcl_GetStdChannel и Tcl_SetStdChannel используются для выполнения действий со стандартными входным и выходным потоками вашего приложения. Для создания сетевых гнезд используются процедуры Tcl_OpenTcpClient и Tcl__OpenTcpServer. Tcl^MakeTcpClientChannel предоставляет платфор- менно-независимые средства создания структуры Tcl-канала для сетевого соединения. Процедуры Tcl.StackChannel, TclJJnstackChannel, Tcl_GetTopChannel и Tcl_GetStackedChannel поддерживают многоуровневое взаимодействие с каналами ввода-вывода. Они могут использоваться для поддержки сжатия и шифрования информации, передаваемой через канал. Драйвер канала реализует набор процедур, которые регистрируются в структуре Tcl.ChannelType. При изменении формата этой структуры обеспечивается обратная совместимость, так, что более старые расширения могут взаимодействовать с новыми версиями Tcl. Информацию о версии структуры канала возвращает процедура Tcl.Channel Vers ion. Непосредственно обращаться к структуре нежелательно. Вместо этого рекомендуется исполь-
1046 Часть VI. Программирование на языке С зовать процедуры Tcl_ChannelBlockModeProc, Tcl_ChannelBlockModeProc, Tcl_ChannelCloseProc, Tcl_ChannelClose2Proc, Tcl.ChannellnputProc, Tcl_ChannelOutputProc, Tcl_ChannelSeekProc, Tcl_ChannelWideSeekProc, Tcl.ChannelSetOptionProc, Tcl_ChannelGetOptionProc, Tcl_ChannelWatchProc, Tcl_ChannelGetHandleProc, Tcl_ChannelFlushProc и Tcl_ChannelHandlerProc. Каждый канал ввода-вывода регистрируется посредством одного или нескольких интерпретаторов; каждая регистрация учитывается в счетчике ссылок. Tcl_SpliceChannel и Tcl.CutChannel используются для добавления или удаления канала из списка, ориентированного на конкретный интерпретатор. Информацию о счетчике ссылок и регистрации можно запросить с помощью процедур Tcl.IsChannelShared, Tcl.IsChannelExisting и Tcl_IsChannelRegistered. Tcl_ChannelBuffered предоставляет сведения об объеме данных, находящихся в буфере канала. Каждый интерпретатор связан с конкретным потоком. Tcl_GetChannelThread используется для нахождения соответствующего потока и получения информации о событиях ввода-вывода. При деактивизации канала все обработчики удаляются с помощью процедуры Tcl_ClearChannelHandlers. Обработка имен файлов Процедуры Tcl_SplitPath, Tcl_JoinPath и Tcl_GetPathType реализуют Tcl-команды file split, file join и file pathtype, используемые для выполнения различных действий с файлами; при этом обеспечивается независимость от конкретной платформы. Процедура Tcl_TranslateFileName преобразует имя файла по правилам, используемым в конкретной операционной системе. Она также расширяет символ ~ в именах файлов в рабочие каталоги пользователей. Получение информации о файловой системе Tcl 8.4 предоставляет набор процедур Tcl_FS, обеспечивающих доступ к файловой системе. Эти процедуры переносимы в системах Unix, Windows и Macintosh. Они поддерживают интерфейс виртуальной файловой системы; таким образом, все С-расширения, использующие данный API, автоматически получают доступ к встроенным файловым системам. Операции с файловой системой поддерживаются следующими основными процедурами: Tcl_FSCopyFile, Tcl_FSCopyDirectory, Tcl_FSCreateDirectory, Tcl_FSDeleteFile, Tcl.FSRemoveDirectory, Tcl_FSRenameFile, Tcl.FSListVolumes, Tcl_FSLink, Tcl_FSLstat, Tcl_FSUtime, Tcl_FSFileAttrsGet, TclJFSFileAttrsSet, Tcl.FSFileAttrStrings, Tcl.FSStat, Tcl_FSAccess,TclJFSOpenFileChannel,
Глава 50. Библиотеки С 1047 Tcl.FSGetCwd, Tcl.FSChdir, Tcl.FSMatchlnDirectory, Tcl.FSFileSystemlnfo и Tcl.AllocStatBuf. Для загрузки с помощью команд source и load используются процедуры Tcl.FSEvalFile и Tcl.FSLoadFile. Действия с именами файлов, обеспечивающие при этом независимость от конкретной платформы, поддерживают процедуры Tcl.FSGetPathType, Tcl_FSPathSeparator, Tcl_FSJoinPath, Tcl.FSSplitPath, Tcl.FSEqualPaths, Tcl.FSGetNormalizedPath, Tcl.FSJoinToPath, Tcl.FSConvertToPathType, Tcl.FSGetlnternalRep, Tcl.FSGetTranslatedPath, Tcl.FSNewNativePath, Tcl.FSGetNativePath и Tcl.FSGetTranslatedStringPath. Tcl.Stat, Tcl_Access, Tcl_Chdir и Tcl_GetCwd в настоящее время не рекомендованы к применению; вместо них предполагается использование соответствующих процедур Tcl_FS. Реализация виртуальной файловой системы Виртуальная файловая система (Virtual File System — VFS) реализуется путем регистрации набора процедур, поддерживающих различные особенности доступа к файловой системе. К этим процедурам относятся Tcl.FSRegister, Tcl.FSUnregister, Tcl.FSData, Tcl.FSMountsChanged и Tcl.FSGetFileSystemForPath. Поддержка потоков Библиотека Tcl обеспечивает безопасную работу с потоками. Указанные ниже процедуры предоставляют удобный кроссплатформенный интерфейс для программирования потоков. Процедуры Tcl.MutexLock, Tcl.MutexUnlock, Tcl.ConditionWait и Tcl.ConditionNotify предоставляют возможность сериализации обращения к структурам данных. Локальное хранилище для потока обеспечивается процедурой Tcl.GetThreadData. Для процедур, ориентированных на работу с потоками, отсутствуют явные действия по инициализации. Однако для них предусмотрены процедуры Tcl.ConditionFinalize и Tcl.MutexFinalize. Процедуры Tcl.CreateThread, Tcl.ExitThread, Tcl.JoinThread и Tcl.FinalizeThread поддерживают жизненный цикл потока. Tcl.CreateThreadExitHandler регистрирует процедуру, вызываемую по завершении потока. В частности, эта процедура может очищать локальное хранилище потока. Для отмены регистрации используется процедура Tcl.DeleteThreadExitHandler.
1048 Часть VI. Программирование на языке С Работа с сигналами Tcl предоставляет простой пакет для обработки сигналов и других асинхронных событий. Регистрация обработчика события осуществляется с помощью Tcl_AsyncCreate. При возникновении события обработчик помечается с помощью Tcl_AsyncMark. Когда интерпретатор Tcl находится в состоянии, допускающем безопасную обработку событий, он определяет готовность обработчиков с помощью Tcl_AsyncReady и вызывает их посредством Tcl_Asyndnvoke. Процедуру Tcl_AsyncInvoke вы также можете вызвать из вашего приложения. Для отмены регистрации обработчика используется Tcl_AsyncDelete. Нормальное завершение программы Процедура Tcl_Exit завершает приложение. Tcl_Finalize очищает память, используемую Tcl, и вызывает соответствующие обработчики, но действия этой процедуры не приводят к окончанию работы программы. Это необходимо при выгрузке Tcl DLL. Tcl_CreateExitHandler и Tcl_DeleteExitHandler устанавливают команды обратного вызова, которые выполняются при обращении к Tcl_Exit. Macintosh На платформе Macintosh предусмотрены API, позволяющие выполнять действия с ресурсами, специфическими для данной системы, и регистрировать обработчики событий. Эти API содержат процедуры Tcl_MacSetEventProc, Tcl_MacConvertTextResource, Tcl_MacEvalResource, Tcl.MacFindResource, Tcl_GetOSTypeFromObj, Tcl.SetOSTypeObj и TclJJewOSTypeOb j. Аварийное завершение Tcl_Panic используется для завершения приложения в случае, когда возникает серьезная внутренняя ошибка. TclJPanicVA позволяет задавать произвольное число параметров. Поведение Tcl^Panic, предусмотренное по умолчанию, можно изменить, установив обработчик. Сделать это позволяет процедура Tcl.SetPanicProc. Макрос panic вызывает Tcl_Panic. Прочие процедуры Процедура Tcl.GetHostName возвращает имя машины, на которой выполняется программа. Это же значение предоставляет операция info hostname. Tcl_GetTime возвращает текущее время в секундах и микросекундах.
Глава 50. Библиотеки С 1049 Tcl_Get Vers ion предоставляет информацию о версии интерпретатора Tcl. Процедура Tcl_PutEnv устанавливает окружение и может использоваться вместо put en v. Tcl_SignalId и Tcl_SignalMsg позволяют определить соответствие между номером сигнала (например, 9) и его именем (например, SIGKILL). Общие сведения о С-библиотеке Тк Главная программа и параметры командной строки Процедура Tk_Main выполняет стандартные действия по настройке главного окна вашего приложения и цикла обработки событий. Tk_ParseArgv выполняет разбор параметров командной строки. Данная процедура специально разработана для использования в главной программе. Она использует таблицу записей Tk_ArgvInf о, описывающих параметры программы. Пример использования процедуры Tk_ParseArgv см. в листинге 47.14. Если в вашем приложении используется библиотека Тк, вам необходимо установить связь с библиотекой-заглушкой. В этом случае необходимо при инициализации приложения вызвать Tk_InitStubs. Создание окон Процедура Tk_Init создает главное окно приложения. Процедуры Tk_CreateWindow и Tk_CreateWindowFromPath используются для создания окон компонентов. Реальные действия по формированию окна откладываются до наступления первого периода бездействия программы. Задать принудительное создание окна можно с помощью процедуры Tk_MakeWindowExist; удалить окно позволяет Tk_DestroyWindow. Tk_MainWindow возвращает дескриптор главного окна приложения. Tk_MapWindow и Tk_UnmapWindow используются соответственно для отображения окна и замены его пиктограммой. Обращение Tk_MoveToplevelWindow используется для позиционирования окна верхнего уровня. Процедура Tk_GetNumMainWindows возвращает число главных окон, открытых текущим процессом. Соответствие между именами окон и типами Tk_Window поддерживают процедуры Tk.Name, Tk.PathName и Tk_NameToWindow. Преобразовать идентификатор окна, используемый в операционной системе, в Tk_Window позволяет процедура Tk_IdToWindow. Процедура Tk_SetClassProcs регистрирует обработчики компонентов, которые реагируют на изменение системного шрифта и цветов, применяются
1050 Часть VI. Программирование на языке С для создания окон, специфических для платформы, и поддерживают модальные циклы ввода. Имя приложения для команды send Определить или изменить имя приложения позволяет процедура Tk.SetAppName. Имя приложения используется при передаче ему Тс1-команд посредством команды send. Настройка окон При настройке окна устанавливается его ширина, высота, вид курсора и другие характеристики. Тк предоставляет набор средств, позволяющих настроить окно и сохранить результаты. Благодаря тому что результаты сохраняются в кэше, эффективность обработки запросов существенно возрастает. Для настройки окон используются следующие процедуры: Tk_Conf igureWindow, Tk_ResizeWindow, Tk_MoveWindow, Tk.MoveResizeWindow, Tk.SetWindowBorderWidth, Tk_DefineCursor, Tk.ChangeWindowAttributes, Tk_SetWindowBackground, Tk_SetWindowColormap, Tk_Undef ineCursor, Tk_SetWindowBackgroundPixmap, Tk.SetWindowBorderPixraap, Tk_MoveWindow и Tk_SetWindowBorder. Опции командной строки Процедуры Tk.CreateOptionTable, Tk_SetOptions, Tk_GetOptionValue, Tk.GetOptionlnfo, Tk.RestoreSavedOptions, Tk_FreeSavedOptions, Tk_InitOptions, Tk_FreeConfigOptions и Tk_DeleteOptionTable используются для разбора опций командной строки. Типы объектов холста поддерживаются Tk_CanvasTagsOption. Tk_AddOption позволяет добавить значение в базу данных опций. Таким способом создаются значения опций по умолчанию. Координаты окон Координаты компонента относительно корневого окна (основного экрана) возвращает процедура Tk_GetRootCoords. Tk_GetVRootGeometry предоставляет сведения о размерах и позиции окна относительно виртуального корневого окна. Tk_CoordsToWindow размещает окно в указанной точке.
Глава 50. Библиотеки С 1051 Стек окон Управлять положением окна в стеке позволяет процедура Tk_RestackWindow. Окна, занимающие более высокое положение в стеке, заслоняют собой нижележащие окна. Информация об окнах Тк предоставляет большой объем информации о каждом окне или компоненте. Следующие макросы возвращают данные, не обращаясь к Х-серверу: Tk_WindowId, Tk_Parent, Tk_StrictMotif, TkJDisplay, Tk_DisplayName, Tk_ScreenNumber, Tk.Screen, Tk_X, Tk_Y, Tk_Width, Tk.Height, Tk_Changes, Tk.Attributes, Tk.IsMapped, Tk_IsTopLevel, Tk.IsContainer, Tk_IsEmbedded, Tk__ReqWidth, Tk.ReqHeight, Tk.InternalBorderWidth, Tk_MinReqWidth, Tk.MinReqHeight, Tk_InternalBorderLeft, Tk_InternalBorderRight, Tk_InternalBorderTop, Tk_InternalBorderBottom, Tk_Visual, TkJDepth и Tk_Colormap. Установка атрибутов компонента Процедура Tk.ConfigureWidget анализирует описание атрибутов в командной строке и выделяет такие ресурсы, как цвета и шрифты. С данной процедурой непосредственно связаны Tk_0ffset, Tk_Configurelnfo, Tk_ConfigureValue и Tk_FreeOptions. Tk_GetScrollInfo и Tk_GetScrollInf oObj выполняют разбор параметров для команд прокрутки. Выделение данных и буфер обмена Получить текущее выделение позволяет процедура Tk_Get Select ion. Очистить выделение можно с помощью Tk_ClearSelection. Регистрацию обработчиков для запросов, относящихся к выделенным данным, выполняет Tk_CreateSelHandler. Отменить регистрацию обработчика можно с помощью Tk_DeleteSelHandler. Право владения выделением заявляется с помощью Tk_OwnSelection. Для выполнения действия с буфером обмена служат процедуры Tk_ClipboardClear и Tk_ClipboardAppend. Интерфейс цикла обработки событий Стандартный цикл обработки событий реализуется посредством Tk.MainLoop. Если вы напишете свою программу обработки, то, чтобы события поддерживались средствами Tcl, вам придется обращаться к Tcl_DoOneEvent. Вызывать Tcl_DoOneEvent с флагом TCLJDONT.WAIT
1052 Часть VI. Программирование на языке С надо до тех пор, пока эта процедура не вернет значение 0, указывающее на то, что события, предназначенные для обработки, исчерпаны. Если вы собираетесь непосредственно обрабатывать события (например, вызывать Tk_CreateGenericHandler), можете перенаправить обращение требуемому обработчику посредством Tk_HandleEvent. Заметьте, что основная часть цикла обработки событий реализована в библиотеке Tcl. Исключением являются Tk_MainLoop и интерфейс обработки оконных событий, которые входят в состав библиотеки Тк. Обработка оконных событий Для установки обработчика оконных событий используется процедура Tk.CreateEventHandler. Реализация компонента предполагает наличие обработчиков для событий, связанных с отображением и изменением размеров. Для отмены регистрации служит процедура TkJDeleteEventHandler. Посредством Tk_CreateGenericHandler вы можете установить обработчики для всех оконных событий. Это находит применение при модальном взаимодействии, когда необходимо извлекать конкретные события. Если вы получили событие, которое не собираетесь обрабатывать самостоятельно, можете поместить его в очередь, вызвав для этого процедуру Tk_QueueWindowEvent. Tk_CollapseMotionEvents исключает лишние перемещения событий, находящихся в очереди. Tk_RestrictEvents позволяет осуществлять фильтрацию и устанавливать задержку для некоторых событий. Для удаления обработчика событий служит процедура Tk_DeleteGenericHandler. Tk_CreateClientMessageHandler используется для событий WM_PR0T0C0L. Удалить обработчик можно посредством Tk_DeleteClientMessageHandler. Связывание событий Средства управления связыванием экспортируются библиотекой Тк, поэтому вы можете управлять связываниями самостоятельно. Например, компонент canvas использует API, чтобы реализовать связывание для объектов холста. Управление связываниями осуществляется с помощью процедур Tk_CreateBindingTable, Tk_DeleteBindingTable, Tk.CreateBinding, Tk.DeleteBinding, Tk_BindEvent, Tk_GetBinding, Tk_GetAllBindings и Tk_DeleteAllBindings. Захват событий клавиатуры Процедуры Tk_Grab и Tk_Ungrab изменяют состояние средств захвата вво- v клавиатуры, в результате действия пользователя могут быть ограничены ретным окном.
Глава 50. Библиотеки С 1053 Обработка ошибок графического протокола Для обработки ошибок графического протокола надо зарегистрировать обработчик с помощью Tk_CreateErrorHandler. Отменить регистрацию позволяет процедура Tk_DeleteErrorHandler. В системе Unix используется асинхронный интерфейс, поэтому сообщения об ошибках становятся доступны несколько позднее, чем был выполнен недопустимый вызов. Для упрощения процесса отладки приходится отключать асинхронное взаимодействие. Сделать это позволяет программа XSynchronize. Использование базы данных ресурсов Процедура Tk_GetOption ищет записи в базе данных ресурсов. Класс ресурса для окна задается посредством Tk_SetClass, а для получения текущих установок класса используется процедура Tk.Class. Управление битовыми картами Тк поддерживает реестр именованных битовых карт (например, gray50 или questhead). Для определения новых битовых карт предназначена процедура TkJDef ineBitmap. Получить битовую карту по ее имени позволяет Tk_GetBitmap. Для работы с битовыми картами предусмотрены также следующие процедуры: Tk.NameOfBitmap, Tk_SizeOfBitmap, Tk.GetBitmapFromData, Tk.FreeBitmap, Tk_AllocBitmapFromObj, Tk.GetBitmapFromObj и Tk_FreeBitmapFromObj. Создание новых типов изображений Процедуры Tk_CreateImageType и Tk_InitImageArgs служат для регистрации и реализации новых типов изображений. Регистрации подлежат опции команды image и некоторые процедуры обратного вызова, поддерживающие создание, вывод и удаление изображений. При изменении изображения компонент, в состав которого оно входит, оповещается путем вызова процедуры Tk_ImageChanged. Процедура Tk_Name0f Image возвращает Tcl-имя изображения. Tk_GetImageMasterData предоставляет клиентские данные, связанные с данным типом. Использование изображений в составе компонентов Для поддержки компонентов, в состав которых входят изображения, предусмотрен ряд процедур. Tk_GetImage преобразует имя в структуру данных Tk.Image. Tk_RedrawImage вызывает обновление отображаемой информации. Tk_Size0f Image предоставляет сведения о размерах изображения. Если
1054 Часть VI. Программирование на языке С изображение больше не используется, следует вызвать Tk_FreeImage. Процедура Tk.DeleTclmage удаляет изображение. Изображения photo Наряду с другими типами изображений в Тк поддерживаются изображения photo. Этот тип имеет собственный С-интерфейс, позволяющий определять новые форматы. Обработчик форматов должен обеспечивать чтение и запись различных типов изображений, например GIF или JPEG. Процедура Tk.CreatePhotoImageFormat настраивает интерфейс. Существуют программные средства, поддерживающие обработчики форматов. Процедура Tk_FindPhoto отображает имя в соответствующую структуру данных Tk_PhotoHandle. Для обновления изображения используются процедуры Tk_PhotoBlank, Tk.PhotoPutBlock и Tk.PhotoPutZoomedBlock. Значения изображений могут быть получены с помощью Tk_PhotoGetImage. Управление размерами изображения осуществляется посредством Tk_PhotoExpand, Tk.PhotoGetSize и Tk_PhotoSetSize. Поддержка объектов холста С-интерфейс для определения новых объектов холста экспортируется посредством процедуры Tk_CreateItemType. Описание объекта холста включает набор процедур, которые компонент canvas использует для обращения к реализации объекта. Tk_GetItemTypes возвращает информацию обо всех типах объектов холста. Поддержка новых объектов осуществляется с помощью процедур Tk_CanvasGetCoord, Tk_CanvasDrawableCoords, Tk_CanvasSetStippleOrigin, Tk_CanvasTkwin, Tk_CanvasWindowCoords и Tk_CanvasEventuallyRedraw. Процедуры Tk.CanvasPsY, Tk.CanvasPsBitmap, Tk_CanvasPsColor, Tk_CanvasPsFont, Tk_CanvasPsPath и Tk_CanvasPsStipple упрощают генерацию Postscript- описаний. При непосредственной работе с текстовыми объектами можно использовать процедуру Tk.CanvasGetTextlnfo, которая предоставляет информацию о состоянии выделения и другие сведения об объекте. Диспетчеры компоновки Запрос на размещение компонента определенного размера осуществляется посредством процедуры Tk.GeometryRequest. Если обрамление отображается внутри области, производится вызов Tk_SetIntemalBorder. Диспетчер компоновки обрабатывает запросы, но не исключено, что размеры пространства, выделенного компоненту, будут отличаться от тех, которые запрашивались
Глава 50. Библиотеки С 1055 ранее. Процедура Tk_ManageGeometry устанавливает связь между диспетчером компоновки и компонентом. Процедура Tk_MaintainGeometry принимает меры для того, чтобы позиция окна относительно другого компонента оставалась фиксированной. Это используется при работе диспетчера компоновки place. Разорвать установленную связь можно путем вызова процедуры Tk_UnmaintainGeometry. Tk.SetGrid разрешает размещение в режиме сетки. Этот режим отключается путем вызова Tk__UnsetGrid. Идентификаторы строк Тк поддерживает базу данных строковых значений. Благодаря наличию такой базы в программе содержится только один экземпляр каждой строки. Тип Tk_Uid реализует ссылки на строки. При проверке совпадения строк в качестве идентификатора используется значение Tk_Uid, которое по сути представляет собой адрес строки. Tk_Uid используется в качестве имени при вызове GetByName. Процедура Tk.GetUid включает строку в реестр. Необходимо помнить, что таблица значений Tk_Uid занимает память. Обычно расход памяти не слишком велик. Однако если вы будете постоянно регистрировать новые строки как значения TkJUid, размеры хэш-таблицы, в которой они хранятся, будут постоянно увеличиваться. Данная таблица не очищается по завершении работы Тк. Отображение строки в постоянное значение лучше выполнять с помощью Tcl_GetIndexFromObj. Карты отображения цвета и визуальные классы Для работы с цветом используются процедуры Tk_GetColor, Tk_GetColorByValue, Tk.AllocColorFromObj и Tk_GetColorFromObj. Имя цвета позволяет получить процедура Tk_NameOfColor. Когда работа с цветом завершается, следует вызвать процедуру Tk_FreeColor или Tk.FreeColorFromObj. Получить графический контекст для вывода конкретного цвета позволяет процедура Tk_GCForColor. Цвета совместно используются различными компонентами, поэтому по окончании работы с цветом необходимо освободить соответствующий ресурс. Для выделения и освобождения карты отображения цвета используются процедуры Tk_GetColormap и Tk_FreeColormap. Карты отображения цвета по возможности используются совместно разными компонентами, поэтому для работы с ними не следует применять платформенно-ориентированные средства. Вместо этого следует использовать указанные выше процедуры. Визуальный класс окна устанавливается с помощью Tk_SetWindowVisual. Получить визуальный контекст можно с помощью Tk_GetVisual.
1056 Часть VI. Программирование на языке С Обрамления с имитацией трехмерных эффектов Трехмерный рельеф обрамления компонентов поддерживается процедурами Tk_Get3DBorder, Tk_3DBorderGC, Tk_Draw3DRectangle, Tk_Fill3DRectangle, Tk_Draw3DPolygon, Tk_Free3DBorder, Tk_Fill3DPolygon, Tk_3DVerticalBevel, Tk_3DHorizontalBevel, Tk.SetBackgroundFromBorder, Tk_Name0f3DBorder, Tk_3DBorderColor, Tk_Alloc3DBorderFrom0bj, Tk_Get3DBorderFrom0bj и Tk_Free3DBorderFrom0bj. Для подсветки, указывающей на наличие фокуса ввода, компоненты используют Tk_DrawFocusHighlight. Курсоры мыши Для работы с курсором предназначены процедуры Tk_GetCursor, Tk_GetCursorFromData, Tk.GetCursorFromObj и Tk_AllocCursorFromObj. Получить имя курсора позволяет процедура Tk_NameOfCursor. Освобождение ресурсов, соответствующих курсору, производится посредством Tk_FreeCursor или Tk_FreeCursorFromObj. Процедура Tk_SetCaretPos устанавливает позицию текстового курсора для конкретного окна. Шрифты и отображение текста Подготовка шрифта выполняется с помощью процедур Tk_GetFont, Tk_GetFontFromObj и Tk_AllocFontFromObj. Имя шрифта позволяет получить процедура Tk_NameOfFont. Освобождение ресурсов производится с помощью Tk_FreeFont или Tk_FreeFontFromObj. По мере необходимости вы можете получить информацию о шрифте с помощью процедур Tk_FontId, Tk_FontMetrics и Tk_PostscriptFontName. Процедуры Tk_MeasureChars, TkJTextWidth, Tk.DrawChars и Tk_UnderlineChars предоставляют информацию о размерах пустых строк и отображают их. Для получения размеров сообщений, состоящих из нескольких строк, отображения их и выравнивания текста предназначены процедуры Tk_Comput eTextLayout, Tk_FreeTextLayout, Tk_DrawTextLayout, TkJJnderlineTextLayout, Tk.CharBbox, TkJDistanceToTextLayout, Tk.PointToChar, Tk_IntersectTextLayout и TkJTextLayoutToPostscript. Графический контекст Графический контекст содержит информацию о цвете, шрифтах, стилях рисования линий и другие подобные сведения. Вместо того чтобы задавать эти данные при выполнении каждой графической операции, программа создает графический контекст. Конкретный контекст указывается для каждой
Глава 50. Библиотеки С 1057 графической операции. Контекст создается с помощью процедуры Tk_GetGC, а для его освобождения используется процедура Tk_FreeGC. Выделение памяти для карты пикселей Карта пикселей представляет собой простое цветное изображение. Выделение и освобождение памяти для карты пикселей производится с помощью процедур Tk_GetPixmap и Tk_FreePixmap. Экранные единицы измерения Преобразование экранных единиц измерений осуществляют процедуры Tk_GetPixels, Tk.GetPixelsFromObj, Tk_GetMMFromObj и Tk_GetScreenMM. При вызове первых двух процедур возвращается целое число пикселей, остальные возвращают размер в миллиметрах, представленный числом с плавающей точкой. Использование рельефа При отображении окон используется имитация трехмерного рельефа. Соответствие между стилями рельефа и именами поддерживают процедуры Tk.GetRelief, Tk_GetReliefFromObj и Tk_Name0fRelief. Позиция фиксации Позиция фиксации определяет расположение окна в пределах области, выделенной при компоновке. Преобразование строк в позиции фиксации производится с помощью процедур Tk_GetAnchor, Tk_GetAnchorFromObj и Tk_Name0fAnchor. Стили концов линий Существует ряд стилей, определяющих оформление концов линий. Соответствие между стилями и именами поддерживается с помощью процедур Tk_GetCapStyle и Tk_Name0f CapStyle. Стили соединения линий Сопряжение двух соединяющихся линий может производиться по-разному. Соответствие между стилями соединения линий и именами поддерживают процедуры Tk_Get JoinStyle и Tk_Name0f JoinStyle.
1058 Часть VI. Программирование на языке С Штриховые линии Tk_GetDash выполняет преобразование строки в шаблон штриховой линии. Стили выравнивания текста Соответствие между стилями выравнивания текста и именами поддерживают процедуры Tk^GetJustify, Tk.GetJustifyFromObj и Tk_Name0fJustify. Атомы Атом — это целое число, идентифицирующее строку, которая была зарегистрирована в системе. Тк поддерживает кэш значений реестра атомов. Наличие кэша позволяет избежать обращения к системе при работе с атомами. Для включения атома в реестр используется процедура Tk_InternAtom, а процедура Tk_GetAtomName возвращает имя, соответствующее атому. Управление идентификаторами ресурсов В каждой оконной системе ресурсу (например, цвету или карте пикселей) ставится в соответствие идентификатор. Процедура Tk.FreeXId освобождает идентификатор, в результате чего он может использоваться повторно. К Tk_FreeXId обращаются такие процедуры, как Tk_FreeColor и Tk_FreePixmap. Дескрипторы приложений Windows Tk.GetHINSTANCE возвращает глобальный дескриптор приложения в системе Windows. Tk.GetHWND возвращает идентификатор HWND окна Тк. Tk_HWNDToWindow отображает дескриптор Windows в соответствующее окно Тк.
ЧАСТЬ VII Изменения в составе Tcl и Тк В части VII описываются особенности различных версий Tcl и Тк, а также средства, добавленные в той или иной реализации. Глава 51 посвящена изменениям в версиях Tcl 7.4 и Tk 4.O. В главе 52 рассматриваются изменения, которые претерпели версии Tcl 7.5 иТк4.1. Средства, реализованные в версиях Tcl 7.6 и Тк 4.2, обсуждаются в главе 53. В главе 54 описывается версия Тс1/Тк 8.0, начиная с которой используются унифицированные номера версии Tcl и Тк. В главе 55 речь идет об изменениях в Tcl/Тк 8.1. Особенности Тс1/Тк 8.2 описываются в главе 56. В главе 57 рассматриваются особенности, характерные для версий Tcl/Tk 8.3. Изменения в версии Tcl/Tk 8.1 рассматриваются в главе 58.
Глава 51 Tcl 7.4/Tk 4.0 В данной главе приводится информация об обновлении приложений, созданных в ранних версиях, для работы в Tcl 7.4 и Тк 4.0. В этих версиях есть новые средства, которыми может воспользоваться автор приложения, но в то же время некоторые изменения несовместимы с предыдущими реализациями, поэтому их необходимо учитывать в программах. /адаптировать для работы в новой версии сценарии, созданные в версии 3, несложно. Изменения, которые необходимо при этом внести, не очень существенны. В данной главе описываются изменения в Тк 4.0 и новые команды, реализованные в этой версии. Оболочка wish Оболочка wish не требует указания опции -file (или -f), поэтому вы можете удалить ее из заголовков вашего сценария. Данная опция не приводит к возникновению ошибки, но необходимости в ней уже нет. Имя класса приложения формируется на основе имени файла, содержащего сценарий. Ранее для этой цели использовалось имя Тк. Например, для сценария /usr/local/bin/f oobar формируется имя класса Foobar. Средства, поддержка которых была прекращена Некоторые средства из предыдущих версий Tcl и Тк больше не поддерживаются.
Глава 51. Tcl 7.4/Tk 4.0 1061 Номер версии содержится в переменной tk_version; переменная tkVersion больше не доступна. Для кнопок отсутствуют операции activate и deactivate. Вместо этого необходимо устанавливать соответствующие значения атрибута state. В меню больше нет операций enable и disable. Вместо них также используется атрибут state. Операция cget Все компоненты поддерживают операцию cget, которая возвращает текущие значения конфигурационных опций. Приведенные ниже команды эквивалентны. lindex [$w config option] 4 $w cget опция Данные изменения не влияют на работоспособность готовых программ, но, создавая сценарии, вы можете воспользоваться новой возможностью. Подсветка при наличии фокуса ввода Каждый компонент может индицировать наличие фокуса ввода с помощью специальной подсветки. Обычно при подсветке отображается цветная рамка, которая выводится за пределами обрамления, отображаемого с имитацией трехмерных эффектов. Принято оставлять вокруг компонента свободную область небольшого размера, даже в том случае, если этот компонент не обладает фокусом ввода. Наличие подсветки фокуса ввода не нарушает работу программ, но внешний вид интерфейса несколько изменяется. В частности, элемент подсветки может частично заслонять некоторые объекты на экране. Об атрибутах компонентов, имеющих отношение к подсветке, см. в главе 40. Связывания Иерархия связываний была несколько изменена, в результате чего появилась возможность определять связывания как на уровне классов, так и на уровне экземпляров компонентов. Новая команда bindtags определяет порядок использования источников информации о связях. По мере необходимости вы можете вводить новые классы связывания (например, InsertMode). Включить вновь созданный класс в иерархию связывания позволяет команда bindtags. Порядок следования классов в команде bindtags определяет последовательность использования связываний. Команда break позволяет прекратить обработку, а команда continue задает переход на следующий уровень.
1062 Часть VII. Изменения в составе Tcl и Тк bindtags $w [list all Text InsertMode $w] В рассматриваемой версии удалены следующие события, связанные с запросом: CirculateRequest, Conf igureRequest, MapRequest и ResizeRequest. Событие Keymap также отсутствует. При проверке соответствия событий лишние модификаторы игнорируются. Несмотря на то что допускается использование модификатора Any, необходимость в нем отпадает. Модификаторы Alt и Meta стали более универсальными; они могут быть поставлены в соответствие символам клавиш Alt_L, Alt Л, Meta_L и Meta_R. Подробно о связывании см. в главе 29. Полосы прокрутки Интерфейс между полосами прокрутки и использующими их компонентами также претерпел изменения. К счастью, для большинства сценариев эти изменения прозрачны. Если для связывания компонентов и полос прокрутки вы используете рекомендуемые средства, ваши программы будут совместимы с новым интерфейсом. Если же вы непосредственно обращаетесь к командам xview и yview компонента, вам, возможно, придется модифицировать код. Прежний интерфейс продолжает поддерживаться, но в новой версии появились средства, предоставляющие возможность более строгого контроля над полосами прокрутки. Вы можете запросить состояние отображаемых данных, поэтому вам нет необходимости обращаться к команде set. Для компонентов, допускающих прокрутку, были введены ограничения, в результате которых окончание данных всегда совпадает с нижней (или правой) границей области отображения. В большинстве случаев внесенные изменения не влияют на работоспособность программ. О работе с полосами прокрутки см. в главе 33. Команда pack В версии 3 Тк был введен новый синтаксис команды pack, однако старые правила использования этой команды по-прежнему поддерживались. Сказанное справедливо практически для всех вариантов pack, за исключением команды pack info. Если вы продолжаете использовать старый формат, вам, возможно, следует воспользоваться возможностью перехода к новому синтаксису. Проблема с pack info состоит в том, что семантика данной команды была изменена. Новая операция называется pack newinf о. Старый вариант, pack info, возвращал список всех дочерних компонентов для окна и их характеристики, имеющие отношение к компоновке. Теперь pack info возвращает
Глава 51. Tcl 7.4/Tk 4.0 1063 конфигурацию конкретного ведомого элемента. Вам следует сначала задать команду pack slaves, чтобы получить список всех ведомых компонентов, а затем использовать новую команду pack info для получения информации о их конфигурации. Подробно о диспетчере компоновки pack см. в главе 25. Поддержка фокуса ввода Механизм поддержки фокуса ввода был модернизирован для поддержки фокуса на нескольких экранах. Команде focus передается опция -displayof. Tk запоминает, какой из компонентов в пределах окна верхнего уровня имел фокус ввода. Когда диспетчер окон передает фокус ввода окну верхнего уровня, Tk автоматически связывает фокус с требуеАмым компонентом. Опция -lastf or позволяет выяснить, какой компонент в окне верхнего уровня должен получить фокус. Описание вопросов передачи фокуса ввода см. в главе 39. Команды focus default и focus none больше не поддерживаются. Необходимость в команде focus default уже не возникает, а чтобы воспользоваться функциональными возможностями focus none, достаточно передать пустую строку обычной команде focus. Процедура tk_focusFollowsMouse теперь поддерживает новую модель, согласно которой при перемещении курсора мыши компонент автоматически получает фокус ввода. Процедуры tk_f ocusNext и tk_f ocusPrev поддерживают передачу фокуса ввода между компонентами посредством клавиатуры. Для большинства компонентов определены связывания для событий <ТаЬ> и <Shift-Tab>. Команда send Команда send также подверглась изменениям. Теперь значение тайм-аута, равное пяти секундам, не действует; вместо этого время ожидания ответа не ограничено. Если вы не хотите ожидать результата, вам надо задать опцию -async. Вы также можете задать альтернативный дисплей с помощью опции -displayof. Описание команды send см. в главе 43. Определить имя приложения и установить новое имя можно с помощью команды tk appname. Ее надо использовать вместо winfo name ".". В результате изменения команды send стало невозможным использовать ее для обмена между Tk 4.0 и более ранними версиями.
1064 Часть VII. Изменения в составе Tcl и Тк Внутреннее дополнение Для кнопок и текстовых меток действуют новые установки по умолчанию, касающиеся выделения пространства вокруг текста. Теперь размер свободного пространства увеличен; в результате, если вы будете использовать значения атрибутов padX и padY, принятые по умолчанию, размеры кнопок увеличатся. Ранее дополнение устанавливалось равным одному пикселю. Новые значения по умолчанию равны Зт для padX и 1т для padY. Между кнопками и другими подобными компонентами имеются существенные различия. Для простых кнопок, независимо от значений padX и padY, добавляются два дополнительных пикселя. Таким образом, если вы хотите, чтобы размеры переключателей и флажков опций, компонентов menubutton и кнопок были одинаковы, вам надо для всех компонентов, кроме простых кнопок, увеличить дополнение на два пикселя. Значения переключателей опций Значением radiobutton по умолчанию больше не является имя компонента. Вместо этого принимается пустая строка. Убедитесь, что для данного компонента задана опция -value. Поле редактирования Для полей редактирования атрибут scrollCommand заменен на xScrollCoramand. Это сделано для обеспечения совместимости с другими компонентами, допускающими горизонтальную прокрутку. По той же причине операция view переименована в xview. О полях редактирования см. в главе 34. В операции delete изменено назначение второго индекса. Теперь он ссылается на символ, непосредственно следующий за текстом, к которому применяется данная операция. Подобным образом изменены операции с выделенными фрагментами. Индекс sel.last ссылается на символ, следующий за выделением, поэтому удаление от sel.first до sel.last воздействует только на выделенный текст. Соответствующим образом были модифицированы связывания, используемые по умолчанию, но, создавая новые связывания, вам необходимо учесть данные изменения.
Глава 51. Tcl 7.4/Tk 4.0 1065 Меню Меню, связанное с menubutton, должно быть по отношению к последнему дочерним компонентом. Аналогично, при создании каскадных меню, меню нижнего уровня должны быть дочерними для меню более высокого уровня. Значение @у всегда представляет собой допустимый индекс; это справедливо даже в том случае, если курсор мыши не указывает ни на один пункт. В этом случае возвращается индекс пункта, ближайшего к курсору. Атрибут selector теперь называется selectColor. Операция postcascade отображает меню, соответствующее указанному пункту. $menu postcascade индекс Операция insert включает пункт меню перед указанным пунктом. $menu insert индекс тип опции ... Подробно о меню см. в главе 30. Окна списков В Тк 4.0 окна списков были несколько изменены. Детально о данном компоненте см. в главе 35. В настоящее время поддерживаются четыре стиля выделения Motif; два из них обеспечивают возможность выделения пунктов, непосредственно не следующих один за другим. Процедура tk_listboxSingleSelect теперь отсутствует. Вместо этого устанавливаются соответствующие значения атрибута selectMode. В окне списка присутствует активный элемент, при отображении которого используется подчеркивание. Он определяется с помощью индекса active. Команды выделения для компонента listbox изменились. Вместо выражений $listbox select from индексе $listbox select to индекс_2 используются следующие: $listbox select anchor индекс^! $listbox select set anchor индекс_2 При выполнении операции set задаются два индекса. Одним из них может быть индекс anchor, определяющий начало выделенного фрагмента. По мере необходимости вы можете отменить часть выделения, а также проверить, есть ли в окне списка выделенные пункты. Команда для очистки выделения была изменена. Теперь при ее вызове задается один или два индекса.
1066 Часть VII. Изменения в составе Tcl и Тк Выражение $listbox select clear теперь записывается следующим образом: $listbox select clear 0 end Атрибут geometry Для компонентов frame и listbox. а также для окон верхнего уровня атрибут geometry больше не поддерживается. Вместо этого используются атрибуты width и height. Заметьте, что для окон списков значения width и height задаются как число строк и символов, а для фреймов и окон верхнего уровня данные значения данных атрибутов указываются в экранных единицах измерения. Текстовый компонент Дескрипторы и маркеры текстового компонента несколько упрощены, добавлены средства управления выравниванием и междустрочными интервалами, появилась возможность определять табуляторы. Кроме того, вы теперь можете включать в состав текстового компонента другие компоненты. В новой версии с маркером связывается левое или правое направление, которое определяет, каким образом происходит включение символа в позиции маркера. Если указано правое направление, включение осуществляется так, как в предыдущих версиях: маркер передвигается вправо и располагается непосредственно после включенного текста. Если указано левое направление, позиция маркера относительно начала текста остается неизменной. По умолчанию используется правое направление. Задать направление можно с помощью операции mark gravity. Включаемый текст испытывает на себе влияние дескрипторов, действующих с обеих сторон текущей позиции. В ранних версиях новый текст наследовал дескрипторы, соответствующие символу слева от текущей позиции. Если действия по умолчанию не устраивают вас, вы можете непосредственно указывать дескрипторы при включении текста. Операция scan компонента поддерживает горизонтальную прокрутку. Вместо @у теперь надо указывать идентификатор (Эх,у. Описание текстового компонента см. в главе 36.
Глава 51. Tcl 7.4/Tk 4.0 1067 Атрибуты управления цветом В табл. 51.1 приведены имена атрибутов, имеющих отношение к управлению цветом, которые в новой версии были изменены. Подробное описание атрибутов см. в главе 41. Таблица 51.1. Изменения в именах атрибутов управления цветом Тк 3.6 Тк4.0 selector selectColor Scrollbar.activeForeground Scrollbar.activeBackground Scrollbar.background troughColor Scrollbar.foreground Scrollbar.background Scale.activeForeground Scale.activeBackground Scale.background troughColor Scale.sliderForeground Scale.background (отсутствует) highl ightBackgr ound (отсутствует) highl ightColor Работа с цветом и команда tk colormodel В Tk 3.6 при переполнении карты отображения цвета процесс распределения цвета мог окончиться неудачей. В этом случае средства Tk возвращались к монохромной модели с использованием только черного и белого цвета. Команда tk colormodel предоставляет информацию о цветовой модели и позволяет установить новую модель. В Tk 4.0 распределение цвета осуществляется корректно, т.е. выбирается наиболее близкий из доступных цветов. В результате операция tk colormodel больше не поддерживается. Для определения характеристик дисплея используется команда winfo visual (см. главу 41). Атрибут scrollincrement Атрибут scrollincrement компонента canvas был заменен двумя атрибутами: xScrollIncrement и yScrollIncrement. По умолчанию для данных атрибутов принимается значение, равное одной десятой высоты (ширины) холста. Приращение при прокрутке на одну страницу выбирается равным девяти десятым соответствующего размера.
1068 Часть VII. Изменения в составе Tcl и Тк Выделение Средства поддержки выделения в Тк 4.0 обобщены для работы с различными типами, например CLIPBOARD и SECONDARY. Изменения не влияют на работоспособность уже имеющихся команд, но могут использоваться при создании новых программ. Некоторые инструментальные средства, в частности OpenLook, позволяют включать данные только из буфера обмена. Подробно о работе с выделенными данными см. в главе 38. Команда bell Команда bell воспроизводит звуковой сигнал средствами терминала. Программа xset позволяет изменять такие характеристики сигнала, как громкость и длительность. Подробно о команде bell см. в главе 32.
Глава 52 Tcl 7.5/Tk 4.1 При разработке Тк 4.1 основное внимание уделялось кроссплат- форменной поддержке. Сценарии Тк могут выполняться в системах Windows, Macintosh и Unix. Изменения в Tcl 7.5 затронули подсистему ввода-вывода, сетевые гнезда и работу с несколькими интерпретаторами. -/ХАРАКТЕРНЫМИ особенностями Tcl 7.5 и Тк 4.1 являются кроссплатфор- менная поддержка, измененные сетевые гнезда, возможность использования нескольких Tcl-интерпретаторов и расширенная команда f oreach. Выполнение сценариев на нескольких платформах Реализация кроссплатформенной поддержки позволила запускать сценарии Tcl/Tk в неизменном виде в системах Unix, Windows и Macintosh. Однако программы еще не стали в полной мере платформенно-независимыми. Основной причиной "привязки" к конкретной платфоме остается возможность вызывать из сценария исполняемые программы или обращаться к расширениям, написанным на языке С. По этой причине при переносе сценария в другие системы приходится прикладывать некоторые усилия по их адаптации. Обработка имен файлов Соглашения по именованию файлов различаются на разных платформах. Для поддержки действий с именами файлов, независимо от используемой платформы, были добавлены новые операции. К ним относятся file join.
1070 Часть VII. Изменения в составе Tcl и Тк file split и file pathtype (см. главу 9). В 7.6 были реализованы дополнительные команды для копирования, удаления и переименования файлов. Преобразование символа новой строки В системах Windows и Macintosh действуют различные соглашения по представлению перевода строки в файлах. Эти различия автоматически учитываются в новой подсистеме ввода-вывода. Кроме того, для управления преобразованием можно использовать команду fconfigure (см. главу 16). Переменная tcl_platform Как правило, в программах объем платформенно-ориентированного кода невелик. Массив tcl_platform (см. главу 13) содержит информацию о компьютере и об операционной системе, под управлением которой выполняется сценарий. Платформенно-ориентированный код можно поместить в файл с именем, отражающим тип платформы, изолировав таким образом этот код от остальной части программы. Ниже приведена команда, которая загружает из библиотеки либо unix.tcl. либо windows.tcl, либо macintosh.tcl. source [file join $lib $tcl_platforra(platform).tcl] Команда console Версии wish для Windows и Macintosh содержат встроенную консоль. Команды, введенные с консоли, обрабатываются главным интерпретатором Tcl. Для устранения конфликтов консоль выполняется под управлением другого Tcl-интерпретатора. Отобразить и скрыть консоль позволяет команда console (см. главу 2). Команда clock Команда clock используется для получения текущего времени вместо exec date. Пример вызова этой команды приведен ниже. clock format [clock seconds] Для операции format предусмотрена необязательная строка формата, позволяющая управлять представлением даты и времени. Операция clock scan предназначена для разбора значений времени, a clock clicks дает возможность получать значения с высоким разрешением. О команде clock см. в главе 13.
Глава 52. Tcl 7.5/Tk 4.1 1071 Команда load Команда load поддерживает общедоступные библиотеки (т.е. DLL), реализующие новые Tcl-команды. Учитывая этот факт, можно порекомендовать оформлять расширения в виде разделяемых библиотек. При этом исчезает необходимость компилировать специальные варианты wish. Вопросы создания разделяемых библиотек см. в главе 47. Ниже приведен пример загрузки библиотеки Tix. load libtix.so Tix Команда info реализует операции sharedlibextention и nameof executable (см. главу 13). Команда package Команда package предоставляет альтернативные средства организации библиотек сценариев. Она также поддерживает расширения, предназначенные для загрузки командой load. Команда package обеспечивает работу в рамках модели запроса-предоставления, согласно которой пакеты предоставляются библиотечными сценариями, а приложение указывает необходимые ему пакеты с помощью команды package require. Рассматриваемая здесь команда по необходимости может поддерживать различные версии пакета. Вопросы работы с пакетами см. в главе 12. Использование нескольких переменных цикла Команда ioreach позволяет работать с несколькими неременными цикла и списками значений. Это означает, что вы можете в пределах одной итерации присваивать значения нескольким переменным. Значения могут содержаться в одном или в нескольких списках, обрабатываемых параллельно. О работе в цикле ioreach с несколькими переменными см. в главе 6. Пример работы в цикле с массивом показан ниже. foreach {name value} [array get arrName] { # Значением является arrName($name) }
1072 Часть VII. Изменения в составе Tcl и Тк Перенос цикла обработки событий из Тк в Tcl Цикл обработки событий был перенесен из Тк в Tcl. Этот перенос осуществлен с целью поддержки сетевых гнезд. Теперь команды after и update доступны в Tcl. Для поддержки неблокирующего ввода-вывода была реализована команда f ileevent. В Tcl добавлена команда vwait, предоставляющая те же возможности, что и команда tkwait variable. Подробно о вводе-выводе, управляемом событиями, см. в главе 16. Команда tkerror была заменена bgerror. Эта процедура вызывается в тех случаях, когда ошибка возникает при обработке события. Наличие обратной совместимости дает возможность выполнять те программы, в которых уже определена процедура tkerror. Описание процедур tkerror и bgerror см. в главе 13. Сетевые гнезда Команда socket обеспечивает доступ к гнездам TCP/IP. Функции С API позволяют определять новые каналы, а специальные расширения обеспечивают поддержку UDP и других протоколов. Описание сетевых гнезд см. в главе 17. Пример использования гнезд вместо Tk-команды send см. в листинге 43.4. Команда info hostname Команда info hostname была добавлена для того, чтобы программа могла определять идентификатор узла. Команда fconfigure Наилучший способ использования гнезд — это организация ввода-вывода, управляемого событиями. Частично решение этой задачи обеспечивает команда fileevent. При написании программ может возникнуть необходимость организовать контроль режима блокирования и буферизации гнезд. Сделать это позволяет команда fconfigure. Вы также можете управлять преобразованием символа перевода строки и получать информацию о параметрах конкретного гнезда. Описание команды fconfigure см. в главе 16.
Глава 52. Tcl 7.5/Tk 4.1 1073 Использование нескольких интерпретаторов и Safe-Tcl Описание команды interp и механизма защиты Safe-Tcl см. в главе 19. В своем приложении вы можете создать несколько Tcl-интерпретаторов и управлять ими с помощью команды interp. Если интерпретатор работает в защищенном режиме, набор его Tcl-команд ограничен таким образом, что сценарии не могут повредить вашу систему или приложение. С помощью псевдонимов можно предоставить сценариям, не пользующимся доверием, ограниченный доступ к ресурсам. Диспетчер компоновки grid Описание нового диспетчера компоновки grid, который предоставляет возможность размещать компоненты в виде таблицы, см. в главе 26. Подобно диспетчеру pack, grid автоматически корректирует размещение при добавлении или удалении компонентов, а также при изменении их размеров. Текстовый компонент Дли текстового компонента был реализован ряд новых операций. Опера- ция dump позволяет получить информацию о компоненте, включая сведения о дескрипторах, маркерах и встроенных окнах. Операции mark next и mark previous обеспечивают поиск маркеров. Операция tag prevrange является дополнением к имеющейся операции tag nextrange. Поле редактирования Для полей редактирования была добавлена операция bbox. Она позволяет оптимизировать связывания, затрагивающие выделение символов и действия с курсором ввода.
Глава 53 Tcl 7.6/Tk 4.2 В Тк 4.2 были модернизированы средства кроссплатформенной поддержки, включая виртуальные события, варианты команды file, команду exec в системе Windows и диалоговые окна, предназначенные для установки цвета и выбора файлов. В диспетчере компоновки grid был реализован улучшенный алгоритм размещения компонентов. LJ Тк 4.2 диспетчер компоновки grid претерпел существенные изменения, в частности, был переработан алгоритм размещения компонентов. Кроссплат- форменные средства были модернизированы путем добавления стандартных диалоговых окон и поддержки виртуальных событий. В Tcl 7.6 были улучшены команда exec и средства поддержки конвейерной обработки. В версии для системы Macintosh была повышена производительность программ за счет модификации механизма выделения памяти. Дополнительные операции file Команда file была дополнена операциями copy, rename, delete и mkdir (см. главу 9). Виртуальные события Новая команда event определяет такие виртуальные события, как «Cut» «Copy» и «Paste». Эти виртуальные события отображаются в физические события для каждой платформы. Например, виртуальное событие «Сору» соответствует событию <Control-c> в системе Windows
Глава 53. Tcl 7.6/Tk 4.2 1075 и <Command-c> в системе Macintosh. Виртуальные события можно использовать в сценариях, кроме того, в процессе работы над приложениями вы можете определять для него новые виртуальные события. Вы также можете использовать команду event для генерации событий с целью тестирования программ. О виртуальных событиях и о командах event см. в главе 30. Стандартные диалоговые окна В новой версии Тк было добавлено несколько стандартных диалоговых окон. Они позволяют отображать предупреждающие сообщения, выводить приглашения для ввода данных пользователем, устанавливать цвета и выбирать файлы. Внешний вид диалоговых окон соответствует стилю, принятому в той системе, в которой выполняется приложение. Например, для того чтобы предложить пользователю ввести ответ yes или по, надо использовать приведенный ниже фрагмент кода. tk.messageBox -type yesno \ -message "Ok to proceed?" \ -icon question => yes Открыть существующий файл можно следующим образом: set file [tk_getOpenFile] Описание стандартных диалоговых окон см. в главе 39. Диспетчер компоновки grid Алгоритм компоновки диспетчера grid был тщательно пересмотрен, и в него было внесено много изменений. Веса строк и столбцов, влияющие на изменение размеров, теперь представляются не числами с плавающей точкой, а целочисленными значениями. Для столбцов и строк был добавлен атрибут -pad, обеспечивающий дополнение соответственно столбца или строки. Операции columnconf igure и rowconf igure, вызванные без параметров, теперь возвращают текущие установки. Для диспетчера компоновки grid были добавлены две новые операции. Операция update вызывает обновление размещения компонентов. Операция remove удаляет компонент из таблицы, но запоминает установки для него так, что его можно впоследствии включить снова.
1076 Часть VII. Изменения в составе Tcl и Тк Команда unsupportedl в системе Macintosh Команда unsupportedl предоставляет доступ к различным оконным стилям в системе Macintosh. При наличии соответствующей поддержки может использоваться операция style команды wm, однако необходимо помнить, что эта операция является специфической для системы Macintosh pi средства ее поддержки могут быть реализованы не полностью. Однако вы можете использовать данную операцию для получения стилей окон Macintosh.
Глава 54 Tcl/Tk 8.0 В рамках Tcl 8.0 был создан компилятор, преобразующий исходный текст Tcl в байтовый код. Данный компилятор, в зависимости от команд, содержащихся в сценарии, повышает его производительность в 2-20 раз. Версии Тк был присвоен номер, соответствующий номеру версии Tcl. В Тк 8.0 поддерживаются кнопки, меню и полосы прокрутки, внешний вид которых согласован с внешним видом элементов, принятых на текущей платформе. Гибкие средства управления шрифтами обеспечивают независимость от конкретной платформы. JD Tcl 8.0 был добавлен компилятор, преобразующий исходный текст в байтовый код. Использование этого компилятора существенно увеличивает производительность программ. Компилятор "прозрачен^' для Tcl-сценариев, в результате разработчикам не приходится принимать специальные меры для того, чтобы воспользоваться его возможностями. Кроме того, в Tcl были реализованы средства поддержки двоичной информации. В настоящее время можно, не подвергая опасности программу, помещать двоичные данные в Tcl-переменные. Новые команды поддерживают преобразование строкового представления информации в двоичное и наоборот. В Тк 8.0 обеспечивается согласование стиля отображения для систем Unix, Windows и Macintosh. Это касается внешнего вида кнопок, меню и полос прокрутки. Кроссплатформенные средства поддержки шрифтов также упрощают работу над программами. Тк позволяет применять встраиваемые приложения. Эта возможность используется при создании дополнительных модулей для браузеров (см. главу 20).
1078 Часть VII. Изменения в составе Tcl и Тк Tcl-компилятор Tcl-компилятор обеспечивает преобразование текста в процессе выполнения программы и практически "прозрачен" для Tcl-сценариев. При первом запуске сценария компилятор транслирует текст программы в байтовый код. При повторном выполнении элементов сценария, например тела цикла или тела процедуры, используется уже готовый байтовый код. В случае переопределения процедуры байтовый код создается повторно. Вместо строковой модели, принятой в ранних версиях Tcl, компилятор использует модель двойных объектов. Двойные объекты содержат строковые значения и платформенно-ориентированное представление данных, например, целые числа, числа с плавающей точкой двойной точности или байтовый код, созданный компилятором. Благодаря такой структуре объекта становится возможным сохранять результаты преобразования различных представлений данных. Описание объектной модели см. в главе 47. Выигрыш в производительности зависит от особенностей приложения. Математические выражения и списки обрабатываются более эффективно. В среднем при использовании компилятора следует ожидать двукратного увеличения производительности. В некоторых случаях удается повысить скорость выполнения программы в 10-20 раз. Выявление ошибок при компиляции При использовании компилятора некоторые типы ошибок выявляются гораздо раньше, чем при работе с обычным интерпретатором. При первом обращении к процедуре компилятор преобразует ее в байтовый код. Если в конце процедуры имеется синтаксическая ошибка, процедура не выполняется. Подобная ситуация может возникать и при работе со списками. Если строка не является корректным списком, команда list прекращает работу с ней даже в том случае, если элементы списка, подлежащие обработке, представлены абсолютно правильно. Для сравнения, lindex обрабатывает только ту часть списка, в которой находятся требуемые элементы. В Tcl 8.0 весь список преобразуется в форму, специфическую для конкретной платформы. Ошибки в конце списка делают невозможным использование элементов, расположенных в его начале. Это в особенности важно тогда, когда списочные операции применяются к данным, вводимым пользователем. Поддержка двоичных строк В Tcl 8.0 поддерживаются двоичные данные. Это означает, что нулевой байт в составе строки не завершает ее. Вместо признака конца строки Tcl использует в строковых объектах счетчики байтов.
Глава 54. Tcl/Tk 8.0 1079 Команды binary format и binary scan поддерживают преобразование двоичных данных в строки (см. главу 59). Команда unsupported*} была модифицирована и получила имя f сору (см. главу 17). Пространства имен В главе 14 описаны пространства имен, которые выделяют в глобальной области видимости разделы для переменных и процедур. Создавая сценарии, не обязательно использовать пространства имен. В простых программах вполне можно обойтись без них, однако в приложениях большого объема пространства имен очень полезны, так как предоставляют дополнительные средства структурирования. В библиотечных пакетах пространства имен следует использовать для того, чтобы исключить конфликты при совместном использовании. Safe-Tcl К модели защиты Safe-Tcl были добавлены скрытые команды. Вместо того чтобы удалять из интерпретатора команды, не обеспечивающие безопасную работу, их оформляют как скрытые. Ведущий интерпретатор может вызывать скрытые команды в ведомом интерпретаторе. Для выполнения таких команд необходимо обеспечить корректный контекст. Для команды interp были реализованы новые операции: invokehidden, hide, expose и hidden. Описание скрытых команд см. в главе 19. В состав Tcl-интерфейса были включены средства инициализации интерпретатора с использованием защищенной базы, вследствие чего стали возможными автозагрузка и применение стандартного псевдонима exit. Процедуры safe: :interpCreate и safe: :interplnit обеспечивают создание или инициализацию ведомого интерпретатора с предоставлением защищенной базы. В новой версии Tcl упрощена процедура safe: : interpDelete. О защищенной базе см. в главе 19. Для поддержки политики безопасности Trusted была добавлена команда interp marktrusted. Она позволяет преобразовать интерпретатор, не пользующийся доверием, в интерпретатор, пользующийся доверием. Очевидно, что вызывать эту команду может только ведущий интерпретатор. Новый вариант Isort Команда lsort подверглась существенным изменениям. Новая реализация данной команды является реентерабельной, т.е. вы можете использовать
1080 Часть VII. Изменения в составе Tcl и Тк ее в составе функции сортировки, вызываемой самой lsort. Новые опции снизили потребность в процедурах сортировки, определяемых пользователями. Опция -dictionary выполняет сортировку независимо от регистра символов и обеспечивает более качественную поддержку чисел. Опция -index сортирует списки по значению ключевого поля. Об использовании lsort см. в главе 5. Переменная tcl_precision В реализации 8.0р2 переменная tcl_precision была удалена, а в реализации 8.0.3 — возвращена снова. Значение этой переменной по умолчанию было увеличено с 6 до 12, что достаточно для большинства приложений. Соглашения 2000 Команда clock преобразует двухсимвольное представление кода в соответствии со стандартными соглашениями. Значения 70-99 отображаются в значения 1970-1999. Значения 00-69 отображаются в значения 2000-2069. Возможность обрабатывать данные после 2037 года и до 1903 года может быть ограничена размером целочисленных переменных в конкретной операционной системе и значением начала эпохи, которое в Windows и большинстве версий Unix соответствует 1 января 1970 года. Пакет http К библиотеке Tcl-сценариев была добавлена реализация протокола HTTP/ 1.0. Описание команды http: :geturl см. в главе 17. Обмен через последовательные линии связи К fconfigure была добавлена поддержка обмена через последовательные линии. Опция -mode позволяет задать скорость обмена, паритет, а также число битов данных и стоповых битов. Описание опции -mode команды fconfigure см. в главе 16. В Windows предусмотрены специальные имена устройств, например coml и сот2. При выполнении команды open эти устройства всегда считаются последовательными. В Unix описания устройств содержатся в каталоге /dev.
Глава 54. Tcl/Tk 8.0 1081 Интерактивные приложения могут обращаться к текущему терминалу как к устройству /dev/tty. На момент написания данной книги в Macintosh отсутствовала возможность открывать последовательные устройства. Возможно, для этой цели будет реализована команда command или введена новая опия команды open. Платформенно-независимые шрифты В Тк 8.0 была введена система именования шрифтов, обеспечивающая независимость от конкретной платформы. Такие имена, как times 10 bold, интерпретируются в любой операционной среде. Команда font позволяет создавать объекты шрифтов, которые могут быть поставлены в соответствие компонентам. Команда font metrics возвращает подробную информацию о размере шрифта. Детально о команде font см. в главе 42. Команда tk scaling Команда tk scaling (см. главу 44) запрашивает или устанавливает отображение пикселей в пункты. При работе со шрифтами размеры чаще всего определяются в пунктах. Для холста наряду с пунктами используются другие экранные единицы измерения. Включение приложений Tk поддерживает включение приложений. Для фреймов и окон верхнего уровня предусмотрен атрибут -container, который указывает на наличие включенной программы. Данная опция необходима для обеспечения работы диспетчеров компоновки и поддержки протоколов передачи фокуса ввода. Опция -use фреймов и окон верхнего уровня включает их в существующие окна. Опцию -use также поддерживает программа wish. О включении окон см. в главах 20 и 32. Платформенно-ориентированные меню В Tk 8.0 реализован механизм отображения меню в виде, специфическом для конкретной платформы. Разработчик определяет меню и связывает его с окном верхнего уровня. В Macintosh при активизации окна строка меню заменяет главное меню в верхней части экрана. В системах Windows и Unix строка меню располагается вдоль верхней границы окна. Подробно о работе с меню см. в главе 30.
1082 Часть VII. Изменения в составе Tcl и Тк Меню, сформированные путем активизации пункта разъединения, отслеживают все изменения в меню, на базе которых они были созданы. В связи с этим атрибут transient был заменен атрибутом type. Для создания меню, состоящего из нескольких колонок, используется атрибут columnbreak. Толщина обрамления В Unix толщина обрамления изменена так, чтобы обеспечить согласованность с CDE. Платформенно-ориентированные кнопки и полосы прокрутки Кнопки, меню и полосы прокрутки широко применяются в системах Windows и Macintosh. Для создания профессионального приложения очень важно, чтобы его внешний вид был выдержан в том же стиле, что и большинство других программ, выполняющихся в той же системе. Связывания для текстовых компонентов и полей редактирования также изменены в соответствии со стандартами, принятыми на той или иной платформе. Примеры одной и той же Tk-программы, выполняющейся на различных платформах, см. в главе 24. На всех платформах кнопки поддерживают атрибут default, который может иметь одно из трех значений: active, normal или disabled. В состоянии active отображается кнопка, заданная по умолчанию. Кнопка, находящаяся в состоянии normal, выглядит как обычная кнопка, но при ее отображении резервируется место для подсветки, индицирующей наличие активного состояния. В состоянии disabled кнопка может иметь несколько меньшие размеры. Связывания, позволяющие выполнять действия с кнопками посредством клавиатуры, должен создать разработчик. Изображения в составе текстового компонента В состав текстового компонента можно включать изображения. Такие изображения практически не отличаются от встроенных окон, но действия с ними осуществляются более эффективно (см. главу 36).
Глава 54. Tcl/Tk 8.0 1083 Команда destroy Ранее команда destroy генерировала ошибку, если указанное окно не существовало. Теперь в подобной ситуации ошибка не возникает. Команда grid При вызове операций grid columnconf igure и grid rowconf igure указывается параметр, определяющий строку или столбец. Это значение может представлять собой список: grid columnconfigure {0 3} -weight 1 Модификации версии 8.0 В связи с переходом Джона Остераута из Sun Microsystems в Scriptics Corporation работа над версией 8.0 несколько замедлилась. Реализация 8.0р2 была выпущена осенью 1997 года, приблизительно в то же время вышла первая альфа-версия 8.1. Приблизительно через год появился целый ряд модификаций: 8.0.3, 8.0.4 и 8.0.5; их выпуск был связан с реализациями инструментальных средств TclPro. Основные изменения в этих модификациях имели отношение к функциям С API, которые были добавлены для поддержки TclPro Wrapper и TclPro Compiler. Описание данных инструментов см. в главе 13. Лишь немногие изменения, внесенные после 8.0р2, реально влияли на работу по созданию Tcl-сценариев. Они описаны ниже. Опция -error команды fconfigure Новая опция -error команды fconfigure позволяет определить, имели ли место неудачные попытки соединения посредством асинхронного гнезда. В случае, если соединения были установлены корректно, команда, вызванная с указанием данной опции, возвращает пустую строку. В противном случае она возвращает сообщение об ошибке. Элемент tcl_platform(debug) К переменной tcl_platf orm был добавлен новый элемент, указывающий на то, что при компиляции были включены символы отладки. Если в системе Windows приложение, скомпилированное с отладочными символами, попытается загрузить библиотеку, скомпилированную без символов отладки, то при этом не исключены ошибки. Аналогичная ситуация возникнет и в том случае, если приложение, скомпилированное без символов отладки, загрузит
1084 Часть VII. Изменения в составе Tcl и Тк DLL, содержащую подобные символы. Данная проблема связана с особенностями библиотеки Microsoft С. Имя библиотеки, построенной с использованием символов отладки, оканчивается буквой d (например, tcl80d.dll). Проверив значение tcl_platform(debug), приложение может выбрать нужный вариант библиотеки. Процедура tcl findLibrary Процедура tcl_f indLibrary была добавлена для того, чтобы упростить поиск каталога библиотеки. Данная процедура используется Тк и другими расширениями. Для поиска библиотеки сценариев Tcl использует сложную систему путей. Поиск осуществляется относительно расположения tclsh или wish и предполагает наличие стандартной среды. Процедура поиска поддерживает несколько инсталляций Tcl и помогает расширениям найти их библиотеки. Процедура tcl_f indLibrary вызывается следующим образом: tcl_findLibrary база версия модификация сценария переменная_окружения результирующая_переменная В качестве базы принимается префикс имени каталога, содержащего библиотеку сценариев. Вторым параметром задается главный номер версии (например, 8.0). Модификацией считается полный номер (например, 8.0.3). Под сценарием подразумевается инициализационный сценарий, содержащийся в каталоге и предназначенный для загрузки с помощью команды source. Переменная окружения используется для определения пути поиска по умолчанию. Последний параметр содержит выходные данные. В указанную переменную помещается имя каталога, найденное tcl_f indLibrary. Побочным эффектом вызова tcl_f indLibrary является загрузка сценария, содержащегося в каталоге. Ниже приведен пример использования данной процедуры. tcl_findLibrary tk 8.0 8.0.3 tk.tcl TK.LIBRARY tk_library Описание процедуры tcl_f indLibrary см. в главе 12. Процедура auto_mkindex_old Процедура auto_mkindex была переработана Майклом Макленнаном (Michael McLennan) для поддержки классов и методов [incr Tcl]. Семантика auto_mkindex изменена, и теперь все процедуры индексируются. Ранее индексации подлежали лишь процедуры, в определении которых в начале строки присутствовало слово ргос. Новая реализация загружает код в защищенный интерпретатор и выявляет команды ргос при выполнении программы, не учитывая, каким образом они были заданы в исходном файле. Старая версия auto_mkindex сохранена под именем auto_mkindex_old и предназначена
Глава 54. Tcl/Tk 8.0 1085 для тех приложений, в которых отступ в определении процедур используется, чтобы исключить их индексацию. Символы клавиш Windows для работы с меню На клавиатуре Microsoft имеются специальные клавиши, предназначенные для быстрого вызова меню Start и для переключения меню. Для них введены новые символы клавиш Арр, Menu_L и Menu_R. Событие MouseWheel Событие MouseWheel было добавлено для поддержки колесика прокрутки, которым оснащены некоторые типы мыши. Параметр °/0D заменяется положительным или отрицательным числом, соответствующим относительному изменению положения колесика. Атрибут fill для текста на холсте Текстовый объект холста можно сделать прозрачным, указав пустой атрибут fill. Ранее это соглашение действовало для всех остальных объектов холста. Благодаря наличию такого соглашения можно скрыть объект, указав следующее выражение: $canvas itemconfigure item -fill "" Процедура safe::loadTk Процедура safe: iloadTk теперь поддерживает опцию -display имя_дисплея. Благодаря этому вы можете управлять созданием главного окна, соответствующего защищенному интерпретатору. Опция -use была модифицирована; ей может быть передан либо идентификатор окна, либо путь к окну Тк.
Глава 55 Tcl/Tk 8.1 В Tcl/Tk 8.1 были реализованы поддержка Unicode, многопотоковое выполнение и новый пакет для обработки регулярных выражений. ОЕРСИИТс18.1 можно было бы присвоить номер 9.0. Изменения, связанные с поддержкой Unicode, затронули практически все элементы системы. В то же время система была пересмотрена с целью использования в многопотоковой среде и реализована платформенно-незавимимая загрузка, осуществляемая посредством библиотек-заглушек. Благодаря усилиям Генри Спенсера (Henry Spencer) был создан новый пакет для обработки расширенных регулярных выражений. Несмотря на внесенные изменения, сценарии, написанные для предыдущих версий, совместимы с Tcl 8.1. Unicode и интернационализация приложений Использование Unicode в Tcl лишь незначительно сказывается на написании сценариев. Появилась новая последовательность символов, начинающаяся с обратной косой черты, \uXXXX, посредством которой определяются 16-битовые символы Unicode. Были также реализованы средства для работы с кодировками и каталогами сообщений. Опция -encoding команды fconfigure Система ввода-вывода Tcl поддерживает преобразование наборов символов. Содержимое файлов при чтении автоматически конвертируется в Unicode, а при выводе данные Unicode преобразуются в кодировку, принятую
Глава 55. Tcl/Tk 8.1 1087 в конкретной операционной системе. Опция -encoding команды fconfigure позволяет задавать альтернативную кодировку содержимого файлов (см. гла- ву 15). Команда encoding Команда encoding (см. главу 15) предоставляет доступ к базовым средствам кодирования, используемым в Tcl. Операции encoding convertfrom и convertto преобразуют строки из одной кодировки в другую. Операция encoding system запрашивает или устанавливает кодировку, используемую операционной системой. Пакет msgcat Каталоги сообщений реализованы в пакете msgcat (см. главу 15). Этот каталог хранит переводы сообщений на различные языки. Для использования каталога сообщений не приходится прикладывать больших усилий. С API для работы с UTF-8 и Unicode Использование Unicode существенно повлияло на Tcl С API. Для внутреннего представления символов Unicode в Tcl используется UTF-8. Данная кодировка совместима с ASCII, поэтому расширения, которые передают Tcl ASCII-строки, продолжают нормально работать. Однако, для того чтобы воспользоваться преимуществами Unicode, расширения Tcl должны, перед тем как обратиться к библиотеке Tcl С, преобразовывать строки в формат UTF- 8 или Unicode. Для этой цели предусмотрены специальные функции С API. Пример их использования см. в главе 47. Поддержка потоков Библиотека Tcl обеспечивает безопасную работу с потоками. Это означает, что вы можете использовать Tcl в многопотоковых приложениях. Согласно модели потоков Tcl, в составе потока может выполняться один или несколько Tcl-интерпретаторов, но использование одного интерпретатора несколькими потоками недопустимо. Для обмена между потоками в Tcl предусмотрена возможность передачи Tcl-сценариев интерпретаторам в других потоках. Библиотека Tcl С предоставляет мютексы, переменные условий и локальные хранилища потоков. Эти средства могут использоваться Тс1-расширения- ми для сериализации доступа к структурам данных. Библиотека Tcl допускает различные реализации примитивов для работы с потоками. Это обеспечивает поддержку многопотокового выполнения в Unix, Windows и Macintosh.
1088 Часть VII. Изменения в составе Tcl и Тк В Tcl применяются как потоки, специфические для Windows, так и потоки Posix, типичные для Unix. В MacOS потоки отсутствуют, поэтому соответствующие средства API реализуются очень просто. Команда testthread Tcl 8.1 не экспортирует средства поддержки потоков на уровень сценария. Единственной доступной командой является testthread. (В главе 21 см. описание расширения Thread, предназначенного для работы совместно с Tcl 8.3 и Tcl 8.4. Оно расширяет описанный здесь пакет testthread.) Для того чтобы воспользоваться возможностями testthread, вам надо вместо обычной оболочки Tclsh скомпилировать программу Tcltest. В табл. 55.1 описаны операции, реализуемые с помощью команды testthread. Соответствующий исходный код находится в файле generic/tclThreadTest .с. Операции, перечисленные в табл. 55.1, в основном совпадают со средствами API, предоставляемыми некоторыми расширениями. Однако сведения, приведенные в таблице, нельзя рассматривать как руководство по работе с расширениями. Необходимую информацию вы найдете в документации. Таблица 55.1. Команда testthread testthread create ?сценарий? testthread id testthread errorproc процедура testthread exit testthread names testthread send идентификатор ?-async? сценарий testthread wait Создает новый поток и интерпретатор Tcl. Сценарий запускается после создания интерпретатора. Если сценарий не указан, новый поток переводится в режим ожидания посредством операции testthread wait Возвращает идентификатор текущего потока Регистрирует процедуру в качестве обработчика ошибок для других потоков. Если поток завершается с ошибкой, вызывается процедура и ей передаются сообщение об ошибке и значение error Info. Если процедура не зарегистрирована, сообщение выводится в поток stderr Завершает текущий поток Возвращает список идентификаторов потоков Передает сценарий другому потоку для выполнения. Если указана опция -async, команда не ожидает получения результатов Запускает цикл обработки событий. Данная операция используется рабочим сценарием, ожидающим поступления сценарий. Для этой же цели в потоке может использоваться vwait
Глава 55. Tcl/Tk 8.1 1089 Расширенные регулярные выражения В рассматриваемой версии поддерживаются Unicode и расширенные регулярные выражения (подробное их описание см. в главе 11). Новые правила записи регулярных выражений составлены так, чтобы обеспечить совместимость с предыдущими версиями. Команды regexp и regsub дополнены новыми опциями, позволяющими управлять обработкой регулярных выражений. Работа со строками Команда string была дополнена новыми операциями, которые позволяют выполнять классификацию (string is), отображение символов (string map), преобразование регистра (string totitle), проверку на соответствие строк (string equal), а также некоторые другие действия (например, string repeat и string replace). Опции -nocase и -length были добавлены к таким командам, как string compare и string tolower. Данные возможности перечислены в табл. 4.1; их подробное описание см. в главе 4. Следует помнить, что указанные выше средства доступны лишь начиная с реализации 8.1.1. Расширение DDE Dynamic Data Exchange (DDE) — это коммуникационный протокол, используемый в системе Windows для связи между приложениями. Протокол обеспечивает обмен данными с сервером, который идентифицируется по имени. Каждая служба реализует ряд операций, которые называются заголовками (topic). Обмен данными может быть синхронным или асинхронным. Команда dde реализуется посредством расширения, которое распространяется с Tcl. Для загрузки расширения используется команда package require dde. Информация о команде dde приведена в табл. 55.2. Таблица 55.2. Команда dde dde servername Регистрирует текущий процесс как сервер DDE ?заголовок? с именем TclEval, который выполняет указанные операции. Если заголовок не задан, команда возвращает текущий зарегистрированный заголовок dde ?-async? execute Передает данные серверу с указанным заголовком сервер заголовок данные dde ?-async? eval Передает команду и параметры серверу TclEval заголовок команда с указанным заголовком. Данная операция пред- ?параметр . . . ? ставляет собой альтернативу Tk-команде send
1090 Часть VII. Изменения в составе Tcl и Тк Окончание табл. 55.2 dde ?-async? poke сервер заголовок данные dde ?-async? request сервер заголовок пункт dde services сервер заголовок dde services сервер О dde services {} заголовок dde services {} {} Выполняет те же действия, что и execute. Необходима потому, что некоторые службы используют при экспортировании poke вместо execute Загружает указанный пункт с сервера с указанным заголовком Если сервер существует, данная операция возвращает сервер и заголовок. В противном случае возвращается пустая строка Возвращает все заголовки, реализованные указанным сервером Возвращает все серверы, реализующие заголовок Возвращает все сведения о серверах и заголовках Дополнительные возможности Обмен через последовательные линии связи В системе Windows драйверы последовательных линий связи были преобразованы для поддержки потоков, поэтому при работе с последовательными устройствами можно использовать f ileevent для ожидания операций ввода- вывода. Функции API не изменились, устранено лишь ограничение, действующее в предыдущих реализациях Tcl для системы Windows. Элемент tcl_platform(user) Элемент массива tcl_platform(user) содержит идентификатор зарегистрированного пользователя. Наличие этого элемента скрывает различия в переменных окружения и системных вызовах, используемых для получения соответствующей информации на различных платформах.
Глава 56 Tcl/Tk 8.2 Реализация Tcl 8.2 задумывалась в основном для того, чтобы исправить замеченные ошибки и повысить надежность работы системы. Она рекомендована к использованию вместо Tcl 8.1. 13 Tcl 8.2 практически отсутствуют новые средства, доступные на уровне сценариев Tcl. Вместо этого было реализовано несколько новых С API, которые позволили добавлять некоторые расширения, не модифицируя базовый дистрибутивный пакет Tcl. В то же время компания Scriptics уделила большое внимание информации о замеченных недостатках и постаралась сделать реализацию 8.2 максимально стабильной. Модификация Trf Андреас Каприс (Andreas Kupries) предложил механизм записи модулей обработки каналов ввода-вывода в открытые каналы. Для поддержки этой возможности появилась новая функция С API Tcl_StackChannel, однако на уровне Tcl-сценариев эти изменения остались невидимыми. Новый механизм позволяет добавить к Tcl некоторые расширения, реализующие, в частности, сжатие и шифрование. Андреас Каприс разработал расширение, экспортирующее механизм фильтрации каналов на уровень Tcl-сценариев. Этот механизм используется в основном для тестирования, однако вы можете осуществлять с его помощью реальную фильтрацию информации.
1092 Часть VII. Изменения в составе Tcl и Тк Эффективные операции со строками Кодировка UTF-8 имеет существенный недостаток: длина кодов разных символов может различаться. Для представления символа используются один, два или три байта. Из-за различия в размерах кодов символов такие операции, как string length, string index и string range, могут существенно замедляться по сравнению со строками, в которых присутствуют только символы фиксированной длины. Для ускорения операций со строками используется кодировка Unicode, в которой все символы представляются 16 битами. Особенности используемой кодировки не видны на уровне Тс1-сценари- ев, за исключением того, что обработка строк осуществляется быстрее, чем в Tcl 8.1. Пустые имена массивов Единственным изменением, проявляющимся на уровне Tcl-сценариев, является поддержка пустых имен массивов. Эта возможность используется при работе с пространствами имен или командой upvar. В качестве примера можно привести ссылку на элемент массива $: :foo: : (item). Подобные выражения доступны в любой версии Tcl, в которой поддерживаются пространства имен. Однако в Tcl 8.2 вы также можете указать $(item); это означает, что имя массива пустое. Данная возможность используется в объектно-ориентированном расширении STOOOP. Особенности создания дополнительных модулей для браузера При создании дополнительных модулей для браузера необходимо изменять цикл обработки событий, поскольку Tcl-сценарий включается в приложение с собственным циклом обработки. Несмотря на то что начиная с Tcl 8.0.3 С API обеспечивает поддержку альтернативных циклов обработки событий, использовать встроенные приложения без перекомпиляции Tcl было сложно. Функция API Tcl.SetNotif ier поддерживает включение набора интерпретаторов Tcl.
Глава 56. Tcl/Tk 8.2 1093 Управление последовательными портами в системе Windows В системе Windows Tcl опрашивает последовательные порты каждые 10 миллисекунд. В Tcl 8.2 была введена опция -pollinterval, которая позволяет указать более короткий интервал опроса. Синтаксис расширенных регулярных выражений Несмотря на то что в Tcl 8.1 была реализована поддержка расширенных регулярных выражений (согласно новым синтаксическим правилам пробелы и комментарии игнорируются), об их использовании необходимо было специально сообщать, включая в состав строки символы ?х. В Tcl 8.2 была реализована опция regexp -expanded, которая позволяет указать на то, что в сценарии используются расширенные регулярные выражения.
Глава 57 Tcl/Tk 8.3 В Tcl/Tk 8.3 возможности компонента canvas были расширены за счет введения шаблона штриховых линий. Кроме того, были несколько улучшены и другие средства Tcl/Tk. ГЗерсия Tcl/Tk 8.3 была модифицирована по сравнению с предыдущей версией. В особенности изменения коснулись Тк. Шаблоны штриховых линий, предложенные Дженом Нийтмансом (Jan Nijtmans), обеспечили новые возможности при работе с холстом. Доработка базовых средств Tcl не только предоставила новые возможности авторам сценариев, но также позволила пользоваться многими популярными расширениями и наборами инструментальных средств, не модифицируя и не перекомпилируя Tcl. Новые команды и опции для работы с файлами Новая команда file channels возвращает список открытых каналов ввода-вывода. Эти каналы могут быть связаны с гнездами или обычными файлами; это также могут быть каналы, созданные расширениями. Для сокращения списка допустимо использовать необязательный шаблон, составленный но правилам команды glob (например, sock*). При вызове команд file atime и file mtime теперь можно передавать необязательные параметры, позволяющие установить время доступа к файлу или время его модификации. В результате появляется возможность выполнять посредством обычного Tcl-кода действия, аналогичные действиям команды touch, используемой в системе Unix.
Глава 57. Tcl/Tk 8.3 1095 Если раньше права доступа к файлу устанавливались посредством указания восьмеричного кода, то теперь можно делать то же самое, задавая права в символьном виде. Для этой цели используется опция -permissions команды file attributes. Символьные атрибуты указываются те же, что и в команде chmod, выполняемой в системе Unix. Поддерживается также девятисим- вольная последовательность в формате rwxrwxrwx, отображаемом командой Is -1. Новые опции команды glob Опции -directory, -join, -path и -types команды glob упрощают действия с каталогами; при этом обеспечивается независимость от конкретной платформы. Подробно о данных опциях см. в главе 9. Команды для работы с регулярными выражениями К командам regexp и regsub добавлена опция -start, которая указывает точку, с которой должна начинаться обработка строки. Команда regexp, вызванная с опцией -inline, вместо того, чтобы помещать символы, соответствующие регулярному выражению, в переменную, возвращает их. Опция -all команды regexp указывает на то, что необходимо производить поиск всех вхождений последовательностей символов, соответствующих шаблону. Если при вызове команды указаны опции -all и -inline, возвращается список значений. Если опция -inline не указана, команда возвращает лишь число соответствий. Результаты команды scan Если вы не укажете переменную, в которую надо поместить результаты выполнения команды scan, она вернет данные о соответствии в виде списка. Удаление повторяющихся элементов списка с помощью Isort Опция -unique команды lsort указывает на то, что при сортировке списка должны быть удалены повторяющиеся элементы.
1096 Часть VII. Изменения в составе Tcl и Тк Удаление элементов массива Новая команда array unset удаляет из массива все элементы, соответствующие шаблону типа glob. Если шаблон не указан, команда удаляет как переменную массива, так и все содержащиеся в массиве элементы. Модификация команды clock Команда clock scan была расширена для поддержки формата даты и времени ISO 8601. Команды clock scan и clock format преобразованы для работы с форматом Stardate (для команды clock format надо указать °/0Q). В составе команды clock clicks была реализована опция -milliseconds, при указании которой возвращаются значения с точностью до миллисекунды. Поддержка отложенной загрузки пакетов Новая опция -lazy, указываемая при вызове pkg_mklndex, генерирует индексный файл, который позволяет отложить реальную загрузку пакета до тех пор, пока приложение не предпримет попытку использовать одну из команд, предоставляемых данным пакетом. Без этой опции пакет загружался сразу же при вызове команды package require. Дополнение Img Дополнение Img улучшает поддержку прозрачных изображений, обеспечивает более качественную обработку формата GIF, а также дает возможность сохранять GIF-изображения. Дополнительную информацию по этому вопросу см. в главе 41. Данное дополнение поддерживает другие типы изображений (например, JPEG); они могут быть загружены как расширения. Для работы с Img не требуется модификация базовых средств Tcl. Шаблон штриховых линий Шаблон штриховых линий в основном используется при работе с холстом, однако может найти применение и для некоторых других компонентов Тк. Особенности работы с холстом В новой версии в компоненте canvas используются шаблоны штриховых линий. Кроме того, при работе с холстом стал доступен ряд новых возможностей.
Глава 57. Tcl/Tk 8.3 1097 • Координаты холста могут быть указаны не только с помощью отдельных параметров, но и посредством списка. Это упрощает формирование команды. • Многие объекты холста могут использовать шаблоны штриховых линий для создания контуров и обрамлений. Эти возможности предоставляются посредством опций, в именах которых присутствует слово dash. В Windows 95 поддерживались штриховые линии толщиной в один пиксель, однако на других платформах можно использовать линии любой толщины. • Для объекта canvas предусмотрен атрибут state, который позволяет модифицировать состояние холста, устанавливаемое по умолчанию. Для каждого из объектов холста также реализован атрибут state, который переопределяет установки, заданные с помощью атрибута state холста. Объекты холста также поддерживают новые атрибуты, позволяющие управлять их внешним видом. Атрибуты, имена которых начинаются со слова active, задают внешний вид объекта в тот момент, когда курсор мыши расположен над ним. Атрибут с именем, начинающимся со слова disabled, управляет внешним видом объекта, доступ к которому запрещен. Эти компоненты не реагируют на связывания для холста. • Расширенный поиск с использованием дескрипторов доступен для всех операций с холстом, для которых в качестве параметра задается дескриптор или идентификатор объекта. Поиск объектов холста можно производить на основе логических выражений, в которых указаны значения дескрипторов. • Холст может генерировать Postscript-описания встроенных изображений на всех платформах. Кроме того, на платформе Unix есть возможность генерировать Postscript-данные для встроенных компонентов, видимых на экране (к ним относятся компоненты, которые располагаются в области видимости холста и не заслоняются другими окнами). • В реализации холста теперь используются значения Tcl_0bj, что повышает производительность работы сценариев. Скрытый текст Дескрипторы в текстовом компоненте теперь поддерживают атрибут -elide, который позволяет скрыть текст, соответствующий дескриптору. Данная возможность используется популярным браузером справочной информации, который теперь можно использовать, не дорабатывая базовые средства Tcl.
1098 Часть VII. Изменения в составе Tcl и Тк Управление курсором мыши Приложение Тк может управлять перемещением курсора мыши. Для генерации событий <KeyPress>, <KeyRelease>,<ButtonPress>,<ButtonRelease> и <Motion> надо при вызове команды event generate указывать опцию -war. Например: event generate .с <Motion> -warp 1 -х 10 -у 20 Проверка содержимого поля редактирования Для проверки данных, введенных посредством поля редактирования, были реализованы дополнительные опции. Они позволяют определить команды обратного вызова для получения и потери фокуса ввода, изменения содержимого компонента и других событий. В командах обратного вызова могут присутствовать ключевые слова, начинающиеся с символа % Подстановка этих ключевых слов осуществляется так же, как и в обычных связываниях. С помощью ключевых слов можно получить имя поля редактирования, введенный символ и другие данные. Прочие средства Тк Модификация окон списков Для окон списков был введен новый атрибут listVariable. Он осуществляет связь содержимого окна списка с переменной, значением которой является список. Соответствие между содержимым компонента и переменной поддерживается так же, как и в тех компонентах, для которых определен атрибут textVariable. Для компонента listbox также были реализованы операции itemconf igure и itemcget, которые позволяют установить цвет отдельных пунктов либо получить сведения об используемом цвете. Диалоговое окно для выбора каталогов Новая команда tk_chooseDirectory позволяет пользователям просматривать файловую систему и выбирать требуемый каталог. Команда tk_chooseDirectory действует по тому же принципу, что и команда tk_getOpenFile, применяемая для работы с обычными файлами.
Глава 57. Tcl/Tk 8.3 1099 Взаимодействие оконного диспетчера с окнами верхнего уровня При вызове команды wm state может быть указан необязательный параметр, позволяющий задавать состояние окна верхнего уровня. В системе Windows wm state также поддерживает состояние zoomed для максимизированных окон. Поддержка системных курсоров Windows В системе Windows можно использовать системные курсоры, определяемые в файлах . ani и . cur. Для этой цели при установке курсора компонента надо задать опцию -cursor @имя_ файла. Поддержка колесика прокрутки в системе Unix По умолчанию для окон списков и текстовых компонентов в системе Unix задаются связывания для колесика прокрутки. В результате данные компоненты реагируют на изменение позиции колесика прокрутки посредством событий <ButtonPress-4> и <ButtonPress-5>. Новый модификатор Quadruple При работе с событиями вы можете использовать новый модификатор Quadruple (например, <Quadruple-ButtonPress-l>). X Input Method (XIM) Новая команда tk useinputmethods определяет поведение Tk в системе X Window. При этом поддерживаются методы ввода X Window (XIM — X Input Method). Начиная с версии 8.3 эти средства распознаются и инициализируются, но для их использования надо установить специальное разрешение (tk useinputmethods l). Модификации версии 8.3 В серии модификаций версии 8.3 были сделаны незначительные улучшения, устранены некоторые ошибки, но основные средства остались неизменными. Последней модификацией серии 8.3 является реализация 8.3.5, которая была выпущена одновременно с 8.4.1. Единственным существенным дополнением 8.3.5 по сравнению с предыдущими реализациями стала возможность генерации Postscript-описаний для включенных объектов на платформе Windows. Одновременно эта же возможность была реализована в версии 8.4.1.
1100 Часть VII. Изменения в составе Tcl и Тк Определение типа проверки Ключевое слово °/0V заменяется сообщением о типе события, по которому активизируется команда обратного вызова (key, focus in, focusout, forced) (8.3.1). Диалоговое окно выбора файлов в системе Macintosh В системе Macintosh при инсталлированной системе Navigation Services для команд tk_getSaveFile и tk_getOpenFile может задаваться опция -message. С ее помощью указывается сообщение, включаемое в клиентскую область диалогового окна (8.3.1). Атрибут state для текстовых меток Компонент label теперь поддерживает атрибут state, значением которого может быть normal, active или disabled (8.3.1). Дополнительно была реализована поддержка атрибутов activeBackground и activeForeground, определяющих внешний вид компонента, находящегося в активном состоянии (8.3.2), а также атрибута disabledForeground, который задает внешний вид компонента, доступ к которому запрещен (8.3.1). Поддержка пиктограмм в системе Windows В системе Windows вы можете, вызывая команду wm iconbitmap, задавать путь к файлам, содержащим пиктограммы для данной системы (обычно это файлы . ico или .icr) (8.3.3). Новые страницы интерактивной справочной системы В интерактивной справочной системе теперь присутствуют новые страницы, colors, cursors и keysyms, которые описывают имена цветов, имена курсоров и символы клавиш (8.3.2).
Глава 58 Tcl/Tk 8.4 При создании Tcl 8.4 были приняты меры для повышения производительности программ. Кроме того, появилось много новых команд и опций, а также три новых компонента. 1 лашюй задачей разработчиков Tcl/Tk 8.4 было повышение производи- телышсти программ. В версии 8.0 было достигнуто резкое повышение эффективности выполнения за счет применения компилятора байтового кода. Однако новые возможности, добавленные в версии 8.1 (в частности, поддержка многопотокового выполнения, средства интернационализации и работа с. символами Unicode), снова замедлили работу Tcl. Разработчики 8.4 приложили все усилия для того, чтобы максимально повысить скорость выполнения программ, добиться, чтобы они работали не только не медленнее, но даже быстрее, чем это было до появления версии 8.0. Эта цель была достигнута практически для всех средств Tcl и Тк. Что касается реализации новых функций, то по этому показателю с Tcl/ Тк 8.4 может сравниться разве что версия 8.1. Возможности практически всех языковых средств были существенно расширены, кроме того, появились новые компоненты. Версия 8.4 также замечательна тем, что проект Tcl/Tk, работу над которым ранее контролировал лично Джон Остераут, теперь выполняется под управлением группы ТСТ (Tcl Core Team). В состав группы вошли специалисты, которые много лет работали над Tcl. ТСТ отвечает за внесение изменений и реализацию дополнительных возможностей в новых версиях Tcl. Участники группы непосредственно взаимодействуют со специалистами по поддержке, отвечающими за работу различных элементов Tcl/Tk, а также с многочисленными добровольцами, предлагающими свои услуги по работе над новыми средствами данного языка. Каждый желающий может внести
1102 Часть VII. Изменения в составе Tcl и Тк предложения по модернизации Tcl в виде TIP (Tcl Improvement Proposal). Описание принципов TIP и список внесенных предложений можно найти по адресу http://www.purl.org/tcl/tip. К тому моменту, как механизм TIP начал действовать, работы над версией 8.4 уже велись, поэтому некоторые из новых средств не прошли контроль, установленный для TIP. В данной главе для тех средств, которые были предложены в виде TIP, приведены соответствующие идентификационные номера. Поддержка 64-битового кода При разработке новой версии были изменены некоторые команды, для которых обработка 64-битового кода осуществляется более эффективно, чем на 32-разрядных платформах (TIP #72). Изменения были внесены так, чтобы обеспечить максимально возможный уровень обратной совместимости. 64-битовая арифметика Команда expr теперь обеспечивает поддержку 64-разрядной арифметики (целых чисел типа wide integer). Целочисленные константы, которые не могут быть представлены как 32-битовые значения, теперь интерпретируются как числа типа wide integer. Если же для их представления не подходит и 64-битовое представление, они преобразуются в числа с плавающей точкой двойной точности. Если хотя бы один из операндов является числом двойной точности, результат арифметической операции представляется как плавающее число двойной точности. Если хотя бы один из операндов является числом типа wide integer, результат операции приводится к данному типу. В противном случае возвращается обычное целое число. Функция int() всегда возвращает обычное целое число (при необходимости старшие биты отбрасываются), а новая функция wide() возвращает число wide integer (при преобразовании знак числа сохраняется). Команда incr корректно инкрементирует 64-битовые значения, но в качестве параметра может принимать только 32-разрядные значения. Преобразование 64-битовых значений В новой версии Tcl команды format и scan поддерживают модификатор 1, используемый совместно с символами преобразования d, u, i, о и х. Данный модификатор сообщает о том, что обработке подлежит 64-разрядное значение. Команда binary допускает указание новых спецификаторов w и W для операций format и scan. Эти спецификаторы указывают на то, что действия должны производиться над 64-битовыми значениями; в остальном они не отличаются от применявшихся ранее спецификаторов i и I.
Глава 58. Tcl/Tk 8.4 1103 Поддержка 64-битовой файловой системы Все Tcl-команды, предназначенные для выполнения действий с файловой системой (file, glob, seek и Tcll), работают корректно с файлами, размер которых превышает 2 Гбайт. Размер машинного слова Теперь в массиве tcl_platform содержится новый элемент tcl_platform(wordSize), который предоставляет сведения о размере машинного слова на текущей платформе. Дополнительные средства для работы с файловой системой Виртуальные файловые системы В новой версии поддерживаются виртуальные файловые системы (VFS — Virtual File System), которые допускают выполнение операций ввода-вывода со структурами, отличающимися от файловой системы текущей операционной системы (TIP #17). Это означает, что при наличии соответствующих расширений в обычном Tcl-коде могут присутствовать стандартные команды для работы с файлами (cd, pwd, glob, file, open и т.д.), действующие с "виртуальными файлами". В роли виртуальных файлов могут выступать удаленные файлы (расположенные на FTP- или HTTP-узлах) либо содержимое архивов. Базовый дистрибутивный пакет Tcl не предоставляет возможности выполнять действия с виртуальной файловой системой на уровне Тс1-сценариев. Однако новые С API допускают работу с различными расширениями и приложениями. TclVFS представляет собой расширение, которое позволяет стандартным Tcl-сценариям "монтировать" и использовать виртуальные файловые системы. TclVFS обеспечивает поддержку различных типов виртуальных файловых систем, в том числе FTP, HTTP, WebDAV, Zip- и tar-архивов, баз данных MetaKit и пространств имен Tcl. Инструмент TclKit (см. главу 2) предоставляет средства доставки приложений в виде одного исполняемого файла. Работа TclKit также базируется на взаимодействии с виртуальной файловой системой. Команды file и glob На базе команды file реализовано несколько новых операций, в основном предназначенных для поддержки виртуальных файловых систем: file
1104 Часть VII. Изменения в составе Tcl и Тк normalize, file separator и file system. B TIP #17 была предложена опция glob -tail. В TIP #99 добавлена команда file link, предназначенная для создания фиксированных и символьных ссылок. Дополнительную информацию о командах file и glob см. в главе 9. Работа со списками В Tcl 8.4 были добавлены команды и опции, предназначенные для повышения производительности при работе со списками. • Новая команда lset позволяет непосредственно изменять значение конкретного элемента списка. При этом программа выполняется намного быстрее по сравнению с заменой элемента посредством команды lreplace. • При вызове команды 1 index теперь можно задавать несколько индексов, извлекая таким образом значения вложенных списков. • Для ускорения поиска в списка и повышения гибкости в команде Isearch были предусмотрены новые опции, Опции -sorted, «ascii, «decreasing, -dictionary, -increasing, -integer и -real указывают на то, что содержимое списка подверглось сортировке соответствующего типа. Данная информации позволяет использовать более эффективные алгоритмы поиска. В TIP #80 были предложены опции -all, -inline, -not и -start. Они позволяют извлекать в процессе поиска несколько элементов. Описание команд Isearch, lset, llength, lindex и lrange см. в главе 5. Поиск в массивах Новые опции -exact, -glob и -regexp команды array names позволяют указывать тип шаблона, используемого при поиске элементов массива. Команда array statistics возвращает данные о содержимом массива, которые используются в основном для отладки и создания профилей. Расширенные средства поддержки обмена через последовательные линии Опции -handshake, -queue, -sysbuffer, -timeout, -ttycontrol, -ttystatus и -xchar команды fconfigure предоставляют дополнительные возможности контроля над взаимодействием по последовательным линиям связи. Подробно об этих опциях см. в главе 16.
Глава 58. Tcl/Tk 8.4 1105 Новые операторы сравнения строк Новые операторы сравнения строк, eq и пе, могут использоваться для принудительного сравнения. В частности, их можно применить в составе выражений expr, for, if и while. Отслеживание выполнения команд В новой версии Tcl для отслеживания выполнения команд используются приблизительно те же способы, которые применялись для отслеживания обращений к переменным (TIP #62). Средства отслеживания могут получать управление до выполнения команды и после, при переименовании и удалении команды и т.д. В новой версии были введены дополнительные синтаксические конструкции для отслеживания процесса создания и обращения к переменным. Для обеспечения обратной совместимости поддерживается также прежний синтаксис, однако использовать эти языковые средства не рекомендуется. Более подробную информацию о средствах отслеживания команд и действиях с переменными см. в главе 13. Интроспекция Для получения информации об интерпретаторе Tcl и для контроля его работы были реализованы новые команды. • Команда info functions возвращает список всех математических функций, определенных на данный момент (TIP #15). • При вызове команды info script может быть задан необязательный параметр, посредством которого указывается путь. Если этот параметр присутствует, он становится возвращаемым значением для всех последующих вызовов info script в течение сеанса работы. Данная возможность может оказаться полезной при работе с виртуальными файловыми системами. • Команда interp recursionlimit устанавливает или возвращает максимальную глубину вложенности Tcl-процедур и других операций, воздействующих на стек (TIP #87). • Команда namespace exists сообщает, существует ли указанное пространство имен.
1106 Часть VII. Изменения в составе Tcl и Тк Прочие изменения в составе Tcl Удаление неиспользуемых переменных Опция -nocomplain, заданная при вызове команды unset, подавляет возможные ошибки. Новая опция -- позволяет удалять переменные, имена которых совпадают с опциями команды unset. Данные, возвращаемые командой regsub Последний параметр команды regsub (имя переменной, в которую должна быть помещена строка, полученная в результате подстановки) может отсутствовать. Если этот параметр не задан, команда regsub возвращает результирующую строку (если подстановка не выполнялась, возвращается исходная строка) (TIP #76). Повышенное разрешение таймера в системе Windows Ранее команды time, clock clicks и другие соответствующие функции были ограничены разрешением таймера, которое в системе Windows составляет около 10 миллисекунд. В Tcl 8.4 была реализована микросекундная точность (TIP #7). Модифицированная команда fcopy В предыдущих версиях Tcl при выполнении команды fcopy кодировка для каналов игнорировалась. Теперь, если данные, получаемые через канал, представлены в кодировке, отличающейся от той, которая используется в Tcl, fcopy автоматически преобразовывает данные. Новые компоненты Тк В Тк 8.4 реализованы три новых компонента. • Компонент spinbox представляет собой модифицированное поле редактирования. В его составе отображаются стрелки, направленные вверх и вниз, посредством которых пользователь, помимо непосредственного ввода информации, может выбирать данные из фиксированного набора. • Компонент labelf rame очень похож на обычный фрейм, но в нем может отображаться текстовая метка (TIP #18).
Глава 58. Tcl/Tk 8.4 1107 • Компонент panedwindow может содержать произвольное число панелей. В каждой панели содержится один компонент, и каждая пара панелей разделена перемещающейся границей. Изменение положения границы приводит к изменению размеров панелей, находящихся по обе стороны от нее (TIP #41). Отмена действий в текстовом компоненте и другие дополнения В Тк 8.4 текстовый компонент подвергся ряду изменений. • Были добавлены средства неограниченной отмены и возврата выполненных действий (TIP #26). Если в качестве значения новой опции -undo задано логическое значение true, компонент записывает в стек сведения обо всех действиях, связанных с включением или удалением информации. Связывания для клавиатуры, установленные по умолчанию, позволяют пользователю отменять или возвращать изменения, а программный интерфейс предоставляет приложению полный контроль над стеками undo и redo. • При изменении содержимого текстового компонента генерируется виртуальное событие <<Modif ied>> (TIP #26). • При изменении выделения текстовый компонент генерирует виртуальное событие «Selection» (TIP #26). • Операции delete и get текстового компонента могут выполняться над несколькими фрагментами текста (TIP #93). Новые возможности диспетчеров компоновки pack и grid Несимметричное дополнение Диспетчеры компоновки pack и grid поддерживают несимметричное дополнение. Если для опции -padx или -pady задано одно значение, оно определяет размеры дополнительного пространства слева и справа от компонента (или соответственно сверху и снизу от него). В предыдущих версиях Тк допускался только такой вариант указания значений опций. Теперь появилась возможность задавать в качестве значения приведенных выше опций список из двух элементов. Первый элемент списка определяет дополнение слева (сверху) от компонента, а второе значение задает дополнение справа (снизу).
1108 Часть VII. Изменения в составе Tcl и Тк Размеры строк и столбцов в диспетчере компоновки grid Новая опция -uniform, задаваемая при вызове команд grid columnconf igure и grid rowconf igure, упрощает создание ячеек с одинаковыми размерами для размещения компонентов (TIP #37). Отображение текста и изображений в составе компонента В предыдущих версиях Тк в составе меток, пунктов меню и кнопок не могли одновременно присутствовать текст и изображения. В Тк 8.4 для текстовых меток, пунктов меню и различных типов кнопок был введен атрибут compound, который позволяет указать, могут ли в составе компонента одновременно отображаться изображение (или битовая карта) и текст. Если совместное отображение таких типов информации разрешено, то тот же атрибут задает расположение изображения относительно текста (TIP #11). Новые атрибуты, определяющие рельеф кнопок Для кнопок различных типов может быть задана опция -overrelief, которая определяет рельеф компонента при помещении на него курсора мыши. Для компонентов checkbutton и radiobutton определена также опция -of f relief, которая определяет рельеф компонента в случае, когда он находится в отключенном состоянии, а индикатор не отображается. Новые опции, управляющие рельефом, упрощают создание панелей инструментов, представляющих собой набор кнопок (TIP #82). Управление состоянием полей редактирования и окон списков В новой версии Тк для полей редактирования и окон списков были реализованы новые атрибуты, позволяющие управлять состоянием компонентов. • Атрибут state поля редактирования теперь может принимать значение readonly. Когда компонент находится в состоянии readonly, его содержимое не может быть изменено ни по инициативе пользователя, ни в результате действий приложения. При этом текстовый курсор не
Глава 58. Tcl/Tk 8.4 1109 выводится. Однако пользователь по-прежнему имеет возможность выделять фрагменты данных, отображаемых в поле редактирования. Поле редактирования также поддерживает атрибуты disabledForeground, disabledBackground и readonlyBackground, позволяющие управлять внешним видом компонента в различных состояниях. • Компонент listbox поддерживает атрибут state, который может принимать значения normal и disabled, а также атрибут disabledForeground, который определяет внешний вид компонента тогда, когда доступ к нему запрещен. Новый атрибут activeStyle задает стиль отображения активного элемента (TIP #94). Работа с оконным диспетчером Операции, реализуемые с помощью команды wm, предоставляют дополнительные возможности по взаимодействию с оконным диспетчером и управлению окнами верхнего уровня. • Операция wm attributes возвращает или устанавливает платформен- но-ориентированные атрибуты для окна (TIP #95). • Операция wm stackorder возвращает информацию о стеке окон верхнего уровня для приложения (TIP #74). • При вызове операции wm iconbitmap на платформе Windows может быть указана новая опция -default, определяющая пиктограммы для окон приложения (TIP #8). • Операция tk windowingsystem возвращает информацию о текущей оконной системе Tk: xll (X Window), Win32 (MS Windows), classic (Mac OS Classic) или aqua (Mac OS X Aqua) (TIP #108). Прочие изменения в составе Tk Автоповтор В новой версии Tk появилась возможность управлять поведением кнопок, линейных регуляторов, полос прокрутки и инкрементных регуляторов в том случае, когда пользователь удерживает нажатой кнопку мыши или клавишу на клавиатуре. Атрибут repeatDelay определяет число миллисекунд, в течение которых должна удерживаться кнопка или клавиша, прежде чем будет включен режим автоповтора. Атрибут repeat Interval задает интервал в миллисекундах между повторяющимися событиями.
1110 Часть VII. Изменения в составе Tcl и Тк Поддержка прозрачных изображений В Тк 8.4 предоставлены дополнительные средства доступа к информации о прозрачности изображений photo. Для объектов данного типа теперь реализованы операции get и set, с помощью которых соответственно предоставляются или устанавливаются данные о прозрачности отдельных пикселей в изображении (TIP #14). Новая опция -compositingrule, задаваемая при вызове операции сору, позволяет указать, каким способом прозрачные пиксели исходного изображения должны объединяться с целевым изображением (TIP #98). Выбор нескольких файлов с помощью tk_getOpenFile Опция -multiple команды tk_getOpenFile позволяет выбрать несколько файлов. Информация о выбранных файлах возвращается в виде списка. Поддержка кнопок фиксированной ширины В системе Windows атрибут width компонента button может приобретать отрицательное значение, используемое для указания минимальной ширины. Данная возможность реализована для лучшего согласования внешнего вида кнопок с элементами, отображаемыми в среде Windows. Доступ к содержимому буфера обмена Новая операция clipboard get возвращает содержимое буфера обмена. Она выполняет те же действия, что и выражение selection get -selection CLIPBOARD. Информация об использовании изображения Команда image inuse возвращает сведения о том, используется ли указанное изображение одним из компонентов. Новые события для диспетчеров окон Для создания диспетчеров окон на базе Тк в версии 8.4 была добавлена поддержка новых событий: <CirculateRequest>, <Create>, <MapRequest>, <ResizeRequest> и <Conf igureRequest>. Кроме того, были добавлены также ключевые слова °/0i и °/0Р (TIP #47).
Глава 58. Tcl/Tk 8.4 1111 Управление текстовым курсором Новая команда tk caret позволяет определить или установить расположение текстового курсора в окне. Текстовый курсор не только указывает на текущую позицию, но и сообщает о наличии фокуса ввода. Новая опция команды bell Команда bell вызывает побочные действия. В большинстве систем она отключает заставку, отображая содержимое экрана. Новая опция -nice позволяет вызывать команду bell, не влияя на состояние заставки. Генерация Postscript-описаний для встроенных окон В Tk 8.4.1 были добавлены средства, которые обеспечивают генерацию в системе Windows Postscript-описаний для встроенных окон, отображаемых на экране (окно должрю находиться в области отображения холста и не заслоняться другими окнами). Одновременно данная возможность была реализована в версии 8.3.5.
Глава 59 Содержимое компакт-диска В данной главе кратко описана информация, которая содержится на компакт-диске, прилагаемом к настоящей книге. Г~1а прилагаемом компакт-диске находятся дистрибутивные пакеты Tcl/Tk, примеры, приведенные в тексте книги программы, имеющие непосредственное отношение к Tcl. Компакт-диск представлен в гибридном формате и доступен для чтения в системах Unix (формат ISO 9660 с расширениями Rock Ridge), Windows (формат Joliet с поддержкой длинных имен файлов) и Macintosh (HFS). Дистрибутивные пакеты Tcl/Tk содержатся в папках tcl8_4 и tcl8_3. В файлах .tar.gz находятся исходные тексты. Эти архивы можно распаковать в системе Unix, используя следующую команду: gunzip < tcl8.4.2.tar.gz I tar xvf - Для компиляции исходного кода можно применить приведенные ниже команды (более подробные инструкции по компиляции кода см. в главе 48). cd tcl8.4.2/unix ./configure make В файлах . zip содержатся те же файлы, что и в архивах . tar. gz. Инсталляционные программы для системы Windows имеют расширение .ехе. С их помощью можно установить интерпретаторы Tcl/Tk и библиотеки сценариев. В папке tcl8_4 расположены также файлы .bin. В них находится дистрибутивный пакет для MacOS 9. Файлы .dmg содержат Tcl/Tk Aqua для MacOS X. Для инсталляции Tcl/Tk на указанных платформах достаточно дважды щелкнуть на соответствующем файле.
Глава 59. Содержимое компакт-диска 1113 В папке ActiveTcl содержатся двоичные коды ActiveTcl и демонстрационные файлы TclDevKit. Описание данных инструментов см. в главе 13. TclDevKit представляет собой набор инструментальных средств, включающий отладчик и средства контроля синтаксиса. Лицензию на использование демонстрационной версии можно получить, обратившись по следующему адресу: http: //www. act ivestate. com/product s/Tcl_Dev_Kit В папке exsource содержатся коды примеров, приведенных в данной книге. Сценарий browser. tcl позволяет просматривать их и запускать на выполнение. В каталоге tclhttpd3.4.3 находится распакованная версия дистрибутивного пакета TclHttpd. Для того чтобы запустить сервер на выполнение, надо загрузить сценарий bin/httpd.tcl в программу wish или tclsh. Данный сценарий реализует Web-сервер, который ожидает обращение через порт 8015. В каталогах tclkit8.4.2 и tclkit8.4.1 содержатся версии Tclkit для различных платформ, а также исходный код, позволяющий самостоятельно построить интерпретатор Tcl/Tk с расширенными возможностями. Созданную программу надо скопировать в требуемый каталог и переименовать ее в tclkit. Инструмент Tclkit используется для запуска пакетов Starkit, расположенных в каталоге sdarchive. В каталоге wiki содержится копия http://wiki.tcl.tk в виде Starkit. Для просмотра данных надо вызвать приведенную ниже команду. Если она вызывается непосредственно с компакт-диска, опция -readonly обязательна. tclkit wiki/wikit.kit wiki/wikit.tkd -readonly В каталоге handheld находится дистрибутивный пакет Tcl/Tk, предназначенный для переносных устройств, работающих под управлением PalmOS, DOS и Windows СЕ. Поддержку Windows СЕ планируется реализовать в базовом коде будущих версий Tcl/Tk. В папках extensions и applications содержится программное обеспечение, скопированное из Internet. Большинство из этих программ можно найти на сервере SourceForge по адресу http://www.sourceforge.net В каталоге mingw находится копия свободно распространяемого компилятора С для системы Windows. Для того чтобы построить Tcl/Tk для Windows с использованием mingw, вам потребуется Cygwin, найти который можно по следующему адресу: http://www.cygwin.com ActiveTcl — это торговая марка ActiveState Corporation. — Прим. авт.
1114 Часть VII. Изменения в составе Tcl и Тк В папке Cd_utils содержатся программы, которые могут пригодиться в процессе работы, например Winzip и версия Таг для Macintosh. Там же расположены сценарии, используемые для создания компакт-диска. Техническая поддержка Prentice Hall не обеспечивает техническую поддержку программного обеспечения, расположенного на компакт-диске. При возникновении проблем вам могут заменить диск, если вы обратитесь по приведенному ниже адресу и опишете возникшую ситуацию. disc_exchange@prenhall.com
Предметный указатель А HTTP-клиент, 364 ActiveTcl, 313 HyperText Markup Language, 94 AppleScript, 189 HyperText Transport Protocol, 370 Application Direct URL, 389; 394 ASCII, 338 I [incr Tcl], 313; 314; 508 В [incr Tk], 542 Basic Authentication, 386 [incr Widgets], 542 BLT, 541 Inspector, 315 BWidgets, 542 IP-адрес, 364 Byte Array, 1040 IRC-клиент, 364 с м С-библиотека, 1033 Metakit, 508; 510; 521 CGI, 93 MIB, 363 Checker, 314 MIME, 340; 398 Compiler, 314 Critcl, 316 N NNTP-клиент, 364 D DDE, 1089 P Debugger, 314 РОРЗ-клиент, 364 DNS, 363 РОРЗ-сервер, 364 DNS-клиент, 363 Post Office Protocol, 364 dual-ported value, 951 Postscript-описание, 805 Dynamic Data Exchange, 1089 Proxy-сервер, 371 E R Expect, 205; 313; 314 RPC, 363 F S FTP, 362 Safe-TcL 432; 1079 FTP-клиент, 363 Safe-Tk, 459 FTP-сервер, 363 Scotty, 363 sdx, 510; 513 Service Manager, 315 ^' Simple Wrapper Interface Generator, HTML-дескриптор, 94 Q4n HTTP, 362: 370
1116 Предметный указатель SMTP-клиент, 364 SMTP-сервер, 364 SNMP, 363 Starkit, 509- 510 Starkit Developer extension, 513 Starpack, 515 SWIG, 940 T Tcl, 57 Tcl Core Team, 1101 Tcl Dev Kit, 313 Tcl Extension Architecture, 985; 990; 996 Tcl Improvement Proposal, 1102 Tcl-библиотека, 939 TclApp, 315 TclHttpd, 388 tcllndex, 281 Tclkit, 508; 509 TclVFS, 512 TclX, 313; 314 TCP, 362 ТСР-гнездо, 362 TCT, 1101 TEA, 985; 996 Tclnet, 362 TIP, 1102 Tix, 542 Tk, 530 Тк-консоль, 316 TkTable, 542 Tool Command Language, 57 и UDP, 363 Unicode, 337: 341: 872; 1086 Universal Resource Locator, 370 URL, 370 UTF-8, 341; 1040 V VFS, 508; 511; 1047; 1103 Virtual File System, 508; 511; 1047; 1103 w Wiki, 527 Wikit, 527 X X Input Method, 1099 XLM, J099 A Автозагрузка, 283 Авторизация X Window, 887 Активная модель фокуса, 902 Асинхронное сообщение, 483 Атом, 910; 1058 Атомарная команда, 494 Атрибут компонента, 535; 840 файла, 198 Б База данных ресурсов, 537; 667; 1053 строковых значений, 1055 Базовый объект, 1039 Байтовый код, 1077 Библиотека, 274 Tcl С, 942 Битовая карта, 786; 860; 1053 Блокирующий режим ввода-вывода, 357 Брандмауэр, 371 Буфер обмена, 1051 Буферизация, 359 в Ведомый интерпретатор, 433 компонент, 566 Ведущий интерпретатор, 433 Вертикальное размещение, 568 Видимая переменная, 297 Визуальный класс, 857; 911; 1055 Виртуальная файловая система, 508; 511; 1047 Виртуальное корневое окно, 909 событие, 531; 615; 631; 764; 1074 Внешнее дополнение, 575; 590 Внутреннее дополнение, 573; 590
Предметный указатель 1117 Возврат из процедуры, 161 Вращение, 811 Встроенная команда Tcl, 57 Встроенное изображение, 756 Встроенный Тс1-компилятор, 318 компонент, 754 Выделение, 750; 1068 CLIPBOARD, 815; 816; 819 PRIMARY, 815; 816 SECONDARY, 816 Выделенный текст, 878 Выравнивание текста, 693; 747 Г Гамма-коррекция, 862 Генерация событий, 632 Главное окно, 531 Глобальная область видимости, 166; 167 переменная, 166; 297 Гнездо, 201; 362 на стороне клиента, 364 на стороне сервера, 366 Горизонтальное размещение, 568 Гостевая книга, 93 Графический контекст, 1055; 1056 Группировка, 57 с помощью двойных кавычек, 63 с помощью фигурных скобок, 63 д Дамп содержимого текстового компонента, 759 Данные формы, 407 Двоичная кодировка, 342 строка, 1078 Двойное значение, 951 Двойной объект, 1039; 1078 Действие, 761 Дескриптор, 738; 743; 774 связывания, 615; 617; 618; 620 Диалоговое окно, 825 определяемое разработчиком, 829 Динамическая Web-страница, 96 строка, 1041 Динамическое формирование процедуры, 222 Диспетчер компоновки, 531; 566; 1054 grid, 587; 1073; 1074 pack, 566 place, 599 Диспетчер окон, 896 Дистрибутивный пакет Tcl, 986 Доменное имя, 364 Дополнение, 847 Доставка приложения, 510 Дочернее окно, 531 Дочерний компонент, 566 Дуга, 785 3 Заголовок окна, 546 Загружаемый пакет, 944 Замена окна пиктограммой, 900 Заполнение, 572 Запрос GET, 373; 375 HEAD, 373 POST, 373; 375 Запуск программ, 186 Захват ввода, 832 Защищенная база, 446 Защищенный интерпретатор, 438 И Идентификатор компонента, 910 процесса, 212 ресурса, 1058 Иерархия интерпретаторов, 436 областей видимости, 321 Известный каталог, 275 Изображение, 787; 850 photo, 862; 1054 Именование переменных, 71 файлов, 192 Имя компонента, 534
1118 Индекс, 117; 172; 739 Индексное выражение, 740 Инициализациониая процедура, 941 Инициализация пакета, 945 Инкрементный регулятор, 708 Инсталлятор, 528 Инсталляционный каталог, 987 Интерпретатор Tcl, 56 не пользующийся доверием, 433 пользующийся доверием, 433 Интерфейс DString, 970 Интроспекция, 330 Итератор, 237; 238 К Канал связи с процессом, 204 Карта отображения цвета, 857; 911; 1055 символов, 122 Каскадное меню, 558 Каталог сообщений, 344 Класс ресурса, 536 символов, 121; 237; 244 эквивалентности, 249 Клиент, 362 Ключевое слово const, 969 события, 616 Кнопка, 530; 640 Код состояния, 950 Кодировка, 337; 338; 361 Команда after, 350; 351; 456 append, 123 array, 175 auto_load, 278 auto_mkindex, 281 bell. 681; 697; 1068 bgerror, 316 binary, 128 bind, 616 bindtags, 618 break, 157; 620 catch, 157 Предметный указатель cd, 209 clipboard, 815; 819 clock, 291; 292; 1070; 1080 close, 209 concat, 138 console, 90; 1070 continue, 157; 620 destroy, 834; 1083 dump, 759 encoding, 343; 1087 error, 159 eval, 217; 218; 224 event, 633 exec, 186 exit, 212 expr, 60 fblocked, 359 fconfigure, 1072; 1087 fcopy, 379 file, 190; 1103 fileevent, 350; 352 focus, 831 font, 876 for, 156 foreach, 153 format, 63; 123 gets, 206 glob, 209 global, 167 grab, 832 grid, 597 history, 309; 310 http::cleanup, 386 http::config, 381 http::formatQuery, 385 http:register, 385 http::reset, 385 http::unregister, 385 if, 148 image, 859 incr, 69 info, 296 interp, 433 join, 146 lappend, 137 lindex, 140
Предметный указатель 1119 linsert, 140 list, 136 llength, 139 load, 944; 1071 lrange, 140 lreplace, 140 lsearch, 141 lset, 138 lsort, 143; 1079; 1095 namespace, 331 open, 202 pack, 584; 1062 package, 280; 1071 pid, 212 pkg_mkIndex, 277 place, 606 proc, 68; 163 puts, 57; 206 pwd, 209 read, 206 regexp, 234; 255 registry, 213 regsub, 234; 262 rename, 165 return, 161 scan, 127; 1095 seek, 208 selection, 8J5: 818 send, 885; 886; 1063 set, 58; 173 socket, 365 source, 86 split, 144 string, 114 subst, 232 switch, 150 Tcll, 208 testthread, 1088 time, 317 tk, 912 tkerror, 316 tkwait, 833 trace, 305 unknown, 278; 283 update, 838 uplevel, 229; 335 upvar, 168; 335 variable, 323 vwait, 350; 351; 354 while, 152 winfo, 904 wm, 806 в вода-вы вода, 201 класса, 1005 экземпляра, 1010 Командная кнопка, 547 процедура, 940; 947 Командный префикс, 221 Комментарии, 74 Компакт-диск, 1112 Компилятор, J 077; 1078 Компонент, 530; 639 canvas, 771 checkbutton, 639 entry, 708; 709 frame, 681 label, 681; 687 labelframe, 681; 682; 684 listbox, 724; 728 menubutton, 651 message, 681; 690 panedwindow, 608 radiobutton, 639 scale, 681; 693; 694 spinbox, 708; 713 toplevel, 08J; 682 Контекстное меню, 654 Координаты холста, 771; 810 Копирование каталогов,196 файлов, 196 Коэффициент сжатия, 690 Курсор мыши, 866 л Линейный регулятор, 693; 776 Линия, 788 Литеральный символ, 237 Локальная область видимости, 166 переменная, 297
1120 Предметный указатель м Макрос CONST, 969 Маркер, 742 Массив, 172; 493 Масштабирование, 810 Междустрочный интервал, 747 Меню, 530] 651 опций, 654 Метрика шрифта, 875 Многопотоковое Тс1-приложение, 474 Многопотоковое приложение, 1087 Многопотоковые операции, 475 Многоугольник, 791 Модель выделения, 815 данных Metakit, 522 Модификатор, 626 Мютекс, 495: 1087 н Набор возможностей, 468; 470 документов, 527 компонентов, 541 символов, 237; 337; 338; 1041 Направление маркера, 743 Неблокирующий режим ввода-вывода, 357 Непосредственная загрузка, 278 Несимметричное дополнение, 1107 о Область видимости, 163; 320 Обработчик документа, 390 домена, 389; 392 типа документа, 398 Обрамление, 845 Обратная ссылка, 247 Обратный вызов, 221; 329 времени бездействия, 1044 Объект, 940 холста, 771; 782; 1054 Овал, 785; 791 Однопотоковое Тс1-приложение, 474 Окно, 530; 799 верхнего уровня, 682 сообщения, 826 списка, 724 Оператор выбора, 237; 239 Открытый синтаксис, 250 Отладка, 312; 420 Отложенная загрузка, 278; 1096 Отмена выполненных действий, 760 Отображение доверия, 468 Отражение, 291 Отрицательное упреждающее сравнение, 247 Очередь компоновки, 580 п Пакет, 274 browser, 467 cgi.tcl, 107 html, 394; 413 http, 380 msgcat, 349 ncgi, 107; 394 Панель, 601; 609 Параметр, 57 командной строки, 90 Пассивная модель фокуса, 902 Первичное, окно, 531 Передача имени массива, 177 фокуса ввода, 831 Переименование каталогов, 198 файлов, 198 Переменная, 58; 297 auto_noload, 284 auto_ path, 282 окружения, 212 LANG, 345 LOCALE, 345 TCL_LIBRARY, 285 условия, 495; 496 Платформенно-независимое имя шрифта, 870 Платформенно-независимые средства для работы с файлами, 186; 190 Платформенно-независимый шрифт, 1081
Предметный указатель 1121 Подстановка, 57 команд, 59; 64 переменных, 59 Подшаблон, 242 Поиск в списке, 141 текста, 753 элементов массива, 1104 Поле редактирования, 547; 708 Ползунковый, регулятор, 693 Политика безопасности, 448; 465 Полностью определенное имя, 324 Положительное упреждающее сравнение, 247 Полоса прокрутки, 530; 548 Полость, 570 Пользовательские установки, 915 Порт, 364 Последовательное устройство, 361 Последовательность событий, 629 Поток, 472; 473; 1087 Правила группировки, 75 подстановки, 75 Предопределенное имя шрифта, 870 Предыстория вызова команд, 291; 309 Преобразование массива в список, 176 списка в массив, 176 Префикс, 288 Программа tclsh, 86 wish, 85 управляемая событиями, 350; 531 Просмотр, 522; 525 Пространство имен, 170; 320; 321; 1079 thread, 476; 501 tpool, 476; 506 tsv, 476; 504 компоновки, 572 отображения, 572 Протокол взаимодействия с полосами прокрутки, 700 Процедура, 57; 66; 163 http::geturl, 381 Процесс, 473 Псевдоним, 432 команды, 439 Пул потоков, 499 Р Разделяемая библиотека, 278 переменная потоков, 493 Разделяемый ресурс, 486 файл Star kit, 519 Размещение относительно направления, 567 Расширение Thread, 476 Расширенное регулярное выражение, 234; 242 Регистрация команды, 1035 Регулярное выражение, 234; 1043; 1095 Режим выделения browse, 731 extended, 732 multiple, 734 single, 732 сетки, 737; 878 Результирующая строка, 950 Рельеф, 1057 Ресурс, 536 Родительский компонент, 566 с Самообновляющаяся программа, 528 Свойство, 522 Связывание, 929; 1061 Сервер, 362 Символьная ссылка, 197 Синхронное сообщение, 483 Системная кодировка, 339 Системное меню, 654 Скрытая команда, 441 Слияние списков, 138 Событие, 309; 615 клавиатуры, 623 меню, 655
1122 Предметный указатель мыши, 624 Соединяемые потоки, 480 Создание интерпретатора, 1034 каталогов, 196 Сортировка списка, 143 Составное имя, 324 Список, 133; 134 массивов, 182 параметров процедуры, 66 Сравнение с шаблоном, 119 Стандартная библиотека Tcl, 363 Стандартное диалоговое окно, 826 Статическая Web-страница, 96; 389 Стек, 180 вызова, 300 окон, 586; 1051 Стиль окна верхнего уровня, 686 Строка, 114 меню, 653 Строковый интерфейс, 940; 948 объект, 1040 Счетчик ссылок, 953 т Табулятор, 750 Текстовая метка, 547; 687 Текстовое окно, 530 Текстовый компонент, 738 курсор, 865 объект, 793 Тело процедуры, 66 Точка фиксации, 847 у Удаление интерпретатора, 1034 каталогов, 197 файлов, 197 Управление панелями, 601 Управляющая структура, 147 Установки сетки, 898 ф Файловая система, 186 Фиксация, 578 Фиксированная ссылка, 197 Фокус ввода, 532; 548; 620; 830; 1061; 1063 Форма самопроверки, 411 Фрейм, 531; 682 X Холст, 530 Хэш-таблица, 1042 ц Цвет, 850 переднего плана, 852 фона, 852 Цветовая палитра, 853 Цикл обработки событий, 351; 977; 1043; 1072 ч Частично-определенное имя, 324 ш Шаблон, 235 HTML+Tcl, 390; 400 ресурса, 668 форматирования, 128 штриховых линий, 1096 Шрифт, 869 X Window, 873 Штриховая линия, 784 э Экономное сопоставление, 246 Экранные координаты, 810 Элемент замены, 249 массива, 173 я Якорь, 240; 578
Научно-популярное издание Брент Б. Уэлш, Кен Джонс, Джеффри Хоббс Практическое программирование на Tcl и Тк, 4-е издание Литературный редактор И.А. Попова Верстка А.П. Полипчик Художественный редактор c.A. Чернокозинский Корректоры З.В. Александрова, Л.А. Гордиенко, О. В. Мишутгта, Л. В. Чернокозииская Издательский дом "Вильяме". 101509, Москва, ул. Лесная, д. 43, стр. 1. Подписано в печать 21.06.2004. Формат 70x100/16. Гарнитура Times. Печать офсетная. Усл. печ. л. 71,0. Уч.-изд. л. 51,5. Тираж 3000 экз. Заказ № 2940. Отпечатано с диапозитивов в ФГУП "Печатный двор" Министерства РФ по делам печати, телерадиовещания и средств массовых коммуникаций. 197110, Санкт-Петербург, Чкаловский пр., 15.