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

    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, это просто великолепная троица.
    • +10
    • 2,3k
    • 6
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 6
    • +1
      Класснаая статья
    • +1

      Сам глубоко уважаю Tcl, но реально что сейчас на нём делается?

      • 0

        А это зависит от нас с вами. А делается много чего. Оглянитесь вокруг.

      • +2
        Шел 2017 год…
        • +2

          И приближался 2018 год.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.