camenduru's picture
thanks to RVC-Project ❤
ef99749
raw
history blame
9.57 kB
# megawidget.tcl
#
# Basic megawidget support classes. Experimental for any use other than
# the ::tk::IconList megawdget, which is itself only designed for use in
# the Unix file dialogs.
#
# Copyright (c) 2009-2010 Donal K. Fellows
#
# See the file "license.terms" for information on usage and redistribution of
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
package require Tk 8.6
::oo::class create ::tk::Megawidget {
superclass ::oo::class
method unknown {w args} {
if {[string match .* $w]} {
[self] create $w {*}$args
return $w
}
next $w {*}$args
}
unexport new unknown
self method create {name superclasses body} {
next $name [list \
superclass ::tk::MegawidgetClass {*}$superclasses]\;$body
}
}
::oo::class create ::tk::MegawidgetClass {
variable w hull options IdleCallbacks
constructor args {
# Extract the "widget name" from the object name
set w [namespace tail [self]]
# Configure things
tclParseConfigSpec [my varname options] [my GetSpecs] "" $args
# Move the object out of the way of the hull widget
rename [self] _tmp
# Make the hull widget(s)
my CreateHull
bind $hull <Destroy> [list [namespace which my] destroy]
# Rename things into their final places
rename ::$w theWidget
rename [self] ::$w
# Make the contents
my Create
}
destructor {
foreach {name cb} [array get IdleCallbacks] {
after cancel $cb
unset IdleCallbacks($name)
}
if {[winfo exists $w]} {
bind $hull <Destroy> {}
destroy $w
}
}
####################################################################
#
# MegawidgetClass::configure --
#
# Implementation of 'configure' for megawidgets. Emulates the operation
# of the standard Tk configure method fairly closely, which makes things
# substantially more complex than they otherwise would be.
#
# This method assumes that the 'GetSpecs' method returns a description
# of all the specifications of the options (i.e., as Tk returns except
# with the actual values removed). It also assumes that the 'options'
# array in the class holds all options; it is up to subclasses to set
# traces on that array if they want to respond to configuration changes.
#
# TODO: allow unambiguous abbreviations.
#
method configure args {
# Configure behaves differently depending on the number of arguments
set argc [llength $args]
if {$argc == 0} {
return [lmap spec [my GetSpecs] {
lappend spec $options([lindex $spec 0])
}]
} elseif {$argc == 1} {
set opt [lindex $args 0]
if {[info exists options($opt)]} {
set spec [lsearch -inline -index 0 -exact [my GetSpecs] $opt]
return [linsert $spec end $options($opt)]
}
} elseif {$argc == 2} {
# Special case for where we're setting a single option. This
# avoids some of the costly operations. We still do the [array
# get] as this gives a sufficiently-consistent trace.
set opt [lindex $args 0]
if {[dict exists [array get options] $opt]} {
# Actually set the new value of the option. Use a catch to
# allow a megawidget user to throw an error from a write trace
# on the options array to reject invalid values.
try {
array set options $args
} on error {ret info} {
# Rethrow the error to get a clean stack trace
return -code error -errorcode [dict get $info -errorcode] $ret
}
return
}
} elseif {$argc % 2 == 0} {
# Check that all specified options exist. Any unknown option will
# cause the merged dictionary to be bigger than the options array
set merge [dict merge [array get options] $args]
if {[dict size $merge] == [array size options]} {
# Actually set the new values of the options. Use a catch to
# allow a megawidget user to throw an error from a write trace
# on the options array to reject invalid values
try {
array set options $args
} on error {ret info} {
# Rethrow the error to get a clean stack trace
return -code error -errorcode [dict get $info -errorcode] $ret
}
return
}
# Due to the order of the merge, the unknown options will be at
# the end of the dict. This makes the first unknown option easy to
# find.
set opt [lindex [dict keys $merge] [array size options]]
} else {
set opt [lindex $args end]
return -code error -errorcode [list TK VALUE_MISSING] \
"value for \"$opt\" missing"
}
return -code error -errorcode [list TK LOOKUP OPTION $opt] \
"bad option \"$opt\": must be [tclListValidFlags options]"
}
####################################################################
#
# MegawidgetClass::cget --
#
# Implementation of 'cget' for megawidgets. Emulates the operation of
# the standard Tk cget method fairly closely.
#
# This method assumes that the 'options' array in the class holds all
# options; it is up to subclasses to set traces on that array if they
# want to respond to configuration reads.
#
# TODO: allow unambiguous abbreviations.
#
method cget option {
return $options($option)
}
####################################################################
#
# MegawidgetClass::TraceOption --
#
# Sets up the tracing of an element of the options variable.
#
method TraceOption {option method args} {
set callback [list my $method {*}$args]
trace add variable options($option) write [namespace code $callback]
}
####################################################################
#
# MegawidgetClass::GetSpecs --
#
# Return a list of descriptions of options supported by this
# megawidget. Each option is described by the 4-tuple list, consisting
# of the name of the option, the "option database" name, the "option
# database" class-name, and the default value of the option. These are
# the same values returned by calling the configure method of a widget,
# except without the current values of the options.
#
method GetSpecs {} {
return {
{-takefocus takeFocus TakeFocus {}}
}
}
####################################################################
#
# MegawidgetClass::CreateHull --
#
# Creates the real main widget of the megawidget. This is often a frame
# or toplevel widget, but isn't always (lightweight megawidgets might
# use a content widget directly).
#
# The name of the hull widget is given by the 'w' instance variable. The
# name should be written into the 'hull' instance variable. The command
# created by this method will be renamed.
#
method CreateHull {} {
return -code error -errorcode {TCL OO ABSTRACT_METHOD} \
"method must be overridden"
}
####################################################################
#
# MegawidgetClass::Create --
#
# Creates the content of the megawidget. The name of the widget to
# create the content in will be in the 'hull' instance variable.
#
method Create {} {
return -code error -errorcode {TCL OO ABSTRACT_METHOD} \
"method must be overridden"
}
####################################################################
#
# MegawidgetClass::WhenIdle --
#
# Arrange for a method to be called on the current instance when Tk is
# idle. Only one such method call per method will be queued; subsequent
# queuing actions before the callback fires will be silently ignored.
# The additional args will be passed to the callback, and the callbacks
# will be properly cancelled if the widget is destroyed.
#
method WhenIdle {method args} {
if {![info exists IdleCallbacks($method)]} {
set IdleCallbacks($method) [after idle [list \
[namespace which my] DoWhenIdle $method $args]]
}
}
method DoWhenIdle {method arguments} {
unset IdleCallbacks($method)
tailcall my $method {*}$arguments
}
}
####################################################################
#
# tk::SimpleWidget --
#
# Simple megawidget class that makes it easy create widgets that behave
# like a ttk widget. It creates the hull as a ttk::frame and maps the
# state manipulation methods of the overall megawidget to the equivalent
# operations on the ttk::frame.
#
::tk::Megawidget create ::tk::SimpleWidget {} {
variable w hull options
method GetSpecs {} {
return {
{-cursor cursor Cursor {}}
{-takefocus takeFocus TakeFocus {}}
}
}
method CreateHull {} {
set hull [::ttk::frame $w -cursor $options(-cursor)]
my TraceOption -cursor UpdateCursorOption
}
method UpdateCursorOption args {
$hull configure -cursor $options(-cursor)
}
# Not fixed names, so can't forward
method state args {
tailcall $hull state {*}$args
}
method instate args {
tailcall $hull instate {*}$args
}
}
####################################################################
#
# tk::FocusableWidget --
#
# Simple megawidget class that makes a ttk-like widget that has a focus
# ring.
#
::tk::Megawidget create ::tk::FocusableWidget ::tk::SimpleWidget {
variable w hull options
method GetSpecs {} {
return {
{-cursor cursor Cursor {}}
{-takefocus takeFocus TakeFocus ::ttk::takefocus}
}
}
method CreateHull {} {
ttk::frame $w
set hull [ttk::entry $w.cHull -takefocus 0 -cursor $options(-cursor)]
pack $hull -expand yes -fill both -ipadx 2 -ipady 2
my TraceOption -cursor UpdateCursorOption
}
}
return
# Local Variables:
# mode: tcl
# fill-column: 78
# End: