Spaces:
Sleeping
Sleeping
# fontchooser.tcl - | |
# | |
# A themeable Tk font selection dialog. See TIP #324. | |
# | |
# Copyright (C) 2008 Keith Vetter | |
# Copyright (C) 2008 Pat Thoyts <patthoyts@users.sourceforge.net> | |
# | |
# See the file "license.terms" for information on usage and redistribution | |
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
namespace eval ::tk::fontchooser { | |
variable S | |
set S(W) .__tk__fontchooser | |
set S(fonts) [lsort -dictionary [font families]] | |
set S(styles) [list \ | |
[::msgcat::mc "Regular"] \ | |
[::msgcat::mc "Italic"] \ | |
[::msgcat::mc "Bold"] \ | |
[::msgcat::mc "Bold Italic"] \ | |
] | |
set S(sizes) {8 9 10 11 12 14 16 18 20 22 24 26 28 36 48 72} | |
set S(strike) 0 | |
set S(under) 0 | |
set S(first) 1 | |
set S(sampletext) [::msgcat::mc "AaBbYyZz01"] | |
set S(-parent) . | |
set S(-title) [::msgcat::mc "Font"] | |
set S(-command) "" | |
set S(-font) TkDefaultFont | |
} | |
proc ::tk::fontchooser::Setup {} { | |
variable S | |
# Canonical versions of font families, styles, etc. for easier searching | |
set S(fonts,lcase) {} | |
foreach font $S(fonts) { lappend S(fonts,lcase) [string tolower $font]} | |
set S(styles,lcase) {} | |
foreach style $S(styles) { lappend S(styles,lcase) [string tolower $style]} | |
set S(sizes,lcase) $S(sizes) | |
::ttk::style layout FontchooserFrame { | |
Entry.field -sticky news -border true -children { | |
FontchooserFrame.padding -sticky news | |
} | |
} | |
bind [winfo class .] <<ThemeChanged>> \ | |
[list +ttk::style layout FontchooserFrame \ | |
[ttk::style layout FontchooserFrame]] | |
namespace ensemble create -map { | |
show ::tk::fontchooser::Show | |
hide ::tk::fontchooser::Hide | |
configure ::tk::fontchooser::Configure | |
} | |
} | |
::tk::fontchooser::Setup | |
proc ::tk::fontchooser::Show {} { | |
variable S | |
if {![winfo exists $S(W)]} { | |
Create | |
wm transient $S(W) [winfo toplevel $S(-parent)] | |
tk::PlaceWindow $S(W) widget $S(-parent) | |
} | |
set S(fonts) [lsort -dictionary [font families]] | |
set S(fonts,lcase) {} | |
foreach font $S(fonts) { lappend S(fonts,lcase) [string tolower $font]} | |
wm deiconify $S(W) | |
} | |
proc ::tk::fontchooser::Hide {} { | |
variable S | |
wm withdraw $S(W) | |
} | |
proc ::tk::fontchooser::Configure {args} { | |
variable S | |
set specs { | |
{-parent "" "" . } | |
{-title "" "" ""} | |
{-font "" "" ""} | |
{-command "" "" ""} | |
} | |
if {[llength $args] == 0} { | |
set result {} | |
foreach spec $specs { | |
foreach {name xx yy default} $spec break | |
lappend result $name \ | |
[expr {[info exists S($name)] ? $S($name) : $default}] | |
} | |
lappend result -visible \ | |
[expr {[winfo exists $S(W)] && [winfo ismapped $S(W)]}] | |
return $result | |
} | |
if {[llength $args] == 1} { | |
set option [lindex $args 0] | |
if {[string equal $option "-visible"]} { | |
return [expr {[winfo exists $S(W)] && [winfo ismapped $S(W)]}] | |
} elseif {[info exists S($option)]} { | |
return $S($option) | |
} | |
return -code error -errorcode [list TK LOOKUP OPTION $option] \ | |
"bad option \"$option\": must be\ | |
-command, -font, -parent, -title or -visible" | |
} | |
set cache [dict create -parent $S(-parent) -title $S(-title) \ | |
-font $S(-font) -command $S(-command)] | |
set r [tclParseConfigSpec [namespace which -variable S] $specs "" $args] | |
if {![winfo exists $S(-parent)]} { | |
set code [list TK LOOKUP WINDOW $S(-parent)] | |
set err "bad window path name \"$S(-parent)\"" | |
array set S $cache | |
return -code error -errorcode $code $err | |
} | |
if {[string trim $S(-title)] eq ""} { | |
set S(-title) [::msgcat::mc "Font"] | |
} | |
if {[winfo exists $S(W)] && [lsearch $args -font] != -1} { | |
Init $S(-font) | |
event generate $S(-parent) <<TkFontchooserFontChanged>> | |
} | |
return $r | |
} | |
proc ::tk::fontchooser::Create {} { | |
variable S | |
set windowName __tk__fontchooser | |
if {$S(-parent) eq "."} { | |
set S(W) .$windowName | |
} else { | |
set S(W) $S(-parent).$windowName | |
} | |
# Now build the dialog | |
if {![winfo exists $S(W)]} { | |
toplevel $S(W) -class TkFontDialog | |
if {[package provide tcltest] ne {}} {set ::tk_dialog $S(W)} | |
wm withdraw $S(W) | |
wm title $S(W) $S(-title) | |
wm transient $S(W) [winfo toplevel $S(-parent)] | |
set outer [::ttk::frame $S(W).outer -padding {10 10}] | |
::tk::AmpWidget ::ttk::label $S(W).font -text [::msgcat::mc "&Font:"] | |
::tk::AmpWidget ::ttk::label $S(W).style -text [::msgcat::mc "Font st&yle:"] | |
::tk::AmpWidget ::ttk::label $S(W).size -text [::msgcat::mc "&Size:"] | |
ttk::entry $S(W).efont -width 18 \ | |
-textvariable [namespace which -variable S](font) | |
ttk::entry $S(W).estyle -width 10 \ | |
-textvariable [namespace which -variable S](style) | |
ttk::entry $S(W).esize -textvariable [namespace which -variable S](size) \ | |
-width 3 -validate key -validatecommand {string is double %P} | |
ttk_slistbox $S(W).lfonts -height 7 -exportselection 0 \ | |
-selectmode browse -activestyle none \ | |
-listvariable [namespace which -variable S](fonts) | |
ttk_slistbox $S(W).lstyles -width 5 -height 7 -exportselection 0 \ | |
-selectmode browse -activestyle none \ | |
-listvariable [namespace which -variable S](styles) | |
ttk_slistbox $S(W).lsizes -width 4 -height 7 -exportselection 0 \ | |
-selectmode browse -activestyle none \ | |
-listvariable [namespace which -variable S](sizes) | |
set WE $S(W).effects | |
::ttk::labelframe $WE -text [::msgcat::mc "Effects"] | |
::tk::AmpWidget ::ttk::checkbutton $WE.strike \ | |
-variable [namespace which -variable S](strike) \ | |
-text [::msgcat::mc "Stri&keout"] \ | |
-command [namespace code [list Click strike]] | |
::tk::AmpWidget ::ttk::checkbutton $WE.under \ | |
-variable [namespace which -variable S](under) \ | |
-text [::msgcat::mc "&Underline"] \ | |
-command [namespace code [list Click under]] | |
set bbox [::ttk::frame $S(W).bbox] | |
::ttk::button $S(W).ok -text [::msgcat::mc OK] -default active\ | |
-command [namespace code [list Done 1]] | |
::ttk::button $S(W).cancel -text [::msgcat::mc Cancel] \ | |
-command [namespace code [list Done 0]] | |
::tk::AmpWidget ::ttk::button $S(W).apply -text [::msgcat::mc "&Apply"] \ | |
-command [namespace code [list Apply]] | |
wm protocol $S(W) WM_DELETE_WINDOW [namespace code [list Done 0]] | |
# Calculate minimum sizes | |
ttk::scrollbar $S(W).tmpvs | |
set scroll_width [winfo reqwidth $S(W).tmpvs] | |
destroy $S(W).tmpvs | |
set minsize(gap) 10 | |
set minsize(bbox) [winfo reqwidth $S(W).ok] | |
set minsize(fonts) \ | |
[expr {[font measure TkDefaultFont "Helvetica"] + $scroll_width}] | |
set minsize(styles) \ | |
[expr {[font measure TkDefaultFont "Bold Italic"] + $scroll_width}] | |
set minsize(sizes) \ | |
[expr {[font measure TkDefaultFont "-99"] + $scroll_width}] | |
set min [expr {$minsize(gap) * 4}] | |
foreach {what width} [array get minsize] { incr min $width } | |
wm minsize $S(W) $min 260 | |
bind $S(W) <Return> [namespace code [list Done 1]] | |
bind $S(W) <Escape> [namespace code [list Done 0]] | |
bind $S(W) <Map> [namespace code [list Visibility %W 1]] | |
bind $S(W) <Unmap> [namespace code [list Visibility %W 0]] | |
bind $S(W) <Destroy> [namespace code [list Visibility %W 0]] | |
bind $S(W).lfonts.list <<ListboxSelect>> [namespace code [list Click font]] | |
bind $S(W).lstyles.list <<ListboxSelect>> [namespace code [list Click style]] | |
bind $S(W).lsizes.list <<ListboxSelect>> [namespace code [list Click size]] | |
bind $S(W) <Alt-Key> [list ::tk::AltKeyInDialog $S(W) %A] | |
bind $S(W).font <<AltUnderlined>> [list ::focus $S(W).efont] | |
bind $S(W).style <<AltUnderlined>> [list ::focus $S(W).estyle] | |
bind $S(W).size <<AltUnderlined>> [list ::focus $S(W).esize] | |
bind $S(W).apply <<AltUnderlined>> [namespace code [list Apply]] | |
bind $WE.strike <<AltUnderlined>> [list $WE.strike invoke] | |
bind $WE.under <<AltUnderlined>> [list $WE.under invoke] | |
set WS $S(W).sample | |
::ttk::labelframe $WS -text [::msgcat::mc "Sample"] | |
::ttk::label $WS.sample -relief sunken -anchor center \ | |
-textvariable [namespace which -variable S](sampletext) | |
set S(sample) $WS.sample | |
grid $WS.sample -sticky news -padx 6 -pady 4 | |
grid rowconfigure $WS 0 -weight 1 | |
grid columnconfigure $WS 0 -weight 1 | |
grid propagate $WS 0 | |
grid $S(W).ok -in $bbox -sticky new -pady {0 2} | |
grid $S(W).cancel -in $bbox -sticky new -pady 2 | |
if {$S(-command) ne ""} { | |
grid $S(W).apply -in $bbox -sticky new -pady 2 | |
} | |
grid columnconfigure $bbox 0 -weight 1 | |
grid $WE.strike -sticky w -padx 10 | |
grid $WE.under -sticky w -padx 10 -pady {0 30} | |
grid columnconfigure $WE 1 -weight 1 | |
grid $S(W).font x $S(W).style x $S(W).size x -in $outer -sticky w | |
grid $S(W).efont x $S(W).estyle x $S(W).esize x $bbox -in $outer -sticky ew | |
grid $S(W).lfonts x $S(W).lstyles x $S(W).lsizes x ^ -in $outer -sticky news | |
grid $WE x $WS - - x ^ -in $outer -sticky news -pady {15 30} | |
grid configure $bbox -sticky n | |
grid columnconfigure $outer {1 3 5} -minsize $minsize(gap) | |
grid columnconfigure $outer {0 2 4} -weight 1 | |
grid columnconfigure $outer 0 -minsize $minsize(fonts) | |
grid columnconfigure $outer 2 -minsize $minsize(styles) | |
grid columnconfigure $outer 4 -minsize $minsize(sizes) | |
grid columnconfigure $outer 6 -minsize $minsize(bbox) | |
grid $outer -sticky news | |
grid rowconfigure $S(W) 0 -weight 1 | |
grid columnconfigure $S(W) 0 -weight 1 | |
Init $S(-font) | |
trace add variable [namespace which -variable S](size) \ | |
write [namespace code [list Tracer]] | |
trace add variable [namespace which -variable S](style) \ | |
write [namespace code [list Tracer]] | |
trace add variable [namespace which -variable S](font) \ | |
write [namespace code [list Tracer]] | |
} else { | |
Init $S(-font) | |
} | |
return | |
} | |
# ::tk::fontchooser::Done -- | |
# | |
# Handles teardown of the dialog, calling -command if needed | |
# | |
# Arguments: | |
# ok true if user pressed OK | |
# | |
proc ::tk::::fontchooser::Done {ok} { | |
variable S | |
if {! $ok} { | |
set S(result) "" | |
} | |
trace vdelete S(size) w [namespace code [list Tracer]] | |
trace vdelete S(style) w [namespace code [list Tracer]] | |
trace vdelete S(font) w [namespace code [list Tracer]] | |
destroy $S(W) | |
if {$ok && $S(-command) ne ""} { | |
uplevel #0 $S(-command) [list $S(result)] | |
} | |
} | |
# ::tk::fontchooser::Apply -- | |
# | |
# Call the -command procedure appending the current font | |
# Errors are reported via the background error mechanism | |
# | |
proc ::tk::fontchooser::Apply {} { | |
variable S | |
if {$S(-command) ne ""} { | |
if {[catch {uplevel #0 $S(-command) [list $S(result)]} err]} { | |
::bgerror $err | |
} | |
} | |
event generate $S(-parent) <<TkFontchooserFontChanged>> | |
} | |
# ::tk::fontchooser::Init -- | |
# | |
# Initializes dialog to a default font | |
# | |
# Arguments: | |
# defaultFont font to use as the default | |
# | |
proc ::tk::fontchooser::Init {{defaultFont ""}} { | |
variable S | |
if {$S(first) || $defaultFont ne ""} { | |
if {$defaultFont eq ""} { | |
set defaultFont [[entry .___e] cget -font] | |
destroy .___e | |
} | |
array set F [font actual $defaultFont] | |
set S(font) $F(-family) | |
set S(size) $F(-size) | |
set S(strike) $F(-overstrike) | |
set S(under) $F(-underline) | |
set S(style) "Regular" | |
if {$F(-weight) eq "bold" && $F(-slant) eq "italic"} { | |
set S(style) "Bold Italic" | |
} elseif {$F(-weight) eq "bold"} { | |
set S(style) "Bold" | |
} elseif {$F(-slant) eq "italic"} { | |
set S(style) "Italic" | |
} | |
set S(first) 0 | |
} | |
Tracer a b c | |
Update | |
} | |
# ::tk::fontchooser::Click -- | |
# | |
# Handles all button clicks, updating the appropriate widgets | |
# | |
# Arguments: | |
# who which widget got pressed | |
# | |
proc ::tk::fontchooser::Click {who} { | |
variable S | |
if {$who eq "font"} { | |
set S(font) [$S(W).lfonts get [$S(W).lfonts curselection]] | |
} elseif {$who eq "style"} { | |
set S(style) [$S(W).lstyles get [$S(W).lstyles curselection]] | |
} elseif {$who eq "size"} { | |
set S(size) [$S(W).lsizes get [$S(W).lsizes curselection]] | |
} | |
Update | |
} | |
# ::tk::fontchooser::Tracer -- | |
# | |
# Handles traces on key variables, updating the appropriate widgets | |
# | |
# Arguments: | |
# standard trace arguments (not used) | |
# | |
proc ::tk::fontchooser::Tracer {var1 var2 op} { | |
variable S | |
set bad 0 | |
set nstate normal | |
# Make selection in each listbox | |
foreach var {font style size} { | |
set value [string tolower $S($var)] | |
$S(W).l${var}s selection clear 0 end | |
set n [lsearch -exact $S(${var}s,lcase) $value] | |
$S(W).l${var}s selection set $n | |
if {$n != -1} { | |
set S($var) [lindex $S(${var}s) $n] | |
$S(W).e$var icursor end | |
$S(W).e$var selection clear | |
} else { ;# No match, try prefix | |
# Size is weird: valid numbers are legal but don't display | |
# unless in the font size list | |
set n [lsearch -glob $S(${var}s,lcase) "$value*"] | |
set bad 1 | |
if {$var ne "size" || ! [string is double -strict $value]} { | |
set nstate disabled | |
} | |
} | |
$S(W).l${var}s see $n | |
} | |
if {!$bad} { Update } | |
$S(W).ok configure -state $nstate | |
} | |
# ::tk::fontchooser::Update -- | |
# | |
# Shows a sample of the currently selected font | |
# | |
proc ::tk::fontchooser::Update {} { | |
variable S | |
set S(result) [list $S(font) $S(size)] | |
if {$S(style) eq "Bold"} { lappend S(result) bold } | |
if {$S(style) eq "Italic"} { lappend S(result) italic } | |
if {$S(style) eq "Bold Italic"} { lappend S(result) bold italic} | |
if {$S(strike)} { lappend S(result) overstrike} | |
if {$S(under)} { lappend S(result) underline} | |
$S(sample) configure -font $S(result) | |
} | |
# ::tk::fontchooser::Visibility -- | |
# | |
# Notify the parent when the dialog visibility changes | |
# | |
proc ::tk::fontchooser::Visibility {w visible} { | |
variable S | |
if {$w eq $S(W)} { | |
event generate $S(-parent) <<TkFontchooserVisibility>> | |
} | |
} | |
# ::tk::fontchooser::ttk_listbox -- | |
# | |
# Create a properly themed scrolled listbox. | |
# This is exactly right on XP but may need adjusting on other platforms. | |
# | |
proc ::tk::fontchooser::ttk_slistbox {w args} { | |
set f [ttk::frame $w -style FontchooserFrame -padding 2] | |
if {[catch { | |
listbox $f.list -relief flat -highlightthickness 0 -borderwidth 0 {*}$args | |
ttk::scrollbar $f.vs -command [list $f.list yview] | |
$f.list configure -yscrollcommand [list $f.vs set] | |
grid $f.list $f.vs -sticky news | |
grid rowconfigure $f 0 -weight 1 | |
grid columnconfigure $f 0 -weight 1 | |
interp hide {} $w | |
interp alias {} $w {} $f.list | |
} err opt]} { | |
destroy $f | |
return -options $opt $err | |
} | |
return $w | |
} | |