python

Tcl/Tk. Тематические виджеты TTK и дизайнер TKproE-2.20

  • среда, 6 декабря 2017 г. в 03:13:08
https://habrahabr.ru/post/343930/
  • Тестирование IT-систем
  • Графический дизайн
  • Графические оболочки
  • Python
  • IT-стандарты


imageПросматривая свои заметки по проектированию GUI с использованием виджетов Tk, я почувствовал какую-то неудовлетворенность. А дело оказалось в том, что я фактически упустил работу с тематическими виджетами ttk (themed tk). Они в скользь были задействованы при рассмотрении пакета Tkinter для Python и использовании дизайнера Page . Там речь шла о виджете TNotebook (блокнот, записная книжка) из пакета ttk.

Виджет TNotebook это один из новых виджетов, наряду с TCombobox, TProgressbar, TSeparator и TSizegrip, появившихся в ttk. Перевод приложений с классического tk на ttk может потребовать минимальных усилий. Порой бывает достаточно вместо, например, button написать ttk::buton. Отдельно хочется сказать о combobox. Если вы в своих приложениях, написанных на tk, использовали виджет spinbox и теперь замените его на ttk::combobox, то в приложении появится этот виджет, которого вам так не хватало:

image

Но все же главное в ttk, это не новые виджеты, а их оформление (themеd). Настройка тем, стилей заслуживает отдельного разговора. А здесь бы я отправил разработчиков к вышедшему буквально на днях (11.11.2017 г.) замечательному учебному пособию:

image

Насмотря на то, что оно написано на немецком языке, но я, даже не знаю английского языка, читал его с превеликим удовольствием. Как говорится, написано для «дурака».

Нас интересует, есть ли интегрированная среда разработки/дизайнеры применительно к виджетам ttk. Если говорить о Python и Tkinter, то это пакет Page. Этот пакет поддерживает как классические виджеты, так и тематические виджеты. Правда почему-то отсутствуют поддержка разделителей TSeparator. Средой проектирования является скриптовый язык Tcl. Именно по этой причине мы еще вернемся к этому пакету. Отметим несомненное достоинство Page – это наличие мольберта, на котором можно просто и удобно размещать виджеты.

Но это для Python-а, а нас интересует конечный продукт на Tcl. Оказалось, что ровно год назад (27.11.2016 г.) был анансирован Tcl/Tk-дизайнер TKproE 2.20:

image

И в нем присутствуют разделители TSeparator. Уникальной особенностью этого дизайнера является то, что он принимает практически любой tcl/tk скрипт.
Для демонстрации возможностей как виджетов ttk, так и дизайнера TKproE-2.20, был разработан GUI-интерфейс для утилиты «изящной печати» pp из пакета NSS с устраненными косяками и поддержкой oid-ов российской pki (инфраструктура открытых ключей). Тем более, что такая утилита по просмотру сертификатов, включая квалифицированные сертификаты, просмотру электронной подписи документа, является востребованной. И именно для нее был спроектирован и реализован графический интерфейс с помощью TKproE:

image

Утилиту и графическую обвязку к ней можно скачать здесь. Еще одна очень приятная неожиданность в отличии от других дизайнеров ждала при работе с изображения (image) и иконками. Если раньше приходилось «ручками» переводить изображение в PEM-кодировку и самому вставлять код в скрипт, то теперь достаточно указать файл с изображением и TKproE сам заберет его и конвертирует:

image

Единственное неудобство здесь – это необходимость ввода имени файла. Но оказалось это легко исправить. Достаточно в скрипте tkproe.tcl в строках 7474 и 7516 код (процедура proc ShowWindow.tpimages )

label .tpimages.propbitmap.frame14.label12  -activebackground {#dcdcdc} \  

-anchor {w}  - background {#dcdcdc}  -borderwidth {2}  -font {Helvetica 10}  \
-highlightbackground  {#dcdcdc}  -text {File:}  -width {10}

заменить на код:

button .tpimages.propbitmap.frame14.label12  -activebackground {#dcdcdc} \ 

-anchor {w} -background {#dcdcdc}  -borderwidth {2}  -font {Helvetica 10}  \
-highlightbackground {#dcdcdc}  -text {File:}  -width {10} \
  -command { global userDir; set fileTypes {{"File image" *}}; \
set file [tk_getOpenFile -title "Find Image"  -filetypes $fileTypes  \
-initialdir "$userDir"]; \
.tpimages.propphoto.frame14.entry13 delete 0 end;  \
.tpimages.propphoto.frame14.entry13 insert end $file; }

А чтобы выбор файла начинался с домашнего каталога после 5 строки в скрипт tkproe.tcl добавляем следующий код:

global userDir
set userDir $env(HOME)

Теперь для выбора файла с изображением достаточно нажать кнопку «File:»:

image

Желающие могут улучшить и этот код, например, по выбранному файлу заполнять поле «Format», либо наоборот, по формату организовать поиск файла, рассматривая формат как расширение файла («Тип файлов» на скриншоте).

Еще одно неудобство, с которым пришлось столкнуться, это получение домашнего каталога пользователя, например, mHOME set $env(HOME). А домашний каталог, как правило, нужен для обеспечения платформанезависимости скрипта. Оказалось через TKproE вставить его некуда. Глобальные переменные в TKproE определяются через процедуру TPinitVars. Естественно, если определяешь переменную mHOME через область глобальных переменных:

image

то переменная mHome будеть иметь значение «$env(HOME)». Казалось бы, возьми и вставь вычисление текущего каталога ручками в основное тело скрипта. Можно, если только вы больше не будете править скрипт в TKproE. В противном случае TKproE озаботится оптимизацией и вместо кода set nHOME $env(HOME) вставит в процедуру инициализации глобальных переменных код с домашним каталогом компьютера, на котором создавался/правился скрипт. Это ужасно.

Здесь пришлось во второй раз залезть в код TKproE. Выход из данной ситуации мы нашли в определении глобальной переменной myHOME и включении в генерируемый скрипт дополнительного кода. И так, в процедуру proc TP_Clone_app сразу же за строкой (13-ая):

set outstr "# Generated by TKproE $TPinfo(revision) - [clock format [clock seconds]]\n\n"

добавляем строку

append outstr "#Add me\nencoding system utf-8\nglobal myHOME\nset myHOME \$env(HOME)\n\n" 

а в процедуру proc TP_Clone_variables после строки (10-ая)

 foreach varname $varlist {

вставляем код

if { $varname == "myHOME" } {
    continue
}

который отключает переустановление глобальной переменной myHOME.

И вот после этих доработок, и удалось создать графическую оболочку для утилиты PP:

image

Если выбрать файл с документом и нажать кнопку «Показать в окне», то мы увидим следующее:

image

Распечатку сертификата, как и любого другого документа, можно сохранить в файле (кнопка «Сохранить в файле» на вкладке «Сертификаты и т.п.»).
Исходный код скрипта GUIPP, а он же является и проектом TKproE, можно скачать здесь.

Выше мы говорили о том, что вернемся к дизайнеру Page. В дизайнере Page очень удобно проектировать GUI со свободным размещением виджетов (place). Как уже говорилось, проект GUI дизайнер Page хранит как Tcl/Tk скрипт, и было бы заманчиво использовать готовый скрипт из Page в TKproE или дописывать его в ручном режиме. В чистом виде проект из Page нельзя выполнить или загрузить в TKproE-2.20. Для устранения этой «несправедливости» был разработан tcl-скрипт, который приводит проекты из Page в выполняемые скрипты tcl/tk:

sh-4.3$ ./fromPageToTcl.tcl  
Usage: ./fromPageToTcl.tcl <file tcl from Page> 
sh-4.3$

Вот код tcl-скрипта fromPageToTcl.tcl:

Код tcl-скрипта fromPageToTcl.tcl
#!/usr/bin/tclsh
# 1 -- source file
#НЕ ЗАБЫВАТЬ КОММЕНТИРОВТЬ return
#    if {[winfo exists $base]} {
####        wm deiconify $base; return
#    }
# и строку
##    vTcl:FireEvent $base <<Ready>>
#LISSI-Soft
namespace eval vTcl::widgets::ttk::sizegrip {

    proc CreateCmd {target args} {

        grid [ttk::sizegrip $target] -column 999 -row 999 -sticky se

    }

}
proc vTcl:font:add_GUI_font {font_name font_descr} {
    # This is called when we load an existing GUI-tcl file. It get rid
    # of actual fonts and replaces them with the definitions from the
    # GUI-tcl file.

    #set font_descr [font configure $font_name]
    if {[catch {
        font delete $font_name
        set newfont [font create $font_name  {*}$font_descr ]
    } result]} {
        # Create failed
        set newfont "TkDefaultFont"
    }
     #set newkey NEEDS WORK
     #set ::vTcl(fonts,$newfont,type)       $font_type
     #set ::vTcl(fonts,$newfont,key)        $newkey
     set ::vTcl(fonts,$newfont,font_descr) $font_descr
     set ::vTcl(fonts,$font_descr,object)  $newfont  ;# Rozen 8/24/13
     #set ::vTcl(fonts,$newkey,object)      $newfont

}
proc {vTcl:font:add_font} {font_descr font_type {newkey {}} {check_fonts {1}}} {
    global vTcl
    ## This procedure may be used free of restrictions.
    ##    Exception added by Christian Gavin on 08/08/02.
    ## Other packages and widget toolkits have different licensing requirements.
    ##    Please read their license agreements for details.

    # With tk you are not allowed to specify the the font name when
    # creating a fomt. So if you want to assign a name to a font then
    # specify then the variable ::vTcl(fonts,$newkey,object) will hold
    # the correspondance between the actual name of the created name
    # and the name you wish to use.  Rozen
    set defined_fonts [font names]
    if {$newkey != ""} {

        if {[info exists ::vTcl(fonts,$font_descr,object)]} {
            set test_font $::vTcl(fonts,$font_descr,object)

            if {[lsearch $defined_fonts $newkey] == -1} {
                set ::vTcl(fonts,$newkey,object) $test_font
                return $test_font
            }
        }
    }
    if {$check_fonts} {
        if {[info exists ::vTcl(fonts,$font_descr,object)]} {
            # It already exists
            return $::vTcl(fonts,$font_descr,object)
        }
        if {[lsearch $defined_fonts $font_descr] > -1} {
            # It's a font already defined..
            return $font_descr
        }
    }
    incr ::vTcl(fonts,counter)
    set newfont [eval font create $font_descr]
    lappend ::vTcl(fonts,objects) $newfont

     ## each font has its unique key so that when a project is
     ## reloaded, the key is used to find the font descriptio
    if {$newkey == ""} {
        set newkey vTcl:font$::vTcl(fonts,counter)
        ## let's find an unused font key
        while {[vTcl:font:get_font $newkey] != ""} {
            incr ::vTcl(fonts,counter)
            set newkey vTcl:font$::vTcl(fonts,counter)
        }
    }
     set ::vTcl(fonts,$newfont,type)       $font_type
     set ::vTcl(fonts,$newfont,key)        $newkey
     set ::vTcl(fonts,$newfont,font_descr) $font_descr
     set ::vTcl(fonts,$font_descr,object)  $newfont
     set ::vTcl(fonts,$newkey,object)      $newfont
     lappend ::vTcl(fonts,$font_type) $newfont
     ## in case caller needs it
     return $newfont
}
proc vTcl:DefineAlias {a b c d e} {
#    puts \"$a.$b\"
#    puts \"$c.$d.$e\"
    return
}
proc Window {type w} {
    if {$w == "."} {
	return
    }
    toplevel $w
    set base $w
    vTclWindow$w $base
}
#END LISSI-Soft
set largv [llength $argv]
if {$largv != 1} {
    puts "Usage: [info script] <file tcl from Page>\n"
    exit
}
set fconf [file exists "$argv"]
if { $fconf == "0" } {
    puts "File=\"$argv\" don't exist\n"
    puts "Usage: [info script] <file tcl from Page>\n"
    exit
}
set srcMy [info script]
set fp [open $srcMy r]
while {![eof $fp]} {
    gets $fp res
    puts $res
    if { $res == "#END LISSI-Soft"} {
	break
    }
}
close $fp
#Обрабатываем файл из Page
set fp [open $argv r]
while {![eof $fp]} {
    gets $fp res
    if { [string first "wm deiconify \$base; return" $res] != -1 } {
	puts "#LISSI-Soft"
	puts "\t\twm deiconify \$base;"
	continue
    } 
    if { [string first "vTcl:FireEvent \$base <<Ready>>" $res] != -1 } {
	puts "#LISSI-Soft"
	puts "#\tvTcl:FireEvent \$base <<Ready>>"
	continue
    } </spoiler>
    if { [string first "vTcl:toplevel \$top -class Toplevel \\" $res] != -1 } {
	puts "#LISSI-Soft"
	puts "#vTcl:toplevel \$top -class Toplevel \\"
	puts "\$top configure  \\"
	continue
    } 
    if { [string first "vTcl::widgets::core::toplevel::createCmd \$top -class Toplevel \\" $res] != -1 } {
	puts "#LISSI-Soft"
	puts "#vTcl::widgets::core::toplevel::createCmd \$top -class Toplevel  \\"
	puts "\$top configure  \\"
	continue
    } 
    puts $res
}
close $fp
exit


И так, берем пример из папки ~/page/examples/complex и получаем посредством скрипта fromPageToTcl.tcl исполняемый tcl-скрипт:

sh-4.3$cd ~/page/examples/complex
sh-4.3$ ./fromPageToTcl.tcl complex.tcl > complex_new.tcl
sh-4.3$

Теперь, чтобы получить совсем хороший код, открываем полученный скрипт complex_new.tcl в дизайнере TKproE-2.20 и удаляем из проекта все ненужные теперь процедуры, а это процедура Window и все процедуры с префиксом vTcl, и пересохраняем его:

image

Отметим, что Page поддерживает еще «Scrolled widgets», мы их, естественно, не поддерживаем.

В целом, дизайнер TKproE оставил очень хорошее впечатление и с ним приятно вести разработку GUI. А тройка Page x TKproE x fromPageToTcl.tcl, состоящая из дизайнеров Page, TKproE и скрипта fromPageToTcl.tcl, это просто великолепная троица.