# 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: | |