SVG-виджеты для tcl/tk. Часть I
Начать статью хочу с цитаты из слов Брайана Кернигана, которую мне посчастливилось найти:
Tcl/Tk придает работе магическую продуктивность, за несколько часов можно достигнуть тех же результатов, что за дни или недели при разработке на C или C++… Tk весьма эффективен для большинства приложений, многие элементы интерфейса (виджеты) реализованы настолько хорошо, что остается только удивляться, как подобная работа могла быть выполнена так качественно… Удачным кажется и то, что разделение задач между Тсl и С/С++ осуществляется достаточно легко, надо только знать, какой инструмент лучше справляется с задачей… Расширение системы дополнительным Tcl-кодом, загружаемым напрямую в Tcl-библиотеку приложения, в полном согласии с оригинальной идеей Остераута, повышает эффективность программы, в целом, упрощает ее структуру и улучшает мобильность… Я не уверен, что Тсl мог бы выжить как самостоятельный продукт — у него слишком много конкурентов. Но у сочетания Tcl/Tk в Unix-мире нет конкурентов… Система исключительно надежна, очень хорошо документирована… свободно доступна… безукоризненно высокого качества
Лично для меня Брайан Керниган не менее авторитетен чем Ричард Столлман, на которого ссылаются когда хотят принизить tcl/tk. В этой связи приведу один комментарий по этому поводу:
Столлман высказался в духе, что Tcl не подходит для больших проектов. Если прочесть критику полностью (а это достаточно известный holywar), то ясно что Tcl для него — средоточие контекстно-зависимых языков и свободных грамматик. Как фанату лиспа ему немного обидно что есть язык даже в теории более гибкий. Он (да и многие сейчас), кстати, тогда так и недопёр, что lisp и tcl, несмотря на всю похожесть, принципиально разные языки и их сравнивать всё равно что сравнивать кислое с мягким.
К тому же Столлман явная противоположность с Остерхаутом (автором tcl) — Ричард идеалист и анархист, Джон прагматик и консерватор. Первый всю жизнь обретается по халявным фондам и публично пиарится, второй в корпоративном секторе и тихо диктаторствует над проектами. Так что личные отношения сыграли не последнюю роль.
Вести проекты с Tcl действительно 'внезапно' сложно ;-) Серьёзное использование Tcl подразумевает создание собственного языка, максимально удобного для разработчика и конкретной задачи.
А чтобы 'язык' всех частей проекта был одинаково понятен всем — сами разработчики должны быть примерно одинаковой (высокой) квалификации и проект должен вестись централизованно и жёстко. Можно сказать Тикль максимально гибок и лёгок в использовании и столь же строг в ведении проектов.
И такая позиция мне импонирует.
В настоящей статье речь пойдет не столько о tcl (хотя все примеры и сам проект написаны именно на нем), а сколько о tk.
Удобство tk мало кто не признает. Более того именно tk под названием Tkinter, а не что-то другое, напрямую интегрирован в Python, да и во многие другие языки.
Но как только покажешь приложение, в которой gui разработано на tk, то тут же можешь услышать, — опять этот убогий, примитивный, в лучшем случае устаревший интерфейс. И я здесь я соглашусь с этими критиками. Предпринималось не мало попыток улучшение презентабельности tk-виджетов (помимо ttk-виджет), некоторые из которых можно посмотреть здесь.
Но даже они на фоне пользовательского интерфейса на мобильниках, на qt или gtk смотрятся бледновато.
А поскольку я фанат tcl/tk, то мне очень хочется поправить это положение. Понятно, что эту проблему можно решить путем привлечения SVG-графики. Поддержка SVG-графики в tcl/tk реализована через пакет tkpath, автором которого был Матс Бенгтссон (Mats Bengtsson). Возможности этого пакета были апробированы мною при встраивании векторной графики в редактор tksvgpaint. Имея за плечами этот опыт, я и решился на разработку SVG-виджетов для tcl/tk. Помимо пакета tkpath для реализации проекта понадобился еще и пакет treectrl, о его роли будет рассказано позже.
В итоге был разработан пакет svgwidgets, некоторые виджеты которого, приведены ниже (пример скрипт_button_PACK.tcl):
Все виджеты в пакете сгруппированы в пять классов — cbutton, ibutton, mbutton, cmenu и cframe. Сейчас по прошествии времени, можно говорить, что много классов или наоборот, но пока так. Пройдет время и может что изменится. Уже сейчас видно, например, что некоторые их них фактически дублируют друг друга. Но пока не будем их трогать. Прежде чем перейти к рассмотрению виджетов, остановимся на инструментарии. Понятно, что этим инструментарием является интерпретатор wish и пакеты tkpath и treectrl. Но помимо wish сегодня есть и другие более удобные интерпретаторы с уже интегрированными в них набором пакетов. Прежде всего это интерпретатор freewrap с графической консолью и его клон tclexecomp. Я добавил в состав этих интерпретаторов не только пакеты tkpath и treectrl, но и представляемый здесь пакет svgwidgets. Теперь достаточно скачать один из этих модифицированных интерпретаторов (сегодня, правда, только tclexecom для 64-х разрядных платформ Linux и Windows) и можно начинать работать. Более того, эти интерпретаторы позволят создать и бинарный код написанного на tcl/tk приложения. Если кто не работал с этими интерпретаторами ранее, то стоит попробовать, чтобы увидеть все их достоинства.
Все рассматриваемые здесь примеры и сами интерпретаторы лежат на github-e. В репозитории TkSVGwidgets находятся три папки. В папке svgwidgets находится одноименный пакет, в папке examples лежат tcl-скрипты, демонстрирующие использование пакета для создания различных виджетов. В папке tclexecomp140_svg лежат два дистрибутива tclexecomp со встроенными пакетами tkpath, treectrl и svgwidgets, для платформ Linux и Windows — tclexecomp140_svg_Linux64 и tckexecomp140_svg_Win64.exe.
Загрузив проект на свой компьютер, вы получите все необходимое для апробации новых виджетов. Чуть не упустил, пакет svgwidgets успешно работает и на платформе андроид. На всех трех платформах svg-виджеты выглядят одинаково.
Для того, чтобы увидеть на своем мониторе, приведенную выше картинку достаточно выполнить команду:
#tclexecomp140_svg_Linux64 ~/TkSVGwidgets/examples/скрипт_,button_PACK.tcl
Но при обучении, разработке, отладке целесообразней сначала запустить интерпретатор:
#tclexecomp140_svg_Linux64
После его запуска выполняем/интерпретируем требуемый скрипт. В нашем случае скрипт_, button_PACK.tcl (см. рисунок выше).
Внимание! Если бы тестируете примеры на платформе Windows, то, прежде чем их выполнять, выполните в консоле tclexecomp следующую команду:
%encoding system utf-8
Все примеры готовились на платформе linux в кодировке utf-8. У вас на windows скорее всего другая кодировка (cp1251). Выполнение этой команды позволит tcl/tk самому разобраться с кодировками примеров.
Теперь перед нами окно с примером и графическая консоль интерпретатора. Консоль нам потребуется, например, для модернизации примера в самом широком смысле: изменение расположения, цветовой палитры, добавления виджетов и т.п.
Здесь самое время рассказать об устройстве svg-виджетов. SVG-виджет формируется на холсте, который создается конструктором как tkp: canvas без окантовки (-borderwidth 0 и -highlightthickness 0):
package require tkpath
tkp::canvas <имя холста> -bd 0 –highlightthickness 0
Имя холста для svg-виджета формируется аналогично тому, как это делается для классических виджетов Tk и начинается оно всегда с точки.
Если кто впервые имеет дело с tcl/tk, то я бы рекомендовал прочитать учебное пособие Андрея Викторовича Столярова «Программирование: введение в профессию. IV: Парадигмы», опубликованное в издательстве МАКС Пресс в 2020 году:
Создание svg-виджетов практически ничем не отличается от создания классических виджетов в tcl/tk. Если вспомнить как создаются объекты того или иного класса при объектно-ориентированном программировании в tcl/tk, то окажется, что есть два способа. Различие этих способов состоит в том, что в первом способе идентификатор создаваемого объекта назначает конструктор класса :
<имя класса> new <имя холста> [параметры объекта]
Во втором способе идентификатор объекта назначает программист.
<имя класса> create <идентификатор объекта> <имя холста> [параметры объекта]
В обоих случаях конструктор возвращает идентификатор созданного объекта.
Для уничтожения объекта используется метод destroy:
<идентификатор объекта> destroy
При уничтожении объекта удаляется и холст, на котором создавался виджет.
Начнем знакомство с svg-виджетами с класса cbutton:
#tclexecomp140_svg_Linux64 ~/TkSVGwidgets/examples/скрипт_button_GRID.tcl
В результате выполнения на экране появится два окна:
Слева располагается консоль для ввода команд tcl/tk, а справа окно с svg-виджетами класса cbutton.
Внешний вид виджета класса cbutton задается опцией –type.
Условно в классе cbutton можно выделить три группы кнопок (не обессудьте, так получилось). Первую группу составляют классические кнопки трех типов (опция –type <тип виджета>):
-type rect – создать прямоугольный виджет;
-type round – создать виджет, у которого узкая сторона имеет полукруглые окончания;
-type ellipse – виджет в виде эллипса.
Если мы хотим создать прямоугольный виджет с закругленными углами, то необходимо указать тип rect и задать значение округлости углов через опцию –rx.
Эти кнопки на скриншоте объединены в левом фрейме с зеленоватой оконтовкой.
Фрейм тоже создается как объект класса cbutton типв frame:
cbutton new $t.frame –type frame –rx 5m
Во вторую группу входят radio и check-кнопки, которые представлены в среднем фрейме с красной окантовкой:
-type radio – создать radio-кнопку
-type check – создать check-кнопку (чекбокс)
И в третью группу входят кнопки по функционалу идентичные первой, но имеющие несколько другой вид:
-type circle – создать круглую кнопку
-type square – создать квадратную кнопку
Эти кнопки представлены в третьем фрейме с серой окантовкой.
Теперь можно взять мышку и побегать по виджетам, и даже пощелкать по ним.
Можно будет увидеть, что какие виджеты реагируют на курсор мыши, на нажатие кнопок на мышке, а какие-то игнорируют мышку.
Для получения списка созданных объектов определенного класса используется команда:
info class instances <класс>
Каждый объект обладает определенным набором свойств, получить или изменить которые можно путем вызова метода config:
<объект> config [[-параметр [значение параметра]]]
При вызове метода config без параметров возвращается список всех параметров с установленными значениями. При вызове метода config с указанием параметра возвращается текущее значение этого параметра. Для изменения значения параметра надо вызвать метод config с указанием изменяемого параметра и его нового значения.
Выполнив в графической консоли следующий скрипт:
puts " Объект\t\t Тип\t\tТекст";
foreach obj [info class instances cbutton] {
puts "obj type]\t[$obj config -text]"
}
мы получим список объектов с указанием их типа (метод type: $obj type) и текста, который отображается в виджете (метод config –text: $obj config -text):
Глядя на полученный результат можно предположить, что для двух объектов, а именно Прямоугольник и Квадрат, идентификаторы были заданы явно при их создании. Для всех остальных объектов интерпретатор сам назначил идентификаторы для них и все они начинаются с префикса :: oo: Obj.
Каждая кнопка может менять свою заливку и обводку при наведении курсора мыши на нее (параметры –fillenter и -strokeenter) или щелчку (-fillpress и -strokepress) по ней.
За текущей заливкой и обводкой следят три метода.
Метод »method enter» при наведении курсора мыши на виджет устанавливает заливку и обводку в соответствии со значениями параметров объекта -fillenter и -strokeenter, метод »method press» использует парамнетры -fillpress и -strokepress, а метод »method leave» — параметры –fillnormal и -strokenormal.
Покажем, как это работает на объекте «Прямоугольник». Заливка объекта может задаваться не только цветом, но и градиентной заливкой. Заливка и обводка для обычного состояния задается параметрами –fillnormal и -strokenormal. Заливка и обводка могут и отсутствовать, например:
<объект> config –fillnormal {} –strokenormal {}
Сразу хочу отметить, что заливку и обводку можно сделать и прозрачной (частично прозрачной). Для этого используются параметры –fillopacity и –strokeopacity. Внешне все это выглядит идентично, но это две большие разницы. В первом случае вы можете спокойно «пройти» через проем без заливки (без стекла), во втором случае вы «упретесь» лбом в прозрачное (невидимое стекло).
Кстати, каждый объект имеет несколько созданных по умолчанию градиентных заливок, список которых можно получить выполнив следующую команду:
<имя холста> gradient names
Если вы забыли имя холста, на котором создан виджет, то его можно получить, воспользовавшись методом canvas:
[<объект> canvas] gradient names
Получение списка градиентных заливок для объекта «Прямоугольник» будет выглядеть так:
%[Прямоугольник canvas] gradient names
gradient2 gradient3 gradient4 gradient0 gradient5 gradient1
Теперь каждый может поиграться заливкой и обводкой различных объектов для различных состояний, например:
%Прямоугольник config –fillnormal gradient2
%Прямоугольник config –fillnormal gradient4 –strokeenter blue
%Прямоугольник config –fillnormal #fefefe
Естественно у кнопок есть и параметр –command, который срабатывает, если щелкнуть по кнопке;
%Квадрат config –command {puts «Нажата кнопка Квадрат»}
После выполнения этой команды, при щелчке по виджету Квадрат в консоле будет появляться текст »Нажата кнопка Квадрат».
Вызвать выполнение команды, закрепленной за кнопкой, можно и вызовом метода invoke:
%Квадрат invore
Нажата кнопка Квадрат
Может показаться, что не хватает виджета типа «label».
Однако он легко реализуется, скажем, через объект типа rect, у которого пустой параметр –command, а параметр –fillpress имеет значение »##» (две решетки). Если у какого-то объекта параметры –fillenter или –fillpress имеют значение »##», то смены цвета заливки и обводки для этого объекта не происходит.
Но есть и более простой способ превратить объект типа rect в метку. Для этого достаточно установить значениеt параметра –state равным disabled:
<объект> config –state disabled
Для возврата объекта в нормальное состояние достаточно выполнить следующую команду:
<объект> config –state normal
Имеется также состояние hidden, когда объект становится невидимым.
В нашем примере использование параметра –state в методе config демонстрируют виджеты :: oo: Obj62, :: oo: Obj64, :: oo: Obj70 и :: oo: Obj76 (метка в начале каждого фрейма):
%::oo::Obj76 config –state
disabled
В объектах типа rect и square можно установить иконки. Это могут быть svg-иконки либо картинки, создаваемые командой image create photo.
Иконка может задаваться как при создании объекта (параметр –image или -isvg), либо устанавливаться в уже существующий объект:
%<объект> config –image <иконка>
Если иконка это картинка типа image, то она задается именем, которое было ей присвоено при создании.
Посмотреть весь список картинок можно командой
%image names
…
::tk::icons::question
…
Из полученного списка выберем иконку »:: tk: icons: question» и вставим ее в виджет «Прямоугольник»:
%Прямоугольник config –image «::tk::icons::question»
Прежде чем установить SVG-картинку в качестве иконки ее надо создать.
Для примера возьмем две функции (файл folderbrown.tcl):
proc folderbrown {canv} {
set grfolder [$canv create group]
set path1 [$canv create path "M 2 3 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 30 8 L 30 5 L 16 5 L 14 3 L 2 3 z "]
$canv itemconfigure $path1 -parent $grfolder -fill "#8b6039" -strokewidth 0
set path2 [$canv create path "m 2 3 0 7 9 0 L 13 8 30 8 30 5 16 5 14 3 2 3 Z"]
$canv itemconfigure $path2 -parent $grfolder -fillopacity 0.33 -fillrule "evenodd" -fill black -strokewidth 0
set path3 [$canv create path "M 14 3 L 15 6 L 30 6 L 30 5 L 16 5 L 14 3 z M 13 8 L 11 10 L 1 10 L 1 11 L 12 11 L 13 8 z"]
$canv itemconfigure $path3 -parent $grfolder -fillopacity 0.2 -fillrule "evenodd" -fill "#ffffff" -strokewidth 0
set path4 [$canv create path "M 13 8 L 11 9 L 2 9 L 2 10 L 11 10 L 13 8 z M 1 28 L 1 29 L 31 29 L 31 28 L 1 28 z"]
$canv itemconfigure $path4 -parent $grfolder -fillopacity 0.2 -stroke "#614d2e" -fill "#614d2e" -fillrule "evenodd" -strokewidth 0
return $grfolder
}
proc foldercolor {canv {fcol blue}} {
set grfolder [$canv create group]
set path1 [$canv create path "M 0.0 0.0 L 0.0 1.0 L 0.0 16.0 L 1.0 16.0 L 16.0 16.0 L 16.0 15.0 L 16.0 2.0 L 9.0 2.0 L 7.0 0.0 L 7.0 0.0 L 7.0 0.0 L 1.0 0.0 L 0.0 0.0 Z \
M 1.0 1.0 L 4.0 1.0 L 6.6 1.0 L 7.6 2.0 L 3.6 6.0 L 3.6 6.0 L 1.0 6.0 L 1.0 1.0 Z \
M 6.0 5.0 L 15.0 5.0 L 15.0 15.0 L 1.0 15.0 L 1.0 7.0 L 2.6 7.0 L 4.0 7.0 L 4.0 7.0 L 4.0 7.0 L 6.0 5.0 Z"]
$canv itemconfigure $path1 -parent $grfolder -fill $fcol -strokewidth 1 -stroke $fcol
return $grfolder
}
Первая функция folderbrow создает иконку папки в шоколадных тонах. Вторая функция foldercolor тоже создает иконку папки, но немного проще и цвет контура папки можно задать самому. По умолчанию цвет контура будет синим (blue).
Обе функции возвращают идентификатор группы, в которой и находится иконка. Иконки создаются на любом холсте. После создания виджета они могут создаваться (временно) и на холсте самого виджета.
Создадим одну иконку на левом фрейме, а вторую на правом: %set idicon1 [foldercolor [::oo::Obj63 canvas] blue]
% set idicon2 [folderbrown [::oo::Obj75 canvas] ]
После создания первую иконку вставим в виджет с закругленными вершинами, а вторую в виджет Квадрат:
%::oo::Obj68 config –image «[::oo::Obj63 canvas] $idicon1»
%Квадрат config –image «[::oo::Obj75] $idicon2»
При установке svg-иконки вместо параметра –image можно использовать параметр –isvg:
%Квадрат config –isvg «[::oo::Obj75] $idicon2»
SVG-иконка задается двумя параметрами, первый — это имя холста, где она располагается, а второй это идентификатор самой иконки. При этом идентификатор может указывать и на группу, если иконка составная.
По умолчанию иконки размещаются в виджете слева (параметр –compound left). Параметр –compound может принимать следующие значения — left, right, top, bottom.
Для задания координат иконки в виджете служит параметр -ipad, который представляет собой список из четырех значений:
-ipad {<смещение по х> <ширина иконки> <смещение по y> <высота иконки>}
После установления иконок в виджетах, исходные иконки следует удалить (но если они вам не мешают, то можете их не удалять или сделать не видимыми): %[::oo::Obj63 canvas] delete $idicin1
%[::oo::Obj75 canvas] delete $idicon2
%
После всех наших манипуляций картинка примера может принять следующий вид:
До сих пор мы использовали для размещения виджетов менеджеры grid и pack, которые не допускают наложения виджетов. И градиентную заливку мы использовали только на конечных виджетах (они не содержат других виджетов). Все это, позволяло нам избегать проблем с цветовой гаммой.
Пора расставаться с этим примером и переходить к следующему. Для этого мы закроем окно примера, а затем загрузим пример из файла demoPackSVGwithImageMes.tcl:
После прочтения информации в левом окне, нажмите на кнопку »Да» и пощелкайте по кнопкам. Если появится окно с предложением выхода, то не спешите и нажмите на кнопку »Нет»:
Внимательно рассмотрите информационные выноски. Это виджеты класса mbutton.
Они могут быть 6 (шести) типов: yesno, msg, left, right, up, down. Тип виджета класса mbutton должен быть задан при создании через параметр –type. На предпоследнем скрипншоте представлены виджеты класса mbutton типа msg (окно слева) и типа down (окно справа), а последнем скриншоте — типа right (левое окно) и типа yesno (правое окно).
У этих виджетов есть специфический параметр –tongue, который задает расположение высунутого язычка на верхней или нижней, правой или левой стороне виджета. Параметр –tongue список из 4-х значений, например:
-tongue «0.45 0.5 0.55 5m»
Последний (четвертый) элемент в списке задает длину язычка (в примере это 5 миллиметров). Остальные три значения (от 0 до 1) определяют координаты трех точек язычка. Координаты задаются в долях от длины стороны виджета, которой принадлежит язычок.
На последнем скриншоте показаны виджеты класса mbutton типа right (виджет слева) и типа yesno (виджет справа). Можно заметить, что у виджетов типа yesno и msg отсутствуют язычки. Иногда бывает полезным убрать язычки и у типов up, down, left и right. Это достигается просто, указывается нулевая длина язычка и, например, тип down:
-type down -tongue «045 0.5 0.55 0»
Если внимательно посмотреть на последние скриншоты, то можно обратить внимание на то, что некоторые подписи на виджетах класса cbutton типа rect повернуты на 90 градусов. («Вертикаль 4», «Вертикаль 8», «Вертикаль 12»). Да, текст на виджетах класса cbutton можно повернуть на определенный градус, задав параметр –rotate <градус поворота>.
В данном примере для виджетов с текстом «Вертикаль 4», «Вертикаль 8» и «Вертикаль 12» были заданы параметры –rotate 90 и –compound top, что обеспечило размещение иконки папки выше текста и разворот текста на 90 градусов.
Но главное в этом примере, как удалось разместить эти сообщения поверх других виджетов. Понятно, что должен использоваться компоновщик place, но просто его применение к нашему информационному виджету привело бы вот к такому результату:
На скриншоте хорошо видно, что наша информационная выноска лежит на какой-то подложке. О том, как была решена эта задача, речь пойдет в следующей статье.
А завершим мы эту первую статью, примером на тему шашек (пример demoGridSVGдоска.tcl):
Здесь можно увидеть и перевернутый текст, и градиентную заливку и даже можно походить. Ходить можно только по очереди и только на пустые черные клетки. У ходящей стороны голубым (cyan) цветом подсвечивается строка с буквами. Для завершения приложения следует щелкнуть по неперевернутой букву E (внизу) . Для возврата в исходное состояние можно щелкнуть по зеленоватым квадратикам в углах доски. Играть просто. Первый щелчок выбирает пешку для хода, а второй щелчок выбирает клетку, куда должна попасть выбранная пешка.
В следующих статья мы познакомимся не только с тем, как работают менеджеры pack, grid и place, но познакомимся с разными видами меню (пример скрипт_menu.tcl):
И продолжим знакомство с градиентной заливкой, прозрачностью (пример скрипт_button_PACK_gradient_opacity.tcl):
А вот скриншот приложения, полностью написанного с использованием пакета svgvidgets:
Жду конструктивной критики.