Version 8 of oowidgets

Updated 2023-03-19 07:40:59 by DDG

NAME

oowidgets - Tcl package to create megawidgets using Tcl object system TclOO

DESCRIPTION

This is yet another approach of creating megawidgets using TclOO. My first little project with TclOO. I had a look at Read-Only Text Megawidget with TclOO and Megawidgets with TclOO and then took some ideas from A Scrolled Widget implemented with TclOO. A lot of code is just stolen from these wiki pages ...

Code

So here my approximately 100 lines of code:

package require Tk
package provide oowidgets 0.1
namespace eval ::oowidgets { }

# this creates a wrapper around the class,
# so that object creation works like for other Tk widgets
proc oowidgets::new name { 
    eval "
    proc [string tolower $name] {path args}  { 
      set obj \[$name create tmp \$path {*}\$args\]
        rename \$obj ::\$path
        return \$path
    }
    "
}
# the BaseWidget from which your MegaWidget should inherit
oo::class create ::oowidgets::BaseWidget {
      variable parentOptions ; # base widgets options
      variable widgetOptions ; # additional options

      variable widgettype
      constructor {path args} {
              my variable widgetOptions
              my variable parentOptions
              array set widgetOptions [list]
              array set parentOptions [list]
              #my configure {*}$args
      }
      # must be currently called in the constructor
      # of the inheriting class
      method install {wtype path args} {
          my variable parentOptions
          my variable widgetOptions
          my variable widget
          $wtype $path
          set widget ${path}_
          foreach opts [$path configure] {
              set opt [lindex $opts 0]
              set val [lindex $opts end]
              set parentOptions($opt) $val
          }
          array set nopts $args
          foreach opt [array names nopts] {
              set widgetOptions($opt) $nopts($opt)
          }
          rename $path $widget
      }
      # overwriting the standard methods cget and configure
      # to deal with possible new options
      method cget { {opt "" }  } {
              my variable widgetOptions
              my variable parentOptions
              if { [string length $opt] == 0 } {
                      return [lsort [list [array get parentOptions] {*}[array get widgetOptions]]]
              }
              if { [info exists widgetOptions($opt) ] } {
                      return $widgetOptions($opt)
              } elseif {[info exists parentOptions($opt)]} {
                      return $parentOptions($opt)
              } 
              return -code error "# unknown option"
      }
      
      method configure { args } {
              my variable widget
              my variable widgetOptions
              my variable parentOptions
              
              if {[llength $args] == 0}  {
                      return [lsort [list [array get parentOptions] {*}[array get widgetOptions]]]
              } elseif {[llength $args] == 1}  {
                   # return configuration value for this option
                   set opt $args
                   if { [info exists widgetOptions($opt) ] } {
                           return $widgetOptions($opt)
                   } elseif {[info exists parentOptions($opt)]} {
                         return $parentOptions($opt)
                   } else {
                       return -code error "# unkown option"
                   }
              }
              
              # error checking
              if {[expr {[llength $args]%2}] == 1}  {
                      return -code error "value for \"[lindex $args end]\" missing"
              }
              
              # process the new configuration options...
              array set opts $args
              
              foreach opt [array names opts] {
                      set val $opts($opt)
                      
                      # overwrite with new value
                      if { [info exists widgetOptions($opt)] } {
                              set widgetOptions($opt) $val
                      } elseif {[info exists parentOptions($opt)]} {
                            set parentOptions($opt) $val
                            $widget configure $opt $val 
                      } else {
                           return -code error "unknown configuration option: \"$opt\" specified"
                          
                      }
              }
      } 
      # delegate all other methods to the widget
      method unknown {args} {
           my variable widget
           $widget {*}$args
      }
}

Examples

Let's now use this code above to create two little widgets which extends the ttk::label and the ttk::button with a flash method:

namespace eval ::flash {}

# create the wrapper function 
# so this creates a proc flash::button
# Please note that uppercasing the class name is required
# the wrapper proc is all lowercase

oowidgets::new ::flash::Button 

# the actual implementation of our sample widget

oo::class create ::flash::Button {
    superclass oowidgets::BaseWidget

    constructor {path args} {
          # new options with their defaults are added at the end
          my install ttk::button $path -flashtime 500
          my configure {*}$args
    }
    # our extension method
    method flash {} {
        set ot [my cget -text]
        set ft [my cget -flashtime]
        for {set i 0} {$i < 5} {incr i} {
            my configure -text "......"
            update idletasks
            after $ft
            my configure -text $ot
            update idletasks
            after $ft
        }
        puts flashed
        my configure -text $ot
    }
}

# Now just for demonstration purposes a second widget

# wrapper method ::flash::label creation
oowidgets::new ::flash::Label

# implementation
oo::class create ::flash::Label {
    superclass oowidgets::BaseWidget
    constructor {path args} {
        my install ttk::label $path -flashtime 500
        my configure {*}$args
    }
    method flash {} {
        set fg [my cget -foreground]
        for {set i 0} {$i < 10} {incr i} {
            my configure -foreground red
            update idletasks
            after [my cget -flashtime]
            my configure -foreground $fg
            update idletasks
            after [my cget -flashtime]
        }
        puts labelflashed
    }
}

Now example code for widget use:

# creating an packing the widgets
set fb [flash::button .fb -text "Exit" -flashtime 100 -command exit]
pack $fb -side top -pady 10 -pady 10 -fill both -expand true
set fl [flash::label .fl -text "FlashLabel" -flashtime 200 -anchor center]
pack $fl -side top -padx 10 -pady 10 -fill both -expand true

# call by variable
$fb flash
puts "done 1"

# call by path
.fb flash
puts "done 2"

# the flash::label
# call by  path and then variable
.fl flash
$fl flash
# calling a standard function of ttk::button (via unknown)
$fb invoke

TODO's

  • getting rid of oowidget::new by just creating a method oowidget::class which creates the class code and the widget in one go (DONE, see below)
  • composite widget demo (the classical LabEntry)
  • mixin demo (flash as mixin, might be anyway better)

Here an idea to create the wrapper and the class in one step:

proc oowidgets::widget {name body} {
    oowidgets::new $name
    oo::class create $name $body 
    oo::define $name { superclass oowidgets::BaseWidget }
}

That way we can get rid of the new call and the superclass statement within our class definition. Let's create a bluelabel widget just for illustration purposes:

oowidgets::widget ::flash::BlueLabel {
    constructor {path args} {
        my install ttk::label $path -flashtime 500
        my configure {*}$args
    }
    method flash {} {
        set fg [my cget -foreground]
        for {set i 0} {$i < 10} {incr i} {
            my configure -foreground blue
            update idletasks
            after [my cget -flashtime]
            my configure -foreground $fg
            update idletasks
            after [my cget -flashtime]
        }
        puts labelflashed
    }
}

Discussion

DDG - 2023-03-18: May be I missed an implementation of megawidgets using TclOO which is in widespread used. Sorry if this is the case and I come here with an other one ... It was just thought as my first exercise using TclOO. With around 100 LOC the result seems to be really impressive - thanks to TclOO and its creators.