package provide carto 0.3 # Creates the carto command handler for canvas widgets. # The canvas coordinate space is changed to a cartographic coordinate space # where y coordinates are positive northt. A number of functions # facilitating cartography are added for convinience. # The shape primitives of the canvas widget are not affected by the carto command. # Two new virtual primitives are added: symbol and crosshair which are special # composit canvas primitives. # Usage : # carto pathName ?options? # Example: # carto .c -setview {0 200 200 200} # pack .c # (C) 2004-2005, Dimitrios Zachariadis # Licensed under the BSD License # proc carto {w args} { namespace eval ::carto::${w} { variable var array set var [list \ -autoscale "meet" \ -detail 1 \ -meets "x" \ -pixelaspectratio 1 \ -space cartesian \ w 200 \ h 200 \ scale 1 \ cxy {} ] } upvar ::carto::${w}::var v if {![winfo exists $w]} { eval [list canvas $w -width 200 -height 200] } rename ::$w ::_$w ::carto::dispatchConfig $w $args # the window dimensions returned by winfo include border and highlight # which we must subtract to get the actual width and height set brdrs [expr {2*([::_$w cget -highlightthickness]+[::_$w cget -borderwidth])}] scan [winfo geometry $w] "%dx%d" v(ww) v(wh) set v(ww) [expr {$v(ww)-$brdrs}] set v(wh) [expr {$v(wh)-$brdrs}] ::_$w configure -xscrollincrement 1 ::_$w configure -yscrollincrement 1 bind $w {::carto::setScale %W} proc ::$w {cmd args} "return \[eval ::carto::Handler $w \$cmd \$args]" set w } namespace eval carto { proc Handler {w cmd args} { set debug 0 upvar ::carto::${w}::var v switch -- $cmd { center { set vcx [expr {([::_$w canvasx 0]+$v(ww)/2.0)/($v(scale)*$v(-pixelaspectratio))}] set vcy [expr {-([::_$w canvasy 0]+$v(wh)/2.0)/$v(scale)}] if {$args=={}} { return [list $vcx $vcy $v(scale)] } if {[llength $args]!=3} { error "wrong # args: should be \"$w center x y scale\"" } set redraw 0 # Set center of cartoview and scale foreach {x y scale} $args {} # if scale has changed find the new viewbox width if {$scale!="-"} { set v(scale) $scale if {$debug} {puts "...set scale to $v(scale)"} set v(w) [expr {double($v(ww))/($v(scale)*$v(-pixelaspectratio))}] set v(h) [expr {double($v(wh))/$v(scale)}] # scale changed so force a redraw set redraw 1 } # if x or y did not change, get them from current view if {$x=="-" || $x==""} { set x $vcx } if {$y=="-" || $y==""} { set y $vcy } # set viewbox according to the new center set x0 [expr {$x-$v(ww)/(2.0*$v(scale)*$v(-pixelaspectratio))}] set y0 [expr {$y+$v(wh)/(2.0*$v(scale))}] # Redraw or just scroll canvas to view if {$redraw} { if {$debug} {puts "...generating event $w <>"} event generate $w <> } setScroll $w $x0 $y0 } cget { switch -- [lindex $args 0] { -autoscale {return $v(-autoscale)} -detail {return $v(-detail)} -meets {return $v(-meets)} -space {return $v(-space)} -pixelaspectratio {return $v(-pixelaspectratio)} default {eval {::_$w $cmd } $args} } } config - configure {eval [list dispatchConfig $w] $args} create { switch -- [lindex $args 0] { crosshair { if {[llength $args]<2} { error "wrong # args: should be \"$w create crosshair tag ?option value?\"" } set tag [lindex $args 1] eval [list ::_$w create line -3000 0 3000 0 -tag [list $tag ${tag}x]] [lrange $args 2 end] eval [list ::_$w create line 0 -3000 0 3000 -tag [list $tag ${tag}y]] [lrange $args 2 end] foreach {x1 y1 x2 y2} [::_$w bbox $tag] {} set v($tag,offset) [list [expr {(abs($x1)+abs($x2))/2.0}] [expr {(abs($y1)+abs($y2))/2.0}]] # save tag in a list so that we can find it for redraw } default {eval [list ::_$w $cmd] $args} } } delete { set tag [lindex $args 0] if {[info exists v($tag,offset)]} { unset v($tag,offset) } eval [list ::_$w $cmd] $args } destroy { # destroy canvas widget and carto command namespace delete ::carto::${w} destroy ::$w rename ::$w {} rename ::_$w {} } forget { # destroy carto command but retain carto widget namespace delete ::carto::${w} rename ::$w {} rename ::_$w ::$w } Move { if {[llength $args]<3} {error "wrong # args: should be \"$w Move tagOrId x y\""} foreach {tag x y} $args {} # calculate tag's center coords foreach {bbx1 bby1 bbx2 bby2} [::_$w bbox $tag] {} if {![info exists bbx1]} { # couldn't find tag return } if {![info exists v($tag,offset)]} { # tag is not a symbol set cx0 $bbx1 set cy0 $bby1 } else { set cx0 [expr {($bbx1+[lindex $v($tag,offset) 0])}] set cy0 [expr {($bby1+[lindex $v($tag,offset) 1])}] } set cx [expr {$x*$v(scale)*$v(-pixelaspectratio)}] set cy [expr {-$y*$v(scale)}] ::_$w move $tag [expr {$cx-$cx0}] [expr {$cy-$cy0}] } Plot { # accepts one of: # pathName Plot x y ?-tag tag? ?-mode [abs|rel]? ?-space [cartesian|polar]? ?-option option? # pathName Plot {x y ...} ?-option option? if {$debug} {puts "Plot...args=$args"} set arglen [llength $args] if {$arglen==0 || $arglen==2 || $arglen==4} { return;error "wrong # args: should be \"$w Plot tagOrId ?x y ... ?\"" } foreach {tag x1 y1 x2 y2} [lrange $args 0 4] {} if {[::_$w find withtag $tag]=={}} { # this is a new tag if {$x2=={}} { set x2 $x1 set y2 $y1 } ::_$w create line \ [expr {$x1*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y1*$v(scale)}] \ [expr {$x2*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y2*$v(scale)}] \ -tag $tag -fill grey if {$arglen>5} { set d [::_$w coords $tag] foreach {x y} [lrange $args 5 end] { lappend d [expr {$x*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y*$v(scale)}] } ::_$w coords $tag $d } } else { set d [::_$w coords $tag] foreach {x y} [lrange $args 1 end] { lappend d [expr {$x*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y*$v(scale)}] } ::_$w coords $tag $d } } lPlot { if {$debug} {puts "lPlot...args=$args"} if {[llength $args]<=5} {return;error "wrong # args: should be \"$w lPlot tagOrId ?x1 y1 x2 y2 ... ?\"" } set tag [lindex $args 0] if {[::_$w find withtag $tag]!={}} { foreach {x y} [lrange $args 1 end] { lappend d [expr {$x*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y*$v(scale)}] } } else { foreach {tag x1 y1 x2 y2} [lrange $args 0 4] {} lappend d [expr {$x1*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y1*$v(scale)}] lappend d [expr {$x2*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y2*$v(scale)}] ::_$w create line $d -tag $tag -fill grey foreach {x y} [lrange $args 5 end] { lappend d [expr {$x*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y*$v(scale)}] } } ::_$w coords $tag $d } setview { set args [join $args] set vx0 [expr {[::_$w canvasx 0]/($v(scale)*$v(-pixelaspectratio))}] set vy0 [expr {-[::_$w canvasy 0]/$v(scale)}] if {$args=={}} { return [list $vx0 $vy0 [expr {$v(ww)/($v(scale)*$v(-pixelaspectratio))}] [expr {$v(wh)/$v(scale)}]] } if {[llength $args]!=4} { error "wrong # args: should be \"$w setview x y w h\"" } set redraw 0 foreach {x0 y0 wi hi} $args {} # do we need this ? if {$x0=="-"} { set x0 $vx0 } if {$y0=="-"} { set y0 $vy0 } # if viewbox width changed force redraw if {$wi!="-"} { set v(w) $wi set redraw 1 } # if viewbox height changed force redraw if {$hi!="-"} { set v(h) $hi set redraw 1 } # Redraw or just scroll canvas to view if {$redraw} { setScale $w } setScroll $w $x0 $y0 } symbolize { if {[llength $args]!=1 && [llength $args]!=3} { error "wrong # args: should be \"$w symbolize tag ?offsetx offsety?\"" } set symbol [lindex $args 0] if {[$w find withtag $symbol]=={}} { error "tag $symbol does not exist" } if {[llength $args]==1} { foreach {x1 y1 x2 y2} [::_$w bbox $symbol] {} set v($symbol,offset) [list [expr {($x2-$x1)/2.0}] [expr {($y2-$y1)/2.0}]] } else { set v($symbol,offset) [lrange $args 1 end] } } default {eval [list ::_$w $cmd] $args} } } #dispatch configure pairs proc dispatchConfig {w args} { set debug 0 if {$debug} {puts "dispatchConfig called by [expr {[info level]>1?[lindex [info level -1] 0]:[list user]}]"} # strip possible ::_ so that namespace is referenced properly regexp {^(::_)*(.*)} $w m pre wg upvar ::carto::${wg}::var v if {[llength $args]==0} { return [concat [eval {::_$w configure}] \ [list [list -space $v(-space)]] \ [list [list -pixelaspectratio $v(-pixelaspectratio)]] \ [list [list -autoscale $v(-autoscale)]] \ ] } foreach {opt val} $args { switch -- $opt { -autoscale { if {$val=={}} {return $v(-autoscale)} if {![regexp -- {meet|slice|none} $val]} { error "unknown option $val: should be meet, slice or none" } set v(-autoscale) $val } -detail { if {$val=={}} {return $v(-detail)} if {![string is int $val]} { error "expected integer but got $val" } set v(-detail) $val } -space { if {$val=={}} {return $v(-space)} if {![regexp -- {meet|slice|none} $val]} { error "unknown option $val: should be cartesian or polar" } set v(-space) $val } -pixelaspectratio { if {$val=={}} {return $v(-pixelaspectratio)} if {![string is double $val]} { error "expected real but got $val" } set v(-pixelaspectratio) $val } default {eval {::_$w configure} $opt $val} } } } proc setScale wg { set debug 0 if {$debug} {puts "setScale called by [expr {[info level]>1?[lindex [info level -1] 0]:[list user]}]"} upvar ::carto::${wg}::var v scan [winfo geometry $wg] "%dx%d" v(ww) v(wh) set brdrs [expr {2*([::_$wg cget -highlightthickness]+[::_$wg cget -borderwidth])}] set v(ww) [expr {$v(ww)-$brdrs}] set v(wh) [expr {$v(wh)-$brdrs}] set aspectratio [expr {double($v(ww))/$v(wh)}] switch -regexp -- $v(-autoscale) { meet { # max(viewbox) -> min(window) if {$v(wh)!=0 && $v(w)!=0 && $v(h)!=0} { if {double($v(w))/$v(h)>$aspectratio} { set v(-meets) "x" set v(scale) [expr {double($v(ww))/$v(w)}] } else { set v(-meets) "y" set v(scale) [expr {double($v(wh))/$v(h)}] } } } slice { # min(viewbox) -> max(window) if {$v(wh)!=0 && $v(w)!=0 && $v(h)!=0} { if {double($v(w))/$v(h)>$aspectratio} { set v(-meets) "y" set v(scale) [expr {double($v(wh))/$v(h)}] } else { set v(-meets) "x" set v(scale) [expr {double($v(ww))/$v(w)}] } } } none { # no event is generated return } default { return } } # set v(w) [expr {double($v(ww))/$v(scale)}] # set v(h) [expr {double($v(wh))/$v(scale)}] if {$debug} {puts "...event generate $wg <>"} event generate $wg <> if {$debug} {puts "...set scale to $v(scale)"} } proc setScroll {w x0 y0} { upvar ::carto::${w}::var v ::_$w xview scroll [expr {round($x0*$v(scale)*$v(-pixelaspectratio)-[::_$w canvasx 0])}] u ::_$w yview scroll [expr {-round($y0*$v(scale)+[::_$w canvasy 0])}] u } proc screentocarto {w x y} { upvar ::carto::${w}::var v return [list \ [expr {($x+[::_$w canvasx 0])/($v(scale)*$v(-pixelaspectratio))}] \ [expr {-($y+[::_$w canvasy 0])/$v(scale)}]] } proc tocarto {w x y} { upvar ::carto::${w}::var v return [list [expr {$x/($v(scale)*$v(-pixelaspectratio))}] [expr {-$y/$v(scale)}]] } proc tocanvas {w x y} { upvar ::carto::${w}::var v return [list [expr {$x*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y*$v(scale)}]] } proc centertoview {w args} { } proc viewtocenter {w args} { } }