The XOWidget class
# here for the world
package provide xowidget 0.1
catch {
package require XOTcl
# this is necessary
namespace import ::xotcl::my
namespace import ::xotcl::self
package require Tk
}
namespace eval xowidget {
# check if we got Tcl 8.5 to use {*}syntax
variable _expand
if {[package vsatisfies [package require Tcl] 8.5]} {
set _expand yes
} else {
set _expand no
}
namespace export XOWidget
::xotcl::Class create _XOWidget -superclass ::xotcl::Class
_XOWidget instproc init {} {
# default values
my set __widget(hulltype) frame
my set __widget(adaptor) no
my set __options(list) ""
my set __options(*,delegated) no
if {$::xowidget::_expand} {
my instproc configurelist {{arg ""}} [expand {
my configure %EXP%$arg
}]
} else {
my instproc configurelist {{arg ""}} {
foreach {opt val} $arg {
my configure $opt $val
}
}
}
my instproc cget {opt} {
array set options [[self class] array get __options]
if {[lsearch -exact $options(list) $opt]<0} {
return [[my set $options(*,target)] cget $opt]
}
if {[info exists options($opt,cgetcmd)]} {
# this is a little machinery to avoid endless
# loops (recursivity when calling configure/cget in
# these handlers)
[self class] unset __options($opt,cgetcmd)
set value [my $options($opt,cgetcmd) $opt]
[self class] set __options($opt,cgetcmd) \
$options($opt,cgetcmd)
return $value
}
return [my set [::xowidget::xopt $opt]]
}
my instproc rawcget {opt} {
return [my set [::xowidget::xopt $opt]]
}
my instproc rawconfigure {opt value} {
my set [::xowidget::xopt $opt] $value
}
my instproc configure {args} {
if {[llength $args] == 0} {
return [my __show_configuration]
}
array set options [[self class] array get __options]
foreach {opt val} $args {
if {[lsearch -exact $options(list) $opt]<0} {
# unknown option, first look at option * delegation
if {$options(*,delegated)} {
[my set $options(*,target)] configure $opt $val
} else {
error "unknown option $opt"
}
} elseif {$options($opt,delegated)} {
# delegated option
[my set $options($opt,target)] configure $options($opt,delname) $val
} else {
# non-delegated option
if {[info exists options($opt,validatecmd)]} {
# throws an error when the value is invalid
my $options($opt,validatecmd) $val
}
if {[info exists options($opt,configurecmd)]} {
# this is a little machinery to avoid endless
# loops (recursivity when calling configure/cget in
# these handlers)
[self class] unset __options($opt,configurecmd)
my $options($opt,configurecmd) $opt $val
[self class] set __options($opt,configurecmd) \
$options($opt,configurecmd)
} else {
my set [::xowidget::xopt $opt] $val
}
}
}
return
}
my instproc __show_configuration {} {
set result ""
foreach opt [[self class] set __options(list)] {
set f $opt
if {[my exists [::xowidget::xopt $opt]]} {
lappend f [my set [::xowidget::xopt $opt]]
} else {
lappend f ""
}
lappend result $f
}
return $result
}
#
# installhull : the first command to call when you instanciate it
# part of Widget-specific methods
#
my instproc installhull {} {
# init options
set class [self class]
foreach opt [$class set __options(list)] {
if {![$class set __options($opt,delegated)] && [$class set __options($opt,default)]} {
my set [::xowidget::xopt $opt] [$class set __options($opt,value)]
}
}
# creates the hull
set path [::xowidget::pathFromSelf [self]]
if {![$class set __widget(adaptor)] ||
[$class set __widget(hulltype)] ne "existing"} {
uplevel 1 [linsert [$class set __widget(hulltype)] 1 $path]
}
# wraps the original widget
set i 0
while 1 {
incr i
set newname ::WidgetCmd$i$path
if {![llength [info commands $newname]]} break
}
rename ::$path $newname
proc ::$path {args} [string map [list %PATH% [self]] {
return [eval [linsert $args 0 %PATH%]]
}]
bind $path <Destroy> [list $path destroy]\n[list rename $path ""]
if {[$class set __widget(adaptor)]} {
my set hull $newname
} else {
my set hull $path
}
}
my instproc init {} {
my installhull
}
}
_XOWidget instproc option {name args} {
set opt [::xowidget::xopt $name]
my lappend __options(list) $name
my set __options($name,delegated) no
if {[llength $args]==0} {
# no default
my set __options($name,default) no
} elseif {[llength $args]==1} {
my set __options($name,default) yes
my set __options($name,value) [lindex $args 0]
return
}
foreach {key value} $args {
switch -- $key {
-default {
my set __options($name,default) yes
my set __options($name,value) $value
}
-configurecmd {
my set __options($name,configurecmd) $value
}
-cgetcmd {
my set __options($name,cgetcmd) $value
}
-validatecmd {
my set __options($name,validatecmd) $value
}
default {
error "unknown option option $key"
}
}
}
return
}
_XOWidget instproc hulltype {widget} {
if {[my set __widget(hulltype)]!="frame"} {
error "hulltype statement called twice"
}
my set __widget(hulltype) $widget
}
_XOWidget instproc setadaptor {args} {
my set __widget(adaptor) yes
my set __widget(hulltype) $args
my delegate option * to hull
my delegate instproc * to hull
}
#
# DELEGATION
#
_XOWidget instproc delegate {type args} {
eval [linsert $args 0 my __delegate_$type]
}
_XOWidget instproc __delegate_instproc {name args} {
set revamped $name
foreach {key value} $args {
switch -- $key {
as {set revamped $value}
to {set target $value}
default {error "unknown delegate statement"}
}
}
if {$name=="*"} {
if {$::xowidget::_expand} {
set body "\[my set $target\] \{*\}\$args"
} else {
set body "eval \[linsert \$args 0 \[my set $target\]\]"
}
my instproc unknown {args} $body
} else {
if {$::xowidget::_expand} {
set body "\[my set $target\] $revamped \{*\}\$args"
} else {
set body "eval \[linsert \$args 0 \[my set $target\] $revamped\]"
}
my instproc $name {args} $body
}
return
}
_XOWidget instproc __delegate_option {name args} {
if {[lsearch -exact [my set __options(list)] $name]>=0 &&
$name ne "*"} {
error "local option cannot be delegated"
}
if {$name ne "*"} {my lappend __options(list) $name}
my set __options($name,delegated) yes
my set __options($name,delname) $name
foreach {key value} $args {
switch -- $key {
as {
my set __options($name,delname) $value
}
to {
set hastarget yes ; # a marker
my set __options($name,target) $value
}
default {error "unknown delegate statement"}
}
}
if {![info exists hastarget]} {
error "delegate ... to target\ndelegation target missing"
}
return
}
#
# Entry point
#
proc XOWidget {class args} {
set wclass [transpose $class]
uplevel 1 ::xowidget::_XOWidget $wclass $args
# destroys the existing alias
catch {uplevel 1 [list interp alias {} $class {}]}
uplevel 1 [list interp alias {} $class {} ::xowidget::wset $class]
return $class
}
#
# Widget part (also in installhull method)
#
proc wset {class path args} {
set wclass [transpose $class]
if {[string index $path 0] ne "."} {
return [uplevel 1 [linsert $args 0 $wclass $path]]
}
set i 0
while {[llength [info commands ::Widget$i$path]]} {
incr i
}
set body [$wclass info instbody init]
if {![regexp {my installhull} $body]} {
uplevel 1 [list $wclass instproc init {} "my installhull\n$body"]
}
uplevel 1 $wclass create Widget$i$path
# parse arguments as any widget should: ?option value ?option value...??
if {[llength $args]} {uplevel 1 [list Widget$i$path configurelist $args]}
return $path
}
proc xopt {name} {
if {[string index $name 0] ne "-"} {
error "does not look like an option: $name"
}
return _[string range $name 1 end]
}
# introduce the expand syntax without making pre-8.5 Tcl
# arguing for this syntax
# to use like: [expand {configure %EXP%$args}]
# returns in this case : {configure {*}$args}
proc expand {body} {
string map {%EXP% \{*\}} $body
}
# given an instance identifier (obfuscated), finds the widget's path
proc pathFromSelf {self} {
return [string range $self [string first . $self] end]
}
#
# procs to transpose a (visible) type name into a (hidden) XOTcl class name
#
proc untranspose {name} {
return [string range $name 0 end-[string length __Widget]]
}
# gives the visible type name of an instance
proc transpose {name} {
return ${name}__Widget
}
}An example:
# widget example to be launched by wish
package require Tk
lappend auto_path .
package require xowidget
xowidget::XOWidget Button
# options
Button option -fontfamily -default "" -configurecmd fontfamily
Button setadaptor existing
Button instproc init {} {
my instvar hull
$hull configure -text "Click me"
}
Button instproc fontfamily {opt value} {
my instvar hull
set font [$hull cget -font]
lset font 0 $value
$hull configure -font $font
my rawconfigure $opt $value
}
proc try {firstname name} {
global btn
tk_messageBox -message "Have you ever tried to look like $firstname $name?"
destroy $btn
button $btn
Button $btn -text "Exit" -command exit
pack $btn
update
}
# 'Button' arguments are treated after creation
# by calling automatically the configure method
button .btn
set btn [Button .btn -fontfamily times -text "Top Cool Language" -command {try Freddie Mercury}]
# comment this when you've got a toplevel hulltype, of course
pack .btn
update
tk_messageBox -message "option list : [$btn configure]"The example is adapted from Xoins, a Snit emulation in XOTcl.Widget adaptor example
# widget example to be launched by wish
package require Tk
lappend auto_path .
package require xowidget
xowidget::XOWidget Button
# options
Button option -packpad -default 10 -configurecmd packpad
Button option -fontfamily -default "" -configurecmd fontfamily
Button delegate option * to button
Button delegate option -text to button
Button instproc init {} {
my instvar hull
my set button [button $hull.b -text "Click me"]
pack [my set button] -padx 10 -pady 10 -in $hull
}
Button instproc destroy {} {
catch {destroy [my set button]}
}
Button instproc packpad {opt value} {
pack configure [my set button] -padx $value -pady $value
my rawconfigure $opt $value
}
Button instproc fontfamily {opt value} {
my instvar button
set font [$button cget -font]
lset font 0 $value
$button configure -font $font
my rawconfigure $opt $value
}
proc try {firstname name} {
global btn
tk_messageBox -message "Have you ever tried to look like $firstname $name?"
destroy $btn
Button $btn -text "Exit" -command exit -packpad 5
pack $btn
}
# 'Button' arguments are treated after creation
# by calling automatically the configure method
set btn [Button .btn -fontfamily times -text "Top Cool Language" -command {try Paul McCartney;update}]
# comment this when you've got a toplevel hulltype, of course
pack .btn
update
.btn configure -packpad 15
tk_messageBox -message "option list : [$btn configure]"
