WJG (13/03/18) The gnocl::drawingArea is an unusual widget. Unusual in the sense that it doesn’t really do much on its own. As a binding to the GtkDrawingArea object, the gnocl::drawingArea widget offers all the event trapping and cairo drawing operations necessary to build custom widgets without faffing around with any C coding.
Here’s an example:
This object creates a simple progress wheel which can either be a hollow circle or a solid disk. It offers some advantages over a rectangular progressbar such as embedding it snugly into a toolbar or statusbar.
Here’s the script.
# !/bin/sh # the next line restarts using tclsh \ exec tclsh "$0" "$@" package require Gnocl package provide progressWheel #--------------- # increment any number, including doubles # https://wiki.tcl-lang.org/1476 #--------------- # proc gnocl::+= { varName step {precision 0} } { upvar 1 $varName var set var [format "%.${precision}f" [expr {$var+$step}]] } ;# RS #--------------- # limit value to range #--------------- # proc gnocl::clamp {varName {min 0.0} {max 1.0} } { upvar 1 $varName var if { $var >= $max } { return $max } elseif { $var <= $min } { return $min } } #--------------- # begin widget construction #--------------- # namespace eval gnocl::progressWheel {} #--------------- # lists of valid widget options, commands and components #--------------- # set gnocl::progressWheel::opts { -data -name -alias -tooltip } set gnocl::progressWheel::cmds { configure cget class opts cmds delete } append gnocl::progressWheel::opts { -myEvent -ring -step -swap } append gnocl::progressWheel::cmds { set draw_ step } #set gnocl::progressWheel::val 0.0 #--------------- # Redraw proc for whenever the widget is exposed. #--------------- # proc gnocl::progressWheel::redraw { w } { array set vars $::gnocl::progressWheel::vars([$w parent]_) set x 0 set y 0 set height 100 set width 100 set padding 5 lassign [$w geometry] x y width height set angle [format "%.2f" [expr -90 + (360.0*$vars(val)) ]] set diameter $height if { $height > $width } { set diameter $width } set radius [expr $diameter/2 - $padding] set radius2 [expr $radius/2]] set cx [expr $width/2] set cy [expr $height/2] if { $vars(swap) } { set clr(1) "1 0 0" set clr(2) "1 1 1" } else { set clr(1) "1 1 1" set clr(2) "1 0 0" } if $vars(ring) { # draw circle on grey background set lw1 [expr $radius / 2] set lw2 [expr $lw1 * 0.5] set radius [expr $radius - $lw1/2 ] set actions " set_source_rgb {0.5 0.5 0.5} rectangle {0 0 $width $height} fill set_line_cap round set_line_width $lw1 set_source_rgb {$clr(1)} arc { $cx $cy $radius 0 360 } stroke set_line_width $lw2 set_source_rgb {$clr(2)} arc { $cx $cy $radius -90 $angle } stroke " } else { # draw disc on grey background set actions " set_source_rgb {0.5 0.5 0.5} rectangle {0 0 $width $height} fill set_source_rgb {$clr(1)} arc { $cx $cy $radius 0 360 } fill move_to {$cx $cy} set_source_rgb {$clr(2)} arc { $cx $cy $radius -90 $angle } fill " } $w configure -actions $actions } #--------------- # #--------------- # proc gnocl::progressWheel::setVal { wid val } { foreach { w id } $gnocl::progressWheel::components($wid) { set $w $id } if { $val < 0.0 || $val > 1.0 } { puts ERROR! ; exit } set gnocl::progressWheel::vars($wid) [lreplace $gnocl::progressWheel::vars($wid) 1 1 $val] gnocl::progressWheel::redraw $da $da configure -tooltip $val $da draw return $val } #--------------- # #--------------- # proc gnocl::progressWheel::swap { wid } { foreach {a b} $gnocl::progressWheel::vars($wid) { set $a $b } set swap [gnocl::toggle swap] set gnocl::progressWheel::vars($wid) [lreplace $gnocl::progressWheel::vars($wid) 5 5 $swap] } #--------------- # Increment wheel display by one step. #--------------- # proc gnocl::progressWheel::step { wid } { foreach { w id } $gnocl::progressWheel::components($wid) { set $w $id } foreach { a b } $gnocl::progressWheel::vars($wid) { set $a $b } set val [expr $val + $step ] set val [format "%.3f" $val] if {$val > 1.0} { set val $step set swap [gnocl::toggle $swap] } set gnocl::progressWheel::vars($wid) [lreplace $gnocl::progressWheel::vars($wid) 1 1 $val] set gnocl::progressWheel::vars($wid) [lreplace $gnocl::progressWheel::vars($wid) 5 5 $swap] gnocl::progressWheel::redraw $da $da configure -tooltip $val $da draw return $val } #--------------- # implement widget commands #--------------- # proc gnocl::progressWheel::cmd { wid cmd args } { gnocl::progressWheel::check $cmd # get list of members foreach { w id } $gnocl::progressWheel::components($wid) { set $w $id } # apply the commands switch -- $cmd { swap { gnocl::progressWheel::swap $wid } step { return [gnocl::progressWheel::step $wid] } set { gnocl::progressWheel::setVal $wid $args } opts - cmds { return [ lsort [ set gnocl::progressWheel::$cmd ] ] } class { return "progressWheel" } configure - delete - cget { {*}"gnocl::progressWheel::$cmd $wid $args" } default { # shouldn't need to get here, but... } } } #--------------- # retrieve current component values #--------------- # proc gnocl::progressWheel::cget { wid args } { gnocl::progressWheel::check $args # get list of members foreach { w id } $gnocl::progressWheel::components($wid) { set $w $id } # obtain current settings foreach { a b } $args { # apply according to each component switch -- $a { -onClicked - -text { return [ $but_1 cget $a ] } -data { return [ $wid cget $a ] } -name { return $::gnocl::progressWheel::names($wid) } default { # shouldn't need to get here, but... } } } } #--------------- # check options and commands for valid values #--------------- # proc gnocl::progressWheel::check { opts } { # test for a valid options if { [string first - $opts ] >= 0 } { foreach { opt val } $opts { if { [string first $opt $gnocl::progressWheel::opts] == -1 } { append errmsg [string repeat - 17]\n append errmsg "ERROR! Invalid gnocl::gnocl::progressWheel option \"$opt\".\n" append errmsg "Should be one of: [lsort $gnocl::progressWheel::opts]\n" append errmsg [string repeat - 17]\n error $errmsg } } return } # test for valid command foreach { cmd } $opts { if { [string first $cmd $gnocl::progressWheel::cmds] == -1 } { append errmsg [string repeat - 17]\n append errmsg "ERROR! Invalid gnocl::gnocl::progressWheel command \"$cmd\".\n" append errmsg "Should be one of: [lsort $gnocl::progressWheel::cmds]\n" append errmsg [string repeat - 17]\n error $errmsg } } } #--------------- # configure widget components #--------------- # proc gnocl::progressWheel::configure { wid args } { gnocl::progressWheel::check $args # recover list of widget components foreach {w id} $::gnocl::progressWheel::components($wid) {set $w $id} array set vars $::gnocl::progressWheel::vars($wid) # apply new options foreach {a b} $args { # apply according to each component switch -- $a { -alias { interp alias {} $b {} $wid } -name { #interp alias {} $b {} $wid $da configure -name $b } -ring { set gnocl::progressWheel::vars($wid) [lreplace $gnocl::progressWheel::vars($wid) 7 7 $b] gnocl::progressWheel::redraw $da $da draw } -data { $wid configure $a $b } -swap { } default { # shouldn't need to get here, but... } } } } #--------------- # delete widget and clean up #--------------- # proc gnocl::progressWheel::delete { wid } { $wid delete array unset gnocl::progressWheel::names $wid array unset gnocl::progressWheel::components $wid } #--------------- # create and assemble widget components #--------------- # proc gnocl::progressWheel::construct {} { set diameter 100 set radius [expr $diameter-4/2] set cx $radius set cy $radius # set some defaults set val 0.000 set step 0.050 # create components set da [gnocl::drawingArea -name DA -actions fill ] # create object container set vbox [gnocl::vBox -borderWidth 0] # assemble components $vbox add $da -fill {1 1} -expand 1 # add to listing set ::gnocl::progressWheel::components(${vbox}_) [list da $da] # set some widget specific variables set ::gnocl::progressWheel::vars(${vbox}_) [list val $val step $step swap 0 ring 0] $da configure -tooltip $val $da configure -onExpose { gnocl::progressWheel::redraw %w } return $vbox } #--------------- # the widget command itself #--------------- # proc gnocl::progressWheel { args } { set wid [gnocl::progressWheel::construct] # overload the box to add commands rename $wid ${wid}_ # configure {*}"gnocl::progressWheel::configure ${wid}_ $args" # widget command proc $wid { cmd args } { set wid [lindex [::info level 0] 0] {*}"gnocl::progressWheel::cmd ${wid}_ $cmd $args" } return $wid } #=============== # DEMO #=============== proc demo {} { set wid(1) [gnocl::progressWheel \ -name PW \ -data "HO HI HO!" \ -name campers] set wid(2) [gnocl::button \ -text incr \ -data $wid(1) \ -onClicked { puts [%d step] } ] set wid(3) [gnocl::button \ -text reset \ -data $wid(1) \ -onClicked { puts [%d set 0.000] } ] set wid(4) [gnocl::toggleButton \ -data $wid(1) \ -text ring/circle \ -onToggled { %d configure -ring %v } ] gnocl::vBox -name vbox vbox add $wid(1) -fill {1 1} -expand 1 vbox add $wid(2) vbox add $wid(3) vbox add $wid(4) gnocl::window -child [vbox] -setSize 0.250 gnocl::update } demo set myVAR 100